import { DataSource } from "../data-source/DataSource" import { FindManyOptions } from "../find-options/FindManyOptions" import { EntityTarget } from "../common/EntityTarget" import { ObjectType } from "../common/ObjectType" import { EntityNotFoundError } from "../error/EntityNotFoundError" import { QueryRunnerProviderAlreadyReleasedError } from "../error/QueryRunnerProviderAlreadyReleasedError" import { FindOneOptions } from "../find-options/FindOneOptions" import { DeepPartial } from "../common/DeepPartial" import { RemoveOptions } from "../repository/RemoveOptions" import { SaveOptions } from "../repository/SaveOptions" import { NoNeedToReleaseEntityManagerError } from "../error/NoNeedToReleaseEntityManagerError" import { MongoRepository } from "../repository/MongoRepository" import { TreeRepository } from "../repository/TreeRepository" import { Repository } from "../repository/Repository" import { FindOptionsUtils } from "../find-options/FindOptionsUtils" import { PlainObjectToNewEntityTransformer } from "../query-builder/transformer/PlainObjectToNewEntityTransformer" import { PlainObjectToDatabaseEntityTransformer } from "../query-builder/transformer/PlainObjectToDatabaseEntityTransformer" import { CustomRepositoryCannotInheritRepositoryError, CustomRepositoryNotFoundError, TreeRepositoryNotSupportedError, TypeORMError, } from "../error" import { AbstractRepository } from "../repository/AbstractRepository" import { QueryRunner } from "../query-runner/QueryRunner" import { SelectQueryBuilder } from "../query-builder/SelectQueryBuilder" import { QueryDeepPartialEntity } from "../query-builder/QueryPartialEntity" import { EntityPersistExecutor } from "../persistence/EntityPersistExecutor" import { ObjectId } from "../driver/mongodb/typings" import { InsertResult } from "../query-builder/result/InsertResult" import { UpdateResult } from "../query-builder/result/UpdateResult" import { DeleteResult } from "../query-builder/result/DeleteResult" import { FindOptionsWhere } from "../find-options/FindOptionsWhere" import { IsolationLevel } from "../driver/types/IsolationLevel" import { ObjectUtils } from "../util/ObjectUtils" import { getMetadataArgsStorage } from "../globals" import { UpsertOptions } from "../repository/UpsertOptions" import { InstanceChecker } from "../util/InstanceChecker" import { ObjectLiteral } from "../common/ObjectLiteral" import { PickKeysByType } from "../common/PickKeysByType" /** * Entity manager supposed to work with any entity, automatically find its repository and call its methods, * whatever entity type are you passing. */ export class EntityManager { readonly "@instanceof" = Symbol.for("EntityManager") // ------------------------------------------------------------------------- // Public Properties // ------------------------------------------------------------------------- /** * Connection used by this entity manager. */ readonly connection: DataSource /** * Custom query runner to be used for operations in this entity manager. * Used only in non-global entity manager. */ readonly queryRunner?: QueryRunner // ------------------------------------------------------------------------- // Protected Properties // ------------------------------------------------------------------------- /** * Once created and then reused by repositories. * Created as a future replacement for the #repositories to provide a bit more perf optimization. */ protected repositories = new Map, Repository>() /** * Once created and then reused by repositories. */ protected treeRepositories: TreeRepository[] = [] /** * Plain to object transformer used in create and merge operations. */ protected plainObjectToEntityTransformer = new PlainObjectToNewEntityTransformer() // ------------------------------------------------------------------------- // Constructor // ------------------------------------------------------------------------- constructor(connection: DataSource, queryRunner?: QueryRunner) { this.connection = connection if (queryRunner) { this.queryRunner = queryRunner // dynamic: this.queryRunner = manager; ObjectUtils.assign(this.queryRunner, { manager: this }) } } // ------------------------------------------------------------------------- // Public Methods // ------------------------------------------------------------------------- /** * Wraps given function execution (and all operations made there) in a transaction. * All database operations must be executed using provided entity manager. */ async transaction( runInTransaction: (entityManager: EntityManager) => Promise, ): Promise /** * Wraps given function execution (and all operations made there) in a transaction. * All database operations must be executed using provided entity manager. */ async transaction( isolationLevel: IsolationLevel, runInTransaction: (entityManager: EntityManager) => Promise, ): Promise /** * Wraps given function execution (and all operations made there) in a transaction. * All database operations must be executed using provided entity manager. */ async transaction( isolationOrRunInTransaction: | IsolationLevel | ((entityManager: EntityManager) => Promise), runInTransactionParam?: (entityManager: EntityManager) => Promise, ): Promise { const isolation = typeof isolationOrRunInTransaction === "string" ? isolationOrRunInTransaction : undefined const runInTransaction = typeof isolationOrRunInTransaction === "function" ? isolationOrRunInTransaction : runInTransactionParam if (!runInTransaction) { throw new TypeORMError( `Transaction method requires callback in second parameter if isolation level is supplied.`, ) } if (this.queryRunner && this.queryRunner.isReleased) throw new QueryRunnerProviderAlreadyReleasedError() // if query runner is already defined in this class, it means this entity manager was already created for a single connection // if its not defined we create a new query runner - single connection where we'll execute all our operations const queryRunner = this.queryRunner || this.connection.createQueryRunner() try { await queryRunner.startTransaction(isolation) const result = await runInTransaction(queryRunner.manager) await queryRunner.commitTransaction() return result } catch (err) { try { // we throw original error even if rollback thrown an error await queryRunner.rollbackTransaction() } catch (rollbackError) {} throw err } finally { if (!this.queryRunner) // if we used a new query runner provider then release it await queryRunner.release() } } /** * Executes raw SQL query and returns raw database results. */ async query(query: string, parameters?: any[]): Promise { return this.connection.query(query, parameters, this.queryRunner) } /** * Creates a new query builder that can be used to build a SQL query. */ createQueryBuilder( entityClass: EntityTarget, alias: string, queryRunner?: QueryRunner, ): SelectQueryBuilder /** * Creates a new query builder that can be used to build a SQL query. */ createQueryBuilder(queryRunner?: QueryRunner): SelectQueryBuilder /** * Creates a new query builder that can be used to build a SQL query. */ createQueryBuilder( entityClass?: EntityTarget | QueryRunner, alias?: string, queryRunner?: QueryRunner, ): SelectQueryBuilder { if (alias) { return this.connection.createQueryBuilder( entityClass as EntityTarget, alias, queryRunner || this.queryRunner, ) } else { return this.connection.createQueryBuilder( (entityClass as QueryRunner | undefined) || queryRunner || this.queryRunner, ) } } /** * Checks if entity has an id. */ hasId(entity: any): boolean /** * Checks if entity of given schema name has an id. */ hasId(target: Function | string, entity: any): boolean /** * Checks if entity has an id by its Function type or schema name. */ hasId(targetOrEntity: any | Function | string, maybeEntity?: any): boolean { const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor const entity = arguments.length === 2 ? maybeEntity : targetOrEntity const metadata = this.connection.getMetadata(target) return metadata.hasId(entity) } /** * Gets entity mixed id. */ getId(entity: any): any /** * Gets entity mixed id. */ getId(target: EntityTarget, entity: any): any /** * Gets entity mixed id. */ getId(targetOrEntity: any | EntityTarget, maybeEntity?: any): any { const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor const entity = arguments.length === 2 ? maybeEntity : targetOrEntity const metadata = this.connection.getMetadata(target) return metadata.getEntityIdMixedMap(entity) } /** * Creates a new entity instance and copies all entity properties from this object into a new entity. * Note that it copies only properties that present in entity schema. */ create( entityClass: EntityTarget, plainObject?: DeepPartial, ): Entity /** * Creates a new entities and copies all entity properties from given objects into their new entities. * Note that it copies only properties that present in entity schema. */ create( entityClass: EntityTarget, plainObjects?: DeepPartial[], ): Entity[] /** * Creates a new entity instance or instances. * Can copy properties from the given object into new entities. */ create( entityClass: EntityTarget, plainObjectOrObjects?: DeepPartial | DeepPartial[], ): Entity | Entity[] { const metadata = this.connection.getMetadata(entityClass) if (!plainObjectOrObjects) return metadata.create(this.queryRunner) if (Array.isArray(plainObjectOrObjects)) return (plainObjectOrObjects as DeepPartial[]).map( (plainEntityLike) => this.create(entityClass, plainEntityLike), ) const mergeIntoEntity = metadata.create(this.queryRunner) this.plainObjectToEntityTransformer.transform( mergeIntoEntity, plainObjectOrObjects, metadata, true, ) return mergeIntoEntity } /** * Merges two entities into one new entity. */ merge( entityClass: EntityTarget, mergeIntoEntity: Entity, ...entityLikes: DeepPartial[] ): Entity { // todo: throw exception if entity manager is released const metadata = this.connection.getMetadata(entityClass) entityLikes.forEach((object) => this.plainObjectToEntityTransformer.transform( mergeIntoEntity, object, metadata, ), ) return mergeIntoEntity } /** * Creates a new entity from the given plain 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. */ async preload( entityClass: EntityTarget, entityLike: DeepPartial, ): Promise { const metadata = this.connection.getMetadata(entityClass) const plainObjectToDatabaseEntityTransformer = new PlainObjectToDatabaseEntityTransformer(this.connection.manager) const transformedEntity = await plainObjectToDatabaseEntityTransformer.transform( entityLike, metadata, ) if (transformedEntity) return this.merge( entityClass as any, transformedEntity as Entity, entityLike, ) return undefined } /** * Saves all given entities in the database. * If entities do not exist in the database then inserts, otherwise updates. */ save(entities: Entity[], options?: SaveOptions): Promise /** * Saves all given entities in the database. * If entities do not exist in the database then inserts, otherwise updates. */ save(entity: Entity, options?: SaveOptions): Promise /** * Saves all given entities in the database. * If entities do not exist in the database then inserts, otherwise updates. */ save>( targetOrEntity: EntityTarget, entities: T[], options: SaveOptions & { reload: false }, ): Promise /** * Saves all given entities in the database. * If entities do not exist in the database then inserts, otherwise updates. */ save>( targetOrEntity: EntityTarget, entities: T[], options?: SaveOptions, ): Promise<(T & Entity)[]> /** * Saves a given entity in the database. * If entity does not exist in the database then inserts, otherwise updates. */ save>( targetOrEntity: EntityTarget, entity: T, options: SaveOptions & { reload: false }, ): Promise /** * Saves a given entity in the database. * If entity does not exist in the database then inserts, otherwise updates. */ save>( targetOrEntity: EntityTarget, entity: T, options?: SaveOptions, ): Promise /** * Saves a given entity in the database. */ save>( targetOrEntity: (T | T[]) | EntityTarget, maybeEntityOrOptions?: T | T[], maybeOptions?: SaveOptions, ): Promise { // normalize mixed parameters let target = arguments.length > 1 && (typeof targetOrEntity === "function" || InstanceChecker.isEntitySchema(targetOrEntity) || typeof targetOrEntity === "string") ? (targetOrEntity as Function | string) : undefined const entity: T | T[] = target ? (maybeEntityOrOptions as T | T[]) : (targetOrEntity as T | T[]) const options = target ? maybeOptions : (maybeEntityOrOptions as SaveOptions) if (InstanceChecker.isEntitySchema(target)) target = target.options.name // if user passed empty array of entities then we don't need to do anything if (Array.isArray(entity) && entity.length === 0) return Promise.resolve(entity) // execute save operation return new EntityPersistExecutor( this.connection, this.queryRunner, "save", target, entity, options, ) .execute() .then(() => entity) } /** * Removes a given entity from the database. */ remove(entity: Entity, options?: RemoveOptions): Promise /** * Removes a given entity from the database. */ remove( targetOrEntity: EntityTarget, entity: Entity, options?: RemoveOptions, ): Promise /** * Removes a given entity from the database. */ remove(entity: Entity[], options?: RemoveOptions): Promise /** * Removes a given entity from the database. */ remove( targetOrEntity: EntityTarget, entity: Entity[], options?: RemoveOptions, ): Promise /** * Removes a given entity from the database. */ remove( targetOrEntity: (Entity | Entity[]) | EntityTarget, maybeEntityOrOptions?: Entity | Entity[], maybeOptions?: RemoveOptions, ): Promise { // normalize mixed parameters const target = arguments.length > 1 && (typeof targetOrEntity === "function" || InstanceChecker.isEntitySchema(targetOrEntity) || typeof targetOrEntity === "string") ? (targetOrEntity as Function | string) : undefined const entity: Entity | Entity[] = target ? (maybeEntityOrOptions as Entity | Entity[]) : (targetOrEntity as Entity | Entity[]) const options = target ? maybeOptions : (maybeEntityOrOptions as SaveOptions) // if user passed empty array of entities then we don't need to do anything if (Array.isArray(entity) && entity.length === 0) return Promise.resolve(entity) // execute save operation return new EntityPersistExecutor( this.connection, this.queryRunner, "remove", target, entity, options, ) .execute() .then(() => entity) } /** * Records the delete date of all given entities. */ softRemove( entities: Entity[], options?: SaveOptions, ): Promise /** * Records the delete date of a given entity. */ softRemove(entity: Entity, options?: SaveOptions): Promise /** * Records the delete date of all given entities. */ softRemove>( targetOrEntity: EntityTarget, entities: T[], options?: SaveOptions, ): Promise /** * Records the delete date of a given entity. */ softRemove>( targetOrEntity: EntityTarget, entity: T, options?: SaveOptions, ): Promise /** * Records the delete date of one or many given entities. */ softRemove>( targetOrEntity: (T | T[]) | EntityTarget, maybeEntityOrOptions?: T | T[], maybeOptions?: SaveOptions, ): Promise { // normalize mixed parameters let target = arguments.length > 1 && (typeof targetOrEntity === "function" || InstanceChecker.isEntitySchema(targetOrEntity) || typeof targetOrEntity === "string") ? (targetOrEntity as Function | string) : undefined const entity: T | T[] = target ? (maybeEntityOrOptions as T | T[]) : (targetOrEntity as T | T[]) const options = target ? maybeOptions : (maybeEntityOrOptions as SaveOptions) if (InstanceChecker.isEntitySchema(target)) target = target.options.name // if user passed empty array of entities then we don't need to do anything if (Array.isArray(entity) && entity.length === 0) return Promise.resolve(entity) // execute soft-remove operation return new EntityPersistExecutor( this.connection, this.queryRunner, "soft-remove", target, entity, options, ) .execute() .then(() => entity) } /** * Recovers all given entities. */ recover( entities: Entity[], options?: SaveOptions, ): Promise /** * Recovers a given entity. */ recover(entity: Entity, options?: SaveOptions): Promise /** * Recovers all given entities. */ recover>( targetOrEntity: EntityTarget, entities: T[], options?: SaveOptions, ): Promise /** * Recovers a given entity. */ recover>( targetOrEntity: EntityTarget, entity: T, options?: SaveOptions, ): Promise /** * Recovers one or many given entities. */ recover>( targetOrEntity: (T | T[]) | EntityTarget, maybeEntityOrOptions?: T | T[], maybeOptions?: SaveOptions, ): Promise { // normalize mixed parameters let target = arguments.length > 1 && (typeof targetOrEntity === "function" || InstanceChecker.isEntitySchema(targetOrEntity) || typeof targetOrEntity === "string") ? (targetOrEntity as Function | string) : undefined const entity: T | T[] = target ? (maybeEntityOrOptions as T | T[]) : (targetOrEntity as T | T[]) const options = target ? maybeOptions : (maybeEntityOrOptions as SaveOptions) if (InstanceChecker.isEntitySchema(target)) target = target.options.name // if user passed empty array of entities then we don't need to do anything if (Array.isArray(entity) && entity.length === 0) return Promise.resolve(entity) // execute recover operation return new EntityPersistExecutor( this.connection, this.queryRunner, "recover", target, entity, options, ) .execute() .then(() => entity) } /** * Inserts a given entity into the database. * Unlike save method executes a primitive operation without cascades, relations and other operations included. * Executes fast and efficient INSERT query. * Does not check if entity exist in the database, so query will fail if duplicate entity is being inserted. * You can execute bulk inserts using this method. */ async insert( target: EntityTarget, entity: | QueryDeepPartialEntity | QueryDeepPartialEntity[], ): Promise { return this.createQueryBuilder() .insert() .into(target) .values(entity) .execute() } async upsert( target: EntityTarget, entityOrEntities: | QueryDeepPartialEntity | QueryDeepPartialEntity[], conflictPathsOrOptions: string[] | UpsertOptions, ): Promise { const metadata = this.connection.getMetadata(target) let options: UpsertOptions if (Array.isArray(conflictPathsOrOptions)) { options = { conflictPaths: conflictPathsOrOptions, } } else { options = conflictPathsOrOptions } let entities: QueryDeepPartialEntity[] if (!Array.isArray(entityOrEntities)) { entities = [entityOrEntities] } else { entities = entityOrEntities } const conflictColumns = metadata.mapPropertyPathsToColumns( Array.isArray(options.conflictPaths) ? options.conflictPaths : Object.keys(options.conflictPaths), ) const overwriteColumns = metadata.columns.filter( (col) => !conflictColumns.includes(col) && entities.some( (entity) => typeof col.getEntityValue(entity) !== "undefined", ), ) return this.createQueryBuilder() .insert() .into(target) .values(entities) .orUpdate( [...conflictColumns, ...overwriteColumns].map( (col) => col.databaseName, ), conflictColumns.map((col) => col.databaseName), { skipUpdateIfNoValuesChanged: options.skipUpdateIfNoValuesChanged, indexPredicate: options.indexPredicate, upsertType: options.upsertType || this.connection.driver.supportedUpsertTypes[0], }, ) .execute() } /** * Updates entity partially. Entity can be found by a given condition(s). * Unlike save method executes a primitive operation without cascades, relations and other operations included. * Executes fast and efficient UPDATE query. * Does not check if entity exist in the database. * Condition(s) cannot be empty. */ update( target: EntityTarget, criteria: | string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | any, partialEntity: QueryDeepPartialEntity, ): Promise { // if user passed empty criteria or empty list of criterias, then throw an error if ( criteria === undefined || criteria === null || criteria === "" || (Array.isArray(criteria) && criteria.length === 0) ) { return Promise.reject( new TypeORMError( `Empty criteria(s) are not allowed for the update method.`, ), ) } if ( typeof criteria === "string" || typeof criteria === "number" || criteria instanceof Date || Array.isArray(criteria) ) { return this.createQueryBuilder() .update(target) .set(partialEntity) .whereInIds(criteria) .execute() } else { return this.createQueryBuilder() .update(target) .set(partialEntity) .where(criteria) .execute() } } /** * Deletes entities by a given condition(s). * Unlike save method executes a primitive operation without cascades, relations and other operations included. * Executes fast and efficient DELETE query. * Does not check if entity exist in the database. * Condition(s) cannot be empty. */ delete( targetOrEntity: EntityTarget, criteria: | string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | any, ): Promise { // if user passed empty criteria or empty list of criterias, then throw an error if ( criteria === undefined || criteria === null || criteria === "" || (Array.isArray(criteria) && criteria.length === 0) ) { return Promise.reject( new TypeORMError( `Empty criteria(s) are not allowed for the delete method.`, ), ) } if ( typeof criteria === "string" || typeof criteria === "number" || criteria instanceof Date || Array.isArray(criteria) ) { return this.createQueryBuilder() .delete() .from(targetOrEntity) .whereInIds(criteria) .execute() } else { return this.createQueryBuilder() .delete() .from(targetOrEntity) .where(criteria) .execute() } } /** * Records the delete date of entities by a given condition(s). * Unlike save method executes a primitive operation without cascades, relations and other operations included. * Executes fast and efficient DELETE query. * Does not check if entity exist in the database. * Condition(s) cannot be empty. */ softDelete( targetOrEntity: EntityTarget, criteria: | string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | any, ): Promise { // if user passed empty criteria or empty list of criterias, then throw an error if ( criteria === undefined || criteria === null || criteria === "" || (Array.isArray(criteria) && criteria.length === 0) ) { return Promise.reject( new TypeORMError( `Empty criteria(s) are not allowed for the delete method.`, ), ) } if ( typeof criteria === "string" || typeof criteria === "number" || criteria instanceof Date || Array.isArray(criteria) ) { return this.createQueryBuilder() .softDelete() .from(targetOrEntity) .whereInIds(criteria) .execute() } else { return this.createQueryBuilder() .softDelete() .from(targetOrEntity) .where(criteria) .execute() } } /** * Restores entities by a given condition(s). * Unlike save method executes a primitive operation without cascades, relations and other operations included. * Executes fast and efficient DELETE query. * Does not check if entity exist in the database. * Condition(s) cannot be empty. */ restore( targetOrEntity: EntityTarget, criteria: | string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | any, ): Promise { // if user passed empty criteria or empty list of criterias, then throw an error if ( criteria === undefined || criteria === null || criteria === "" || (Array.isArray(criteria) && criteria.length === 0) ) { return Promise.reject( new TypeORMError( `Empty criteria(s) are not allowed for the delete method.`, ), ) } if ( typeof criteria === "string" || typeof criteria === "number" || criteria instanceof Date || Array.isArray(criteria) ) { return this.createQueryBuilder() .restore() .from(targetOrEntity) .whereInIds(criteria) .execute() } else { return this.createQueryBuilder() .restore() .from(targetOrEntity) .where(criteria) .execute() } } /** * Checks whether any entity exists with the given condition */ exists( entityClass: EntityTarget, options?: FindManyOptions, ): Promise { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass, FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name, ) .setFindOptions(options || {}) .getExists() } /** * Counts entities that match given options. * Useful for pagination. */ count( entityClass: EntityTarget, options?: FindManyOptions, ): Promise { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass, FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name, ) .setFindOptions(options || {}) .getCount() } /** * Counts entities that match given conditions. * Useful for pagination. */ countBy( entityClass: EntityTarget, where: FindOptionsWhere | FindOptionsWhere[], ): Promise { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder(entityClass, metadata.name) .setFindOptions({ where }) .getCount() } /** * Return the SUM of a column */ sum( entityClass: EntityTarget, columnName: PickKeysByType, where?: FindOptionsWhere | FindOptionsWhere[], ): Promise { return this.callAggregateFun(entityClass, "SUM", columnName, where) } /** * Return the AVG of a column */ average( entityClass: EntityTarget, columnName: PickKeysByType, where?: FindOptionsWhere | FindOptionsWhere[], ): Promise { return this.callAggregateFun(entityClass, "AVG", columnName, where) } /** * Return the MIN of a column */ minimum( entityClass: EntityTarget, columnName: PickKeysByType, where?: FindOptionsWhere | FindOptionsWhere[], ): Promise { return this.callAggregateFun(entityClass, "MIN", columnName, where) } /** * Return the MAX of a column */ maximum( entityClass: EntityTarget, columnName: PickKeysByType, where?: FindOptionsWhere | FindOptionsWhere[], ): Promise { return this.callAggregateFun(entityClass, "MAX", columnName, where) } private async callAggregateFun( entityClass: EntityTarget, fnName: "SUM" | "AVG" | "MIN" | "MAX", columnName: PickKeysByType, where: FindOptionsWhere | FindOptionsWhere[] = {}, ): Promise { const metadata = this.connection.getMetadata(entityClass) const result = await this.createQueryBuilder(entityClass, metadata.name) .setFindOptions({ where }) .select( `${fnName}(${this.connection.driver.escape( String(columnName), )})`, fnName, ) .getRawOne() return result[fnName] === null ? null : parseFloat(result[fnName]) } /** * Finds entities that match given find options. */ async find( entityClass: EntityTarget, options?: FindManyOptions, ): Promise { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass as any, FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name, ) .setFindOptions(options || {}) .getMany() } /** * Finds entities that match given find options. */ async findBy( entityClass: EntityTarget, where: FindOptionsWhere | FindOptionsWhere[], ): Promise { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass as any, metadata.name, ) .setFindOptions({ where: where }) .getMany() } /** * Finds entities that match given find options. * Also counts all entities that match given conditions, * but ignores pagination settings (from and take options). */ findAndCount( entityClass: EntityTarget, options?: FindManyOptions, ): Promise<[Entity[], number]> { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass as any, FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name, ) .setFindOptions(options || {}) .getManyAndCount() } /** * Finds entities that match given WHERE conditions. * Also counts all entities that match given conditions, * but ignores pagination settings (from and take options). */ findAndCountBy( entityClass: EntityTarget, where: FindOptionsWhere | FindOptionsWhere[], ): Promise<[Entity[], number]> { const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass as any, metadata.name, ) .setFindOptions({ where }) .getManyAndCount() } /** * Finds entities with ids. * Optionally find options or conditions can be applied. * * @deprecated use `findBy` method instead in conjunction with `In` operator, for example: * * .findBy({ * id: In([1, 2, 3]) * }) */ async findByIds( entityClass: EntityTarget, ids: any[], ): Promise { // if no ids passed, no need to execute a query - just return an empty array of values if (!ids.length) return Promise.resolve([]) const metadata = this.connection.getMetadata(entityClass) return this.createQueryBuilder( entityClass as any, metadata.name, ) .andWhereInIds(ids) .getMany() } /** * Finds first entity by a given find options. * If entity was not found in the database - returns null. */ async findOne( entityClass: EntityTarget, options: FindOneOptions, ): Promise { const metadata = this.connection.getMetadata(entityClass) // prepare alias for built query let alias: string = metadata.name if (options && options.join) { alias = options.join.alias } if (!options.where) { throw new Error( `You must provide selection conditions in order to find a single row.`, ) } // create query builder and apply find options return this.createQueryBuilder(entityClass, alias) .setFindOptions({ ...options, take: 1, }) .getOne() } /** * Finds first entity that matches given where condition. * If entity was not found in the database - returns null. */ async findOneBy( entityClass: EntityTarget, where: FindOptionsWhere | FindOptionsWhere[], ): Promise { const metadata = this.connection.getMetadata(entityClass) // create query builder and apply find options return this.createQueryBuilder(entityClass, metadata.name) .setFindOptions({ where, take: 1, }) .getOne() } /** * Finds first entity that matches given id. * If entity was not found in the database - returns null. * * @deprecated use `findOneBy` method instead in conjunction with `In` operator, for example: * * .findOneBy({ * id: 1 // where "id" is your primary column name * }) */ async findOneById( entityClass: EntityTarget, id: number | string | Date | ObjectId, ): Promise { const metadata = this.connection.getMetadata(entityClass) // create query builder and apply find options return this.createQueryBuilder(entityClass, metadata.name) .setFindOptions({ take: 1, }) .whereInIds(metadata.ensureEntityIdMap(id)) .getOne() } /** * Finds first entity by a given find options. * If entity was not found in the database - rejects with error. */ async findOneOrFail( entityClass: EntityTarget, options: FindOneOptions, ): Promise { return this.findOne(entityClass as any, options).then( (value) => { if (value === null) { return Promise.reject( new EntityNotFoundError(entityClass, options), ) } return Promise.resolve(value) }, ) } /** * Finds first entity that matches given where condition. * If entity was not found in the database - rejects with error. */ async findOneByOrFail( entityClass: EntityTarget, where: FindOptionsWhere | FindOptionsWhere[], ): Promise { return this.findOneBy(entityClass as any, where).then( (value) => { if (value === null) { return Promise.reject( new EntityNotFoundError(entityClass, where), ) } return Promise.resolve(value) }, ) } /** * Clears all the data from the given table (truncates/drops it). * * Note: this method uses TRUNCATE and may not work as you expect in transactions on some platforms. * @see https://stackoverflow.com/a/5972738/925151 */ async clear(entityClass: EntityTarget): Promise { const metadata = this.connection.getMetadata(entityClass) const queryRunner = this.queryRunner || this.connection.createQueryRunner() try { return await queryRunner.clearTable(metadata.tablePath) // await is needed here because we are using finally } finally { if (!this.queryRunner) await queryRunner.release() } } /** * Increments some column by provided value of the entities matched given conditions. */ async increment( entityClass: EntityTarget, conditions: any, propertyPath: string, value: number | string, ): Promise { const metadata = this.connection.getMetadata(entityClass) const column = metadata.findColumnWithPropertyPath(propertyPath) if (!column) throw new TypeORMError( `Column ${propertyPath} was not found in ${metadata.targetName} entity.`, ) if (isNaN(Number(value))) throw new TypeORMError(`Value "${value}" is not a number.`) // convert possible embeded path "social.likes" into object { social: { like: () => value } } const values: QueryDeepPartialEntity = propertyPath .split(".") .reduceRight( (value, key) => ({ [key]: value } as any), () => this.connection.driver.escape(column.databaseName) + " + " + value, ) return this.createQueryBuilder(entityClass as any, "entity") .update(entityClass) .set(values) .where(conditions) .execute() } /** * Decrements some column by provided value of the entities matched given conditions. */ async decrement( entityClass: EntityTarget, conditions: any, propertyPath: string, value: number | string, ): Promise { const metadata = this.connection.getMetadata(entityClass) const column = metadata.findColumnWithPropertyPath(propertyPath) if (!column) throw new TypeORMError( `Column ${propertyPath} was not found in ${metadata.targetName} entity.`, ) if (isNaN(Number(value))) throw new TypeORMError(`Value "${value}" is not a number.`) // convert possible embeded path "social.likes" into object { social: { like: () => value } } const values: QueryDeepPartialEntity = propertyPath .split(".") .reduceRight( (value, key) => ({ [key]: value } as any), () => this.connection.driver.escape(column.databaseName) + " - " + value, ) return this.createQueryBuilder(entityClass as any, "entity") .update(entityClass) .set(values) .where(conditions) .execute() } /** * Gets repository for the given entity class or name. * If single database connection mode is used, then repository is obtained from the * repository aggregator, where each repository is individually created for this entity manager. * When single database connection is not used, repository is being obtained from the connection. */ getRepository( target: EntityTarget, ): Repository { // find already created repository instance and return it if found const repoFromMap = this.repositories.get(target) if (repoFromMap) return repoFromMap // if repository was not found then create it, store its instance and return it if (this.connection.driver.options.type === "mongodb") { const newRepository = new MongoRepository( target, this, this.queryRunner, ) this.repositories.set(target, newRepository) return newRepository } else { const newRepository = new Repository( target, this, this.queryRunner, ) this.repositories.set(target, newRepository) return newRepository } } /** * Gets tree repository for the given entity class or name. * If single database connection mode is used, then repository is obtained from the * repository aggregator, where each repository is individually created for this entity manager. * When single database connection is not used, repository is being obtained from the connection. */ getTreeRepository( target: EntityTarget, ): TreeRepository { // tree tables aren't supported by some drivers (mongodb) if (this.connection.driver.treeSupport === false) throw new TreeRepositoryNotSupportedError(this.connection.driver) // find already created repository instance and return it if found const repository = this.treeRepositories.find( (repository) => repository.target === target, ) if (repository) return repository // check if repository is real tree repository const newRepository = new TreeRepository(target, this, this.queryRunner) this.treeRepositories.push(newRepository) return newRepository } /** * Gets mongodb repository for the given entity class. */ getMongoRepository( target: EntityTarget, ): MongoRepository { return this.connection.getMongoRepository(target) } /** * Creates a new repository instance out of a given Repository and * sets current EntityManager instance to it. Used to work with custom repositories * in transactions. */ withRepository>( repository: R & Repository, ): R { const repositoryConstructor = repository.constructor as typeof Repository const { target, manager, queryRunner, ...otherRepositoryProperties } = repository return Object.assign( new repositoryConstructor(repository.target, this) as R, { ...otherRepositoryProperties, }, ) } /** * Gets custom entity repository marked with @EntityRepository decorator. * * @deprecated use Repository.extend to create custom repositories */ getCustomRepository(customRepository: ObjectType): T { const entityRepositoryMetadataArgs = getMetadataArgsStorage().entityRepositories.find((repository) => { return ( repository.target === (typeof customRepository === "function" ? customRepository : (customRepository as any).constructor) ) }) if (!entityRepositoryMetadataArgs) throw new CustomRepositoryNotFoundError(customRepository) const entityMetadata = entityRepositoryMetadataArgs.entity ? this.connection.getMetadata(entityRepositoryMetadataArgs.entity) : undefined const entityRepositoryInstance = new (entityRepositoryMetadataArgs.target as any)( this, entityMetadata, ) // NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed, // however we need these properties for internal work of the class if (entityRepositoryInstance instanceof AbstractRepository) { if (!(entityRepositoryInstance as any)["manager"]) (entityRepositoryInstance as any)["manager"] = this } else { if (!entityMetadata) throw new CustomRepositoryCannotInheritRepositoryError( customRepository, ) ;(entityRepositoryInstance as any)["manager"] = this ;(entityRepositoryInstance as any)["metadata"] = entityMetadata } return entityRepositoryInstance } /** * Releases all resources used by entity manager. * This is used when entity manager is created with a single query runner, * and this single query runner needs to be released after job with entity manager is done. */ async release(): Promise { if (!this.queryRunner) throw new NoNeedToReleaseEntityManagerError() return this.queryRunner.release() } }