katagami 1.1.0 → 2.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/README.md +136 -35
- package/dist/container/index.d.ts +14 -20
- package/dist/disposable/index.cjs +76 -0
- package/dist/disposable/index.d.ts +61 -0
- package/dist/disposable/index.js +45 -0
- package/dist/index-g50fxds1.js +34 -0
- package/dist/index-jx8b52m0.js +4 -0
- package/dist/index.cjs +17 -125
- package/dist/index.d.ts +2 -1
- package/dist/index.js +22 -157
- package/dist/internal.d.ts +27 -0
- package/dist/scope/index.cjs +145 -0
- package/dist/scope/index.d.ts +24 -23
- package/dist/scope/index.js +86 -0
- package/package.json +14 -3
- package/README.de.md +0 -438
- package/README.es.md +0 -386
- package/README.fr.md +0 -386
- package/README.ja.md +0 -438
- package/README.ko.md +0 -438
- package/README.zh-CN.md +0 -438
- package/README.zh-TW.md +0 -438
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,6 @@ var __export = (target, all) => {
|
|
|
30
30
|
var exports_src = {};
|
|
31
31
|
__export(exports_src, {
|
|
32
32
|
createContainer: () => createContainer,
|
|
33
|
-
Scope: () => Scope,
|
|
34
33
|
ContainerError: () => ContainerError,
|
|
35
34
|
Container: () => Container
|
|
36
35
|
});
|
|
@@ -44,6 +43,9 @@ class ContainerError extends Error {
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
// src/internal.ts
|
|
47
|
+
var INTERNALS = Symbol("katagami.internals");
|
|
48
|
+
|
|
47
49
|
// src/resolver/index.ts
|
|
48
50
|
function tokenToString(token) {
|
|
49
51
|
if (typeof token === "function") {
|
|
@@ -69,97 +71,6 @@ function buildCircularPath(resolvingTokens, token) {
|
|
|
69
71
|
return path.join(" -> ");
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
// src/scope/index.ts
|
|
73
|
-
class Scope {
|
|
74
|
-
registrations;
|
|
75
|
-
singletonInstances;
|
|
76
|
-
scopedInstances;
|
|
77
|
-
resolvingTokens;
|
|
78
|
-
disposed = false;
|
|
79
|
-
constructor(registrations, singletonInstances) {
|
|
80
|
-
this.registrations = registrations;
|
|
81
|
-
this.singletonInstances = singletonInstances;
|
|
82
|
-
this.scopedInstances = new Map;
|
|
83
|
-
this.resolvingTokens = new Set;
|
|
84
|
-
}
|
|
85
|
-
resolve(token) {
|
|
86
|
-
return this.resolveToken(token, true);
|
|
87
|
-
}
|
|
88
|
-
tryResolve(token) {
|
|
89
|
-
return this.resolveToken(token, false);
|
|
90
|
-
}
|
|
91
|
-
resolveToken(token, required) {
|
|
92
|
-
if (this.disposed) {
|
|
93
|
-
throw new ContainerError("Cannot resolve from a disposed scope.");
|
|
94
|
-
}
|
|
95
|
-
const singletonCached = this.singletonInstances.get(token);
|
|
96
|
-
if (singletonCached !== undefined) {
|
|
97
|
-
return singletonCached;
|
|
98
|
-
}
|
|
99
|
-
const scopedCached = this.scopedInstances.get(token);
|
|
100
|
-
if (scopedCached !== undefined) {
|
|
101
|
-
return scopedCached;
|
|
102
|
-
}
|
|
103
|
-
const registration = this.registrations.get(token);
|
|
104
|
-
if (registration === undefined) {
|
|
105
|
-
if (required) {
|
|
106
|
-
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (this.resolvingTokens.has(token)) {
|
|
111
|
-
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
112
|
-
}
|
|
113
|
-
this.resolvingTokens.add(token);
|
|
114
|
-
try {
|
|
115
|
-
const instance = registration.factory(this);
|
|
116
|
-
if (registration.lifetime === "singleton") {
|
|
117
|
-
this.singletonInstances.set(token, instance);
|
|
118
|
-
} else if (registration.lifetime === "scoped") {
|
|
119
|
-
this.scopedInstances.set(token, instance);
|
|
120
|
-
}
|
|
121
|
-
return instance;
|
|
122
|
-
} finally {
|
|
123
|
-
this.resolvingTokens.delete(token);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
createScope() {
|
|
127
|
-
if (this.disposed) {
|
|
128
|
-
throw new ContainerError("Cannot create a scope from a disposed scope.");
|
|
129
|
-
}
|
|
130
|
-
return new Scope(this.registrations, this.singletonInstances);
|
|
131
|
-
}
|
|
132
|
-
async[Symbol.asyncDispose]() {
|
|
133
|
-
if (this.disposed) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
this.disposed = true;
|
|
137
|
-
const instances = [...this.scopedInstances.values()].reverse();
|
|
138
|
-
const errors = [];
|
|
139
|
-
for (const instance of instances) {
|
|
140
|
-
try {
|
|
141
|
-
let resolved = instance;
|
|
142
|
-
if (instance instanceof Promise) {
|
|
143
|
-
resolved = await instance;
|
|
144
|
-
}
|
|
145
|
-
if (resolved != null && typeof resolved === "object") {
|
|
146
|
-
if (Symbol.asyncDispose in resolved) {
|
|
147
|
-
await resolved[Symbol.asyncDispose]();
|
|
148
|
-
} else if (Symbol.dispose in resolved) {
|
|
149
|
-
resolved[Symbol.dispose]();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} catch (error) {
|
|
153
|
-
errors.push(error);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
this.scopedInstances.clear();
|
|
157
|
-
if (errors.length > 0) {
|
|
158
|
-
throw new AggregateError(errors, "One or more errors occurred during disposal.");
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
74
|
// src/container/index.ts
|
|
164
75
|
function createContainer() {
|
|
165
76
|
return new Container;
|
|
@@ -170,10 +81,20 @@ class Container {
|
|
|
170
81
|
instances;
|
|
171
82
|
resolvingTokens;
|
|
172
83
|
disposed = false;
|
|
84
|
+
[INTERNALS];
|
|
173
85
|
constructor() {
|
|
174
86
|
this.registrations = new Map;
|
|
175
87
|
this.instances = new Map;
|
|
176
88
|
this.resolvingTokens = new Set;
|
|
89
|
+
this[INTERNALS] = {
|
|
90
|
+
instances: this.instances,
|
|
91
|
+
isDisposed: () => this.disposed,
|
|
92
|
+
markDisposed: () => {
|
|
93
|
+
this.disposed = true;
|
|
94
|
+
},
|
|
95
|
+
ownInstances: this.instances,
|
|
96
|
+
registrations: this.registrations
|
|
97
|
+
};
|
|
177
98
|
}
|
|
178
99
|
registerSingleton(token, factory) {
|
|
179
100
|
return this.addRegistration(token, factory, "singleton");
|
|
@@ -184,11 +105,11 @@ class Container {
|
|
|
184
105
|
registerScoped(token, factory) {
|
|
185
106
|
return this.addRegistration(token, factory, "scoped");
|
|
186
107
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
108
|
+
use(source) {
|
|
109
|
+
for (const [token, registration] of source[INTERNALS].registrations) {
|
|
110
|
+
this.registrations.set(token, registration);
|
|
190
111
|
}
|
|
191
|
-
return
|
|
112
|
+
return this;
|
|
192
113
|
}
|
|
193
114
|
resolve(token) {
|
|
194
115
|
return this.resolveToken(token, true);
|
|
@@ -228,35 +149,6 @@ class Container {
|
|
|
228
149
|
this.resolvingTokens.delete(token);
|
|
229
150
|
}
|
|
230
151
|
}
|
|
231
|
-
async[Symbol.asyncDispose]() {
|
|
232
|
-
if (this.disposed) {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
this.disposed = true;
|
|
236
|
-
const instances = [...this.instances.values()].reverse();
|
|
237
|
-
const errors = [];
|
|
238
|
-
for (const instance of instances) {
|
|
239
|
-
try {
|
|
240
|
-
let resolved = instance;
|
|
241
|
-
if (instance instanceof Promise) {
|
|
242
|
-
resolved = await instance;
|
|
243
|
-
}
|
|
244
|
-
if (resolved != null && typeof resolved === "object") {
|
|
245
|
-
if (Symbol.asyncDispose in resolved) {
|
|
246
|
-
await resolved[Symbol.asyncDispose]();
|
|
247
|
-
} else if (Symbol.dispose in resolved) {
|
|
248
|
-
resolved[Symbol.dispose]();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
errors.push(error);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
this.instances.clear();
|
|
256
|
-
if (errors.length > 0) {
|
|
257
|
-
throw new AggregateError(errors, "One or more errors occurred during disposal.");
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
152
|
addRegistration(token, factory, lifetime) {
|
|
261
153
|
this.registrations.set(token, { factory, lifetime });
|
|
262
154
|
return this;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Container, createContainer } from './container';
|
|
2
|
+
export type { DisposableContainer, DisposableScope, disposable } from './disposable';
|
|
2
3
|
export { ContainerError } from './error';
|
|
3
4
|
export type { Resolver } from './resolver';
|
|
4
|
-
export { Scope } from './scope';
|
|
5
|
+
export type { createScope, Scope } from './scope';
|
package/dist/index.js
CHANGED
|
@@ -1,126 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// src/resolver/index.ts
|
|
10
|
-
function tokenToString(token) {
|
|
11
|
-
if (typeof token === "function") {
|
|
12
|
-
return token.name || "anonymous function";
|
|
13
|
-
}
|
|
14
|
-
if (typeof token === "symbol") {
|
|
15
|
-
return token.toString();
|
|
16
|
-
}
|
|
17
|
-
return String(token);
|
|
18
|
-
}
|
|
19
|
-
function buildCircularPath(resolvingTokens, token) {
|
|
20
|
-
const path = [];
|
|
21
|
-
let found = false;
|
|
22
|
-
for (const t of resolvingTokens) {
|
|
23
|
-
if (t === token) {
|
|
24
|
-
found = true;
|
|
25
|
-
}
|
|
26
|
-
if (found) {
|
|
27
|
-
path.push(tokenToString(t));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
path.push(tokenToString(token));
|
|
31
|
-
return path.join(" -> ");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// src/scope/index.ts
|
|
35
|
-
class Scope {
|
|
36
|
-
registrations;
|
|
37
|
-
singletonInstances;
|
|
38
|
-
scopedInstances;
|
|
39
|
-
resolvingTokens;
|
|
40
|
-
disposed = false;
|
|
41
|
-
constructor(registrations, singletonInstances) {
|
|
42
|
-
this.registrations = registrations;
|
|
43
|
-
this.singletonInstances = singletonInstances;
|
|
44
|
-
this.scopedInstances = new Map;
|
|
45
|
-
this.resolvingTokens = new Set;
|
|
46
|
-
}
|
|
47
|
-
resolve(token) {
|
|
48
|
-
return this.resolveToken(token, true);
|
|
49
|
-
}
|
|
50
|
-
tryResolve(token) {
|
|
51
|
-
return this.resolveToken(token, false);
|
|
52
|
-
}
|
|
53
|
-
resolveToken(token, required) {
|
|
54
|
-
if (this.disposed) {
|
|
55
|
-
throw new ContainerError("Cannot resolve from a disposed scope.");
|
|
56
|
-
}
|
|
57
|
-
const singletonCached = this.singletonInstances.get(token);
|
|
58
|
-
if (singletonCached !== undefined) {
|
|
59
|
-
return singletonCached;
|
|
60
|
-
}
|
|
61
|
-
const scopedCached = this.scopedInstances.get(token);
|
|
62
|
-
if (scopedCached !== undefined) {
|
|
63
|
-
return scopedCached;
|
|
64
|
-
}
|
|
65
|
-
const registration = this.registrations.get(token);
|
|
66
|
-
if (registration === undefined) {
|
|
67
|
-
if (required) {
|
|
68
|
-
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (this.resolvingTokens.has(token)) {
|
|
73
|
-
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
74
|
-
}
|
|
75
|
-
this.resolvingTokens.add(token);
|
|
76
|
-
try {
|
|
77
|
-
const instance = registration.factory(this);
|
|
78
|
-
if (registration.lifetime === "singleton") {
|
|
79
|
-
this.singletonInstances.set(token, instance);
|
|
80
|
-
} else if (registration.lifetime === "scoped") {
|
|
81
|
-
this.scopedInstances.set(token, instance);
|
|
82
|
-
}
|
|
83
|
-
return instance;
|
|
84
|
-
} finally {
|
|
85
|
-
this.resolvingTokens.delete(token);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
createScope() {
|
|
89
|
-
if (this.disposed) {
|
|
90
|
-
throw new ContainerError("Cannot create a scope from a disposed scope.");
|
|
91
|
-
}
|
|
92
|
-
return new Scope(this.registrations, this.singletonInstances);
|
|
93
|
-
}
|
|
94
|
-
async[Symbol.asyncDispose]() {
|
|
95
|
-
if (this.disposed) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
this.disposed = true;
|
|
99
|
-
const instances = [...this.scopedInstances.values()].reverse();
|
|
100
|
-
const errors = [];
|
|
101
|
-
for (const instance of instances) {
|
|
102
|
-
try {
|
|
103
|
-
let resolved = instance;
|
|
104
|
-
if (instance instanceof Promise) {
|
|
105
|
-
resolved = await instance;
|
|
106
|
-
}
|
|
107
|
-
if (resolved != null && typeof resolved === "object") {
|
|
108
|
-
if (Symbol.asyncDispose in resolved) {
|
|
109
|
-
await resolved[Symbol.asyncDispose]();
|
|
110
|
-
} else if (Symbol.dispose in resolved) {
|
|
111
|
-
resolved[Symbol.dispose]();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
errors.push(error);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
this.scopedInstances.clear();
|
|
119
|
-
if (errors.length > 0) {
|
|
120
|
-
throw new AggregateError(errors, "One or more errors occurred during disposal.");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
ContainerError,
|
|
3
|
+
buildCircularPath,
|
|
4
|
+
tokenToString
|
|
5
|
+
} from "./index-g50fxds1.js";
|
|
6
|
+
import {
|
|
7
|
+
INTERNALS
|
|
8
|
+
} from "./index-jx8b52m0.js";
|
|
124
9
|
|
|
125
10
|
// src/container/index.ts
|
|
126
11
|
function createContainer() {
|
|
@@ -132,10 +17,20 @@ class Container {
|
|
|
132
17
|
instances;
|
|
133
18
|
resolvingTokens;
|
|
134
19
|
disposed = false;
|
|
20
|
+
[INTERNALS];
|
|
135
21
|
constructor() {
|
|
136
22
|
this.registrations = new Map;
|
|
137
23
|
this.instances = new Map;
|
|
138
24
|
this.resolvingTokens = new Set;
|
|
25
|
+
this[INTERNALS] = {
|
|
26
|
+
instances: this.instances,
|
|
27
|
+
isDisposed: () => this.disposed,
|
|
28
|
+
markDisposed: () => {
|
|
29
|
+
this.disposed = true;
|
|
30
|
+
},
|
|
31
|
+
ownInstances: this.instances,
|
|
32
|
+
registrations: this.registrations
|
|
33
|
+
};
|
|
139
34
|
}
|
|
140
35
|
registerSingleton(token, factory) {
|
|
141
36
|
return this.addRegistration(token, factory, "singleton");
|
|
@@ -146,11 +41,11 @@ class Container {
|
|
|
146
41
|
registerScoped(token, factory) {
|
|
147
42
|
return this.addRegistration(token, factory, "scoped");
|
|
148
43
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
44
|
+
use(source) {
|
|
45
|
+
for (const [token, registration] of source[INTERNALS].registrations) {
|
|
46
|
+
this.registrations.set(token, registration);
|
|
152
47
|
}
|
|
153
|
-
return
|
|
48
|
+
return this;
|
|
154
49
|
}
|
|
155
50
|
resolve(token) {
|
|
156
51
|
return this.resolveToken(token, true);
|
|
@@ -190,35 +85,6 @@ class Container {
|
|
|
190
85
|
this.resolvingTokens.delete(token);
|
|
191
86
|
}
|
|
192
87
|
}
|
|
193
|
-
async[Symbol.asyncDispose]() {
|
|
194
|
-
if (this.disposed) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
this.disposed = true;
|
|
198
|
-
const instances = [...this.instances.values()].reverse();
|
|
199
|
-
const errors = [];
|
|
200
|
-
for (const instance of instances) {
|
|
201
|
-
try {
|
|
202
|
-
let resolved = instance;
|
|
203
|
-
if (instance instanceof Promise) {
|
|
204
|
-
resolved = await instance;
|
|
205
|
-
}
|
|
206
|
-
if (resolved != null && typeof resolved === "object") {
|
|
207
|
-
if (Symbol.asyncDispose in resolved) {
|
|
208
|
-
await resolved[Symbol.asyncDispose]();
|
|
209
|
-
} else if (Symbol.dispose in resolved) {
|
|
210
|
-
resolved[Symbol.dispose]();
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} catch (error) {
|
|
214
|
-
errors.push(error);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
this.instances.clear();
|
|
218
|
-
if (errors.length > 0) {
|
|
219
|
-
throw new AggregateError(errors, "One or more errors occurred during disposal.");
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
88
|
addRegistration(token, factory, lifetime) {
|
|
223
89
|
this.registrations.set(token, { factory, lifetime });
|
|
224
90
|
return this;
|
|
@@ -226,7 +92,6 @@ class Container {
|
|
|
226
92
|
}
|
|
227
93
|
export {
|
|
228
94
|
createContainer,
|
|
229
|
-
Scope,
|
|
230
95
|
ContainerError,
|
|
231
96
|
Container
|
|
232
97
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Registration } from './resolver';
|
|
2
|
+
/**
|
|
3
|
+
* Symbol used by extension modules (scope, disposable) to access container/scope internals.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export declare const INTERNALS: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* Internal state exposed via the INTERNALS symbol.
|
|
10
|
+
*
|
|
11
|
+
* Both Container and Scope implement this interface so that extension modules
|
|
12
|
+
* (scope, disposable) can operate on either without importing the concrete class.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export interface ContainerInternals {
|
|
17
|
+
/** All registrations (singleton / transient / scoped). */
|
|
18
|
+
readonly registrations: Map<unknown, Registration>;
|
|
19
|
+
/** Instance cache used during resolution (Container: singletons, Scope: singletonInstances). */
|
|
20
|
+
readonly instances: Map<unknown, unknown>;
|
|
21
|
+
/** Instances owned by this container / scope (disposal target). */
|
|
22
|
+
readonly ownInstances: Map<unknown, unknown>;
|
|
23
|
+
/** Whether this container / scope has been disposed. */
|
|
24
|
+
isDisposed(): boolean;
|
|
25
|
+
/** Mark this container / scope as disposed. */
|
|
26
|
+
markDisposed(): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
6
|
+
var __toCommonJS = (from) => {
|
|
7
|
+
var entry = __moduleCache.get(from), desc;
|
|
8
|
+
if (entry)
|
|
9
|
+
return entry;
|
|
10
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
13
|
+
get: () => from[key],
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
}));
|
|
16
|
+
__moduleCache.set(from, entry);
|
|
17
|
+
return entry;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
set: (newValue) => all[name] = () => newValue
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/scope/index.ts
|
|
30
|
+
var exports_scope = {};
|
|
31
|
+
__export(exports_scope, {
|
|
32
|
+
createScope: () => createScope,
|
|
33
|
+
Scope: () => Scope
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(exports_scope);
|
|
36
|
+
|
|
37
|
+
// src/error/index.ts
|
|
38
|
+
class ContainerError extends Error {
|
|
39
|
+
constructor(message) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = "ContainerError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/internal.ts
|
|
46
|
+
var INTERNALS = Symbol("katagami.internals");
|
|
47
|
+
|
|
48
|
+
// src/resolver/index.ts
|
|
49
|
+
function tokenToString(token) {
|
|
50
|
+
if (typeof token === "function") {
|
|
51
|
+
return token.name || "anonymous function";
|
|
52
|
+
}
|
|
53
|
+
if (typeof token === "symbol") {
|
|
54
|
+
return token.toString();
|
|
55
|
+
}
|
|
56
|
+
return String(token);
|
|
57
|
+
}
|
|
58
|
+
function buildCircularPath(resolvingTokens, token) {
|
|
59
|
+
const path = [];
|
|
60
|
+
let found = false;
|
|
61
|
+
for (const t of resolvingTokens) {
|
|
62
|
+
if (t === token) {
|
|
63
|
+
found = true;
|
|
64
|
+
}
|
|
65
|
+
if (found) {
|
|
66
|
+
path.push(tokenToString(t));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
path.push(tokenToString(token));
|
|
70
|
+
return path.join(" -> ");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/scope/index.ts
|
|
74
|
+
function createScope(source) {
|
|
75
|
+
const internals = source[INTERNALS];
|
|
76
|
+
if (internals.isDisposed()) {
|
|
77
|
+
throw new ContainerError("Cannot create a scope from a disposed container.");
|
|
78
|
+
}
|
|
79
|
+
return new Scope(internals.registrations, internals.instances);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class Scope {
|
|
83
|
+
registrations;
|
|
84
|
+
singletonInstances;
|
|
85
|
+
scopedInstances;
|
|
86
|
+
resolvingTokens;
|
|
87
|
+
disposed = false;
|
|
88
|
+
[INTERNALS];
|
|
89
|
+
constructor(registrations, singletonInstances) {
|
|
90
|
+
this.registrations = registrations;
|
|
91
|
+
this.singletonInstances = singletonInstances;
|
|
92
|
+
this.scopedInstances = new Map;
|
|
93
|
+
this.resolvingTokens = new Set;
|
|
94
|
+
this[INTERNALS] = {
|
|
95
|
+
instances: this.singletonInstances,
|
|
96
|
+
isDisposed: () => this.disposed,
|
|
97
|
+
markDisposed: () => {
|
|
98
|
+
this.disposed = true;
|
|
99
|
+
},
|
|
100
|
+
ownInstances: this.scopedInstances,
|
|
101
|
+
registrations: this.registrations
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
resolve(token) {
|
|
105
|
+
return this.resolveToken(token, true);
|
|
106
|
+
}
|
|
107
|
+
tryResolve(token) {
|
|
108
|
+
return this.resolveToken(token, false);
|
|
109
|
+
}
|
|
110
|
+
resolveToken(token, required) {
|
|
111
|
+
if (this.disposed) {
|
|
112
|
+
throw new ContainerError("Cannot resolve from a disposed scope.");
|
|
113
|
+
}
|
|
114
|
+
const singletonCached = this.singletonInstances.get(token);
|
|
115
|
+
if (singletonCached !== undefined) {
|
|
116
|
+
return singletonCached;
|
|
117
|
+
}
|
|
118
|
+
const scopedCached = this.scopedInstances.get(token);
|
|
119
|
+
if (scopedCached !== undefined) {
|
|
120
|
+
return scopedCached;
|
|
121
|
+
}
|
|
122
|
+
const registration = this.registrations.get(token);
|
|
123
|
+
if (registration === undefined) {
|
|
124
|
+
if (required) {
|
|
125
|
+
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (this.resolvingTokens.has(token)) {
|
|
130
|
+
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
131
|
+
}
|
|
132
|
+
this.resolvingTokens.add(token);
|
|
133
|
+
try {
|
|
134
|
+
const instance = registration.factory(this);
|
|
135
|
+
if (registration.lifetime === "singleton") {
|
|
136
|
+
this.singletonInstances.set(token, instance);
|
|
137
|
+
} else if (registration.lifetime === "scoped") {
|
|
138
|
+
this.scopedInstances.set(token, instance);
|
|
139
|
+
}
|
|
140
|
+
return instance;
|
|
141
|
+
} finally {
|
|
142
|
+
this.resolvingTokens.delete(token);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
import type { Container } from '../container';
|
|
2
|
+
import type { DisposableContainer, DisposableScope } from '../disposable';
|
|
3
|
+
import { type ContainerInternals, INTERNALS } from '../internal';
|
|
1
4
|
import type { AbstractConstructor, Registration } from '../resolver';
|
|
5
|
+
/**
|
|
6
|
+
* Create a new scope (child container) from a Container, Scope, or their disposable variants.
|
|
7
|
+
*
|
|
8
|
+
* The scope inherits all registrations from the source.
|
|
9
|
+
* Singleton instances are shared with the parent, while scoped instances are local to the scope.
|
|
10
|
+
*
|
|
11
|
+
* @param source A Container, Scope, DisposableContainer, or DisposableScope to create a child scope from
|
|
12
|
+
* @returns A new Scope instance
|
|
13
|
+
* @throws ContainerError if the source has been disposed
|
|
14
|
+
*/
|
|
15
|
+
export declare function createScope<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor>(source: Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>): Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
16
|
+
export declare function createScope<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor>(source: Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>): Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
17
|
+
export declare function createScope<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor>(source: DisposableContainer<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>): Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
18
|
+
export declare function createScope<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor>(source: DisposableScope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>): Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
2
19
|
/**
|
|
3
20
|
* Scoped child container.
|
|
4
21
|
*
|
|
@@ -13,12 +30,18 @@ import type { AbstractConstructor, Registration } from '../resolver';
|
|
|
13
30
|
* @template ScopedSync Union of scoped sync class constructors
|
|
14
31
|
* @template ScopedAsync Union of scoped async class constructors
|
|
15
32
|
*/
|
|
16
|
-
export declare class Scope<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, ScopedT = Record<never, never>, ScopedSync extends AbstractConstructor = never, ScopedAsync extends AbstractConstructor = never>
|
|
33
|
+
export declare class Scope<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, ScopedT = Record<never, never>, ScopedSync extends AbstractConstructor = never, ScopedAsync extends AbstractConstructor = never> {
|
|
17
34
|
private readonly registrations;
|
|
18
35
|
private readonly singletonInstances;
|
|
19
36
|
private readonly scopedInstances;
|
|
20
37
|
private readonly resolvingTokens;
|
|
21
38
|
private disposed;
|
|
39
|
+
/**
|
|
40
|
+
* Internal state accessor for extension modules (scope, disposable).
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
readonly [INTERNALS]: ContainerInternals;
|
|
22
45
|
constructor(registrations: Map<unknown, Registration>, singletonInstances: Map<unknown, unknown>);
|
|
23
46
|
/**
|
|
24
47
|
* Resolve an instance for the given token.
|
|
@@ -56,26 +79,4 @@ export declare class Scope<T = Record<never, never>, Sync extends AbstractConstr
|
|
|
56
79
|
* @returns The resolved instance, or undefined if not registered and required is false
|
|
57
80
|
*/
|
|
58
81
|
private resolveToken;
|
|
59
|
-
/**
|
|
60
|
-
* Create a nested scope.
|
|
61
|
-
*
|
|
62
|
-
* The nested scope shares singleton instances with the parent but has its own scoped instance cache.
|
|
63
|
-
*
|
|
64
|
-
* @returns A new Scope instance
|
|
65
|
-
* @throws ContainerError if the scope has been disposed
|
|
66
|
-
*/
|
|
67
|
-
createScope(): Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
68
|
-
/**
|
|
69
|
-
* Dispose all scoped instances managed by this scope.
|
|
70
|
-
*
|
|
71
|
-
* Iterates through scoped instances in reverse creation order (LIFO) and calls
|
|
72
|
-
* `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them.
|
|
73
|
-
* Singleton instances are not disposed as they are owned by the parent container.
|
|
74
|
-
*
|
|
75
|
-
* This method is idempotent — subsequent calls after the first are no-ops.
|
|
76
|
-
* After disposal, `resolve()` and `createScope()` will throw `ContainerError`.
|
|
77
|
-
*
|
|
78
|
-
* @throws AggregateError if one or more instances throw during disposal
|
|
79
|
-
*/
|
|
80
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
81
82
|
}
|