diff --git a/package.json b/package.json index e7615aed0..0eee81f8e 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "path": "^0.12.7", "reflect-metadata": "^0.1.3", "require-all": "^2.0.0", + "rxjs": "^5.0.0-beta.7", "sha1": "^1.1.1" }, "scripts": { diff --git a/sample/sample1-simple-entity/app.ts b/sample/sample1-simple-entity/app.ts index 0641a2487..d835255af 100644 --- a/sample/sample1-simple-entity/app.ts +++ b/sample/sample1-simple-entity/app.ts @@ -19,6 +19,7 @@ createConnection(options).then(connection => { let post = new Post(); post.text = "Hello how are you?"; post.title = "hello"; + post.likesCount = 100; let postRepository = connection.getRepository(Post); diff --git a/sample/sample15-we-are-reactive/app.ts b/sample/sample15-we-are-reactive/app.ts new file mode 100644 index 000000000..66cc48e38 --- /dev/null +++ b/sample/sample15-we-are-reactive/app.ts @@ -0,0 +1,31 @@ +import {createConnection, CreateConnectionOptions} from "../../src/typeorm"; +import {Post} from "./entity/Post"; + +const options: CreateConnectionOptions = { + driver: "mysql", + connection: { + host: "192.168.99.100", + port: 3306, + username: "root", + password: "admin", + database: "test", + autoSchemaCreate: true + }, + entities: [Post] +}; + +createConnection(options).then(connection => { + + let post = new Post(); + post.text = "Hello how are you?"; + post.title = "hello"; + post.likesCount = 0; + + let postRepository = connection.getReactiveRepository(Post); + + postRepository + .persist(post) + .do(post => console.log(post.title + " stream!")) + .subscribe(post => console.log("Post has been saved")); + +}, error => console.log("Cannot connect: ", error)); \ No newline at end of file diff --git a/sample/sample15-we-are-reactive/entity/Post.ts b/sample/sample15-we-are-reactive/entity/Post.ts new file mode 100644 index 000000000..31367dfff --- /dev/null +++ b/sample/sample15-we-are-reactive/entity/Post.ts @@ -0,0 +1,19 @@ +import {PrimaryColumn, Column} from "../../../src/columns"; +import {Table} from "../../../src/tables"; + +@Table("sample15_post") +export class Post { + + @PrimaryColumn("int", { generated: true }) + id: number; + + @Column() + title: string; + + @Column() + text: string; + + @Column() + likesCount: number; + +} \ No newline at end of file diff --git a/src/connection/Connection.ts b/src/connection/Connection.ts index 718d65091..46898292d 100644 --- a/src/connection/Connection.ts +++ b/src/connection/Connection.ts @@ -18,11 +18,13 @@ import {NoConnectionForRepositoryError} from "./error/NoConnectionForRepositoryE import {CannotImportAlreadyConnectedError} from "./error/CannotImportAlreadyConnectedError"; import {CannotCloseNotConnectedError} from "./error/CannotCloseNotConnectedError"; import {CannotConnectAlreadyConnectedError} from "./error/CannotConnectAlreadyConnectedError"; +import {ReactiveRepository} from "../repository/ReactiveRepository"; +import {ReactiveEntityManager} from "../repository/ReactiveEntityManager"; /** * Temporary type to store and link both repository and its metadata. */ -type RepositoryAndMetadata = { repository: Repository, metadata: EntityMetadata }; +type RepositoryAndMetadata = { metadata: EntityMetadata, repository: Repository, reactiveRepository: ReactiveRepository }; /** * A single connection instance to the database. Each connection has its own repositories, subscribers and metadatas. @@ -44,6 +46,11 @@ export class Connection { */ readonly entityManager: EntityManager; + /** + * Gets ReactiveEntityManager of this connection. + */ + readonly reactiveEntityManager: ReactiveEntityManager; + /** * The name of the connection. */ @@ -113,6 +120,7 @@ export class Connection { this.driver.connectionOptions = options; this.options = options; this.entityManager = new EntityManager(this); + this.reactiveEntityManager = new ReactiveEntityManager(this); } // ------------------------------------------------------------------------- @@ -245,6 +253,21 @@ export class Connection { return repoMeta.repository; } + /** + * Gets repository for the given entity class. + */ + getReactiveRepository(entityClass: ConstructorFunction|Function): ReactiveRepository { + if (!this.isConnected) + throw new NoConnectionForRepositoryError(this.name); + + const metadata = this.entityMetadatas.findByTarget(entityClass); + const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata); + if (!repoMeta) + throw new RepositoryNotFoundError(this.name, entityClass); + + return repoMeta.reactiveRepository; + } + // ------------------------------------------------------------------------- // Private Methods // ------------------------------------------------------------------------- @@ -298,9 +321,11 @@ export class Connection { * Creates a temporary object RepositoryAndMetadata to store relation between repository and metadata. */ private createRepoMeta(metadata: EntityMetadata): RepositoryAndMetadata { + const repository = new Repository(this, this.entityMetadatas, metadata); return { metadata: metadata, - repository: new Repository(this, this.entityMetadatas, metadata) + repository: repository, + reactiveRepository: new ReactiveRepository(repository) }; } diff --git a/src/repository/EntityManager.ts b/src/repository/EntityManager.ts index a1a835ebc..3ccfb24cb 100644 --- a/src/repository/EntityManager.ts +++ b/src/repository/EntityManager.ts @@ -3,6 +3,7 @@ import {QueryBuilder} from "../query-builder/QueryBuilder"; import {FindOptions} from "./FindOptions"; import {Repository} from "./Repository"; import {ConstructorFunction} from "../common/ConstructorFunction"; +import {ReactiveRepository} from "./ReactiveRepository"; /** * Entity manager supposed to work with any entity, automatically find its repository and call its method, whatever @@ -27,6 +28,13 @@ export class EntityManager { getRepository(entityClass: ConstructorFunction|Function): Repository { return this.connection.getRepository(entityClass); } + + /** + * Gets reactive repository of the given entity. + */ + getReactiveRepository(entityClass: ConstructorFunction|Function): ReactiveRepository { + return this.connection.getReactiveRepository(entityClass); + } /** * Checks if entity has an id. diff --git a/src/repository/ReactiveEntityManager.ts b/src/repository/ReactiveEntityManager.ts new file mode 100644 index 000000000..25aa23154 --- /dev/null +++ b/src/repository/ReactiveEntityManager.ts @@ -0,0 +1,235 @@ +import {Connection} from "../connection/Connection"; +import {QueryBuilder} from "../query-builder/QueryBuilder"; +import {FindOptions} from "./FindOptions"; +import {Repository} from "./Repository"; +import {ConstructorFunction} from "../common/ConstructorFunction"; +import {ReactiveRepository} from "./ReactiveRepository"; +import * as Rx from "rxjs/Rx"; + +/** + * Entity manager supposed to work with any entity, automatically find its repository and call its method, whatever + * entity type are you passing. This version of ReactiveEntityManager works with reactive streams and observables. + */ +export class ReactiveEntityManager { + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private connection: Connection) { + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + /** + * Gets repository of the given entity. + */ + getRepository(entityClass: ConstructorFunction|Function): Repository { + return this.connection.getRepository(entityClass); + } + + /** + * Gets reactive repository of the given entity. + */ + getReactiveRepository(entityClass: ConstructorFunction|Function): ReactiveRepository { + return this.connection.getReactiveRepository(entityClass); + } + + /** + * Checks if entity has an id. + */ + hasId(entity: Function): boolean { + return this.getReactiveRepository(entity.constructor).hasId(entity); + } + + /** + * Creates a new query builder that can be used to build an sql query. + */ + createQueryBuilder(entityClass: ConstructorFunction|Function, alias: string): QueryBuilder { + return this.getReactiveRepository(entityClass).createQueryBuilder(alias); + } + + /** + * Creates a new entity. If fromRawEntity is given then it creates a new entity and copies all entity properties + * from this object into a new entity (copies only properties that should be in a new entity). + */ + create(entityClass: ConstructorFunction|Function, fromRawEntity?: Object): Entity { + return this.getReactiveRepository(entityClass).create(fromRawEntity); + } + + /** + * Creates a entities from the given array of plain javascript objects. + */ + createMany(entityClass: ConstructorFunction|Function, copyFromObjects: any[]): Entity[] { + return this.getReactiveRepository(entityClass).createMany(copyFromObjects); + } + + /** + * Creates a new entity from the given plan javascript object. If entity already exist in the database, then + * it loads it (and everything related to it), replaces all values with the new ones from the given object + * and returns this new entity. This new entity is actually a loaded from the db entity with all properties + * replaced from the new object. + */ + initialize(entityClass: ConstructorFunction|Function, object: Object): Rx.Observable { + return this.getReactiveRepository(entityClass).initialize(object); + } + + /** + * Merges two entities into one new entity. + */ + merge(entity1: Entity, entity2: Entity): Entity { + return this.getReactiveRepository( entity1).merge(entity1, entity2); + } + + /** + * Persists (saves) a given entity in the database. + */ + persist(entity: Entity): Rx.Observable { + // todo: extra casting is used strange tsc error here, check later maybe typescript bug + return this.getReactiveRepository( entity.constructor).persist(entity); + } + + /** + * Removes a given entity from the database. + */ + remove(entity: Entity) { + return this.getReactiveRepository( entity.constructor).remove(entity); + } + + /** + * Finds entities that match given conditions. + */ + find(entityClass: ConstructorFunction|Function): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(entityClass: ConstructorFunction|Function, conditions: Object): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(entityClass: ConstructorFunction|Function, options: FindOptions): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(entityClass: ConstructorFunction|Function, conditions: Object, options: FindOptions): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(entityClass: ConstructorFunction|Function, conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Rx.Observable { + if (conditionsOrFindOptions && options) { + return this.getReactiveRepository(entityClass).find(conditionsOrFindOptions, options); + + } else if (conditionsOrFindOptions) { + return this.getReactiveRepository(entityClass).find(conditionsOrFindOptions); + + } else { + return this.getReactiveRepository(entityClass).find(); + } + } + + /** + * Finds entities that match given conditions. + */ + findAndCount(entityClass: ConstructorFunction|Function): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(entityClass: ConstructorFunction|Function, conditions: Object): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(entityClass: ConstructorFunction|Function, options: FindOptions): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(entityClass: ConstructorFunction|Function, conditions: Object, options: FindOptions): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(entityClass: ConstructorFunction|Function, conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Rx.Observable<[Entity[], number]> { + if (conditionsOrFindOptions && options) { + return this.getReactiveRepository(entityClass).findAndCount(conditionsOrFindOptions, options); + + } else if (conditionsOrFindOptions) { + return this.getReactiveRepository(entityClass).findAndCount(conditionsOrFindOptions); + + } else { + return this.getReactiveRepository(entityClass).findAndCount(); + } + } + + /** + * Finds first entity that matches given conditions. + */ + findOne(entityClass: ConstructorFunction|Function): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(entityClass: ConstructorFunction|Function, conditions: Object): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(entityClass: ConstructorFunction|Function, options: FindOptions): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(entityClass: ConstructorFunction|Function, conditions: Object, options: FindOptions): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(entityClass: ConstructorFunction|Function, conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Rx.Observable { + if (conditionsOrFindOptions && options) { + return this.getReactiveRepository(entityClass).findOne(conditionsOrFindOptions, options); + + } else if (conditionsOrFindOptions) { + return this.getReactiveRepository(entityClass).findOne(conditionsOrFindOptions); + + } else { + return this.getReactiveRepository(entityClass).findOne(); + } + } + + /** + * Finds entity with given id. + */ + findOneById(entityClass: ConstructorFunction|Function, id: any, options?: FindOptions): Rx.Observable { + return this.getReactiveRepository(entityClass).findOneById(id, options); + } + + /** + * Executes raw SQL query and returns raw database results. + */ + query(query: string): Rx.Observable { + return Rx.Observable.fromPromise(this.connection.driver.query(query)); + } + + /** + * Wraps given function execution (and all operations made there) in a transaction. + */ + transaction(runInTransaction: () => Promise): Rx.Observable { + let runInTransactionResult: any; + return Rx.Observable.fromPromise(this.connection.driver + .beginTransaction() + .then(() => runInTransaction()) + .then(result => { + runInTransactionResult = result; + return this.connection.driver.endTransaction(); + }) + .then(() => runInTransactionResult)); + } + +} \ No newline at end of file diff --git a/src/repository/ReactiveRepository.ts b/src/repository/ReactiveRepository.ts new file mode 100644 index 000000000..efc56fa31 --- /dev/null +++ b/src/repository/ReactiveRepository.ts @@ -0,0 +1,211 @@ +import {QueryBuilder} from "../query-builder/QueryBuilder"; +import {FindOptions} from "./FindOptions"; +import {Repository} from "./Repository"; +import * as Rx from "rxjs/Rx"; + +/** + * Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc. + * This version of Repository is using rxjs library and Observables instead of promises. + */ +export class ReactiveRepository { + + // ------------------------------------------------------------------------- + // Constructor + // ------------------------------------------------------------------------- + + constructor(private repository: Repository) { + + } + + // ------------------------------------------------------------------------- + // Public Methods + // ------------------------------------------------------------------------- + + /** + * Checks if entity has an id. + */ + hasId(entity: Entity): boolean { + return this.repository.hasId(entity); + } + + /** + * Creates a new query builder that can be used to build a sql query. + */ + createQueryBuilder(alias: string): QueryBuilder { + return this.repository.createQueryBuilder(alias); + } + + /** + * Creates a new entity. If fromRawEntity is given then it creates a new entity and copies all entity properties + * from this object into a new entity (copies only properties that should be in a new entity). + */ + create(fromRawEntity?: Object): Entity { + return this.repository.create(fromRawEntity); + } + + /** + * Creates entities from a given array of plain javascript objects. + */ + createMany(copyFromObjects: Object[]): Entity[] { + return this.repository.createMany(copyFromObjects); + } + + /** + * Creates a new entity from the given plan javascript object. If entity already exist in the database, then + * it loads it (and everything related to it), replaces all values with the new ones from the given object + * and returns this new entity. This new entity is actually a loaded from the db entity with all properties + * replaced from the new object. + */ + initialize(object: Object): Rx.Observable { + return Rx.Observable.fromPromise(this.repository.initialize(object)); + } + + /** + * Merges two entities into one new entity. + */ + merge(entity1: Entity, entity2: Entity): Entity { + return this.repository.merge(entity1, entity2); + } + + /** + * Persists (saves) a given entity in the database. If entity does not exist in the database then it inserts it, + * else if entity already exist in the database then it updates it. + */ + persist(entity: Entity): Rx.Observable { + return Rx.Observable.fromPromise(this.repository.persist(entity)); + } + + /** + * Removes a given entity from the database. + */ + remove(entity: Entity): Rx.Observable { + return Rx.Observable.fromPromise(this.repository.remove(entity)); + } + + /** + * Finds all entities. + */ + find(): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(conditions: Object): Rx.Observable; + + /** + * Finds entities with . + */ + find(options: FindOptions): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(conditions: Object, options: FindOptions): Rx.Observable; + + /** + * Finds entities that match given conditions. + */ + find(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Rx.Observable { + if (conditionsOrFindOptions && options) { + return Rx.Observable.fromPromise(this.repository.find(conditionsOrFindOptions, options)); + + } else if (conditionsOrFindOptions) { + return Rx.Observable.fromPromise(this.repository.find(conditionsOrFindOptions)); + + } else { + return Rx.Observable.fromPromise(this.repository.find()); + } + } + + /** + * Finds entities that match given conditions. + */ + findAndCount(): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(conditions: Object): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(options: FindOptions): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(conditions: Object, options: FindOptions): Rx.Observable<[ Entity[], number ]>; + + /** + * Finds entities that match given conditions. + */ + findAndCount(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Rx.Observable<[ Entity[], number ]> { + if (conditionsOrFindOptions && options) { + return Rx.Observable.fromPromise(this.repository.findAndCount(conditionsOrFindOptions, options)); + + } else if (conditionsOrFindOptions) { + return Rx.Observable.fromPromise(this.repository.findAndCount(conditionsOrFindOptions)); + + } else { + return Rx.Observable.fromPromise(this.repository.findAndCount()); + } + } + + /** + * Finds first entity that matches given conditions. + */ + findOne(): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(conditions: Object): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(options: FindOptions): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(conditions: Object, options: FindOptions): Rx.Observable; + + /** + * Finds first entity that matches given conditions. + */ + findOne(conditionsOrFindOptions?: Object|FindOptions, options?: FindOptions): Rx.Observable { + if (conditionsOrFindOptions && options) { + return Rx.Observable.fromPromise(this.repository.findOne(conditionsOrFindOptions, options)); + + } else if (conditionsOrFindOptions) { + return Rx.Observable.fromPromise(this.repository.findOne(conditionsOrFindOptions)); + + } else { + return Rx.Observable.fromPromise(this.repository.findOne()); + } + } + + /** + * Finds entity with given id. + */ + findOneById(id: any, options?: FindOptions): Rx.Observable { + return Rx.Observable.fromPromise(this.repository.findOneById(id, options)); + } + + /** + * Executes raw SQL query and returns raw database results. + */ + query(query: string): Rx.Observable { + return Rx.Observable.fromPromise(this.repository.query(query)); + } + + /** + * Wraps given function execution (and all operations made there) in a transaction. + */ + transaction(runInTransaction: () => Promise): Rx.Observable { + return Rx.Observable.fromPromise(this.repository.transaction(runInTransaction)); + } + +} \ No newline at end of file