@loopback/context 1.23.4 → 1.25.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 (60) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/binding-config.js +1 -1
  3. package/dist/binding-config.js.map +1 -1
  4. package/dist/binding-inspector.js +12 -11
  5. package/dist/binding-inspector.js.map +1 -1
  6. package/dist/binding-sorter.js +2 -2
  7. package/dist/binding-sorter.js.map +1 -1
  8. package/dist/binding.d.ts +13 -7
  9. package/dist/binding.js +26 -9
  10. package/dist/binding.js.map +1 -1
  11. package/dist/context-view.d.ts +1 -1
  12. package/dist/context-view.js +5 -2
  13. package/dist/context-view.js.map +1 -1
  14. package/dist/context.d.ts +6 -1
  15. package/dist/context.js +33 -10
  16. package/dist/context.js.map +1 -1
  17. package/dist/index.d.ts +3 -3
  18. package/dist/index.js +3 -3
  19. package/dist/index.js.map +1 -1
  20. package/dist/inject-config.js +3 -3
  21. package/dist/inject-config.js.map +1 -1
  22. package/dist/inject.d.ts +1 -1
  23. package/dist/inject.js +18 -11
  24. package/dist/inject.js.map +1 -1
  25. package/dist/interception-proxy.d.ts +15 -3
  26. package/dist/interception-proxy.js +20 -4
  27. package/dist/interception-proxy.js.map +1 -1
  28. package/dist/interceptor-chain.js +5 -2
  29. package/dist/interceptor-chain.js.map +1 -1
  30. package/dist/interceptor.d.ts +6 -0
  31. package/dist/interceptor.js +37 -11
  32. package/dist/interceptor.js.map +1 -1
  33. package/dist/invocation.d.ts +23 -4
  34. package/dist/invocation.js +14 -8
  35. package/dist/invocation.js.map +1 -1
  36. package/dist/keys.d.ts +6 -0
  37. package/dist/keys.js +6 -0
  38. package/dist/keys.js.map +1 -1
  39. package/dist/resolution-session.d.ts +5 -4
  40. package/dist/resolution-session.js +13 -6
  41. package/dist/resolution-session.js.map +1 -1
  42. package/dist/resolver.js +13 -8
  43. package/dist/resolver.js.map +1 -1
  44. package/package.json +10 -10
  45. package/src/binding-config.ts +1 -1
  46. package/src/binding-inspector.ts +6 -8
  47. package/src/binding-sorter.ts +2 -2
  48. package/src/binding.ts +30 -8
  49. package/src/context-view.ts +1 -1
  50. package/src/context.ts +26 -10
  51. package/src/index.ts +3 -3
  52. package/src/inject-config.ts +3 -3
  53. package/src/inject.ts +19 -10
  54. package/src/interception-proxy.ts +25 -3
  55. package/src/interceptor-chain.ts +1 -1
  56. package/src/interceptor.ts +31 -6
  57. package/src/invocation.ts +25 -4
  58. package/src/keys.ts +7 -0
  59. package/src/resolution-session.ts +9 -5
  60. package/src/resolver.ts +5 -5
package/src/context.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {EventEmitter} from 'events';
8
8
  import {v1 as uuidv1} from 'uuid';
9
9
  import {Binding, BindingTag} from './binding';
@@ -133,7 +133,7 @@ export class Context extends EventEmitter {
133
133
  _parent = undefined;
134
134
  }
135
135
  this._parent = _parent;
136
- this.name = name || uuidv1();
136
+ this.name = name ?? uuidv1();
137
137
  }
138
138
 
