Skip to content

Commit

Permalink
refactor: move boot related stuff from core to a mixin
Browse files Browse the repository at this point in the history
  • Loading branch information
virkt25 committed Feb 21, 2018
1 parent edfe2ff commit e55b757
Show file tree
Hide file tree
Showing 62 changed files with 481 additions and 1,599 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @bajtos @raymondfeng @kjdelisle

packages/authentication/* @bajtos @kjdelisle
packages/boot/* @raymondfeng @virkt25
packages/build/* @bajtos @raymondfeng
packages/cli/* @raymondfeng @kjdelisle @shimks
packages/context/* @bajtos @raymondfeng @kjdelisle
Expand Down
1 change: 1 addition & 0 deletions MONOREPO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The [loopback-next](https://github.com/strongloop/loopback-next) repository uses
|[metadata](packages/metadata) |@loopback/metadata | Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata |
|[context](packages/context) |@loopback/context | Facilities to manage artifacts and their dependencies in your Node.js applications. The module exposes TypeScript/JavaScript APIs and decorators to register artifacts, declare dependencies, and resolve artifacts by keys. It also serves as an IoC container to support dependency injection. |
|[core](packages/core) |@loopback/core | Define and implement core constructs such as Application and Component |
|[boot](packages/boot) |@loopback/boot | Convention based Bootstrapper and Booters |
|[openapi-spec](packages/openapi-spec) |@loopback/openapi-spec | TypeScript type definitions for OpenAPI Spec/Swagger documents |
|[openapi-spec-builder](packages/openapi-spec-builder) |@loopback/openapi-spec-builder | Builders to create OpenAPI (Swagger) specification documents in tests |
|[openapi-v2](packages/openapi-v2) |@loopback/openapi-v2 | Decorators that annotate LoopBack artifacts with OpenAPI v2 (Swagger) metadata and utilities that transform LoopBack metadata to OpenAPI v2 (Swagger) specifications|
Expand Down
49 changes: 28 additions & 21 deletions packages/boot/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @loopback/boot

A collection of Booters for LoopBack Applications
A convention based project Bootstrapper and Booters for LoopBack Applications

# Overview

Expand All @@ -11,9 +11,12 @@ phases to complete its task.
An example task of a Booter may be to discover and bind all artifacts of a
given type.

A BootStrapper is needed to manage the Booters and to run them. This is packaged
in BootComponent. Add `BootComponent` to your `Application` to use the default
`BootStrapper` and `Booters`.
A Bootstrapper is needed to manage the Booters and execute them. This is provided
in this package. For ease of use, everything needed is packages using a BootMixin.
This Mixin will add convenience methods such as `boot` and `booter`, as well as
properties needed for Bootstrapper such as `projectRoot`. The Mixin also adds the
`BootComponent` to your `Application` which binds the `Bootstrapper` and default
`Booters` made available by this package.

## Installation

Expand All @@ -25,31 +28,35 @@ $ npm i @loopback/boot

```ts
import {Application} from '@loopback/core';
import {BootComponent} from '@loopback/boot';
const app = new Application();
app.component(BootComponent);

await app.boot({
projectRoot: __dirname,
booters: [RepositoryBooter], // Register Booters as part of call to app.boot()
controllers: {
dirs: ['ctrl'],
extensions: ['.ctrl.js'],
nested: true
import {BootMixin} from '@loopback/boot';
class BootApp extends BootMixin(Application) {}

const app = new BootApp();
app.projectRoot = __dirname;
app.bootOptions = {
controlles: {
// Configure ControllerBooter Conventiones here.
}
}); // Booter gets run by the Application
}

await app.boot();
await app.start();
```

### BootOptions
List of Options available on BootOptions Object.

|Option|Type|Description|
|-|-|-|
|`projectRoot`|`string`|Absolute path to the root of the LoopBack 4 Project. **Required**|
|`booters`|`Constructor<Booter>[]`|Array of Booters to bind before booting. *Optional*|
|`filter`|`Object`|An Object to filter Booters and phases for finer control over the boot process. *Optional*|
|`filter.booters`|`string[]`|Names of Booters that should be run (all other bound booters will be ignored).|
|`filter.phases`|`string[]`|Names of phases and order that they should be run in.|
|`controllers`|`ArtifactOptions`|ControllerBooter convention options|

### ArtifactOptions

**Add Table for ArtifactOptions**

### BootExecOptions

**Add Table for BootExecOptions**

## Available Booters

Expand Down
2 changes: 2 additions & 0 deletions packages/boot/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
"src/booters/controller.booter.ts",
"src/booters/index.ts",
"src/boot.component.ts",
"src/boot.mixin.ts",
"src/bootstrapper.ts",
"src/index.ts",
"src/interfaces.ts",
"src/keys.ts"
],
"codeSectionDepth": 4
Expand Down
8 changes: 8 additions & 0 deletions packages/boot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"engines": {
"node": ">=8"
},
"publishConfig": {
"access": "public"
},
"scripts": {
"acceptance": "lb-mocha \"DIST/test/acceptance/**/*.js\"",
"build": "npm run build:dist",
Expand All @@ -20,6 +23,7 @@
"verify": "npm pack && tar xf loopback-boot*.tgz && tree package && npm run clean"
},
"author": "IBM",
"copyright.owner": "IBM Corp.",
"license": "MIT",
"dependencies": {
"@loopback/context": "^4.0.0-alpha.27",
Expand All @@ -38,8 +42,12 @@
"files": [
"README.md",
"index.js",
"index.js.map",
"index.d.ts",
"dist/src",
"dist/index.js",
"dist/index.js.map",
"dist/index.d.ts",
"api-docs",
"src"
],
Expand Down
3 changes: 2 additions & 1 deletion packages/boot/src/boot.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Bootstrapper} from './bootstrapper';
import {Component, Application, CoreBindings} from '@loopback/core';
import {inject, BindingScope} from '@loopback/context';
import {ControllerBooter} from './booters';
import {BootBindings} from './keys';

