@loopback/core 2.4.1 → 2.7.0

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.
@@ -5,16 +5,20 @@
5
5
 
6
6
  import {
7
7
  Binding,
8
+ BindingFromClassOptions,
8
9
  BindingScope,
9
10
  Constructor,
10
11
  Context,
11
12
  createBindingFromClass,
13
+ Interceptor,
14
+ InterceptorBindingOptions,
12
15
  JSONObject,
13
16
  Provider,
17
+ registerInterceptor,
14
18
  } from '@loopback/context';
15
19
  import assert from 'assert';
16
20
  import debugFactory from 'debug';
17
- import pEvent from 'p-event';
21
+ import {once} from 'events';
18
22
  import {Component, mountComponent} from './component';
19
23
  import {CoreBindings, CoreTags} from './keys';
20
24
  import {
@@ -145,13 +149,16 @@ export class Application extends Context implements LifeCycleObserver {
145
149
  * app.controller(MyController).lock();
146
150
  * ```
147
151
  */
148
- controller<T>(controllerCtor: ControllerClass<T>, name?: string): Binding<T> {
149
- this.debug('Adding controller %s', name ?? controllerCtor.name);
152
+ controller<T>(
153
+ controllerCtor: ControllerClass<T>,
154
+ nameOrOptions?: string | BindingFromClassOptions,
155
+ ): Binding<T> {
156
+ this.debug('Adding controller %s', nameOrOptions ?? controllerCtor.name);
150
157
  const binding = createBindingFromClass(controllerCtor, {
151
- name,
152
158
  namespace: CoreBindings.CONTROLLERS,
153
159
  type: CoreTags.CONTROLLER,
154
160
  defaultScope: BindingScope.TRANSIENT,
161
+ ...toOptions(nameOrOptions),
155
162
  });
156
163
  this.add(binding);
157
164
  return binding;
@@ -171,20 +178,20 @@ export class Application extends Context implements LifeCycleObserver {
171
178
  * ```
172
179
  *
173
180
  * @param server - The server constructor.
174
- * @param name - Optional override for key name.
181
+ * @param nameOrOptions - Optional override for name or options.
175
182
  * @returns Binding for the server class
176
183
  *
177
184
  */
178
185
  public server<T extends Server>(
179
186
  ctor: Constructor<T>,
180
- name?: string,
187
+ nameOrOptions?: string | BindingFromClassOptions,
181
188
  ): Binding<T> {
182
- this.debug('Adding server %s', name ?? ctor.name);
189
+ this.debug('Adding server %s', nameOrOptions ?? ctor.name);
183
190
  const binding = createBindingFromClass(ctor, {
184
- name,
185
191
  namespace: CoreBindings.SERVERS,
186
192
  type: CoreTags.SERVER,
187
193
  defaultScope: BindingScope.SINGLETON,
194
+ ...toOptions(nameOrOptions),
188
195
  }).apply(asLifeCycleObserver);
189
196
  this.add(binding);
190
197
  return binding;
@@ -278,7 +285,7 @@ export class Application extends Context implements LifeCycleObserver {
278
285
  }
279
286
 
280
287
  protected async awaitState(state: string) {
281
- await pEvent(this, state);
288
+ await once(this, state);
282
289
  }
283
290
 
284
291
  /**
@@ -332,7 +339,8 @@ export class Application extends Context implements LifeCycleObserver {
332
339
  * controllers, providers, and servers from the component.
333
340
  *
334
341
  * @param componentCtor - The component class to add.
335
- * @param name - Optional component name, default to the class name
342
+ * @param nameOrOptions - Optional component name or options, default to the
343
+ * class name
336
344
  *
337
345
  * @example
338
346
  * ```ts
@@ -351,14 +359,14 @@ export class Application extends Context implements LifeCycleObserver {
351
359
  */
352
360
  public component<T extends Component = Component>(
353
361
  componentCtor: Constructor<T>,
354
- name?: string,
362
+ nameOrOptions?: string | BindingFromClassOptions,
355
363
  ) {
356
- this.debug('Adding component: %s', name ?? componentCtor.name);
364
+ this.debug('Adding component: %s', nameOrOptions ?? componentCtor.name);
357
365
  const binding = createBindingFromClass(componentCtor, {
358
- name,
359
366
  namespace: CoreBindings.COMPONENTS,
360
367
  type: CoreTags.COMPONENT,
361
368
  defaultScope: BindingScope.SINGLETON,
369
+ ...toOptions(nameOrOptions),
362
370
  });
363
371
  if (isLifeCycleObserverClass(componentCtor)) {
364
372
  binding.apply(asLifeCycleObserver);
@@ -383,18 +391,18 @@ export class Application extends Context implements LifeCycleObserver {
383
391
  /**
384
392
  * Register a life cycle observer class
385
393
  * @param ctor - A class implements LifeCycleObserver
386
- * @param name - Optional name for the life cycle observer
394
+ * @param nameOrOptions - Optional name or options for the life cycle observer
387
395
  */
388
396
  public lifeCycleObserver<T extends LifeCycleObserver>(
389
397
  ctor: Constructor<T>,
390
- name?: string,
398
+ nameOrOptions?: string | BindingFromClassOptions,
391
399
  ): Binding<T> {
392
- this.debug('Adding life cycle observer %s', name ?? ctor.name);
400
+ this.debug('Adding life cycle observer %s', nameOrOptions ?? ctor.name);
393
401
  const binding = createBindingFromClass(ctor, {
394
- name,
395
402
  namespace: CoreBindings.LIFE_CYCLE_OBSERVERS,
396
403
  type: CoreTags.LIFE_CYCLE_OBSERVER,
397
404
  defaultScope: BindingScope.SINGLETON,
405
+ ...toOptions(nameOrOptions),
398
406
  }).apply(asLifeCycleObserver);
399
407
  this.add(binding);
400
408
  return binding;
@@ -443,15 +451,28 @@ export class Application extends Context implements LifeCycleObserver {
443
451
  * ```
444
452
  */
445
453
  public service<S>(
446
- cls: Constructor<S> | Constructor<Provider<S>>,
447
- name?: string | ServiceOptions,
454
+ cls: Constructor<S | Provider<S>>,
455
+ nameOrOptions?: string | ServiceOptions,
448
456
  ): Binding<S> {
449
- const options = typeof name === 'string' ? {name} : name;
457
+ const options = toOptions(nameOrOptions);
450
458
  const binding = createServiceBinding(cls, options);
451
459
  this.add(binding);
452
460
  return binding;
453
461
  }
454
462
 
463
+ /**
464
+ * Register an interceptor
465
+ * @param interceptor - An interceptor function or provider class
466
+ * @param nameOrOptions - Binding name or options
467
+ */
468
+ public interceptor(
469
+ interceptor: Interceptor | Constructor<Provider<Interceptor>>,
470
+ nameOrOptions?: string | InterceptorBindingOptions,
471
+ ) {
472
+ const options = toOptions(nameOrOptions);
473
+ return registerInterceptor(this, interceptor, options);
474
+ }
475
+
455
476
  /**
456
477
  * Set up signals that are captured to shutdown the application
457
478
  */
@@ -532,6 +553,17 @@ export class Application extends Context implements LifeCycleObserver {
532
553
  }
533
554
  }
534
555
 
556
+ /**
557
+ * Normalize name or options to `BindingFromClassOptions`
558
+ * @param nameOrOptions - Name or options for binding from class
559
+ */
560
+ function toOptions(nameOrOptions?: string | BindingFromClassOptions) {
561
+ if (typeof nameOrOptions === 'string') {
562
+ return {name: nameOrOptions};
563
+ }
564
+ return nameOrOptions ?? {};
565
+ }
566
+
535
567
  /**
536
568
  * Options to set up application shutdown
537
569
  */
@@ -227,11 +227,13 @@ function inferExtensionPointName(
227
227
  /**
228
228
  * A factory function to create binding filter for extensions of a named
229
229
  * extension point
230
- * @param extensionPointName - Name of the extension point
230
+ * @param extensionPointNames - A list of names of extension points
231
231
  */
232
- export function extensionFilter(extensionPointName: string): BindingFilter {
232
+ export function extensionFilter(
233
+ ...extensionPointNames: string[]
234
+ ): BindingFilter {
233
235
  return filterByTag({
234
- [CoreTags.EXTENSION_FOR]: includesTagValue(extensionPointName),
236
+ [CoreTags.EXTENSION_FOR]: includesTagValue(...extensionPointNames),
235
237
  });
236
238
  }
237
239
 
package/src/index.ts CHANGED
@@ -24,5 +24,6 @@ export * from './extension-point';
24
24
  export * from './keys';
25
25
  export * from './lifecycle';
26
26
  export * from './lifecycle-registry';
27
+ export * from './mixin-target';
27
28
  export * from './server';
28
29
  export * from './service';
package/src/keys.ts CHANGED
@@ -30,9 +30,9 @@ export namespace CoreBindings {
30
30
  /**
31
31
  * Binding key for application configuration
32
32
  */
33
- export const APPLICATION_CONFIG = BindingKey.create<ApplicationConfig>(
34
- 'application.config',
35
- );
33
+ export const APPLICATION_CONFIG: BindingKey<ApplicationConfig> = BindingKey.create<
34
+ ApplicationConfig
35
+ >('application.config');
36
36
 
37
37
  /**
38
38
  * Binding key for the content of `package.json`
@@ -60,9 +60,9 @@ export namespace CoreBindings {
60
60
  * Binding key for the controller class resolved in the current request
61
61
  * context
62
62
  */
63
- export const CONTROLLER_CLASS = BindingKey.create<ControllerClass>(
64
- 'controller.current.ctor',
65
- );
63
+ export const CONTROLLER_CLASS: BindingKey<ControllerClass> = BindingKey.create<
64
+ ControllerClass
65
+ >('controller.current.ctor');
66
66
 
67
67
  /**
68
68
  * Binding key for the controller method resolved in the current request
package/src/lifecycle.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  bind,
8
8
  Binding,
9
9
  BindingSpec,
10
+ BindingTagFilter,
10
11
  Constructor,
11
12
  filterByTag,
12
13
  ValueOrPromise,
@@ -62,7 +63,7 @@ export function asLifeCycleObserver<T = unknown>(binding: Binding<T>) {
62
63
  * Find all life cycle observer bindings. By default, a binding tagged with
63
64
  * `CoreTags.LIFE_CYCLE_OBSERVER`. It's used as `BindingFilter`.
64
65
  */
65
- export const lifeCycleObserverFilter = filterByTag(
66
+ export const lifeCycleObserverFilter: BindingTagFilter = filterByTag(
66
67
  CoreTags.LIFE_CYCLE_OBSERVER,
67
68
  );
68
69
 
@@ -0,0 +1,70 @@
1
+ // Copyright IBM Corp. 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 {Constructor} from '@loopback/context';
7
+
8
+ /**
9
+ * A replacement for `typeof Target` to be used in mixin class definitions.
10
+ * This is a workaround for TypeScript limitation described in
11
+ * - https://github.com/microsoft/TypeScript/issues/17293
12
+ * - https://github.com/microsoft/TypeScript/issues/17744
13
+ * - https://github.com/microsoft/TypeScript/issues/36060
14
+ *
15
+ * @example
16
+ *
17
+ * ```ts
18
+ * export function MyMixin<T extends MixinTarget<Application>>(superClass: T) {
19
+ * return class extends superClass {
20
+ * // contribute new class members
21
+ * }
22
+ * };
23
+ * ```
24
+ *
25
+ * TypeScript does not allow class mixins to access protected members from
26
+ * the base class. You can use the following approach as a workaround:
27
+ *
28
+ * ```ts
29
+ * // @ts-ignore
30
+ * (this as unknown as {YourBaseClass}).protectedMember
31
+ * ```
32
+ *
33
+ * The directive `@ts-ignore` suppresses compiler error about accessing
34
+ * a protected member from outside. Unfortunately, it also disables other
35
+ * compile-time checks (e.g. to verify that a protected method was invoked
36
+ * with correct arguments, and so on). This is the same behavior you
37
+ * would get by using `Constructor<any>` instead of `MixinTarget<Application>`.
38
+ * The major improvement is that TypeScript can still infer the return
39
+ * type of the protected member, therefore `any` is NOT introduced to subsequent
40
+ * code.
41
+ *
42
+ * TypeScript also does not allow mixin class to overwrite a method inherited
43
+ * from a mapped type, see https://github.com/microsoft/TypeScript/issues/38496
44
+ * As a workaround, use `@ts-ignore` to disable the error.
45
+ *
46
+ * ```ts
47
+ * export function RepositoryMixin<T extends MixinTarget<Application>>(
48
+ * superClass: T,
49
+ * ) {
50
+ * return class extends superClass {
51
+ * // @ts-ignore
52
+ * public component<C extends Component = Component>(
53
+ * componentCtor: Constructor<C>,
54
+ * nameOrOptions?: string | BindingFromClassOptions,
55
+ * ) {
56
+ * const binding = super.component(componentCtor, nameOrOptions);
57
+ * // ...
58
+ * return binding;
59
+ * }
60
+ * }
61
+ * ```
62
+ */
63
+ export type MixinTarget<T extends object> = Constructor<
64
+ {
65
+ // Enumerate only public members to avoid the following compiler error:
66
+ // Property '(name)' of exported class expression
67
+ // may not be private or protected.ts(4094)
68
+ [P in keyof T]: T[P];
69
+ }
70
+ >;
package/src/service.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  import {
7
7
  Binding,
8
8
  BindingFilter,
9
+ BindingFromClassOptions,
9
10
  BindingTemplate,
10
11
  bindingTemplateFor,
11
12
  Constructor,
@@ -31,10 +32,9 @@ export type ServiceInterface = string | symbol | Function;
31
32
  /**
32
33
  * Options to register a service binding
33
34
  */
34
- export type ServiceOptions = {
35
- name?: string;
35
+ export interface ServiceOptions extends BindingFromClassOptions {
36
36
  interface?: ServiceInterface;
37
- };
37
+ }
38
38
 
39
39
  /**
40
40
  * `@service` injects a service instance that matches the class or interface.
@@ -142,7 +142,7 @@ export function filterByServiceInterface(
142
142
  * @param options - Service options
143
143
  */
144
144
  export function createServiceBinding<S>(
145
- cls: Constructor<S> | Constructor<Provider<S>>,
145
+ cls: Constructor<S | Provider<S>>,
146
146
  options: ServiceOptions = {},
147
147
  ): Binding<S> {
148
148
  let name = options.name;
@@ -162,6 +162,7 @@ export function createServiceBinding<S>(
162
162
  const binding = createBindingFromClass(cls, {
163
163
  name,
164
164
  type: CoreTags.SERVICE,
165
+ ...options,
165
166
  }).apply(asService(options.interface ?? cls));
166
167
  return binding;
167
168
  }
package/index.js DELETED
@@ -1,6 +0,0 @@
1
- // Copyright IBM Corp. 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
- module.exports = require('./dist');