@loopback/context 4.0.0-alpha.8 → 4.1.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 (201) 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/{lib/src/provider.js → dist/json-types.js} +3 -3
  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/{lib6/src → dist}/provider.js +2 -2
  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 -34
  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 -103
  116. package/lib/binding.js.map +0 -1
  117. package/lib/context.d.ts +0 -14
  118. package/lib/context.js +0 -97
  119. package/lib/context.js.map +0 -1
  120. package/lib/index.d.ts +0 -1
  121. package/lib/index.js +0 -12
  122. package/lib/index.js.map +0 -1
  123. package/lib/inject.d.ts +0 -46
  124. package/lib/inject.js +0 -74
  125. package/lib/inject.js.map +0 -1
  126. package/lib/isPromise.d.ts +0 -1
  127. package/lib/isPromise.js +0 -15
  128. package/lib/isPromise.js.map +0 -1
  129. package/lib/reflect.d.ts +0 -39
  130. package/lib/reflect.js +0 -20
  131. package/lib/reflect.js.map +0 -1
  132. package/lib/resolver.d.ts +0 -30
  133. package/lib/resolver.js +0 -129
  134. package/lib/resolver.js.map +0 -1
  135. package/lib/src/binding.d.ts +0 -85
  136. package/lib/src/binding.js +0 -123
  137. package/lib/src/binding.js.map +0 -1
  138. package/lib/src/context.d.ts +0 -14
  139. package/lib/src/context.js +0 -97
  140. package/lib/src/context.js.map +0 -1
  141. package/lib/src/index.d.ts +0 -10
  142. package/lib/src/index.js +0 -27
  143. package/lib/src/index.js.map +0 -1
  144. package/lib/src/inject.d.ts +0 -46
  145. package/lib/src/inject.js +0 -74
  146. package/lib/src/inject.js.map +0 -1
  147. package/lib/src/isPromise.d.ts +0 -1
  148. package/lib/src/isPromise.js +0 -15
  149. package/lib/src/isPromise.js.map +0 -1
  150. package/lib/src/provider.d.ts +0 -29
  151. package/lib/src/provider.js.map +0 -1
  152. package/lib/src/reflect.d.ts +0 -38
  153. package/lib/src/reflect.js +0 -143
  154. package/lib/src/reflect.js.map +0 -1
  155. package/lib/src/resolver.d.ts +0 -34
  156. package/lib/src/resolver.js +0 -144
  157. package/lib/src/resolver.js.map +0 -1
  158. package/lib6/binding.d.ts +0 -75
  159. package/lib6/binding.js +0 -103
  160. package/lib6/binding.js.map +0 -1
  161. package/lib6/context.d.ts +0 -14
  162. package/lib6/context.js +0 -97
  163. package/lib6/context.js.map +0 -1
  164. package/lib6/index.d.ts +0 -1
  165. package/lib6/index.js +0 -12
  166. package/lib6/index.js.map +0 -1
  167. package/lib6/inject.d.ts +0 -46
  168. package/lib6/inject.js +0 -74
  169. package/lib6/inject.js.map +0 -1
  170. package/lib6/isPromise.d.ts +0 -1
  171. package/lib6/isPromise.js +0 -15
  172. package/lib6/isPromise.js.map +0 -1
  173. package/lib6/reflect.d.ts +0 -39
  174. package/lib6/reflect.js +0 -20
  175. package/lib6/reflect.js.map +0 -1
  176. package/lib6/resolver.d.ts +0 -30
  177. package/lib6/resolver.js +0 -129
  178. package/lib6/resolver.js.map +0 -1
  179. package/lib6/src/binding.d.ts +0 -85
  180. package/lib6/src/binding.js +0 -133
  181. package/lib6/src/binding.js.map +0 -1
  182. package/lib6/src/context.d.ts +0 -14
  183. package/lib6/src/context.js +0 -97
  184. package/lib6/src/context.js.map +0 -1
  185. package/lib6/src/index.d.ts +0 -10
  186. package/lib6/src/index.js +0 -27
  187. package/lib6/src/index.js.map +0 -1
  188. package/lib6/src/inject.d.ts +0 -46
  189. package/lib6/src/inject.js +0 -74
  190. package/lib6/src/inject.js.map +0 -1
  191. package/lib6/src/isPromise.d.ts +0 -1
  192. package/lib6/src/isPromise.js +0 -15
  193. package/lib6/src/isPromise.js.map +0 -1
  194. package/lib6/src/provider.d.ts +0 -29
  195. package/lib6/src/provider.js.map +0 -1
  196. package/lib6/src/reflect.d.ts +0 -38
  197. package/lib6/src/reflect.js +0 -143
  198. package/lib6/src/reflect.js.map +0 -1
  199. package/lib6/src/resolver.d.ts +0 -34
  200. package/lib6/src/resolver.js +0 -154
  201. package/lib6/src/resolver.js.map +0 -1
