@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/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?: ResolutionSession,
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?: InjectionMetadata; // Related 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
- metadata = Object.assign({decorator: '@inject'}, metadata);
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(key)`.
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?: InjectionMetadata,
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
- * @example
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?: ResolutionSession,
406
+ session: ResolutionSession,
322
407
  ) {
323
- assertTargetIsGetter(injection);
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
- session = ResolutionSession.fork(session);
411
+ const forkedSession = ResolutionSession.fork(session);
327
412
  return function getter() {
328
413
  return ctx.get(bindingSelector, {
329
- session,
330
- optional: injection.metadata && injection.metadata.optional,
414
+ session: forkedSession,
415
+ optional: injection.metadata.optional,
331
416
  });
332
417
  };
333
418
  }
334
419
 
335
- function assertTargetIsGetter(injection: Readonly<Injection>) {
336
- const targetType = inspectTargetType(injection);
337
- if (targetType && targetType !== Function) {
338
- const targetName = ResolutionSession.describeInjection(injection)!
339
- .targetName;
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
- `The type of ${targetName} (${targetType.name}) is not a Getter function`,
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 resolveAsSetter(ctx: Context, injection: Injection) {
347
- const targetType = inspectTargetType(injection);
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.setter for (${targetType.name}) does not allow BindingFilter`,
445
+ `@inject.binding (${targetName}) does not allow BindingFilter.`,
358
446
  );
359
447
  }
360
- // No resolution session should be propagated into the setter
361
- return function setter(value: unknown) {
362
- ctx.bind(bindingSelector).to(value);
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
- * @param injection
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?: ResolutionSession,
530
+ session: ResolutionSession,
431
531
  ) {
432
- assertTargetIsArray(injection);
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?: ResolutionSession,
549
+ session: ResolutionSession,
461
550
  ) {
462
- assertTargetIsGetter(injection);
551
+ assertTargetType(injection, Function, 'Getter function');
463
552
  const bindingFilter = injection.bindingSelector as BindingFilter;
464
- const view = new ContextView(ctx, bindingFilter);
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
- ctx: Context,
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 {ValueOrPromise, BoundValue, tryWithFinally} from './value-promise';
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?: ResolutionSession,
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 undefined;
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
- bindingKey: injection.bindingSelector,
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(ctor, '', ctx, session);
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 && injection.metadata.optional,
155
+ optional: injection.metadata.optional,
150
156
  });
151
157
  }
152
158
  },