@tsed/di 8.0.1 → 8.0.3

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 (126) hide show
  1. package/package.json +10 -9
  2. package/readme.md +6 -6
  3. package/src/common/constants/constants.ts +5 -0
  4. package/src/common/decorators/autoInjectable.spec.ts +135 -0
  5. package/src/common/decorators/autoInjectable.ts +59 -0
  6. package/src/common/decorators/configuration.spec.ts +45 -0
  7. package/src/common/decorators/configuration.ts +34 -0
  8. package/src/common/decorators/constant.spec.ts +67 -0
  9. package/src/common/decorators/constant.ts +72 -0
  10. package/src/common/decorators/controller.spec.ts +43 -0
  11. package/src/common/decorators/controller.ts +64 -0
  12. package/src/common/decorators/inject.spec.ts +298 -0
  13. package/src/common/decorators/inject.ts +144 -0
  14. package/src/common/decorators/injectable.spec.ts +43 -0
  15. package/src/common/decorators/injectable.ts +30 -0
  16. package/src/common/decorators/intercept.spec.ts +103 -0
  17. package/src/common/decorators/intercept.ts +77 -0
  18. package/src/common/decorators/interceptor.ts +17 -0
  19. package/src/common/decorators/lazyInject.spec.ts +53 -0
  20. package/src/common/decorators/lazyInject.ts +91 -0
  21. package/src/common/decorators/module.ts +52 -0
  22. package/src/common/decorators/opts.ts +51 -0
  23. package/src/common/decorators/overrideProvider.spec.ts +30 -0
  24. package/src/common/decorators/overrideProvider.ts +15 -0
  25. package/src/common/decorators/scope.spec.ts +25 -0
  26. package/src/common/decorators/scope.ts +13 -0
  27. package/src/common/decorators/service.ts +14 -0
  28. package/src/common/decorators/useOpts.spec.ts +90 -0
  29. package/src/common/decorators/useOpts.ts +61 -0
  30. package/src/common/decorators/value.spec.ts +59 -0
  31. package/src/common/decorators/value.ts +60 -0
  32. package/src/common/domain/Container.spec.ts +85 -0
  33. package/src/common/domain/Container.ts +80 -0
  34. package/src/common/domain/ControllerProvider.spec.ts +55 -0
  35. package/src/common/domain/ControllerProvider.ts +48 -0
  36. package/src/common/domain/InjectablePropertyType.ts +7 -0
  37. package/src/common/domain/LocalsContainer.spec.ts +20 -0
  38. package/src/common/domain/LocalsContainer.ts +11 -0
  39. package/src/common/domain/Provider.spec.ts +107 -0
  40. package/src/common/domain/Provider.ts +205 -0
  41. package/src/common/domain/ProviderScope.ts +5 -0
  42. package/src/common/domain/ProviderType.ts +8 -0
  43. package/src/common/errors/InjectionError.spec.ts +17 -0
  44. package/src/common/errors/InjectionError.ts +68 -0
  45. package/src/common/errors/InvalidPropertyTokenError.ts +13 -0
  46. package/src/common/fn/configuration.spec.ts +42 -0
  47. package/src/common/fn/configuration.ts +22 -0
  48. package/src/common/fn/constant.spec.ts +37 -0
  49. package/src/common/fn/constant.ts +7 -0
  50. package/src/common/fn/inject.spec.ts +54 -0
  51. package/src/common/fn/inject.ts +29 -0
  52. package/src/common/fn/injectMany.ts +16 -0
  53. package/src/common/fn/injectable.spec.ts +87 -0
  54. package/src/common/fn/injectable.ts +15 -0
  55. package/src/common/fn/injector.ts +26 -0
  56. package/src/common/fn/lazyInject.spec.ts +22 -0
  57. package/src/common/fn/lazyInject.ts +36 -0
  58. package/src/common/fn/localsContainer.ts +55 -0
  59. package/src/common/fn/refValue.spec.ts +47 -0
  60. package/src/common/fn/refValue.ts +33 -0
  61. package/src/common/index.ts +63 -0
  62. package/src/common/integration/async-factory.spec.ts +60 -0
  63. package/src/common/integration/di.spec.ts +153 -0
  64. package/src/common/integration/imports.spec.ts +39 -0
  65. package/src/common/integration/request.spec.ts +79 -0
  66. package/src/common/integration/singleton.spec.ts +125 -0
  67. package/src/common/interfaces/DIConfigurationOptions.ts +29 -0
  68. package/src/common/interfaces/DILogger.ts +13 -0
  69. package/src/common/interfaces/DILoggerOptions.ts +54 -0
  70. package/src/common/interfaces/ImportTokenProviderOpts.ts +29 -0
  71. package/src/common/interfaces/InterceptorContext.ts +13 -0
  72. package/src/common/interfaces/InterceptorMethods.ts +5 -0
  73. package/src/common/interfaces/InvokeOptions.ts +28 -0
  74. package/src/common/interfaces/OnDestroy.ts +3 -0
  75. package/src/common/interfaces/OnInit.ts +3 -0
  76. package/src/common/interfaces/ProviderOpts.ts +57 -0
  77. package/src/common/interfaces/RegistrySettings.ts +11 -0
  78. package/src/common/interfaces/ResolvedInvokeOptions.ts +14 -0
  79. package/src/common/interfaces/TokenProvider.ts +3 -0
  80. package/src/common/interfaces/TokenRoute.ts +6 -0
  81. package/src/common/registries/GlobalProviders.spec.ts +94 -0
  82. package/src/common/registries/GlobalProviders.ts +131 -0
  83. package/src/common/registries/ProviderRegistry.spec.ts +36 -0
  84. package/src/common/registries/ProviderRegistry.ts +32 -0
  85. package/src/common/services/DIConfiguration.spec.ts +60 -0
  86. package/src/common/services/DIConfiguration.ts +140 -0
  87. package/src/common/services/DILogger.spec.ts +22 -0
  88. package/src/common/services/DILogger.ts +8 -0
  89. package/src/common/services/InjectorService.spec.ts +793 -0
  90. package/src/common/services/InjectorService.ts +601 -0
  91. package/src/common/utils/colors.spec.ts +9 -0
  92. package/src/common/utils/colors.ts +11 -0
  93. package/src/common/utils/createContainer.spec.ts +18 -0
  94. package/src/common/utils/createContainer.ts +14 -0
  95. package/src/common/utils/discoverHooks.ts +15 -0
  96. package/src/common/utils/getConfiguration.spec.ts +40 -0
  97. package/src/common/utils/getConfiguration.ts +20 -0
  98. package/src/common/utils/getConstructorDependencies.ts +9 -0
  99. package/src/common/utils/providerBuilder.ts +81 -0
  100. package/src/index.ts +2 -0
  101. package/src/node/decorators/injectContext.spec.ts +60 -0
  102. package/src/node/decorators/injectContext.ts +29 -0
  103. package/src/node/domain/ContextLogger.spec.ts +277 -0
  104. package/src/node/domain/ContextLogger.ts +137 -0
  105. package/src/node/domain/DIContext.spec.ts +103 -0
  106. package/src/node/domain/DIContext.ts +115 -0
  107. package/src/node/fn/context.ts +24 -0
  108. package/src/node/fn/logger.spec.ts +46 -0
  109. package/src/node/fn/logger.ts +17 -0
  110. package/src/node/index.ts +16 -0
  111. package/src/node/integration/async-factory-invoke.spec.ts +58 -0
  112. package/src/node/integration/inject-context.spec.ts +29 -0
  113. package/src/node/integration/invoke-oninit.spec.ts +25 -0
  114. package/src/node/interfaces/AlterRunInContext.ts +3 -0
  115. package/src/node/services/DILogger.spec.ts +24 -0
  116. package/src/node/services/DILogger.ts +6 -0
  117. package/src/node/services/DITest.spec.ts +109 -0
  118. package/src/node/services/DITest.ts +119 -0
  119. package/src/node/utils/asyncHookContext.spec.ts +147 -0
  120. package/src/node/utils/asyncHookContext.ts +48 -0
  121. package/src/node/utils/attachLogger.spec.ts +14 -0
  122. package/src/node/utils/attachLogger.ts +7 -0
  123. package/src/node/utils/setLoggerConfiguration.spec.ts +43 -0
  124. package/src/node/utils/setLoggerConfiguration.ts +7 -0
  125. package/src/node/utils/setLoggerFormat.ts +33 -0
  126. package/src/node/utils/setLoggerLevel.ts +12 -0
