ts-ioc-container 72.0.0 → 72.2.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/AGENTS.md +179 -0
- package/README.md +94 -0
- package/cjm/container/Container.js +2 -2
- package/cjm/container/EmptyContainer.js +17 -15
- package/cjm/errors/ArgumentNotFoundError.js +1 -0
- package/cjm/errors/CannonSingletonApplyTwiceError.js +2 -14
- package/cjm/errors/CannotApplySingletonTwiceError.js +18 -0
- package/cjm/errors/ContainerDisposedError.js +1 -0
- package/cjm/errors/ContainerError.js +1 -0
- package/cjm/errors/ContainerNotFoundError.js +1 -0
- package/cjm/errors/DependencyMissingKeyError.js +1 -0
- package/cjm/errors/DependencyNotFoundError.js +1 -0
- package/cjm/errors/MethodNotImplementedError.js +1 -0
- package/cjm/errors/ProviderDisposedError.js +1 -0
- package/cjm/errors/TypedEventDisposedError.js +1 -0
- package/cjm/errors/UnsupportedTokenTypeError.js +1 -0
- package/cjm/index.js +8 -3
- package/cjm/metadata/class.js +3 -1
- package/cjm/metadata/method.js +3 -1
- package/cjm/metadata/parameter.js +7 -1
- package/cjm/provider/Provider.js +6 -6
- package/cjm/registration/Registration.js +2 -2
- package/cjm/token/ConstantToken.js +4 -4
- package/cjm/token/FunctionToken.js +1 -1
- package/cjm/token/GroupInstanceToken.js +4 -4
- package/cjm/token/toToken.js +1 -1
- package/cjm/utils/TypedEvent.js +1 -1
- package/cjm/utils/array.js +1 -1
- package/esm/container/Container.js +2 -2
- package/esm/container/EmptyContainer.js +17 -15
- package/esm/errors/ArgumentNotFoundError.js +1 -0
- package/esm/errors/CannonSingletonApplyTwiceError.js +2 -13
- package/esm/errors/CannotApplySingletonTwiceError.js +14 -0
- package/esm/errors/ContainerDisposedError.js +1 -0
- package/esm/errors/ContainerError.js +1 -0
- package/esm/errors/ContainerNotFoundError.js +1 -0
- package/esm/errors/DependencyMissingKeyError.js +1 -0
- package/esm/errors/DependencyNotFoundError.js +1 -0
- package/esm/errors/MethodNotImplementedError.js +1 -0
- package/esm/errors/ProviderDisposedError.js +1 -0
- package/esm/errors/TypedEventDisposedError.js +1 -0
- package/esm/errors/UnsupportedTokenTypeError.js +1 -0
- package/esm/index.js +4 -3
- package/esm/metadata/class.js +1 -0
- package/esm/metadata/method.js +1 -0
- package/esm/metadata/parameter.js +5 -0
- package/esm/provider/Provider.js +6 -6
- package/esm/registration/Registration.js +2 -2
- package/esm/token/ConstantToken.js +4 -4
- package/esm/token/FunctionToken.js +1 -1
- package/esm/token/GroupInstanceToken.js +4 -4
- package/esm/token/toToken.js +1 -1
- package/esm/utils/TypedEvent.js +1 -1
- package/esm/utils/array.js +1 -1
- package/lib/ExecutionContext.ts +6 -0
- package/lib/container/AliasMap.ts +30 -0
- package/lib/container/AutoResolveModule.ts +24 -0
- package/lib/container/Container.ts +311 -0
- package/lib/container/EmptyContainer.ts +166 -0
- package/lib/container/IContainer.ts +156 -0
- package/lib/errors/ArgumentNotFoundError.ts +13 -0
- package/lib/errors/CannonSingletonApplyTwiceError.ts +6 -0
- package/lib/errors/CannotApplySingletonTwiceError.ts +22 -0
- package/lib/errors/ContainerDisposedError.ts +13 -0
- package/lib/errors/ContainerError.ts +15 -0
- package/lib/errors/ContainerNotFoundError.ts +13 -0
- package/lib/errors/DependencyMissingKeyError.ts +13 -0
- package/lib/errors/DependencyNotFoundError.ts +13 -0
- package/lib/errors/MethodNotImplementedError.ts +13 -0
- package/lib/errors/ProviderDisposedError.ts +22 -0
- package/lib/errors/TypedEventDisposedError.ts +22 -0
- package/lib/errors/UnsupportedTokenTypeError.ts +13 -0
- package/lib/hooks/HookCollector.ts +109 -0
- package/lib/hooks/HookContext.ts +85 -0
- package/lib/hooks/combinators.ts +51 -0
- package/lib/hooks/hook.ts +89 -0
- package/lib/hooks/injectProp.ts +14 -0
- package/lib/index.ts +181 -0
- package/lib/injector/IInjector.ts +88 -0
- package/lib/injector/MetadataInjector.ts +100 -0
- package/lib/injector/ProxyInjector.ts +31 -0
- package/lib/injector/SimpleInjector.ts +20 -0
- package/lib/metadata/class.ts +50 -0
- package/lib/metadata/method.ts +47 -0
- package/lib/metadata/parameter.ts +60 -0
- package/lib/metadata/target.ts +25 -0
- package/lib/provider/IProvider.ts +65 -0
- package/lib/provider/Provider.ts +174 -0
- package/lib/registration/IRegistration.ts +235 -0
- package/lib/registration/Registration.ts +122 -0
- package/lib/select.ts +33 -0
- package/lib/token/BindToken.ts +13 -0
- package/lib/token/ClassToken.ts +72 -0
- package/lib/token/ConstantToken.ts +50 -0
- package/lib/token/FunctionToken.ts +79 -0
- package/lib/token/GroupAliasToken.ts +92 -0
- package/lib/token/GroupInstanceToken.ts +70 -0
- package/lib/token/InjectionToken.ts +46 -0
- package/lib/token/SingleAliasToken.ts +84 -0
- package/lib/token/SingleToken.ts +93 -0
- package/lib/token/toToken.ts +46 -0
- package/lib/utils/ProxyRegistry.ts +149 -0
- package/lib/utils/TypedEvent.ts +96 -0
- package/lib/utils/array.ts +28 -0
- package/lib/utils/basic.ts +34 -0
- package/lib/utils/debounce.ts +18 -0
- package/lib/utils/errorHandler.ts +34 -0
- package/lib/utils/fp.ts +86 -0
- package/lib/utils/getConstructorChain.ts +17 -0
- package/lib/utils/memoize.ts +24 -0
- package/lib/utils/once.ts +24 -0
- package/lib/utils/shallowCache.ts +22 -0
- package/lib/utils/task.ts +33 -0
- package/lib/utils/throttle.ts +17 -0
- package/package.json +5 -3
- package/typings/ExecutionContext.d.ts +2 -0
- package/typings/ExecutionContext.d.ts.map +1 -0
- package/typings/container/AliasMap.d.ts +1 -0
- package/typings/container/AliasMap.d.ts.map +1 -0
- package/typings/container/AutoResolveModule.d.ts +16 -0
- package/typings/container/AutoResolveModule.d.ts.map +1 -0
- package/typings/container/Container.d.ts +60 -0
- package/typings/container/Container.d.ts.map +1 -0
- package/typings/container/EmptyContainer.d.ts +51 -0
- package/typings/container/EmptyContainer.d.ts.map +1 -0
- package/typings/container/IContainer.d.ts +75 -0
- package/typings/container/IContainer.d.ts.map +1 -0
- package/typings/errors/ArgumentNotFoundError.d.ts +3 -0
- package/typings/errors/ArgumentNotFoundError.d.ts.map +1 -0
- package/typings/errors/CannonSingletonApplyTwiceError.d.ts +6 -6
- package/typings/errors/CannonSingletonApplyTwiceError.d.ts.map +1 -0
- package/typings/errors/CannotApplySingletonTwiceError.d.ts +12 -0
- package/typings/errors/CannotApplySingletonTwiceError.d.ts.map +1 -0
- package/typings/errors/ContainerDisposedError.d.ts +3 -0
- package/typings/errors/ContainerDisposedError.d.ts.map +1 -0
- package/typings/errors/ContainerError.d.ts +7 -0
- package/typings/errors/ContainerError.d.ts.map +1 -0
- package/typings/errors/ContainerNotFoundError.d.ts +3 -0
- package/typings/errors/ContainerNotFoundError.d.ts.map +1 -0
- package/typings/errors/DependencyMissingKeyError.d.ts +3 -0
- package/typings/errors/DependencyMissingKeyError.d.ts.map +1 -0
- package/typings/errors/DependencyNotFoundError.d.ts +3 -0
- package/typings/errors/DependencyNotFoundError.d.ts.map +1 -0
- package/typings/errors/MethodNotImplementedError.d.ts +3 -0
- package/typings/errors/MethodNotImplementedError.d.ts.map +1 -0
- package/typings/errors/ProviderDisposedError.d.ts +6 -0
- package/typings/errors/ProviderDisposedError.d.ts.map +1 -0
- package/typings/errors/TypedEventDisposedError.d.ts +6 -0
- package/typings/errors/TypedEventDisposedError.d.ts.map +1 -0
- package/typings/errors/UnsupportedTokenTypeError.d.ts +3 -0
- package/typings/errors/UnsupportedTokenTypeError.d.ts.map +1 -0
- package/typings/hooks/HookCollector.d.ts +36 -0
- package/typings/hooks/HookCollector.d.ts.map +1 -0
- package/typings/hooks/HookContext.d.ts +10 -0
- package/typings/hooks/HookContext.d.ts.map +1 -0
- package/typings/hooks/combinators.d.ts +23 -0
- package/typings/hooks/combinators.d.ts.map +1 -0
- package/typings/hooks/hook.d.ts +39 -0
- package/typings/hooks/hook.d.ts.map +1 -0
- package/typings/hooks/injectProp.d.ts +9 -0
- package/typings/hooks/injectProp.d.ts.map +1 -0
- package/typings/index.d.ts +5 -3
- package/typings/index.d.ts.map +1 -0
- package/typings/injector/IInjector.d.ts +32 -0
- package/typings/injector/IInjector.d.ts.map +1 -0
- package/typings/injector/MetadataInjector.d.ts +58 -0
- package/typings/injector/MetadataInjector.d.ts.map +1 -0
- package/typings/injector/ProxyInjector.d.ts +13 -0
- package/typings/injector/ProxyInjector.d.ts.map +1 -0
- package/typings/injector/SimpleInjector.d.ts +13 -0
- package/typings/injector/SimpleInjector.d.ts.map +1 -0
- package/typings/metadata/class.d.ts +28 -0
- package/typings/metadata/class.d.ts.map +1 -0
- package/typings/metadata/method.d.ts +25 -0
- package/typings/metadata/method.d.ts.map +1 -0
- package/typings/metadata/parameter.d.ts +22 -0
- package/typings/metadata/parameter.d.ts.map +1 -0
- package/typings/metadata/target.d.ts +19 -0
- package/typings/metadata/target.d.ts.map +1 -0
- package/typings/provider/IProvider.d.ts +25 -0
- package/typings/provider/IProvider.d.ts.map +1 -0
- package/typings/provider/Provider.d.ts +30 -0
- package/typings/provider/Provider.d.ts.map +1 -0
- package/typings/registration/IRegistration.d.ts +137 -0
- package/typings/registration/IRegistration.d.ts.map +1 -0
- package/typings/registration/Registration.d.ts +23 -0
- package/typings/registration/Registration.d.ts.map +1 -0
- package/typings/select.d.ts +15 -0
- package/typings/select.d.ts.map +1 -0
- package/typings/token/BindToken.d.ts +3 -0
- package/typings/token/BindToken.d.ts.map +1 -0
- package/typings/token/ClassToken.d.ts +2 -0
- package/typings/token/ClassToken.d.ts.map +1 -0
- package/typings/token/ConstantToken.d.ts +14 -0
- package/typings/token/ConstantToken.d.ts.map +1 -0
- package/typings/token/FunctionToken.d.ts +10 -0
- package/typings/token/FunctionToken.d.ts.map +1 -0
- package/typings/token/GroupAliasToken.d.ts +14 -0
- package/typings/token/GroupAliasToken.d.ts.map +1 -0
- package/typings/token/GroupInstanceToken.d.ts +19 -0
- package/typings/token/GroupInstanceToken.d.ts.map +1 -0
- package/typings/token/InjectionToken.d.ts +8 -0
- package/typings/token/InjectionToken.d.ts.map +1 -0
- package/typings/token/SingleAliasToken.d.ts +6 -0
- package/typings/token/SingleAliasToken.d.ts.map +1 -0
- package/typings/token/SingleToken.d.ts +18 -0
- package/typings/token/SingleToken.d.ts.map +1 -0
- package/typings/token/toToken.d.ts +12 -0
- package/typings/token/toToken.d.ts.map +1 -0
- package/typings/utils/ProxyRegistry.d.ts +63 -0
- package/typings/utils/ProxyRegistry.d.ts.map +1 -0
- package/typings/utils/TypedEvent.d.ts +42 -0
- package/typings/utils/TypedEvent.d.ts.map +1 -0
- package/typings/utils/array.d.ts +7 -0
- package/typings/utils/array.d.ts.map +1 -0
- package/typings/utils/basic.d.ts +12 -0
- package/typings/utils/basic.d.ts.map +1 -0
- package/typings/utils/debounce.d.ts +2 -0
- package/typings/utils/debounce.d.ts.map +1 -0
- package/typings/utils/errorHandler.d.ts +4 -0
- package/typings/utils/errorHandler.d.ts.map +1 -0
- package/typings/utils/fp.d.ts +8 -0
- package/typings/utils/fp.d.ts.map +1 -0
- package/typings/utils/getConstructorChain.d.ts +9 -0
- package/typings/utils/getConstructorChain.d.ts.map +1 -0
- package/typings/utils/memoize.d.ts +10 -0
- package/typings/utils/memoize.d.ts.map +1 -0
- package/typings/utils/once.d.ts +2 -0
- package/typings/utils/once.d.ts.map +1 -0
- package/typings/utils/shallowCache.d.ts +2 -0
- package/typings/utils/shallowCache.d.ts.map +1 -0
- package/typings/utils/task.d.ts +13 -0
- package/typings/utils/task.d.ts.map +1 -0
- package/typings/utils/throttle.d.ts +2 -0
- package/typings/utils/throttle.d.ts.map +1 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ArgsFn,
|
|
3
|
+
type DecorateFn,
|
|
4
|
+
type GetCacheKey,
|
|
5
|
+
type IProvider,
|
|
6
|
+
type ProviderOptions,
|
|
7
|
+
type ResolveDependency,
|
|
8
|
+
type ScopeAccessOptions,
|
|
9
|
+
type ScopeAccessRule,
|
|
10
|
+
type ProviderHook,
|
|
11
|
+
} from './IProvider';
|
|
12
|
+
import type { DependencyKey } from '../container/IContainer';
|
|
13
|
+
import { type constructor, toString } from '../utils/basic';
|
|
14
|
+
import { CannotApplySingletonTwiceError } from '../errors/CannotApplySingletonTwiceError';
|
|
15
|
+
import { ProviderDisposedError } from '../errors/ProviderDisposedError';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The factory behind a registration: creates or returns the dependency and
|
|
19
|
+
* applies the pipes (`singleton`, `lazy`, `decorate`, args and access rules).
|
|
20
|
+
* Usually created for you by {@link Registration}; use it directly only for
|
|
21
|
+
* low-level `container.register(key, provider)` calls.
|
|
22
|
+
*/
|
|
23
|
+
export class Provider<T = any> implements IProvider<T> {
|
|
24
|
+
static fromClass<T>(Target: constructor<T>): IProvider<T> {
|
|
25
|
+
return new Provider(({ scope, ...options }) => scope.resolve(Target, options));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static fromValue<T>(value: T): IProvider<T> {
|
|
29
|
+
return new Provider(() => value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static fromKey<T>(key: DependencyKey) {
|
|
33
|
+
return new Provider<T>(({ scope }) => scope.resolve(key));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private readonly argsFnList: ArgsFn[] = [];
|
|
37
|
+
private readonly accessRules: ScopeAccessRule[] = [];
|
|
38
|
+
private readonly mappers: DecorateFn<T>[] = [];
|
|
39
|
+
private isLazy = false;
|
|
40
|
+
private isAutoResolve = false;
|
|
41
|
+
private cache = new Map<string, unknown>();
|
|
42
|
+
private getKey: GetCacheKey | undefined;
|
|
43
|
+
private isDisposed: boolean = false;
|
|
44
|
+
private readonly onResolvedHookList: ProviderHook[] = [];
|
|
45
|
+
|
|
46
|
+
constructor(private readonly resolveDependency: ResolveDependency<T>) {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @throws {ProviderDisposedError} when the provider has already been disposed.
|
|
50
|
+
* @throws {unknown} rethrows whatever an `onResolved` hook threw.
|
|
51
|
+
*/
|
|
52
|
+
resolve(options: ProviderOptions): T {
|
|
53
|
+
ProviderDisposedError.assert(
|
|
54
|
+
!this.isDisposed,
|
|
55
|
+
'Provider is already disposed: its scope was disposed. Resolve from a live scope.',
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
if (!this.getKey) {
|
|
59
|
+
return this.resolveDep(options);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const key = toString(this.getKey(options.args ?? []));
|
|
63
|
+
|
|
64
|
+
if (!this.cache.has(key)) {
|
|
65
|
+
this.cache.set(key, this.resolveDep(options));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return this.cache.get(key)! as T;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @throws {unknown} rethrows whatever an `onResolved` hook threw.
|
|
73
|
+
*/
|
|
74
|
+
private resolveDep({ scope, args = [], lazy }: ProviderOptions): T {
|
|
75
|
+
let dependency = this.resolveDependency({
|
|
76
|
+
scope,
|
|
77
|
+
args: this.argsFnList.reduce((acc, current) => current({ scope, args: acc }), args),
|
|
78
|
+
lazy: lazy ?? this.isLazy,
|
|
79
|
+
});
|
|
80
|
+
dependency = this.mappers.reduce((acc, current) => current(acc, scope), dependency);
|
|
81
|
+
for (const onResolved of this.onResolvedHookList) {
|
|
82
|
+
onResolved(dependency, scope);
|
|
83
|
+
}
|
|
84
|
+
return dependency;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
map(...mappers: DecorateFn<T>[]): this {
|
|
88
|
+
this.mappers.push(...mappers);
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
addAccessRule(...rules: ScopeAccessRule[]): this {
|
|
93
|
+
this.accessRules.push(...rules);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lazy(): this {
|
|
98
|
+
this.isLazy = true;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
autoResolve(): this {
|
|
103
|
+
this.isAutoResolve = true;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @throws {ProviderDisposedError} when the provider has already been disposed.
|
|
109
|
+
*/
|
|
110
|
+
isAutoResolvable(): boolean {
|
|
111
|
+
ProviderDisposedError.assert(
|
|
112
|
+
!this.isDisposed,
|
|
113
|
+
'Provider is already disposed: its scope was disposed. Resolve from a live scope.',
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return this.isAutoResolve;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
addArgsFn(...fns: ArgsFn[]): this {
|
|
120
|
+
this.argsFnList.push(...fns);
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @throws {ProviderDisposedError} when the provider has already been disposed.
|
|
126
|
+
*/
|
|
127
|
+
hasAccess(options: ScopeAccessOptions): boolean {
|
|
128
|
+
ProviderDisposedError.assert(
|
|
129
|
+
!this.isDisposed,
|
|
130
|
+
'Provider is already disposed: its scope was disposed. Resolve from a live scope.',
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return this.accessRules.reduce((acc, rule) => rule(options, acc), true);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @throws {CannotApplySingletonTwiceError} when the provider is already configured as a singleton.
|
|
138
|
+
*/
|
|
139
|
+
singleton(getCacheKey: GetCacheKey = () => '1'): this {
|
|
140
|
+
CannotApplySingletonTwiceError.assert(
|
|
141
|
+
!this.getKey,
|
|
142
|
+
'Provider is already singleton: singleton() was applied twice to the same provider. Remove the duplicate singleton().',
|
|
143
|
+
);
|
|
144
|
+
this.getKey = getCacheKey;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Hooks run after every mapper, on each resolved dependency. A singleton provider
|
|
150
|
+
* caches the dependency, so its hooks run once — on the resolve that filled the cache.
|
|
151
|
+
*/
|
|
152
|
+
onResolved(...hooks: ProviderHook[]): this {
|
|
153
|
+
this.onResolvedHookList.push(...hooks);
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @throws {ProviderDisposedError} when the provider has already been disposed.
|
|
159
|
+
*/
|
|
160
|
+
dispose(): void {
|
|
161
|
+
ProviderDisposedError.assert(
|
|
162
|
+
!this.isDisposed,
|
|
163
|
+
'Provider is already disposed: its scope was disposed. Resolve from a live scope.',
|
|
164
|
+
);
|
|
165
|
+
this.isDisposed = true;
|
|
166
|
+
this.isAutoResolve = false;
|
|
167
|
+
this.getKey = undefined;
|
|
168
|
+
this.cache.clear();
|
|
169
|
+
this.accessRules.splice(0, this.accessRules.length);
|
|
170
|
+
this.mappers.splice(0, this.mappers.length);
|
|
171
|
+
this.argsFnList.splice(0, this.argsFnList.length);
|
|
172
|
+
this.onResolvedHookList.splice(0, this.onResolvedHookList.length);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { type DependencyKey, type IContainer, type IContainerModule, isDependencyKey } from '../container/IContainer';
|
|
2
|
+
import type { ArgsFn, DecorateFn, GetCacheKey, IProvider, ProviderHook, ScopeAccessRule } from '../provider/IProvider';
|
|
3
|
+
import { SingleToken } from '../token/SingleToken';
|
|
4
|
+
import { BindToken } from '../token/BindToken';
|
|
5
|
+
import { MapFn } from '../utils/fp';
|
|
6
|
+
import { addClassMeta, getClassMeta } from '../metadata/class';
|
|
7
|
+
import { type constructor } from '../utils/basic';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Decides whether a registration is copied into a scope when that scope is
|
|
11
|
+
* created (or when the registration is added). `prev` is the result of the
|
|
12
|
+
* rules before it, so rules chain. Set with {@link scope} or `registration.when(...)`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const onlyRequest: ScopeMatchRule = (s) => s.hasTag('request');
|
|
16
|
+
*/
|
|
17
|
+
export type ScopeMatchRule = (s: IContainer, prev: boolean) => boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A provider transform usable in both places: `registration.pipe(...)` calls
|
|
21
|
+
* `mapProvider`, `@register(...)` calls `mapRegistration`. Build one with
|
|
22
|
+
* {@link registerPipe}; `singleton()`, `lazy()`, `scopeAccess()` etc. are all pipes.
|
|
23
|
+
*/
|
|
24
|
+
export interface ProviderPipe<T = unknown> {
|
|
25
|
+
mapProvider(p: IProvider<T>): IProvider<T>;
|
|
26
|
+
|
|
27
|
+
mapRegistration(r: IRegistration<T>): IRegistration<T>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Narrows `obj` to a {@link ProviderPipe}. */
|
|
31
|
+
export const isProviderPipe = <T>(obj: unknown): obj is ProviderPipe<T> =>
|
|
32
|
+
obj !== null && typeof obj === 'object' && 'mapProvider' in obj;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Turns a provider transform into a {@link ProviderPipe}, so it works both in
|
|
36
|
+
* `@register(...)` and in `registration.pipe(...)`.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const cached = <T>() => registerPipe<T>((p) => p.singleton());
|
|
40
|
+
*
|
|
41
|
+
* @register(bindTo('Cache'), cached())
|
|
42
|
+
* class Cache {}
|
|
43
|
+
*/
|
|
44
|
+
export const registerPipe = <T>(mapProvider: (p: IProvider<T>) => IProvider<T>): ProviderPipe<T> => ({
|
|
45
|
+
mapProvider,
|
|
46
|
+
mapRegistration: (r) => r.pipe(mapProvider),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Anything that can name a binding key: a raw key, or a token that knows how to bind itself.
|
|
51
|
+
*/
|
|
52
|
+
export type Bindable<T = any> = DependencyKey | BindToken<T>;
|
|
53
|
+
|
|
54
|
+
/** Normalizes a {@link Bindable}: a raw key becomes a `SingleToken`, a token is returned as is. */
|
|
55
|
+
export const toBindToken = <T>(target: Bindable<T>): BindToken<T> =>
|
|
56
|
+
isDependencyKey(target) ? new SingleToken<T>(target) : target;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Everything accepted at provider level - by `IRegistration.pipe(...)`.
|
|
60
|
+
*/
|
|
61
|
+
export type ProviderMapper<T = any> = MapFn<IProvider<T>> | ProviderPipe<T>;
|
|
62
|
+
|
|
63
|
+
/** Normalizes a {@link ProviderMapper} to a plain provider transform. */
|
|
64
|
+
export const toProviderFn = <T>(mapper: ProviderMapper<T>): MapFn<IProvider<T>> =>
|
|
65
|
+
isProviderPipe<T>(mapper) ? mapper.mapProvider.bind(mapper) : mapper;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Everything accepted at registration level - by `@register(...)`.
|
|
69
|
+
*/
|
|
70
|
+
export type RegistrationMapper<T = any> = MapFn<IRegistration<T>> | ProviderPipe<T> | Bindable<T>;
|
|
71
|
+
|
|
72
|
+
/** Normalizes a {@link RegistrationMapper} to a plain registration transform (a `Bindable` becomes `bindTo(...)`). */
|
|
73
|
+
export const toRegistrationFn = <T>(mapper: RegistrationMapper<T>): MapFn<IRegistration<T>> => {
|
|
74
|
+
if (typeof mapper === 'function') return mapper;
|
|
75
|
+
if (isProviderPipe<T>(mapper)) return (r) => mapper.mapRegistration(r);
|
|
76
|
+
return bindTo(mapper);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A recipe for putting one provider into matching scopes. Created with
|
|
81
|
+
* `Registration.fromClass` / `fromValue` / `fromFn` / `fromKey` and added with
|
|
82
|
+
* `container.addRegistration(...)`; `applyTo(scope)` registers the provider
|
|
83
|
+
* when the scope rules match.
|
|
84
|
+
*/
|
|
85
|
+
export interface IRegistration<T = any> extends IContainerModule {
|
|
86
|
+
getKeyOrFail(): DependencyKey;
|
|
87
|
+
|
|
88
|
+
when(...predicates: ScopeMatchRule[]): this;
|
|
89
|
+
|
|
90
|
+
bindToKey(key: DependencyKey): this;
|
|
91
|
+
|
|
92
|
+
bindTo(key: Bindable): this;
|
|
93
|
+
|
|
94
|
+
pipe(...mappers: ProviderMapper<T>[]): this;
|
|
95
|
+
|
|
96
|
+
bindToAlias(alias: DependencyKey): this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The dependency type `T` of an `IRegistration<T>`. */
|
|
100
|
+
export type ReturnTypeOfRegistration<T> = T extends IRegistration<infer R> ? R : never;
|
|
101
|
+
|
|
102
|
+
const METADATA_KEY = 'registration';
|
|
103
|
+
/** Reads the registration transforms written on a class by {@link register}. */
|
|
104
|
+
export const getTransformers = (Target: constructor<unknown>) =>
|
|
105
|
+
getClassMeta<MapFn<IRegistration>[]>(Target, METADATA_KEY) ?? [];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Class decorator that configures how `Registration.fromClass(Target)` registers
|
|
109
|
+
* the class: binding keys, scope rules and provider pipes. A bare key or token
|
|
110
|
+
* is shorthand for `bindTo(...)`. Without a key the class name is used.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* @register(bindTo(ILoggerToken), scope((s) => s.hasTag('application')), singleton())
|
|
114
|
+
* class Logger implements ILogger {}
|
|
115
|
+
*
|
|
116
|
+
* container.addRegistration(Registration.fromClass(Logger));
|
|
117
|
+
*/
|
|
118
|
+
export const register = (...mappers: RegistrationMapper[]) =>
|
|
119
|
+
addClassMeta(METADATA_KEY, (acc: MapFn<IRegistration>[] | undefined) => {
|
|
120
|
+
const result = mappers.map((m) => toRegistrationFn(m));
|
|
121
|
+
return acc ? [...result, ...acc] : result;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Binds a registration to one or more keys or tokens. A `SingleToken` binds its
|
|
126
|
+
* key; an alias token (`toSingleAlias`, `toGroupAlias`) adds an alias.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* @register(bindTo(ILoggerToken))
|
|
130
|
+
* class Logger {}
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* Registration.fromValue(config).bindTo('Config');
|
|
134
|
+
*/
|
|
135
|
+
export const bindTo =
|
|
136
|
+
(...tokens: Bindable[]): MapFn<IRegistration> =>
|
|
137
|
+
(r) => {
|
|
138
|
+
for (const token of tokens) {
|
|
139
|
+
toBindToken(token).bindTo(r);
|
|
140
|
+
}
|
|
141
|
+
return r;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Limits which scopes a registration is copied into. Without it, a registration
|
|
146
|
+
* lands in every scope, so a `singleton()` becomes one instance per scope.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* @register(bindTo('Repo'), scope((s) => s.hasTag('request')))
|
|
150
|
+
* class Repo {}
|
|
151
|
+
*/
|
|
152
|
+
export const scope =
|
|
153
|
+
(...rules: ScopeMatchRule[]): MapFn<IRegistration> =>
|
|
154
|
+
(r) =>
|
|
155
|
+
r.when(...rules);
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Appends fixed values after the runtime args handed to the provider.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* @register(bindTo('Client'), appendArgs('https://api.example.com'))
|
|
162
|
+
* class Client {
|
|
163
|
+
* constructor(@inject(arg(0)) readonly baseUrl: string) {}
|
|
164
|
+
* }
|
|
165
|
+
*/
|
|
166
|
+
export const appendArgs = <T>(...extraArgs: unknown[]) =>
|
|
167
|
+
registerPipe<T>((p) => p.addArgsFn(({ args = [] }) => [...args, ...extraArgs]));
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Appends values computed at resolve time after the runtime args.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* @register(bindTo('Client'), appendArgsFn(({ scope }) => [scope.resolve('API_URL')]))
|
|
174
|
+
* class Client {}
|
|
175
|
+
*/
|
|
176
|
+
export const appendArgsFn = <T>(fn: ArgsFn) =>
|
|
177
|
+
registerPipe<T>((p) => p.addArgsFn((options) => [...(options.args ?? []), ...fn(options)]));
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Controls from which scopes the provider may be resolved (unlike {@link scope},
|
|
181
|
+
* which controls where it is registered). A denied lookup falls through to the parent.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* @register(bindTo('Admin'), scopeAccess(({ invocationScope }) => invocationScope.hasTag('admin')))
|
|
185
|
+
* class AdminService {}
|
|
186
|
+
*/
|
|
187
|
+
export const scopeAccess = <T>(rule: ScopeAccessRule) => registerPipe<T>((p) => p.addAccessRule(rule));
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Resolves the dependency as a proxy that constructs the real instance on first
|
|
191
|
+
* member access.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* @register(bindTo('Heavy'), lazy())
|
|
195
|
+
* class HeavyService {}
|
|
196
|
+
*/
|
|
197
|
+
export const lazy = <T>() => registerPipe<T>((p) => p.lazy());
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Marks the provider for eager resolution when the scope is set up; needs
|
|
201
|
+
* `container.useModule(new AutoResolveModule())`.
|
|
202
|
+
*/
|
|
203
|
+
export const autoResolve = <T>() => registerPipe<T>((p) => p.autoResolve());
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Wraps or replaces the resolved dependency. Order relative to `lazy()` decides
|
|
207
|
+
* whether the proxy or the real instance is decorated.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* @register(bindTo('Api'), decorate((api, scope) => new LoggingApi(api)))
|
|
211
|
+
* class Api {}
|
|
212
|
+
*/
|
|
213
|
+
export const decorate = (...fns: DecorateFn[]) => registerPipe((p) => p.map(...fns));
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Caches the dependency per provider, which means per scope the registration
|
|
217
|
+
* was copied into (combine with {@link scope}). `getCacheKey` receives the
|
|
218
|
+
* runtime args and caches one instance per distinct key.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* @register(bindTo('Db'), scope((s) => s.hasTag('application')), singleton())
|
|
222
|
+
* class Db {}
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* // one instance per tenant id passed as the first runtime arg
|
|
226
|
+
* @register(bindTo('TenantDb'), singleton(([tenantId]) => String(tenantId)))
|
|
227
|
+
* class TenantDb {}
|
|
228
|
+
*/
|
|
229
|
+
export const singleton = <T = unknown>(getCacheKey?: GetCacheKey) => registerPipe<T>((p) => p.singleton(getCacheKey));
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Registration-level form of `IProvider.onResolved`: attaches provider hooks to
|
|
233
|
+
* the piped registration's provider.
|
|
234
|
+
*/
|
|
235
|
+
export const onResolve = <T = unknown>(...hooks: ProviderHook[]) => registerPipe<T>((p) => p.onResolved(...hooks));
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DependencyKey, IContainer } from '../container/IContainer';
|
|
2
|
+
import { Provider } from '../provider/Provider';
|
|
3
|
+
import type { IProvider, ResolveDependency } from '../provider/IProvider';
|
|
4
|
+
import { DependencyMissingKeyError } from '../errors/DependencyMissingKeyError';
|
|
5
|
+
import {
|
|
6
|
+
type Bindable,
|
|
7
|
+
type IRegistration,
|
|
8
|
+
type ProviderMapper,
|
|
9
|
+
type ScopeMatchRule,
|
|
10
|
+
getTransformers,
|
|
11
|
+
toBindToken,
|
|
12
|
+
toProviderFn,
|
|
13
|
+
} from './IRegistration';
|
|
14
|
+
import { type MapFn, pipe } from '../utils/fp';
|
|
15
|
+
import { type constructor, Is } from '../utils/basic';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Builds registrations for `container.addRegistration(...)`. Usually imported as
|
|
19
|
+
* `Registration as R`.
|
|
20
|
+
*
|
|
21
|
+
* - `R.fromClass(Class)` - reads the class's `@register(...)` config; the key
|
|
22
|
+
* defaults to the class name.
|
|
23
|
+
* - `R.fromValue(value).bindTo('Key')` - a constant.
|
|
24
|
+
* - `R.fromFn(({ scope, args }) => ...).bindTo('Key')` - a factory.
|
|
25
|
+
* - `R.fromKey('Other').bindTo('Key')` - an alias of another key.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* container
|
|
29
|
+
* .addRegistration(R.fromClass(Logger))
|
|
30
|
+
* .addRegistration(R.fromValue('https://api.example.com').bindTo('API_URL'))
|
|
31
|
+
* .addRegistration(R.fromFn(({ scope }) => new Client(scope.resolve('API_URL'))).bindTo('Client'));
|
|
32
|
+
*/
|
|
33
|
+
export class Registration<T = any> implements IRegistration<T> {
|
|
34
|
+
static fromClass<T>(Target: constructor<T>, { name }: { name?: string } = {}) {
|
|
35
|
+
const transform = pipe(...getTransformers(Target));
|
|
36
|
+
return transform(new Registration(() => Provider.fromClass(Target), name ?? Target.name));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static fromValue<T>(value: T) {
|
|
40
|
+
if (Is.constructor(value)) {
|
|
41
|
+
const transform = pipe(...getTransformers(value as constructor<T>));
|
|
42
|
+
return transform(new Registration(() => Provider.fromValue(value), value.name));
|
|
43
|
+
}
|
|
44
|
+
return new Registration(() => Provider.fromValue(value));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static fromFn<T>(fn: ResolveDependency<T>) {
|
|
48
|
+
return new Registration(() => new Provider(fn));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static fromKey<T>(key: DependencyKey) {
|
|
52
|
+
return new Registration<T>(() => Provider.fromKey(key));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private mappers: MapFn<IProvider<T>>[] = [];
|
|
56
|
+
private aliases: Set<DependencyKey> = new Set();
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
private createProvider: () => IProvider<T>,
|
|
60
|
+
public key?: DependencyKey,
|
|
61
|
+
private scopeRules: ScopeMatchRule[] = [],
|
|
62
|
+
) {}
|
|
63
|
+
|
|
64
|
+
bindToKey(key: DependencyKey): this {
|
|
65
|
+
this.key = key;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
bindToAlias(alias: DependencyKey): this {
|
|
70
|
+
this.aliases.add(alias);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pipe(...mappers: ProviderMapper<T>[]): this {
|
|
75
|
+
this.mappers.push(...mappers.map(toProviderFn));
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
when(...predicates: ScopeMatchRule[]): this {
|
|
80
|
+
this.scopeRules.push(...predicates);
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
bindTo(key: Bindable): this {
|
|
85
|
+
toBindToken(key).bindTo(this);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private matchScope(container: IContainer): boolean {
|
|
90
|
+
return this.scopeRules.reduce((prev, curr) => curr(container, prev), true);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @throws {DependencyMissingKeyError} when the registration matches the scope but has no binding key.
|
|
95
|
+
*/
|
|
96
|
+
applyTo(container: IContainer): void {
|
|
97
|
+
if (!this.matchScope(container)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!this.key) {
|
|
102
|
+
throw new DependencyMissingKeyError(
|
|
103
|
+
'No key provided for registration: bind it with @register(bindTo(...)) or .bindTo(...).',
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const provider = this.mappers.reduce<IProvider<T>>((p, m) => m(p), this.createProvider());
|
|
108
|
+
container.register(this.key, provider, { aliases: [...this.aliases] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @throws {DependencyMissingKeyError} when no binding key has been set for this registration.
|
|
113
|
+
*/
|
|
114
|
+
getKeyOrFail(): DependencyKey {
|
|
115
|
+
if (!this.key) {
|
|
116
|
+
throw new DependencyMissingKeyError(
|
|
117
|
+
'No key provided for registration: bind it with @register(bindTo(...)) or .bindTo(...).',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return this.key;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/lib/select.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CreateScopeOptions } from './container/IContainer';
|
|
2
|
+
import { toGroupAlias } from './token/GroupAliasToken';
|
|
3
|
+
import { FunctionToken } from './token/FunctionToken';
|
|
4
|
+
import { GroupInstanceToken, InstancePredicate } from './token/GroupInstanceToken';
|
|
5
|
+
import { toToken } from './token/toToken';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shortcuts for common tokens.
|
|
9
|
+
*
|
|
10
|
+
* - `select.token(x)` - `toToken(x)`
|
|
11
|
+
* - `select.alias('Key')` - a {@link GroupAliasToken}
|
|
12
|
+
* - `select.instances(predicate)` - instances already created in the scope
|
|
13
|
+
* - `select.scope.current` - the resolving scope itself
|
|
14
|
+
* - `select.scope.create({ tags })` - a new child scope
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* class Handler {
|
|
18
|
+
* constructor(@inject(by(select.scope.current)) private scope: IContainer) {}
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
export const select = {
|
|
22
|
+
alias: toGroupAlias,
|
|
23
|
+
|
|
24
|
+
token: toToken,
|
|
25
|
+
|
|
26
|
+
instances: (predicate: InstancePredicate = () => true) => new GroupInstanceToken(predicate),
|
|
27
|
+
|
|
28
|
+
scope: {
|
|
29
|
+
current: new FunctionToken(({ scope }) => scope),
|
|
30
|
+
|
|
31
|
+
create: (options: CreateScopeOptions) => new FunctionToken(({ scope }) => scope.createScope(options)),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IRegistration } from '../registration/IRegistration';
|
|
2
|
+
|
|
3
|
+
import { Is } from '../utils/basic';
|
|
4
|
+
|
|
5
|
+
/** A token that can bind a registration to itself (a key or an alias). */
|
|
6
|
+
export interface BindToken<T = any> {
|
|
7
|
+
bindTo(r: IRegistration<T>): void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Narrows `token` to a {@link BindToken}. */
|
|
11
|
+
export function isBindToken(token: unknown): token is BindToken {
|
|
12
|
+
return !Is.nullish(token) && typeof token === 'object' && 'bindTo' in token;
|
|
13
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { IContainer } from '../container/IContainer';
|
|
2
|
+
import { forwardArgs, InjectionToken } from './InjectionToken';
|
|
3
|
+
import { type constructor, Serializable } from '../utils/basic';
|
|
4
|
+
import { ArgsFn, ResolveOptions } from '../provider/IProvider';
|
|
5
|
+
|
|
6
|
+
/** A token for a class: resolves it through the scope's injector. `toToken(Class)` creates one. */
|
|
7
|
+
export class ClassToken<T = any> extends InjectionToken<T> implements Serializable {
|
|
8
|
+
private readonly _getArgsFn: ArgsFn;
|
|
9
|
+
private readonly _isLazy: boolean;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly target: constructor<T>,
|
|
13
|
+
{
|
|
14
|
+
getArgsFn = forwardArgs,
|
|
15
|
+
isLazy = false,
|
|
16
|
+
tags = [],
|
|
17
|
+
}: { getArgsFn?: ArgsFn; isLazy?: boolean; tags?: string[] } = {},
|
|
18
|
+
) {
|
|
19
|
+
super(tags);
|
|
20
|
+
this._getArgsFn = getArgsFn;
|
|
21
|
+
this._isLazy = isLazy;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
select<R>(fn: (target: T) => R) {
|
|
25
|
+
return (s: IContainer) => fn(this.resolve(s));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolve(s: IContainer, { args = [], lazy }: ResolveOptions = {}): T {
|
|
29
|
+
return s.resolve(this.target, {
|
|
30
|
+
args: this._getArgsFn({ scope: s, args }),
|
|
31
|
+
lazy: this._isLazy || lazy,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
args(...newArgs: unknown[]) {
|
|
36
|
+
const parentFn = this._getArgsFn;
|
|
37
|
+
return new ClassToken<T>(this.target, {
|
|
38
|
+
getArgsFn: (options) => [...parentFn(options), ...newArgs],
|
|
39
|
+
isLazy: this._isLazy,
|
|
40
|
+
tags: this.getTags(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
argsFn(fn: (s: IContainer) => unknown[]) {
|
|
45
|
+
const parentFn = this._getArgsFn;
|
|
46
|
+
return new ClassToken<T>(this.target, {
|
|
47
|
+
getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
|
|
48
|
+
isLazy: this._isLazy,
|
|
49
|
+
tags: this.getTags(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
lazy() {
|
|
54
|
+
return new ClassToken<T>(this.target, {
|
|
55
|
+
getArgsFn: this._getArgsFn,
|
|
56
|
+
isLazy: true,
|
|
57
|
+
tags: this.getTags(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
addTags(...tags: string[]): ClassToken<T> {
|
|
62
|
+
return new ClassToken<T>(this.target, {
|
|
63
|
+
getArgsFn: this._getArgsFn,
|
|
64
|
+
isLazy: this._isLazy,
|
|
65
|
+
tags: [...this.getTags(), ...tags],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
toString(): string {
|
|
70
|
+
return this.target.name;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { IContainer } from '../container/IContainer';
|
|
2
|
+
import { InjectionToken } from './InjectionToken';
|
|
3
|
+
import { MethodNotImplementedError } from '../errors/MethodNotImplementedError';
|
|
4
|
+
import { Serializable } from '../utils/basic';
|
|
5
|
+
|
|
6
|
+
/** A token that always resolves to a fixed value. It takes no args and cannot be lazy. */
|
|
7
|
+
export class ConstantToken<T = any> extends InjectionToken<T> implements Serializable {
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly token: T,
|
|
10
|
+
{ tags = [] }: { tags?: string[] } = {},
|
|
11
|
+
) {
|
|
12
|
+
super(tags);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
resolve(s: IContainer): T {
|
|
16
|
+
return this.token;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @throws {MethodNotImplementedError} always — a constant token cannot receive static args.
|
|
21
|
+
*/
|
|
22
|
+
args(...deps: unknown[]): InjectionToken<T> {
|
|
23
|
+
throw new MethodNotImplementedError('ConstantToken.args is not implemented');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @throws {MethodNotImplementedError} always — a constant token cannot receive resolved args.
|
|
28
|
+
*/
|
|
29
|
+
argsFn(getArgsFn: (s: IContainer) => unknown[]): InjectionToken<T> {
|
|
30
|
+
throw new MethodNotImplementedError('ConstantToken.argsFn is not implemented');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @throws {MethodNotImplementedError} always — a constant token cannot be made lazy.
|
|
35
|
+
*/
|
|
36
|
+
lazy(): InjectionToken<T> {
|
|
37
|
+
throw new MethodNotImplementedError('ConstantToken.lazy is not implemented');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
addTags(...tags: string[]): ConstantToken<T> {
|
|
41
|
+
return new ConstantToken<T>(this.token, { tags: [...this.getTags(), ...tags] });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @throws {MethodNotImplementedError} always — a constant token has no underlying key.
|
|
46
|
+
*/
|
|
47
|
+
toString(): string {
|
|
48
|
+
throw new MethodNotImplementedError('ConstantToken.toString is not implemented');
|
|
49
|
+
}
|
|
50
|
+
}
|