@loopback/core 4.0.0-alpha.9 → 4.0.2

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.
Files changed (121) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +77 -2
  3. package/dist/application.d.ts +341 -0
  4. package/dist/application.js +554 -0
  5. package/dist/application.js.map +1 -0
  6. package/dist/component.d.ts +80 -0
  7. package/dist/component.js +59 -0
  8. package/dist/component.js.map +1 -0
  9. package/dist/extension-point.d.ts +121 -0
  10. package/dist/extension-point.js +227 -0
  11. package/dist/extension-point.js.map +1 -0
  12. package/dist/index.d.ts +21 -0
  13. package/dist/index.js +31 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/keys.d.ts +97 -0
  16. package/dist/keys.js +109 -0
  17. package/dist/keys.js.map +1 -0
  18. package/dist/lifecycle-registry.d.ts +91 -0
  19. package/dist/lifecycle-registry.js +191 -0
  20. package/dist/lifecycle-registry.js.map +1 -0
  21. package/dist/lifecycle.d.ts +47 -0
  22. package/dist/lifecycle.js +56 -0
  23. package/dist/lifecycle.js.map +1 -0
  24. package/dist/mixin-target.d.ts +60 -0
  25. package/{lib6/internal-types.js → dist/mixin-target.js} +2 -3
  26. package/dist/mixin-target.js.map +1 -0
  27. package/dist/server.d.ts +16 -0
  28. package/{lib6/component.js → dist/server.js} +2 -2
  29. package/dist/server.js.map +1 -0
  30. package/dist/service.d.ts +63 -0
  31. package/dist/service.js +151 -0
  32. package/dist/service.js.map +1 -0
  33. package/package.json +39 -37
  34. package/src/application.ts +719 -0
  35. package/src/component.ts +155 -0
  36. package/src/extension-point.ts +312 -0
  37. package/src/index.ts +29 -0
  38. package/src/keys.ts +144 -0
  39. package/src/lifecycle-registry.ts +268 -0
  40. package/src/lifecycle.ts +90 -0
  41. package/src/mixin-target.ts +69 -0
  42. package/src/server.ts +22 -0
  43. package/src/service.ts +211 -0
  44. package/index.d.ts +0 -6
  45. package/index.js +0 -9
  46. package/lib/application.d.ts +0 -54
  47. package/lib/application.js +0 -81
  48. package/lib/application.js.map +0 -1
  49. package/lib/component.d.ts +0 -2
  50. package/lib/component.js +0 -7
  51. package/lib/component.js.map +0 -1
  52. package/lib/http-handler.d.ts +0 -16
  53. package/lib/http-handler.js +0 -62
  54. package/lib/http-handler.js.map +0 -1
  55. package/lib/index.d.ts +0 -17
  56. package/lib/index.js +0 -37
  57. package/lib/index.js.map +0 -1
  58. package/lib/internal-types.d.ts +0 -29
  59. package/lib/internal-types.js +0 -8
  60. package/lib/internal-types.js.map +0 -1
  61. package/lib/keys.d.ts +0 -7
  62. package/lib/keys.js +0 -16
  63. package/lib/keys.js.map +0 -1
  64. package/lib/parser.d.ts +0 -11
  65. package/lib/parser.js +0 -96
  66. package/lib/parser.js.map +0 -1
  67. package/lib/promisify.d.ts +0 -3
  68. package/lib/promisify.js +0 -34
  69. package/lib/promisify.js.map +0 -1
  70. package/lib/router/metadata.d.ts +0 -12
  71. package/lib/router/metadata.js +0 -30
  72. package/lib/router/metadata.js.map +0 -1
  73. package/lib/router/routing-table.d.ts +0 -16
  74. package/lib/router/routing-table.js +0 -97
  75. package/lib/router/routing-table.js.map +0 -1
  76. package/lib/sequence.d.ts +0 -55
  77. package/lib/sequence.js +0 -99
  78. package/lib/sequence.js.map +0 -1
  79. package/lib/server.d.ts +0 -23
  80. package/lib/server.js +0 -64
  81. package/lib/server.js.map +0 -1
  82. package/lib/writer.d.ts +0 -11
  83. package/lib/writer.js +0 -34
  84. package/lib/writer.js.map +0 -1
  85. package/lib6/application.d.ts +0 -54
  86. package/lib6/application.js +0 -81
  87. package/lib6/application.js.map +0 -1
  88. package/lib6/component.d.ts +0 -2
  89. package/lib6/component.js.map +0 -1
  90. package/lib6/http-handler.d.ts +0 -16
  91. package/lib6/http-handler.js +0 -72
  92. package/lib6/http-handler.js.map +0 -1
  93. package/lib6/index.d.ts +0 -17
  94. package/lib6/index.js +0 -37
  95. package/lib6/index.js.map +0 -1
  96. package/lib6/internal-types.d.ts +0 -29
  97. package/lib6/internal-types.js.map +0 -1
  98. package/lib6/keys.d.ts +0 -7
  99. package/lib6/keys.js +0 -16
  100. package/lib6/keys.js.map +0 -1
  101. package/lib6/parser.d.ts +0 -11
  102. package/lib6/parser.js +0 -106
  103. package/lib6/parser.js.map +0 -1
  104. package/lib6/promisify.d.ts +0 -3
  105. package/lib6/promisify.js +0 -34
  106. package/lib6/promisify.js.map +0 -1
  107. package/lib6/router/metadata.d.ts +0 -12
  108. package/lib6/router/metadata.js +0 -30
  109. package/lib6/router/metadata.js.map +0 -1
  110. package/lib6/router/routing-table.d.ts +0 -16
  111. package/lib6/router/routing-table.js +0 -97
  112. package/lib6/router/routing-table.js.map +0 -1
  113. package/lib6/sequence.d.ts +0 -55
  114. package/lib6/sequence.js +0 -109
  115. package/lib6/sequence.js.map +0 -1
  116. package/lib6/server.d.ts +0 -23
  117. package/lib6/server.js +0 -74
  118. package/lib6/server.js.map +0 -1
  119. package/lib6/writer.d.ts +0 -11
  120. package/lib6/writer.js +0 -34
  121. 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
+ }