@@ -0,0 +1,717 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2017,2020. All Rights Reserved.
3
+ // Node module: @loopback/context
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BindingCreationPolicy = exports.Context = void 0;
8
+ const tslib_1 = require("tslib");
9
+ const debug_1 = (0, tslib_1.__importDefault)(require("debug"));
10
+ const events_1 = require("events");
11
+ const binding_1 = require("./binding");
12
+ const binding_config_1 = require("./binding-config");
13
+ const binding_filter_1 = require("./binding-filter");
14
+ const binding_key_1 = require("./binding-key");
15
+ const context_subscription_1 = require("./context-subscription");
16
+ const context_tag_indexer_1 = require("./context-tag-indexer");
17
+ const context_view_1 = require("./context-view");
18
+ const keys_1 = require("./keys");
19
+ const resolution_session_1 = require("./resolution-session");
20
+ const unique_id_1 = require("./unique-id");
21
+ const value_promise_1 = require("./value-promise");
22
+ /**
23
+ * Context provides an implementation of Inversion of Control (IoC) container
24
+ */
25
+ class Context extends events_1.EventEmitter {
26
+ /**
27
+ * Create a new context.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * // Create a new root context, let the framework to create a unique name
32
+ * const rootCtx = new Context();
33
+ *
34
+ * // Create a new child context inheriting bindings from `rootCtx`
35
+ * const childCtx = new Context(rootCtx);
36
+ *
37
+ * // Create another root context called "application"
38
+ * const appCtx = new Context('application');
39
+ *
40
+ * // Create a new child context called "request" and inheriting bindings
41
+ * // from `appCtx`
42
+ * const reqCtx = new Context(appCtx, 'request');
43
+ * ```
44
+ * @param _parent - The optional parent context
45
+ * @param name - Name of the context. If not provided, a unique identifier
46
+ * will be generated as the name.
47
+ */
48
+ constructor(_parent, name) {
49
+ super();
50
+ /**
51
+ * Key to binding map as the internal registry
52
+ */
53
+ this.registry = new Map();
54
+ /**
55
+ * Scope for binding resolution
56
+ */
57
+ this.scope = binding_1.BindingScope.CONTEXT;
58
+ // The number of listeners can grow with the number of child contexts
59
+ // For example, each request can add a listener to the RestServer and the
60
+ // listener is removed when the request processing is finished.
61
+ // See https://github.com/loopbackio/loopback-next/issues/4363
62
+ this.setMaxListeners(Infinity);
63
+ if (typeof _parent === 'string') {
64
+ name = _parent;
65
+ _parent = undefined;
66
+ }
67
+ this._parent = _parent;
68
+ this.name = name !== null && name !== void 0 ? name : this.generateName();
69
+ this.tagIndexer = new context_tag_indexer_1.ContextTagIndexer(this);
70
+ this.subscriptionManager = new context_subscription_1.ContextSubscriptionManager(this);
71
+ this._debug = (0, debug_1.default)(this.getDebugNamespace());
72
+ }
73
+ /**
74
+ * Get the debug namespace for the context class. Subclasses can override
75
+ * this method to supply its own namespace.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * export class Application extends Context {
80
+ * super('application');
81
+ * }
82
+ *
83
+ * protected getDebugNamespace() {
84
+ * return 'loopback:context:application';
85
+ * }
86
+ * ```
87
+ */
88
+ getDebugNamespace() {
89
+ if (this.constructor === Context)
90
+ return 'loopback:context';
91
+ const name = this.constructor.name.toLowerCase();
92
+ return `loopback:context:${name}`;
93
+ }
94
+ generateName() {
95
+ const id = (0, unique_id_1.generateUniqueId)();
96
+ if (this.constructor === Context)
97
+ return id;
98
+ return `${this.constructor.name}-${id}`;
99
+ }
100
+ /**
101
+ * @internal
102
+ * Getter for ContextSubscriptionManager
103
+ */
104
+ get parent() {
105
+ return this._parent;
106
+ }
107
+ /**
108
+ * Wrap the debug statement so that it always print out the context name
109
+ * as the prefix
110
+ * @param args - Arguments for the debug
111
+ */
112
+ debug(...args) {
113
+ /* istanbul ignore if */
114
+ if (!this._debug.enabled)
115
+ return;
116
+ const formatter = args.shift();
117
+ if (typeof formatter === 'string') {
118
+ this._debug(`[%s] ${formatter}`, this.name, ...args);
119
+ }
120
+ else {
121
+ this._debug('[%s] ', this.name, formatter, ...args);
122
+ }
123
+ }
124
+ /**
125
+ * A strongly-typed method to emit context events
126
+ * @param type Event type
127
+ * @param event Context event
128
+ */
129
+ emitEvent(type, event) {
130
+ this.emit(type, event);
131
+ }
132
+ /**
133
+ * Emit an `error` event
134
+ * @param err Error
135
+ */
136
+ emitError(err) {
137
+ this.emit('error', err);
138
+ }
139
+ /**
140
+ * Create a binding with the given key in the context. If a locked binding
141
+ * already exists with the same key, an error will be thrown.
142
+ *
143
+ * @param key - Binding key
144
+ */
145
+ bind(key) {
146
+ const binding = new binding_1.Binding(key.toString());
147
+ this.add(binding);
148
+ return binding;
149
+ }
150
+ /**
151
+ * Add a binding to the context. If a locked binding already exists with the
152
+ * same key, an error will be thrown.
153
+ * @param binding - The configured binding to be added
154
+ */
155
+ add(binding) {
156
+ const key = binding.key;
157
+ this.debug('[%s] Adding binding: %s', key);
158
+ let existingBinding;
159
+ const keyExists = this.registry.has(key);
160
+ if (keyExists) {
161
+ existingBinding = this.registry.get(key);
162
+ const bindingIsLocked = existingBinding === null || existingBinding === void 0 ? void 0 : existingBinding.isLocked;
163
+ if (bindingIsLocked)
164
+ throw new Error(`Cannot rebind key "${key}" to a locked binding`);
165
+ }
166
+ this.registry.set(key, binding);
167
+ if (existingBinding !== binding) {
168
+ if (existingBinding != null) {
169
+ this.emitEvent('unbind', {
170
+ binding: existingBinding,
171
+ context: this,
172
+ type: 'unbind',
173
+ });
174
+ }
175
+ this.emitEvent('bind', { binding, context: this, type: 'bind' });
176
+ }
177
+ return this;
178
+ }
179
+ /**
180
+ * Create a corresponding binding for configuration of the target bound by
181
+ * the given key in the context.
182
+ *
183
+ * For example, `ctx.configure('controllers.MyController').to({x: 1})` will
184
+ * create binding `controllers.MyController:$config` with value `{x: 1}`.
185
+ *
186
+ * @param key - The key for the binding to be configured
187
+ */
188
+ configure(key = '') {
189
+ const bindingForConfig = binding_1.Binding.configure(key);
190
+ this.add(bindingForConfig);
191
+ return bindingForConfig;
192
+ }
193
+ /**
194
+ * Get the value or promise of configuration for a given binding by key
195
+ *
196
+ * @param key - Binding key
197
+ * @param propertyPath - Property path for the option. For example, `x.y`
198
+ * requests for `<config>.x.y`. If not set, the `<config>` object will be
199
+ * returned.
200
+ * @param resolutionOptions - Options for the resolution.
201
+ * - optional: if not set or set to `true`, `undefined` will be returned if
202
+ * no corresponding value is found. Otherwise, an error will be thrown.
203
+ */
204
+ getConfigAsValueOrPromise(key, propertyPath, resolutionOptions) {
205
+ this.setupConfigurationResolverIfNeeded();
206
+ return this.configResolver.getConfigAsValueOrPromise(key, propertyPath, resolutionOptions);
207
+ }
208
+ /**
209
+ * Set up the configuration resolver if needed
210
+ */
211
+ setupConfigurationResolverIfNeeded() {
212
+ if (!this.configResolver) {
213
+ // First try the bound ConfigurationResolver to this context
214
+ const configResolver = this.getSync(keys_1.ContextBindings.CONFIGURATION_RESOLVER, {
215
+ optional: true,
216
+ });
217
+ if (configResolver) {
218
+ this.debug('Custom ConfigurationResolver is loaded from %s.', keys_1.ContextBindings.CONFIGURATION_RESOLVER.toString());
219
+ this.configResolver = configResolver;
220
+ }
221
+ else {
222
+ // Fallback to DefaultConfigurationResolver
223
+ this.debug('DefaultConfigurationResolver is used.');
224
+ this.configResolver = new binding_config_1.DefaultConfigurationResolver(this);
225
+ }
226
+ }
227
+ return this.configResolver;
228
+ }
229
+ /**
230
+ * Resolve configuration for the binding by key
231
+ *
232
+ * @param key - Binding key
233
+ * @param propertyPath - Property path for the option. For example, `x.y`
234
+ * requests for `<config>.x.y`. If not set, the `<config>` object will be
235
+ * returned.
236
+ * @param resolutionOptions - Options for the resolution.
237
+ */
238
+ async getConfig(key, propertyPath, resolutionOptions) {
239
+ return this.getConfigAsValueOrPromise(key, propertyPath, resolutionOptions);
240
+ }
241
+ /**
242
+ * Resolve configuration synchronously for the binding by key
243
+ *
244
+ * @param key - Binding key
245
+ * @param propertyPath - Property path for the option. For example, `x.y`
246
+ * requests for `config.x.y`. If not set, the `config` object will be
247
+ * returned.
248
+ * @param resolutionOptions - Options for the resolution.
249
+ */
250
+ getConfigSync(key, propertyPath, resolutionOptions) {
251
+ const valueOrPromise = this.getConfigAsValueOrPromise(key, propertyPath, resolutionOptions);
252
+ if ((0, value_promise_1.isPromiseLike)(valueOrPromise)) {
253
+ const prop = propertyPath ? ` property ${propertyPath}` : '';
254
+ throw new Error(`Cannot get config${prop} for ${key} synchronously: the value is a promise`);
255
+ }
256
+ return valueOrPromise;
257
+ }
258
+ /**
259
+ * Unbind a binding from the context. No parent contexts will be checked.
260
+ *
261
+ * @remarks
262
+ * If you need to unbind a binding owned by a parent context, use the code
263
+ * below:
264
+ *
265
+ * ```ts
266
+ * const ownerCtx = ctx.getOwnerContext(key);
267
+ * return ownerCtx != null && ownerCtx.unbind(key);
268
+ * ```
269
+ *
270
+ * @param key - Binding key
271
+ * @returns true if the binding key is found and removed from this context
272
+ */
273
+ unbind(key) {
274
+ this.debug('Unbind %s', key);
275
+ key = binding_key_1.BindingKey.validate(key);
276
+ const binding = this.registry.get(key);
277
+ // If not found, return `false`
278
+ if (binding == null)
279
+ return false;
280
+ if (binding === null || binding === void 0 ? void 0 : binding.isLocked)
281
+ throw new Error(`Cannot unbind key "${key}" of a locked binding`);
282
+ this.registry.delete(key);
283
+ this.emitEvent('unbind', { binding, context: this, type: 'unbind' });
284
+ return true;
285
+ }
286
+ /**
287
+ * Add a context event observer to the context
288
+ * @param observer - Context observer instance or function
289
+ */
290
+ subscribe(observer) {
291
+ return this.subscriptionManager.subscribe(observer);
292
+ }
293
+ /**
294
+ * Remove the context event observer from the context
295
+ * @param observer - Context event observer
296
+ */
297
+ unsubscribe(observer) {
298
+ return this.subscriptionManager.unsubscribe(observer);
299
+ }
300
+ /**
301
+ * Close the context: clear observers, stop notifications, and remove event
302
+ * listeners from its parent context.
303
+ *
304
+ * @remarks
305
+ * This method MUST be called to avoid memory leaks once a context object is
306
+ * no longer needed and should be recycled. An example is the `RequestContext`,
307
+ * which is created per request.
308
+ */
309
+ close() {
310
+ this.debug('Closing context...');
311
+ this.subscriptionManager.close();
312
+ this.tagIndexer.close();
313
+ }
314
+ /**
315
+ * Check if an observer is subscribed to this context
316
+ * @param observer - Context observer
317
+ */
318
+ isSubscribed(observer) {
319
+ return this.subscriptionManager.isSubscribed(observer);
320
+ }
321
+ /**
322
+ * Create a view of the context chain with the given binding filter
323
+ * @param filter - A function to match bindings
324
+ * @param comparator - A function to sort matched bindings
325
+ * @param options - Resolution options
326
+ */
327
+ createView(filter, comparator, options) {
328
+ const view = new context_view_1.ContextView(this, filter, comparator, options);
329
+ view.open();
330
+ return view;
331
+ }
332
+ /**
333
+ * Check if a binding exists with the given key in the local context without
334
+ * delegating to the parent context
335
+ * @param key - Binding key
336
+ */
337
+ contains(key) {
338
+ key = binding_key_1.BindingKey.validate(key);
339
+ return this.registry.has(key);
340
+ }
341
+ /**
342
+ * Check if a key is bound in the context or its ancestors
343
+ * @param key - Binding key
344
+ */
345
+ isBound(key) {
346
+ if (this.contains(key))
347
+ return true;
348
+ if (this._parent) {
349
+ return this._parent.isBound(key);
350
+ }
351
+ return false;
352
+ }
353
+ /**
354
+ * Get the owning context for a binding or its key
355
+ * @param keyOrBinding - Binding object or key
356
+ */
357
+ getOwnerContext(keyOrBinding) {
358
+ let key;
359
+ if (keyOrBinding instanceof binding_1.Binding) {
360
+ key = keyOrBinding.key;
361
+ }
362
+ else {
363
+ key = keyOrBinding;
364
+ }
365
+ if (this.contains(key)) {
366
+ if (keyOrBinding instanceof binding_1.Binding) {
367
+ // Check if the contained binding is the same
368
+ if (this.registry.get(key.toString()) === keyOrBinding) {
369
+ return this;
370
+ }
371
+ return undefined;
372
+ }
373
+ return this;
374
+ }
375
+ if (this._parent) {
376
+ return this._parent.getOwnerContext(key);
377
+ }
378
+ return undefined;
379
+ }
380
+ /**
381
+ * Get the context matching the scope
382
+ * @param scope - Binding scope
383
+ */
384
+ getScopedContext(scope) {
385
+ if (this.scope === scope)
386
+ return this;
387
+ if (this._parent) {
388
+ return this._parent.getScopedContext(scope);
389
+ }
390
+ return undefined;
391
+ }
392
+ /**
393
+ * Locate the resolution context for the given binding. Only bindings in the
394
+ * resolution context and its ancestors are visible as dependencies to resolve
395
+ * the given binding
396
+ * @param binding - Binding object
397
+ */
398
+ getResolutionContext(binding) {
399
+ let resolutionCtx;
400
+ switch (binding.scope) {
401
+ case binding_1.BindingScope.SINGLETON:
402
+ // Use the owner context
403
+ return this.getOwnerContext(binding.key);
404
+ case binding_1.BindingScope.TRANSIENT:
405
+ case binding_1.BindingScope.CONTEXT:
406
+ // Use the current context
407
+ return this;
408
+ case binding_1.BindingScope.REQUEST:
409
+ resolutionCtx = this.getScopedContext(binding.scope);
410
+ if (resolutionCtx != null) {
411
+ return resolutionCtx;
412
+ }
413
+ else {
414
+ // If no `REQUEST` scope exists in the chain, fall back to the current
415
+ // context
416
+ this.debug('No context is found for binding "%s (scope=%s)". Fall back to the current context.', binding.key, binding.scope);
417
+ return this;
418
+ }
419
+ default:
420
+ // Use the scoped context
421
+ return this.getScopedContext(binding.scope);
422
+ }
423
+ }
424
+ /**
425
+ * Check if this context is visible (same or ancestor) to the given one
426
+ * @param ctx - Another context object
427
+ */
428
+ isVisibleTo(ctx) {
429
+ let current = ctx;
430
+ while (current != null) {
431
+ if (current === this)
432
+ return true;
433
+ current = current._parent;
434
+ }
435
+ return false;
436
+ }
437
+ /**
438
+ * Find bindings using a key pattern or filter function
439
+ * @param pattern - A filter function, a regexp or a wildcard pattern with
440
+ * optional `*` and `?`. Find returns such bindings where the key matches
441
+ * the provided pattern.
442
+ *
443
+ * For a wildcard:
444
+ * - `*` matches zero or more characters except `.` and `:`
445
+ * - `?` matches exactly one character except `.` and `:`
446
+ *
447
+ * For a filter function:
448
+ * - return `true` to include the binding in the results
449
+ * - return `false` to exclude it.
450
+ */
451
+ find(pattern) {
452
+ var _a;
453
+ // Optimize if the binding filter is for tags
454
+ if (typeof pattern === 'function' && (0, binding_filter_1.isBindingTagFilter)(pattern)) {
455
+ return this._findByTagIndex(pattern.bindingTagPattern);
456
+ }
457
+ const bindings = [];
458
+ const filter = (0, binding_filter_1.filterByKey)(pattern);
459
+ for (const b of this.registry.values()) {
460
+ if (filter(b))
461
+ bindings.push(b);
462
+ }
463
+ const parentBindings = (_a = this._parent) === null || _a === void 0 ? void 0 : _a.find(filter);
464
+ return this._mergeWithParent(bindings, parentBindings);
465
+ }
466
+ /**
467
+ * Find bindings using the tag filter. If the filter matches one of the
468
+ * binding tags, the binding is included.
469
+ *
470
+ * @param tagFilter - A filter for tags. It can be in one of the following
471
+ * forms:
472
+ * - A regular expression, such as `/controller/`
473
+ * - A wildcard pattern string with optional `*` and `?`, such as `'con*'`
474
+ * For a wildcard:
475
+ * - `*` matches zero or more characters except `.` and `:`
476
+ * - `?` matches exactly one character except `.` and `:`
477
+ * - An object containing tag name/value pairs, such as
478
+ * `{name: 'my-controller'}`
479
+ */
480
+ findByTag(tagFilter) {
481
+ return this.find((0, binding_filter_1.filterByTag)(tagFilter));
482
+ }
483
+ /**
484
+ * Find bindings by tag leveraging indexes
485
+ * @param tag - Tag name pattern or name/value pairs
486
+ */
487
+ _findByTagIndex(tag) {
488
+ var _a;
489
+ const currentBindings = this.tagIndexer.findByTagIndex(tag);
490
+ const parentBindings = (_a = this._parent) === null || _a === void 0 ? void 0 : _a._findByTagIndex(tag);
491
+ return this._mergeWithParent(currentBindings, parentBindings);
492
+ }
493
+ _mergeWithParent(childList, parentList) {
494
+ if (!parentList)
495
+ return childList;
496
+ const additions = parentList.filter(parentBinding => {
497
+ // children bindings take precedence
498
+ return !childList.some(childBinding => childBinding.key === parentBinding.key);
499
+ });
500
+ return childList.concat(additions);
501
+ }
502
+ // Implementation
503
+ async get(keyWithPath, optionsOrSession) {
504
+ this.debug('Resolving binding: %s', keyWithPath);
505
+ return this.getValueOrPromise(keyWithPath, optionsOrSession);
506
+ }
507
+ // Implementation
508
+ getSync(keyWithPath, optionsOrSession) {
509
+ this.debug('Resolving binding synchronously: %s', keyWithPath);
510
+ const valueOrPromise = this.getValueOrPromise(keyWithPath, optionsOrSession);
511
+ if ((0, value_promise_1.isPromiseLike)(valueOrPromise)) {
512
+ throw new Error(`Cannot get ${keyWithPath} synchronously: the value is a promise`);
513
+ }
514
+ return valueOrPromise;
515
+ }
516
+ getBinding(key, options) {
517
+ key = binding_key_1.BindingKey.validate(key);
518
+ const binding = this.registry.get(key);
519
+ if (binding) {
520
+ return binding;
521
+ }
522
+ if (this._parent) {
523
+ return this._parent.getBinding(key, options);
524
+ }
525
+ if (options === null || options === void 0 ? void 0 : options.optional)
526
+ return undefined;
527
+ throw new Error(`The key '${key}' is not bound to any value in context ${this.name}`);
528
+ }
529
+ /**
530
+ * Find or create a binding for the given key
531
+ * @param key - Binding address
532
+ * @param policy - Binding creation policy
533
+ */
534
+ findOrCreateBinding(key, policy) {
535
+ let binding;
536
+ if (policy === BindingCreationPolicy.ALWAYS_CREATE) {
537
+ binding = this.bind(key);
538
+ }
539
+ else if (policy === BindingCreationPolicy.NEVER_CREATE) {
540
+ binding = this.getBinding(key);
541
+ }
542
+ else if (this.isBound(key)) {
543
+ // CREATE_IF_NOT_BOUND - the key is bound
544
+ binding = this.getBinding(key);
545
+ }
546
+ else {
547
+ // CREATE_IF_NOT_BOUND - the key is not bound
548
+ binding = this.bind(key);
549
+ }
550
+ return binding;
551
+ }
552
+ /**
553
+ * Get the value bound to the given key.
554
+ *
555
+ * This is an internal version that preserves the dual sync/async result
556
+ * of `Binding#getValue()`. Users should use `get()` or `getSync()` instead.
557
+ *
558
+ * @example
559
+ *
560
+ * ```ts
561
+ * // get the value bound to "application.instance"
562
+ * ctx.getValueOrPromise<Application>('application.instance');
563
+ *
564
+ * // get "rest" property from the value bound to "config"
565
+ * ctx.getValueOrPromise<RestComponentConfig>('config#rest');
566
+ *
567
+ * // get "a" property of "numbers" property from the value bound to "data"
568
+ * ctx.bind('data').to({numbers: {a: 1, b: 2}, port: 3000});
569
+ * ctx.getValueOrPromise<number>('data#numbers.a');
570
+ * ```
571
+ *
572
+ * @param keyWithPath - The binding key, optionally suffixed with a path to the
573
+ * (deeply) nested property to retrieve.
574
+ * @param optionsOrSession - Options for resolution or a session
575
+ * @returns The bound value or a promise of the bound value, depending
576
+ * on how the binding is configured.
577
+ * @internal
578
+ */
579
+ getValueOrPromise(keyWithPath, optionsOrSession) {
580
+ const { key, propertyPath } = binding_key_1.BindingKey.parseKeyWithPath(keyWithPath);
581
+ const options = (0, resolution_session_1.asResolutionOptions)(optionsOrSession);
582
+ const binding = this.getBinding(key, { optional: true });
583
+ if (binding == null) {
584
+ if (options.optional)
585
+ return undefined;
586
+ throw new resolution_session_1.ResolutionError(`The key '${key}' is not bound to any value in context ${this.name}`, {
587
+ context: this,
588
+ binding: binding_1.Binding.bind(key),
589
+ options,
590
+ });
591
+ }
592
+ const boundValue = binding.getValue(this, options);
593
+ return propertyPath == null || propertyPath === ''
594
+ ? boundValue
595
+ : (0, value_promise_1.transformValueOrPromise)(boundValue, v => (0, value_promise_1.getDeepProperty)(v, propertyPath));
596
+ }
597
+ /**
598
+ * Create a plain JSON object for the context
599
+ */
600
+ toJSON() {
601
+ const bindings = {};
602
+ for (const [k, v] of this.registry) {
603
+ bindings[k] = v.toJSON();
604
+ }
605
+ return bindings;
606
+ }
607
+ /**
608
+ * Inspect the context and dump out a JSON object representing the context
609
+ * hierarchy
610
+ * @param options - Options for inspect
611
+ */
612
+ // TODO(rfeng): Evaluate https://nodejs.org/api/util.html#util_custom_inspection_functions_on_objects
613
+ inspect(options = {}) {
614
+ return this._inspect(options, new ClassNameMap());
615
+ }
616
+ /**
617
+ * Inspect the context hierarchy
618
+ * @param options - Options for inspect
619
+ * @param visitedClasses - A map to keep class to name so that we can have
620
+ * different names for classes with colliding names. The situation can happen
621
+ * when two classes with the same name are bound in different modules.
622
+ */
623
+ _inspect(options, visitedClasses) {
624
+ var _a;
625
+ options = {
626
+ includeParent: true,
627
+ includeInjections: false,
628
+ ...options,
629
+ };
630
+ const bindings = {};
631
+ for (const [k, v] of this.registry) {
632
+ const ctor = (_a = v.valueConstructor) !== null && _a !== void 0 ? _a : v.providerConstructor;
633
+ let name = undefined;
634
+ if (ctor != null) {
635
+ name = visitedClasses.visit(ctor);
636
+ }
637
+ bindings[k] = v.inspect(options);
638
+ if (name != null) {
639
+ const binding = bindings[k];
640
+ if (v.valueConstructor) {
641
+ binding.valueConstructor = name;
642
+ }
643
+ else if (v.providerConstructor) {
644
+ binding.providerConstructor = name;
645
+ }
646
+ }
647
+ }
648
+ const json = {
649
+ name: this.name,
650
+ bindings,
651
+ };
652
+ if (!options.includeParent)
653
+ return json;
654
+ if (this._parent) {
655
+ json.parent = this._parent._inspect(options, visitedClasses);
656
+ }
657
+ return json;
658
+ }
659
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
660
+ on(event, listener) {
661
+ return super.on(event, listener);
662
+ }
663
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
664
+ once(event, listener) {
665
+ return super.once(event, listener);
666
+ }
667
+ }
668
+ exports.Context = Context;
669
+ /**
670
+ * An internal utility class to handle class name conflicts
671
+ */
672
+ class ClassNameMap {
673
+ constructor() {
674
+ this.classes = new Map();
675
+ this.nameIndex = new Map();
676
+ }
677
+ visit(ctor) {
678
+ let name = this.classes.get(ctor);
679
+ if (name == null) {
680
+ name = ctor.name;
681
+ // Now check if the name collides with another class
682
+ let index = this.nameIndex.get(name);
683
+ if (typeof index === 'number') {
684
+ // A conflict is found, mangle the name as `ClassName #1`
685
+ this.nameIndex.set(name, ++index);
686
+ name = `${name} #${index}`;
687
+ }
688
+ else {
689
+ // The name is used for the 1st time
690
+ this.nameIndex.set(name, 0);
691
+ }
692
+ this.classes.set(ctor, name);
693
+ }
694
+ return name;
695
+ }
696
+ }
697
+ /**
698
+ * Policy to control if a binding should be created for the context
699
+ */
700
+ var BindingCreationPolicy;
701
+ (function (BindingCreationPolicy) {
702
+ /**
703
+ * Always create a binding with the key for the context
704
+ */
705
+ BindingCreationPolicy["ALWAYS_CREATE"] = "Always";
706
+ /**
707
+ * Never create a binding for the context. If the key is not bound in the
708
+ * context, throw an error.
709
+ */
710
+ BindingCreationPolicy["NEVER_CREATE"] = "Never";
711
+ /**
712
+ * Create a binding if the key is not bound in the context. Otherwise, return
713
+ * the existing binding.
714
+ */
715
+ BindingCreationPolicy["CREATE_IF_NOT_BOUND"] = "IfNotBound";
716
+ })(BindingCreationPolicy = exports.BindingCreationPolicy || (exports.BindingCreationPolicy = {}));
717
+ //# sourceMappingURL=context.js.map