@loopback/context 1.8.1 → 1.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 +58 -0
- package/dist/binding-filter.d.ts +28 -1
- package/dist/binding-filter.js.map +1 -1
- package/dist/binding.d.ts +36 -6
- package/dist/binding.js +65 -38
- package/dist/binding.js.map +1 -1
- package/dist/context-view.d.ts +7 -0
- package/dist/context-view.js +15 -2
- package/dist/context-view.js.map +1 -1
- package/dist/context.d.ts +38 -15
- package/dist/context.js +52 -8
- package/dist/context.js.map +1 -1
- package/dist/inject.d.ts +61 -7
- package/dist/inject.js +93 -46
- package/dist/inject.js.map +1 -1
- package/dist/resolution-session.d.ts +17 -4
- package/dist/resolution-session.js +16 -4
- package/dist/resolution-session.js.map +1 -1
- package/dist/resolver.js +2 -2
- package/dist/resolver.js.map +1 -1
- package/package.json +9 -9
- package/src/binding-filter.ts +29 -1
- package/src/binding.ts +101 -23
- package/src/context-view.ts +20 -3
- package/src/context.ts +85 -29
- package/src/inject.ts +145 -70
- package/src/resolution-session.ts +25 -10
- package/src/resolver.ts +8 -2
package/src/inject.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
ParameterDecoratorFactory,
|
|
13
13
|
PropertyDecoratorFactory,
|
|
14
14
|
} from '@loopback/metadata';
|
|
15
|
-
import {BindingTag} from './binding';
|
|
15
|
+
import {Binding, BindingTag} from './binding';
|
|
16
16
|
import {
|
|
17
17
|
BindingFilter,
|
|
18
18
|
BindingSelector,
|
|
@@ -20,8 +20,8 @@ import {
|
|
|
20
20
|
isBindingAddress,
|
|
21
21
|
} from './binding-filter';
|
|
22
22
|
import {BindingAddress} from './binding-key';
|
|
23
|
-
import {Context} from './context';
|
|
24
|
-
import {ContextView} from './context-view';
|
|
23
|
+
import {BindingCreationPolicy, Context} from './context';
|
|
24
|
+
import {ContextView, createViewGetter} from './context-view';
|
|
25
25
|
import {ResolutionSession} from './resolution-session';
|
|
26
26
|
import {BoundValue, ValueOrPromise} from './value-promise';
|
|
27
27
|
|
|
@@ -39,7 +39,7 @@ export interface ResolverFunction {
|
|
|
39
39
|
(
|
|
40
40
|
ctx: Context,
|
|
41
41
|
injection: Readonly<Injection>,
|
|
42
|
-
session
|
|
42
|
+
session: ResolutionSession,
|
|
43
43
|
): ValueOrPromise<BoundValue>;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -73,7 +73,7 @@ export interface Injection<ValueType = BoundValue> {
|
|
|
73
73
|
| number;
|
|
74
74
|
|
|
75
75
|
bindingSelector: BindingSelector<ValueType>; // Binding selector
|
|
76
|
-
metadata
|
|
76
|
+
metadata: InjectionMetadata; // Related metadata
|
|
77
77
|
resolve?: ResolverFunction; // A custom resolve function
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -111,7 +111,7 @@ export function inject(
|
|
|
111
111
|
if (typeof bindingSelector === 'function' && !resolve) {
|
|
112
112
|
resolve = resolveValuesByFilter;
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
const injectionMetadata = Object.assign({decorator: '@inject'}, metadata);
|
|
115
115
|
return function markParameterOrPropertyAsInjected(
|
|
116
116
|
target: Object,
|
|
117
117
|
member: string,
|
|
@@ -131,7 +131,7 @@ export function inject(
|
|
|
131
131
|
member,
|
|
132
132
|
methodDescriptorOrParameterIndex,
|
|
133
133
|
bindingSelector,
|
|
134
|
-
metadata,
|
|
134
|
+
metadata: injectionMetadata,
|
|
135
135
|
resolve,
|
|
136
136
|
},
|
|
137
137
|
// Do not deep clone the spec as only metadata is mutable and it's
|
|
@@ -167,7 +167,7 @@ export function inject(
|
|
|
167
167
|
member,
|
|
168
168
|
methodDescriptorOrParameterIndex,
|
|
169
169
|
bindingSelector,
|
|
170
|
-
metadata,
|
|
170
|
+
metadata: injectionMetadata,
|
|
171
171
|
resolve,
|
|
172
172
|
},
|
|
173
173
|
// Do not deep clone the spec as only metadata is mutable and it's
|
|
@@ -186,7 +186,9 @@ export function inject(
|
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
|
-
* The function injected by `@inject.getter(bindingSelector)`.
|
|
189
|
+
* The function injected by `@inject.getter(bindingSelector)`. It can be used
|
|
190
|
+
* to fetch bound value(s) from the underlying binding(s). The return value will
|
|
191
|
+
* be an array if the `bindingSelector` is a `BindingFilter` function.
|
|
190
192
|
*/
|
|
191
193
|
export type Getter<T> = () => Promise<T>;
|
|
192
194
|
|
|
@@ -201,10 +203,28 @@ export namespace Getter {
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
/**
|
|
204
|
-
* The function injected by `@inject.setter(
|
|
206
|
+
* The function injected by `@inject.setter(bindingKey)`. It sets the underlying
|
|
207
|
+
* binding to a constant value using `binding.to(value)`.
|
|
208
|
+
*
|
|
209
|
+
* For example:
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* setterFn('my-value');
|
|
213
|
+
* ```
|
|
214
|
+
* @param value The value for the underlying binding
|
|
205
215
|
*/
|
|
206
216
|
export type Setter<T> = (value: T) => void;
|
|
207
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Metadata for `@inject.binding`
|
|
220
|
+
*/
|
|
221
|
+
export interface InjectBindingMetadata extends InjectionMetadata {
|
|
222
|
+
/**
|
|
223
|
+
* Controls how the underlying binding is resolved/created
|
|
224
|
+
*/
|
|
225
|
+
bindingCreation?: BindingCreationPolicy;
|
|
226
|
+
}
|
|
227
|
+
|
|
208
228
|
export namespace inject {
|
|
209
229
|
/**
|
|
210
230
|
* Inject a function for getting the actual bound value.
|
|
@@ -249,16 +269,49 @@ export namespace inject {
|
|
|
249
269
|
*/
|
|
250
270
|
export const setter = function injectSetter(
|
|
251
271
|
bindingKey: BindingAddress,
|
|
252
|
-
metadata?:
|
|
272
|
+
metadata?: InjectBindingMetadata,
|
|
253
273
|
) {
|
|
254
274
|
metadata = Object.assign({decorator: '@inject.setter'}, metadata);
|
|
255
275
|
return inject(bindingKey, metadata, resolveAsSetter);
|
|
256
276
|
};
|
|
257
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Inject the binding object for the given key. This is useful if a binding
|
|
280
|
+
* needs to be set up beyond just a constant value allowed by
|
|
281
|
+
* `@inject.setter`. The injected binding is found or created based on the
|
|
282
|
+
* `metadata.bindingCreation` option. See `BindingCreationPolicy` for more
|
|
283
|
+
* details.
|
|
284
|
+
*
|
|
285
|
+
* For example:
|
|
286
|
+
*
|
|
287
|
+
* ```ts
|
|
288
|
+
* class MyAuthAction {
|
|
289
|
+
* @inject.binding('current-user', {
|
|
290
|
+
* bindingCreation: BindingCreationPolicy.ALWAYS_CREATE,
|
|
291
|
+
* })
|
|
292
|
+
* private userBinding: Binding<UserProfile>;
|
|
293
|
+
*
|
|
294
|
+
* async authenticate() {
|
|
295
|
+
* this.userBinding.toDynamicValue(() => {...});
|
|
296
|
+
* }
|
|
297
|
+
* }
|
|
298
|
+
* ```
|
|
299
|
+
*
|
|
300
|
+
* @param bindingKey Binding key
|
|
301
|
+
* @param metadata Metadata for the injection
|
|
302
|
+
*/
|
|
303
|
+
export const binding = function injectBinding(
|
|
304
|
+
bindingKey: BindingAddress,
|
|
305
|
+
metadata?: InjectBindingMetadata,
|
|
306
|
+
) {
|
|
307
|
+
metadata = Object.assign({decorator: '@inject.binding'}, metadata);
|
|
308
|
+
return inject(bindingKey, metadata, resolveAsBinding);
|
|
309
|
+
};
|
|
310
|
+
|
|
258
311
|
/**
|
|
259
312
|
* Inject an array of values by a tag pattern string or regexp
|
|
260
313
|
*
|
|
261
|
-
*
|
|
314
|
+
* For example,
|
|
262
315
|
* ```ts
|
|
263
316
|
* class AuthenticationManager {
|
|
264
317
|
* constructor(
|
|
@@ -315,52 +368,98 @@ export namespace inject {
|
|
|
315
368
|
};
|
|
316
369
|
}
|
|
317
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Assert the target type inspected from TypeScript for injection to be the
|
|
373
|
+
* expected type. If the types don't match, an error is thrown.
|
|
374
|
+
* @param injection Injection information
|
|
375
|
+
* @param expectedType Expected type
|
|
376
|
+
* @param expectedTypeName Name of the expected type to be used in the error
|
|
377
|
+
* @returns The name of the target
|
|
378
|
+
*/
|
|
379
|
+
export function assertTargetType(
|
|
380
|
+
injection: Readonly<Injection>,
|
|
381
|
+
expectedType: Function,
|
|
382
|
+
expectedTypeName?: string,
|
|
383
|
+
) {
|
|
384
|
+
const targetName = ResolutionSession.describeInjection(injection).targetName;
|
|
385
|
+
const targetType = inspectTargetType(injection);
|
|
386
|
+
if (targetType && targetType !== expectedType) {
|
|
387
|
+
expectedTypeName = expectedTypeName || expectedType.name;
|
|
388
|
+
throw new Error(
|
|
389
|
+
`The type of ${targetName} (${
|
|
390
|
+
targetType.name
|
|
391
|
+
}) is not ${expectedTypeName}`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return targetName;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Resolver for `@inject.getter`
|
|
399
|
+
* @param ctx
|
|
400
|
+
* @param injection
|
|
401
|
+
* @param session
|
|
402
|
+
*/
|
|
318
403
|
function resolveAsGetter(
|
|
319
404
|
ctx: Context,
|
|
320
405
|
injection: Readonly<Injection>,
|
|
321
|
-
session
|
|
406
|
+
session: ResolutionSession,
|
|
322
407
|
) {
|
|
323
|
-
|
|
408
|
+
assertTargetType(injection, Function, 'Getter function');
|
|
324
409
|
const bindingSelector = injection.bindingSelector as BindingAddress;
|
|
325
410
|
// We need to clone the session for the getter as it will be resolved later
|
|
326
|
-
|
|
411
|
+
const forkedSession = ResolutionSession.fork(session);
|
|
327
412
|
return function getter() {
|
|
328
413
|
return ctx.get(bindingSelector, {
|
|
329
|
-
session,
|
|
330
|
-
optional: injection.metadata
|
|
414
|
+
session: forkedSession,
|
|
415
|
+
optional: injection.metadata.optional,
|
|
331
416
|
});
|
|
332
417
|
};
|
|
333
418
|
}
|
|
334
419
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Resolver for `@inject.setter`
|
|
422
|
+
* @param ctx
|
|
423
|
+
* @param injection
|
|
424
|
+
*/
|
|
425
|
+
function resolveAsSetter(ctx: Context, injection: Injection) {
|
|
426
|
+
const targetName = assertTargetType(injection, Function, 'Setter function');
|
|
427
|
+
const bindingSelector = injection.bindingSelector;
|
|
428
|
+
if (!isBindingAddress(bindingSelector)) {
|
|
340
429
|
throw new Error(
|
|
341
|
-
|
|
430
|
+
`@inject.setter (${targetName}) does not allow BindingFilter.`,
|
|
342
431
|
);
|
|
343
432
|
}
|
|
433
|
+
// No resolution session should be propagated into the setter
|
|
434
|
+
return function setter(value: unknown) {
|
|
435
|
+
const binding = findOrCreateBindingForInjection(ctx, injection);
|
|
436
|
+
binding.to(value);
|
|
437
|
+
};
|
|
344
438
|
}
|
|
345
439
|
|
|
346
|
-
function
|
|
347
|
-
const
|
|
348
|
-
const targetName = ResolutionSession.describeInjection(injection)!.targetName;
|
|
349
|
-
if (targetType && targetType !== Function) {
|
|
350
|
-
throw new Error(
|
|
351
|
-
`The type of ${targetName} (${targetType.name}) is not a Setter function`,
|
|
352
|
-
);
|
|
353
|
-
}
|
|
440
|
+
function resolveAsBinding(ctx: Context, injection: Injection) {
|
|
441
|
+
const targetName = assertTargetType(injection, Binding);
|
|
354
442
|
const bindingSelector = injection.bindingSelector;
|
|
355
443
|
if (!isBindingAddress(bindingSelector)) {
|
|
356
444
|
throw new Error(
|
|
357
|
-
`@inject.
|
|
445
|
+
`@inject.binding (${targetName}) does not allow BindingFilter.`,
|
|
358
446
|
);
|
|
359
447
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
448
|
+
return findOrCreateBindingForInjection(ctx, injection);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function findOrCreateBindingForInjection(
|
|
452
|
+
ctx: Context,
|
|
453
|
+
injection: Injection<unknown>,
|
|
454
|
+
) {
|
|
455
|
+
const bindingCreation =
|
|
456
|
+
injection.metadata &&
|
|
457
|
+
(injection.metadata as InjectBindingMetadata).bindingCreation;
|
|
458
|
+
const binding: Binding<unknown> = ctx.findOrCreateBinding(
|
|
459
|
+
injection.bindingSelector as BindingAddress,
|
|
460
|
+
bindingCreation,
|
|
461
|
+
);
|
|
462
|
+
return binding;
|
|
364
463
|
}
|
|
365
464
|
|
|
366
465
|
/**
|
|
@@ -396,8 +495,9 @@ export function describeInjectedArguments(
|
|
|
396
495
|
}
|
|
397
496
|
|
|
398
497
|
/**
|
|
399
|
-
* Inspect the target type
|
|
400
|
-
*
|
|
498
|
+
* Inspect the target type for the injection to find out the corresponding
|
|
499
|
+
* JavaScript type
|
|
500
|
+
* @param injection Injection information
|
|
401
501
|
*/
|
|
402
502
|
function inspectTargetType(injection: Readonly<Injection>) {
|
|
403
503
|
let type = MetadataInspector.getDesignTypeForProperty(
|
|
@@ -427,25 +527,14 @@ function inspectTargetType(injection: Readonly<Injection>) {
|
|
|
427
527
|
function resolveValuesByFilter(
|
|
428
528
|
ctx: Context,
|
|
429
529
|
injection: Readonly<Injection>,
|
|
430
|
-
session
|
|
530
|
+
session: ResolutionSession,
|
|
431
531
|
) {
|
|
432
|
-
|
|
532
|
+
assertTargetType(injection, Array);
|
|
433
533
|
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
434
534
|
const view = new ContextView(ctx, bindingFilter);
|
|
435
535
|
return view.resolve(session);
|
|
436
536
|
}
|
|
437
537
|
|
|
438
|
-
function assertTargetIsArray(injection: Readonly<Injection>) {
|
|
439
|
-
const targetType = inspectTargetType(injection);
|
|
440
|
-
if (targetType !== Array) {
|
|
441
|
-
const targetName = ResolutionSession.describeInjection(injection)!
|
|
442
|
-
.targetName;
|
|
443
|
-
throw new Error(
|
|
444
|
-
`The type of ${targetName} (${targetType.name}) is not Array`,
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
538
|
/**
|
|
450
539
|
* Resolve to a getter function that returns an array of bound values matching
|
|
451
540
|
* the filter function for `@inject.getter`.
|
|
@@ -457,13 +546,11 @@ function assertTargetIsArray(injection: Readonly<Injection>) {
|
|
|
457
546
|
function resolveAsGetterByFilter(
|
|
458
547
|
ctx: Context,
|
|
459
548
|
injection: Readonly<Injection>,
|
|
460
|
-
session
|
|
549
|
+
session: ResolutionSession,
|
|
461
550
|
) {
|
|
462
|
-
|
|
551
|
+
assertTargetType(injection, Function, 'Getter function');
|
|
463
552
|
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
464
|
-
|
|
465
|
-
view.open();
|
|
466
|
-
return view.asGetter(session);
|
|
553
|
+
return createViewGetter(ctx, bindingFilter, session);
|
|
467
554
|
}
|
|
468
555
|
|
|
469
556
|
/**
|
|
@@ -471,21 +558,9 @@ function resolveAsGetterByFilter(
|
|
|
471
558
|
* for `@inject.view`
|
|
472
559
|
* @param ctx Context object
|
|
473
560
|
* @param injection Injection information
|
|
474
|
-
* @param session Resolution session
|
|
475
561
|
*/
|
|
476
|
-
function resolveAsContextView(
|
|
477
|
-
|
|
478
|
-
injection: Readonly<Injection>,
|
|
479
|
-
session?: ResolutionSession,
|
|
480
|
-
) {
|
|
481
|
-
const targetType = inspectTargetType(injection);
|
|
482
|
-
if (targetType && targetType !== ContextView) {
|
|
483
|
-
const targetName = ResolutionSession.describeInjection(injection)!
|
|
484
|
-
.targetName;
|
|
485
|
-
throw new Error(
|
|
486
|
-
`The type of ${targetName} (${targetType.name}) is not ContextView`,
|
|
487
|
-
);
|
|
488
|
-
}
|
|
562
|
+
function resolveAsContextView(ctx: Context, injection: Readonly<Injection>) {
|
|
563
|
+
assertTargetType(injection, ContextView);
|
|
489
564
|
|
|
490
565
|
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
491
566
|
const view = new ContextView(ctx, bindingFilter);
|
|
@@ -3,24 +3,20 @@
|
|
|
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 {DecoratorFactory} from '@loopback/metadata';
|
|
7
|
+
import * as debugModule from 'debug';
|
|
6
8
|
import {Binding} from './binding';
|
|
7
9
|
import {Injection} from './inject';
|
|
8
|
-
import {
|
|
9
|
-
import * as debugModule from 'debug';
|
|
10
|
-
import {DecoratorFactory} from '@loopback/metadata';
|
|
10
|
+
import {BoundValue, tryWithFinally, ValueOrPromise} from './value-promise';
|
|
11
11
|
|
|
12
12
|
const debugSession = debugModule('loopback:context:resolver:session');
|
|
13
13
|
const getTargetName = DecoratorFactory.getTargetName;
|
|
14
14
|
|
|
15
|
-
// NOTE(bajtos) The following import is required to satisfy TypeScript compiler
|
|
16
|
-
// tslint:disable-next-line:no-unused
|
|
17
|
-
import {BindingKey} from './binding-key';
|
|
18
|
-
|
|
19
15
|
/**
|
|
20
16
|
* A function to be executed with the resolution session
|
|
21
17
|
*/
|
|
22
18
|
export type ResolutionAction = (
|
|
23
|
-
session
|
|
19
|
+
session: ResolutionSession,
|
|
24
20
|
) => ValueOrPromise<BoundValue>;
|
|
25
21
|
|
|
26
22
|
/**
|
|
@@ -161,7 +157,7 @@ export class ResolutionSession {
|
|
|
161
157
|
*/
|
|
162
158
|
static describeInjection(injection?: Readonly<Injection>) {
|
|
163
159
|
/* istanbul ignore if */
|
|
164
|
-
if (injection == null) return
|
|
160
|
+
if (injection == null) return {};
|
|
165
161
|
const name = getTargetName(
|
|
166
162
|
injection.target,
|
|
167
163
|
injection.member,
|
|
@@ -169,7 +165,7 @@ export class ResolutionSession {
|
|
|
169
165
|
);
|
|
170
166
|
return {
|
|
171
167
|
targetName: name,
|
|
172
|
-
|
|
168
|
+
bindingSelector: injection.bindingSelector,
|
|
173
169
|
// Cast to Object so that we don't have to expose InjectionMetadata
|
|
174
170
|
metadata: injection.metadata as Object,
|
|
175
171
|
};
|
|
@@ -343,3 +339,22 @@ export interface ResolutionOptions {
|
|
|
343
339
|
*/
|
|
344
340
|
optional?: boolean;
|
|
345
341
|
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Resolution options or session
|
|
345
|
+
*/
|
|
346
|
+
export type ResolutionOptionsOrSession = ResolutionOptions | ResolutionSession;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Normalize ResolutionOptionsOrSession to ResolutionOptions
|
|
350
|
+
* @param optionsOrSession resolution options or session
|
|
351
|
+
*/
|
|
352
|
+
export function asResolutionOptions(
|
|
353
|
+
optionsOrSession?: ResolutionOptionsOrSession,
|
|
354
|
+
): ResolutionOptions {
|
|
355
|
+
// backwards compatibility
|
|
356
|
+
if (optionsOrSession instanceof ResolutionSession) {
|
|
357
|
+
return {session: optionsOrSession};
|
|
358
|
+
}
|
|
359
|
+
return optionsOrSession || {};
|
|
360
|
+
}
|
package/src/resolver.ts
CHANGED
|
@@ -55,7 +55,13 @@ export function instantiateClass<T>(
|
|
|
55
55
|
debug('Non-injected arguments:', nonInjectedArgs);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
const argsOrPromise = resolveInjectedArguments(
|
|
58
|
+
const argsOrPromise = resolveInjectedArguments(
|
|
59
|
+
ctor,
|
|
60
|
+
'',
|
|
61
|
+
ctx,
|
|
62
|
+
session,
|
|
63
|
+
nonInjectedArgs,
|
|
64
|
+
);
|
|
59
65
|
const propertiesOrPromise = resolveInjectedProperties(ctor, ctx, session);
|
|
60
66
|
const inst: ValueOrPromise<T> = transformValueOrPromise(
|
|
61
67
|
argsOrPromise,
|
|
@@ -146,7 +152,7 @@ function resolve<T>(
|
|
|
146
152
|
session: s,
|
|
147
153
|
// If the `optional` flag is set for the injection, the resolution
|
|
148
154
|
// will return `undefined` instead of throwing an error
|
|
149
|
-
optional: injection.metadata
|
|
155
|
+
optional: injection.metadata.optional,
|
|
150
156
|
});
|
|
151
157
|
}
|
|
152
158
|
},
|