@fougere/container 0.11.0-alpha.0 → 0.12.1-alpha.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/dist/ContainerError.d.ts +5 -0
- package/dist/ContainerError.d.ts.map +1 -0
- package/dist/ContainerError.js +10 -0
- package/dist/ContainerError.js.map +1 -0
- package/dist/Disposable.d.ts +1 -4
- package/dist/Disposable.d.ts.map +1 -1
- package/dist/Disposable.js +2 -3
- package/dist/Disposable.js.map +1 -1
- package/dist/ScopeContainer.d.ts +8 -6
- package/dist/ScopeContainer.d.ts.map +1 -1
- package/dist/ScopeContainer.js +65 -68
- package/dist/ScopeContainer.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ContainerError.ts +11 -0
- package/src/Disposable.ts +3 -10
- package/src/ScopeContainer.ts +93 -79
- package/src/index.ts +5 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContainerError.d.ts","sourceRoot":"","sources":["../src/ContainerError.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAO,EAAE,MAAM,EAI1B;IAED,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAE/D;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContainerError.js","sourceRoot":"","sources":["../src/ContainerError.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QAEf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,QAAmB,EAAE,OAAe;QAC7C,OAAO,IAAI,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;CACF"}
|
package/dist/Disposable.d.ts
CHANGED
package/dist/Disposable.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Disposable.d.ts","sourceRoot":"","sources":["../src/Disposable.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"Disposable.d.ts","sourceRoot":"","sources":["../src/Disposable.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW;IACtB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAGlD;CACF"}
|
package/dist/Disposable.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
export class Disposables {
|
|
2
2
|
static is(value) {
|
|
3
|
-
return
|
|
4
|
-
value
|
|
5
|
-
typeof value.dispose === 'function');
|
|
3
|
+
return typeof value === 'object' && value !== null
|
|
4
|
+
&& typeof value[Symbol.asyncDispose] === 'function';
|
|
6
5
|
}
|
|
7
6
|
}
|
|
8
7
|
//# sourceMappingURL=Disposable.js.map
|
package/dist/Disposable.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Disposable.js","sourceRoot":"","sources":["../src/Disposable.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Disposable.js","sourceRoot":"","sources":["../src/Disposable.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,WAAW;IACtB,MAAM,CAAC,EAAE,CAAC,KAAc;QACtB,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;eAC7C,OAAQ,KAAyB,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,UAAU,CAAC;IAC7E,CAAC;CACF"}
|
package/dist/ScopeContainer.d.ts
CHANGED
|
@@ -4,11 +4,11 @@ import type { RegisterOptions } from './registration/RegisterOptions.js';
|
|
|
4
4
|
/** A scope reaches its parent and its children through members only a scope can read. */
|
|
5
5
|
export declare class ScopeContainer implements Container {
|
|
6
6
|
private readonly parent?;
|
|
7
|
-
private readonly registry;
|
|
8
7
|
private readonly built;
|
|
9
8
|
private readonly children;
|
|
10
|
-
private
|
|
9
|
+
private readonly registry;
|
|
11
10
|
private readonly resolving;
|
|
11
|
+
private fallback;
|
|
12
12
|
constructor(parent?: ScopeContainer | undefined);
|
|
13
13
|
register<T>(name: string, ctor: Constructor<T>, options?: RegisterOptions): void;
|
|
14
14
|
registerValue<T>(name: string, value: T): void;
|
|
@@ -16,12 +16,14 @@ export declare class ScopeContainer implements Container {
|
|
|
16
16
|
has(name: string): boolean;
|
|
17
17
|
createScope(): Container;
|
|
18
18
|
dispose(): Promise<void>;
|
|
19
|
+
private disposeChildren;
|
|
20
|
+
private disposeBuilt;
|
|
19
21
|
setFallback(resolve: (name: string) => unknown): void;
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
private fallbackOf;
|
|
22
|
+
private build;
|
|
23
|
+
private buildFallback;
|
|
23
24
|
private forget;
|
|
24
|
-
|
|
25
|
+
/** What this scope will close. A value that answers no `[Symbol.asyncDispose]` is not one of them. */
|
|
25
26
|
private remember;
|
|
27
|
+
private errorMessage;
|
|
26
28
|
}
|
|
27
29
|
//# sourceMappingURL=ScopeContainer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScopeContainer.d.ts","sourceRoot":"","sources":["../src/ScopeContainer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ScopeContainer.d.ts","sourceRoot":"","sources":["../src/ScopeContainer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAGhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAQzE,yFAAyF;AACzF,qBAAa,cAAe,YAAW,SAAS;IAQlC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAPpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;IAErC,OAAO,CAAC,QAAQ,CAA0C;IAE1D,YAA6B,MAAM,CAAC,EAAE,cAAc,YAAA,EAEnD;IAED,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAS/E;IAED,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAM7C;IAED,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAY1B;IAED,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzB;IAED,WAAW,IAAI,SAAS,CAMvB;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAoB7B;YAEa,eAAe;YAkBf,YAAY;IAa1B,WAAW,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,CAIpD;IAED,OAAO,CAAC,KAAK;IAwBb,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,MAAM;IAMd,sGAAsG;IACtG,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,YAAY;CAQrB"}
|
package/dist/ScopeContainer.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { Disposables } from './Disposable.js';
|
|
2
|
+
import { ContainerError } from './ContainerError.js';
|
|
2
3
|
/** A scope reaches its parent and its children through members only a scope can read. */
|
|
3
4
|
export class ScopeContainer {
|
|
4
5
|
parent;
|
|
5
|
-
registry = new Map();
|
|
6
6
|
built = [];
|
|
7
|
-
// Closed by this container, and before `built` — a child may hold what this scope built,
|
|
8
|
-
// never the other way round.
|
|
9
7
|
children = [];
|
|
10
|
-
|
|
11
|
-
// The names being built right now, shared by the whole tree: a miss here is answered by the
|
|
12
|
-
// parent and the descent continues there, so only one stack can name the path whole —
|
|
13
|
-
// `child.resolve('A')` reaching a parent's `B` that asks for `A` back reports `A → B → A`.
|
|
8
|
+
registry = new Map();
|
|
14
9
|
resolving;
|
|
10
|
+
fallback;
|
|
15
11
|
constructor(parent) {
|
|
16
12
|
this.parent = parent;
|
|
17
13
|
this.resolving = parent?.resolving ?? [];
|
|
@@ -25,7 +21,6 @@ export class ScopeContainer {
|
|
|
25
21
|
});
|
|
26
22
|
}
|
|
27
23
|
registerValue(name, value) {
|
|
28
|
-
// A value the container did not build is not the container's to dispose.
|
|
29
24
|
this.registry.set(name, {
|
|
30
25
|
factory: () => value,
|
|
31
26
|
lifetime: 'singleton',
|
|
@@ -34,45 +29,14 @@ export class ScopeContainer {
|
|
|
34
29
|
}
|
|
35
30
|
resolve(name) {
|
|
36
31
|
const entry = this.registry.get(name);
|
|
37
|
-
// Not found locally — the parent holds it, and holds its instance too.
|
|
38
|
-
if (!entry && this.parent?.entryOf(name)) {
|
|
39
|
-
return this.parent.resolve(name);
|
|
40
|
-
}
|
|
41
|
-
// Nobody holds it. A frond declared in `remotes` registers nothing here, so its
|
|
42
|
-
// façade is fabricated by the fallback rather than found.
|
|
43
32
|
if (!entry) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
factory: () => made,
|
|
48
|
-
lifetime: 'singleton',
|
|
49
|
-
instance: made,
|
|
50
|
-
});
|
|
51
|
-
return made;
|
|
52
|
-
}
|
|
53
|
-
throw new Error(`[container] '${name}' is not registered${this.through(name)}`);
|
|
33
|
+
if (this.parent)
|
|
34
|
+
return this.parent.resolve(name);
|
|
35
|
+
return this.buildFallback(name);
|
|
54
36
|
}
|
|
55
37
|
if (entry.instance !== undefined)
|
|
56
38
|
return entry.instance;
|
|
57
|
-
|
|
58
|
-
throw new Error(`[container] dependency cycle: ${[...this.resolving, name].join(' → ')}`);
|
|
59
|
-
}
|
|
60
|
-
// Popped in a finally, because a constructor that throws leaves the name on the stack
|
|
61
|
-
// otherwise and the next resolution of it reports a cycle that is not there.
|
|
62
|
-
this.resolving.push(name);
|
|
63
|
-
try {
|
|
64
|
-
const value = entry.factory(this);
|
|
65
|
-
// The container disposes what it KEEPS: a transient is handed over and forgotten,
|
|
66
|
-
// and its caller is the one who knows when it is done.
|
|
67
|
-
if (entry.lifetime === 'singleton') {
|
|
68
|
-
entry.instance = value;
|
|
69
|
-
this.remember(value);
|
|
70
|
-
}
|
|
71
|
-
return value;
|
|
72
|
-
}
|
|
73
|
-
finally {
|
|
74
|
-
this.resolving.pop();
|
|
75
|
-
}
|
|
39
|
+
return this.build(name, entry);
|
|
76
40
|
}
|
|
77
41
|
has(name) {
|
|
78
42
|
return this.registry.has(name) || (this.parent?.has(name) ?? false);
|
|
@@ -83,60 +47,93 @@ export class ScopeContainer {
|
|
|
83
47
|
return child;
|
|
84
48
|
}
|
|
85
49
|
async dispose() {
|
|
86
|
-
// Everything is told before anything throws, then the failures travel together.
|
|
87
|
-
// Dropped from the parent first: a scope that closes itself leaves a reference the
|
|
88
|
-
// parent would hold for the life of the process.
|
|
89
50
|
this.parent?.forget(this);
|
|
51
|
+
const failures = await this.disposeChildren();
|
|
52
|
+
failures.push(...(await this.disposeBuilt()));
|
|
53
|
+
this.registry.clear();
|
|
54
|
+
if (this.children.length > 0) {
|
|
55
|
+
failures.push(new ContainerError(`${this.children.length} scope(s) still held after this one closed — probably opened while it was closing.`));
|
|
56
|
+
}
|
|
57
|
+
if (failures.length > 0) {
|
|
58
|
+
throw ContainerError.all(failures, 'one or more disposals failed');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async disposeChildren() {
|
|
90
62
|
const failures = [];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
for (const child of [...this.children].reverse()) {
|
|
63
|
+
while (this.children.length > 0) {
|
|
64
|
+
const child = this.children[this.children.length - 1];
|
|
94
65
|
try {
|
|
95
66
|
await child.dispose();
|
|
96
67
|
}
|
|
97
68
|
catch (error) {
|
|
98
69
|
failures.push(error);
|
|
99
70
|
}
|
|
71
|
+
finally {
|
|
72
|
+
this.forget(child);
|
|
73
|
+
}
|
|
100
74
|
}
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
return failures;
|
|
76
|
+
}
|
|
77
|
+
async disposeBuilt() {
|
|
78
|
+
const failures = [];
|
|
79
|
+
while (this.built.length > 0) {
|
|
103
80
|
try {
|
|
104
|
-
await
|
|
81
|
+
await this.built.pop()?.[Symbol.asyncDispose]();
|
|
105
82
|
}
|
|
106
83
|
catch (error) {
|
|
107
84
|
failures.push(error);
|
|
108
85
|
}
|
|
109
86
|
}
|
|
110
|
-
|
|
111
|
-
this.registry.clear();
|
|
112
|
-
if (failures.length > 0) {
|
|
113
|
-
throw new AggregateError(failures, '[container] one or more disposals failed');
|
|
114
|
-
}
|
|
87
|
+
return failures;
|
|
115
88
|
}
|
|
116
89
|
setFallback(resolve) {
|
|
90
|
+
if (this.parent)
|
|
91
|
+
return this.parent.setFallback(resolve);
|
|
117
92
|
this.fallback = resolve;
|
|
118
93
|
}
|
|
119
|
-
|
|
120
|
-
|
|
94
|
+
build(name, entry) {
|
|
95
|
+
if (this.resolving.includes(name)) {
|
|
96
|
+
throw new ContainerError(`dependency cycle: ${[...this.resolving, name].join(' → ')}`);
|
|
97
|
+
}
|
|
98
|
+
this.resolving.push(name);
|
|
99
|
+
try {
|
|
100
|
+
const value = entry.factory(this);
|
|
101
|
+
if (entry.lifetime === 'singleton') {
|
|
102
|
+
entry.instance = value;
|
|
103
|
+
this.remember(value);
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
this.resolving.pop();
|
|
109
|
+
}
|
|
121
110
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
buildFallback(name) {
|
|
112
|
+
const made = this.fallback?.(name);
|
|
113
|
+
if (made === undefined)
|
|
114
|
+
throw new ContainerError(this.errorMessage(name));
|
|
115
|
+
this.registry.set(name, {
|
|
116
|
+
factory: () => made,
|
|
117
|
+
lifetime: 'singleton',
|
|
118
|
+
instance: made,
|
|
119
|
+
});
|
|
120
|
+
return made;
|
|
125
121
|
}
|
|
126
122
|
forget(child) {
|
|
127
123
|
const at = this.children.indexOf(child);
|
|
128
124
|
if (at !== -1)
|
|
129
125
|
this.children.splice(at, 1);
|
|
130
126
|
}
|
|
131
|
-
|
|
132
|
-
return this.resolving.length > 0
|
|
133
|
-
? ` (resolving: ${[...this.resolving, name].join(' → ')})`
|
|
134
|
-
: '';
|
|
135
|
-
}
|
|
127
|
+
/** What this scope will close. A value that answers no `[Symbol.asyncDispose]` is not one of them. */
|
|
136
128
|
remember(value) {
|
|
137
129
|
if (Disposables.is(value))
|
|
138
130
|
this.built.push(value);
|
|
139
|
-
|
|
131
|
+
}
|
|
132
|
+
errorMessage(name) {
|
|
133
|
+
const path = this.resolving.length > 0
|
|
134
|
+
? ` (resolving: ${[...this.resolving, name].join(' → ')})`
|
|
135
|
+
: '';
|
|
136
|
+
return `'${name}' is not registered${path}`;
|
|
140
137
|
}
|
|
141
138
|
}
|
|
142
139
|
//# sourceMappingURL=ScopeContainer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScopeContainer.js","sourceRoot":"","sources":["../src/ScopeContainer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"ScopeContainer.js","sourceRoot":"","sources":["../src/ScopeContainer.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { ContainerError } from './ContainerError.js';
|
|
1
2
|
import type { Container } from './Container.js';
|
|
2
3
|
import type { Constructor } from './registration/Constructor.js';
|
|
3
|
-
|
|
4
|
+
import type { Lifetime } from './registration/Lifetime.js';
|
|
5
|
+
import type { RegisterOptions } from './registration/RegisterOptions.js';
|
|
6
|
+
export type { Container, Constructor, RegisterOptions, Lifetime };
|
|
7
|
+
export { ContainerError };
|
|
4
8
|
export declare function createContainer(): Container;
|
|
5
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEzE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,eAAe,IAAI,SAAS,CAE3C"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAOrD,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,cAAc,EAAE,CAAC;AAC9B,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class ContainerError extends Error {
|
|
2
|
+
constructor(message: string) {
|
|
3
|
+
super(message);
|
|
4
|
+
|
|
5
|
+
this.name = 'ContainerError';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static all(failures: unknown[], message: string): AggregateError {
|
|
9
|
+
return new AggregateError(failures, message);
|
|
10
|
+
}
|
|
11
|
+
}
|
package/src/Disposable.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
export interface Disposable {
|
|
2
|
-
dispose(): void | Promise<void>;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
1
|
export class Disposables {
|
|
6
|
-
static is(value: unknown): value is
|
|
7
|
-
return
|
|
8
|
-
typeof value === '
|
|
9
|
-
value !== null &&
|
|
10
|
-
typeof (value as Disposable).dispose === 'function'
|
|
11
|
-
);
|
|
2
|
+
static is(value: unknown): value is AsyncDisposable {
|
|
3
|
+
return typeof value === 'object' && value !== null
|
|
4
|
+
&& typeof (value as AsyncDisposable)[Symbol.asyncDispose] === 'function';
|
|
12
5
|
}
|
|
13
6
|
}
|
package/src/ScopeContainer.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Container } from './Container.js';
|
|
2
|
-
import { Disposables
|
|
2
|
+
import { Disposables } from './Disposable.js';
|
|
3
|
+
import { ContainerError } from './ContainerError.js';
|
|
3
4
|
import type { Constructor } from './registration/Constructor.js';
|
|
4
5
|
import type { Lifetime } from './registration/Lifetime.js';
|
|
5
6
|
import type { RegisterOptions } from './registration/RegisterOptions.js';
|
|
@@ -12,21 +13,13 @@ interface Entry {
|
|
|
12
13
|
|
|
13
14
|
/** A scope reaches its parent and its children through members only a scope can read. */
|
|
14
15
|
export class ScopeContainer implements Container {
|
|
15
|
-
private readonly
|
|
16
|
-
|
|
17
|
-
private readonly built: unknown[] = [];
|
|
18
|
-
|
|
19
|
-
// Closed by this container, and before `built` — a child may hold what this scope built,
|
|
20
|
-
// never the other way round.
|
|
16
|
+
private readonly built: AsyncDisposable[] = [];
|
|
21
17
|
private readonly children: ScopeContainer[] = [];
|
|
18
|
+
private readonly registry = new Map<string, Entry>();
|
|
19
|
+
private readonly resolving: string[];
|
|
22
20
|
|
|
23
21
|
private fallback: ((name: string) => unknown) | undefined;
|
|
24
22
|
|
|
25
|
-
// The names being built right now, shared by the whole tree: a miss here is answered by the
|
|
26
|
-
// parent and the descent continues there, so only one stack can name the path whole —
|
|
27
|
-
// `child.resolve('A')` reaching a parent's `B` that asks for `A` back reports `A → B → A`.
|
|
28
|
-
private readonly resolving: string[];
|
|
29
|
-
|
|
30
23
|
constructor(private readonly parent?: ScopeContainer) {
|
|
31
24
|
this.resolving = parent?.resolving ?? [];
|
|
32
25
|
}
|
|
@@ -43,7 +36,6 @@ export class ScopeContainer implements Container {
|
|
|
43
36
|
}
|
|
44
37
|
|
|
45
38
|
registerValue<T>(name: string, value: T): void {
|
|
46
|
-
// A value the container did not build is not the container's to dispose.
|
|
47
39
|
this.registry.set(name, {
|
|
48
40
|
factory: () => value,
|
|
49
41
|
lifetime: 'singleton',
|
|
@@ -54,51 +46,15 @@ export class ScopeContainer implements Container {
|
|
|
54
46
|
resolve<T>(name: string): T {
|
|
55
47
|
const entry = this.registry.get(name);
|
|
56
48
|
|
|
57
|
-
// Not found locally — the parent holds it, and holds its instance too.
|
|
58
|
-
if (!entry && this.parent?.entryOf(name)) {
|
|
59
|
-
return this.parent.resolve<T>(name);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Nobody holds it. A frond declared in `remotes` registers nothing here, so its
|
|
63
|
-
// façade is fabricated by the fallback rather than found.
|
|
64
49
|
if (!entry) {
|
|
65
|
-
|
|
66
|
-
if (made !== undefined) {
|
|
67
|
-
this.registry.set(name, {
|
|
68
|
-
factory: () => made,
|
|
69
|
-
lifetime: 'singleton',
|
|
70
|
-
instance: made,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return made as T;
|
|
74
|
-
}
|
|
75
|
-
throw new Error(`[container] '${name}' is not registered${this.through(name)}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (entry.instance !== undefined) return entry.instance as T;
|
|
50
|
+
if (this.parent) return this.parent.resolve<T>(name);
|
|
79
51
|
|
|
80
|
-
|
|
81
|
-
throw new Error(
|
|
82
|
-
`[container] dependency cycle: ${[...this.resolving, name].join(' → ')}`,
|
|
83
|
-
);
|
|
52
|
+
return this.buildFallback<T>(name);
|
|
84
53
|
}
|
|
85
54
|
|
|
86
|
-
|
|
87
|
-
// otherwise and the next resolution of it reports a cycle that is not there.
|
|
88
|
-
this.resolving.push(name);
|
|
89
|
-
try {
|
|
90
|
-
const value = entry.factory(this) as T;
|
|
91
|
-
// The container disposes what it KEEPS: a transient is handed over and forgotten,
|
|
92
|
-
// and its caller is the one who knows when it is done.
|
|
93
|
-
if (entry.lifetime === 'singleton') {
|
|
94
|
-
entry.instance = value;
|
|
95
|
-
this.remember(value);
|
|
96
|
-
}
|
|
55
|
+
if (entry.instance !== undefined) return entry.instance as T;
|
|
97
56
|
|
|
98
|
-
|
|
99
|
-
} finally {
|
|
100
|
-
this.resolving.pop();
|
|
101
|
-
}
|
|
57
|
+
return this.build<T>(name, entry);
|
|
102
58
|
}
|
|
103
59
|
|
|
104
60
|
has(name: string): boolean {
|
|
@@ -107,68 +63,126 @@ export class ScopeContainer implements Container {
|
|
|
107
63
|
|
|
108
64
|
createScope(): Container {
|
|
109
65
|
const child = new ScopeContainer(this);
|
|
66
|
+
|
|
110
67
|
this.children.push(child);
|
|
111
68
|
|
|
112
69
|
return child;
|
|
113
70
|
}
|
|
114
71
|
|
|
115
72
|
async dispose(): Promise<void> {
|
|
116
|
-
// Everything is told before anything throws, then the failures travel together.
|
|
117
|
-
// Dropped from the parent first: a scope that closes itself leaves a reference the
|
|
118
|
-
// parent would hold for the life of the process.
|
|
119
73
|
this.parent?.forget(this);
|
|
74
|
+
|
|
75
|
+
const failures = await this.disposeChildren();
|
|
76
|
+
|
|
77
|
+
failures.push(...(await this.disposeBuilt()));
|
|
78
|
+
|
|
79
|
+
this.registry.clear();
|
|
80
|
+
|
|
81
|
+
if (this.children.length > 0) {
|
|
82
|
+
failures.push(
|
|
83
|
+
new ContainerError(
|
|
84
|
+
`${this.children.length} scope(s) still held after this one closed — probably opened while it was closing.`,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (failures.length > 0) {
|
|
90
|
+
throw ContainerError.all(failures, 'one or more disposals failed');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async disposeChildren(): Promise<unknown[]> {
|
|
120
95
|
const failures: unknown[] = [];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
96
|
+
|
|
97
|
+
while (this.children.length > 0) {
|
|
98
|
+
const child = this.children[this.children.length - 1]!;
|
|
99
|
+
|
|
124
100
|
try {
|
|
125
101
|
await child.dispose();
|
|
126
102
|
} catch (error) {
|
|
127
103
|
failures.push(error);
|
|
104
|
+
} finally {
|
|
105
|
+
this.forget(child);
|
|
128
106
|
}
|
|
129
107
|
}
|
|
130
|
-
|
|
131
|
-
|
|
108
|
+
|
|
109
|
+
return failures;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async disposeBuilt(): Promise<unknown[]> {
|
|
113
|
+
const failures: unknown[] = [];
|
|
114
|
+
while (this.built.length > 0) {
|
|
132
115
|
try {
|
|
133
|
-
await (
|
|
116
|
+
await this.built.pop()?.[Symbol.asyncDispose]();
|
|
134
117
|
} catch (error) {
|
|
135
118
|
failures.push(error);
|
|
136
119
|
}
|
|
137
120
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (failures.length > 0) {
|
|
141
|
-
throw new AggregateError(failures, '[container] one or more disposals failed');
|
|
142
|
-
}
|
|
121
|
+
|
|
122
|
+
return failures;
|
|
143
123
|
}
|
|
144
124
|
|
|
145
125
|
setFallback(resolve: (name: string) => unknown): void {
|
|
126
|
+
if (this.parent) return this.parent.setFallback(resolve);
|
|
127
|
+
|
|
146
128
|
this.fallback = resolve;
|
|
147
129
|
}
|
|
148
130
|
|
|
149
|
-
private
|
|
150
|
-
|
|
131
|
+
private build<T>(name: string, entry: Entry): T {
|
|
132
|
+
if (this.resolving.includes(name)) {
|
|
133
|
+
throw new ContainerError(
|
|
134
|
+
`dependency cycle: ${[...this.resolving, name].join(' → ')}`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.resolving.push(name);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const value = entry.factory(this) as T;
|
|
142
|
+
|
|
143
|
+
if (entry.lifetime === 'singleton') {
|
|
144
|
+
entry.instance = value;
|
|
145
|
+
|
|
146
|
+
this.remember(value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return value;
|
|
150
|
+
} finally {
|
|
151
|
+
this.resolving.pop();
|
|
152
|
+
}
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
private buildFallback<T>(name: string): T {
|
|
156
|
+
const made = this.fallback?.(name);
|
|
157
|
+
|
|
158
|
+
if (made === undefined) throw new ContainerError(this.errorMessage(name));
|
|
159
|
+
|
|
160
|
+
this.registry.set(name, {
|
|
161
|
+
factory: () => made,
|
|
162
|
+
lifetime: 'singleton',
|
|
163
|
+
instance: made,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return made as T;
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
private forget(child: ScopeContainer): void {
|
|
159
170
|
const at = this.children.indexOf(child);
|
|
171
|
+
|
|
160
172
|
if (at !== -1) this.children.splice(at, 1);
|
|
161
173
|
}
|
|
162
174
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
: '';
|
|
175
|
+
/** What this scope will close. A value that answers no `[Symbol.asyncDispose]` is not one of them. */
|
|
176
|
+
private remember(value: unknown): void {
|
|
177
|
+
if (Disposables.is(value)) this.built.push(value);
|
|
167
178
|
}
|
|
168
179
|
|
|
169
|
-
private
|
|
170
|
-
|
|
180
|
+
private errorMessage(name: string): string {
|
|
181
|
+
const path =
|
|
182
|
+
this.resolving.length > 0
|
|
183
|
+
? ` (resolving: ${[...this.resolving, name].join(' → ')})`
|
|
184
|
+
: '';
|
|
171
185
|
|
|
172
|
-
return
|
|
186
|
+
return `'${name}' is not registered${path}`;
|
|
173
187
|
}
|
|
174
188
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { ScopeContainer } from './ScopeContainer.js';
|
|
2
|
+
import { ContainerError } from './ContainerError.js';
|
|
2
3
|
import type { Container } from './Container.js';
|
|
3
4
|
import type { Constructor } from './registration/Constructor.js';
|
|
5
|
+
import type { Lifetime } from './registration/Lifetime.js';
|
|
6
|
+
import type { RegisterOptions } from './registration/RegisterOptions.js';
|
|
4
7
|
|
|
5
|
-
export type { Container, Constructor };
|
|
8
|
+
export type { Container, Constructor, RegisterOptions, Lifetime };
|
|
9
|
+
export { ContainerError };
|
|
6
10
|
|
|
7
11
|
export function createContainer(): Container {
|
|
8
12
|
return new ScopeContainer();
|