diff --git a/CODEOWNERS b/CODEOWNERS index 144aa7383c1a..f88bfe4eddda 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -246,6 +246,7 @@ # - Primary owner(s): @jannyHou # - Standby owner(s): n/a /packages/boot @jannyHou +/packages/booter @raymondfeng @jannyHou # # Migration from LB3 diff --git a/docs/site/MONOREPO.md b/docs/site/MONOREPO.md index 81dcb9946a33..6ff2c4ddb336 100644 --- a/docs/site/MONOREPO.md +++ b/docs/site/MONOREPO.md @@ -56,6 +56,7 @@ one in the monorepo: `npm run update-monorepo-file` | [packages/authentication](https://github.com/strongloop/loopback-next/tree/master/packages/authentication) | @loopback/authentication | A LoopBack component for authentication support. | | [packages/authorization](https://github.com/strongloop/loopback-next/tree/master/packages/authorization) | @loopback/authorization | A LoopBack component for authorization support. | | [packages/boot](https://github.com/strongloop/loopback-next/tree/master/packages/boot) | @loopback/boot | A collection of Booters for LoopBack 4 Applications | +| [packages/booter](https://github.com/strongloop/loopback-next/tree/master/packages/booter) | @loopback/booter | @loopback/booter | | [packages/booter-lb3app](https://github.com/strongloop/loopback-next/tree/master/packages/booter-lb3app) | @loopback/booter-lb3app | A booter component for LoopBack 3 applications to expose their REST API via LoopBack 4 | | [packages/build](https://github.com/strongloop/loopback-next/tree/master/packages/build) | @loopback/build | A set of common scripts and default configurations to build LoopBack 4 or other TypeScript modules | | [packages/cli](https://github.com/strongloop/loopback-next/tree/master/packages/cli) | @loopback/cli | Yeoman generator for LoopBack 4 | diff --git a/extensions/graphql/package.json b/extensions/graphql/package.json index 69226000716a..b7cdd85a5206 100644 --- a/extensions/graphql/package.json +++ b/extensions/graphql/package.json @@ -29,10 +29,11 @@ "type-graphql": "^1.1.0" }, "peerDependencies": { - "@loopback/boot": "^3.0.2", + "@loopback/booter": "^1.0.0", "@loopback/core": "^2.11.0" }, "devDependencies": { + "@loopback/booter": "^1.0.0", "@loopback/boot": "^3.0.2", "@loopback/build": "^6.2.5", "@loopback/core": "^2.11.0", diff --git a/extensions/graphql/src/booters/resolver.booter.ts b/extensions/graphql/src/booters/resolver.booter.ts index 902a92ee0933..cda644abb423 100644 --- a/extensions/graphql/src/booters/resolver.booter.ts +++ b/extensions/graphql/src/booters/resolver.booter.ts @@ -6,9 +6,9 @@ import { ArtifactOptions, BaseArtifactBooter, - BootBindings, booter, -} from '@loopback/boot'; + BooterBindings, +} from '@loopback/booter'; import { Application, config, @@ -41,7 +41,7 @@ export class GraphQLResolverBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: Application, - @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(BooterBindings.PROJECT_ROOT) projectRoot: string, @config() public interceptorConfig: ArtifactOptions = {}, ) { diff --git a/extensions/graphql/tsconfig.json b/extensions/graphql/tsconfig.json index 93415ac36cdc..b2bc9a6aa548 100644 --- a/extensions/graphql/tsconfig.json +++ b/extensions/graphql/tsconfig.json @@ -13,6 +13,9 @@ { "path": "../../packages/boot/tsconfig.json" }, + { + "path": "../../packages/booter/tsconfig.json" + }, { "path": "../../packages/core/tsconfig.json" }, diff --git a/extensions/typeorm/package.json b/extensions/typeorm/package.json index fe50ec56c0c5..fa9576d59b5a 100644 --- a/extensions/typeorm/package.json +++ b/extensions/typeorm/package.json @@ -22,7 +22,7 @@ "access": "public" }, "peerDependencies": { - "@loopback/boot": "^3.0.2", + "@loopback/booter": "^1.0.0", "@loopback/core": "^2.11.0", "@loopback/rest": "^8.0.0" }, @@ -31,6 +31,7 @@ "typeorm": "^0.2.28" }, "devDependencies": { + "@loopback/booter": "^1.0.0", "@loopback/boot": "^3.0.2", "@loopback/build": "^6.2.5", "@loopback/core": "^2.11.0", diff --git a/extensions/typeorm/src/typeorm-connection.booter.ts b/extensions/typeorm/src/typeorm-connection.booter.ts index cb6f8d270e89..c21c4acf4fb6 100644 --- a/extensions/typeorm/src/typeorm-connection.booter.ts +++ b/extensions/typeorm/src/typeorm-connection.booter.ts @@ -6,9 +6,9 @@ import { ArtifactOptions, BaseArtifactBooter, - BootBindings, booter, -} from '@loopback/boot'; + BooterBindings, +} from '@loopback/booter'; import {config, CoreBindings, inject} from '@loopback/core'; import debugFactory from 'debug'; import {ApplicationUsingTypeOrm, ConnectionOptions} from './'; @@ -28,7 +28,7 @@ export class TypeOrmConnectionBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationUsingTypeOrm, - @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(BooterBindings.PROJECT_ROOT) projectRoot: string, @config() public entityConfig: ArtifactOptions = {}, ) { diff --git a/extensions/typeorm/tsconfig.json b/extensions/typeorm/tsconfig.json index 81add39467bb..a101f6ab5a61 100644 --- a/extensions/typeorm/tsconfig.json +++ b/extensions/typeorm/tsconfig.json @@ -13,6 +13,9 @@ { "path": "../../packages/boot/tsconfig.json" }, + { + "path": "../../packages/booter/tsconfig.json" + }, { "path": "../../packages/core/tsconfig.json" }, diff --git a/packages/boot/package.json b/packages/boot/package.json index 0ad69f86d54b..733d52a8f148 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -27,11 +27,7 @@ "@loopback/core": "^2.11.0" }, "dependencies": { - "@loopback/model-api-builder": "^2.1.16", - "@loopback/repository": "^3.1.0", - "@loopback/service-proxy": "^3.0.2", - "@types/debug": "^4.1.5", - "@types/glob": "^7.1.3", + "@loopback/booter": "^1.0.0", "debug": "^4.2.0", "glob": "^7.1.6", "tslib": "^2.0.3" @@ -40,9 +36,9 @@ "@loopback/build": "^6.2.5", "@loopback/core": "^2.11.0", "@loopback/eslint-config": "^10.0.1", - "@loopback/rest": "^8.0.0", - "@loopback/rest-crud": "^0.8.16", "@loopback/testlab": "^3.2.7", + "@types/debug": "^4.1.5", + "@types/glob": "^7.1.3", "@types/node": "^10.17.35" }, "files": [ diff --git a/packages/boot/src/__tests__/acceptance/controller.booter.acceptance.ts b/packages/boot/src/__tests__/acceptance/controller.booter.acceptance.ts index dc8d2258164d..372ac2b64201 100644 --- a/packages/boot/src/__tests__/acceptance/controller.booter.acceptance.ts +++ b/packages/boot/src/__tests__/acceptance/controller.booter.acceptance.ts @@ -3,11 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { - createRestAppClient, - givenHttpServerConfig, - TestSandbox, -} from '@loopback/testlab'; +import {expect, givenHttpServerConfig, TestSandbox} from '@loopback/testlab'; import {resolve} from 'path'; import {BooterApp} from '../fixtures/application'; @@ -18,17 +14,10 @@ describe('controller booter acceptance tests', () => { beforeEach('reset sandbox', () => sandbox.reset()); beforeEach(getApp); - afterEach(stopApp); - - it('binds controllers using ControllerDefaults and REST endpoints work', async () => { + it('binds controllers using ControllerDefaults', async () => { await app.boot(); - await app.start(); - - const client = createRestAppClient(app); - - // Default Controllers = /controllers with .controller.js ending (nested = true); - await client.get('/one').expect(200, 'ControllerOne.one()'); - await client.get('/two').expect(200, 'ControllerTwo.two()'); + const bindings = app.find('controllers.*'); + expect(bindings.length).to.eql(2); }); async function getApp() { @@ -44,8 +33,4 @@ describe('controller booter acceptance tests', () => { rest: givenHttpServerConfig(), }); } - - async function stopApp() { - await app?.stop(); - } }); diff --git a/packages/boot/src/__tests__/fixtures/application.ts b/packages/boot/src/__tests__/fixtures/application.ts index 7ad7acc0e148..dd0eae529120 100644 --- a/packages/boot/src/__tests__/fixtures/application.ts +++ b/packages/boot/src/__tests__/fixtures/application.ts @@ -3,19 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {ApplicationConfig} from '@loopback/core'; -import {RepositoryMixin} from '@loopback/repository'; -import {RestApplication} from '@loopback/rest'; -import {ServiceMixin} from '@loopback/service-proxy'; +import {Application, ApplicationConfig} from '@loopback/core'; import {BootMixin} from '../..'; -// Force package.json to be copied to `dist` by `tsc` -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import * as pkg from './package.json'; - -export class BooterApp extends BootMixin( - ServiceMixin(RepositoryMixin(RestApplication)), -) { +export class BooterApp extends BootMixin(Application) { constructor(options?: ApplicationConfig) { super(options); this.projectRoot = __dirname; diff --git a/packages/boot/src/__tests__/fixtures/bindable-classes.artifact.ts b/packages/boot/src/__tests__/fixtures/bindable-classes.artifact.ts index a9c5b922f942..da61d1415aeb 100644 --- a/packages/boot/src/__tests__/fixtures/bindable-classes.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/bindable-classes.artifact.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/__tests__/fixtures/interceptor.artifact.ts b/packages/boot/src/__tests__/fixtures/interceptor.artifact.ts index 4d871b1644ce..9c01657ed514 100644 --- a/packages/boot/src/__tests__/fixtures/interceptor.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/interceptor.artifact.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/__tests__/fixtures/lifecycle-observer.artifact.ts b/packages/boot/src/__tests__/fixtures/lifecycle-observer.artifact.ts index 0927cd216958..45a93099977f 100644 --- a/packages/boot/src/__tests__/fixtures/lifecycle-observer.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/lifecycle-observer.artifact.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2018. All Rights Reserved. +// Copyright IBM Corp. 2018,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/__tests__/fixtures/multiple.artifact.ts b/packages/boot/src/__tests__/fixtures/multiple.artifact.ts index a3fcab4834b2..00cc0d71e893 100644 --- a/packages/boot/src/__tests__/fixtures/multiple.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/multiple.artifact.ts @@ -3,17 +3,13 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {get} from '@loopback/rest'; - export class ArtifactOne { - @get('/one') one() { return 'ControllerOne.one()'; } } export class ArtifactTwo { - @get('/two') two() { return 'ControllerTwo.two()'; } diff --git a/packages/boot/src/__tests__/fixtures/non-global-interceptor.artifact.ts b/packages/boot/src/__tests__/fixtures/non-global-interceptor.artifact.ts index 0300cf35d859..ec8990e2618d 100644 --- a/packages/boot/src/__tests__/fixtures/non-global-interceptor.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/non-global-interceptor.artifact.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/__tests__/fixtures/service-class.artifact.ts b/packages/boot/src/__tests__/fixtures/service-class.artifact.ts index c62abb898c29..9aea5cdccdb7 100644 --- a/packages/boot/src/__tests__/fixtures/service-class.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/service-class.artifact.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/__tests__/fixtures/service-provider.artifact.ts b/packages/boot/src/__tests__/fixtures/service-provider.artifact.ts index 151ab88a957d..5bd227efd34e 100644 --- a/packages/boot/src/__tests__/fixtures/service-provider.artifact.ts +++ b/packages/boot/src/__tests__/fixtures/service-provider.artifact.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/__tests__/unit/boot.component.unit.ts b/packages/boot/src/__tests__/unit/boot.component.unit.ts index 483475d480b5..f409677cb58e 100644 --- a/packages/boot/src/__tests__/unit/boot.component.unit.ts +++ b/packages/boot/src/__tests__/unit/boot.component.unit.ts @@ -10,8 +10,6 @@ import { BootMixin, Bootstrapper, ControllerBooter, - DataSourceBooter, - RepositoryBooter, ServiceBooter, } from '../../'; @@ -34,20 +32,6 @@ describe('boot.component unit tests', () => { expect(booterInst).to.be.an.instanceOf(ControllerBooter); }); - it('RepositoryBooter is bound as a booter by default', async () => { - const booterInst = await app.get( - `${BootBindings.BOOTERS}.RepositoryBooter`, - ); - expect(booterInst).to.be.an.instanceOf(RepositoryBooter); - }); - - it('DataSourceBooter is bound as a booter by default', async () => { - const booterInst = await app.get( - `${BootBindings.BOOTERS}.DataSourceBooter`, - ); - expect(booterInst).to.be.an.instanceOf(DataSourceBooter); - }); - it('ServiceBooter is bound as a booter by default', async () => { const booterInst = await app.get(`${BootBindings.BOOTERS}.ServiceBooter`); expect(booterInst).to.be.an.instanceOf(ServiceBooter); diff --git a/packages/boot/src/__tests__/unit/boot.custom-binding.component.unit.ts b/packages/boot/src/__tests__/unit/boot.custom-binding.component.unit.ts index 9423b52b00e6..1841ea888590 100644 --- a/packages/boot/src/__tests__/unit/boot.custom-binding.component.unit.ts +++ b/packages/boot/src/__tests__/unit/boot.custom-binding.component.unit.ts @@ -1,8 +1,9 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {BaseArtifactBooter} from '@loopback/booter'; import { Application, BindingKey, @@ -13,7 +14,6 @@ import { } from '@loopback/core'; import {expect} from '@loopback/testlab'; import {BootBindings, BootMixin} from '../../'; -import {BaseArtifactBooter} from '../../booters'; import {InstanceWithBooters} from '../../types'; describe('boot.component unit tests', () => { diff --git a/packages/boot/src/__tests__/unit/booters/service.booter.unit.ts b/packages/boot/src/__tests__/unit/booters/service.booter.unit.ts index 60b2c77055e1..3180cb4c5da4 100644 --- a/packages/boot/src/__tests__/unit/booters/service.booter.unit.ts +++ b/packages/boot/src/__tests__/unit/booters/service.booter.unit.ts @@ -4,7 +4,6 @@ // License text available at https://opensource.org/licenses/MIT import {Application} from '@loopback/core'; -import {ApplicationWithServices, ServiceMixin} from '@loopback/service-proxy'; import {expect, sinon, TestSandbox} from '@loopback/testlab'; import {resolve} from 'path'; import {ServiceBooter, ServiceDefaults} from '../../..'; @@ -15,7 +14,7 @@ describe('service booter unit tests', () => { const SERVICES_PREFIX = 'services'; const SERVICES_TAG = 'service'; - class AppWithRepo extends ServiceMixin(Application) {} + class AppWithRepo extends Application {} let app: AppWithRepo; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -32,10 +31,7 @@ describe('service booter unit tests', () => { resolve(__dirname, '../../fixtures/service-provider.artifact.js'), ); - const booterInst = new ServiceBooter( - normalApp as ApplicationWithServices, - sandbox.path, - ); + const booterInst = new ServiceBooter(normalApp, sandbox.path); booterInst.discovered = [ resolve(sandbox.path, 'service-provider.artifact.js'), diff --git a/packages/boot/src/__tests__/unit/bootstrapper.unit.ts b/packages/boot/src/__tests__/unit/bootstrapper.unit.ts index 9ebb2cb6a2bc..292d28525983 100644 --- a/packages/boot/src/__tests__/unit/bootstrapper.unit.ts +++ b/packages/boot/src/__tests__/unit/bootstrapper.unit.ts @@ -1,17 +1,14 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {Application} from '@loopback/core'; -import {RepositoryMixin} from '@loopback/repository'; import {expect, sinon} from '@loopback/testlab'; import {BootBindings, Booter, BootMixin, Bootstrapper} from '../..'; describe('boot-strapper unit tests', () => { - // RepositoryMixin is added to avoid warning message printed logged to console - // due to the fact that RepositoryBooter is a default Booter loaded via BootMixin. - class BootApp extends BootMixin(RepositoryMixin(Application)) {} + class BootApp extends BootMixin(Application) {} let app: BootApp; let bootstrapper: Bootstrapper; diff --git a/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts b/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts index ce065a8a4d2c..d99ba89653c9 100644 --- a/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts +++ b/packages/boot/src/__tests__/unit/mixins/boot.mixin.unit.ts @@ -1,4 +1,4 @@ -// Copyright IBM Corp. 2019. All Rights Reserved. +// Copyright IBM Corp. 2019,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT diff --git a/packages/boot/src/boot.component.ts b/packages/boot/src/boot.component.ts index 0338e7b8da96..cdee4a259aff 100644 --- a/packages/boot/src/boot.component.ts +++ b/packages/boot/src/boot.component.ts @@ -3,56 +3,47 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {Booter} from '@loopback/booter'; import { Application, - BindingScope, + Binding, Component, + Constructor, CoreBindings, + createBindingFromClass, inject, } from '@loopback/core'; import { ApplicationMetadataBooter, ControllerBooter, - DataSourceBooter, InterceptorProviderBooter, LifeCycleObserverBooter, - ModelApiBooter, - ModelBooter, - RepositoryBooter, ServiceBooter, } from './booters'; import {Bootstrapper} from './bootstrapper'; -import {BootBindings} from './keys'; /** - * BootComponent is used to export the default list of Booter's made + * BootstrapComponent is used to export the default list of Booter's made * available by this module as well as bind the BootStrapper to the app so it * can be used to run the Booters. */ export class BootComponent implements Component { + bindings: Binding[] = [createBindingFromClass(Bootstrapper)]; // Export a list of default booters in the component so they get bound // automatically when this component is mounted. - booters = [ - ApplicationMetadataBooter, - ControllerBooter, - RepositoryBooter, - ServiceBooter, - DataSourceBooter, - LifeCycleObserverBooter, - InterceptorProviderBooter, - ModelApiBooter, - ModelBooter, - ]; + booters: Constructor[]; /** * * @param app - Application instance */ constructor(@inject(CoreBindings.APPLICATION_INSTANCE) app: Application) { - // Bound as a SINGLETON so it can be cached as it has no state - app - .bind(BootBindings.BOOTSTRAPPER_KEY) - .toClass(Bootstrapper) - .inScope(BindingScope.SINGLETON); + this.booters = [ + ApplicationMetadataBooter, + ControllerBooter, + ServiceBooter, + LifeCycleObserverBooter, + InterceptorProviderBooter, + ]; } } diff --git a/packages/boot/src/booters/application-metadata.booter.ts b/packages/boot/src/booters/application-metadata.booter.ts index 45cdfafc0988..4126501d73d0 100644 --- a/packages/boot/src/booters/application-metadata.booter.ts +++ b/packages/boot/src/booters/application-metadata.booter.ts @@ -6,8 +6,8 @@ import {inject, Application, CoreBindings} from '@loopback/core'; import debugModule from 'debug'; import {BootBindings} from '../keys'; -import {Booter} from '../types'; import path = require('path'); +import {Booter} from '@loopback/booter'; const debug = debugModule('loopback:boot:booter:application-metadata'); diff --git a/packages/boot/src/booters/component-application.booter.ts b/packages/boot/src/booters/component-application.booter.ts index 372df359662c..3cbb03460318 100644 --- a/packages/boot/src/booters/component-application.booter.ts +++ b/packages/boot/src/booters/component-application.booter.ts @@ -3,6 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {booter, Booter} from '@loopback/booter'; import { Application, Binding, @@ -14,7 +15,7 @@ import { } from '@loopback/core'; import debugFactory from 'debug'; import {BootBindings} from '../keys'; -import {Bootable, Booter, booter} from '../types'; +import {Bootable} from '../types'; const debug = debugFactory('loopback:boot:booter:component-application'); diff --git a/packages/boot/src/booters/controller.booter.ts b/packages/boot/src/booters/controller.booter.ts index 334782ec1a10..2ae8ddfab998 100644 --- a/packages/boot/src/booters/controller.booter.ts +++ b/packages/boot/src/booters/controller.booter.ts @@ -3,10 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {config, inject, Application, CoreBindings} from '@loopback/core'; +import {ArtifactOptions, BaseArtifactBooter, booter} from '@loopback/booter'; +import {Application, config, CoreBindings, inject} from '@loopback/core'; import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; /** * A class that extends BaseArtifactBooter to boot the 'Controller' artifact type. diff --git a/packages/boot/src/booters/index.ts b/packages/boot/src/booters/index.ts index a3fa2e6350db..8a7682c375e0 100644 --- a/packages/boot/src/booters/index.ts +++ b/packages/boot/src/booters/index.ts @@ -4,14 +4,8 @@ // License text available at https://opensource.org/licenses/MIT export * from './application-metadata.booter'; -export * from './base-artifact.booter'; -export * from './booter-utils'; export * from './component-application.booter'; export * from './controller.booter'; -export * from './datasource.booter'; export * from './interceptor.booter'; export * from './lifecyle-observer.booter'; -export * from './model-api.booter'; -export * from './model.booter'; -export * from './repository.booter'; export * from './service.booter'; diff --git a/packages/boot/src/booters/interceptor.booter.ts b/packages/boot/src/booters/interceptor.booter.ts index 12960eb89ac2..fd3ba7c46457 100644 --- a/packages/boot/src/booters/interceptor.booter.ts +++ b/packages/boot/src/booters/interceptor.booter.ts @@ -3,6 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {ArtifactOptions, BaseArtifactBooter, booter} from '@loopback/booter'; import { Application, config, @@ -14,8 +15,6 @@ import { } from '@loopback/core'; import debugFactory from 'debug'; import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; const debug = debugFactory('loopback:boot:interceptor-booter'); diff --git a/packages/boot/src/booters/lifecyle-observer.booter.ts b/packages/boot/src/booters/lifecyle-observer.booter.ts index 6cdf9b824ea7..9d2854cd1cbe 100644 --- a/packages/boot/src/booters/lifecyle-observer.booter.ts +++ b/packages/boot/src/booters/lifecyle-observer.booter.ts @@ -3,17 +3,18 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {config, Constructor, inject} from '@loopback/core'; +import {ArtifactOptions, BaseArtifactBooter, booter} from '@loopback/booter'; import { Application, + config, + Constructor, CoreBindings, + inject, isLifeCycleObserverClass, LifeCycleObserver, } from '@loopback/core'; import debugFactory from 'debug'; import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; const debug = debugFactory('loopback:boot:lifecycle-observer-booter'); diff --git a/packages/boot/src/booters/service.booter.ts b/packages/boot/src/booters/service.booter.ts index 737c237ebb46..4d9da872cdd2 100644 --- a/packages/boot/src/booters/service.booter.ts +++ b/packages/boot/src/booters/service.booter.ts @@ -3,7 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {ArtifactOptions, BaseArtifactBooter, booter} from '@loopback/booter'; import { + Application, BINDING_METADATA_KEY, config, Constructor, @@ -13,11 +15,8 @@ import { isDynamicValueProviderClass, MetadataInspector, } from '@loopback/core'; -import {ApplicationWithServices} from '@loopback/service-proxy'; import debugFactory from 'debug'; import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; const debug = debugFactory('loopback:boot:service-booter'); @@ -35,7 +34,7 @@ const debug = debugFactory('loopback:boot:service-booter'); export class ServiceBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) - public app: ApplicationWithServices, + public app: Application, @inject(BootBindings.PROJECT_ROOT) projectRoot: string, @config() public serviceConfig: ArtifactOptions = {}, diff --git a/packages/boot/src/bootstrapper.ts b/packages/boot/src/bootstrapper.ts index 3a47d80246ca..3c2ea81f37a0 100644 --- a/packages/boot/src/bootstrapper.ts +++ b/packages/boot/src/bootstrapper.ts @@ -1,25 +1,24 @@ -// Copyright IBM Corp. 2018,2019. All Rights Reserved. +// Copyright IBM Corp. 2018,2020. All Rights Reserved. // Node module: @loopback/boot // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {bindBooter, BOOTER_PHASES} from '@loopback/booter'; import { Application, + BindingScope, + config, Context, + ContextTags, CoreBindings, inject, + injectable, resolveList, } from '@loopback/core'; import debugModule from 'debug'; import {resolve} from 'path'; import {BootBindings, BootTags} from './keys'; -import {bindBooter} from './mixins'; -import { - Bootable, - BOOTER_PHASES, - BootExecutionOptions, - BootOptions, -} from './types'; +import {Bootable, BootExecutionOptions, BootOptions} from './types'; const debug = debugModule('loopback:boot:bootstrapper'); @@ -34,12 +33,16 @@ const debug = debugModule('loopback:boot:bootstrapper'); * @param projectRoot - The root directory of the project, relative to which all other paths are resolved * @param bootOptions - The BootOptions describing the conventions to be used by various Booters */ +@injectable({ + scope: BindingScope.SINGLETON, + tags: {[ContextTags.KEY]: BootBindings.BOOTSTRAPPER_KEY}, +}) export class Bootstrapper { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) private app: Application & Bootable, @inject(BootBindings.PROJECT_ROOT) private projectRoot: string, - @inject(BootBindings.BOOT_OPTIONS, {optional: true}) + @config() private bootOptions: BootOptions = {}, ) { // Resolve path to projectRoot and re-bind @@ -48,7 +51,7 @@ export class Bootstrapper { // This is re-bound for testing reasons where this value may be passed directly // and needs to be propagated to the Booters via DI - app.bind(BootBindings.BOOT_OPTIONS).to(this.bootOptions); + app.configure(BootBindings.BOOTSTRAPPER_KEY).to(this.bootOptions); } /** diff --git a/packages/boot/src/index.ts b/packages/boot/src/index.ts index 81916ebdf90b..62f522c0f02c 100644 --- a/packages/boot/src/index.ts +++ b/packages/boot/src/index.ts @@ -27,6 +27,7 @@ * @packageDocumentation */ +export * from '@loopback/booter'; export * from './boot.component'; export * from './booters'; export * from './bootstrapper'; diff --git a/packages/boot/src/keys.ts b/packages/boot/src/keys.ts index 9214ffff5a19..4655e008d849 100644 --- a/packages/boot/src/keys.ts +++ b/packages/boot/src/keys.ts @@ -3,6 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {BooterBindings, BooterTags} from '@loopback/booter'; import {BindingKey} from '@loopback/core'; import {Bootstrapper} from './bootstrapper'; import {BootOptions} from './types'; @@ -12,33 +13,34 @@ import {BootOptions} from './types'; */ export namespace BootBindings { /** - * Binding key for boot options + * Binding key for binding the BootStrapper class */ - export const BOOT_OPTIONS = BindingKey.create('boot.options'); + export const BOOTSTRAPPER_KEY = BindingKey.create( + BooterBindings.BOOTSTRAPPER_KEY.toString(), + ); /** - * Binding key for determining project root directory + * Binding key for boot options */ - export const PROJECT_ROOT = BindingKey.create('boot.project_root'); - + export const BOOT_OPTIONS = BindingKey.create( + BooterBindings.BOOT_OPTIONS.toString(), + ); /** - * Binding key for binding the BootStrapper class + * Binding key for determining project root directory */ - export const BOOTSTRAPPER_KEY = BindingKey.create( - 'application.bootstrapper', - ); + export const PROJECT_ROOT = BooterBindings.PROJECT_ROOT; /** * Booter binding namespace */ - export const BOOTERS = 'booters'; - export const BOOTER_PREFIX = 'booters'; + export const BOOTERS = BooterBindings.BOOTERS; + export const BOOTER_PREFIX = BooterBindings.BOOTERS; } /** * Namespace for boot related tags */ export namespace BootTags { - export const BOOTER = 'booter'; + export const BOOTER = BooterTags.BOOTER; /** * @deprecated Use `BootTags.BOOTER` instead. */ diff --git a/packages/boot/src/mixins/boot.mixin.ts b/packages/boot/src/mixins/boot.mixin.ts index 130c9f0e5c66..dd8bba86b217 100644 --- a/packages/boot/src/mixins/boot.mixin.ts +++ b/packages/boot/src/mixins/boot.mixin.ts @@ -7,10 +7,8 @@ import { Binding, BindingFilter, BindingFromClassOptions, - BindingScope, Constructor, Context, - createBindingFromClass, Application, Component, CoreBindings, @@ -19,8 +17,9 @@ import { import {BootComponent} from '../boot.component'; import {createComponentApplicationBooterBinding} from '../booters/component-application.booter'; import {Bootstrapper} from '../bootstrapper'; -import {BootBindings, BootTags} from '../keys'; -import {Bootable, Booter, BootOptions, InstanceWithBooters} from '../types'; +import {BootBindings} from '../keys'; +import {Bootable, BootOptions, InstanceWithBooters} from '../types'; +import {Booter, bindBooter} from '@loopback/booter'; // FIXME(rfeng): Workaround for https://github.com/microsoft/rushstack/pull/1867 /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -38,7 +37,7 @@ export {Binding}; * - Add a `projectRoot` property to the Class * - Adds an optional `bootOptions` property to the Class that can be used to * store the Booter conventions. - * - Adds the `BootComponent` to the Class (which binds the Bootstrapper and default Booters) + * - Adds the `BootstrapComponent` to the Class (which binds the Bootstrapper and default Booters) * - Provides the `boot()` convenience method to call Bootstrapper.boot() * - Provides the `booter()` convenience method to bind a Booter(s) to the Application * - Override `component()` to call `mountComponentBooters` @@ -221,35 +220,5 @@ export function BootMixin>(superClass: T) { }; } -/** - * Method which binds a given Booter to a given Context with the Prefix and - * Tags expected by the Bootstrapper - * - * @param ctx - The Context to bind the Booter Class - * @param booterCls - Booter class to be bound - */ -export function bindBooter( - ctx: Context, - booterCls: Constructor, -): Binding { - const binding = createBindingFromClass(booterCls, { - namespace: BootBindings.BOOTERS, - defaultScope: BindingScope.SINGLETON, - }).tag(BootTags.BOOTER); - ctx.add(binding); - /** - * Set up configuration binding as alias to `BootBindings.BOOT_OPTIONS` - * so that the booter can use `@config`. - */ - if (binding.tagMap.artifactNamespace) { - ctx - .configure(binding.key) - .toAlias( - `${BootBindings.BOOT_OPTIONS.key}#${binding.tagMap.artifactNamespace}`, - ); - } - return binding; -} - // eslint-disable-next-line @typescript-eslint/naming-convention export const _bindBooter = bindBooter; // For backward-compatibility diff --git a/packages/boot/src/types.ts b/packages/boot/src/types.ts index 32673c12e3bf..2515e9aa0bc4 100644 --- a/packages/boot/src/types.ts +++ b/packages/boot/src/types.ts @@ -3,70 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import { - Binding, - BindingSpec, - Constructor, - ContextTags, - injectable, -} from '@loopback/core'; -import {BootBindings, BootTags} from './keys'; - -/** - * Type definition for ArtifactOptions. These are the options supported by - * this Booter. - */ -export type ArtifactOptions = { - /** - * Array of directories to check for artifacts. - * Paths must be relative. Defaults to ['controllers'] - */ - dirs?: string | string[]; - /** - * Array of file extensions to match artifact - * files in dirs. Defaults to ['.controller.js'] - */ - extensions?: string | string[]; - /** - * A flag to control if artifact discovery should check nested - * folders or not. Default to true - */ - nested?: boolean; - /** - * A `glob` string to use when searching for files. This takes - * precedence over other options. - */ - glob?: string; -}; - -/** - * Defines the requirements to implement a Booter for LoopBack applications: - * - configure() - * - discover() - * - load() - * - * A Booter will run through the above methods in order. - */ -export interface Booter { - /** - * Configure phase of the Booter. It should set options / defaults in this phase. - */ - configure?(): Promise; - /** - * Discover phase of the Booter. It should search for artifacts in this phase. - */ - discover?(): Promise; - /** - * Load phase of the Booter. It should bind the artifacts in this phase. - */ - load?(): Promise; -} - -/** - * Export of an array of all the Booter phases supported by the interface - * above, in the order they should be run. - */ -export const BOOTER_PHASES = ['configure', 'discover', 'load']; +import {ArtifactOptions, Booter} from '@loopback/booter'; +import {Binding, Constructor} from '@loopback/core'; /** * Options to configure `Bootstrapper` @@ -133,45 +71,6 @@ export interface Bootable { booters(...booterClasses: Constructor[]): Binding[]; } -/** - * `@booter` decorator to mark a class as a `Booter` and specify the artifact - * namespace for the configuration of the booter - * - * @example - * ```ts - * @booter('controllers') - * export class ControllerBooter extends BaseArtifactBooter { - * constructor( - * @inject(CoreBindings.APPLICATION_INSTANCE) public app: Application, - * @inject(BootBindings.PROJECT_ROOT) projectRoot: string, - * @config() - * public controllerConfig: ArtifactOptions = {}, - * ) { - * // ... - * } - * } - * ``` - * - * @param artifactNamespace - Namespace for the artifact. It will be used to - * inject configuration from boot options. For example, the Booter class - * decorated with `@booter('controllers')` can receive its configuration via - * `@config()` from the `controllers` property of boot options. - * - * @param specs - Extra specs for the binding - */ -export function booter(artifactNamespace: string, ...specs: BindingSpec[]) { - return injectable( - { - tags: { - artifactNamespace, - [BootTags.BOOTER]: BootTags.BOOTER, - [ContextTags.NAMESPACE]: BootBindings.BOOTERS, - }, - }, - ...specs, - ); -} - /** * Interface to describe an object that may have an array of `booters`. */ diff --git a/packages/boot/tsconfig.json b/packages/boot/tsconfig.json index 15ff0870c691..4146bd1ae9f0 100644 --- a/packages/boot/tsconfig.json +++ b/packages/boot/tsconfig.json @@ -12,22 +12,10 @@ ], "references": [ { - "path": "../core/tsconfig.json" - }, - { - "path": "../model-api-builder/tsconfig.json" - }, - { - "path": "../repository/tsconfig.json" + "path": "../booter/tsconfig.json" }, { - "path": "../rest-crud/tsconfig.json" - }, - { - "path": "../rest/tsconfig.json" - }, - { - "path": "../service-proxy/tsconfig.json" + "path": "../core/tsconfig.json" }, { "path": "../testlab/tsconfig.json" diff --git a/packages/booter-lb3app/package.json b/packages/booter-lb3app/package.json index 771f50777c6d..e34f57b12382 100644 --- a/packages/booter-lb3app/package.json +++ b/packages/booter-lb3app/package.json @@ -21,7 +21,7 @@ "access": "public" }, "peerDependencies": { - "@loopback/boot": "^3.0.2", + "@loopback/booter": "^1.0.0", "@loopback/core": "^2.11.0", "@loopback/rest": "^8.0.0" }, @@ -34,6 +34,7 @@ "tslib": "^2.0.3" }, "devDependencies": { + "@loopback/booter": "^1.0.0", "@loopback/boot": "^3.0.2", "@loopback/build": "^6.2.5", "@loopback/core": "^2.11.0", diff --git a/packages/booter-lb3app/src/lb3app.booter.ts b/packages/booter-lb3app/src/lb3app.booter.ts index 71e14e7be7a6..914c9fb60185 100644 --- a/packages/booter-lb3app/src/lb3app.booter.ts +++ b/packages/booter-lb3app/src/lb3app.booter.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {BootBindings, Booter} from '@loopback/boot'; +import {Booter, BooterBindings} from '@loopback/booter'; import {CoreBindings, inject} from '@loopback/core'; import { ExpressRequestHandler, @@ -35,9 +35,9 @@ export class Lb3AppBooter implements Booter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: RestApplication, - @inject(BootBindings.PROJECT_ROOT) + @inject(BooterBindings.PROJECT_ROOT) public projectRoot: string, - @inject(`${BootBindings.BOOT_OPTIONS}#lb3app`) + @inject(`${BooterBindings.BOOT_OPTIONS}#lb3app`) options: Partial = {}, ) { this.options = Object.assign({}, DefaultOptions, options); diff --git a/packages/booter-lb3app/tsconfig.json b/packages/booter-lb3app/tsconfig.json index 813f7cf5178f..0ea456a790a3 100644 --- a/packages/booter-lb3app/tsconfig.json +++ b/packages/booter-lb3app/tsconfig.json @@ -13,6 +13,9 @@ { "path": "../boot/tsconfig.json" }, + { + "path": "../booter/tsconfig.json" + }, { "path": "../core/tsconfig.json" }, diff --git a/packages/booter/.npmrc b/packages/booter/.npmrc new file mode 100644 index 000000000000..34fbbbb3f3e4 --- /dev/null +++ b/packages/booter/.npmrc @@ -0,0 +1,2 @@ +package-lock=true +scripts-prepend-node-path=true diff --git a/packages/booter/LICENSE b/packages/booter/LICENSE new file mode 100644 index 000000000000..cb40670380e5 --- /dev/null +++ b/packages/booter/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2020. +Node module: @loopback/booter +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/booter/README.md b/packages/booter/README.md new file mode 100644 index 000000000000..acfebf401db7 --- /dev/null +++ b/packages/booter/README.md @@ -0,0 +1,54 @@ +# @loopback/booter + +This module defines common types/interfaces for LoopBack booters that are +plugged into `@loopback/boot` as extensions. + +A Booter is a Class that can be bound to an Application and is called to perform +a task before the Application is started. A Booter may have multiple phases to +complete its task. The task for a convention based Booter is to discover and +bind Artifacts (Controllers, Repositories, Models, etc.). + +An example task of a Booter may be to discover and bind all artifacts of a given +type. + +## Installation + +```sh +$ npm install @loopback/booter +``` + +## Basic Use + +### Implement a Booter + +```ts +@booter('my-artifacts') +class MyBooter implements Booter {} +``` + +### ArtifactOptions + +| Options | Type | Description | +| ------------ | -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `dirs` | `string \| string[]` | Paths relative to projectRoot to look in for Artifact | +| `extensions` | `string \| string[]` | File extensions to match for Artifact | +| `nested` | `boolean` | Look in nested directories in `dirs` for Artifact | +| `glob` | `string` | A `glob` pattern string. This takes precedence over above 3 options (which are used to make a glob pattern). | + +## Contributions + +- [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. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/packages/booter/package-lock.json b/packages/booter/package-lock.json new file mode 100644 index 000000000000..4b3594724c62 --- /dev/null +++ b/packages/booter/package-lock.json @@ -0,0 +1,137 @@ +{ + "name": "@loopback/booter", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "10.17.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.39.tgz", + "integrity": "sha512-dJLCxrpQmgyxYGcl0Ae9MTsQgI22qHHcGFj/8VKu7McJA5zQpnuGjoksnxbo1JxSjW/Nahnl13W8MYZf01CZHA==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} diff --git a/packages/booter/package.json b/packages/booter/package.json new file mode 100644 index 000000000000..3dd2a24843c6 --- /dev/null +++ b/packages/booter/package.json @@ -0,0 +1,54 @@ +{ + "name": "@loopback/booter", + "version": "1.0.0", + "description": "Types/interfaces for booters", + "keywords": [ + "loopback" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": "^10.16 || 12 || 14" + }, + "scripts": { + "build": "lb-tsc", + "build:watch": "lb-tsc --watch", + "pretest": "npm run clean && npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "clean": "lb-clean dist *.tsbuildinfo .eslintcache" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "packages/booter" + }, + "author": "IBM Corp.", + "license": "MIT", + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "peerDependencies": { + "@loopback/core": "^2.11.0" + }, + "dependencies": { + "debug": "^4.2.0", + "glob": "^7.1.6", + "tslib": "^2.0.0" + }, + "devDependencies": { + "@loopback/build": "^6.2.5", + "@loopback/core": "^2.11.0", + "@loopback/testlab": "^3.2.7", + "@types/debug": "^4.1.5", + "@types/glob": "^7.1.3", + "@types/node": "^10.17.37", + "typescript": "~4.0.3" + }, + "copyright.owner": "IBM Corp.", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/booter/src/__tests__/fixtures/empty.artifact.ts b/packages/booter/src/__tests__/fixtures/empty.artifact.ts new file mode 100644 index 000000000000..28ee7fa60c2a --- /dev/null +++ b/packages/booter/src/__tests__/fixtures/empty.artifact.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// THIS FILE IS INTENTIONALLY LEFT EMPTY! diff --git a/packages/booter/src/__tests__/fixtures/interceptor.artifact.ts b/packages/booter/src/__tests__/fixtures/interceptor.artifact.ts new file mode 100644 index 000000000000..9c01657ed514 --- /dev/null +++ b/packages/booter/src/__tests__/fixtures/interceptor.artifact.ts @@ -0,0 +1,55 @@ +// Copyright IBM Corp. 2019,2020. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + globalInterceptor, + Interceptor, + InvocationContext, + InvocationResult, + Provider, + ValueOrPromise, +} from '@loopback/core'; + +/** + * This class will be bound to the application as a global `Interceptor` during + * `boot` + */ +@globalInterceptor('auth', {tags: {name: 'myGlobalInterceptor'}}) +export class MyGlobalInterceptor implements Provider { + /* + constructor() {} + */ + + /** + * This method is used by LoopBack context to produce an interceptor function + * for the binding. + * + * @returns An interceptor function + */ + value() { + return this.intercept.bind(this); + } + + /** + * The logic to intercept an invocation + * @param invocationCtx - Invocation context + * @param next - A function to invoke next interceptor or the target method + */ + async intercept( + invocationCtx: InvocationContext, + next: () => ValueOrPromise, + ) { + // eslint-disable-next-line no-useless-catch + try { + // Add pre-invocation logic here + const result = await next(); + // Add post-invocation logic here + return result; + } catch (err) { + // Add error handling logic here + throw err; + } + } +} diff --git a/packages/booter/src/__tests__/fixtures/multiple.artifact.ts b/packages/booter/src/__tests__/fixtures/multiple.artifact.ts new file mode 100644 index 000000000000..00cc0d71e893 --- /dev/null +++ b/packages/booter/src/__tests__/fixtures/multiple.artifact.ts @@ -0,0 +1,20 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export class ArtifactOne { + one() { + return 'ControllerOne.one()'; + } +} + +export class ArtifactTwo { + two() { + return 'ControllerTwo.two()'; + } +} + +export function hello() { + return 'hello world'; +} diff --git a/packages/booter/src/__tests__/fixtures/package.json b/packages/booter/src/__tests__/fixtures/package.json new file mode 100644 index 000000000000..236921b8868b --- /dev/null +++ b/packages/booter/src/__tests__/fixtures/package.json @@ -0,0 +1,19 @@ +{ + "name": "boot-test-app", + "version": "1.0.0", + "description": "boot-test-app", + "keywords": [ + "loopback-application", + "loopback" + ], + "engines": { + "node": ">=10.16" + }, + "scripts": { + }, + "repository": { + "type": "git" + }, + "author": "IBM Corp.", + "license": "MIT" +} diff --git a/packages/booter/src/__tests__/unit/booter.unit.ts b/packages/booter/src/__tests__/unit/booter.unit.ts new file mode 100644 index 000000000000..8d65630bdf75 --- /dev/null +++ b/packages/booter/src/__tests__/unit/booter.unit.ts @@ -0,0 +1,21 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {createBindingFromClass} from '@loopback/core'; +import {expect} from '@loopback/testlab'; +import {Booter, booter} from '../..'; +import {BooterBindings, BooterTags} from '../../keys'; + +describe('@booter', () => { + it('decorates a class as booter', () => { + @booter('my-artifacts') + class MyBooter implements Booter {} + + const binding = createBindingFromClass(MyBooter); + expect(binding.tagMap).to.have.property(BooterTags.BOOTER); + expect(binding.tagMap.namespace).to.equal(BooterBindings.BOOTERS); + expect(binding.key).to.equal(`${BooterBindings.BOOTERS}.MyBooter`); + }); +}); diff --git a/packages/boot/src/__tests__/unit/booters/base-artifact.booter.unit.ts b/packages/booter/src/__tests__/unit/booters/base-artifact.booter.unit.ts similarity index 100% rename from packages/boot/src/__tests__/unit/booters/base-artifact.booter.unit.ts rename to packages/booter/src/__tests__/unit/booters/base-artifact.booter.unit.ts diff --git a/packages/boot/src/__tests__/unit/booters/booter-utils.unit.ts b/packages/booter/src/__tests__/unit/booters/booter-utils.unit.ts similarity index 100% rename from packages/boot/src/__tests__/unit/booters/booter-utils.unit.ts rename to packages/booter/src/__tests__/unit/booters/booter-utils.unit.ts diff --git a/packages/boot/src/booters/base-artifact.booter.ts b/packages/booter/src/base-artifact.booter.ts similarity index 96% rename from packages/boot/src/booters/base-artifact.booter.ts rename to packages/booter/src/base-artifact.booter.ts index 88e535ad43ee..2d6b973fd17b 100644 --- a/packages/boot/src/booters/base-artifact.booter.ts +++ b/packages/booter/src/base-artifact.booter.ts @@ -1,13 +1,13 @@ -// Copyright IBM Corp. 2018,2020. All Rights Reserved. -// Node module: @loopback/boot +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {Constructor} from '@loopback/core'; import debugFactory from 'debug'; import path from 'path'; -import {ArtifactOptions, Booter} from '../types'; import {discoverFiles, loadClassesFromFiles} from './booter-utils'; +import {ArtifactOptions, Booter} from './types'; const debug = debugFactory('loopback:boot:base-artifact-booter'); diff --git a/packages/boot/src/booters/booter-utils.ts b/packages/booter/src/booter-utils.ts similarity index 63% rename from packages/boot/src/booters/booter-utils.ts rename to packages/booter/src/booter-utils.ts index ca232b62b7ec..42af67423160 100644 --- a/packages/boot/src/booters/booter-utils.ts +++ b/packages/booter/src/booter-utils.ts @@ -1,13 +1,23 @@ -// Copyright IBM Corp. 2018,2019. All Rights Reserved. -// Node module: @loopback/boot +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Constructor} from '@loopback/core'; +import { + Binding, + BindingScope, + Constructor, + Context, + createBindingFromClass, +} from '@loopback/core'; import debugFactory from 'debug'; +import globFn from 'glob'; import path from 'path'; import {promisify} from 'util'; -const glob = promisify(require('glob')); +import {BooterBindings, BooterTags} from './keys'; +import {Booter} from './types'; + +const glob = promisify(globFn); const debug = debugFactory('loopback:boot:booter-utils'); @@ -68,3 +78,33 @@ export function loadClassesFromFiles( return classes; } + +/** + * Method which binds a given Booter to a given Context with the Prefix and + * Tags expected by the Bootstrapper + * + * @param ctx - The Context to bind the Booter Class + * @param booterCls - Booter class to be bound + */ +export function bindBooter( + ctx: Context, + booterCls: Constructor, +): Binding { + const binding = createBindingFromClass(booterCls, { + namespace: BooterBindings.BOOTERS, + defaultScope: BindingScope.SINGLETON, + }).tag(BooterTags.BOOTER); + ctx.add(binding); + /** + * Set up configuration binding as alias to `BootBindings.BOOT_OPTIONS` + * so that the booter can use `@config`. + */ + if (binding.tagMap.artifactNamespace) { + ctx + .configure(binding.key) + .toAlias( + `${BooterBindings.BOOT_OPTIONS.key}#${binding.tagMap.artifactNamespace}`, + ); + } + return binding; +} diff --git a/packages/booter/src/booter.decorator.ts b/packages/booter/src/booter.decorator.ts new file mode 100644 index 000000000000..f767c3c2f907 --- /dev/null +++ b/packages/booter/src/booter.decorator.ts @@ -0,0 +1,46 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingSpec, ContextTags, injectable} from '@loopback/core'; +import {BooterBindings, BooterTags} from './keys'; + +/** + * `@booter` decorator to mark a class as a `Booter` and specify the artifact + * namespace for the configuration of the booter + * + * @example + * ```ts + * @booter('controllers') + * export class ControllerBooter extends BaseArtifactBooter { + * constructor( + * @inject(CoreBindings.APPLICATION_INSTANCE) public app: Application, + * @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + * @config() + * public controllerConfig: ArtifactOptions = {}, + * ) { + * // ... + * } + * } + * ``` + * + * @param artifactNamespace - Namespace for the artifact. It will be used to + * inject configuration from boot options. For example, the Booter class + * decorated with `@booter('controllers')` can receive its configuration via + * `@config()` from the `controllers` property of boot options. + * + * @param specs - Extra specs for the binding + */ +export function booter(artifactNamespace: string, ...specs: BindingSpec[]) { + return injectable( + { + tags: { + artifactNamespace, + [BooterTags.BOOTER]: BooterTags.BOOTER, + [ContextTags.NAMESPACE]: BooterBindings.BOOTERS, + }, + }, + ...specs, + ); +} diff --git a/packages/booter/src/index.ts b/packages/booter/src/index.ts new file mode 100644 index 000000000000..a2a075c432b3 --- /dev/null +++ b/packages/booter/src/index.ts @@ -0,0 +1,10 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './base-artifact.booter'; +export * from './booter-utils'; +export * from './booter.decorator'; +export * from './keys'; +export * from './types'; diff --git a/packages/booter/src/keys.ts b/packages/booter/src/keys.ts new file mode 100644 index 000000000000..8442e0397088 --- /dev/null +++ b/packages/booter/src/keys.ts @@ -0,0 +1,39 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; + +/** + * Namespace for boot related binding keys + */ +export namespace BooterBindings { + /** + * Binding key for binding the BootStrapper class + */ + export const BOOTSTRAPPER_KEY = BindingKey.create( + 'application.bootstrapper', + ); + /** + * Binding key for boot options + */ + export const BOOT_OPTIONS = BindingKey.create( + BindingKey.buildKeyForConfig(BOOTSTRAPPER_KEY.key).toString(), + ); + /** + * Booter binding namespace + */ + export const BOOTERS = 'booters'; + /** + * Binding key for determining project root directory + */ + export const PROJECT_ROOT = BindingKey.create('boot.project_root'); +} + +/** + * Namespace for boot related tags + */ +export namespace BooterTags { + export const BOOTER = 'booter'; +} diff --git a/packages/booter/src/types.ts b/packages/booter/src/types.ts new file mode 100644 index 000000000000..55353afadf36 --- /dev/null +++ b/packages/booter/src/types.ts @@ -0,0 +1,60 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/booter +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Type definition for ArtifactOptions. These are the options supported by + * this Booter. + */ +export type ArtifactOptions = { + /** + * Array of directories to check for artifacts. + * Paths must be relative. Defaults to ['controllers'] + */ + dirs?: string | string[]; + /** + * Array of file extensions to match artifact + * files in dirs. Defaults to ['.controller.js'] + */ + extensions?: string | string[]; + /** + * A flag to control if artifact discovery should check nested + * folders or not. Default to true + */ + nested?: boolean; + /** + * A `glob` string to use when searching for files. This takes + * precedence over other options. + */ + glob?: string; +}; + +/** + * Defines the requirements to implement a Booter for LoopBack applications: + * - configure() + * - discover() + * - load() + * + * A Booter will run through the above methods in order. + */ +export interface Booter { + /** + * Configure phase of the Booter. It should set options / defaults in this phase. + */ + configure?(): Promise; + /** + * Discover phase of the Booter. It should search for artifacts in this phase. + */ + discover?(): Promise; + /** + * Load phase of the Booter. It should bind the artifacts in this phase. + */ + load?(): Promise; +} + +/** + * Export of an array of all the Booter phases supported by the interface + * above, in the order they should be run. + */ +export const BOOTER_PHASES = ['configure', 'discover', 'load']; diff --git a/packages/booter/tsconfig.json b/packages/booter/tsconfig.json new file mode 100644 index 000000000000..134a68e33164 --- /dev/null +++ b/packages/booter/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": true + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../core/tsconfig.json" + }, + { + "path": "../testlab/tsconfig.json" + } + ] +} diff --git a/packages/cli/generators/project/templates/package.json.ejs b/packages/cli/generators/project/templates/package.json.ejs index acc1a5760570..15dae002dba0 100644 --- a/packages/cli/generators/project/templates/package.json.ejs +++ b/packages/cli/generators/project/templates/package.json.ejs @@ -127,6 +127,7 @@ "dependencies": { <% if (project.projectType === 'application') { -%> "@loopback/boot": "<%= project.dependencies['@loopback/boot'] -%>", + "@loopback/booter": "<%= project.dependencies['@loopback/booter'] -%>", "@loopback/core": "<%= project.dependencies['@loopback/core'] -%>", <% if (project.repositories) { -%> "@loopback/repository": "<%= project.dependencies['@loopback/repository'] -%>", diff --git a/packages/cli/generators/project/templates/package.plain.json.ejs b/packages/cli/generators/project/templates/package.plain.json.ejs index 445acf2ed33f..bcff5cb02fa5 100644 --- a/packages/cli/generators/project/templates/package.plain.json.ejs +++ b/packages/cli/generators/project/templates/package.plain.json.ejs @@ -130,6 +130,7 @@ "dependencies": { <% if (project.projectType === 'application') { -%> "@loopback/boot": "<%= project.dependencies['@loopback/boot'] -%>", + "@loopback/booter": "<%= project.dependencies['@loopback/booter'] -%>", "@loopback/core": "<%= project.dependencies['@loopback/core'] -%>", "@loopback/repository": "<%= project.dependencies['@loopback/repository'] -%>", <% if (project.apiconnect) { -%> diff --git a/packages/cli/package.json b/packages/cli/package.json index e260a6b362bd..76e0f7f5a416 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -114,6 +114,7 @@ "source-map-support": "^0.5.19", "typescript": "~4.0.3", "@loopback/authentication": "^7.0.2", + "@loopback/booter": "^1.0.0", "@loopback/boot": "^3.0.2", "@loopback/build": "^6.2.5", "@loopback/cli": "^2.16.1", diff --git a/packages/model-api-builder/package.json b/packages/model-api-builder/package.json index 432968139e09..a16e26351df6 100644 --- a/packages/model-api-builder/package.json +++ b/packages/model-api-builder/package.json @@ -21,6 +21,7 @@ "access": "public" }, "peerDependencies": { + "@loopback/booter": "^1.0.0", "@loopback/core": "^2.11.0", "@loopback/repository": "^3.1.0" }, @@ -29,6 +30,8 @@ }, "devDependencies": { "@loopback/build": "^6.2.5", + "@loopback/booter": "^1.0.0", + "@loopback/boot": "^3.0.2", "@loopback/core": "^2.11.0", "@loopback/repository": "^3.1.0", "@types/node": "^10.17.35" diff --git a/packages/model-api-builder/src/booters/index.ts b/packages/model-api-builder/src/booters/index.ts new file mode 100644 index 000000000000..4a2fbb5a2315 --- /dev/null +++ b/packages/model-api-builder/src/booters/index.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2018,2020. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './model-api.booter'; diff --git a/packages/boot/src/booters/model-api.booter.ts b/packages/model-api-builder/src/booters/model-api.booter.ts similarity index 90% rename from packages/boot/src/booters/model-api.booter.ts rename to packages/model-api-builder/src/booters/model-api.booter.ts index a0d71f233343..ff595c9ed4f7 100644 --- a/packages/boot/src/booters/model-api.booter.ts +++ b/packages/model-api-builder/src/booters/model-api.booter.ts @@ -1,8 +1,14 @@ // Copyright IBM Corp. 2019. All Rights Reserved. -// Node module: @loopback/boot +// Node module: @loopback/model-api-builder // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import { + ArtifactOptions, + BaseArtifactBooter, + booter, + BooterBindings, +} from '@loopback/booter'; import { config, CoreBindings, @@ -11,17 +17,11 @@ import { Getter, inject, } from '@loopback/core'; -import { - ModelApiBuilder, - ModelApiConfig, - MODEL_API_BUILDER_PLUGINS, -} from '@loopback/model-api-builder'; import {ApplicationWithRepositories} from '@loopback/repository'; import debugFactory from 'debug'; import * as path from 'path'; -import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; +import {ModelApiBuilder, MODEL_API_BUILDER_PLUGINS} from '../model-api-builder'; +import {ModelApiConfig} from '../model-api-config'; const debug = debugFactory('loopback:boot:model-api'); @@ -31,7 +31,7 @@ export class ModelApiBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, - @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(BooterBindings.PROJECT_ROOT) projectRoot: string, @extensions() public getModelApiBuilders: Getter, @config() diff --git a/packages/model-api-builder/src/index.ts b/packages/model-api-builder/src/index.ts index 2abf288b7a48..32f5385439b8 100644 --- a/packages/model-api-builder/src/index.ts +++ b/packages/model-api-builder/src/index.ts @@ -15,5 +15,6 @@ * @packageDocumentation */ +export * from './booters'; export * from './model-api-builder'; export * from './model-api-config'; diff --git a/packages/model-api-builder/tsconfig.json b/packages/model-api-builder/tsconfig.json index 49dd1973c7d8..21cadc03c26f 100644 --- a/packages/model-api-builder/tsconfig.json +++ b/packages/model-api-builder/tsconfig.json @@ -10,6 +10,12 @@ "src" ], "references": [ + { + "path": "../boot/tsconfig.json" + }, + { + "path": "../booter/tsconfig.json" + }, { "path": "../core/tsconfig.json" }, diff --git a/packages/repository/package.json b/packages/repository/package.json index 4360c7c90f94..41ce42509fa0 100644 --- a/packages/repository/package.json +++ b/packages/repository/package.json @@ -22,10 +22,13 @@ "access": "public" }, "peerDependencies": { + "@loopback/booter": "^1.0.0", "@loopback/core": "^2.11.0" }, "devDependencies": { "@loopback/build": "^6.2.5", + "@loopback/booter": "^1.0.0", + "@loopback/boot": "^3.0.2", "@loopback/core": "^2.11.0", "@loopback/eslint-config": "^10.0.1", "@loopback/testlab": "^3.2.7", diff --git a/packages/repository/src/__tests__/fixtures/booters/application.ts b/packages/repository/src/__tests__/fixtures/booters/application.ts new file mode 100644 index 000000000000..b1ac8145e23a --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/application.ts @@ -0,0 +1,15 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BootMixin} from '@loopback/boot'; +import {Application, ApplicationConfig} from '@loopback/core'; +import {RepositoryMixin} from '../../..'; + +export class BooterApp extends BootMixin(RepositoryMixin(Application)) { + constructor(options?: ApplicationConfig) { + super(options); + this.projectRoot = __dirname; + } +} diff --git a/packages/repository/src/__tests__/fixtures/booters/datasource.artifact.ts b/packages/repository/src/__tests__/fixtures/booters/datasource.artifact.ts new file mode 100644 index 000000000000..5475df73c636 --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/datasource.artifact.ts @@ -0,0 +1,14 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {juggler} from '../../..'; + +export class DbDataSource extends juggler.DataSource { + static dataSourceName = 'db'; + + constructor() { + super({name: 'db'}); + } +} diff --git a/packages/repository/src/__tests__/fixtures/booters/empty.artifact.ts b/packages/repository/src/__tests__/fixtures/booters/empty.artifact.ts new file mode 100644 index 000000000000..28ee7fa60c2a --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/empty.artifact.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// THIS FILE IS INTENTIONALLY LEFT EMPTY! diff --git a/packages/boot/src/__tests__/fixtures/multiple-models.model.ts b/packages/repository/src/__tests__/fixtures/booters/multiple-models.model.ts similarity index 83% rename from packages/boot/src/__tests__/fixtures/multiple-models.model.ts rename to packages/repository/src/__tests__/fixtures/booters/multiple-models.model.ts index e6f47b18417c..bf9b6dafe6f6 100644 --- a/packages/boot/src/__tests__/fixtures/multiple-models.model.ts +++ b/packages/repository/src/__tests__/fixtures/booters/multiple-models.model.ts @@ -3,7 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Entity, Model} from '@loopback/repository'; +import {Entity, Model} from '../../..'; export class Model1 extends Model {} diff --git a/packages/repository/src/__tests__/fixtures/booters/multiple.artifact.ts b/packages/repository/src/__tests__/fixtures/booters/multiple.artifact.ts new file mode 100644 index 000000000000..00cc0d71e893 --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/multiple.artifact.ts @@ -0,0 +1,20 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export class ArtifactOne { + one() { + return 'ControllerOne.one()'; + } +} + +export class ArtifactTwo { + two() { + return 'ControllerTwo.two()'; + } +} + +export function hello() { + return 'hello world'; +} diff --git a/packages/repository/src/__tests__/fixtures/booters/no-entity.model.ts b/packages/repository/src/__tests__/fixtures/booters/no-entity.model.ts new file mode 100644 index 000000000000..b361495dd782 --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/no-entity.model.ts @@ -0,0 +1,15 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {model, Model, property} from '../../..'; + +@model() +export class NoEntity extends Model { + @property({id: true}) + id: number; + + @property({required: true}) + name: string; +} diff --git a/packages/repository/src/__tests__/fixtures/booters/package.json b/packages/repository/src/__tests__/fixtures/booters/package.json new file mode 100644 index 000000000000..24d9646e6635 --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/package.json @@ -0,0 +1,19 @@ +{ + "name": "boot-test-app", + "version": "1.0.0", + "description": "boot-test-app", + "keywords": [ + "loopback-application", + "loopback" + ], + "engines": { + "node": ">=10" + }, + "scripts": { + }, + "repository": { + "type": "git" + }, + "author": "IBM Corp.", + "license": "MIT" +} diff --git a/packages/repository/src/__tests__/fixtures/booters/product.model.ts b/packages/repository/src/__tests__/fixtures/booters/product.model.ts new file mode 100644 index 000000000000..582a0a777579 --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/product.model.ts @@ -0,0 +1,15 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Entity, model, property} from '../../..'; + +@model() +export class Product extends Entity { + @property({id: true}) + id: number; + + @property({required: true}) + name: string; +} diff --git a/packages/repository/src/__tests__/fixtures/booters/product.repository.ts b/packages/repository/src/__tests__/fixtures/booters/product.repository.ts new file mode 100644 index 000000000000..259eafc28a86 --- /dev/null +++ b/packages/repository/src/__tests__/fixtures/booters/product.repository.ts @@ -0,0 +1,17 @@ +// Copyright IBM Corp. 2020. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject} from '@loopback/core'; +import {DefaultCrudRepository, juggler} from '../../..'; +import {Product} from './product.model'; + +export class ProductRepository extends DefaultCrudRepository< + Product, + typeof Product.prototype.id +> { + constructor(@inject('datasources.db') dataSource: juggler.DataSource) { + super(Product, dataSource); + } +} diff --git a/packages/boot/src/__tests__/integration/datasource.booter.integration.ts b/packages/repository/src/__tests__/integration/booters/datasource.booter.integration.ts similarity index 76% rename from packages/boot/src/__tests__/integration/datasource.booter.integration.ts rename to packages/repository/src/__tests__/integration/booters/datasource.booter.integration.ts index c7eb0d86d2cb..e353a0b2f885 100644 --- a/packages/boot/src/__tests__/integration/datasource.booter.integration.ts +++ b/packages/repository/src/__tests__/integration/booters/datasource.booter.integration.ts @@ -5,10 +5,10 @@ import {expect, TestSandbox} from '@loopback/testlab'; import {resolve} from 'path'; -import {BooterApp} from '../fixtures/application'; +import {BooterApp} from '../../fixtures/booters/application'; describe('datasource booter integration tests', () => { - const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); const DATASOURCES_PREFIX = 'datasources'; const DATASOURCES_TAG = 'datasource'; @@ -28,9 +28,11 @@ describe('datasource booter integration tests', () => { }); async function getApp() { - await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); await sandbox.copyFile( - resolve(__dirname, '../fixtures/datasource.artifact.js'), + resolve(__dirname, '../../fixtures/booters/application.js'), + ); + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/booters/datasource.artifact.js'), 'datasources/db.datasource.js', ); diff --git a/packages/boot/src/__tests__/integration/model.booter.integration.ts b/packages/repository/src/__tests__/integration/booters/model.booter.integration.ts similarity index 72% rename from packages/boot/src/__tests__/integration/model.booter.integration.ts rename to packages/repository/src/__tests__/integration/booters/model.booter.integration.ts index a34c1d5aa6b5..dd5a88c1cf14 100644 --- a/packages/boot/src/__tests__/integration/model.booter.integration.ts +++ b/packages/repository/src/__tests__/integration/booters/model.booter.integration.ts @@ -5,10 +5,10 @@ import {expect, TestSandbox} from '@loopback/testlab'; import {resolve} from 'path'; -import {BooterApp} from '../fixtures/application'; +import {BooterApp} from '../../fixtures/booters/application'; describe('repository booter integration tests', () => { - const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); const MODELS_TAG = 'model'; @@ -32,18 +32,20 @@ describe('repository booter integration tests', () => { }); async function getApp() { - await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); await sandbox.copyFile( - resolve(__dirname, '../fixtures/no-entity.model.js'), + resolve(__dirname, '../../fixtures/booters/application.js'), + ); + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/booters/no-entity.model.js'), 'models/no-entity.model.js', ); await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/booters/product.model.js'), 'models/product.model.js', ); await sandbox.copyFile( - resolve(__dirname, '../fixtures/multiple-models.model.js'), + resolve(__dirname, '../../fixtures/booters/multiple-models.model.js'), 'models/multiple-models.model.js', ); diff --git a/packages/boot/src/__tests__/integration/repository.booter.integration.ts b/packages/repository/src/__tests__/integration/booters/repository.booter.integration.ts similarity index 78% rename from packages/boot/src/__tests__/integration/repository.booter.integration.ts rename to packages/repository/src/__tests__/integration/booters/repository.booter.integration.ts index 3669512287fa..23e5404aacc9 100644 --- a/packages/boot/src/__tests__/integration/repository.booter.integration.ts +++ b/packages/repository/src/__tests__/integration/booters/repository.booter.integration.ts @@ -5,10 +5,10 @@ import {expect, TestSandbox} from '@loopback/testlab'; import {resolve} from 'path'; -import {BooterApp} from '../fixtures/application'; +import {BooterApp} from '../../fixtures/booters/application'; describe('repository booter integration tests', () => { - const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); // Remnants from Refactor -- need to add these to core const REPOSITORIES_PREFIX = 'repositories'; @@ -32,9 +32,11 @@ describe('repository booter integration tests', () => { }); async function getApp() { - await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js')); await sandbox.copyFile( - resolve(__dirname, '../fixtures/multiple.artifact.js'), + resolve(__dirname, '../../fixtures/booters/application.js'), + ); + await sandbox.copyFile( + resolve(__dirname, '../../fixtures/booters/multiple.artifact.js'), 'repositories/multiple.repository.js', ); diff --git a/packages/boot/src/__tests__/unit/booters/datasource.booter.unit.ts b/packages/repository/src/__tests__/unit/booters/datasource.booter.unit.ts similarity index 92% rename from packages/boot/src/__tests__/unit/booters/datasource.booter.unit.ts rename to packages/repository/src/__tests__/unit/booters/datasource.booter.unit.ts index b627a012bbf9..8481e059f357 100644 --- a/packages/boot/src/__tests__/unit/booters/datasource.booter.unit.ts +++ b/packages/repository/src/__tests__/unit/booters/datasource.booter.unit.ts @@ -4,13 +4,14 @@ // License text available at https://opensource.org/licenses/MIT import {Application} from '@loopback/core'; +import {expect, sinon, TestSandbox} from '@loopback/testlab'; +import {resolve} from 'path'; import { ApplicationWithRepositories, + DataSourceBooter, + DataSourceDefaults, RepositoryMixin, -} from '@loopback/repository'; -import {expect, sinon, TestSandbox} from '@loopback/testlab'; -import {resolve} from 'path'; -import {DataSourceBooter, DataSourceDefaults} from '../../..'; +} from '../../..'; describe('datasource booter unit tests', () => { const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); @@ -32,7 +33,7 @@ describe('datasource booter unit tests', () => { it('gives a warning if called on an app without RepositoryMixin', async () => { const normalApp = new Application(); await sandbox.copyFile( - resolve(__dirname, '../../fixtures/datasource.artifact.js'), + resolve(__dirname, '../../fixtures/booters/datasource.artifact.js'), ); const booterInst = new DataSourceBooter( @@ -72,7 +73,7 @@ describe('datasource booter unit tests', () => { it('binds datasources during the load phase', async () => { const expected = [`${DATASOURCES_PREFIX}.db`]; await sandbox.copyFile( - resolve(__dirname, '../../fixtures/datasource.artifact.js'), + resolve(__dirname, '../../fixtures/booters/datasource.artifact.js'), ); const booterInst = new DataSourceBooter(app, sandbox.path); const NUM_CLASSES = 1; // 1 class in above file. diff --git a/packages/boot/src/__tests__/unit/booters/repository.booter.unit.ts b/packages/repository/src/__tests__/unit/booters/repository.booter.unit.ts similarity index 93% rename from packages/boot/src/__tests__/unit/booters/repository.booter.unit.ts rename to packages/repository/src/__tests__/unit/booters/repository.booter.unit.ts index 7ca4d349c9e5..1728d6a82618 100644 --- a/packages/boot/src/__tests__/unit/booters/repository.booter.unit.ts +++ b/packages/repository/src/__tests__/unit/booters/repository.booter.unit.ts @@ -4,13 +4,14 @@ // License text available at https://opensource.org/licenses/MIT import {Application} from '@loopback/core'; +import {expect, sinon, TestSandbox} from '@loopback/testlab'; +import {resolve} from 'path'; import { ApplicationWithRepositories, + RepositoryBooter, + RepositoryDefaults, RepositoryMixin, -} from '@loopback/repository'; -import {expect, sinon, TestSandbox} from '@loopback/testlab'; -import {resolve} from 'path'; -import {RepositoryBooter, RepositoryDefaults} from '../../..'; +} from '../../..'; describe('repository booter unit tests', () => { const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); @@ -32,7 +33,7 @@ describe('repository booter unit tests', () => { it('gives a warning if called on an app without RepositoryMixin', async () => { const normalApp = new Application(); await sandbox.copyFile( - resolve(__dirname, '../../fixtures/multiple.artifact.js'), + resolve(__dirname, '../../fixtures/booters/multiple.artifact.js'), ); const booterInst = new RepositoryBooter( @@ -76,7 +77,7 @@ describe('repository booter unit tests', () => { `${REPOSITORIES_PREFIX}.ArtifactTwo`, ]; await sandbox.copyFile( - resolve(__dirname, '../../fixtures/multiple.artifact.js'), + resolve(__dirname, '../../fixtures/booters/multiple.artifact.js'), ); const booterInst = new RepositoryBooter(app, sandbox.path); const NUM_CLASSES = 2; // 2 classes in above file. diff --git a/packages/boot/src/booters/datasource.booter.ts b/packages/repository/src/booters/datasource.booter.ts similarity index 85% rename from packages/boot/src/booters/datasource.booter.ts rename to packages/repository/src/booters/datasource.booter.ts index d8e05835bbb7..7b23cb8e66dc 100644 --- a/packages/boot/src/booters/datasource.booter.ts +++ b/packages/repository/src/booters/datasource.booter.ts @@ -3,15 +3,16 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {config, inject, CoreBindings} from '@loopback/core'; import { - ApplicationWithRepositories, - Class, - juggler, -} from '@loopback/repository'; -import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; + ArtifactOptions, + BaseArtifactBooter, + booter, + BooterBindings, +} from '@loopback/booter'; +import {config, CoreBindings, inject} from '@loopback/core'; +import {Class} from '../common-types'; +import {ApplicationWithRepositories} from '../mixins'; +import {juggler} from '../repositories'; /** * A class that extends BaseArtifactBooter to boot the 'DataSource' artifact type. @@ -28,7 +29,7 @@ export class DataSourceBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, - @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(BooterBindings.PROJECT_ROOT) projectRoot: string, @config() public datasourceConfig: ArtifactOptions = {}, ) { diff --git a/packages/repository/src/booters/index.ts b/packages/repository/src/booters/index.ts new file mode 100644 index 000000000000..3154c12caa9d --- /dev/null +++ b/packages/repository/src/booters/index.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2018,2020. All Rights Reserved. +// Node module: @loopback/rest +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './datasource.booter'; +export * from './model.booter'; +export * from './repository.booter'; diff --git a/packages/boot/src/booters/model.booter.ts b/packages/repository/src/booters/model.booter.ts similarity index 84% rename from packages/boot/src/booters/model.booter.ts rename to packages/repository/src/booters/model.booter.ts index df23cc408b35..f3ea2592b62c 100644 --- a/packages/boot/src/booters/model.booter.ts +++ b/packages/repository/src/booters/model.booter.ts @@ -3,15 +3,16 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {config, Constructor, inject, CoreBindings} from '@loopback/core'; import { - ApplicationWithRepositories, - ModelMetadataHelper, -} from '@loopback/repository'; + ArtifactOptions, + BaseArtifactBooter, + booter, + BooterBindings, +} from '@loopback/booter'; +import {config, Constructor, CoreBindings, inject} from '@loopback/core'; import debugFactory from 'debug'; -import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; +import {ModelMetadataHelper} from '../decorators'; +import {ApplicationWithRepositories} from '../mixins'; const debug = debugFactory('loopback:boot:model-booter'); @@ -29,7 +30,7 @@ export class ModelBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, - @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(BooterBindings.PROJECT_ROOT) projectRoot: string, @config() public modelConfig: ArtifactOptions = {}, ) { diff --git a/packages/boot/src/booters/repository.booter.ts b/packages/repository/src/booters/repository.booter.ts similarity index 86% rename from packages/boot/src/booters/repository.booter.ts rename to packages/repository/src/booters/repository.booter.ts index 35dc5f748e20..0f2eec6c9a77 100644 --- a/packages/boot/src/booters/repository.booter.ts +++ b/packages/repository/src/booters/repository.booter.ts @@ -3,11 +3,14 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {config, inject, CoreBindings} from '@loopback/core'; -import {ApplicationWithRepositories} from '@loopback/repository'; -import {BootBindings} from '../keys'; -import {ArtifactOptions, booter} from '../types'; -import {BaseArtifactBooter} from './base-artifact.booter'; +import { + ArtifactOptions, + BaseArtifactBooter, + booter, + BooterBindings, +} from '@loopback/booter'; +import {config, CoreBindings, inject} from '@loopback/core'; +import {ApplicationWithRepositories} from '../mixins'; /** * A class that extends BaseArtifactBooter to boot the 'Repository' artifact type. @@ -25,7 +28,7 @@ export class RepositoryBooter extends BaseArtifactBooter { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) public app: ApplicationWithRepositories, - @inject(BootBindings.PROJECT_ROOT) projectRoot: string, + @inject(BooterBindings.PROJECT_ROOT) projectRoot: string, @config() public repositoryOptions: ArtifactOptions = {}, ) { diff --git a/packages/repository/src/index.ts b/packages/repository/src/index.ts index c8ca6f4e319e..edb02f9f53ae 100644 --- a/packages/repository/src/index.ts +++ b/packages/repository/src/index.ts @@ -14,6 +14,7 @@ export * from '@loopback/filter'; export {JSONSchema7 as JsonSchema} from 'json-schema'; +export * from './booters'; export * from './common-types'; export * from './connectors'; export * from './datasource'; diff --git a/packages/repository/src/mixins/repository.mixin.ts b/packages/repository/src/mixins/repository.mixin.ts index f7764f587fe9..ad1fdde333ae 100644 --- a/packages/repository/src/mixins/repository.mixin.ts +++ b/packages/repository/src/mixins/repository.mixin.ts @@ -3,10 +3,12 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {bindBooter} from '@loopback/booter'; import { Binding, BindingFromClassOptions, BindingScope, + Context, createBindingFromClass, } from '@loopback/core'; import { @@ -17,6 +19,7 @@ import { MixinTarget, } from '@loopback/core'; import debugFactory from 'debug'; +import {DataSourceBooter, ModelBooter, RepositoryBooter} from '../booters'; import {Class} from '../common-types'; import {SchemaMigrationOptions} from '../datasource'; import {RepositoryBindings, RepositoryTags} from '../keys'; @@ -55,6 +58,15 @@ export function RepositoryMixin>( superClass: T, ) { return class extends superClass { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + constructor(...args: any[]) { + super(...args); + const ctx = (this as unknown) as Context; + bindBooter(ctx, ModelBooter); + bindBooter(ctx, DataSourceBooter); + bindBooter(ctx, RepositoryBooter); + } + /** * Add a repository to this application. * diff --git a/packages/repository/tsconfig.json b/packages/repository/tsconfig.json index bcaafd0cf140..ca73e8da068a 100644 --- a/packages/repository/tsconfig.json +++ b/packages/repository/tsconfig.json @@ -10,6 +10,12 @@ "src" ], "references": [ + { + "path": "../boot/tsconfig.json" + }, + { + "path": "../booter/tsconfig.json" + }, { "path": "../core/tsconfig.json" }, diff --git a/packages/rest-crud/package.json b/packages/rest-crud/package.json index 803a06f37723..a8c2920cd1f8 100644 --- a/packages/rest-crud/package.json +++ b/packages/rest-crud/package.json @@ -21,6 +21,7 @@ "access": "public" }, "peerDependencies": { + "@loopback/booter": "^1.0.0", "@loopback/core": "^2.11.0", "@loopback/repository": "^3.1.0", "@loopback/rest": "^8.0.0" @@ -32,6 +33,8 @@ }, "devDependencies": { "@loopback/build": "^6.2.5", + "@loopback/booter": "^1.0.0", + "@loopback/boot": "^3.0.2", "@loopback/core": "^2.11.0", "@loopback/repository": "^3.1.0", "@loopback/rest": "^8.0.0", diff --git a/packages/boot/src/__tests__/acceptance/crud-rest.api-builder.acceptance.ts b/packages/rest-crud/src/__tests__/acceptance/booters/crud-rest.api-builder.acceptance.ts similarity index 88% rename from packages/boot/src/__tests__/acceptance/crud-rest.api-builder.acceptance.ts rename to packages/rest-crud/src/__tests__/acceptance/booters/crud-rest.api-builder.acceptance.ts index 5cee86b9b78b..b367728c88d3 100644 --- a/packages/boot/src/__tests__/acceptance/crud-rest.api-builder.acceptance.ts +++ b/packages/rest-crud/src/__tests__/acceptance/booters/crud-rest.api-builder.acceptance.ts @@ -3,18 +3,19 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; +import {ModelApiBooter} from '@loopback/model-api-builder'; import {juggler, RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; -import {CrudRestComponent} from '@loopback/rest-crud'; import {expect, givenHttpServerConfig, TestSandbox} from '@loopback/testlab'; import {resolve} from 'path'; -import {BootMixin, ModelApiBooter} from '../..'; -import {ProductRepository} from '../fixtures/product.repository'; +import {CrudRestComponent} from '../../..'; +import {ProductRepository} from '../../fixtures/product.repository'; describe('CRUD rest builder acceptance tests', () => { let app: BooterApp; - const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); beforeEach('reset sandbox', () => sandbox.reset()); beforeEach(givenAppWithDataSource); @@ -23,7 +24,7 @@ describe('CRUD rest builder acceptance tests', () => { it('binds the controller and repository to the application', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); @@ -58,7 +59,7 @@ module.exports = { it('uses bound repository class if it exists', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); @@ -99,7 +100,7 @@ module.exports = { it('throws if there is no base path in the config', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); @@ -124,7 +125,7 @@ module.exports = { it('throws if a Model is used instead of an Entity', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/no-entity.model.js'), + resolve(__dirname, '../../fixtures/no-entity.model.js'), 'models/no-entity.model.js', ); diff --git a/packages/boot/src/__tests__/acceptance/model-api.booter.acceptance.ts b/packages/rest-crud/src/__tests__/acceptance/booters/model-api.booter.acceptance.ts similarity index 88% rename from packages/boot/src/__tests__/acceptance/model-api.booter.acceptance.ts rename to packages/rest-crud/src/__tests__/acceptance/booters/model-api.booter.acceptance.ts index c610e517fbcd..09d2eadbeff5 100644 --- a/packages/boot/src/__tests__/acceptance/model-api.booter.acceptance.ts +++ b/packages/rest-crud/src/__tests__/acceptance/booters/model-api.booter.acceptance.ts @@ -3,7 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +import {BootMixin} from '@loopback/boot'; import {ApplicationConfig} from '@loopback/core'; +import {ModelApiBooter} from '@loopback/model-api-builder'; import {juggler, RepositoryMixin} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import { @@ -13,8 +15,7 @@ import { toJSON, } from '@loopback/testlab'; import {resolve} from 'path'; -import {BootMixin, ModelApiBooter} from '../..'; -import {Product} from '../fixtures/product.model'; +import {Product} from '../../fixtures/product.model'; import { buildCalls, samePatternBuildCalls, @@ -22,11 +23,11 @@ import { similarPatternBuildCalls, SimilarPatternModelApiBuilderComponent, StubModelApiBuilderComponent, -} from '../fixtures/stub-model-api-builder'; +} from '../../fixtures/stub-model-api-builder'; describe('model API booter acceptance tests', () => { let app: BooterApp; - const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox')); + const sandbox = new TestSandbox(resolve(__dirname, '../../../.sandbox')); beforeEach('reset sandbox', () => sandbox.reset()); beforeEach(givenAppWithDataSource); @@ -35,7 +36,7 @@ describe('model API booter acceptance tests', () => { it('uses the correct model API builder', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); @@ -73,7 +74,7 @@ module.exports = { it('uses the API builder registered first if there is a duplicate pattern name', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); @@ -102,7 +103,7 @@ module.exports = { it('throws if there are no patterns matching', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); @@ -126,7 +127,7 @@ module.exports = { it('throws if the model class is invalid', async () => { await sandbox.copyFile( - resolve(__dirname, '../fixtures/product.model.js'), + resolve(__dirname, '../../fixtures/product.model.js'), 'models/product.model.js', ); diff --git a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts index b409babbe794..f50b11b89184 100644 --- a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts +++ b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts @@ -56,7 +56,7 @@ describe('CrudRestController for a simple Product model', () => { Object.freeze(PATCH_DATA); before(setupTestScenario); - after(stopTheApp); + after(stopApp); beforeEach(cleanDatabase); describe('create', () => { @@ -311,8 +311,8 @@ describe('CrudRestController for a simple Product model', () => { client = createRestAppClient(app); } - async function stopTheApp() { - await app.stop(); + async function stopApp() { + if (app?.state === 'started') await app?.stop(); } async function cleanDatabase() { diff --git a/packages/rest-crud/src/__tests__/fixtures/application.ts b/packages/rest-crud/src/__tests__/fixtures/application.ts new file mode 100644 index 000000000000..04f06508a1c7 --- /dev/null +++ b/packages/rest-crud/src/__tests__/fixtures/application.ts @@ -0,0 +1,16 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/boot +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BootMixin} from '@loopback/boot'; +import {ApplicationConfig} from '@loopback/core'; +import {RepositoryMixin} from '@loopback/repository'; +import {RestApplication} from '@loopback/rest'; + +export class BooterApp extends BootMixin(RepositoryMixin(RestApplication)) { + constructor(options?: ApplicationConfig) { + super(options); + this.projectRoot = __dirname; + } +} diff --git a/packages/boot/src/__tests__/fixtures/datasource.artifact.ts b/packages/rest-crud/src/__tests__/fixtures/datasource.artifact.ts similarity index 100% rename from packages/boot/src/__tests__/fixtures/datasource.artifact.ts rename to packages/rest-crud/src/__tests__/fixtures/datasource.artifact.ts diff --git a/packages/boot/src/__tests__/fixtures/no-entity.model.ts b/packages/rest-crud/src/__tests__/fixtures/no-entity.model.ts similarity index 100% rename from packages/boot/src/__tests__/fixtures/no-entity.model.ts rename to packages/rest-crud/src/__tests__/fixtures/no-entity.model.ts diff --git a/packages/rest-crud/src/__tests__/fixtures/package.json b/packages/rest-crud/src/__tests__/fixtures/package.json new file mode 100644 index 000000000000..24d9646e6635 --- /dev/null +++ b/packages/rest-crud/src/__tests__/fixtures/package.json @@ -0,0 +1,19 @@ +{ + "name": "boot-test-app", + "version": "1.0.0", + "description": "boot-test-app", + "keywords": [ + "loopback-application", + "loopback" + ], + "engines": { + "node": ">=10" + }, + "scripts": { + }, + "repository": { + "type": "git" + }, + "author": "IBM Corp.", + "license": "MIT" +} diff --git a/packages/boot/src/__tests__/fixtures/product.model.ts b/packages/rest-crud/src/__tests__/fixtures/product.model.ts similarity index 100% rename from packages/boot/src/__tests__/fixtures/product.model.ts rename to packages/rest-crud/src/__tests__/fixtures/product.model.ts diff --git a/packages/boot/src/__tests__/fixtures/product.repository.ts b/packages/rest-crud/src/__tests__/fixtures/product.repository.ts similarity index 100% rename from packages/boot/src/__tests__/fixtures/product.repository.ts rename to packages/rest-crud/src/__tests__/fixtures/product.repository.ts diff --git a/packages/boot/src/__tests__/fixtures/stub-model-api-builder.ts b/packages/rest-crud/src/__tests__/fixtures/stub-model-api-builder.ts similarity index 100% rename from packages/boot/src/__tests__/fixtures/stub-model-api-builder.ts rename to packages/rest-crud/src/__tests__/fixtures/stub-model-api-builder.ts diff --git a/packages/rest-crud/src/crud-rest.component.ts b/packages/rest-crud/src/crud-rest.component.ts index a067b98811a1..c8331b160a41 100644 --- a/packages/rest-crud/src/crud-rest.component.ts +++ b/packages/rest-crud/src/crud-rest.component.ts @@ -3,9 +3,17 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -import {Binding, Component, createBindingFromClass} from '@loopback/core'; +import {Booter} from '@loopback/booter'; +import { + Binding, + Component, + Constructor, + createBindingFromClass, +} from '@loopback/core'; +import {ModelApiBooter} from '@loopback/model-api-builder'; import {CrudRestApiBuilder} from './crud-rest.api-builder'; export class CrudRestComponent implements Component { bindings: Binding[] = [createBindingFromClass(CrudRestApiBuilder)]; + booters: Constructor[] = [ModelApiBooter]; } diff --git a/packages/rest-crud/tsconfig.json b/packages/rest-crud/tsconfig.json index e36098a74a4c..1011c3d05be5 100644 --- a/packages/rest-crud/tsconfig.json +++ b/packages/rest-crud/tsconfig.json @@ -10,6 +10,12 @@ "src" ], "references": [ + { + "path": "../boot/tsconfig.json" + }, + { + "path": "../booter/tsconfig.json" + }, { "path": "../core/tsconfig.json" }, diff --git a/tsconfig.json b/tsconfig.json index f59df2688492..f0dffb5dfeef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -142,6 +142,9 @@ { "path": "packages/booter-lb3app/tsconfig.json" }, + { + "path": "packages/booter/tsconfig.json" + }, { "path": "packages/context/tsconfig.json" },