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