Skip to content

Commit

Permalink
feat(boot): add a booter for life cycle scripts
Browse files Browse the repository at this point in the history
See #2034
  • Loading branch information
raymondfeng committed Nov 27, 2018
1 parent a4efcea commit 469f594
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 8 deletions.
12 changes: 7 additions & 5 deletions packages/boot/src/boot.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Bootstrapper} from './bootstrapper';
import {Component, Application, CoreBindings} from '@loopback/core';
import {inject, BindingScope} from '@loopback/context';
import {BindingScope, inject} from '@loopback/context';
import {Application, Component, CoreBindings} from '@loopback/core';
import {
ApplicationMetadataBooter,
ControllerBooter,
RepositoryBooter,
DataSourceBooter,
RepositoryBooter,
ServiceBooter,
ApplicationMetadataBooter,
LifeCycleScriptBooter,
} from './booters';
import {Bootstrapper} from './bootstrapper';
import {BootBindings} from './keys';

/**
Expand All @@ -29,6 +30,7 @@ export class BootComponent implements Component {
RepositoryBooter,
ServiceBooter,
DataSourceBooter,
LifeCycleScriptBooter,
];

/**
Expand Down
1 change: 1 addition & 0 deletions packages/boot/src/booters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './datasource.booter';
export * from './repository.booter';
export * from './service.booter';
export * from './application-metadata.booter';
export * from './lifecyle-script.booter';
70 changes: 70 additions & 0 deletions packages/boot/src/booters/lifecyle-script.booter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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, inject} from '@loopback/context';
import {
Application,
asLifeCycleObserverBinding,
CoreBindings,
isLifeCycleObserverClass,
LifeCycleObserver,
} from '@loopback/core';
import {ArtifactOptions} from '../interfaces';
import {BootBindings} from '../keys';
import {BaseArtifactBooter} from './base-artifact.booter';

type LifeCycleObserverClass = Constructor<LifeCycleObserver>;

/**
* A class that extends BaseArtifactBooter to boot the 'LifeCycleScript' artifact type.
*
* Supported phases: configure, discover, load
*
* @param app Application instance
* @param projectRoot Root of User Project relative to which all paths are resolved
* @param [bootConfig] LifeCycleScript Artifact Options Object
*/
export class LifeCycleScriptBooter extends BaseArtifactBooter {
observers: LifeCycleObserverClass[];

constructor(
@inject(CoreBindings.APPLICATION_INSTANCE)
public app: Application,
@inject(BootBindings.PROJECT_ROOT) projectRoot: string,
@inject(`${BootBindings.BOOT_OPTIONS}#scripts`)
public scriptConfig: ArtifactOptions = {},
) {
super(
projectRoot,
// Set LifeCycleScript Booter Options if passed in via bootConfig
Object.assign({}, LifeCycleScriptDefaults, scriptConfig),
);
}

/**
* Uses super method to get a list of Artifact classes. Boot each file by
* creating a DataSourceConstructor and binding it to the application class.
*/
async load() {
await super.load();

this.observers = this.classes.filter(isLifeCycleObserverClass);
for (const observer of this.observers) {
this.app
.bind(`lifeCycleObservers.${observer.name}`)
.toClass(observer)
.apply(asLifeCycleObserverBinding);
}
}
}

/**
* Default ArtifactOptions for DataSourceBooter.
*/
export const LifeCycleScriptDefaults: ArtifactOptions = {
dirs: ['scripts'],
extensions: ['.script.js'],
nested: true,
};
6 changes: 3 additions & 3 deletions packages/boot/src/booters/service.booter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import {BootBindings} from '../keys';
type ServiceProviderClass = Constructor<Provider<object>>;

/**
* A class that extends BaseArtifactBooter to boot the 'DataSource' artifact type.
* A class that extends BaseArtifactBooter to boot the 'Service' artifact type.
* Discovered DataSources are bound using `app.controller()`.
*
* Supported phases: configure, discover, load
*
* @param app Application instance
* @param projectRoot Root of User Project relative to which all paths are resolved
* @param [bootConfig] DataSource Artifact Options Object
* @param [bootConfig] Service Artifact Options Object
*/
export class ServiceBooter extends BaseArtifactBooter {
serviceProviders: ServiceProviderClass[];
Expand All @@ -34,7 +34,7 @@ export class ServiceBooter extends BaseArtifactBooter {
) {
super(
projectRoot,
// Set DataSource Booter Options if passed in via bootConfig
// Set Service Booter Options if passed in via bootConfig
Object.assign({}, ServiceDefaults, serviceConfig),
);
}
Expand Down
18 changes: 18 additions & 0 deletions packages/boot/test/fixtures/lifecycle-script.artifact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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 {LifeCycleObserver} from '@loopback/core';

export class MyLifeCycleObserver implements LifeCycleObserver {
status = '';

async start() {
this.status = 'started';
}

stop() {
this.status = 'stopped';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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 {expect, TestSandbox} from '@loopback/testlab';
import {resolve} from 'path';
import {BooterApp} from '../fixtures/application';
import {CoreBindings, BindingScope} from '@loopback/core';

describe('lifecycle script booter integration tests', () => {
const SANDBOX_PATH = resolve(__dirname, '../../.sandbox');
const sandbox = new TestSandbox(SANDBOX_PATH);

const SCRIPTS_PREFIX = 'lifeCycleObservers';
const SCRIPTS_TAG = CoreBindings.LIFE_CYCLE_OBSERVER_TAG;

let app: BooterApp;

beforeEach('reset sandbox', () => sandbox.reset());
beforeEach(getApp);

it('boots scripts when app.boot() is called', async () => {
const expectedBinding = {
key: `${SCRIPTS_PREFIX}.MyLifeCycleObserver`,
tags: [SCRIPTS_TAG],
scope: BindingScope.SINGLETON,
};

await app.boot();

const bindings = app
.findByTag(SCRIPTS_TAG)
.map(b => ({key: b.key, tags: b.tagNames, scope: b.scope}));
expect(bindings).to.containEql(expectedBinding);
});

async function getApp() {
await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js'));
await sandbox.copyFile(
resolve(__dirname, '../fixtures/lifecycle-script.artifact.js'),
'scripts/lifecycle-script.script.js',
);

const MyApp = require(resolve(SANDBOX_PATH, 'application.js')).BooterApp;
app = new MyApp();
}
});

0 comments on commit 469f594

Please sign in to comment.