@codefast/di 0.5.0 → 0.6.1
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 +712 -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 +62 -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 +281 -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 +53 -0
- package/dist/core/constraint-requirement.d.ts.map +1 -0
- package/dist/core/constraint-requirement.js +62 -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 +96 -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} +9 -5
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +55 -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} +37 -7
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/plan/instantiation-plan.js +419 -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 +581 -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 +79 -44
- package/src/container/container.ts +357 -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 +94 -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 +158 -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} +63 -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 +615 -0
- package/src/resolution/resolver.ts +663 -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
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { NO_INSTANCE } from "#/binding";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { NO_INSTANCE } from "#/core/binding";
|
|
2
|
+
import { NO_TAG_KEYS } from "#/core/tag";
|
|
3
|
+
import { tokenName } from "#/core/token";
|
|
4
|
+
import { AsyncActivationError, AsyncResolutionError, CircularDependencyError, DisposedContainerError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#/errors/errors";
|
|
5
|
+
import { isNameOnlyOptions, resolveOptionsForSlot, singleTagOnlyOf } from "#/injection/resolve-options";
|
|
6
|
+
import { SCOPED_MISS } from "#/lifecycle/scope-manager";
|
|
7
|
+
import { ActivationNeedCache } from "#/resolution/cache/activation-need";
|
|
8
|
+
import { BindingLookupCache } from "#/resolution/cache/binding-lookup-cache";
|
|
9
|
+
import { ClassIntrospector } from "#/resolution/cache/class-introspector";
|
|
10
|
+
import { AsyncCascadeContext, AsyncLevelContext, buildResolutionFrame, DefaultResolutionContext, } from "#/resolution/context";
|
|
11
|
+
import { branchDepthOf, cycleNamesOf, enterResolutionPath, extendResolutionBranch, ROOT_BRANCH, UNOWNED_BRANCH, } from "#/resolution/path/resolution-path";
|
|
12
|
+
import { InstantiationPlanCompiler, PLAN_RETRY } from "#/resolution/plan/instantiation-plan";
|
|
13
|
+
import { matchesSlot, requestedTagKeyMask, selectAllBindings, selectBinding } from "#/resolution/select/binding-select";
|
|
14
|
+
// Where a multi-tag resolve switches from scanning the token's list to walking the tag indexes.
|
|
15
|
+
const MULTI_TAG_INDEX_THRESHOLD = 8;
|
|
12
16
|
const EMPTY_STRING_LIST = [];
|
|
13
17
|
const EMPTY_FRAME_LIST = [];
|
|
14
18
|
const EMPTY_PARAM_LIST = [];
|
|
@@ -24,26 +28,29 @@ const ROOT_CONSTRAINT_CONTEXT = {
|
|
|
24
28
|
*/
|
|
25
29
|
export class DependencyResolver {
|
|
26
30
|
#syncResolutionContextPool = [];
|
|
31
|
+
// Contexts bound to the cascade pair — deferred: only an async cascade's sync resolves need it.
|
|
32
|
+
#cascadeContextPool;
|
|
27
33
|
/**
|
|
28
|
-
* The
|
|
34
|
+
* The stack a top-level **sync** resolve reuses instead of minting an array per call.
|
|
29
35
|
*
|
|
30
|
-
* @remarks Read directly rather than through an accessor
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* context skip storing pointers it already holds.
|
|
36
|
+
* @remarks Read directly rather than through an accessor: a shallow resolve is one top-level
|
|
37
|
+
* call, so a call there is not amortised over anything. Every sync lane pops what it pushes, so
|
|
38
|
+
* `rootStack.length === 0` means no resolve holds the stack; async appends without popping and
|
|
39
|
+
* mints its own. Keeping it stable is also what lets a pooled context skip re-storing it.
|
|
35
40
|
*/
|
|
36
|
-
rootPath = [];
|
|
37
41
|
rootStack = [];
|
|
38
|
-
// The open synchronous factory cascade: its
|
|
42
|
+
// The open synchronous factory cascade: its stack is the ancestor chain, and it is balanced
|
|
39
43
|
// because synchronous code does not interleave.
|
|
40
|
-
#cascadePath = [];
|
|
41
44
|
#cascadeStack = [];
|
|
42
45
|
#cascadeContext;
|
|
43
46
|
// Compiled plans; `null` marks a binding as unplannable under the current cache versions.
|
|
44
47
|
#classPlanByBindingId = new Map();
|
|
45
48
|
#classPlanRegistryVersion = -1;
|
|
46
49
|
#classPlanActivationVersion = -1;
|
|
50
|
+
// The async lane's plans, stamped and invalidated apart so neither lane pays the other's misses.
|
|
51
|
+
#asyncPlanByBindingId = new Map();
|
|
52
|
+
#asyncPlanRegistryVersion = -1;
|
|
53
|
+
#asyncPlanActivationVersion = -1;
|
|
47
54
|
#registry;
|
|
48
55
|
#scope;
|
|
49
56
|
#lifecycle;
|
|
@@ -62,7 +69,11 @@ export class DependencyResolver {
|
|
|
62
69
|
this.#classes = new ClassIntrospector(metadataReader, container);
|
|
63
70
|
this.#activation = new ActivationNeedCache(lifecycle, this.#classes, registry);
|
|
64
71
|
}
|
|
65
|
-
/**
|
|
72
|
+
/** The reader this resolver was built with, which is the one its container answers with. */
|
|
73
|
+
get metadataReader() {
|
|
74
|
+
return this.#metadataReader;
|
|
75
|
+
}
|
|
76
|
+
/** Structural counts for the {@link ResolutionDiagnostics} a container reports. */
|
|
66
77
|
describeCaches() {
|
|
67
78
|
let compiledPlanCount = 0;
|
|
68
79
|
for (const plan of this.#classPlanByBindingId.values()) {
|
|
@@ -70,13 +81,20 @@ export class DependencyResolver {
|
|
|
70
81
|
compiledPlanCount += 1;
|
|
71
82
|
}
|
|
72
83
|
}
|
|
84
|
+
let compiledAsyncPlanCount = 0;
|
|
85
|
+
for (const plan of this.#asyncPlanByBindingId.values()) {
|
|
86
|
+
if (plan !== null) {
|
|
87
|
+
compiledAsyncPlanCount += 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
73
90
|
return {
|
|
74
91
|
compiledPlanCount,
|
|
92
|
+
compiledAsyncPlanCount,
|
|
75
93
|
syncContextPoolSize: this.#syncResolutionContextPool.length,
|
|
76
94
|
};
|
|
77
95
|
}
|
|
78
|
-
// ── Binding lookup
|
|
79
|
-
#findBinding(token, options,
|
|
96
|
+
// ── Binding lookup ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
#findBinding(token, options, resolutionStack) {
|
|
80
98
|
if (options === undefined) {
|
|
81
99
|
const fastDefaultBinding = this.#registry.getFastDefault(token);
|
|
82
100
|
if (fastDefaultBinding !== undefined) {
|
|
@@ -85,35 +103,49 @@ export class DependencyResolver {
|
|
|
85
103
|
}
|
|
86
104
|
else if (isNameOnlyOptions(options)) {
|
|
87
105
|
const namedBinding = this.#registry.getSimpleNamed(token, options.name);
|
|
88
|
-
if (namedBinding !== undefined &&
|
|
89
|
-
this.#matchesBindingFast(namedBinding, options, resolutionPath, resolutionStack)) {
|
|
106
|
+
if (namedBinding !== undefined && this.#matchesBindingFast(namedBinding, options, resolutionStack)) {
|
|
90
107
|
return { binding: namedBinding, owner: this };
|
|
91
108
|
}
|
|
92
109
|
}
|
|
93
110
|
else {
|
|
94
111
|
const singleTag = singleTagOnlyOf(options);
|
|
95
112
|
if (singleTag !== undefined) {
|
|
96
|
-
const tagged = this.#registry.getSimpleTagged(token, singleTag
|
|
97
|
-
if (tagged !== undefined &&
|
|
113
|
+
const tagged = this.#registry.getSimpleTagged(token, singleTag);
|
|
114
|
+
if (tagged !== undefined && this.#satisfiesPredicate(tagged, options, resolutionStack)) {
|
|
98
115
|
return { binding: tagged, owner: this };
|
|
99
116
|
}
|
|
100
117
|
}
|
|
118
|
+
else if (options.name === undefined &&
|
|
119
|
+
// A threshold switches the data structure, never the semantics: under it the generic scan
|
|
120
|
+
// below beats walking the indexes, and both paths answer identically. Sized first, so a
|
|
121
|
+
// small list pays one length read and nothing else.
|
|
122
|
+
this.#registry.getAll(token).length > MULTI_TAG_INDEX_THRESHOLD &&
|
|
123
|
+
requestedTagKeyMask(options) !== NO_TAG_KEYS) {
|
|
124
|
+
// A name-less multi-tag request matches only name-less tagged slots, and every such slot
|
|
125
|
+
// lives in one of the two tag indexes — so their union is the whole candidate set and the
|
|
126
|
+
// token's full list never needs scanning. Selection still owns predicates and specificity.
|
|
127
|
+
const selected = this.#selectMultiTagged(token, options, resolutionStack);
|
|
128
|
+
if (selected !== undefined) {
|
|
129
|
+
return { binding: selected, owner: this };
|
|
130
|
+
}
|
|
131
|
+
return this.#parent === undefined ? undefined : this.#parent.#findBinding(token, options, resolutionStack);
|
|
132
|
+
}
|
|
101
133
|
}
|
|
102
134
|
const bindings = this.#registry.getAll(token);
|
|
103
135
|
if (bindings.length > 0) {
|
|
104
136
|
// A lone candidate is its own selection: matching it is the whole decision, with no
|
|
105
137
|
// specificity to weigh and no ambiguity to report.
|
|
106
138
|
const selected = bindings.length === 1
|
|
107
|
-
? this.#matchesBindingFast(bindings[0], options,
|
|
139
|
+
? this.#matchesBindingFast(bindings[0], options, resolutionStack)
|
|
108
140
|
? bindings[0]
|
|
109
141
|
: undefined
|
|
110
|
-
: selectBinding(bindings, options, this.#makeConstraintContext(
|
|
142
|
+
: selectBinding(bindings, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
111
143
|
if (selected !== undefined) {
|
|
112
144
|
return { binding: selected, owner: this };
|
|
113
145
|
}
|
|
114
146
|
}
|
|
115
147
|
if (this.#parent !== undefined) {
|
|
116
|
-
return this.#parent.#findBinding(token, options,
|
|
148
|
+
return this.#parent.#findBinding(token, options, resolutionStack);
|
|
117
149
|
}
|
|
118
150
|
return undefined;
|
|
119
151
|
}
|
|
@@ -123,10 +155,10 @@ export class DependencyResolver {
|
|
|
123
155
|
* @remarks Alias hops are followed iteratively with exact cycle detection — a revisited alias
|
|
124
156
|
* token raises {@link CircularDependencyError} instead of overflowing the call stack.
|
|
125
157
|
*/
|
|
126
|
-
#requireBinding(token, options,
|
|
158
|
+
#requireBinding(token, options, resolutionStack) {
|
|
127
159
|
let currentToken = token;
|
|
128
160
|
let visitedAliasTokens;
|
|
129
|
-
let found = this.#findBinding(currentToken, options,
|
|
161
|
+
let found = this.#findBinding(currentToken, options, resolutionStack);
|
|
130
162
|
while (found !== undefined && found.binding.kind === "alias") {
|
|
131
163
|
const target = found.binding.target;
|
|
132
164
|
visitedAliasTokens ??= new Set([currentToken]);
|
|
@@ -135,7 +167,7 @@ export class DependencyResolver {
|
|
|
135
167
|
}
|
|
136
168
|
visitedAliasTokens.add(target);
|
|
137
169
|
currentToken = target;
|
|
138
|
-
found = this.#findBinding(currentToken, options,
|
|
170
|
+
found = this.#findBinding(currentToken, options, resolutionStack);
|
|
139
171
|
}
|
|
140
172
|
if (found === undefined) {
|
|
141
173
|
// Thrown here rather than from a helper: the error captures this stack, and an error path is
|
|
@@ -151,40 +183,42 @@ export class DependencyResolver {
|
|
|
151
183
|
* Binding lookup aligned with `resolve` — used by `Container.validate` without instantiating.
|
|
152
184
|
*/
|
|
153
185
|
peekBindingForValidate(token, options) {
|
|
154
|
-
return this.#findBinding(token, options, []
|
|
186
|
+
return this.#findBinding(token, options, []);
|
|
155
187
|
}
|
|
156
188
|
/**
|
|
157
189
|
* Mirrors {@link DependencyResolver.resolveAll} candidate selection only (no instantiation).
|
|
158
190
|
*/
|
|
159
191
|
peekCandidateBindingsForValidate(token, options) {
|
|
160
|
-
return this.#candidateBindings(token, options, []
|
|
192
|
+
return this.#candidateBindings(token, options, []);
|
|
161
193
|
}
|
|
162
|
-
// ── Sync resolve
|
|
163
|
-
resolveFromContext(token,
|
|
194
|
+
// ── Sync resolve ───────────────────────────────────────────────────────────────────────────────────────────────────
|
|
195
|
+
resolveFromContext(token, resolutionStack) {
|
|
164
196
|
// Hot lane: own-registry fast default. Fall back to the chain-versioned memo
|
|
165
197
|
// (parent-chain walk + alias folding) only on miss or alias.
|
|
166
198
|
const fastBinding = this.#registry.getFastDefault(token);
|
|
167
199
|
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
168
|
-
return this.#resolveDefaultEntry(fastBinding, this,
|
|
200
|
+
return this.#resolveDefaultEntry(fastBinding, this, resolutionStack);
|
|
169
201
|
}
|
|
170
202
|
const entry = this.#lookup.defaultEntry(token);
|
|
171
203
|
if (entry === null) {
|
|
172
|
-
return this.resolve(token, undefined,
|
|
204
|
+
return this.resolve(token, undefined, resolutionStack);
|
|
173
205
|
}
|
|
174
|
-
return this.#resolveDefaultEntry(entry.binding, entry.owner,
|
|
206
|
+
return this.#resolveDefaultEntry(entry.binding, entry.owner, resolutionStack);
|
|
175
207
|
}
|
|
176
|
-
#resolveDefaultEntry(binding, owner,
|
|
208
|
+
#resolveDefaultEntry(binding, owner, resolutionStack) {
|
|
177
209
|
const scope = binding.scope;
|
|
178
210
|
if (scope === "transient") {
|
|
179
211
|
if (binding.kind === "dynamic") {
|
|
180
|
-
|
|
212
|
+
// Container-level hooks belong to the binding's owner — a child-registered hook must not
|
|
213
|
+
// fire for a parent-owned binding, and the owner's must.
|
|
214
|
+
const containerHooks = owner.#lifecycle.activationVersion === 0 ? undefined : owner.#lifecycle.activationHandlersFor(binding.token);
|
|
181
215
|
if (binding.onActivation === undefined && (containerHooks === undefined || containerHooks.length === 0)) {
|
|
182
|
-
return this.#resolveTransientDynamicSyncFromContext(binding,
|
|
216
|
+
return this.#resolveTransientDynamicSyncFromContext(binding, resolutionStack);
|
|
183
217
|
}
|
|
184
|
-
return this.#resolveTransientDynamicActivatedSync(binding, containerHooks,
|
|
218
|
+
return this.#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionStack);
|
|
185
219
|
}
|
|
186
220
|
// Compiled plans only run at the top level — inner levels keep the runtime cycle guard.
|
|
187
|
-
if ((binding.kind === "class" || binding.kind === "resolved") &&
|
|
221
|
+
if ((binding.kind === "class" || binding.kind === "resolved") && resolutionStack.length === 0) {
|
|
188
222
|
const plan = this.#getInstantiationPlan(binding);
|
|
189
223
|
if (plan !== null) {
|
|
190
224
|
return plan();
|
|
@@ -193,7 +227,7 @@ export class DependencyResolver {
|
|
|
193
227
|
}
|
|
194
228
|
else if (scope === "singleton") {
|
|
195
229
|
// A constant is a singleton that is already its own instance.
|
|
196
|
-
if (
|
|
230
|
+
if (owner.#isPlainConstant(binding)) {
|
|
197
231
|
return binding.value;
|
|
198
232
|
}
|
|
199
233
|
const cachedSingleton = binding.instance;
|
|
@@ -201,7 +235,7 @@ export class DependencyResolver {
|
|
|
201
235
|
return cachedSingleton;
|
|
202
236
|
}
|
|
203
237
|
if (owner !== this) {
|
|
204
|
-
return owner.#resolveBinding(binding, undefined,
|
|
238
|
+
return owner.#resolveBinding(binding, undefined, resolutionStack, owner);
|
|
205
239
|
}
|
|
206
240
|
}
|
|
207
241
|
else {
|
|
@@ -210,27 +244,26 @@ export class DependencyResolver {
|
|
|
210
244
|
return cachedScoped;
|
|
211
245
|
}
|
|
212
246
|
}
|
|
213
|
-
return this.#resolveBinding(binding, undefined,
|
|
247
|
+
return this.#resolveBinding(binding, undefined, resolutionStack, owner);
|
|
214
248
|
}
|
|
215
249
|
// Lean lane for an activated transient dynamic binding: same observable behavior as the
|
|
216
250
|
// generic #resolveBinding path (guard, frame, ctx, per-binding then container hooks) with
|
|
217
251
|
// the kind/activation dispatch resolved statically.
|
|
218
|
-
#resolveTransientDynamicActivatedSync(binding, containerHooks,
|
|
252
|
+
#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionStack) {
|
|
219
253
|
// Same O(1) cycle guard as the unhooked lane: this is still one sync call stack, so the flag
|
|
220
|
-
// *is* exact path membership
|
|
254
|
+
// *is* exact path membership.
|
|
221
255
|
const frame = this.#getResolutionFrame(binding);
|
|
222
256
|
const tokenDisplayName = frame.tokenName;
|
|
223
257
|
if (binding.inFlight) {
|
|
224
|
-
throw new CircularDependencyError(
|
|
258
|
+
throw new CircularDependencyError(cycleNamesOf(resolutionStack, tokenDisplayName));
|
|
225
259
|
}
|
|
226
260
|
binding.inFlight = true;
|
|
227
|
-
resolutionPath.push(tokenDisplayName);
|
|
228
261
|
resolutionStack.push(frame);
|
|
229
262
|
try {
|
|
230
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
263
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, undefined);
|
|
231
264
|
const factoryResult = binding.factory(resolutionCtx);
|
|
232
265
|
if (factoryResult instanceof Promise) {
|
|
233
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
266
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
234
267
|
}
|
|
235
268
|
let activated = factoryResult;
|
|
236
269
|
if (binding.onActivation !== undefined) {
|
|
@@ -253,13 +286,20 @@ export class DependencyResolver {
|
|
|
253
286
|
}
|
|
254
287
|
finally {
|
|
255
288
|
resolutionStack.pop();
|
|
256
|
-
resolutionPath.pop();
|
|
257
289
|
binding.inFlight = false;
|
|
258
290
|
}
|
|
259
291
|
}
|
|
292
|
+
/** Chain-summed activation version: a plan can inline a parent-owned binding, so a parent's hook registration must invalidate it. */
|
|
293
|
+
#chainActivationVersion() {
|
|
294
|
+
let version = this.#lifecycle.activationVersion;
|
|
295
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
296
|
+
version += current.#lifecycle.activationVersion;
|
|
297
|
+
}
|
|
298
|
+
return version;
|
|
299
|
+
}
|
|
260
300
|
#getInstantiationPlan(binding) {
|
|
261
301
|
const registryVersion = this.#lookup.chainVersion();
|
|
262
|
-
const activationVersion = this.#
|
|
302
|
+
const activationVersion = this.#chainActivationVersion();
|
|
263
303
|
if (registryVersion !== this.#classPlanRegistryVersion || activationVersion !== this.#classPlanActivationVersion) {
|
|
264
304
|
this.#classPlanByBindingId.clear();
|
|
265
305
|
this.#classPlanRegistryVersion = registryVersion;
|
|
@@ -279,7 +319,7 @@ export class DependencyResolver {
|
|
|
279
319
|
}
|
|
280
320
|
// Compiler behind #getInstantiationPlan — cold path, so the host indirection costs nothing hot.
|
|
281
321
|
#planCompiler = new InstantiationPlanCompiler({
|
|
282
|
-
hasActivationHandlers: (
|
|
322
|
+
hasActivationHandlers: (binding) => this.#ownerOf(binding).#lifecycle.hasActivationHandlers(binding.token),
|
|
283
323
|
knownPostConstruct: (target) => this.#classes.knownPostConstruct(target),
|
|
284
324
|
needsActiveContainer: (target) => this.#classes.needsActiveContainer(target),
|
|
285
325
|
getConstructorMetadata: (target) => this.#classes.constructorMetadata(target),
|
|
@@ -287,30 +327,85 @@ export class DependencyResolver {
|
|
|
287
327
|
const entry = this.#lookup.defaultEntry(token);
|
|
288
328
|
return entry === null ? null : { binding: entry.binding };
|
|
289
329
|
},
|
|
330
|
+
// Exactly what #findBinding's named lane accepts, minus the half that reads a path: a predicate
|
|
331
|
+
// is the compiler's cue to leave the selection to the runtime.
|
|
332
|
+
lookupPathIndependentNamedEntry: (token, options) => {
|
|
333
|
+
const entry = this.#lookup.namedEntry(token, options.name);
|
|
334
|
+
if (entry === null || entry.binding.predicate !== undefined || !matchesSlot(entry.binding.slot, options)) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return { binding: entry.binding };
|
|
338
|
+
},
|
|
339
|
+
// The named rule verbatim, on the single-tag lane's memo.
|
|
340
|
+
lookupPathIndependentTaggedEntry: (token, options) => {
|
|
341
|
+
const singleTag = singleTagOnlyOf(options);
|
|
342
|
+
if (singleTag === undefined) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const entry = this.#lookup.taggedEntry(token, singleTag);
|
|
346
|
+
if (entry === null || entry.binding.predicate !== undefined || !matchesSlot(entry.binding.slot, options)) {
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
return { binding: entry.binding };
|
|
350
|
+
},
|
|
290
351
|
getResolutionFrame: (binding) => this.#getResolutionFrame(binding),
|
|
291
352
|
// Dispatches exactly as #resolveDep does, so an escaped dep is indistinguishable
|
|
292
353
|
// from the same dep on a fully interpreted resolve.
|
|
293
|
-
resolveEscaped: (token, options, arity,
|
|
354
|
+
resolveEscaped: (token, options, arity, resolutionStack) => {
|
|
355
|
+
if (arity === "all") {
|
|
356
|
+
return this.resolveAll(token, options, resolutionStack);
|
|
357
|
+
}
|
|
358
|
+
if (arity === "optional") {
|
|
359
|
+
return this.resolveOptional(token, options, resolutionStack);
|
|
360
|
+
}
|
|
361
|
+
if (options === undefined) {
|
|
362
|
+
return this.resolveFromContext(token, resolutionStack);
|
|
363
|
+
}
|
|
364
|
+
return this.resolve(token, options, resolutionStack);
|
|
365
|
+
},
|
|
366
|
+
// Dispatches exactly as #resolveDepAsync does, for the async lane's escapes.
|
|
367
|
+
resolveEscapedAsync: (token, options, arity, resolutionStack) => {
|
|
294
368
|
if (arity === "all") {
|
|
295
|
-
return this.
|
|
369
|
+
return this.resolveAllAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
296
370
|
}
|
|
297
371
|
if (arity === "optional") {
|
|
298
|
-
return this.
|
|
372
|
+
return this.resolveOptionalAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
299
373
|
}
|
|
300
374
|
if (options === undefined) {
|
|
301
|
-
return this.
|
|
375
|
+
return this.resolveAsyncFromContext(token, resolutionStack, UNOWNED_BRANCH);
|
|
302
376
|
}
|
|
303
|
-
return this.
|
|
377
|
+
return this.resolveAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
304
378
|
},
|
|
305
379
|
});
|
|
306
|
-
|
|
380
|
+
/** The async lane's plan for a statically-visible transient binding, mirroring the sync getter. */
|
|
381
|
+
#getAsyncInstantiationPlan(binding) {
|
|
382
|
+
const registryVersion = this.#lookup.chainVersion();
|
|
383
|
+
const activationVersion = this.#chainActivationVersion();
|
|
384
|
+
if (registryVersion !== this.#asyncPlanRegistryVersion || activationVersion !== this.#asyncPlanActivationVersion) {
|
|
385
|
+
this.#asyncPlanByBindingId.clear();
|
|
386
|
+
this.#asyncPlanRegistryVersion = registryVersion;
|
|
387
|
+
this.#asyncPlanActivationVersion = activationVersion;
|
|
388
|
+
}
|
|
389
|
+
const cached = this.#asyncPlanByBindingId.get(binding.id);
|
|
390
|
+
if (cached !== undefined) {
|
|
391
|
+
return cached;
|
|
392
|
+
}
|
|
393
|
+
const compiled = this.#planCompiler.compileAsync(binding);
|
|
394
|
+
if (compiled === PLAN_RETRY) {
|
|
395
|
+
// Lifecycle metadata not discovered yet — the fallback resolve discovers it; retry then.
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
this.#asyncPlanByBindingId.set(binding.id, compiled);
|
|
399
|
+
return compiled;
|
|
400
|
+
}
|
|
401
|
+
resolve(token, options, resolutionStack) {
|
|
307
402
|
// Name-only fast lane: memoized lookup, dispatching just the shapes whose
|
|
308
403
|
// semantics involve no resolution context (constants, cached singletons).
|
|
309
404
|
if (options !== undefined && isNameOnlyOptions(options)) {
|
|
310
405
|
const namedEntry = this.#lookup.namedEntry(token, options.name);
|
|
311
406
|
if (namedEntry !== null) {
|
|
312
407
|
const namedBinding = namedEntry.binding;
|
|
313
|
-
if (
|
|
408
|
+
if (namedEntry.owner.#isPlainConstant(namedBinding)) {
|
|
314
409
|
return namedBinding.value;
|
|
315
410
|
}
|
|
316
411
|
if (namedBinding.scope === "singleton" && namedBinding.instance !== NO_INSTANCE) {
|
|
@@ -319,15 +414,32 @@ export class DependencyResolver {
|
|
|
319
414
|
// Everything else keeps the full path (context, activation, guards).
|
|
320
415
|
}
|
|
321
416
|
}
|
|
322
|
-
|
|
417
|
+
else if (options !== undefined) {
|
|
418
|
+
// Single-tag fast lane: the named lane's tagged twin, memoizing the chain walk.
|
|
419
|
+
const singleTag = singleTagOnlyOf(options);
|
|
420
|
+
if (singleTag !== undefined) {
|
|
421
|
+
const taggedEntry = this.#lookup.taggedEntry(token, singleTag);
|
|
422
|
+
if (taggedEntry !== null) {
|
|
423
|
+
const taggedBinding = taggedEntry.binding;
|
|
424
|
+
if (taggedEntry.owner.#isPlainConstant(taggedBinding)) {
|
|
425
|
+
return taggedBinding.value;
|
|
426
|
+
}
|
|
427
|
+
if (taggedBinding.scope === "singleton" && taggedBinding.instance !== NO_INSTANCE) {
|
|
428
|
+
return taggedBinding.instance;
|
|
429
|
+
}
|
|
430
|
+
// Everything else keeps the full path (context, activation, guards).
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
323
435
|
// A singleton owned by a parent resolver is resolved there, so the parent caches it.
|
|
324
436
|
if (binding.scope === "singleton" && owner !== this) {
|
|
325
|
-
return owner.#resolveBinding(binding, options,
|
|
437
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
326
438
|
}
|
|
327
|
-
return this.#resolveBinding(binding, options,
|
|
439
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
328
440
|
}
|
|
329
|
-
#resolveBinding(binding, options,
|
|
330
|
-
if (
|
|
441
|
+
#resolveBinding(binding, options, resolutionStack, owner) {
|
|
442
|
+
if (owner.#isPlainConstant(binding)) {
|
|
331
443
|
return binding.value;
|
|
332
444
|
}
|
|
333
445
|
const scope = binding.scope;
|
|
@@ -335,49 +447,70 @@ export class DependencyResolver {
|
|
|
335
447
|
if (binding.instance !== NO_INSTANCE) {
|
|
336
448
|
return binding.instance;
|
|
337
449
|
}
|
|
450
|
+
// An async materialization already in flight must not be raced by a second, sync one.
|
|
451
|
+
if (this.#scope.getInflight(binding.id) !== undefined) {
|
|
452
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenName(binding.token), tokenName(binding.token));
|
|
453
|
+
}
|
|
454
|
+
if (this.#scope.isClosed) {
|
|
455
|
+
throw new DisposedContainerError();
|
|
456
|
+
}
|
|
338
457
|
}
|
|
339
458
|
else if (scope === "scoped") {
|
|
340
459
|
const cachedScoped = this.#readScoped(binding);
|
|
341
460
|
if (cachedScoped !== SCOPED_MISS) {
|
|
342
461
|
return cachedScoped;
|
|
343
462
|
}
|
|
463
|
+
if (this.#scope.getInflight(binding.id) !== undefined) {
|
|
464
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenName(binding.token), tokenName(binding.token));
|
|
465
|
+
}
|
|
466
|
+
if (this.#scope.isClosed) {
|
|
467
|
+
throw new DisposedContainerError();
|
|
468
|
+
}
|
|
344
469
|
}
|
|
345
470
|
const frame = this.#getResolutionFrame(binding);
|
|
346
471
|
const tokenDisplayName = frame.tokenName;
|
|
347
|
-
const resolutionSet = enterResolutionPath(
|
|
348
|
-
resolutionStack.push(frame);
|
|
472
|
+
const resolutionSet = enterResolutionPath(resolutionStack, frame);
|
|
349
473
|
try {
|
|
350
|
-
const needsActivation =
|
|
474
|
+
const needsActivation = owner.#activation.needsActivation(binding);
|
|
351
475
|
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
352
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
476
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, options);
|
|
353
477
|
const dynamicResult = binding.factory(resolutionCtx);
|
|
354
478
|
if (dynamicResult instanceof Promise) {
|
|
355
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
479
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
356
480
|
}
|
|
357
481
|
return dynamicResult;
|
|
358
482
|
}
|
|
359
483
|
const resolutionCtx = needsActivation || requiresResolutionContext(binding)
|
|
360
|
-
? this.#acquireSyncResolutionContext(
|
|
484
|
+
? this.#acquireSyncResolutionContext(resolutionStack, options)
|
|
361
485
|
: undefined;
|
|
362
|
-
const instance = this.#instantiateSync(binding, resolutionCtx,
|
|
363
|
-
|
|
364
|
-
|
|
486
|
+
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionStack);
|
|
487
|
+
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
488
|
+
const activated = owner.#activation.refreshAfterFirstInstantiation(binding, needsActivation)
|
|
489
|
+
? owner.#lifecycle.runActivationSync(resolutionCtx, binding, instance, owner.#metadataReader)
|
|
365
490
|
: instance;
|
|
366
491
|
if (scope === "singleton") {
|
|
367
492
|
this.#scope.setSingleton(binding, activated);
|
|
368
493
|
}
|
|
369
494
|
else if (scope === "scoped") {
|
|
370
|
-
this.#scope.setScoped(binding
|
|
495
|
+
this.#scope.setScoped(binding, activated);
|
|
371
496
|
}
|
|
372
497
|
return activated;
|
|
373
498
|
}
|
|
374
499
|
finally {
|
|
375
500
|
resolutionStack.pop();
|
|
376
|
-
|
|
377
|
-
resolutionSet?.delete(tokenDisplayName);
|
|
501
|
+
resolutionSet?.delete(frame.bindingId);
|
|
378
502
|
}
|
|
379
503
|
}
|
|
380
|
-
|
|
504
|
+
/** Path-continuing resolution handed to the ambient slot while an accessor class constructs. */
|
|
505
|
+
#ambientResolutionFor(resolutionStack) {
|
|
506
|
+
return {
|
|
507
|
+
resolve: (token, options) => options === undefined
|
|
508
|
+
? this.resolveFromContext(token, resolutionStack)
|
|
509
|
+
: this.resolve(token, options, resolutionStack),
|
|
510
|
+
resolveOptional: (token, options) => this.resolveOptional(token, options, resolutionStack),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
#instantiateSync(binding, ctx, resolutionStack) {
|
|
381
514
|
switch (binding.kind) {
|
|
382
515
|
case "constant":
|
|
383
516
|
return binding.value;
|
|
@@ -387,26 +520,26 @@ export class DependencyResolver {
|
|
|
387
520
|
}
|
|
388
521
|
const factoryResult = binding.factory(ctx);
|
|
389
522
|
if (factoryResult instanceof Promise) {
|
|
390
|
-
throw
|
|
523
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
391
524
|
}
|
|
392
525
|
return factoryResult;
|
|
393
526
|
}
|
|
394
527
|
case "dynamic-async":
|
|
395
|
-
throw
|
|
528
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
396
529
|
case "class": {
|
|
397
|
-
const deps = this.#resolveDeps(this.#constructorParams(binding.target),
|
|
398
|
-
return this.#classes.instantiate(binding.target, deps);
|
|
530
|
+
const deps = this.#resolveDeps(this.#constructorParams(binding.target), resolutionStack);
|
|
531
|
+
return this.#classes.instantiate(binding.target, deps, this.#classes.needsActiveContainer(binding.target) ? this.#ambientResolutionFor(resolutionStack) : undefined);
|
|
399
532
|
}
|
|
400
533
|
case "resolved": {
|
|
401
|
-
const deps = this.#resolveDeps(binding.deps,
|
|
534
|
+
const deps = this.#resolveDeps(binding.deps, resolutionStack);
|
|
402
535
|
const factoryResult = binding.factory(...deps);
|
|
403
536
|
if (factoryResult instanceof Promise) {
|
|
404
|
-
throw
|
|
537
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
405
538
|
}
|
|
406
539
|
return factoryResult;
|
|
407
540
|
}
|
|
408
541
|
case "resolved-async":
|
|
409
|
-
throw
|
|
542
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
410
543
|
case "alias":
|
|
411
544
|
throw new InternalError("alias should have been followed before instantiation");
|
|
412
545
|
}
|
|
@@ -429,66 +562,73 @@ export class DependencyResolver {
|
|
|
429
562
|
}
|
|
430
563
|
// One dispatch table for both dependency sources — constructor params and `toResolved`
|
|
431
564
|
// descriptors declare the same four things.
|
|
432
|
-
#resolveDeps(deps,
|
|
565
|
+
#resolveDeps(deps, resolutionStack) {
|
|
433
566
|
const count = deps.length;
|
|
434
567
|
if (count === 0) {
|
|
435
568
|
return [];
|
|
436
569
|
}
|
|
437
570
|
if (count === 1) {
|
|
438
|
-
return [this.#resolveDep(deps[0],
|
|
571
|
+
return [this.#resolveDep(deps[0], resolutionStack)];
|
|
439
572
|
}
|
|
440
573
|
const resolved = new Array(count);
|
|
441
574
|
for (let index = 0; index < count; index += 1) {
|
|
442
|
-
resolved[index] = this.#resolveDep(deps[index],
|
|
575
|
+
resolved[index] = this.#resolveDep(deps[index], resolutionStack);
|
|
443
576
|
}
|
|
444
577
|
return resolved;
|
|
445
578
|
}
|
|
446
|
-
#resolveDep(dep,
|
|
447
|
-
const options =
|
|
579
|
+
#resolveDep(dep, resolutionStack) {
|
|
580
|
+
const options = resolveOptionsForSlot(dep);
|
|
448
581
|
if (dep.multi) {
|
|
449
|
-
return this.resolveAll(dep.token, options,
|
|
582
|
+
return this.resolveAll(dep.token, options, resolutionStack);
|
|
450
583
|
}
|
|
451
584
|
if (dep.optional) {
|
|
452
|
-
return this.resolveOptional(dep.token, options,
|
|
585
|
+
return this.resolveOptional(dep.token, options, resolutionStack);
|
|
453
586
|
}
|
|
454
587
|
if (options === undefined) {
|
|
455
|
-
return this.resolveFromContext(dep.token,
|
|
588
|
+
return this.resolveFromContext(dep.token, resolutionStack);
|
|
456
589
|
}
|
|
457
|
-
return this.resolve(dep.token, options,
|
|
590
|
+
return this.resolve(dep.token, options, resolutionStack);
|
|
458
591
|
}
|
|
459
|
-
resolveOptional(token, options,
|
|
460
|
-
|
|
592
|
+
resolveOptional(token, options, resolutionStack) {
|
|
593
|
+
const entry = this.#findBinding(token, options, resolutionStack);
|
|
594
|
+
if (entry === undefined) {
|
|
461
595
|
return undefined;
|
|
462
596
|
}
|
|
463
|
-
|
|
597
|
+
// Resolve the entry the probe found: re-looking the token up would evaluate every `when()`
|
|
598
|
+
// predicate a second time, and a changed answer would throw where `undefined` was promised.
|
|
599
|
+
const { binding, owner } = entry;
|
|
600
|
+
if (binding.kind === "alias") {
|
|
601
|
+
return this.resolve(token, options, resolutionStack);
|
|
602
|
+
}
|
|
603
|
+
if (binding.scope === "singleton" && owner !== this) {
|
|
604
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
605
|
+
}
|
|
606
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
464
607
|
}
|
|
465
|
-
resolveAll(token, options,
|
|
466
|
-
const candidates = this.#candidateBindings(token, options,
|
|
608
|
+
resolveAll(token, options, resolutionStack) {
|
|
609
|
+
const candidates = this.#candidateBindings(token, options, resolutionStack);
|
|
467
610
|
const resolved = new Array(candidates.length);
|
|
468
611
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
469
|
-
resolved[index] = this.#resolveCandidateSync(candidates[index], options,
|
|
612
|
+
resolved[index] = this.#resolveCandidateSync(candidates[index], options, resolutionStack);
|
|
470
613
|
}
|
|
471
614
|
return resolved;
|
|
472
615
|
}
|
|
473
616
|
/** Every binding in the chain a `resolveAll` request matches, in chain order. */
|
|
474
|
-
#candidateBindings(token, options,
|
|
475
|
-
if (options !== undefined
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (!anyPredicate(named)) {
|
|
480
|
-
return named;
|
|
617
|
+
#candidateBindings(token, options, resolutionStack) {
|
|
618
|
+
if (options !== undefined) {
|
|
619
|
+
const indexed = this.#indexedCandidates(token, options, resolutionStack);
|
|
620
|
+
if (indexed !== null) {
|
|
621
|
+
return indexed;
|
|
481
622
|
}
|
|
482
|
-
return selectAllBindings(named, options, this.#makeConstraintContext(resolutionPath, resolutionStack, options));
|
|
483
623
|
}
|
|
484
624
|
const allBindings = this.#allBindingsFromChain(token);
|
|
485
625
|
if (allBindings.length === 0) {
|
|
486
626
|
return allBindings;
|
|
487
627
|
}
|
|
488
|
-
return selectAllBindings(allBindings, options, this.#makeConstraintContext(
|
|
628
|
+
return selectAllBindings(allBindings, options, this.#makeConstraintContext(resolutionStack, options));
|
|
489
629
|
}
|
|
490
|
-
// ── Async resolve
|
|
491
|
-
resolveAsyncFromContext(token,
|
|
630
|
+
// ── Async resolve ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
631
|
+
resolveAsyncFromContext(token, resolutionStack, branchDepth) {
|
|
492
632
|
// Hot lane: own-registry fast default (async chains resolve sibling dynamic bindings).
|
|
493
633
|
// Fall back to the chain-versioned memo only on miss or alias.
|
|
494
634
|
const fastBinding = this.#registry.getFastDefault(token);
|
|
@@ -497,24 +637,24 @@ export class DependencyResolver {
|
|
|
497
637
|
if ((fastBinding.kind === "dynamic-async" || fastBinding.kind === "dynamic") &&
|
|
498
638
|
fastBinding.scope === "transient" &&
|
|
499
639
|
!this.#hasAnyActivation(fastBinding)) {
|
|
500
|
-
return this.#resolveTransientDynamicAsyncFromContext(fastBinding,
|
|
640
|
+
return this.#resolveTransientDynamicAsyncFromContext(fastBinding, resolutionStack, branchDepth);
|
|
501
641
|
}
|
|
502
|
-
return this.#resolveAsyncDefaultEntry(fastBinding, this,
|
|
642
|
+
return this.#resolveAsyncDefaultEntry(fastBinding, this, resolutionStack, branchDepth);
|
|
503
643
|
}
|
|
504
644
|
const entry = this.#lookup.defaultEntry(token);
|
|
505
645
|
if (entry === null) {
|
|
506
|
-
return this.resolveAsync(token, undefined,
|
|
646
|
+
return this.resolveAsync(token, undefined, resolutionStack, branchDepth);
|
|
507
647
|
}
|
|
508
|
-
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner,
|
|
648
|
+
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner, resolutionStack, branchDepth);
|
|
509
649
|
}
|
|
510
|
-
#resolveAsyncDefaultEntry(binding, owner,
|
|
511
|
-
if (
|
|
650
|
+
#resolveAsyncDefaultEntry(binding, owner, resolutionStack, branchDepth) {
|
|
651
|
+
if (owner.#isPlainConstant(binding)) {
|
|
512
652
|
return Promise.resolve(binding.value);
|
|
513
653
|
}
|
|
514
654
|
const scope = binding.scope;
|
|
515
655
|
if (scope === "transient") {
|
|
516
|
-
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !
|
|
517
|
-
return this.#resolveTransientDynamicAsyncFromContext(binding,
|
|
656
|
+
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !owner.#hasAnyActivation(binding)) {
|
|
657
|
+
return this.#resolveTransientDynamicAsyncFromContext(binding, resolutionStack, branchDepth);
|
|
518
658
|
}
|
|
519
659
|
}
|
|
520
660
|
else if (scope === "singleton") {
|
|
@@ -522,29 +662,30 @@ export class DependencyResolver {
|
|
|
522
662
|
return Promise.resolve(binding.instance);
|
|
523
663
|
}
|
|
524
664
|
if (owner !== this) {
|
|
525
|
-
return owner.#resolveBindingAsync(binding, undefined,
|
|
665
|
+
return owner.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
526
666
|
}
|
|
527
667
|
}
|
|
528
668
|
else if (this.#scope.isChild) {
|
|
529
|
-
|
|
530
|
-
|
|
669
|
+
const cachedScoped = this.#scope.readScoped(binding.id);
|
|
670
|
+
if (cachedScoped !== SCOPED_MISS) {
|
|
671
|
+
return Promise.resolve(cachedScoped);
|
|
531
672
|
}
|
|
532
673
|
}
|
|
533
674
|
else {
|
|
534
675
|
// Not `#readScoped`: this entry point reports failure as a rejection, never a sync throw.
|
|
535
676
|
return Promise.reject(new MissingScopeContextError(tokenName(binding.token)));
|
|
536
677
|
}
|
|
537
|
-
return this.#resolveBindingAsync(binding, undefined,
|
|
678
|
+
return this.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
538
679
|
}
|
|
539
|
-
async resolveAsync(token, options,
|
|
540
|
-
const { binding, owner } = this.#requireBinding(token, options,
|
|
680
|
+
async resolveAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
681
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
541
682
|
if (binding.scope === "singleton" && owner !== this) {
|
|
542
|
-
return owner.#resolveBindingAsync(binding, options,
|
|
683
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
543
684
|
}
|
|
544
|
-
return this.#resolveBindingAsync(binding, options,
|
|
685
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
545
686
|
}
|
|
546
|
-
async #resolveBindingAsync(binding, options,
|
|
547
|
-
if (
|
|
687
|
+
async #resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner) {
|
|
688
|
+
if (owner.#isPlainConstant(binding)) {
|
|
548
689
|
return binding.value;
|
|
549
690
|
}
|
|
550
691
|
const scope = binding.scope;
|
|
@@ -557,21 +698,31 @@ export class DependencyResolver {
|
|
|
557
698
|
if (inflight !== undefined) {
|
|
558
699
|
return inflight;
|
|
559
700
|
}
|
|
701
|
+
if (this.#scope.isClosed) {
|
|
702
|
+
throw new DisposedContainerError();
|
|
703
|
+
}
|
|
560
704
|
}
|
|
561
705
|
else if (scope === "scoped") {
|
|
562
706
|
const cachedScoped = this.#readScoped(binding);
|
|
563
707
|
if (cachedScoped !== SCOPED_MISS) {
|
|
564
708
|
return cachedScoped;
|
|
565
709
|
}
|
|
710
|
+
// In-flight dedup, scoped flavor: one instance per scope even under concurrency.
|
|
711
|
+
const inflight = this.#scope.getInflight(binding.id);
|
|
712
|
+
if (inflight !== undefined) {
|
|
713
|
+
return inflight;
|
|
714
|
+
}
|
|
715
|
+
if (this.#scope.isClosed) {
|
|
716
|
+
throw new DisposedContainerError();
|
|
717
|
+
}
|
|
566
718
|
}
|
|
567
719
|
const frame = this.#getResolutionFrame(binding);
|
|
568
|
-
// This level appends to its own branch and never unwinds
|
|
569
|
-
const
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
const needsActivation = this.#activation.needsActivation(binding);
|
|
720
|
+
// This level appends to its own branch and never unwinds.
|
|
721
|
+
const levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
722
|
+
const levelDepth = branchDepthOf(levelStack);
|
|
723
|
+
const needsActivation = owner.#activation.needsActivation(binding);
|
|
573
724
|
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
574
|
-
const resolutionCtx = new AsyncLevelContext(this,
|
|
725
|
+
const resolutionCtx = new AsyncLevelContext(this, levelStack, options);
|
|
575
726
|
if (binding.kind === "dynamic-async") {
|
|
576
727
|
return await binding.factory(resolutionCtx);
|
|
577
728
|
}
|
|
@@ -579,11 +730,11 @@ export class DependencyResolver {
|
|
|
579
730
|
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
580
731
|
}
|
|
581
732
|
const resolutionCtx = needsActivation || requiresResolutionContext(binding)
|
|
582
|
-
? new AsyncLevelContext(this,
|
|
733
|
+
? new AsyncLevelContext(this, levelStack, options)
|
|
583
734
|
: undefined;
|
|
584
735
|
if (scope === "singleton") {
|
|
585
736
|
// The promise is published before it settles, so concurrent callers dedup onto it.
|
|
586
|
-
const singletonPromise = this.#instantiateAndActivateAsync(binding, resolutionCtx,
|
|
737
|
+
const singletonPromise = this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner).then((activated) => {
|
|
587
738
|
this.#scope.setSingleton(binding, activated);
|
|
588
739
|
this.#scope.clearInflight(binding.id);
|
|
589
740
|
return activated;
|
|
@@ -594,20 +745,42 @@ export class DependencyResolver {
|
|
|
594
745
|
this.#scope.setInflight(binding.id, singletonPromise);
|
|
595
746
|
return await singletonPromise;
|
|
596
747
|
}
|
|
597
|
-
const activated = await this.#instantiateAndActivateAsync(binding, resolutionCtx, levelPath, levelStack, levelDepth, needsActivation);
|
|
598
748
|
if (scope === "scoped") {
|
|
599
|
-
|
|
749
|
+
// Published before it settles, like the singleton lane: concurrent callers share one creation.
|
|
750
|
+
const scopedPromise = this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner).then((activated) => {
|
|
751
|
+
this.#scope.setScoped(binding, activated);
|
|
752
|
+
this.#scope.clearInflight(binding.id);
|
|
753
|
+
return activated;
|
|
754
|
+
}, (error) => {
|
|
755
|
+
this.#scope.clearInflight(binding.id);
|
|
756
|
+
throw error;
|
|
757
|
+
});
|
|
758
|
+
this.#scope.setInflight(binding.id, scopedPromise);
|
|
759
|
+
return await scopedPromise;
|
|
600
760
|
}
|
|
601
|
-
return
|
|
761
|
+
return await this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner);
|
|
602
762
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
763
|
+
/**
|
|
764
|
+
* Settles this resolver's own `postConstruct` answer for a class binding a parent owns.
|
|
765
|
+
*
|
|
766
|
+
* @remarks The owner discovers it on first instantiation, but the plan compiler reads the
|
|
767
|
+
* introspector of whoever is resolving — left unknown, that resolver refuses to compile a plan for
|
|
768
|
+
* this binding on every call, forever.
|
|
769
|
+
*/
|
|
770
|
+
#mirrorPostConstructFromOwner(binding, owner) {
|
|
771
|
+
if (owner !== this && binding.kind === "class" && this.#classes.knownPostConstruct(binding.target) === undefined) {
|
|
772
|
+
this.#classes.discoverPostConstruct(binding.target);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async #instantiateAndActivateAsync(binding, ctx, resolutionStack, branchDepth, needsActivation, owner) {
|
|
776
|
+
const instance = await this.#instantiateAsync(binding, ctx, resolutionStack, branchDepth);
|
|
777
|
+
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
778
|
+
if (!owner.#activation.refreshAfterFirstInstantiation(binding, needsActivation)) {
|
|
606
779
|
return instance;
|
|
607
780
|
}
|
|
608
|
-
return
|
|
781
|
+
return owner.#lifecycle.runActivation(ctx, binding, instance, owner.#metadataReader);
|
|
609
782
|
}
|
|
610
|
-
async #instantiateAsync(binding, ctx,
|
|
783
|
+
async #instantiateAsync(binding, ctx, resolutionStack, branchDepth) {
|
|
611
784
|
switch (binding.kind) {
|
|
612
785
|
case "constant":
|
|
613
786
|
return binding.value;
|
|
@@ -624,69 +797,76 @@ export class DependencyResolver {
|
|
|
624
797
|
}
|
|
625
798
|
return binding.factory(ctx);
|
|
626
799
|
case "class": {
|
|
627
|
-
const deps = await this.#resolveDepsAsync(this.#constructorParams(binding.target),
|
|
628
|
-
|
|
800
|
+
const deps = await this.#resolveDepsAsync(this.#constructorParams(binding.target), resolutionStack, branchDepth);
|
|
801
|
+
// Accessor initializers resolve synchronously, so the branch-owned path serves them directly.
|
|
802
|
+
return this.#classes.instantiate(binding.target, deps, this.#classes.needsActiveContainer(binding.target) ? this.#ambientResolutionFor(resolutionStack) : undefined);
|
|
629
803
|
}
|
|
630
804
|
case "resolved": {
|
|
631
|
-
|
|
632
|
-
throw new InternalError("resolved binding requires resolution context");
|
|
633
|
-
}
|
|
634
|
-
const deps = await this.#resolveDepsAsync(binding.deps, resolutionPath, resolutionStack, branchDepth);
|
|
805
|
+
const deps = await this.#resolveDepsAsync(binding.deps, resolutionStack, branchDepth);
|
|
635
806
|
const factoryResult = binding.factory(...deps);
|
|
636
807
|
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
637
808
|
}
|
|
638
809
|
case "resolved-async": {
|
|
639
|
-
const deps = await this.#resolveDepsAsync(binding.deps,
|
|
810
|
+
const deps = await this.#resolveDepsAsync(binding.deps, resolutionStack, branchDepth);
|
|
640
811
|
return binding.factory(...deps);
|
|
641
812
|
}
|
|
642
813
|
case "alias":
|
|
643
814
|
throw new InternalError("alias should have been followed before instantiation");
|
|
644
815
|
}
|
|
645
816
|
}
|
|
646
|
-
async #resolveDepsAsync(deps,
|
|
817
|
+
async #resolveDepsAsync(deps, resolutionStack, branchDepth) {
|
|
647
818
|
const count = deps.length;
|
|
648
819
|
if (count === 0) {
|
|
649
820
|
return [];
|
|
650
821
|
}
|
|
651
822
|
if (count === 1) {
|
|
652
|
-
return [await this.#resolveDepAsync(deps[0],
|
|
823
|
+
return [await this.#resolveDepAsync(deps[0], resolutionStack, branchDepth)];
|
|
653
824
|
}
|
|
654
825
|
// Siblings resolve concurrently and each extends the same branch, so the first appends in
|
|
655
826
|
// place and the rest copy the prefix — no caller has to isolate them.
|
|
656
827
|
const pending = new Array(count);
|
|
657
828
|
for (let index = 0; index < count; index += 1) {
|
|
658
|
-
pending[index] = this.#resolveDepAsync(deps[index],
|
|
829
|
+
pending[index] = this.#resolveDepAsync(deps[index], resolutionStack, branchDepth);
|
|
659
830
|
}
|
|
660
831
|
return Promise.all(pending);
|
|
661
832
|
}
|
|
662
|
-
#resolveDepAsync(dep,
|
|
663
|
-
const options =
|
|
833
|
+
#resolveDepAsync(dep, resolutionStack, branchDepth) {
|
|
834
|
+
const options = resolveOptionsForSlot(dep);
|
|
664
835
|
if (dep.multi) {
|
|
665
|
-
return this.resolveAllAsync(dep.token, options,
|
|
836
|
+
return this.resolveAllAsync(dep.token, options, resolutionStack, branchDepth);
|
|
666
837
|
}
|
|
667
838
|
if (dep.optional) {
|
|
668
|
-
return this.resolveOptionalAsync(dep.token, options,
|
|
839
|
+
return this.resolveOptionalAsync(dep.token, options, resolutionStack, branchDepth);
|
|
669
840
|
}
|
|
670
841
|
if (options === undefined) {
|
|
671
|
-
return this.resolveAsyncFromContext(dep.token,
|
|
842
|
+
return this.resolveAsyncFromContext(dep.token, resolutionStack, branchDepth);
|
|
672
843
|
}
|
|
673
|
-
return this.resolveAsync(dep.token, options,
|
|
844
|
+
return this.resolveAsync(dep.token, options, resolutionStack, branchDepth);
|
|
674
845
|
}
|
|
675
|
-
async resolveOptionalAsync(token, options,
|
|
676
|
-
|
|
846
|
+
async resolveOptionalAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
847
|
+
const entry = this.#findBinding(token, options, resolutionStack);
|
|
848
|
+
if (entry === undefined) {
|
|
677
849
|
return undefined;
|
|
678
850
|
}
|
|
679
|
-
|
|
851
|
+
// Same single-evaluation contract as the sync lane: resolve what the probe found.
|
|
852
|
+
const { binding, owner } = entry;
|
|
853
|
+
if (binding.kind === "alias") {
|
|
854
|
+
return this.resolveAsync(token, options, resolutionStack, branchDepth);
|
|
855
|
+
}
|
|
856
|
+
if (binding.scope === "singleton" && owner !== this) {
|
|
857
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
858
|
+
}
|
|
859
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
680
860
|
}
|
|
681
|
-
async resolveAllAsync(token, options,
|
|
682
|
-
const candidates = this.#candidateBindings(token, options,
|
|
861
|
+
async resolveAllAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
862
|
+
const candidates = this.#candidateBindings(token, options, resolutionStack);
|
|
683
863
|
const pending = new Array(candidates.length);
|
|
684
864
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
685
|
-
pending[index] = this.#resolveCandidateAsync(candidates[index], options,
|
|
865
|
+
pending[index] = this.#resolveCandidateAsync(candidates[index], options, resolutionStack, branchDepth);
|
|
686
866
|
}
|
|
687
867
|
return Promise.all(pending);
|
|
688
868
|
}
|
|
689
|
-
// ── Helpers
|
|
869
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
690
870
|
#allBindingsFromChain(token) {
|
|
691
871
|
const ownBindings = this.#registry.getAll(token);
|
|
692
872
|
if (this.#parent === undefined) {
|
|
@@ -718,6 +898,52 @@ export class DependencyResolver {
|
|
|
718
898
|
}
|
|
719
899
|
return result;
|
|
720
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* The candidates an index can name outright, or `null` when the request needs full selection.
|
|
903
|
+
*
|
|
904
|
+
* @remarks Kept off `#candidateBindings` so that method stays the size it was: a request neither
|
|
905
|
+
* index serves must not pay for the two that do.
|
|
906
|
+
* An index has matched the slot already, but a hit may still carry a predicate, and evaluating
|
|
907
|
+
* that is the selection path's job.
|
|
908
|
+
*/
|
|
909
|
+
#indexedCandidates(token, options, resolutionStack) {
|
|
910
|
+
if (isNameOnlyOptions(options)) {
|
|
911
|
+
const named = this.#namedBindingsFromChain(token, options.name);
|
|
912
|
+
return anyPredicate(named)
|
|
913
|
+
? selectAllBindings(named, options, this.#makeConstraintContext(resolutionStack, options))
|
|
914
|
+
: named;
|
|
915
|
+
}
|
|
916
|
+
const singleTag = singleTagOnlyOf(options);
|
|
917
|
+
if (singleTag === undefined) {
|
|
918
|
+
return null;
|
|
919
|
+
}
|
|
920
|
+
const tagged = this.#taggedBindingsFromChain(token, singleTag);
|
|
921
|
+
return anyPredicate(tagged)
|
|
922
|
+
? selectAllBindings(tagged, options, this.#makeConstraintContext(resolutionStack, options))
|
|
923
|
+
: tagged;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Every binding the chain's tag indexes hold for one tag, nearest container first.
|
|
927
|
+
*
|
|
928
|
+
* @remarks A request for one tag and no name matches exactly the bindings the index keys, so this
|
|
929
|
+
* is the whole candidate set rather than a prefilter — a named or multi-tag slot cannot satisfy it.
|
|
930
|
+
*/
|
|
931
|
+
#taggedBindingsFromChain(token, tag) {
|
|
932
|
+
// A tag matches at most one binding per registry, so a root container's answer is built whole
|
|
933
|
+
// rather than grown — the shape `#namedBindingsFromChain` takes, for the same reason.
|
|
934
|
+
const ownBinding = this.#registry.getSimpleTagged(token, tag);
|
|
935
|
+
if (this.#parent === undefined) {
|
|
936
|
+
return ownBinding === undefined ? [] : [ownBinding];
|
|
937
|
+
}
|
|
938
|
+
const result = ownBinding === undefined ? [] : [ownBinding];
|
|
939
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
940
|
+
const binding = current.#registry.getSimpleTagged(token, tag);
|
|
941
|
+
if (binding !== undefined) {
|
|
942
|
+
result.push(binding);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return result;
|
|
946
|
+
}
|
|
721
947
|
/** A constant with no activation anywhere resolves to its value with no pipeline at all. */
|
|
722
948
|
#isPlainConstant(binding) {
|
|
723
949
|
return (binding.kind === "constant" &&
|
|
@@ -741,70 +967,100 @@ export class DependencyResolver {
|
|
|
741
967
|
if (!this.#scope.isChild) {
|
|
742
968
|
throw new MissingScopeContextError(tokenName(binding.token));
|
|
743
969
|
}
|
|
744
|
-
|
|
745
|
-
return this.#scope.getScoped(binding.id);
|
|
746
|
-
}
|
|
747
|
-
return SCOPED_MISS;
|
|
970
|
+
return this.#scope.readScoped(binding.id);
|
|
748
971
|
}
|
|
749
|
-
|
|
750
|
-
|
|
972
|
+
// The shared root context answers every top-level request; building one is the rarer half and
|
|
973
|
+
// lives outside, so what a selection inlines is the test and not the literal.
|
|
974
|
+
#makeConstraintContext(resolutionStack, options) {
|
|
975
|
+
if (options === undefined && resolutionStack.length === 0) {
|
|
751
976
|
return ROOT_CONSTRAINT_CONTEXT;
|
|
752
977
|
}
|
|
753
|
-
return
|
|
754
|
-
resolutionPath,
|
|
755
|
-
resolutionStack,
|
|
756
|
-
parent: resolutionStack.at(-1),
|
|
757
|
-
ancestors: resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [],
|
|
758
|
-
currentResolveOptions: options,
|
|
759
|
-
};
|
|
978
|
+
return buildConstraintContext(resolutionStack, options);
|
|
760
979
|
}
|
|
761
|
-
|
|
980
|
+
/** Selection for a name-less multi-tag request, over the union of the two tag indexes. */
|
|
981
|
+
#selectMultiTagged(token, options, resolutionStack) {
|
|
982
|
+
const candidates = [];
|
|
983
|
+
this.#gatherTagCandidates(token, options.tag, candidates);
|
|
984
|
+
const listed = options.tags;
|
|
985
|
+
if (listed !== undefined) {
|
|
986
|
+
for (let index = 0; index < listed.length; index += 1) {
|
|
987
|
+
this.#gatherTagCandidates(token, listed[index], candidates);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
if (candidates.length === 0) {
|
|
991
|
+
return undefined;
|
|
992
|
+
}
|
|
993
|
+
return selectBinding(candidates, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
994
|
+
}
|
|
995
|
+
/** One request criterion's candidates: its exact single-tag binding, plus its first-tag bucket. */
|
|
996
|
+
#gatherTagCandidates(token, criterion, out) {
|
|
997
|
+
if (criterion === undefined) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
// The includes probes only guard a request repeating a criterion across its two spellings.
|
|
1001
|
+
const single = this.#registry.getSimpleTagged(token, criterion);
|
|
1002
|
+
if (single !== undefined && !out.includes(single)) {
|
|
1003
|
+
out.push(single);
|
|
1004
|
+
}
|
|
1005
|
+
const bucket = this.#registry.getMultiTagged(token, criterion);
|
|
1006
|
+
if (bucket !== undefined) {
|
|
1007
|
+
for (let index = 0; index < bucket.length; index += 1) {
|
|
1008
|
+
const candidate = bucket[index];
|
|
1009
|
+
if (!out.includes(candidate)) {
|
|
1010
|
+
out.push(candidate);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
#matchesBindingFast(binding, options, resolutionStack) {
|
|
762
1016
|
if (!matchesSlot(binding.slot, options)) {
|
|
763
1017
|
return false;
|
|
764
1018
|
}
|
|
765
|
-
|
|
1019
|
+
return this.#satisfiesPredicate(binding, options, resolutionStack);
|
|
1020
|
+
}
|
|
1021
|
+
/** The predicate half of a match, for a lane whose index has already settled the slot. */
|
|
1022
|
+
#satisfiesPredicate(binding, options, resolutionStack) {
|
|
1023
|
+
const predicate = binding.predicate;
|
|
1024
|
+
if (predicate === undefined) {
|
|
766
1025
|
return true;
|
|
767
1026
|
}
|
|
768
|
-
return
|
|
1027
|
+
return predicate(this.#makeConstraintContext(resolutionStack, options));
|
|
769
1028
|
}
|
|
770
|
-
#resolveTransientDynamicSyncFromContext(binding,
|
|
1029
|
+
#resolveTransientDynamicSyncFromContext(binding, resolutionStack) {
|
|
771
1030
|
// One lane at every depth: `binding.inFlight` is O(1), so there is nothing to escape.
|
|
772
1031
|
const frame = this.#getResolutionFrame(binding);
|
|
773
1032
|
const tokenDisplayName = frame.tokenName;
|
|
774
1033
|
if (binding.inFlight) {
|
|
775
|
-
throw new CircularDependencyError(
|
|
1034
|
+
throw new CircularDependencyError(cycleNamesOf(resolutionStack, tokenDisplayName));
|
|
776
1035
|
}
|
|
777
1036
|
binding.inFlight = true;
|
|
778
|
-
resolutionPath.push(tokenDisplayName);
|
|
779
1037
|
resolutionStack.push(frame);
|
|
780
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
1038
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, undefined);
|
|
781
1039
|
try {
|
|
782
1040
|
const dynamicResult = binding.factory(resolutionCtx);
|
|
783
1041
|
if (dynamicResult instanceof Promise) {
|
|
784
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
1042
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
785
1043
|
}
|
|
786
1044
|
return dynamicResult;
|
|
787
1045
|
}
|
|
788
1046
|
finally {
|
|
789
1047
|
resolutionStack.pop();
|
|
790
|
-
resolutionPath.pop();
|
|
791
1048
|
binding.inFlight = false;
|
|
792
1049
|
}
|
|
793
1050
|
}
|
|
794
1051
|
// Deliberately not `async`: that would allocate a state machine and a promise per level.
|
|
795
|
-
#resolveTransientDynamicAsyncFromContext(binding,
|
|
1052
|
+
#resolveTransientDynamicAsyncFromContext(binding, resolutionStack, branchDepth) {
|
|
796
1053
|
const frame = this.#getResolutionFrame(binding);
|
|
797
|
-
let
|
|
1054
|
+
let levelStack;
|
|
798
1055
|
try {
|
|
799
|
-
|
|
1056
|
+
levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
800
1057
|
}
|
|
801
1058
|
catch (cycleError) {
|
|
802
1059
|
// This method is not `async`; keep failures as rejections rather than sync throws.
|
|
803
1060
|
return Promise.reject(cycleError);
|
|
804
1061
|
}
|
|
805
|
-
const levelStack = extendResolutionStackBranch(resolutionStack, branchDepth, frame);
|
|
806
1062
|
// Nothing this level appended is ever removed, so no level observes its own settlement.
|
|
807
|
-
const ctx = new AsyncLevelContext(this,
|
|
1063
|
+
const ctx = new AsyncLevelContext(this, levelStack, undefined);
|
|
808
1064
|
try {
|
|
809
1065
|
if (binding.kind === "dynamic-async") {
|
|
810
1066
|
return binding.factory(ctx);
|
|
@@ -816,16 +1072,16 @@ export class DependencyResolver {
|
|
|
816
1072
|
return Promise.reject(factoryError);
|
|
817
1073
|
}
|
|
818
1074
|
}
|
|
819
|
-
// ── The cascade lane
|
|
1075
|
+
// ── The cascade lane ───────────────────────────────────────────────────────────────────────────────────────────────
|
|
820
1076
|
/**
|
|
821
1077
|
* Entry for a request a factory makes from inside an open synchronous cascade.
|
|
822
1078
|
*
|
|
823
1079
|
* @remarks A request arriving with no cascade open came out of a continuation, so its ancestors
|
|
824
|
-
* are on no call stack — it escapes to the branch lane.
|
|
1080
|
+
* are on no call stack — it escapes to the branch lane.
|
|
825
1081
|
*/
|
|
826
1082
|
resolveAsyncFromCascade(token) {
|
|
827
|
-
if (this.#
|
|
828
|
-
return this.resolveAsyncFromContext(token, [],
|
|
1083
|
+
if (this.#cascadeStack.length === 0) {
|
|
1084
|
+
return this.resolveAsyncFromContext(token, [], ROOT_BRANCH);
|
|
829
1085
|
}
|
|
830
1086
|
return this.#dispatchCascade(token);
|
|
831
1087
|
}
|
|
@@ -849,10 +1105,39 @@ export class DependencyResolver {
|
|
|
849
1105
|
if (fastBinding.scope === "singleton" && fastBinding.instance !== NO_INSTANCE) {
|
|
850
1106
|
return Promise.resolve(fastBinding.instance);
|
|
851
1107
|
}
|
|
1108
|
+
const planned = this.#plannedCascadeAnswer(fastBinding);
|
|
1109
|
+
if (planned !== null) {
|
|
1110
|
+
return planned;
|
|
1111
|
+
}
|
|
852
1112
|
}
|
|
853
1113
|
// Anything else leaves the cascade lane for good, seeded with a snapshot of the ancestors it
|
|
854
1114
|
// accumulated — so a cycle across the boundary is still on one path.
|
|
855
|
-
return this.resolveAsyncFromContext(token, [...this.#
|
|
1115
|
+
return this.resolveAsyncFromContext(token, [...this.#cascadeStack], UNOWNED_BRANCH);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* A statically-visible transient graph at a true root answers from its compiled async plan.
|
|
1119
|
+
*
|
|
1120
|
+
* @remarks Kept out of the dispatcher so its size stays inlinable. Inside an open cascade the
|
|
1121
|
+
* graph must escape instead, so its escapes carry the live ancestors — hence the idle gate.
|
|
1122
|
+
*/
|
|
1123
|
+
#plannedCascadeAnswer(fastBinding) {
|
|
1124
|
+
if (this.#cascadeStack.length !== 0 ||
|
|
1125
|
+
fastBinding.scope !== "transient" ||
|
|
1126
|
+
(fastBinding.kind !== "class" && fastBinding.kind !== "resolved" && fastBinding.kind !== "resolved-async")) {
|
|
1127
|
+
return null;
|
|
1128
|
+
}
|
|
1129
|
+
const plan = this.#getAsyncInstantiationPlan(fastBinding);
|
|
1130
|
+
if (plan === null) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
const planned = plan();
|
|
1135
|
+
return planned instanceof Promise ? planned : Promise.resolve(planned);
|
|
1136
|
+
}
|
|
1137
|
+
catch (planError) {
|
|
1138
|
+
// The interpreted lane is async, so a sync throw is a rejection there too.
|
|
1139
|
+
return Promise.reject(planError);
|
|
1140
|
+
}
|
|
856
1141
|
}
|
|
857
1142
|
#resolveTransientDynamicAsyncCascade(binding) {
|
|
858
1143
|
const frame = this.#getResolutionFrame(binding);
|
|
@@ -860,11 +1145,10 @@ export class DependencyResolver {
|
|
|
860
1145
|
// code does not interleave — so the O(1) flag is exact path membership here, as it is for the
|
|
861
1146
|
// sync lane. It is cleared when the factory returns its promise, not when that promise settles.
|
|
862
1147
|
if (binding.inFlight) {
|
|
863
|
-
return Promise.reject(new CircularDependencyError(
|
|
1148
|
+
return Promise.reject(new CircularDependencyError(cycleNamesOf(this.#cascadeStack, frame.tokenName)));
|
|
864
1149
|
}
|
|
865
|
-
const ctx = (this.#cascadeContext ??= new AsyncCascadeContext(this, this.#
|
|
1150
|
+
const ctx = (this.#cascadeContext ??= new AsyncCascadeContext(this, this.#cascadeStack));
|
|
866
1151
|
binding.inFlight = true;
|
|
867
|
-
this.#cascadePath.push(frame.tokenName);
|
|
868
1152
|
this.#cascadeStack.push(frame);
|
|
869
1153
|
try {
|
|
870
1154
|
if (binding.kind === "dynamic-async") {
|
|
@@ -878,35 +1162,64 @@ export class DependencyResolver {
|
|
|
878
1162
|
}
|
|
879
1163
|
finally {
|
|
880
1164
|
this.#cascadeStack.pop();
|
|
881
|
-
this.#cascadePath.pop();
|
|
882
1165
|
binding.inFlight = false;
|
|
883
1166
|
}
|
|
884
1167
|
}
|
|
885
1168
|
// A cached candidate answers here rather than re-entering the generic path: `resolveAll` pays
|
|
886
1169
|
// this per candidate, and a fan-out over cached handlers is the shape that makes it matter.
|
|
887
|
-
#resolveCandidateSync(binding, options,
|
|
888
|
-
|
|
1170
|
+
#resolveCandidateSync(binding, options, resolutionStack) {
|
|
1171
|
+
// Fan-outs are dominated by constants: with no activation hook anywhere in the chain, a
|
|
1172
|
+
// hook-free constant is plain no matter which container owns it — skip the owner probe.
|
|
1173
|
+
if (binding.kind === "constant" && binding.onActivation === undefined && this.#chainActivationVersion() === 0) {
|
|
1174
|
+
return binding.value;
|
|
1175
|
+
}
|
|
1176
|
+
const owner = this.#ownerOf(binding);
|
|
1177
|
+
if (owner.#isPlainConstant(binding)) {
|
|
889
1178
|
return binding.value;
|
|
890
1179
|
}
|
|
891
1180
|
if (binding.kind === "alias") {
|
|
892
|
-
return this.resolve(binding.target, options,
|
|
1181
|
+
return this.resolve(binding.target, options, resolutionStack);
|
|
893
1182
|
}
|
|
894
|
-
if (binding.scope === "singleton"
|
|
895
|
-
|
|
1183
|
+
if (binding.scope === "singleton") {
|
|
1184
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
1185
|
+
return binding.instance;
|
|
1186
|
+
}
|
|
1187
|
+
// Owner-routed like `resolve`: the owner materializes and caches its own singleton.
|
|
1188
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
896
1189
|
}
|
|
897
|
-
return this.#resolveBinding(binding, options,
|
|
1190
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
898
1191
|
}
|
|
899
|
-
#resolveCandidateAsync(binding, options,
|
|
900
|
-
if (this.#
|
|
1192
|
+
#resolveCandidateAsync(binding, options, resolutionStack, branchDepth) {
|
|
1193
|
+
if (binding.kind === "constant" && binding.onActivation === undefined && this.#chainActivationVersion() === 0) {
|
|
1194
|
+
return Promise.resolve(binding.value);
|
|
1195
|
+
}
|
|
1196
|
+
const owner = this.#ownerOf(binding);
|
|
1197
|
+
if (owner.#isPlainConstant(binding)) {
|
|
901
1198
|
return Promise.resolve(binding.value);
|
|
902
1199
|
}
|
|
903
1200
|
if (binding.kind === "alias") {
|
|
904
|
-
return this.resolveAsync(binding.target, options,
|
|
1201
|
+
return this.resolveAsync(binding.target, options, resolutionStack, branchDepth);
|
|
905
1202
|
}
|
|
906
|
-
if (binding.scope === "singleton"
|
|
907
|
-
|
|
1203
|
+
if (binding.scope === "singleton") {
|
|
1204
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
1205
|
+
return Promise.resolve(binding.instance);
|
|
1206
|
+
}
|
|
1207
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
908
1208
|
}
|
|
909
|
-
return this.#resolveBindingAsync(binding, options,
|
|
1209
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
1210
|
+
}
|
|
1211
|
+
/** The resolver whose registry holds `binding` — `this` (the common case) when it is own. */
|
|
1212
|
+
#ownerOf(binding) {
|
|
1213
|
+
// A root resolver can only hold its own bindings, so the per-candidate id probe is chain-only.
|
|
1214
|
+
if (this.#parent === undefined || this.#registry.getById(binding.id) !== undefined) {
|
|
1215
|
+
return this;
|
|
1216
|
+
}
|
|
1217
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
1218
|
+
if (current.#registry.getById(binding.id) !== undefined) {
|
|
1219
|
+
return current;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return this;
|
|
910
1223
|
}
|
|
911
1224
|
#getResolutionFrame(binding) {
|
|
912
1225
|
// Memoized on the binding rather than in a per-resolver Map: the frame derives only from
|
|
@@ -920,20 +1233,40 @@ export class DependencyResolver {
|
|
|
920
1233
|
binding.frame = frame;
|
|
921
1234
|
return frame;
|
|
922
1235
|
}
|
|
923
|
-
|
|
1236
|
+
// A pool is keyed by the one array pair its contexts hold, so reuse can never re-point a context
|
|
1237
|
+
// a live frame still reads — a nested top-level resolve reaches the same depth while the outer
|
|
1238
|
+
// factory runs, and it must get its own context, not the outer frame's re-bound.
|
|
1239
|
+
#acquireSyncResolutionContext(resolutionStack, options) {
|
|
1240
|
+
if (resolutionStack === this.rootStack) {
|
|
1241
|
+
const depth = resolutionStack.length;
|
|
1242
|
+
const existing = this.#syncResolutionContextPool[depth];
|
|
1243
|
+
if (existing !== undefined) {
|
|
1244
|
+
existing.reset(this, resolutionStack, options);
|
|
1245
|
+
return existing;
|
|
1246
|
+
}
|
|
1247
|
+
const created = new DefaultResolutionContext(this, resolutionStack, options);
|
|
1248
|
+
this.#syncResolutionContextPool[depth] = created;
|
|
1249
|
+
return created;
|
|
1250
|
+
}
|
|
1251
|
+
return this.#acquireOffRootSyncContext(resolutionStack, options);
|
|
1252
|
+
}
|
|
1253
|
+
/** The cascade pair pools separately; a throwaway pair (nested resolve, async snapshot) mints per call. */
|
|
1254
|
+
#acquireOffRootSyncContext(resolutionStack, options) {
|
|
1255
|
+
if (resolutionStack !== this.#cascadeStack) {
|
|
1256
|
+
return new DefaultResolutionContext(this, resolutionStack, options);
|
|
1257
|
+
}
|
|
924
1258
|
const depth = resolutionStack.length;
|
|
925
|
-
const
|
|
1259
|
+
const pool = (this.#cascadeContextPool ??= []);
|
|
1260
|
+
const existing = pool[depth];
|
|
926
1261
|
if (existing !== undefined) {
|
|
927
|
-
existing.reset(this,
|
|
1262
|
+
existing.reset(this, resolutionStack, options);
|
|
928
1263
|
return existing;
|
|
929
1264
|
}
|
|
930
|
-
const created = new DefaultResolutionContext(this,
|
|
931
|
-
|
|
1265
|
+
const created = new DefaultResolutionContext(this, resolutionStack, options);
|
|
1266
|
+
pool[depth] = created;
|
|
932
1267
|
return created;
|
|
933
1268
|
}
|
|
934
1269
|
}
|
|
935
|
-
/** Absent scoped entry — distinguishes it from a cached `undefined`. */
|
|
936
|
-
const SCOPED_MISS = Symbol("di:scoped-miss");
|
|
937
1270
|
function anyPredicate(bindings) {
|
|
938
1271
|
for (let index = 0; index < bindings.length; index += 1) {
|
|
939
1272
|
if (bindings[index].predicate !== undefined) {
|
|
@@ -942,20 +1275,29 @@ function anyPredicate(bindings) {
|
|
|
942
1275
|
}
|
|
943
1276
|
return false;
|
|
944
1277
|
}
|
|
1278
|
+
function buildConstraintContext(resolutionStack, options) {
|
|
1279
|
+
return {
|
|
1280
|
+
// Derived per read, never cached: the stack may be live, and the names must report it as it stands.
|
|
1281
|
+
get resolutionPath() {
|
|
1282
|
+
const names = new Array(resolutionStack.length);
|
|
1283
|
+
for (let index = 0; index < resolutionStack.length; index += 1) {
|
|
1284
|
+
names[index] = resolutionStack[index].tokenName;
|
|
1285
|
+
}
|
|
1286
|
+
return names;
|
|
1287
|
+
},
|
|
1288
|
+
resolutionStack,
|
|
1289
|
+
parent: resolutionStack.at(-1),
|
|
1290
|
+
ancestors: resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [],
|
|
1291
|
+
currentResolveOptions: options,
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
/** The async-resolution failure for a binding reached on a sync path, naming what to await instead. */
|
|
1295
|
+
function asyncResolutionErrorFor(binding, resolutionStack) {
|
|
1296
|
+
const sourceName = tokenName(binding.token);
|
|
1297
|
+
return new AsyncResolutionError(resolutionStack[0]?.tokenName ?? sourceName, sourceName);
|
|
1298
|
+
}
|
|
945
1299
|
/** Only a factory is handed the resolution context; everything else gets its deps directly. */
|
|
946
1300
|
function requiresResolutionContext(binding) {
|
|
947
1301
|
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
948
1302
|
}
|
|
949
|
-
/**
|
|
950
|
-
* Whether the tag index's answer is the one `Object.is` would give.
|
|
951
|
-
*
|
|
952
|
-
* @remarks An indexed binding has no name, no predicate and exactly one tag, and the request carries
|
|
953
|
-
* only that tag, so `matchesSlot` reduces to the tag values — and the index matched the key already.
|
|
954
|
-
* It answers by SameValueZero, which parts from `Object.is` (SPEC §3.5) on exactly one pair: `+0` and
|
|
955
|
-
* `-0`. So a request whose value is not zero is already exact, and only a zero-valued one is worth
|
|
956
|
-
* reading the stored value for.
|
|
957
|
-
*/
|
|
958
|
-
function matchesIndexedTagValue(binding, requestedValue) {
|
|
959
|
-
return requestedValue !== 0 || Object.is(binding.slot.tags[0][1], requestedValue);
|
|
960
|
-
}
|
|
961
1303
|
//# sourceMappingURL=resolver.js.map
|