@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.
- 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 -36
- 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 -24
- package/lib/inject.js +0 -43
- package/lib/isPromise.d.ts +0 -1
- package/lib/isPromise.js +0 -14
- package/lib/resolver.d.ts +0 -26
- package/lib/resolver.js +0 -72
- 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 -24
- package/lib6/inject.js +0 -43
- package/lib6/isPromise.d.ts +0 -1
- package/lib6/isPromise.js +0 -14
- package/lib6/resolver.d.ts +0 -26
- package/lib6/resolver.js +0 -72
package/src/inject.ts
ADDED
|
@@ -0,0 +1,796 @@
|
|
|
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 {
|
|
7
|
+
DecoratorFactory,
|
|
8
|
+
InspectionOptions,
|
|
9
|
+
MetadataAccessor,
|
|
10
|
+
MetadataInspector,
|
|
11
|
+
MetadataMap,
|
|
12
|
+
ParameterDecoratorFactory,
|
|
13
|
+
PropertyDecoratorFactory,
|
|
14
|
+
Reflector,
|
|
15
|
+
} from '@loopback/metadata';
|
|
16
|
+
import {Binding, BindingTag} from './binding';
|
|
17
|
+
import {
|
|
18
|
+
BindingFilter,
|
|
19
|
+
BindingSelector,
|
|
20
|
+
filterByTag,
|
|
21
|
+
isBindingAddress,
|
|
22
|
+
isBindingTagFilter,
|
|
23
|
+
} from './binding-filter';
|
|
24
|
+
import {BindingAddress, BindingKey} from './binding-key';
|
|
25
|
+
import {BindingComparator} from './binding-sorter';
|
|
26
|
+
import {BindingCreationPolicy, Context} from './context';
|
|
27
|
+
import {ContextView, createViewGetter} from './context-view';
|
|
28
|
+
import {JSONObject} from './json-types';
|
|
29
|
+
import {ResolutionOptions, ResolutionSession} from './resolution-session';
|
|
30
|
+
import {BoundValue, Constructor, ValueOrPromise} from './value-promise';
|
|
31
|
+
|
|
32
|
+
const INJECT_PARAMETERS_KEY = MetadataAccessor.create<
|
|
33
|
+
Injection,
|
|
34
|
+
ParameterDecorator
|
|
35
|
+
>('inject:parameters');
|
|
36
|
+
|
|
37
|
+
const INJECT_PROPERTIES_KEY = MetadataAccessor.create<
|
|
38
|
+
Injection,
|
|
39
|
+
PropertyDecorator
|
|
40
|
+
>('inject:properties');
|
|
41
|
+
|
|
42
|
+
// A key to cache described argument injections
|
|
43
|
+
const INJECT_METHODS_KEY = MetadataAccessor.create<Injection, MethodDecorator>(
|
|
44
|
+
'inject:methods',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// TODO(rfeng): We may want to align it with `ValueFactory` interface that takes
|
|
48
|
+
// an argument of `ResolutionContext`.
|
|
49
|
+
/**
|
|
50
|
+
* A function to provide resolution of injected values.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* const resolver: ResolverFunction = (ctx, injection, session) {
|
|
55
|
+
* return session.currentBinding?.key;
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export interface ResolverFunction {
|
|
60
|
+
(
|
|
61
|
+
ctx: Context,
|
|
62
|
+
injection: Readonly<Injection>,
|
|
63
|
+
session: ResolutionSession,
|
|
64
|
+
): ValueOrPromise<BoundValue>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* An object to provide metadata for `@inject`
|
|
69
|
+
*/
|
|
70
|
+
export interface InjectionMetadata extends Omit<ResolutionOptions, 'session'> {
|
|
71
|
+
/**
|
|
72
|
+
* Name of the decorator function, such as `@inject` or `@inject.setter`.
|
|
73
|
+
* It's usually set by the decorator implementation.
|
|
74
|
+
*/
|
|
75
|
+
decorator?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Optional comparator for matched bindings
|
|
78
|
+
*/
|
|
79
|
+
bindingComparator?: BindingComparator;
|
|
80
|
+
/**
|
|
81
|
+
* Other attributes
|
|
82
|
+
*/
|
|
83
|
+
[attribute: string]: BoundValue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Descriptor for an injection point
|
|
88
|
+
*/
|
|
89
|
+
export interface Injection<ValueType = BoundValue> {
|
|
90
|
+
target: Object;
|
|
91
|
+
member?: string;
|
|
92
|
+
methodDescriptorOrParameterIndex?:
|
|
93
|
+
| TypedPropertyDescriptor<ValueType>
|
|
94
|
+
| number;
|
|
95
|
+
|
|
96
|
+
bindingSelector: BindingSelector<ValueType>; // Binding selector
|
|
97
|
+
metadata: InjectionMetadata; // Related metadata
|
|
98
|
+
resolve?: ResolverFunction; // A custom resolve function
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A decorator to annotate method arguments for automatic injection
|
|
103
|
+
* by LoopBack IoC container.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* Usage - Typescript:
|
|
107
|
+
*
|
|
108
|
+
* ```ts
|
|
109
|
+
* class InfoController {
|
|
110
|
+
* @inject('authentication.user') public userName: string;
|
|
111
|
+
*
|
|
112
|
+
* constructor(@inject('application.name') public appName: string) {
|
|
113
|
+
* }
|
|
114
|
+
* // ...
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* Usage - JavaScript:
|
|
119
|
+
*
|
|
120
|
+
* - TODO(bajtos)
|
|
121
|
+
*
|
|
122
|
+
* @param bindingSelector - What binding to use in order to resolve the value of the
|
|
123
|
+
* decorated constructor parameter or property.
|
|
124
|
+
* @param metadata - Optional metadata to help the injection
|
|
125
|
+
* @param resolve - Optional function to resolve the injection
|
|
126
|
+
*
|
|
127
|
+
*/
|
|
128
|
+
export function inject(
|
|
129
|
+
bindingSelector: BindingSelector,
|
|
130
|
+
metadata?: InjectionMetadata,
|
|
131
|
+
resolve?: ResolverFunction,
|
|
132
|
+
) {
|
|
133
|
+
if (typeof bindingSelector === 'function' && !resolve) {
|
|
134
|
+
resolve = resolveValuesByFilter;
|
|
135
|
+
}
|
|
136
|
+
const injectionMetadata = Object.assign({decorator: '@inject'}, metadata);
|
|
137
|
+
if (injectionMetadata.bindingComparator && !resolve) {
|
|
138
|
+
throw new Error('Binding comparator is only allowed with a binding filter');
|
|
139
|
+
}
|
|
140
|
+
if (!bindingSelector && typeof resolve !== 'function') {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'A non-empty binding selector or resolve function is required for @inject',
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return function markParameterOrPropertyAsInjected(
|
|
146
|
+
target: Object,
|
|
147
|
+
member: string | undefined,
|
|
148
|
+
methodDescriptorOrParameterIndex?:
|
|
149
|
+
| TypedPropertyDescriptor<BoundValue>
|
|
150
|
+
| number,
|
|
151
|
+
) {
|
|
152
|
+
if (typeof methodDescriptorOrParameterIndex === 'number') {
|
|
153
|
+
// The decorator is applied to a method parameter
|
|
154
|
+
// Please note propertyKey is `undefined` for constructor
|
|
155
|
+
const paramDecorator: ParameterDecorator =
|
|
156
|
+
ParameterDecoratorFactory.createDecorator<Injection>(
|
|
157
|
+
INJECT_PARAMETERS_KEY,
|
|
158
|
+
{
|
|
159
|
+
target,
|
|
160
|
+
member,
|
|
161
|
+
methodDescriptorOrParameterIndex,
|
|
162
|
+
bindingSelector,
|
|
163
|
+
metadata: injectionMetadata,
|
|
164
|
+
resolve,
|
|
165
|
+
},
|
|
166
|
+
// Do not deep clone the spec as only metadata is mutable and it's
|
|
167
|
+
// shallowly cloned
|
|
168
|
+
{cloneInputSpec: false, decoratorName: injectionMetadata.decorator},
|
|
169
|
+
);
|
|
170
|
+
paramDecorator(target, member!, methodDescriptorOrParameterIndex);
|
|
171
|
+
} else if (member) {
|
|
172
|
+
// Property or method
|
|
173
|
+
if (target instanceof Function) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
'@inject is not supported for a static property: ' +
|
|
176
|
+
DecoratorFactory.getTargetName(target, member),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (methodDescriptorOrParameterIndex) {
|
|
180
|
+
// Method
|
|
181
|
+
throw new Error(
|
|
182
|
+
'@inject cannot be used on a method: ' +
|
|
183
|
+
DecoratorFactory.getTargetName(
|
|
184
|
+
target,
|
|
185
|
+
member,
|
|
186
|
+
methodDescriptorOrParameterIndex,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const propDecorator: PropertyDecorator =
|
|
191
|
+
PropertyDecoratorFactory.createDecorator<Injection>(
|
|
192
|
+
INJECT_PROPERTIES_KEY,
|
|
193
|
+
{
|
|
194
|
+
target,
|
|
195
|
+
member,
|
|
196
|
+
methodDescriptorOrParameterIndex,
|
|
197
|
+
bindingSelector,
|
|
198
|
+
metadata: injectionMetadata,
|
|
199
|
+
resolve,
|
|
200
|
+
},
|
|
201
|
+
// Do not deep clone the spec as only metadata is mutable and it's
|
|
202
|
+
// shallowly cloned
|
|
203
|
+
{cloneInputSpec: false, decoratorName: injectionMetadata.decorator},
|
|
204
|
+
);
|
|
205
|
+
propDecorator(target, member!);
|
|
206
|
+
} else {
|
|
207
|
+
// It won't happen here as `@inject` is not compatible with ClassDecorator
|
|
208
|
+
/* istanbul ignore next */
|
|
209
|
+
throw new Error(
|
|
210
|
+
'@inject can only be used on a property or a method parameter',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The function injected by `@inject.getter(bindingSelector)`. It can be used
|
|
218
|
+
* to fetch bound value(s) from the underlying binding(s). The return value will
|
|
219
|
+
* be an array if the `bindingSelector` is a `BindingFilter` function.
|
|
220
|
+
*/
|
|
221
|
+
export type Getter<T> = () => Promise<T>;
|
|
222
|
+
|
|
223
|
+
export namespace Getter {
|
|
224
|
+
/**
|
|
225
|
+
* Convert a value into a Getter returning that value.
|
|
226
|
+
* @param value
|
|
227
|
+
*/
|
|
228
|
+
export function fromValue<T>(value: T): Getter<T> {
|
|
229
|
+
return () => Promise.resolve(value);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The function injected by `@inject.setter(bindingKey)`. It sets the underlying
|
|
235
|
+
* binding to a constant value using `binding.to(value)`.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
*
|
|
239
|
+
* ```ts
|
|
240
|
+
* setterFn('my-value');
|
|
241
|
+
* ```
|
|
242
|
+
* @param value - The value for the underlying binding
|
|
243
|
+
*/
|
|
244
|
+
export type Setter<T> = (value: T) => void;
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Metadata for `@inject.binding`
|
|
248
|
+
*/
|
|
249
|
+
export interface InjectBindingMetadata extends InjectionMetadata {
|
|
250
|
+
/**
|
|
251
|
+
* Controls how the underlying binding is resolved/created
|
|
252
|
+
*/
|
|
253
|
+
bindingCreation?: BindingCreationPolicy;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export namespace inject {
|
|
257
|
+
/**
|
|
258
|
+
* Inject a function for getting the actual bound value.
|
|
259
|
+
*
|
|
260
|
+
* This is useful when implementing Actions, where
|
|
261
|
+
* the action is instantiated for Sequence constructor, but some
|
|
262
|
+
* of action's dependencies become bound only after other actions
|
|
263
|
+
* have been executed by the sequence.
|
|
264
|
+
*
|
|
265
|
+
* See also `Getter<T>`.
|
|
266
|
+
*
|
|
267
|
+
* @param bindingSelector - The binding key or filter we want to eventually get
|
|
268
|
+
* value(s) from.
|
|
269
|
+
* @param metadata - Optional metadata to help the injection
|
|
270
|
+
*/
|
|
271
|
+
export const getter = function injectGetter(
|
|
272
|
+
bindingSelector: BindingSelector<unknown>,
|
|
273
|
+
metadata?: InjectionMetadata,
|
|
274
|
+
) {
|
|
275
|
+
metadata = Object.assign({decorator: '@inject.getter'}, metadata);
|
|
276
|
+
return inject(
|
|
277
|
+
bindingSelector,
|
|
278
|
+
metadata,
|
|
279
|
+
isBindingAddress(bindingSelector)
|
|
280
|
+
? resolveAsGetter
|
|
281
|
+
: resolveAsGetterByFilter,
|
|
282
|
+
);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Inject a function for setting (binding) the given key to a given
|
|
287
|
+
* value. (Only static/constant values are supported, it's not possible
|
|
288
|
+
* to bind a key to a class or a provider.)
|
|
289
|
+
*
|
|
290
|
+
* This is useful e.g. when implementing Actions that are contributing
|
|
291
|
+
* new Elements.
|
|
292
|
+
*
|
|
293
|
+
* See also `Setter<T>`.
|
|
294
|
+
*
|
|
295
|
+
* @param bindingKey - The key of the value we want to set.
|
|
296
|
+
* @param metadata - Optional metadata to help the injection
|
|
297
|
+
*/
|
|
298
|
+
export const setter = function injectSetter(
|
|
299
|
+
bindingKey: BindingAddress,
|
|
300
|
+
metadata?: InjectBindingMetadata,
|
|
301
|
+
) {
|
|
302
|
+
metadata = Object.assign({decorator: '@inject.setter'}, metadata);
|
|
303
|
+
return inject(bindingKey, metadata, resolveAsSetter);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Inject the binding object for the given key. This is useful if a binding
|
|
308
|
+
* needs to be set up beyond just a constant value allowed by
|
|
309
|
+
* `@inject.setter`. The injected binding is found or created based on the
|
|
310
|
+
* `metadata.bindingCreation` option. See `BindingCreationPolicy` for more
|
|
311
|
+
* details.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
*
|
|
315
|
+
* ```ts
|
|
316
|
+
* class MyAuthAction {
|
|
317
|
+
* @inject.binding('current-user', {
|
|
318
|
+
* bindingCreation: BindingCreationPolicy.ALWAYS_CREATE,
|
|
319
|
+
* })
|
|
320
|
+
* private userBinding: Binding<UserProfile>;
|
|
321
|
+
*
|
|
322
|
+
* async authenticate() {
|
|
323
|
+
* this.userBinding.toDynamicValue(() => {...});
|
|
324
|
+
* }
|
|
325
|
+
* }
|
|
326
|
+
* ```
|
|
327
|
+
*
|
|
328
|
+
* @param bindingKey - Binding key
|
|
329
|
+
* @param metadata - Metadata for the injection
|
|
330
|
+
*/
|
|
331
|
+
export const binding = function injectBinding(
|
|
332
|
+
bindingKey?: string | BindingKey<unknown>,
|
|
333
|
+
metadata?: InjectBindingMetadata,
|
|
334
|
+
) {
|
|
335
|
+
metadata = Object.assign({decorator: '@inject.binding'}, metadata);
|
|
336
|
+
return inject(bindingKey ?? '', metadata, resolveAsBinding);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Inject an array of values by a tag pattern string or regexp
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* class AuthenticationManager {
|
|
345
|
+
* constructor(
|
|
346
|
+
* @inject.tag('authentication.strategy') public strategies: Strategy[],
|
|
347
|
+
* ) {}
|
|
348
|
+
* }
|
|
349
|
+
* ```
|
|
350
|
+
* @param bindingTag - Tag name, regex or object
|
|
351
|
+
* @param metadata - Optional metadata to help the injection
|
|
352
|
+
*/
|
|
353
|
+
export const tag = function injectByTag(
|
|
354
|
+
bindingTag: BindingTag | RegExp,
|
|
355
|
+
metadata?: InjectionMetadata,
|
|
356
|
+
) {
|
|
357
|
+
metadata = Object.assign(
|
|
358
|
+
{decorator: '@inject.tag', tag: bindingTag},
|
|
359
|
+
metadata,
|
|
360
|
+
);
|
|
361
|
+
return inject(filterByTag(bindingTag), metadata);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Inject matching bound values by the filter function
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```ts
|
|
369
|
+
* class MyControllerWithView {
|
|
370
|
+
* @inject.view(filterByTag('foo'))
|
|
371
|
+
* view: ContextView<string[]>;
|
|
372
|
+
* }
|
|
373
|
+
* ```
|
|
374
|
+
* @param bindingFilter - A binding filter function
|
|
375
|
+
* @param metadata
|
|
376
|
+
*/
|
|
377
|
+
export const view = function injectContextView(
|
|
378
|
+
bindingFilter: BindingFilter,
|
|
379
|
+
metadata?: InjectionMetadata,
|
|
380
|
+
) {
|
|
381
|
+
metadata = Object.assign({decorator: '@inject.view'}, metadata);
|
|
382
|
+
return inject(bindingFilter, metadata, resolveAsContextView);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Inject the context object.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* class MyProvider {
|
|
391
|
+
* constructor(@inject.context() private ctx: Context) {}
|
|
392
|
+
* }
|
|
393
|
+
* ```
|
|
394
|
+
*/
|
|
395
|
+
export const context = function injectContext() {
|
|
396
|
+
return inject('', {decorator: '@inject.context'}, (ctx: Context) => ctx);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Assert the target type inspected from TypeScript for injection to be the
|
|
402
|
+
* expected type. If the types don't match, an error is thrown.
|
|
403
|
+
* @param injection - Injection information
|
|
404
|
+
* @param expectedType - Expected type
|
|
405
|
+
* @param expectedTypeName - Name of the expected type to be used in the error
|
|
406
|
+
* @returns The name of the target
|
|
407
|
+
*/
|
|
408
|
+
export function assertTargetType(
|
|
409
|
+
injection: Readonly<Injection>,
|
|
410
|
+
expectedType: Function,
|
|
411
|
+
expectedTypeName?: string,
|
|
412
|
+
) {
|
|
413
|
+
const targetName = ResolutionSession.describeInjection(injection).targetName;
|
|
414
|
+
const targetType = inspectTargetType(injection);
|
|
415
|
+
if (targetType && targetType !== expectedType) {
|
|
416
|
+
expectedTypeName = expectedTypeName ?? expectedType.name;
|
|
417
|
+
throw new Error(
|
|
418
|
+
`The type of ${targetName} (${targetType.name}) is not ${expectedTypeName}`,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return targetName;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Resolver for `@inject.getter`
|
|
426
|
+
* @param ctx
|
|
427
|
+
* @param injection
|
|
428
|
+
* @param session
|
|
429
|
+
*/
|
|
430
|
+
function resolveAsGetter(
|
|
431
|
+
ctx: Context,
|
|
432
|
+
injection: Readonly<Injection>,
|
|
433
|
+
session: ResolutionSession,
|
|
434
|
+
) {
|
|
435
|
+
assertTargetType(injection, Function, 'Getter function');
|
|
436
|
+
const bindingSelector = injection.bindingSelector as BindingAddress;
|
|
437
|
+
// We need to clone the session for the getter as it will be resolved later
|
|
438
|
+
const forkedSession = ResolutionSession.fork(session);
|
|
439
|
+
const options: ResolutionOptions = {
|
|
440
|
+
session: forkedSession,
|
|
441
|
+
...injection.metadata,
|
|
442
|
+
};
|
|
443
|
+
return function getter() {
|
|
444
|
+
return ctx.get(bindingSelector, options);
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Resolver for `@inject.setter`
|
|
450
|
+
* @param ctx
|
|
451
|
+
* @param injection
|
|
452
|
+
*/
|
|
453
|
+
function resolveAsSetter(ctx: Context, injection: Injection) {
|
|
454
|
+
const targetName = assertTargetType(injection, Function, 'Setter function');
|
|
455
|
+
const bindingSelector = injection.bindingSelector;
|
|
456
|
+
if (!isBindingAddress(bindingSelector)) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`@inject.setter (${targetName}) does not allow BindingFilter.`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
if (bindingSelector === '') {
|
|
462
|
+
throw new Error('Binding key is not set for @inject.setter');
|
|
463
|
+
}
|
|
464
|
+
// No resolution session should be propagated into the setter
|
|
465
|
+
return function setter(value: unknown) {
|
|
466
|
+
const binding = findOrCreateBindingForInjection(ctx, injection);
|
|
467
|
+
binding!.to(value);
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function resolveAsBinding(
|
|
472
|
+
ctx: Context,
|
|
473
|
+
injection: Injection,
|
|
474
|
+
session: ResolutionSession,
|
|
475
|
+
) {
|
|
476
|
+
const targetName = assertTargetType(injection, Binding);
|
|
477
|
+
const bindingSelector = injection.bindingSelector;
|
|
478
|
+
if (!isBindingAddress(bindingSelector)) {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`@inject.binding (${targetName}) does not allow BindingFilter.`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
return findOrCreateBindingForInjection(ctx, injection, session);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function findOrCreateBindingForInjection(
|
|
487
|
+
ctx: Context,
|
|
488
|
+
injection: Injection<unknown>,
|
|
489
|
+
session?: ResolutionSession,
|
|
490
|
+
) {
|
|
491
|
+
if (injection.bindingSelector === '') return session?.currentBinding;
|
|
492
|
+
const bindingCreation =
|
|
493
|
+
injection.metadata &&
|
|
494
|
+
(injection.metadata as InjectBindingMetadata).bindingCreation;
|
|
495
|
+
const binding: Binding<unknown> = ctx.findOrCreateBinding(
|
|
496
|
+
injection.bindingSelector as BindingAddress,
|
|
497
|
+
bindingCreation,
|
|
498
|
+
);
|
|
499
|
+
return binding;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Check if constructor injection should be applied to the base class
|
|
504
|
+
* of the given target class
|
|
505
|
+
*
|
|
506
|
+
* @param targetClass - Target class
|
|
507
|
+
*/
|
|
508
|
+
function shouldSkipBaseConstructorInjection(targetClass: Object) {
|
|
509
|
+
// FXIME(rfeng): We use the class definition to check certain patterns
|
|
510
|
+
const classDef = targetClass.toString();
|
|
511
|
+
return (
|
|
512
|
+
/*
|
|
513
|
+
* See https://github.com/loopbackio/loopback-next/issues/2946
|
|
514
|
+
* A class decorator can return a new constructor that mixes in
|
|
515
|
+
* additional properties/methods.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```ts
|
|
519
|
+
* class extends baseConstructor {
|
|
520
|
+
* // The constructor calls `super(...arguments)`
|
|
521
|
+
* constructor() {
|
|
522
|
+
* super(...arguments);
|
|
523
|
+
* }
|
|
524
|
+
* classProperty = 'a classProperty';
|
|
525
|
+
* classFunction() {
|
|
526
|
+
* return 'a classFunction';
|
|
527
|
+
* }
|
|
528
|
+
* };
|
|
529
|
+
* ```
|
|
530
|
+
*
|
|
531
|
+
* We check the following pattern:
|
|
532
|
+
* ```ts
|
|
533
|
+
* constructor() {
|
|
534
|
+
* super(...arguments);
|
|
535
|
+
* }
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
!classDef.match(
|
|
539
|
+
/\s+constructor\s*\(\s*\)\s*\{\s*super\(\.\.\.arguments\)/,
|
|
540
|
+
) &&
|
|
541
|
+
/*
|
|
542
|
+
* See https://github.com/loopbackio/loopback-next/issues/1565
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* ```ts
|
|
546
|
+
* class BaseClass {
|
|
547
|
+
* constructor(@inject('foo') protected foo: string) {}
|
|
548
|
+
* // ...
|
|
549
|
+
* }
|
|
550
|
+
*
|
|
551
|
+
* class SubClass extends BaseClass {
|
|
552
|
+
* // No explicit constructor is present
|
|
553
|
+
*
|
|
554
|
+
* @inject('bar')
|
|
555
|
+
* private bar: number;
|
|
556
|
+
* // ...
|
|
557
|
+
* };
|
|
558
|
+
*
|
|
559
|
+
*/
|
|
560
|
+
classDef.match(/\s+constructor\s*\([^\)]*\)\s+\{/m)
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Return an array of injection objects for parameters
|
|
566
|
+
* @param target - The target class for constructor or static methods,
|
|
567
|
+
* or the prototype for instance methods
|
|
568
|
+
* @param method - Method name, undefined for constructor
|
|
569
|
+
*/
|
|
570
|
+
export function describeInjectedArguments(
|
|
571
|
+
target: Object,
|
|
572
|
+
method?: string,
|
|
573
|
+
): Readonly<Injection>[] {
|
|
574
|
+
method = method ?? '';
|
|
575
|
+
|
|
576
|
+
// Try to read from cache
|
|
577
|
+
const cache =
|
|
578
|
+
MetadataInspector.getAllMethodMetadata<Readonly<Injection>[]>(
|
|
579
|
+
INJECT_METHODS_KEY,
|
|
580
|
+
target,
|
|
581
|
+
{
|
|
582
|
+
ownMetadataOnly: true,
|
|
583
|
+
},
|
|
584
|
+
) ?? {};
|
|
585
|
+
let meta: Readonly<Injection>[] = cache[method];
|
|
586
|
+
if (meta) return meta;
|
|
587
|
+
|
|
588
|
+
// Build the description
|
|
589
|
+
const options: InspectionOptions = {};
|
|
590
|
+
if (method === '') {
|
|
591
|
+
if (shouldSkipBaseConstructorInjection(target)) {
|
|
592
|
+
options.ownMetadataOnly = true;
|
|
593
|
+
}
|
|
594
|
+
} else if (Object.prototype.hasOwnProperty.call(target, method)) {
|
|
595
|
+
// The method exists in the target, no injections on the super method
|
|
596
|
+
// should be honored
|
|
597
|
+
options.ownMetadataOnly = true;
|
|
598
|
+
}
|
|
599
|
+
meta =
|
|
600
|
+
MetadataInspector.getAllParameterMetadata<Readonly<Injection>>(
|
|
601
|
+
INJECT_PARAMETERS_KEY,
|
|
602
|
+
target,
|
|
603
|
+
method,
|
|
604
|
+
options,
|
|
605
|
+
) ?? [];
|
|
606
|
+
|
|
607
|
+
// Cache the result
|
|
608
|
+
cache[method] = meta;
|
|
609
|
+
MetadataInspector.defineMetadata<MetadataMap<Readonly<Injection>[]>>(
|
|
610
|
+
INJECT_METHODS_KEY,
|
|
611
|
+
cache,
|
|
612
|
+
target,
|
|
613
|
+
);
|
|
614
|
+
return meta;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Inspect the target type for the injection to find out the corresponding
|
|
619
|
+
* JavaScript type
|
|
620
|
+
* @param injection - Injection information
|
|
621
|
+
*/
|
|
622
|
+
export function inspectTargetType(injection: Readonly<Injection>) {
|
|
623
|
+
if (typeof injection.methodDescriptorOrParameterIndex === 'number') {
|
|
624
|
+
const designType = MetadataInspector.getDesignTypeForMethod(
|
|
625
|
+
injection.target,
|
|
626
|
+
injection.member!,
|
|
627
|
+
);
|
|
628
|
+
return designType?.parameterTypes?.[
|
|
629
|
+
injection.methodDescriptorOrParameterIndex as number
|
|
630
|
+
];
|
|
631
|
+
}
|
|
632
|
+
return MetadataInspector.getDesignTypeForProperty(
|
|
633
|
+
injection.target,
|
|
634
|
+
injection.member!,
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Resolve an array of bound values matching the filter function for `@inject`.
|
|
640
|
+
* @param ctx - Context object
|
|
641
|
+
* @param injection - Injection information
|
|
642
|
+
* @param session - Resolution session
|
|
643
|
+
*/
|
|
644
|
+
function resolveValuesByFilter(
|
|
645
|
+
ctx: Context,
|
|
646
|
+
injection: Readonly<Injection>,
|
|
647
|
+
session: ResolutionSession,
|
|
648
|
+
) {
|
|
649
|
+
assertTargetType(injection, Array);
|
|
650
|
+
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
651
|
+
const view = new ContextView(
|
|
652
|
+
ctx,
|
|
653
|
+
bindingFilter,
|
|
654
|
+
injection.metadata.bindingComparator,
|
|
655
|
+
);
|
|
656
|
+
return view.resolve(session);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Resolve to a getter function that returns an array of bound values matching
|
|
661
|
+
* the filter function for `@inject.getter`.
|
|
662
|
+
*
|
|
663
|
+
* @param ctx - Context object
|
|
664
|
+
* @param injection - Injection information
|
|
665
|
+
* @param session - Resolution session
|
|
666
|
+
*/
|
|
667
|
+
function resolveAsGetterByFilter(
|
|
668
|
+
ctx: Context,
|
|
669
|
+
injection: Readonly<Injection>,
|
|
670
|
+
session: ResolutionSession,
|
|
671
|
+
) {
|
|
672
|
+
assertTargetType(injection, Function, 'Getter function');
|
|
673
|
+
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
674
|
+
return createViewGetter(
|
|
675
|
+
ctx,
|
|
676
|
+
bindingFilter,
|
|
677
|
+
injection.metadata.bindingComparator,
|
|
678
|
+
session,
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Resolve to an instance of `ContextView` by the binding filter function
|
|
684
|
+
* for `@inject.view`
|
|
685
|
+
* @param ctx - Context object
|
|
686
|
+
* @param injection - Injection information
|
|
687
|
+
*/
|
|
688
|
+
function resolveAsContextView(ctx: Context, injection: Readonly<Injection>) {
|
|
689
|
+
assertTargetType(injection, ContextView);
|
|
690
|
+
|
|
691
|
+
const bindingFilter = injection.bindingSelector as BindingFilter;
|
|
692
|
+
const view = new ContextView(
|
|
693
|
+
ctx,
|
|
694
|
+
bindingFilter,
|
|
695
|
+
injection.metadata.bindingComparator,
|
|
696
|
+
);
|
|
697
|
+
view.open();
|
|
698
|
+
return view;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Return a map of injection objects for properties
|
|
703
|
+
* @param target - The target class for static properties or
|
|
704
|
+
* prototype for instance properties.
|
|
705
|
+
*/
|
|
706
|
+
export function describeInjectedProperties(
|
|
707
|
+
target: Object,
|
|
708
|
+
): MetadataMap<Readonly<Injection>> {
|
|
709
|
+
const metadata =
|
|
710
|
+
MetadataInspector.getAllPropertyMetadata<Readonly<Injection>>(
|
|
711
|
+
INJECT_PROPERTIES_KEY,
|
|
712
|
+
target,
|
|
713
|
+
) ?? {};
|
|
714
|
+
return metadata;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Inspect injections for a binding created with `toClass` or `toProvider`
|
|
719
|
+
* @param binding - Binding object
|
|
720
|
+
*/
|
|
721
|
+
export function inspectInjections(binding: Readonly<Binding<unknown>>) {
|
|
722
|
+
const json: JSONObject = {};
|
|
723
|
+
const ctor = binding.valueConstructor ?? binding.providerConstructor;
|
|
724
|
+
if (ctor == null) return json;
|
|
725
|
+
const constructorInjections = describeInjectedArguments(ctor, '').map(
|
|
726
|
+
inspectInjection,
|
|
727
|
+
);
|
|
728
|
+
if (constructorInjections.length) {
|
|
729
|
+
json.constructorArguments = constructorInjections;
|
|
730
|
+
}
|
|
731
|
+
const propertyInjections = describeInjectedProperties(ctor.prototype);
|
|
732
|
+
const properties: JSONObject = {};
|
|
733
|
+
for (const p in propertyInjections) {
|
|
734
|
+
properties[p] = inspectInjection(propertyInjections[p]);
|
|
735
|
+
}
|
|
736
|
+
if (Object.keys(properties).length) {
|
|
737
|
+
json.properties = properties;
|
|
738
|
+
}
|
|
739
|
+
return json;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Inspect an injection
|
|
744
|
+
* @param injection - Injection information
|
|
745
|
+
*/
|
|
746
|
+
function inspectInjection(injection: Readonly<Injection<unknown>>) {
|
|
747
|
+
const injectionInfo = ResolutionSession.describeInjection(injection);
|
|
748
|
+
const descriptor: JSONObject = {};
|
|
749
|
+
if (injectionInfo.targetName) {
|
|
750
|
+
descriptor.targetName = injectionInfo.targetName;
|
|
751
|
+
}
|
|
752
|
+
if (isBindingAddress(injectionInfo.bindingSelector)) {
|
|
753
|
+
// Binding key
|
|
754
|
+
descriptor.bindingKey = injectionInfo.bindingSelector.toString();
|
|
755
|
+
} else if (isBindingTagFilter(injectionInfo.bindingSelector)) {
|
|
756
|
+
// Binding tag filter
|
|
757
|
+
descriptor.bindingTagPattern = JSON.parse(
|
|
758
|
+
JSON.stringify(injectionInfo.bindingSelector.bindingTagPattern),
|
|
759
|
+
);
|
|
760
|
+
} else {
|
|
761
|
+
// Binding filter function
|
|
762
|
+
descriptor.bindingFilter =
|
|
763
|
+
injectionInfo.bindingSelector?.name ?? '<function>';
|
|
764
|
+
}
|
|
765
|
+
// Inspect metadata
|
|
766
|
+
if (injectionInfo.metadata) {
|
|
767
|
+
if (
|
|
768
|
+
injectionInfo.metadata.decorator &&
|
|
769
|
+
injectionInfo.metadata.decorator !== '@inject'
|
|
770
|
+
) {
|
|
771
|
+
descriptor.decorator = injectionInfo.metadata.decorator;
|
|
772
|
+
}
|
|
773
|
+
if (injectionInfo.metadata.optional) {
|
|
774
|
+
descriptor.optional = injectionInfo.metadata.optional;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return descriptor;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Check if the given class has `@inject` or other decorations that map to
|
|
782
|
+
* `@inject`.
|
|
783
|
+
*
|
|
784
|
+
* @param cls - Class with possible `@inject` decorations
|
|
785
|
+
*/
|
|
786
|
+
export function hasInjections(cls: Constructor<unknown>): boolean {
|
|
787
|
+
return (
|
|
788
|
+
MetadataInspector.getClassMetadata(INJECT_PARAMETERS_KEY, cls) != null ||
|
|
789
|
+
Reflector.getMetadata(INJECT_PARAMETERS_KEY.toString(), cls.prototype) !=
|
|
790
|
+
null ||
|
|
791
|
+
MetadataInspector.getAllPropertyMetadata(
|
|
792
|
+
INJECT_PROPERTIES_KEY,
|
|
793
|
+
cls.prototype,
|
|
794
|
+
) != null
|
|
795
|
+
);
|
|
796
|
+
}
|