@loopback/core 4.0.0-alpha.8 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +25 -0
- package/README.md +77 -2
- package/dist/application.d.ts +341 -0
- package/dist/application.js +554 -0
- package/dist/application.js.map +1 -0
- package/dist/component.d.ts +80 -0
- package/dist/component.js +59 -0
- package/dist/component.js.map +1 -0
- package/dist/extension-point.d.ts +121 -0
- package/dist/extension-point.js +227 -0
- package/dist/extension-point.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +97 -0
- package/dist/keys.js +109 -0
- package/dist/keys.js.map +1 -0
- package/dist/lifecycle-registry.d.ts +91 -0
- package/dist/lifecycle-registry.js +191 -0
- package/dist/lifecycle-registry.js.map +1 -0
- package/dist/lifecycle.d.ts +47 -0
- package/dist/lifecycle.js +56 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/mixin-target.d.ts +60 -0
- package/{lib/internal-types.js → dist/mixin-target.js} +2 -3
- package/dist/mixin-target.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/{lib6/src/Component.js → dist/server.js} +2 -1
- package/dist/server.js.map +1 -0
- package/dist/service.d.ts +63 -0
- package/dist/service.js +151 -0
- package/dist/service.js.map +1 -0
- package/package.json +39 -37
- package/src/application.ts +719 -0
- package/src/component.ts +155 -0
- package/src/extension-point.ts +312 -0
- package/src/index.ts +29 -0
- package/src/keys.ts +144 -0
- package/src/lifecycle-registry.ts +268 -0
- package/src/lifecycle.ts +90 -0
- package/src/mixin-target.ts +69 -0
- package/src/server.ts +22 -0
- package/src/service.ts +211 -0
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/lib/Component.d.ts +0 -2
- package/lib/Component.js +0 -7
- package/lib/Sequence.d.ts +0 -14
- package/lib/Sequence.js +0 -58
- package/lib/application.d.ts +0 -52
- package/lib/application.js +0 -79
- package/lib/application.js.map +0 -1
- package/lib/component.js.map +0 -1
- package/lib/handlers/http.d.ts +0 -3
- package/lib/handlers/http.js +0 -13
- package/lib/handlers/http.js.map +0 -1
- package/lib/handlers/index.d.ts +0 -1
- package/lib/handlers/index.js +0 -11
- package/lib/handlers/index.js.map +0 -1
- package/lib/http-handler.d.ts +0 -16
- package/lib/http-handler.js +0 -62
- package/lib/http-handler.js.map +0 -1
- package/lib/index.d.ts +0 -16
- package/lib/index.js +0 -33
- package/lib/index.js.map +0 -1
- package/lib/internal-types.d.ts +0 -20
- package/lib/internal-types.js.map +0 -1
- package/lib/invoke.d.ts +0 -5
- package/lib/invoke.js +0 -30
- package/lib/parser.d.ts +0 -3
- package/lib/parser.js +0 -73
- package/lib/parser.js.map +0 -1
- package/lib/promisify.d.ts +0 -3
- package/lib/promisify.js +0 -34
- package/lib/promisify.js.map +0 -1
- package/lib/router/SwaggerRouter.d.ts +0 -39
- package/lib/router/SwaggerRouter.js +0 -205
- package/lib/router/metadata.d.ts +0 -12
- package/lib/router/metadata.js +0 -30
- package/lib/router/metadata.js.map +0 -1
- package/lib/router/routing-table.d.ts +0 -16
- package/lib/router/routing-table.js +0 -95
- package/lib/router/routing-table.js.map +0 -1
- package/lib/sequence.js.map +0 -1
- package/lib/server.d.ts +0 -23
- package/lib/server.js +0 -53
- package/lib/server.js.map +0 -1
- package/lib/src/Component.d.ts +0 -2
- package/lib/src/Component.js +0 -6
- package/lib/src/Sequence.d.ts +0 -6
- package/lib/src/Sequence.js +0 -26
- package/lib/src/application.d.ts +0 -27
- package/lib/src/application.js +0 -70
- package/lib/src/index.d.ts +0 -13
- package/lib/src/index.js +0 -29
- package/lib/src/invoke.d.ts +0 -5
- package/lib/src/invoke.js +0 -34
- package/lib/src/parser.d.ts +0 -3
- package/lib/src/parser.js +0 -72
- package/lib/src/promisify.d.ts +0 -3
- package/lib/src/promisify.js +0 -33
- package/lib/src/router/SwaggerRouter.d.ts +0 -53
- package/lib/src/router/SwaggerRouter.js +0 -101
- package/lib/src/router/metadata.d.ts +0 -13
- package/lib/src/router/metadata.js +0 -29
- package/lib/src/router/routing-table.d.ts +0 -13
- package/lib/src/router/routing-table.js +0 -83
- package/lib/src/server.d.ts +0 -17
- package/lib/src/server.js +0 -40
- package/lib/writer.d.ts +0 -4
- package/lib/writer.js +0 -24
- package/lib/writer.js.map +0 -1
- package/lib6/Component.d.ts +0 -2
- package/lib6/Component.js +0 -7
- package/lib6/Sequence.d.ts +0 -14
- package/lib6/Sequence.js +0 -68
- package/lib6/application.d.ts +0 -52
- package/lib6/application.js +0 -79
- package/lib6/application.js.map +0 -1
- package/lib6/component.js.map +0 -1
- package/lib6/handlers/http.d.ts +0 -3
- package/lib6/handlers/http.js +0 -21
- package/lib6/handlers/http.js.map +0 -1
- package/lib6/handlers/index.d.ts +0 -1
- package/lib6/handlers/index.js +0 -11
- package/lib6/handlers/index.js.map +0 -1
- package/lib6/http-handler.d.ts +0 -16
- package/lib6/http-handler.js +0 -72
- package/lib6/http-handler.js.map +0 -1
- package/lib6/index.d.ts +0 -16
- package/lib6/index.js +0 -33
- package/lib6/index.js.map +0 -1
- package/lib6/internal-types.d.ts +0 -20
- package/lib6/internal-types.js +0 -8
- package/lib6/internal-types.js.map +0 -1
- package/lib6/invoke.d.ts +0 -5
- package/lib6/invoke.js +0 -30
- package/lib6/parser.d.ts +0 -3
- package/lib6/parser.js +0 -83
- package/lib6/parser.js.map +0 -1
- package/lib6/promisify.d.ts +0 -3
- package/lib6/promisify.js +0 -34
- package/lib6/promisify.js.map +0 -1
- package/lib6/router/SwaggerRouter.d.ts +0 -39
- package/lib6/router/SwaggerRouter.js +0 -205
- package/lib6/router/metadata.d.ts +0 -12
- package/lib6/router/metadata.js +0 -30
- package/lib6/router/metadata.js.map +0 -1
- package/lib6/router/routing-table.d.ts +0 -16
- package/lib6/router/routing-table.js +0 -95
- package/lib6/router/routing-table.js.map +0 -1
- package/lib6/sequence.js.map +0 -1
- package/lib6/server.d.ts +0 -23
- package/lib6/server.js +0 -63
- package/lib6/server.js.map +0 -1
- package/lib6/src/Component.d.ts +0 -2
- package/lib6/src/Sequence.d.ts +0 -6
- package/lib6/src/Sequence.js +0 -36
- package/lib6/src/application.d.ts +0 -27
- package/lib6/src/application.js +0 -70
- package/lib6/src/index.d.ts +0 -13
- package/lib6/src/index.js +0 -29
- package/lib6/src/invoke.d.ts +0 -5
- package/lib6/src/invoke.js +0 -34
- package/lib6/src/parser.d.ts +0 -3
- package/lib6/src/parser.js +0 -82
- package/lib6/src/promisify.d.ts +0 -3
- package/lib6/src/promisify.js +0 -33
- package/lib6/src/router/SwaggerRouter.d.ts +0 -53
- package/lib6/src/router/SwaggerRouter.js +0 -101
- package/lib6/src/router/metadata.d.ts +0 -13
- package/lib6/src/router/metadata.js +0 -29
- package/lib6/src/router/routing-table.d.ts +0 -13
- package/lib6/src/router/routing-table.js +0 -83
- package/lib6/src/server.d.ts +0 -17
- package/lib6/src/server.js +0 -50
- package/lib6/writer.d.ts +0 -4
- package/lib6/writer.js +0 -24
- package/lib6/writer.js.map +0 -1
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
// Copyright IBM Corp. and LoopBack contributors 2017,2020. All Rights Reserved.
|
|
2
|
+
// Node module: @loopback/core
|
|
3
|
+
// This file is licensed under the MIT License.
|
|
4
|
+
// License text available at https://opensource.org/licenses/MIT
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Binding,
|
|
8
|
+
BindingFromClassOptions,
|
|
9
|
+
BindingScope,
|
|
10
|
+
Constructor,
|
|
11
|
+
Context,
|
|
12
|
+
createBindingFromClass,
|
|
13
|
+
DynamicValueProviderClass,
|
|
14
|
+
generateUniqueId,
|
|
15
|
+
Interceptor,
|
|
16
|
+
InterceptorBindingOptions,
|
|
17
|
+
JSONObject,
|
|
18
|
+
Provider,
|
|
19
|
+
registerInterceptor,
|
|
20
|
+
ValueOrPromise,
|
|
21
|
+
} from '@loopback/context';
|
|
22
|
+
import assert from 'assert';
|
|
23
|
+
import debugFactory from 'debug';
|
|
24
|
+
import {once} from 'events';
|
|
25
|
+
import {Component, mountComponent} from './component';
|
|
26
|
+
import {CoreBindings, CoreTags} from './keys';
|
|
27
|
+
import {
|
|
28
|
+
asLifeCycleObserver,
|
|
29
|
+
isLifeCycleObserverClass,
|
|
30
|
+
LifeCycleObserver,
|
|
31
|
+
} from './lifecycle';
|
|
32
|
+
import {LifeCycleObserverRegistry} from './lifecycle-registry';
|
|
33
|
+
import {Server} from './server';
|
|
34
|
+
import {createServiceBinding, ServiceOptions} from './service';
|
|
35
|
+
const debug = debugFactory('loopback:core:application');
|
|
36
|
+
const debugShutdown = debugFactory('loopback:core:application:shutdown');
|
|
37
|
+
const debugWarning = debugFactory('loopback:core:application:warning');
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A helper function to build constructor args for `Context`
|
|
41
|
+
* @param configOrParent - Application config or parent context
|
|
42
|
+
* @param parent - Parent context if the first arg is application config
|
|
43
|
+
*/
|
|
44
|
+
function buildConstructorArgs(
|
|
45
|
+
configOrParent?: ApplicationConfig | Context,
|
|
46
|
+
parent?: Context,
|
|
47
|
+
) {
|
|
48
|
+
let name: string | undefined;
|
|
49
|
+
let parentCtx: Context | undefined;
|
|
50
|
+
|
|
51
|
+
if (configOrParent instanceof Context) {
|
|
52
|
+
parentCtx = configOrParent;
|
|
53
|
+
name = undefined;
|
|
54
|
+
} else {
|
|
55
|
+
parentCtx = parent;
|
|
56
|
+
name = configOrParent?.name;
|
|
57
|
+
}
|
|
58
|
+
return [parentCtx, name];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Application is the container for various types of artifacts, such as
|
|
63
|
+
* components, servers, controllers, repositories, datasources, connectors,
|
|
64
|
+
* and models.
|
|
65
|
+
*/
|
|
66
|
+
export class Application extends Context implements LifeCycleObserver {
|
|
67
|
+
public readonly options: ApplicationConfig;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* A flag to indicate that the application is being shut down
|
|
71
|
+
*/
|
|
72
|
+
private _isShuttingDown = false;
|
|
73
|
+
private _shutdownOptions: ShutdownOptions;
|
|
74
|
+
private _signalListener: (signal: string) => Promise<void>;
|
|
75
|
+
|
|
76
|
+
private _initialized = false;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* State of the application
|
|
80
|
+
*/
|
|
81
|
+
private _state = 'created';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the state of the application. The initial state is `created` and it can
|
|
85
|
+
* transition as follows by `start` and `stop`:
|
|
86
|
+
*
|
|
87
|
+
* 1. start
|
|
88
|
+
* - !started -> starting -> started
|
|
89
|
+
* - started -> started (no-op)
|
|
90
|
+
* 2. stop
|
|
91
|
+
* - (started | initialized) -> stopping -> stopped
|
|
92
|
+
* - ! (started || initialized) -> stopped (no-op)
|
|
93
|
+
*
|
|
94
|
+
* Two types of states are expected:
|
|
95
|
+
* - stable, such as `started` and `stopped`
|
|
96
|
+
* - in process, such as `booting` and `starting`
|
|
97
|
+
*
|
|
98
|
+
* Operations such as `start` and `stop` can only be called at a stable state.
|
|
99
|
+
* The logic should immediately set the state to a new one indicating work in
|
|
100
|
+
* process, such as `starting` and `stopping`.
|
|
101
|
+
*/
|
|
102
|
+
public get state() {
|
|
103
|
+
return this._state;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create an application with the given parent context
|
|
108
|
+
* @param parent - Parent context
|
|
109
|
+
*/
|
|
110
|
+
constructor(parent: Context);
|
|
111
|
+
/**
|
|
112
|
+
* Create an application with the given configuration and parent context
|
|
113
|
+
* @param config - Application configuration
|
|
114
|
+
* @param parent - Parent context
|
|
115
|
+
*/
|
|
116
|
+
constructor(config?: ApplicationConfig, parent?: Context);
|
|
117
|
+
|
|
118
|
+
constructor(configOrParent?: ApplicationConfig | Context, parent?: Context) {
|
|
119
|
+
// super() has to be first statement for a constructor
|
|
120
|
+
super(...buildConstructorArgs(configOrParent, parent));
|
|
121
|
+
this.scope = BindingScope.APPLICATION;
|
|
122
|
+
|
|
123
|
+
this.options =
|
|
124
|
+
configOrParent instanceof Context ? {} : configOrParent ?? {};
|
|
125
|
+
|
|
126
|
+
// Configure debug
|
|
127
|
+
this._debug = debug;
|
|
128
|
+
|
|
129
|
+
// Bind the life cycle observer registry
|
|
130
|
+
this.bind(CoreBindings.LIFE_CYCLE_OBSERVER_REGISTRY)
|
|
131
|
+
.toClass(LifeCycleObserverRegistry)
|
|
132
|
+
.inScope(BindingScope.SINGLETON);
|
|
133
|
+
// Bind to self to allow injection of application context in other modules.
|
|
134
|
+
this.bind(CoreBindings.APPLICATION_INSTANCE).to(this);
|
|
135
|
+
// Make options available to other modules as well.
|
|
136
|
+
this.bind(CoreBindings.APPLICATION_CONFIG).to(this.options);
|
|
137
|
+
|
|
138
|
+
// Also configure the application instance to allow `@config`
|
|
139
|
+
this.configure(CoreBindings.APPLICATION_INSTANCE).toAlias(
|
|
140
|
+
CoreBindings.APPLICATION_CONFIG,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
this._shutdownOptions = {signals: ['SIGTERM'], ...this.options.shutdown};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Register a controller class with this application.
|
|
148
|
+
*
|
|
149
|
+
* @param controllerCtor - The controller class
|
|
150
|
+
* (constructor function).
|
|
151
|
+
* @param name - Optional controller name, default to the class name
|
|
152
|
+
* @returns The newly created binding, you can use the reference to
|
|
153
|
+
* further modify the binding, e.g. lock the value to prevent further
|
|
154
|
+
* modifications.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* class MyController {
|
|
159
|
+
* }
|
|
160
|
+
* app.controller(MyController).lock();
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
controller<T>(
|
|
164
|
+
controllerCtor: ControllerClass<T>,
|
|
165
|
+
nameOrOptions?: string | BindingFromClassOptions,
|
|
166
|
+
): Binding<T> {
|
|
167
|
+
this.debug('Adding controller %s', nameOrOptions ?? controllerCtor.name);
|
|
168
|
+
const binding = createBindingFromClass(controllerCtor, {
|
|
169
|
+
namespace: CoreBindings.CONTROLLERS,
|
|
170
|
+
type: CoreTags.CONTROLLER,
|
|
171
|
+
defaultScope: BindingScope.TRANSIENT,
|
|
172
|
+
...toOptions(nameOrOptions),
|
|
173
|
+
});
|
|
174
|
+
this.add(binding);
|
|
175
|
+
return binding;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Bind a Server constructor to the Application's master context.
|
|
180
|
+
* Each server constructor added in this way must provide a unique prefix
|
|
181
|
+
* to prevent binding overlap.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* app.server(RestServer);
|
|
186
|
+
* // This server constructor will be bound under "servers.RestServer".
|
|
187
|
+
* app.server(RestServer, "v1API");
|
|
188
|
+
* // This server instance will be bound under "servers.v1API".
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @param server - The server constructor.
|
|
192
|
+
* @param nameOrOptions - Optional override for name or options.
|
|
193
|
+
* @returns Binding for the server class
|
|
194
|
+
*
|
|
195
|
+
*/
|
|
196
|
+
public server<T extends Server>(
|
|
197
|
+
ctor: Constructor<T>,
|
|
198
|
+
nameOrOptions?: string | BindingFromClassOptions,
|
|
199
|
+
): Binding<T> {
|
|
200
|
+
this.debug('Adding server %s', nameOrOptions ?? ctor.name);
|
|
201
|
+
const binding = createBindingFromClass(ctor, {
|
|
202
|
+
namespace: CoreBindings.SERVERS,
|
|
203
|
+
type: CoreTags.SERVER,
|
|
204
|
+
defaultScope: BindingScope.SINGLETON,
|
|
205
|
+
...toOptions(nameOrOptions),
|
|
206
|
+
}).apply(asLifeCycleObserver);
|
|
207
|
+
this.add(binding);
|
|
208
|
+
return binding;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Bind an array of Server constructors to the Application's master
|
|
213
|
+
* context.
|
|
214
|
+
* Each server added in this way will automatically be named based on the
|
|
215
|
+
* class constructor name with the "servers." prefix.
|
|
216
|
+
*
|
|
217
|
+
* @remarks
|
|
218
|
+
* If you wish to control the binding keys for particular server instances,
|
|
219
|
+
* use the app.server function instead.
|
|
220
|
+
* ```ts
|
|
221
|
+
* app.servers([
|
|
222
|
+
* RestServer,
|
|
223
|
+
* GRPCServer,
|
|
224
|
+
* ]);
|
|
225
|
+
* // Creates a binding for "servers.RestServer" and a binding for
|
|
226
|
+
* // "servers.GRPCServer";
|
|
227
|
+
* ```
|
|
228
|
+
*
|
|
229
|
+
* @param ctors - An array of Server constructors.
|
|
230
|
+
* @returns An array of bindings for the registered server classes
|
|
231
|
+
*
|
|
232
|
+
*/
|
|
233
|
+
public servers<T extends Server>(ctors: Constructor<T>[]): Binding[] {
|
|
234
|
+
return ctors.map(ctor => this.server(ctor));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Retrieve the singleton instance for a bound server.
|
|
239
|
+
*
|
|
240
|
+
* @typeParam T - Server type
|
|
241
|
+
* @param ctor - The constructor that was used to make the
|
|
242
|
+
* binding.
|
|
243
|
+
* @returns A Promise of server instance
|
|
244
|
+
*
|
|
245
|
+
*/
|
|
246
|
+
public async getServer<T extends Server>(
|
|
247
|
+
target: Constructor<T> | string,
|
|
248
|
+
): Promise<T> {
|
|
249
|
+
let key: string;
|
|
250
|
+
// instanceof check not reliable for string.
|
|
251
|
+
if (typeof target === 'string') {
|
|
252
|
+
key = `${CoreBindings.SERVERS}.${target}`;
|
|
253
|
+
} else {
|
|
254
|
+
const ctor = target as Constructor<T>;
|
|
255
|
+
key = `${CoreBindings.SERVERS}.${ctor.name}`;
|
|
256
|
+
}
|
|
257
|
+
return this.get<T>(key);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Assert there is no other operation is in progress, i.e., the state is not
|
|
262
|
+
* `*ing`, such as `starting` or `stopping`.
|
|
263
|
+
*
|
|
264
|
+
* @param op - The operation name, such as 'boot', 'start', or 'stop'
|
|
265
|
+
*/
|
|
266
|
+
protected assertNotInProcess(op: string) {
|
|
267
|
+
assert(
|
|
268
|
+
!this._state.endsWith('ing'),
|
|
269
|
+
`Cannot ${op} the application as it is ${this._state}.`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Assert current state of the application to be one of the expected values
|
|
275
|
+
* @param op - The operation name, such as 'boot', 'start', or 'stop'
|
|
276
|
+
* @param states - Valid states
|
|
277
|
+
*/
|
|
278
|
+
protected assertInStates(op: string, ...states: string[]) {
|
|
279
|
+
assert(
|
|
280
|
+
states.includes(this._state),
|
|
281
|
+
`Cannot ${op} the application as it is ${this._state}. Valid states are ${states}.`,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Transition the application to a new state and emit an event
|
|
287
|
+
* @param state - The new state
|
|
288
|
+
*/
|
|
289
|
+
protected setState(state: string) {
|
|
290
|
+
const oldState = this._state;
|
|
291
|
+
this._state = state;
|
|
292
|
+
if (oldState !== state) {
|
|
293
|
+
this.emit('stateChanged', {from: oldState, to: this._state});
|
|
294
|
+
this.emit(state);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
protected async awaitState(state: string) {
|
|
299
|
+
await once(this, state);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Initialize the application, and all of its registered observers. The
|
|
304
|
+
* application state is checked to ensure the integrity of `initialize`.
|
|
305
|
+
*
|
|
306
|
+
* If the application is already initialized, no operation is performed.
|
|
307
|
+
*
|
|
308
|
+
* This method is automatically invoked by `start()` if the application is not
|
|
309
|
+
* initialized.
|
|
310
|
+
*/
|
|
311
|
+
public async init(): Promise<void> {
|
|
312
|
+
if (this._initialized) return;
|
|
313
|
+
if (this._state === 'initializing') return this.awaitState('initialized');
|
|
314
|
+
this.assertNotInProcess('initialize');
|
|
315
|
+
this.setState('initializing');
|
|
316
|
+
|
|
317
|
+
const registry = await this.getLifeCycleObserverRegistry();
|
|
318
|
+
await registry.init();
|
|
319
|
+
this._initialized = true;
|
|
320
|
+
this.setState('initialized');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Register a function to be called when the application initializes.
|
|
325
|
+
*
|
|
326
|
+
* This is a shortcut for adding a binding for a LifeCycleObserver
|
|
327
|
+
* implementing a `init()` method.
|
|
328
|
+
*
|
|
329
|
+
* @param fn The function to invoke, it can be synchronous (returning `void`)
|
|
330
|
+
* or asynchronous (returning `Promise<void>`).
|
|
331
|
+
* @returns The LifeCycleObserver binding created.
|
|
332
|
+
*/
|
|
333
|
+
public onInit(fn: () => ValueOrPromise<void>): Binding<LifeCycleObserver> {
|
|
334
|
+
const key = [
|
|
335
|
+
CoreBindings.LIFE_CYCLE_OBSERVERS,
|
|
336
|
+
fn.name || '<onInit>',
|
|
337
|
+
generateUniqueId(),
|
|
338
|
+
].join('.');
|
|
339
|
+
|
|
340
|
+
return this.bind<LifeCycleObserver>(key)
|
|
341
|
+
.to({init: fn})
|
|
342
|
+
.apply(asLifeCycleObserver);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Start the application, and all of its registered observers. The application
|
|
347
|
+
* state is checked to ensure the integrity of `start`.
|
|
348
|
+
*
|
|
349
|
+
* If the application is not initialized, it calls first `init()` to
|
|
350
|
+
* initialize the application. This only happens if `start()` is called for
|
|
351
|
+
* the first time.
|
|
352
|
+
*
|
|
353
|
+
* If the application is already started, no operation is performed.
|
|
354
|
+
*/
|
|
355
|
+
public async start(): Promise<void> {
|
|
356
|
+
if (!this._initialized) await this.init();
|
|
357
|
+
if (this._state === 'starting') return this.awaitState('started');
|
|
358
|
+
this.assertNotInProcess('start');
|
|
359
|
+
// No-op if it's started
|
|
360
|
+
if (this._state === 'started') return;
|
|
361
|
+
this.setState('starting');
|
|
362
|
+
this.setupShutdown();
|
|
363
|
+
|
|
364
|
+
const registry = await this.getLifeCycleObserverRegistry();
|
|
365
|
+
await registry.start();
|
|
366
|
+
this.setState('started');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Register a function to be called when the application starts.
|
|
371
|
+
*
|
|
372
|
+
* This is a shortcut for adding a binding for a LifeCycleObserver
|
|
373
|
+
* implementing a `start()` method.
|
|
374
|
+
*
|
|
375
|
+
* @param fn The function to invoke, it can be synchronous (returning `void`)
|
|
376
|
+
* or asynchronous (returning `Promise<void>`).
|
|
377
|
+
* @returns The LifeCycleObserver binding created.
|
|
378
|
+
*/
|
|
379
|
+
public onStart(fn: () => ValueOrPromise<void>): Binding<LifeCycleObserver> {
|
|
380
|
+
const key = [
|
|
381
|
+
CoreBindings.LIFE_CYCLE_OBSERVERS,
|
|
382
|
+
fn.name || '<onStart>',
|
|
383
|
+
generateUniqueId(),
|
|
384
|
+
].join('.');
|
|
385
|
+
|
|
386
|
+
return this.bind<LifeCycleObserver>(key)
|
|
387
|
+
.to({start: fn})
|
|
388
|
+
.apply(asLifeCycleObserver);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Stop the application instance and all of its registered observers. The
|
|
393
|
+
* application state is checked to ensure the integrity of `stop`.
|
|
394
|
+
*
|
|
395
|
+
* If the application is already stopped or not started, no operation is
|
|
396
|
+
* performed.
|
|
397
|
+
*/
|
|
398
|
+
public async stop(): Promise<void> {
|
|
399
|
+
if (this._state === 'stopping') return this.awaitState('stopped');
|
|
400
|
+
this.assertNotInProcess('stop');
|
|
401
|
+
// No-op if it's created or stopped
|
|
402
|
+
if (this._state !== 'started' && this._state !== 'initialized') return;
|
|
403
|
+
this.setState('stopping');
|
|
404
|
+
if (!this._isShuttingDown) {
|
|
405
|
+
// Explicit stop is called, let's remove signal listeners to avoid
|
|
406
|
+
// memory leak and max listener warning
|
|
407
|
+
this.removeSignalListener();
|
|
408
|
+
}
|
|
409
|
+
const registry = await this.getLifeCycleObserverRegistry();
|
|
410
|
+
await registry.stop();
|
|
411
|
+
this.setState('stopped');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Register a function to be called when the application starts.
|
|
416
|
+
*
|
|
417
|
+
* This is a shortcut for adding a binding for a LifeCycleObserver
|
|
418
|
+
* implementing a `start()` method.
|
|
419
|
+
*
|
|
420
|
+
* @param fn The function to invoke, it can be synchronous (returning `void`)
|
|
421
|
+
* or asynchronous (returning `Promise<void>`).
|
|
422
|
+
* @returns The LifeCycleObserver binding created.
|
|
423
|
+
*/
|
|
424
|
+
public onStop(fn: () => ValueOrPromise<void>): Binding<LifeCycleObserver> {
|
|
425
|
+
const key = [
|
|
426
|
+
CoreBindings.LIFE_CYCLE_OBSERVERS,
|
|
427
|
+
fn.name || '<onStop>',
|
|
428
|
+
generateUniqueId(),
|
|
429
|
+
].join('.');
|
|
430
|
+
return this.bind<LifeCycleObserver>(key)
|
|
431
|
+
.to({stop: fn})
|
|
432
|
+
.apply(asLifeCycleObserver);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async getLifeCycleObserverRegistry() {
|
|
436
|
+
return this.get(CoreBindings.LIFE_CYCLE_OBSERVER_REGISTRY);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Add a component to this application and register extensions such as
|
|
441
|
+
* controllers, providers, and servers from the component.
|
|
442
|
+
*
|
|
443
|
+
* @param componentCtor - The component class to add.
|
|
444
|
+
* @param nameOrOptions - Optional component name or options, default to the
|
|
445
|
+
* class name
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
*
|
|
450
|
+
* export class ProductComponent {
|
|
451
|
+
* controllers = [ProductController];
|
|
452
|
+
* repositories = [ProductRepo, UserRepo];
|
|
453
|
+
* providers = {
|
|
454
|
+
* [AUTHENTICATION_STRATEGY]: AuthStrategy,
|
|
455
|
+
* [AUTHORIZATION_ROLE]: Role,
|
|
456
|
+
* };
|
|
457
|
+
* };
|
|
458
|
+
*
|
|
459
|
+
* app.component(ProductComponent);
|
|
460
|
+
* ```
|
|
461
|
+
*/
|
|
462
|
+
public component<T extends Component = Component>(
|
|
463
|
+
componentCtor: Constructor<T>,
|
|
464
|
+
nameOrOptions?: string | BindingFromClassOptions,
|
|
465
|
+
) {
|
|
466
|
+
this.debug('Adding component: %s', nameOrOptions ?? componentCtor.name);
|
|
467
|
+
const binding = createBindingFromClass(componentCtor, {
|
|
468
|
+
namespace: CoreBindings.COMPONENTS,
|
|
469
|
+
type: CoreTags.COMPONENT,
|
|
470
|
+
defaultScope: BindingScope.SINGLETON,
|
|
471
|
+
...toOptions(nameOrOptions),
|
|
472
|
+
});
|
|
473
|
+
if (isLifeCycleObserverClass(componentCtor)) {
|
|
474
|
+
binding.apply(asLifeCycleObserver);
|
|
475
|
+
}
|
|
476
|
+
this.add(binding);
|
|
477
|
+
// Assuming components can be synchronously instantiated
|
|
478
|
+
const instance = this.getSync<Component>(binding.key);
|
|
479
|
+
mountComponent(this, instance);
|
|
480
|
+
return binding;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Set application metadata. `@loopback/boot` calls this method to populate
|
|
485
|
+
* the metadata from `package.json`.
|
|
486
|
+
*
|
|
487
|
+
* @param metadata - Application metadata
|
|
488
|
+
*/
|
|
489
|
+
public setMetadata(metadata: ApplicationMetadata) {
|
|
490
|
+
this.bind(CoreBindings.APPLICATION_METADATA).to(metadata);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Register a life cycle observer class
|
|
495
|
+
* @param ctor - A class implements LifeCycleObserver
|
|
496
|
+
* @param nameOrOptions - Optional name or options for the life cycle observer
|
|
497
|
+
*/
|
|
498
|
+
public lifeCycleObserver<T extends LifeCycleObserver>(
|
|
499
|
+
ctor: Constructor<T>,
|
|
500
|
+
nameOrOptions?: string | BindingFromClassOptions,
|
|
501
|
+
): Binding<T> {
|
|
502
|
+
this.debug('Adding life cycle observer %s', nameOrOptions ?? ctor.name);
|
|
503
|
+
const binding = createBindingFromClass(ctor, {
|
|
504
|
+
namespace: CoreBindings.LIFE_CYCLE_OBSERVERS,
|
|
505
|
+
type: CoreTags.LIFE_CYCLE_OBSERVER,
|
|
506
|
+
defaultScope: BindingScope.SINGLETON,
|
|
507
|
+
...toOptions(nameOrOptions),
|
|
508
|
+
}).apply(asLifeCycleObserver);
|
|
509
|
+
this.add(binding);
|
|
510
|
+
return binding;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Add a service to this application.
|
|
515
|
+
*
|
|
516
|
+
* @param cls - The service or provider class
|
|
517
|
+
*
|
|
518
|
+
* @example
|
|
519
|
+
*
|
|
520
|
+
* ```ts
|
|
521
|
+
* // Define a class to be bound via ctx.toClass()
|
|
522
|
+
* @injectable({scope: BindingScope.SINGLETON})
|
|
523
|
+
* export class LogService {
|
|
524
|
+
* log(msg: string) {
|
|
525
|
+
* console.log(msg);
|
|
526
|
+
* }
|
|
527
|
+
* }
|
|
528
|
+
*
|
|
529
|
+
* // Define a class to be bound via ctx.toProvider()
|
|
530
|
+
* import {v4 as uuidv4} from 'uuid';
|
|
531
|
+
* export class UuidProvider implements Provider<string> {
|
|
532
|
+
* value() {
|
|
533
|
+
* return uuidv4();
|
|
534
|
+
* }
|
|
535
|
+
* }
|
|
536
|
+
*
|
|
537
|
+
* // Register the local services
|
|
538
|
+
* app.service(LogService);
|
|
539
|
+
* app.service(UuidProvider, 'uuid');
|
|
540
|
+
*
|
|
541
|
+
* export class MyController {
|
|
542
|
+
* constructor(
|
|
543
|
+
* @inject('services.uuid') private uuid: string,
|
|
544
|
+
* @inject('services.LogService') private log: LogService,
|
|
545
|
+
* ) {
|
|
546
|
+
* }
|
|
547
|
+
*
|
|
548
|
+
* greet(name: string) {
|
|
549
|
+
* this.log(`Greet request ${this.uuid} received: ${name}`);
|
|
550
|
+
* return `${this.uuid}: ${name}`;
|
|
551
|
+
* }
|
|
552
|
+
* }
|
|
553
|
+
* ```
|
|
554
|
+
*/
|
|
555
|
+
public service<S>(
|
|
556
|
+
cls: ServiceOrProviderClass<S>,
|
|
557
|
+
nameOrOptions?: string | ServiceOptions,
|
|
558
|
+
): Binding<S> {
|
|
559
|
+
const options = toOptions(nameOrOptions);
|
|
560
|
+
const binding = createServiceBinding(cls, options);
|
|
561
|
+
this.add(binding);
|
|
562
|
+
return binding;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Register an interceptor
|
|
567
|
+
* @param interceptor - An interceptor function or provider class
|
|
568
|
+
* @param nameOrOptions - Binding name or options
|
|
569
|
+
*/
|
|
570
|
+
public interceptor(
|
|
571
|
+
interceptor: Interceptor | Constructor<Provider<Interceptor>>,
|
|
572
|
+
nameOrOptions?: string | InterceptorBindingOptions,
|
|
573
|
+
) {
|
|
574
|
+
const options = toOptions(nameOrOptions);
|
|
575
|
+
return registerInterceptor(this, interceptor, options);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Set up signals that are captured to shutdown the application
|
|
580
|
+
*/
|
|
581
|
+
protected setupShutdown() {
|
|
582
|
+
if (this._signalListener != null) {
|
|
583
|
+
this.registerSignalListener();
|
|
584
|
+
return this._signalListener;
|
|
585
|
+
}
|
|
586
|
+
const gracePeriod = this._shutdownOptions.gracePeriod;
|
|
587
|
+
this._signalListener = async (signal: string) => {
|
|
588
|
+
const kill = () => {
|
|
589
|
+
this.removeSignalListener();
|
|
590
|
+
process.kill(process.pid, signal);
|
|
591
|
+
};
|
|
592
|
+
debugShutdown(
|
|
593
|
+
'[%s] Signal %s received for process %d',
|
|
594
|
+
this.name,
|
|
595
|
+
signal,
|
|
596
|
+
process.pid,
|
|
597
|
+
);
|
|
598
|
+
if (!this._isShuttingDown) {
|
|
599
|
+
this._isShuttingDown = true;
|
|
600
|
+
let timer;
|
|
601
|
+
if (typeof gracePeriod === 'number' && !isNaN(gracePeriod)) {
|
|
602
|
+
timer = setTimeout(kill, gracePeriod);
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
await this.stop();
|
|
606
|
+
} finally {
|
|
607
|
+
if (timer != null) clearTimeout(timer);
|
|
608
|
+
kill();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
this.registerSignalListener();
|
|
613
|
+
return this._signalListener;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private registerSignalListener() {
|
|
617
|
+
const {signals = []} = this._shutdownOptions;
|
|
618
|
+
debugShutdown(
|
|
619
|
+
'[%s] Registering signal listeners on the process %d',
|
|
620
|
+
this.name,
|
|
621
|
+
process.pid,
|
|
622
|
+
signals,
|
|
623
|
+
);
|
|
624
|
+
signals.forEach(sig => {
|
|
625
|
+
if (process.getMaxListeners() <= process.listenerCount(sig)) {
|
|
626
|
+
if (debugWarning.enabled) {
|
|
627
|
+
debugWarning(
|
|
628
|
+
'[%s] %d %s listeners are added to process %d',
|
|
629
|
+
this.name,
|
|
630
|
+
process.listenerCount(sig),
|
|
631
|
+
sig,
|
|
632
|
+
process.pid,
|
|
633
|
+
new Error('MaxListenersExceededWarning'),
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
638
|
+
process.on(sig, this._signalListener);
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private removeSignalListener() {
|
|
643
|
+
if (this._signalListener == null) return;
|
|
644
|
+
const {signals = []} = this._shutdownOptions;
|
|
645
|
+
debugShutdown(
|
|
646
|
+
'[%s] Removing signal listeners on the process %d',
|
|
647
|
+
this.name,
|
|
648
|
+
process.pid,
|
|
649
|
+
signals,
|
|
650
|
+
);
|
|
651
|
+
signals.forEach(sig =>
|
|
652
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
653
|
+
process.removeListener(sig, this._signalListener),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Normalize name or options to `BindingFromClassOptions`
|
|
660
|
+
* @param nameOrOptions - Name or options for binding from class
|
|
661
|
+
*/
|
|
662
|
+
function toOptions(nameOrOptions?: string | BindingFromClassOptions) {
|
|
663
|
+
if (typeof nameOrOptions === 'string') {
|
|
664
|
+
return {name: nameOrOptions};
|
|
665
|
+
}
|
|
666
|
+
return nameOrOptions ?? {};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Options to set up application shutdown
|
|
671
|
+
*/
|
|
672
|
+
export type ShutdownOptions = {
|
|
673
|
+
/**
|
|
674
|
+
* An array of signals to be trapped for graceful shutdown
|
|
675
|
+
*/
|
|
676
|
+
signals?: NodeJS.Signals[];
|
|
677
|
+
/**
|
|
678
|
+
* Period in milliseconds to wait for the grace shutdown to finish before
|
|
679
|
+
* exiting the process
|
|
680
|
+
*/
|
|
681
|
+
gracePeriod?: number;
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Configuration for application
|
|
686
|
+
*/
|
|
687
|
+
export interface ApplicationConfig {
|
|
688
|
+
/**
|
|
689
|
+
* Name of the application context
|
|
690
|
+
*/
|
|
691
|
+
name?: string;
|
|
692
|
+
/**
|
|
693
|
+
* Configuration for signals that shut down the application
|
|
694
|
+
*/
|
|
695
|
+
shutdown?: ShutdownOptions;
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Other properties
|
|
699
|
+
*/
|
|
700
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
701
|
+
[prop: string]: any;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
705
|
+
export type ControllerClass<T = any> = Constructor<T>;
|
|
706
|
+
|
|
707
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
708
|
+
export type ServiceOrProviderClass<T = any> =
|
|
709
|
+
| Constructor<T | Provider<T>>
|
|
710
|
+
| DynamicValueProviderClass<T>;
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Type description for `package.json`
|
|
714
|
+
*/
|
|
715
|
+
export interface ApplicationMetadata extends JSONObject {
|
|
716
|
+
name: string;
|
|
717
|
+
version: string;
|
|
718
|
+
description: string;
|
|
719
|
+
}
|