@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.
- package/CHANGELOG.md +41 -0
- package/dist/binding-config.js +1 -1
- package/dist/binding-config.js.map +1 -1
- package/dist/binding-inspector.js +12 -11
- package/dist/binding-inspector.js.map +1 -1
- package/dist/binding-sorter.js +2 -2
- package/dist/binding-sorter.js.map +1 -1
- package/dist/binding.d.ts +13 -7
- package/dist/binding.js +26 -9
- package/dist/binding.js.map +1 -1
- package/dist/context-view.d.ts +1 -1
- package/dist/context-view.js +5 -2
- package/dist/context-view.js.map +1 -1
- package/dist/context.d.ts +6 -1
- package/dist/context.js +33 -10
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/inject-config.js +3 -3
- package/dist/inject-config.js.map +1 -1
- package/dist/inject.d.ts +1 -1
- package/dist/inject.js +18 -11
- package/dist/inject.js.map +1 -1
- package/dist/interception-proxy.d.ts +15 -3
- package/dist/interception-proxy.js +20 -4
- package/dist/interception-proxy.js.map +1 -1
- package/dist/interceptor-chain.js +5 -2
- package/dist/interceptor-chain.js.map +1 -1
- package/dist/interceptor.d.ts +6 -0
- package/dist/interceptor.js +37 -11
- package/dist/interceptor.js.map +1 -1
- package/dist/invocation.d.ts +23 -4
- package/dist/invocation.js +14 -8
- package/dist/invocation.js.map +1 -1
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +6 -0
- package/dist/keys.js.map +1 -1
- package/dist/resolution-session.d.ts +5 -4
- package/dist/resolution-session.js +13 -6
- package/dist/resolution-session.js.map +1 -1
- package/dist/resolver.js +13 -8
- package/dist/resolver.js.map +1 -1
- package/package.json +10 -10
- package/src/binding-config.ts +1 -1
- package/src/binding-inspector.ts +6 -8
- package/src/binding-sorter.ts +2 -2
- package/src/binding.ts +30 -8
- package/src/context-view.ts +1 -1
- package/src/context.ts +26 -10
- package/src/index.ts +3 -3
- package/src/inject-config.ts +3 -3
- package/src/inject.ts +19 -10
- package/src/interception-proxy.ts +25 -3
- package/src/interceptor-chain.ts +1 -1
- package/src/interceptor.ts +31 -6
- package/src/invocation.ts +25 -4
- package/src/keys.ts +7 -0
- package/src/resolution-session.ts +9 -5
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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():
|
|
972
|
-
const
|
|
971
|
+
toJSON(): object {
|
|
972
|
+
const bindings: Record<string, object> = {};
|
|
973
973
|
for (const [k, v] of this.registry) {
|
|
974
|
-
|
|
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 './
|
|
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';
|
package/src/inject-config.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
449
|
+
binding!.to(value);
|
|
447
450
|
};
|
|
448
451
|
}
|
|
449
452
|
|
|
450
|
-
function resolveAsBinding(
|
|
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(
|
|
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(
|
|
116
|
+
return new Proxy(
|
|
117
|
+
target,
|
|
118
|
+
new InterceptionHandler(context, ResolutionSession.fork(session)),
|
|
119
|
+
) as AsyncProxy<T>;
|
|
98
120
|
}
|
package/src/interceptor-chain.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
|
|
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';
|
package/src/interceptor.ts
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
MetadataMap,
|
|
12
12
|
MethodDecoratorFactory,
|
|
13
13
|
} from '@loopback/metadata';
|
|
14
|
-
import
|
|
15
|
-
import
|
|
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
|
-
|
|
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
|
|
8
|
-
import
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8
|
-
import
|
|
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
|
|
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
|
|
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
|
|