@codefast/di 0.5.0-canary.9 → 0.6.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/CHANGELOG.md +865 -0
- package/README.md +119 -30
- package/dist/ambient/active-container.d.ts +37 -0
- package/dist/ambient/active-container.d.ts.map +1 -0
- package/dist/ambient/active-container.js +41 -0
- package/dist/ambient/active-container.js.map +1 -0
- package/dist/container/binding-builders.d.ts +12 -8
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +56 -31
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts +33 -19
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +279 -118
- package/dist/container/container.js.map +1 -1
- package/dist/{resolution → core}/binding-scope.d.ts +2 -2
- package/dist/core/binding-scope.d.ts.map +1 -0
- package/dist/core/binding-scope.js.map +1 -0
- package/dist/{binding.d.ts → core/binding.d.ts} +70 -15
- package/dist/core/binding.d.ts.map +1 -0
- package/dist/{binding.js → core/binding.js} +27 -12
- package/dist/core/binding.js.map +1 -0
- package/dist/core/constraint-requirement.d.ts +38 -0
- package/dist/core/constraint-requirement.d.ts.map +1 -0
- package/dist/core/constraint-requirement.js +28 -0
- package/dist/core/constraint-requirement.js.map +1 -0
- package/dist/{constructor-type.d.ts → core/constructor-type.d.ts} +1 -1
- package/dist/core/constructor-type.d.ts.map +1 -0
- package/dist/core/constructor-type.js.map +1 -0
- package/dist/{module.d.ts → core/module.d.ts} +5 -5
- package/dist/core/module.d.ts.map +1 -0
- package/dist/{module.js → core/module.js} +4 -4
- package/dist/core/module.js.map +1 -0
- package/dist/{registry.d.ts → core/registry.d.ts} +27 -7
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/{registry.js → core/registry.js} +125 -52
- package/dist/core/registry.js.map +1 -0
- package/dist/core/tag.d.ts +80 -0
- package/dist/core/tag.d.ts.map +1 -0
- package/dist/core/tag.js +84 -0
- package/dist/core/tag.js.map +1 -0
- package/dist/{token.d.ts → core/token.d.ts} +2 -6
- package/dist/core/token.d.ts.map +1 -0
- package/dist/core/token.js +13 -0
- package/dist/core/token.js.map +1 -0
- package/dist/{types.d.ts → core/types.d.ts} +27 -21
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/decorators/inject.d.ts +5 -49
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js +34 -104
- package/dist/decorators/inject.js.map +1 -1
- package/dist/decorators/injectable.d.ts +25 -5
- package/dist/decorators/injectable.d.ts.map +1 -1
- package/dist/decorators/injectable.js +2 -3
- package/dist/decorators/injectable.js.map +1 -1
- package/dist/decorators/lifecycle-decorators.d.ts +4 -2
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
- package/dist/decorators/lifecycle-decorators.js +19 -25
- package/dist/decorators/lifecycle-decorators.js.map +1 -1
- package/dist/{resolution → errors}/diagnostics.d.ts +2 -0
- package/dist/errors/diagnostics.d.ts.map +1 -0
- package/dist/errors/diagnostics.js.map +1 -0
- package/dist/{errors.d.ts → errors/errors.d.ts} +89 -4
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/{errors.js → errors/errors.js} +129 -7
- package/dist/errors/errors.js.map +1 -0
- package/dist/index.d.ts +21 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/injection/descriptor.d.ts +79 -0
- package/dist/injection/descriptor.d.ts.map +1 -0
- package/dist/injection/descriptor.js +118 -0
- package/dist/injection/descriptor.js.map +1 -0
- package/dist/{resolution → injection}/resolve-options.d.ts +21 -11
- package/dist/injection/resolve-options.d.ts.map +1 -0
- package/dist/injection/resolve-options.js +101 -0
- package/dist/injection/resolve-options.js.map +1 -0
- package/dist/introspection/dependency-graph.d.ts +16 -5
- package/dist/introspection/dependency-graph.d.ts.map +1 -1
- package/dist/introspection/dependency-graph.js +138 -57
- package/dist/introspection/dependency-graph.js.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts +6 -3
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.js +3 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/dot.js +12 -5
- package/dist/introspection/graph-adapters/dot.js.map +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +9 -0
- package/dist/introspection/graph-adapters/mermaid.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/mermaid.js +47 -0
- package/dist/introspection/graph-adapters/mermaid.js.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts +6 -3
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/reactflow.js +5 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
- package/dist/introspection/inspector.d.ts +4 -4
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +10 -6
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/lifecycle/lifecycle-manager.d.ts +27 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
- package/dist/{resolution/lifecycle.js → lifecycle/lifecycle-manager.js} +39 -9
- package/dist/lifecycle/lifecycle-manager.js.map +1 -0
- package/dist/lifecycle/scope-manager.d.ts +48 -0
- package/dist/lifecycle/scope-manager.d.ts.map +1 -0
- package/dist/lifecycle/scope-manager.js +135 -0
- package/dist/lifecycle/scope-manager.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +1 -1
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-reader-token.js.map +1 -1
- package/dist/metadata/metadata-types.d.ts +9 -9
- package/dist/metadata/metadata-types.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts +19 -6
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.js +89 -9
- package/dist/metadata/symbol-metadata-reader.js.map +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +16 -0
- package/dist/metadata/verifying-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/verifying-metadata-reader.js +32 -0
- package/dist/metadata/verifying-metadata-reader.js.map +1 -0
- package/dist/resolution/{activation-need.d.ts → cache/activation-need.d.ts} +5 -5
- package/dist/resolution/cache/activation-need.d.ts.map +1 -0
- package/dist/resolution/cache/activation-need.js.map +1 -0
- package/dist/resolution/{binding-lookup-cache.d.ts → cache/binding-lookup-cache.d.ts} +6 -5
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/cache/class-introspector.d.ts +66 -0
- package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
- package/dist/resolution/cache/class-introspector.js +194 -0
- package/dist/resolution/cache/class-introspector.js.map +1 -0
- package/dist/resolution/context.d.ts +84 -0
- package/dist/resolution/context.d.ts.map +1 -0
- package/dist/resolution/{environment.js → context.js} +54 -80
- package/dist/resolution/context.js.map +1 -0
- package/dist/resolution/path/resolution-path.d.ts +94 -0
- package/dist/resolution/path/resolution-path.d.ts.map +1 -0
- package/dist/resolution/path/resolution-path.js +117 -0
- package/dist/resolution/path/resolution-path.js.map +1 -0
- package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +30 -7
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/plan/instantiation-plan.js +390 -0
- package/dist/resolution/plan/instantiation-plan.js.map +1 -0
- package/dist/resolution/resolver.d.ts +28 -28
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +552 -239
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/select/binding-select.d.ts +34 -0
- package/dist/resolution/select/binding-select.d.ts.map +1 -0
- package/dist/resolution/select/binding-select.js +175 -0
- package/dist/resolution/select/binding-select.js.map +1 -0
- package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
- package/dist/resolution/select/constraints.d.ts.map +1 -0
- package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
- package/dist/resolution/select/constraints.js.map +1 -0
- package/package.json +160 -20
- package/src/ambient/active-container.ts +65 -0
- package/src/container/binding-builders.ts +72 -44
- package/src/container/container.ts +355 -170
- package/src/{resolution → core}/binding-scope.ts +2 -2
- package/src/{binding.ts → core/binding.ts} +101 -39
- package/src/core/constraint-requirement.ts +51 -0
- package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
- package/src/{module.ts → core/module.ts} +10 -10
- package/src/core/registry.ts +365 -0
- package/src/core/tag.ts +144 -0
- package/src/{token.ts → core/token.ts} +2 -14
- package/src/{types.ts → core/types.ts} +37 -34
- package/src/decorators/inject.ts +39 -175
- package/src/decorators/injectable.ts +40 -10
- package/src/decorators/lifecycle-decorators.ts +22 -34
- package/src/{resolution → errors}/diagnostics.ts +2 -0
- package/src/{errors.ts → errors/errors.ts} +152 -8
- package/src/index.ts +43 -15
- package/src/injection/descriptor.ts +206 -0
- package/src/injection/resolve-options.ts +144 -0
- package/src/introspection/dependency-graph.ts +223 -63
- package/src/introspection/graph-adapters/cytoscape.ts +9 -3
- package/src/introspection/graph-adapters/dot.ts +13 -5
- package/src/introspection/graph-adapters/mermaid.ts +61 -0
- package/src/introspection/graph-adapters/reactflow.ts +11 -4
- package/src/introspection/inspector.ts +16 -12
- package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
- package/src/lifecycle/scope-manager.ts +156 -0
- package/src/metadata/metadata-reader-token.ts +2 -2
- package/src/metadata/metadata-types.ts +9 -9
- package/src/metadata/symbol-metadata-reader.ts +95 -18
- package/src/metadata/verifying-metadata-reader.ts +41 -0
- package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
- package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +6 -5
- package/src/resolution/cache/class-introspector.ts +251 -0
- package/src/resolution/{environment.ts → context.ts} +84 -145
- package/src/resolution/path/resolution-path.ts +167 -0
- package/src/resolution/plan/instantiation-plan.ts +579 -0
- package/src/resolution/resolver.ts +635 -313
- package/src/resolution/select/binding-select.ts +210 -0
- package/src/resolution/select/constraints.ts +130 -0
- package/dist/binding.d.ts.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/constructor-type.d.ts.map +0 -1
- package/dist/constructor-type.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/module.d.ts.map +0 -1
- package/dist/module.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/resolution/activation-need.d.ts.map +0 -1
- package/dist/resolution/activation-need.js.map +0 -1
- package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
- package/dist/resolution/binding-lookup-cache.js.map +0 -1
- package/dist/resolution/binding-scope.d.ts.map +0 -1
- package/dist/resolution/binding-scope.js.map +0 -1
- package/dist/resolution/binding-select.d.ts +0 -23
- package/dist/resolution/binding-select.d.ts.map +0 -1
- package/dist/resolution/binding-select.js +0 -121
- package/dist/resolution/binding-select.js.map +0 -1
- package/dist/resolution/class-introspector.d.ts +0 -27
- package/dist/resolution/class-introspector.d.ts.map +0 -1
- package/dist/resolution/class-introspector.js +0 -60
- package/dist/resolution/class-introspector.js.map +0 -1
- package/dist/resolution/constraints.d.ts.map +0 -1
- package/dist/resolution/constraints.js.map +0 -1
- package/dist/resolution/diagnostics.d.ts.map +0 -1
- package/dist/resolution/diagnostics.js.map +0 -1
- package/dist/resolution/environment.d.ts +0 -94
- package/dist/resolution/environment.d.ts.map +0 -1
- package/dist/resolution/environment.js.map +0 -1
- package/dist/resolution/instantiation-plan.d.ts.map +0 -1
- package/dist/resolution/instantiation-plan.js +0 -183
- package/dist/resolution/instantiation-plan.js.map +0 -1
- package/dist/resolution/lifecycle.d.ts +0 -23
- package/dist/resolution/lifecycle.d.ts.map +0 -1
- package/dist/resolution/lifecycle.js.map +0 -1
- package/dist/resolution/resolution-path.d.ts +0 -98
- package/dist/resolution/resolution-path.d.ts.map +0 -1
- package/dist/resolution/resolution-path.js +0 -98
- package/dist/resolution/resolution-path.js.map +0 -1
- package/dist/resolution/resolve-options.d.ts.map +0 -1
- package/dist/resolution/resolve-options.js +0 -56
- package/dist/resolution/resolve-options.js.map +0 -1
- package/dist/resolution/scope.d.ts +0 -28
- package/dist/resolution/scope.d.ts.map +0 -1
- package/dist/resolution/scope.js +0 -88
- package/dist/resolution/scope.js.map +0 -1
- package/dist/token.d.ts.map +0 -1
- package/dist/token.js +0 -22
- package/dist/token.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/registry.ts +0 -282
- package/src/resolution/binding-select.ts +0 -154
- package/src/resolution/class-introspector.ts +0 -74
- package/src/resolution/constraints.ts +0 -121
- package/src/resolution/instantiation-plan.ts +0 -292
- package/src/resolution/resolution-path.ts +0 -156
- package/src/resolution/resolve-options.ts +0 -89
- package/src/resolution/scope.ts +0 -105
- /package/dist/{resolution → core}/binding-scope.js +0 -0
- /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/dist/{resolution → errors}/diagnostics.js +0 -0
- /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
- /package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +0 -0
|
@@ -1,23 +1,40 @@
|
|
|
1
|
-
import { NO_INSTANCE } from "#/binding";
|
|
2
1
|
import { BindingChain } from "#/container/binding-builders";
|
|
3
|
-
import {
|
|
2
|
+
import { NO_INSTANCE } from "#/core/binding";
|
|
3
|
+
import { effectiveBindingScope } from "#/core/binding-scope";
|
|
4
|
+
import { constraintRequirementOf } from "#/core/constraint-requirement";
|
|
5
|
+
import { isSyncModule, MODULE_SETUP } from "#/core/module";
|
|
6
|
+
import { BindingRegistry } from "#/core/registry";
|
|
7
|
+
import { tokenName } from "#/core/token";
|
|
8
|
+
import { RESOLUTION_DIAGNOSTICS } from "#/errors/diagnostics";
|
|
9
|
+
import { AsyncModuleLoadError, CircularDependencyError, DisposedContainerError, InternalError, RebindUnboundTokenError, ScopeViolationError, SyncDisposalNotSupportedError, UnreachableConstraintError, UnreachableLifecycleHookError, } from "#/errors/errors";
|
|
10
|
+
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/injection/resolve-options";
|
|
4
11
|
import { buildDependencyGraph } from "#/introspection/dependency-graph";
|
|
5
12
|
import { Inspector } from "#/introspection/inspector";
|
|
13
|
+
import { LifecycleManager } from "#/lifecycle/lifecycle-manager";
|
|
14
|
+
import { ScopeManager } from "#/lifecycle/scope-manager";
|
|
6
15
|
import { MetadataReaderToken } from "#/metadata/metadata-reader-token";
|
|
7
16
|
import { defaultMetadataReader } from "#/metadata/symbol-metadata-reader";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { effectiveBindingScope } from "#/resolution/binding-scope";
|
|
11
|
-
import { RESOLUTION_DIAGNOSTICS } from "#/resolution/diagnostics";
|
|
12
|
-
import { LifecycleManager } from "#/resolution/lifecycle";
|
|
13
|
-
import { ROOT_BRANCH } from "#/resolution/resolution-path";
|
|
14
|
-
import { injectionSlotToResolveOptions, bindingSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
17
|
+
import { verifyingMetadataReader } from "#/metadata/verifying-metadata-reader";
|
|
18
|
+
import { ROOT_BRANCH } from "#/resolution/path/resolution-path";
|
|
15
19
|
import { DependencyResolver } from "#/resolution/resolver";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
// A Record rather than an if-chain, so a new `BindingScope` is a compile error here instead of
|
|
21
|
+
// silently landing in whichever branch happened to be last.
|
|
22
|
+
const APPLY_BINDING_SCOPE = {
|
|
23
|
+
singleton: (builder) => {
|
|
24
|
+
builder.singleton();
|
|
25
|
+
},
|
|
26
|
+
scoped: (builder) => {
|
|
27
|
+
builder.scoped();
|
|
28
|
+
},
|
|
29
|
+
transient: (builder) => {
|
|
30
|
+
builder.transient();
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// ── DefaultContainer ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
19
34
|
class DefaultContainer {
|
|
20
35
|
#disposed = false;
|
|
36
|
+
// The one teardown run — every dispose() call returns it once it exists.
|
|
37
|
+
#disposePromise;
|
|
21
38
|
#registry;
|
|
22
39
|
#scope;
|
|
23
40
|
#lifecycle;
|
|
@@ -31,12 +48,12 @@ class DefaultContainer {
|
|
|
31
48
|
#moduleBindingIds;
|
|
32
49
|
// One shared registration for every chain this container's own `bind()` creates.
|
|
33
50
|
#registration;
|
|
34
|
-
constructor(parent) {
|
|
51
|
+
constructor(parent, options) {
|
|
35
52
|
this.#parent = parent;
|
|
36
53
|
this.#registry = new BindingRegistry();
|
|
37
54
|
this.#scope = new ScopeManager(parent !== undefined);
|
|
38
55
|
this.#lifecycle = new LifecycleManager();
|
|
39
|
-
this.#initResolver();
|
|
56
|
+
this.#initResolver(options?.metadataReader);
|
|
40
57
|
}
|
|
41
58
|
#getInspector() {
|
|
42
59
|
return (this.#inspector ??= new Inspector(this.#registry, this.#scope, this.#parent !== undefined, () => this.#disposed));
|
|
@@ -49,54 +66,59 @@ class DefaultContainer {
|
|
|
49
66
|
if (this.#moduleRefs !== undefined || this.#moduleBindingIds !== undefined) {
|
|
50
67
|
builtSubsystems.push("container.moduleTables");
|
|
51
68
|
}
|
|
52
|
-
if (this.#registry.
|
|
69
|
+
if (this.#registry.isNamedIndexBuilt) {
|
|
53
70
|
builtSubsystems.push("registry.namedIndex");
|
|
54
71
|
}
|
|
55
|
-
if (this.#
|
|
72
|
+
if (this.#registry.isTaggedIndexBuilt) {
|
|
73
|
+
builtSubsystems.push("registry.taggedIndex");
|
|
74
|
+
}
|
|
75
|
+
if (this.#scope.isScopedCacheBuilt) {
|
|
56
76
|
builtSubsystems.push("scope.scoped");
|
|
57
77
|
}
|
|
58
|
-
if (this.#lifecycle.
|
|
78
|
+
if (this.#lifecycle.isActivationTableBuilt) {
|
|
59
79
|
builtSubsystems.push("lifecycle.activationHooks");
|
|
60
80
|
}
|
|
61
81
|
return { ...this.#resolver.describeCaches(), scopedInstanceCount: this.#scope.scopedCount, builtSubsystems };
|
|
62
82
|
}
|
|
63
|
-
#initResolver() {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
83
|
+
#initResolver(configuredReader) {
|
|
84
|
+
const parent = this.#parent;
|
|
85
|
+
const metadataReader = verifyingMetadataReader(configuredReader ?? (parent === undefined ? defaultMetadataReader : parent.#readerForChild()));
|
|
86
|
+
const parentResolver = parent === undefined ? undefined : parent.#resolver;
|
|
66
87
|
this.#resolver = new DependencyResolver(this.#registry, this.#scope, this.#lifecycle, metadataReader, this, parentResolver);
|
|
67
88
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (metaBindings.length > 0) {
|
|
89
|
+
/** What a container being constructed under this one inherits: a reader bound here, else this one's. */
|
|
90
|
+
#readerForChild() {
|
|
91
|
+
if (this.#registry.getAll(MetadataReaderToken).length > 0) {
|
|
72
92
|
try {
|
|
73
|
-
return this.#resolver.resolve(MetadataReaderToken, undefined, []
|
|
93
|
+
return this.#resolver.resolve(MetadataReaderToken, undefined, []);
|
|
74
94
|
}
|
|
75
95
|
catch {
|
|
76
|
-
//
|
|
96
|
+
// An unresolvable reader binding is not worth failing a child over.
|
|
77
97
|
}
|
|
78
98
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
return this.#resolver.metadataReader;
|
|
100
|
+
}
|
|
101
|
+
/** One reader per container, fixed when its resolver was built — so every path agrees on it. */
|
|
102
|
+
#getMetadataReader() {
|
|
103
|
+
return this.#resolver.metadataReader;
|
|
83
104
|
}
|
|
84
105
|
get isDisposed() {
|
|
85
106
|
return this.#disposed;
|
|
86
107
|
}
|
|
87
|
-
// ── Binding
|
|
108
|
+
// ── Binding ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
88
109
|
bind(token) {
|
|
89
110
|
this.#assertNotDisposed();
|
|
90
111
|
return this.#createBindToBuilder(token);
|
|
91
112
|
}
|
|
92
113
|
/** The registration every non-module chain shares, so `bind()` allocates only the builder. */
|
|
93
114
|
#ownRegistration() {
|
|
94
|
-
return (this.#registration ??= { registry: this.#registry, moduleBindingIds: undefined });
|
|
115
|
+
return (this.#registration ??= { registry: this.#registry, scope: this.#scope, moduleBindingIds: undefined });
|
|
95
116
|
}
|
|
96
117
|
/** One registration per module load, holding that module's id list directly. */
|
|
97
118
|
#moduleRegistration(moduleRef) {
|
|
98
119
|
return {
|
|
99
120
|
registry: this.#registry,
|
|
121
|
+
scope: this.#scope,
|
|
100
122
|
moduleBindingIds: (this.#moduleBindingIds ??= new Map()).getOrInsert(moduleRef, []),
|
|
101
123
|
};
|
|
102
124
|
}
|
|
@@ -117,7 +139,7 @@ class DefaultContainer {
|
|
|
117
139
|
// re-index the token's binding list once per binding.
|
|
118
140
|
return this.#drainSingletons(this.#registry.removeByToken(tokenOrId));
|
|
119
141
|
}
|
|
120
|
-
/** Drain scope entries for already-removed bindings
|
|
142
|
+
/** Drain scope entries for already-removed bindings, and pair each one that still owes a deactivation. */
|
|
121
143
|
#drainSingletons(bindings) {
|
|
122
144
|
const pairs = [];
|
|
123
145
|
for (const binding of bindings) {
|
|
@@ -125,36 +147,71 @@ class DefaultContainer {
|
|
|
125
147
|
pairs.push([binding, binding.instance]);
|
|
126
148
|
this.#scope.deleteSingleton(binding);
|
|
127
149
|
}
|
|
150
|
+
else if (this.#owesConstantDeactivation(binding)) {
|
|
151
|
+
pairs.push([binding, binding.value]);
|
|
152
|
+
}
|
|
128
153
|
this.#scope.deleteScoped(binding.id);
|
|
129
154
|
}
|
|
130
155
|
return pairs;
|
|
131
156
|
}
|
|
132
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Whether a constant still owes its deactivation.
|
|
159
|
+
*
|
|
160
|
+
* @remarks A constant's value is handed in at bind time rather than built on demand, so its hook is
|
|
161
|
+
* owed whether or not anything ever resolved it. Callers check `instance` first: a constant that
|
|
162
|
+
* carries one was cached through activation and is deactivated with that value instead.
|
|
163
|
+
*/
|
|
164
|
+
#owesConstantDeactivation(binding) {
|
|
165
|
+
return (binding.kind === "constant" &&
|
|
166
|
+
(binding.onDeactivation !== undefined || this.#lifecycle.hasDeactivationHandlers(binding.token)));
|
|
167
|
+
}
|
|
168
|
+
/** Runs every pair's deactivation even when one throws, then reports what threw. */
|
|
169
|
+
#deactivatePairsSync(pairs) {
|
|
170
|
+
if (pairs.length === 0) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
133
173
|
const reader = this.#getMetadataReader();
|
|
134
|
-
|
|
135
|
-
|
|
174
|
+
const errors = [];
|
|
175
|
+
for (const [binding, instance] of pairs) {
|
|
176
|
+
try {
|
|
177
|
+
this.#lifecycle.runDeactivationSync(binding, instance, reader);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
errors.push(error);
|
|
181
|
+
}
|
|
136
182
|
}
|
|
183
|
+
throwCollected(errors, "unbind completed, but deactivation hooks threw");
|
|
137
184
|
}
|
|
138
|
-
async
|
|
139
|
-
|
|
185
|
+
async #deactivatePairs(pairs) {
|
|
186
|
+
if (pairs.length === 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
140
189
|
const reader = this.#getMetadataReader();
|
|
141
|
-
|
|
142
|
-
|
|
190
|
+
const errors = [];
|
|
191
|
+
for (const [binding, instance] of pairs) {
|
|
192
|
+
try {
|
|
193
|
+
await this.#lifecycle.runDeactivation(binding, instance, reader);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
errors.push(error);
|
|
197
|
+
}
|
|
143
198
|
}
|
|
199
|
+
throwCollected(errors, "unbind completed, but deactivation hooks threw");
|
|
200
|
+
}
|
|
201
|
+
#unbindSync(tokenOrId) {
|
|
202
|
+
this.#deactivatePairsSync(this.#collectDeactivationPairs(tokenOrId));
|
|
203
|
+
}
|
|
204
|
+
async unbindAsync(tokenOrId) {
|
|
205
|
+
this.#assertNotDisposed();
|
|
206
|
+
await this.#deactivatePairs(this.#collectDeactivationPairs(tokenOrId));
|
|
144
207
|
}
|
|
145
208
|
unbindAll() {
|
|
146
209
|
this.#assertNotDisposed();
|
|
147
|
-
|
|
148
|
-
for (const [binding, instance] of this.#drainSingletons(this.#registry.clear())) {
|
|
149
|
-
this.#lifecycle.runDeactivationSync(binding, instance, reader);
|
|
150
|
-
}
|
|
210
|
+
this.#deactivatePairsSync(this.#drainSingletons(this.#registry.clear()));
|
|
151
211
|
}
|
|
152
212
|
async unbindAllAsync() {
|
|
153
213
|
this.#assertNotDisposed();
|
|
154
|
-
|
|
155
|
-
for (const [binding, instance] of this.#drainSingletons(this.#registry.clear())) {
|
|
156
|
-
await this.#lifecycle.runDeactivation(binding, instance, reader);
|
|
157
|
-
}
|
|
214
|
+
await this.#deactivatePairs(this.#drainSingletons(this.#registry.clear()));
|
|
158
215
|
}
|
|
159
216
|
rebind(token) {
|
|
160
217
|
this.#assertNotDisposed();
|
|
@@ -165,7 +222,7 @@ class DefaultContainer {
|
|
|
165
222
|
this.#unbindSync(token);
|
|
166
223
|
return this.#createBindToBuilder(token);
|
|
167
224
|
}
|
|
168
|
-
// ── Module
|
|
225
|
+
// ── Module ─────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
169
226
|
load(...modules) {
|
|
170
227
|
this.#assertNotDisposed();
|
|
171
228
|
this.#loadSyncModules(modules);
|
|
@@ -186,15 +243,42 @@ class DefaultContainer {
|
|
|
186
243
|
}
|
|
187
244
|
moduleRefs.set(moduleRef, 1);
|
|
188
245
|
const builder = this.#createModuleBuilder(moduleRef);
|
|
189
|
-
|
|
246
|
+
try {
|
|
247
|
+
module[MODULE_SETUP](builder);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
throw this.#rollbackFailedLoadSync(moduleRef, error);
|
|
251
|
+
}
|
|
190
252
|
}
|
|
191
253
|
}
|
|
192
254
|
async loadAsync(...modules) {
|
|
193
255
|
this.#assertNotDisposed();
|
|
194
|
-
|
|
256
|
+
// Deduped by identity like the sync lane, so `loadAsync(m, m)` ref-counts once.
|
|
257
|
+
for (const module of new Set(modules)) {
|
|
195
258
|
await this.#loadOneModuleAsync(module);
|
|
196
259
|
}
|
|
197
260
|
}
|
|
261
|
+
/** Undo a failed load — bindings registered before the throw and the ref-count both roll back. */
|
|
262
|
+
#rollbackFailedLoadSync(moduleRef, cause) {
|
|
263
|
+
const pairs = this.#removeModuleBindings(moduleRef);
|
|
264
|
+
try {
|
|
265
|
+
this.#deactivatePairsSync(pairs);
|
|
266
|
+
}
|
|
267
|
+
catch (rollbackError) {
|
|
268
|
+
return new AggregateError([cause, rollbackError], "module load failed, and rolling it back threw too");
|
|
269
|
+
}
|
|
270
|
+
return cause;
|
|
271
|
+
}
|
|
272
|
+
async #rollbackFailedLoad(moduleRef, cause) {
|
|
273
|
+
const pairs = this.#removeModuleBindings(moduleRef);
|
|
274
|
+
try {
|
|
275
|
+
await this.#deactivatePairs(pairs);
|
|
276
|
+
}
|
|
277
|
+
catch (rollbackError) {
|
|
278
|
+
return new AggregateError([cause, rollbackError], "module load failed, and rolling it back threw too");
|
|
279
|
+
}
|
|
280
|
+
return cause;
|
|
281
|
+
}
|
|
198
282
|
async #loadOneModuleAsync(module) {
|
|
199
283
|
const moduleRef = module;
|
|
200
284
|
const moduleRefs = (this.#moduleRefs ??= new Map());
|
|
@@ -206,15 +290,28 @@ class DefaultContainer {
|
|
|
206
290
|
moduleRefs.set(moduleRef, 1);
|
|
207
291
|
if (isSyncModule(module)) {
|
|
208
292
|
const builder = this.#createModuleBuilder(moduleRef);
|
|
209
|
-
|
|
293
|
+
try {
|
|
294
|
+
module[MODULE_SETUP](builder);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
throw this.#rollbackFailedLoadSync(moduleRef, error);
|
|
298
|
+
}
|
|
210
299
|
}
|
|
211
300
|
else {
|
|
212
301
|
const importPromises = [];
|
|
213
302
|
const builder = this.#createAsyncModuleBuilder(moduleRef, importPromises);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
303
|
+
try {
|
|
304
|
+
await module[MODULE_SETUP](builder);
|
|
305
|
+
// Await nested async imports triggered inside the setup callback
|
|
306
|
+
if (importPromises.length > 0) {
|
|
307
|
+
await Promise.all(importPromises);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
// Nested imports may still be registering — let them settle before rolling back, so no
|
|
312
|
+
// registration lands after the rejection and none becomes an unhandled rejection.
|
|
313
|
+
await Promise.allSettled(importPromises);
|
|
314
|
+
throw await this.#rollbackFailedLoad(moduleRef, error);
|
|
218
315
|
}
|
|
219
316
|
}
|
|
220
317
|
}
|
|
@@ -249,27 +346,19 @@ class DefaultContainer {
|
|
|
249
346
|
this.#moduleRefs?.delete(ref);
|
|
250
347
|
const ids = this.#moduleBindingIds?.get(ref) ?? [];
|
|
251
348
|
this.#moduleBindingIds?.delete(ref);
|
|
252
|
-
const
|
|
349
|
+
const removed = [];
|
|
253
350
|
for (const id of ids) {
|
|
254
|
-
const binding = this.#registry.
|
|
351
|
+
const binding = this.#registry.removeById(id);
|
|
255
352
|
if (binding !== undefined) {
|
|
256
|
-
|
|
257
|
-
if (binding.instance !== NO_INSTANCE) {
|
|
258
|
-
pairs.push([binding, binding.instance]);
|
|
259
|
-
this.#scope.deleteSingleton(binding);
|
|
260
|
-
}
|
|
261
|
-
this.#scope.deleteScoped(binding.id);
|
|
353
|
+
removed.push(binding);
|
|
262
354
|
}
|
|
263
355
|
}
|
|
264
|
-
return
|
|
356
|
+
return this.#drainSingletons(removed);
|
|
265
357
|
}
|
|
266
358
|
#unloadModuleSync(ref) {
|
|
267
359
|
const count = this.#moduleRefs?.get(ref) ?? 0;
|
|
268
360
|
if (count <= 1) {
|
|
269
|
-
|
|
270
|
-
for (const [binding, instance] of this.#removeModuleBindings(ref)) {
|
|
271
|
-
this.#lifecycle.runDeactivationSync(binding, instance, reader);
|
|
272
|
-
}
|
|
361
|
+
this.#deactivatePairsSync(this.#removeModuleBindings(ref));
|
|
273
362
|
}
|
|
274
363
|
else {
|
|
275
364
|
this.#moduleRefs.set(ref, count - 1);
|
|
@@ -284,10 +373,7 @@ class DefaultContainer {
|
|
|
284
373
|
async #unloadModuleAsync(ref) {
|
|
285
374
|
const count = this.#moduleRefs?.get(ref) ?? 0;
|
|
286
375
|
if (count <= 1) {
|
|
287
|
-
|
|
288
|
-
for (const [binding, instance] of this.#removeModuleBindings(ref)) {
|
|
289
|
-
await this.#lifecycle.runDeactivation(binding, instance, reader);
|
|
290
|
-
}
|
|
376
|
+
await this.#deactivatePairs(this.#removeModuleBindings(ref));
|
|
291
377
|
}
|
|
292
378
|
else {
|
|
293
379
|
this.#moduleRefs.set(ref, count - 1);
|
|
@@ -297,21 +383,11 @@ class DefaultContainer {
|
|
|
297
383
|
this.#assertNotDisposed();
|
|
298
384
|
const entries = registry.entries();
|
|
299
385
|
for (const { target, scope } of entries) {
|
|
300
|
-
|
|
301
|
-
const bindingBuilder = builder.toSelf();
|
|
302
|
-
if (scope === "singleton") {
|
|
303
|
-
bindingBuilder.singleton();
|
|
304
|
-
}
|
|
305
|
-
else if (scope === "scoped") {
|
|
306
|
-
bindingBuilder.scoped();
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
bindingBuilder.transient();
|
|
310
|
-
}
|
|
386
|
+
APPLY_BINDING_SCOPE[scope](this.#createBindToBuilder(target).toSelf());
|
|
311
387
|
}
|
|
312
388
|
return entries.length;
|
|
313
389
|
}
|
|
314
|
-
// ── Lifecycle hooks
|
|
390
|
+
// ── Lifecycle hooks ────────────────────────────────────────────────────────────────────────────────────────────────
|
|
315
391
|
onActivation(token, handler) {
|
|
316
392
|
this.#assertNotDisposed();
|
|
317
393
|
this.#lifecycle.registerActivation(token, handler);
|
|
@@ -320,69 +396,97 @@ class DefaultContainer {
|
|
|
320
396
|
this.#assertNotDisposed();
|
|
321
397
|
this.#lifecycle.registerDeactivation(token, handler);
|
|
322
398
|
}
|
|
323
|
-
// ── Resolution
|
|
399
|
+
// ── Resolution ─────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
324
400
|
resolve(token, options) {
|
|
325
401
|
this.#assertNotDisposed();
|
|
326
402
|
const rootStack = this.#resolver.rootStack;
|
|
327
|
-
// A resolve already holding the shared
|
|
403
|
+
// A resolve already holding the shared stack means this one is nested; it mints its own.
|
|
328
404
|
if (rootStack.length !== 0) {
|
|
329
405
|
return options === undefined
|
|
330
|
-
? this.#resolver.resolveFromContext(token, []
|
|
331
|
-
: this.#resolver.resolve(token, options, []
|
|
406
|
+
? this.#resolver.resolveFromContext(token, [])
|
|
407
|
+
: this.#resolver.resolve(token, options, []);
|
|
332
408
|
}
|
|
333
409
|
if (options === undefined) {
|
|
334
|
-
return this.#resolver.resolveFromContext(token,
|
|
410
|
+
return this.#resolver.resolveFromContext(token, rootStack);
|
|
335
411
|
}
|
|
336
|
-
return this.#resolver.resolve(token, options,
|
|
412
|
+
return this.#resolver.resolve(token, options, rootStack);
|
|
337
413
|
}
|
|
338
414
|
resolveAsync(token, options) {
|
|
339
415
|
this.#assertNotDisposed();
|
|
340
416
|
if (options === undefined) {
|
|
341
417
|
return this.#resolver.resolveAsyncFromRoot(token);
|
|
342
418
|
}
|
|
343
|
-
return this.#resolver.resolveAsync(token, options, [],
|
|
419
|
+
return this.#resolver.resolveAsync(token, options, [], ROOT_BRANCH);
|
|
344
420
|
}
|
|
345
421
|
resolveOptional(token, options) {
|
|
346
422
|
this.#assertNotDisposed();
|
|
347
423
|
const rootStack = this.#resolver.rootStack;
|
|
348
424
|
return rootStack.length === 0
|
|
349
|
-
? this.#resolver.resolveOptional(token, options,
|
|
350
|
-
: this.#resolver.resolveOptional(token, options, []
|
|
425
|
+
? this.#resolver.resolveOptional(token, options, rootStack)
|
|
426
|
+
: this.#resolver.resolveOptional(token, options, []);
|
|
351
427
|
}
|
|
352
428
|
resolveOptionalAsync(token, options) {
|
|
353
429
|
this.#assertNotDisposed();
|
|
354
|
-
return this.#resolver.resolveOptionalAsync(token, options, [],
|
|
430
|
+
return this.#resolver.resolveOptionalAsync(token, options, [], ROOT_BRANCH);
|
|
355
431
|
}
|
|
356
432
|
resolveAll(token, options) {
|
|
357
433
|
this.#assertNotDisposed();
|
|
358
434
|
const rootStack = this.#resolver.rootStack;
|
|
359
435
|
return rootStack.length === 0
|
|
360
|
-
? this.#resolver.resolveAll(token, options,
|
|
361
|
-
: this.#resolver.resolveAll(token, options, []
|
|
436
|
+
? this.#resolver.resolveAll(token, options, rootStack)
|
|
437
|
+
: this.#resolver.resolveAll(token, options, []);
|
|
362
438
|
}
|
|
363
439
|
resolveAllAsync(token, options) {
|
|
364
440
|
this.#assertNotDisposed();
|
|
365
|
-
return this.#resolver.resolveAllAsync(token, options, [],
|
|
441
|
+
return this.#resolver.resolveAllAsync(token, options, [], ROOT_BRANCH);
|
|
366
442
|
}
|
|
367
|
-
// ── Child
|
|
443
|
+
// ── Child ──────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
368
444
|
createChild() {
|
|
369
445
|
this.#assertNotDisposed();
|
|
370
446
|
return new DefaultContainer(this);
|
|
371
447
|
}
|
|
372
|
-
// ── Dispose
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
448
|
+
// ── Dispose ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
449
|
+
dispose() {
|
|
450
|
+
// One teardown, shared: a second caller awaits the same run instead of returning early while
|
|
451
|
+
// hooks from the first are still executing.
|
|
452
|
+
return (this.#disposePromise ??= this.#runDispose());
|
|
453
|
+
}
|
|
454
|
+
async #runDispose() {
|
|
377
455
|
this.#disposed = true;
|
|
378
|
-
//
|
|
456
|
+
// Refuses new materializations immediately; in-flight ones are drained just below.
|
|
457
|
+
this.#scope.markClosed();
|
|
458
|
+
await this.#scope.settleInflight();
|
|
379
459
|
const reader = this.#getMetadataReader();
|
|
460
|
+
const errors = [];
|
|
380
461
|
// Iterate a copy: a deactivation handler is user code, and the live list is what
|
|
381
|
-
// materializing or dropping a singleton mutates.
|
|
382
|
-
|
|
383
|
-
|
|
462
|
+
// materializing or dropping a singleton mutates. Reverse materialization order, so a
|
|
463
|
+
// dependent tears down before the dependencies it may still reach through.
|
|
464
|
+
const cached = this.#scope.cachedSingletons().slice();
|
|
465
|
+
for (let index = cached.length - 1; index >= 0; index -= 1) {
|
|
466
|
+
const binding = cached[index];
|
|
467
|
+
try {
|
|
468
|
+
await this.#lifecycle.runDeactivation(binding, binding.instance, reader);
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
errors.push(error);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// A constant never reaches the singleton cache unless activation put it there, so its hook is
|
|
475
|
+
// owed from the registry instead — and only a container that has held one pays for looking.
|
|
476
|
+
if (this.#registry.hasHeldConstantBinding) {
|
|
477
|
+
for (const binding of this.#registry.allBindings()) {
|
|
478
|
+
if (binding.instance === NO_INSTANCE && this.#owesConstantDeactivation(binding)) {
|
|
479
|
+
try {
|
|
480
|
+
await this.#lifecycle.runDeactivation(binding, binding.value, reader);
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
errors.push(error);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
384
487
|
}
|
|
385
488
|
this.#scope.clearAll();
|
|
489
|
+
throwCollected(errors, "dispose() completed, but deactivation hooks threw");
|
|
386
490
|
}
|
|
387
491
|
[Symbol.asyncDispose]() {
|
|
388
492
|
return this.dispose();
|
|
@@ -390,7 +494,7 @@ class DefaultContainer {
|
|
|
390
494
|
[Symbol.dispose]() {
|
|
391
495
|
throw new SyncDisposalNotSupportedError();
|
|
392
496
|
}
|
|
393
|
-
// ── Initialization
|
|
497
|
+
// ── Initialization ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
394
498
|
async initializeAsync() {
|
|
395
499
|
this.#assertNotDisposed();
|
|
396
500
|
const allBindings = this.#registry.allBindings();
|
|
@@ -403,8 +507,11 @@ class DefaultContainer {
|
|
|
403
507
|
if (binding.predicate !== undefined) {
|
|
404
508
|
continue;
|
|
405
509
|
}
|
|
406
|
-
//
|
|
407
|
-
|
|
510
|
+
// A constant is already its own instance, so only an activation hook gives the warm-up
|
|
511
|
+
// something to run — and a container-level hook counts as one just as a per-binding hook does.
|
|
512
|
+
if (binding.kind === "constant" &&
|
|
513
|
+
binding.onActivation === undefined &&
|
|
514
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
408
515
|
continue;
|
|
409
516
|
}
|
|
410
517
|
const slotOptions = bindingSlotToResolveOptions(binding.slot);
|
|
@@ -412,7 +519,7 @@ class DefaultContainer {
|
|
|
412
519
|
}
|
|
413
520
|
}
|
|
414
521
|
}
|
|
415
|
-
// ── Validate
|
|
522
|
+
// ── Validate ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
416
523
|
validate() {
|
|
417
524
|
this.#assertNotDisposed();
|
|
418
525
|
const reader = this.#getMetadataReader();
|
|
@@ -423,6 +530,48 @@ class DefaultContainer {
|
|
|
423
530
|
}
|
|
424
531
|
this.#validateSingletonBindingGraph(binding, reader);
|
|
425
532
|
}
|
|
533
|
+
for (const [hookToken, phase] of this.#lifecycle.hookedTokens()) {
|
|
534
|
+
if (!this.#isBoundInChain(hookToken)) {
|
|
535
|
+
throw new UnreachableLifecycleHookError(tokenName(hookToken), phase);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
this.#validateConstraintRequirements(allBindings);
|
|
539
|
+
}
|
|
540
|
+
/** A constraint waiting on a slot name no binding declares can never hold. */
|
|
541
|
+
#validateConstraintRequirements(allBindings) {
|
|
542
|
+
let declaredSlotNames;
|
|
543
|
+
for (const binding of allBindings) {
|
|
544
|
+
const { predicate } = binding;
|
|
545
|
+
if (predicate === undefined) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
const requirement = constraintRequirementOf(predicate);
|
|
549
|
+
if (requirement === undefined) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
declaredSlotNames ??= this.#slotNamesInChain();
|
|
553
|
+
if (!declaredSlotNames.has(requirement.name)) {
|
|
554
|
+
throw new UnreachableConstraintError(tokenName(binding.token), requirement.name, requirement.helperName);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/** Every slot name declared anywhere a resolve through this container could reach. */
|
|
559
|
+
#slotNamesInChain() {
|
|
560
|
+
const names = this.#parent === undefined ? new Set() : this.#parent.#slotNamesInChain();
|
|
561
|
+
for (const binding of this.#registry.allBindings()) {
|
|
562
|
+
if (binding.slot.name !== undefined) {
|
|
563
|
+
names.add(binding.slot.name);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return names;
|
|
567
|
+
}
|
|
568
|
+
// Ancestors count: a parent-owned binding is one this container can still resolve through.
|
|
569
|
+
#isBoundInChain(token) {
|
|
570
|
+
if (this.#registry.has(token)) {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
const parent = this.#parent;
|
|
574
|
+
return parent !== undefined && parent.#isBoundInChain(token);
|
|
426
575
|
}
|
|
427
576
|
#isSingletonStaticAnalyzableBinding(binding) {
|
|
428
577
|
if (effectiveBindingScope(binding) !== "singleton") {
|
|
@@ -432,7 +581,7 @@ class DefaultContainer {
|
|
|
432
581
|
}
|
|
433
582
|
/**
|
|
434
583
|
* DFS over explicit constructor / `toResolved*` dependency edges. Follows `toAlias` chains to the
|
|
435
|
-
* terminal binding for scope checks
|
|
584
|
+
* terminal binding for scope checks.
|
|
436
585
|
*
|
|
437
586
|
* @remarks A `toDynamic*` dependency is scope-checked like any other — its declared scope is what
|
|
438
587
|
* makes it captive — but the DFS does not descend into the factory, whose body is opaque.
|
|
@@ -531,7 +680,7 @@ class DefaultContainer {
|
|
|
531
680
|
}
|
|
532
681
|
return edges;
|
|
533
682
|
}
|
|
534
|
-
// ── Introspection
|
|
683
|
+
// ── Introspection ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
535
684
|
has(token, options) {
|
|
536
685
|
this.#assertNotDisposed();
|
|
537
686
|
return this.#getInspector().has(token, options, () => this.#parent?.has(token, options) ?? false);
|
|
@@ -552,21 +701,33 @@ class DefaultContainer {
|
|
|
552
701
|
this.#assertNotDisposed();
|
|
553
702
|
return buildDependencyGraph(this.#registry, this.#getMetadataReader(), options, this.#parent === undefined ? undefined : this.#parent.#registry);
|
|
554
703
|
}
|
|
555
|
-
// ── Internal
|
|
704
|
+
// ── Internal ───────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
556
705
|
#assertNotDisposed() {
|
|
557
706
|
if (this.#disposed) {
|
|
558
707
|
throw new DisposedContainerError();
|
|
559
708
|
}
|
|
560
709
|
}
|
|
561
710
|
}
|
|
562
|
-
|
|
711
|
+
/** Reports collected hook failures once teardown finished: the one error as itself, several aggregated. */
|
|
712
|
+
function throwCollected(errors, message) {
|
|
713
|
+
if (errors.length === 0) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (errors.length === 1) {
|
|
717
|
+
throw errors[0];
|
|
718
|
+
}
|
|
719
|
+
throw new AggregateError(errors, message);
|
|
720
|
+
}
|
|
721
|
+
// ── Container static ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
563
722
|
/**
|
|
564
723
|
* @since 0.3.16-canary.0
|
|
565
724
|
*/
|
|
566
725
|
export const Container = {
|
|
567
|
-
create() {
|
|
568
|
-
return new DefaultContainer();
|
|
726
|
+
create(options) {
|
|
727
|
+
return new DefaultContainer(undefined, options);
|
|
569
728
|
},
|
|
729
|
+
// Variadic modules leave no room for an options argument. A container that needs both is
|
|
730
|
+
// `Container.create(options)` followed by `load(...)`, which is what these two do anyway.
|
|
570
731
|
fromModules(...modules) {
|
|
571
732
|
const container = new DefaultContainer();
|
|
572
733
|
container.load(...modules);
|