@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
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tsed/di",
3
3
  "description": "DI module for Ts.ED Framework",
4
4
  "type": "module",
5
- "version": "8.0.1",
5
+ "version": "8.0.3",
6
6
  "source": "./src/index.ts",
7
7
  "main": "./lib/esm/index.js",
8
8
  "module": "./lib/esm/index.js",
@@ -10,6 +10,7 @@
10
10
  "browser": "./lib/browser/di.umd.min.js",
11
11
  "exports": {
12
12
  ".": {
13
+ "@tsed/source": "./src/index.ts",
13
14
  "types": "./lib/types/index.d.ts",
14
15
  "browser": "./lib/browser/di.umd.min.js",
15
16
  "import": "./lib/esm/index.js",
@@ -29,12 +30,12 @@
29
30
  "uuid": "^10.0.0"
30
31
  },
31
32
  "devDependencies": {
32
- "@tsed/barrels": "8.0.1",
33
- "@tsed/core": "8.0.1",
34
- "@tsed/hooks": "8.0.1",
33
+ "@tsed/barrels": "8.0.3",
34
+ "@tsed/core": "8.0.3",
35
+ "@tsed/hooks": "8.0.3",
35
36
  "@tsed/logger": "^6.7.8",
36
- "@tsed/schema": "8.0.1",
37
- "@tsed/typescript": "8.0.1",
37
+ "@tsed/schema": "8.0.3",
38
+ "@tsed/typescript": "8.0.3",
38
39
  "cross-env": "7.0.3",
39
40
  "eslint": "9.12.0",
40
41
  "typescript": "5.4.5",
@@ -42,10 +43,10 @@
42
43
  "webpack": "^5.75.0"
43
44
  },
44
45
  "peerDependencies": {
45
- "@tsed/core": "8.0.1",
46
- "@tsed/hooks": "8.0.1",
46
+ "@tsed/core": "8.0.3",
47
+ "@tsed/hooks": "8.0.3",
47
48
  "@tsed/logger": ">=6.7.5",
48
- "@tsed/schema": "8.0.1"
49
+ "@tsed/schema": "8.0.3"
49
50
  },
50
51
  "peerDependenciesMeta": {
51
52
  "@tsed/core": {
package/readme.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p style="text-align: center" align="center">
2
- <a href="https://tsed.io" target="_blank"><img src="https://tsed.io/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
2
+ <a href="https://tsed.dev" target="_blank"><img src="https://tsed.dev/tsed-og.png" width="200" alt="Ts.ED logo"/></a>
3
3
  </p>
4
4
 
5
5
  <div align="center">
@@ -16,9 +16,9 @@
16
16
  </div>
17
17
 
18
18
  <div align="center">
19
- <a href="https://tsed.io/">Website</a>
19
+ <a href="https://tsed.dev/">Website</a>
20
20
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
21
- <a href="https://tsed.io/getting-started/">Getting started</a>
21
+ <a href="https://tsed.dev/getting-started/">Getting started</a>
22
22
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
23
23
  <a href="https://api.tsed.io/rest/slack/tsedio/tsed">Slack</a>
24
24
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
@@ -27,7 +27,7 @@
27
27
 
28
28
  <hr />
29
29
 
30
- A package of Ts.ED framework. See website: https://tsed.io/
30
+ A package of Ts.ED framework. See website: https://tsed.dev/
31
31
 
32
32
  # Installation
33
33
 
@@ -150,11 +150,11 @@ To organize your code Ts.ED DI provide different kind of providers:
150
150
  - Interceptor can be declared with `@Interceptor`,
151
151
  - Factory and Value can be declared with `registerProvider` and `registerValue`.
152
152
 
153
- See more details on our documentation https://tsed.io/providers.html
153
+ See more details on our documentation https://tsed.dev/providers.html
154
154
 
155
155
  ## Contributors
156
156
 
157
- Please read [contributing guidelines here](https://tsed.io/contributing.html)
157
+ Please read [contributing guidelines here](https://tsed.dev/contributing.html)
158
158
 
159
159
  <a href="https://github.com/tsedio/tsed/graphs/contributors"><img src="https://opencollective.com/tsed/contributors.svg?width=890" /></a>
160
160
 
@@ -0,0 +1,5 @@
1
+ export const DI_INVOKE_OPTIONS = Symbol("DI_INVOKE_OPTIONS");
2
+ export const DI_INJECTABLE_PROPS = Symbol("DI_INJECTABLE_PROPS");
3
+ export const DI_USE_OPTIONS = "DI_USE_OPTIONS";
4
+ export const DI_USE_PARAM_OPTIONS = "DI_USE_PARAM_OPTIONS";
5
+ export const DI_INTERCEPTOR_OPTIONS = "DI_INTERCEPTOR_OPTIONS";
@@ -0,0 +1,135 @@
1
+ import {Logger} from "@tsed/logger";
2
+ import {beforeEach} from "vitest";
3
+
4
+ import {DITest} from "../../node/index.js";
5
+ import {registerProvider} from "../registries/ProviderRegistry.js";
6
+ import {InjectorService} from "../services/InjectorService.js";
7
+ import {AutoInjectable} from "./autoInjectable.js";
8
+ import {Inject} from "./inject.js";
9
+ import {Injectable} from "./injectable.js";
10
+
11
+ const TOKEN_GROUPS = Symbol.for("groups:1");
12
+
13
+ interface InterfaceGroup {
14
+ type: string;
15
+ }
16
+
17
+ @Injectable({
18
+ type: TOKEN_GROUPS
19
+ })
20
+ class MyService1 implements InterfaceGroup {
21
+ readonly type: string = "service1";
22
+
23
+ constructor(@Inject(InjectorService) readonly injector: any) {}
24
+ }
25
+
26
+ @Injectable({
27
+ type: TOKEN_GROUPS
28
+ })
29
+ class MyService2 implements InterfaceGroup {
30
+ readonly type: string = "service2";
31
+
32
+ constructor(@Inject(InjectorService) readonly injector: any) {}
33
+ }
34
+
35
+ const TokenAsync = Symbol.for("MyService2");
36
+
37
+ registerProvider({
38
+ token: TokenAsync,
39
+ type: TOKEN_GROUPS,
40
+ deps: [],
41
+ useAsyncFactory() {
42
+ return Promise.resolve({
43
+ type: "async"
44
+ });
45
+ }
46
+ });
47
+
48
+ describe("AutoInjectable", () => {
49
+ describe("when the instance is created during an injection context", () => {
50
+ beforeEach(() => DITest.create());
51
+ afterEach(() => DITest.reset());
52
+ it("should return a class that extends the original class", () => {
53
+ @AutoInjectable()
54
+ class Test {
55
+ @Inject(Logger)
56
+ logger: Logger;
57
+
58
+ foo() {
59
+ this.logger.info("test");
60
+ }
61
+ }
62
+
63
+ const test = new Test();
64
+ const test2 = new Test();
65
+
66
+ expect(test).toBeInstanceOf(Test);
67
+ expect(test2).toBeInstanceOf(Test);
68
+ expect(test).not.toBe(test2);
69
+
70
+ vi.spyOn(test.logger, "info").mockResolvedValue(undefined as never);
71
+
72
+ test.foo();
73
+
74
+ expect(test.logger.info).toHaveBeenCalledWith("test");
75
+ });
76
+ it("should return a class that extends the original class (with additional args)", () => {
77
+ @AutoInjectable()
78
+ class Test {
79
+ @Inject(Logger)
80
+ logger: Logger;
81
+
82
+ private value: string;
83
+
84
+ constructor(initialValue: string, @Inject(InjectorService) injector?: InjectorService) {
85
+ this.value = initialValue;
86
+ expect(injector).toBeInstanceOf(InjectorService);
87
+ }
88
+
89
+ foo() {
90
+ this.logger.info("test_" + this.value);
91
+ }
92
+ }
93
+
94
+ const test = new Test("test");
95
+
96
+ vi.spyOn(test.logger, "info").mockResolvedValue(undefined as never);
97
+
98
+ test.foo();
99
+
100
+ expect(test.logger.info).toHaveBeenCalledWith("test_test");
101
+ });
102
+ it("should return a class that extends the original class (with inject many)", () => {
103
+ @AutoInjectable()
104
+ class Test {
105
+ @Inject(Logger)
106
+ logger: Logger;
107
+ instances?: InterfaceGroup[];
108
+ private value: string;
109
+
110
+ constructor(initialValue: string, @Inject(TOKEN_GROUPS) instances?: InterfaceGroup[]) {
111
+ this.value = initialValue;
112
+ this.instances = instances;
113
+ }
114
+ }
115
+
116
+ expect(new Test("test").instances).toHaveLength(3);
117
+ });
118
+ it("should return a class that extends the original class (with 3 arguments)", () => {
119
+ @AutoInjectable()
120
+ class Test {
121
+ constructor(
122
+ public items: string[],
123
+ public group: string,
124
+ @Inject() logger?: Logger
125
+ ) {
126
+ expect(items).toEqual(["item1", "item2", "item3"]);
127
+ expect(group).toBe("group");
128
+ expect(logger).toBeInstanceOf(Logger);
129
+ }
130
+ }
131
+
132
+ new Test(["item1", "item2", "item3"], "group");
133
+ });
134
+ });
135
+ });
@@ -0,0 +1,59 @@
1
+ import {isArray, type Type} from "@tsed/core";
2
+
3
+ import {LocalsContainer} from "../domain/LocalsContainer.js";
4
+ import {injector} from "../fn/injector.js";
5
+ import type {TokenProvider} from "../interfaces/TokenProvider.js";
6
+ import {getConstructorDependencies} from "../utils/getConstructorDependencies.js";
7
+
8
+ function resolveAutoInjectableArgs(token: Type, args: unknown[]) {
9
+ const inj = injector();
10
+ const locals = new LocalsContainer();
11
+ const deps: TokenProvider[] = getConstructorDependencies(token);
12
+ const list: any[] = [];
13
+ const length = Math.max(deps.length, args.length);
14
+
15
+ for (let i = 0; i < length; i++) {
16
+ if (args[i] !== undefined) {
17
+ list.push(args[i]);
18
+ } else {
19
+ const value = deps[i];
20
+ const instance = isArray(value) ? inj.getMany(value[0], {locals, parent: token}) : inj.invoke(value, {locals, parent: token});
21
+
22
+ list.push(instance);
23
+ }
24
+ }
25
+
26
+ return list;
27
+ }
28
+
29
+ /**
30
+ * AutoInjectable decorator is used to automatically inject dependencies in the constructor.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * @AutoInjectable()
35
+ * class UserService {
36
+ * constructor(
37
+ * private database: Database, // automatically injected
38
+ * customConfig?: Config // can be passed manually
39
+ * ) {}
40
+ * }
41
+ * ```
42
+ *
43
+ * @description
44
+ * When applied to a class, this decorator:
45
+ * - Automatically resolves and injects dependencies based on constructor parameter types
46
+ * - Preserves the ability to manually pass arguments which override automatic injection
47
+ *
48
+ * @decorator
49
+ * @returns {ClassDecorator} A class decorator that enables automatic dependency injection
50
+ */
51
+ export function AutoInjectable() {
52
+ return <T extends {new (...args: any[]): NonNullable<unknown>}>(constr: T): T => {
53
+ return class AutoInjectable extends constr {
54
+ constructor(...args: any[]) {
55
+ super(...resolveAutoInjectableArgs(constr, args));
56
+ }
57
+ } as unknown as T;
58
+ };
59
+ }
@@ -0,0 +1,45 @@
1
+ import {Store} from "@tsed/core";
2
+ import {afterEach} from "vitest";
3
+
4
+ import {Container} from "../domain/Container.js";
5
+ import {Provider} from "../domain/Provider.js";
6
+ import {destroyInjector, injector} from "../fn/injector.js";
7
+ import {GlobalProviders} from "../registries/GlobalProviders.js";
8
+ import {InjectorService} from "../services/InjectorService.js";
9
+ import {Configuration} from "./configuration.js";
10
+ import {Injectable} from "./injectable.js";
11
+
12
+ describe("@Configuration", () => {
13
+ afterEach(() => destroyInjector());
14
+ it("should declare a new provider with custom configuration", () => {
15
+ @Configuration({})
16
+ class Test {}
17
+
18
+ const provider = new Provider(Test);
19
+ provider.injectable = false;
20
+ provider.configuration = {};
21
+
22
+ expect(Store.from(Test).get("configuration")).toEqual({});
23
+ });
24
+
25
+ it("should inject configuration", async () => {
26
+ @Configuration({
27
+ feature: "feature"
28
+ })
29
+ @Injectable()
30
+ class Test {
31
+ constructor(@Configuration() public config: Configuration) {}
32
+ }
33
+
34
+ const container = new Container();
35
+
36
+ injector().setProvider(Test, GlobalProviders.get(Test)!.clone());
37
+
38
+ await injector().load(container);
39
+
40
+ const instance = injector().invoke<Test>(Test);
41
+
42
+ expect(instance.config).toEqual(injector().settings);
43
+ expect(instance.config.get("feature")).toEqual("feature");
44
+ });
45
+ });
@@ -0,0 +1,34 @@
1
+ import {DecoratorParameters, decoratorTypeOf, DecoratorTypes} from "@tsed/core";
2
+
3
+ import {configuration} from "../fn/configuration.js";
4
+ import {injectable} from "../fn/injectable.js";
5
+ import {injector} from "../fn/injector.js";
6
+ import {CONFIGURATION, DIConfiguration} from "../services/DIConfiguration.js";
7
+ import {Inject} from "./inject.js";
8
+
9
+ /**
10
+ * Get or set Configuration on a class.
11
+ *
12
+ * @decorator
13
+ */
14
+ export function Configuration(): any;
15
+ export function Configuration(settings: Partial<TsED.Configuration>): ClassDecorator;
16
+ export function Configuration(settings: Partial<TsED.Configuration> = {}): Function {
17
+ return (...args: DecoratorParameters) => {
18
+ switch (decoratorTypeOf(args)) {
19
+ case DecoratorTypes.CLASS:
20
+ configuration(args[0], settings);
21
+
22
+ break;
23
+ default:
24
+ case DecoratorTypes.PARAM_CTOR:
25
+ return Inject(DIConfiguration)(args[0], args[1], args[2] as number);
26
+ }
27
+ };
28
+ }
29
+
30
+ export type Configuration = TsED.DIConfiguration & DIConfiguration;
31
+
32
+ // To maintain compatibility with the previous implementation, we need to declare Configuration as
33
+ // injectable token.
34
+ injectable(Configuration).factory(() => injector().settings);
@@ -0,0 +1,67 @@
1
+ import {DITest} from "../../node/index.js";
2
+ import {Constant} from "./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 create a getter", async () => {
14
+ // WHEN
15
+ class Test {
16
+ @Constant("logger.level", "default value")
17
+ test: string;
18
+ }
19
+
20
+ // THEN
21
+
22
+ const test = await DITest.invoke<Test>(Test);
23
+
24
+ expect(test.test).toEqual("off");
25
+ });
26
+ it("should create a getter with default value", async () => {
27
+ // WHEN
28
+ class Test {
29
+ @Constant("logger.test", "default value")
30
+ test: string;
31
+ }
32
+
33
+ // THEN
34
+
35
+ const test = await DITest.invoke<Test>(Test);
36
+
37
+ expect(test.test).toEqual("default value");
38
+ });
39
+ it("shouldn't be possible to modify injected value from injector.settings", async () => {
40
+ // WHEN
41
+ class Test {
42
+ @Constant("logger.level")
43
+ test: string;
44
+ }
45
+
46
+ // THEN
47
+
48
+ const test = await DITest.invoke<Test>(Test);
49
+
50
+ test.test = "new value";
51
+
52
+ expect(test.test).toEqual("off");
53
+ });
54
+ it("should create a getter with native default value", async () => {
55
+ // WHEN
56
+ class Test {
57
+ @Constant("logger.test")
58
+ test: string = "default prop";
59
+ }
60
+
61
+ // THEN
62
+
63
+ const test = await DITest.invoke<Test>(Test);
64
+
65
+ expect(test.test).toEqual("default prop");
66
+ });
67
+ });
@@ -0,0 +1,72 @@
1
+ import {catchError, deepClone} from "@tsed/core";
2
+
3
+ import {constant} from "../fn/constant.js";
4
+
5
+ export function bindConstant(target: any, propertyKey: string | symbol, expression: string, defaultValue?: any) {
6
+ const symbol = Symbol();
7
+
8
+ catchError(() => Reflect.deleteProperty(target, propertyKey));
9
+ Reflect.defineProperty(target, propertyKey, {
10
+ get() {
11
+ if (this[symbol] !== undefined) {
12
+ return this[symbol];
13
+ }
14
+
15
+ const value = constant(expression, defaultValue);
16
+
17
+ this[symbol] = Object.freeze(deepClone(value));
18
+
19
+ return this[symbol];
20
+ },
21
+ set(value: unknown) {
22
+ const bean = constant(expression, defaultValue) || this[symbol];
23
+
24
+ if (bean === undefined && value !== undefined) {
25
+ this[symbol] = value;
26
+ }
27
+ },
28
+ enumerable: true,
29
+ configurable: true
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Return value from Configuration.
35
+ *
36
+ * ## Example
37
+ *
38
+ * ```typescript
39
+ * import {Env} from "@tsed/core";
40
+ * import {Constant, Value} from "@tsed/di";
41
+ *
42
+ * export class MyClass {
43
+ *
44
+ * @Constant("env")
45
+ * env: Env;
46
+ *
47
+ * @Value("swagger.path")
48
+ * swaggerPath: string;
49
+ *
50
+ * @Value("swagger.path", "defaultValue")
51
+ * swaggerPath: string;
52
+ *
53
+ * constructor() {
54
+ * console.log(this.swaggerPath) // undefined. Not available on constructor
55
+ * }
56
+ *
57
+ * $onInit() {
58
+ * console.log(this.swaggerPath) // something
59
+ * }
60
+ * }
61
+ * ```
62
+ *
63
+ * @param {string} expression
64
+ * @param defaultValue
65
+ * @returns {(targetClass: any, attributeName: string) => any}
66
+ * @decorator
67
+ */
68
+ export function Constant<Type = unknown>(expression: string, defaultValue?: Type): PropertyDecorator {
69
+ return (target, propertyKey) => {
70
+ return bindConstant(target, propertyKey, expression, defaultValue);
71
+ };
72
+ }
@@ -0,0 +1,43 @@
1
+ import {ProviderScope} from "../domain/ProviderScope.js";
2
+ import {ProviderType} from "../domain/ProviderType.js";
3
+ import {GlobalProviders} from "../registries/GlobalProviders.js";
4
+ import {Controller} from "./controller.js";
5
+
6
+ class Test {}
7
+
8
+ class Dep {}
9
+
10
+ describe("@Controller", () => {
11
+ afterAll(() => {
12
+ GlobalProviders.delete(Test);
13
+ });
14
+ it("should register a controller with his path and Dependency", () => {
15
+ // WHEN
16
+ Controller("/test")(Test);
17
+
18
+ // THEN
19
+ const provider = GlobalProviders.get(Test)!;
20
+
21
+ // THEN
22
+ expect(provider.type).toEqual(ProviderType.CONTROLLER);
23
+ expect(provider.path).toEqual("/test");
24
+ });
25
+
26
+ it("should register a controller with customer provider options", () => {
27
+ // WHEN
28
+ Controller({
29
+ path: "/test",
30
+ children: [Dep],
31
+ scope: ProviderScope.REQUEST
32
+ })(Test);
33
+
34
+ // THEN
35
+ const provider = GlobalProviders.get(Test)!;
36
+
37
+ // THEN
38
+ expect(provider.type).toEqual(ProviderType.CONTROLLER);
39
+ expect(provider.scope).toEqual(ProviderScope.REQUEST);
40
+ expect(provider.path).toEqual("/test");
41
+ expect(provider.children).toEqual([Dep]);
42
+ });
43
+ });
@@ -0,0 +1,64 @@
1
+ import {isArrayOrArrayClass, Type, useDecorators} from "@tsed/core";
2
+ import {Children, Path} from "@tsed/schema";
3
+
4
+ import type {ControllerMiddlewares} from "../domain/ControllerProvider.js";
5
+ import {controller} from "../fn/injectable.js";
6
+ import {ProviderOpts} from "../interfaces/ProviderOpts.js";
7
+
8
+ export type PathType = string | RegExp | (string | RegExp)[];
9
+
10
+ export interface ControllerOptions extends Partial<ProviderOpts<any>> {
11
+ path?: PathType;
12
+ children?: Type<any>[];
13
+ middlewares?: Partial<ControllerMiddlewares>;
14
+ }
15
+
16
+ function mapOptions(options: any): ControllerOptions {
17
+ if (typeof options === "string" || options instanceof RegExp || isArrayOrArrayClass(options)) {
18
+ return {
19
+ path: options
20
+ };
21
+ }
22
+
23
+ return options;
24
+ }
25
+
26
+ /**
27
+ * Declare a new controller with his Rest path. His methods annotated will be collected to build the routing list.
28
+ * This routing listing will be built with the `express.Router` object.
29
+ *
30
+ * ::: tip
31
+ * See [Controllers](/docs/controllers.md) section for more details
32
+ * :::
33
+ *
34
+ * ```typescript
35
+ * @Controller("/calendars")
36
+ * export class CalendarCtrl {
37
+ *
38
+ * @Get("/:id")
39
+ * public get(
40
+ * @Req() request: Req,
41
+ * @Res() response: Res,
42
+ * @Next() next: Next
43
+ * ): void {
44
+ *
45
+ * }
46
+ * }
47
+ * ```
48
+ *
49
+ * @param options
50
+ * @controller
51
+ * @decorator
52
+ * @classDecorator
53
+ */
54
+ export function Controller(options: PathType | ControllerOptions): ClassDecorator {
55
+ const {children = [], path, ...opts} = mapOptions(options);
56
+
57
+ return useDecorators(
58
+ (target: Type) => {
59
+ controller(target, opts);
60
+ },
61
+ path && Path(path as any),
62
+ Children(...children)
63
+ );
64
+ }