@zudojs/container 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/containerCore/containerCore.core.d.ts +8 -3
- package/dist/containerCore/containerCore.core.js +12 -7
- package/dist/containerCore/containerCore.scope.js +4 -3
- package/dist/containerResolution/containerResolution.core.d.ts +18 -3
- package/dist/containerResolution/containerResolution.core.js +40 -8
- package/package.json +2 -2
|
@@ -77,7 +77,10 @@ export declare class Container implements ContainerLike {
|
|
|
77
77
|
getRegistration<T>(token: RegistrationToken<T>): ContainerRegistration<T> | undefined;
|
|
78
78
|
replace<T>(token: RegistrationToken<T>, provider: ContainerProvider<T>, options?: CreateRegistrationOptions): ContainerRegistration<T>;
|
|
79
79
|
remove<T>(token: RegistrationToken<T>): boolean;
|
|
80
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Removes every registration, evicting and disposing cached instances:
|
|
82
|
+
* container-owned singletons and every live scope's SCOPED copies alike.
|
|
83
|
+
*/
|
|
81
84
|
clearRegistrations(): void;
|
|
82
85
|
createScope(options?: ContainerScopeOptions): ContainerScopeContext;
|
|
83
86
|
getRegistrations(): readonly ContainerRegistration[];
|
|
@@ -91,8 +94,10 @@ export declare class Container implements ContainerLike {
|
|
|
91
94
|
snapshot(): readonly ContainerRegistration[];
|
|
92
95
|
/**
|
|
93
96
|
* Wholesale-replaces the registration set with a previous snapshot.
|
|
94
|
-
* Entries are validated, cached
|
|
95
|
-
*
|
|
97
|
+
* Entries are validated, cached instances built from the old set are
|
|
98
|
+
* evicted and disposed — container-owned singletons and every live
|
|
99
|
+
* scope's SCOPED copies alike — and the operation is refused when
|
|
100
|
+
* registrations are frozen.
|
|
96
101
|
*/
|
|
97
102
|
restoreSnapshot(registrations: readonly ContainerRegistration[]): void;
|
|
98
103
|
isStarted(): boolean;
|
|
@@ -19,7 +19,7 @@ import { ContainerResolver } from "../containerResolution/containerResolution.co
|
|
|
19
19
|
import { ContainerLifecycle, ContainerLifecycleOwner, } from "../containerLifecycle/containerLifecycle.core.js";
|
|
20
20
|
import { resolveContainerOptions } from "../containerOptions/containerOptions.type.js";
|
|
21
21
|
import { unwrapToken } from "../containerToken/containerToken.type.js";
|
|
22
|
-
import { RegistrationNotFoundError } from "@zudojs/errors";
|
|
22
|
+
import { ContainerError, ContainerLifecycleError, RegistrationNotFoundError, } from "@zudojs/errors";
|
|
23
23
|
import { ContainerScopeContext } from "./containerCore.scope.js";
|
|
24
24
|
export class Container {
|
|
25
25
|
name;
|
|
@@ -147,7 +147,10 @@ export class Container {
|
|
|
147
147
|
this.ensureMutable();
|
|
148
148
|
return this.#registry.remove(token);
|
|
149
149
|
}
|
|
150
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* Removes every registration, evicting and disposing cached instances:
|
|
152
|
+
* container-owned singletons and every live scope's SCOPED copies alike.
|
|
153
|
+
*/
|
|
151
154
|
clearRegistrations() {
|
|
152
155
|
this.ensureMutable();
|
|
153
156
|
this.#registry.clear();
|
|
@@ -155,7 +158,7 @@ export class Container {
|
|
|
155
158
|
createScope(options = {}) {
|
|
156
159
|
this.ensureActive();
|
|
157
160
|
if (!this.options.allowScopes)
|
|
158
|
-
throw new
|
|
161
|
+
throw new ContainerError(`Container scopes are disabled for "${this.name}".`);
|
|
159
162
|
const scope = new ContainerScopeContext(this, options);
|
|
160
163
|
this.#liveScopes.add(scope);
|
|
161
164
|
return scope;
|
|
@@ -182,8 +185,10 @@ export class Container {
|
|
|
182
185
|
}
|
|
183
186
|
/**
|
|
184
187
|
* Wholesale-replaces the registration set with a previous snapshot.
|
|
185
|
-
* Entries are validated, cached
|
|
186
|
-
*
|
|
188
|
+
* Entries are validated, cached instances built from the old set are
|
|
189
|
+
* evicted and disposed — container-owned singletons and every live
|
|
190
|
+
* scope's SCOPED copies alike — and the operation is refused when
|
|
191
|
+
* registrations are frozen.
|
|
187
192
|
*/
|
|
188
193
|
restoreSnapshot(registrations) {
|
|
189
194
|
this.ensureMutable();
|
|
@@ -318,14 +323,14 @@ export class Container {
|
|
|
318
323
|
}
|
|
319
324
|
ensureNotDisposed() {
|
|
320
325
|
if (this.#disposed)
|
|
321
|
-
throw new
|
|
326
|
+
throw new ContainerLifecycleError("dispose", `Container "${this.name}" has already been disposed.`);
|
|
322
327
|
}
|
|
323
328
|
ensureMutable() {
|
|
324
329
|
this.ensureNotDisposed();
|
|
325
330
|
if (!this.options.freezeRegistrations)
|
|
326
331
|
return;
|
|
327
332
|
if (this.#started)
|
|
328
|
-
throw new
|
|
333
|
+
throw new ContainerError(`Registrations for container "${this.name}" are frozen.`);
|
|
329
334
|
}
|
|
330
335
|
}
|
|
331
336
|
export function createContainer(options = {}) {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { ContainerLifecycle, ContainerLifecycleOwner, } from "../containerLifecycle/containerLifecycle.core.js";
|
|
20
20
|
import { ContainerScope } from "../containerScope/containerScope.type.js";
|
|
21
|
+
import { ContainerLifecycleError } from "@zudojs/errors";
|
|
21
22
|
export class ContainerScopeContext {
|
|
22
23
|
disposed = false;
|
|
23
24
|
disposing;
|
|
@@ -189,13 +190,13 @@ export class ContainerScopeContext {
|
|
|
189
190
|
*/
|
|
190
191
|
ensureActive() {
|
|
191
192
|
if (this.disposed) {
|
|
192
|
-
throw new
|
|
193
|
+
throw new ContainerLifecycleError("dispose", `Container scope "${this.name}" has already been disposed.`);
|
|
193
194
|
}
|
|
194
195
|
if (this.parentScope?.isDisposed()) {
|
|
195
|
-
throw new
|
|
196
|
+
throw new ContainerLifecycleError("dispose", `Parent scope "${this.parentScope.name}" of scope "${this.name}" has been disposed.`);
|
|
196
197
|
}
|
|
197
198
|
if (this.container.isDisposed()) {
|
|
198
|
-
throw new
|
|
199
|
+
throw new ContainerLifecycleError("dispose", `Container "${this.container.name}" owning scope "${this.name}" has been disposed.`);
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
}
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
* The resolver subscribes to registry change events: REPLACE/REMOVE evict the
|
|
23
23
|
* affected token's cached singleton, CLEAR/RESTORE evict all cached
|
|
24
24
|
* singletons. Each eviction is reported through the `onSingletonEvicted`
|
|
25
|
-
* callback so the owning container can dispose the instance
|
|
25
|
+
* callback so the owning container can dispose the instance, and every
|
|
26
|
+
* invalidated token — for CLEAR/RESTORE that is every cached token, SCOPED
|
|
27
|
+
* ones included — through `onTokenInvalidated` so live scopes drop and
|
|
28
|
+
* dispose their own copies.
|
|
26
29
|
*/
|
|
27
30
|
import type { RegistrationToken } from "../containerRegistration/containerRegistration.core.js";
|
|
28
31
|
import type { ContainerRegistry } from "../containerRegistry/containerRegistry.core.js";
|
|
@@ -34,12 +37,24 @@ export declare class ContainerResolver {
|
|
|
34
37
|
private readonly onSingletonEvicted;
|
|
35
38
|
private readonly onTokenInvalidated;
|
|
36
39
|
private readonly dependents;
|
|
40
|
+
/**
|
|
41
|
+
* Tokens for which a SCOPED instance has ever been cached in a scope.
|
|
42
|
+
*
|
|
43
|
+
* The singleton cache only knows about SINGLETON tokens and the
|
|
44
|
+
* dependent index only records tokens consumed by another cached
|
|
45
|
+
* instance, so neither can name a SCOPED token that a scope resolved
|
|
46
|
+
* directly. A wholesale CLEAR/RESTORE must still tell live scopes to
|
|
47
|
+
* drop those instances.
|
|
48
|
+
*/
|
|
49
|
+
private readonly scopedTokens;
|
|
37
50
|
/**
|
|
38
51
|
* @param onSingletonEvicted Called for each evicted cached singleton so
|
|
39
52
|
* the owner can dispose it.
|
|
40
53
|
* @param onTokenInvalidated Called for every token invalidated by a
|
|
41
|
-
* `replace()`/`remove()`
|
|
42
|
-
*
|
|
54
|
+
* registry change — for `replace()`/`remove()` the token itself and
|
|
55
|
+
* each cached consumer, for `clear()`/`restore()` every token that
|
|
56
|
+
* was cached at all — so owners of scope caches can drop and dispose
|
|
57
|
+
* their SCOPED copies.
|
|
43
58
|
*/
|
|
44
59
|
constructor(registry: ContainerRegistry, onSingletonEvicted?: (token: Token<unknown>) => void, onTokenInvalidated?: (token: Token<unknown>) => void);
|
|
45
60
|
resolve<T>(token: RegistrationToken<T>, options?: ResolutionOptions): T;
|
|
@@ -22,14 +22,17 @@
|
|
|
22
22
|
* The resolver subscribes to registry change events: REPLACE/REMOVE evict the
|
|
23
23
|
* affected token's cached singleton, CLEAR/RESTORE evict all cached
|
|
24
24
|
* singletons. Each eviction is reported through the `onSingletonEvicted`
|
|
25
|
-
* callback so the owning container can dispose the instance
|
|
25
|
+
* callback so the owning container can dispose the instance, and every
|
|
26
|
+
* invalidated token — for CLEAR/RESTORE that is every cached token, SCOPED
|
|
27
|
+
* ones included — through `onTokenInvalidated` so live scopes drop and
|
|
28
|
+
* dispose their own copies.
|
|
26
29
|
*/
|
|
27
30
|
import { isClassProvider, isExistingProvider, isFactoryProvider, isValueProvider, normalizeProvider, } from "../containerProvider/containerProvider.core.js";
|
|
28
31
|
import { ContainerScope as Scope } from "../containerScope/containerScope.type.js";
|
|
29
32
|
import { defineRegistration, getRegistrationToken, } from "../containerRegistration/containerRegistration.core.js";
|
|
30
33
|
import { RegistryOperation } from "../containerRegistry/containerRegistry.type.js";
|
|
31
34
|
import { unwrapToken } from "../containerToken/containerToken.type.js";
|
|
32
|
-
import { CircularDependencyError, ProviderResolutionError, RegistrationNotFoundError, } from "@zudojs/errors";
|
|
35
|
+
import { CircularDependencyError, ContainerError, ProviderResolutionError, RegistrationNotFoundError, } from "@zudojs/errors";
|
|
33
36
|
import { AsyncProviderError, CaptiveDependencyError, DependencyResolutionError, MaxResolutionDepthError, ScopedResolutionError, } from "./containerResolution.error.js";
|
|
34
37
|
import { describeToken } from "../containerToken/containerToken.type.js";
|
|
35
38
|
import { DependentIndex } from "./containerResolution.dependents.js";
|
|
@@ -69,12 +72,24 @@ export class ContainerResolver {
|
|
|
69
72
|
onSingletonEvicted;
|
|
70
73
|
onTokenInvalidated;
|
|
71
74
|
dependents = new DependentIndex();
|
|
75
|
+
/**
|
|
76
|
+
* Tokens for which a SCOPED instance has ever been cached in a scope.
|
|
77
|
+
*
|
|
78
|
+
* The singleton cache only knows about SINGLETON tokens and the
|
|
79
|
+
* dependent index only records tokens consumed by another cached
|
|
80
|
+
* instance, so neither can name a SCOPED token that a scope resolved
|
|
81
|
+
* directly. A wholesale CLEAR/RESTORE must still tell live scopes to
|
|
82
|
+
* drop those instances.
|
|
83
|
+
*/
|
|
84
|
+
scopedTokens = new Set();
|
|
72
85
|
/**
|
|
73
86
|
* @param onSingletonEvicted Called for each evicted cached singleton so
|
|
74
87
|
* the owner can dispose it.
|
|
75
88
|
* @param onTokenInvalidated Called for every token invalidated by a
|
|
76
|
-
* `replace()`/`remove()`
|
|
77
|
-
*
|
|
89
|
+
* registry change — for `replace()`/`remove()` the token itself and
|
|
90
|
+
* each cached consumer, for `clear()`/`restore()` every token that
|
|
91
|
+
* was cached at all — so owners of scope caches can drop and dispose
|
|
92
|
+
* their SCOPED copies.
|
|
78
93
|
*/
|
|
79
94
|
constructor(registry, onSingletonEvicted, onTokenInvalidated) {
|
|
80
95
|
this.registry = registry;
|
|
@@ -165,8 +180,11 @@ export class ContainerResolver {
|
|
|
165
180
|
}
|
|
166
181
|
if (registration.scope === Scope.SINGLETON)
|
|
167
182
|
this.singletonCache.set(token, value);
|
|
168
|
-
else if (registration.scope === Scope.SCOPED)
|
|
183
|
+
else if (registration.scope === Scope.SCOPED) {
|
|
169
184
|
state.scopeCache?.set(token, value);
|
|
185
|
+
if (state.scopeCache)
|
|
186
|
+
this.scopedTokens.add(token);
|
|
187
|
+
}
|
|
170
188
|
const result = {
|
|
171
189
|
value,
|
|
172
190
|
token,
|
|
@@ -197,8 +215,8 @@ export class ContainerResolver {
|
|
|
197
215
|
const target = unwrapToken(provider.useExisting);
|
|
198
216
|
if (!this.registry.has(target) &&
|
|
199
217
|
!(state.autoRegisterClasses && typeof target === "function")) {
|
|
200
|
-
throw new
|
|
201
|
-
`"${describeToken(token)}" is not registered
|
|
218
|
+
throw new ContainerError(`useExisting target "${describeToken(target)}" for token ` +
|
|
219
|
+
`"${describeToken(token)}" is not registered.`, { token: describeToken(token) });
|
|
202
220
|
}
|
|
203
221
|
const resolved = this.resolveInternal(target, state, singletonAncestor);
|
|
204
222
|
// Only a TRANSIENT target has no owner of its own; a cached alias
|
|
@@ -224,7 +242,9 @@ export class ContainerResolver {
|
|
|
224
242
|
const ctor = provider.useClass;
|
|
225
243
|
return { value: new ctor(...args), owned: true };
|
|
226
244
|
}
|
|
227
|
-
throw new
|
|
245
|
+
throw new ContainerError("Unsupported container provider.", {
|
|
246
|
+
token: describeToken(token),
|
|
247
|
+
});
|
|
228
248
|
}
|
|
229
249
|
catch (error) {
|
|
230
250
|
// Resolution errors created deeper in the chain already carry the full
|
|
@@ -282,9 +302,21 @@ export class ContainerResolver {
|
|
|
282
302
|
}
|
|
283
303
|
case RegistryOperation.CLEAR:
|
|
284
304
|
case RegistryOperation.RESTORE: {
|
|
305
|
+
// Every cached token is discarded by a wholesale change, so every
|
|
306
|
+
// one of them must be reported as invalidated — not just the
|
|
307
|
+
// singletons. Only REPLACE/REMOVE used to notify, so a live scope
|
|
308
|
+
// went on serving (and never disposed) the SCOPED instance built
|
|
309
|
+
// from a registration that clear()/restore() had thrown away.
|
|
310
|
+
const invalidated = new Set([
|
|
311
|
+
...this.singletonCache.keys(),
|
|
312
|
+
...this.scopedTokens,
|
|
313
|
+
]);
|
|
285
314
|
for (const t of [...this.singletonCache.keys()])
|
|
286
315
|
this.evictSingleton(t);
|
|
287
316
|
this.dependents.clear();
|
|
317
|
+
this.scopedTokens.clear();
|
|
318
|
+
for (const t of invalidated)
|
|
319
|
+
this.onTokenInvalidated?.(t);
|
|
288
320
|
break;
|
|
289
321
|
}
|
|
290
322
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/container",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Token-based dependency injection container for managing application dependencies and service lifetimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"!dist/.tsbuildinfo"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@zudojs/errors": "1.
|
|
21
|
+
"@zudojs/errors": "1.2.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"typescript": "7.0.2",
|