@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
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
+ }