@loopback/context 1.25.0 → 2.1.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/binding-filter.d.ts +20 -2
  3. package/dist/binding-filter.js +58 -9
  4. package/dist/binding-filter.js.map +1 -1
  5. package/dist/binding.d.ts +50 -2
  6. package/dist/binding.js +33 -1
  7. package/dist/binding.js.map +1 -1
  8. package/dist/context-event.d.ts +23 -0
  9. package/dist/context-event.js +7 -0
  10. package/dist/context-event.js.map +1 -0
  11. package/dist/context-observer.d.ts +1 -36
  12. package/dist/context-subscription.d.ts +147 -0
  13. package/dist/context-subscription.js +336 -0
  14. package/dist/context-subscription.js.map +1 -0
  15. package/dist/context-tag-indexer.d.ts +42 -0
  16. package/dist/context-tag-indexer.js +134 -0
  17. package/dist/context-tag-indexer.js.map +1 -0
  18. package/dist/context-view.d.ts +2 -1
  19. package/dist/context-view.js.map +1 -1
  20. package/dist/context.d.ts +44 -68
  21. package/dist/context.js +69 -249
  22. package/dist/context.js.map +1 -1
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/inject.d.ts +9 -2
  27. package/dist/inject.js +60 -0
  28. package/dist/inject.js.map +1 -1
  29. package/dist/interceptor.js +4 -4
  30. package/dist/interceptor.js.map +1 -1
  31. package/dist/invocation.d.ts +0 -1
  32. package/dist/invocation.js +1 -5
  33. package/dist/invocation.js.map +1 -1
  34. package/dist/json-types.d.ts +28 -0
  35. package/dist/json-types.js +7 -0
  36. package/dist/json-types.js.map +1 -0
  37. package/dist/resolution-session.d.ts +2 -6
  38. package/dist/resolution-session.js +0 -4
  39. package/dist/resolution-session.js.map +1 -1
  40. package/dist/value-promise.d.ts +1 -3
  41. package/package.json +9 -9
  42. package/src/binding-filter.ts +83 -11
  43. package/src/binding.ts +79 -3
  44. package/src/context-event.ts +30 -0
  45. package/src/context-observer.ts +1 -38
  46. package/src/context-subscription.ts +403 -0
  47. package/src/context-tag-indexer.ts +149 -0
  48. package/src/context-view.ts +2 -5
  49. package/src/context.ts +104 -290
  50. package/src/index.ts +3 -0
  51. package/src/inject.ts +65 -0
  52. package/src/interceptor.ts +7 -6
  53. package/src/invocation.ts +1 -3
  54. package/src/json-types.ts +35 -0
  55. package/src/resolution-session.ts +4 -7
  56. package/src/value-promise.ts +1 -1
package/src/context.ts CHANGED
@@ -6,22 +6,25 @@
6
6
  import debugFactory from 'debug';
7
7
  import {EventEmitter} from 'events';
8
8
  import {v1 as uuidv1} from 'uuid';
9
- import {Binding, BindingTag} from './binding';
9
+ import {Binding, BindingInspectOptions, BindingTag} from './binding';
10
10
  import {
11
11
  ConfigurationResolver,
12
12
  DefaultConfigurationResolver,
13
13
  } from './binding-config';
14
- import {BindingFilter, filterByKey, filterByTag} from './binding-filter';
14
+ import {
15
+ BindingFilter,
16
+ filterByKey,
17
+ filterByTag,
18
+ isBindingTagFilter,
19
+ } from './binding-filter';
15
20
  import {BindingAddress, BindingKey} from './binding-key';
16
21
  import {BindingComparator} from './binding-sorter';
17
- import {
18
- ContextEventObserver,
19
- ContextEventType,
20
- ContextObserver,
21
- Notification,
22
- Subscription,
23
- } from './context-observer';
22
+ import {ContextEvent} from './context-event';
23
+ import {ContextEventObserver, ContextObserver} from './context-observer';
24
+ import {ContextSubscriptionManager, Subscription} from './context-subscription';
25
+ import {ContextTagIndexer} from './context-tag-indexer';
24
26
  import {ContextView} from './context-view';
27
+ import {JSONObject} from './json-types';
25
28
  import {ContextBindings} from './keys';
