@loopback/context 3.10.0 → 3.12.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/binding-decorator.d.ts +24 -4
  3. package/dist/binding-decorator.js +34 -11
  4. package/dist/binding-decorator.js.map +1 -1
  5. package/dist/binding-inspector.d.ts +8 -5
  6. package/dist/binding-inspector.js +6 -3
  7. package/dist/binding-inspector.js.map +1 -1
  8. package/dist/binding-key.d.ts +1 -1
  9. package/dist/binding-key.js +1 -0
  10. package/dist/binding-key.js.map +1 -1
  11. package/dist/binding.d.ts +63 -6
  12. package/dist/binding.js +112 -36
  13. package/dist/binding.js.map +1 -1
  14. package/dist/context-view.d.ts +8 -8
  15. package/dist/context-view.js.map +1 -1
  16. package/dist/context.d.ts +25 -4
  17. package/dist/context.js +80 -4
  18. package/dist/context.js.map +1 -1
  19. package/dist/inject.d.ts +9 -2
  20. package/dist/inject.js +25 -11
  21. package/dist/inject.js.map +1 -1
  22. package/dist/interceptor-chain.d.ts +1 -0
  23. package/dist/interceptor-chain.js.map +1 -1
  24. package/dist/interceptor.js +1 -1
  25. package/dist/interceptor.js.map +1 -1
  26. package/dist/resolver.js +5 -7
  27. package/dist/resolver.js.map +1 -1
  28. package/dist/value-promise.d.ts +4 -4
  29. package/dist/value-promise.js +2 -2
  30. package/dist/value-promise.js.map +1 -1
  31. package/package.json +11 -11
  32. package/src/binding-decorator.ts +35 -9
  33. package/src/binding-inspector.ts +8 -5
  34. package/src/binding-key.ts +3 -5
  35. package/src/binding.ts +123 -36
  36. package/src/context-view.ts +8 -7
  37. package/src/context.ts +100 -5
  38. package/src/inject.ts +37 -15
  39. package/src/interceptor-chain.ts +1 -0
  40. package/src/interceptor.ts +2 -2
  41. package/src/resolver.ts +5 -9
  42. package/src/value-promise.ts +4 -4
