@lppedd/di-wise-neo 0.3.0
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/LICENSE +22 -0
- package/README.md +488 -0
- package/dist/cjs/index.d.ts +772 -0
- package/dist/cjs/index.js +1055 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/es/index.d.mts +772 -0
- package/dist/es/index.mjs +1037 -0
- package/dist/es/index.mjs.map +1 -0
- package/package.json +77 -0
- package/src/container.ts +292 -0
- package/src/decorators/autoRegister.ts +24 -0
- package/src/decorators/decorators.ts +47 -0
- package/src/decorators/index.ts +7 -0
- package/src/decorators/inject.ts +42 -0
- package/src/decorators/injectAll.ts +45 -0
- package/src/decorators/injectable.ts +61 -0
- package/src/decorators/optional.ts +39 -0
- package/src/decorators/optionalAll.ts +42 -0
- package/src/decorators/scoped.ts +32 -0
- package/src/defaultContainer.ts +519 -0
- package/src/errors.ts +47 -0
- package/src/index.ts +16 -0
- package/src/inject.ts +88 -0
- package/src/injectAll.ts +21 -0
- package/src/injectionContext.ts +46 -0
- package/src/injector.ts +117 -0
- package/src/metadata.ts +41 -0
- package/src/middleware.ts +85 -0
- package/src/optional.ts +65 -0
- package/src/optionalAll.ts +19 -0
- package/src/provider.ts +61 -0
- package/src/scope.ts +8 -0
- package/src/token.ts +84 -0
- package/src/tokenRegistry.ts +165 -0
- package/src/tokensRef.ts +46 -0
- package/src/utils/context.ts +19 -0
- package/src/utils/disposable.ts +10 -0
- package/src/utils/invariant.ts +6 -0
- package/src/utils/keyedStack.ts +26 -0
- package/src/utils/typeName.ts +28 -0
- package/src/utils/weakRefMap.ts +28 -0
- package/src/valueRef.ts +3 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
import type { Container } from "./container";
|
2
|
+
import { assert } from "./errors";
|
3
|
+
import type { Provider } from "./provider";
|
4
|
+
import type { Scope } from "./scope";
|
5
|
+
import { createInjectionContext } from "./utils/context";
|
6
|
+
import { KeyedStack } from "./utils/keyedStack";
|
7
|
+
import { WeakRefMap } from "./utils/weakRefMap";
|
8
|
+
import type { ValueRef } from "./valueRef";
|
9
|
+
|
10
|
+
// @internal
|
11
|
+
export interface ResolutionFrame {
|
12
|
+
readonly scope: Exclude<Scope, typeof Scope.Inherited>;
|
13
|
+
readonly provider: Provider;
|
14
|
+
}
|
15
|
+
|
16
|
+
// @internal
|
17
|
+
export interface Resolution {
|
18
|
+
readonly stack: KeyedStack<Provider, ResolutionFrame>;
|
19
|
+
readonly values: WeakRefMap<Provider, ValueRef>;
|
20
|
+
readonly dependents: WeakRefMap<Provider, ValueRef>;
|
21
|
+
}
|
22
|
+
|
23
|
+
// @internal
|
24
|
+
export interface InjectionContext {
|
25
|
+
readonly container: Container;
|
26
|
+
readonly resolution: Resolution;
|
27
|
+
}
|
28
|
+
|
29
|
+
// @internal
|
30
|
+
export function createResolution(): Resolution {
|
31
|
+
return {
|
32
|
+
stack: new KeyedStack(),
|
33
|
+
values: new WeakRefMap(),
|
34
|
+
dependents: new WeakRefMap(),
|
35
|
+
};
|
36
|
+
}
|
37
|
+
|
38
|
+
// @internal
|
39
|
+
export const [provideInjectionContext, useInjectionContext] = createInjectionContext<InjectionContext>();
|
40
|
+
|
41
|
+
// @internal
|
42
|
+
export function ensureInjectionContext(fn: Function): InjectionContext {
|
43
|
+
const context = useInjectionContext();
|
44
|
+
assert(context, `${fn.name}() can only be invoked within an injection context`);
|
45
|
+
return context;
|
46
|
+
}
|
package/src/injector.ts
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
import { inject } from "./inject";
|
2
|
+
import { injectAll } from "./injectAll";
|
3
|
+
import { ensureInjectionContext, provideInjectionContext, useInjectionContext } from "./injectionContext";
|
4
|
+
import { optional } from "./optional";
|
5
|
+
import { optionalAll } from "./optionalAll";
|
6
|
+
import type { Constructor, Token, Type } from "./token";
|
7
|
+
import { Build } from "./tokenRegistry";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Injector API.
|
11
|
+
*/
|
12
|
+
export interface Injector {
|
13
|
+
/**
|
14
|
+
* Injects the instance associated with the given class.
|
15
|
+
*
|
16
|
+
* Throws an error if the class is not registered in the container.
|
17
|
+
*/
|
18
|
+
inject<Instance extends object>(Class: Constructor<Instance>): Instance;
|
19
|
+
|
20
|
+
/**
|
21
|
+
* Injects the value associated with the given token.
|
22
|
+
*
|
23
|
+
* Throws an error if the token is not registered in the container.
|
24
|
+
*/
|
25
|
+
inject<Value>(token: Token<Value>): Value;
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Injects all instances provided by the registrations associated with the given class.
|
29
|
+
*
|
30
|
+
* Throws an error if the class is not registered in the container.
|
31
|
+
*/
|
32
|
+
injectAll<Instance extends object>(Class: Constructor<Instance>): Instance[];
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Injects all values provided by the registrations associated with the given token.
|
36
|
+
*
|
37
|
+
* Throws an error if the token is not registered in the container.
|
38
|
+
*/
|
39
|
+
injectAll<Value>(token: Token<Value>): NonNullable<Value>[];
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Injects the instance associated with the given class,
|
43
|
+
* or `undefined` if the class is not registered in the container.
|
44
|
+
*/
|
45
|
+
optional<Instance extends object>(Class: Constructor<Instance>): Instance | undefined;
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Injects the value associated with the given token,
|
49
|
+
* or `undefined` if the token is not registered in the container.
|
50
|
+
*/
|
51
|
+
optional<Value>(token: Token<Value>): Value | undefined;
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Injects all instances provided by the registrations associated with the given class,
|
55
|
+
* or an empty array if the class is not registered in the container.
|
56
|
+
*/
|
57
|
+
optionalAll<Instance extends object>(Class: Constructor<Instance>): Instance[];
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Injects all values provided by the registrations associated with the given token,
|
61
|
+
* or an empty array if the token is not registered in the container.
|
62
|
+
*/
|
63
|
+
optionalAll<Value>(token: Token<Value>): NonNullable<Value>[];
|
64
|
+
}
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Injector token for dynamic injections.
|
68
|
+
*
|
69
|
+
* @example
|
70
|
+
* ```ts
|
71
|
+
* class Wizard {
|
72
|
+
* private injector = inject(Injector);
|
73
|
+
* private wand?: Wand;
|
74
|
+
*
|
75
|
+
* getWand(): Wand {
|
76
|
+
* return (this.wand ??= this.injector.inject(Wand));
|
77
|
+
* }
|
78
|
+
* }
|
79
|
+
*
|
80
|
+
* const wizard = container.resolve(Wizard);
|
81
|
+
* wizard.getWand(); // => Wand
|
82
|
+
* ```
|
83
|
+
*/
|
84
|
+
export const Injector: Type<Injector> = /*@__PURE__*/ Build(function Injector() {
|
85
|
+
const context = ensureInjectionContext(Injector);
|
86
|
+
const resolution = context.resolution;
|
87
|
+
|
88
|
+
const dependentFrame = resolution.stack.peek();
|
89
|
+
const dependentRef = dependentFrame && resolution.dependents.get(dependentFrame.provider);
|
90
|
+
|
91
|
+
function withCurrentContext<R>(fn: () => R): R {
|
92
|
+
if (useInjectionContext()) {
|
93
|
+
return fn();
|
94
|
+
}
|
95
|
+
|
96
|
+
const cleanups = [
|
97
|
+
provideInjectionContext(context),
|
98
|
+
dependentFrame && resolution.stack.push(dependentFrame.provider, dependentFrame),
|
99
|
+
dependentRef && resolution.dependents.set(dependentFrame.provider, dependentRef),
|
100
|
+
];
|
101
|
+
|
102
|
+
try {
|
103
|
+
return fn();
|
104
|
+
} finally {
|
105
|
+
for (const cleanup of cleanups) {
|
106
|
+
cleanup?.();
|
107
|
+
}
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
return {
|
112
|
+
inject: <T>(token: Token<T>) => withCurrentContext(() => inject(token)),
|
113
|
+
injectAll: <T>(token: Token<T>) => withCurrentContext(() => injectAll(token)),
|
114
|
+
optional: <T>(token: Token<T>) => withCurrentContext(() => optional(token)),
|
115
|
+
optionalAll: <T>(token: Token<T>) => withCurrentContext(() => optionalAll(token)),
|
116
|
+
};
|
117
|
+
});
|
package/src/metadata.ts
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
import type { ClassProvider } from "./provider";
|
2
|
+
import type { Scope } from "./scope";
|
3
|
+
import type { Constructor } from "./token";
|
4
|
+
import type { Dependencies } from "./tokenRegistry";
|
5
|
+
import type { TokensRef } from "./tokensRef";
|
6
|
+
|
7
|
+
// @internal
|
8
|
+
export interface Metadata<This extends object = any> {
|
9
|
+
autoRegister?: boolean;
|
10
|
+
scope?: Scope;
|
11
|
+
tokensRef: TokensRef<This>;
|
12
|
+
provider: ClassProvider<This>;
|
13
|
+
dependencies: Dependencies;
|
14
|
+
}
|
15
|
+
|
16
|
+
// @internal
|
17
|
+
export function getMetadata<T extends object>(Class: Constructor<T>): Metadata<T> {
|
18
|
+
let metadata = metadataMap.get(Class);
|
19
|
+
|
20
|
+
if (!metadata) {
|
21
|
+
metadataMap.set(
|
22
|
+
Class,
|
23
|
+
(metadata = {
|
24
|
+
tokensRef: {
|
25
|
+
getRefTokens: () => new Set(),
|
26
|
+
},
|
27
|
+
provider: {
|
28
|
+
useClass: Class,
|
29
|
+
},
|
30
|
+
dependencies: {
|
31
|
+
constructor: [],
|
32
|
+
methods: new Map(),
|
33
|
+
},
|
34
|
+
}),
|
35
|
+
);
|
36
|
+
}
|
37
|
+
|
38
|
+
return metadata;
|
39
|
+
}
|
40
|
+
|
41
|
+
const metadataMap = new WeakMap<Constructor<object>, Metadata>();
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import type { Container } from "./container";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Composer API for middleware functions.
|
5
|
+
*/
|
6
|
+
export interface MiddlewareComposer {
|
7
|
+
/**
|
8
|
+
* Add a middleware function to the composer.
|
9
|
+
*/
|
10
|
+
use<MethodKey extends keyof Container>(
|
11
|
+
key: MethodKey,
|
12
|
+
wrap: Container[MethodKey] extends Function
|
13
|
+
? (next: Container[MethodKey]) => Container[MethodKey]
|
14
|
+
: never,
|
15
|
+
): MiddlewareComposer;
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Middleware function that can be used to extend the container.
|
20
|
+
*
|
21
|
+
* @example
|
22
|
+
* ```ts
|
23
|
+
* const logger: Middleware = (composer, _api) => {
|
24
|
+
* composer
|
25
|
+
* .use("resolve", (next) => (...args) => {
|
26
|
+
* console.log("resolve", args);
|
27
|
+
* return next(...args);
|
28
|
+
* })
|
29
|
+
* .use("resolveAll", (next) => (...args) => {
|
30
|
+
* console.log("resolveAll", args);
|
31
|
+
* return next(...args);
|
32
|
+
* });
|
33
|
+
* };
|
34
|
+
* ```
|
35
|
+
*/
|
36
|
+
export interface Middleware {
|
37
|
+
(composer: MiddlewareComposer, api: Readonly<Container>): void;
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Apply middleware functions to a container.
|
42
|
+
*
|
43
|
+
* Middlewares are applied in array order, but execute in reverse order.
|
44
|
+
*
|
45
|
+
* @example
|
46
|
+
* ```ts
|
47
|
+
* const container = applyMiddleware(
|
48
|
+
* createContainer(),
|
49
|
+
* [A, B],
|
50
|
+
* );
|
51
|
+
* ```
|
52
|
+
*
|
53
|
+
* The execution order will be:
|
54
|
+
*
|
55
|
+
* 1. B before
|
56
|
+
* 2. A before
|
57
|
+
* 3. original function
|
58
|
+
* 4. A after
|
59
|
+
* 5. B after
|
60
|
+
*
|
61
|
+
* This allows outer middlewares to wrap and control the behavior of inner middlewares.
|
62
|
+
*/
|
63
|
+
export function applyMiddleware(container: Container, middlewares: Middleware[]): Container {
|
64
|
+
const composer: MiddlewareComposer = {
|
65
|
+
use(key, wrap): MiddlewareComposer {
|
66
|
+
// We need to bind the 'this' context of the function to the container
|
67
|
+
// before passing it to the middleware wrapper.
|
68
|
+
//
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
|
70
|
+
const fn = (container[key] as any).bind(container);
|
71
|
+
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
73
|
+
container[key] = wrap(fn);
|
74
|
+
return composer;
|
75
|
+
},
|
76
|
+
};
|
77
|
+
|
78
|
+
const api = (container.api ||= { ...container });
|
79
|
+
|
80
|
+
for (const middleware of middlewares) {
|
81
|
+
middleware(composer, api);
|
82
|
+
}
|
83
|
+
|
84
|
+
return container;
|
85
|
+
}
|
package/src/optional.ts
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
import { ensureInjectionContext } from "./injectionContext";
|
2
|
+
import type { Constructor, Token } from "./token";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Injects the instance associated with the given class,
|
6
|
+
* or `undefined` if the class is not registered in the container.
|
7
|
+
*/
|
8
|
+
export function optional<Instance extends object>(Class: Constructor<Instance>): Instance | undefined;
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Injects the value associated with the given token,
|
12
|
+
* or `undefined` if the token is not registered in the container.
|
13
|
+
*/
|
14
|
+
export function optional<Value>(token: Token<Value>): Value | undefined;
|
15
|
+
|
16
|
+
export function optional<T>(token: Token<T>): T | undefined {
|
17
|
+
const context = ensureInjectionContext(optional);
|
18
|
+
return context.container.resolve(token, true);
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Injects the instance associated with the given class,
|
23
|
+
* or `undefined` if the class is not registered in the container.
|
24
|
+
*
|
25
|
+
* Compared to {@link optional}, `optionalBy` accepts a `thisArg` argument
|
26
|
+
* (the containing class) which is used to resolve circular dependencies.
|
27
|
+
*
|
28
|
+
* @param thisArg - The containing instance, used to help resolve circular dependencies.
|
29
|
+
* @param Class - The class to resolve.
|
30
|
+
*/
|
31
|
+
export function optionalBy<Instance extends object>(
|
32
|
+
thisArg: any,
|
33
|
+
Class: Constructor<Instance>,
|
34
|
+
): Instance | undefined;
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Injects the value associated with the given token,
|
38
|
+
* or `undefined` if the token is not registered in the container.
|
39
|
+
*
|
40
|
+
* Compared to {@link optional}, `optionalBy` accepts a `thisArg` argument
|
41
|
+
* (the containing class) which is used to resolve circular dependencies.
|
42
|
+
*
|
43
|
+
* @param thisArg - The containing instance, used to help resolve circular dependencies.
|
44
|
+
* @param token - The token to resolve.
|
45
|
+
*/
|
46
|
+
export function optionalBy<Value>(thisArg: any, token: Token<Value>): Value | undefined;
|
47
|
+
|
48
|
+
export function optionalBy<T>(thisArg: any, token: Token<T>): T | undefined {
|
49
|
+
const context = ensureInjectionContext(optionalBy);
|
50
|
+
const resolution = context.resolution;
|
51
|
+
const currentFrame = resolution.stack.peek();
|
52
|
+
|
53
|
+
if (!currentFrame) {
|
54
|
+
return optional(token);
|
55
|
+
}
|
56
|
+
|
57
|
+
const currentRef = { current: thisArg };
|
58
|
+
const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
|
59
|
+
|
60
|
+
try {
|
61
|
+
return optional(token);
|
62
|
+
} finally {
|
63
|
+
cleanup();
|
64
|
+
}
|
65
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { ensureInjectionContext } from "./injectionContext";
|
2
|
+
import type { Constructor, Token } from "./token";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Injects all instances provided by the registrations associated with the given class,
|
6
|
+
* or an empty array if the class is not registered in the container.
|
7
|
+
*/
|
8
|
+
export function optionalAll<Instance extends object>(Class: Constructor<Instance>): Instance[];
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Injects all values provided by the registrations associated with the given token,
|
12
|
+
* or an empty array if the token is not registered in the container.
|
13
|
+
*/
|
14
|
+
export function optionalAll<Value>(token: Token<Value>): NonNullable<Value>[];
|
15
|
+
|
16
|
+
export function optionalAll<T>(token: Token<T>): NonNullable<T>[] {
|
17
|
+
const context = ensureInjectionContext(optionalAll);
|
18
|
+
return context.container.resolveAll(token, true);
|
19
|
+
}
|
package/src/provider.ts
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
import type { Constructor, Token } from "./token";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Provides a class instance for a token via a class constructor.
|
5
|
+
*/
|
6
|
+
export interface ClassProvider<Instance extends object> {
|
7
|
+
readonly useClass: Constructor<Instance>;
|
8
|
+
}
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Provides a value for a token via another existing token.
|
12
|
+
*/
|
13
|
+
export interface ExistingProvider<Value> {
|
14
|
+
readonly useExisting: Token<Value>;
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Provides a value for a token via a factory function.
|
19
|
+
*
|
20
|
+
* The factory function runs inside the injection context
|
21
|
+
* and can thus access dependencies via {@link inject}.
|
22
|
+
*/
|
23
|
+
export interface FactoryProvider<Value> {
|
24
|
+
readonly useFactory: (...args: []) => Value;
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Provides a direct - already constructed - value for a token.
|
29
|
+
*/
|
30
|
+
export interface ValueProvider<T> {
|
31
|
+
readonly useValue: T;
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* A token provider.
|
36
|
+
*/
|
37
|
+
export type Provider<Value = any> =
|
38
|
+
| ClassProvider<Value & object>
|
39
|
+
| ExistingProvider<Value>
|
40
|
+
| FactoryProvider<Value>
|
41
|
+
| ValueProvider<Value>;
|
42
|
+
|
43
|
+
// @internal
|
44
|
+
export function isClassProvider<T>(provider: Provider<T>): provider is ClassProvider<T & object> {
|
45
|
+
return "useClass" in provider;
|
46
|
+
}
|
47
|
+
|
48
|
+
// @internal
|
49
|
+
export function isExistingProvider<T>(provider: Provider<T>): provider is ExistingProvider<T> {
|
50
|
+
return "useExisting" in provider;
|
51
|
+
}
|
52
|
+
|
53
|
+
// @internal
|
54
|
+
export function isFactoryProvider<T>(provider: Provider<T>): provider is FactoryProvider<T> {
|
55
|
+
return "useFactory" in provider;
|
56
|
+
}
|
57
|
+
|
58
|
+
// @internal
|
59
|
+
export function isValueProvider<T>(provider: Provider<T>): provider is ValueProvider<T> {
|
60
|
+
return "useValue" in provider;
|
61
|
+
}
|
package/src/scope.ts
ADDED
package/src/token.ts
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
/**
|
2
|
+
* Type API.
|
3
|
+
*/
|
4
|
+
export interface Type<A> {
|
5
|
+
/**
|
6
|
+
* Name of the type.
|
7
|
+
*/
|
8
|
+
readonly name: string;
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Create an intersection type from another type.
|
12
|
+
*
|
13
|
+
* @example
|
14
|
+
* ```ts
|
15
|
+
* const A = Type<A>("A");
|
16
|
+
* const B = Type<B>("B");
|
17
|
+
*
|
18
|
+
* A.inter("I", B); // => Type<A & B>
|
19
|
+
* ```
|
20
|
+
*/
|
21
|
+
inter<B>(typeName: string, B: Type<B>): Type<A & B>;
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Create a union type from another type.
|
25
|
+
*
|
26
|
+
* @example
|
27
|
+
* ```ts
|
28
|
+
* const A = Type<A>("A");
|
29
|
+
* const B = Type<B>("B");
|
30
|
+
*
|
31
|
+
* A.union("U", B); // => Type<A | B>
|
32
|
+
* ```
|
33
|
+
*/
|
34
|
+
union<B>(typeName: string, B: Type<B>): Type<A | B>;
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Constructor type.
|
39
|
+
*/
|
40
|
+
export interface Constructor<Instance extends object> {
|
41
|
+
new (...args: any[]): Instance;
|
42
|
+
readonly name: string;
|
43
|
+
readonly length: number;
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Token type.
|
48
|
+
*/
|
49
|
+
export type Token<Value = any> = [Value] extends [object] // Avoids distributive union behavior
|
50
|
+
? Type<Value> | Constructor<Value>
|
51
|
+
: Type<Value>;
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Describes a {@link Token} array with at least one element.
|
55
|
+
*/
|
56
|
+
export type Tokens<Value = any> = [Token<Value>, ...Token<Value>[]];
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Create a type token.
|
60
|
+
*
|
61
|
+
* @example
|
62
|
+
* ```ts
|
63
|
+
* const Spell = Type<Spell>("Spell");
|
64
|
+
* ```
|
65
|
+
*
|
66
|
+
* @__NO_SIDE_EFFECTS__
|
67
|
+
*/
|
68
|
+
export function Type<T>(typeName: string): Type<T> {
|
69
|
+
const type = {
|
70
|
+
name: `Type<${typeName}>`,
|
71
|
+
inter: Type,
|
72
|
+
union: Type,
|
73
|
+
toString(): string {
|
74
|
+
return type.name;
|
75
|
+
},
|
76
|
+
};
|
77
|
+
|
78
|
+
return type;
|
79
|
+
}
|
80
|
+
|
81
|
+
// @internal
|
82
|
+
export function isConstructor<T>(token: Type<T> | Constructor<T & object>): token is Constructor<T & object> {
|
83
|
+
return typeof token == "function";
|
84
|
+
}
|
@@ -0,0 +1,165 @@
|
|
1
|
+
import { assert } from "./errors";
|
2
|
+
import type { FactoryProvider, Provider } from "./provider";
|
3
|
+
import { Scope } from "./scope";
|
4
|
+
import { type Constructor, type Token, Type } from "./token";
|
5
|
+
import type { TokenRef } from "./tokensRef";
|
6
|
+
import { getTypeName } from "./utils/typeName";
|
7
|
+
import type { ValueRef } from "./valueRef";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Token registration options.
|
11
|
+
*/
|
12
|
+
export interface RegistrationOptions {
|
13
|
+
/**
|
14
|
+
* The scope of the registration.
|
15
|
+
*/
|
16
|
+
readonly scope?: Scope;
|
17
|
+
}
|
18
|
+
|
19
|
+
// @internal
|
20
|
+
export type Decorator = "Inject" | "InjectAll" | "Optional" | "OptionalAll";
|
21
|
+
|
22
|
+
// @internal
|
23
|
+
export interface MethodDependency {
|
24
|
+
readonly decorator: Decorator;
|
25
|
+
readonly tokenRef: TokenRef;
|
26
|
+
|
27
|
+
// The index of the annotated parameter (zero-based)
|
28
|
+
readonly index: number;
|
29
|
+
}
|
30
|
+
|
31
|
+
// @internal
|
32
|
+
export interface Dependencies {
|
33
|
+
readonly constructor: MethodDependency[];
|
34
|
+
readonly methods: Map<string | symbol, MethodDependency[]>;
|
35
|
+
}
|
36
|
+
|
37
|
+
// @internal
|
38
|
+
export interface Registration<T = any> {
|
39
|
+
value?: ValueRef<T>;
|
40
|
+
readonly provider: Provider<T>;
|
41
|
+
readonly options?: RegistrationOptions;
|
42
|
+
readonly dependencies?: Dependencies;
|
43
|
+
}
|
44
|
+
|
45
|
+
// @internal
|
46
|
+
export class TokenRegistry {
|
47
|
+
private readonly myMap = new Map<Token, Registration[]>();
|
48
|
+
|
49
|
+
constructor(private readonly parent: TokenRegistry | undefined) {}
|
50
|
+
|
51
|
+
get<T>(token: Token<T>): Registration<T> | undefined {
|
52
|
+
// To clarify, at(-1) means we take the last added registration for this token
|
53
|
+
return this.getAll(token)?.at(-1);
|
54
|
+
}
|
55
|
+
|
56
|
+
getAll<T>(token: Token<T>): Registration<T>[] | undefined {
|
57
|
+
const internal = internals.get(token);
|
58
|
+
return (internal && [internal]) || this.getAllFromParent(token);
|
59
|
+
}
|
60
|
+
|
61
|
+
//
|
62
|
+
// set(...) overloads added because of TS distributive conditional types.
|
63
|
+
//
|
64
|
+
// TODO(Edoardo): is there a better way? Maybe refactor the Token<T> type
|
65
|
+
// into two types, TokenObject<T extends object> | Token<T>
|
66
|
+
//
|
67
|
+
|
68
|
+
set<T extends object>(token: Type<T> | Constructor<T>, registration: Registration<T>): void;
|
69
|
+
set<T>(token: Token<T>, registration: Registration<T>): void;
|
70
|
+
set<T>(token: Token<T>, registration: Registration<T>): void {
|
71
|
+
assert(!internals.has(token), `cannot register reserved token ${token.name}`);
|
72
|
+
|
73
|
+
let registrations = this.myMap.get(token);
|
74
|
+
|
75
|
+
if (!registrations) {
|
76
|
+
this.myMap.set(token, (registrations = []));
|
77
|
+
}
|
78
|
+
|
79
|
+
registrations.push(registration);
|
80
|
+
}
|
81
|
+
|
82
|
+
delete<T>(token: Token<T>): Registration<T>[] | undefined {
|
83
|
+
const registrations = this.myMap.get(token);
|
84
|
+
this.myMap.delete(token);
|
85
|
+
return registrations;
|
86
|
+
}
|
87
|
+
|
88
|
+
deleteAll(): [Token[], Registration[]] {
|
89
|
+
const tokens = Array.from(this.myMap.keys());
|
90
|
+
const registrations = Array.from(this.myMap.values()).flat();
|
91
|
+
this.myMap.clear();
|
92
|
+
return [tokens, registrations];
|
93
|
+
}
|
94
|
+
|
95
|
+
clearRegistrations(): unknown[] {
|
96
|
+
const values = new Set<unknown>();
|
97
|
+
|
98
|
+
for (const registrations of this.myMap.values()) {
|
99
|
+
for (let i = 0; i < registrations.length; i++) {
|
100
|
+
const registration = registrations[i]!;
|
101
|
+
const value = registration.value;
|
102
|
+
|
103
|
+
if (value) {
|
104
|
+
values.add(value.current);
|
105
|
+
}
|
106
|
+
|
107
|
+
registrations[i] = {
|
108
|
+
...registration,
|
109
|
+
value: undefined,
|
110
|
+
};
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
return Array.from(values);
|
115
|
+
}
|
116
|
+
|
117
|
+
private getAllFromParent<T>(token: Token<T>): Registration<T>[] | undefined {
|
118
|
+
const registrations = this.myMap.get(token);
|
119
|
+
return registrations || this.parent?.getAllFromParent(token);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
// @internal
|
124
|
+
export function isBuilder(provider: Provider): boolean {
|
125
|
+
return builders.has(provider);
|
126
|
+
}
|
127
|
+
|
128
|
+
/**
|
129
|
+
* Create a one-off type token from a factory function.
|
130
|
+
*
|
131
|
+
* @example
|
132
|
+
* ```ts
|
133
|
+
* class Wizard {
|
134
|
+
* wand = inject(
|
135
|
+
* Build(() => {
|
136
|
+
* const wand = inject(Wand);
|
137
|
+
* wand.owner = this;
|
138
|
+
* // ...
|
139
|
+
* return wand;
|
140
|
+
* }),
|
141
|
+
* );
|
142
|
+
* }
|
143
|
+
* ```
|
144
|
+
*
|
145
|
+
* @__NO_SIDE_EFFECTS__
|
146
|
+
*/
|
147
|
+
export function Build<Value>(factory: (...args: []) => Value): Type<Value> {
|
148
|
+
const token = Type<Value>(`Build<${getTypeName(factory)}>`);
|
149
|
+
const provider: FactoryProvider<Value> = {
|
150
|
+
useFactory: factory,
|
151
|
+
};
|
152
|
+
|
153
|
+
internals.set(token, {
|
154
|
+
provider: provider,
|
155
|
+
options: {
|
156
|
+
scope: Scope.Transient,
|
157
|
+
},
|
158
|
+
});
|
159
|
+
|
160
|
+
builders.add(provider);
|
161
|
+
return token;
|
162
|
+
}
|
163
|
+
|
164
|
+
const internals = new WeakMap<Token, Registration>();
|
165
|
+
const builders = new WeakSet<Provider>();
|