UNPKG

@loopback/repository

Version:

Define and implement a common set of interfaces for interacting with databases

279 lines (266 loc) 10.2 kB
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved. // Node module: @loopback/repository // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {Getter} from '@loopback/core'; import {Filter, Where} from '@loopback/filter'; import {TypeResolver} from '../../'; import {Count, DataObject, Options} from '../../common-types'; import {EntityNotFoundError, InvalidPolymorphismError} from '../../errors'; import {Entity} from '../../model'; import { constrainDataObject, constrainFilter, constrainWhere, EntityCrudRepository, } from '../../repositories'; /** * CRUD operations for a target repository of a HasMany relation */ export interface HasOneRepository<Target extends Entity> { /** * Create a target model instance * @param targetModelData - The target model data * @param options - Options for the operation * options.polymorphicType - If polymorphic target model, * specify of which concrete model the created instance should be * @returns A promise which resolves to the newly created target model instance */ create( targetModelData: DataObject<Target>, options?: Options & {polymorphicType?: string}, ): Promise<Target>; /** * Find the only target model instance that belongs to the declaring model. * @param filter - Query filter without a Where condition * @param options - Options for the operations * options.polymorphicType - a string or a string array of polymorphic type names * to specify which repositories should are expected to be searched * It is highly recommended to contain this param especially for * datasources using deplicated ids across tables * @returns A promise resolved with the target object or rejected * with an EntityNotFoundError when target model instance was not found. */ get( filter?: Pick<Filter<Target>, Exclude<keyof Filter<Target>, 'where'>>, options?: Options & {polymorphicType?: string | string[]}, ): Promise<Target>; /** * Delete the related target model instance * @param options * options.polymorphicType - a string or a string array of polymorphic type names * to specify which repositories should are expected to be searched * It is highly recommended to contain this param especially for * datasources using deplicated ids across tables * @returns A promise which resolves the deleted target model instances */ delete( options?: Options & {polymorphicType?: string | string[]}, ): Promise<Count>; /** * Patch the related target model instance * @param dataObject - The target model fields and their new values to patch * If the target models are of different types, this should be a dictionary * @param options * options.isPolymorphic - whether dataObject is a dictionary * @returns A promise which resolves the patched target model instances */ patch( dataObject: | DataObject<Target> | {[polymorphicType: string]: DataObject<Target>}, options?: Options & {isPolymorphic?: boolean}, ): Promise<Count>; } export class DefaultHasOneRepository< TargetEntity extends Entity, TargetID, TargetRepository extends EntityCrudRepository<TargetEntity, TargetID>, > implements HasOneRepository<TargetEntity> { /** * Constructor of DefaultHasOneEntityCrudRepository * @param getTargetRepository - either a dictionary of target model type - target repository instance * or a single target repository instance * e.g. if the target is of a non-polymorphic type "Student", put the studentRepositoryGetterInstance * if the target is of a polymorphic type "Person" which can be either a "Student" or a "Teacher" * then put "{Student: studentRepositoryGetterInstance, Teacher: teacherRepositoryGetterInstance}" * @param constraint - the key value pair representing foreign key name to constrain * the target repository instance * @param targetResolver - () => Target to resolve the target class * e.g. if the target is of type "Student", then put "() => Student" */ constructor( public getTargetRepository: | Getter<TargetRepository> | { [repoType: string]: Getter<TargetRepository>; }, public constraint: DataObject<TargetEntity>, public targetResolver: TypeResolver<Entity, typeof Entity>, ) { if (typeof getTargetRepository === 'function') { this.getTargetRepositoryDict = { [targetResolver().name]: getTargetRepository as Getter<TargetRepository>, }; } else { this.getTargetRepositoryDict = getTargetRepository as { [repoType: string]: Getter<TargetRepository>; }; } } public getTargetRepositoryDict: { [repoType: string]: Getter<TargetRepository>; }; async create( targetModelData: DataObject<TargetEntity>, options?: Options & {polymorphicType?: string}, ): Promise<TargetEntity> { let polymorphicTypeName = options?.polymorphicType; if (polymorphicTypeName) { if (!this.getTargetRepositoryDict[polymorphicTypeName]) { throw new InvalidPolymorphismError(polymorphicTypeName); } } else { if (Object.keys(this.getTargetRepositoryDict).length > 1) { console.warn( 'It is highly recommended to specify the polymorphicType param when using polymorphic types.', ); } polymorphicTypeName = this.targetResolver().name; if (!this.getTargetRepositoryDict[polymorphicTypeName]) { throw new InvalidPolymorphismError(polymorphicTypeName); } } const targetRepository = await this.getTargetRepositoryDict[polymorphicTypeName](); return targetRepository.create( constrainDataObject(targetModelData, this.constraint), options, ); } async get( filter?: Pick< Filter<TargetEntity>, Exclude<keyof Filter<TargetEntity>, 'where'> >, options?: Options & {polymorphicType?: string | string[]}, ): Promise<TargetEntity> { let polymorphicTypes = options?.polymorphicType; let allKeys: string[]; if (Object.keys(this.getTargetRepositoryDict).length <= 1) { allKeys = Object.keys(this.getTargetRepositoryDict); } else if (!polymorphicTypes || polymorphicTypes.length === 0) { console.warn( 'It is highly recommended to specify the polymorphicType param when using polymorphic types.', ); allKeys = Object.keys(this.getTargetRepositoryDict); } else { if (typeof polymorphicTypes === 'string') { polymorphicTypes = [polymorphicTypes]; } allKeys = []; new Set(polymorphicTypes!).forEach(element => { if (Object.keys(this.getTargetRepositoryDict).includes(element)) { allKeys.push(element); } }); } for (const key of allKeys) { const targetRepository = await this.getTargetRepositoryDict[key](); const found = await targetRepository.find( Object.assign({limit: 1}, constrainFilter(filter, this.constraint)), {...options, polymorphicType: key}, ); if (found.length >= 1) { return found[0]; } } // We don't have a direct access to the foreign key value here :( const id = 'constraint ' + JSON.stringify(this.constraint); throw new EntityNotFoundError(this.targetResolver().name, id); } async delete( options?: Options & {polymorphicType?: string | string[]}, ): Promise<Count> { let polymorphicTypes = options?.polymorphicType; let allKeys: string[]; if (Object.keys(this.getTargetRepositoryDict).length <= 1) { allKeys = Object.keys(this.getTargetRepositoryDict); } else if (!polymorphicTypes || polymorphicTypes.length === 0) { console.warn( 'It is highly recommended to specify the polymorphicType param when using polymorphic types.', ); allKeys = Object.keys(this.getTargetRepositoryDict); } else { if (typeof polymorphicTypes === 'string') { polymorphicTypes = [polymorphicTypes]; } allKeys = []; new Set(polymorphicTypes!).forEach(element => { if (Object.keys(this.getTargetRepositoryDict).includes(element)) { allKeys.push(element); } }); } let total = 0; for (const key of allKeys) { const targetRepository = await this.getTargetRepositoryDict[key](); total += ( await targetRepository.deleteAll( constrainWhere({}, this.constraint as Where<TargetEntity>), options, ) )?.count ?? 0; } return {count: total}; } async patch( dataObject: | DataObject<TargetEntity> | {[polymorphicType: string]: DataObject<TargetEntity>}, options?: Options & {isPolymorphic?: boolean}, ): Promise<Count> { const isMultipleTypes = options?.isPolymorphic; let allKeys: string[]; if (!isMultipleTypes) { if (Object.keys(this.getTargetRepositoryDict).length > 1) { console.warn( 'It is highly recommended to specify the isPolymorphic param and pass in a dictionary of dataobjects when using polymorphic types.', ); } allKeys = Object.keys(this.getTargetRepositoryDict); } else { allKeys = []; new Set(Object.keys(dataObject)).forEach(element => { if (Object.keys(this.getTargetRepositoryDict).includes(element)) { allKeys.push(element); } }); } let total = 0; for (const key of allKeys) { const targetRepository = await this.getTargetRepositoryDict[key](); total += ( await targetRepository.updateAll( constrainDataObject( isMultipleTypes ? ( dataObject as { [polymorphicType: string]: DataObject<TargetEntity>; } )[key] : (dataObject as DataObject<TargetEntity>), this.constraint, ), constrainWhere({}, this.constraint as Where<TargetEntity>), options, ) )?.count ?? 0; } return {count: total}; } }