package/src/binding.ts CHANGED
@@ -58,6 +58,10 @@ export enum BindingScope {
58
58
  TRANSIENT = 'Transient',
59
59
 
60
60
  /**
61
+ * @deprecated Finer-grained scopes such as `APPLICATION`, `SERVER`, or
62
+ * `REQUEST` should be used instead to ensure the scope of sharing of resolved
63
+ * binding values.
64
+ *
61
65
  * The binding provides a value as a singleton within each local context. The
62
66
  * value is calculated only once per context and cached for subsequential
63
67
  * uses. Child contexts have their own value and do not share with their
@@ -79,6 +83,7 @@ export enum BindingScope {
79
83
  * 3. `'b1'` is resolved in `app` but not in `req2`, a new value `2` is
80
84
  * calculated and used for `req2` afterward
81
85
  * - req2.get('b1') ==> 2 (always)
86
+ *
82
87
  */
83
88
  CONTEXT = 'Context',
84
89
 
@@ -104,6 +109,62 @@ export enum BindingScope {
104
109
  * - req2.get('b1') ==> 0 (always)
105
110
  */
106
111
  SINGLETON = 'Singleton',
112
+
113
+ /*
114
+ * The following scopes are checked against the context hierarchy to find
115
+ * the first matching context for a given scope in the chain. Resolved binding
116
+ * values will be cached and shared on the scoped context. This ensures a
117
+ * binding to have the same value for the scoped context.
118
+ */
119
+
120
+ /**
121
+ * Application scope
122
+ *
123
+ * @remarks
124
+ * The binding provides an application-scoped value within the context
125
+ * hierarchy. Resolved value for this binding will be cached and shared for
126
+ * the same application context (denoted by its scope property set to
127
+ * `BindingScope.APPLICATION`).
128
+ *
129
+ */
130
+ APPLICATION = 'Application',
131
+
132
+ /**
133
+ * Server scope
134
+ *
135
+ * @remarks
136
+ * The binding provides an server-scoped value within the context hierarchy.
137
+ * Resolved value for this binding will be cached and shared for the same
138
+ * server context (denoted by its scope property set to
139
+ * `BindingScope.SERVER`).
140
+ *
141
+ * It's possible that an application has more than one servers configured,
142
+ * such as a `RestServer` and a `GrpcServer`. Both server contexts are created
143
+ * with `scope` set to `BindingScope.SERVER`. Depending on where a binding
144
+ * is resolved:
145
+ * - If the binding is resolved from the RestServer or below, it will be
146
+ * cached using the RestServer context as the key.
147
+ * - If the binding is resolved from the GrpcServer or below, it will be
148
+ * cached using the GrpcServer context as the key.
149
+ *
150
+ * The same binding can resolved/shared/cached for all servers, each of which
151
+ * has its own value for the binding.
152
+ */
153
+ SERVER = 'Server',
154
+
155
+ /**
156
+ * Request scope
157
+ *
158
+ * @remarks
159
+ * The binding provides an request-scoped value within the context hierarchy.
160
+ * Resolved value for this binding will be cached and shared for the same
161
+ * request context (denoted by its scope property set to
162
+ * `BindingScope.REQUEST`).
163
+ *
164
+ * The `REQUEST` scope is very useful for controllers, services and artifacts
165
+ * that want to have a single instance/value for a given request.
166
+ */
167
+ REQUEST = 'Request',
107
168
  }
108
169
 
109
170
  /**
@@ -352,23 +413,18 @@ export class Binding<T = BoundValue> extends EventEmitter {
352
413
 
353
414
  /**
354
415
  * Cache the resolved value by the binding scope
355
- * @param ctx - The current context
416
+ * @param resolutionCtx - The resolution context
356
417
  * @param result - The calculated value for the binding
357
418
  */
358
419
  private _cacheValue(
359
- ctx: Context,
420
+ resolutionCtx: Context,
360
421
  result: ValueOrPromise<T>,
361
422
  ): ValueOrPromise<T> {
362
423
  // Initialize the cache as a weakmap keyed by context
363
424
  if (!this._cache) this._cache = new WeakMap<Context, ValueOrPromise<T>>();
364
- if (this.scope === BindingScope.SINGLETON) {
365
- // Cache the value
366
- this._cache.set(ctx.getOwnerContext(this.key)!, result);
367
- } else if (this.scope === BindingScope.CONTEXT) {
368
- // Cache the value at the current context
369
- this._cache.set(ctx, result);
425
+ if (this.scope !== BindingScope.TRANSIENT) {
426
+ this._cache.set(resolutionCtx!, result);
370
427
  }
371
- // Do not cache for `TRANSIENT`
372
428
  return result;
373
429
  }
374
430
 
@@ -383,7 +439,7 @@ export class Binding<T = BoundValue> extends EventEmitter {
383
439
 
384
440
  /**
385
441
  * Invalidate the binding cache so that its value will be reloaded next time.
386
- * This is useful to force reloading a singleton when its configuration or
442
+ * This is useful to force reloading a cached value when its configuration or
387
443
  * dependencies are changed.
388
444
  * **WARNING**: The state held in the cached value will be gone.
389
445
  *
@@ -391,15 +447,11 @@ export class Binding<T = BoundValue> extends EventEmitter {
391
447
  */
392
448
  refresh(ctx: Context) {
393
449
  if (!this._cache) return;
394
- if (this.scope === BindingScope.SINGLETON) {
395
- // Cache the value
396
- const ownerCtx = ctx.getOwnerContext(this.key);
397
- if (ownerCtx != null) {
398
- this._cache.delete(ownerCtx);
450
+ if (this.scope !== BindingScope.TRANSIENT) {
451
+ const resolutionCtx = ctx.getResolutionContext(this);
452
+ if (resolutionCtx != null) {
453
+ this._cache.delete(resolutionCtx);
399
454
  }
400
- } else if (this.scope === BindingScope.CONTEXT) {
401
- // Cache the value at the current context
402
- this._cache.delete(ctx);
403
455
  }
404
456
  }
405
457
 
@@ -451,22 +503,20 @@ export class Binding<T = BoundValue> extends EventEmitter {
451
503
  if (debug.enabled) {
452
504
  debug('Get value for binding %s', this.key);
453
505
  }
506
+
507
+ const options = asResolutionOptions(optionsOrSession);
508
+ const resolutionCtx = this.getResolutionContext(ctx, options);
509
+ if (resolutionCtx == null) return undefined;
454
510
  // First check cached value for non-transient
455
511
  if (this._cache) {
456
- if (this.scope === BindingScope.SINGLETON) {
457
- const ownerCtx = ctx.getOwnerContext(this.key);
458
- if (ownerCtx && this._cache.has(ownerCtx)) {
459
- return this._cache.get(ownerCtx)!;
460
- }
461
- } else if (this.scope === BindingScope.CONTEXT) {
462
- if (this._cache.has(ctx)) {
463
- return this._cache.get(ctx)!;
512
+ if (this.scope !== BindingScope.TRANSIENT) {
513
+ if (resolutionCtx && this._cache.has(resolutionCtx)) {
514
+ return this._cache.get(resolutionCtx)!;
464
515
  }
465
516
  }
466
517
  }
467
- const options = asResolutionOptions(optionsOrSession);
468
- const resolutionCtx = {
469
- context: ctx,
518
+ const resolutionMetadata = {
519
+ context: resolutionCtx!,
470
520
  binding: this,
471
521
  options,
472
522
  };
@@ -477,25 +527,62 @@ export class Binding<T = BoundValue> extends EventEmitter {
477
527
  // We already test `this._getValue` is a function. It's safe to assert
478
528
  // that `this._getValue` is not undefined.
479
529
  return this._getValue!({
480
- ...resolutionCtx,
530
+ ...resolutionMetadata,
481
531
  options: optionsWithSession,
482
532
  });
483
533
  },
484
534
  this,
485
535
  options.session,
486
536
  );
487
- return this._cacheValue(ctx, result);
537
+ return this._cacheValue(resolutionCtx!, result);
488
538
  }
489
539
  // `@inject.binding` adds a binding without _getValue
490
540
  if (options.optional) return undefined;
491
541
  return Promise.reject(
492
542
  new ResolutionError(
493
543
  `No value was configured for binding ${this.key}.`,
494
- resolutionCtx,
544
+ resolutionMetadata,
495
545
  ),
496
546
  );
497
547
  }
498
548
 
549
+ /**
550
+ * Locate and validate the resolution context
551
+ * @param ctx - Current context
552
+ * @param options - Resolution options
553
+ */
554
+ private getResolutionContext(ctx: Context, options: ResolutionOptions) {
555
+ const resolutionCtx = ctx.getResolutionContext(this);
556
+ switch (this.scope) {
557
+ case BindingScope.APPLICATION:
558
+ case BindingScope.SERVER:
559
+ case BindingScope.REQUEST:
560
+ if (resolutionCtx == null) {
561
+ const msg =
562
+ `Binding "${this.key}" in context "${ctx.name}" cannot` +
563
+ ` be resolved in scope "${this.scope}"`;
564
+ if (options.optional) {
565
+ debug(msg);
566
+ return undefined;
567
+ }
568
+ throw new Error(msg);
569
+ }
570
+ }
571
+
572
+ const ownerCtx = ctx.getOwnerContext(this.key);
573
+ if (ownerCtx != null && !ownerCtx.isVisibleTo(resolutionCtx!)) {
574
+ const msg =
575
+ `Resolution context "${resolutionCtx?.name}" does not have ` +
576
+ `visibility to binding "${this.key} (scope:${this.scope})" in context "${ownerCtx.name}"`;
577
+ if (options.optional) {
578
+ debug(msg);
579
+ return undefined;
580
+ }
581
+ throw new Error(msg);
582
+ }
583
+ return resolutionCtx;
584
+ }
585
+
499
586
  /**
500
587
  * Lock the binding so that it cannot be rebound
501
588
  */
@@ -855,7 +942,7 @@ export class Binding<T = BoundValue> extends EventEmitter {
855
942
  * easy to read.
856
943
  * @param key - Binding key
857
944
  */
858
- static bind<T = unknown>(key: BindingAddress<T>): Binding<T> {
945
+ static bind<V = unknown>(key: BindingAddress<V>): Binding<V> {
859
946
  return new Binding(key);
860
947
  }
861
948
 
@@ -868,13 +955,13 @@ export class Binding<T = BoundValue> extends EventEmitter {
868
955
  * .to({port: 3000});
869
956
  * ```
870
957
  *
871
- * @typeParam T Generic type for the configuration value (not the binding to
958
+ * @typeParam V Generic type for the configuration value (not the binding to
872
959
  * be configured)
873
960
  *
874
961
  * @param key - Key for the binding to be configured
875
962
  */
876
- static configure<T = unknown>(key: BindingAddress): Binding<T> {
877
- return new Binding(BindingKey.buildKeyForConfig<T>(key)).tag({
963
+ static configure<V = unknown>(key: BindingAddress): Binding<V> {
964
+ return new Binding(BindingKey.buildKeyForConfig<V>(key)).tag({
878
965
  [ContextTags.CONFIGURATION_FOR]: key.toString(),
879
966
  });
880
967
  }
@@ -45,7 +45,8 @@ export interface ContextViewEvent<T> extends ContextEvent {
45
45
  * - 'refresh': when the view is refreshed as bindings are added/removed
46
46
  * - 'resolve': when the cached values are resolved and updated
47
47
  */
48
- export class ContextView<T = unknown> extends EventEmitter
48
+ export class ContextView<T = unknown>
49
+ extends EventEmitter
49
50
  implements ContextObserver {
50
51
  /**
51
52
  * An array of cached bindings that matches the binding filter
@@ -250,7 +251,7 @@ export class ContextView<T = unknown> extends EventEmitter
250
251
  */
251
252
  on(
252
253
  eventName: 'bind',
253
- listener: <T>(event: ContextViewEvent<T>) => void,
254
+ listener: <V>(event: ContextViewEvent<V>) => void,
254
255
  ): this;
255
256
 
256
257
  /**
@@ -261,7 +262,7 @@ export class ContextView<T = unknown> extends EventEmitter
261
262
  */
262
263
  on(
263
264
  eventName: 'unbind',
264
- listener: <T>(event: ContextViewEvent<T> & {cachedValue?: T}) => void,
265
+ listener: <V>(event: ContextViewEvent<V> & {cachedValue?: V}) => void,
265
266
  ): this;
266
267
 
267
268
  /**
@@ -281,7 +282,7 @@ export class ContextView<T = unknown> extends EventEmitter
281
282
  * @param listener The listener function to call when the event is emitted.
282
283
  */
283
284
  // eslint-disable-next-line @typescript-eslint/unified-signatures
284
- on(eventName: 'refresh', listener: <T>(result: T[]) => void): this;
285
+ on(eventName: 'refresh', listener: <V>(result: V[]) => void): this;
285
286
 
286
287
  /**
287
288
  * The "close" event is emitted when the view is closed (stopped observing
@@ -310,7 +311,7 @@ export class ContextView<T = unknown> extends EventEmitter
310
311
  */
311
312
  once(
312
313
  eventName: 'bind',
313
- listener: <T>(event: ContextViewEvent<T>) => void,
314
+ listener: <V>(event: ContextViewEvent<V>) => void,
314
315
  ): this;
315
316
 
316
317
  /**
@@ -321,7 +322,7 @@ export class ContextView<T = unknown> extends EventEmitter
321
322
  */
322
323
  once(
323
324
  eventName: 'unbind',
324
- listener: <T>(event: ContextViewEvent<T> & {cachedValue?: T}) => void,
325
+ listener: <V>(event: ContextViewEvent<V> & {cachedValue?: V}) => void,
325
326
  ): this;
326
327
 
327
328
  /**
@@ -341,7 +342,7 @@ export class ContextView<T = unknown> extends EventEmitter
341
342
  * @param listener The listener function to call when the event is emitted.
342
343
  */
343
344
  // eslint-disable-next-line @typescript-eslint/unified-signatures
344
- once(eventName: 'refresh', listener: <T>(result: T[]) => void): this;
345
+ once(eventName: 'refresh', listener: <V>(result: V[]) => void): this;
345
346
 
346
347
  /**
347
348
  * The "close" event is emitted when the view is closed (stopped observing
package/src/context.ts CHANGED
@@ -5,7 +5,12 @@
5
5
 
6
6
  import debugFactory, {Debugger} from 'debug';
7
7
  import {EventEmitter} from 'events';
8
- import {Binding, BindingInspectOptions, BindingTag} from './binding';
8
+ import {
9
+ Binding,
10
+ BindingInspectOptions,
11
+ BindingScope,
12
+ BindingTag,
13
+ } from './binding';
9
14
  import {
10
15
  ConfigurationResolver,
11
16
  DefaultConfigurationResolver,
@@ -91,6 +96,11 @@ export class Context extends EventEmitter {
91
96
  */
92
97
  protected _debug: Debugger;
93
98
 
99
+ /**
100
+ * Scope for binding resolution
101
+ */
102
+ scope: BindingScope = BindingScope.CONTEXT;
103
+
94
104
  /**
95
105
  * Create a new context.
96
106
  *
@@ -467,17 +477,102 @@ export class Context extends EventEmitter {
467
477
  }
468
478
 
469
479
  /**
470
- * Get the owning context for a binding key
471
- * @param key - Binding key
480
+ * Get the owning context for a binding or its key
481
+ * @param keyOrBinding - Binding object or key
472
482
  */
473
- getOwnerContext(key: BindingAddress): Context | undefined {
474
- if (this.contains(key)) return this;
483
+ getOwnerContext(
484
+ keyOrBinding: BindingAddress | Readonly<Binding<unknown>>,
485
+ ): Context | undefined {
486
+ let key: BindingAddress;
487
+ if (keyOrBinding instanceof Binding) {
488
+ key = keyOrBinding.key;
489
+ } else {
490
+ key = keyOrBinding as BindingAddress;
491
+ }
492
+ if (this.contains(key)) {
493
+ if (keyOrBinding instanceof Binding) {
494
+ // Check if the contained binding is the same
495
+ if (this.registry.get(key.toString()) === keyOrBinding) {
496
+ return this;
497
+ }
498
+ return undefined;
499
+ }
500
+ return this;
501
+ }
475
502
  if (this._parent) {
476
503
  return this._parent.getOwnerContext(key);
477
504
  }
478
505
  return undefined;
479
506
  }
480
507
 
508
+ /**
509
+ * Get the context matching the scope
510
+ * @param scope - Binding scope
511
+ */
512
+ getScopedContext(
513
+ scope:
514
+ | BindingScope.APPLICATION
515
+ | BindingScope.SERVER
516
+ | BindingScope.REQUEST,
517
+ ): Context | undefined {
518
+ if (this.scope === scope) return this;
519
+ if (this._parent) {
520
+ return this._parent.getScopedContext(scope);
521
+ }
522
+ return undefined;
523
+ }
524
+
525
+ /**
526
+ * Locate the resolution context for the given binding. Only bindings in the
527
+ * resolution context and its ancestors are visible as dependencies to resolve
528
+ * the given binding
529
+ * @param binding - Binding object
530
+ */
531
+ getResolutionContext(
532
+ binding: Readonly<Binding<unknown>>,
533
+ ): Context | undefined {
534
+ let resolutionCtx: Context | undefined;
535
+ switch (binding.scope) {
536
+ case BindingScope.SINGLETON:
537
+ // Use the owner context
538
+ return this.getOwnerContext(binding.key);
539
+ case BindingScope.TRANSIENT:
540
+ case BindingScope.CONTEXT:
541
+ // Use the current context
542
+ return this;
543
+ case BindingScope.REQUEST:
544
+ resolutionCtx = this.getScopedContext(binding.scope);
545
+ if (resolutionCtx != null) {
546
+ return resolutionCtx;
547
+ } else {
548
+ // If no `REQUEST` scope exists in the chain, fall back to the current
549
+ // context
550
+ this.debug(
551
+ 'No context is found for binding "%s (scope=%s)". Fall back to the current context.',
552
+ binding.key,
553
+ binding.scope,
554
+ );
555
+ return this;
556
+ }
557
+ default:
558
+ // Use the scoped context
559
+ return this.getScopedContext(binding.scope);
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Check if this context is visible (same or ancestor) to the given one
565
+ * @param ctx - Another context object
566
+ */
567
+ isVisibleTo(ctx: Context) {
568
+ let current: Context | undefined = ctx;
569
+ while (current != null) {
570
+ if (current === this) return true;
571
+ current = current._parent;
572
+ }
573
+ return false;
574
+ }
575
+
481
576
  /**
482
577
  * Find bindings using a key pattern or filter function
483
578
  * @param pattern - A filter function, a regexp or a wildcard pattern with
package/src/inject.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  MetadataMap,
12
12
  ParameterDecoratorFactory,
13
13
  PropertyDecoratorFactory,
14
+ Reflector,
14
15
  } from '@loopback/metadata';
15
16
  import {Binding, BindingTag} from './binding';
16
17
  import {
@@ -26,17 +27,20 @@ import {BindingCreationPolicy, Context} from './context';
26
27
  import {ContextView, createViewGetter} from './context-view';
27
28
  import {JSONObject} from './json-types';
28
29
  import {ResolutionOptions, ResolutionSession} from './resolution-session';
29
- import {BoundValue, ValueOrPromise} from './value-promise';
30
+ import {BoundValue, Constructor, ValueOrPromise} from './value-promise';
30
31
 
31
- const PARAMETERS_KEY = MetadataAccessor.create<Injection, ParameterDecorator>(
32
- 'inject:parameters',
33
- );
34
- const PROPERTIES_KEY = MetadataAccessor.create<Injection, PropertyDecorator>(
35
- 'inject:properties',
36
- );
32
+ const INJECT_PARAMETERS_KEY = MetadataAccessor.create<
33
+ Injection,
34
+ ParameterDecorator
35
+ >('inject:parameters');
36
+
37
+ const INJECT_PROPERTIES_KEY = MetadataAccessor.create<
38
+ Injection,
39
+ PropertyDecorator
40
+ >('inject:properties');
37
41
 
38
42
  // A key to cache described argument injections
39
- const METHODS_KEY = MetadataAccessor.create<Injection, MethodDecorator>(
43
+ const INJECT_METHODS_KEY = MetadataAccessor.create<Injection, MethodDecorator>(
40
44
  'inject:methods',
41
45
  );
42
46
 
@@ -151,7 +155,7 @@ export function inject(
151
155
  const paramDecorator: ParameterDecorator = ParameterDecoratorFactory.createDecorator<
152
156
  Injection
153
157
  >(
154
- PARAMETERS_KEY,
158
+ INJECT_PARAMETERS_KEY,
155
159
  {
156
160
  target,
157
161
  member,
@@ -187,7 +191,7 @@ export function inject(
187
191
  const propDecorator: PropertyDecorator = PropertyDecoratorFactory.createDecorator<
188
192
  Injection
189
193
  >(
190
- PROPERTIES_KEY,
194
+ INJECT_PROPERTIES_KEY,
191
195
  {
192
196
  target,
193
197
  member,
@@ -574,7 +578,7 @@ export function describeInjectedArguments(
574
578
  // Try to read from cache
575
579
  const cache =
576
580
  MetadataInspector.getAllMethodMetadata<Readonly<Injection>[]>(
577
- METHODS_KEY,
581
+ INJECT_METHODS_KEY,
578
582
  target,
579
583
  {
580
584
  ownMetadataOnly: true,
@@ -596,7 +600,7 @@ export function describeInjectedArguments(
596
600
  }
597
601
  meta =
598
602
  MetadataInspector.getAllParameterMetadata<Readonly<Injection>>(
599
- PARAMETERS_KEY,
603
+ INJECT_PARAMETERS_KEY,
600
604
  target,
601
605
  method,
602
606
  options,
@@ -605,7 +609,7 @@ export function describeInjectedArguments(
605
609
  // Cache the result
606
610
  cache[method] = meta;
607
611
  MetadataInspector.defineMetadata<MetadataMap<Readonly<Injection>[]>>(
608
- METHODS_KEY,
612
+ INJECT_METHODS_KEY,
609
613
  cache,
610
614
  target,
611
615
  );
@@ -623,7 +627,7 @@ export function inspectTargetType(injection: Readonly<Injection>) {
623
627
  injection.target,
624
628
  injection.member!,
625
629
  );
626
- return designType.parameterTypes[
630
+ return designType?.parameterTypes?.[
627
631
  injection.methodDescriptorOrParameterIndex as number
628
632
  ];
629
633
  }
@@ -706,7 +710,7 @@ export function describeInjectedProperties(
706
710
  ): MetadataMap<Readonly<Injection>> {
707
711
  const metadata =
708
712
  MetadataInspector.getAllPropertyMetadata<Readonly<Injection>>(
709
- PROPERTIES_KEY,
713
+ INJECT_PROPERTIES_KEY,
710
714
  target,
711
715
  ) ?? {};
712
716
  return metadata;
@@ -774,3 +778,21 @@ function inspectInjection(injection: Readonly<Injection<unknown>>) {
774
778
  }
775
779
  return descriptor;
776
780
  }
781
+
782
+ /**
783
+ * Check if the given class has `@inject` or other decorations that map to
784
+ * `@inject`.
785
+ *
786
+ * @param cls - Class with possible `@inject` decorations
787
+ */
788
+ export function hasInjections(cls: Constructor<unknown>): boolean {
789
+ return (
790
+ MetadataInspector.getClassMetadata(INJECT_PARAMETERS_KEY, cls) != null ||
791
+ Reflector.getMetadata(INJECT_PARAMETERS_KEY.toString(), cls.prototype) !=
792
+ null ||
793
+ MetadataInspector.getAllPropertyMetadata(
794
+ INJECT_PROPERTIES_KEY,
795
+ cls.prototype,
796
+ ) != null
797
+ );
798
+ }
@@ -29,6 +29,7 @@ export type Next = () => ValueOrPromise<NonVoid>;
29
29
  * It serves as the base interface for various types of interceptors, such
30
30
  * as method invocation interceptor or request/response processing interceptor.
31
31
  *
32
+ * @remarks
32
33
  * We choose `NonVoid` as the return type to avoid possible bugs that an
33
34
  * interceptor forgets to return the value from `next()`. For example, the code
34
35
  * below will fail to compile.
@@ -14,7 +14,7 @@ import {
14
14
  import assert from 'assert';
15
15
  import debugFactory from 'debug';
16
16
  import {Binding, BindingTemplate} from './binding';
17
- import {bind} from './binding-decorator';
17
+ import {injectable} from './binding-decorator';
18
18
  import {
19
19
  BindingFromClassOptions,
20
20
  BindingSpec,
@@ -159,7 +159,7 @@ export function asGlobalInterceptor(group?: string): BindingTemplate {
159
159
  * @param specs - Extra binding specs
160
160
  */
161
161
  export function globalInterceptor(group?: string, ...specs: BindingSpec[]) {
162
- return bind(asGlobalInterceptor(group), ...specs);
162
+ return injectable(asGlobalInterceptor(group), ...specs);
163
163
  }
164
164
 
165
165
  /**
package/src/resolver.ts CHANGED
@@ -6,7 +6,6 @@
6
6
  import {DecoratorFactory} from '@loopback/metadata';
7
7
  import assert from 'assert';
8
8
  import debugModule from 'debug';
9
- import {BindingScope} from './binding';
10
9
  import {isBindingAddress} from './binding-filter';
11
10
  import {BindingAddress} from './binding-key';
12
11
  import {Context} from './context';
@@ -98,11 +97,8 @@ function resolveContext(
98
97
  session?: ResolutionSession,
99
98
  ) {
100
99
  const currentBinding = session?.currentBinding;
101
- if (
102
- currentBinding == null ||
103
- currentBinding.scope !== BindingScope.SINGLETON
104
- ) {
105
- // No current binding or its scope is not `SINGLETON`
100
+ if (currentBinding == null) {
101
+ // No current binding
106
102
  return ctx;
107
103
  }
108
104
 
@@ -113,9 +109,9 @@ function resolveContext(
113
109
  typeof injection.methodDescriptorOrParameterIndex !== 'number';
114
110
 
115
111
  if (isConstructorOrPropertyInjection) {
116
- // Set context to the owner context of the current binding for constructor
117
- // or property injections against a singleton
118
- ctx = ctx.getOwnerContext(currentBinding.key)!;
112
+ // Set context to the resolution context of the current binding for
113
+ // constructor or property injections against a singleton
114
+ ctx = ctx.getResolutionContext(currentBinding)!;
119
115
  }
120
116
  return ctx;
121
117
  }
@@ -94,7 +94,7 @@ export function getDeepProperty<OUT = BoundValue, IN = BoundValue>(
94
94
  */
95
95
  export function resolveMap<T, V>(
96
96
  map: MapObject<T>,
97
- resolver: (val: T, key: string, map: MapObject<T>) => ValueOrPromise<V>,
97
+ resolver: (val: T, key: string, values: MapObject<T>) => ValueOrPromise<V>,
98
98
  ): ValueOrPromise<MapObject<V>> {
99
99
  const result: MapObject<V> = {};
100
100
  let asyncResolvers: PromiseLike<void>[] | undefined = undefined;
@@ -156,7 +156,7 @@ export function resolveMap<T, V>(
156
156
  */
157
157
  export function resolveList<T, V>(
158
158
  list: T[],
159
- resolver: (val: T, index: number, list: T[]) => ValueOrPromise<V>,
159
+ resolver: (val: T, index: number, values: T[]) => ValueOrPromise<V>,
160
160
  ): ValueOrPromise<V[]> {
161
161
  const result: V[] = new Array<V>(list.length);
162
162
  let asyncResolvers: PromiseLike<void>[] | undefined = undefined;
@@ -302,8 +302,8 @@ export function transformValueOrPromise<T, V>(
302
302
  /**
303
303
  * A utility to generate uuid v4
304
304
  *
305
- * @deprecated Use [uuid](https://www.npmjs.com/package/uuid) or
306
- * [hyperid](https://www.npmjs.com/package/hyperid) instead.
305
+ * @deprecated Use `generateUniqueId`, [uuid](https://www.npmjs.com/package/uuid)
306
+ * or [hyperid](https://www.npmjs.com/package/hyperid) instead.
307
307
  */
308
308
  export function uuid() {
309
309
  return uuidv4();