@loopback/context 4.0.0-alpha.6 → 4.0.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.
Files changed (137) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +116 -0
  3. package/dist/binding-config.d.ts +40 -0
  4. package/dist/binding-config.js +33 -0
  5. package/dist/binding-config.js.map +1 -0
  6. package/dist/binding-decorator.d.ts +45 -0
  7. package/dist/binding-decorator.js +118 -0
  8. package/dist/binding-decorator.js.map +1 -0
  9. package/dist/binding-filter.d.ts +108 -0
  10. package/dist/binding-filter.js +162 -0
  11. package/dist/binding-filter.js.map +1 -0
  12. package/dist/binding-inspector.d.ts +150 -0
  13. package/dist/binding-inspector.js +249 -0
  14. package/dist/binding-inspector.js.map +1 -0
  15. package/dist/binding-key.d.ts +66 -0
  16. package/dist/binding-key.js +121 -0
  17. package/dist/binding-key.js.map +1 -0
  18. package/dist/binding-sorter.d.ts +71 -0
  19. package/dist/binding-sorter.js +89 -0
  20. package/dist/binding-sorter.js.map +1 -0
  21. package/dist/binding.d.ts +577 -0
  22. package/dist/binding.js +788 -0
  23. package/dist/binding.js.map +1 -0
  24. package/dist/context-event.d.ts +23 -0
  25. package/dist/context-event.js +7 -0
  26. package/dist/context-event.js.map +1 -0
  27. package/dist/context-observer.d.ts +36 -0
  28. package/dist/context-observer.js +7 -0
  29. package/dist/context-observer.js.map +1 -0
  30. package/dist/context-subscription.d.ts +147 -0
  31. package/dist/context-subscription.js +317 -0
  32. package/dist/context-subscription.js.map +1 -0
  33. package/dist/context-tag-indexer.d.ts +42 -0
  34. package/dist/context-tag-indexer.js +135 -0
  35. package/dist/context-tag-indexer.js.map +1 -0
  36. package/dist/context-view.d.ts +209 -0
  37. package/dist/context-view.js +240 -0
  38. package/dist/context-view.js.map +1 -0
  39. package/dist/context.d.ts +513 -0
  40. package/dist/context.js +717 -0
  41. package/dist/context.js.map +1 -0
  42. package/dist/index.d.ts +52 -0
  43. package/dist/index.js +60 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/inject-config.d.ts +67 -0
  46. package/dist/inject-config.js +181 -0
  47. package/dist/inject-config.js.map +1 -0
  48. package/dist/inject.d.ts +250 -0
  49. package/dist/inject.js +535 -0
  50. package/dist/inject.js.map +1 -0
  51. package/dist/interception-proxy.d.ts +76 -0
  52. package/dist/interception-proxy.js +67 -0
  53. package/dist/interception-proxy.js.map +1 -0
  54. package/dist/interceptor-chain.d.ts +121 -0
  55. package/dist/interceptor-chain.js +148 -0
  56. package/dist/interceptor-chain.js.map +1 -0
  57. package/dist/interceptor.d.ts +138 -0
  58. package/dist/interceptor.js +299 -0
  59. package/dist/interceptor.js.map +1 -0
  60. package/dist/invocation.d.ts +101 -0
  61. package/dist/invocation.js +163 -0
  62. package/dist/invocation.js.map +1 -0
  63. package/dist/json-types.d.ts +28 -0
  64. package/dist/json-types.js +7 -0
  65. package/dist/json-types.js.map +1 -0
  66. package/dist/keys.d.ts +65 -0
  67. package/dist/keys.js +74 -0
  68. package/dist/keys.js.map +1 -0
  69. package/dist/provider.d.ts +31 -0
  70. package/dist/provider.js +7 -0
  71. package/dist/provider.js.map +1 -0
  72. package/dist/resolution-session.d.ts +180 -0
  73. package/dist/resolution-session.js +274 -0
  74. package/dist/resolution-session.js.map +1 -0
  75. package/dist/resolver.d.ts +46 -0
  76. package/dist/resolver.js +203 -0
  77. package/dist/resolver.js.map +1 -0
  78. package/dist/unique-id.d.ts +14 -0
  79. package/dist/unique-id.js +26 -0
  80. package/dist/unique-id.js.map +1 -0
  81. package/dist/value-promise.d.ts +134 -0
  82. package/dist/value-promise.js +277 -0
  83. package/dist/value-promise.js.map +1 -0
  84. package/package.json +49 -36
  85. package/src/binding-config.ts +73 -0
  86. package/src/binding-decorator.ts +136 -0
  87. package/src/binding-filter.ts +250 -0
  88. package/src/binding-inspector.ts +371 -0
  89. package/src/binding-key.ts +136 -0
  90. package/src/binding-sorter.ts +124 -0
  91. package/src/binding.ts +1107 -0
  92. package/src/context-event.ts +30 -0
  93. package/src/context-observer.ts +50 -0
  94. package/src/context-subscription.ts +402 -0
  95. package/src/context-tag-indexer.ts +147 -0
  96. package/src/context-view.ts +440 -0
  97. package/src/context.ts +1079 -0
  98. package/src/index.ts +58 -0
  99. package/src/inject-config.ts +239 -0
  100. package/src/inject.ts +796 -0
  101. package/src/interception-proxy.ts +127 -0
  102. package/src/interceptor-chain.ts +268 -0
  103. package/src/interceptor.ts +430 -0
  104. package/src/invocation.ts +269 -0
  105. package/src/json-types.ts +35 -0
  106. package/src/keys.ts +85 -0
  107. package/src/provider.ts +37 -0
  108. package/src/resolution-session.ts +414 -0
  109. package/src/resolver.ts +282 -0
  110. package/src/unique-id.ts +24 -0
  111. package/src/value-promise.ts +318 -0
  112. package/index.d.ts +0 -6
  113. package/index.js +0 -9
  114. package/lib/binding.d.ts +0 -75
  115. package/lib/binding.js +0 -102
  116. package/lib/context.d.ts +0 -14
  117. package/lib/context.js +0 -96
  118. package/lib/index.d.ts +0 -5
  119. package/lib/index.js +0 -13
  120. package/lib/inject.d.ts +0 -24
  121. package/lib/inject.js +0 -43
  122. package/lib/isPromise.d.ts +0 -1
  123. package/lib/isPromise.js +0 -14
  124. package/lib/resolver.d.ts +0 -26
  125. package/lib/resolver.js +0 -72
  126. package/lib6/binding.d.ts +0 -75
  127. package/lib6/binding.js +0 -102
  128. package/lib6/context.d.ts +0 -14
  129. package/lib6/context.js +0 -96
  130. package/lib6/index.d.ts +0 -5
  131. package/lib6/index.js +0 -13
  132. package/lib6/inject.d.ts +0 -24
  133. package/lib6/inject.js +0 -43
  134. package/lib6/isPromise.d.ts +0 -1
  135. package/lib6/isPromise.js +0 -14
  136. package/lib6/resolver.d.ts +0 -26
  137. package/lib6/resolver.js +0 -72
