@loopback/context 4.0.0-alpha.7 → 4.0.1
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/LICENSE +25 -0
- package/README.md +116 -0
- package/dist/binding-config.d.ts +40 -0
- package/dist/binding-config.js +33 -0
- package/dist/binding-config.js.map +1 -0
- package/dist/binding-decorator.d.ts +45 -0
- package/dist/binding-decorator.js +118 -0
- package/dist/binding-decorator.js.map +1 -0
- package/dist/binding-filter.d.ts +108 -0
- package/dist/binding-filter.js +162 -0
- package/dist/binding-filter.js.map +1 -0
- package/dist/binding-inspector.d.ts +150 -0
- package/dist/binding-inspector.js +249 -0
- package/dist/binding-inspector.js.map +1 -0
- package/dist/binding-key.d.ts +66 -0
- package/dist/binding-key.js +121 -0
- package/dist/binding-key.js.map +1 -0
- package/dist/binding-sorter.d.ts +71 -0
- package/dist/binding-sorter.js +89 -0
- package/dist/binding-sorter.js.map +1 -0
- package/dist/binding.d.ts +577 -0
- package/dist/binding.js +788 -0
- package/dist/binding.js.map +1 -0
- package/dist/context-event.d.ts +23 -0
- package/dist/context-event.js +7 -0
- package/dist/context-event.js.map +1 -0
- package/dist/context-observer.d.ts +36 -0
- package/dist/context-observer.js +7 -0
- package/dist/context-observer.js.map +1 -0
- package/dist/context-subscription.d.ts +147 -0
- package/dist/context-subscription.js +317 -0
- package/dist/context-subscription.js.map +1 -0
- package/dist/context-tag-indexer.d.ts +42 -0
- package/dist/context-tag-indexer.js +135 -0
- package/dist/context-tag-indexer.js.map +1 -0
- package/dist/context-view.d.ts +209 -0
- package/dist/context-view.js +240 -0
- package/dist/context-view.js.map +1 -0
- package/dist/context.d.ts +513 -0
- package/dist/context.js +717 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/inject-config.d.ts +67 -0
- package/dist/inject-config.js +181 -0
- package/dist/inject-config.js.map +1 -0
- package/dist/inject.d.ts +250 -0
- package/dist/inject.js +535 -0
- package/dist/inject.js.map +1 -0
- package/dist/interception-proxy.d.ts +76 -0
- package/dist/interception-proxy.js +67 -0
- package/dist/interception-proxy.js.map +1 -0
- package/dist/interceptor-chain.d.ts +121 -0
- package/dist/interceptor-chain.js +148 -0
- package/dist/interceptor-chain.js.map +1 -0
- package/dist/interceptor.d.ts +138 -0
- package/dist/interceptor.js +299 -0
- package/dist/interceptor.js.map +1 -0
- package/dist/invocation.d.ts +101 -0
- package/dist/invocation.js +163 -0
- package/dist/invocation.js.map +1 -0
- package/dist/json-types.d.ts +28 -0
- package/dist/json-types.js +7 -0
- package/dist/json-types.js.map +1 -0
- package/dist/keys.d.ts +65 -0
- package/dist/keys.js +74 -0
- package/dist/keys.js.map +1 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.js +7 -0
- package/dist/provider.js.map +1 -0
- package/dist/resolution-session.d.ts +180 -0
- package/dist/resolution-session.js +274 -0
- package/dist/resolution-session.js.map +1 -0
- package/dist/resolver.d.ts +46 -0
- package/dist/resolver.js +203 -0
- package/dist/resolver.js.map +1 -0
- package/dist/unique-id.d.ts +14 -0
- package/dist/unique-id.js +26 -0
- package/dist/unique-id.js.map +1 -0
- package/dist/value-promise.d.ts +134 -0
- package/dist/value-promise.js +277 -0
- package/dist/value-promise.js.map +1 -0
- package/package.json +49 -35
- package/src/binding-config.ts +73 -0
- package/src/binding-decorator.ts +136 -0
- package/src/binding-filter.ts +250 -0
- package/src/binding-inspector.ts +371 -0
- package/src/binding-key.ts +136 -0
- package/src/binding-sorter.ts +124 -0
- package/src/binding.ts +1107 -0
- package/src/context-event.ts +30 -0
- package/src/context-observer.ts +50 -0
- package/src/context-subscription.ts +402 -0
- package/src/context-tag-indexer.ts +147 -0
- package/src/context-view.ts +440 -0
- package/src/context.ts +1079 -0
- package/src/index.ts +58 -0
- package/src/inject-config.ts +239 -0
- package/src/inject.ts +796 -0
- package/src/interception-proxy.ts +127 -0
- package/src/interceptor-chain.ts +268 -0
- package/src/interceptor.ts +430 -0
- package/src/invocation.ts +269 -0
- package/src/json-types.ts +35 -0
- package/src/keys.ts +85 -0
- package/src/provider.ts +37 -0
- package/src/resolution-session.ts +414 -0
- package/src/resolver.ts +282 -0
- package/src/unique-id.ts +24 -0
- package/src/value-promise.ts +318 -0
- package/index.d.ts +0 -6
- package/index.js +0 -9
- package/lib/binding.d.ts +0 -75
- package/lib/binding.js +0 -102
- package/lib/context.d.ts +0 -14
- package/lib/context.js +0 -96
- package/lib/index.d.ts +0 -5
- package/lib/index.js +0 -13
- package/lib/inject.d.ts +0 -47
- package/lib/inject.js +0 -73
- package/lib/isPromise.d.ts +0 -1
- package/lib/isPromise.js +0 -14
- package/lib/resolver.d.ts +0 -30
- package/lib/resolver.js +0 -128
- package/lib6/binding.d.ts +0 -75
- package/lib6/binding.js +0 -102
- package/lib6/context.d.ts +0 -14
- package/lib6/context.js +0 -96
- package/lib6/index.d.ts +0 -5
- package/lib6/index.js +0 -13
- package/lib6/inject.d.ts +0 -47
- package/lib6/inject.js +0 -73
- package/lib6/isPromise.d.ts +0 -1
- package/lib6/isPromise.js +0 -14
- package/lib6/resolver.d.ts +0 -30
- package/lib6/resolver.js +0 -128
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
|
+
}
|