@tsed/di 8.0.0 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -9
- package/src/common/constants/constants.ts +5 -0
- package/src/common/decorators/autoInjectable.spec.ts +135 -0
- package/src/common/decorators/autoInjectable.ts +59 -0
- package/src/common/decorators/configuration.spec.ts +45 -0
- package/src/common/decorators/configuration.ts +34 -0
- package/src/common/decorators/constant.spec.ts +67 -0
- package/src/common/decorators/constant.ts +72 -0
- package/src/common/decorators/controller.spec.ts +43 -0
- package/src/common/decorators/controller.ts +64 -0
- package/src/common/decorators/inject.spec.ts +298 -0
- package/src/common/decorators/inject.ts +144 -0
- package/src/common/decorators/injectable.spec.ts +43 -0
- package/src/common/decorators/injectable.ts +30 -0
- package/src/common/decorators/intercept.spec.ts +103 -0
- package/src/common/decorators/intercept.ts +77 -0
- package/src/common/decorators/interceptor.ts +17 -0
- package/src/common/decorators/lazyInject.spec.ts +53 -0
- package/src/common/decorators/lazyInject.ts +91 -0
- package/src/common/decorators/module.ts +52 -0
- package/src/common/decorators/opts.ts +51 -0
- package/src/common/decorators/overrideProvider.spec.ts +30 -0
- package/src/common/decorators/overrideProvider.ts +15 -0
- package/src/common/decorators/scope.spec.ts +25 -0
- package/src/common/decorators/scope.ts +13 -0
- package/src/common/decorators/service.ts +14 -0
- package/src/common/decorators/useOpts.spec.ts +90 -0
- package/src/common/decorators/useOpts.ts +61 -0
- package/src/common/decorators/value.spec.ts +59 -0
- package/src/common/decorators/value.ts +60 -0
- package/src/common/domain/Container.spec.ts +85 -0
- package/src/common/domain/Container.ts +80 -0
- package/src/common/domain/ControllerProvider.spec.ts +55 -0
- package/src/common/domain/ControllerProvider.ts +48 -0
- package/src/common/domain/InjectablePropertyType.ts +7 -0
- package/src/common/domain/LocalsContainer.spec.ts +20 -0
- package/src/common/domain/LocalsContainer.ts +11 -0
- package/src/common/domain/Provider.spec.ts +107 -0
- package/src/common/domain/Provider.ts +205 -0
- package/src/common/domain/ProviderScope.ts +5 -0
- package/src/common/domain/ProviderType.ts +8 -0
- package/src/common/errors/InjectionError.spec.ts +17 -0
- package/src/common/errors/InjectionError.ts +68 -0
- package/src/common/errors/InvalidPropertyTokenError.ts +13 -0
- package/src/common/fn/configuration.spec.ts +42 -0
- package/src/common/fn/configuration.ts +22 -0
- package/src/common/fn/constant.spec.ts +37 -0
- package/src/common/fn/constant.ts +7 -0
- package/src/common/fn/inject.spec.ts +54 -0
- package/src/common/fn/inject.ts +29 -0
- package/src/common/fn/injectMany.ts +16 -0
- package/src/common/fn/injectable.spec.ts +87 -0
- package/src/common/fn/injectable.ts +15 -0
- package/src/common/fn/injector.ts +26 -0
- package/src/common/fn/lazyInject.spec.ts +22 -0
- package/src/common/fn/lazyInject.ts +36 -0
- package/src/common/fn/localsContainer.ts +55 -0
- package/src/common/fn/refValue.spec.ts +47 -0
- package/src/common/fn/refValue.ts +33 -0
- package/src/common/index.ts +63 -0
- package/src/common/integration/async-factory.spec.ts +60 -0
- package/src/common/integration/di.spec.ts +153 -0
- package/src/common/integration/imports.spec.ts +39 -0
- package/src/common/integration/request.spec.ts +79 -0
- package/src/common/integration/singleton.spec.ts +125 -0
- package/src/common/interfaces/DIConfigurationOptions.ts +29 -0
- package/src/common/interfaces/DILogger.ts +13 -0
- package/src/common/interfaces/DILoggerOptions.ts +54 -0
- package/src/common/interfaces/ImportTokenProviderOpts.ts +29 -0
- package/src/common/interfaces/InterceptorContext.ts +13 -0
- package/src/common/interfaces/InterceptorMethods.ts +5 -0
- package/src/common/interfaces/InvokeOptions.ts +28 -0
- package/src/common/interfaces/OnDestroy.ts +3 -0
- package/src/common/interfaces/OnInit.ts +3 -0
- package/src/common/interfaces/ProviderOpts.ts +57 -0
- package/src/common/interfaces/RegistrySettings.ts +11 -0
- package/src/common/interfaces/ResolvedInvokeOptions.ts +14 -0
- package/src/common/interfaces/TokenProvider.ts +3 -0
- package/src/common/interfaces/TokenRoute.ts +6 -0
- package/src/common/registries/GlobalProviders.spec.ts +94 -0
- package/src/common/registries/GlobalProviders.ts +131 -0
- package/src/common/registries/ProviderRegistry.spec.ts +36 -0
- package/src/common/registries/ProviderRegistry.ts +32 -0
- package/src/common/services/DIConfiguration.spec.ts +60 -0
- package/src/common/services/DIConfiguration.ts +140 -0
- package/src/common/services/DILogger.spec.ts +22 -0
- package/src/common/services/DILogger.ts +8 -0
- package/src/common/services/InjectorService.spec.ts +793 -0
- package/src/common/services/InjectorService.ts +601 -0
- package/src/common/utils/colors.spec.ts +9 -0
- package/src/common/utils/colors.ts +11 -0
- package/src/common/utils/createContainer.spec.ts +18 -0
- package/src/common/utils/createContainer.ts +14 -0
- package/src/common/utils/discoverHooks.ts +15 -0
- package/src/common/utils/getConfiguration.spec.ts +40 -0
- package/src/common/utils/getConfiguration.ts +20 -0
- package/src/common/utils/getConstructorDependencies.ts +9 -0
- package/src/common/utils/providerBuilder.ts +81 -0
- package/src/index.ts +2 -0
- package/src/node/decorators/injectContext.spec.ts +60 -0
- package/src/node/decorators/injectContext.ts +29 -0
- package/src/node/domain/ContextLogger.spec.ts +277 -0
- package/src/node/domain/ContextLogger.ts +137 -0
- package/src/node/domain/DIContext.spec.ts +103 -0
- package/src/node/domain/DIContext.ts +115 -0
- package/src/node/fn/context.ts +24 -0
- package/src/node/fn/logger.spec.ts +46 -0
- package/src/node/fn/logger.ts +17 -0
- package/src/node/index.ts +16 -0
- package/src/node/integration/async-factory-invoke.spec.ts +58 -0
- package/src/node/integration/inject-context.spec.ts +29 -0
- package/src/node/integration/invoke-oninit.spec.ts +25 -0
- package/src/node/interfaces/AlterRunInContext.ts +3 -0
- package/src/node/services/DILogger.spec.ts +24 -0
- package/src/node/services/DILogger.ts +6 -0
- package/src/node/services/DITest.spec.ts +109 -0
- package/src/node/services/DITest.ts +119 -0
- package/src/node/utils/asyncHookContext.spec.ts +147 -0
- package/src/node/utils/asyncHookContext.ts +48 -0
- package/src/node/utils/attachLogger.spec.ts +14 -0
- package/src/node/utils/attachLogger.ts +7 -0
- package/src/node/utils/setLoggerConfiguration.spec.ts +43 -0
- package/src/node/utils/setLoggerConfiguration.ts +7 -0
- package/src/node/utils/setLoggerFormat.ts +33 -0
- package/src/node/utils/setLoggerLevel.ts +12 -0
|
@@ -0,0 +1,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
|
+
}
|