@loopback/context 1.11.0 → 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 +13 -0
- package/dist/binding-filter.d.ts +28 -1
- package/dist/binding-filter.js.map +1 -1
- package/dist/binding.d.ts +4 -4
- package/dist/binding.js +7 -5
- package/dist/binding.js.map +1 -1
- package/dist/context-view.d.ts +3 -3
- package/dist/context-view.js +3 -2
- package/dist/context-view.js.map +1 -1
- package/dist/context.d.ts +25 -0
- package/dist/context.js +48 -1
- package/dist/context.js.map +1 -1
- package/dist/inject.d.ts +59 -5
- package/dist/inject.js +88 -39
- package/dist/inject.js.map +1 -1
- package/dist/resolution-session.d.ts +5 -1
- package/dist/resolution-session.js +1 -1
- package/dist/resolution-session.js.map +1 -1
- package/package.json +6 -6
- package/src/binding-filter.ts +29 -1
- package/src/binding.ts +7 -5
- package/src/context-view.ts +7 -6
- package/src/context.ts +50 -2
- package/src/inject.ts +137 -60
- package/src/resolution-session.ts +1 -1
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,7 +20,7 @@ import {
|
|
|
20
20
|
isBindingAddress,
|
|
21
21
|
} from './binding-filter';
|
|
22
22
|
import {BindingAddress} from './binding-key';
|
|
23
|
-
import {Context} from './context';
|
|
23
|
+
import {BindingCreationPolicy, Context} from './context';
|
|
24
24
|
import {ContextView, createViewGetter} from './context-view';
|
|
25
25
|
import {ResolutionSession} from './resolution-session';
|
|
26
26
|
import {BoundValue, ValueOrPromise} from './value-promise';
|
|
@@ -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,
|
|
414
|
+
session: forkedSession,
|
|
330
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,9 +546,9 @@ 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
553
|
return createViewGetter(ctx, bindingFilter, session);
|
|
465
554
|
}
|
|
@@ -469,21 +558,9 @@ function resolveAsGetterByFilter(
|
|
|
469
558
|
* for `@inject.view`
|
|
470
559
|
* @param ctx Context object
|
|
471
560
|
* @param injection Injection information
|
|
472
|
-
* @param session Resolution session
|
|
473
561
|
*/
|
|
474
|
-
function resolveAsContextView(
|
|
475
|
-
|
|
476
|
-
injection: Readonly<Injection>,
|
|
477
|
-
session?: ResolutionSession,
|
|
478
|
-
) {
|
|
479
|
-
const targetType = inspectTargetType(injection);
|
|
480
|
-
if (targetType && targetType !== ContextView) {
|
|
481
|
-
const targetName = ResolutionSession.describeInjection(injection)!
|
|
482
|
-
.targetName;
|
|
483
|
-
throw new Error(
|
|
484
|
-
`The type of ${targetName} (${targetType.name}) is not ContextView`,
|
|
485
|
-
);
|
|
486
|
-
}
|
|
562
|
+
function resolveAsContextView(ctx: Context, injection: Readonly<Injection>) {
|
|
563
|
+
assertTargetType(injection, ContextView);
|
|
487
564
|
|
|
488
565
|
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
489
566
|
const view = new ContextView(ctx, bindingFilter);
|
|
@@ -157,7 +157,7 @@ export class ResolutionSession {
|
|
|
157
157
|
*/
|
|
158
158
|
static describeInjection(injection?: Readonly<Injection>) {
|
|
159
159
|
/* istanbul ignore if */
|
|
160
|
-
if (injection == null) return
|
|
160
|
+
if (injection == null) return {};
|
|
161
161
|
const name = getTargetName(
|
|
162
162
|
injection.target,
|
|
163
163
|
injection.member,
|