-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: move functions to define repository classes from @loopback/rest-crud to @loopback/repository #4792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: move functions to define repository classes from @loopback/rest-crud to @loopback/repository #4792
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| // 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 {expect} from '@loopback/testlab'; | ||
| import { | ||
| AnyObject, | ||
| Count, | ||
| CrudRepository, | ||
| DataObject, | ||
| DefaultCrudRepository, | ||
| DefaultKeyValueRepository, | ||
| defineCrudRepositoryClass, | ||
| defineKeyValueRepositoryClass, | ||
| defineRepositoryClass, | ||
| Entity, | ||
| Filter, | ||
| juggler, | ||
| model, | ||
| Model, | ||
| property, | ||
| Where, | ||
| } from '../../..'; | ||
|
|
||
| describe('RepositoryClass builder', () => { | ||
| describe('defineRepositoryClass', () => { | ||
| it('should generate custom repository class', async () => { | ||
| const AddressRepository = defineRepositoryClass< | ||
| typeof Address, | ||
| DummyCrudRepository<Address> | ||
| >(Address, DummyCrudRepository); | ||
| // `CrudRepository.prototype.find` is inherited | ||
| expect(AddressRepository.prototype.find).to.be.a.Function(); | ||
| // `DummyCrudRepository.prototype.findByTitle` is inherited | ||
| expect(AddressRepository.prototype.findByTitle).to.be.a.Function(); | ||
| expect(AddressRepository.name).to.equal('AddressRepository'); | ||
| expect(Object.getPrototypeOf(AddressRepository)).to.equal( | ||
| DummyCrudRepository, | ||
| ); | ||
bajtos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
| }); | ||
|
|
||
| describe('defineCrudRepositoryClass', () => { | ||
| it('should generate entity CRUD repository class', async () => { | ||
| const ProductRepository = defineCrudRepositoryClass(Product); | ||
|
|
||
| expect(ProductRepository.name).to.equal('ProductRepository'); | ||
| expect(ProductRepository.prototype.find).to.be.a.Function(); | ||
| expect(ProductRepository.prototype.findById).to.be.a.Function(); | ||
| expect(Object.getPrototypeOf(ProductRepository)).to.equal( | ||
| DefaultCrudRepository, | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('defineKeyValueRepositoryClass', () => { | ||
| it('should generate key value repository class', async () => { | ||
| const ProductRepository = defineKeyValueRepositoryClass(Product); | ||
|
|
||
| expect(ProductRepository.name).to.equal('ProductRepository'); | ||
| expect(ProductRepository.prototype.get).to.be.a.Function(); | ||
| expect(Object.getPrototypeOf(ProductRepository)).to.equal( | ||
| DefaultKeyValueRepository, | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| @model() | ||
| class Product extends Entity { | ||
| @property({id: true}) | ||
| id: number; | ||
|
|
||
| @property() | ||
| name: string; | ||
| } | ||
|
|
||
| @model() | ||
| class Address extends Model { | ||
| @property() | ||
| street: string; | ||
|
|
||
| @property() | ||
| city: string; | ||
|
|
||
| @property() | ||
| state: string; | ||
| } | ||
|
|
||
| class DummyCrudRepository<M extends Model> implements CrudRepository<M> { | ||
| constructor( | ||
| private modelCtor: typeof Model & {prototype: M}, | ||
| private dataSource: juggler.DataSource, | ||
| ) {} | ||
| create( | ||
| dataObject: DataObject<M>, | ||
| options?: AnyObject | undefined, | ||
| ): Promise<M> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| createAll( | ||
| dataObjects: DataObject<M>[], | ||
| options?: AnyObject | undefined, | ||
| ): Promise<M[]> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| find( | ||
| filter?: Filter<M> | undefined, | ||
| options?: AnyObject | undefined, | ||
| ): Promise<(M & {})[]> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| updateAll( | ||
| dataObject: DataObject<M>, | ||
| where?: Where<M> | undefined, | ||
| options?: AnyObject | undefined, | ||
| ): Promise<Count> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| deleteAll( | ||
| where?: Where<M> | undefined, | ||
| options?: AnyObject | undefined, | ||
| ): Promise<Count> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| count( | ||
| where?: Where<M> | undefined, | ||
| options?: AnyObject | undefined, | ||
| ): Promise<Count> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| // An extra method to verify it's available for the defined repo class | ||
| findByTitle(title: string): Promise<M[]> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| // 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 assert from 'assert'; | ||
| import {PrototypeOf} from './common-types'; | ||
| import {Entity, Model} from './model'; | ||
| import { | ||
| DefaultCrudRepository, | ||
| DefaultKeyValueRepository, | ||
| juggler, | ||
| Repository, | ||
| } from './repositories'; | ||
|
|
||
| /** | ||
| * Signature for a Repository class bound to a given model. The constructor | ||
| * accepts only the dataSource to use for persistence. | ||
| * | ||
| * `define*` functions return a class implementing this interface. | ||
| * | ||
| * @typeParam M - Model class | ||
| * @typeParam R - Repository class/interface | ||
| */ | ||
| export interface ModelRepositoryClass< | ||
| M extends Model, | ||
| R extends Repository<M> | ||
| > { | ||
| /** | ||
| * The constructor for the generated repository class | ||
| * @param dataSource - DataSource object | ||
| */ | ||
| new (dataSource: juggler.DataSource): R; | ||
| prototype: R; | ||
| } | ||
|
|
||
| /** | ||
| * Signature for repository classes that can be used as the base class for | ||
| * `define*` functions. The constructor of a base repository class accepts | ||
| * the target model constructor and the datasource to use. | ||
| * | ||
| * `define*` functions require a class implementing this interface on input. | ||
| * | ||
| * @typeParam M - Model class constructor, e.g `typeof Model`. | ||
| * **❗️IMPORTANT: The type argument `M` is describing the model constructor type | ||
| * (e.g. `typeof Model`), not the model instance type (`Model`) as is the case | ||
| * in other repository-related types. The constructor type is required | ||
| * to support custom repository classes requiring a Model subclass in the | ||
| * constructor arguments, e.g. `Entity` or a user-provided model.** | ||
| * | ||
| * @typeParam R - Repository class/interface | ||
| */ | ||
| export interface BaseRepositoryClass< | ||
| M extends typeof Model, | ||
| R extends Repository<PrototypeOf<M>> | ||
| > { | ||
| /** | ||
| * The constructor for the generated repository class | ||
| * @param modelClass - Model class | ||
| * @param dataSource - DataSource object | ||
| */ | ||
| new (modelClass: M, dataSource: juggler.DataSource): R; | ||
| prototype: R; | ||
| } | ||
|
|
||
| /** | ||
| * Create (define) a repository class for the given model. | ||
| * | ||
| * See also `defineCrudRepositoryClass` and `defineKeyValueRepositoryClass` | ||
| * for convenience wrappers providing repository class factory for the default | ||
| * CRUD and KeyValue implementations. | ||
| * | ||
| * **❗️IMPORTANT: The compiler (TypeScript 3.8) is not able to correctly infer | ||
| * generic arguments `M` and `R` from the class constructors provided in | ||
| * function arguments. You must always provide both M and R types explicitly.** | ||
| * | ||
| * @example | ||
| * | ||
| * ```ts | ||
| * const AddressRepository = defineRepositoryClass< | ||
| * typeof Address, | ||
| * DefaultEntityCrudRepository< | ||
| * Address, | ||
| * typeof Address.prototype.id, | ||
| * AddressRelations | ||
| * >, | ||
| * >(Address, DefaultCrudRepository); | ||
| * ``` | ||
| * | ||
| * @param modelClass - A model class such as `Address`. | ||
| * @param baseRepositoryClass - Repository implementation to use as the base, | ||
| * e.g. `DefaultCrudRepository`. | ||
| * | ||
| * @typeParam M - Model class constructor (e.g. `typeof Address`) | ||
| * @typeParam R - Repository class (e.g. `DefaultCrudRepository<Address, number>`) | ||
| */ | ||
| export function defineRepositoryClass< | ||
| M extends typeof Model, | ||
| R extends Repository<PrototypeOf<M>> | ||
| >( | ||
| modelClass: M, | ||
| baseRepositoryClass: BaseRepositoryClass<M, R>, | ||
| ): ModelRepositoryClass<PrototypeOf<M>, R> { | ||
| const repoName = modelClass.name + 'Repository'; | ||
| const defineNamedRepo = new Function( | ||
| 'ModelCtor', | ||
| 'BaseRepository', | ||
| `return class ${repoName} extends BaseRepository { | ||
| constructor(dataSource) { | ||
| super(ModelCtor, dataSource); | ||
| } | ||
| };`, | ||
| ); | ||
|
|
||
| const repo = defineNamedRepo(modelClass, baseRepositoryClass); | ||
| assert.equal(repo.name, repoName); | ||
| return repo; | ||
| } | ||
|
|
||
| /** | ||
| * Create (define) an entity CRUD repository class for the given model. | ||
| * This function always uses `DefaultCrudRepository` as the base class, | ||
| * use `defineRepositoryClass` if you want to use your own base repository. | ||
| * | ||
| * @example | ||
| * | ||
| * ```ts | ||
| * const ProductRepository = defineCrudRepositoryClass< | ||
| * Product, | ||
| * typeof Product.prototype.id, | ||
| * ProductRelations | ||
| * >(Product); | ||
| * ``` | ||
| * | ||
| * @param entityClass - An entity class such as `Product`. | ||
| * | ||
| * @typeParam E - An entity class | ||
| * @typeParam IdType - ID type for the entity | ||
| * @typeParam Relations - Relations for the entity | ||
| */ | ||
| export function defineCrudRepositoryClass< | ||
| E extends Entity, | ||
| IdType, | ||
| Relations extends object | ||
| >( | ||
| entityClass: typeof Entity & {prototype: E}, | ||
| ): ModelRepositoryClass<E, DefaultCrudRepository<E, IdType, Relations>> { | ||
| return defineRepositoryClass(entityClass, DefaultCrudRepository); | ||
| } | ||
|
|
||
| /** | ||
| * Create (define) a KeyValue repository class for the given entity. | ||
| * This function always uses `DefaultKeyValueRepository` as the base class, | ||
| * use `defineRepositoryClass` if you want to use your own base repository. | ||
| * | ||
| * @example | ||
| * | ||
| * ```ts | ||
| * const ProductKeyValueRepository = defineKeyValueRepositoryClass(Product); | ||
| * ``` | ||
| * | ||
| * @param modelClass - An entity class such as `Product`. | ||
| * | ||
| * @typeParam M - Model class | ||
| */ | ||
| export function defineKeyValueRepositoryClass<M extends Model>( | ||
| modelClass: typeof Model & {prototype: M}, | ||
| ): ModelRepositoryClass<M, DefaultKeyValueRepository<M>> { | ||
| return defineRepositoryClass(modelClass, DefaultKeyValueRepository); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,9 +64,9 @@ class defined without the need for a repository or controller class file. | |
|
|
||
| If you would like more flexibility, e.g. if you would only like to define a | ||
| default `CrudRest` controller or repository, you can use the two helper methods | ||
| (`defineCrudRestController` and `defineCrudRepositoryClass`) exposed from | ||
| `@loopback/rest-crud`. These functions will help you create controllers and | ||
| respositories using code. | ||
| (`defineCrudRestController` from `@loopback/rest-crud` and | ||
| `defineCrudRepositoryClass` from `@loopback/repository`). These functions will | ||
| help you create controllers and repositories using code. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we revert this change since
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm fine either way. |
||
|
|
||
| For the examples in the following sections, we are also assuming a model named | ||
| `Product`, and a datasource named `db` have already been created. | ||
|
|
@@ -106,6 +106,8 @@ on the Model) for your app. | |
| Usage example: | ||
|
|
||
| ```ts | ||
| import {defineCrudRepositoryClass} from '@loopback/repository'; | ||
|
|
||
| const ProductRepository = defineCrudRepositoryClass(Product); | ||
| this.repository(ProductRepository); | ||
| inject('datasources.db')(ProductRepository, undefined, 0); | ||
|
|
@@ -118,6 +120,8 @@ Here is an example of an app which uses `defineCrudRepositoryClass` and | |
| requirements. | ||
|
|
||
| ```ts | ||
| import {defineCrudRepositoryClass} from '@loopback/repository'; | ||
|
|
||
| export class TryApplication extends BootMixin( | ||
| ServiceMixin(RepositoryMixin(RestApplication)), | ||
| ) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.