@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,298 @@
1
+ import {catchAsyncError} from "@tsed/core";
2
+
3
+ import {DITest} from "../../node/index.js";
4
+ import {inject} from "../fn/inject.js";
5
+ import {injector} from "../fn/injector.js";
6
+ import {registerProvider} from "../registries/ProviderRegistry.js";
7
+ import {InjectorService} from "../services/InjectorService.js";
8
+ import {Inject} from "./inject.js";
9
+ import {Injectable} from "./injectable.js";
10
+
11
+ @Injectable()
12
+ class ProvidersList extends Map<string, string> {}
13
+
14
+ @Injectable()
15
+ class MyService {
16
+ @Inject(ProvidersList)
17
+ providersList: ProvidersList;
18
+
19
+ getValue() {
20
+ return this.providersList.get("key");
21
+ }
22
+ }
23
+
24
+ describe("@Inject()", () => {
25
+ beforeEach(() => DITest.create());
26
+ afterEach(() => DITest.reset());
27
+
28
+ describe("when the decorator used on property", () => {
29
+ it("should inject service", async () => {
30
+ // GIVEN
31
+ @Injectable()
32
+ class Test {
33
+ @Inject()
34
+ test: InjectorService;
35
+ }
36
+
37
+ const instance = inject<Test>(Test);
38
+
39
+ expect(instance).toBeInstanceOf(Test);
40
+ expect(instance.test).toBeInstanceOf(InjectorService);
41
+ });
42
+ it("should inject service and async factory", async () => {
43
+ // GIVEN
44
+ class Test {
45
+ constructor(public type: string) {}
46
+ }
47
+
48
+ const TokenAsync = Symbol.for("MyService");
49
+
50
+ registerProvider<Test>({
51
+ token: TokenAsync,
52
+ type: "test:async",
53
+ deps: [],
54
+ useAsyncFactory() {
55
+ return Promise.resolve(new Test("async"));
56
+ }
57
+ });
58
+
59
+ @Injectable()
60
+ class Parent1 {
61
+ @Inject(TokenAsync)
62
+ test: Test;
63
+ }
64
+
65
+ @Injectable()
66
+ class Parent2 {
67
+ @Inject(TokenAsync)
68
+ test: Test;
69
+ }
70
+
71
+ await injector().load();
72
+
73
+ const parent1 = inject<Parent1>(Parent1);
74
+ const parent2 = inject<Parent2>(Parent2);
75
+
76
+ expect(parent1.test).toBeInstanceOf(Test);
77
+ expect(parent2.test).toBeInstanceOf(Test);
78
+ });
79
+ it("should inject service and use onGet option to transform injected service", async () => {
80
+ // GIVEN
81
+ @Injectable()
82
+ class Test {
83
+ @Inject(InjectorService, {transform: (instance) => instance.get(InjectorService)})
84
+ test: InjectorService;
85
+ }
86
+
87
+ const instance = inject(Test);
88
+
89
+ expect(instance).toBeInstanceOf(Test);
90
+ expect(instance.test).toBeInstanceOf(InjectorService);
91
+ });
92
+ it("should inject service and use onGet option to transform injected service (legacy)", async () => {
93
+ // GIVEN
94
+ @Injectable()
95
+ class Test {
96
+ @Inject(InjectorService, (instance) => instance.get(InjectorService))
97
+ test: InjectorService;
98
+ }
99
+
100
+ const instance = inject<Test>(Test);
101
+
102
+ expect(instance).toBeInstanceOf(Test);
103
+ expect(instance.test).toBeInstanceOf(InjectorService);
104
+ });
105
+ it("should inject many services", async () => {
106
+ const TOKEN_GROUPS = Symbol.for("groups:1");
107
+
108
+ interface InterfaceGroup {
109
+ type: string;
110
+ }
111
+
112
+ @Injectable({
113
+ type: TOKEN_GROUPS
114
+ })
115
+ class MyService1 implements InterfaceGroup {
116
+ readonly type: string = "service1";
117
+
118
+ constructor(@Inject(InjectorService) readonly injector: any) {}
119
+ }
120
+
121
+ @Injectable({
122
+ type: TOKEN_GROUPS
123
+ })
124
+ class MyService2 implements InterfaceGroup {
125
+ readonly type: string = "service2";
126
+
127
+ constructor(@Inject(InjectorService) readonly injector: any) {}
128
+ }
129
+
130
+ const TokenAsync = Symbol.for("MyService2");
131
+
132
+ registerProvider({
133
+ token: TokenAsync,
134
+ type: TOKEN_GROUPS,
135
+ deps: [],
136
+ useAsyncFactory() {
137
+ return Promise.resolve({
138
+ type: "async"
139
+ });
140
+ }
141
+ });
142
+
143
+ @Injectable()
144
+ class MyInjectable {
145
+ @Inject(TOKEN_GROUPS)
146
+ instances: InterfaceGroup[];
147
+ }
148
+
149
+ await injector().load();
150
+
151
+ const instance = inject<MyInjectable>(MyInjectable);
152
+
153
+ expect(instance.instances).toBeInstanceOf(Array);
154
+ expect(instance.instances).toHaveLength(3);
155
+ expect(instance.instances[0].type).toEqual("service1");
156
+ expect(instance.instances[1].type).toEqual("service2");
157
+ expect(instance.instances[2].type).toEqual("async");
158
+ });
159
+ it("should throw error", () => {
160
+ try {
161
+ // GIVEN
162
+ @Injectable()
163
+ class Test {
164
+ @Inject()
165
+ test: Object;
166
+ }
167
+ } catch (er) {
168
+ expect(er.message).toContain("Object isn't a valid token. Please check the token set on Test.test");
169
+ }
170
+ });
171
+ it("should inject service and use mock", async () => {
172
+ @Injectable()
173
+ class Nested {
174
+ get cache() {
175
+ return true;
176
+ }
177
+ }
178
+
179
+ @Injectable()
180
+ class Test {
181
+ @Inject()
182
+ nested: Nested;
183
+ }
184
+
185
+ const instance = await DITest.invoke(Test, [
186
+ {
187
+ token: Nested,
188
+ use: {
189
+ cache: false
190
+ }
191
+ }
192
+ ]);
193
+
194
+ expect(instance.nested.cache).toEqual(false);
195
+
196
+ const instance2 = await DITest.invoke(Test, []);
197
+ expect(instance2.nested.cache).toEqual(true);
198
+ });
199
+ });
200
+ describe("when the decorator is used on constructor parameter", () => {
201
+ describe("when token is given on constructor", () => {
202
+ it("should inject the expected provider", async () => {
203
+ @Injectable()
204
+ class MyInjectable {
205
+ constructor(@Inject(InjectorService) readonly injector: InjectorService) {}
206
+ }
207
+
208
+ const instance = inject(MyInjectable);
209
+
210
+ expect(instance.injector).toBeInstanceOf(InjectorService);
211
+ });
212
+ });
213
+
214
+ describe("when a group token is given on constructor", () => {
215
+ it("should inject the expected provider", async () => {
216
+ const TOKEN_GROUPS = Symbol.for("groups:2");
217
+
218
+ interface InterfaceGroup {
219
+ type: string;
220
+ }
221
+
222
+ @Injectable({
223
+ type: TOKEN_GROUPS
224
+ })
225
+ class MyService1 implements InterfaceGroup {
226
+ readonly type: string = "service1";
227
+
228
+ constructor(@Inject(InjectorService) readonly injector: InjectorService) {}
229
+ }
230
+
231
+ @Injectable({
232
+ type: TOKEN_GROUPS
233
+ })
234
+ class MyService2 implements InterfaceGroup {
235
+ readonly type: string = "service2";
236
+
237
+ constructor(@Inject(InjectorService) readonly injector: InjectorService) {}
238
+ }
239
+
240
+ const TokenAsync = Symbol.for("MyService1");
241
+
242
+ registerProvider({
243
+ token: TokenAsync,
244
+ type: TOKEN_GROUPS,
245
+ deps: [],
246
+ useAsyncFactory() {
247
+ return Promise.resolve({
248
+ type: "async"
249
+ });
250
+ }
251
+ });
252
+
253
+ @Injectable()
254
+ class MyInjectable {
255
+ constructor(@Inject(TOKEN_GROUPS) readonly instances: InterfaceGroup[]) {}
256
+ }
257
+
258
+ await injector().load();
259
+
260
+ const instance = inject(MyInjectable);
261
+
262
+ expect(instance.instances).toBeInstanceOf(Array);
263
+ expect(instance.instances).toHaveLength(3);
264
+ expect(instance.instances[0].type).toEqual("service1");
265
+ expect(instance.instances[1].type).toEqual("service2");
266
+ expect(instance.instances[2].type).toEqual("async");
267
+ });
268
+ });
269
+ });
270
+ describe("when token is Object", () => {
271
+ it("should throw error", async () => {
272
+ class Test {
273
+ @Inject()
274
+ test: any;
275
+ }
276
+
277
+ const error = await catchAsyncError(async () => {
278
+ const instance = await DITest.invoke(Test);
279
+
280
+ return instance.test;
281
+ });
282
+
283
+ expect(error?.message).toContain("Object isn't a valid token. Please check the token set on Test.test");
284
+ });
285
+ });
286
+ it("should rebuild all dependencies using invoke", async () => {
287
+ const providersList = inject(ProvidersList);
288
+ const myService = inject(MyService);
289
+ providersList.set("key", "value");
290
+
291
+ expect(inject(ProvidersList).get("key")).toEqual("value");
292
+ expect(myService.getValue()).toEqual("value");
293
+
294
+ const newMyService = await DITest.invoke(MyService, []);
295
+ expect(newMyService.getValue()).toEqual(undefined);
296
+ expect(myService.getValue()).toEqual("value");
297
+ });
298
+ });
@@ -0,0 +1,144 @@
1
+ import {catchError, decoratorTypeOf, DecoratorTypes, isPromise, Metadata, Store, type Type} from "@tsed/core";
2
+
3
+ import {DI_INJECTABLE_PROPS, DI_INVOKE_OPTIONS, DI_USE_OPTIONS} from "../constants/constants.js";
4
+ import {InvalidPropertyTokenError} from "../errors/InvalidPropertyTokenError.js";
5
+ import {inject} from "../fn/inject.js";
6
+ import {injectMany} from "../fn/injectMany.js";
7
+ import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
8
+ import {TokenProvider} from "../interfaces/TokenProvider.js";
9
+ import {getConstructorDependencies, setConstructorDependencies} from "../utils/getConstructorDependencies.js";
10
+
11
+ function setToken(
12
+ token: TokenProvider,
13
+ {
14
+ target,
15
+ propertyKey,
16
+ parameterIndex
17
+ }: {
18
+ target: any;
19
+ propertyKey: string | symbol | undefined;
20
+ parameterIndex: number;
21
+ }
22
+ ) {
23
+ const paramTypes = getConstructorDependencies(target, propertyKey);
24
+ const type = paramTypes[parameterIndex];
25
+
26
+ paramTypes[parameterIndex] = type === Array ? [token] : token;
27
+
28
+ Metadata.setParamTypes(target, propertyKey!, paramTypes);
29
+ setConstructorDependencies(target, paramTypes);
30
+ }
31
+
32
+ function getTokenType(token: TokenProvider | (() => TokenProvider) | undefined, target: any, propertyKey: string | symbol) {
33
+ const useType = token || Metadata.getType(target, propertyKey);
34
+
35
+ if (useType === Object) {
36
+ throw new InvalidPropertyTokenError(target, String(propertyKey));
37
+ }
38
+
39
+ return useType;
40
+ }
41
+
42
+ export type TransformInjectedProviderCB<T, Klass = any> = (
43
+ instance: T,
44
+ {target, propertyKey}: {self: Klass; target: Type<Klass>; propertyKey: symbol | string}
45
+ ) => unknown;
46
+
47
+ export interface BindInjectablePropertyOpts<T = any> {
48
+ token?: TokenProvider;
49
+ useOpts?: Record<string, unknown>;
50
+ transform?: TransformInjectedProviderCB<T>;
51
+ }
52
+
53
+ function bindInjectableProperty<T = any>(
54
+ target: any,
55
+ propertyKey: string | symbol,
56
+ {token, transform = (o) => o, useOpts}: BindInjectablePropertyOpts<T>
57
+ ) {
58
+ const symbol = Symbol();
59
+
60
+ if (!target[DI_INJECTABLE_PROPS]) {
61
+ Reflect.defineProperty(target, DI_INJECTABLE_PROPS, {
62
+ value: new Set(),
63
+ enumerable: false,
64
+ configurable: false
65
+ });
66
+ }
67
+
68
+ target[DI_INJECTABLE_PROPS].add(propertyKey);
69
+
70
+ catchError(() => Reflect.deleteProperty(target, propertyKey));
71
+ Reflect.defineProperty(target, propertyKey, {
72
+ get() {
73
+ const useType = getTokenType(token, target, propertyKey!);
74
+ const originalType = Metadata.getType(target, propertyKey);
75
+
76
+ const invokeOpts: Partial<InvokeOptions> = {
77
+ rebuild: !!this[DI_INVOKE_OPTIONS]?.rebuild,
78
+ locals: this[DI_INVOKE_OPTIONS]?.locals,
79
+ useOpts: useOpts || Store.from(target, propertyKey).get(DI_USE_OPTIONS)
80
+ };
81
+
82
+ if (this[symbol] === undefined) {
83
+ this[symbol] = originalType === Array ? injectMany(token as string, invokeOpts) : inject(useType, invokeOpts);
84
+ }
85
+
86
+ [].concat(this[symbol]).forEach((instance: any, index) => {
87
+ if (isPromise(this[symbol])) {
88
+ instance.then((result: any) => {
89
+ this[symbol]![index] = result;
90
+ if (originalType !== Array) {
91
+ this[symbol] = result;
92
+ }
93
+ });
94
+ }
95
+ });
96
+
97
+ return transform(this[symbol], {self: this, target, propertyKey});
98
+ }
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Inject a provider to another provider.
104
+ *
105
+ * Use this decorator to inject a custom provider on constructor parameter or property.
106
+ *
107
+ * ```typescript
108
+ * @Injectable()
109
+ * export class MyService {
110
+ * @Inject(CONNECTION)
111
+ * connection: CONNECTION;
112
+ * }
113
+ * ```
114
+ *
115
+ * @param token A token provider or token provider group
116
+ * @param transform
117
+ * @returns {Function}
118
+ * @decorator
119
+ */
120
+ export function Inject<T = any>(token?: TokenProvider<T> | (() => TokenProvider<T>), transform?: TransformInjectedProviderCB<T>): any;
121
+ export function Inject<T = any>(
122
+ token?: TokenProvider<T> | (() => TokenProvider<T>),
123
+ opts?: Partial<Omit<BindInjectablePropertyOpts<T>, "token">>
124
+ ): any;
125
+ export function Inject<T = any>(
126
+ token?: TokenProvider<T> | (() => TokenProvider<T>),
127
+ opts: TransformInjectedProviderCB<T> | Partial<Omit<BindInjectablePropertyOpts<T>, "token">> = {}
128
+ ) {
129
+ opts = typeof opts === "function" ? {transform: opts} : opts;
130
+
131
+ return (target: any, propertyKey: string | symbol | undefined, index?: number) => {
132
+ const bindingType = decoratorTypeOf([target, propertyKey, index]);
133
+
134
+ switch (bindingType) {
135
+ case DecoratorTypes.PARAM_CTOR:
136
+ if (token) {
137
+ setToken(token, {target, propertyKey, parameterIndex: index!});
138
+ }
139
+ break;
140
+ case DecoratorTypes.PROP:
141
+ bindInjectableProperty(target, propertyKey!, {...opts, token});
142
+ }
143
+ };
144
+ }
@@ -0,0 +1,43 @@
1
+ import {GlobalProviders} from "../registries/GlobalProviders.js";
2
+ import {Injectable} from "./injectable.js";
3
+
4
+ describe("@Injectable()", () => {
5
+ afterEach(() => {
6
+ vi.resetAllMocks();
7
+ });
8
+
9
+ it("should call `registerProvider` setting `token` according to the target class", () => {
10
+ // GIVEN
11
+ class Test {}
12
+
13
+ // WHEN
14
+ Injectable()(Test);
15
+
16
+ // THEN
17
+ expect(GlobalProviders.get(Test)?.useClass).toEqual(Test);
18
+ });
19
+
20
+ it("should call `registerProvider` passing an additional options", () => {
21
+ // GIVEN
22
+ class Test {}
23
+
24
+ // WHEN
25
+ Injectable({options: "options"})(Test);
26
+
27
+ // THEN
28
+ expect(GlobalProviders.get(Test)?.useClass).toEqual(Test);
29
+ expect(GlobalProviders.get(Test)?.options).toEqual("options");
30
+ });
31
+
32
+ it("should override `token`", () => {
33
+ // GIVEN
34
+ class Test {}
35
+ const token = "custom";
36
+
37
+ // WHEN
38
+ Injectable({token})(Test);
39
+
40
+ // THEN
41
+ expect(GlobalProviders.get("custom")?.useClass).toEqual(Test);
42
+ });
43
+ });
@@ -0,0 +1,30 @@
1
+ import {injectable} from "../fn/injectable.js";
2
+ import type {ProviderOpts} from "../interfaces/ProviderOpts.js";
3
+
4
+ /**
5
+ * The decorators `@Injectable()` declare a new service can be injected in other service, controller, interceptor, etc.. on there `constructor`.
6
+ * All classes annotated with `@Injectable()` are built one time, excepted if you change the default provider configuration.
7
+ *
8
+ * ::: tip
9
+ * `@Injectable()` use the `reflect-metadata` to collect and inject the built provided to other services.
10
+ * :::
11
+ *
12
+ * ### Options
13
+ *
14
+ * - type (@@ProviderType@@ or `string`): Kind of provider. (Default: `ProviderType.PROVIDER`)
15
+ * - scope (@@ProviderScope@@): Kind of provider. (Default: `ProviderScope.SINGLETON`)
16
+ * - token (@@TokenProvider@@): An injection token (Note: This option override default metadata generated by Typescript).
17
+ * - deps (`Type<any>`): List of class or provider which will be injected to the constructor (Note: This options override default metadata generated by Typescript).
18
+ *
19
+ * @returns {Function}
20
+ * @decorator
21
+ */
22
+ export function Injectable(options: Partial<ProviderOpts> = {}): ClassDecorator {
23
+ return (target: any) => {
24
+ const opts = {
25
+ ...options,
26
+ ...(options.token ? {useClass: target} : {token: target})
27
+ };
28
+ injectable(opts.token, opts);
29
+ };
30
+ }
@@ -0,0 +1,103 @@
1
+ import {catchError} from "@tsed/core";
2
+
3
+ import {DITest} from "../../node/index.js";
4
+ import {InterceptorContext} from "../interfaces/InterceptorContext.js";
5
+ import {InterceptorMethods} from "../interfaces/InterceptorMethods.js";
6
+ import {InjectorService} from "../services/InjectorService.js";
7
+ import {getInterceptorOptions, Intercept} from "./intercept.js";
8
+ import {Interceptor} from "./interceptor.js";
9
+ import {Service} from "./service.js";
10
+
11
+ @Interceptor()
12
+ class MyInterceptor implements InterceptorMethods {
13
+ intercept(context: InterceptorContext<any>) {
14
+ const r = typeof context.args[0] === "string" ? undefined : new Error(`Error message`);
15
+ const retValue = context.next(r);
16
+
17
+ return `${retValue} - ${context.options || ""} - intercepted`;
18
+ }
19
+ }
20
+
21
+ @Service()
22
+ class ServiceTest {
23
+ @Intercept(MyInterceptor, "options data")
24
+ exec(param: string) {
25
+ return `Original data - ${param}`;
26
+ }
27
+ }
28
+
29
+ @Service()
30
+ @Intercept(MyInterceptor, "options data")
31
+ class ServiceTest2 {
32
+ exec(param: string) {
33
+ return `Original data - ${param}`;
34
+ }
35
+ }
36
+
37
+ describe("@Intercept", () => {
38
+ beforeEach(() => DITest.create());
39
+ afterEach(() => DITest.reset());
40
+
41
+ describe("when the decorator is used on a method", () => {
42
+ it("should intercept the method", async () => {
43
+ // GIVEN
44
+ const serviceTest = await DITest.invoke<ServiceTest>(ServiceTest)!;
45
+
46
+ // WHEN
47
+ const result = serviceTest.exec("param data");
48
+
49
+ expect(getInterceptorOptions(ServiceTest, "exec")).toEqual("options data");
50
+
51
+ // THEN
52
+ expect(result).toEqual("Original data - param data - options data - intercepted");
53
+ });
54
+ it("should intercept the method and mock interceptor", async () => {
55
+ // GIVEN
56
+ const serviceTest = await DITest.invoke<ServiceTest>(ServiceTest, [
57
+ {
58
+ token: MyInterceptor,
59
+ use: {
60
+ intercept: vi.fn().mockReturnValue("intercepted")
61
+ }
62
+ }
63
+ ]);
64
+
65
+ // WHEN
66
+ const result = serviceTest.exec("param data");
67
+
68
+ // THEN
69
+ expect(result).toEqual("intercepted");
70
+ });
71
+ it("should intercept the method and throw error", async () => {
72
+ // GIVEN
73
+ const serviceTest = await DITest.invoke<ServiceTest>(ServiceTest)!;
74
+
75
+ // WHEN
76
+ let actualError = catchError(() => serviceTest.exec({} as any));
77
+
78
+ // THEN
79
+ expect(actualError?.message).toEqual("Error message");
80
+ });
81
+ });
82
+ describe("when the decorator is used on a class", () => {
83
+ it("should intercept the method", async () => {
84
+ // GIVEN
85
+ const serviceTest = await DITest.invoke<ServiceTest2>(ServiceTest2)!;
86
+
87
+ // WHEN
88
+ const result = serviceTest.exec("param data");
89
+
90
+ // THEN
91
+ expect(result).toEqual("Original data - param data - options data - intercepted");
92
+ });
93
+ it("should intercept the method and throw error", async () => {
94
+ // GIVEN
95
+ const serviceTest = await DITest.invoke<ServiceTest2>(ServiceTest2)!;
96
+
97
+ // WHEN
98
+ let actualError = catchError(() => serviceTest.exec({} as any));
99
+ // THEN
100
+ expect(actualError?.message).toEqual("Error message");
101
+ });
102
+ });
103
+ });
@@ -0,0 +1,77 @@
1
+ import {classOf, decorateMethodsOf, DecoratorParameters, decoratorTypeOf, DecoratorTypes, Store, Type} from "@tsed/core";
2
+
3
+ import {DI_INTERCEPTOR_OPTIONS, DI_INVOKE_OPTIONS} from "../constants/constants.js";
4
+ import {inject} from "../fn/inject.js";
5
+ import type {InterceptorContext} from "../interfaces/InterceptorContext.js";
6
+ import type {InterceptorMethods} from "../interfaces/InterceptorMethods.js";
7
+ import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
8
+ import type {TokenProvider} from "../interfaces/TokenProvider.js";
9
+
10
+ export function getInterceptorOptions<T>(target: Type<any>, propertyKey: string | symbol) {
11
+ return Store.fromMethod(target, propertyKey).get(DI_INTERCEPTOR_OPTIONS) as T;
12
+ }
13
+
14
+ export function bindIntercept(target: any, propertyKey: string | symbol, token: TokenProvider, options: Record<string, unknown>) {
15
+ const klass = classOf(target);
16
+ const descriptor = Reflect.getOwnPropertyDescriptor(klass.prototype, propertyKey);
17
+ const originalMethod = descriptor!.value;
18
+ const symbol = Symbol();
19
+
20
+ Store.fromMethod(klass, propertyKey).set(DI_INTERCEPTOR_OPTIONS, options);
21
+
22
+ descriptor!.value = function (...args: any[]) {
23
+ const next = (err?: Error) => {
24
+ if (!err) {
25
+ return originalMethod.apply(this, args);
26
+ }
27
+
28
+ throw err;
29
+ };
30
+
31
+ const context: InterceptorContext<any> = {
32
+ target,
33
+ propertyKey,
34
+ args,
35
+ options,
36
+ next
37
+ };
38
+
39
+ const invokeOpts: Partial<InvokeOptions> = {
40
+ rebuild: !!this[DI_INVOKE_OPTIONS]?.rebuild,
41
+ locals: this[DI_INVOKE_OPTIONS]?.locals
42
+ };
43
+
44
+ this[symbol] = this[symbol] || inject(token, invokeOpts)!;
45
+
46
+ return this[symbol].intercept!(
47
+ {
48
+ ...context,
49
+ options
50
+ },
51
+ next
52
+ );
53
+ };
54
+
55
+ return descriptor;
56
+ }
57
+
58
+ /**
59
+ * Attaches interceptor to method call and executes the before and after methods
60
+ *
61
+ * @param interceptor
62
+ * @param options
63
+ * @decorator
64
+ */
65
+ export function Intercept<T extends InterceptorMethods>(interceptor: Type<T>, options?: any): any {
66
+ return (...args: DecoratorParameters) => {
67
+ const [target, propertyKey] = args;
68
+ const type = decoratorTypeOf(args);
69
+ switch (type) {
70
+ case DecoratorTypes.CLASS:
71
+ decorateMethodsOf(target, Intercept(interceptor, options));
72
+ break;
73
+ case DecoratorTypes.METHOD:
74
+ return bindIntercept(target, propertyKey, interceptor, options);
75
+ }
76
+ };
77
+ }