@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.
Files changed (125) hide show
  1. package/package.json +10 -9
  2. package/src/common/constants/constants.ts +5 -0
  3. package/src/common/decorators/autoInjectable.spec.ts +135 -0
  4. package/src/common/decorators/autoInjectable.ts +59 -0
  5. package/src/common/decorators/configuration.spec.ts +45 -0
  6. package/src/common/decorators/configuration.ts +34 -0
  7. package/src/common/decorators/constant.spec.ts +67 -0
  8. package/src/common/decorators/constant.ts +72 -0
  9. package/src/common/decorators/controller.spec.ts +43 -0
  10. package/src/common/decorators/controller.ts +64 -0
  11. package/src/common/decorators/inject.spec.ts +298 -0
  12. package/src/common/decorators/inject.ts +144 -0
  13. package/src/common/decorators/injectable.spec.ts +43 -0
  14. package/src/common/decorators/injectable.ts +30 -0
  15. package/src/common/decorators/intercept.spec.ts +103 -0
  16. package/src/common/decorators/intercept.ts +77 -0
  17. package/src/common/decorators/interceptor.ts +17 -0
  18. package/src/common/decorators/lazyInject.spec.ts +53 -0
  19. package/src/common/decorators/lazyInject.ts +91 -0
  20. package/src/common/decorators/module.ts +52 -0
  21. package/src/common/decorators/opts.ts +51 -0
  22. package/src/common/decorators/overrideProvider.spec.ts +30 -0
  23. package/src/common/decorators/overrideProvider.ts +15 -0
  24. package/src/common/decorators/scope.spec.ts +25 -0
  25. package/src/common/decorators/scope.ts +13 -0
  26. package/src/common/decorators/service.ts +14 -0
  27. package/src/common/decorators/useOpts.spec.ts +90 -0
  28. package/src/common/decorators/useOpts.ts +61 -0
  29. package/src/common/decorators/value.spec.ts +59 -0
  30. package/src/common/decorators/value.ts +60 -0
  31. package/src/common/domain/Container.spec.ts +85 -0
  32. package/src/common/domain/Container.ts +80 -0
  33. package/src/common/domain/ControllerProvider.spec.ts +55 -0
  34. package/src/common/domain/ControllerProvider.ts +48 -0
  35. package/src/common/domain/InjectablePropertyType.ts +7 -0
  36. package/src/common/domain/LocalsContainer.spec.ts +20 -0
  37. package/src/common/domain/LocalsContainer.ts +11 -0
  38. package/src/common/domain/Provider.spec.ts +107 -0
  39. package/src/common/domain/Provider.ts +205 -0
  40. package/src/common/domain/ProviderScope.ts +5 -0
  41. package/src/common/domain/ProviderType.ts +8 -0
  42. package/src/common/errors/InjectionError.spec.ts +17 -0
  43. package/src/common/errors/InjectionError.ts +68 -0
  44. package/src/common/errors/InvalidPropertyTokenError.ts +13 -0
  45. package/src/common/fn/configuration.spec.ts +42 -0
  46. package/src/common/fn/configuration.ts +22 -0
  47. package/src/common/fn/constant.spec.ts +37 -0
  48. package/src/common/fn/constant.ts +7 -0
  49. package/src/common/fn/inject.spec.ts +54 -0
  50. package/src/common/fn/inject.ts +29 -0
  51. package/src/common/fn/injectMany.ts +16 -0
  52. package/src/common/fn/injectable.spec.ts +87 -0
  53. package/src/common/fn/injectable.ts +15 -0
  54. package/src/common/fn/injector.ts +26 -0
  55. package/src/common/fn/lazyInject.spec.ts +22 -0
  56. package/src/common/fn/lazyInject.ts +36 -0
  57. package/src/common/fn/localsContainer.ts +55 -0
  58. package/src/common/fn/refValue.spec.ts +47 -0
  59. package/src/common/fn/refValue.ts +33 -0
  60. package/src/common/index.ts +63 -0
  61. package/src/common/integration/async-factory.spec.ts +60 -0
  62. package/src/common/integration/di.spec.ts +153 -0
  63. package/src/common/integration/imports.spec.ts +39 -0
  64. package/src/common/integration/request.spec.ts +79 -0
  65. package/src/common/integration/singleton.spec.ts +125 -0
  66. package/src/common/interfaces/DIConfigurationOptions.ts +29 -0
  67. package/src/common/interfaces/DILogger.ts +13 -0
  68. package/src/common/interfaces/DILoggerOptions.ts +54 -0
  69. package/src/common/interfaces/ImportTokenProviderOpts.ts +29 -0
  70. package/src/common/interfaces/InterceptorContext.ts +13 -0
  71. package/src/common/interfaces/InterceptorMethods.ts +5 -0
  72. package/src/common/interfaces/InvokeOptions.ts +28 -0
  73. package/src/common/interfaces/OnDestroy.ts +3 -0
  74. package/src/common/interfaces/OnInit.ts +3 -0
  75. package/src/common/interfaces/ProviderOpts.ts +57 -0
  76. package/src/common/interfaces/RegistrySettings.ts +11 -0
  77. package/src/common/interfaces/ResolvedInvokeOptions.ts +14 -0
  78. package/src/common/interfaces/TokenProvider.ts +3 -0
  79. package/src/common/interfaces/TokenRoute.ts +6 -0
  80. package/src/common/registries/GlobalProviders.spec.ts +94 -0
  81. package/src/common/registries/GlobalProviders.ts +131 -0
  82. package/src/common/registries/ProviderRegistry.spec.ts +36 -0
  83. package/src/common/registries/ProviderRegistry.ts +32 -0
  84. package/src/common/services/DIConfiguration.spec.ts +60 -0
  85. package/src/common/services/DIConfiguration.ts +140 -0
  86. package/src/common/services/DILogger.spec.ts +22 -0
  87. package/src/common/services/DILogger.ts +8 -0
  88. package/src/common/services/InjectorService.spec.ts +793 -0
  89. package/src/common/services/InjectorService.ts +601 -0
  90. package/src/common/utils/colors.spec.ts +9 -0
  91. package/src/common/utils/colors.ts +11 -0
  92. package/src/common/utils/createContainer.spec.ts +18 -0
  93. package/src/common/utils/createContainer.ts +14 -0
  94. package/src/common/utils/discoverHooks.ts +15 -0
  95. package/src/common/utils/getConfiguration.spec.ts +40 -0
  96. package/src/common/utils/getConfiguration.ts +20 -0
  97. package/src/common/utils/getConstructorDependencies.ts +9 -0
  98. package/src/common/utils/providerBuilder.ts +81 -0
  99. package/src/index.ts +2 -0
  100. package/src/node/decorators/injectContext.spec.ts +60 -0
  101. package/src/node/decorators/injectContext.ts +29 -0
  102. package/src/node/domain/ContextLogger.spec.ts +277 -0
  103. package/src/node/domain/ContextLogger.ts +137 -0
  104. package/src/node/domain/DIContext.spec.ts +103 -0
  105. package/src/node/domain/DIContext.ts +115 -0
  106. package/src/node/fn/context.ts +24 -0
  107. package/src/node/fn/logger.spec.ts +46 -0
  108. package/src/node/fn/logger.ts +17 -0
  109. package/src/node/index.ts +16 -0
  110. package/src/node/integration/async-factory-invoke.spec.ts +58 -0
  111. package/src/node/integration/inject-context.spec.ts +29 -0
  112. package/src/node/integration/invoke-oninit.spec.ts +25 -0
  113. package/src/node/interfaces/AlterRunInContext.ts +3 -0
  114. package/src/node/services/DILogger.spec.ts +24 -0
  115. package/src/node/services/DILogger.ts +6 -0
  116. package/src/node/services/DITest.spec.ts +109 -0
  117. package/src/node/services/DITest.ts +119 -0
  118. package/src/node/utils/asyncHookContext.spec.ts +147 -0
  119. package/src/node/utils/asyncHookContext.ts +48 -0
  120. package/src/node/utils/attachLogger.spec.ts +14 -0
  121. package/src/node/utils/attachLogger.ts +7 -0
  122. package/src/node/utils/setLoggerConfiguration.spec.ts +43 -0
  123. package/src/node/utils/setLoggerConfiguration.ts +7 -0
  124. package/src/node/utils/setLoggerFormat.ts +33 -0
  125. 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,9 @@
1
+ import {colors} from "./colors.js";
2
+
3
+ describe("colors", () => {
4
+ it("should call colors", () => {
5
+ colors.green("text");
6
+ colors.yellow("text");
7
+ colors.red("text");
8
+ });
9
+ });
@@ -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
+ }