@zudojs/container 0.1.0 → 1.1.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 +21 -0
- package/README.md +221 -8
- package/dist/containerCore/containerCore.core.d.ts +112 -14
- package/dist/containerCore/containerCore.core.js +228 -46
- package/dist/containerCore/containerCore.scope.d.ts +66 -18
- package/dist/containerCore/containerCore.scope.js +120 -31
- package/dist/containerCore/containerCore.type.d.ts +22 -16
- package/dist/containerLifecycle/containerLifecycle.core.d.ts +59 -9
- package/dist/containerLifecycle/containerLifecycle.core.js +60 -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 +51 -10
- package/dist/containerResolution/containerResolution.core.js +255 -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/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/package.json +25 -12
- 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,42 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ContainerScopeContext —
|
|
3
|
-
* its own
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
2
|
+
* ContainerScopeContext — a dependency scope with its own scoped-instance
|
|
3
|
+
* cache and its own lifecycle for SCOPED instances.
|
|
4
|
+
*
|
|
5
|
+
* Ownership model:
|
|
6
|
+
* - SCOPED instances created through this scope — including those created
|
|
7
|
+
* transitively as dependencies — are owned by the scope and disposed when
|
|
8
|
+
* the scope is disposed.
|
|
9
|
+
* - SINGLETON instances resolved through a scope stay owned by the parent
|
|
10
|
+
* container — a scope's disposal never touches them.
|
|
11
|
+
* - TRANSIENT instances are not tracked anywhere; callers own their disposal.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
13
|
+
* Nesting: `scope.createScope()` creates a true child scope. A child sees
|
|
14
|
+
* SCOPED instances already created by its ancestors (lookups chain upward),
|
|
15
|
+
* while instances it creates itself are private to the child. Disposing a
|
|
16
|
+
* scope disposes its children first, and a child refuses to resolve once any
|
|
17
|
+
* ancestor (scope or container) is disposed.
|
|
11
18
|
*/
|
|
19
|
+
import { ContainerLifecycle, ContainerLifecycleOwner, } from "../containerLifecycle/containerLifecycle.core.js";
|
|
20
|
+
import { ContainerScope } from "../containerScope/containerScope.type.js";
|
|
12
21
|
export class ContainerScopeContext {
|
|
13
|
-
parent;
|
|
14
22
|
disposed = false;
|
|
23
|
+
disposing;
|
|
15
24
|
cache;
|
|
16
25
|
lifecycle;
|
|
26
|
+
container;
|
|
27
|
+
parentScope;
|
|
28
|
+
children = new Set();
|
|
17
29
|
name;
|
|
18
30
|
metadata;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
/**
|
|
32
|
+
* @param container The container that owns the scope tree.
|
|
33
|
+
* @param options Scope name / metadata.
|
|
34
|
+
* @param parentScope When given, the new scope is a nested child of it.
|
|
35
|
+
* Application code should use `container.createScope()` /
|
|
36
|
+
* `scope.createScope()` rather than constructing scopes directly.
|
|
37
|
+
*/
|
|
38
|
+
constructor(container, options = {}, parentScope) {
|
|
39
|
+
this.container = container;
|
|
40
|
+
this.parentScope = parentScope;
|
|
41
|
+
this.name = options.name ?? `${(parentScope ?? container).name}:scope`;
|
|
22
42
|
this.metadata = Object.freeze({ ...(options.metadata ?? {}) });
|
|
23
|
-
this.cache =
|
|
43
|
+
this.cache = container.createScopeCache(parentScope?.cache);
|
|
24
44
|
this.lifecycle = new ContainerLifecycle();
|
|
25
45
|
}
|
|
26
46
|
/**
|
|
27
47
|
* Resolves a dependency within this scope.
|
|
48
|
+
*
|
|
49
|
+
* SCOPED instances are cached per scope (visible to child scopes) and
|
|
50
|
+
* tracked for disposal with the scope that created them. SINGLETON
|
|
51
|
+
* instances come from (and are tracked by) the container. TRANSIENT
|
|
52
|
+
* instances are created fresh and never tracked.
|
|
28
53
|
*/
|
|
29
54
|
resolve(token) {
|
|
30
55
|
this.ensureActive();
|
|
31
|
-
const result = this.
|
|
32
|
-
...this.parent.resolutionOptions,
|
|
33
|
-
cache: this.cache,
|
|
34
|
-
});
|
|
35
|
-
this.lifecycle.track(result.token, result.value, ContainerLifecycleOwner.SCOPE);
|
|
56
|
+
const result = this.container.resolveInScope(token, this.cache, (created) => this.trackCreated(created));
|
|
36
57
|
return result.value;
|
|
37
58
|
}
|
|
38
59
|
/**
|
|
39
|
-
* Resolves multiple dependencies within this scope.
|
|
60
|
+
* Resolves multiple dependencies within this scope, typed per token.
|
|
40
61
|
*/
|
|
41
62
|
resolveMany(tokens) {
|
|
42
63
|
return tokens.map((token) => this.resolve(token));
|
|
@@ -46,35 +67,75 @@ export class ContainerScopeContext {
|
|
|
46
67
|
*/
|
|
47
68
|
canResolve(token) {
|
|
48
69
|
this.ensureActive();
|
|
49
|
-
return this.
|
|
70
|
+
return this.container.canResolve(token);
|
|
50
71
|
}
|
|
51
72
|
/**
|
|
52
73
|
* Checks whether a registration exists.
|
|
53
74
|
*/
|
|
54
75
|
has(token) {
|
|
55
76
|
this.ensureActive();
|
|
56
|
-
return this.
|
|
77
|
+
return this.container.has(token);
|
|
57
78
|
}
|
|
58
79
|
/**
|
|
59
|
-
* Creates
|
|
80
|
+
* Creates a nested child scope.
|
|
81
|
+
*
|
|
82
|
+
* The child inherits this scope's cached SCOPED instances for lookups,
|
|
83
|
+
* caches the SCOPED instances it creates itself, and is disposed
|
|
84
|
+
* automatically when this scope is disposed.
|
|
60
85
|
*/
|
|
61
86
|
createScope(options = {}) {
|
|
62
87
|
this.ensureActive();
|
|
63
|
-
|
|
88
|
+
const child = new ContainerScopeContext(this.container, options, this);
|
|
89
|
+
this.children.add(child);
|
|
90
|
+
return child;
|
|
64
91
|
}
|
|
65
92
|
/**
|
|
66
|
-
* Disposes
|
|
93
|
+
* Disposes child scopes (most recent first), then all SCOPED instances
|
|
94
|
+
* belonging to this scope in reverse creation order, and detaches the scope
|
|
95
|
+
* from its parent. Container-owned singletons are not touched. Idempotent.
|
|
96
|
+
*
|
|
97
|
+
* Every failure is collected; the scope is marked disposed regardless and
|
|
98
|
+
* an AggregateError listing the failures is thrown afterwards. Concurrent
|
|
99
|
+
* callers share the in-flight disposal rather than returning early.
|
|
67
100
|
*/
|
|
68
|
-
|
|
101
|
+
dispose() {
|
|
102
|
+
if (this.disposing)
|
|
103
|
+
return this.disposing;
|
|
69
104
|
if (this.disposed)
|
|
70
|
-
return;
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
this.disposed = true;
|
|
107
|
+
this.disposing = this.runDispose().finally(() => {
|
|
108
|
+
this.disposing = undefined;
|
|
109
|
+
});
|
|
110
|
+
return this.disposing;
|
|
111
|
+
}
|
|
112
|
+
async runDispose() {
|
|
113
|
+
const failures = [];
|
|
114
|
+
for (const child of [...this.children].reverse()) {
|
|
115
|
+
try {
|
|
116
|
+
await child.dispose();
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
failures.push(error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.children.clear();
|
|
71
123
|
try {
|
|
72
|
-
await this.lifecycle.
|
|
124
|
+
await this.lifecycle.dispose();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
failures.push(error);
|
|
73
128
|
}
|
|
74
129
|
finally {
|
|
130
|
+
this.lifecycle.shutdown();
|
|
75
131
|
this.cache.clear();
|
|
76
|
-
this.
|
|
132
|
+
if (this.parentScope)
|
|
133
|
+
this.parentScope.releaseChild(this);
|
|
134
|
+
else
|
|
135
|
+
this.container.releaseScope(this);
|
|
77
136
|
}
|
|
137
|
+
if (failures.length > 0)
|
|
138
|
+
throw new AggregateError(failures, `Container scope "${this.name}" was disposed, but ${failures.length} cleanup step(s) failed.`);
|
|
78
139
|
}
|
|
79
140
|
/**
|
|
80
141
|
* Returns whether the scope has been disposed.
|
|
@@ -83,18 +144,46 @@ export class ContainerScopeContext {
|
|
|
83
144
|
return this.disposed;
|
|
84
145
|
}
|
|
85
146
|
/**
|
|
86
|
-
* Returns the parent container
|
|
147
|
+
* Returns the direct parent: the container for a top-level scope, or the
|
|
148
|
+
* parent scope for a nested one.
|
|
87
149
|
*/
|
|
88
150
|
getParent() {
|
|
89
|
-
return this.
|
|
151
|
+
return this.parentScope ?? this.container;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Returns the container that owns the whole scope tree.
|
|
155
|
+
*/
|
|
156
|
+
getContainer() {
|
|
157
|
+
return this.container;
|
|
158
|
+
}
|
|
159
|
+
/** @internal Detaches a disposed child scope. */
|
|
160
|
+
releaseChild(scope) {
|
|
161
|
+
this.children.delete(scope);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Tracks SCOPED instances created during a resolution through this scope
|
|
165
|
+
* (top-level result or transitively created dependency). Instances served
|
|
166
|
+
* from an ancestor's cache are cache hits and never reach here.
|
|
167
|
+
*/
|
|
168
|
+
trackCreated(result) {
|
|
169
|
+
if (result.scope !== ContainerScope.SCOPED)
|
|
170
|
+
return;
|
|
171
|
+
this.lifecycle.track(result.token, result.value, ContainerLifecycleOwner.SCOPE);
|
|
90
172
|
}
|
|
91
173
|
/**
|
|
92
|
-
* Throws when the scope
|
|
174
|
+
* Throws when the scope — or any ancestor scope, or the container that
|
|
175
|
+
* owns it — is no longer active.
|
|
93
176
|
*/
|
|
94
177
|
ensureActive() {
|
|
95
178
|
if (this.disposed) {
|
|
96
179
|
throw new Error(`Container scope "${this.name}" has already been disposed.`);
|
|
97
180
|
}
|
|
181
|
+
if (this.parentScope?.isDisposed()) {
|
|
182
|
+
throw new Error(`Parent scope "${this.parentScope.name}" of scope "${this.name}" has been disposed.`);
|
|
183
|
+
}
|
|
184
|
+
if (this.container.isDisposed()) {
|
|
185
|
+
throw new Error(`Container "${this.container.name}" owning scope "${this.name}" has been disposed.`);
|
|
186
|
+
}
|
|
98
187
|
}
|
|
99
188
|
}
|
|
100
189
|
//# sourceMappingURL=containerCore.scope.js.map
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Types for the core container.
|
|
3
3
|
*/
|
|
4
4
|
import type { RegistrationToken } from "../containerRegistration/containerRegistration.core.js";
|
|
5
|
+
import type { ResolutionCache, ResolutionResult } from "../containerResolution/containerResolution.type.js";
|
|
6
|
+
import type { ContainerScopeContext } from "./containerCore.scope.js";
|
|
5
7
|
/**
|
|
6
8
|
* Options used when creating a child container scope.
|
|
7
9
|
*/
|
|
@@ -16,25 +18,29 @@ export interface ContainerScopeOptions {
|
|
|
16
18
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
+
* The surface a ContainerScopeContext needs from its owning container.
|
|
22
|
+
* Implemented by Container; the `resolveInScope` / `createScopeCache` /
|
|
23
|
+
* `releaseScope` members are internal plumbing for scopes and should not be
|
|
24
|
+
* called by application code.
|
|
21
25
|
*/
|
|
22
26
|
export interface ContainerLike {
|
|
23
27
|
readonly name: string;
|
|
24
|
-
readonly resolver: {
|
|
25
|
-
createScope(): import("../containerResolution/containerResolution.type.js").ResolutionCache;
|
|
26
|
-
resolveDetailed(token: unknown, options: unknown): {
|
|
27
|
-
token: unknown;
|
|
28
|
-
value: unknown;
|
|
29
|
-
};
|
|
30
|
-
canResolve(token: unknown): boolean;
|
|
31
|
-
};
|
|
32
|
-
readonly resolutionOptions: {
|
|
33
|
-
autoRegisterClasses: boolean;
|
|
34
|
-
detectCircularDependencies: boolean;
|
|
35
|
-
maxResolutionDepth: number;
|
|
36
|
-
};
|
|
37
28
|
has<T>(token: RegistrationToken<T>): boolean;
|
|
38
|
-
|
|
29
|
+
canResolve<T>(token: RegistrationToken<T>): boolean;
|
|
30
|
+
isDisposed(): boolean;
|
|
31
|
+
createScope(options?: ContainerScopeOptions): ContainerScopeContext;
|
|
32
|
+
/**
|
|
33
|
+
* @internal Creates a fresh scoped-instance cache, optionally chained to a
|
|
34
|
+
* parent scope's cache (nested scopes).
|
|
35
|
+
*/
|
|
36
|
+
createScopeCache(parent?: ResolutionCache): ResolutionCache;
|
|
37
|
+
/**
|
|
38
|
+
* @internal Resolves a token using the given scope cache. `onInstanceCreated`
|
|
39
|
+
* is invoked for every instance created during the resolution (including
|
|
40
|
+
* transitively created dependencies) so the scope can track SCOPED ones.
|
|
41
|
+
*/
|
|
42
|
+
resolveInScope<T>(token: RegistrationToken<T>, cache: ResolutionCache, onInstanceCreated?: (result: ResolutionResult<unknown>) => void): ResolutionResult<T>;
|
|
43
|
+
/** @internal Unregisters a top-level scope from the container's live-scope set. */
|
|
44
|
+
releaseScope(scope: ContainerScopeContext): void;
|
|
39
45
|
}
|
|
40
46
|
//# sourceMappingURL=containerCore.type.d.ts.map
|
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lifecycle management for resolved container instances.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Tracks SINGLETON instances (container-owned) and SCOPED instances
|
|
5
|
+
* (scope-owned) that need cleanup and disposes them when their owner is
|
|
6
|
+
* destroyed, in reverse creation order.
|
|
7
|
+
*
|
|
8
|
+
* TRANSIENT instances are deliberately NOT tracked: the container creates a
|
|
9
|
+
* new transient per resolution, so tracking them per token would either leak
|
|
10
|
+
* (unbounded growth) or silently drop all but the last instance. Callers own
|
|
11
|
+
* the disposal of transient instances they resolve.
|
|
4
12
|
*/
|
|
5
13
|
import type { Token } from "../containerToken/containerToken.type.js";
|
|
6
14
|
import { ContainerLifecycleError } from "@zudojs/errors";
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
/**
|
|
16
|
+
* An object disposable via a `dispose()` method (sync or async).
|
|
17
|
+
* Named `DisposableLike` to avoid shadowing the ES2023 `Disposable` built-in.
|
|
18
|
+
*/
|
|
19
|
+
export interface DisposableLike {
|
|
20
|
+
dispose(): void | Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/** An object disposable via the ES2023 `Symbol.dispose` protocol. */
|
|
23
|
+
export interface SymbolDisposableLike {
|
|
24
|
+
[Symbol.dispose](): void;
|
|
9
25
|
}
|
|
10
|
-
|
|
11
|
-
|
|
26
|
+
/**
|
|
27
|
+
* An object disposable via the ES2023 `Symbol.asyncDispose` protocol.
|
|
28
|
+
* Named `AsyncDisposableLike` to avoid shadowing the `AsyncDisposable`
|
|
29
|
+
* built-in.
|
|
30
|
+
*/
|
|
31
|
+
export interface AsyncDisposableLike {
|
|
32
|
+
[Symbol.asyncDispose](): Promise<void> | void;
|
|
12
33
|
}
|
|
13
|
-
|
|
34
|
+
/** @deprecated Use {@link DisposableLike} instead. */
|
|
35
|
+
export type Disposable = DisposableLike;
|
|
36
|
+
/** @deprecated Use {@link AsyncDisposableLike} instead. */
|
|
37
|
+
export type AsyncDisposable = AsyncDisposableLike;
|
|
38
|
+
export type DisposableInstance = DisposableLike | SymbolDisposableLike | AsyncDisposableLike;
|
|
14
39
|
export declare enum ContainerLifecycleOwner {
|
|
15
40
|
CONTAINER = "container",
|
|
16
41
|
SCOPE = "scope"
|
|
@@ -30,26 +55,51 @@ export declare class ContainerDisposalError extends ContainerLifecycleError {
|
|
|
30
55
|
readonly tokens: readonly Token<unknown>[];
|
|
31
56
|
constructor(errors: readonly unknown[], tokens: readonly Token<unknown>[]);
|
|
32
57
|
}
|
|
33
|
-
export declare function isDisposable(value: unknown): value is
|
|
34
|
-
export declare function
|
|
58
|
+
export declare function isDisposable(value: unknown): value is DisposableLike;
|
|
59
|
+
export declare function isSymbolDisposable(value: unknown): value is SymbolDisposableLike;
|
|
60
|
+
export declare function isAsyncDisposable(value: unknown): value is AsyncDisposableLike;
|
|
35
61
|
export declare function isDisposableInstance(value: unknown): value is DisposableInstance;
|
|
36
62
|
export declare class ContainerLifecycle {
|
|
37
63
|
private readonly instances;
|
|
38
64
|
private readonly options;
|
|
39
65
|
private disposed;
|
|
40
66
|
constructor(options?: ContainerLifecycleOptions);
|
|
67
|
+
/**
|
|
68
|
+
* Tracks a disposable instance for later cleanup.
|
|
69
|
+
*
|
|
70
|
+
* Re-tracking a token removes the previous entry and re-inserts it at the
|
|
71
|
+
* end of the map, so reverse-creation disposal order stays sound.
|
|
72
|
+
* Non-disposable instances are ignored.
|
|
73
|
+
*/
|
|
41
74
|
track<T>(token: Token<T>, instance: T, owner?: ContainerLifecycleOwner): void;
|
|
42
75
|
has<T>(token: Token<T>): boolean;
|
|
43
76
|
get<T>(token: Token<T>): TrackedInstance<T> | undefined;
|
|
44
77
|
getAll(): readonly TrackedInstance[];
|
|
45
78
|
get size(): number;
|
|
46
79
|
untrack<T>(token: Token<T>): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Disposes a single tracked instance and untracks it. Untracking happens
|
|
82
|
+
* even when disposal fails — a failed disposal is terminal for the entry.
|
|
83
|
+
*/
|
|
47
84
|
disposeInstance<T>(token: Token<T>): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Disposes tracked instances in reverse creation order.
|
|
87
|
+
*
|
|
88
|
+
* Entries are untracked even when their disposal fails (terminal). When
|
|
89
|
+
* `owner` is omitted the whole lifecycle is marked disposed — this happens
|
|
90
|
+
* even on failure — and every failure is reported in the thrown
|
|
91
|
+
* ContainerDisposalError.
|
|
92
|
+
*/
|
|
48
93
|
dispose(owner?: ContainerLifecycleOwner): Promise<void>;
|
|
49
94
|
disposeScope(): Promise<void>;
|
|
50
95
|
disposeContainer(): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Releases all tracked references WITHOUT disposing them and marks the
|
|
98
|
+
* lifecycle disposed so further `track()` calls are refused. Used when a
|
|
99
|
+
* container is disposed with `autoDispose: false`.
|
|
100
|
+
*/
|
|
101
|
+
shutdown(): void;
|
|
51
102
|
isDisposed(): boolean;
|
|
52
|
-
reset(): void;
|
|
53
103
|
getTrackedTokens(): readonly Token<unknown>[];
|
|
54
104
|
}
|
|
55
105
|
export declare function createContainerLifecycle(options?: ContainerLifecycleOptions): ContainerLifecycle;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lifecycle management for resolved container instances.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Tracks SINGLETON instances (container-owned) and SCOPED instances
|
|
5
|
+
* (scope-owned) that need cleanup and disposes them when their owner is
|
|
6
|
+
* destroyed, in reverse creation order.
|
|
7
|
+
*
|
|
8
|
+
* TRANSIENT instances are deliberately NOT tracked: the container creates a
|
|
9
|
+
* new transient per resolution, so tracking them per token would either leak
|
|
10
|
+
* (unbounded growth) or silently drop all but the last instance. Callers own
|
|
11
|
+
* the disposal of transient instances they resolve.
|
|
4
12
|
*/
|
|
5
13
|
import { describeToken } from "../containerToken/containerToken.type.js";
|
|
6
14
|
import { ContainerLifecycleError } from "@zudojs/errors";
|
|
@@ -24,6 +32,12 @@ export function isDisposable(value) {
|
|
|
24
32
|
"dispose" in value &&
|
|
25
33
|
typeof value.dispose === "function");
|
|
26
34
|
}
|
|
35
|
+
export function isSymbolDisposable(value) {
|
|
36
|
+
return (typeof value === "object" &&
|
|
37
|
+
value !== null &&
|
|
38
|
+
Symbol.dispose in value &&
|
|
39
|
+
typeof value[Symbol.dispose] === "function");
|
|
40
|
+
}
|
|
27
41
|
export function isAsyncDisposable(value) {
|
|
28
42
|
return (typeof value === "object" &&
|
|
29
43
|
value !== null &&
|
|
@@ -31,7 +45,7 @@ export function isAsyncDisposable(value) {
|
|
|
31
45
|
typeof value[Symbol.asyncDispose] === "function");
|
|
32
46
|
}
|
|
33
47
|
export function isDisposableInstance(value) {
|
|
34
|
-
return isDisposable(value) || isAsyncDisposable(value);
|
|
48
|
+
return (isDisposable(value) || isSymbolDisposable(value) || isAsyncDisposable(value));
|
|
35
49
|
}
|
|
36
50
|
export class ContainerLifecycle {
|
|
37
51
|
instances = new Map();
|
|
@@ -40,11 +54,19 @@ export class ContainerLifecycle {
|
|
|
40
54
|
constructor(options = {}) {
|
|
41
55
|
this.options = { failFast: options.failFast ?? false };
|
|
42
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Tracks a disposable instance for later cleanup.
|
|
59
|
+
*
|
|
60
|
+
* Re-tracking a token removes the previous entry and re-inserts it at the
|
|
61
|
+
* end of the map, so reverse-creation disposal order stays sound.
|
|
62
|
+
* Non-disposable instances are ignored.
|
|
63
|
+
*/
|
|
43
64
|
track(token, instance, owner = ContainerLifecycleOwner.CONTAINER) {
|
|
44
65
|
if (this.disposed)
|
|
45
|
-
throw new
|
|
66
|
+
throw new ContainerLifecycleError("track", "Cannot track an instance after the container lifecycle has been disposed.");
|
|
46
67
|
if (!isDisposableInstance(instance))
|
|
47
68
|
return;
|
|
69
|
+
this.instances.delete(token);
|
|
48
70
|
this.instances.set(token, {
|
|
49
71
|
token,
|
|
50
72
|
instance,
|
|
@@ -68,14 +90,18 @@ export class ContainerLifecycle {
|
|
|
68
90
|
untrack(token) {
|
|
69
91
|
return this.instances.delete(token);
|
|
70
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Disposes a single tracked instance and untracks it. Untracking happens
|
|
95
|
+
* even when disposal fails — a failed disposal is terminal for the entry.
|
|
96
|
+
*/
|
|
71
97
|
async disposeInstance(token) {
|
|
72
|
-
const tracked = this.get(token);
|
|
98
|
+
const tracked = this.instances.get(token);
|
|
73
99
|
if (!tracked || tracked.disposed)
|
|
74
100
|
return;
|
|
101
|
+
tracked.disposed = true;
|
|
102
|
+
this.instances.delete(token);
|
|
75
103
|
try {
|
|
76
104
|
await disposeValue(tracked.instance);
|
|
77
|
-
tracked.disposed = true;
|
|
78
|
-
this.instances.delete(token);
|
|
79
105
|
}
|
|
80
106
|
catch (error) {
|
|
81
107
|
if (this.options.failFast)
|
|
@@ -83,9 +109,21 @@ export class ContainerLifecycle {
|
|
|
83
109
|
throw new ContainerDisposalError([error], [token]);
|
|
84
110
|
}
|
|
85
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Disposes tracked instances in reverse creation order.
|
|
114
|
+
*
|
|
115
|
+
* Entries are untracked even when their disposal fails (terminal). When
|
|
116
|
+
* `owner` is omitted the whole lifecycle is marked disposed — this happens
|
|
117
|
+
* even on failure — and every failure is reported in the thrown
|
|
118
|
+
* ContainerDisposalError.
|
|
119
|
+
*/
|
|
86
120
|
async dispose(owner) {
|
|
87
121
|
if (this.disposed)
|
|
88
122
|
return;
|
|
123
|
+
// Mark before the first await: an instance tracked while disposal is in
|
|
124
|
+
// flight would be dropped by `shutdown()` without ever being disposed.
|
|
125
|
+
if (owner === undefined)
|
|
126
|
+
this.disposed = true;
|
|
89
127
|
const tracked = [...this.instances.values()].reverse();
|
|
90
128
|
const selected = owner
|
|
91
129
|
? tracked.filter((entry) => entry.owner === owner)
|
|
@@ -95,10 +133,10 @@ export class ContainerLifecycle {
|
|
|
95
133
|
for (const entry of selected) {
|
|
96
134
|
if (entry.disposed)
|
|
97
135
|
continue;
|
|
136
|
+
entry.disposed = true;
|
|
137
|
+
this.instances.delete(entry.token);
|
|
98
138
|
try {
|
|
99
139
|
await disposeValue(entry.instance);
|
|
100
|
-
entry.disposed = true;
|
|
101
|
-
this.instances.delete(entry.token);
|
|
102
140
|
}
|
|
103
141
|
catch (error) {
|
|
104
142
|
errors.push(error);
|
|
@@ -109,8 +147,6 @@ export class ContainerLifecycle {
|
|
|
109
147
|
}
|
|
110
148
|
if (errors.length > 0)
|
|
111
149
|
throw new ContainerDisposalError(errors, failedTokens);
|
|
112
|
-
if (owner === undefined)
|
|
113
|
-
this.disposed = true;
|
|
114
150
|
}
|
|
115
151
|
async disposeScope() {
|
|
116
152
|
await this.dispose(ContainerLifecycleOwner.SCOPE);
|
|
@@ -118,13 +154,18 @@ export class ContainerLifecycle {
|
|
|
118
154
|
async disposeContainer() {
|
|
119
155
|
await this.dispose(ContainerLifecycleOwner.CONTAINER);
|
|
120
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Releases all tracked references WITHOUT disposing them and marks the
|
|
159
|
+
* lifecycle disposed so further `track()` calls are refused. Used when a
|
|
160
|
+
* container is disposed with `autoDispose: false`.
|
|
161
|
+
*/
|
|
162
|
+
shutdown() {
|
|
163
|
+
this.instances.clear();
|
|
164
|
+
this.disposed = true;
|
|
165
|
+
}
|
|
121
166
|
isDisposed() {
|
|
122
167
|
return this.disposed;
|
|
123
168
|
}
|
|
124
|
-
reset() {
|
|
125
|
-
this.instances.clear();
|
|
126
|
-
this.disposed = false;
|
|
127
|
-
}
|
|
128
169
|
getTrackedTokens() {
|
|
129
170
|
return [...this.instances.keys()];
|
|
130
171
|
}
|
|
@@ -134,8 +175,12 @@ async function disposeValue(value) {
|
|
|
134
175
|
await value[Symbol.asyncDispose]();
|
|
135
176
|
return;
|
|
136
177
|
}
|
|
178
|
+
if (isSymbolDisposable(value)) {
|
|
179
|
+
value[Symbol.dispose]();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
137
182
|
if (isDisposable(value)) {
|
|
138
|
-
value.dispose();
|
|
183
|
+
await value.dispose();
|
|
139
184
|
return;
|
|
140
185
|
}
|
|
141
186
|
}
|
|
@@ -4,26 +4,18 @@
|
|
|
4
4
|
import type { ContainerLifecycleOptions } from "../containerLifecycle/containerLifecycle.core.js";
|
|
5
5
|
import type { ContainerRegistryOptions } from "../containerRegistry/containerRegistry.type.js";
|
|
6
6
|
import type { ResolutionOptions } from "../containerResolution/containerResolution.type.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
export declare const DEFAULT_CONTAINER_LOG_LEVEL: ContainerLogLevel;
|
|
16
|
-
export interface ContainerResolutionOptions extends ResolutionOptions {
|
|
17
|
-
readonly autoRegisterClasses?: boolean;
|
|
18
|
-
readonly detectCircularDependencies?: boolean;
|
|
19
|
-
readonly maxResolutionDepth?: number;
|
|
20
|
-
}
|
|
7
|
+
/**
|
|
8
|
+
* The subset of {@link ResolutionOptions} a container accepts at
|
|
9
|
+
* construction time. Per-call plumbing (`cache`, `path`,
|
|
10
|
+
* `allowRegistration`, `onInstanceCreated`) is managed by the container
|
|
11
|
+
* itself and deliberately not configurable here.
|
|
12
|
+
*/
|
|
13
|
+
export type ContainerResolutionOptions = Pick<ResolutionOptions, "autoRegisterClasses" | "detectCircularDependencies" | "maxResolutionDepth">;
|
|
21
14
|
export interface ContainerOptions {
|
|
22
15
|
readonly name?: string;
|
|
23
16
|
readonly registry?: ContainerRegistryOptions;
|
|
24
17
|
readonly lifecycle?: ContainerLifecycleOptions;
|
|
25
18
|
readonly resolution?: ContainerResolutionOptions;
|
|
26
|
-
readonly logLevel?: ContainerLogLevel;
|
|
27
19
|
readonly autoDispose?: boolean;
|
|
28
20
|
readonly allowScopes?: boolean;
|
|
29
21
|
readonly freezeRegistrations?: boolean;
|
|
@@ -34,7 +26,6 @@ export interface ResolvedContainerOptions {
|
|
|
34
26
|
readonly registry: ContainerRegistryOptions;
|
|
35
27
|
readonly lifecycle: ContainerLifecycleOptions;
|
|
36
28
|
readonly resolution: ContainerResolutionOptions;
|
|
37
|
-
readonly logLevel: ContainerLogLevel;
|
|
38
29
|
readonly autoDispose: boolean;
|
|
39
30
|
readonly allowScopes: boolean;
|
|
40
31
|
readonly freezeRegistrations: boolean;
|
|
@@ -47,7 +38,6 @@ export declare const DEFAULT_FREEZE_REGISTRATIONS = false;
|
|
|
47
38
|
export declare const DEFAULT_RESOLUTION_OPTIONS: Required<Pick<ContainerResolutionOptions, "autoRegisterClasses" | "detectCircularDependencies" | "maxResolutionDepth">>;
|
|
48
39
|
export declare function resolveContainerOptions(options?: ContainerOptions): ResolvedContainerOptions;
|
|
49
40
|
export declare function validateResolutionOptions(options: ContainerResolutionOptions): void;
|
|
50
|
-
export declare function isContainerLogLevel(value: unknown): value is ContainerLogLevel;
|
|
51
41
|
export declare function allowsContainerScopes(options: ResolvedContainerOptions): boolean;
|
|
52
42
|
export declare function shouldAutoDisposeContainer(options: ResolvedContainerOptions): boolean;
|
|
53
43
|
export declare function canModifyRegistrations(options: ResolvedContainerOptions): boolean;
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Configuration options for the Zudojs dependency injection container.
|
|
3
3
|
*/
|
|
4
|
-
export var ContainerLogLevel;
|
|
5
|
-
(function (ContainerLogLevel) {
|
|
6
|
-
ContainerLogLevel["NONE"] = "none";
|
|
7
|
-
ContainerLogLevel["ERROR"] = "error";
|
|
8
|
-
ContainerLogLevel["WARN"] = "warn";
|
|
9
|
-
ContainerLogLevel["INFO"] = "info";
|
|
10
|
-
ContainerLogLevel["DEBUG"] = "debug";
|
|
11
|
-
ContainerLogLevel["TRACE"] = "trace";
|
|
12
|
-
})(ContainerLogLevel || (ContainerLogLevel = {}));
|
|
13
|
-
export const DEFAULT_CONTAINER_LOG_LEVEL = ContainerLogLevel.WARN;
|
|
14
4
|
export const DEFAULT_CONTAINER_NAME = "zudojs-container";
|
|
15
5
|
export const DEFAULT_AUTO_DISPOSE = true;
|
|
16
6
|
export const DEFAULT_ALLOW_SCOPES = true;
|
|
@@ -39,7 +29,6 @@ export function resolveContainerOptions(options = {}) {
|
|
|
39
29
|
registry: Object.freeze({ ...(options.registry ?? {}) }),
|
|
40
30
|
lifecycle: Object.freeze({ ...(options.lifecycle ?? {}) }),
|
|
41
31
|
resolution: Object.freeze({ ...resolution }),
|
|
42
|
-
logLevel: options.logLevel ?? DEFAULT_CONTAINER_LOG_LEVEL,
|
|
43
32
|
autoDispose: options.autoDispose ?? DEFAULT_AUTO_DISPOSE,
|
|
44
33
|
allowScopes: options.allowScopes ?? DEFAULT_ALLOW_SCOPES,
|
|
45
34
|
freezeRegistrations: options.freezeRegistrations ?? DEFAULT_FREEZE_REGISTRATIONS,
|
|
@@ -53,14 +42,6 @@ export function validateResolutionOptions(options) {
|
|
|
53
42
|
throw new RangeError("Container maxResolutionDepth must be a positive integer.");
|
|
54
43
|
}
|
|
55
44
|
}
|
|
56
|
-
export function isContainerLogLevel(value) {
|
|
57
|
-
return (value === ContainerLogLevel.NONE ||
|
|
58
|
-
value === ContainerLogLevel.ERROR ||
|
|
59
|
-
value === ContainerLogLevel.WARN ||
|
|
60
|
-
value === ContainerLogLevel.INFO ||
|
|
61
|
-
value === ContainerLogLevel.DEBUG ||
|
|
62
|
-
value === ContainerLogLevel.TRACE);
|
|
63
|
-
}
|
|
64
45
|
export function allowsContainerScopes(options) {
|
|
65
46
|
return options.allowScopes;
|
|
66
47
|
}
|
|
@@ -6,6 +6,11 @@ import type { Constructor, InjectionToken, Token } from "../containerToken/conta
|
|
|
6
6
|
export type ProviderToken<T = unknown> = Token<T> | InjectionToken<T>;
|
|
7
7
|
export interface ClassProvider<T> {
|
|
8
8
|
readonly useClass: Constructor<T>;
|
|
9
|
+
/**
|
|
10
|
+
* Tokens resolved and passed to the constructor, in order.
|
|
11
|
+
* Omit (or leave empty) for zero-argument constructors.
|
|
12
|
+
*/
|
|
13
|
+
readonly inject?: readonly ProviderToken[];
|
|
9
14
|
}
|
|
10
15
|
export interface FactoryProvider<T> {
|
|
11
16
|
readonly useFactory: (...dependencies: unknown[]) => T;
|
|
@@ -25,6 +30,7 @@ export interface TokenProvider<T = unknown> {
|
|
|
25
30
|
export interface ClassRegistration<T = unknown> {
|
|
26
31
|
readonly provide: ProviderToken<T>;
|
|
27
32
|
readonly useClass: Constructor<T>;
|
|
33
|
+
readonly inject?: readonly ProviderToken[];
|
|
28
34
|
}
|
|
29
35
|
export interface FactoryRegistration<T = unknown> {
|
|
30
36
|
readonly provide: ProviderToken<T>;
|
|
@@ -45,12 +51,14 @@ export declare function isFactoryProvider<T = unknown>(provider: Provider<T>): p
|
|
|
45
51
|
export declare function isValueProvider<T = unknown>(provider: Provider<T>): provider is ValueProvider<T>;
|
|
46
52
|
export declare function isExistingProvider<T = unknown>(provider: Provider<T>): provider is ExistingProvider<T>;
|
|
47
53
|
export declare function isTokenProvider<T = unknown>(provider: ContainerProvider<T>): provider is TokenProvider<T>;
|
|
48
|
-
export declare function hasInjectedDependencies<T = unknown>(provider: Provider<T>): provider is FactoryProvider<T
|
|
49
|
-
|
|
54
|
+
export declare function hasInjectedDependencies<T = unknown>(provider: Provider<T>): provider is (FactoryProvider<T> | ClassProvider<T>) & {
|
|
55
|
+
readonly inject: readonly ProviderToken[];
|
|
56
|
+
};
|
|
57
|
+
export declare function classProvider<T>(useClass: Constructor<T>, inject?: readonly ProviderToken[]): ClassProvider<T>;
|
|
50
58
|
export declare function factoryProvider<T>(useFactory: (...dependencies: unknown[]) => T, inject?: readonly ProviderToken[]): FactoryProvider<T>;
|
|
51
59
|
export declare function valueProvider<T>(useValue: T): ValueProvider<T>;
|
|
52
60
|
export declare function existingProvider<T>(useExisting: ProviderToken<T>): ExistingProvider<T>;
|
|
53
|
-
export declare function provideClass<T>(provide: ProviderToken<T>, useClass: Constructor<T
|
|
61
|
+
export declare function provideClass<T>(provide: ProviderToken<T>, useClass: Constructor<T>, inject?: readonly ProviderToken[]): ClassRegistration<T>;
|
|
54
62
|
export declare function provideFactory<T>(provide: ProviderToken<T>, useFactory: (...dependencies: unknown[]) => T, inject?: readonly ProviderToken[]): FactoryRegistration<T>;
|
|
55
63
|
export declare function provideValue<T>(provide: ProviderToken<T>, useValue: T): ValueRegistration<T>;
|
|
56
64
|
export declare function provideExisting<T>(provide: ProviderToken<T>, useExisting: ProviderToken<T>): ExistingRegistration<T>;
|