@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,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,73 @@ 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
|
+
},
|
|
290
339
|
getResolutionFrame: (binding) => this.#getResolutionFrame(binding),
|
|
291
340
|
// Dispatches exactly as #resolveDep does, so an escaped dep is indistinguishable
|
|
292
341
|
// from the same dep on a fully interpreted resolve.
|
|
293
|
-
resolveEscaped: (token, options, arity,
|
|
342
|
+
resolveEscaped: (token, options, arity, resolutionStack) => {
|
|
343
|
+
if (arity === "all") {
|
|
344
|
+
return this.resolveAll(token, options, resolutionStack);
|
|
345
|
+
}
|
|
346
|
+
if (arity === "optional") {
|
|
347
|
+
return this.resolveOptional(token, options, resolutionStack);
|
|
348
|
+
}
|
|
349
|
+
if (options === undefined) {
|
|
350
|
+
return this.resolveFromContext(token, resolutionStack);
|
|
351
|
+
}
|
|
352
|
+
return this.resolve(token, options, resolutionStack);
|
|
353
|
+
},
|
|
354
|
+
// Dispatches exactly as #resolveDepAsync does, for the async lane's escapes.
|
|
355
|
+
resolveEscapedAsync: (token, options, arity, resolutionStack) => {
|
|
294
356
|
if (arity === "all") {
|
|
295
|
-
return this.
|
|
357
|
+
return this.resolveAllAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
296
358
|
}
|
|
297
359
|
if (arity === "optional") {
|
|
298
|
-
return this.
|
|
360
|
+
return this.resolveOptionalAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
299
361
|
}
|
|
300
362
|
if (options === undefined) {
|
|
301
|
-
return this.
|
|
363
|
+
return this.resolveAsyncFromContext(token, resolutionStack, UNOWNED_BRANCH);
|
|
302
364
|
}
|
|
303
|
-
return this.
|
|
365
|
+
return this.resolveAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
304
366
|
},
|
|
305
367
|
});
|
|
306
|
-
|
|
368
|
+
/** The async lane's plan for a statically-visible transient binding, mirroring the sync getter. */
|
|
369
|
+
#getAsyncInstantiationPlan(binding) {
|
|
370
|
+
const registryVersion = this.#lookup.chainVersion();
|
|
371
|
+
const activationVersion = this.#chainActivationVersion();
|
|
372
|
+
if (registryVersion !== this.#asyncPlanRegistryVersion || activationVersion !== this.#asyncPlanActivationVersion) {
|
|
373
|
+
this.#asyncPlanByBindingId.clear();
|
|
374
|
+
this.#asyncPlanRegistryVersion = registryVersion;
|
|
375
|
+
this.#asyncPlanActivationVersion = activationVersion;
|
|
376
|
+
}
|
|
377
|
+
const cached = this.#asyncPlanByBindingId.get(binding.id);
|
|
378
|
+
if (cached !== undefined) {
|
|
379
|
+
return cached;
|
|
380
|
+
}
|
|
381
|
+
const compiled = this.#planCompiler.compileAsync(binding);
|
|
382
|
+
if (compiled === PLAN_RETRY) {
|
|
383
|
+
// Lifecycle metadata not discovered yet — the fallback resolve discovers it; retry then.
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
this.#asyncPlanByBindingId.set(binding.id, compiled);
|
|
387
|
+
return compiled;
|
|
388
|
+
}
|
|
389
|
+
resolve(token, options, resolutionStack) {
|
|
307
390
|
// Name-only fast lane: memoized lookup, dispatching just the shapes whose
|
|
308
391
|
// semantics involve no resolution context (constants, cached singletons).
|
|
309
392
|
if (options !== undefined && isNameOnlyOptions(options)) {
|
|
310
393
|
const namedEntry = this.#lookup.namedEntry(token, options.name);
|
|
311
394
|
if (namedEntry !== null) {
|
|
312
395
|
const namedBinding = namedEntry.binding;
|
|
313
|
-
if (
|
|
396
|
+
if (namedEntry.owner.#isPlainConstant(namedBinding)) {
|
|
314
397
|
return namedBinding.value;
|
|
315
398
|
}
|
|
316
399
|
if (namedBinding.scope === "singleton" && namedBinding.instance !== NO_INSTANCE) {
|
|
@@ -319,15 +402,15 @@ export class DependencyResolver {
|
|
|
319
402
|
// Everything else keeps the full path (context, activation, guards).
|
|
320
403
|
}
|
|
321
404
|
}
|
|
322
|
-
const { binding, owner } = this.#requireBinding(token, options,
|
|
405
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
323
406
|
// A singleton owned by a parent resolver is resolved there, so the parent caches it.
|
|
324
407
|
if (binding.scope === "singleton" && owner !== this) {
|
|
325
|
-
return owner.#resolveBinding(binding, options,
|
|
408
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
326
409
|
}
|
|
327
|
-
return this.#resolveBinding(binding, options,
|
|
410
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
328
411
|
}
|
|
329
|
-
#resolveBinding(binding, options,
|
|
330
|
-
if (
|
|
412
|
+
#resolveBinding(binding, options, resolutionStack, owner) {
|
|
413
|
+
if (owner.#isPlainConstant(binding)) {
|
|
331
414
|
return binding.value;
|
|
332
415
|
}
|
|
333
416
|
const scope = binding.scope;
|
|
@@ -335,49 +418,70 @@ export class DependencyResolver {
|
|
|
335
418
|
if (binding.instance !== NO_INSTANCE) {
|
|
336
419
|
return binding.instance;
|
|
337
420
|
}
|
|
421
|
+
// An async materialization already in flight must not be raced by a second, sync one.
|
|
422
|
+
if (this.#scope.getInflight(binding.id) !== undefined) {
|
|
423
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenName(binding.token), tokenName(binding.token));
|
|
424
|
+
}
|
|
425
|
+
if (this.#scope.isClosed) {
|
|
426
|
+
throw new DisposedContainerError();
|
|
427
|
+
}
|
|
338
428
|
}
|
|
339
429
|
else if (scope === "scoped") {
|
|
340
430
|
const cachedScoped = this.#readScoped(binding);
|
|
341
431
|
if (cachedScoped !== SCOPED_MISS) {
|
|
342
432
|
return cachedScoped;
|
|
343
433
|
}
|
|
434
|
+
if (this.#scope.getInflight(binding.id) !== undefined) {
|
|
435
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenName(binding.token), tokenName(binding.token));
|
|
436
|
+
}
|
|
437
|
+
if (this.#scope.isClosed) {
|
|
438
|
+
throw new DisposedContainerError();
|
|
439
|
+
}
|
|
344
440
|
}
|
|
345
441
|
const frame = this.#getResolutionFrame(binding);
|
|
346
442
|
const tokenDisplayName = frame.tokenName;
|
|
347
|
-
const resolutionSet = enterResolutionPath(
|
|
348
|
-
resolutionStack.push(frame);
|
|
443
|
+
const resolutionSet = enterResolutionPath(resolutionStack, frame);
|
|
349
444
|
try {
|
|
350
|
-
const needsActivation =
|
|
445
|
+
const needsActivation = owner.#activation.needsActivation(binding);
|
|
351
446
|
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
352
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
447
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, options);
|
|
353
448
|
const dynamicResult = binding.factory(resolutionCtx);
|
|
354
449
|
if (dynamicResult instanceof Promise) {
|
|
355
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
450
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
356
451
|
}
|
|
357
452
|
return dynamicResult;
|
|
358
453
|
}
|
|
359
454
|
const resolutionCtx = needsActivation || requiresResolutionContext(binding)
|
|
360
|
-
? this.#acquireSyncResolutionContext(
|
|
455
|
+
? this.#acquireSyncResolutionContext(resolutionStack, options)
|
|
361
456
|
: undefined;
|
|
362
|
-
const instance = this.#instantiateSync(binding, resolutionCtx,
|
|
363
|
-
|
|
364
|
-
|
|
457
|
+
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionStack);
|
|
458
|
+
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
459
|
+
const activated = owner.#activation.refreshAfterFirstInstantiation(binding, needsActivation)
|
|
460
|
+
? owner.#lifecycle.runActivationSync(resolutionCtx, binding, instance, owner.#metadataReader)
|
|
365
461
|
: instance;
|
|
366
462
|
if (scope === "singleton") {
|
|
367
463
|
this.#scope.setSingleton(binding, activated);
|
|
368
464
|
}
|
|
369
465
|
else if (scope === "scoped") {
|
|
370
|
-
this.#scope.setScoped(binding
|
|
466
|
+
this.#scope.setScoped(binding, activated);
|
|
371
467
|
}
|
|
372
468
|
return activated;
|
|
373
469
|
}
|
|
374
470
|
finally {
|
|
375
471
|
resolutionStack.pop();
|
|
376
|
-
|
|
377
|
-
resolutionSet?.delete(tokenDisplayName);
|
|
472
|
+
resolutionSet?.delete(frame.bindingId);
|
|
378
473
|
}
|
|
379
474
|
}
|
|
380
|
-
|
|
475
|
+
/** Path-continuing resolution handed to the ambient slot while an accessor class constructs. */
|
|
476
|
+
#ambientResolutionFor(resolutionStack) {
|
|
477
|
+
return {
|
|
478
|
+
resolve: (token, options) => options === undefined
|
|
479
|
+
? this.resolveFromContext(token, resolutionStack)
|
|
480
|
+
: this.resolve(token, options, resolutionStack),
|
|
481
|
+
resolveOptional: (token, options) => this.resolveOptional(token, options, resolutionStack),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
#instantiateSync(binding, ctx, resolutionStack) {
|
|
381
485
|
switch (binding.kind) {
|
|
382
486
|
case "constant":
|
|
383
487
|
return binding.value;
|
|
@@ -387,26 +491,26 @@ export class DependencyResolver {
|
|
|
387
491
|
}
|
|
388
492
|
const factoryResult = binding.factory(ctx);
|
|
389
493
|
if (factoryResult instanceof Promise) {
|
|
390
|
-
throw
|
|
494
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
391
495
|
}
|
|
392
496
|
return factoryResult;
|
|
393
497
|
}
|
|
394
498
|
case "dynamic-async":
|
|
395
|
-
throw
|
|
499
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
396
500
|
case "class": {
|
|
397
|
-
const deps = this.#resolveDeps(this.#constructorParams(binding.target),
|
|
398
|
-
return this.#classes.instantiate(binding.target, deps);
|
|
501
|
+
const deps = this.#resolveDeps(this.#constructorParams(binding.target), resolutionStack);
|
|
502
|
+
return this.#classes.instantiate(binding.target, deps, this.#classes.needsActiveContainer(binding.target) ? this.#ambientResolutionFor(resolutionStack) : undefined);
|
|
399
503
|
}
|
|
400
504
|
case "resolved": {
|
|
401
|
-
const deps = this.#resolveDeps(binding.deps,
|
|
505
|
+
const deps = this.#resolveDeps(binding.deps, resolutionStack);
|
|
402
506
|
const factoryResult = binding.factory(...deps);
|
|
403
507
|
if (factoryResult instanceof Promise) {
|
|
404
|
-
throw
|
|
508
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
405
509
|
}
|
|
406
510
|
return factoryResult;
|
|
407
511
|
}
|
|
408
512
|
case "resolved-async":
|
|
409
|
-
throw
|
|
513
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
410
514
|
case "alias":
|
|
411
515
|
throw new InternalError("alias should have been followed before instantiation");
|
|
412
516
|
}
|
|
@@ -429,66 +533,73 @@ export class DependencyResolver {
|
|
|
429
533
|
}
|
|
430
534
|
// One dispatch table for both dependency sources — constructor params and `toResolved`
|
|
431
535
|
// descriptors declare the same four things.
|
|
432
|
-
#resolveDeps(deps,
|
|
536
|
+
#resolveDeps(deps, resolutionStack) {
|
|
433
537
|
const count = deps.length;
|
|
434
538
|
if (count === 0) {
|
|
435
539
|
return [];
|
|
436
540
|
}
|
|
437
541
|
if (count === 1) {
|
|
438
|
-
return [this.#resolveDep(deps[0],
|
|
542
|
+
return [this.#resolveDep(deps[0], resolutionStack)];
|
|
439
543
|
}
|
|
440
544
|
const resolved = new Array(count);
|
|
441
545
|
for (let index = 0; index < count; index += 1) {
|
|
442
|
-
resolved[index] = this.#resolveDep(deps[index],
|
|
546
|
+
resolved[index] = this.#resolveDep(deps[index], resolutionStack);
|
|
443
547
|
}
|
|
444
548
|
return resolved;
|
|
445
549
|
}
|
|
446
|
-
#resolveDep(dep,
|
|
447
|
-
const options =
|
|
550
|
+
#resolveDep(dep, resolutionStack) {
|
|
551
|
+
const options = resolveOptionsForSlot(dep);
|
|
448
552
|
if (dep.multi) {
|
|
449
|
-
return this.resolveAll(dep.token, options,
|
|
553
|
+
return this.resolveAll(dep.token, options, resolutionStack);
|
|
450
554
|
}
|
|
451
555
|
if (dep.optional) {
|
|
452
|
-
return this.resolveOptional(dep.token, options,
|
|
556
|
+
return this.resolveOptional(dep.token, options, resolutionStack);
|
|
453
557
|
}
|
|
454
558
|
if (options === undefined) {
|
|
455
|
-
return this.resolveFromContext(dep.token,
|
|
559
|
+
return this.resolveFromContext(dep.token, resolutionStack);
|
|
456
560
|
}
|
|
457
|
-
return this.resolve(dep.token, options,
|
|
561
|
+
return this.resolve(dep.token, options, resolutionStack);
|
|
458
562
|
}
|
|
459
|
-
resolveOptional(token, options,
|
|
460
|
-
|
|
563
|
+
resolveOptional(token, options, resolutionStack) {
|
|
564
|
+
const entry = this.#findBinding(token, options, resolutionStack);
|
|
565
|
+
if (entry === undefined) {
|
|
461
566
|
return undefined;
|
|
462
567
|
}
|
|
463
|
-
|
|
568
|
+
// Resolve the entry the probe found: re-looking the token up would evaluate every `when()`
|
|
569
|
+
// predicate a second time, and a changed answer would throw where `undefined` was promised.
|
|
570
|
+
const { binding, owner } = entry;
|
|
571
|
+
if (binding.kind === "alias") {
|
|
572
|
+
return this.resolve(token, options, resolutionStack);
|
|
573
|
+
}
|
|
574
|
+
if (binding.scope === "singleton" && owner !== this) {
|
|
575
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
576
|
+
}
|
|
577
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
464
578
|
}
|
|
465
|
-
resolveAll(token, options,
|
|
466
|
-
const candidates = this.#candidateBindings(token, options,
|
|
579
|
+
resolveAll(token, options, resolutionStack) {
|
|
580
|
+
const candidates = this.#candidateBindings(token, options, resolutionStack);
|
|
467
581
|
const resolved = new Array(candidates.length);
|
|
468
582
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
469
|
-
resolved[index] = this.#resolveCandidateSync(candidates[index], options,
|
|
583
|
+
resolved[index] = this.#resolveCandidateSync(candidates[index], options, resolutionStack);
|
|
470
584
|
}
|
|
471
585
|
return resolved;
|
|
472
586
|
}
|
|
473
587
|
/** 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;
|
|
588
|
+
#candidateBindings(token, options, resolutionStack) {
|
|
589
|
+
if (options !== undefined) {
|
|
590
|
+
const indexed = this.#indexedCandidates(token, options, resolutionStack);
|
|
591
|
+
if (indexed !== null) {
|
|
592
|
+
return indexed;
|
|
481
593
|
}
|
|
482
|
-
return selectAllBindings(named, options, this.#makeConstraintContext(resolutionPath, resolutionStack, options));
|
|
483
594
|
}
|
|
484
595
|
const allBindings = this.#allBindingsFromChain(token);
|
|
485
596
|
if (allBindings.length === 0) {
|
|
486
597
|
return allBindings;
|
|
487
598
|
}
|
|
488
|
-
return selectAllBindings(allBindings, options, this.#makeConstraintContext(
|
|
599
|
+
return selectAllBindings(allBindings, options, this.#makeConstraintContext(resolutionStack, options));
|
|
489
600
|
}
|
|
490
|
-
// ── Async resolve
|
|
491
|
-
resolveAsyncFromContext(token,
|
|
601
|
+
// ── Async resolve ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
602
|
+
resolveAsyncFromContext(token, resolutionStack, branchDepth) {
|
|
492
603
|
// Hot lane: own-registry fast default (async chains resolve sibling dynamic bindings).
|
|
493
604
|
// Fall back to the chain-versioned memo only on miss or alias.
|
|
494
605
|
const fastBinding = this.#registry.getFastDefault(token);
|
|
@@ -497,24 +608,24 @@ export class DependencyResolver {
|
|
|
497
608
|
if ((fastBinding.kind === "dynamic-async" || fastBinding.kind === "dynamic") &&
|
|
498
609
|
fastBinding.scope === "transient" &&
|
|
499
610
|
!this.#hasAnyActivation(fastBinding)) {
|
|
500
|
-
return this.#resolveTransientDynamicAsyncFromContext(fastBinding,
|
|
611
|
+
return this.#resolveTransientDynamicAsyncFromContext(fastBinding, resolutionStack, branchDepth);
|
|
501
612
|
}
|
|
502
|
-
return this.#resolveAsyncDefaultEntry(fastBinding, this,
|
|
613
|
+
return this.#resolveAsyncDefaultEntry(fastBinding, this, resolutionStack, branchDepth);
|
|
503
614
|
}
|
|
504
615
|
const entry = this.#lookup.defaultEntry(token);
|
|
505
616
|
if (entry === null) {
|
|
506
|
-
return this.resolveAsync(token, undefined,
|
|
617
|
+
return this.resolveAsync(token, undefined, resolutionStack, branchDepth);
|
|
507
618
|
}
|
|
508
|
-
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner,
|
|
619
|
+
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner, resolutionStack, branchDepth);
|
|
509
620
|
}
|
|
510
|
-
#resolveAsyncDefaultEntry(binding, owner,
|
|
511
|
-
if (
|
|
621
|
+
#resolveAsyncDefaultEntry(binding, owner, resolutionStack, branchDepth) {
|
|
622
|
+
if (owner.#isPlainConstant(binding)) {
|
|
512
623
|
return Promise.resolve(binding.value);
|
|
513
624
|
}
|
|
514
625
|
const scope = binding.scope;
|
|
515
626
|
if (scope === "transient") {
|
|
516
|
-
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !
|
|
517
|
-
return this.#resolveTransientDynamicAsyncFromContext(binding,
|
|
627
|
+
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !owner.#hasAnyActivation(binding)) {
|
|
628
|
+
return this.#resolveTransientDynamicAsyncFromContext(binding, resolutionStack, branchDepth);
|
|
518
629
|
}
|
|
519
630
|
}
|
|
520
631
|
else if (scope === "singleton") {
|
|
@@ -522,29 +633,30 @@ export class DependencyResolver {
|
|
|
522
633
|
return Promise.resolve(binding.instance);
|
|
523
634
|
}
|
|
524
635
|
if (owner !== this) {
|
|
525
|
-
return owner.#resolveBindingAsync(binding, undefined,
|
|
636
|
+
return owner.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
526
637
|
}
|
|
527
638
|
}
|
|
528
639
|
else if (this.#scope.isChild) {
|
|
529
|
-
|
|
530
|
-
|
|
640
|
+
const cachedScoped = this.#scope.readScoped(binding.id);
|
|
641
|
+
if (cachedScoped !== SCOPED_MISS) {
|
|
642
|
+
return Promise.resolve(cachedScoped);
|
|
531
643
|
}
|
|
532
644
|
}
|
|
533
645
|
else {
|
|
534
646
|
// Not `#readScoped`: this entry point reports failure as a rejection, never a sync throw.
|
|
535
647
|
return Promise.reject(new MissingScopeContextError(tokenName(binding.token)));
|
|
536
648
|
}
|
|
537
|
-
return this.#resolveBindingAsync(binding, undefined,
|
|
649
|
+
return this.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
538
650
|
}
|
|
539
|
-
async resolveAsync(token, options,
|
|
540
|
-
const { binding, owner } = this.#requireBinding(token, options,
|
|
651
|
+
async resolveAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
652
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
541
653
|
if (binding.scope === "singleton" && owner !== this) {
|
|
542
|
-
return owner.#resolveBindingAsync(binding, options,
|
|
654
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
543
655
|
}
|
|
544
|
-
return this.#resolveBindingAsync(binding, options,
|
|
656
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
545
657
|
}
|
|
546
|
-
async #resolveBindingAsync(binding, options,
|
|
547
|
-
if (
|
|
658
|
+
async #resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner) {
|
|
659
|
+
if (owner.#isPlainConstant(binding)) {
|
|
548
660
|
return binding.value;
|
|
549
661
|
}
|
|
550
662
|
const scope = binding.scope;
|
|
@@ -557,21 +669,31 @@ export class DependencyResolver {
|
|
|
557
669
|
if (inflight !== undefined) {
|
|
558
670
|
return inflight;
|
|
559
671
|
}
|
|
672
|
+
if (this.#scope.isClosed) {
|
|
673
|
+
throw new DisposedContainerError();
|
|
674
|
+
}
|
|
560
675
|
}
|
|
561
676
|
else if (scope === "scoped") {
|
|
562
677
|
const cachedScoped = this.#readScoped(binding);
|
|
563
678
|
if (cachedScoped !== SCOPED_MISS) {
|
|
564
679
|
return cachedScoped;
|
|
565
680
|
}
|
|
681
|
+
// In-flight dedup, scoped flavor: one instance per scope even under concurrency.
|
|
682
|
+
const inflight = this.#scope.getInflight(binding.id);
|
|
683
|
+
if (inflight !== undefined) {
|
|
684
|
+
return inflight;
|
|
685
|
+
}
|
|
686
|
+
if (this.#scope.isClosed) {
|
|
687
|
+
throw new DisposedContainerError();
|
|
688
|
+
}
|
|
566
689
|
}
|
|
567
690
|
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);
|
|
691
|
+
// This level appends to its own branch and never unwinds.
|
|
692
|
+
const levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
693
|
+
const levelDepth = branchDepthOf(levelStack);
|
|
694
|
+
const needsActivation = owner.#activation.needsActivation(binding);
|
|
573
695
|
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
574
|
-
const resolutionCtx = new AsyncLevelContext(this,
|
|
696
|
+
const resolutionCtx = new AsyncLevelContext(this, levelStack, options);
|
|
575
697
|
if (binding.kind === "dynamic-async") {
|
|
576
698
|
return await binding.factory(resolutionCtx);
|
|
577
699
|
}
|
|
@@ -579,11 +701,11 @@ export class DependencyResolver {
|
|
|
579
701
|
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
580
702
|
}
|
|
581
703
|
const resolutionCtx = needsActivation || requiresResolutionContext(binding)
|
|
582
|
-
? new AsyncLevelContext(this,
|
|
704
|
+
? new AsyncLevelContext(this, levelStack, options)
|
|
583
705
|
: undefined;
|
|
584
706
|
if (scope === "singleton") {
|
|
585
707
|
// The promise is published before it settles, so concurrent callers dedup onto it.
|
|
586
|
-
const singletonPromise = this.#instantiateAndActivateAsync(binding, resolutionCtx,
|
|
708
|
+
const singletonPromise = this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner).then((activated) => {
|
|
587
709
|
this.#scope.setSingleton(binding, activated);
|
|
588
710
|
this.#scope.clearInflight(binding.id);
|
|
589
711
|
return activated;
|
|
@@ -594,20 +716,42 @@ export class DependencyResolver {
|
|
|
594
716
|
this.#scope.setInflight(binding.id, singletonPromise);
|
|
595
717
|
return await singletonPromise;
|
|
596
718
|
}
|
|
597
|
-
const activated = await this.#instantiateAndActivateAsync(binding, resolutionCtx, levelPath, levelStack, levelDepth, needsActivation);
|
|
598
719
|
if (scope === "scoped") {
|
|
599
|
-
|
|
720
|
+
// Published before it settles, like the singleton lane: concurrent callers share one creation.
|
|
721
|
+
const scopedPromise = this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner).then((activated) => {
|
|
722
|
+
this.#scope.setScoped(binding, activated);
|
|
723
|
+
this.#scope.clearInflight(binding.id);
|
|
724
|
+
return activated;
|
|
725
|
+
}, (error) => {
|
|
726
|
+
this.#scope.clearInflight(binding.id);
|
|
727
|
+
throw error;
|
|
728
|
+
});
|
|
729
|
+
this.#scope.setInflight(binding.id, scopedPromise);
|
|
730
|
+
return await scopedPromise;
|
|
600
731
|
}
|
|
601
|
-
return
|
|
732
|
+
return await this.#instantiateAndActivateAsync(binding, resolutionCtx, levelStack, levelDepth, needsActivation, owner);
|
|
602
733
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
734
|
+
/**
|
|
735
|
+
* Settles this resolver's own `postConstruct` answer for a class binding a parent owns.
|
|
736
|
+
*
|
|
737
|
+
* @remarks The owner discovers it on first instantiation, but the plan compiler reads the
|
|
738
|
+
* introspector of whoever is resolving — left unknown, that resolver refuses to compile a plan for
|
|
739
|
+
* this binding on every call, forever.
|
|
740
|
+
*/
|
|
741
|
+
#mirrorPostConstructFromOwner(binding, owner) {
|
|
742
|
+
if (owner !== this && binding.kind === "class" && this.#classes.knownPostConstruct(binding.target) === undefined) {
|
|
743
|
+
this.#classes.discoverPostConstruct(binding.target);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async #instantiateAndActivateAsync(binding, ctx, resolutionStack, branchDepth, needsActivation, owner) {
|
|
747
|
+
const instance = await this.#instantiateAsync(binding, ctx, resolutionStack, branchDepth);
|
|
748
|
+
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
749
|
+
if (!owner.#activation.refreshAfterFirstInstantiation(binding, needsActivation)) {
|
|
606
750
|
return instance;
|
|
607
751
|
}
|
|
608
|
-
return
|
|
752
|
+
return owner.#lifecycle.runActivation(ctx, binding, instance, owner.#metadataReader);
|
|
609
753
|
}
|
|
610
|
-
async #instantiateAsync(binding, ctx,
|
|
754
|
+
async #instantiateAsync(binding, ctx, resolutionStack, branchDepth) {
|
|
611
755
|
switch (binding.kind) {
|
|
612
756
|
case "constant":
|
|
613
757
|
return binding.value;
|
|
@@ -624,69 +768,76 @@ export class DependencyResolver {
|
|
|
624
768
|
}
|
|
625
769
|
return binding.factory(ctx);
|
|
626
770
|
case "class": {
|
|
627
|
-
const deps = await this.#resolveDepsAsync(this.#constructorParams(binding.target),
|
|
628
|
-
|
|
771
|
+
const deps = await this.#resolveDepsAsync(this.#constructorParams(binding.target), resolutionStack, branchDepth);
|
|
772
|
+
// Accessor initializers resolve synchronously, so the branch-owned path serves them directly.
|
|
773
|
+
return this.#classes.instantiate(binding.target, deps, this.#classes.needsActiveContainer(binding.target) ? this.#ambientResolutionFor(resolutionStack) : undefined);
|
|
629
774
|
}
|
|
630
775
|
case "resolved": {
|
|
631
|
-
|
|
632
|
-
throw new InternalError("resolved binding requires resolution context");
|
|
633
|
-
}
|
|
634
|
-
const deps = await this.#resolveDepsAsync(binding.deps, resolutionPath, resolutionStack, branchDepth);
|
|
776
|
+
const deps = await this.#resolveDepsAsync(binding.deps, resolutionStack, branchDepth);
|
|
635
777
|
const factoryResult = binding.factory(...deps);
|
|
636
778
|
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
637
779
|
}
|
|
638
780
|
case "resolved-async": {
|
|
639
|
-
const deps = await this.#resolveDepsAsync(binding.deps,
|
|
781
|
+
const deps = await this.#resolveDepsAsync(binding.deps, resolutionStack, branchDepth);
|
|
640
782
|
return binding.factory(...deps);
|
|
641
783
|
}
|
|
642
784
|
case "alias":
|
|
643
785
|
throw new InternalError("alias should have been followed before instantiation");
|
|
644
786
|
}
|
|
645
787
|
}
|
|
646
|
-
async #resolveDepsAsync(deps,
|
|
788
|
+
async #resolveDepsAsync(deps, resolutionStack, branchDepth) {
|
|
647
789
|
const count = deps.length;
|
|
648
790
|
if (count === 0) {
|
|
649
791
|
return [];
|
|
650
792
|
}
|
|
651
793
|
if (count === 1) {
|
|
652
|
-
return [await this.#resolveDepAsync(deps[0],
|
|
794
|
+
return [await this.#resolveDepAsync(deps[0], resolutionStack, branchDepth)];
|
|
653
795
|
}
|
|
654
796
|
// Siblings resolve concurrently and each extends the same branch, so the first appends in
|
|
655
797
|
// place and the rest copy the prefix — no caller has to isolate them.
|
|
656
798
|
const pending = new Array(count);
|
|
657
799
|
for (let index = 0; index < count; index += 1) {
|
|
658
|
-
pending[index] = this.#resolveDepAsync(deps[index],
|
|
800
|
+
pending[index] = this.#resolveDepAsync(deps[index], resolutionStack, branchDepth);
|
|
659
801
|
}
|
|
660
802
|
return Promise.all(pending);
|
|
661
803
|
}
|
|
662
|
-
#resolveDepAsync(dep,
|
|
663
|
-
const options =
|
|
804
|
+
#resolveDepAsync(dep, resolutionStack, branchDepth) {
|
|
805
|
+
const options = resolveOptionsForSlot(dep);
|
|
664
806
|
if (dep.multi) {
|
|
665
|
-
return this.resolveAllAsync(dep.token, options,
|
|
807
|
+
return this.resolveAllAsync(dep.token, options, resolutionStack, branchDepth);
|
|
666
808
|
}
|
|
667
809
|
if (dep.optional) {
|
|
668
|
-
return this.resolveOptionalAsync(dep.token, options,
|
|
810
|
+
return this.resolveOptionalAsync(dep.token, options, resolutionStack, branchDepth);
|
|
669
811
|
}
|
|
670
812
|
if (options === undefined) {
|
|
671
|
-
return this.resolveAsyncFromContext(dep.token,
|
|
813
|
+
return this.resolveAsyncFromContext(dep.token, resolutionStack, branchDepth);
|
|
672
814
|
}
|
|
673
|
-
return this.resolveAsync(dep.token, options,
|
|
815
|
+
return this.resolveAsync(dep.token, options, resolutionStack, branchDepth);
|
|
674
816
|
}
|
|
675
|
-
async resolveOptionalAsync(token, options,
|
|
676
|
-
|
|
817
|
+
async resolveOptionalAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
818
|
+
const entry = this.#findBinding(token, options, resolutionStack);
|
|
819
|
+
if (entry === undefined) {
|
|
677
820
|
return undefined;
|
|
678
821
|
}
|
|
679
|
-
|
|
822
|
+
// Same single-evaluation contract as the sync lane: resolve what the probe found.
|
|
823
|
+
const { binding, owner } = entry;
|
|
824
|
+
if (binding.kind === "alias") {
|
|
825
|
+
return this.resolveAsync(token, options, resolutionStack, branchDepth);
|
|
826
|
+
}
|
|
827
|
+
if (binding.scope === "singleton" && owner !== this) {
|
|
828
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
829
|
+
}
|
|
830
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
680
831
|
}
|
|
681
|
-
async resolveAllAsync(token, options,
|
|
682
|
-
const candidates = this.#candidateBindings(token, options,
|
|
832
|
+
async resolveAllAsync(token, options, resolutionStack, branchDepth = UNOWNED_BRANCH) {
|
|
833
|
+
const candidates = this.#candidateBindings(token, options, resolutionStack);
|
|
683
834
|
const pending = new Array(candidates.length);
|
|
684
835
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
685
|
-
pending[index] = this.#resolveCandidateAsync(candidates[index], options,
|
|
836
|
+
pending[index] = this.#resolveCandidateAsync(candidates[index], options, resolutionStack, branchDepth);
|
|
686
837
|
}
|
|
687
838
|
return Promise.all(pending);
|
|
688
839
|
}
|
|
689
|
-
// ── Helpers
|
|
840
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
690
841
|
#allBindingsFromChain(token) {
|
|
691
842
|
const ownBindings = this.#registry.getAll(token);
|
|
692
843
|
if (this.#parent === undefined) {
|
|
@@ -718,6 +869,52 @@ export class DependencyResolver {
|
|
|
718
869
|
}
|
|
719
870
|
return result;
|
|
720
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* The candidates an index can name outright, or `null` when the request needs full selection.
|
|
874
|
+
*
|
|
875
|
+
* @remarks Kept off `#candidateBindings` so that method stays the size it was: a request neither
|
|
876
|
+
* index serves must not pay for the two that do.
|
|
877
|
+
* An index has matched the slot already, but a hit may still carry a predicate, and evaluating
|
|
878
|
+
* that is the selection path's job.
|
|
879
|
+
*/
|
|
880
|
+
#indexedCandidates(token, options, resolutionStack) {
|
|
881
|
+
if (isNameOnlyOptions(options)) {
|
|
882
|
+
const named = this.#namedBindingsFromChain(token, options.name);
|
|
883
|
+
return anyPredicate(named)
|
|
884
|
+
? selectAllBindings(named, options, this.#makeConstraintContext(resolutionStack, options))
|
|
885
|
+
: named;
|
|
886
|
+
}
|
|
887
|
+
const singleTag = singleTagOnlyOf(options);
|
|
888
|
+
if (singleTag === undefined) {
|
|
889
|
+
return null;
|
|
890
|
+
}
|
|
891
|
+
const tagged = this.#taggedBindingsFromChain(token, singleTag);
|
|
892
|
+
return anyPredicate(tagged)
|
|
893
|
+
? selectAllBindings(tagged, options, this.#makeConstraintContext(resolutionStack, options))
|
|
894
|
+
: tagged;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Every binding the chain's tag indexes hold for one tag, nearest container first.
|
|
898
|
+
*
|
|
899
|
+
* @remarks A request for one tag and no name matches exactly the bindings the index keys, so this
|
|
900
|
+
* is the whole candidate set rather than a prefilter — a named or multi-tag slot cannot satisfy it.
|
|
901
|
+
*/
|
|
902
|
+
#taggedBindingsFromChain(token, tag) {
|
|
903
|
+
// A tag matches at most one binding per registry, so a root container's answer is built whole
|
|
904
|
+
// rather than grown — the shape `#namedBindingsFromChain` takes, for the same reason.
|
|
905
|
+
const ownBinding = this.#registry.getSimpleTagged(token, tag);
|
|
906
|
+
if (this.#parent === undefined) {
|
|
907
|
+
return ownBinding === undefined ? [] : [ownBinding];
|
|
908
|
+
}
|
|
909
|
+
const result = ownBinding === undefined ? [] : [ownBinding];
|
|
910
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
911
|
+
const binding = current.#registry.getSimpleTagged(token, tag);
|
|
912
|
+
if (binding !== undefined) {
|
|
913
|
+
result.push(binding);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
721
918
|
/** A constant with no activation anywhere resolves to its value with no pipeline at all. */
|
|
722
919
|
#isPlainConstant(binding) {
|
|
723
920
|
return (binding.kind === "constant" &&
|
|
@@ -741,70 +938,100 @@ export class DependencyResolver {
|
|
|
741
938
|
if (!this.#scope.isChild) {
|
|
742
939
|
throw new MissingScopeContextError(tokenName(binding.token));
|
|
743
940
|
}
|
|
744
|
-
|
|
745
|
-
return this.#scope.getScoped(binding.id);
|
|
746
|
-
}
|
|
747
|
-
return SCOPED_MISS;
|
|
941
|
+
return this.#scope.readScoped(binding.id);
|
|
748
942
|
}
|
|
749
|
-
|
|
750
|
-
|
|
943
|
+
// The shared root context answers every top-level request; building one is the rarer half and
|
|
944
|
+
// lives outside, so what a selection inlines is the test and not the literal.
|
|
945
|
+
#makeConstraintContext(resolutionStack, options) {
|
|
946
|
+
if (options === undefined && resolutionStack.length === 0) {
|
|
751
947
|
return ROOT_CONSTRAINT_CONTEXT;
|
|
752
948
|
}
|
|
753
|
-
return
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
949
|
+
return buildConstraintContext(resolutionStack, options);
|
|
950
|
+
}
|
|
951
|
+
/** Selection for a name-less multi-tag request, over the union of the two tag indexes. */
|
|
952
|
+
#selectMultiTagged(token, options, resolutionStack) {
|
|
953
|
+
const candidates = [];
|
|
954
|
+
this.#gatherTagCandidates(token, options.tag, candidates);
|
|
955
|
+
const listed = options.tags;
|
|
956
|
+
if (listed !== undefined) {
|
|
957
|
+
for (let index = 0; index < listed.length; index += 1) {
|
|
958
|
+
this.#gatherTagCandidates(token, listed[index], candidates);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (candidates.length === 0) {
|
|
962
|
+
return undefined;
|
|
963
|
+
}
|
|
964
|
+
return selectBinding(candidates, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
760
965
|
}
|
|
761
|
-
|
|
966
|
+
/** One request criterion's candidates: its exact single-tag binding, plus its first-tag bucket. */
|
|
967
|
+
#gatherTagCandidates(token, criterion, out) {
|
|
968
|
+
if (criterion === undefined) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
// The includes probes only guard a request repeating a criterion across its two spellings.
|
|
972
|
+
const single = this.#registry.getSimpleTagged(token, criterion);
|
|
973
|
+
if (single !== undefined && !out.includes(single)) {
|
|
974
|
+
out.push(single);
|
|
975
|
+
}
|
|
976
|
+
const bucket = this.#registry.getMultiTagged(token, criterion);
|
|
977
|
+
if (bucket !== undefined) {
|
|
978
|
+
for (let index = 0; index < bucket.length; index += 1) {
|
|
979
|
+
const candidate = bucket[index];
|
|
980
|
+
if (!out.includes(candidate)) {
|
|
981
|
+
out.push(candidate);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
#matchesBindingFast(binding, options, resolutionStack) {
|
|
762
987
|
if (!matchesSlot(binding.slot, options)) {
|
|
763
988
|
return false;
|
|
764
989
|
}
|
|
765
|
-
|
|
990
|
+
return this.#satisfiesPredicate(binding, options, resolutionStack);
|
|
991
|
+
}
|
|
992
|
+
/** The predicate half of a match, for a lane whose index has already settled the slot. */
|
|
993
|
+
#satisfiesPredicate(binding, options, resolutionStack) {
|
|
994
|
+
const predicate = binding.predicate;
|
|
995
|
+
if (predicate === undefined) {
|
|
766
996
|
return true;
|
|
767
997
|
}
|
|
768
|
-
return
|
|
998
|
+
return predicate(this.#makeConstraintContext(resolutionStack, options));
|
|
769
999
|
}
|
|
770
|
-
#resolveTransientDynamicSyncFromContext(binding,
|
|
1000
|
+
#resolveTransientDynamicSyncFromContext(binding, resolutionStack) {
|
|
771
1001
|
// One lane at every depth: `binding.inFlight` is O(1), so there is nothing to escape.
|
|
772
1002
|
const frame = this.#getResolutionFrame(binding);
|
|
773
1003
|
const tokenDisplayName = frame.tokenName;
|
|
774
1004
|
if (binding.inFlight) {
|
|
775
|
-
throw new CircularDependencyError(
|
|
1005
|
+
throw new CircularDependencyError(cycleNamesOf(resolutionStack, tokenDisplayName));
|
|
776
1006
|
}
|
|
777
1007
|
binding.inFlight = true;
|
|
778
|
-
resolutionPath.push(tokenDisplayName);
|
|
779
1008
|
resolutionStack.push(frame);
|
|
780
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
1009
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, undefined);
|
|
781
1010
|
try {
|
|
782
1011
|
const dynamicResult = binding.factory(resolutionCtx);
|
|
783
1012
|
if (dynamicResult instanceof Promise) {
|
|
784
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
1013
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
785
1014
|
}
|
|
786
1015
|
return dynamicResult;
|
|
787
1016
|
}
|
|
788
1017
|
finally {
|
|
789
1018
|
resolutionStack.pop();
|
|
790
|
-
resolutionPath.pop();
|
|
791
1019
|
binding.inFlight = false;
|
|
792
1020
|
}
|
|
793
1021
|
}
|
|
794
1022
|
// Deliberately not `async`: that would allocate a state machine and a promise per level.
|
|
795
|
-
#resolveTransientDynamicAsyncFromContext(binding,
|
|
1023
|
+
#resolveTransientDynamicAsyncFromContext(binding, resolutionStack, branchDepth) {
|
|
796
1024
|
const frame = this.#getResolutionFrame(binding);
|
|
797
|
-
let
|
|
1025
|
+
let levelStack;
|
|
798
1026
|
try {
|
|
799
|
-
|
|
1027
|
+
levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
800
1028
|
}
|
|
801
1029
|
catch (cycleError) {
|
|
802
1030
|
// This method is not `async`; keep failures as rejections rather than sync throws.
|
|
803
1031
|
return Promise.reject(cycleError);
|
|
804
1032
|
}
|
|
805
|
-
const levelStack = extendResolutionStackBranch(resolutionStack, branchDepth, frame);
|
|
806
1033
|
// Nothing this level appended is ever removed, so no level observes its own settlement.
|
|
807
|
-
const ctx = new AsyncLevelContext(this,
|
|
1034
|
+
const ctx = new AsyncLevelContext(this, levelStack, undefined);
|
|
808
1035
|
try {
|
|
809
1036
|
if (binding.kind === "dynamic-async") {
|
|
810
1037
|
return binding.factory(ctx);
|
|
@@ -816,16 +1043,16 @@ export class DependencyResolver {
|
|
|
816
1043
|
return Promise.reject(factoryError);
|
|
817
1044
|
}
|
|
818
1045
|
}
|
|
819
|
-
// ── The cascade lane
|
|
1046
|
+
// ── The cascade lane ───────────────────────────────────────────────────────────────────────────────────────────────
|
|
820
1047
|
/**
|
|
821
1048
|
* Entry for a request a factory makes from inside an open synchronous cascade.
|
|
822
1049
|
*
|
|
823
1050
|
* @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.
|
|
1051
|
+
* are on no call stack — it escapes to the branch lane.
|
|
825
1052
|
*/
|
|
826
1053
|
resolveAsyncFromCascade(token) {
|
|
827
|
-
if (this.#
|
|
828
|
-
return this.resolveAsyncFromContext(token, [],
|
|
1054
|
+
if (this.#cascadeStack.length === 0) {
|
|
1055
|
+
return this.resolveAsyncFromContext(token, [], ROOT_BRANCH);
|
|
829
1056
|
}
|
|
830
1057
|
return this.#dispatchCascade(token);
|
|
831
1058
|
}
|
|
@@ -849,10 +1076,39 @@ export class DependencyResolver {
|
|
|
849
1076
|
if (fastBinding.scope === "singleton" && fastBinding.instance !== NO_INSTANCE) {
|
|
850
1077
|
return Promise.resolve(fastBinding.instance);
|
|
851
1078
|
}
|
|
1079
|
+
const planned = this.#plannedCascadeAnswer(fastBinding);
|
|
1080
|
+
if (planned !== null) {
|
|
1081
|
+
return planned;
|
|
1082
|
+
}
|
|
852
1083
|
}
|
|
853
1084
|
// Anything else leaves the cascade lane for good, seeded with a snapshot of the ancestors it
|
|
854
1085
|
// accumulated — so a cycle across the boundary is still on one path.
|
|
855
|
-
return this.resolveAsyncFromContext(token, [...this.#
|
|
1086
|
+
return this.resolveAsyncFromContext(token, [...this.#cascadeStack], UNOWNED_BRANCH);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* A statically-visible transient graph at a true root answers from its compiled async plan.
|
|
1090
|
+
*
|
|
1091
|
+
* @remarks Kept out of the dispatcher so its size stays inlinable. Inside an open cascade the
|
|
1092
|
+
* graph must escape instead, so its escapes carry the live ancestors — hence the idle gate.
|
|
1093
|
+
*/
|
|
1094
|
+
#plannedCascadeAnswer(fastBinding) {
|
|
1095
|
+
if (this.#cascadeStack.length !== 0 ||
|
|
1096
|
+
fastBinding.scope !== "transient" ||
|
|
1097
|
+
(fastBinding.kind !== "class" && fastBinding.kind !== "resolved" && fastBinding.kind !== "resolved-async")) {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
const plan = this.#getAsyncInstantiationPlan(fastBinding);
|
|
1101
|
+
if (plan === null) {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
try {
|
|
1105
|
+
const planned = plan();
|
|
1106
|
+
return planned instanceof Promise ? planned : Promise.resolve(planned);
|
|
1107
|
+
}
|
|
1108
|
+
catch (planError) {
|
|
1109
|
+
// The interpreted lane is async, so a sync throw is a rejection there too.
|
|
1110
|
+
return Promise.reject(planError);
|
|
1111
|
+
}
|
|
856
1112
|
}
|
|
857
1113
|
#resolveTransientDynamicAsyncCascade(binding) {
|
|
858
1114
|
const frame = this.#getResolutionFrame(binding);
|
|
@@ -860,11 +1116,10 @@ export class DependencyResolver {
|
|
|
860
1116
|
// code does not interleave — so the O(1) flag is exact path membership here, as it is for the
|
|
861
1117
|
// sync lane. It is cleared when the factory returns its promise, not when that promise settles.
|
|
862
1118
|
if (binding.inFlight) {
|
|
863
|
-
return Promise.reject(new CircularDependencyError(
|
|
1119
|
+
return Promise.reject(new CircularDependencyError(cycleNamesOf(this.#cascadeStack, frame.tokenName)));
|
|
864
1120
|
}
|
|
865
|
-
const ctx = (this.#cascadeContext ??= new AsyncCascadeContext(this, this.#
|
|
1121
|
+
const ctx = (this.#cascadeContext ??= new AsyncCascadeContext(this, this.#cascadeStack));
|
|
866
1122
|
binding.inFlight = true;
|
|
867
|
-
this.#cascadePath.push(frame.tokenName);
|
|
868
1123
|
this.#cascadeStack.push(frame);
|
|
869
1124
|
try {
|
|
870
1125
|
if (binding.kind === "dynamic-async") {
|
|
@@ -878,35 +1133,64 @@ export class DependencyResolver {
|
|
|
878
1133
|
}
|
|
879
1134
|
finally {
|
|
880
1135
|
this.#cascadeStack.pop();
|
|
881
|
-
this.#cascadePath.pop();
|
|
882
1136
|
binding.inFlight = false;
|
|
883
1137
|
}
|
|
884
1138
|
}
|
|
885
1139
|
// A cached candidate answers here rather than re-entering the generic path: `resolveAll` pays
|
|
886
1140
|
// this per candidate, and a fan-out over cached handlers is the shape that makes it matter.
|
|
887
|
-
#resolveCandidateSync(binding, options,
|
|
888
|
-
|
|
1141
|
+
#resolveCandidateSync(binding, options, resolutionStack) {
|
|
1142
|
+
// Fan-outs are dominated by constants: with no activation hook anywhere in the chain, a
|
|
1143
|
+
// hook-free constant is plain no matter which container owns it — skip the owner probe.
|
|
1144
|
+
if (binding.kind === "constant" && binding.onActivation === undefined && this.#chainActivationVersion() === 0) {
|
|
1145
|
+
return binding.value;
|
|
1146
|
+
}
|
|
1147
|
+
const owner = this.#ownerOf(binding);
|
|
1148
|
+
if (owner.#isPlainConstant(binding)) {
|
|
889
1149
|
return binding.value;
|
|
890
1150
|
}
|
|
891
1151
|
if (binding.kind === "alias") {
|
|
892
|
-
return this.resolve(binding.target, options,
|
|
1152
|
+
return this.resolve(binding.target, options, resolutionStack);
|
|
893
1153
|
}
|
|
894
|
-
if (binding.scope === "singleton"
|
|
895
|
-
|
|
1154
|
+
if (binding.scope === "singleton") {
|
|
1155
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
1156
|
+
return binding.instance;
|
|
1157
|
+
}
|
|
1158
|
+
// Owner-routed like `resolve`: the owner materializes and caches its own singleton.
|
|
1159
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
896
1160
|
}
|
|
897
|
-
return this.#resolveBinding(binding, options,
|
|
1161
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
898
1162
|
}
|
|
899
|
-
#resolveCandidateAsync(binding, options,
|
|
900
|
-
if (this.#
|
|
1163
|
+
#resolveCandidateAsync(binding, options, resolutionStack, branchDepth) {
|
|
1164
|
+
if (binding.kind === "constant" && binding.onActivation === undefined && this.#chainActivationVersion() === 0) {
|
|
1165
|
+
return Promise.resolve(binding.value);
|
|
1166
|
+
}
|
|
1167
|
+
const owner = this.#ownerOf(binding);
|
|
1168
|
+
if (owner.#isPlainConstant(binding)) {
|
|
901
1169
|
return Promise.resolve(binding.value);
|
|
902
1170
|
}
|
|
903
1171
|
if (binding.kind === "alias") {
|
|
904
|
-
return this.resolveAsync(binding.target, options,
|
|
1172
|
+
return this.resolveAsync(binding.target, options, resolutionStack, branchDepth);
|
|
905
1173
|
}
|
|
906
|
-
if (binding.scope === "singleton"
|
|
907
|
-
|
|
1174
|
+
if (binding.scope === "singleton") {
|
|
1175
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
1176
|
+
return Promise.resolve(binding.instance);
|
|
1177
|
+
}
|
|
1178
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
908
1179
|
}
|
|
909
|
-
return this.#resolveBindingAsync(binding, options,
|
|
1180
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
1181
|
+
}
|
|
1182
|
+
/** The resolver whose registry holds `binding` — `this` (the common case) when it is own. */
|
|
1183
|
+
#ownerOf(binding) {
|
|
1184
|
+
// A root resolver can only hold its own bindings, so the per-candidate id probe is chain-only.
|
|
1185
|
+
if (this.#parent === undefined || this.#registry.getById(binding.id) !== undefined) {
|
|
1186
|
+
return this;
|
|
1187
|
+
}
|
|
1188
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
1189
|
+
if (current.#registry.getById(binding.id) !== undefined) {
|
|
1190
|
+
return current;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
return this;
|
|
910
1194
|
}
|
|
911
1195
|
#getResolutionFrame(binding) {
|
|
912
1196
|
// Memoized on the binding rather than in a per-resolver Map: the frame derives only from
|
|
@@ -920,20 +1204,40 @@ export class DependencyResolver {
|
|
|
920
1204
|
binding.frame = frame;
|
|
921
1205
|
return frame;
|
|
922
1206
|
}
|
|
923
|
-
|
|
1207
|
+
// A pool is keyed by the one array pair its contexts hold, so reuse can never re-point a context
|
|
1208
|
+
// a live frame still reads — a nested top-level resolve reaches the same depth while the outer
|
|
1209
|
+
// factory runs, and it must get its own context, not the outer frame's re-bound.
|
|
1210
|
+
#acquireSyncResolutionContext(resolutionStack, options) {
|
|
1211
|
+
if (resolutionStack === this.rootStack) {
|
|
1212
|
+
const depth = resolutionStack.length;
|
|
1213
|
+
const existing = this.#syncResolutionContextPool[depth];
|
|
1214
|
+
if (existing !== undefined) {
|
|
1215
|
+
existing.reset(this, resolutionStack, options);
|
|
1216
|
+
return existing;
|
|
1217
|
+
}
|
|
1218
|
+
const created = new DefaultResolutionContext(this, resolutionStack, options);
|
|
1219
|
+
this.#syncResolutionContextPool[depth] = created;
|
|
1220
|
+
return created;
|
|
1221
|
+
}
|
|
1222
|
+
return this.#acquireOffRootSyncContext(resolutionStack, options);
|
|
1223
|
+
}
|
|
1224
|
+
/** The cascade pair pools separately; a throwaway pair (nested resolve, async snapshot) mints per call. */
|
|
1225
|
+
#acquireOffRootSyncContext(resolutionStack, options) {
|
|
1226
|
+
if (resolutionStack !== this.#cascadeStack) {
|
|
1227
|
+
return new DefaultResolutionContext(this, resolutionStack, options);
|
|
1228
|
+
}
|
|
924
1229
|
const depth = resolutionStack.length;
|
|
925
|
-
const
|
|
1230
|
+
const pool = (this.#cascadeContextPool ??= []);
|
|
1231
|
+
const existing = pool[depth];
|
|
926
1232
|
if (existing !== undefined) {
|
|
927
|
-
existing.reset(this,
|
|
1233
|
+
existing.reset(this, resolutionStack, options);
|
|
928
1234
|
return existing;
|
|
929
1235
|
}
|
|
930
|
-
const created = new DefaultResolutionContext(this,
|
|
931
|
-
|
|
1236
|
+
const created = new DefaultResolutionContext(this, resolutionStack, options);
|
|
1237
|
+
pool[depth] = created;
|
|
932
1238
|
return created;
|
|
933
1239
|
}
|
|
934
1240
|
}
|
|
935
|
-
/** Absent scoped entry — distinguishes it from a cached `undefined`. */
|
|
936
|
-
const SCOPED_MISS = Symbol("di:scoped-miss");
|
|
937
1241
|
function anyPredicate(bindings) {
|
|
938
1242
|
for (let index = 0; index < bindings.length; index += 1) {
|
|
939
1243
|
if (bindings[index].predicate !== undefined) {
|
|
@@ -942,20 +1246,29 @@ function anyPredicate(bindings) {
|
|
|
942
1246
|
}
|
|
943
1247
|
return false;
|
|
944
1248
|
}
|
|
1249
|
+
function buildConstraintContext(resolutionStack, options) {
|
|
1250
|
+
return {
|
|
1251
|
+
// Derived per read, never cached: the stack may be live, and the names must report it as it stands.
|
|
1252
|
+
get resolutionPath() {
|
|
1253
|
+
const names = new Array(resolutionStack.length);
|
|
1254
|
+
for (let index = 0; index < resolutionStack.length; index += 1) {
|
|
1255
|
+
names[index] = resolutionStack[index].tokenName;
|
|
1256
|
+
}
|
|
1257
|
+
return names;
|
|
1258
|
+
},
|
|
1259
|
+
resolutionStack,
|
|
1260
|
+
parent: resolutionStack.at(-1),
|
|
1261
|
+
ancestors: resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [],
|
|
1262
|
+
currentResolveOptions: options,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
/** The async-resolution failure for a binding reached on a sync path, naming what to await instead. */
|
|
1266
|
+
function asyncResolutionErrorFor(binding, resolutionStack) {
|
|
1267
|
+
const sourceName = tokenName(binding.token);
|
|
1268
|
+
return new AsyncResolutionError(resolutionStack[0]?.tokenName ?? sourceName, sourceName);
|
|
1269
|
+
}
|
|
945
1270
|
/** Only a factory is handed the resolution context; everything else gets its deps directly. */
|
|
946
1271
|
function requiresResolutionContext(binding) {
|
|
947
1272
|
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
948
1273
|
}
|
|
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
1274
|
//# sourceMappingURL=resolver.js.map
|