@loopback/context 3.5.1 → 3.8.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 +60 -0
- package/dist/binding-config.js +1 -0
- package/dist/binding-config.js.map +1 -1
- package/dist/binding-decorator.d.ts +1 -2
- package/dist/binding-decorator.js +1 -0
- package/dist/binding-decorator.js.map +1 -1
- package/dist/binding-filter.d.ts +2 -2
- package/dist/binding-filter.js +4 -3
- package/dist/binding-filter.js.map +1 -1
- package/dist/binding-inspector.d.ts +14 -7
- package/dist/binding-inspector.js +14 -6
- package/dist/binding-inspector.js.map +1 -1
- package/dist/binding-key.d.ts +5 -0
- package/dist/binding-key.js +104 -90
- package/dist/binding-key.js.map +1 -1
- package/dist/binding-sorter.js +1 -0
- package/dist/binding-sorter.js.map +1 -1
- package/dist/binding.d.ts +84 -7
- package/dist/binding.js +120 -35
- package/dist/binding.js.map +1 -1
- package/dist/context-subscription.js +1 -0
- package/dist/context-subscription.js.map +1 -1
- package/dist/context-tag-indexer.js +1 -0
- package/dist/context-tag-indexer.js.map +1 -1
- package/dist/context-view.js +1 -0
- package/dist/context-view.js.map +1 -1
- package/dist/context.js +2 -2
- package/dist/context.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/inject-config.js +1 -0
- package/dist/inject-config.js.map +1 -1
- package/dist/inject.d.ts +13 -6
- package/dist/inject.js +6 -8
- package/dist/inject.js.map +1 -1
- package/dist/interception-proxy.js +1 -0
- package/dist/interception-proxy.js.map +1 -1
- package/dist/interceptor-chain.d.ts +39 -4
- package/dist/interceptor-chain.js +26 -4
- package/dist/interceptor-chain.js.map +1 -1
- package/dist/interceptor.d.ts +28 -3
- package/dist/interceptor.js +54 -0
- package/dist/interceptor.js.map +1 -1
- package/dist/invocation.js +1 -0
- package/dist/invocation.js.map +1 -1
- package/dist/keys.d.ts +5 -0
- package/dist/keys.js +6 -0
- package/dist/keys.js.map +1 -1
- package/dist/resolution-session.d.ts +26 -6
- package/dist/resolution-session.js +1 -0
- package/dist/resolution-session.js.map +1 -1
- package/dist/resolver.js +1 -0
- package/dist/resolver.js.map +1 -1
- package/dist/value-promise.d.ts +8 -4
- package/dist/value-promise.js +17 -0
- package/dist/value-promise.js.map +1 -1
- package/package.json +13 -13
- package/src/binding-decorator.ts +2 -3
- package/src/binding-filter.ts +8 -7
- package/src/binding-inspector.ts +32 -9
- package/src/binding-key.ts +12 -0
- package/src/binding.ts +210 -44
- package/src/context.ts +2 -2
- package/src/inject.ts +22 -16
- package/src/interceptor-chain.ts +66 -7
- package/src/interceptor.ts +85 -2
- package/src/keys.ts +6 -0
- package/src/resolution-session.ts +30 -2
- package/src/value-promise.ts +13 -0
- package/index.d.ts +0 -6
- package/index.js +0 -6
package/src/binding.ts
CHANGED
|
@@ -9,11 +9,13 @@ import {BindingAddress, BindingKey} from './binding-key';
|
|
|
9
9
|
import {Context} from './context';
|
|
10
10
|
import {inspectInjections} from './inject';
|
|
11
11
|
import {createProxyWithInterceptors} from './interception-proxy';
|
|
12
|
+
import {invokeMethod} from './invocation';
|
|
12
13
|
import {JSONObject} from './json-types';
|
|
13
14
|
import {ContextTags} from './keys';
|
|
14
15
|
import {Provider} from './provider';
|
|
15
16
|
import {
|
|
16
17
|
asResolutionOptions,
|
|
18
|
+
ResolutionContext,
|
|
17
19
|
ResolutionOptions,
|
|
18
20
|
ResolutionOptionsOrSession,
|
|
19
21
|
ResolutionSession,
|
|
@@ -129,6 +131,56 @@ export enum BindingType {
|
|
|
129
131
|
ALIAS = 'Alias',
|
|
130
132
|
}
|
|
131
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Binding source for `to`
|
|
136
|
+
*/
|
|
137
|
+
export type ConstantBindingSource<T> = {
|
|
138
|
+
type: BindingType.CONSTANT;
|
|
139
|
+
value: T;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Binding source for `toDynamicValue`
|
|
144
|
+
*/
|
|
145
|
+
export type DynamicValueBindingSource<T> = {
|
|
146
|
+
type: BindingType.DYNAMIC_VALUE;
|
|
147
|
+
value: ValueFactory<T> | DynamicValueProviderClass<T>;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Binding source for `toClass`
|
|
152
|
+
*/
|
|
153
|
+
export type ClassBindingSource<T> = {
|
|
154
|
+
type: BindingType.CLASS;
|
|
155
|
+
value: Constructor<T>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Binding source for `toProvider`
|
|
160
|
+
*/
|
|
161
|
+
export type ProviderBindingSource<T> = {
|
|
162
|
+
type: BindingType.PROVIDER;
|
|
163
|
+
value: Constructor<Provider<T>>;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Binding source for `toAlias`
|
|
168
|
+
*/
|
|
169
|
+
export type AliasBindingSource<T> = {
|
|
170
|
+
type: BindingType.ALIAS;
|
|
171
|
+
value: BindingAddress<T>;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Source for the binding, including the type and value
|
|
176
|
+
*/
|
|
177
|
+
export type BindingSource<T> =
|
|
178
|
+
| ConstantBindingSource<T>
|
|
179
|
+
| DynamicValueBindingSource<T>
|
|
180
|
+
| ClassBindingSource<T>
|
|
181
|
+
| ProviderBindingSource<T>
|
|
182
|
+
| AliasBindingSource<T>;
|
|
183
|
+
|
|
132
184
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
185
|
export type TagMap = MapObject<any>;
|
|
134
186
|
|
|
@@ -170,11 +222,62 @@ export type BindingEventListener = (
|
|
|
170
222
|
event: BindingEvent,
|
|
171
223
|
) => void;
|
|
172
224
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
225
|
+
/**
|
|
226
|
+
* A factory function for `toDynamicValue`
|
|
227
|
+
*/
|
|
228
|
+
export type ValueFactory<T = unknown> = (
|
|
229
|
+
resolutionCtx: ResolutionContext,
|
|
176
230
|
) => ValueOrPromise<T | undefined>;
|
|
177
231
|
|
|
232
|
+
/**
|
|
233
|
+
* A class with a static `value` method as the factory function for
|
|
234
|
+
* `toDynamicValue`.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* import {inject} from '@loopback/context';
|
|
239
|
+
*
|
|
240
|
+
* export class DynamicGreetingProvider {
|
|
241
|
+
* static value(@inject('currentUser') user: string) {
|
|
242
|
+
* return `Hello, ${user}`;
|
|
243
|
+
* }
|
|
244
|
+
* }
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
export interface DynamicValueProviderClass<T = unknown>
|
|
248
|
+
extends Constructor<unknown>,
|
|
249
|
+
Function {
|
|
250
|
+
value: (...args: BoundValue[]) => ValueOrPromise<T>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Adapt the ValueFactoryProvider class to be a value factory
|
|
255
|
+
* @param provider - ValueFactoryProvider class
|
|
256
|
+
*/
|
|
257
|
+
function toValueFactory<T = unknown>(
|
|
258
|
+
provider: DynamicValueProviderClass<T>,
|
|
259
|
+
): ValueFactory<T> {
|
|
260
|
+
return resolutionCtx =>
|
|
261
|
+
invokeMethod(provider, 'value', resolutionCtx.context, [], {
|
|
262
|
+
skipInterceptors: true,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if the factory is a value factory provider class
|
|
268
|
+
* @param factory - A factory function or a dynamic value provider class
|
|
269
|
+
*/
|
|
270
|
+
export function isDynamicValueProviderClass<T = unknown>(
|
|
271
|
+
factory: unknown,
|
|
272
|
+
): factory is DynamicValueProviderClass<T> {
|
|
273
|
+
// Not a class
|
|
274
|
+
if (typeof factory !== 'function' || !String(factory).startsWith('class ')) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const valueMethod = (factory as DynamicValueProviderClass).value;
|
|
278
|
+
return typeof valueMethod === 'function';
|
|
279
|
+
}
|
|
280
|
+
|
|
178
281
|
/**
|
|
179
282
|
* Binding represents an entry in the `Context`. Each binding has a key and a
|
|
180
283
|
* corresponding value getter.
|
|
@@ -199,27 +302,34 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
199
302
|
return this._scope ?? BindingScope.TRANSIENT;
|
|
200
303
|
}
|
|
201
304
|
|
|
202
|
-
private _type?: BindingType;
|
|
203
305
|
/**
|
|
204
306
|
* Type of the binding value getter
|
|
205
307
|
*/
|
|
206
308
|
public get type(): BindingType | undefined {
|
|
207
|
-
return this.
|
|
309
|
+
return this._source?.type;
|
|
208
310
|
}
|
|
209
311
|
|
|
210
312
|
private _cache: WeakMap<Context, T>;
|
|
211
|
-
private _getValue
|
|
313
|
+
private _getValue?: ValueFactory<T>;
|
|
212
314
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
315
|
+
/**
|
|
316
|
+
* The original source value received from `to`, `toClass`, `toDynamicValue`,
|
|
317
|
+
* `toProvider`, or `toAlias`.
|
|
318
|
+
*/
|
|
319
|
+
private _source?: BindingSource<T>;
|
|
320
|
+
|
|
321
|
+
public get source() {
|
|
322
|
+
return this._source;
|
|
323
|
+
}
|
|
216
324
|
|
|
217
325
|
/**
|
|
218
326
|
* For bindings bound via `toClass()`, this property contains the constructor
|
|
219
327
|
* function of the class
|
|
220
328
|
*/
|
|
221
329
|
public get valueConstructor(): Constructor<T> | undefined {
|
|
222
|
-
return this.
|
|
330
|
+
return this._source?.type === BindingType.CLASS
|
|
331
|
+
? this._source?.value
|
|
332
|
+
: undefined;
|
|
223
333
|
}
|
|
224
334
|
|
|
225
335
|
/**
|
|
@@ -227,7 +337,9 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
227
337
|
* constructor function of the provider class
|
|
228
338
|
*/
|
|
229
339
|
public get providerConstructor(): Constructor<Provider<T>> | undefined {
|
|
230
|
-
return this.
|
|
340
|
+
return this._source?.type === BindingType.PROVIDER
|
|
341
|
+
? this._source?.value
|
|
342
|
+
: undefined;
|
|
231
343
|
}
|
|
232
344
|
|
|
233
345
|
constructor(key: BindingAddress<T>, public isLocked: boolean = false) {
|
|
@@ -269,6 +381,28 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
269
381
|
this._cache = new WeakMap();
|
|
270
382
|
}
|
|
271
383
|
|
|
384
|
+
/**
|
|
385
|
+
* 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
|
|
387
|
+
* dependencies are changed.
|
|
388
|
+
* **WARNING**: The state held in the cached value will be gone.
|
|
389
|
+
*
|
|
390
|
+
* @param ctx - Context object
|
|
391
|
+
*/
|
|
392
|
+
refresh(ctx: Context) {
|
|
393
|
+
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);
|
|
399
|
+
}
|
|
400
|
+
} else if (this.scope === BindingScope.CONTEXT) {
|
|
401
|
+
// Cache the value at the current context
|
|
402
|
+
this._cache.delete(ctx);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
272
406
|
/**
|
|
273
407
|
* This is an internal function optimized for performance.
|
|
274
408
|
* Users should use `@inject(key)` or `ctx.get(key)` instead.
|
|
@@ -331,11 +465,17 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
331
465
|
}
|
|
332
466
|
}
|
|
333
467
|
const options = asResolutionOptions(optionsOrSession);
|
|
334
|
-
if (this._getValue) {
|
|
468
|
+
if (typeof this._getValue === 'function') {
|
|
335
469
|
const result = ResolutionSession.runWithBinding(
|
|
336
470
|
s => {
|
|
337
471
|
const optionsWithSession = Object.assign({}, options, {session: s});
|
|
338
|
-
|
|
472
|
+
// We already test `this._getValue` is a function. It's safe to assert
|
|
473
|
+
// that `this._getValue` is not undefined.
|
|
474
|
+
return this._getValue!({
|
|
475
|
+
context: ctx,
|
|
476
|
+
binding: this,
|
|
477
|
+
options: optionsWithSession,
|
|
478
|
+
});
|
|
339
479
|
},
|
|
340
480
|
this,
|
|
341
481
|
options.session,
|
|
@@ -442,16 +582,19 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
442
582
|
* Set the `_getValue` function
|
|
443
583
|
* @param getValue - getValue function
|
|
444
584
|
*/
|
|
445
|
-
private _setValueGetter(getValue:
|
|
585
|
+
private _setValueGetter(getValue: ValueFactory<T>) {
|
|
446
586
|
// Clear the cache
|
|
447
587
|
this._clearCache();
|
|
448
|
-
this._getValue =
|
|
449
|
-
if (
|
|
588
|
+
this._getValue = resolutionCtx => {
|
|
589
|
+
if (
|
|
590
|
+
resolutionCtx.options.asProxyWithInterceptors &&
|
|
591
|
+
this._source?.type !== BindingType.CLASS
|
|
592
|
+
) {
|
|
450
593
|
throw new Error(
|
|
451
|
-
`Binding '${this.key}' (${this.
|
|
594
|
+
`Binding '${this.key}' (${this._source?.type}) does not support 'asProxyWithInterceptors'`,
|
|
452
595
|
);
|
|
453
596
|
}
|
|
454
|
-
return getValue(
|
|
597
|
+
return getValue(resolutionCtx);
|
|
455
598
|
};
|
|
456
599
|
this.emitChangedEvent('value');
|
|
457
600
|
}
|
|
@@ -494,7 +637,10 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
494
637
|
if (debug.enabled) {
|
|
495
638
|
debug('Bind %s to constant:', this.key, value);
|
|
496
639
|
}
|
|
497
|
-
this.
|
|
640
|
+
this._source = {
|
|
641
|
+
type: BindingType.CONSTANT,
|
|
642
|
+
value,
|
|
643
|
+
};
|
|
498
644
|
this._setValueGetter(() => value);
|
|
499
645
|
return this;
|
|
500
646
|
}
|
|
@@ -517,13 +663,25 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
517
663
|
* );
|
|
518
664
|
* ```
|
|
519
665
|
*/
|
|
520
|
-
toDynamicValue(
|
|
666
|
+
toDynamicValue(
|
|
667
|
+
factory: ValueFactory<T> | DynamicValueProviderClass<T>,
|
|
668
|
+
): this {
|
|
521
669
|
/* istanbul ignore if */
|
|
522
670
|
if (debug.enabled) {
|
|
523
|
-
debug('Bind %s to dynamic value:', this.key,
|
|
671
|
+
debug('Bind %s to dynamic value:', this.key, factory);
|
|
524
672
|
}
|
|
525
|
-
this.
|
|
526
|
-
|
|
673
|
+
this._source = {
|
|
674
|
+
type: BindingType.DYNAMIC_VALUE,
|
|
675
|
+
value: factory,
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
let factoryFn: ValueFactory<T>;
|
|
679
|
+
if (isDynamicValueProviderClass(factory)) {
|
|
680
|
+
factoryFn = toValueFactory(factory);
|
|
681
|
+
} else {
|
|
682
|
+
factoryFn = factory;
|
|
683
|
+
}
|
|
684
|
+
this._setValueGetter(resolutionCtx => factoryFn(resolutionCtx));
|
|
527
685
|
return this;
|
|
528
686
|
}
|
|
529
687
|
|
|
@@ -548,12 +706,14 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
548
706
|
if (debug.enabled) {
|
|
549
707
|
debug('Bind %s to provider %s', this.key, providerClass.name);
|
|
550
708
|
}
|
|
551
|
-
this.
|
|
552
|
-
|
|
553
|
-
|
|
709
|
+
this._source = {
|
|
710
|
+
type: BindingType.PROVIDER,
|
|
711
|
+
value: providerClass,
|
|
712
|
+
};
|
|
713
|
+
this._setValueGetter(({context, options}) => {
|
|
554
714
|
const providerOrPromise = instantiateClass<Provider<T>>(
|
|
555
715
|
providerClass,
|
|
556
|
-
|
|
716
|
+
context,
|
|
557
717
|
options.session,
|
|
558
718
|
);
|
|
559
719
|
return transformValueOrPromise(providerOrPromise, p => p.value());
|
|
@@ -573,17 +733,19 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
573
733
|
if (debug.enabled) {
|
|
574
734
|
debug('Bind %s to class %s', this.key, ctor.name);
|
|
575
735
|
}
|
|
576
|
-
this.
|
|
577
|
-
|
|
578
|
-
|
|
736
|
+
this._source = {
|
|
737
|
+
type: BindingType.CLASS,
|
|
738
|
+
value: ctor,
|
|
739
|
+
};
|
|
740
|
+
this._setValueGetter(({context, options}) => {
|
|
741
|
+
const instOrPromise = instantiateClass(ctor, context, options.session);
|
|
579
742
|
if (!options.asProxyWithInterceptors) return instOrPromise;
|
|
580
743
|
return createInterceptionProxyFromInstance(
|
|
581
744
|
instOrPromise,
|
|
582
|
-
|
|
745
|
+
context,
|
|
583
746
|
options.session,
|
|
584
747
|
);
|
|
585
748
|
});
|
|
586
|
-
this._valueConstructor = ctor;
|
|
587
749
|
return this;
|
|
588
750
|
}
|
|
589
751
|
|
|
@@ -597,10 +759,12 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
597
759
|
if (debug.enabled) {
|
|
598
760
|
debug('Bind %s to alias %s', this.key, keyWithPath);
|
|
599
761
|
}
|
|
600
|
-
this.
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
762
|
+
this._source = {
|
|
763
|
+
type: BindingType.ALIAS,
|
|
764
|
+
value: keyWithPath,
|
|
765
|
+
};
|
|
766
|
+
this._setValueGetter(({context, options}) => {
|
|
767
|
+
return context.getValueOrPromise(keyWithPath, options);
|
|
604
768
|
});
|
|
605
769
|
return this;
|
|
606
770
|
}
|
|
@@ -647,14 +811,16 @@ export class Binding<T = BoundValue> extends EventEmitter {
|
|
|
647
811
|
if (this.type != null) {
|
|
648
812
|
json.type = this.type;
|
|
649
813
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
814
|
+
switch (this._source?.type) {
|
|
815
|
+
case BindingType.CLASS:
|
|
816
|
+
json.valueConstructor = this._source?.value.name;
|
|
817
|
+
break;
|
|
818
|
+
case BindingType.PROVIDER:
|
|
819
|
+
json.providerConstructor = this._source?.value.name;
|
|
820
|
+
break;
|
|
821
|
+
case BindingType.ALIAS:
|
|
822
|
+
json.alias = this._source?.value.toString();
|
|
823
|
+
break;
|
|
658
824
|
}
|
|
659
825
|
return json;
|
|
660
826
|
}
|
package/src/context.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import debugFactory, {Debugger} from 'debug';
|
|
7
7
|
import {EventEmitter} from 'events';
|
|
8
|
-
import {v4 as uuidv4} from 'uuid';
|
|
9
8
|
import {Binding, BindingInspectOptions, BindingTag} from './binding';
|
|
10
9
|
import {
|
|
11
10
|
ConfigurationResolver,
|
|
@@ -37,6 +36,7 @@ import {
|
|
|
37
36
|
Constructor,
|
|
38
37
|
getDeepProperty,
|
|
39
38
|
isPromiseLike,
|
|
39
|
+
uuid,
|
|
40
40
|
ValueOrPromise,
|
|
41
41
|
} from './value-promise';
|
|
42
42
|
|
|
@@ -151,7 +151,7 @@ export class Context extends EventEmitter {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
private generateName() {
|
|
154
|
-
const id =
|
|
154
|
+
const id = uuid();
|
|
155
155
|
if (this.constructor === Context) return id;
|
|
156
156
|
return `${this.constructor.name}-${id}`;
|
|
157
157
|
}
|
package/src/inject.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
isBindingAddress,
|
|
21
21
|
isBindingTagFilter,
|
|
22
22
|
} from './binding-filter';
|
|
23
|
-
import {BindingAddress} from './binding-key';
|
|
23
|
+
import {BindingAddress, BindingKey} from './binding-key';
|
|
24
24
|
import {BindingComparator} from './binding-sorter';
|
|
25
25
|
import {BindingCreationPolicy, Context} from './context';
|
|
26
26
|
import {ContextView, createViewGetter} from './context-view';
|
|
@@ -40,8 +40,17 @@ const METHODS_KEY = MetadataAccessor.create<Injection, MethodDecorator>(
|
|
|
40
40
|
'inject:methods',
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
+
// TODO(rfeng): We may want to align it with `ValueFactory` interface that takes
|
|
44
|
+
// an argument of `ResolutionContext`.
|
|
43
45
|
/**
|
|
44
|
-
* A function to provide resolution of injected values
|
|
46
|
+
* A function to provide resolution of injected values.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const resolver: ResolverFunction = (ctx, injection, session) {
|
|
51
|
+
* return session.currentBinding?.key;
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
45
54
|
*/
|
|
46
55
|
export interface ResolverFunction {
|
|
47
56
|
(
|
|
@@ -313,7 +322,7 @@ export namespace inject {
|
|
|
313
322
|
* @param metadata - Metadata for the injection
|
|
314
323
|
*/
|
|
315
324
|
export const binding = function injectBinding(
|
|
316
|
-
bindingKey?:
|
|
325
|
+
bindingKey?: string | BindingKey<unknown>,
|
|
317
326
|
metadata?: InjectBindingMetadata,
|
|
318
327
|
) {
|
|
319
328
|
metadata = Object.assign({decorator: '@inject.binding'}, metadata);
|
|
@@ -377,7 +386,7 @@ export namespace inject {
|
|
|
377
386
|
* ```
|
|
378
387
|
*/
|
|
379
388
|
export const context = function injectContext() {
|
|
380
|
-
return inject('', {decorator: '@inject.context'}, ctx => ctx);
|
|
389
|
+
return inject('', {decorator: '@inject.context'}, (ctx: Context) => ctx);
|
|
381
390
|
};
|
|
382
391
|
}
|
|
383
392
|
|
|
@@ -604,22 +613,19 @@ export function describeInjectedArguments(
|
|
|
604
613
|
* @param injection - Injection information
|
|
605
614
|
*/
|
|
606
615
|
export function inspectTargetType(injection: Readonly<Injection>) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
return
|
|
616
|
+
if (typeof injection.methodDescriptorOrParameterIndex === 'number') {
|
|
617
|
+
const designType = MetadataInspector.getDesignTypeForMethod(
|
|
618
|
+
injection.target,
|
|
619
|
+
injection.member!,
|
|
620
|
+
);
|
|
621
|
+
return designType.parameterTypes[
|
|
622
|
+
injection.methodDescriptorOrParameterIndex as number
|
|
623
|
+
];
|
|
613
624
|
}
|
|
614
|
-
|
|
625
|
+
return MetadataInspector.getDesignTypeForProperty(
|
|
615
626
|
injection.target,
|
|
616
627
|
injection.member!,
|
|
617
628
|
);
|
|
618
|
-
type =
|
|
619
|
-
designType.parameterTypes[
|
|
620
|
-
injection.methodDescriptorOrParameterIndex as number
|
|
621
|
-
];
|
|
622
|
-
return type;
|
|
623
629
|
}
|
|
624
630
|
|
|
625
631
|
/**
|
package/src/interceptor-chain.ts
CHANGED
|
@@ -12,28 +12,55 @@ import {InvocationResult} from './invocation';
|
|
|
12
12
|
import {transformValueOrPromise, ValueOrPromise} from './value-promise';
|
|
13
13
|
const debug = debugFactory('loopback:context:interceptor-chain');
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Any type except `void`. We use this type to enforce that interceptor functions
|
|
17
|
+
* always return a value (including undefined or null).
|
|
18
|
+
*/
|
|
19
|
+
export type NonVoid = string | number | boolean | null | undefined | object;
|
|
20
|
+
|
|
15
21
|
/**
|
|
16
22
|
* The `next` function that can be used to invoke next generic interceptor in
|
|
17
23
|
* the chain
|
|
18
24
|
*/
|
|
19
|
-
export type Next = () => ValueOrPromise<
|
|
25
|
+
export type Next = () => ValueOrPromise<NonVoid>;
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* An interceptor function to be invoked in a chain for the given context.
|
|
23
29
|
* It serves as the base interface for various types of interceptors, such
|
|
24
30
|
* as method invocation interceptor or request/response processing interceptor.
|
|
25
31
|
*
|
|
32
|
+
* We choose `NonVoid` as the return type to avoid possible bugs that an
|
|
33
|
+
* interceptor forgets to return the value from `next()`. For example, the code
|
|
34
|
+
* below will fail to compile.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const myInterceptor: Interceptor = async (ctx, next) {
|
|
38
|
+
* // preprocessing
|
|
39
|
+
* // ...
|
|
40
|
+
*
|
|
41
|
+
* // There is a subtle bug that the result from `next()` is not further
|
|
42
|
+
* // returned back to the upstream interceptors
|
|
43
|
+
* const result = await next();
|
|
44
|
+
*
|
|
45
|
+
* // postprocessing
|
|
46
|
+
* // ...
|
|
47
|
+
* // We must have `return ...` here
|
|
48
|
+
* // either return `result` or another value if the interceptor decides to
|
|
49
|
+
* // have its own response
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
26
53
|
* @typeParam C - `Context` class or a subclass of `Context`
|
|
27
54
|
* @param context - Context object
|
|
28
55
|
* @param next - A function to proceed with downstream interceptors or the
|
|
29
56
|
* target operation
|
|
30
57
|
*
|
|
31
|
-
* @returns The invocation result as a value (sync) or promise (async)
|
|
58
|
+
* @returns The invocation result as a value (sync) or promise (async).
|
|
32
59
|
*/
|
|
33
60
|
export type GenericInterceptor<C extends Context = Context> = (
|
|
34
61
|
context: C,
|
|
35
62
|
next: Next,
|
|
36
|
-
) => ValueOrPromise<
|
|
63
|
+
) => ValueOrPromise<NonVoid>;
|
|
37
64
|
|
|
38
65
|
/**
|
|
39
66
|
* Interceptor function or a binding key that resolves a generic interceptor
|
|
@@ -53,8 +80,12 @@ class InterceptorChainState<C extends Context = Context> {
|
|
|
53
80
|
/**
|
|
54
81
|
* Create a state for the interceptor chain
|
|
55
82
|
* @param interceptors - Interceptor functions or binding keys
|
|
83
|
+
* @param finalHandler - An optional final handler
|
|
56
84
|
*/
|
|
57
|
-
constructor(
|
|
85
|
+
constructor(
|
|
86
|
+
public readonly interceptors: GenericInterceptorOrKey<C>[],
|
|
87
|
+
public readonly finalHandler: Next = () => undefined,
|
|
88
|
+
) {}
|
|
58
89
|
|
|
59
90
|
/**
|
|
60
91
|
* Get the index for the current interceptor
|
|
@@ -138,12 +169,24 @@ export class GenericInterceptorChain<C extends Context = Context> {
|
|
|
138
169
|
/**
|
|
139
170
|
* Invoke the interceptor chain
|
|
140
171
|
*/
|
|
141
|
-
invokeInterceptors(): ValueOrPromise<InvocationResult> {
|
|
172
|
+
invokeInterceptors(finalHandler?: Next): ValueOrPromise<InvocationResult> {
|
|
142
173
|
// Create a state for each invocation to provide isolation
|
|
143
|
-
const state = new InterceptorChainState<C>(
|
|
174
|
+
const state = new InterceptorChainState<C>(
|
|
175
|
+
this.getInterceptors(),
|
|
176
|
+
finalHandler,
|
|
177
|
+
);
|
|
144
178
|
return this.next(state);
|
|
145
179
|
}
|
|
146
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Use the interceptor chain as an interceptor
|
|
183
|
+
*/
|
|
184
|
+
asInterceptor(): GenericInterceptor<C> {
|
|
185
|
+
return (ctx, next) => {
|
|
186
|
+
return this.invokeInterceptors(next);
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
147
190
|
/**
|
|
148
191
|
* Invoke downstream interceptors or the target method
|
|
149
192
|
*/
|
|
@@ -152,7 +195,7 @@ export class GenericInterceptorChain<C extends Context = Context> {
|
|
|
152
195
|
): ValueOrPromise<InvocationResult> {
|
|
153
196
|
if (state.done()) {
|
|
154
197
|
// No more interceptors
|
|
155
|
-
return
|
|
198
|
+
return state.finalHandler();
|
|
156
199
|
}
|
|
157
200
|
// Invoke the next interceptor in the chain
|
|
158
201
|
return this.invokeNextInterceptor(state);
|
|
@@ -206,3 +249,19 @@ export function invokeInterceptors<
|
|
|
206
249
|
const chain = new GenericInterceptorChain(context, interceptors);
|
|
207
250
|
return chain.invokeInterceptors();
|
|
208
251
|
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Compose a list of interceptors as a single interceptor
|
|
255
|
+
* @param interceptors - A list of interceptor functions or binding keys
|
|
256
|
+
*/
|
|
257
|
+
export function composeInterceptors<C extends Context = Context>(
|
|
258
|
+
...interceptors: GenericInterceptorOrKey<C>[]
|
|
259
|
+
): GenericInterceptor<C> {
|
|
260
|
+
return (ctx, next) => {
|
|
261
|
+
const interceptor = new GenericInterceptorChain(
|
|
262
|
+
ctx,
|
|
263
|
+
interceptors,
|
|
264
|
+
).asInterceptor();
|
|
265
|
+
return interceptor(ctx, next);
|
|
266
|
+
};
|
|
267
|
+
}
|