@zudojs/container 0.0.1 → 1.0.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/README.md +205 -8
- package/dist/containerCore/containerCore.core.d.ts +105 -14
- package/dist/containerCore/containerCore.core.js +211 -44
- package/dist/containerCore/containerCore.scope.d.ts +63 -18
- package/dist/containerCore/containerCore.scope.js +108 -29
- package/dist/containerCore/containerCore.type.d.ts +22 -16
- package/dist/containerLifecycle/containerLifecycle.core.d.ts +59 -9
- package/dist/containerLifecycle/containerLifecycle.core.js +58 -15
- package/dist/containerOptions/containerOptions.type.d.ts +7 -17
- package/dist/containerOptions/containerOptions.type.js +0 -19
- package/dist/containerProvider/containerProvider.core.d.ts +11 -3
- package/dist/containerProvider/containerProvider.core.js +11 -5
- package/dist/containerRegistration/containerRegistration.core.d.ts +9 -3
- package/dist/containerRegistration/containerRegistration.core.js +0 -6
- package/dist/containerRegistry/containerRegistry.core.d.ts +23 -9
- package/dist/containerRegistry/containerRegistry.core.js +50 -47
- package/dist/containerRegistry/containerRegistry.type.d.ts +3 -1
- package/dist/containerRegistry/containerRegistry.type.js +2 -0
- package/dist/containerRegistry/index.d.ts +0 -1
- package/dist/containerRegistry/index.js +0 -1
- package/dist/containerResolution/containerResolution.core.d.ts +42 -10
- package/dist/containerResolution/containerResolution.core.js +207 -64
- package/dist/containerResolution/containerResolution.error.d.ts +68 -3
- package/dist/containerResolution/containerResolution.error.js +107 -3
- package/dist/containerResolution/containerResolution.type.d.ts +35 -3
- package/dist/containerResolution/index.d.ts +1 -0
- package/dist/containerResolution/index.js +1 -0
- package/dist/containerToken/containerToken.type.d.ts +26 -1
- package/dist/containerToken/containerToken.type.js +18 -0
- package/package.json +17 -8
- package/dist/.tsbuildinfo +0 -1
- package/dist/containerCore/containerCore.core.d.ts.map +0 -1
- package/dist/containerCore/containerCore.core.js.map +0 -1
- package/dist/containerCore/containerCore.scope.d.ts.map +0 -1
- package/dist/containerCore/containerCore.scope.js.map +0 -1
- package/dist/containerCore/containerCore.type.d.ts.map +0 -1
- package/dist/containerCore/containerCore.type.js.map +0 -1
- package/dist/containerCore/index.d.ts.map +0 -1
- package/dist/containerCore/index.js.map +0 -1
- package/dist/containerLifecycle/containerLifecycle.core.d.ts.map +0 -1
- package/dist/containerLifecycle/containerLifecycle.core.js.map +0 -1
- package/dist/containerLifecycle/index.d.ts.map +0 -1
- package/dist/containerLifecycle/index.js.map +0 -1
- package/dist/containerOptions/containerOptions.type.d.ts.map +0 -1
- package/dist/containerOptions/containerOptions.type.js.map +0 -1
- package/dist/containerOptions/index.d.ts.map +0 -1
- package/dist/containerOptions/index.js.map +0 -1
- package/dist/containerProvider/containerProvider.core.d.ts.map +0 -1
- package/dist/containerProvider/containerProvider.core.js.map +0 -1
- package/dist/containerProvider/index.d.ts.map +0 -1
- package/dist/containerProvider/index.js.map +0 -1
- package/dist/containerRegistration/containerRegistration.core.d.ts.map +0 -1
- package/dist/containerRegistration/containerRegistration.core.js.map +0 -1
- package/dist/containerRegistration/index.d.ts.map +0 -1
- package/dist/containerRegistration/index.js.map +0 -1
- package/dist/containerRegistry/containerRegistry.core.d.ts.map +0 -1
- package/dist/containerRegistry/containerRegistry.core.js.map +0 -1
- package/dist/containerRegistry/containerRegistry.error.d.ts +0 -12
- package/dist/containerRegistry/containerRegistry.error.d.ts.map +0 -1
- package/dist/containerRegistry/containerRegistry.error.js +0 -22
- package/dist/containerRegistry/containerRegistry.error.js.map +0 -1
- package/dist/containerRegistry/containerRegistry.type.d.ts.map +0 -1
- package/dist/containerRegistry/containerRegistry.type.js.map +0 -1
- package/dist/containerRegistry/index.d.ts.map +0 -1
- package/dist/containerRegistry/index.js.map +0 -1
- package/dist/containerResolution/containerResolution.core.d.ts.map +0 -1
- package/dist/containerResolution/containerResolution.core.js.map +0 -1
- package/dist/containerResolution/containerResolution.error.d.ts.map +0 -1
- package/dist/containerResolution/containerResolution.error.js.map +0 -1
- package/dist/containerResolution/containerResolution.type.d.ts.map +0 -1
- package/dist/containerResolution/containerResolution.type.js.map +0 -1
- package/dist/containerResolution/index.d.ts.map +0 -1
- package/dist/containerResolution/index.js.map +0 -1
- package/dist/containerScope/containerScope.type.d.ts.map +0 -1
- package/dist/containerScope/containerScope.type.js.map +0 -1
- package/dist/containerScope/index.d.ts.map +0 -1
- package/dist/containerScope/index.js.map +0 -1
- package/dist/containerToken/containerToken.type.d.ts.map +0 -1
- package/dist/containerToken/containerToken.type.js.map +0 -1
- package/dist/containerToken/index.d.ts.map +0 -1
- package/dist/containerToken/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Container registration registry.
|
|
3
|
+
*
|
|
4
|
+
* The registry stores normalized registrations and emits change events
|
|
5
|
+
* (REGISTER / REPLACE / REMOVE / CLEAR / RESTORE) so collaborators — notably
|
|
6
|
+
* the resolver's singleton cache — can react to mutations.
|
|
3
7
|
*/
|
|
4
|
-
import { defineRegistration, getRegistrationToken, } from "../containerRegistration/containerRegistration.core.js";
|
|
5
|
-
import { unwrapToken } from "../containerToken/containerToken.type.js";
|
|
8
|
+
import { assertValidProvider, assertValidRegistrationToken, defineRegistration, getRegistrationToken, } from "../containerRegistration/containerRegistration.core.js";
|
|
9
|
+
import { describeToken, unwrapToken, } from "../containerToken/containerToken.type.js";
|
|
6
10
|
import { RegistryOperation } from "./containerRegistry.type.js";
|
|
7
11
|
import { DuplicateRegistrationError, RegistrationNotFoundError, } from "@zudojs/errors";
|
|
8
|
-
|
|
12
|
+
/** Token used for registry-wide events (CLEAR / RESTORE). */
|
|
13
|
+
const REGISTRY_EVENT_TOKEN = Symbol.for("zudojs:container:registry");
|
|
9
14
|
export class ContainerRegistry {
|
|
10
15
|
registrations = new Map();
|
|
11
16
|
listeners = new Set();
|
|
@@ -16,7 +21,7 @@ export class ContainerRegistry {
|
|
|
16
21
|
register(token, provider, options = {}) {
|
|
17
22
|
const t = unwrapToken(token);
|
|
18
23
|
if (this.registrations.has(t) && !this.allowDuplicates)
|
|
19
|
-
throw new DuplicateRegistrationError(
|
|
24
|
+
throw new DuplicateRegistrationError(describeToken(t));
|
|
20
25
|
const prev = this.registrations.get(t);
|
|
21
26
|
const reg = defineRegistration(t, provider, options);
|
|
22
27
|
this.registrations.set(t, reg);
|
|
@@ -29,25 +34,10 @@ export class ContainerRegistry {
|
|
|
29
34
|
});
|
|
30
35
|
return reg;
|
|
31
36
|
}
|
|
32
|
-
registerRegistration(registration) {
|
|
33
|
-
const t = getRegistrationToken(registration);
|
|
34
|
-
if (this.registrations.has(t) && !this.allowDuplicates)
|
|
35
|
-
throw new DuplicateRegistrationError(describeRegistryToken(t));
|
|
36
|
-
const prev = this.registrations.get(t);
|
|
37
|
-
this.registrations.set(t, registration);
|
|
38
|
-
this.emit({
|
|
39
|
-
operation: prev ? RegistryOperation.REPLACE : RegistryOperation.REGISTER,
|
|
40
|
-
token: t,
|
|
41
|
-
registration,
|
|
42
|
-
previous: prev,
|
|
43
|
-
timestamp: new Date(),
|
|
44
|
-
});
|
|
45
|
-
return registration;
|
|
46
|
-
}
|
|
47
37
|
replace(token, provider, options = {}) {
|
|
48
38
|
const t = unwrapToken(token);
|
|
49
39
|
if (!this.registrations.has(t))
|
|
50
|
-
throw new RegistrationNotFoundError(
|
|
40
|
+
throw new RegistrationNotFoundError(describeToken(t));
|
|
51
41
|
const prev = this.registrations.get(t);
|
|
52
42
|
const reg = defineRegistration(t, provider, options);
|
|
53
43
|
this.registrations.set(t, reg);
|
|
@@ -63,12 +53,6 @@ export class ContainerRegistry {
|
|
|
63
53
|
get(token) {
|
|
64
54
|
return this.registrations.get(unwrapToken(token));
|
|
65
55
|
}
|
|
66
|
-
getOrThrow(token) {
|
|
67
|
-
const r = this.get(token);
|
|
68
|
-
if (!r)
|
|
69
|
-
throw new RegistrationNotFoundError(describeRegistryToken(unwrapToken(token)));
|
|
70
|
-
return r;
|
|
71
|
-
}
|
|
72
56
|
has(token) {
|
|
73
57
|
return this.registrations.has(unwrapToken(token));
|
|
74
58
|
}
|
|
@@ -87,10 +71,6 @@ export class ContainerRegistry {
|
|
|
87
71
|
});
|
|
88
72
|
return removed;
|
|
89
73
|
}
|
|
90
|
-
removeOrThrow(token) {
|
|
91
|
-
if (!this.remove(unwrapToken(token)))
|
|
92
|
-
throw new RegistrationNotFoundError(describeRegistryToken(unwrapToken(token)));
|
|
93
|
-
}
|
|
94
74
|
get size() {
|
|
95
75
|
return this.registrations.size;
|
|
96
76
|
}
|
|
@@ -100,25 +80,13 @@ export class ContainerRegistry {
|
|
|
100
80
|
getTokens() {
|
|
101
81
|
return [...this.registrations.keys()];
|
|
102
82
|
}
|
|
103
|
-
getByModule(module) {
|
|
104
|
-
return [...this.registrations.values()].filter((r) => r.metadata.module === module);
|
|
105
|
-
}
|
|
106
|
-
getByScope(scope) {
|
|
107
|
-
return [...this.registrations.values()].filter((r) => r.scope === scope);
|
|
108
|
-
}
|
|
109
|
-
find(predicate) {
|
|
110
|
-
return [...this.registrations.values()].filter(predicate);
|
|
111
|
-
}
|
|
112
|
-
getMetadata(token) {
|
|
113
|
-
return this.get(token)?.metadata;
|
|
114
|
-
}
|
|
115
83
|
clear() {
|
|
116
84
|
if (this.registrations.size === 0)
|
|
117
85
|
return;
|
|
118
86
|
this.registrations.clear();
|
|
119
87
|
this.emit({
|
|
120
88
|
operation: RegistryOperation.CLEAR,
|
|
121
|
-
token:
|
|
89
|
+
token: REGISTRY_EVENT_TOKEN,
|
|
122
90
|
timestamp: new Date(),
|
|
123
91
|
});
|
|
124
92
|
}
|
|
@@ -131,23 +99,58 @@ export class ContainerRegistry {
|
|
|
131
99
|
clearListeners() {
|
|
132
100
|
this.listeners.clear();
|
|
133
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Notifies all listeners. Listener errors are collected and rethrown as a
|
|
104
|
+
* single AggregateError after every listener has been notified — they are
|
|
105
|
+
* never silently swallowed.
|
|
106
|
+
*/
|
|
134
107
|
emit(event) {
|
|
108
|
+
const errors = [];
|
|
135
109
|
for (const l of this.listeners) {
|
|
136
110
|
try {
|
|
137
111
|
l(event);
|
|
138
112
|
}
|
|
139
|
-
catch {
|
|
140
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
errors.push(error);
|
|
141
115
|
}
|
|
142
116
|
}
|
|
117
|
+
if (errors.length > 0)
|
|
118
|
+
throw new AggregateError(errors, `${errors.length} container registry listener(s) failed while handling "${event.operation}".`);
|
|
143
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns an immutable snapshot of the current registrations.
|
|
122
|
+
* Pass the result to {@link restore} to roll the registry back.
|
|
123
|
+
*/
|
|
144
124
|
snapshot() {
|
|
145
125
|
return this.getAll();
|
|
146
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Wholesale replacement of the registry's contents.
|
|
129
|
+
*
|
|
130
|
+
* Every existing registration is discarded and replaced by `regs` (entries
|
|
131
|
+
* are validated first; duplicate tokens in `regs` throw unless
|
|
132
|
+
* `allowDuplicates` is enabled, in which case the last entry wins).
|
|
133
|
+
* Emits a single RESTORE event so caches keyed on the old registrations
|
|
134
|
+
* can be evicted.
|
|
135
|
+
*/
|
|
147
136
|
restore(regs) {
|
|
137
|
+
const next = new Map();
|
|
138
|
+
for (const r of regs) {
|
|
139
|
+
assertValidRegistrationToken(r.token);
|
|
140
|
+
assertValidProvider(r.provider);
|
|
141
|
+
const t = getRegistrationToken(r);
|
|
142
|
+
if (next.has(t) && !this.allowDuplicates)
|
|
143
|
+
throw new DuplicateRegistrationError(describeToken(t));
|
|
144
|
+
next.set(t, r);
|
|
145
|
+
}
|
|
148
146
|
this.registrations.clear();
|
|
149
|
-
for (const r of
|
|
150
|
-
this.registrations.set(
|
|
147
|
+
for (const [t, r] of next)
|
|
148
|
+
this.registrations.set(t, r);
|
|
149
|
+
this.emit({
|
|
150
|
+
operation: RegistryOperation.RESTORE,
|
|
151
|
+
token: REGISTRY_EVENT_TOKEN,
|
|
152
|
+
timestamp: new Date(),
|
|
153
|
+
});
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
//# sourceMappingURL=containerRegistry.core.js.map
|
|
@@ -14,7 +14,9 @@ export declare enum RegistryOperation {
|
|
|
14
14
|
REGISTER = "register",
|
|
15
15
|
REPLACE = "replace",
|
|
16
16
|
REMOVE = "remove",
|
|
17
|
-
CLEAR = "clear"
|
|
17
|
+
CLEAR = "clear",
|
|
18
|
+
/** Wholesale replacement of all registrations via `restore()`. */
|
|
19
|
+
RESTORE = "restore"
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Event emitted when the registry changes.
|
|
@@ -10,5 +10,7 @@ export var RegistryOperation;
|
|
|
10
10
|
RegistryOperation["REPLACE"] = "replace";
|
|
11
11
|
RegistryOperation["REMOVE"] = "remove";
|
|
12
12
|
RegistryOperation["CLEAR"] = "clear";
|
|
13
|
+
/** Wholesale replacement of all registrations via `restore()`. */
|
|
14
|
+
RegistryOperation["RESTORE"] = "restore";
|
|
13
15
|
})(RegistryOperation || (RegistryOperation = {}));
|
|
14
16
|
//# sourceMappingURL=containerRegistry.type.js.map
|
|
@@ -1,26 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency resolver for Zudojs.
|
|
3
|
+
*
|
|
4
|
+
* Lifetime semantics:
|
|
5
|
+
* - SINGLETON instances are always looked up in and stored to the resolver's
|
|
6
|
+
* root singleton cache, no matter where the resolution happens (root or
|
|
7
|
+
* scope). A scope can therefore never re-create or shadow a singleton.
|
|
8
|
+
* - SCOPED instances live in the scope cache supplied via
|
|
9
|
+
* `ResolutionOptions.cache`. Resolving a SCOPED registration without a
|
|
10
|
+
* scope cache throws a ScopedResolutionError; a SCOPED dependency of a
|
|
11
|
+
* SINGLETON throws a CaptiveDependencyError.
|
|
12
|
+
* - TRANSIENT instances are never cached.
|
|
13
|
+
*
|
|
14
|
+
* Every freshly created instance (any lifetime, never a cache hit) is
|
|
15
|
+
* reported through `ResolutionOptions.onInstanceCreated` in creation order,
|
|
16
|
+
* so owners can track transitively created dependencies for disposal — not
|
|
17
|
+
* just the top-level result of a `resolve()` call.
|
|
18
|
+
*
|
|
19
|
+
* Factories registered as SINGLETON or SCOPED must be synchronous: a Promise
|
|
20
|
+
* result throws an AsyncProviderError instead of being cached as the instance.
|
|
21
|
+
*
|
|
22
|
+
* The resolver subscribes to registry change events: REPLACE/REMOVE evict the
|
|
23
|
+
* affected token's cached singleton, CLEAR/RESTORE evict all cached
|
|
24
|
+
* singletons. Each eviction is reported through the `onSingletonEvicted`
|
|
25
|
+
* callback so the owning container can dispose the instance.
|
|
3
26
|
*/
|
|
4
27
|
import type { RegistrationToken } from "../containerRegistration/containerRegistration.core.js";
|
|
5
28
|
import type { ContainerRegistry } from "../containerRegistry/containerRegistry.core.js";
|
|
29
|
+
import type { Token } from "../containerToken/containerToken.type.js";
|
|
6
30
|
import type { ResolutionCache, ResolutionOptions, ResolutionResult } from "./containerResolution.type.js";
|
|
7
31
|
export declare class ContainerResolver {
|
|
8
32
|
private readonly registry;
|
|
9
33
|
private readonly singletonCache;
|
|
10
|
-
|
|
34
|
+
private readonly onSingletonEvicted;
|
|
35
|
+
constructor(registry: ContainerRegistry, onSingletonEvicted?: (token: Token<unknown>) => void);
|
|
11
36
|
resolve<T>(token: RegistrationToken<T>, options?: ResolutionOptions): T;
|
|
12
37
|
resolveDetailed<T>(token: RegistrationToken<T>, options?: ResolutionOptions): ResolutionResult<T>;
|
|
13
38
|
private resolveInternal;
|
|
14
39
|
private createInstance;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Creates an empty scope cache for SCOPED instances. When `parent` is
|
|
42
|
+
* given, lookups fall back to it (nested scope semantics) while writes stay
|
|
43
|
+
* local to the new cache.
|
|
44
|
+
*/
|
|
45
|
+
createScope(parent?: ResolutionCache): ResolutionCache;
|
|
46
|
+
/** Tokens currently held in the singleton cache. */
|
|
47
|
+
getCachedSingletonTokens(): readonly Token<unknown>[];
|
|
48
|
+
/**
|
|
49
|
+
* Clears the singleton cache WITHOUT invoking eviction callbacks.
|
|
50
|
+
* Callers are responsible for disposing the previously cached instances.
|
|
51
|
+
*/
|
|
52
|
+
clearSingletonCache(): void;
|
|
23
53
|
resolveMany<T>(tokens: readonly RegistrationToken<T>[], options?: ResolutionOptions): T[];
|
|
24
|
-
canResolve<T>(token: RegistrationToken<T
|
|
54
|
+
canResolve<T>(token: RegistrationToken<T>, autoRegisterClasses?: boolean): boolean;
|
|
55
|
+
private handleRegistryChange;
|
|
56
|
+
private evictSingleton;
|
|
25
57
|
}
|
|
26
58
|
//# sourceMappingURL=containerResolution.core.d.ts.map
|
|
@@ -1,51 +1,117 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dependency resolver for Zudojs.
|
|
3
|
+
*
|
|
4
|
+
* Lifetime semantics:
|
|
5
|
+
* - SINGLETON instances are always looked up in and stored to the resolver's
|
|
6
|
+
* root singleton cache, no matter where the resolution happens (root or
|
|
7
|
+
* scope). A scope can therefore never re-create or shadow a singleton.
|
|
8
|
+
* - SCOPED instances live in the scope cache supplied via
|
|
9
|
+
* `ResolutionOptions.cache`. Resolving a SCOPED registration without a
|
|
10
|
+
* scope cache throws a ScopedResolutionError; a SCOPED dependency of a
|
|
11
|
+
* SINGLETON throws a CaptiveDependencyError.
|
|
12
|
+
* - TRANSIENT instances are never cached.
|
|
13
|
+
*
|
|
14
|
+
* Every freshly created instance (any lifetime, never a cache hit) is
|
|
15
|
+
* reported through `ResolutionOptions.onInstanceCreated` in creation order,
|
|
16
|
+
* so owners can track transitively created dependencies for disposal — not
|
|
17
|
+
* just the top-level result of a `resolve()` call.
|
|
18
|
+
*
|
|
19
|
+
* Factories registered as SINGLETON or SCOPED must be synchronous: a Promise
|
|
20
|
+
* result throws an AsyncProviderError instead of being cached as the instance.
|
|
21
|
+
*
|
|
22
|
+
* The resolver subscribes to registry change events: REPLACE/REMOVE evict the
|
|
23
|
+
* affected token's cached singleton, CLEAR/RESTORE evict all cached
|
|
24
|
+
* singletons. Each eviction is reported through the `onSingletonEvicted`
|
|
25
|
+
* callback so the owning container can dispose the instance.
|
|
3
26
|
*/
|
|
4
27
|
import { isClassProvider, isExistingProvider, isFactoryProvider, isValueProvider, normalizeProvider, } from "../containerProvider/containerProvider.core.js";
|
|
5
28
|
import { ContainerScope as Scope } from "../containerScope/containerScope.type.js";
|
|
6
|
-
import { getRegistrationToken } from "../containerRegistration/containerRegistration.core.js";
|
|
7
|
-
import {
|
|
8
|
-
import { describeRegistryToken } from "../containerRegistry/containerRegistry.error.js";
|
|
29
|
+
import { defineRegistration, getRegistrationToken, } from "../containerRegistration/containerRegistration.core.js";
|
|
30
|
+
import { RegistryOperation } from "../containerRegistry/containerRegistry.type.js";
|
|
9
31
|
import { unwrapToken } from "../containerToken/containerToken.type.js";
|
|
10
|
-
import { CircularDependencyError, ProviderResolutionError, } from "@zudojs/errors";
|
|
32
|
+
import { CircularDependencyError, ProviderResolutionError, RegistrationNotFoundError, } from "@zudojs/errors";
|
|
33
|
+
import { AsyncProviderError, CaptiveDependencyError, DependencyResolutionError, MaxResolutionDepthError, ScopedResolutionError, } from "./containerResolution.error.js";
|
|
11
34
|
import { describeToken } from "../containerToken/containerToken.type.js";
|
|
35
|
+
/**
|
|
36
|
+
* Scope cache that falls back to its parent scope's cache for lookups while
|
|
37
|
+
* writing only to its own map. Nested scopes therefore see SCOPED instances
|
|
38
|
+
* already created by their ancestors, while instances they create themselves
|
|
39
|
+
* stay private to (and are disposed with) the nested scope.
|
|
40
|
+
*/
|
|
41
|
+
class ChainedResolutionCache {
|
|
42
|
+
#own = new Map();
|
|
43
|
+
#parent;
|
|
44
|
+
constructor(parent) {
|
|
45
|
+
this.#parent = parent;
|
|
46
|
+
}
|
|
47
|
+
has(token) {
|
|
48
|
+
return this.#own.has(token) || (this.#parent?.has(token) ?? false);
|
|
49
|
+
}
|
|
50
|
+
get(token) {
|
|
51
|
+
if (this.#own.has(token))
|
|
52
|
+
return this.#own.get(token);
|
|
53
|
+
return this.#parent?.get(token);
|
|
54
|
+
}
|
|
55
|
+
set(token, value) {
|
|
56
|
+
this.#own.set(token, value);
|
|
57
|
+
}
|
|
58
|
+
clear() {
|
|
59
|
+
this.#own.clear();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
12
62
|
export class ContainerResolver {
|
|
13
63
|
registry;
|
|
14
|
-
singletonCache;
|
|
15
|
-
|
|
64
|
+
singletonCache = new Map();
|
|
65
|
+
onSingletonEvicted;
|
|
66
|
+
constructor(registry, onSingletonEvicted) {
|
|
16
67
|
this.registry = registry;
|
|
17
|
-
this.
|
|
68
|
+
this.onSingletonEvicted = onSingletonEvicted;
|
|
69
|
+
registry.subscribe((event) => this.handleRegistryChange(event));
|
|
18
70
|
}
|
|
19
71
|
resolve(token, options = {}) {
|
|
20
72
|
return this.resolveDetailed(token, options).value;
|
|
21
73
|
}
|
|
22
74
|
resolveDetailed(token, options = {}) {
|
|
23
75
|
const normalized = unwrapToken(token);
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
76
|
+
const path = [...(options.path ?? [])];
|
|
77
|
+
const state = {
|
|
78
|
+
scopeCache: options.cache,
|
|
79
|
+
autoRegisterClasses: options.autoRegisterClasses ?? true,
|
|
80
|
+
allowRegistration: options.allowRegistration ?? true,
|
|
81
|
+
detectCircularDependencies: options.detectCircularDependencies ?? true,
|
|
82
|
+
maxResolutionDepth: options.maxResolutionDepth ?? 100,
|
|
83
|
+
onInstanceCreated: options.onInstanceCreated,
|
|
84
|
+
path,
|
|
85
|
+
pathSet: new Set(path),
|
|
86
|
+
};
|
|
87
|
+
return this.resolveInternal(normalized, state, undefined);
|
|
88
|
+
}
|
|
89
|
+
resolveInternal(token, state, singletonAncestor) {
|
|
90
|
+
const depth = state.path.length + 1;
|
|
91
|
+
if (depth > state.maxResolutionDepth)
|
|
92
|
+
throw new MaxResolutionDepthError(describeToken(token), depth, state.maxResolutionDepth, [...state.path, token].map((t) => describeToken(t)));
|
|
93
|
+
if (state.detectCircularDependencies && state.pathSet.has(token))
|
|
94
|
+
throw new CircularDependencyError([...state.path, token].map((t) => describeToken(t)));
|
|
95
|
+
let registration = this.registry.get(token);
|
|
32
96
|
if (!registration) {
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
97
|
+
if (state.autoRegisterClasses && typeof token === "function") {
|
|
98
|
+
if (state.allowRegistration) {
|
|
99
|
+
registration = this.registry.register(token, { useClass: token }, { scope: Scope.TRANSIENT });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Registrations are frozen: instantiate ephemerally, do not register.
|
|
103
|
+
registration = defineRegistration(token, { useClass: token }, { scope: Scope.TRANSIENT });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
throw new RegistrationNotFoundError(describeToken(token));
|
|
40
108
|
}
|
|
41
|
-
throw new RegistrationNotFoundError(describeRegistryToken(token));
|
|
42
109
|
}
|
|
43
|
-
const currentPath = [...path, token];
|
|
44
|
-
if (registration.scope
|
|
45
|
-
|
|
46
|
-
if (cached !== undefined) {
|
|
110
|
+
const currentPath = [...state.path, token];
|
|
111
|
+
if (registration.scope === Scope.SINGLETON) {
|
|
112
|
+
if (this.singletonCache.has(token)) {
|
|
47
113
|
return {
|
|
48
|
-
value:
|
|
114
|
+
value: this.singletonCache.get(token),
|
|
49
115
|
token,
|
|
50
116
|
registration,
|
|
51
117
|
scope: registration.scope,
|
|
@@ -54,12 +120,38 @@ export class ContainerResolver {
|
|
|
54
120
|
};
|
|
55
121
|
}
|
|
56
122
|
}
|
|
57
|
-
|
|
123
|
+
else if (registration.scope === Scope.SCOPED) {
|
|
124
|
+
if (singletonAncestor !== undefined)
|
|
125
|
+
throw new CaptiveDependencyError(describeToken(singletonAncestor), describeToken(token), currentPath.map((t) => describeToken(t)));
|
|
126
|
+
if (!state.scopeCache)
|
|
127
|
+
throw new ScopedResolutionError(describeToken(token), currentPath.map((t) => describeToken(t)));
|
|
128
|
+
if (state.scopeCache.has(token)) {
|
|
129
|
+
return {
|
|
130
|
+
value: state.scopeCache.get(token),
|
|
131
|
+
token,
|
|
132
|
+
registration,
|
|
133
|
+
scope: registration.scope,
|
|
134
|
+
fromCache: true,
|
|
135
|
+
path: currentPath,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const nextAncestor = registration.scope === Scope.SINGLETON ? token : singletonAncestor;
|
|
140
|
+
state.path.push(token);
|
|
141
|
+
state.pathSet.add(token);
|
|
142
|
+
let value;
|
|
143
|
+
try {
|
|
144
|
+
value = this.createInstance(registration, state, nextAncestor);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
state.path.pop();
|
|
148
|
+
state.pathSet.delete(token);
|
|
149
|
+
}
|
|
58
150
|
if (registration.scope === Scope.SINGLETON)
|
|
59
151
|
this.singletonCache.set(token, value);
|
|
60
152
|
else if (registration.scope === Scope.SCOPED)
|
|
61
|
-
|
|
62
|
-
|
|
153
|
+
state.scopeCache?.set(token, value);
|
|
154
|
+
const result = {
|
|
63
155
|
value,
|
|
64
156
|
token,
|
|
65
157
|
registration,
|
|
@@ -67,59 +159,110 @@ export class ContainerResolver {
|
|
|
67
159
|
fromCache: false,
|
|
68
160
|
path: currentPath,
|
|
69
161
|
};
|
|
162
|
+
state.onInstanceCreated?.(result);
|
|
163
|
+
return result;
|
|
70
164
|
}
|
|
71
|
-
createInstance(registration,
|
|
165
|
+
createInstance(registration, state, singletonAncestor) {
|
|
72
166
|
const provider = normalizeProvider(registration.provider);
|
|
167
|
+
const token = getRegistrationToken(registration);
|
|
73
168
|
try {
|
|
74
169
|
if (isValueProvider(provider))
|
|
75
170
|
return provider.useValue;
|
|
76
|
-
if (isExistingProvider(provider))
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
171
|
+
if (isExistingProvider(provider)) {
|
|
172
|
+
const target = unwrapToken(provider.useExisting);
|
|
173
|
+
if (!this.registry.has(target) &&
|
|
174
|
+
!(state.autoRegisterClasses && typeof target === "function")) {
|
|
175
|
+
throw new Error(`useExisting target "${describeToken(target)}" for token ` +
|
|
176
|
+
`"${describeToken(token)}" is not registered.`);
|
|
177
|
+
}
|
|
178
|
+
return this.resolveInternal(target, state, singletonAncestor)
|
|
179
|
+
.value;
|
|
180
|
+
}
|
|
181
|
+
if (isFactoryProvider(provider)) {
|
|
182
|
+
const deps = provider.inject ?? [];
|
|
183
|
+
const args = deps.map((d) => this.resolveInternal(unwrapToken(d), state, singletonAncestor)
|
|
184
|
+
.value);
|
|
185
|
+
const produced = provider.useFactory(...args);
|
|
186
|
+
if (registration.scope !== Scope.TRANSIENT && isPromiseLike(produced))
|
|
187
|
+
throw new AsyncProviderError(describeToken(token), registration.scope);
|
|
188
|
+
return produced;
|
|
189
|
+
}
|
|
190
|
+
if (isClassProvider(provider)) {
|
|
191
|
+
const deps = provider.inject ?? [];
|
|
192
|
+
const args = deps.map((d) => this.resolveInternal(unwrapToken(d), state, singletonAncestor)
|
|
193
|
+
.value);
|
|
194
|
+
const ctor = provider.useClass;
|
|
195
|
+
return new ctor(...args);
|
|
196
|
+
}
|
|
82
197
|
throw new Error("Unsupported container provider.");
|
|
83
198
|
}
|
|
84
199
|
catch (error) {
|
|
200
|
+
// Resolution errors created deeper in the chain already carry the full
|
|
201
|
+
// chain in their message/details — propagate them unchanged.
|
|
85
202
|
if (error instanceof CircularDependencyError ||
|
|
86
|
-
error instanceof ProviderResolutionError
|
|
203
|
+
error instanceof ProviderResolutionError ||
|
|
204
|
+
error instanceof ScopedResolutionError ||
|
|
205
|
+
error instanceof CaptiveDependencyError ||
|
|
206
|
+
error instanceof MaxResolutionDepthError ||
|
|
207
|
+
error instanceof AsyncProviderError)
|
|
87
208
|
throw error;
|
|
88
|
-
|
|
89
|
-
throw new ProviderResolutionError(describeRegistryToken(getRegistrationToken(registration)), message, error);
|
|
209
|
+
throw new DependencyResolutionError(describeToken(token), error, state.path.map((t) => describeToken(t)));
|
|
90
210
|
}
|
|
91
211
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Creates an empty scope cache for SCOPED instances. When `parent` is
|
|
214
|
+
* given, lookups fall back to it (nested scope semantics) while writes stay
|
|
215
|
+
* local to the new cache.
|
|
216
|
+
*/
|
|
217
|
+
createScope(parent) {
|
|
218
|
+
return new ChainedResolutionCache(parent);
|
|
95
219
|
}
|
|
96
|
-
|
|
97
|
-
|
|
220
|
+
/** Tokens currently held in the singleton cache. */
|
|
221
|
+
getCachedSingletonTokens() {
|
|
222
|
+
return [...this.singletonCache.keys()];
|
|
98
223
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Clears the singleton cache WITHOUT invoking eviction callbacks.
|
|
226
|
+
* Callers are responsible for disposing the previously cached instances.
|
|
227
|
+
*/
|
|
228
|
+
clearSingletonCache() {
|
|
103
229
|
this.singletonCache.clear();
|
|
104
230
|
}
|
|
105
|
-
hasSingleton(token) {
|
|
106
|
-
return this.singletonCache.has(unwrapToken(token));
|
|
107
|
-
}
|
|
108
|
-
getSingleton(token) {
|
|
109
|
-
return this.singletonCache.get(unwrapToken(token));
|
|
110
|
-
}
|
|
111
|
-
removeSingleton(token) {
|
|
112
|
-
return this.singletonCache.delete(unwrapToken(token));
|
|
113
|
-
}
|
|
114
|
-
clearScope(cache) {
|
|
115
|
-
cache.clear();
|
|
116
|
-
}
|
|
117
231
|
resolveMany(tokens, options = {}) {
|
|
118
232
|
return tokens.map((t) => this.resolve(t, options));
|
|
119
233
|
}
|
|
120
|
-
canResolve(token) {
|
|
234
|
+
canResolve(token, autoRegisterClasses = true) {
|
|
121
235
|
const t = unwrapToken(token);
|
|
122
|
-
return this.registry.has(t) || typeof t === "function";
|
|
236
|
+
return (this.registry.has(t) || (autoRegisterClasses && typeof t === "function"));
|
|
123
237
|
}
|
|
238
|
+
handleRegistryChange(event) {
|
|
239
|
+
switch (event.operation) {
|
|
240
|
+
case RegistryOperation.REPLACE:
|
|
241
|
+
case RegistryOperation.REMOVE: {
|
|
242
|
+
this.evictSingleton(unwrapToken(event.token));
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case RegistryOperation.CLEAR:
|
|
246
|
+
case RegistryOperation.RESTORE: {
|
|
247
|
+
for (const t of [...this.singletonCache.keys()])
|
|
248
|
+
this.evictSingleton(t);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
default:
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
evictSingleton(token) {
|
|
256
|
+
if (!this.singletonCache.has(token))
|
|
257
|
+
return;
|
|
258
|
+
this.singletonCache.delete(token);
|
|
259
|
+
this.onSingletonEvicted?.(token);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function isPromiseLike(value) {
|
|
263
|
+
return (typeof value === "object" &&
|
|
264
|
+
value !== null &&
|
|
265
|
+
"then" in value &&
|
|
266
|
+
typeof value.then === "function");
|
|
124
267
|
}
|
|
125
268
|
//# sourceMappingURL=containerResolution.core.js.map
|