katagami 2.0.0 → 2.2.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 +168 -26
- package/dist/container/index.d.ts +57 -2
- package/dist/disposable/index.cjs +2 -2
- package/dist/disposable/index.d.ts +41 -4
- package/dist/disposable/index.js +2 -2
- package/dist/index.cjs +66 -13
- package/dist/index.d.ts +2 -1
- package/dist/index.js +66 -13
- package/dist/internal.d.ts +6 -6
- package/dist/lazy/index.cjs +88 -0
- package/dist/lazy/index.d.ts +35 -0
- package/dist/lazy/index.js +58 -0
- package/dist/resolver/index.d.ts +25 -0
- package/dist/scope/index.cjs +65 -20
- package/dist/scope/index.d.ts +45 -5
- package/dist/scope/index.js +65 -20
- package/package.json +9 -5
package/dist/index.js
CHANGED
|
@@ -14,22 +14,22 @@ function createContainer() {
|
|
|
14
14
|
|
|
15
15
|
class Container {
|
|
16
16
|
registrations;
|
|
17
|
-
|
|
17
|
+
singletonCache;
|
|
18
18
|
resolvingTokens;
|
|
19
19
|
disposed = false;
|
|
20
20
|
[INTERNALS];
|
|
21
21
|
constructor() {
|
|
22
22
|
this.registrations = new Map;
|
|
23
|
-
this.
|
|
23
|
+
this.singletonCache = new Map;
|
|
24
24
|
this.resolvingTokens = new Set;
|
|
25
25
|
this[INTERNALS] = {
|
|
26
|
-
instances: this.instances,
|
|
27
26
|
isDisposed: () => this.disposed,
|
|
28
27
|
markDisposed: () => {
|
|
29
28
|
this.disposed = true;
|
|
30
29
|
},
|
|
31
|
-
|
|
32
|
-
registrations: this.registrations
|
|
30
|
+
ownCache: this.singletonCache,
|
|
31
|
+
registrations: this.registrations,
|
|
32
|
+
singletonCache: this.singletonCache
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
registerSingleton(token, factory) {
|
|
@@ -41,27 +41,40 @@ class Container {
|
|
|
41
41
|
registerScoped(token, factory) {
|
|
42
42
|
return this.addRegistration(token, factory, "scoped");
|
|
43
43
|
}
|
|
44
|
+
use(source) {
|
|
45
|
+
for (const [token, registrations] of source[INTERNALS].registrations) {
|
|
46
|
+
this.registrations.set(token, [...registrations]);
|
|
47
|
+
}
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
44
50
|
resolve(token) {
|
|
45
51
|
return this.resolveToken(token, true);
|
|
46
52
|
}
|
|
47
53
|
tryResolve(token) {
|
|
48
54
|
return this.resolveToken(token, false);
|
|
49
55
|
}
|
|
56
|
+
resolveAll(token) {
|
|
57
|
+
return this.resolveAllTokens(token, true);
|
|
58
|
+
}
|
|
59
|
+
tryResolveAll(token) {
|
|
60
|
+
return this.resolveAllTokens(token, false);
|
|
61
|
+
}
|
|
50
62
|
resolveToken(token, required) {
|
|
51
63
|
if (this.disposed) {
|
|
52
64
|
throw new ContainerError("Cannot resolve from a disposed container.");
|
|
53
65
|
}
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
return cached;
|
|
57
|
-
}
|
|
58
|
-
const registration = this.registrations.get(token);
|
|
59
|
-
if (registration === undefined) {
|
|
66
|
+
const registrations = this.registrations.get(token);
|
|
67
|
+
if (registrations === undefined || registrations.length === 0) {
|
|
60
68
|
if (required) {
|
|
61
69
|
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
62
70
|
}
|
|
63
71
|
return;
|
|
64
72
|
}
|
|
73
|
+
const registration = registrations[registrations.length - 1];
|
|
74
|
+
const cached = this.singletonCache.get(registration);
|
|
75
|
+
if (cached !== undefined) {
|
|
76
|
+
return cached;
|
|
77
|
+
}
|
|
65
78
|
if (registration.lifetime === "scoped") {
|
|
66
79
|
throw new ContainerError(`Cannot resolve scoped token "${tokenToString(token)}" from the root container. Use createScope() to create a scope first.`);
|
|
67
80
|
}
|
|
@@ -72,15 +85,55 @@ class Container {
|
|
|
72
85
|
try {
|
|
73
86
|
const instance = registration.factory(this);
|
|
74
87
|
if (registration.lifetime === "singleton") {
|
|
75
|
-
this.
|
|
88
|
+
this.singletonCache.set(registration, instance);
|
|
76
89
|
}
|
|
77
90
|
return instance;
|
|
78
91
|
} finally {
|
|
79
92
|
this.resolvingTokens.delete(token);
|
|
80
93
|
}
|
|
81
94
|
}
|
|
95
|
+
resolveAllTokens(token, required) {
|
|
96
|
+
if (this.disposed) {
|
|
97
|
+
throw new ContainerError("Cannot resolve from a disposed container.");
|
|
98
|
+
}
|
|
99
|
+
const registrations = this.registrations.get(token);
|
|
100
|
+
if (registrations === undefined || registrations.length === 0) {
|
|
101
|
+
if (required) {
|
|
102
|
+
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (this.resolvingTokens.has(token)) {
|
|
107
|
+
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
108
|
+
}
|
|
109
|
+
this.resolvingTokens.add(token);
|
|
110
|
+
try {
|
|
111
|
+
return registrations.map((registration) => {
|
|
112
|
+
const reg = registration;
|
|
113
|
+
const cached = this.singletonCache.get(registration);
|
|
114
|
+
if (cached !== undefined) {
|
|
115
|
+
return cached;
|
|
116
|
+
}
|
|
117
|
+
if (reg.lifetime === "scoped") {
|
|
118
|
+
throw new ContainerError(`Cannot resolve scoped token "${tokenToString(token)}" from the root container. Use createScope() to create a scope first.`);
|
|
119
|
+
}
|
|
120
|
+
const instance = reg.factory(this);
|
|
121
|
+
if (reg.lifetime === "singleton") {
|
|
122
|
+
this.singletonCache.set(registration, instance);
|
|
123
|
+
}
|
|
124
|
+
return instance;
|
|
125
|
+
});
|
|
126
|
+
} finally {
|
|
127
|
+
this.resolvingTokens.delete(token);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
82
130
|
addRegistration(token, factory, lifetime) {
|
|
83
|
-
this.registrations.
|
|
131
|
+
const existing = this.registrations.get(token);
|
|
132
|
+
if (existing !== undefined) {
|
|
133
|
+
existing.push({ factory, lifetime });
|
|
134
|
+
} else {
|
|
135
|
+
this.registrations.set(token, [{ factory, lifetime }]);
|
|
136
|
+
}
|
|
84
137
|
return this;
|
|
85
138
|
}
|
|
86
139
|
}
|
package/dist/internal.d.ts
CHANGED
|
@@ -14,12 +14,12 @@ export declare const INTERNALS: unique symbol;
|
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
16
16
|
export interface ContainerInternals {
|
|
17
|
-
/** All registrations (singleton / transient / scoped). */
|
|
18
|
-
readonly registrations: Map<unknown, Registration>;
|
|
19
|
-
/**
|
|
20
|
-
readonly
|
|
21
|
-
/** Instances owned by this container / scope (disposal target). */
|
|
22
|
-
readonly
|
|
17
|
+
/** All registrations (singleton / transient / scoped). Each token maps to an array of registrations. */
|
|
18
|
+
readonly registrations: Map<unknown, Registration[]>;
|
|
19
|
+
/** Singleton cache keyed by Registration object (Container: singletons, Scope: shared with parent). */
|
|
20
|
+
readonly singletonCache: Map<Registration, unknown>;
|
|
21
|
+
/** Instances owned by this container / scope (disposal target), keyed by Registration object. */
|
|
22
|
+
readonly ownCache: Map<Registration, unknown>;
|
|
23
23
|
/** Whether this container / scope has been disposed. */
|
|
24
24
|
isDisposed(): boolean;
|
|
25
25
|
/** Mark this container / scope as disposed. */
|
|
@@ -0,0 +1,88 @@
|
|
|
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/lazy/index.ts
|
|
30
|
+
var exports_lazy = {};
|
|
31
|
+
__export(exports_lazy, {
|
|
32
|
+
lazy: () => lazy
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(exports_lazy);
|
|
35
|
+
function lazy(source, token) {
|
|
36
|
+
let instance;
|
|
37
|
+
let resolved = false;
|
|
38
|
+
const ensureResolved = () => {
|
|
39
|
+
if (!resolved) {
|
|
40
|
+
instance = source.resolve(token);
|
|
41
|
+
resolved = true;
|
|
42
|
+
}
|
|
43
|
+
return instance;
|
|
44
|
+
};
|
|
45
|
+
const proxyTarget = Object.create(null);
|
|
46
|
+
return new Proxy(proxyTarget, {
|
|
47
|
+
defineProperty(_, prop, desc) {
|
|
48
|
+
return Reflect.defineProperty(ensureResolved(), prop, desc);
|
|
49
|
+
},
|
|
50
|
+
deleteProperty(_, prop) {
|
|
51
|
+
return Reflect.deleteProperty(ensureResolved(), prop);
|
|
52
|
+
},
|
|
53
|
+
get(_, prop) {
|
|
54
|
+
const target = ensureResolved();
|
|
55
|
+
const value = Reflect.get(target, prop, target);
|
|
56
|
+
if (typeof value === "function") {
|
|
57
|
+
return value.bind(target);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
},
|
|
61
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
62
|
+
return Reflect.getOwnPropertyDescriptor(ensureResolved(), prop);
|
|
63
|
+
},
|
|
64
|
+
getPrototypeOf() {
|
|
65
|
+
return Reflect.getPrototypeOf(ensureResolved());
|
|
66
|
+
},
|
|
67
|
+
has(_, prop) {
|
|
68
|
+
return Reflect.has(ensureResolved(), prop);
|
|
69
|
+
},
|
|
70
|
+
isExtensible() {
|
|
71
|
+
return Reflect.isExtensible(ensureResolved());
|
|
72
|
+
},
|
|
73
|
+
ownKeys() {
|
|
74
|
+
return Reflect.ownKeys(ensureResolved());
|
|
75
|
+
},
|
|
76
|
+
preventExtensions() {
|
|
77
|
+
Reflect.preventExtensions(ensureResolved());
|
|
78
|
+
Reflect.preventExtensions(proxyTarget);
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
set(_, prop, value) {
|
|
82
|
+
return Reflect.set(ensureResolved(), prop, value, ensureResolved());
|
|
83
|
+
},
|
|
84
|
+
setPrototypeOf(_, proto) {
|
|
85
|
+
return Reflect.setPrototypeOf(ensureResolved(), proto);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Container } from '../container';
|
|
2
|
+
import type { DisposableContainer, DisposableScope } from '../disposable';
|
|
3
|
+
import type { AbstractConstructor } from '../resolver';
|
|
4
|
+
import type { Scope } from '../scope';
|
|
5
|
+
/**
|
|
6
|
+
* Create a lazy proxy that defers resolution until the first property access.
|
|
7
|
+
*
|
|
8
|
+
* The returned object looks and behaves like `V`, but the underlying instance
|
|
9
|
+
* is not created until a property is read, written, or otherwise accessed.
|
|
10
|
+
* Once resolved the instance is cached — subsequent accesses hit the cache.
|
|
11
|
+
*
|
|
12
|
+
* Only **sync class tokens** are supported. Async tokens and PropertyKey tokens
|
|
13
|
+
* are rejected at the type level.
|
|
14
|
+
*
|
|
15
|
+
* @param source A Container, Scope, DisposableContainer, or DisposableScope
|
|
16
|
+
* @param token A sync class constructor token
|
|
17
|
+
* @returns A proxy that transparently forwards to the lazily-resolved instance
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { createContainer } from 'katagami';
|
|
22
|
+
* import { lazy } from 'katagami/lazy';
|
|
23
|
+
*
|
|
24
|
+
* const container = createContainer()
|
|
25
|
+
* .registerSingleton(HeavyService, () => new HeavyService());
|
|
26
|
+
*
|
|
27
|
+
* const service = lazy(container, HeavyService);
|
|
28
|
+
* // Instance is NOT created yet
|
|
29
|
+
* service.doSomething(); // resolved here, then cached
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function lazy<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor, V>(source: Container<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>, token: AbstractConstructor<V> & Sync): V;
|
|
33
|
+
export declare function lazy<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor, V>(source: Scope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>, token: AbstractConstructor<V> & (Sync | ScopedSync)): V;
|
|
34
|
+
export declare function lazy<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor, V>(source: DisposableContainer<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>, token: AbstractConstructor<V> & Sync): V;
|
|
35
|
+
export declare function lazy<T, Sync extends AbstractConstructor, Async extends AbstractConstructor, ScopedT, ScopedSync extends AbstractConstructor, ScopedAsync extends AbstractConstructor, V>(source: DisposableScope<T, Sync, Async, ScopedT, ScopedSync, ScopedAsync>, token: AbstractConstructor<V> & (Sync | ScopedSync)): V;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/lazy/index.ts
|
|
2
|
+
function lazy(source, token) {
|
|
3
|
+
let instance;
|
|
4
|
+
let resolved = false;
|
|
5
|
+
const ensureResolved = () => {
|
|
6
|
+
if (!resolved) {
|
|
7
|
+
instance = source.resolve(token);
|
|
8
|
+
resolved = true;
|
|
9
|
+
}
|
|
10
|
+
return instance;
|
|
11
|
+
};
|
|
12
|
+
const proxyTarget = Object.create(null);
|
|
13
|
+
return new Proxy(proxyTarget, {
|
|
14
|
+
defineProperty(_, prop, desc) {
|
|
15
|
+
return Reflect.defineProperty(ensureResolved(), prop, desc);
|
|
16
|
+
},
|
|
17
|
+
deleteProperty(_, prop) {
|
|
18
|
+
return Reflect.deleteProperty(ensureResolved(), prop);
|
|
19
|
+
},
|
|
20
|
+
get(_, prop) {
|
|
21
|
+
const target = ensureResolved();
|
|
22
|
+
const value = Reflect.get(target, prop, target);
|
|
23
|
+
if (typeof value === "function") {
|
|
24
|
+
return value.bind(target);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
},
|
|
28
|
+
getOwnPropertyDescriptor(_, prop) {
|
|
29
|
+
return Reflect.getOwnPropertyDescriptor(ensureResolved(), prop);
|
|
30
|
+
},
|
|
31
|
+
getPrototypeOf() {
|
|
32
|
+
return Reflect.getPrototypeOf(ensureResolved());
|
|
33
|
+
},
|
|
34
|
+
has(_, prop) {
|
|
35
|
+
return Reflect.has(ensureResolved(), prop);
|
|
36
|
+
},
|
|
37
|
+
isExtensible() {
|
|
38
|
+
return Reflect.isExtensible(ensureResolved());
|
|
39
|
+
},
|
|
40
|
+
ownKeys() {
|
|
41
|
+
return Reflect.ownKeys(ensureResolved());
|
|
42
|
+
},
|
|
43
|
+
preventExtensions() {
|
|
44
|
+
Reflect.preventExtensions(ensureResolved());
|
|
45
|
+
Reflect.preventExtensions(proxyTarget);
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
set(_, prop, value) {
|
|
49
|
+
return Reflect.set(ensureResolved(), prop, value, ensureResolved());
|
|
50
|
+
},
|
|
51
|
+
setPrototypeOf(_, proto) {
|
|
52
|
+
return Reflect.setPrototypeOf(ensureResolved(), proto);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
lazy
|
|
58
|
+
};
|
package/dist/resolver/index.d.ts
CHANGED
|
@@ -30,6 +30,31 @@ export interface Resolver<T, Sync extends AbstractConstructor = AbstractConstruc
|
|
|
30
30
|
tryResolve<K extends keyof T>(token: K): T[K] | undefined;
|
|
31
31
|
tryResolve<V>(token: AbstractConstructor<V>): V | Promise<V> | undefined;
|
|
32
32
|
tryResolve(token: PropertyKey): unknown;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve all instances for the given token.
|
|
35
|
+
*
|
|
36
|
+
* Returns an array of instances from all registrations for the token.
|
|
37
|
+
*
|
|
38
|
+
* @param token A registered token
|
|
39
|
+
* @returns An array of instances associated with the token
|
|
40
|
+
*/
|
|
41
|
+
resolveAll<V>(token: AbstractConstructor<V> & Async): Promise<V>[];
|
|
42
|
+
resolveAll<V>(token: AbstractConstructor<V> & Sync): V[];
|
|
43
|
+
resolveAll<K extends keyof T>(token: K): T[K][];
|
|
44
|
+
/**
|
|
45
|
+
* Try to resolve all instances for the given token.
|
|
46
|
+
*
|
|
47
|
+
* Returns `undefined` instead of throwing when the token is not registered.
|
|
48
|
+
* Other errors (circular dependency, disposed container) are still thrown.
|
|
49
|
+
*
|
|
50
|
+
* @param token A token to resolve
|
|
51
|
+
* @returns An array of instances associated with the token, or `undefined` if not registered
|
|
52
|
+
*/
|
|
53
|
+
tryResolveAll<V>(token: AbstractConstructor<V> & Async): Promise<V>[] | undefined;
|
|
54
|
+
tryResolveAll<V>(token: AbstractConstructor<V> & Sync): V[] | undefined;
|
|
55
|
+
tryResolveAll<K extends keyof T>(token: K): T[K][] | undefined;
|
|
56
|
+
tryResolveAll<V>(token: AbstractConstructor<V>): (V | Promise<V>)[] | undefined;
|
|
57
|
+
tryResolveAll(token: PropertyKey): unknown;
|
|
33
58
|
}
|
|
34
59
|
/**
|
|
35
60
|
* Lifetime of a registration.
|
package/dist/scope/index.cjs
CHANGED
|
@@ -76,29 +76,29 @@ function createScope(source) {
|
|
|
76
76
|
if (internals.isDisposed()) {
|
|
77
77
|
throw new ContainerError("Cannot create a scope from a disposed container.");
|
|
78
78
|
}
|
|
79
|
-
return new Scope(internals.registrations, internals.
|
|
79
|
+
return new Scope(internals.registrations, internals.singletonCache);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
class Scope {
|
|
83
83
|
registrations;
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
singletonCache;
|
|
85
|
+
scopedCache;
|
|
86
86
|
resolvingTokens;
|
|
87
87
|
disposed = false;
|
|
88
88
|
[INTERNALS];
|
|
89
|
-
constructor(registrations,
|
|
89
|
+
constructor(registrations, singletonCache) {
|
|
90
90
|
this.registrations = registrations;
|
|
91
|
-
this.
|
|
92
|
-
this.
|
|
91
|
+
this.singletonCache = singletonCache;
|
|
92
|
+
this.scopedCache = new Map;
|
|
93
93
|
this.resolvingTokens = new Set;
|
|
94
94
|
this[INTERNALS] = {
|
|
95
|
-
instances: this.singletonInstances,
|
|
96
95
|
isDisposed: () => this.disposed,
|
|
97
96
|
markDisposed: () => {
|
|
98
97
|
this.disposed = true;
|
|
99
98
|
},
|
|
100
|
-
|
|
101
|
-
registrations: this.registrations
|
|
99
|
+
ownCache: this.scopedCache,
|
|
100
|
+
registrations: this.registrations,
|
|
101
|
+
singletonCache: this.singletonCache
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
resolve(token) {
|
|
@@ -107,25 +107,32 @@ class Scope {
|
|
|
107
107
|
tryResolve(token) {
|
|
108
108
|
return this.resolveToken(token, false);
|
|
109
109
|
}
|
|
110
|
+
resolveAll(token) {
|
|
111
|
+
return this.resolveAllTokens(token, true);
|
|
112
|
+
}
|
|
113
|
+
tryResolveAll(token) {
|
|
114
|
+
return this.resolveAllTokens(token, false);
|
|
115
|
+
}
|
|
110
116
|
resolveToken(token, required) {
|
|
111
117
|
if (this.disposed) {
|
|
112
118
|
throw new ContainerError("Cannot resolve from a disposed scope.");
|
|
113
119
|
}
|
|
114
|
-
const
|
|
120
|
+
const registrations = this.registrations.get(token);
|
|
121
|
+
if (registrations === undefined || registrations.length === 0) {
|
|
122
|
+
if (required) {
|
|
123
|
+
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const registration = registrations[registrations.length - 1];
|
|
128
|
+
const singletonCached = this.singletonCache.get(registration);
|
|
115
129
|
if (singletonCached !== undefined) {
|
|
116
130
|
return singletonCached;
|
|
117
131
|
}
|
|
118
|
-
const scopedCached = this.
|
|
132
|
+
const scopedCached = this.scopedCache.get(registration);
|
|
119
133
|
if (scopedCached !== undefined) {
|
|
120
134
|
return scopedCached;
|
|
121
135
|
}
|
|
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
136
|
if (this.resolvingTokens.has(token)) {
|
|
130
137
|
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
131
138
|
}
|
|
@@ -133,13 +140,51 @@ class Scope {
|
|
|
133
140
|
try {
|
|
134
141
|
const instance = registration.factory(this);
|
|
135
142
|
if (registration.lifetime === "singleton") {
|
|
136
|
-
this.
|
|
143
|
+
this.singletonCache.set(registration, instance);
|
|
137
144
|
} else if (registration.lifetime === "scoped") {
|
|
138
|
-
this.
|
|
145
|
+
this.scopedCache.set(registration, instance);
|
|
139
146
|
}
|
|
140
147
|
return instance;
|
|
141
148
|
} finally {
|
|
142
149
|
this.resolvingTokens.delete(token);
|
|
143
150
|
}
|
|
144
151
|
}
|
|
152
|
+
resolveAllTokens(token, required) {
|
|
153
|
+
if (this.disposed) {
|
|
154
|
+
throw new ContainerError("Cannot resolve from a disposed scope.");
|
|
155
|
+
}
|
|
156
|
+
const registrations = this.registrations.get(token);
|
|
157
|
+
if (registrations === undefined || registrations.length === 0) {
|
|
158
|
+
if (required) {
|
|
159
|
+
throw new ContainerError(`Token "${tokenToString(token)}" is not registered.`);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (this.resolvingTokens.has(token)) {
|
|
164
|
+
throw new ContainerError(`Circular dependency detected: ${buildCircularPath(this.resolvingTokens, token)}`);
|
|
165
|
+
}
|
|
166
|
+
this.resolvingTokens.add(token);
|
|
167
|
+
try {
|
|
168
|
+
return registrations.map((registration) => {
|
|
169
|
+
const reg = registration;
|
|
170
|
+
const singletonCached = this.singletonCache.get(registration);
|
|
171
|
+
if (singletonCached !== undefined) {
|
|
172
|
+
return singletonCached;
|
|
173
|
+
}
|
|
174
|
+
const scopedCached = this.scopedCache.get(registration);
|
|
175
|
+
if (scopedCached !== undefined) {
|
|
176
|
+
return scopedCached;
|
|
177
|
+
}
|
|
178
|
+
const instance = reg.factory(this);
|
|
179
|
+
if (reg.lifetime === "singleton") {
|
|
180
|
+
this.singletonCache.set(registration, instance);
|
|
181
|
+
} else if (reg.lifetime === "scoped") {
|
|
182
|
+
this.scopedCache.set(registration, instance);
|
|
183
|
+
}
|
|
184
|
+
return instance;
|
|
185
|
+
});
|
|
186
|
+
} finally {
|
|
187
|
+
this.resolvingTokens.delete(token);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
145
190
|
}
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import type { Container } from '../container';
|
|
2
|
+
import type { DisposableContainer, DisposableScope } from '../disposable';
|
|
2
3
|
import { type ContainerInternals, INTERNALS } from '../internal';
|
|
3
4
|
import type { AbstractConstructor, Registration } from '../resolver';
|
|
4
5
|
/**
|
|
5
|
-
* Create a new scope (child container) from a Container or
|
|
6
|
+
* Create a new scope (child container) from a Container, Scope, or their disposable variants.
|
|
6
7
|
*
|
|
7
8
|
* The scope inherits all registrations from the source.
|
|
8
9
|
* Singleton instances are shared with the parent, while scoped instances are local to the scope.
|
|
9
10
|
*
|
|
10
|
-
* @param source A Container or
|
|
11
|
+
* @param source A Container, Scope, DisposableContainer, or DisposableScope to create a child scope from
|
|
11
12
|
* @returns A new Scope instance
|
|
12
13
|
* @throws ContainerError if the source has been disposed
|
|
13
14
|
*/
|
|
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>;
|
|
15
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>;
|
|
16
19
|
/**
|
|
17
20
|
* Scoped child container.
|
|
18
21
|
*
|
|
@@ -29,8 +32,8 @@ export declare function createScope<T, Sync extends AbstractConstructor, Async e
|
|
|
29
32
|
*/
|
|
30
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> {
|
|
31
34
|
private readonly registrations;
|
|
32
|
-
private readonly
|
|
33
|
-
private readonly
|
|
35
|
+
private readonly singletonCache;
|
|
36
|
+
private readonly scopedCache;
|
|
34
37
|
private readonly resolvingTokens;
|
|
35
38
|
private disposed;
|
|
36
39
|
/**
|
|
@@ -39,7 +42,7 @@ export declare class Scope<T = Record<never, never>, Sync extends AbstractConstr
|
|
|
39
42
|
* @internal
|
|
40
43
|
*/
|
|
41
44
|
readonly [INTERNALS]: ContainerInternals;
|
|
42
|
-
constructor(registrations: Map<unknown, Registration>,
|
|
45
|
+
constructor(registrations: Map<unknown, Registration[]>, singletonCache: Map<Registration, unknown>);
|
|
43
46
|
/**
|
|
44
47
|
* Resolve an instance for the given token.
|
|
45
48
|
*
|
|
@@ -68,12 +71,49 @@ export declare class Scope<T = Record<never, never>, Sync extends AbstractConstr
|
|
|
68
71
|
tryResolve<K extends keyof (T & ScopedT)>(token: K): (T & ScopedT)[K] | undefined;
|
|
69
72
|
tryResolve<V>(token: AbstractConstructor<V>): V | Promise<V> | undefined;
|
|
70
73
|
tryResolve(token: PropertyKey): unknown;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve all instances for the given token.
|
|
76
|
+
*
|
|
77
|
+
* Returns an array of instances from all registered factories for the token,
|
|
78
|
+
* in registration order.
|
|
79
|
+
*
|
|
80
|
+
* @param token A registered token
|
|
81
|
+
* @returns An array of instances associated with the token
|
|
82
|
+
* @throws ContainerError if the token is not registered
|
|
83
|
+
*/
|
|
84
|
+
resolveAll<V>(token: AbstractConstructor<V> & (Async | ScopedAsync)): Promise<V>[];
|
|
85
|
+
resolveAll<V>(token: AbstractConstructor<V> & (Sync | ScopedSync)): V[];
|
|
86
|
+
resolveAll<K extends keyof (T & ScopedT)>(token: K): (T & ScopedT)[K][];
|
|
87
|
+
/**
|
|
88
|
+
* Try to resolve all instances for the given token.
|
|
89
|
+
*
|
|
90
|
+
* Returns `undefined` instead of throwing when the token is not registered.
|
|
91
|
+
* Other errors (circular dependency, disposed scope) are still thrown.
|
|
92
|
+
*
|
|
93
|
+
* @param token A token to resolve
|
|
94
|
+
* @returns An array of instances associated with the token, or `undefined` if not registered
|
|
95
|
+
*/
|
|
96
|
+
tryResolveAll<V>(token: AbstractConstructor<V> & (Async | ScopedAsync)): Promise<V>[] | undefined;
|
|
97
|
+
tryResolveAll<V>(token: AbstractConstructor<V> & (Sync | ScopedSync)): V[] | undefined;
|
|
98
|
+
tryResolveAll<K extends keyof (T & ScopedT)>(token: K): (T & ScopedT)[K][] | undefined;
|
|
99
|
+
tryResolveAll<V>(token: AbstractConstructor<V>): (V | Promise<V>)[] | undefined;
|
|
100
|
+
tryResolveAll(token: PropertyKey): unknown;
|
|
71
101
|
/**
|
|
72
102
|
* Internal resolution logic shared by resolve and tryResolve.
|
|
103
|
+
* Resolves the last registered factory for the token.
|
|
73
104
|
*
|
|
74
105
|
* @param token Token to resolve
|
|
75
106
|
* @param required If true, throws when the token is not registered. If false, returns undefined.
|
|
76
107
|
* @returns The resolved instance, or undefined if not registered and required is false
|
|
77
108
|
*/
|
|
78
109
|
private resolveToken;
|
|
110
|
+
/**
|
|
111
|
+
* Internal resolution logic shared by resolveAll and tryResolveAll.
|
|
112
|
+
* Resolves all registered factories for the token.
|
|
113
|
+
*
|
|
114
|
+
* @param token Token to resolve
|
|
115
|
+
* @param required If true, throws when the token is not registered. If false, returns undefined.
|
|
116
|
+
* @returns An array of resolved instances, or undefined if not registered and required is false
|
|
117
|
+
*/
|
|
118
|
+
private resolveAllTokens;
|
|
79
119
|
}
|