139
139
  /**
@@ -186,7 +186,7 @@ export class Context extends EventEmitter {
186
186
  if (this._parent == null) return;
187
187
 
188
188
  // Keep track of parent event listeners so that we can remove them
189
- this._parentEventListeners = this._parentEventListeners || new Map();
189
+ this._parentEventListeners = this._parentEventListeners ?? new Map();
190
190
  if (this._parentEventListeners.has(event)) return;
191
191
 
192
192
  const parentEventListener = (
@@ -350,7 +350,7 @@ export class Context extends EventEmitter {
350
350
  const keyExists = this.registry.has(key);
351
351
  if (keyExists) {
352
352
  existingBinding = this.registry.get(key);
353
- const bindingIsLocked = existingBinding && existingBinding.isLocked;
353
+ const bindingIsLocked = existingBinding?.isLocked;
354
354
  if (bindingIsLocked)
355
355
  throw new Error(`Cannot rebind key "${key}" to a locked binding`);
356
356
  }
@@ -502,7 +502,7 @@ export class Context extends EventEmitter {
502
502
  const binding = this.registry.get(key);
503
503
  // If not found, return `false`
504
504
  if (binding == null) return false;
505
- if (binding && binding.isLocked)
505
+ if (binding?.isLocked)
506
506
  throw new Error(`Cannot unbind key "${key}" of a locked binding`);
507
507
  this.registry.delete(key);
508
508
  this.emit('unbind', binding, this);
@@ -514,7 +514,7 @@ export class Context extends EventEmitter {
514
514
  * @param observer - Context observer instance or function
515
515
  */