/**
* BootComponent is used to export the default list of Booter's made
Expand All @@ -25,7 +26,7 @@ export class BootComponent implements Component {
constructor(@inject(CoreBindings.APPLICATION_INSTANCE) app: Application) {
// Bound as a SINGLETON so it can be cached as it has no state
app
.bind(CoreBindings.BOOTSTRAPPER)
.bind(BootBindings.BOOTSTRAPPER_KEY)
.toClass(Bootstrapper)
.inScope(BindingScope.SINGLETON);
}
Expand Down
142 changes: 142 additions & 0 deletions packages/boot/src/boot.mixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright IBM Corp. 2018. 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 {Constructor, Binding, BindingScope, Context} from '@loopback/context';
import {Booter, BootOptions, Bootable} from './interfaces';
import {BootComponent} from './boot.component';
import {Bootstrapper} from './bootstrapper';
import {BootBindings} from './keys';

// Binding is re-exported as Binding / Booter types are needed when consuming
// BootMixin and this allows a user to import them from the same package (UX!)
export {Binding};

/**
* Mixin for @loopback/boot. This Mixin provides the following:
* - Implements the Bootable Interface as follows.
* - 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)
* - 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`
* - Adds `mountComponentBooters` which binds Booters to the application from `component.booters[]`
*
* ******************** NOTE ********************
* Trying to constrain the type of this Mixin (or any Mixin) will cause errors.
* For example, constraining this Mixin to type Application require all types using by
* Application to be imported (including it's dependencies such as ResolutionSession).
* Another issue was that if a Mixin that is type constrained is used with another Mixin
* that is not, it will result in an error.
* Example (class MyApp extends BootMixin(RepositoryMixin(Application))) {};
********************* END OF NOTE ********************
*/
// tslint:disable-next-line:no-any
export function BootMixin<T extends Constructor<any>>(superClass: T) {
return class extends superClass implements Bootable {
projectRoot: string;
bootOptions?: BootOptions;

// tslint:disable-next-line:no-any
constructor(...args: any[]) {
super(...args);
this.component(BootComponent);

// We Dynamically bind the Project Root and Boot Options so these values can
// be used to resolve an instance of the Bootstrapper (as they are dependencies)
this.bind(BootBindings.PROJECT_ROOT).toDynamicValue(
() => this.projectRoot,
);
this.bind(BootBindings.BOOT_OPTIONS).toDynamicValue(
() => this.bootOptions,
);
}

/**
* Convenience method to call bootstrapper.boot() by resolving bootstrapper
*/
async boot(): Promise<void> {
// Get a instance of the BootStrapper
const bootstrapper: Bootstrapper = await this.get(
BootBindings.BOOTSTRAPPER_KEY,
);

await bootstrapper.boot();
}

/**
* Given a N number of Booter Classes, this method binds them using the
* prefix and tag expected by the Bootstrapper.
*
* @param booterCls Booter classes to bind to the Application
*
* ```ts
* app.booters(MyBooter, MyOtherBooter)
* ```
*/
booters(...booterCls: Constructor<Booter>[]): Binding[] {
// tslint:disable-next-line:no-any
return booterCls.map(cls => _bindBooter(<Context>(<any>this), cls));
}

/**
* Override to ensure any Booter's on a Component are also mounted.
*
* @param component The component to add.
*
* ```ts
*
* export class ProductComponent {
* booters = [ControllerBooter, RepositoryBooter];
* providers = {
* [AUTHENTICATION_STRATEGY]: AuthStrategy,
* [AUTHORIZATION_ROLE]: Role,
* };
* };
*
* app.component(ProductComponent);
* ```
*/
public component(component: Constructor<{}>) {
super.component(component);
this.mountComponentBooters(component);
}

/**
* Get an instance of a component and mount all it's
* booters. This function is intended to be used internally
* by component()
*
* @param component The component to mount booters of
*/
mountComponentBooters(component: Constructor<{}>) {
const componentKey = `components.${component.name}`;
const compInstance = this.getSync(componentKey);

if (compInstance.booters) {
this.booters(...compInstance.booters);
}
}
};
}

/**
* 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<Booter>,
): Binding {
return ctx
.bind(`${BootBindings.BOOTER_PREFIX}.${booterCls.name}`)
.toClass(booterCls)
.inScope(BindingScope.CONTEXT)
.tag(BootBindings.BOOTER_TAG);
}
Loading

0 comments on commit e55b757

Please sign in to comment.