From 10bea99256f59c3b847d645af0dd943b254b1b91 Mon Sep 17 00:00:00 2001 From: Nora Date: Tue, 21 May 2019 11:05:04 -0400 Subject: [PATCH] feat: add navigational properties to find* methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated find* methods to include navigational properties, introduced *Relations to models, and updated DefaultCrudRepository to include Relations Co-authored-by: Miroslav Bajtoš --- docs/site/BelongsTo-relation.md | 25 +++++++++++++-- docs/site/HasMany-relation.md | 19 ++++++++++-- docs/site/Repositories.md | 5 +-- docs/site/hasOne-relation.md | 21 ++++++++++--- .../todo-list/todo-list-tutorial-model.md | 14 ++++++++- .../todo-list-tutorial-repository.md | 5 +-- .../src/models/note.model.ts | 6 ++++ .../src/repositories/note.repository.ts | 7 +++-- .../src/models/todo-list-image.model.ts | 10 ++++-- .../todo-list/src/models/todo-list.model.ts | 16 ++++++++-- examples/todo-list/src/models/todo.model.ts | 10 ++++-- .../todo-list-image.repository.ts | 11 ++++--- .../src/repositories/todo-list.repository.ts | 9 +++--- .../src/repositories/todo.repository.ts | 5 +-- examples/todo/src/models/todo.model.ts | 8 ++++- .../todo/src/repositories/todo.repository.ts | 9 +++--- packages/cli/generators/discover/index.js | 2 +- packages/cli/generators/model/index.js | 1 - .../generators/model/templates/model.ts.ejs | 6 ++++ .../repository-crud-default-template.ts.ejs | 5 +-- .../generators/model.integration.js | 28 +++++++++++++++++ .../generators/repository.integration.js | 1 + packages/repository/README.md | 11 +++++-- ...has-many-without-di.relation.acceptance.ts | 2 +- .../fixtures/models/address.model.ts | 10 ++++-- .../fixtures/models/customer.model.ts | 12 +++++-- .../__tests__/fixtures/models/order.model.ts | 8 ++++- .../fixtures/models/product.model.ts | 8 ++++- .../repositories/address.repository.ts | 5 +-- .../repositories/customer.repository.ts | 9 +++--- .../fixtures/repositories/order.repository.ts | 5 +-- .../repositories/product.repository.ts | 5 +-- .../src/repositories/legacy-juggler-bridge.ts | 31 +++++++++++++------ .../repository/src/repositories/repository.ts | 22 ++++++++----- 34 files changed, 270 insertions(+), 81 deletions(-) diff --git a/docs/site/BelongsTo-relation.md b/docs/site/BelongsTo-relation.md index c905e2759046..c5c23fb15ed8 100644 --- a/docs/site/BelongsTo-relation.md +++ b/docs/site/BelongsTo-relation.md @@ -67,6 +67,12 @@ export class Order extends Entity { super(data); } } + +export interface OrderRelations { + // describe navigational properties here +} + +export type OrderWithRelations = Order & OrderRelations; ``` The definition of the `belongsTo` relation is inferred by using the `@belongsTo` @@ -83,6 +89,10 @@ class Order extends Entity { @belongsTo(() => Customer, {keyTo: 'pk'}) customerId: number; } + +export interface OrderRelations { + customer?: CustomerWithRelations; +} ``` ## Configuring a belongsTo relation @@ -120,12 +130,13 @@ import { juggler, repository, } from '@loopback/repository'; -import {Customer, Order} from '../models'; +import {Customer, Order, OrderRelations} from '../models'; import {CustomerRepository} from '../repositories'; export class OrderRepository extends DefaultCrudRepository< Order, - typeof Order.prototype.id + typeof Order.prototype.id, + OrderRelations > { public readonly customer: BelongsToAccessor< Customer, @@ -212,6 +223,13 @@ export class Category extends Entity { super(data); } } + +export interface CategoryRelations { + categories?: CategoryWithRelations[]; + parent?: CategoryWithRelations; +} + +export type CategoryWithRelations = Category & CategoryRelations; ``` The `CategoryRepository` must be declared like below @@ -219,7 +237,8 @@ The `CategoryRepository` must be declared like below ```ts export class CategoryRepository extends DefaultCrudRepository< Category, - typeof Category.prototype.id + typeof Category.prototype.id, + CategoryRelations > { public readonly parent: BelongsToAccessor< Category, diff --git a/docs/site/HasMany-relation.md b/docs/site/HasMany-relation.md index f4a6053e1249..c223ac8121bc 100644 --- a/docs/site/HasMany-relation.md +++ b/docs/site/HasMany-relation.md @@ -131,6 +131,12 @@ export class Order extends Entity { super(data); } } + +export interface OrderRelations { + // describe navigational properties here +} + +export type OrderWithRelations = Order & OrderRelations; ``` The foreign key property (`customerId`) in the target model can be added via a @@ -140,7 +146,7 @@ corresponding [belongsTo](BelongsTo-relation.md) relation, too. ```ts import {Entity, model, property, belongsTo} from '@loopback/repository'; -import {Customer} from './customer.model'; +import {Customer, CustomerWithRelations} from './customer.model'; @model() export class Order extends Entity { @@ -164,6 +170,12 @@ export class Order extends Entity { super(data); } } + +export interface OrderRelations { + customer?: CustomerWithRelations; +} + +export type OrderWithRelations = Order & OrderRelations; ``` ## Configuring a hasMany relation @@ -195,7 +207,7 @@ The following code snippet shows how it would look like: content="/src/repositories/customer.repository.ts" %} ```ts -import {Order, Customer} from '../models'; +import {Order, Customer, CustomerRelations} from '../models'; import {OrderRepository} from './order.repository'; import { DefaultCrudRepository, @@ -207,7 +219,8 @@ import {inject, Getter} from '@loopback/core'; export class CustomerRepository extends DefaultCrudRepository< Customer, - typeof Customer.prototype.id + typeof Customer.prototype.id, + CustomerRelations > { public readonly orders: HasManyRepositoryFactory< Order, diff --git a/docs/site/Repositories.md b/docs/site/Repositories.md index 8614b605f9a9..22739b814627 100644 --- a/docs/site/Repositories.md +++ b/docs/site/Repositories.md @@ -162,13 +162,14 @@ TypeScript version: ```ts import {DefaultCrudRepository, juggler} from '@loopback/repository'; -import {Account} from '../models'; +import {Account, AccountRelations} from '../models'; import {DbDataSource} from '../datasources'; import {inject} from '@loopback/context'; export class AccountRepository extends DefaultCrudRepository< Account, - typeof Account.prototype.id + typeof Account.prototype.id, + AccountRelations > { constructor(@inject('datasources.db') dataSource: DbDataSource) { super(Account, dataSource); diff --git a/docs/site/hasOne-relation.md b/docs/site/hasOne-relation.md index 064154377239..8b6785890b3b 100644 --- a/docs/site/hasOne-relation.md +++ b/docs/site/hasOne-relation.md @@ -57,7 +57,7 @@ defined on a source model `Supplier` in the example below: {% include code-caption.html content="/src/models/supplier.model.ts" %} ```ts -import {Account} from './account.model'; +import {Account, AccountWithRelations} from './account.model'; import {Entity, property, hasOne} from '@loopback/repository'; export class Supplier extends Entity { @@ -80,13 +80,19 @@ export class Supplier extends Entity { super(data); } } + +export interface SupplierRelations { + account?: AccountWithRelations; +} + +export type SupplierWithRelations = Supplier & SupplierRelations; ``` On the other side of the relation, we'd need to declare a `belongsTo` relation since every `Account` has to belong to exactly one `Supplier`: ```ts -import {Supplier} from './supplier.model'; +import {Supplier, SupplierWithRelations} from './supplier.model'; import {Entity, property, belongsTo} from '@loopback/repository'; export class Account extends Entity { @@ -108,6 +114,12 @@ export class Account extends Entity { super(data); } } + +export interface AccountRelations { + supplier?: SupplierWithRelations; +} + +export type AccountWithRelations = Account & AccountRelations; ``` The definition of the `hasOne` relation is inferred by using the `@hasOne` @@ -190,7 +202,7 @@ The following code snippet shows how it would look like: content="/src/repositories/supplier.repository.ts" %} ```ts -import {Account, Supplier} from '../models'; +import {Account, Supplier, SupplierRelations} from '../models'; import {AccountRepository} from './account.repository'; import { DefaultCrudRepository, @@ -202,7 +214,8 @@ import {inject, Getter} from '@loopback/core'; export class SupplierRepository extends DefaultCrudRepository< Supplier, - typeof Supplier.prototype.id + typeof Supplier.prototype.id, + SupplierRelations > { public readonly account: HasOneRepositoryFactory< Account, diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-model.md b/docs/site/tutorials/todo-list/todo-list-tutorial-model.md index 146e58958585..f0dd65c33532 100644 --- a/docs/site/tutorials/todo-list/todo-list-tutorial-model.md +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-model.md @@ -76,7 +76,7 @@ model. Add the following import statements and property to the `TodoList` model: ```ts import {hasMany} from '@loopback/repository'; -import {Todo} from './todo.model'; +import {Todo, TodoWithRelations} from './todo.model'; @model() export class TodoList extends Entity { @@ -87,6 +87,12 @@ export class TodoList extends Entity { // ...constructor def... } + +export interface TodoListRelations { + todos?: TodoWithRelations[]; +} + +export type TodoListWithRelations = TodoList & TodoListRelations; ``` The `@hasMany()` decorator defines this property. As the decorator's name @@ -108,6 +114,12 @@ export class Todo extends Entity { // ...constructor def... } + +export interface TodoRelations { + todoList?: TodoListWithRelations; +} + +export type TodoWithRelations = Todo & TodoRelations; ``` Once the models have been completely configured, it's time to move on to adding diff --git a/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md b/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md index 8bc7e680bdd5..90a9277f1733 100644 --- a/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md +++ b/docs/site/tutorials/todo-list/todo-list-tutorial-repository.md @@ -58,12 +58,13 @@ import { juggler, repository, } from '@loopback/repository'; -import {Todo, TodoList} from '../models'; +import {Todo, TodoList, TodoListRelations} from '../models'; import {TodoRepository} from './todo.repository'; export class TodoListRepository extends DefaultCrudRepository< TodoList, - typeof TodoList.prototype.id + typeof TodoList.prototype.id, + TodoListRelations > { public readonly todos: HasManyRepositoryFactory< Todo, diff --git a/examples/express-composition/src/models/note.model.ts b/examples/express-composition/src/models/note.model.ts index bbb4945a4588..de92e8290043 100644 --- a/examples/express-composition/src/models/note.model.ts +++ b/examples/express-composition/src/models/note.model.ts @@ -28,3 +28,9 @@ export class Note extends Entity { super(data); } } + +export interface NoteRelations { + // describe navigational properties here +} + +export type NoteWithRelations = Note & NoteRelations; diff --git a/examples/express-composition/src/repositories/note.repository.ts b/examples/express-composition/src/repositories/note.repository.ts index 5c46de96ed89..f8a5046243fb 100644 --- a/examples/express-composition/src/repositories/note.repository.ts +++ b/examples/express-composition/src/repositories/note.repository.ts @@ -3,14 +3,15 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {inject} from '@loopback/core'; import {DefaultCrudRepository} from '@loopback/repository'; -import {Note} from '../models'; import {DsDataSource} from '../datasources'; -import {inject} from '@loopback/core'; +import {Note, NoteRelations} from '../models'; export class NoteRepository extends DefaultCrudRepository< Note, - typeof Note.prototype.id + typeof Note.prototype.id, + NoteRelations > { constructor(@inject('datasources.ds') dataSource: DsDataSource) { super(Note, dataSource); diff --git a/examples/todo-list/src/models/todo-list-image.model.ts b/examples/todo-list/src/models/todo-list-image.model.ts index 92c5077cec8a..2da331cd8125 100644 --- a/examples/todo-list/src/models/todo-list-image.model.ts +++ b/examples/todo-list/src/models/todo-list-image.model.ts @@ -3,8 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Entity, model, property, belongsTo} from '@loopback/repository'; -import {TodoList} from './todo-list.model'; +import {belongsTo, Entity, model, property} from '@loopback/repository'; +import {TodoList, TodoListWithRelations} from './todo-list.model'; @model() export class TodoListImage extends Entity { @@ -29,3 +29,9 @@ export class TodoListImage extends Entity { super(data); } } + +export interface TodoListImageRelations { + todoList?: TodoListWithRelations; +} + +export type TodoListImageWithRelations = TodoListImage & TodoListImageRelations; diff --git a/examples/todo-list/src/models/todo-list.model.ts b/examples/todo-list/src/models/todo-list.model.ts index bdb1eb8d1382..94c24762c7d3 100644 --- a/examples/todo-list/src/models/todo-list.model.ts +++ b/examples/todo-list/src/models/todo-list.model.ts @@ -3,9 +3,12 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Entity, model, property, hasMany, hasOne} from '@loopback/repository'; -import {Todo} from './todo.model'; -import {TodoListImage} from './todo-list-image.model'; +import {Entity, hasMany, hasOne, model, property} from '@loopback/repository'; +import { + TodoListImage, + TodoListImageWithRelations, +} from './todo-list-image.model'; +import {Todo, TodoWithRelations} from './todo.model'; @model() export class TodoList extends Entity { @@ -36,3 +39,10 @@ export class TodoList extends Entity { super(data); } } + +export interface TodoListRelations { + todos?: TodoWithRelations[]; + image?: TodoListImageWithRelations; +} + +export type TodoListWithRelations = TodoList & TodoListRelations; diff --git a/examples/todo-list/src/models/todo.model.ts b/examples/todo-list/src/models/todo.model.ts index bc30ce80def0..3848282311cd 100644 --- a/examples/todo-list/src/models/todo.model.ts +++ b/examples/todo-list/src/models/todo.model.ts @@ -3,8 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Entity, property, model, belongsTo} from '@loopback/repository'; -import {TodoList} from './todo-list.model'; +import {belongsTo, Entity, model, property} from '@loopback/repository'; +import {TodoList, TodoListWithRelations} from './todo-list.model'; @model() export class Todo extends Entity { @@ -41,3 +41,9 @@ export class Todo extends Entity { super(data); } } + +export interface TodoRelations { + todoList?: TodoListWithRelations[]; +} + +export type TodoWithRelations = Todo & TodoRelations; diff --git a/examples/todo-list/src/repositories/todo-list-image.repository.ts b/examples/todo-list/src/repositories/todo-list-image.repository.ts index c23c477c694f..7fa13fb8041e 100644 --- a/examples/todo-list/src/repositories/todo-list-image.repository.ts +++ b/examples/todo-list/src/repositories/todo-list-image.repository.ts @@ -1,21 +1,22 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. +// Copyright IBM Corp. 2018,2019. All Rights Reserved. // Node module: @loopback/example-todo-list // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Getter, inject} from '@loopback/core'; import { + BelongsToAccessor, DefaultCrudRepository, repository, - BelongsToAccessor, } from '@loopback/repository'; -import {TodoListImage, TodoList} from '../models'; import {DbDataSource} from '../datasources'; -import {inject, Getter} from '@loopback/core'; +import {TodoList, TodoListImage, TodoListImageRelations} from '../models'; import {TodoListRepository} from './todo-list.repository'; export class TodoListImageRepository extends DefaultCrudRepository< TodoListImage, - typeof TodoListImage.prototype.id + typeof TodoListImage.prototype.id, + TodoListImageRelations > { public readonly todoList: BelongsToAccessor< TodoList, diff --git a/examples/todo-list/src/repositories/todo-list.repository.ts b/examples/todo-list/src/repositories/todo-list.repository.ts index 4b1d63fe8f71..17004ab276db 100644 --- a/examples/todo-list/src/repositories/todo-list.repository.ts +++ b/examples/todo-list/src/repositories/todo-list.repository.ts @@ -7,17 +7,18 @@ import {Getter, inject} from '@loopback/core'; import { DefaultCrudRepository, HasManyRepositoryFactory, + HasOneRepositoryFactory, juggler, repository, - HasOneRepositoryFactory, } from '@loopback/repository'; -import {Todo, TodoList, TodoListImage} from '../models'; -import {TodoRepository} from './todo.repository'; +import {Todo, TodoList, TodoListImage, TodoListRelations} from '../models'; import {TodoListImageRepository} from './todo-list-image.repository'; +import {TodoRepository} from './todo.repository'; export class TodoListRepository extends DefaultCrudRepository< TodoList, - typeof TodoList.prototype.id + typeof TodoList.prototype.id, + TodoListRelations > { public readonly todos: HasManyRepositoryFactory< Todo, diff --git a/examples/todo-list/src/repositories/todo.repository.ts b/examples/todo-list/src/repositories/todo.repository.ts index 4f26693b602c..a558e747d1bd 100644 --- a/examples/todo-list/src/repositories/todo.repository.ts +++ b/examples/todo-list/src/repositories/todo.repository.ts @@ -10,12 +10,13 @@ import { juggler, repository, } from '@loopback/repository'; -import {Todo, TodoList} from '../models'; +import {Todo, TodoList, TodoRelations} from '../models'; import {TodoListRepository} from './todo-list.repository'; export class TodoRepository extends DefaultCrudRepository< Todo, - typeof Todo.prototype.id + typeof Todo.prototype.id, + TodoRelations > { public readonly todoList: BelongsToAccessor< TodoList, diff --git a/examples/todo/src/models/todo.model.ts b/examples/todo/src/models/todo.model.ts index d71ced310cb9..93ed89c900a6 100644 --- a/examples/todo/src/models/todo.model.ts +++ b/examples/todo/src/models/todo.model.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. +// Copyright IBM Corp. 2018,2019. All Rights Reserved. // Node module: @loopback/example-todo // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT @@ -44,3 +44,9 @@ export class Todo extends Entity { super(data); } } + +export interface TodoRelations { + // describe navigational properties here +} + +export type TodoWithRelations = Todo & TodoRelations; diff --git a/examples/todo/src/repositories/todo.repository.ts b/examples/todo/src/repositories/todo.repository.ts index 100385769805..4693c6df21b5 100644 --- a/examples/todo/src/repositories/todo.repository.ts +++ b/examples/todo/src/repositories/todo.repository.ts @@ -1,15 +1,16 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. +// Copyright IBM Corp. 2018,2019. All Rights Reserved. // Node module: @loopback/example-todo // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {DefaultCrudRepository, juggler} from '@loopback/repository'; -import {Todo} from '../models'; import {inject} from '@loopback/core'; +import {DefaultCrudRepository, juggler} from '@loopback/repository'; +import {Todo, TodoRelations} from '../models'; export class TodoRepository extends DefaultCrudRepository< Todo, - typeof Todo.prototype.id + typeof Todo.prototype.id, + TodoRelations > { constructor(@inject('datasources.db') dataSource: juggler.DataSource) { super(Todo, dataSource); diff --git a/packages/cli/generators/discover/index.js b/packages/cli/generators/discover/index.js index 1b252b49ad60..10b70616fa52 100644 --- a/packages/cli/generators/discover/index.js +++ b/packages/cli/generators/discover/index.js @@ -219,7 +219,7 @@ module.exports = class DiscoveryGenerator extends ArtifactGenerator { modelDefinition.isModelBaseBuiltin = true; modelDefinition.modelBaseClass = 'Entity'; modelDefinition.className = utils.pascalCase(modelDefinition.name); - // These last two are so that the template doesn't error out of they aren't there + // These last two are so that the template doesn't error out if they aren't there modelDefinition.allowAdditionalProperties = true; // modelDefinition.modelSettings = modelDefinition.settings || {}; modelDefinition.modelSettings = utils.stringifyModelSettings( diff --git a/packages/cli/generators/model/index.js b/packages/cli/generators/model/index.js index 386d79383148..ca2ddab837b1 100644 --- a/packages/cli/generators/model/index.js +++ b/packages/cli/generators/model/index.js @@ -71,7 +71,6 @@ module.exports = class ModelGenerator extends ArtifactGenerator { this.artifactInfo.properties = {}; this.artifactInfo.modelSettings = {}; - this.propCounter = 0; this.artifactInfo.modelDir = path.resolve( this.artifactInfo.rootDir, diff --git a/packages/cli/generators/model/templates/model.ts.ejs b/packages/cli/generators/model/templates/model.ts.ejs index 3aa37d3dada0..0e416bc139da 100644 --- a/packages/cli/generators/model/templates/model.ts.ejs +++ b/packages/cli/generators/model/templates/model.ts.ejs @@ -33,3 +33,9 @@ export class <%= className %> extends <%= modelBaseClass %> { super(data); } } + +export interface <%= className %>Relations { + // describe navigational properties here +} + +export type <%= className %>WithRelations = <%= className %> & <%= className %>Relations; diff --git a/packages/cli/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs b/packages/cli/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs index 10c17ca0151c..ba8a3ca29418 100644 --- a/packages/cli/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs +++ b/packages/cli/generators/repository/templates/src/repositories/repository-crud-default-template.ts.ejs @@ -1,7 +1,7 @@ <%if (isRepositoryBaseBuiltin) { -%> import {<%= repositoryTypeClass %>} from '@loopback/repository'; <% } -%> -import {<%= modelName %>} from '../models'; +import {<%= modelName %>, <%= modelName %>Relations} from '../models'; import {<%= dataSourceClassName %>} from '../datasources'; import {inject} from '@loopback/core'; <%if ( !isRepositoryBaseBuiltin ) { -%> @@ -10,7 +10,8 @@ import {<%=repositoryBaseClass %>} from './<%=repositoryBaseFile %>'; export class <%= className %>Repository extends <%= repositoryBaseClass %>< <%= modelName %>, - typeof <%= modelName %>.prototype.<%= idProperty %> + typeof <%= modelName %>.prototype.<%= idProperty %>, + <%= modelName %>Relations > { constructor( @inject('datasources.<%= dataSourceName %>') dataSource: <%= dataSourceClassName %>, diff --git a/packages/cli/test/integration/generators/model.integration.js b/packages/cli/test/integration/generators/model.integration.js index 794676a9902a..6f5794e203c5 100644 --- a/packages/cli/test/integration/generators/model.integration.js +++ b/packages/cli/test/integration/generators/model.integration.js @@ -201,6 +201,34 @@ describe('lb4 model integration', () => { assert.fileContent(expectedModelFile, /\[prop: string\]: any;/); }); + it('scaffolds empty model relation interface and relation type', async () => { + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => testUtils.givenLBProject(SANDBOX_PATH)) + .withPrompts({ + name: 'test', + propName: null, + modelBaseClass: 'Entity', + allowAdditionalProperties: false, + }); + + assert.file(expectedModelFile); + assert.file(expectedIndexFile); + + assert.fileContent( + expectedModelFile, + /import {Entity, model, property} from '@loopback\/repository';/, + ); + assert.fileContent( + expectedModelFile, + /export type TestWithRelations = Test & TestRelations;/, + ); + assert.fileContent( + expectedModelFile, + /export interface TestRelations {\n \/\/ describe navigational properties here\n}/, + ); + }); + it('scaffolds correct files with args', async () => { await testUtils .executeGenerator(generator) diff --git a/packages/cli/test/integration/generators/repository.integration.js b/packages/cli/test/integration/generators/repository.integration.js index f57bd6163d9f..a4911bb10b74 100644 --- a/packages/cli/test/integration/generators/repository.integration.js +++ b/packages/cli/test/integration/generators/repository.integration.js @@ -282,6 +282,7 @@ describe('lb4 repository', function() { /export class DefaultModelRepository extends DefaultCrudRepository\ { constructor(@inject('datasources.db') protected dataSource: DataSourceType) { super(Note, dataSource); diff --git a/packages/repository/src/__tests__/acceptance/has-many-without-di.relation.acceptance.ts b/packages/repository/src/__tests__/acceptance/has-many-without-di.relation.acceptance.ts index a55803b47c76..f5a395720781 100644 --- a/packages/repository/src/__tests__/acceptance/has-many-without-di.relation.acceptance.ts +++ b/packages/repository/src/__tests__/acceptance/has-many-without-di.relation.acceptance.ts @@ -8,12 +8,12 @@ import { DefaultCrudRepository, Entity, EntityCrudRepository, + Getter, hasMany, HasManyRepositoryFactory, juggler, model, property, - Getter, } from '../..'; describe('HasMany relation', () => { diff --git a/packages/repository/src/__tests__/fixtures/models/address.model.ts b/packages/repository/src/__tests__/fixtures/models/address.model.ts index 82c45b1e9236..3f62ea9484b5 100644 --- a/packages/repository/src/__tests__/fixtures/models/address.model.ts +++ b/packages/repository/src/__tests__/fixtures/models/address.model.ts @@ -3,8 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Entity, model, property, belongsTo} from '../../..'; -import {Customer} from './customer.model'; +import {belongsTo, Entity, model, property} from '../../..'; +import {Customer, CustomerWithRelations} from './customer.model'; @model() export class Address extends Entity { @@ -29,3 +29,9 @@ export class Address extends Entity { @belongsTo(() => Customer) customerId: number; } + +export interface AddressRelations { + customer?: CustomerWithRelations; +} + +export type AddressWithRelations = Address & AddressRelations; diff --git a/packages/repository/src/__tests__/fixtures/models/customer.model.ts b/packages/repository/src/__tests__/fixtures/models/customer.model.ts index 52461c032ade..ae7bf09d12a2 100644 --- a/packages/repository/src/__tests__/fixtures/models/customer.model.ts +++ b/packages/repository/src/__tests__/fixtures/models/customer.model.ts @@ -3,9 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Entity, hasMany, model, property, hasOne, belongsTo} from '../../..'; +import {belongsTo, Entity, hasMany, hasOne, model, property} from '../../..'; +import {Address, AddressWithRelations} from './address.model'; import {Order} from './order.model'; -import {Address} from './address.model'; @model() export class Customer extends Entity { @@ -32,3 +32,11 @@ export class Customer extends Entity { @belongsTo(() => Customer) parentId?: number; } + +export interface CustomerRelations { + address?: AddressWithRelations; + customers?: CustomerWithRelations[]; + parentCustomer?: CustomerWithRelations; +} + +export type CustomerWithRelations = Customer & CustomerRelations; diff --git a/packages/repository/src/__tests__/fixtures/models/order.model.ts b/packages/repository/src/__tests__/fixtures/models/order.model.ts index 206943a384f1..3e6ab1504e8b 100644 --- a/packages/repository/src/__tests__/fixtures/models/order.model.ts +++ b/packages/repository/src/__tests__/fixtures/models/order.model.ts @@ -4,7 +4,7 @@ // License text available at https://opensource.org/licenses/MIT import {belongsTo, Entity, model, property} from '../../..'; -import {Customer} from './customer.model'; +import {Customer, CustomerWithRelations} from './customer.model'; @model() export class Order extends Entity { @@ -29,3 +29,9 @@ export class Order extends Entity { @belongsTo(() => Customer) customerId: number; } + +export interface OrderRelations { + customer?: CustomerWithRelations; +} + +export type OrderWithRelations = Order & OrderRelations; diff --git a/packages/repository/src/__tests__/fixtures/models/product.model.ts b/packages/repository/src/__tests__/fixtures/models/product.model.ts index c6c534658f8e..7612e577e8e1 100644 --- a/packages/repository/src/__tests__/fixtures/models/product.model.ts +++ b/packages/repository/src/__tests__/fixtures/models/product.model.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {model, property, Entity} from '../../..'; +import {Entity, model, property} from '../../..'; @model() export class Product extends Entity { @@ -24,3 +24,9 @@ export class Product extends Entity { super(data); } } + +export interface ProductRelations { + // describe navigational properties here +} + +export type ProductWithRelations = Product & ProductRelations; diff --git a/packages/repository/src/__tests__/fixtures/repositories/address.repository.ts b/packages/repository/src/__tests__/fixtures/repositories/address.repository.ts index 5e97fb9bd08b..3be973cef13c 100644 --- a/packages/repository/src/__tests__/fixtures/repositories/address.repository.ts +++ b/packages/repository/src/__tests__/fixtures/repositories/address.repository.ts @@ -10,12 +10,13 @@ import { juggler, repository, } from '../../..'; -import {Customer, Address} from '../models'; +import {Address, AddressRelations, Customer} from '../models'; import {CustomerRepository} from '../repositories'; export class AddressRepository extends DefaultCrudRepository< Address, - typeof Address.prototype.zipcode + typeof Address.prototype.zipcode, + AddressRelations > { public readonly customer: BelongsToAccessor< Customer, diff --git a/packages/repository/src/__tests__/fixtures/repositories/customer.repository.ts b/packages/repository/src/__tests__/fixtures/repositories/customer.repository.ts index fd1be3fee75c..9a2dcb302c98 100644 --- a/packages/repository/src/__tests__/fixtures/repositories/customer.repository.ts +++ b/packages/repository/src/__tests__/fixtures/repositories/customer.repository.ts @@ -5,20 +5,21 @@ import {Getter, inject} from '@loopback/context'; import { + BelongsToAccessor, DefaultCrudRepository, HasManyRepositoryFactory, juggler, repository, - BelongsToAccessor, } from '../../..'; -import {Customer, Order, Address} from '../models'; -import {OrderRepository} from './order.repository'; import {HasOneRepositoryFactory} from '../../../'; +import {Address, Customer, CustomerRelations, Order} from '../models'; import {AddressRepository} from './address.repository'; +import {OrderRepository} from './order.repository'; export class CustomerRepository extends DefaultCrudRepository< Customer, - typeof Customer.prototype.id + typeof Customer.prototype.id, + CustomerRelations > { public readonly orders: HasManyRepositoryFactory< Order, diff --git a/packages/repository/src/__tests__/fixtures/repositories/order.repository.ts b/packages/repository/src/__tests__/fixtures/repositories/order.repository.ts index 7358eaa9bf9d..2d83b256458e 100644 --- a/packages/repository/src/__tests__/fixtures/repositories/order.repository.ts +++ b/packages/repository/src/__tests__/fixtures/repositories/order.repository.ts @@ -10,12 +10,13 @@ import { juggler, repository, } from '../../..'; -import {Customer, Order} from '../models'; +import {Customer, Order, OrderRelations} from '../models'; import {CustomerRepository} from '../repositories'; export class OrderRepository extends DefaultCrudRepository< Order, - typeof Order.prototype.id + typeof Order.prototype.id, + OrderRelations > { public readonly customer: BelongsToAccessor< Customer, diff --git a/packages/repository/src/__tests__/fixtures/repositories/product.repository.ts b/packages/repository/src/__tests__/fixtures/repositories/product.repository.ts index 0b8b51d1bc30..2ab4b0408e7d 100644 --- a/packages/repository/src/__tests__/fixtures/repositories/product.repository.ts +++ b/packages/repository/src/__tests__/fixtures/repositories/product.repository.ts @@ -4,12 +4,13 @@ // License text available at https://opensource.org/licenses/MIT import {DefaultCrudRepository, juggler} from '../../..'; -import {Product} from '../models/product.model'; +import {Product, ProductRelations} from '../models/product.model'; export {Product}; export class ProductRepository extends DefaultCrudRepository< Product, - typeof Product.prototype.id + typeof Product.prototype.id, + ProductRelations > { constructor(dataSource: juggler.DataSource) { super(Product, dataSource); diff --git a/packages/repository/src/repositories/legacy-juggler-bridge.ts b/packages/repository/src/repositories/legacy-juggler-bridge.ts index ee0404dd9302..e1869e715979 100644 --- a/packages/repository/src/repositories/legacy-juggler-bridge.ts +++ b/packages/repository/src/repositories/legacy-juggler-bridge.ts @@ -88,8 +88,11 @@ export function ensurePromise(p: legacy.PromiseOrVoid): Promise { * Default implementation of CRUD repository using legacy juggler model * and data source */ -export class DefaultCrudRepository - implements EntityCrudRepository { +export class DefaultCrudRepository< + T extends Entity, + ID, + Relations extends object = {} +> implements EntityCrudRepository { modelClass: juggler.PersistedModelClass; /** @@ -207,7 +210,8 @@ export class DefaultCrudRepository * ```ts * class CustomerRepository extends DefaultCrudRepository< * Customer, - * typeof Customer.prototype.id + * typeof Customer.prototype.id, + * CustomerRelations * > { * public readonly orders: HasManyRepositoryFactory; * @@ -340,7 +344,10 @@ export class DefaultCrudRepository } } - async find(filter?: Filter, options?: Options): Promise { + async find( + filter?: Filter, + options?: Options, + ): Promise<(T & Relations)[]> { const models = await ensurePromise( this.modelClass.find(filter as legacy.Filter, options), ); @@ -355,14 +362,18 @@ export class DefaultCrudRepository return this.toEntity(model); } - async findById(id: ID, filter?: Filter, options?: Options): Promise { + async findById( + id: ID, + filter?: Filter, + options?: Options, + ): Promise { const model = await ensurePromise( this.modelClass.findById(id, filter as legacy.Filter, options), ); if (!model) { throw new EntityNotFoundError(this.entityClass, id); } - return this.toEntity(model); + return this.toEntity(model); } update(entity: T, options?: Options): Promise { @@ -445,11 +456,11 @@ export class DefaultCrudRepository return ensurePromise(this.dataSource.execute(command, parameters, options)); } - protected toEntity(model: juggler.PersistedModel): T { - return new this.entityClass(model.toObject()) as T; + protected toEntity(model: juggler.PersistedModel): R { + return new this.entityClass(model.toObject()) as R; } - protected toEntities(models: juggler.PersistedModel[]): T[] { - return models.map(m => this.toEntity(m)); + protected toEntities(models: juggler.PersistedModel[]): R[] { + return models.map(m => this.toEntity(m)); } } diff --git a/packages/repository/src/repositories/repository.ts b/packages/repository/src/repositories/repository.ts index b1b3dfba76a2..1e173ed9a35b 100644 --- a/packages/repository/src/repositories/repository.ts +++ b/packages/repository/src/repositories/repository.ts @@ -40,8 +40,10 @@ export interface ExecutableRepository extends Repository { /** * Basic CRUD operations for ValueObject and Entity. No ID is required. */ -export interface CrudRepository - extends Repository { +export interface CrudRepository< + T extends ValueObject | Entity, + Relations extends object = {} +> extends Repository { /** * Create a new record * @param dataObject - The data to be created @@ -64,7 +66,7 @@ export interface CrudRepository * @param options - Options for the operations * @returns A promise of an array of records found */ - find(filter?: Filter, options?: Options): Promise; + find(filter?: Filter, options?: Options): Promise<(T & Relations)[]>; /** * Updating matching records with attributes from the data object @@ -105,9 +107,11 @@ export interface EntityRepository /** * CRUD operations for a repository of entities */ -export interface EntityCrudRepository - extends EntityRepository, - CrudRepository { +export interface EntityCrudRepository< + T extends Entity, + ID, + Relations extends object = {} +> extends EntityRepository, CrudRepository { // entityClass should have type "typeof T", but that's not supported by TSC entityClass: typeof Entity & {prototype: T}; @@ -146,7 +150,11 @@ export interface EntityCrudRepository * @param options - Options for the operations * @returns A promise of an entity found for the id */ - findById(id: ID, filter?: Filter, options?: Options): Promise; + findById( + id: ID, + filter?: Filter, + options?: Options, + ): Promise; /** * Update an entity by id with property/value pairs in the data object