diff --git a/packages/repository/src/__tests__/unit/repositories/has-many-through-repository-factory.unit.ts b/packages/repository/src/__tests__/unit/repositories/has-many-through-repository-factory.unit.ts new file mode 100644 index 000000000000..680c2919b2cd --- /dev/null +++ b/packages/repository/src/__tests__/unit/repositories/has-many-through-repository-factory.unit.ts @@ -0,0 +1,113 @@ +// Copyright IBM Corp. 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 {createStubInstance, expect} from '@loopback/testlab'; +import { + DefaultCrudRepository, + Entity, + Getter, + juggler, + model, + property, +} from '../../..'; +import {createHasManyThroughRepositoryFactory} from '../../../relations/has-many/has-many-through-repository.factory'; +import {HasManyThroughResolvedDefinition} from '../../../relations/has-many/has-many-through.helpers'; + +describe('createHasManyThroughRepositoryFactory', () => { + let categoryProductLinkRepo: CategoryProductLinkRepository; + let productRepo: ProductRepository; + + beforeEach(() => { + givenStubbedProductRepo(); + givenStubbedCategoryProductLinkRepo(); + }); + + it('should return a function that could create hasManyThrough repository', () => { + const relationMeta = resolvedMetadata as HasManyThroughResolvedDefinition; + const result = createHasManyThroughRepositoryFactory( + relationMeta, + Getter.fromValue(productRepo), + Getter.fromValue(categoryProductLinkRepo), + ); + expect(result).to.be.Function(); + }); + + /*------------- HELPERS ---------------*/ + + @model() + class Category extends Entity { + @property({id: true}) + id: number; + + constructor(data: Partial) { + super(data); + } + } + + @model() + class Product extends Entity { + @property({id: true}) + id: number; + + constructor(data: Partial) { + super(data); + } + } + + @model() + class CategoryProductLink extends Entity { + @property({id: true}) + id: number; + @property() + categoryId: number; + @property() + productId: number; + + constructor(data: Partial) { + super(data); + } + } + + class ProductRepository extends DefaultCrudRepository< + Product, + typeof Product.prototype.id + > { + constructor(dataSource: juggler.DataSource) { + super(Product, dataSource); + } + } + + class CategoryProductLinkRepository extends DefaultCrudRepository< + CategoryProductLink, + typeof CategoryProductLink.prototype.id + > { + constructor(dataSource: juggler.DataSource) { + super(CategoryProductLink, dataSource); + } + } + + const resolvedMetadata = { + name: 'products', + type: 'hasMany', + targetsMany: true, + source: Category, + keyFrom: 'id', + target: () => Product, + keyTo: 'id', + through: { + model: () => CategoryProductLink, + keyFrom: 'categoryId', + keyTo: 'productId', + }, + }; + + function givenStubbedProductRepo() { + productRepo = createStubInstance(ProductRepository); + } + + function givenStubbedCategoryProductLinkRepo() { + categoryProductLinkRepo = createStubInstance(CategoryProductLinkRepository); + } +}); diff --git a/packages/repository/src/relations/has-many/has-many-through-repository.factory.ts b/packages/repository/src/relations/has-many/has-many-through-repository.factory.ts new file mode 100644 index 000000000000..9234e51d9833 --- /dev/null +++ b/packages/repository/src/relations/has-many/has-many-through-repository.factory.ts @@ -0,0 +1,93 @@ +// Copyright IBM Corp. 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 { + DataObject, + Entity, + EntityCrudRepository, + Getter, + HasManyDefinition, +} from '../..'; +import { + createTargetConstraint, + createThroughConstraint, + createThroughFkConstraint, + resolveHasManyThroughMetadata, +} from './has-many-through.helpers'; +import { + DefaultHasManyThroughRepository, + HasManyThroughRepository, +} from './has-many-through.repository'; + +/** + * a factory to generate hasManyThrough repository class. + * + * Warning: The hasManyThrough interface is experimental and is subject to change. + * If backwards-incompatible changes are made, a new major version may not be + * released. + */ + +export type HasManyThroughRepositoryFactory< + TargetEntity extends Entity, + TargetID, + ThroughEntity extends Entity, + ForeignKeyType +> = ( + fkValue: ForeignKeyType, +) => HasManyThroughRepository; + +export function createHasManyThroughRepositoryFactory< + Target extends Entity, + TargetID, + Through extends Entity, + ThroughID, + ForeignKeyType +>( + relationMetadata: HasManyDefinition, + targetRepositoryGetter: Getter>, + throughRepositoryGetter: Getter>, +): HasManyThroughRepositoryFactory { + const meta = resolveHasManyThroughMetadata(relationMetadata); + const result = function (fkValue: ForeignKeyType) { + function getTargetContraint( + throughInstances: Through | Through[], + ): DataObject { + return createTargetConstraint(meta, throughInstances); + } + function getThroughConstraint(): DataObject { + const constriant: DataObject = createThroughConstraint< + Through, + ForeignKeyType + >(meta, fkValue); + return constriant; + } + + function getThroughFkConstraint( + targetInstance: Target, + ): DataObject { + const constriant: DataObject = createThroughFkConstraint< + Target, + Through + >(meta, targetInstance); + return constriant; + } + + return new DefaultHasManyThroughRepository< + Target, + TargetID, + EntityCrudRepository, + Through, + ThroughID, + EntityCrudRepository + >( + targetRepositoryGetter, + throughRepositoryGetter, + getTargetContraint, + getThroughConstraint, + getThroughFkConstraint, + ); + }; + return result; +} diff --git a/packages/repository/src/relations/has-many/has-many-through.repository.ts b/packages/repository/src/relations/has-many/has-many-through.repository.ts index 33e96e3e85f7..07380690476a 100644 --- a/packages/repository/src/relations/has-many/has-many-through.repository.ts +++ b/packages/repository/src/relations/has-many/has-many-through.repository.ts @@ -3,7 +3,16 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Count, DataObject, Entity, Filter, Options, Where} from '../..'; +import { + Count, + DataObject, + Entity, + EntityCrudRepository, + Filter, + Getter, + Options, + Where, +} from '../..'; /** * CRUD operations for a target repository of a HasManyThrough relation @@ -28,7 +37,7 @@ export interface HasManyThroughRepository< throughData?: DataObject; throughOptions?: Options; }, - ): Promise; + ): Promise; /** * Find target model instance(s) @@ -41,7 +50,7 @@ export interface HasManyThroughRepository< options?: Options & { throughOptions?: Options; }, - ): Promise; + ): Promise; /** * Delete multiple target model instances @@ -98,3 +107,88 @@ export interface HasManyThroughRepository< }, ): Promise; } + +/** + * a class for CRUD operations for hasManyThrough relation. + * + * Warning: The hasManyThrough interface is experimental and is subject to change. + * If backwards-incompatible changes are made, a new major version may not be + * released. + */ +export class DefaultHasManyThroughRepository< + TargetEntity extends Entity, + TargetID, + TargetRepository extends EntityCrudRepository, + ThroughEntity extends Entity, + ThroughID, + ThroughRepository extends EntityCrudRepository +> implements HasManyThroughRepository { + constructor( + public getTargetRepository: Getter, + public getThroughRepository: Getter, + public getTargetConstraint: ( + throughInstances: ThroughEntity | ThroughEntity[], + ) => DataObject, + public getThroughConstraint: () => DataObject, + public getThroughFkConstraint: ( + targetInstance: TargetEntity, + ) => DataObject, + ) {} + + async create( + targetModelData: DataObject, + options?: Options & { + throughData?: DataObject; + throughOptions?: Options; + }, + ): Promise { + throw new Error('Method not implemented.'); + } + + async find( + filter?: Filter, + options?: Options & { + throughOptions?: Options; + }, + ): Promise { + throw new Error('Method not implemented.'); + } + + async delete( + where?: Where, + options?: Options & { + throughOptions?: Options; + }, + ): Promise { + throw new Error('Method not implemented.'); + } + + async patch( + dataObject: DataObject, + where?: Where, + options?: Options & { + throughOptions?: Options; + }, + ): Promise { + throw new Error('Method not implemented.'); + } + + async link( + targetModelId: TargetID, + options?: Options & { + throughData?: DataObject; + throughOptions?: Options; + }, + ): Promise { + throw new Error('Method not implemented.'); + } + + async unlink( + targetModelId: TargetID, + options?: Options & { + throughOptions?: Options; + }, + ): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/repository/src/relations/has-many/index.ts b/packages/repository/src/relations/has-many/index.ts index a1fd1dee453a..bddc202d8d6f 100644 --- a/packages/repository/src/relations/has-many/index.ts +++ b/packages/repository/src/relations/has-many/index.ts @@ -3,7 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -export * from './has-many.decorator'; -export * from './has-many.repository'; export * from './has-many-repository.factory'; +export * from './has-many-through-repository.factory'; +export * from './has-many-through.repository'; +export * from './has-many.decorator'; export * from './has-many.inclusion-resolver'; +export * from './has-many.repository';