@tsed/di 8.0.0 → 8.0.2
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/package.json +10 -9
- package/src/common/constants/constants.ts +5 -0
- package/src/common/decorators/autoInjectable.spec.ts +135 -0
- package/src/common/decorators/autoInjectable.ts +59 -0
- package/src/common/decorators/configuration.spec.ts +45 -0
- package/src/common/decorators/configuration.ts +34 -0
- package/src/common/decorators/constant.spec.ts +67 -0
- package/src/common/decorators/constant.ts +72 -0
- package/src/common/decorators/controller.spec.ts +43 -0
- package/src/common/decorators/controller.ts +64 -0
- package/src/common/decorators/inject.spec.ts +298 -0
- package/src/common/decorators/inject.ts +144 -0
- package/src/common/decorators/injectable.spec.ts +43 -0
- package/src/common/decorators/injectable.ts +30 -0
- package/src/common/decorators/intercept.spec.ts +103 -0
- package/src/common/decorators/intercept.ts +77 -0
- package/src/common/decorators/interceptor.ts +17 -0
- package/src/common/decorators/lazyInject.spec.ts +53 -0
- package/src/common/decorators/lazyInject.ts +91 -0
- package/src/common/decorators/module.ts +52 -0
- package/src/common/decorators/opts.ts +51 -0
- package/src/common/decorators/overrideProvider.spec.ts +30 -0
- package/src/common/decorators/overrideProvider.ts +15 -0
- package/src/common/decorators/scope.spec.ts +25 -0
- package/src/common/decorators/scope.ts +13 -0
- package/src/common/decorators/service.ts +14 -0
- package/src/common/decorators/useOpts.spec.ts +90 -0
- package/src/common/decorators/useOpts.ts +61 -0
- package/src/common/decorators/value.spec.ts +59 -0
- package/src/common/decorators/value.ts +60 -0
- package/src/common/domain/Container.spec.ts +85 -0
- package/src/common/domain/Container.ts +80 -0
- package/src/common/domain/ControllerProvider.spec.ts +55 -0
- package/src/common/domain/ControllerProvider.ts +48 -0
- package/src/common/domain/InjectablePropertyType.ts +7 -0
- package/src/common/domain/LocalsContainer.spec.ts +20 -0
- package/src/common/domain/LocalsContainer.ts +11 -0
- package/src/common/domain/Provider.spec.ts +107 -0
- package/src/common/domain/Provider.ts +205 -0
- package/src/common/domain/ProviderScope.ts +5 -0
- package/src/common/domain/ProviderType.ts +8 -0
- package/src/common/errors/InjectionError.spec.ts +17 -0
- package/src/common/errors/InjectionError.ts +68 -0
- package/src/common/errors/InvalidPropertyTokenError.ts +13 -0
- package/src/common/fn/configuration.spec.ts +42 -0
- package/src/common/fn/configuration.ts +22 -0
- package/src/common/fn/constant.spec.ts +37 -0
- package/src/common/fn/constant.ts +7 -0
- package/src/common/fn/inject.spec.ts +54 -0
- package/src/common/fn/inject.ts +29 -0
- package/src/common/fn/injectMany.ts +16 -0
- package/src/common/fn/injectable.spec.ts +87 -0
- package/src/common/fn/injectable.ts +15 -0
- package/src/common/fn/injector.ts +26 -0
- package/src/common/fn/lazyInject.spec.ts +22 -0
- package/src/common/fn/lazyInject.ts +36 -0
- package/src/common/fn/localsContainer.ts +55 -0
- package/src/common/fn/refValue.spec.ts +47 -0
- package/src/common/fn/refValue.ts +33 -0
- package/src/common/index.ts +63 -0
- package/src/common/integration/async-factory.spec.ts +60 -0
- package/src/common/integration/di.spec.ts +153 -0
- package/src/common/integration/imports.spec.ts +39 -0
- package/src/common/integration/request.spec.ts +79 -0
- package/src/common/integration/singleton.spec.ts +125 -0
- package/src/common/interfaces/DIConfigurationOptions.ts +29 -0
- package/src/common/interfaces/DILogger.ts +13 -0
- package/src/common/interfaces/DILoggerOptions.ts +54 -0
- package/src/common/interfaces/ImportTokenProviderOpts.ts +29 -0
- package/src/common/interfaces/InterceptorContext.ts +13 -0
- package/src/common/interfaces/InterceptorMethods.ts +5 -0
- package/src/common/interfaces/InvokeOptions.ts +28 -0
- package/src/common/interfaces/OnDestroy.ts +3 -0
- package/src/common/interfaces/OnInit.ts +3 -0
- package/src/common/interfaces/ProviderOpts.ts +57 -0
- package/src/common/interfaces/RegistrySettings.ts +11 -0
- package/src/common/interfaces/ResolvedInvokeOptions.ts +14 -0
- package/src/common/interfaces/TokenProvider.ts +3 -0
- package/src/common/interfaces/TokenRoute.ts +6 -0
- package/src/common/registries/GlobalProviders.spec.ts +94 -0
- package/src/common/registries/GlobalProviders.ts +131 -0
- package/src/common/registries/ProviderRegistry.spec.ts +36 -0
- package/src/common/registries/ProviderRegistry.ts +32 -0
- package/src/common/services/DIConfiguration.spec.ts +60 -0
- package/src/common/services/DIConfiguration.ts +140 -0
- package/src/common/services/DILogger.spec.ts +22 -0
- package/src/common/services/DILogger.ts +8 -0
- package/src/common/services/InjectorService.spec.ts +793 -0
- package/src/common/services/InjectorService.ts +601 -0
- package/src/common/utils/colors.spec.ts +9 -0
- package/src/common/utils/colors.ts +11 -0
- package/src/common/utils/createContainer.spec.ts +18 -0
- package/src/common/utils/createContainer.ts +14 -0
- package/src/common/utils/discoverHooks.ts +15 -0
- package/src/common/utils/getConfiguration.spec.ts +40 -0
- package/src/common/utils/getConfiguration.ts +20 -0
- package/src/common/utils/getConstructorDependencies.ts +9 -0
- package/src/common/utils/providerBuilder.ts +81 -0
- package/src/index.ts +2 -0
- package/src/node/decorators/injectContext.spec.ts +60 -0
- package/src/node/decorators/injectContext.ts +29 -0
- package/src/node/domain/ContextLogger.spec.ts +277 -0
- package/src/node/domain/ContextLogger.ts +137 -0
- package/src/node/domain/DIContext.spec.ts +103 -0
- package/src/node/domain/DIContext.ts +115 -0
- package/src/node/fn/context.ts +24 -0
- package/src/node/fn/logger.spec.ts +46 -0
- package/src/node/fn/logger.ts +17 -0
- package/src/node/index.ts +16 -0
- package/src/node/integration/async-factory-invoke.spec.ts +58 -0
- package/src/node/integration/inject-context.spec.ts +29 -0
- package/src/node/integration/invoke-oninit.spec.ts +25 -0
- package/src/node/interfaces/AlterRunInContext.ts +3 -0
- package/src/node/services/DILogger.spec.ts +24 -0
- package/src/node/services/DILogger.ts +6 -0
- package/src/node/services/DITest.spec.ts +109 -0
- package/src/node/services/DITest.ts +119 -0
- package/src/node/utils/asyncHookContext.spec.ts +147 -0
- package/src/node/utils/asyncHookContext.ts +48 -0
- package/src/node/utils/attachLogger.spec.ts +14 -0
- package/src/node/utils/attachLogger.ts +7 -0
- package/src/node/utils/setLoggerConfiguration.spec.ts +43 -0
- package/src/node/utils/setLoggerConfiguration.ts +7 -0
- package/src/node/utils/setLoggerFormat.ts +33 -0
- package/src/node/utils/setLoggerLevel.ts +12 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import {classOf, deepClone, deepMerge, isArray, isClass, isFunction, isInheritedFrom, isObject, isPromise, nameOf} from "@tsed/core";
|
|
2
|
+
import {$alter, $asyncAlter, $asyncEmit, $emit, $off, $on} from "@tsed/hooks";
|
|
3
|
+
|
|
4
|
+
import {DI_INVOKE_OPTIONS, DI_USE_PARAM_OPTIONS} from "../constants/constants.js";
|
|
5
|
+
import {Container} from "../domain/Container.js";
|
|
6
|
+
import {LocalsContainer} from "../domain/LocalsContainer.js";
|
|
7
|
+
import {Provider} from "../domain/Provider.js";
|
|
8
|
+
import {ProviderScope} from "../domain/ProviderScope.js";
|
|
9
|
+
import {InjectionError} from "../errors/InjectionError.js";
|
|
10
|
+
import type {DILogger} from "../interfaces/DILogger.js";
|
|
11
|
+
import type {ImportTokenProviderOpts} from "../interfaces/ImportTokenProviderOpts.js";
|
|
12
|
+
import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
|
|
13
|
+
import type {ResolvedInvokeOptions} from "../interfaces/ResolvedInvokeOptions.js";
|
|
14
|
+
import type {TokenProvider} from "../interfaces/TokenProvider.js";
|
|
15
|
+
import {GlobalProviders} from "../registries/GlobalProviders.js";
|
|
16
|
+
import {createContainer} from "../utils/createContainer.js";
|
|
17
|
+
import {getConstructorDependencies} from "../utils/getConstructorDependencies.js";
|
|
18
|
+
import {DIConfiguration} from "./DIConfiguration.js";
|
|
19
|
+
|
|
20
|
+
const EXCLUDED_CONFIGURATION_KEYS = ["mount", "imports"];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This service contain all services collected by `@Service` or services declared manually with `InjectorService.factory()` or `InjectorService.service()`.
|
|
24
|
+
*
|
|
25
|
+
* ### Example:
|
|
26
|
+
*
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import {InjectorService} from "@tsed/di";
|
|
29
|
+
*
|
|
30
|
+
* // Import the services (all services are decorated with @Service()";
|
|
31
|
+
* import MyService1 from "./services/service1.js";
|
|
32
|
+
* import MyService2 from "./services/service2.js";
|
|
33
|
+
* import MyService3 from "./services/service3.js";
|
|
34
|
+
*
|
|
35
|
+
* // When all services are imported, you can load InjectorService.
|
|
36
|
+
*
|
|
37
|
+
* await injector().load();
|
|
38
|
+
*
|
|
39
|
+
* const myService1 = injector.get<MyService1>(MyService1);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class InjectorService extends Container {
|
|
43
|
+
public logger: DILogger = console;
|
|
44
|
+
private resolvedConfiguration: boolean = false;
|
|
45
|
+
#cache = new LocalsContainer();
|
|
46
|
+
#loaded: boolean = false;
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
super();
|
|
50
|
+
this.#cache.set(InjectorService, this);
|
|
51
|
+
this.#cache.set(DIConfiguration, new DIConfiguration());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get settings(): DIConfiguration {
|
|
55
|
+
return this.#cache.get(DIConfiguration);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set settings(settings: DIConfiguration) {
|
|
59
|
+
this.#cache.set(DIConfiguration, settings);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
isLoaded() {
|
|
63
|
+
return this.#loaded;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Return a list of instance build by the injector.
|
|
68
|
+
*/
|
|
69
|
+
public toArray(): any[] {
|
|
70
|
+
return [...this.#cache.values()];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get a service or factory already constructed from his symbol or class.
|
|
75
|
+
*
|
|
76
|
+
* #### Example
|
|
77
|
+
*
|
|
78
|
+
* ```typescript
|
|
79
|
+
* import {InjectorService} from "@tsed/di";
|
|
80
|
+
* import MyService from "./services.js";
|
|
81
|
+
*
|
|
82
|
+
* class OtherService {
|
|
83
|
+
* constructor(injectorService: InjectorService) {
|
|
84
|
+
* const myService = injectorService.get<MyService>(MyService);
|
|
85
|
+
* }
|
|
86
|
+
* }
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @param token The class or symbol registered in InjectorService.
|
|
90
|
+
* @param options
|
|
91
|
+
*/
|
|
92
|
+
get<T = any>(token: TokenProvider<T>, options?: Partial<InvokeOptions>): T {
|
|
93
|
+
if (!this.has(token)) {
|
|
94
|
+
return this.resolve(token, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this.#cache.get(token);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Return all instance of the same provider type
|
|
102
|
+
*/
|
|
103
|
+
getMany<Type = any>(type: any, options?: Partial<InvokeOptions>): Type[] {
|
|
104
|
+
return this.getProviders(type).map((provider) => {
|
|
105
|
+
return this.resolve<Type>(provider.token, options);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The has() method returns a boolean indicating whether an element with the specified key exists or not.
|
|
111
|
+
*/
|
|
112
|
+
has(token: TokenProvider): boolean {
|
|
113
|
+
return this.#cache.get(token) !== undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Declare an alias for a given token.
|
|
118
|
+
*/
|
|
119
|
+
alias(token: TokenProvider, alias: TokenProvider) {
|
|
120
|
+
this.#cache.set(alias, this.#cache.get(token));
|
|
121
|
+
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the token depending on his provider configuration.
|
|
127
|
+
*
|
|
128
|
+
* If the token isn't cached, the injector will invoke the provider and cache the result.
|
|
129
|
+
*
|
|
130
|
+
* #### Example
|
|
131
|
+
*
|
|
132
|
+
* ```typescript
|
|
133
|
+
* import {InjectorService} from "@tsed/di";
|
|
134
|
+
* import MyService from "./services.js";
|
|
135
|
+
*
|
|
136
|
+
* class OtherService {
|
|
137
|
+
* constructor(injectorService: InjectorService) {
|
|
138
|
+
* const myService = injectorService.invoke<MyService>(MyService);
|
|
139
|
+
* }
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @param token The injectable class to invoke. Class parameters are injected according constructor signature.
|
|
144
|
+
* @param options {InvokeOptions} Optional options to invoke the class.
|
|
145
|
+
* @returns {Type} The class constructed.
|
|
146
|
+
*/
|
|
147
|
+
public resolve<Type = any>(token: TokenProvider<Type>, options: Partial<InvokeOptions> = {}): Type {
|
|
148
|
+
let instance: any = options.locals ? options.locals.get(token) : undefined;
|
|
149
|
+
|
|
150
|
+
if (instance !== undefined) {
|
|
151
|
+
return instance;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (token === DI_USE_PARAM_OPTIONS) {
|
|
155
|
+
return options.useOpts as Type;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
instance = !options.rebuild ? this.#cache.get(token) : undefined;
|
|
159
|
+
|
|
160
|
+
if (instance != undefined) {
|
|
161
|
+
return instance;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const provider = this.ensureProvider(token);
|
|
165
|
+
|
|
166
|
+
// maybe not necessary
|
|
167
|
+
if (!provider || options.rebuild) {
|
|
168
|
+
instance = this.invokeToken(token, options);
|
|
169
|
+
|
|
170
|
+
if (provider) {
|
|
171
|
+
return this.setToCache(provider!, instance);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return instance;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
instance = this.invokeToken(token, options);
|
|
178
|
+
|
|
179
|
+
switch (provider.scope) {
|
|
180
|
+
case ProviderScope.SINGLETON:
|
|
181
|
+
if (!options.rebuild) {
|
|
182
|
+
this.registerHooks(provider, options);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return this.setToCache(provider, instance);
|
|
186
|
+
case ProviderScope.REQUEST:
|
|
187
|
+
if (options.locals) {
|
|
188
|
+
options.locals.set(provider.token, instance);
|
|
189
|
+
|
|
190
|
+
this.registerHooks(provider, options);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return instance;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return instance;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Resolve the token depending on his provider configuration.
|
|
201
|
+
*
|
|
202
|
+
* If the token isn't cached, the injector will invoke the provider and cache the result.
|
|
203
|
+
*
|
|
204
|
+
* #### Example
|
|
205
|
+
*
|
|
206
|
+
* ```typescript
|
|
207
|
+
* import {InjectorService} from "@tsed/di";
|
|
208
|
+
* import MyService from "./services.js";
|
|
209
|
+
*
|
|
210
|
+
* class OtherService {
|
|
211
|
+
* constructor(injectorService: InjectorService) {
|
|
212
|
+
* const myService = injectorService.invoke<MyService>(MyService);
|
|
213
|
+
* }
|
|
214
|
+
* }
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @param token The injectable class to invoke. Class parameters are injected according constructor signature.
|
|
218
|
+
* @param options {InvokeOptions} Optional options to invoke the class.
|
|
219
|
+
* @returns {Type} The class constructed.
|
|
220
|
+
* @alias InjectorService.resolve
|
|
221
|
+
*/
|
|
222
|
+
public invoke<Type = any>(token: TokenProvider<Type>, options: Partial<InvokeOptions> = {}): Type {
|
|
223
|
+
return this.resolve(token, options);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Build only providers which are asynchronous.
|
|
228
|
+
*/
|
|
229
|
+
async loadAsync() {
|
|
230
|
+
for (const [, provider] of this) {
|
|
231
|
+
if (!this.has(provider.token) && provider.isAsync()) {
|
|
232
|
+
await this.resolve(provider.token);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Build only providers which are synchronous.
|
|
239
|
+
*/
|
|
240
|
+
loadSync() {
|
|
241
|
+
for (const [, provider] of this) {
|
|
242
|
+
// TODO try to lazy provider instead initiate all providers (&& provider.hasRegisteredHooks())
|
|
243
|
+
if (!this.has(provider.token) && provider.scope === ProviderScope.SINGLETON) {
|
|
244
|
+
this.resolve(provider.token);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Build all providers from given container (or GlobalProviders) and emit `$onInit` event.
|
|
251
|
+
*
|
|
252
|
+
* @param container
|
|
253
|
+
*/
|
|
254
|
+
async load(container: Container = createContainer()) {
|
|
255
|
+
// avoid provider registration in the GlobalContainer during the loading phase
|
|
256
|
+
// using injectable() or providerBuilder()
|
|
257
|
+
this.#loaded = true;
|
|
258
|
+
|
|
259
|
+
await $asyncEmit("$beforeInit");
|
|
260
|
+
|
|
261
|
+
this.bootstrap(container);
|
|
262
|
+
|
|
263
|
+
// build async and sync provider
|
|
264
|
+
await this.loadAsync();
|
|
265
|
+
|
|
266
|
+
// load sync provider
|
|
267
|
+
this.loadSync();
|
|
268
|
+
|
|
269
|
+
await $asyncEmit("$onInit");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Load all configurations registered on providers via @Configuration decorator.
|
|
274
|
+
* It inspects for each provider some fields like imports, mount, etc. to resolve the configuration.
|
|
275
|
+
*/
|
|
276
|
+
resolveConfiguration() {
|
|
277
|
+
if (this.resolvedConfiguration) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const mergedConfiguration = new Map();
|
|
281
|
+
|
|
282
|
+
super.forEach((provider) => {
|
|
283
|
+
if (provider.configuration && provider.type !== "server:module") {
|
|
284
|
+
Object.entries(provider.configuration).forEach(([key, value]) => {
|
|
285
|
+
if (!EXCLUDED_CONFIGURATION_KEYS.includes(key)) {
|
|
286
|
+
value = mergedConfiguration.has(key) ? deepMerge(mergedConfiguration.get(key), value) : deepClone(value);
|
|
287
|
+
mergedConfiguration.set(key, value);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
mergedConfiguration.forEach((value, key) => {
|
|
294
|
+
this.settings.set(key, deepMerge(value, this.settings.get(key)));
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
this.resolvedConfiguration = true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Emit an event to all service. See service [lifecycle hooks](/docs/services.md#lifecycle-hooks).
|
|
302
|
+
* @param eventName The event name to emit at all services.
|
|
303
|
+
* @param args List of the parameters to give to each service.
|
|
304
|
+
* @returns A list of promises.
|
|
305
|
+
* @deprecated use $asyncEmit instead
|
|
306
|
+
*/
|
|
307
|
+
public emit(eventName: string, ...args: unknown[]) {
|
|
308
|
+
return $asyncEmit(eventName, args);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Alter value attached to an event.
|
|
313
|
+
* @param eventName
|
|
314
|
+
* @param value
|
|
315
|
+
* @param args
|
|
316
|
+
* @deprecated use $alter instead
|
|
317
|
+
*/
|
|
318
|
+
public alter<T = any>(eventName: string, value: any, ...args: any[]): T {
|
|
319
|
+
return $alter(eventName, value, args);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Alter value attached to an event asynchronously.
|
|
324
|
+
* @param eventName
|
|
325
|
+
* @param value
|
|
326
|
+
* @param args
|
|
327
|
+
* @deprecated use $asyncAlter instead
|
|
328
|
+
*/
|
|
329
|
+
public alterAsync<T = any>(eventName: string, value: any, ...args: any[]): Promise<T> {
|
|
330
|
+
return $asyncAlter(eventName, value, args);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Destroy the injector and all services.
|
|
335
|
+
*/
|
|
336
|
+
async destroy() {
|
|
337
|
+
await $asyncEmit("$onDestroy");
|
|
338
|
+
this.#cache.forEach((_, token) => {
|
|
339
|
+
$off(token);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Bootstrap injector from container, resolve configuration and providers.
|
|
345
|
+
*
|
|
346
|
+
* @param container
|
|
347
|
+
*/
|
|
348
|
+
protected bootstrap(container: Container = createContainer()) {
|
|
349
|
+
// Clone all providers in the container
|
|
350
|
+
this.addProviders(container);
|
|
351
|
+
|
|
352
|
+
// Resolve all configuration
|
|
353
|
+
this.resolveConfiguration();
|
|
354
|
+
|
|
355
|
+
// allow mocking or changing provider instance before loading injector
|
|
356
|
+
this.resolveImportsProviders();
|
|
357
|
+
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Ensure that a provider is added to the container.
|
|
363
|
+
* @protected
|
|
364
|
+
*/
|
|
365
|
+
protected ensureProvider(token: TokenProvider, force: true): Provider;
|
|
366
|
+
protected ensureProvider(token: TokenProvider, force: false): Provider | undefined;
|
|
367
|
+
protected ensureProvider(token: TokenProvider): Provider | undefined;
|
|
368
|
+
protected ensureProvider(token: TokenProvider, force = false): Provider | undefined {
|
|
369
|
+
if (!this.hasProvider(token) && (GlobalProviders.has(token) || force)) {
|
|
370
|
+
this.addProvider(token);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return this.getProvider(token)!;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Invoke a class method and inject service.
|
|
378
|
+
*
|
|
379
|
+
* #### IInjectableMethod options
|
|
380
|
+
*
|
|
381
|
+
* * **target**: Optional. The class instance.
|
|
382
|
+
* * **methodName**: `string` Optional. The method name.
|
|
383
|
+
* * **designParamTypes**: `any[]` Optional. List of injectable types.
|
|
384
|
+
* * **locals**: `Map<Function, any>` Optional. If preset then any argument Class are read from this object first, before the `InjectorService` is consulted.
|
|
385
|
+
*
|
|
386
|
+
* #### Example
|
|
387
|
+
*
|
|
388
|
+
* @param target
|
|
389
|
+
* @param options
|
|
390
|
+
* @private
|
|
391
|
+
*/
|
|
392
|
+
protected invokeToken<T>(target: TokenProvider, options: Partial<InvokeOptions> = {}): T | Promise<T> {
|
|
393
|
+
const resolvedOpts = this.mapInvokeOptions(target, options);
|
|
394
|
+
|
|
395
|
+
if (!resolvedOpts) {
|
|
396
|
+
return undefined as T;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const {token, deps, construct, imports, provider} = resolvedOpts;
|
|
400
|
+
|
|
401
|
+
$emit("$beforeInvoke", token, [resolvedOpts]);
|
|
402
|
+
$emit(`$beforeInvoke:${String(provider.type)}`, [resolvedOpts]);
|
|
403
|
+
|
|
404
|
+
let instance: any;
|
|
405
|
+
let currentDependency: any = false;
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const invokeDependency =
|
|
409
|
+
(parent?: any) =>
|
|
410
|
+
(token: TokenProvider | [TokenProvider], index: number): any => {
|
|
411
|
+
currentDependency = {token, index, deps};
|
|
412
|
+
|
|
413
|
+
if (isArray(token)) {
|
|
414
|
+
return this.getMany(token[0], options);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return isInheritedFrom(token, Provider, 1)
|
|
418
|
+
? provider
|
|
419
|
+
: this.resolve(token, {
|
|
420
|
+
parent,
|
|
421
|
+
locals: options.locals,
|
|
422
|
+
useOpts: provider?.getArgOpts(index) || options.useOpts
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// Invoke manually imported providers
|
|
427
|
+
imports.forEach(invokeDependency());
|
|
428
|
+
|
|
429
|
+
// Inject dependencies
|
|
430
|
+
const services = deps.map(invokeDependency(token));
|
|
431
|
+
|
|
432
|
+
currentDependency = false;
|
|
433
|
+
|
|
434
|
+
instance = construct(services);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
InjectionError.throwInjectorError(token, currentDependency, error);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (instance === undefined) {
|
|
440
|
+
throw new InjectionError(
|
|
441
|
+
token,
|
|
442
|
+
`Unable to create new instance from undefined value. Check your provider declaration for ${nameOf(token)}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (instance && isClass(classOf(instance))) {
|
|
447
|
+
Reflect.defineProperty(instance, DI_INVOKE_OPTIONS, {
|
|
448
|
+
get: () => ({rebuild: options.rebuild, locals: options.locals})
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
$emit("$afterInvoke", token, [instance, resolvedOpts]);
|
|
453
|
+
|
|
454
|
+
return instance;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private resolveImportsProviders() {
|
|
458
|
+
this.settings.imports = this.settings.imports
|
|
459
|
+
?.filter((meta) => isObject(meta) && "token" in meta && meta.token !== InjectorService)
|
|
460
|
+
.map((meta) => {
|
|
461
|
+
if (isObject(meta) && "token" in meta) {
|
|
462
|
+
const {token, ...props} = meta;
|
|
463
|
+
|
|
464
|
+
const provider = this.ensureProvider(token, true);
|
|
465
|
+
|
|
466
|
+
if (provider) {
|
|
467
|
+
provider.useValue = undefined;
|
|
468
|
+
provider.useAsyncFactory = undefined;
|
|
469
|
+
provider.useFactory = undefined;
|
|
470
|
+
|
|
471
|
+
if ("useClass" in props) {
|
|
472
|
+
provider.useClass = props.useClass;
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if ("useFactory" in props) {
|
|
477
|
+
provider.useFactory = props.useFactory as never;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if ("useAsyncFactory" in props) {
|
|
482
|
+
provider.useAsyncFactory = props.useAsyncFactory as never;
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if ("use" in props) {
|
|
487
|
+
provider.useValue = props.use;
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return meta;
|
|
494
|
+
})
|
|
495
|
+
.filter(Boolean) as unknown as (TokenProvider | ImportTokenProviderOpts)[];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Create options to invoke a provider or class.
|
|
500
|
+
* @param token
|
|
501
|
+
* @param options
|
|
502
|
+
*/
|
|
503
|
+
private mapInvokeOptions(token: TokenProvider, options: Partial<InvokeOptions>): ResolvedInvokeOptions | false {
|
|
504
|
+
const locals = options.locals || new LocalsContainer();
|
|
505
|
+
|
|
506
|
+
options.locals = locals;
|
|
507
|
+
|
|
508
|
+
let imports: (TokenProvider | [TokenProvider])[] | undefined = options.imports;
|
|
509
|
+
let deps: TokenProvider[] | undefined = options.deps;
|
|
510
|
+
let construct;
|
|
511
|
+
|
|
512
|
+
if (!token || token === Object) {
|
|
513
|
+
throw new Error("Given token is undefined. Could mean a circular dependency problem. Try to use @Inject(() => Token) to solve it.");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let provider: Provider;
|
|
517
|
+
|
|
518
|
+
if (!this.hasProvider(token)) {
|
|
519
|
+
provider = new Provider(token);
|
|
520
|
+
} else {
|
|
521
|
+
provider = this.getProvider(token)!;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
deps = deps || provider.deps;
|
|
525
|
+
imports = imports || provider.imports;
|
|
526
|
+
|
|
527
|
+
if (provider.useValue !== undefined) {
|
|
528
|
+
construct = () => (isFunction(provider.useValue) ? provider.useValue() : provider.useValue);
|
|
529
|
+
} else if (provider.useFactory) {
|
|
530
|
+
construct = (deps: any[]) => provider.useFactory!(...deps);
|
|
531
|
+
} else if (provider.useAsyncFactory) {
|
|
532
|
+
construct = async (deps: any[]) => {
|
|
533
|
+
deps = await Promise.all(deps);
|
|
534
|
+
return provider.useAsyncFactory!(...deps);
|
|
535
|
+
};
|
|
536
|
+
} else if (provider.useClass) {
|
|
537
|
+
// useClass
|
|
538
|
+
deps = deps || getConstructorDependencies(provider.useClass);
|
|
539
|
+
construct = (deps: TokenProvider[]) => new provider.useClass(...deps);
|
|
540
|
+
} else {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
token,
|
|
546
|
+
deps: deps! || [],
|
|
547
|
+
imports: imports || [],
|
|
548
|
+
construct,
|
|
549
|
+
provider,
|
|
550
|
+
locals
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private registerHooks(provider: Provider, options: Partial<InvokeOptions>) {
|
|
555
|
+
if (provider.hooks) {
|
|
556
|
+
if (provider.scope === ProviderScope.REQUEST) {
|
|
557
|
+
if (options.locals && provider.hooks?.$onDestroy) {
|
|
558
|
+
const {locals} = options;
|
|
559
|
+
|
|
560
|
+
options.locals.hooks.on("$onDestroy", (...args: unknown[]) => {
|
|
561
|
+
return provider.hooks?.$onDestroy?.(locals.get(provider.token), ...args);
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
Object.entries(provider.hooks).forEach(([event, cb]) => {
|
|
569
|
+
const callback = (...args: any[]) => {
|
|
570
|
+
return cb(this.#cache.get(provider.token), ...args);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
$on(event, provider.token, callback);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private setToCache(provider: Provider, instance: any) {
|
|
579
|
+
const set = (instance: any) => {
|
|
580
|
+
this.#cache.set(provider.token, instance);
|
|
581
|
+
provider?.alias && this.alias(provider.token, provider.alias);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
if ("isAsync" in provider && !provider.isAsync() && !isPromise(instance)) {
|
|
585
|
+
set(instance);
|
|
586
|
+
|
|
587
|
+
return instance;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// store promise to lock token in cache
|
|
591
|
+
set(instance);
|
|
592
|
+
|
|
593
|
+
instance = instance.then((instance: any) => {
|
|
594
|
+
set(instance);
|
|
595
|
+
|
|
596
|
+
return instance;
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
return instance;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type ColorTextFn = (text: string) => string;
|
|
2
|
+
|
|
3
|
+
// istanbul ignore next
|
|
4
|
+
const colorIfAllowed = (colorFn: ColorTextFn) => (text: string) =>
|
|
5
|
+
!process.env.NO_COLOR && process.env.NODE_ENV !== "production" ? colorFn(text) : text;
|
|
6
|
+
|
|
7
|
+
const red = colorIfAllowed((text: string) => `\x1B[31m${text}\x1B[39m`);
|
|
8
|
+
const yellow = colorIfAllowed((text: string) => `\x1B[33m${text}\x1B[39m`);
|
|
9
|
+
const green = colorIfAllowed((text: string) => `\x1B[32m${text}\x1B[39m`);
|
|
10
|
+
|
|
11
|
+
export const colors = {red, yellow, green};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {Module} from "../decorators/module.js";
|
|
2
|
+
import {Container} from "../domain/Container.js";
|
|
3
|
+
import {createContainer} from "./createContainer.js";
|
|
4
|
+
|
|
5
|
+
describe("createContainer", () => {
|
|
6
|
+
it("should createContainer", () => {
|
|
7
|
+
expect(createContainer()).toBeInstanceOf(Container);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should createContainer without rootModule", () => {
|
|
11
|
+
@Module()
|
|
12
|
+
class MyModule {}
|
|
13
|
+
|
|
14
|
+
const container = createContainer(MyModule);
|
|
15
|
+
|
|
16
|
+
expect(container.has(MyModule)).toEqual(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {Type} from "@tsed/core";
|
|
2
|
+
|
|
3
|
+
import {Container} from "../domain/Container.js";
|
|
4
|
+
import {GlobalProviders} from "../registries/GlobalProviders.js";
|
|
5
|
+
|
|
6
|
+
export function createContainer(rootModule?: Type<any>) {
|
|
7
|
+
const container = new Container(GlobalProviders.entries());
|
|
8
|
+
|
|
9
|
+
if (rootModule) {
|
|
10
|
+
container.delete(rootModule);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return container;
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {type AbstractType, methodsOf, type Type} from "@tsed/core";
|
|
2
|
+
|
|
3
|
+
export function discoverHooks(token: Type | AbstractType<any>) {
|
|
4
|
+
return methodsOf(token).reduce((hooks, {propertyKey}) => {
|
|
5
|
+
if (String(propertyKey).startsWith("$")) {
|
|
6
|
+
const listener = (instance: any, ...args: any[]) => instance?.[propertyKey](...args);
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
...hooks,
|
|
10
|
+
[propertyKey]: listener
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return hooks;
|
|
14
|
+
}, {} as any);
|
|
15
|
+
}
|