26
29
  import {
27
30
  asResolutionOptions,
@@ -36,21 +39,6 @@ import {
36
39
  ValueOrPromise,
37
40
  } from './value-promise';
38
41
 
39
- /**
40
- * Polyfill Symbol.asyncIterator as required by TypeScript for Node 8.x.
41
- * See https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html
42
- */
43
- if (!Symbol.asyncIterator) {
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- (Symbol as any).asyncIterator = Symbol.for('Symbol.asyncIterator');
46
- }
47
- /**
48
- * WARNING: This following import must happen after the polyfill. The
49
- * `auto-import` by an IDE such as VSCode may move the import before the
50
- * polyfill. It must be then fixed manually.
51
- */
52
- import {iterator, multiple} from 'p-event';
53
-
54
42
  const debug = debugFactory('loopback:context');
55
43
 
56
44
  /**
@@ -68,41 +56,24 @@ export class Context extends EventEmitter {
68
56
  protected readonly registry: Map<string, Binding> = new Map();
69
57
 
70
58
  /**
71
- * Parent context
72
- */
73
- protected _parent?: Context;
74
-
75
- protected configResolver: ConfigurationResolver;
76
-
77
- /**
78
- * Event listeners for parent context keyed by event names. It keeps track
79
- * of listeners from this context against its parent so that we can remove
80
- * these listeners when this context is closed.
59
+ * Indexer for bindings by tag
81
60
  */
82
- protected _parentEventListeners:
83
- | Map<
84
- string,
85
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
- (...args: any[]) => void
87
- >
88
- | undefined;
61
+ protected readonly tagIndexer: ContextTagIndexer;
89
62
 
90
63
  /**
91
- * A list of registered context observers. The Set will be created when the
92
- * first observer is added.
64
+ * Manager for observer subscriptions
93
65
  */
94
- protected observers: Set<ContextEventObserver> | undefined;
66
+ readonly subscriptionManager: ContextSubscriptionManager;
95
67
 
96
68
  /**
97
- * Internal counter for pending notification events which are yet to be
98
- * processed by observers.
69
+ * Parent context
99
70
  */
100
- private pendingNotifications = 0;
71
+ protected _parent?: Context;
101
72
 
102
73
  /**
103
- * Queue for background notifications for observers
74
+ * Configuration resolver
104
75
  */
105
- private notificationQueue: AsyncIterableIterator<Notification> | undefined;
76
+ protected configResolver: ConfigurationResolver;
106
77
 
107
78
  /**
108
79
  * Create a new context.
@@ -128,12 +99,34 @@ export class Context extends EventEmitter {
128
99
  */
129
100
  constructor(_parent?: Context | string, name?: string) {
130
101
  super();
102
+ // The number of listeners can grow with the number of child contexts
103
+ // For example, each request can add a listener to the RestServer and the
104
+ // listener is removed when the request processing is finished.
105
+ // See https://github.com/strongloop/loopback-next/issues/4363
106
+ this.setMaxListeners(Infinity);
131
107
  if (typeof _parent === 'string') {
132
108
  name = _parent;
133
109
  _parent = undefined;
134
110
  }
135
111
  this._parent = _parent;
136
- this.name = name ?? uuidv1();
112
+ this.name = name ?? this.generateName();
113
+ this.tagIndexer = new ContextTagIndexer(this);
114
+ this.subscriptionManager = new ContextSubscriptionManager(this);
115
+ }
116
+
117
+ private generateName() {
118
+ const id = uuidv1();
119
+ let prefix = `${this.constructor.name}-`;
120
+ if (prefix === 'Context-') prefix = '';
121
+ return `${prefix}${id}`;
122
+ }
123
+
124
+ /**
125
+ * @internal
126
+ * Getter for ContextSubscriptionManager
127
+ */
128
+ get parent() {
129
+ return this._parent;
137
130
  }
138
131
 
139
132
  /**
@@ -141,8 +134,7 @@ export class Context extends EventEmitter {
141
134
  * as the prefix
142
135
  * @param args - Arguments for the debug
143
136
  */
144
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
- private _debug(...args: any[]) {
137
+ private _debug(...args: unknown[]) {
146
138
  /* istanbul ignore if */
147
139
  if (!debug.enabled) return;
148
140
  const formatter = args.shift();
@@ -154,176 +146,22 @@ export class Context extends EventEmitter {
154
146
  }
155
147
 
156
148
  /**
157
- * Set up an internal listener to notify registered observers asynchronously
158
- * upon `bind` and `unbind` events. This method will be called lazily when
159
- * the first observer is added.
160
- */
161
- private setupEventHandlersIfNeeded() {
162
- if (this.notificationQueue != null) return;
163
-
164
- this.addParentEventListener('bind');
165
- this.addParentEventListener('unbind');
166
-
167
- // The following are two async functions. Returned promises are ignored as
168
- // they are long-running background tasks.
169
- this.startNotificationTask().catch(err => {
170
- this.handleNotificationError(err);
171
- });
172
-
173
- let ctx = this._parent;
174
- while (ctx) {
175
- ctx.setupEventHandlersIfNeeded();
176
- ctx = ctx._parent;
177
- }
178
- }
179
-
180
- /**
181
- * Add an event listener to its parent context so that this context will
182
- * be notified of parent events, such as `bind` or `unbind`.
183
- * @param event - Event name
149
+ * A strongly-typed method to emit context events
150
+ * @param type Event type
151
+ * @param event Context event
184
152
  */
185
- private addParentEventListener(event: string) {
186
- if (this._parent == null) return;
187
-
188
- // Keep track of parent event listeners so that we can remove them
189
- this._parentEventListeners = this._parentEventListeners ?? new Map();
190
- if (this._parentEventListeners.has(event)) return;
191
-
192
- const parentEventListener = (
193
- binding: Readonly<Binding<unknown>>,
194
- context: Context,
195
- ) => {
196
- // Propagate the event to this context only if the binding key does not
197
- // exist in this context. The parent binding is shadowed if there is a
198
- // binding with the same key in this one.
199
- if (this.contains(binding.key)) {
200
- this._debug(
201
- 'Event %s %s is not re-emitted from %s to %s',
202
- event,
203
- binding.key,
204
- context.name,
205
- this.name,
206
- );
207
- return;
208
- }
209
- this._debug(
210
- 'Re-emitting %s %s from %s to %s',
211
- event,
212
- binding.key,
213
- context.name,
214
- this.name,
215
- );
216
- this.emit(event, binding, context);
217
- };
218
- this._parentEventListeners.set(event, parentEventListener);
219
- // Listen on the parent context events
220
- this._parent.on(event, parentEventListener);
153
+ emitEvent<T extends ContextEvent>(type: string, event: T) {
154
+ this.emit(type, event);
221
155
  }
222
156
 
223
157
  /**
224
- * Handle errors caught during the notification of observers
225
- * @param err - Error
158
+ * Emit an `error` event
159
+ * @param err Error
226
160
  */
227
- private handleNotificationError(err: unknown) {
228
- // Bubbling up the error event over the context chain
229
- // until we find an error listener
230
- // eslint-disable-next-line @typescript-eslint/no-this-alias
231
- let ctx: Context | undefined = this;
232
- while (ctx) {
233
- if (ctx.listenerCount('error') === 0) {
234
- // No error listener found, try its parent
235
- ctx = ctx._parent;
236
- continue;
237
- }
238
- this._debug('Emitting error to context %s', ctx.name, err);
239
- ctx.emit('error', err);
240
- return;
241
- }
242
- // No context with error listeners found
243
- this._debug('No error handler is configured for the context chain', err);
244
- // Let it crash now by emitting an error event
161
+ emitError(err: unknown) {
245
162
  this.emit('error', err);
246
163
  }
247
164
 
248
- /**
249
- * Start a background task to listen on context events and notify observers
250
- */
251
- private startNotificationTask() {
252
- // Set up listeners on `bind` and `unbind` for notifications
253
- this.setupNotification('bind', 'unbind');
254
-
255
- // Create an async iterator for the `notification` event as a queue
256
- this.notificationQueue = iterator(this, 'notification');
257
-
258
- return this.processNotifications();
259
- }
260
-
261
- /**
262
- * Process notification events as they arrive on the queue
263
- */
264
- private async processNotifications() {
265
- const events = this.notificationQueue;
266
- if (events == null) return;
267
- for await (const {eventType, binding, context, observers} of events) {
268
- // The loop will happen asynchronously upon events
269
- try {
270
- // The execution of observers happen in the Promise micro-task queue
271
- await this.notifyObservers(eventType, binding, context, observers);
272
- this.pendingNotifications--;
273
- this._debug(
274
- 'Observers notified for %s of binding %s',
275
- eventType,
276
- binding.key,
277
- );
278
- this.emit('observersNotified', {eventType, binding});
279
- } catch (err) {
280
- this.pendingNotifications--;
281
- this._debug('Error caught from observers', err);
282
- // Errors caught from observers. Emit it to the current context.
283
- // If no error listeners are registered, crash the process.
284
- this.emit('error', err);
285
- }
286
- }
287
- }
288
-
289
- /**
290
- * Listen on given event types and emit `notification` event. This method
291
- * merge multiple event types into one for notification.
292
- * @param eventTypes - Context event types
293
- */
294
- private setupNotification(...eventTypes: ContextEventType[]) {
295
- for (const eventType of eventTypes) {
296
- this.on(eventType, (binding, context) => {
297
- // No need to schedule notifications if no observers are present
298
- if (!this.observers || this.observers.size === 0) return;
299
- // Track pending events
300
- this.pendingNotifications++;
301
- // Take a snapshot of current observers to ensure notifications of this
302
- // event will only be sent to current ones. Emit a new event to notify
303
- // current context observers.
304
- this.emit('notification', {
305
- eventType,
306
- binding,
307
- context,
308
- observers: new Set(this.observers),
309
- });
310
- });
311
- }
312
- }
313
-
314
- /**
315
- * Wait until observers are notified for all of currently pending notification
316
- * events.
317
- *
318
- * This method is for test only to perform assertions after observers are
319
- * notified for relevant events.
320
- */
321
- protected async waitUntilPendingNotificationsDone(timeout?: number) {
322
- const count = this.pendingNotifications;
323
- if (count === 0) return;
324
- await multiple(this, 'observersNotified', {count, timeout});
325
- }
326
-
327
165
  /**
328
166
  * Create a binding with the given key in the context. If a locked binding
329
167
  * already exists with the same key, an error will be thrown.
@@ -357,9 +195,13 @@ export class Context extends EventEmitter {
357
195
  this.registry.set(key, binding);
358
196
  if (existingBinding !== binding) {
359
197
  if (existingBinding != null) {
360
- this.emit('unbind', existingBinding, this);
198
+ this.emitEvent('unbind', {
199
+ binding: existingBinding,
200
+ context: this,
201
+ type: 'unbind',
202
+ });
361
203
  }
362
- this.emit('bind', binding, this);
204
+ this.emitEvent('bind', {binding, context: this, type: 'bind'});
363
205
  }
364
206
  return this;
365
207
  }
@@ -505,7 +347,7 @@ export class Context extends EventEmitter {
505
347
  if (binding?.isLocked)
506
348
  throw new Error(`Cannot unbind key "${key}" of a locked binding`);
507
349
  this.registry.delete(key);
508
- this.emit('unbind', binding, this);
350
+ this.emitEvent('unbind', {binding, context: this, type: 'unbind'});
509
351
  return true;
510
352
  }
511
353
 
@@ -514,10 +356,7 @@ export class Context extends EventEmitter {
514
356
  * @param observer - Context observer instance or function
515
357
  */
516
358
  subscribe(observer: ContextEventObserver): Subscription {
517
- this.observers = this.observers ?? new Set();
518
- this.setupEventHandlersIfNeeded();
519
- this.observers.add(observer);
520
- return new ContextSubscription(this, observer);
359
+ return this.subscriptionManager.subscribe(observer);
521
360
  }
522
361
 
523
362
  /**
@@ -525,8 +364,7 @@ export class Context extends EventEmitter {
525
364
  * @param observer - Context event observer
526
365
  */
527
366
  unsubscribe(observer: ContextEventObserver): boolean {
528
- if (!this.observers) return false;
529
- return this.observers.delete(observer);
367
+ return this.subscriptionManager.unsubscribe(observer);
530
368
  }
531
369
 
532
370
  /**
@@ -540,20 +378,8 @@ export class Context extends EventEmitter {
540
378
  */
541
379
  close() {
542
380
  this._debug('Closing context...');
543
- this.observers = undefined;
544
- if (this.notificationQueue != null) {
545
- // Cancel the notification iterator
546
- this.notificationQueue.return!(undefined).catch(err => {
547
- this.handleNotificationError(err);
548
- });
549
- this.notificationQueue = undefined;
550
- }
551
- if (this._parent && this._parentEventListeners) {
552
- for (const [event, listener] of this._parentEventListeners) {
553
- this._parent.removeListener(event, listener);
554
- }
555
- this._parentEventListeners = undefined;
556
- }
381
+ this.subscriptionManager.close();
382
+ this.tagIndexer.close();
557
383
  }
558
384
 
559
385
  /**
@@ -561,8 +387,7 @@ export class Context extends EventEmitter {
561
387
  * @param observer - Context observer
562
388
  */
563
389
  isSubscribed(observer: ContextObserver) {
564
- if (!this.observers) return false;
565
- return this.observers.has(observer);
390
+ return this.subscriptionManager.isSubscribed(observer);
566
391
  }
567
392
 
568
393
  /**
@@ -579,34 +404,6 @@ export class Context extends EventEmitter {
579
404
  return view;
580
405
  }
581
406
 
582
- /**
583
- * Publish an event to the registered observers. Please note the
584
- * notification is queued and performed asynchronously so that we allow fluent
585
- * APIs such as `ctx.bind('key').to(...).tag(...);` and give observers the
586
- * fully populated binding.
587
- *
588
- * @param eventType - Event names: `bind` or `unbind`
589
- * @param binding - Binding bound or unbound
590
- * @param context - Owner context
591
- * @param observers - Current set of context observers
592
- */
593
- protected async notifyObservers(
594
- eventType: ContextEventType,
595
- binding: Readonly<Binding<unknown>>,
596
- context: Context,
597
- observers = this.observers,
598
- ) {
599
- if (!observers || observers.size === 0) return;
600
-
601
- for (const observer of observers) {
602
- if (typeof observer === 'function') {
603
- await observer(eventType, binding, context);
604
- } else if (!observer.filter || observer.filter(binding)) {
605
- await observer.observe(eventType, binding, context);
606
- }
607
- }
608
- }
609
-
610
407
  /**
611
408
  * Check if a binding exists with the given key in the local context without
612
409
  * delegating to the parent context
@@ -658,6 +455,11 @@ export class Context extends EventEmitter {
658
455
  find<ValueType = BoundValue>(
659
456
  pattern?: string | RegExp | BindingFilter,
660
457
  ): Readonly<Binding<ValueType>>[] {
458
+ // Optimize if the binding filter is for tags
459
+ if (typeof pattern === 'function' && isBindingTagFilter(pattern)) {
460
+ return this._findByTagIndex(pattern.bindingTagPattern);
461
+ }
462
+
661
463
  const bindings: Readonly<Binding<ValueType>>[] = [];
662
464
  const filter = filterByKey(pattern);
663
465
 
@@ -689,6 +491,18 @@ export class Context extends EventEmitter {
689
491
  return this.find(filterByTag(tagFilter));
690
492
  }
691
493
 
494
+ /**
495
+ * Find bindings by tag leveraging indexes
496
+ * @param tag - Tag name pattern or name/value pairs
497
+ */
498
+ protected _findByTagIndex<ValueType = BoundValue>(
499
+ tag: BindingTag | RegExp,
500
+ ): Readonly<Binding<ValueType>>[] {
501
+ const currentBindings = this.tagIndexer.findByTagIndex(tag);
502
+ const parentBindings = this._parent && this._parent?._findByTagIndex(tag);
503
+ return this._mergeWithParent(currentBindings, parentBindings);
504
+ }
505
+
692
506
  protected _mergeWithParent<ValueType>(
693
507
  childList: Readonly<Binding<ValueType>>[],
694
508
  parentList?: Readonly<Binding<ValueType>>[],
@@ -968,8 +782,8 @@ export class Context extends EventEmitter {
968
782
  /**
969
783
  * Create a plain JSON object for the context
970
784
  */
971
- toJSON(): object {
972
- const bindings: Record<string, object> = {};
785
+ toJSON(): JSONObject {
786
+ const bindings: JSONObject = {};
973
787
  for (const [k, v] of this.registry) {
974
788
  bindings[k] = v.toJSON();
975
789
  }
@@ -979,39 +793,39 @@ export class Context extends EventEmitter {
979
793
  /**
980
794
  * Inspect the context and dump out a JSON object representing the context
981
795
  * hierarchy
796
+ * @param options - Options for inspect
982
797
  */
983
798
  // TODO(rfeng): Evaluate https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
984
- inspect(): object {
985
- const json: Record<string, unknown> = {
799
+ inspect(options: ContextInspectOptions = {}): JSONObject {
800
+ options = {
801
+ includeParent: true,
802
+ includeInjections: false,
803
+ ...options,
804
+ };
805
+ const bindings: JSONObject = {};
806
+ for (const [k, v] of this.registry) {
807
+ bindings[k] = v.inspect(options);
808
+ }
809
+ const json: JSONObject = {
986
810
  name: this.name,
987
- bindings: this.toJSON(),
811
+ bindings,
988
812
  };
813
+ if (!options.includeParent) return json;
989
814
  if (this._parent) {
990
- json.parent = this._parent.inspect();
815
+ json.parent = this._parent.inspect(options);
991
816
  }
992
817
  return json;
993
818
  }
994
819
  }
995
820
 
996
821
  /**
997
- * An implementation of `Subscription` interface for context events
822
+ * Options for context.inspect()
998
823
  */
999
- class ContextSubscription implements Subscription {
1000
- constructor(
1001
- protected context: Context,
1002
- protected observer: ContextEventObserver,
1003
- ) {}
1004
-
1005
- private _closed = false;
1006
-
1007
- unsubscribe() {
1008
- this.context.unsubscribe(this.observer);
1009
- this._closed = true;
1010
- }
1011
-
1012
- get closed() {
1013
- return this._closed;
1014
- }
824
+ export interface ContextInspectOptions extends BindingInspectOptions {
825
+ /**
826
+ * The flag to control if parent context should be inspected
827
+ */
828
+ includeParent?: boolean;
1015
829
  }
1016
830
 
1017
831
  /**
package/src/index.ts CHANGED
@@ -12,7 +12,9 @@ export * from './binding-inspector';
12
12
  export * from './binding-key';
13
13
  export * from './binding-sorter';
14
14
  export * from './context';
15
+ export * from './context-event';
15
16
  export * from './context-observer';
17
+ export * from './context-subscription';
16
18
  export * from './context-view';
17
19
  export * from './inject';
18
20
  export * from './inject-config';
@@ -25,3 +27,4 @@ export * from './provider';
25
27
  export * from './resolution-session';
26
28
  export * from './resolver';
27
29
  export * from './value-promise';
30
+ export * from './json-types';
package/src/inject.ts CHANGED
@@ -18,11 +18,13 @@ import {
18
18
  BindingSelector,
19
19
  filterByTag,
20
20
  isBindingAddress,
21
+ isBindingTagFilter,
21
22
  } from './binding-filter';
22
23
  import {BindingAddress} from './binding-key';
23
24
  import {BindingComparator} from './binding-sorter';
24
25
  import {BindingCreationPolicy, Context} from './context';
25
26
  import {ContextView, createViewGetter} from './context-view';
27
+ import {JSONObject} from './json-types';
26
28
  import {ResolutionOptions, ResolutionSession} from './resolution-session';
27
29
  import {BoundValue, ValueOrPromise} from './value-promise';
28
30
 
@@ -698,3 +700,66 @@ export function describeInjectedProperties(
698
700
  ) ?? {};
699
701
  return metadata;
700
702
  }
703
+
704
+ /**
705
+ * Inspect injections for a binding created with `toClass` or `toProvider`
706
+ * @param binding - Binding object
707
+ */
708
+ export function inspectInjections(binding: Readonly<Binding<unknown>>) {
709
+ const json: JSONObject = {};
710
+ const ctor = binding.valueConstructor ?? binding.providerConstructor;
711
+ if (ctor == null) return json;
712
+ const constructorInjections = describeInjectedArguments(ctor, '').map(
713
+ inspectInjection,
714
+ );
715
+ if (constructorInjections.length) {
716
+ json.constructorArguments = constructorInjections;
717
+ }
718
+ const propertyInjections = describeInjectedProperties(ctor.prototype);
719
+ const properties: JSONObject = {};
720
+ for (const p in propertyInjections) {
721
+ properties[p] = inspectInjection(propertyInjections[p]);
722
+ }
723
+ if (Object.keys(properties).length) {
724
+ json.properties = properties;
725
+ }
726
+ return json;
727
+ }
728
+
729
+ /**
730
+ * Inspect an injection
731
+ * @param injection - Injection information
732
+ */
733
+ function inspectInjection(injection: Readonly<Injection<unknown>>) {
734
+ const injectionInfo = ResolutionSession.describeInjection(injection);
735
+ const descriptor: JSONObject = {};
736
+ if (injectionInfo.targetName) {
737
+ descriptor.targetName = injectionInfo.targetName;
738
+ }
739
+ if (isBindingAddress(injectionInfo.bindingSelector)) {
740
+ // Binding key
741
+ descriptor.bindingKey = injectionInfo.bindingSelector.toString();
742
+ } else if (isBindingTagFilter(injectionInfo.bindingSelector)) {
743
+ // Binding tag filter
744
+ descriptor.bindingTagPattern = JSON.parse(
745
+ JSON.stringify(injectionInfo.bindingSelector.bindingTagPattern),
746
+ );
747
+ } else {
748
+ // Binding filter function
749
+ descriptor.bindingFilter =
750
+ injectionInfo.bindingSelector?.name ?? '<function>';
751
+ }
752
+ // Inspect metadata
753
+ if (injectionInfo.metadata) {
754
+ if (
755
+ injectionInfo.metadata.decorator &&
756
+ injectionInfo.metadata.decorator !== '@inject'
757
+ ) {
758
+ descriptor.decorator = injectionInfo.metadata.decorator;
759
+ }
760
+ if (injectionInfo.metadata.optional) {
761
+ descriptor.optional = injectionInfo.metadata.optional;
762
+ }
763
+ }
764
+ return descriptor;
765
+ }
@@ -15,7 +15,6 @@ import assert from 'assert';
15
15
  import debugFactory from 'debug';
16
16
  import {Binding, BindingTemplate} from './binding';
17
17
  import {bind} from './binding-decorator';
18
- import {filterByTag} from './binding-filter';
19
18
  import {BindingSpec} from './binding-inspector';
20
19
  import {sortBindingsByPhase} from './binding-sorter';
21
20
  import {Context} from './context';
@@ -47,12 +46,14 @@ export class InterceptedInvocationContext extends InvocationContext {
47
46
  * ContextTags.GLOBAL_INTERCEPTOR)
48
47
  */
49
48
  getGlobalInterceptorBindingKeys(): string[] {
50
- const bindings: Readonly<Binding<Interceptor>>[] = this.find(
51
- binding =>
52
- filterByTag(ContextTags.GLOBAL_INTERCEPTOR)(binding) &&
53
- // Only include interceptors that match the source type of the invocation
54
- this.applicableTo(binding),
49
+ let bindings: Readonly<Binding<Interceptor>>[] = this.findByTag(
50
+ ContextTags.GLOBAL_INTERCEPTOR,
55
51
  );
52
+ bindings = bindings.filter(binding =>
53
+ // Only include interceptors that match the source type of the invocation
54
+ this.applicableTo(binding),
55
+ );
56
+
56
57
  this.sortGlobalInterceptorBindings(bindings);
57
58
  const keys = bindings.map(b => b.key);
58
59
  debug('Global interceptor binding keys:', keys);
package/src/invocation.ts CHANGED
@@ -55,9 +55,7 @@ export class InvocationContext extends Context {
55
55
  * @param args - An array of arguments
56
56
  */
57
57
  constructor(
58
- // Make `parent` public so that the interceptor can add bindings to
59
- // the request context, for example, tracing id
60
- public readonly parent: Context,
58
+ parent: Context,
61
59
  public readonly target: object,
62
60
  public readonly methodName: string,
63
61
  public readonly args: InvocationArgs,