package/src/binding.ts ADDED
@@ -0,0 +1,1107 @@
1
+ // Copyright IBM Corp. 2017,2020. All Rights Reserved.
2
+ // Node module: @loopback/context
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import debugFactory from 'debug';
7
+ import {EventEmitter} from 'events';
8
+ import {bindingTemplateFor} from './binding-inspector';
9
+ import {BindingAddress, BindingKey} from './binding-key';
10
+ import {Context} from './context';
11
+ import {inspectInjections} from './inject';
12
+ import {createProxyWithInterceptors} from './interception-proxy';
13
+ import {invokeMethod} from './invocation';
14
+ import {JSONObject} from './json-types';
15
+ import {ContextTags} from './keys';
16
+ import {Provider} from './provider';
17
+ import {
18
+ asResolutionOptions,
19
+ ResolutionContext,
20
+ ResolutionError,
21
+ ResolutionOptions,
22
+ ResolutionOptionsOrSession,
23
+ ResolutionSession,
24
+ } from './resolution-session';
25
+ import {instantiateClass} from './resolver';
26
+ import {
27
+ BoundValue,
28
+ Constructor,
29
+ isPromiseLike,
30
+ MapObject,
31
+ transformValueOrPromise,
32
+ ValueOrPromise,
33
+ } from './value-promise';
34
+
35
+ const debug = debugFactory('loopback:context:binding');
36
+
37
+ /**
38
+ * Scope for binding values
39
+ */
40
+ export enum BindingScope {
41
+ /**
42
+ * The binding provides a value that is calculated each time. This will be
43
+ * the default scope if not set.
44
+ *
45
+ * For example, with the following context hierarchy:
46
+ *
47
+ * - `app` (with a binding `'b1'` that produces sequential values 0, 1, ...)
48
+ * - req1
49
+ * - req2
50
+ *
51
+ * Now `'b1'` is resolved to a new value each time for `app` and its
52
+ * descendants `req1` and `req2`:
53
+ * - app.get('b1') ==> 0
54
+ * - req1.get('b1') ==> 1
55
+ * - req2.get('b1') ==> 2
56
+ * - req2.get('b1') ==> 3
57
+ * - app.get('b1') ==> 4
58
+ */
59
+ TRANSIENT = 'Transient',
60
+
61
+ /**
62
+ * @deprecated Finer-grained scopes such as `APPLICATION`, `SERVER`, or
63
+ * `REQUEST` should be used instead to ensure the scope of sharing of resolved
64
+ * binding values.
65
+ *
66
+ * The binding provides a value as a singleton within each local context. The
67
+ * value is calculated only once per context and cached for subsequential
68
+ * uses. Child contexts have their own value and do not share with their
69
+ * ancestors.
70
+ *
71
+ * For example, with the following context hierarchy:
72
+ *
73
+ * - `app` (with a binding `'b1'` that produces sequential values 0, 1, ...)
74
+ * - req1
75
+ * - req2
76
+ *
77
+ * 1. `0` is the resolved value for `'b1'` within the `app` afterward
78
+ * - app.get('b1') ==> 0 (always)
79
+ *
80
+ * 2. `'b1'` is resolved in `app` but not in `req1`, a new value `1` is
81
+ * calculated and used for `req1` afterward
82
+ * - req1.get('b1') ==> 1 (always)
83
+ *
84
+ * 3. `'b1'` is resolved in `app` but not in `req2`, a new value `2` is
85
+ * calculated and used for `req2` afterward
86
+ * - req2.get('b1') ==> 2 (always)
87
+ *
88
+ */
89
+ CONTEXT = 'Context',
90
+
91
+ /**
92
+ * The binding provides a value as a singleton within the context hierarchy
93
+ * (the owning context and its descendants). The value is calculated only
94
+ * once for the owning context and cached for subsequential uses. Child
95
+ * contexts share the same value as their ancestors.
96
+ *
97
+ * For example, with the following context hierarchy:
98
+ *
99
+ * - `app` (with a binding `'b1'` that produces sequential values 0, 1, ...)
100
+ * - req1
101
+ * - req2
102
+ *
103
+ * 1. `0` is the singleton for `app` afterward
104
+ * - app.get('b1') ==> 0 (always)
105
+ *
106
+ * 2. `'b1'` is resolved in `app`, reuse it for `req1`
107
+ * - req1.get('b1') ==> 0 (always)
108
+ *
109
+ * 3. `'b1'` is resolved in `app`, reuse it for `req2`
110
+ * - req2.get('b1') ==> 0 (always)
111
+ */
112
+ SINGLETON = 'Singleton',
113
+
114
+ /*
115
+ * The following scopes are checked against the context hierarchy to find
116
+ * the first matching context for a given scope in the chain. Resolved binding
117
+ * values will be cached and shared on the scoped context. This ensures a
118
+ * binding to have the same value for the scoped context.
119
+ */
120
+
121
+ /**
122
+ * Application scope
123
+ *
124
+ * @remarks
125
+ * The binding provides an application-scoped value within the context
126
+ * hierarchy. Resolved value for this binding will be cached and shared for
127
+ * the same application context (denoted by its scope property set to
128
+ * `BindingScope.APPLICATION`).
129
+ *
130
+ */
131
+ APPLICATION = 'Application',
132
+
133
+ /**
134
+ * Server scope
135
+ *
136
+ * @remarks
137
+ * The binding provides an server-scoped value within the context hierarchy.
138
+ * Resolved value for this binding will be cached and shared for the same
139
+ * server context (denoted by its scope property set to
140
+ * `BindingScope.SERVER`).
141
+ *
142
+ * It's possible that an application has more than one servers configured,
143
+ * such as a `RestServer` and a `GrpcServer`. Both server contexts are created
144
+ * with `scope` set to `BindingScope.SERVER`. Depending on where a binding
145
+ * is resolved:
146
+ * - If the binding is resolved from the RestServer or below, it will be
147
+ * cached using the RestServer context as the key.
148
+ * - If the binding is resolved from the GrpcServer or below, it will be
149
+ * cached using the GrpcServer context as the key.
150
+ *
151
+ * The same binding can resolved/shared/cached for all servers, each of which
152
+ * has its own value for the binding.
153
+ */
154
+ SERVER = 'Server',
155
+
156
+ /**
157
+ * Request scope
158
+ *
159
+ * @remarks
160
+ * The binding provides an request-scoped value within the context hierarchy.
161
+ * Resolved value for this binding will be cached and shared for the same
162
+ * request context (denoted by its scope property set to
163
+ * `BindingScope.REQUEST`).
164
+ *
165
+ * The `REQUEST` scope is very useful for controllers, services and artifacts
166
+ * that want to have a single instance/value for a given request.
167
+ */
168
+ REQUEST = 'Request',
169
+ }
170
+
171
+ /**
172
+ * Type of the binding source
173
+ */
174
+ export enum BindingType {
175
+ /**
176
+ * A fixed value
177
+ */
178
+ CONSTANT = 'Constant',
179
+ /**
180
+ * A function to get the value
181
+ */
182
+ DYNAMIC_VALUE = 'DynamicValue',
183
+ /**
184
+ * A class to be instantiated as the value
185
+ */
186
+ CLASS = 'Class',
187
+ /**
188
+ * A provider class with `value()` function to get the value
189
+ */
190
+ PROVIDER = 'Provider',
191
+ /**
192
+ * A alias to another binding key with optional path
193
+ */
194
+ ALIAS = 'Alias',
195
+ }
196
+
197
+ /**
198
+ * Binding source for `to`
199
+ */
200
+ export type ConstantBindingSource<T> = {
201
+ type: BindingType.CONSTANT;
202
+ value: T;
203
+ };
204
+
205
+ /**
206
+ * Binding source for `toDynamicValue`
207
+ */
208
+ export type DynamicValueBindingSource<T> = {
209
+ type: BindingType.DYNAMIC_VALUE;
210
+ value: ValueFactory<T> | DynamicValueProviderClass<T>;
211
+ };
212
+
213
+ /**
214
+ * Binding source for `toClass`
215
+ */
216
+ export type ClassBindingSource<T> = {
217
+ type: BindingType.CLASS;
218
+ value: Constructor<T>;
219
+ };
220
+
221
+ /**
222
+ * Binding source for `toProvider`
223
+ */
224
+ export type ProviderBindingSource<T> = {
225
+ type: BindingType.PROVIDER;
226
+ value: Constructor<Provider<T>>;
227
+ };
228
+
229
+ /**
230
+ * Binding source for `toAlias`
231
+ */
232
+ export type AliasBindingSource<T> = {
233
+ type: BindingType.ALIAS;
234
+ value: BindingAddress<T>;
235
+ };
236
+
237
+ /**
238
+ * Source for the binding, including the type and value
239
+ */
240
+ export type BindingSource<T> =
241
+ | ConstantBindingSource<T>
242
+ | DynamicValueBindingSource<T>
243
+ | ClassBindingSource<T>
244
+ | ProviderBindingSource<T>
245
+ | AliasBindingSource<T>;
246
+
247
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
248
+ export type TagMap = MapObject<any>;
249
+
250
+ /**
251
+ * Binding tag can be a simple name or name/value pairs
252
+ */
253
+ export type BindingTag = TagMap | string;
254
+
255
+ /**
256
+ * A function as the template to configure bindings
257
+ */
258
+ export type BindingTemplate<T = unknown> = (binding: Binding<T>) => void;
259
+
260
+ /**
261
+ * Information for a binding event
262
+ */
263
+ export type BindingEvent = {
264
+ /**
265
+ * Event type
266
+ */
267
+ type: 'changed' | string;
268
+ /**
269
+ * Source binding that emits the event
270
+ */
271
+ binding: Readonly<Binding<unknown>>;
272
+ /**
273
+ * Operation that triggers the event
274
+ */
275
+ operation: 'tag' | 'scope' | 'value' | string;
276
+ };
277
+
278
+ /**
279
+ * Event listeners for binding events
280
+ */
281
+ export type BindingEventListener = (
282
+ /**
283
+ * Binding event
284
+ */
285
+ event: BindingEvent,
286
+ ) => void;
287
+
288
+ /**
289
+ * A factory function for `toDynamicValue`
290
+ */
291
+ export type ValueFactory<T = unknown> = (
292
+ resolutionCtx: ResolutionContext,
293
+ ) => ValueOrPromise<T | undefined>;
294
+
295
+ /**
296
+ * A class with a static `value` method as the factory function for
297
+ * `toDynamicValue`.
298
+ *
299
+ * @example
300
+ * ```ts
301
+ * import {inject} from '@loopback/context';
302
+ *
303
+ * export class DynamicGreetingProvider {
304
+ * static value(@inject('currentUser') user: string) {
305
+ * return `Hello, ${user}`;
306
+ * }
307
+ * }
308
+ * ```
309
+ */
310
+ export interface DynamicValueProviderClass<T = unknown>
311
+ extends Constructor<unknown>,
312
+ Function {
313
+ value: (...args: BoundValue[]) => ValueOrPromise<T>;
314
+ }
315
+
316
+ /**
317
+ * Adapt the ValueFactoryProvider class to be a value factory
318
+ * @param provider - ValueFactoryProvider class
319
+ */
320
+ function toValueFactory<T = unknown>(
321
+ provider: DynamicValueProviderClass<T>,
322
+ ): ValueFactory<T> {
323
+ return resolutionCtx =>
324
+ invokeMethod(provider, 'value', resolutionCtx.context, [], {
325
+ skipInterceptors: true,
326
+ session: resolutionCtx.options.session,
327
+ });
328
+ }
329
+
330
+ /**
331
+ * Check if the factory is a value factory provider class
332
+ * @param factory - A factory function or a dynamic value provider class
333
+ */
334
+ export function isDynamicValueProviderClass<T = unknown>(
335
+ factory: unknown,
336
+ ): factory is DynamicValueProviderClass<T> {
337
+ // Not a class
338
+ if (typeof factory !== 'function' || !String(factory).startsWith('class ')) {
339
+ return false;
340
+ }
341
+ const valueMethod = (factory as DynamicValueProviderClass).value;
342
+ return typeof valueMethod === 'function';
343
+ }
344
+
345
+ /**
346
+ * Binding represents an entry in the `Context`. Each binding has a key and a
347
+ * corresponding value getter.
348
+ */
349
+ export class Binding<T = BoundValue> extends EventEmitter {
350
+ /**
351
+ * Key of the binding
352
+ */
353
+ public readonly key: string;
354
+
355
+ /**
356
+ * Map for tag name/value pairs
357
+ */
358
+ public readonly tagMap: TagMap = {};
359
+
360
+ private _scope?: BindingScope;
361
+ /**
362
+ * Scope of the binding to control how the value is cached/shared
363
+ */
364
+ public get scope(): BindingScope {
365
+ // Default to TRANSIENT if not set
366
+ return this._scope ?? BindingScope.TRANSIENT;
367
+ }
368
+
369
+ /**
370
+ * Type of the binding value getter
371
+ */
372
+ public get type(): BindingType | undefined {
373
+ return this._source?.type;
374
+ }
375
+
376
+ private _cache: WeakMap<Context, ValueOrPromise<T>>;
377
+ private _getValue?: ValueFactory<T>;
378
+
379
+ /**
380
+ * The original source value received from `to`, `toClass`, `toDynamicValue`,
381
+ * `toProvider`, or `toAlias`.
382
+ */
383
+ private _source?: BindingSource<T>;
384
+
385
+ public get source() {
386
+ return this._source;
387
+ }
388
+
389
+ /**
390
+ * For bindings bound via `toClass()`, this property contains the constructor
391
+ * function of the class
392
+ */
393
+ public get valueConstructor(): Constructor<T> | undefined {
394
+ return this._source?.type === BindingType.CLASS
395
+ ? this._source?.value
396
+ : undefined;
397
+ }
398
+
399
+ /**
400
+ * For bindings bound via `toProvider()`, this property contains the
401
+ * constructor function of the provider class
402
+ */
403
+ public get providerConstructor(): Constructor<Provider<T>> | undefined {
404
+ return this._source?.type === BindingType.PROVIDER
405
+ ? this._source?.value
406
+ : undefined;
407
+ }
408
+
409
+ constructor(key: BindingAddress<T>, public isLocked: boolean = false) {
410
+ super();
411
+ BindingKey.validate(key);
412
+ this.key = key.toString();
413
+ }
414
+
415
+ /**
416
+ * Cache the resolved value by the binding scope
417
+ * @param resolutionCtx - The resolution context
418
+ * @param result - The calculated value for the binding
419
+ */
420
+ private _cacheValue(
421
+ resolutionCtx: Context,
422
+ result: ValueOrPromise<T>,
423
+ ): ValueOrPromise<T> {
424
+ // Initialize the cache as a weakmap keyed by context
425
+ if (!this._cache) this._cache = new WeakMap<Context, ValueOrPromise<T>>();
426
+ if (this.scope !== BindingScope.TRANSIENT) {
427
+ this._cache.set(resolutionCtx!, result);
428
+ }
429
+ return result;
430
+ }
431
+
432
+ /**
433
+ * Clear the cache
434
+ */
435
+ private _clearCache() {
436
+ if (!this._cache) return;
437
+ // WeakMap does not have a `clear` method
438
+ this._cache = new WeakMap();
439
+ }
440
+
441
+ /**
442
+ * Invalidate the binding cache so that its value will be reloaded next time.
443
+ * This is useful to force reloading a cached value when its configuration or
444
+ * dependencies are changed.
445
+ * **WARNING**: The state held in the cached value will be gone.
446
+ *
447
+ * @param ctx - Context object
448
+ */
449
+ refresh(ctx: Context) {
450
+ if (!this._cache) return;
451
+ if (this.scope !== BindingScope.TRANSIENT) {
452
+ const resolutionCtx = ctx.getResolutionContext(this);
453
+ if (resolutionCtx != null) {
454
+ this._cache.delete(resolutionCtx);
455
+ }
456
+ }
457
+ }
458
+
459
+ /**
460
+ * This is an internal function optimized for performance.
461
+ * Users should use `@inject(key)` or `ctx.get(key)` instead.
462
+ *
463
+ * Get the value bound to this key. Depending on `isSync`, this
464
+ * function returns either:
465
+ * - the bound value
466
+ * - a promise of the bound value
467
+ *
468
+ * Consumers wishing to consume sync values directly should use `isPromiseLike`
469
+ * to check the type of the returned value to decide how to handle it.
470
+ *
471
+ * @example
472
+ * ```
473
+ * const result = binding.getValue(ctx);
474
+ * if (isPromiseLike(result)) {
475
+ * result.then(doSomething)
476
+ * } else {
477
+ * doSomething(result);
478
+ * }
479
+ * ```
480
+ *
481
+ * @param ctx - Context for the resolution
482
+ * @param session - Optional session for binding and dependency resolution
483
+ */
484
+ getValue(ctx: Context, session?: ResolutionSession): ValueOrPromise<T>;
485
+
486
+ /**
487
+ * Returns a value or promise for this binding in the given context. The
488
+ * resolved value can be `undefined` if `optional` is set to `true` in
489
+ * `options`.
490
+ * @param ctx - Context for the resolution
491
+ * @param options - Optional options for binding and dependency resolution
492
+ */
493
+ getValue(
494
+ ctx: Context,
495
+ options?: ResolutionOptions,
496
+ ): ValueOrPromise<T | undefined>;
497
+
498
+ // Implementation
499
+ getValue(
500
+ ctx: Context,
501
+ optionsOrSession?: ResolutionOptionsOrSession,
502
+ ): ValueOrPromise<T | undefined> {
503
+ /* istanbul ignore if */
504
+ if (debug.enabled) {
505
+ debug('Get value for binding %s', this.key);
506
+ }
507
+
508
+ const options = asResolutionOptions(optionsOrSession);
509
+ const resolutionCtx = this.getResolutionContext(ctx, options);
510
+ if (resolutionCtx == null) return undefined;
511
+
512
+ // Keep a snapshot for proxy
513
+ const savedSession =
514
+ ResolutionSession.fork(options.session) ?? new ResolutionSession();
515
+
516
+ // First check cached value for non-transient
517
+ if (this._cache) {
518
+ if (this.scope !== BindingScope.TRANSIENT) {
519
+ if (resolutionCtx && this._cache.has(resolutionCtx)) {
520
+ const value = this._cache.get(resolutionCtx)!;
521
+ return this.getValueOrProxy(
522
+ resolutionCtx,
523
+ {...options, session: savedSession},
524
+ value,
525
+ );
526
+ }
527
+ }
528
+ }
529
+ const resolutionMetadata = {
530
+ context: resolutionCtx!,
531
+ binding: this,
532
+ options,
533
+ };
534
+ if (typeof this._getValue === 'function') {
535
+ const result = ResolutionSession.runWithBinding(
536
+ s => {
537
+ const optionsWithSession = {
538
+ ...options,
539
+ session: s,
540
+ // Force to be the non-proxy version
541
+ asProxyWithInterceptors: false,
542
+ };
543
+ // We already test `this._getValue` is a function. It's safe to assert
544
+ // that `this._getValue` is not undefined.
545
+ return this._getValue!({
546
+ ...resolutionMetadata,
547
+ options: optionsWithSession,
548
+ });
549
+ },
550
+ this,
551
+ options.session,
552
+ );
553
+ const value = this._cacheValue(resolutionCtx!, result);
554
+ return this.getValueOrProxy(
555
+ resolutionCtx,
556
+ {...options, session: savedSession},
557
+ value,
558
+ );
559
+ }
560
+ // `@inject.binding` adds a binding without _getValue
561
+ if (options.optional) return undefined;
562
+ return Promise.reject(
563
+ new ResolutionError(
564
+ `No value was configured for binding ${this.key}.`,
565
+ resolutionMetadata,
566
+ ),
567
+ );
568
+ }
569
+
570
+ private getValueOrProxy(
571
+ resolutionCtx: Context,
572
+ options: ResolutionOptions,
573
+ value: ValueOrPromise<T>,
574
+ ): ValueOrPromise<T> {
575
+ const session = options.session!;
576
+ session.pushBinding(this);
577
+ return Binding.valueOrProxy(
578
+ {
579
+ context: resolutionCtx,
580
+ binding: this,
581
+ options,
582
+ },
583
+ value,
584
+ );
585
+ }
586
+
587
+ /**
588
+ * Locate and validate the resolution context
589
+ * @param ctx - Current context
590
+ * @param options - Resolution options
591
+ */
592
+ private getResolutionContext(ctx: Context, options: ResolutionOptions) {
593
+ const resolutionCtx = ctx.getResolutionContext(this);
594
+ switch (this.scope) {
595
+ case BindingScope.APPLICATION:
596
+ case BindingScope.SERVER:
597
+ case BindingScope.REQUEST:
598
+ if (resolutionCtx == null) {
599
+ const msg =
600
+ `Binding "${this.key}" in context "${ctx.name}" cannot` +
601
+ ` be resolved in scope "${this.scope}"`;
602
+ if (options.optional) {
603
+ debug(msg);
604
+ return undefined;
605
+ }
606
+ throw new Error(msg);
607
+ }
608
+ }
609
+
610
+ const ownerCtx = ctx.getOwnerContext(this.key);
611
+ if (ownerCtx != null && !ownerCtx.isVisibleTo(resolutionCtx!)) {
612
+ const msg =
613
+ `Resolution context "${resolutionCtx?.name}" does not have ` +
614
+ `visibility to binding "${this.key} (scope:${this.scope})" in context "${ownerCtx.name}"`;
615
+ if (options.optional) {
616
+ debug(msg);
617
+ return undefined;
618
+ }
619
+ throw new Error(msg);
620
+ }
621
+ return resolutionCtx;
622
+ }
623
+
624
+ /**
625
+ * Lock the binding so that it cannot be rebound
626
+ */
627
+ lock(): this {
628
+ this.isLocked = true;
629
+ return this;
630
+ }
631
+
632
+ /**
633
+ * Emit a `changed` event
634
+ * @param operation - Operation that makes changes
635
+ */
636
+ private emitChangedEvent(operation: string) {
637
+ const event: BindingEvent = {binding: this, operation, type: 'changed'};
638
+ this.emit('changed', event);
639
+ }
640
+
641
+ /**
642
+ * Tag the binding with names or name/value objects. A tag has a name and
643
+ * an optional value. If not supplied, the tag name is used as the value.
644
+ *
645
+ * @param tags - A list of names or name/value objects. Each
646
+ * parameter can be in one of the following forms:
647
+ * - string: A tag name without value
648
+ * - string[]: An array of tag names
649
+ * - TagMap: A map of tag name/value pairs
650
+ *
651
+ * @example
652
+ * ```ts
653
+ * // Add a named tag `controller`
654
+ * binding.tag('controller');
655
+ *
656
+ * // Add two named tags: `controller` and `rest`
657
+ * binding.tag('controller', 'rest');
658
+ *
659
+ * // Add two tags
660
+ * // - `controller` (name = 'controller')
661
+ * // `{name: 'my-controller'}` (name = 'name', value = 'my-controller')
662
+ * binding.tag('controller', {name: 'my-controller'});
663
+ *
664
+ * ```
665
+ */
666
+ tag(...tags: BindingTag[]): this {
667
+ for (const t of tags) {
668
+ if (typeof t === 'string') {
669
+ this.tagMap[t] = t;
670
+ } else if (Array.isArray(t)) {
671
+ // Throw an error as TypeScript cannot exclude array from TagMap
672
+ throw new Error(
673
+ 'Tag must be a string or an object (but not array): ' + t,
674
+ );
675
+ } else {
676
+ Object.assign(this.tagMap, t);
677
+ }
678
+ }
679
+ this.emitChangedEvent('tag');
680
+ return this;
681
+ }
682
+
683
+ /**
684
+ * Get an array of tag names
685
+ */
686
+ get tagNames() {
687
+ return Object.keys(this.tagMap);
688
+ }
689
+
690
+ /**
691
+ * Set the binding scope
692
+ * @param scope - Binding scope
693
+ */
694
+ inScope(scope: BindingScope): this {
695
+ if (this._scope !== scope) this._clearCache();
696
+ this._scope = scope;
697
+ this.emitChangedEvent('scope');
698
+ return this;
699
+ }
700
+
701
+ /**
702
+ * Apply default scope to the binding. It only changes the scope if it's not
703
+ * set yet
704
+ * @param scope - Default binding scope
705
+ */
706
+ applyDefaultScope(scope: BindingScope): this {
707
+ if (!this._scope) {
708
+ this.inScope(scope);
709
+ }
710
+ return this;
711
+ }
712
+
713
+ /**
714
+ * Set the `_getValue` function
715
+ * @param getValue - getValue function
716
+ */
717
+ private _setValueGetter(getValue: ValueFactory<T>) {
718
+ // Clear the cache
719
+ this._clearCache();
720
+ this._getValue = resolutionCtx => {
721
+ return getValue(resolutionCtx);
722
+ };
723
+ this.emitChangedEvent('value');
724
+ }
725
+
726
+ /**
727
+ * Bind the key to a constant value. The value must be already available
728
+ * at binding time, it is not allowed to pass a Promise instance.
729
+ *
730
+ * @param value - The bound value.
731
+ *
732
+ * @example
733
+ *
734
+ * ```ts
735
+ * ctx.bind('appName').to('CodeHub');
736
+ * ```
737
+ */
738
+ to(value: T): this {
739
+ if (isPromiseLike(value)) {
740
+ // Promises are a construct primarily intended for flow control:
741
+ // In an algorithm with steps 1 and 2, we want to wait for the outcome
742
+ // of step 1 before starting step 2.
743
+ //
744
+ // Promises are NOT a tool for storing values that may become available
745
+ // in the future, depending on the success or a failure of a background
746
+ // async task.
747
+ //
748
+ // Values stored in bindings are typically accessed only later,
749
+ // in a different turn of the event loop or the Promise micro-queue.
750
+ // As a result, when a promise is stored via `.to()` and is rejected
751
+ // later, then more likely than not, there will be no error (catch)
752
+ // handler registered yet, and Node.js will print
753
+ // "Unhandled Rejection Warning".
754
+ throw new Error(
755
+ 'Promise instances are not allowed for constant values ' +
756
+ 'bound via ".to()". Register an async getter function ' +
757
+ 'via ".toDynamicValue()" instead.',
758
+ );
759
+ }
760
+ /* istanbul ignore if */
761
+ if (debug.enabled) {
762
+ debug('Bind %s to constant:', this.key, value);
763
+ }
764
+ this._source = {
765
+ type: BindingType.CONSTANT,
766
+ value,
767
+ };
768
+ this._setValueGetter(resolutionCtx => {
769
+ return Binding.valueOrProxy(resolutionCtx, value);
770
+ });
771
+ return this;
772
+ }
773
+
774
+ /**
775
+ * Bind the key to a computed (dynamic) value.
776
+ *
777
+ * @param factoryFn - The factory function creating the value.
778
+ * Both sync and async functions are supported.
779
+ *
780
+ * @example
781
+ *
782
+ * ```ts
783
+ * // synchronous
784
+ * ctx.bind('now').toDynamicValue(() => Date.now());
785
+ *
786
+ * // asynchronous
787
+ * ctx.bind('something').toDynamicValue(
788
+ * async () => Promise.delay(10).then(doSomething)
789
+ * );
790
+ * ```
791
+ */
792
+ toDynamicValue(
793
+ factory: ValueFactory<T> | DynamicValueProviderClass<T>,
794
+ ): this {
795
+ /* istanbul ignore if */
796
+ if (debug.enabled) {
797
+ debug('Bind %s to dynamic value:', this.key, factory);
798
+ }
799
+ this._source = {
800
+ type: BindingType.DYNAMIC_VALUE,
801
+ value: factory,
802
+ };
803
+
804
+ let factoryFn: ValueFactory<T>;
805
+ if (isDynamicValueProviderClass(factory)) {
806
+ factoryFn = toValueFactory(factory);
807
+ } else {
808
+ factoryFn = factory;
809
+ }
810
+ this._setValueGetter(resolutionCtx => {
811
+ const value = factoryFn(resolutionCtx);
812
+ return Binding.valueOrProxy(resolutionCtx, value);
813
+ });
814
+ return this;
815
+ }
816
+
817
+ private static valueOrProxy<V>(
818
+ resolutionCtx: ResolutionContext,
819
+ value: ValueOrPromise<V>,
820
+ ) {
821
+ if (!resolutionCtx.options.asProxyWithInterceptors) return value;
822
+ return createInterceptionProxyFromInstance(
823
+ value,
824
+ resolutionCtx.context,
825
+ resolutionCtx.options.session,
826
+ );
827
+ }
828
+
829
+ /**
830
+ * Bind the key to a value computed by a Provider.
831
+ *
832
+ * * @example
833
+ *
834
+ * ```ts
835
+ * export class DateProvider implements Provider<Date> {
836
+ * constructor(@inject('stringDate') private param: String){}
837
+ * value(): Date {
838
+ * return new Date(param);
839
+ * }
840
+ * }
841
+ * ```
842
+ *
843
+ * @param provider - The value provider to use.
844
+ */
845
+ toProvider(providerClass: Constructor<Provider<T>>): this {
846
+ /* istanbul ignore if */
847
+ if (debug.enabled) {
848
+ debug('Bind %s to provider %s', this.key, providerClass.name);
849
+ }
850
+ this._source = {
851
+ type: BindingType.PROVIDER,
852
+ value: providerClass,
853
+ };
854
+ this._setValueGetter(resolutionCtx => {
855
+ const providerOrPromise = instantiateClass<Provider<T>>(
856
+ providerClass,
857
+ resolutionCtx.context,
858
+ resolutionCtx.options.session,
859
+ );
860
+ const value = transformValueOrPromise(providerOrPromise, p => p.value());
861
+ return Binding.valueOrProxy(resolutionCtx, value);
862
+ });
863
+ return this;
864
+ }
865
+
866
+ /**
867
+ * Bind the key to an instance of the given class.
868
+ *
869
+ * @param ctor - The class constructor to call. Any constructor
870
+ * arguments must be annotated with `@inject` so that
871
+ * we can resolve them from the context.
872
+ */
873
+ toClass(ctor: Constructor<T>): this {
874
+ /* istanbul ignore if */
875
+ if (debug.enabled) {
876
+ debug('Bind %s to class %s', this.key, ctor.name);
877
+ }
878
+ this._source = {
879
+ type: BindingType.CLASS,
880
+ value: ctor,
881
+ };
882
+ this._setValueGetter(resolutionCtx => {
883
+ const value = instantiateClass(
884
+ ctor,
885
+ resolutionCtx.context,
886
+ resolutionCtx.options.session,
887
+ );
888
+ return Binding.valueOrProxy(resolutionCtx, value);
889
+ });
890
+ return this;
891
+ }
892
+
893
+ /**
894
+ * Bind to a class optionally decorated with `@injectable`. Based on the
895
+ * introspection of the class, it calls `toClass/toProvider/toDynamicValue`
896
+ * internally. The current binding key will be preserved (not being overridden
897
+ * by the key inferred from the class or options).
898
+ *
899
+ * This is similar to {@link createBindingFromClass} but applies to an
900
+ * existing binding.
901
+ *
902
+ * @example
903
+ *
904
+ * ```ts
905
+ * @injectable({scope: BindingScope.SINGLETON, tags: {service: 'MyService}})
906
+ * class MyService {
907
+ * // ...
908
+ * }
909
+ *
910
+ * const ctx = new Context();
911
+ * ctx.bind('services.MyService').toInjectable(MyService);
912
+ * ```
913
+ *
914
+ * @param ctor - A class decorated with `@injectable`.
915
+ */
916
+ toInjectable(
917
+ ctor: DynamicValueProviderClass<T> | Constructor<T | Provider<T>>,
918
+ ) {
919
+ this.apply(bindingTemplateFor(ctor));
920
+ return this;
921
+ }
922
+
923
+ /**
924
+ * Bind the key to an alias of another binding
925
+ * @param keyWithPath - Target binding key with optional path,
926
+ * such as `servers.RestServer.options#apiExplorer`
927
+ */
928
+ toAlias(keyWithPath: BindingAddress<T>) {
929
+ /* istanbul ignore if */
930
+ if (debug.enabled) {
931
+ debug('Bind %s to alias %s', this.key, keyWithPath);
932
+ }
933
+ this._source = {
934
+ type: BindingType.ALIAS,
935
+ value: keyWithPath,
936
+ };
937
+ this._setValueGetter(({context, options}) => {
938
+ return context.getValueOrPromise(keyWithPath, options);
939
+ });
940
+ return this;
941
+ }
942
+
943
+ /**
944
+ * Unlock the binding
945
+ */
946
+ unlock(): this {
947
+ this.isLocked = false;
948
+ return this;
949
+ }
950
+
951
+ /**
952
+ * Apply one or more template functions to set up the binding with scope,
953
+ * tags, and other attributes as a group.
954
+ *
955
+ * @example
956
+ * ```ts
957
+ * const serverTemplate = (binding: Binding) =>
958
+ * binding.inScope(BindingScope.SINGLETON).tag('server');
959
+ *
960
+ * const serverBinding = new Binding<RestServer>('servers.RestServer1');
961
+ * serverBinding.apply(serverTemplate);
962
+ * ```
963
+ * @param templateFns - One or more functions to configure the binding
964
+ */
965
+ apply(...templateFns: BindingTemplate<T>[]): this {
966
+ for (const fn of templateFns) {
967
+ fn(this);
968
+ }
969
+ return this;
970
+ }
971
+
972
+ /**
973
+ * Convert to a plain JSON object
974
+ */
975
+ toJSON(): JSONObject {
976
+ const json: JSONObject = {
977
+ key: this.key,
978
+ scope: this.scope,
979
+ tags: this.tagMap,
980
+ isLocked: this.isLocked,
981
+ };
982
+ if (this.type != null) {
983
+ json.type = this.type;
984
+ }
985
+ switch (this._source?.type) {
986
+ case BindingType.CLASS:
987
+ json.valueConstructor = this._source?.value.name;
988
+ break;
989
+ case BindingType.PROVIDER:
990
+ json.providerConstructor = this._source?.value.name;
991
+ break;
992
+ case BindingType.ALIAS:
993
+ json.alias = this._source?.value.toString();
994
+ break;
995
+ }
996
+ return json;
997
+ }
998
+
999
+ /**
1000
+ * Inspect the binding to return a json representation of the binding information
1001
+ * @param options - Options to control what information should be included
1002
+ */
1003
+ inspect(options: BindingInspectOptions = {}): JSONObject {
1004
+ options = {
1005
+ includeInjections: false,
1006
+ ...options,
1007
+ };
1008
+ const json = this.toJSON();
1009
+ if (options.includeInjections) {
1010
+ const injections = inspectInjections(this);
1011
+ if (Object.keys(injections).length) json.injections = injections;
1012
+ }
1013
+ return json;
1014
+ }
1015
+
1016
+ /**
1017
+ * A static method to create a binding so that we can do
1018
+ * `Binding.bind('foo').to('bar');` as `new Binding('foo').to('bar')` is not
1019
+ * easy to read.
1020
+ * @param key - Binding key
1021
+ */
1022
+ static bind<V = unknown>(key: BindingAddress<V>): Binding<V> {
1023
+ return new Binding(key);
1024
+ }
1025
+
1026
+ /**
1027
+ * Create a configuration binding for the given key
1028
+ *
1029
+ * @example
1030
+ * ```ts
1031
+ * const configBinding = Binding.configure('servers.RestServer.server1')
1032
+ * .to({port: 3000});
1033
+ * ```
1034
+ *
1035
+ * @typeParam V Generic type for the configuration value (not the binding to
1036
+ * be configured)
1037
+ *
1038
+ * @param key - Key for the binding to be configured
1039
+ */
1040
+ static configure<V = unknown>(key: BindingAddress): Binding<V> {
1041
+ return new Binding(BindingKey.buildKeyForConfig<V>(key)).tag({
1042
+ [ContextTags.CONFIGURATION_FOR]: key.toString(),
1043
+ });
1044
+ }
1045
+
1046
+ /**
1047
+ * The "changed" event is emitted by methods such as `tag`, `inScope`, `to`,
1048
+ * and `toClass`.
1049
+ *
1050
+ * @param eventName The name of the event - always `changed`.
1051
+ * @param listener The listener function to call when the event is emitted.
1052
+ */
1053
+ on(eventName: 'changed', listener: BindingEventListener): this;
1054
+
1055
+ // The generic variant inherited from EventEmitter
1056
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1057
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
1058
+
1059
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1060
+ on(event: string | symbol, listener: (...args: any[]) => void): this {
1061
+ return super.on(event, listener);
1062
+ }
1063
+
1064
+ /**
1065
+ * The "changed" event is emitted by methods such as `tag`, `inScope`, `to`,
1066
+ * and `toClass`.
1067
+ *
1068
+ * @param eventName The name of the event - always `changed`.
1069
+ * @param listener The listener function to call when the event is emitted.
1070
+ */
1071
+ once(eventName: 'changed', listener: BindingEventListener): this;
1072
+
1073
+ // The generic variant inherited from EventEmitter
1074
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1075
+ once(event: string | symbol, listener: (...args: any[]) => void): this;
1076
+
1077
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1078
+ once(event: string | symbol, listener: (...args: any[]) => void): this {
1079
+ return super.once(event, listener);
1080
+ }
1081
+ }
1082
+
1083
+ /**
1084
+ * Options for binding.inspect()
1085
+ */
1086
+ export interface BindingInspectOptions {
1087
+ /**
1088
+ * The flag to control if injections should be inspected
1089
+ */
1090
+ includeInjections?: boolean;
1091
+ }
1092
+
1093
+ function createInterceptionProxyFromInstance<T>(
1094
+ instOrPromise: ValueOrPromise<T>,
1095
+ context: Context,
1096
+ session?: ResolutionSession,
1097
+ ) {
1098
+ return transformValueOrPromise(instOrPromise, inst => {
1099
+ if (typeof inst !== 'object' || inst == null) return inst;
1100
+ return createProxyWithInterceptors(
1101
+ // Cast inst from `T` to `object`
1102
+ inst as unknown as object,
1103
+ context,
1104
+ session,
1105
+ ) as unknown as T;
1106
+ });
1107
+ }