Skip to content

Commit 6912f76

Browse files
committed
feat(boot): add a booter for life cycle scripts
See #2034
1 parent 27c8127 commit 6912f76

File tree

6 files changed

+165
-8
lines changed

6 files changed

+165
-8
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/boot
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {LifeCycleObserver} from '@loopback/core';
7+
8+
/**
9+
* An mock-up `LifeCycleObserver`. Please note that `start` and `stop` methods
10+
* can be async or sync.
11+
*/
12+
export class MyLifeCycleObserver implements LifeCycleObserver {
13+
status = '';
14+
15+
/**
16+
* Handling `start` event asynchronously
17+
*/
18+
async start() {
19+
// Perform some work asynchronously
20+
// await startSomeAsyncWork(...)
21+
this.status = 'started';
22+
}
23+
24+
/**
25+
* Handling `stop` event synchronously.
26+
*/
27+
stop() {
28+
this.status = 'stopped';
29+
}
30+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/boot
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {
7+
BindingScope,
8+
ContextTags,
9+
CoreBindings,
10+
CoreTags,
11+
} from '@loopback/core';
12+
import {expect, TestSandbox} from '@loopback/testlab';
13+
import {resolve} from 'path';
14+
import {BooterApp} from '../fixtures/application';
15+
16+
describe('lifecycle script booter integration tests', () => {
17+
const SANDBOX_PATH = resolve(__dirname, '../../.sandbox');
18+
const sandbox = new TestSandbox(SANDBOX_PATH);
19+
20+
const OBSERVER_PREFIX = CoreBindings.LIFE_CYCLE_OBSERVERS;
21+
const OBSERVER_TAG = CoreTags.LIFE_CYCLE_OBSERVER;
22+
23+
let app: BooterApp;
24+
25+
beforeEach('reset sandbox', () => sandbox.reset());
26+
beforeEach(getApp);
27+
28+
it('boots life cycle observers when app.boot() is called', async () => {
29+
const expectedBinding = {
30+
key: `${OBSERVER_PREFIX}.MyLifeCycleObserver`,
31+
tags: [ContextTags.TYPE, OBSERVER_TAG],
32+
scope: BindingScope.SINGLETON,
33+
};
34+
35+
await app.boot();
36+
37+
const bindings = app
38+
.findByTag(OBSERVER_TAG)
39+
.map(b => ({key: b.key, tags: b.tagNames, scope: b.scope}));
40+
expect(bindings).to.containEql(expectedBinding);
41+
});
42+
43+
async function getApp() {
44+
await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js'));
45+
await sandbox.copyFile(
46+
resolve(__dirname, '../fixtures/lifecycle-observer.artifact.js'),
47+
'observers/lifecycle-observer.observer.js',
48+
);
49+
50+
const MyApp = require(resolve(SANDBOX_PATH, 'application.js')).BooterApp;
51+
app = new MyApp();
52+
}
53+
});

packages/boot/src/boot.component.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
import {Bootstrapper} from './bootstrapper';
7-
import {Component, Application, CoreBindings} from '@loopback/core';
8-
import {inject, BindingScope} from '@loopback/context';
6+
import {BindingScope, inject} from '@loopback/context';
7+
import {Application, Component, CoreBindings} from '@loopback/core';
98
import {
9+
ApplicationMetadataBooter,
1010
ControllerBooter,
11-
RepositoryBooter,
1211
DataSourceBooter,
12+
RepositoryBooter,
1313
ServiceBooter,
14-
ApplicationMetadataBooter,
14+
LifeCycleObserverBooter,
1515
} from './booters';
16+
import {Bootstrapper} from './bootstrapper';
1617
import {BootBindings} from './keys';
1718

1819
/**
@@ -29,6 +30,7 @@ export class BootComponent implements Component {
2930
RepositoryBooter,
3031
ServiceBooter,
3132
DataSourceBooter,
33+
LifeCycleObserverBooter,
3234
];
3335

3436
/**

packages/boot/src/booters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './datasource.booter';
1010
export * from './repository.booter';
1111
export * from './service.booter';
1212
export * from './application-metadata.booter';
13+
export * from './lifecyle-observer.booter';
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright IBM Corp. 2018. All Rights Reserved.
2+
// Node module: @loopback/boot
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {Constructor, inject} from '@loopback/context';
7+
import {
8+
Application,
9+
CoreBindings,
10+
isLifeCycleObserverClass,
11+
LifeCycleObserver,
12+
} from '@loopback/core';
13+
import * as debugFactory from 'debug';
14+
import {ArtifactOptions} from '../interfaces';
15+
import {BootBindings} from '../keys';
16+
import {BaseArtifactBooter} from './base-artifact.booter';
17+
18+
const debug = debugFactory('loopback:boot:lifecycle-observer-booter');
19+
20+
type LifeCycleObserverClass = Constructor<LifeCycleObserver>;
21+
22+
/**
23+
* A class that extends BaseArtifactBooter to boot the 'LifeCycleObserver' artifact type.
24+
*
25+
* Supported phases: configure, discover, load
26+
*
27+
* @param app Application instance
28+
* @param projectRoot Root of User Project relative to which all paths are resolved
29+
* @param [bootConfig] LifeCycleObserver Artifact Options Object
30+
*/
31+
export class LifeCycleObserverBooter extends BaseArtifactBooter {
32+
observers: LifeCycleObserverClass[];
33+
34+
constructor(
35+
@inject(CoreBindings.APPLICATION_INSTANCE)
36+
public app: Application,
37+
@inject(BootBindings.PROJECT_ROOT) projectRoot: string,
38+
@inject(`${BootBindings.BOOT_OPTIONS}#observers`)
39+
public observerConfig: ArtifactOptions = {},
40+
) {
41+
super(
42+
projectRoot,
43+
// Set LifeCycleObserver Booter Options if passed in via bootConfig
44+
Object.assign({}, LifeCycleObserverDefaults, observerConfig),
45+
);
46+
}
47+
48+
/**
49+
* Uses super method to get a list of Artifact classes. Boot each file by
50+
* creating a DataSourceConstructor and binding it to the application class.
51+
*/
52+
async load() {
53+
await super.load();
54+
55+
this.observers = this.classes.filter(isLifeCycleObserverClass);
56+
for (const observer of this.observers) {
57+
debug('Bind life cycle observer: %s', observer.name);
58+
const binding = this.app.lifeCycleObserver(observer);
59+
debug('Binding created for life cycle observer: %j', binding);
60+
}
61+
}
62+
}
63+
64+
/**
65+
* Default ArtifactOptions for DataSourceBooter.
66+
*/
67+
export const LifeCycleObserverDefaults: ArtifactOptions = {
68+
dirs: ['observers'],
69+
extensions: ['.observer.js'],
70+
nested: true,
71+
};

packages/boot/src/booters/service.booter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ import {BootBindings} from '../keys';
1313
type ServiceProviderClass = Constructor<Provider<object>>;
1414

1515
/**
16-
* A class that extends BaseArtifactBooter to boot the 'DataSource' artifact type.
16+
* A class that extends BaseArtifactBooter to boot the 'Service' artifact type.
1717
* Discovered DataSources are bound using `app.controller()`.
1818
*
1919
* Supported phases: configure, discover, load
2020
*
2121
* @param app Application instance
2222
* @param projectRoot Root of User Project relative to which all paths are resolved
23-
* @param [bootConfig] DataSource Artifact Options Object
23+
* @param [bootConfig] Service Artifact Options Object
2424
*/
2525
export class ServiceBooter extends BaseArtifactBooter {
2626
serviceProviders: ServiceProviderClass[];
@@ -34,7 +34,7 @@ export class ServiceBooter extends BaseArtifactBooter {
3434
) {
3535
super(
3636
projectRoot,
37-
// Set DataSource Booter Options if passed in via bootConfig
37+
// Set Service Booter Options if passed in via bootConfig
3838
Object.assign({}, ServiceDefaults, serviceConfig),
3939
);
4040
}

0 commit comments

Comments
 (0)