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/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[English](./README.md) | [日本語](./README.ja.md) | [한국어](./README.ko.md) | [繁體中文](./README.zh-TW.md) | [简体中文](./README.zh-CN.md) | [Español](./README.es.md) | [Deutsch](./README.de.md) | [Français](./README.fr.md)
|
|
1
|
+
[English](./README.md) | [日本語](./docs/README.ja.md) | [한국어](./docs/README.ko.md) | [繁體中文](./docs/README.zh-TW.md) | [简体中文](./docs/README.zh-CN.md) | [Español](./docs/README.es.md) | [Deutsch](./docs/README.de.md) | [Français](./docs/README.fr.md)
|
|
2
2
|
|
|
3
3
|
# Katagami
|
|
4
4
|
|
|
@@ -112,10 +112,11 @@ container.resolve(RequestHandler) === container.resolve(RequestHandler); // fals
|
|
|
112
112
|
|
|
113
113
|
### Scoped Lifetime & Child Containers
|
|
114
114
|
|
|
115
|
-
Scoped registrations behave like singletons within a scope but produce a fresh instance in each new scope.
|
|
115
|
+
Scoped registrations behave like singletons within a scope but produce a fresh instance in each new scope. Import `createScope` from `katagami/scope` to create a child container. Scoped tokens cannot be resolved from the root container.
|
|
116
116
|
|
|
117
117
|
```ts
|
|
118
118
|
import { createContainer } from 'katagami';
|
|
119
|
+
import { createScope } from 'katagami/scope';
|
|
119
120
|
|
|
120
121
|
class DbPool {
|
|
121
122
|
constructor(public name = 'main') {}
|
|
@@ -130,8 +131,8 @@ const root = createContainer()
|
|
|
130
131
|
.registerScoped(RequestContext, () => new RequestContext());
|
|
131
132
|
|
|
132
133
|
// Create a scope for each request
|
|
133
|
-
const scope1 =
|
|
134
|
-
const scope2 =
|
|
134
|
+
const scope1 = createScope(root);
|
|
135
|
+
const scope2 = createScope(root);
|
|
135
136
|
|
|
136
137
|
// Scoped — same within a scope, different across scopes
|
|
137
138
|
scope1.resolve(RequestContext) === scope1.resolve(RequestContext); // true
|
|
@@ -144,8 +145,8 @@ scope1.resolve(DbPool) === scope2.resolve(DbPool); // true
|
|
|
144
145
|
Scopes can also be nested. Each nested scope has its own scoped instance cache while sharing singletons with its parent:
|
|
145
146
|
|
|
146
147
|
```ts
|
|
147
|
-
const parentScope =
|
|
148
|
-
const childScope =
|
|
148
|
+
const parentScope = createScope(root);
|
|
149
|
+
const childScope = createScope(parentScope);
|
|
149
150
|
|
|
150
151
|
// Each nested scope gets its own scoped instances
|
|
151
152
|
parentScope.resolve(RequestContext) === childScope.resolve(RequestContext); // false
|
|
@@ -229,10 +230,11 @@ ContainerError: Circular dependency detected: ServiceX -> ServiceY -> ServiceZ -
|
|
|
229
230
|
|
|
230
231
|
### Disposable Support
|
|
231
232
|
|
|
232
|
-
|
|
233
|
+
Disposal is provided by the `disposable()` wrapper from `katagami/disposable`. Wrapping a container or scope attaches `[Symbol.asyncDispose]`, enabling `await using` syntax. When disposed, owned instances are iterated in reverse creation order (LIFO) and their `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` methods are called automatically.
|
|
233
234
|
|
|
234
235
|
```ts
|
|
235
236
|
import { createContainer } from 'katagami';
|
|
237
|
+
import { disposable } from 'katagami/disposable';
|
|
236
238
|
|
|
237
239
|
class Connection {
|
|
238
240
|
async [Symbol.asyncDispose]() {
|
|
@@ -241,7 +243,9 @@ class Connection {
|
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
// Manual disposal
|
|
244
|
-
const container =
|
|
246
|
+
const container = disposable(
|
|
247
|
+
createContainer().registerSingleton(Connection, () => new Connection()),
|
|
248
|
+
);
|
|
245
249
|
|
|
246
250
|
container.resolve(Connection);
|
|
247
251
|
await container[Symbol.asyncDispose]();
|
|
@@ -251,12 +255,15 @@ await container[Symbol.asyncDispose]();
|
|
|
251
255
|
With `await using`, scopes are automatically disposed at the end of the block:
|
|
252
256
|
|
|
253
257
|
```ts
|
|
258
|
+
import { createScope } from 'katagami/scope';
|
|
259
|
+
import { disposable } from 'katagami/disposable';
|
|
260
|
+
|
|
254
261
|
const root = createContainer()
|
|
255
262
|
.registerSingleton(DbPool, () => new DbPool())
|
|
256
263
|
.registerScoped(Connection, () => new Connection());
|
|
257
264
|
|
|
258
265
|
{
|
|
259
|
-
await using scope =
|
|
266
|
+
await using scope = disposable(createScope(root));
|
|
260
267
|
const conn = scope.resolve(Connection);
|
|
261
268
|
// ... use conn ...
|
|
262
269
|
} // scope is disposed here — Connection is cleaned up, DbPool is not
|
|
@@ -413,17 +420,17 @@ Resolves and returns the instance for the given token. Throws `ContainerError` i
|
|
|
413
420
|
|
|
414
421
|
Attempts to resolve the instance for the given token. Returns `undefined` if the token is not registered, instead of throwing. Still throws `ContainerError` for circular dependencies or operations on disposed containers/scopes.
|
|
415
422
|
|
|
416
|
-
### `
|
|
423
|
+
### `createScope(source)` — `katagami/scope`
|
|
417
424
|
|
|
418
|
-
Creates a new `Scope` (child container)
|
|
425
|
+
Creates a new `Scope` (child container) from a `Container` or an existing `Scope`. The scope inherits all registrations from the source. Singleton instances are shared with the parent, while scoped instances are local to the scope.
|
|
419
426
|
|
|
420
427
|
### `Scope`
|
|
421
428
|
|
|
422
|
-
A scoped child container created by `createScope()`. Provides `resolve(token)
|
|
429
|
+
A scoped child container created by `createScope()`. Provides `resolve(token)` and `tryResolve(token)`.
|
|
423
430
|
|
|
424
|
-
### `container
|
|
431
|
+
### `disposable(container)` — `katagami/disposable`
|
|
425
432
|
|
|
426
|
-
Disposes all
|
|
433
|
+
Attaches `[Symbol.asyncDispose]` to a `Container` or `Scope`, enabling `await using` syntax. Disposes all owned instances in reverse creation order (LIFO). Calls `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them. Idempotent — subsequent calls are no-ops. After disposal, `resolve()` and `createScope()` will throw `ContainerError`.
|
|
427
434
|
|
|
428
435
|
### `ContainerError`
|
|
429
436
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { type ContainerInternals, INTERNALS } from '../internal';
|
|
1
2
|
import type { AbstractConstructor, Resolver } from '../resolver';
|
|
2
|
-
import { Scope } from '../scope';
|
|
3
3
|
/**
|
|
4
4
|
* Create a new DI container.
|
|
5
5
|
*
|
|
@@ -30,11 +30,17 @@ export declare function createContainer<T = Record<never, never>, ScopedT = Reco
|
|
|
30
30
|
* @template ScopedSync Union of scoped sync class constructors (accumulated via chaining, order-dependent)
|
|
31
31
|
* @template ScopedAsync Union of scoped async class constructors (accumulated via chaining, order-dependent)
|
|
32
32
|
*/
|
|
33
|
-
export declare class Container<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 Container<T = Record<never, never>, Sync extends AbstractConstructor = never, Async extends AbstractConstructor = never, ScopedT = Record<never, never>, ScopedSync extends AbstractConstructor = never, ScopedAsync extends AbstractConstructor = never> {
|
|
34
34
|
private readonly registrations;
|
|
35
35
|
private readonly instances;
|
|
36
36
|
private readonly resolvingTokens;
|
|
37
37
|
private disposed;
|
|
38
|
+
/**
|
|
39
|
+
* Internal state accessor for extension modules (scope, disposable).
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
readonly [INTERNALS]: ContainerInternals;
|
|
38
44
|
constructor();
|
|
39
45
|
/**
|
|
40
46
|
* Register a factory function as a singleton for the given token.
|
|
@@ -77,16 +83,6 @@ export declare class Container<T = Record<never, never>, Sync extends AbstractCo
|
|
|
77
83
|
registerScoped<V>(token: AbstractConstructor<V>, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V): Container<T, Sync, Async, ScopedT, ScopedSync | AbstractConstructor<V>, ScopedAsync>;
|
|
78
84
|
registerScoped<K extends PropertyKey, V>(token: K, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V): Container<T, Sync, Async, Record<K, V> & ScopedT, ScopedSync, ScopedAsync>;
|
|
79
85
|
registerScoped<V>(token: unknown, factory: (resolver: Resolver<T & ScopedT, Sync | ScopedSync, Async | ScopedAsync>) => V): Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
80
|
-
/**
|
|
81
|
-
* Create a new scope (child container).
|
|
82
|
-
*
|
|
83
|
-
* The scope inherits all registrations from this container.
|
|
84
|
-
* Singleton instances are shared with the parent, while scoped instances are local to the scope.
|
|
85
|
-
*
|
|
86
|
-
* @returns A new Scope instance
|
|
87
|
-
* @throws ContainerError if the container has been disposed
|
|
88
|
-
*/
|
|
89
|
-
createScope(): Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>;
|
|
90
86
|
/**
|
|
91
87
|
* Resolve an instance for the given token.
|
|
92
88
|
*
|
|
@@ -122,18 +118,6 @@ export declare class Container<T = Record<never, never>, Sync extends AbstractCo
|
|
|
122
118
|
* @returns The resolved instance, or undefined if not registered and required is false
|
|
123
119
|
*/
|
|
124
120
|
private resolveToken;
|
|
125
|
-
/**
|
|
126
|
-
* Dispose all singleton instances managed by this container.
|
|
127
|
-
*
|
|
128
|
-
* Iterates through singleton instances in reverse creation order (LIFO) and calls
|
|
129
|
-
* `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them.
|
|
130
|
-
*
|
|
131
|
-
* This method is idempotent — subsequent calls after the first are no-ops.
|
|
132
|
-
* After disposal, `resolve()` and `createScope()` will throw `ContainerError`.
|
|
133
|
-
*
|
|
134
|
-
* @throws AggregateError if one or more instances throw during disposal
|
|
135
|
-
*/
|
|
136
|
-
[Symbol.asyncDispose](): Promise<void>;
|
|
137
121
|
/**
|
|
138
122
|
* Add a registration entry.
|
|
139
123
|
*
|
|
@@ -0,0 +1,76 @@
|
|
|
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/disposable/index.ts
|
|
30
|
+
var exports_disposable = {};
|
|
31
|
+
__export(exports_disposable, {
|
|
32
|
+
disposable: () => disposable
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(exports_disposable);
|
|
35
|
+
|
|
36
|
+
// src/internal.ts
|
|
37
|
+
var INTERNALS = Symbol("katagami.internals");
|
|
38
|
+
|
|
39
|
+
// src/disposable/index.ts
|
|
40
|
+
function disposable(container) {
|
|
41
|
+
const asyncDispose = async () => {
|
|
42
|
+
const internals = container[INTERNALS];
|
|
43
|
+
if (internals.isDisposed()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
internals.markDisposed();
|
|
47
|
+
const instances = [...internals.ownInstances.values()].reverse();
|
|
48
|
+
const errors = [];
|
|
49
|
+
for (const instance of instances) {
|
|
50
|
+
try {
|
|
51
|
+
let resolved = instance;
|
|
52
|
+
if (instance instanceof Promise) {
|
|
53
|
+
resolved = await instance;
|
|
54
|
+
}
|
|
55
|
+
if (resolved != null && typeof resolved === "object") {
|
|
56
|
+
if (Symbol.asyncDispose in resolved) {
|
|
57
|
+
await resolved[Symbol.asyncDispose]();
|
|
58
|
+
} else if (Symbol.dispose in resolved) {
|
|
59
|
+
resolved[Symbol.dispose]();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
errors.push(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
internals.ownInstances.clear();
|
|
67
|
+
if (errors.length > 0) {
|
|
68
|
+
throw new AggregateError(errors, "One or more errors occurred during disposal.");
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
Object.defineProperty(container, Symbol.asyncDispose, {
|
|
72
|
+
configurable: true,
|
|
73
|
+
value: asyncDispose
|
|
74
|
+
});
|
|
75
|
+
return container;
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ContainerInternals, INTERNALS } from '../internal';
|
|
2
|
+
/**
|
|
3
|
+
* Add async disposal capability to a container or scope.
|
|
4
|
+
*
|
|
5
|
+
* Enables `await using` syntax by attaching `[Symbol.asyncDispose]` to the target.
|
|
6
|
+
* Disposes owned instances in reverse creation order (LIFO), calling
|
|
7
|
+
* `[Symbol.asyncDispose]()` or `[Symbol.dispose]()` on each instance that implements them.
|
|
8
|
+
*
|
|
9
|
+
* @param container A Container or Scope to make disposable
|
|
10
|
+
* @returns The same object with `AsyncDisposable` capability added
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createContainer } from 'katagami';
|
|
15
|
+
* import { disposable } from 'katagami/disposable';
|
|
16
|
+
*
|
|
17
|
+
* await using container = disposable(
|
|
18
|
+
* createContainer().registerSingleton(DB, () => new Database())
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function disposable<C extends {
|
|
23
|
+
readonly [INTERNALS]: ContainerInternals;
|
|
24
|
+
}>(container: C): C & AsyncDisposable;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INTERNALS
|
|
3
|
+
} from "../index-jx8b52m0.js";
|
|
4
|
+
|
|
5
|
+
// src/disposable/index.ts
|
|
6
|
+
function disposable(container) {
|
|
7
|
+
const asyncDispose = async () => {
|
|
8
|
+
const internals = container[INTERNALS];
|
|
9
|
+
if (internals.isDisposed()) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
internals.markDisposed();
|
|
13
|
+
const instances = [...internals.ownInstances.values()].reverse();
|
|
14
|
+
const errors = [];
|
|
15
|
+
for (const instance of instances) {
|
|
16
|
+
try {
|
|
17
|
+
let resolved = instance;
|
|
18
|
+
if (instance instanceof Promise) {
|
|
19
|
+
resolved = await instance;
|
|
20
|
+
}
|
|
21
|
+
if (resolved != null && typeof resolved === "object") {
|
|
22
|
+
if (Symbol.asyncDispose in resolved) {
|
|
23
|
+
await resolved[Symbol.asyncDispose]();
|
|
24
|
+
} else if (Symbol.dispose in resolved) {
|
|
25
|
+
resolved[Symbol.dispose]();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {
|
|
29
|
+
errors.push(error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
internals.ownInstances.clear();
|
|
33
|
+
if (errors.length > 0) {
|
|
34
|
+
throw new AggregateError(errors, "One or more errors occurred during disposal.");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(container, Symbol.asyncDispose, {
|
|
38
|
+
configurable: true,
|
|
39
|
+
value: asyncDispose
|
|
40
|
+
});
|
|
41
|
+
return container;
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
disposable
|
|
45
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/error/index.ts
|
|
2
|
+
class ContainerError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ContainerError";
|
|
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
|
+
export { ContainerError, tokenToString, buildCircularPath };
|
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,12 +105,6 @@ class Container {
|
|
|
184
105
|
registerScoped(token, factory) {
|
|
185
106
|
return this.addRegistration(token, factory, "scoped");
|
|
186
107
|
}
|
|
187
|
-
createScope() {
|
|
188
|
-
if (this.disposed) {
|
|
189
|
-
throw new ContainerError("Cannot create a scope from a disposed container.");
|
|
190
|
-
}
|
|
191
|
-
return new Scope(this.registrations, this.instances);
|
|
192
|
-
}
|
|
193
108
|
resolve(token) {
|
|
194
109
|
return this.resolveToken(token, true);
|
|
195
110
|
}
|
|
@@ -228,35 +143,6 @@ class Container {
|
|
|
228
143
|
this.resolvingTokens.delete(token);
|
|
229
144
|
}
|
|
230
145
|
}
|
|
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
146
|
addRegistration(token, factory, lifetime) {
|
|
261
147
|
this.registrations.set(token, { factory, lifetime });
|
|
262
148
|
return this;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { Container, createContainer } from './container';
|
|
2
|
+
export type { 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';
|