516
516
  subscribe(observer: ContextEventObserver): Subscription {
517
- this.observers = this.observers || new Set();
517
+ this.observers = this.observers ?? new Set();
518
518
  this.setupEventHandlersIfNeeded();
519
519
  this.observers.add(observer);
520
520
  return new ContextSubscription(this, observer);
@@ -885,7 +885,7 @@ export class Context extends EventEmitter {
885
885
  return this._parent.getBinding<ValueType>(key, options);
886
886
  }
887
887
 
888
- if (options && options.optional) return undefined;
888
+ if (options?.optional) return undefined;
889
889
  throw new Error(
890
890
  `The key '${key}' is not bound to any value in context ${this.name}`,
891
891
  );
@@ -968,10 +968,26 @@ export class Context extends EventEmitter {
968
968
  /**
969
969
  * Create a plain JSON object for the context
970
970
  */
971
- toJSON(): Object {
972
- const json: {[key: string]: Object} = {};
971
+ toJSON(): object {
972
+ const bindings: Record<string, object> = {};
973
973
  for (const [k, v] of this.registry) {
974
- json[k] = v.toJSON();
974
+ bindings[k] = v.toJSON();
975
+ }
976
+ return bindings;
977
+ }
978
+
979
+ /**
980
+ * Inspect the context and dump out a JSON object representing the context
981
+ * hierarchy
982
+ */
983
+ // TODO(rfeng): Evaluate https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
984
+ inspect(): object {
985
+ const json: Record<string, unknown> = {
986
+ name: this.name,
987
+ bindings: this.toJSON(),
988
+ };
989
+ if (this._parent) {
990
+ json.parent = this._parent.inspect();
975
991
  }
976
992
  return json;
977
993
  }
package/src/index.ts CHANGED
@@ -14,14 +14,14 @@ export * from './binding-sorter';
14
14
  export * from './context';
15
15
  export * from './context-observer';
16
16
  export * from './context-view';
17
- export * from './interceptor-chain';
18
17
  export * from './inject';
18
+ export * from './inject-config';
19
19
  export * from './interception-proxy';
20
20
  export * from './interceptor';
21
- export * from './inject-config';
21
+ export * from './interceptor-chain';
22
+ export * from './invocation';
22
23
  export * from './keys';
23
24
  export * from './provider';
24
25
  export * from './resolution-session';
25
26
  export * from './resolver';
26
27
  export * from './value-promise';
27
- export * from './invocation';
@@ -65,7 +65,7 @@ export function config(
65
65
  propertyPath?: string | ConfigInjectionMetadata,
66
66
  metadata?: ConfigInjectionMetadata,
67
67
  ) {
68
- propertyPath = propertyPath || '';
68
+ propertyPath = propertyPath ?? '';
69
69
  if (typeof propertyPath === 'object') {
70
70
  metadata = propertyPath;
71
71
  propertyPath = '';
@@ -87,7 +87,7 @@ export namespace config {
87
87
  propertyPath?: string | ConfigInjectionMetadata,
88
88
  metadata?: ConfigInjectionMetadata,
89
89
  ) {
90
- propertyPath = propertyPath || '';
90
+ propertyPath = propertyPath ?? '';
91
91
  if (typeof propertyPath === 'object') {
92
92
  metadata = propertyPath;
93
93
  propertyPath = '';
@@ -109,7 +109,7 @@ export namespace config {
109
109
  propertyPath?: string | ConfigInjectionMetadata,
110
110
  metadata?: ConfigInjectionMetadata,
111
111
  ) {
112
- propertyPath = propertyPath || '';
112
+ propertyPath = propertyPath ?? '';
113
113
  if (typeof propertyPath === 'object') {
114
114
  metadata = propertyPath;
115
115
  propertyPath = '';
package/src/inject.ts CHANGED
@@ -311,11 +311,11 @@ export namespace inject {
311
311
  * @param metadata - Metadata for the injection
312
312
  */
313
313
  export const binding = function injectBinding(
314
- bindingKey: BindingAddress,
314
+ bindingKey?: BindingAddress,
315
315
  metadata?: InjectBindingMetadata,
316
316
  ) {
317
317
  metadata = Object.assign({decorator: '@inject.binding'}, metadata);
318
- return inject(bindingKey, metadata, resolveAsBinding);
318
+ return inject(bindingKey ?? '', metadata, resolveAsBinding);
319
319
  };
320
320
 
321
321
  /**
@@ -395,7 +395,7 @@ export function assertTargetType(
395
395
  const targetName = ResolutionSession.describeInjection(injection).targetName;
396
396
  const targetType = inspectTargetType(injection);
397
397
  if (targetType && targetType !== expectedType) {
398
- expectedTypeName = expectedTypeName || expectedType.name;
398
+ expectedTypeName = expectedTypeName ?? expectedType.name;
399
399
  throw new Error(
400
400
  `The type of ${targetName} (${targetType.name}) is not ${expectedTypeName}`,
401
401
  );
@@ -440,14 +440,21 @@ function resolveAsSetter(ctx: Context, injection: Injection) {
440
440
  `@inject.setter (${targetName}) does not allow BindingFilter.`,
441
441
  );
442
442
  }
443
+ if (bindingSelector === '') {
444
+ throw new Error('Binding key is not set for @inject.setter');
445
+ }
443
446
  // No resolution session should be propagated into the setter
444
447
  return function setter(value: unknown) {
445
448
  const binding = findOrCreateBindingForInjection(ctx, injection);
446
- binding.to(value);
449
+ binding!.to(value);
447
450
  };
448
451
  }
449
452
 
450
- function resolveAsBinding(ctx: Context, injection: Injection) {
453
+ function resolveAsBinding(
454
+ ctx: Context,
455
+ injection: Injection,
456
+ session: ResolutionSession,
457
+ ) {
451
458
  const targetName = assertTargetType(injection, Binding);
452
459
  const bindingSelector = injection.bindingSelector;
453
460
  if (!isBindingAddress(bindingSelector)) {
@@ -455,13 +462,15 @@ function resolveAsBinding(ctx: Context, injection: Injection) {
455
462
  `@inject.binding (${targetName}) does not allow BindingFilter.`,
456
463
  );
457
464
  }
458
- return findOrCreateBindingForInjection(ctx, injection);
465
+ return findOrCreateBindingForInjection(ctx, injection, session);
459
466
  }
460
467
 
461
468
  function findOrCreateBindingForInjection(
462
469
  ctx: Context,
463
470
  injection: Injection<unknown>,
471
+ session?: ResolutionSession,
464
472
  ) {
473
+ if (injection.bindingSelector === '') return session?.currentBinding;
465
474
  const bindingCreation =
466
475
  injection.metadata &&
467
476
  (injection.metadata as InjectBindingMetadata).bindingCreation;
@@ -544,7 +553,7 @@ export function describeInjectedArguments(
544
553
  target: Object,
545
554
  method?: string,
546
555
  ): Readonly<Injection>[] {
547
- method = method || '';
556
+ method = method ?? '';
548
557
 
549
558
  // Try to read from cache
550
559
  const cache =
@@ -554,7 +563,7 @@ export function describeInjectedArguments(
554
563
  {
555
564
  ownMetadataOnly: true,
556
565
  },
557
- ) || {};
566
+ ) ?? {};
558
567
  let meta: Readonly<Injection>[] = cache[method];
559
568
  if (meta) return meta;
560
569
 
@@ -575,7 +584,7 @@ export function describeInjectedArguments(
575
584
  target,
576
585
  method,
577
586
  options,
578
- ) || [];
587
+ ) ?? [];
579
588
 
580
589
  // Cache the result
581
590
  cache[method] = meta;
@@ -686,6 +695,6 @@ export function describeInjectedProperties(
686
695
  MetadataInspector.getAllPropertyMetadata<Readonly<Injection>>(
687
696
  PROPERTIES_KEY,
688
697
  target,
689
- ) || {};
698
+ ) ?? {};
690
699
  return metadata;
691
700
  }
@@ -5,7 +5,8 @@
5
5
 
6
6
  import {Context} from './context';
7
7
  import {invokeMethodWithInterceptors} from './interceptor';
8
- import {InvocationArgs} from './invocation';
8
+ import {InvocationArgs, InvocationSource} from './invocation';
9
+ import {ResolutionSession} from './resolution-session';
9
10
  import {ValueOrPromise} from './value-promise';
10
11
 
11
12
  /**
@@ -57,13 +58,29 @@ export type AsInterceptedFunction<T> = T extends (
57
58
  */
58
59
  export type AsyncProxy<T> = {[P in keyof T]: AsInterceptedFunction<T[P]>};
59
60
 
61
+ /**
62
+ * Invocation source for injected proxies. It wraps a snapshot of the
63
+ * `ResolutionSession` that tracks the binding/injection stack.
64
+ */
65
+ export class ProxySource implements InvocationSource<ResolutionSession> {
66
+ type = 'proxy';
67
+ constructor(readonly value: ResolutionSession) {}
68
+
69
+ toString() {
70
+ return this.value.getBindingPath();
71
+ }
72
+ }
73
+
60
74
  /**
61
75
  * A proxy handler that applies interceptors
62
76
  *
63
77
  * See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Proxy
64
78
  */
65
79
  export class InterceptionHandler<T extends object> implements ProxyHandler<T> {
66
- constructor(private context = new Context()) {}
80
+ constructor(
81
+ private context = new Context(),
82
+ private session?: ResolutionSession,
83
+ ) {}
67
84
 
68
85
  get(target: T, propertyName: PropertyKey, receiver: unknown) {
69
86
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -77,6 +94,7 @@ export class InterceptionHandler<T extends object> implements ProxyHandler<T> {
77
94
  target,
78
95
  propertyName,
79
96
  args,
97
+ {source: this.session && new ProxySource(this.session)},
80
98
  );
81
99
  };
82
100
  } else {
@@ -93,6 +111,10 @@ export class InterceptionHandler<T extends object> implements ProxyHandler<T> {
93
111
  export function createProxyWithInterceptors<T extends object>(
94
112
  target: T,
95
113
  context?: Context,
114
+ session?: ResolutionSession,
96
115
  ): AsyncProxy<T> {
97
- return new Proxy(target, new InterceptionHandler(context)) as AsyncProxy<T>;
116
+ return new Proxy(
117
+ target,
118
+ new InterceptionHandler(context, ResolutionSession.fork(session)),
119
+ ) as AsyncProxy<T>;
98
120
  }
@@ -3,7 +3,7 @@
3
3
  // This file is licensed under the MIT License.
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
- import * as debugFactory from 'debug';
6
+ import debugFactory from 'debug';
7
7
  import {BindingFilter} from './binding-filter';
8
8
  import {BindingAddress} from './binding-key';
9
9
  import {BindingComparator} from './binding-sorter';
@@ -11,8 +11,8 @@ import {
11
11
  MetadataMap,
12
12
  MethodDecoratorFactory,
13
13
  } from '@loopback/metadata';
14
- import * as assert from 'assert';
15
- import * as debugFactory from 'debug';
14
+ import assert from 'assert';
15
+ import debugFactory from 'debug';
16
16
  import {Binding, BindingTemplate} from './binding';
17
17
  import {bind} from './binding-decorator';
18
18
  import {filterByTag} from './binding-filter';
@@ -48,7 +48,10 @@ export class InterceptedInvocationContext extends InvocationContext {
48
48
  */
49
49
  getGlobalInterceptorBindingKeys(): string[] {
50
50
  const bindings: Readonly<Binding<Interceptor>>[] = this.find(
51
- filterByTag(ContextTags.GLOBAL_INTERCEPTOR),
51
+ binding =>
52
+ filterByTag(ContextTags.GLOBAL_INTERCEPTOR)(binding) &&
53
+ // Only include interceptors that match the source type of the invocation
54
+ this.applicableTo(binding),
52
55
  );
53
56
  this.sortGlobalInterceptorBindings(bindings);
54
57
  const keys = bindings.map(b => b.key);
@@ -56,6 +59,27 @@ export class InterceptedInvocationContext extends InvocationContext {
56
59
  return keys;
57
60
  }
58
61
 
62
+ /**
63
+ * Check if the binding for a global interceptor matches the source type
64
+ * of the invocation
65
+ * @param binding - Binding
66
+ */
67
+ private applicableTo(binding: Readonly<Binding<unknown>>) {
68
+ const sourceType = this.source?.type;
69
+ // Unknown source type, always apply
70
+ if (sourceType == null) return true;
71
+ const allowedSource: string | string[] =
72
+ binding.tagMap[ContextTags.GLOBAL_INTERCEPTOR_SOURCE];
73
+ return (
74
+ // No tag, always apply
75
+ allowedSource == null ||
76
+ // source matched
77
+ allowedSource === sourceType ||
78
+ // source included in the string[]
79
+ (Array.isArray(allowedSource) && allowedSource.includes(sourceType))
80
+ );
81
+ }
82
+
59
83
  /**
60
84
  * Sort global interceptor bindings by `globalInterceptorGroup` tags
61
85
  * @param bindings - An array of global interceptor bindings
@@ -67,7 +91,7 @@ export class InterceptedInvocationContext extends InvocationContext {
67
91
  const orderedGroups =
68
92
  this.getSync(ContextBindings.GLOBAL_INTERCEPTOR_ORDERED_GROUPS, {
69
93
  optional: true,
70
- }) || [];
94
+ }) ?? [];
71
95
  return sortBindingsByPhase(
72
96
  bindings,
73
97
  ContextTags.GLOBAL_INTERCEPTOR_GROUP,
@@ -88,11 +112,11 @@ export class InterceptedInvocationContext extends InvocationContext {
88
112
  INTERCEPT_METHOD_KEY,
89
113
  this.target,
90
114
  this.methodName,
91
- ) || [];
115
+ ) ?? [];
92
116
  const targetClass =
93
117
  typeof this.target === 'function' ? this.target : this.target.constructor;
94
118
  const classInterceptors =
95
- MetadataInspector.getClassMetadata(INTERCEPT_CLASS_KEY, targetClass) ||
119
+ MetadataInspector.getClassMetadata(INTERCEPT_CLASS_KEY, targetClass) ??
96
120
  [];
97
121
  // Inserting class level interceptors before method level ones
98
122
  interceptors = mergeInterceptors(classInterceptors, interceptors);
@@ -305,6 +329,7 @@ export function invokeMethodWithInterceptors(
305
329
  target,
306
330
  methodName,
307
331
  args,
332
+ options.source,
308
333
  );
309
334
 
310
335
  invocationCtx.assertMethodExists();
package/src/invocation.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {DecoratorFactory} from '@loopback/metadata';
7
- import * as assert from 'assert';
8
- import * as debugFactory from 'debug';
7
+ import assert from 'assert';
8
+ import debugFactory from 'debug';
9
9
  import {Context} from './context';
10
10
  import {invokeMethodWithInterceptors} from './interceptor';
11
11
  import {resolveInjectedArguments} from './resolver';
@@ -26,6 +26,20 @@ export type InvocationResult = any;
26
26
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
27
  export type InvocationArgs = any[];
28
28
 
29
+ /**
30
+ * An interface to represent the caller of the invocation
31
+ */
32
+ export interface InvocationSource<T = unknown> {
33
+ /**
34
+ * Type of the invoker, such as `proxy` and `route`
35
+ */
36
+ readonly type: string;
37
+ /**
38
+ * Metadata for the source, such as `ResolutionSession`
39
+ */
40
+ readonly value: T;
41
+ }
42
+
29
43
  /**
30
44
  * InvocationContext represents the context to invoke interceptors for a method.
31
45
  * The context can be used to access metadata about the invocation as well as
@@ -47,6 +61,7 @@ export class InvocationContext extends Context {
47
61
  public readonly target: object,
48
62
  public readonly methodName: string,
49
63
  public readonly args: InvocationArgs,
64
+ public readonly source?: InvocationSource,
50
65
  ) {
51
66
  super(parent);
52
67
  }
@@ -71,7 +86,8 @@ export class InvocationContext extends Context {
71
86
  * Description of the invocation
72
87
  */
73
88
  get description() {
74
- return `InvocationContext(${this.name}): ${this.targetName}`;
89
+ const source = this.source == null ? '' : `${this.source} => `;
90
+ return `InvocationContext(${this.name}): ${source}${this.targetName}`;
75
91
  }
76
92
 
77
93
  toString() {
@@ -129,6 +145,11 @@ export type InvocationOptions = {
129
145
  * Skip invocation of interceptors
130
146
  */
131
147
  skipInterceptors?: boolean;
148
+ /**
149
+ * Information about the source object that makes the invocation. For REST,
150
+ * it's a `Route`. For injected proxies, it's a `Binding`.
151
+ */
152
+ source?: InvocationSource;
132
153
  };
133
154
 
134
155
  /**
@@ -189,7 +210,7 @@ function invokeTargetMethodWithInjection(
189
210
  /* istanbul ignore if */
190
211
  if (debug.enabled) {
191
212
  debug('Invoking method %s', methodName);
192
- if (nonInjectedArgs && nonInjectedArgs.length) {
213
+ if (nonInjectedArgs?.length) {
193
214
  debug('Non-injected arguments:', nonInjectedArgs);
194
215
  }
195
216
  }
package/src/keys.ts CHANGED
@@ -40,6 +40,13 @@ export namespace ContextTags {
40
40
  */
41
41
  export const GLOBAL_INTERCEPTOR = 'globalInterceptor';
42
42
 
43
+ /**
44
+ * Binding tag for global interceptors to specify sources of invocations that
45
+ * the interceptor should apply. The tag value can be a string or string[], such
46
+ * as `'route'` or `['route', 'proxy']`.
47
+ */
48
+ export const GLOBAL_INTERCEPTOR_SOURCE = 'globalInterceptorSource';
49
+
43
50
  /**
44
51
  * Binding tag for group name of global interceptors
45
52
  */
@@ -4,7 +4,7 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {DecoratorFactory} from '@loopback/metadata';
7
- import * as debugModule from 'debug';
7
+ import debugModule from 'debug';
8
8
  import {Binding} from './binding';
9
9
  import {Injection} from './inject';
10
10
  import {BoundValue, tryWithFinally, ValueOrPromise} from './value-promise';
@@ -93,7 +93,7 @@ export class ResolutionSession {
93
93
  binding: Readonly<Binding>,
94
94
  session?: ResolutionSession,
95
95
  ): ResolutionSession {
96
- session = session || new ResolutionSession();
96
+ session = session ?? new ResolutionSession();
97
97
  session.pushBinding(binding);
98
98
  return session;
99
99
  }
@@ -125,7 +125,7 @@ export class ResolutionSession {
125
125
  injection: Readonly<Injection>,
126
126
  session?: ResolutionSession,
127
127
  ): ResolutionSession {
128
- session = session || new ResolutionSession();
128
+ session = session ?? new ResolutionSession();
129
129
  session.pushInjection(injection);
130
130
  return session;
131
131
  }
@@ -268,7 +268,7 @@ export class ResolutionSession {
268
268
  const binding = top.value;
269
269
  /* istanbul ignore if */
270
270
  if (debugSession.enabled) {
271
- debugSession('Exit binding:', binding && binding.toJSON());
271
+ debugSession('Exit binding:', binding?.toJSON());
272
272
  debugSession('Resolution path:', this.getResolutionPath() || '<empty>');
273
273
  }
274
274
  return binding;
@@ -321,6 +321,10 @@ export class ResolutionSession {
321
321
  getResolutionPath() {
322
322
  return this.stack.map(i => ResolutionSession.describe(i)).join(' --> ');
323
323
  }
324
+
325
+ toString() {
326
+ return this.getResolutionPath();
327
+ }
324
328
  }
325
329
 
326
330
  /**
@@ -363,5 +367,5 @@ export function asResolutionOptions(
363
367
  if (optionsOrSession instanceof ResolutionSession) {
364
368
  return {session: optionsOrSession};
365
369
  }
366
- return optionsOrSession || {};
370
+ return optionsOrSession ?? {};
367
371
  }
package/src/resolver.ts CHANGED
@@ -4,8 +4,8 @@
4
4
  // License text available at https://opensource.org/licenses/MIT
5
5
 
6
6
  import {DecoratorFactory} from '@loopback/metadata';
7
- import * as assert from 'assert';
8
- import * as debugModule from 'debug';
7
+ import assert from 'assert';
8
+ import debugModule from 'debug';
9
9
  import {BindingScope} from './binding';
10
10
  import {isBindingAddress} from './binding-filter';
11
11
  import {BindingAddress} from './binding-key';
@@ -51,7 +51,7 @@ export function instantiateClass<T>(
51
51
  /* istanbul ignore if */
52
52
  if (debug.enabled) {
53
53
  debug('Instantiating %s', getTargetName(ctor));
54
- if (nonInjectedArgs && nonInjectedArgs.length) {
54
+ if (nonInjectedArgs?.length) {
55
55
  debug('Non-injected arguments:', nonInjectedArgs);
56
56
  }
57
57
  }
@@ -93,7 +93,7 @@ function resolveContext(
93
93
  injection: Readonly<Injection>,
94
94
  session?: ResolutionSession,
95
95
  ) {
96
- const currentBinding = session && session.currentBinding;
96
+ const currentBinding = session?.currentBinding;
97
97
  if (
98
98
  currentBinding == null ||
99
99
  currentBinding.scope !== BindingScope.SINGLETON
@@ -201,7 +201,7 @@ export function resolveInjectedArguments(
201
201
  // Example value:
202
202
  // [ , 'key1', , 'key2']
203
203
  const injectedArgs = describeInjectedArguments(target, method);
204
- const extraArgs = nonInjectedArgs || [];
204
+ const extraArgs = nonInjectedArgs ?? [];
205
205
 
206
206
  let argLength = DecoratorFactory.getNumberOfParameters(target, method);
207
207