@@ -0,0 +1,68 @@
1
+ import {getConstructorArgNames, isClass, isString, nameOf} from "@tsed/core";
2
+
3
+ import {TokenProvider} from "../interfaces/TokenProvider.js";
4
+ import {colors} from "../utils/colors.js";
5
+
6
+ export class InjectionError extends Error {
7
+ name = "INJECTION_ERROR";
8
+
9
+ public tokens: TokenProvider[] = [];
10
+ public origin: any;
11
+
12
+ constructor(token: TokenProvider, origin?: any) {
13
+ super(isString(origin) ? origin : "");
14
+
15
+ this.tokens = [token];
16
+
17
+ if (origin) {
18
+ if (isString(origin)) {
19
+ this.origin = {
20
+ message: origin,
21
+ stack: this.stack
22
+ };
23
+ } else {
24
+ if (origin.tokens) {
25
+ this.tokens = this.tokens.concat(origin.tokens);
26
+ this.origin = origin.origin;
27
+ } else {
28
+ this.origin = origin;
29
+ this.stack = origin.stack;
30
+ }
31
+ }
32
+ }
33
+
34
+ const originMessage = this.origin ? "\nOrigin: " + this.origin.message : "";
35
+ const tokensMessage = this.tokens.map((token) => nameOf(token)).join(" > ");
36
+
37
+ this.message = `Injection failed on ${tokensMessage}${originMessage}`;
38
+ }
39
+
40
+ static throwInjectorError(token: any, currentDependency: any, error: any) {
41
+ if (currentDependency && isClass(token)) {
42
+ error.message = printDependencyInjectionError(token, {...currentDependency, message: error.message});
43
+ }
44
+
45
+ throw new InjectionError(token, error);
46
+ }
47
+ }
48
+
49
+ function printDependencyInjectionError(token: any, options: {token: any; index: number; deps: any[]; message: string}) {
50
+ let erroredArg = "";
51
+
52
+ const args = getConstructorArgNames(token)
53
+ .map((arg, index) => {
54
+ if (options.index === index) {
55
+ erroredArg = arg;
56
+ arg = colors.red(arg);
57
+ }
58
+
59
+ return `${arg}: ${nameOf(options.deps[index])}`;
60
+ })
61
+ .join(", ");
62
+
63
+ const signature = nameOf(token) + "->constructor(" + args + ")";
64
+ const indexOf = signature.indexOf(erroredArg) - 5;
65
+ const drawline = (indexOf: number) => " ".repeat(indexOf) + colors.red("^" + "‾".repeat(erroredArg.length - 1));
66
+
67
+ return "Unable to inject dependency. " + options.message + "\n\n" + signature + "\n" + (indexOf > -1 ? drawline(indexOf) : "");
68
+ }
@@ -0,0 +1,13 @@
1
+ import {classOf, nameOf} from "@tsed/core";
2
+
3
+ export class InvalidPropertyTokenError extends Error {
4
+ name = "INVALID_TOKEN_ERROR";
5
+
6
+ constructor(target: any, propertyKey: string) {
7
+ super(
8
+ `Object isn't a valid token. Please check the token set on ${nameOf(
9
+ classOf(target)
10
+ )}.${propertyKey}.\n- Check that it is not a circular reference.\n- Check that the token (class or symbol) exists`
11
+ );
12
+ }
13
+ }
@@ -0,0 +1,42 @@
1
+ import {Store} from "@tsed/core";
2
+ import {beforeEach} from "vitest";
3
+
4
+ import {DITest} from "../../node/index.js";
5
+ import {Configuration} from "../decorators/configuration.js";
6
+ import {Injectable} from "../decorators/injectable.js";
7
+ import {Provider} from "../domain/Provider.js";
8
+ import {configuration} from "./configuration.js";
9
+ import {inject} from "./inject.js";
10
+ import {injector} from "./injector.js";
11
+
12
+ @Injectable()
13
+ class Test {
14
+ public config = configuration();
15
+ }
16
+
17
+ describe("configuration()", () => {
18
+ beforeEach(() =>
19
+ DITest.create({
20
+ feature: "feature"
21
+ })
22
+ );
23
+ afterEach(() => DITest.reset());
24
+
25
+ it("should inject configuration", async () => {
26
+ const instance = inject(Test);
27
+
28
+ expect(instance.config).toEqual(injector().settings);
29
+ expect(instance.config.get("feature")).toEqual("feature");
30
+ });
31
+
32
+ it("should declare a new provider with custom configuration", () => {
33
+ @Configuration({})
34
+ class Test {}
35
+
36
+ const provider = new Provider(Test);
37
+ provider.injectable = false;
38
+ provider.configuration = {};
39
+
40
+ expect(Store.from(Test).get("configuration")).toEqual({});
41
+ });
42
+ });
@@ -0,0 +1,22 @@
1
+ import {Store} from "@tsed/core";
2
+
3
+ import type {TokenProvider} from "../interfaces/TokenProvider.js";
4
+ import {DIConfiguration} from "../services/DIConfiguration.js";
5
+ import {injector} from "./injector.js";
6
+
7
+ export function configuration(): TsED.DIConfiguration & DIConfiguration;
8
+ export function configuration(token: TokenProvider): Partial<TsED.Configuration>;
9
+ export function configuration(token: TokenProvider, configuration: Partial<TsED.Configuration>): Partial<TsED.Configuration>;
10
+ export function configuration(token?: TokenProvider, configuration?: Partial<TsED.Configuration>) {
11
+ if (token) {
12
+ const store = Store.from(token);
13
+
14
+ if (configuration) {
15
+ store.set("configuration", configuration);
16
+ }
17
+
18
+ return store.get("configuration", configuration);
19
+ }
20
+
21
+ return injector().settings as TsED.DIConfiguration & DIConfiguration;
22
+ }
@@ -0,0 +1,37 @@
1
+ import {DITest} from "../../node/index.js";
2
+ import {constant} from "../fn/constant.js";
3
+
4
+ describe("constant()", () => {
5
+ beforeEach(() =>
6
+ DITest.create({
7
+ logger: {
8
+ level: "off"
9
+ }
10
+ })
11
+ );
12
+ afterEach(() => DITest.reset());
13
+ it("should inject constant to the property", async () => {
14
+ // WHEN
15
+ class Test {
16
+ test: string = constant("logger.level", "default value");
17
+ }
18
+
19
+ // THEN
20
+
21
+ const test = await DITest.invoke<Test>(Test);
22
+
23
+ expect(test.test).toEqual("off");
24
+ });
25
+ it("should return the default value if expression is undefined", async () => {
26
+ // WHEN
27
+ class Test {
28
+ test: string = constant("logger.test", "default value");
29
+ }
30
+
31
+ // THEN
32
+
33
+ const test = await DITest.invoke<Test>(Test);
34
+
35
+ expect(test.test).toEqual("default value");
36
+ });
37
+ });
@@ -0,0 +1,7 @@
1
+ import {injector} from "./injector.js";
2
+
3
+ export function constant<Type>(expression: string): Type | undefined;
4
+ export function constant<Type>(expression: string, defaultValue: Type | undefined): Type;
5
+ export function constant<Type>(expression: string, defaultValue?: Type | undefined): Type | undefined {
6
+ return injector().settings.get(expression, defaultValue);
7
+ }
@@ -0,0 +1,54 @@
1
+ import {DITest} from "../../node/index.js";
2
+ import {Injectable} from "../decorators/injectable.js";
3
+ import {InjectorService} from "../services/InjectorService.js";
4
+ import {inject} from "./inject.js";
5
+
6
+ @Injectable()
7
+ class ProvidersList extends Map<string, string> {}
8
+
9
+ @Injectable()
10
+ class MyService {
11
+ readonly providersList = inject(ProvidersList);
12
+
13
+ getValue() {
14
+ return this.providersList.get("key");
15
+ }
16
+ }
17
+
18
+ describe("inject()", () => {
19
+ beforeEach(() => DITest.create());
20
+ afterEach(() => DITest.reset());
21
+
22
+ it("should inject the expected provider", async () => {
23
+ class Nested {}
24
+
25
+ class Test {
26
+ readonly injector = inject(InjectorService);
27
+ readonly nested = inject(Nested);
28
+
29
+ constructor() {
30
+ expect(this.injector).toBeInstanceOf(InjectorService);
31
+ expect(this.nested).not.toBeInstanceOf(Nested);
32
+ }
33
+ }
34
+
35
+ await DITest.invoke(Test, [
36
+ {
37
+ token: Nested,
38
+ use: {}
39
+ }
40
+ ]);
41
+ });
42
+ it("should rebuild all dependencies using invoke", async () => {
43
+ const providersList = inject(ProvidersList);
44
+ const myService = inject(MyService);
45
+ providersList.set("key", "value");
46
+
47
+ expect(inject(ProvidersList).get("key")).toEqual("value");
48
+ expect(myService.getValue()).toEqual("value");
49
+
50
+ const newMyService = await DITest.invoke(MyService, []);
51
+ expect(newMyService.getValue()).toEqual(undefined);
52
+ expect(myService.getValue()).toEqual("value");
53
+ });
54
+ });
@@ -0,0 +1,29 @@
1
+ import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
2
+ import type {TokenProvider} from "../interfaces/TokenProvider.js";
3
+ import {injector} from "./injector.js";
4
+ import {invokeOptions, localsContainer} from "./localsContainer.js";
5
+
6
+ /**
7
+ * Inject a provider to another provider.
8
+ *
9
+ * Use this function to inject a custom provider on constructor parameter or property.
10
+ *
11
+ * ```typescript
12
+ * @Injectable()
13
+ * export class MyService {
14
+ * connection = inject(CONNECTION);
15
+ * }
16
+ * ```
17
+ *
18
+ * @param token A token provider or token provider group
19
+ * @param opts
20
+ * @returns {Function}
21
+ * @decorator
22
+ */
23
+ export function inject<T>(token: TokenProvider<T>, opts?: Partial<Pick<InvokeOptions, "useOpts" | "rebuild" | "locals">>): T {
24
+ return injector().resolve(token, {
25
+ ...opts,
26
+ ...invokeOptions(),
27
+ locals: opts?.locals || localsContainer()
28
+ });
29
+ }
@@ -0,0 +1,16 @@
1
+ import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
2
+ import {injector} from "./injector.js";
3
+ import {localsContainer} from "./localsContainer.js";
4
+
5
+ /**
6
+ * Injects multiple instances of a given token using the injector service.
7
+ * @param token - The injection token to resolve
8
+ * @param opts - Optional configuration for the injection
9
+ * @param opts.useOpts - Options for instance creation
10
+ * @param opts.rebuild - Whether to rebuild the instance
11
+ * @param opts.locals - Local container overrides
12
+ * @returns Array of resolved instances
13
+ */
14
+ export function injectMany<T>(token: string | symbol, opts?: Partial<Pick<InvokeOptions, "useOpts" | "rebuild" | "locals">>): T[] {
15
+ return injector().getMany<T>(token, {...opts, locals: opts?.locals || localsContainer()} as InvokeOptions);
16
+ }
@@ -0,0 +1,87 @@
1
+ import {Store} from "@tsed/core";
2
+
3
+ import {DITest, logger} from "../../node/index.js";
4
+ import {ProviderScope} from "../domain/ProviderScope.js";
5
+ import {ProviderType} from "../domain/ProviderType.js";
6
+ import {inject} from "./inject.js";
7
+ import {controller, injectable, interceptor} from "./injectable.js";
8
+
9
+ class Nested {
10
+ get() {
11
+ return "hello";
12
+ }
13
+ }
14
+
15
+ class MyClass {
16
+ nested = inject(Nested);
17
+ logger = logger();
18
+ }
19
+
20
+ class MyController {}
21
+
22
+ injectable(Nested).scope(ProviderScope.SINGLETON).class(Nested);
23
+ injectable(MyClass);
24
+
25
+ describe("injectable", () => {
26
+ describe("injectable()", () => {
27
+ it("should define a singleton scope", async () => {
28
+ const instance = await DITest.invoke(MyClass);
29
+
30
+ expect(instance.nested).toBeInstanceOf(Nested);
31
+ expect(instance.nested.get()).toEqual("hello");
32
+ });
33
+ it("should define class with scope", async () => {
34
+ injectable(MyClass).scope(ProviderScope.SINGLETON).class(MyClass).store().set("test", "test");
35
+
36
+ expect(Store.from(MyClass).get("test")).toEqual("test");
37
+ });
38
+ it("should create a factory", async () => {
39
+ const builder = injectable(Symbol.for("Test")).factory(() => "test");
40
+ const provider = builder.inspect();
41
+
42
+ expect(provider.type).toEqual(ProviderType.PROVIDER);
43
+ expect(builder.token()).toEqual(Symbol.for("Test"));
44
+ });
45
+ it("should create an async factory", async () => {
46
+ const builder = injectable(Symbol.for("Test")).asyncFactory(() => Promise.resolve("test"));
47
+ const provider = builder.inspect();
48
+
49
+ expect(provider.type).toEqual(ProviderType.PROVIDER);
50
+ expect(builder.token()).toEqual(Symbol.for("Test"));
51
+ });
52
+ it("should create a value", async () => {
53
+ const builder = injectable(Symbol.for("Test")).value({
54
+ test: "test"
55
+ });
56
+ const provider = builder.inspect();
57
+
58
+ expect(provider.type).toEqual(ProviderType.VALUE);
59
+ expect(builder.token()).toEqual(Symbol.for("Test"));
60
+ });
61
+ });
62
+
63
+ describe("controller()", () => {
64
+ it("should define a singleton scope", async () => {
65
+ const builder = controller(MyController)
66
+ .path("/my-controller")
67
+ .scope(ProviderScope.REQUEST)
68
+ .middlewares({
69
+ use: [() => {}]
70
+ });
71
+
72
+ const provider = builder.inspect();
73
+
74
+ expect(provider.type).toEqual(ProviderType.CONTROLLER);
75
+ expect(provider.scope).toEqual(ProviderScope.REQUEST);
76
+ });
77
+ });
78
+
79
+ describe("interceptor()", () => {
80
+ it("should define a singleton scope", async () => {
81
+ const builder = interceptor(MyController);
82
+ const provider = builder.inspect();
83
+
84
+ expect(provider.type).toEqual(ProviderType.INTERCEPTOR);
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,15 @@
1
+ import {ControllerProvider} from "../domain/ControllerProvider.js";
2
+ import type {Provider} from "../domain/Provider.js";
3
+ import {ProviderType} from "../domain/ProviderType.js";
4
+ import {providerBuilder} from "../utils/providerBuilder.js";
5
+
6
+ type PickedProps = "scope" | "path" | "alias" | "hooks" | "deps" | "imports" | "configuration" | "priority";
7
+
8
+ const Props = ["type", "scope", "path", "alias", "hooks", "deps", "imports", "configuration", "priority"];
9
+ export const injectable = providerBuilder<Provider, PickedProps | "type">(Props);
10
+ export const interceptor = providerBuilder<Provider, PickedProps | "type">(Props, {
11
+ type: ProviderType.INTERCEPTOR
12
+ });
13
+ export const controller = providerBuilder<ControllerProvider, PickedProps | "children" | "middlewares">([...Props, "middlewares"], {
14
+ type: ProviderType.CONTROLLER
15
+ });
@@ -0,0 +1,26 @@
1
+ import {InjectorService} from "../services/InjectorService.js";
2
+
3
+ let globalInjector: InjectorService = new InjectorService();
4
+
5
+ /**
6
+ * Return the existing injector service.
7
+ *
8
+ * Example:
9
+ *
10
+ * ```typescript
11
+ * import {injector, Injectable} from "@tsed/di";
12
+ *
13
+ * @Injectable()
14
+ * class MyService {
15
+ * injector = injector();
16
+ * }
17
+ * ```
18
+ */
19
+ export function injector(): InjectorService {
20
+ return globalInjector;
21
+ }
22
+
23
+ export async function destroyInjector() {
24
+ await globalInjector.destroy();
25
+ globalInjector = new InjectorService();
26
+ }
@@ -0,0 +1,22 @@
1
+ import {afterEach, beforeEach} from "vitest";
2
+
3
+ import {DITest} from "../../node/index.js";
4
+ import {lazyInject, optionalLazyInject} from "./lazyInject.js";
5
+
6
+ describe("lazyInject", () => {
7
+ beforeEach(() => DITest.create());
8
+ afterEach(() => DITest.reset());
9
+
10
+ it("should lazy load module", async () => {
11
+ const service = await lazyInject(() => import("./__mock__/lazy.import.module.js"));
12
+
13
+ expect(service).toBeDefined();
14
+ expect(service.called).toBeTruthy();
15
+ });
16
+
17
+ it("should optionally lazy load module", async () => {
18
+ const service = await optionalLazyInject(() => import("./__mock__/lazy.import.module.js"));
19
+
20
+ expect(service).toBeDefined();
21
+ });
22
+ });
@@ -0,0 +1,36 @@
1
+ import {isFunction} from "@tsed/core";
2
+
3
+ import type {TokenProvider} from "../interfaces/TokenProvider.js";
4
+ import {inject} from "./inject.js";
5
+ import {injector} from "./injector.js";
6
+
7
+ /**
8
+ * Lazy load a provider from his package and invoke only when the provider is used.
9
+ */
10
+ export async function lazyInject<Token>(factory: () => Promise<{default: TokenProvider<Token>}>): Promise<Token> {
11
+ const {default: token} = await factory();
12
+
13
+ if (!injector().has(token)) {
14
+ const instance = await inject(token);
15
+
16
+ const instanceWithHook = instance as unknown as {$onInit?: () => Promise<void>};
17
+
18
+ if ("$onInit" in instanceWithHook && isFunction(instanceWithHook.$onInit)) {
19
+ await instanceWithHook.$onInit();
20
+ }
21
+ }
22
+
23
+ return injector().get(token) as unknown as Promise<Token>;
24
+ }
25
+
26
+ export async function optionalLazyInject<Token>(
27
+ factory: () => Promise<{
28
+ default: TokenProvider;
29
+ }>
30
+ ): Promise<Token | undefined> {
31
+ try {
32
+ return await lazyInject(factory);
33
+ } catch (er) {
34
+ return undefined;
35
+ }
36
+ }
@@ -0,0 +1,55 @@
1
+ import {LocalsContainer} from "../domain/LocalsContainer.js";
2
+ import type {UseImportTokenProviderOpts} from "../interfaces/ImportTokenProviderOpts.js";
3
+ import {InjectorService} from "../services/InjectorService.js";
4
+ import {injector} from "./injector.js";
5
+
6
+ let globalLocals: LocalsContainer | undefined;
7
+ let globalInvOpts: any = {};
8
+ const stagedLocals: LocalsContainer[] = [];
9
+
10
+ /**
11
+ * Get the locals container initiated by DITest or .bootstrap() method.
12
+ */
13
+ export function localsContainer({
14
+ providers,
15
+ rebuild
16
+ }: {
17
+ providers?: UseImportTokenProviderOpts[];
18
+ rebuild?: boolean;
19
+ } = {}) {
20
+ if (!globalLocals || providers) {
21
+ globalLocals = new LocalsContainer();
22
+
23
+ if (providers) {
24
+ providers.forEach((p) => {
25
+ globalLocals!.set(p.token, p.use);
26
+ });
27
+
28
+ globalLocals.set(InjectorService, injector());
29
+ }
30
+
31
+ if (rebuild) {
32
+ globalInvOpts.rebuild = rebuild;
33
+ }
34
+ }
35
+
36
+ return globalLocals;
37
+ }
38
+
39
+ export function invokeOptions() {
40
+ return {...globalInvOpts};
41
+ }
42
+
43
+ /**
44
+ * Reset the locals container.
45
+ */
46
+ export function detachLocalsContainer() {
47
+ globalLocals && stagedLocals.push(globalLocals);
48
+ globalLocals = undefined;
49
+ globalInvOpts = {};
50
+ }
51
+
52
+ export function cleanAllLocalsContainer() {
53
+ detachLocalsContainer();
54
+ stagedLocals.map((item) => item.clear());
55
+ }
@@ -0,0 +1,47 @@
1
+ import {DITest} from "../../node/index.js";
2
+ import {configuration} from "./configuration.js";
3
+ import {refValue} from "./refValue.js";
4
+
5
+ describe("refValue()", () => {
6
+ beforeEach(() =>
7
+ DITest.create({
8
+ logger: {
9
+ level: "off"
10
+ }
11
+ })
12
+ );
13
+ afterEach(() => DITest.reset());
14
+ describe("when decorator is used as property decorator", () => {
15
+ it("should create a getter", async () => {
16
+ // WHEN
17
+ class Test {
18
+ test = refValue("logger.level", "default value");
19
+ }
20
+
21
+ // THEN
22
+
23
+ const test = await DITest.invoke<Test>(Test);
24
+
25
+ expect(test.test.value).toEqual("off");
26
+
27
+ test.test.value = "test";
28
+
29
+ expect(test.test.value).toEqual("test");
30
+ });
31
+ it("should create a getter with default value", async () => {
32
+ expect(configuration().get("logger.test")).toEqual(undefined);
33
+
34
+ // WHEN
35
+ class Test {
36
+ test = refValue("logger.test", "default value");
37
+ }
38
+
39
+ // THEN
40
+
41
+ const test = await DITest.invoke<Test>(Test);
42
+
43
+ expect(test.test.value).toEqual("default value");
44
+ expect(configuration().get("logger.test")).toEqual(undefined);
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,33 @@
1
+ import {injector} from "./injector.js";
2
+
3
+ /**
4
+ * Get a value from the `injector.settings`.
5
+ *
6
+ * ## Example
7
+ *
8
+ * ```ts
9
+ * import {refValue, Injectable} from "@tsed/di";
10
+ *
11
+ * @Injectable()
12
+ * class Test {
13
+ * test = refValue("logger.level", "default value");
14
+ *
15
+ * constructor() {
16
+ * console.log(this.test.value); // "off"
17
+ * }
18
+ * }
19
+ *
20
+ * @param expression The expression to get the value from the `injector.settings`.
21
+ */
22
+ export function refValue<Type>(expression: string): {value: Type | undefined};
23
+ export function refValue<Type>(expression: string, defaultValue: Type | undefined): {value: Type};
24
+ export function refValue<Type>(expression: string, defaultValue?: Type | undefined): {value: Type | undefined} {
25
+ return Object.freeze({
26
+ get value() {
27
+ return injector().settings.get(expression, defaultValue);
28
+ },
29
+ set value(value: Type) {
30
+ injector().settings.set(expression, value);
31
+ }
32
+ });
33
+ }