katagami 1.1.0 → 2.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 +21 -14
- package/dist/container/index.d.ts +8 -24
- package/dist/disposable/index.cjs +76 -0
- package/dist/disposable/index.d.ts +24 -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 +13 -127
- package/dist/index.d.ts +2 -1
- package/dist/index.js +18 -159
- package/dist/internal.d.ts +27 -0
- package/dist/scope/index.cjs +145 -0
- package/dist/scope/index.d.ts +21 -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.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,12 +41,6 @@ class Container {
|
|
|
146
41
|
registerScoped(token, factory) {
|
|
147
42
|
return this.addRegistration(token, factory, "scoped");
|
|
148
43
|
}
|
|
149
|
-
createScope() {
|
|
150
|
-
if (this.disposed) {
|
|
151
|
-
throw new ContainerError("Cannot create a scope from a disposed container.");
|
|
152
|
-
}
|
|
153
|
-
return new Scope(this.registrations, this.instances);
|
|
154
|
-
}
|
|
155
44
|
resolve(token) {
|
|
156
45
|
return this.resolveToken(token, true);
|
|
157
46
|
}
|
|
@@ -190,35 +79,6 @@ class Container {
|
|
|
190
79
|
this.resolvingTokens.delete(token);
|
|
191
80
|
}
|
|
192
81
|
}
|
|
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
82
|
addRegistration(token, factory, lifetime) {
|
|
223
83
|
this.registrations.set(token, { factory, lifetime });
|
|
224
84
|
return this;
|
|
@@ -226,7 +86,6 @@ class Container {
|
|
|
226
86
|
}
|
|
227
87
|
export {
|
|
228
88
|
createContainer,
|
|
229
|
-
Scope,
|
|
230
89
|
ContainerError,
|
|
231
90
|
Container
|
|
232
91
|
};
|
|
@@ -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,18 @@
|
|
|
1
|
+
import type { Container } from '../container';
|
|
2
|
+
import { type ContainerInternals, INTERNALS } from '../internal';
|
|
1
3
|
import type { AbstractConstructor, Registration } from '../resolver';
|
|
4
|
+
/**
|
|
5
|
+
* Create a new scope (child container) from a Container or an existing Scope.
|
|
6
|
+
*
|
|
7
|
+
* The scope inherits all registrations from the source.
|
|
8
|
+
* Singleton instances are shared with the parent, while scoped instances are local to the scope.
|
|
9
|
+
*
|
|
10
|
+
* @param source A Container or Scope to create a child scope from
|
|
11
|
+
* @returns A new Scope instance
|
|
12
|
+
* @throws ContainerError if the source has been disposed
|
|
13
|
+
*/
|
|
14
|
+
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>;
|
|
15
|
+
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>;
|
|
2
16
|
/**
|
|
3
17
|
* Scoped child container.
|
|
4
18
|
*
|
|
@@ -13,12 +27,18 @@ import type { AbstractConstructor, Registration } from '../resolver';
|
|
|
13
27
|
* @template ScopedSync Union of scoped sync class constructors
|
|
14
28
|
* @template ScopedAsync Union of scoped async class constructors
|
|
15
29
|
*/
|
|
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>
|
|
30
|
+
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
31
|
private readonly registrations;
|
|
18
32
|
private readonly singletonInstances;
|
|
19
33
|
private readonly scopedInstances;
|
|
20
34
|
private readonly resolvingTokens;
|
|
21
35
|
private disposed;
|
|
36
|
+
/**
|
|
37
|
+
* Internal state accessor for extension modules (scope, disposable).
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
readonly [INTERNALS]: ContainerInternals;
|
|
22
42
|
constructor(registrations: Map<unknown, Registration>, singletonInstances: Map<unknown, unknown>);
|
|
23
43
|
/**
|
|
24
44
|
* Resolve an instance for the given token.
|
|
@@ -56,26 +76,4 @@ export declare class Scope<T = Record<never, never>, Sync extends AbstractConstr
|
|
|
56
76
|
* @returns The resolved instance, or undefined if not registered and required is false
|
|
57
77
|
*/
|
|
58
78
|
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
79
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ContainerError,
|
|
3
|
+
buildCircularPath,
|
|
4
|
+
tokenToString
|
|
5
|
+
} from "../index-g50fxds1.js";
|
|
6
|
+
import {
|
|
7
|
+
INTERNALS
|
|
8
|
+
} from "../index-jx8b52m0.js";
|
|
9
|
+
|
|
10
|
+
// src/scope/index.ts
|
|
11
|
+
function createScope(source) {
|
|
12
|
+
const internals = source[INTERNALS];
|
|
13
|
+
if (internals.isDisposed()) {
|
|
14
|
+
throw new ContainerError("Cannot create a scope from a disposed container.");
|
|
15
|
+
}
|
|
16
|
+
return new Scope(internals.registrations, internals.instances);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class Scope {
|
|
20
|
+
registrations;
|
|
21
|
+
singletonInstances;
|
|
22
|
+
scopedInstances;
|
|
23
|
+
resolvingTokens;
|
|
24
|
+
disposed = false;
|
|
25
|
+
[INTERNALS];
|
|
26
|
+
constructor(registrations, singletonInstances) {
|
|
27
|
+
this.registrations = registrations;
|
|
28
|
+
this.singletonInstances = singletonInstances;
|
|
29
|
+
this.scopedInstances = new Map;
|
|
30
|
+
this.resolvingTokens = new Set;
|
|
31
|
+
this[INTERNALS] = {
|
|
32
|
+
instances: this.singletonInstances,
|
|
33
|
+
isDisposed: () => this.disposed,
|
|
34
|
+
markDisposed: () => {
|
|
35
|
+
this.disposed = true;
|
|
36
|
+
},
|
|
37
|
+
ownInstances: this.scopedInstances,
|
|
38
|
+
registrations: this.registrations
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
resolve(token) {
|
|
42
|
+
return this.resolveToken(token, true);
|
|
43
|
+
}
|
|
44
|
+
tryResolve(token) {
|
|
45
|
+
return this.resolveToken(token, false);
|
|
46
|
+
}
|
|
47
|
+
resolveToken(token, required) {
|
|
48
|
+
if (this.disposed) {
|
|
49
|
+
throw new ContainerError("Cannot resolve from a disposed scope.");
|
|
50
|
+
}
|
|
51
|
+
const singletonCached = this.singletonInstances.get(token);
|
|
52
|
+
if (singletonCached !== undefined) {
|
|
53
|
+
return singletonCached;
|
|
54
|
+
}
|
|
55
|
+
const scopedCached = this.scopedInstances.get(token);
|
|
56
|
+
if (scopedCached !== undefined) {
|
|
57
|
+
return scopedCached;
|
|
58
|
+
}
|
|
59
|
+
const registration = this.registrations.get(token);
|
|
60
|
+
if (registration === undefined) {
|
|
61
|
+
if (required) {
|
|
62
|
+
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.resolvingTokens.has(token)) {
|
|
67
|
+
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
68
|
+
}
|
|
69
|
+
this.resolvingTokens.add(token);
|
|
70
|
+
try {
|
|
71
|
+
const instance = registration.factory(this);
|
|
72
|
+
if (registration.lifetime === "singleton") {
|
|
73
|
+
this.singletonInstances.set(token, instance);
|
|
74
|
+
} else if (registration.lifetime === "scoped") {
|
|
75
|
+
this.scopedInstances.set(token, instance);
|
|
76
|
+
}
|
|
77
|
+
return instance;
|
|
78
|
+
} finally {
|
|
79
|
+
this.resolvingTokens.delete(token);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
createScope,
|
|
85
|
+
Scope
|
|
86
|
+
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "katagami",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Lightweight DI container for TypeScript and JavaScript — full type inference, no decorators, no reflect-metadata, hybrid class & PropertyKey tokens.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
9
10
|
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.js",
|
|
11
12
|
"require": "./dist/index.cjs"
|
|
13
|
+
},
|
|
14
|
+
"./scope": {
|
|
15
|
+
"types": "./dist/scope/index.d.ts",
|
|
16
|
+
"import": "./dist/scope/index.js",
|
|
17
|
+
"require": "./dist/scope/index.cjs"
|
|
18
|
+
},
|
|
19
|
+
"./disposable": {
|
|
20
|
+
"types": "./dist/disposable/index.d.ts",
|
|
21
|
+
"import": "./dist/disposable/index.js",
|
|
22
|
+
"require": "./dist/disposable/index.cjs"
|
|
12
23
|
}
|
|
13
24
|
},
|
|
14
25
|
"main": "./dist/index.cjs",
|
|
@@ -34,8 +45,8 @@
|
|
|
34
45
|
"clean": "rm -rf dist",
|
|
35
46
|
"build": "bun run clean && bun run build:types && bun run build:esm && bun run build:cjs",
|
|
36
47
|
"build:types": "tsc -p tsconfig.build.json",
|
|
37
|
-
"build:esm": "bun build ./src/index.ts --outdir dist --format esm",
|
|
38
|
-
"build:cjs": "bun build ./src/index.ts --outfile dist/index.cjs --format cjs",
|
|
48
|
+
"build:esm": "bun build ./src/index.ts ./src/scope/index.ts ./src/disposable/index.ts --outdir dist --format esm --splitting",
|
|
49
|
+
"build:cjs": "bun build ./src/index.ts --outfile dist/index.cjs --format cjs && bun build ./src/scope/index.ts --outfile dist/scope/index.cjs --format cjs && bun build ./src/disposable/index.ts --outfile dist/disposable/index.cjs --format cjs",
|
|
39
50
|
"test": "bun test --coverage --dots",
|
|
40
51
|
"prepublishOnly": "bun run build",
|
|
41
52
|
"check": "bun run format",
|