@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,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
|
+
}
|