From e3e97d9da48e43338a6739f4aa8ce408e18f243e Mon Sep 17 00:00:00 2001 From: shimks Date: Mon, 12 Mar 2018 13:15:55 -0400 Subject: [PATCH] fix(repository): fix broken code in readme --- packages/repository/README.md | 416 +++++++++++----------------------- 1 file changed, 129 insertions(+), 287 deletions(-) diff --git a/packages/repository/README.md b/packages/repository/README.md index 2f769969f6a3..a6e8a3d514b2 100644 --- a/packages/repository/README.md +++ b/packages/repository/README.md @@ -9,9 +9,8 @@ refactored and decomposed into multiple modules as we refine the story based on the legacy `loopback-datasource-juggler` and connector modules from LoopBack 3.x. -This module provides data access facilities to various databases and services. -It contains the constructs for modeling and accessing data. Repositories can be -used alone or as part of a `Controller` implementation. +This module provides data access facilities to various databases and services as +well as the constructs for modeling and accessing those data. ## Installation @@ -21,47 +20,137 @@ $ npm install --save @loopback/repository ## Basic use +At the moment, we only have implementations of `Repository` based on LoopBack +3.x `loopback-datasource-juggler` and connectors. The following steps illustrate +how to define repositories and use them with controllers. + +### Defining a legacy datasource and a model + The repository module provides APIs to define LoopBack 3.x data sources and models. For example, ```ts -import { - DataSourceConstructor, - juggler, - Entity, - model, - ModelDefinition -} from '@loopback/repository'; +// src/datasources/db.datasource.ts +import {juggler, DataSourceConstructor} from '@loopback/repository'; -export const ds: juggler.DataSource = new DataSourceConstructor({ +export const db: juggler.DataSource = new DataSourceConstructor({ name: 'db', connector: 'memory', }); +``` + +```ts +// src/models/note.model.ts +import {model, Entity, property} from '@loopback/repository'; @model() export class Note extends Entity { - static definition = new ModelDefinition({ - name: 'note', - properties: { - id: {name: 'id', type: 'number', id: true}, - title: 'string', - content: 'string' - } - }) -}; + @property({id: true}) + id: string; + @property() title: string; + @property() content: string; +} +``` + +**NOTE**: There is no declarative support for data source and model yet in +LoopBack 4. These constructs need to be created programmatically as +illustrated above. + +### Defining a repository + +A repository can be created by extending `DefaultCrudRepository` and using +dependency injection to resolve the datasource. + +```ts +// src/repositories/note.repository.ts +import {DefaultCrudRepository, DataSourceType} from '@loopback/repository'; +import {Note} from '../models'; +import {inject} from '@loopback/core'; + +export class NoteRepository extends DefaultCrudRepository< + Note, + typeof Note.prototype.id +> { + constructor(@inject('datasources.db') protected dataSource: DataSourceType) { + super(Note, dataSource); + } +} ``` -A repository can be created directly using `DefaultCrudRepository`. + +### Defining a controller + +Controllers serve as handlers for API requests. We declare controllers as +classes with optional dependency injection by decorating constructor parameters +or properties. ```ts -import {DefaultCrudRepository} from '@loopback/repository'; -// also import Note and ds from wherever you defined them +// src/controllers/note.controller.ts +import {repository} from '@loopback/repository'; +import {NoteRepository} from '../repositories'; +import {Note} from '../models'; +import {post, requestBody, get, param} from '@loopback/openapi-v3'; + +export class NoteController { + constructor( + // Use constructor dependency injection to set up the repository + @repository(NoteRepository.name) public noteRepo: NoteRepository, + ) {} + + // Create a new note + @post('/note') + create(@requestBody() data: Note) { + return this.noteRepo.create(data); + } + + // Find notes by title + @get('/note/{title}') + findByTitle(@param.path.string('title') title: string) { + return this.noteRepo.find({where: {title}}); + } +} +``` + +### Run the controller and repository together + +#### Using the Repository Mixin for Application + +A Repository Mixin is available for Application that provides convenience +methods for binding and instantiating a repository class. Bound instances can be +used anywhere in your application using Dependency Injection. +The `.repository(RepositoryClass)` function can be used to bind a repository +class to an Application. The mixin will also instantiate any repositories +declared by a component in its constructor using the `repositories` key. + +Repositories will be bound to the key `repositories.RepositoryClass` where +`RepositoryClass` is the name of the Repository class being bound. - const repo = new DefaultCrudRepository(Note, ds); +We'll use `BootMixin` on top of `RepositoryMixin` so that Repository bindings +can be taken care of automatically at boot time before the application starts. - // Bind the repository instance to the 'ctx' Context. - ctx.bind('repositories.noteRepo').to(repo); +```ts +import {ApplicationConfig} from '@loopback/core'; +import {RestApplication} from '@loopback/rest'; +import {db} from './datasources/db.datasource'; +/* tslint:disable:no-unused-variable */ +import {BootMixin, Booter, Binding} from '@loopback/boot'; +import { + RepositoryMixin, + Class, + Repository, + juggler, +} from '@loopback/repository'; +/* tslint:enable:no-unused-variable */ + +export class RepoApplication extends BootMixin( + RepositoryMixin(RestApplication), +) { + constructor(options?: ApplicationConfig) { + super(options); + this.projectRoot = __dirname; + this.dataSource(db); + } +} ``` -Fore more detailed info about the repository usage and implementation with a controller, please refer to [Use Repository](#use-repository) ## Concepts @@ -73,7 +162,7 @@ against the underlying database or service. `Repository` can be defined and implemented by application developers. LoopBack ships a few predefined `Repository` interfaces for typical CRUD and KV -operations. Such `Repository` implements leverage `Model` definition and +operations. These `Repository` implementations leverage `Model` definition and `DataSource` configuration to fulfill the logic for data access. ```js @@ -143,8 +232,9 @@ There are two subtly different types of models for domain objects: `DataSource` is a named configuration of a connector. The configuration properties vary by connectors. For example, a datasource for `MySQL` needs to -set the `connector` property to `loopback-connector-mysql` with settings such -as: +set the `connector` property to `loopback-connector-mysql` with settings as +follows: + ```json { "host": "localhost", @@ -180,21 +270,16 @@ For example, the mixins belows add methods and properties to a base class to create a new one. ```ts +import {Class} from '@loopback/repository'; + // Mixin as a function -function timestampMixin(Base) { +function timestampMixin>(Base: T) { return class extends Base { created: Date = new Date(); modified: Date = new Date(); - } + }; } -// Mixin as an arrow function -const fullNameMixin = Base => class extends Base { - fullName() { - return `${this.firstName} ${this.lastName}`; - } -}; - // The base class class Customer { id: string; @@ -204,13 +289,11 @@ class Customer { // Mix in timestamp const CustomerWithTS = timestampMixin(Customer); -// Mix in full name -const CustomerWithTSAndFullName = fullNameMixin(CustomerWithTS); ``` ### Type -To support property and parameter typing, LoopBack Next introduces an extensible +To support property and parameter typing, LoopBack 4 introduces an extensible typing system to capture the metadata and perform corresponding checks and coercion. The following types are supported out of box. @@ -222,263 +305,22 @@ coercion. The following types are supported out of box. - AnyType - ArrayType - UnionType +- ObjectType -## Use Repository -The `Repository` and other interfaces extended from `Repository` provide access -to backend databases and services. Repositories can be used alone or as part -of `Controller` implementation. - -At the moment, we only have implementations of `Repository` based on LoopBack -3.x `loopback-datasource-juggler` and connectors. The following steps illustrate -how to define repositories and use them with controllers. - -### Define legacy data sources and models - -The repository module provides APIs to define LoopBack 3.x data sources and -models. For example, - -```ts -import { - DataSourceConstructor, - juggler, - Entity, - model, - ModelDefinition -} from '@loopback/repository'; - -export const ds: juggler.DataSource = new DataSourceConstructor({ - name: 'db', - connector: 'memory', -}); - -@model() -export class Note extends Entity { - static definition = new ModelDefinition({ - name: 'note', - properties: { - id: {name: 'id', type: 'number', id: true}, - title: 'string', - content: 'string' - } - }) -}; -``` - -**NOTE**: There is no declarative support for data source and model yet in -LoopBack Next. These constructs need to be created programmatically as -illustrated above. - -### Define a repository - -A repository can be created directly using `DefaultCrudRepository`. - -```ts -import {DefaultCrudRepository} from '@loopback/repository'; -// also import Note and ds from wherever you defined them - - const repo = new DefaultCrudRepository(Note, ds); - - // Bind the repository instance to the 'ctx' Context. - ctx.bind('repositories.noteRepo').to(repo); -``` - -Alternatively, we can define a new Repository subclass and use dependency -injection to resolve the data source and model. - -```ts -import {DataSourceType} from '@loopback/repository'; - -class MyNoteRepository extends DefaultCrudRepository { - constructor( - @inject('models.Note') myModel: typeof Note, - @inject('dataSources.memory') dataSource: DataSourceType) { - super(myModel, dataSource); - } -} -``` - -### Define a controller - -Controllers serve as handlers for API requests. We declare controllers as -classes with optional dependency injection by decorating constructor parameters -or properties. - -```ts -import {Context, inject} from '@loopback/context'; - -import { - repository, - Entity, - Options, - DataObject, - EntityCrudRepository, -} from '@loopback/repository'; - -// The Controller for Note -class NoteController { - constructor( - // Use constructor dependency injection to set up the repository - @repository('noteRepo') - public noteRepo: EntityCrudRepository, - ) {} - - // Create a new note - create(data: DataObject, options?: Options) { - return this.noteRepo.create(data, options); - } - - // Find notes by title - findByTitle(title: string, options?: Options) { - return this.noteRepo.find({where: {title}}, options); - } -} -``` - -Alternatively, the controller can be declared using property injection: - -```ts -class NoteController { - @repository('noteRepo') - public noteRepo: EntityCrudRepository; -} -``` - -### Run the controller and repository together - -#### Bind the repository to context - -```ts -// Create a context -const ctx = new Context(); - -// Mock up a predefined repository -const repo = new DefaultCrudRepository(Note, ds); - -// Bind the repository instance -ctx.bind('repositories.noteRepo').to(repo); -``` - -```ts -// Create a context -const ctx = new Context(); - -// Bind model `Note` -ctx.bind('models.Note').to(Note); - -// Bind the in-memory DB dataSource -ctx.bind('dataSources.memory').to(ds); - -// Bind the repository class -ctx.bind('repositories.noteRepo').toClass(MyNoteRepository); -``` - -#### Using the Repository Mixin for Application -A Repository Mixin is available for Application that provides convenience methods for binding and instantiating a repository class. Bound instances can be used anywhere in your application using Dependency Injection. The `.repository(RepositoryClass)` function can be used to bind a repository class to an Application. The mixin will also instantiate any repositories declared by a component in its constructor using the `repositories` key. - -Repositories will be bound to the key `repositories.RepositoryClass` where `RepositoryClass` is the name of the Repository class being bound. -```ts -import { Application } from '@loopback/core'; -import { RepositoryMixin } from '@loopback/repository'; -import { ProductRepository, CategoryRepository } from './repository'; - -// Using the Mixin -class MyApplication extends RepositoryMixin(Application) {} - -// ProductRepository will be bound to key `repositories.ProductRepository` -const app = new MyApplication({repositories: [ProductRepository]}); -// CategoryRepository will be bound to key `repositories.CategoryRepository` -app.repository(CategoryRepository); -``` - -### Compose repositories and controllers in a context - -```ts -async function main() { - // Create a context - const ctx = new Context(); - - // Mock up a predefined repository - const repo = new DefaultCrudRepository(Note, ds); - - // Bind the repository instance - ctx.bind('repositories.noteRepo').to(repo); - - // Bind the controller class - ctx.bind('controllers.MyController').toClass(NoteController); - - // Resolve the controller - const controller: NoteController = await ctx.get('controllers.MyController'); - - // Create some notes - await controller.create({title: 't1', content: 'Note 1'}); - await controller.create({title: 't2', content: 'Note 2'}); - - // Find notes by title - const notes = await controller.findByTitle('t1'); - return notes; -} - -// Invoke the example -main().then(notes => { - // It should print `Notes [ { title: 't1', content: 'Note 1', id: 1 } ]` - console.log('Notes', notes); -}); -``` - -### Mix in a repository into the controller (To be implemented) - -This style allows repository methods to be mixed into the controller class -to mimic LoopBack 3.x style model classes with remote CRUD methods. It blends -the repository responsibility/capability into the controller. - -```ts -import {EntityCrudRepository} from '../../src/repository'; -import {Customer} from '../models/customer'; -import {repository} from "../../src/decorator"; - -/** - * Use class level @repository decorator to mixin repository methods into the - * controller class. Think about @repository as a shortcut to @mixin(...) - */ -// Style 1 -// Create a repository that binds Customer to mongodbDataSource -@repository(Customer, 'mongodbDataSource') -// Style 2 -// Reference a pre-configured repository by name. This is close to LoopBack -// 3.x model-config.json -// @repository('myCustomerRepository') -export class CustomerController { - // find() will be mixed in -} -``` - -## Declare pre-defined repositories in JSON/YAML (To be implemented) - -Repositories can be declared in JSON/YAML files as follows: - -server/repositories.json -```json -{ - "customerRepo": { - "dataSource": "mysql", - "model": "Customer", - "settings": {} - } -} -``` ## Related resources + - https://martinfowler.com/eaaCatalog/repository.html - https://msdn.microsoft.com/en-us/library/ff649690.aspx - http://docs.spring.io/spring-data/data-commons/docs/2.0.0.M3/reference/html/#repositories ## Contributions -- [Guidelines](https://github.com/strongloop/loopback-next/wiki/Contributing##guidelines) +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) - [Join the team](https://github.com/strongloop/loopback-next/issues/110) ## Tests -run 'npm test' from the root folder. +run `npm test` from the root folder. ## Contributors