@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,55 +1,63 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { NO_INSTANCE } from "#/binding";
|
|
1
|
+
import type { AmbientResolution } from "#/ambient/active-container";
|
|
3
2
|
import type { Container } from "#/container/container";
|
|
3
|
+
import type { Binding, ConstantBinding, DynamicAsyncBinding, DynamicBinding } from "#/core/binding";
|
|
4
|
+
import { NO_INSTANCE } from "#/core/binding";
|
|
5
|
+
import type { BindingRegistry } from "#/core/registry";
|
|
6
|
+
import { NO_TAG_KEYS } from "#/core/tag";
|
|
7
|
+
import type { Token } from "#/core/token";
|
|
8
|
+
import { tokenName } from "#/core/token";
|
|
9
|
+
import type {
|
|
10
|
+
ActivationHandler,
|
|
11
|
+
BindingIdentifier,
|
|
12
|
+
BindingTag,
|
|
13
|
+
ConstraintContext,
|
|
14
|
+
Constructor,
|
|
15
|
+
ResolutionFrame,
|
|
16
|
+
ResolveOptions,
|
|
17
|
+
} from "#/core/types";
|
|
18
|
+
import type { ResolutionDiagnostics } from "#/errors/diagnostics";
|
|
4
19
|
import {
|
|
5
20
|
AsyncActivationError,
|
|
6
21
|
AsyncResolutionError,
|
|
7
22
|
CircularDependencyError,
|
|
23
|
+
DisposedContainerError,
|
|
8
24
|
InternalError,
|
|
9
25
|
MissingMetadataError,
|
|
10
26
|
MissingScopeContextError,
|
|
11
27
|
NoMatchingBindingError,
|
|
12
28
|
TokenNotBoundError,
|
|
13
|
-
} from "#/errors";
|
|
29
|
+
} from "#/errors/errors";
|
|
30
|
+
import type { DependencySlot } from "#/injection/resolve-options";
|
|
31
|
+
import { isNameOnlyOptions, resolveOptionsForSlot, singleTagOnlyOf } from "#/injection/resolve-options";
|
|
32
|
+
import type { LifecycleManager } from "#/lifecycle/lifecycle-manager";
|
|
33
|
+
import type { ScopeManager } from "#/lifecycle/scope-manager";
|
|
34
|
+
import { SCOPED_MISS } from "#/lifecycle/scope-manager";
|
|
14
35
|
import type { MetadataReader, ParamMetadata } from "#/metadata/metadata-types";
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { ClassIntrospector } from "#/resolution/class-introspector";
|
|
21
|
-
import type { ResolutionDiagnostics } from "#/resolution/diagnostics";
|
|
22
|
-
import type { ResolverCallbacks } from "#/resolution/environment";
|
|
36
|
+
import { ActivationNeedCache } from "#/resolution/cache/activation-need";
|
|
37
|
+
import type { DefaultLookupEntry } from "#/resolution/cache/binding-lookup-cache";
|
|
38
|
+
import { BindingLookupCache } from "#/resolution/cache/binding-lookup-cache";
|
|
39
|
+
import { ClassIntrospector } from "#/resolution/cache/class-introspector";
|
|
40
|
+
import type { ResolverCallbacks } from "#/resolution/context";
|
|
23
41
|
import {
|
|
24
42
|
AsyncCascadeContext,
|
|
25
43
|
AsyncLevelContext,
|
|
26
44
|
buildResolutionFrame,
|
|
27
45
|
DefaultResolutionContext,
|
|
28
|
-
} from "#/resolution/
|
|
29
|
-
import {
|
|
30
|
-
import type { LifecycleManager } from "#/resolution/lifecycle";
|
|
31
|
-
import type { BranchDepth, OwnedBranchPath } from "#/resolution/resolution-path";
|
|
46
|
+
} from "#/resolution/context";
|
|
47
|
+
import type { BranchDepth, OwnedBranchStack } from "#/resolution/path/resolution-path";
|
|
32
48
|
import {
|
|
33
49
|
branchDepthOf,
|
|
50
|
+
cycleNamesOf,
|
|
34
51
|
enterResolutionPath,
|
|
35
52
|
extendResolutionBranch,
|
|
36
|
-
extendResolutionStackBranch,
|
|
37
53
|
ROOT_BRANCH,
|
|
38
54
|
UNOWNED_BRANCH,
|
|
39
|
-
} from "#/resolution/resolution-path";
|
|
40
|
-
import
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
import type {
|
|
46
|
-
ActivationHandler,
|
|
47
|
-
BindingIdentifier,
|
|
48
|
-
ConstraintContext,
|
|
49
|
-
Constructor,
|
|
50
|
-
ResolutionFrame,
|
|
51
|
-
ResolveOptions,
|
|
52
|
-
} from "#/types";
|
|
55
|
+
} from "#/resolution/path/resolution-path";
|
|
56
|
+
import { InstantiationPlanCompiler, PLAN_RETRY } from "#/resolution/plan/instantiation-plan";
|
|
57
|
+
import { matchesSlot, requestedTagKeyMask, selectAllBindings, selectBinding } from "#/resolution/select/binding-select";
|
|
58
|
+
|
|
59
|
+
// Where a multi-tag resolve switches from scanning the token's list to walking the tag indexes.
|
|
60
|
+
const MULTI_TAG_INDEX_THRESHOLD = 8;
|
|
53
61
|
|
|
54
62
|
const EMPTY_STRING_LIST: ReadonlyArray<string> = [];
|
|
55
63
|
const EMPTY_FRAME_LIST: ReadonlyArray<ResolutionFrame> = [];
|
|
@@ -67,26 +75,29 @@ const ROOT_CONSTRAINT_CONTEXT = {
|
|
|
67
75
|
*/
|
|
68
76
|
export class DependencyResolver implements ResolverCallbacks {
|
|
69
77
|
readonly #syncResolutionContextPool: Array<DefaultResolutionContext> = [];
|
|
78
|
+
// Contexts bound to the cascade pair — deferred: only an async cascade's sync resolves need it.
|
|
79
|
+
#cascadeContextPool: Array<DefaultResolutionContext> | undefined;
|
|
70
80
|
/**
|
|
71
|
-
* The
|
|
81
|
+
* The stack a top-level **sync** resolve reuses instead of minting an array per call.
|
|
72
82
|
*
|
|
73
|
-
* @remarks Read directly rather than through an accessor
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* context skip storing pointers it already holds.
|
|
83
|
+
* @remarks Read directly rather than through an accessor: a shallow resolve is one top-level
|
|
84
|
+
* call, so a call there is not amortised over anything. Every sync lane pops what it pushes, so
|
|
85
|
+
* `rootStack.length === 0` means no resolve holds the stack; async appends without popping and
|
|
86
|
+
* mints its own. Keeping it stable is also what lets a pooled context skip re-storing it.
|
|
78
87
|
*/
|
|
79
|
-
readonly rootPath: Array<string> = [];
|
|
80
88
|
readonly rootStack: Array<ResolutionFrame> = [];
|
|
81
|
-
// The open synchronous factory cascade: its
|
|
89
|
+
// The open synchronous factory cascade: its stack is the ancestor chain, and it is balanced
|
|
82
90
|
// because synchronous code does not interleave.
|
|
83
|
-
readonly #cascadePath: Array<string> = [];
|
|
84
91
|
readonly #cascadeStack: Array<ResolutionFrame> = [];
|
|
85
92
|
#cascadeContext: AsyncCascadeContext | undefined;
|
|
86
93
|
// Compiled plans; `null` marks a binding as unplannable under the current cache versions.
|
|
87
94
|
readonly #classPlanByBindingId = new Map<BindingIdentifier, (() => unknown) | null>();
|
|
88
95
|
#classPlanRegistryVersion = -1;
|
|
89
96
|
#classPlanActivationVersion = -1;
|
|
97
|
+
// The async lane's plans, stamped and invalidated apart so neither lane pays the other's misses.
|
|
98
|
+
readonly #asyncPlanByBindingId = new Map<BindingIdentifier, (() => unknown) | null>();
|
|
99
|
+
#asyncPlanRegistryVersion = -1;
|
|
100
|
+
#asyncPlanActivationVersion = -1;
|
|
90
101
|
|
|
91
102
|
readonly #registry: BindingRegistry;
|
|
92
103
|
readonly #scope: ScopeManager;
|
|
@@ -119,26 +130,40 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
119
130
|
this.#activation = new ActivationNeedCache(lifecycle, this.#classes, registry);
|
|
120
131
|
}
|
|
121
132
|
|
|
122
|
-
/**
|
|
123
|
-
|
|
133
|
+
/** The reader this resolver was built with, which is the one its container answers with. */
|
|
134
|
+
get metadataReader(): MetadataReader {
|
|
135
|
+
return this.#metadataReader;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Structural counts for the {@link ResolutionDiagnostics} a container reports. */
|
|
139
|
+
describeCaches(): Pick<
|
|
140
|
+
ResolutionDiagnostics,
|
|
141
|
+
"compiledPlanCount" | "compiledAsyncPlanCount" | "syncContextPoolSize"
|
|
142
|
+
> {
|
|
124
143
|
let compiledPlanCount = 0;
|
|
125
144
|
for (const plan of this.#classPlanByBindingId.values()) {
|
|
126
145
|
if (plan !== null) {
|
|
127
146
|
compiledPlanCount += 1;
|
|
128
147
|
}
|
|
129
148
|
}
|
|
149
|
+
let compiledAsyncPlanCount = 0;
|
|
150
|
+
for (const plan of this.#asyncPlanByBindingId.values()) {
|
|
151
|
+
if (plan !== null) {
|
|
152
|
+
compiledAsyncPlanCount += 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
130
155
|
return {
|
|
131
156
|
compiledPlanCount,
|
|
157
|
+
compiledAsyncPlanCount,
|
|
132
158
|
syncContextPoolSize: this.#syncResolutionContextPool.length,
|
|
133
159
|
};
|
|
134
160
|
}
|
|
135
161
|
|
|
136
|
-
// ── Binding lookup
|
|
162
|
+
// ── Binding lookup ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
137
163
|
|
|
138
164
|
#findBinding(
|
|
139
165
|
token: Token<unknown> | Constructor,
|
|
140
166
|
options: ResolveOptions | undefined,
|
|
141
|
-
resolutionPath: Array<string>,
|
|
142
167
|
resolutionStack: Array<ResolutionFrame>,
|
|
143
168
|
): DefaultLookupEntry<DependencyResolver> | undefined {
|
|
144
169
|
if (options === undefined) {
|
|
@@ -148,19 +173,32 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
148
173
|
}
|
|
149
174
|
} else if (isNameOnlyOptions(options)) {
|
|
150
175
|
const namedBinding = this.#registry.getSimpleNamed(token, options.name);
|
|
151
|
-
if (
|
|
152
|
-
namedBinding !== undefined &&
|
|
153
|
-
this.#matchesBindingFast(namedBinding, options, resolutionPath, resolutionStack)
|
|
154
|
-
) {
|
|
176
|
+
if (namedBinding !== undefined && this.#matchesBindingFast(namedBinding, options, resolutionStack)) {
|
|
155
177
|
return { binding: namedBinding, owner: this };
|
|
156
178
|
}
|
|
157
179
|
} else {
|
|
158
180
|
const singleTag = singleTagOnlyOf(options);
|
|
159
181
|
if (singleTag !== undefined) {
|
|
160
|
-
const tagged = this.#registry.getSimpleTagged(token, singleTag
|
|
161
|
-
if (tagged !== undefined &&
|
|
182
|
+
const tagged = this.#registry.getSimpleTagged(token, singleTag);
|
|
183
|
+
if (tagged !== undefined && this.#satisfiesPredicate(tagged, options, resolutionStack)) {
|
|
162
184
|
return { binding: tagged, owner: this };
|
|
163
185
|
}
|
|
186
|
+
} else if (
|
|
187
|
+
options.name === undefined &&
|
|
188
|
+
// A threshold switches the data structure, never the semantics: under it the generic scan
|
|
189
|
+
// below beats walking the indexes, and both paths answer identically. Sized first, so a
|
|
190
|
+
// small list pays one length read and nothing else.
|
|
191
|
+
this.#registry.getAll(token).length > MULTI_TAG_INDEX_THRESHOLD &&
|
|
192
|
+
requestedTagKeyMask(options) !== NO_TAG_KEYS
|
|
193
|
+
) {
|
|
194
|
+
// A name-less multi-tag request matches only name-less tagged slots, and every such slot
|
|
195
|
+
// lives in one of the two tag indexes — so their union is the whole candidate set and the
|
|
196
|
+
// token's full list never needs scanning. Selection still owns predicates and specificity.
|
|
197
|
+
const selected = this.#selectMultiTagged(token, options, resolutionStack);
|
|
198
|
+
if (selected !== undefined) {
|
|
199
|
+
return { binding: selected, owner: this };
|
|
200
|
+
}
|
|
201
|
+
return this.#parent === undefined ? undefined : this.#parent.#findBinding(token, options, resolutionStack);
|
|
164
202
|
}
|
|
165
203
|
}
|
|
166
204
|
|
|
@@ -170,21 +208,16 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
170
208
|
// specificity to weigh and no ambiguity to report.
|
|
171
209
|
const selected =
|
|
172
210
|
bindings.length === 1
|
|
173
|
-
? this.#matchesBindingFast(bindings[0]!, options,
|
|
211
|
+
? this.#matchesBindingFast(bindings[0]!, options, resolutionStack)
|
|
174
212
|
? bindings[0]
|
|
175
213
|
: undefined
|
|
176
|
-
: selectBinding(
|
|
177
|
-
bindings,
|
|
178
|
-
options,
|
|
179
|
-
this.#makeConstraintContext(resolutionPath, resolutionStack, options),
|
|
180
|
-
tokenName(token),
|
|
181
|
-
);
|
|
214
|
+
: selectBinding(bindings, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
182
215
|
if (selected !== undefined) {
|
|
183
216
|
return { binding: selected, owner: this };
|
|
184
217
|
}
|
|
185
218
|
}
|
|
186
219
|
if (this.#parent !== undefined) {
|
|
187
|
-
return this.#parent.#findBinding(token, options,
|
|
220
|
+
return this.#parent.#findBinding(token, options, resolutionStack);
|
|
188
221
|
}
|
|
189
222
|
return undefined;
|
|
190
223
|
}
|
|
@@ -198,12 +231,11 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
198
231
|
#requireBinding(
|
|
199
232
|
token: Token<unknown> | Constructor,
|
|
200
233
|
options: ResolveOptions | undefined,
|
|
201
|
-
resolutionPath: Array<string>,
|
|
202
234
|
resolutionStack: Array<ResolutionFrame>,
|
|
203
235
|
): DefaultLookupEntry<DependencyResolver> {
|
|
204
236
|
let currentToken = token;
|
|
205
237
|
let visitedAliasTokens: Set<Token<unknown> | Constructor> | undefined;
|
|
206
|
-
let found = this.#findBinding(currentToken, options,
|
|
238
|
+
let found = this.#findBinding(currentToken, options, resolutionStack);
|
|
207
239
|
|
|
208
240
|
while (found !== undefined && found.binding.kind === "alias") {
|
|
209
241
|
const target = found.binding.target;
|
|
@@ -213,7 +245,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
213
245
|
}
|
|
214
246
|
visitedAliasTokens.add(target);
|
|
215
247
|
currentToken = target;
|
|
216
|
-
found = this.#findBinding(currentToken, options,
|
|
248
|
+
found = this.#findBinding(currentToken, options, resolutionStack);
|
|
217
249
|
}
|
|
218
250
|
|
|
219
251
|
if (found === undefined) {
|
|
@@ -238,7 +270,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
238
270
|
token: Token<unknown> | Constructor,
|
|
239
271
|
options: ResolveOptions | undefined,
|
|
240
272
|
): DefaultLookupEntry<DependencyResolver> | undefined {
|
|
241
|
-
return this.#findBinding(token, options, []
|
|
273
|
+
return this.#findBinding(token, options, []);
|
|
242
274
|
}
|
|
243
275
|
|
|
244
276
|
/**
|
|
@@ -248,47 +280,40 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
248
280
|
token: Token<unknown> | Constructor,
|
|
249
281
|
options: ResolveOptions | undefined,
|
|
250
282
|
): ReadonlyArray<Binding> {
|
|
251
|
-
return this.#candidateBindings(token, options, []
|
|
283
|
+
return this.#candidateBindings(token, options, []);
|
|
252
284
|
}
|
|
253
285
|
|
|
254
|
-
// ── Sync resolve
|
|
286
|
+
// ── Sync resolve ───────────────────────────────────────────────────────────────────────────────────────────────────
|
|
255
287
|
|
|
256
|
-
resolveFromContext<
|
|
257
|
-
token: Token<Value> | Constructor<Value>,
|
|
258
|
-
resolutionPath: Array<string>,
|
|
259
|
-
resolutionStack: Array<ResolutionFrame>,
|
|
260
|
-
): Value {
|
|
288
|
+
resolveFromContext<Value>(token: Token<Value> | Constructor<Value>, resolutionStack: Array<ResolutionFrame>): Value {
|
|
261
289
|
// Hot lane: own-registry fast default. Fall back to the chain-versioned memo
|
|
262
290
|
// (parent-chain walk + alias folding) only on miss or alias.
|
|
263
291
|
const fastBinding = this.#registry.getFastDefault(token);
|
|
264
292
|
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
265
|
-
return this.#resolveDefaultEntry(fastBinding, this,
|
|
293
|
+
return this.#resolveDefaultEntry(fastBinding, this, resolutionStack) as Value;
|
|
266
294
|
}
|
|
267
295
|
const entry = this.#lookup.defaultEntry(token);
|
|
268
296
|
if (entry === null) {
|
|
269
|
-
return this.resolve(token, undefined,
|
|
297
|
+
return this.resolve(token, undefined, resolutionStack);
|
|
270
298
|
}
|
|
271
|
-
return this.#resolveDefaultEntry(entry.binding, entry.owner,
|
|
299
|
+
return this.#resolveDefaultEntry(entry.binding, entry.owner, resolutionStack) as Value;
|
|
272
300
|
}
|
|
273
301
|
|
|
274
|
-
#resolveDefaultEntry(
|
|
275
|
-
binding: Binding,
|
|
276
|
-
owner: DependencyResolver,
|
|
277
|
-
resolutionPath: Array<string>,
|
|
278
|
-
resolutionStack: Array<ResolutionFrame>,
|
|
279
|
-
): unknown {
|
|
302
|
+
#resolveDefaultEntry(binding: Binding, owner: DependencyResolver, resolutionStack: Array<ResolutionFrame>): unknown {
|
|
280
303
|
const scope = binding.scope;
|
|
281
304
|
if (scope === "transient") {
|
|
282
305
|
if (binding.kind === "dynamic") {
|
|
306
|
+
// Container-level hooks belong to the binding's owner — a child-registered hook must not
|
|
307
|
+
// fire for a parent-owned binding, and the owner's must.
|
|
283
308
|
const containerHooks =
|
|
284
|
-
|
|
309
|
+
owner.#lifecycle.activationVersion === 0 ? undefined : owner.#lifecycle.activationHandlersFor(binding.token);
|
|
285
310
|
if (binding.onActivation === undefined && (containerHooks === undefined || containerHooks.length === 0)) {
|
|
286
|
-
return this.#resolveTransientDynamicSyncFromContext(binding,
|
|
311
|
+
return this.#resolveTransientDynamicSyncFromContext(binding, resolutionStack);
|
|
287
312
|
}
|
|
288
|
-
return this.#resolveTransientDynamicActivatedSync(binding, containerHooks,
|
|
313
|
+
return this.#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionStack);
|
|
289
314
|
}
|
|
290
315
|
// Compiled plans only run at the top level — inner levels keep the runtime cycle guard.
|
|
291
|
-
if ((binding.kind === "class" || binding.kind === "resolved") &&
|
|
316
|
+
if ((binding.kind === "class" || binding.kind === "resolved") && resolutionStack.length === 0) {
|
|
292
317
|
const plan = this.#getInstantiationPlan(binding);
|
|
293
318
|
if (plan !== null) {
|
|
294
319
|
return plan();
|
|
@@ -296,7 +321,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
296
321
|
}
|
|
297
322
|
} else if (scope === "singleton") {
|
|
298
323
|
// A constant is a singleton that is already its own instance.
|
|
299
|
-
if (
|
|
324
|
+
if (owner.#isPlainConstant(binding)) {
|
|
300
325
|
return binding.value;
|
|
301
326
|
}
|
|
302
327
|
const cachedSingleton = binding.instance;
|
|
@@ -304,7 +329,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
304
329
|
return cachedSingleton;
|
|
305
330
|
}
|
|
306
331
|
if (owner !== this) {
|
|
307
|
-
return owner.#resolveBinding(binding, undefined,
|
|
332
|
+
return owner.#resolveBinding(binding, undefined, resolutionStack, owner);
|
|
308
333
|
}
|
|
309
334
|
} else {
|
|
310
335
|
const cachedScoped = this.#readScoped(binding);
|
|
@@ -312,7 +337,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
312
337
|
return cachedScoped;
|
|
313
338
|
}
|
|
314
339
|
}
|
|
315
|
-
return this.#resolveBinding(binding, undefined,
|
|
340
|
+
return this.#resolveBinding(binding, undefined, resolutionStack, owner);
|
|
316
341
|
}
|
|
317
342
|
|
|
318
343
|
// Lean lane for an activated transient dynamic binding: same observable behavior as the
|
|
@@ -321,24 +346,22 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
321
346
|
#resolveTransientDynamicActivatedSync(
|
|
322
347
|
binding: DynamicBinding<unknown>,
|
|
323
348
|
containerHooks: ReadonlyArray<ActivationHandler<unknown>> | undefined,
|
|
324
|
-
resolutionPath: Array<string>,
|
|
325
349
|
resolutionStack: Array<ResolutionFrame>,
|
|
326
350
|
): unknown {
|
|
327
351
|
// Same O(1) cycle guard as the unhooked lane: this is still one sync call stack, so the flag
|
|
328
|
-
// *is* exact path membership
|
|
352
|
+
// *is* exact path membership.
|
|
329
353
|
const frame = this.#getResolutionFrame(binding);
|
|
330
354
|
const tokenDisplayName = frame.tokenName;
|
|
331
355
|
if (binding.inFlight) {
|
|
332
|
-
throw new CircularDependencyError(
|
|
356
|
+
throw new CircularDependencyError(cycleNamesOf(resolutionStack, tokenDisplayName));
|
|
333
357
|
}
|
|
334
358
|
binding.inFlight = true;
|
|
335
|
-
resolutionPath.push(tokenDisplayName);
|
|
336
359
|
resolutionStack.push(frame);
|
|
337
360
|
try {
|
|
338
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
361
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, undefined);
|
|
339
362
|
const factoryResult = binding.factory(resolutionCtx);
|
|
340
363
|
if (factoryResult instanceof Promise) {
|
|
341
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
364
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
342
365
|
}
|
|
343
366
|
let activated = factoryResult;
|
|
344
367
|
if (binding.onActivation !== undefined) {
|
|
@@ -360,14 +383,22 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
360
383
|
return activated;
|
|
361
384
|
} finally {
|
|
362
385
|
resolutionStack.pop();
|
|
363
|
-
resolutionPath.pop();
|
|
364
386
|
binding.inFlight = false;
|
|
365
387
|
}
|
|
366
388
|
}
|
|
367
389
|
|
|
390
|
+
/** Chain-summed activation version: a plan can inline a parent-owned binding, so a parent's hook registration must invalidate it. */
|
|
391
|
+
#chainActivationVersion(): number {
|
|
392
|
+
let version = this.#lifecycle.activationVersion;
|
|
393
|
+
for (let current = this.#parent; current !== undefined; current = current.#parent) {
|
|
394
|
+
version += current.#lifecycle.activationVersion;
|
|
395
|
+
}
|
|
396
|
+
return version;
|
|
397
|
+
}
|
|
398
|
+
|
|
368
399
|
#getInstantiationPlan(binding: Binding & { kind: "class" | "resolved" }): (() => unknown) | null {
|
|
369
400
|
const registryVersion = this.#lookup.chainVersion();
|
|
370
|
-
const activationVersion = this.#
|
|
401
|
+
const activationVersion = this.#chainActivationVersion();
|
|
371
402
|
if (registryVersion !== this.#classPlanRegistryVersion || activationVersion !== this.#classPlanActivationVersion) {
|
|
372
403
|
this.#classPlanByBindingId.clear();
|
|
373
404
|
this.#classPlanRegistryVersion = registryVersion;
|
|
@@ -388,7 +419,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
388
419
|
|
|
389
420
|
// Compiler behind #getInstantiationPlan — cold path, so the host indirection costs nothing hot.
|
|
390
421
|
readonly #planCompiler = new InstantiationPlanCompiler({
|
|
391
|
-
hasActivationHandlers: (
|
|
422
|
+
hasActivationHandlers: (binding) => this.#ownerOf(binding).#lifecycle.hasActivationHandlers(binding.token),
|
|
392
423
|
knownPostConstruct: (target) => this.#classes.knownPostConstruct(target),
|
|
393
424
|
needsActiveContainer: (target) => this.#classes.needsActiveContainer(target),
|
|
394
425
|
getConstructorMetadata: (target) => this.#classes.constructorMetadata(target),
|
|
@@ -396,27 +427,84 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
396
427
|
const entry = this.#lookup.defaultEntry(token);
|
|
397
428
|
return entry === null ? null : { binding: entry.binding };
|
|
398
429
|
},
|
|
430
|
+
// Exactly what #findBinding's named lane accepts, minus the half that reads a path: a predicate
|
|
431
|
+
// is the compiler's cue to leave the selection to the runtime.
|
|
432
|
+
lookupPathIndependentNamedEntry: (token, options) => {
|
|
433
|
+
const entry = this.#lookup.namedEntry(token, options.name);
|
|
434
|
+
if (entry === null || entry.binding.predicate !== undefined || !matchesSlot(entry.binding.slot, options)) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return { binding: entry.binding };
|
|
438
|
+
},
|
|
439
|
+
// The named rule verbatim, on the single-tag lane's memo.
|
|
440
|
+
lookupPathIndependentTaggedEntry: (token, options) => {
|
|
441
|
+
const singleTag = singleTagOnlyOf(options);
|
|
442
|
+
if (singleTag === undefined) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
const entry = this.#lookup.taggedEntry(token, singleTag);
|
|
446
|
+
if (entry === null || entry.binding.predicate !== undefined || !matchesSlot(entry.binding.slot, options)) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
return { binding: entry.binding };
|
|
450
|
+
},
|
|
399
451
|
getResolutionFrame: (binding) => this.#getResolutionFrame(binding),
|
|
400
452
|
// Dispatches exactly as #resolveDep does, so an escaped dep is indistinguishable
|
|
401
453
|
// from the same dep on a fully interpreted resolve.
|
|
402
|
-
resolveEscaped: (token, options, arity,
|
|
454
|
+
resolveEscaped: (token, options, arity, resolutionStack) => {
|
|
403
455
|
if (arity === "all") {
|
|
404
|
-
return this.resolveAll(token, options,
|
|
456
|
+
return this.resolveAll(token, options, resolutionStack);
|
|
405
457
|
}
|
|
406
458
|
if (arity === "optional") {
|
|
407
|
-
return this.resolveOptional(token, options,
|
|
459
|
+
return this.resolveOptional(token, options, resolutionStack);
|
|
408
460
|
}
|
|
409
461
|
if (options === undefined) {
|
|
410
|
-
return this.resolveFromContext(token,
|
|
462
|
+
return this.resolveFromContext(token, resolutionStack);
|
|
411
463
|
}
|
|
412
|
-
return this.resolve(token, options,
|
|
464
|
+
return this.resolve(token, options, resolutionStack);
|
|
465
|
+
},
|
|
466
|
+
// Dispatches exactly as #resolveDepAsync does, for the async lane's escapes.
|
|
467
|
+
resolveEscapedAsync: (token, options, arity, resolutionStack) => {
|
|
468
|
+
if (arity === "all") {
|
|
469
|
+
return this.resolveAllAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
470
|
+
}
|
|
471
|
+
if (arity === "optional") {
|
|
472
|
+
return this.resolveOptionalAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
473
|
+
}
|
|
474
|
+
if (options === undefined) {
|
|
475
|
+
return this.resolveAsyncFromContext(token, resolutionStack, UNOWNED_BRANCH);
|
|
476
|
+
}
|
|
477
|
+
return this.resolveAsync(token, options, resolutionStack, UNOWNED_BRANCH);
|
|
413
478
|
},
|
|
414
479
|
});
|
|
415
480
|
|
|
416
|
-
|
|
481
|
+
/** The async lane's plan for a statically-visible transient binding, mirroring the sync getter. */
|
|
482
|
+
#getAsyncInstantiationPlan(
|
|
483
|
+
binding: Binding & { kind: "class" | "resolved" | "resolved-async" },
|
|
484
|
+
): (() => unknown) | null {
|
|
485
|
+
const registryVersion = this.#lookup.chainVersion();
|
|
486
|
+
const activationVersion = this.#chainActivationVersion();
|
|
487
|
+
if (registryVersion !== this.#asyncPlanRegistryVersion || activationVersion !== this.#asyncPlanActivationVersion) {
|
|
488
|
+
this.#asyncPlanByBindingId.clear();
|
|
489
|
+
this.#asyncPlanRegistryVersion = registryVersion;
|
|
490
|
+
this.#asyncPlanActivationVersion = activationVersion;
|
|
491
|
+
}
|
|
492
|
+
const cached = this.#asyncPlanByBindingId.get(binding.id);
|
|
493
|
+
if (cached !== undefined) {
|
|
494
|
+
return cached;
|
|
495
|
+
}
|
|
496
|
+
const compiled = this.#planCompiler.compileAsync(binding);
|
|
497
|
+
if (compiled === PLAN_RETRY) {
|
|
498
|
+
// Lifecycle metadata not discovered yet — the fallback resolve discovers it; retry then.
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
this.#asyncPlanByBindingId.set(binding.id, compiled);
|
|
502
|
+
return compiled;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
resolve<Value>(
|
|
417
506
|
token: Token<Value> | Constructor<Value>,
|
|
418
507
|
options: ResolveOptions | undefined,
|
|
419
|
-
resolutionPath: Array<string>,
|
|
420
508
|
resolutionStack: Array<ResolutionFrame>,
|
|
421
509
|
): Value {
|
|
422
510
|
// Name-only fast lane: memoized lookup, dispatching just the shapes whose
|
|
@@ -425,7 +513,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
425
513
|
const namedEntry = this.#lookup.namedEntry(token, options.name);
|
|
426
514
|
if (namedEntry !== null) {
|
|
427
515
|
const namedBinding = namedEntry.binding;
|
|
428
|
-
if (
|
|
516
|
+
if (namedEntry.owner.#isPlainConstant(namedBinding)) {
|
|
429
517
|
return namedBinding.value as Value;
|
|
430
518
|
}
|
|
431
519
|
if (namedBinding.scope === "singleton" && namedBinding.instance !== NO_INSTANCE) {
|
|
@@ -433,24 +521,40 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
433
521
|
}
|
|
434
522
|
// Everything else keeps the full path (context, activation, guards).
|
|
435
523
|
}
|
|
524
|
+
} else if (options !== undefined) {
|
|
525
|
+
// Single-tag fast lane: the named lane's tagged twin, memoizing the chain walk.
|
|
526
|
+
const singleTag = singleTagOnlyOf(options);
|
|
527
|
+
if (singleTag !== undefined) {
|
|
528
|
+
const taggedEntry = this.#lookup.taggedEntry(token, singleTag);
|
|
529
|
+
if (taggedEntry !== null) {
|
|
530
|
+
const taggedBinding = taggedEntry.binding;
|
|
531
|
+
if (taggedEntry.owner.#isPlainConstant(taggedBinding)) {
|
|
532
|
+
return taggedBinding.value as Value;
|
|
533
|
+
}
|
|
534
|
+
if (taggedBinding.scope === "singleton" && taggedBinding.instance !== NO_INSTANCE) {
|
|
535
|
+
return taggedBinding.instance as Value;
|
|
536
|
+
}
|
|
537
|
+
// Everything else keeps the full path (context, activation, guards).
|
|
538
|
+
}
|
|
539
|
+
}
|
|
436
540
|
}
|
|
437
541
|
|
|
438
|
-
const { binding, owner } = this.#requireBinding(token, options,
|
|
542
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
439
543
|
|
|
440
544
|
// A singleton owned by a parent resolver is resolved there, so the parent caches it.
|
|
441
545
|
if (binding.scope === "singleton" && owner !== this) {
|
|
442
|
-
return owner.#resolveBinding(binding, options,
|
|
546
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner) as Value;
|
|
443
547
|
}
|
|
444
|
-
return this.#resolveBinding(binding, options,
|
|
548
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner) as Value;
|
|
445
549
|
}
|
|
446
550
|
|
|
447
551
|
#resolveBinding(
|
|
448
552
|
binding: Binding,
|
|
449
553
|
options: ResolveOptions | undefined,
|
|
450
|
-
resolutionPath: Array<string>,
|
|
451
554
|
resolutionStack: Array<ResolutionFrame>,
|
|
555
|
+
owner: DependencyResolver,
|
|
452
556
|
): unknown {
|
|
453
|
-
if (
|
|
557
|
+
if (owner.#isPlainConstant(binding)) {
|
|
454
558
|
return binding.value;
|
|
455
559
|
}
|
|
456
560
|
|
|
@@ -459,62 +563,91 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
459
563
|
if (binding.instance !== NO_INSTANCE) {
|
|
460
564
|
return binding.instance;
|
|
461
565
|
}
|
|
566
|
+
// An async materialization already in flight must not be raced by a second, sync one.
|
|
567
|
+
if (this.#scope.getInflight(binding.id) !== undefined) {
|
|
568
|
+
throw new AsyncResolutionError(
|
|
569
|
+
resolutionStack[0]?.tokenName ?? tokenName(binding.token),
|
|
570
|
+
tokenName(binding.token),
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
if (this.#scope.isClosed) {
|
|
574
|
+
throw new DisposedContainerError();
|
|
575
|
+
}
|
|
462
576
|
} else if (scope === "scoped") {
|
|
463
577
|
const cachedScoped = this.#readScoped(binding);
|
|
464
578
|
if (cachedScoped !== SCOPED_MISS) {
|
|
465
579
|
return cachedScoped;
|
|
466
580
|
}
|
|
581
|
+
if (this.#scope.getInflight(binding.id) !== undefined) {
|
|
582
|
+
throw new AsyncResolutionError(
|
|
583
|
+
resolutionStack[0]?.tokenName ?? tokenName(binding.token),
|
|
584
|
+
tokenName(binding.token),
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
if (this.#scope.isClosed) {
|
|
588
|
+
throw new DisposedContainerError();
|
|
589
|
+
}
|
|
467
590
|
}
|
|
468
591
|
|
|
469
592
|
const frame = this.#getResolutionFrame(binding);
|
|
470
593
|
const tokenDisplayName = frame.tokenName;
|
|
471
|
-
const resolutionSet = enterResolutionPath(
|
|
472
|
-
resolutionStack.push(frame);
|
|
594
|
+
const resolutionSet = enterResolutionPath(resolutionStack, frame);
|
|
473
595
|
try {
|
|
474
|
-
const needsActivation =
|
|
596
|
+
const needsActivation = owner.#activation.needsActivation(binding);
|
|
475
597
|
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
476
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
598
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, options);
|
|
477
599
|
const dynamicResult = binding.factory(resolutionCtx);
|
|
478
600
|
if (dynamicResult instanceof Promise) {
|
|
479
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
601
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
480
602
|
}
|
|
481
603
|
return dynamicResult;
|
|
482
604
|
}
|
|
483
605
|
|
|
484
606
|
const resolutionCtx =
|
|
485
607
|
needsActivation || requiresResolutionContext(binding)
|
|
486
|
-
? this.#acquireSyncResolutionContext(
|
|
608
|
+
? this.#acquireSyncResolutionContext(resolutionStack, options)
|
|
487
609
|
: undefined;
|
|
488
610
|
|
|
489
|
-
const instance = this.#instantiateSync(binding, resolutionCtx,
|
|
611
|
+
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionStack);
|
|
490
612
|
|
|
491
|
-
|
|
492
|
-
|
|
613
|
+
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
614
|
+
const activated = owner.#activation.refreshAfterFirstInstantiation(binding, needsActivation)
|
|
615
|
+
? owner.#lifecycle.runActivationSync(
|
|
493
616
|
resolutionCtx as DefaultResolutionContext,
|
|
494
617
|
binding,
|
|
495
618
|
instance,
|
|
496
|
-
|
|
619
|
+
owner.#metadataReader,
|
|
497
620
|
)
|
|
498
621
|
: instance;
|
|
499
622
|
|
|
500
623
|
if (scope === "singleton") {
|
|
501
624
|
this.#scope.setSingleton(binding, activated);
|
|
502
625
|
} else if (scope === "scoped") {
|
|
503
|
-
this.#scope.setScoped(binding
|
|
626
|
+
this.#scope.setScoped(binding, activated);
|
|
504
627
|
}
|
|
505
628
|
|
|
506
629
|
return activated;
|
|
507
630
|
} finally {
|
|
508
631
|
resolutionStack.pop();
|
|
509
|
-
|
|
510
|
-
resolutionSet?.delete(tokenDisplayName);
|
|
632
|
+
resolutionSet?.delete(frame.bindingId);
|
|
511
633
|
}
|
|
512
634
|
}
|
|
513
635
|
|
|
636
|
+
/** Path-continuing resolution handed to the ambient slot while an accessor class constructs. */
|
|
637
|
+
#ambientResolutionFor(resolutionStack: Array<ResolutionFrame>): AmbientResolution {
|
|
638
|
+
return {
|
|
639
|
+
resolve: <Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value =>
|
|
640
|
+
options === undefined
|
|
641
|
+
? this.resolveFromContext(token, resolutionStack)
|
|
642
|
+
: this.resolve(token, options, resolutionStack),
|
|
643
|
+
resolveOptional: <Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined =>
|
|
644
|
+
this.resolveOptional(token, options, resolutionStack),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
514
648
|
#instantiateSync(
|
|
515
649
|
binding: Binding,
|
|
516
650
|
ctx: DefaultResolutionContext | undefined,
|
|
517
|
-
resolutionPath: Array<string>,
|
|
518
651
|
resolutionStack: Array<ResolutionFrame>,
|
|
519
652
|
): unknown {
|
|
520
653
|
switch (binding.kind) {
|
|
@@ -527,30 +660,34 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
527
660
|
}
|
|
528
661
|
const factoryResult = binding.factory(ctx);
|
|
529
662
|
if (factoryResult instanceof Promise) {
|
|
530
|
-
throw
|
|
663
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
531
664
|
}
|
|
532
665
|
return factoryResult;
|
|
533
666
|
}
|
|
534
667
|
|
|
535
668
|
case "dynamic-async":
|
|
536
|
-
throw
|
|
669
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
537
670
|
|
|
538
671
|
case "class": {
|
|
539
|
-
const deps = this.#resolveDeps(this.#constructorParams(binding.target),
|
|
540
|
-
return this.#classes.instantiate(
|
|
672
|
+
const deps = this.#resolveDeps(this.#constructorParams(binding.target), resolutionStack);
|
|
673
|
+
return this.#classes.instantiate(
|
|
674
|
+
binding.target,
|
|
675
|
+
deps,
|
|
676
|
+
this.#classes.needsActiveContainer(binding.target) ? this.#ambientResolutionFor(resolutionStack) : undefined,
|
|
677
|
+
);
|
|
541
678
|
}
|
|
542
679
|
|
|
543
680
|
case "resolved": {
|
|
544
|
-
const deps = this.#resolveDeps(binding.deps,
|
|
681
|
+
const deps = this.#resolveDeps(binding.deps, resolutionStack);
|
|
545
682
|
const factoryResult = binding.factory(...deps);
|
|
546
683
|
if (factoryResult instanceof Promise) {
|
|
547
|
-
throw
|
|
684
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
548
685
|
}
|
|
549
686
|
return factoryResult;
|
|
550
687
|
}
|
|
551
688
|
|
|
552
689
|
case "resolved-async":
|
|
553
|
-
throw
|
|
690
|
+
throw asyncResolutionErrorFor(binding, resolutionStack);
|
|
554
691
|
|
|
555
692
|
case "alias":
|
|
556
693
|
throw new InternalError("alias should have been followed before instantiation");
|
|
@@ -576,66 +713,65 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
576
713
|
|
|
577
714
|
// One dispatch table for both dependency sources — constructor params and `toResolved`
|
|
578
715
|
// descriptors declare the same four things.
|
|
579
|
-
#resolveDeps(
|
|
580
|
-
deps: ReadonlyArray<DependencySlot>,
|
|
581
|
-
resolutionPath: Array<string>,
|
|
582
|
-
resolutionStack: Array<ResolutionFrame>,
|
|
583
|
-
): Array<unknown> {
|
|
716
|
+
#resolveDeps(deps: ReadonlyArray<DependencySlot>, resolutionStack: Array<ResolutionFrame>): Array<unknown> {
|
|
584
717
|
const count = deps.length;
|
|
585
718
|
if (count === 0) {
|
|
586
719
|
return [];
|
|
587
720
|
}
|
|
588
721
|
if (count === 1) {
|
|
589
|
-
return [this.#resolveDep(deps[0]!,
|
|
722
|
+
return [this.#resolveDep(deps[0]!, resolutionStack)];
|
|
590
723
|
}
|
|
591
724
|
const resolved = new Array<unknown>(count);
|
|
592
725
|
for (let index = 0; index < count; index += 1) {
|
|
593
|
-
resolved[index] = this.#resolveDep(deps[index]!,
|
|
726
|
+
resolved[index] = this.#resolveDep(deps[index]!, resolutionStack);
|
|
594
727
|
}
|
|
595
728
|
return resolved;
|
|
596
729
|
}
|
|
597
730
|
|
|
598
|
-
#resolveDep(dep: DependencySlot,
|
|
599
|
-
const options =
|
|
731
|
+
#resolveDep(dep: DependencySlot, resolutionStack: Array<ResolutionFrame>): unknown {
|
|
732
|
+
const options = resolveOptionsForSlot(dep);
|
|
600
733
|
if (dep.multi) {
|
|
601
|
-
return this.resolveAll(dep.token, options,
|
|
734
|
+
return this.resolveAll(dep.token, options, resolutionStack);
|
|
602
735
|
}
|
|
603
736
|
if (dep.optional) {
|
|
604
|
-
return this.resolveOptional(dep.token, options,
|
|
737
|
+
return this.resolveOptional(dep.token, options, resolutionStack);
|
|
605
738
|
}
|
|
606
739
|
if (options === undefined) {
|
|
607
|
-
return this.resolveFromContext(dep.token,
|
|
740
|
+
return this.resolveFromContext(dep.token, resolutionStack);
|
|
608
741
|
}
|
|
609
|
-
return this.resolve(dep.token, options,
|
|
742
|
+
return this.resolve(dep.token, options, resolutionStack);
|
|
610
743
|
}
|
|
611
744
|
|
|
612
|
-
resolveOptional<
|
|
745
|
+
resolveOptional<Value>(
|
|
613
746
|
token: Token<Value> | Constructor<Value>,
|
|
614
747
|
options: ResolveOptions | undefined,
|
|
615
|
-
resolutionPath: Array<string>,
|
|
616
748
|
resolutionStack: Array<ResolutionFrame>,
|
|
617
749
|
): Value | undefined {
|
|
618
|
-
|
|
750
|
+
const entry = this.#findBinding(token, options, resolutionStack);
|
|
751
|
+
if (entry === undefined) {
|
|
619
752
|
return undefined;
|
|
620
753
|
}
|
|
621
|
-
|
|
754
|
+
// Resolve the entry the probe found: re-looking the token up would evaluate every `when()`
|
|
755
|
+
// predicate a second time, and a changed answer would throw where `undefined` was promised.
|
|
756
|
+
const { binding, owner } = entry;
|
|
757
|
+
if (binding.kind === "alias") {
|
|
758
|
+
return this.resolve(token, options, resolutionStack);
|
|
759
|
+
}
|
|
760
|
+
if (binding.scope === "singleton" && owner !== this) {
|
|
761
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner) as Value;
|
|
762
|
+
}
|
|
763
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner) as Value;
|
|
622
764
|
}
|
|
623
765
|
|
|
624
|
-
resolveAll<
|
|
766
|
+
resolveAll<Value>(
|
|
625
767
|
token: Token<Value> | Constructor<Value>,
|
|
626
768
|
options: ResolveOptions | undefined,
|
|
627
|
-
resolutionPath: Array<string>,
|
|
628
769
|
resolutionStack: Array<ResolutionFrame>,
|
|
629
770
|
): Array<Value> {
|
|
630
|
-
const candidates = this.#candidateBindings(token, options,
|
|
771
|
+
const candidates = this.#candidateBindings(token, options, resolutionStack);
|
|
631
772
|
const resolved = new Array<Value>(candidates.length);
|
|
632
773
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
633
|
-
resolved[index] = this.#resolveCandidateSync(
|
|
634
|
-
candidates[index]!,
|
|
635
|
-
options,
|
|
636
|
-
resolutionPath,
|
|
637
|
-
resolutionStack,
|
|
638
|
-
) as Value;
|
|
774
|
+
resolved[index] = this.#resolveCandidateSync(candidates[index]!, options, resolutionStack) as Value;
|
|
639
775
|
}
|
|
640
776
|
return resolved;
|
|
641
777
|
}
|
|
@@ -644,34 +780,25 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
644
780
|
#candidateBindings(
|
|
645
781
|
token: Token<unknown> | Constructor,
|
|
646
782
|
options: ResolveOptions | undefined,
|
|
647
|
-
resolutionPath: Array<string>,
|
|
648
783
|
resolutionStack: Array<ResolutionFrame>,
|
|
649
784
|
): ReadonlyArray<Binding> {
|
|
650
|
-
if (options !== undefined
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (!anyPredicate(named)) {
|
|
655
|
-
return named;
|
|
785
|
+
if (options !== undefined) {
|
|
786
|
+
const indexed = this.#indexedCandidates(token, options, resolutionStack);
|
|
787
|
+
if (indexed !== null) {
|
|
788
|
+
return indexed;
|
|
656
789
|
}
|
|
657
|
-
return selectAllBindings(named, options, this.#makeConstraintContext(resolutionPath, resolutionStack, options));
|
|
658
790
|
}
|
|
659
791
|
const allBindings = this.#allBindingsFromChain(token);
|
|
660
792
|
if (allBindings.length === 0) {
|
|
661
793
|
return allBindings;
|
|
662
794
|
}
|
|
663
|
-
return selectAllBindings(
|
|
664
|
-
allBindings,
|
|
665
|
-
options,
|
|
666
|
-
this.#makeConstraintContext(resolutionPath, resolutionStack, options),
|
|
667
|
-
);
|
|
795
|
+
return selectAllBindings(allBindings, options, this.#makeConstraintContext(resolutionStack, options));
|
|
668
796
|
}
|
|
669
797
|
|
|
670
|
-
// ── Async resolve
|
|
798
|
+
// ── Async resolve ──────────────────────────────────────────────────────────────────────────────────────────────────
|
|
671
799
|
|
|
672
|
-
resolveAsyncFromContext<
|
|
800
|
+
resolveAsyncFromContext<Value>(
|
|
673
801
|
token: Token<Value> | Constructor<Value>,
|
|
674
|
-
resolutionPath: Array<string>,
|
|
675
802
|
resolutionStack: Array<ResolutionFrame>,
|
|
676
803
|
branchDepth: BranchDepth,
|
|
677
804
|
): Promise<Value> {
|
|
@@ -687,88 +814,74 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
687
814
|
) {
|
|
688
815
|
return this.#resolveTransientDynamicAsyncFromContext(
|
|
689
816
|
fastBinding,
|
|
690
|
-
resolutionPath,
|
|
691
817
|
resolutionStack,
|
|
692
818
|
branchDepth,
|
|
693
819
|
) as Promise<Value>;
|
|
694
820
|
}
|
|
695
|
-
return this.#resolveAsyncDefaultEntry(
|
|
696
|
-
fastBinding,
|
|
697
|
-
this,
|
|
698
|
-
resolutionPath,
|
|
699
|
-
resolutionStack,
|
|
700
|
-
branchDepth,
|
|
701
|
-
) as Promise<Value>;
|
|
821
|
+
return this.#resolveAsyncDefaultEntry(fastBinding, this, resolutionStack, branchDepth) as Promise<Value>;
|
|
702
822
|
}
|
|
703
823
|
const entry = this.#lookup.defaultEntry(token);
|
|
704
824
|
if (entry === null) {
|
|
705
|
-
return this.resolveAsync(token, undefined,
|
|
706
|
-
}
|
|
707
|
-
return this.#resolveAsyncDefaultEntry(
|
|
708
|
-
entry.binding,
|
|
709
|
-
entry.owner,
|
|
710
|
-
resolutionPath,
|
|
711
|
-
resolutionStack,
|
|
712
|
-
branchDepth,
|
|
713
|
-
) as Promise<Value>;
|
|
825
|
+
return this.resolveAsync(token, undefined, resolutionStack, branchDepth);
|
|
826
|
+
}
|
|
827
|
+
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner, resolutionStack, branchDepth) as Promise<Value>;
|
|
714
828
|
}
|
|
715
829
|
|
|
716
830
|
#resolveAsyncDefaultEntry(
|
|
717
831
|
binding: Binding,
|
|
718
832
|
owner: DependencyResolver,
|
|
719
|
-
resolutionPath: Array<string>,
|
|
720
833
|
resolutionStack: Array<ResolutionFrame>,
|
|
721
834
|
branchDepth: BranchDepth,
|
|
722
835
|
): Promise<unknown> {
|
|
723
|
-
if (
|
|
836
|
+
if (owner.#isPlainConstant(binding)) {
|
|
724
837
|
return Promise.resolve(binding.value);
|
|
725
838
|
}
|
|
726
839
|
const scope = binding.scope;
|
|
727
840
|
if (scope === "transient") {
|
|
728
|
-
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !
|
|
729
|
-
return this.#resolveTransientDynamicAsyncFromContext(binding,
|
|
841
|
+
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") && !owner.#hasAnyActivation(binding)) {
|
|
842
|
+
return this.#resolveTransientDynamicAsyncFromContext(binding, resolutionStack, branchDepth);
|
|
730
843
|
}
|
|
731
844
|
} else if (scope === "singleton") {
|
|
732
845
|
if (binding.instance !== NO_INSTANCE) {
|
|
733
846
|
return Promise.resolve(binding.instance);
|
|
734
847
|
}
|
|
735
848
|
if (owner !== this) {
|
|
736
|
-
return owner.#resolveBindingAsync(binding, undefined,
|
|
849
|
+
return owner.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
737
850
|
}
|
|
738
851
|
} else if (this.#scope.isChild) {
|
|
739
|
-
|
|
740
|
-
|
|
852
|
+
const cachedScoped = this.#scope.readScoped(binding.id);
|
|
853
|
+
if (cachedScoped !== SCOPED_MISS) {
|
|
854
|
+
return Promise.resolve(cachedScoped);
|
|
741
855
|
}
|
|
742
856
|
} else {
|
|
743
857
|
// Not `#readScoped`: this entry point reports failure as a rejection, never a sync throw.
|
|
744
858
|
return Promise.reject(new MissingScopeContextError(tokenName(binding.token)));
|
|
745
859
|
}
|
|
746
|
-
return this.#resolveBindingAsync(binding, undefined,
|
|
860
|
+
return this.#resolveBindingAsync(binding, undefined, resolutionStack, branchDepth, owner);
|
|
747
861
|
}
|
|
748
862
|
|
|
749
|
-
async resolveAsync<
|
|
863
|
+
async resolveAsync<Value>(
|
|
750
864
|
token: Token<Value> | Constructor<Value>,
|
|
751
865
|
options: ResolveOptions | undefined,
|
|
752
|
-
resolutionPath: Array<string>,
|
|
753
866
|
resolutionStack: Array<ResolutionFrame>,
|
|
754
867
|
branchDepth: BranchDepth = UNOWNED_BRANCH,
|
|
755
868
|
): Promise<Value> {
|
|
756
|
-
const { binding, owner } = this.#requireBinding(token, options,
|
|
869
|
+
const { binding, owner } = this.#requireBinding(token, options, resolutionStack);
|
|
757
870
|
|
|
758
871
|
if (binding.scope === "singleton" && owner !== this) {
|
|
759
|
-
return owner.#resolveBindingAsync(binding, options,
|
|
872
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner) as Promise<Value>;
|
|
760
873
|
}
|
|
761
|
-
return this.#resolveBindingAsync(binding, options,
|
|
874
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner) as Promise<Value>;
|
|
762
875
|
}
|
|
763
876
|
|
|
764
877
|
async #resolveBindingAsync(
|
|
765
878
|
binding: Binding,
|
|
766
879
|
options: ResolveOptions | undefined,
|
|
767
|
-
resolutionPath: Array<string>,
|
|
768
880
|
resolutionStack: Array<ResolutionFrame>,
|
|
769
881
|
branchDepth: BranchDepth,
|
|
882
|
+
owner: DependencyResolver,
|
|
770
883
|
): Promise<unknown> {
|
|
771
|
-
if (
|
|
884
|
+
if (owner.#isPlainConstant(binding)) {
|
|
772
885
|
return binding.value;
|
|
773
886
|
}
|
|
774
887
|
|
|
@@ -782,22 +895,32 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
782
895
|
if (inflight !== undefined) {
|
|
783
896
|
return inflight;
|
|
784
897
|
}
|
|
898
|
+
if (this.#scope.isClosed) {
|
|
899
|
+
throw new DisposedContainerError();
|
|
900
|
+
}
|
|
785
901
|
} else if (scope === "scoped") {
|
|
786
902
|
const cachedScoped = this.#readScoped(binding);
|
|
787
903
|
if (cachedScoped !== SCOPED_MISS) {
|
|
788
904
|
return cachedScoped;
|
|
789
905
|
}
|
|
906
|
+
// In-flight dedup, scoped flavor: one instance per scope even under concurrency.
|
|
907
|
+
const inflight = this.#scope.getInflight(binding.id);
|
|
908
|
+
if (inflight !== undefined) {
|
|
909
|
+
return inflight;
|
|
910
|
+
}
|
|
911
|
+
if (this.#scope.isClosed) {
|
|
912
|
+
throw new DisposedContainerError();
|
|
913
|
+
}
|
|
790
914
|
}
|
|
791
915
|
|
|
792
916
|
const frame = this.#getResolutionFrame(binding);
|
|
793
|
-
// This level appends to its own branch and never unwinds
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
const levelDepth = branchDepthOf(levelPath);
|
|
917
|
+
// This level appends to its own branch and never unwinds.
|
|
918
|
+
const levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
919
|
+
const levelDepth = branchDepthOf(levelStack);
|
|
797
920
|
|
|
798
|
-
const needsActivation =
|
|
921
|
+
const needsActivation = owner.#activation.needsActivation(binding);
|
|
799
922
|
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
800
|
-
const resolutionCtx = new AsyncLevelContext(this,
|
|
923
|
+
const resolutionCtx = new AsyncLevelContext(this, levelStack, options);
|
|
801
924
|
if (binding.kind === "dynamic-async") {
|
|
802
925
|
return await binding.factory(resolutionCtx);
|
|
803
926
|
}
|
|
@@ -807,7 +930,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
807
930
|
|
|
808
931
|
const resolutionCtx =
|
|
809
932
|
needsActivation || requiresResolutionContext(binding)
|
|
810
|
-
? new AsyncLevelContext(this,
|
|
933
|
+
? new AsyncLevelContext(this, levelStack, options)
|
|
811
934
|
: undefined;
|
|
812
935
|
|
|
813
936
|
if (scope === "singleton") {
|
|
@@ -815,10 +938,10 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
815
938
|
const singletonPromise = this.#instantiateAndActivateAsync(
|
|
816
939
|
binding,
|
|
817
940
|
resolutionCtx,
|
|
818
|
-
levelPath,
|
|
819
941
|
levelStack,
|
|
820
942
|
levelDepth,
|
|
821
943
|
needsActivation,
|
|
944
|
+
owner,
|
|
822
945
|
).then(
|
|
823
946
|
(activated) => {
|
|
824
947
|
this.#scope.setSingleton(binding, activated);
|
|
@@ -834,39 +957,72 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
834
957
|
return await singletonPromise;
|
|
835
958
|
}
|
|
836
959
|
|
|
837
|
-
|
|
960
|
+
if (scope === "scoped") {
|
|
961
|
+
// Published before it settles, like the singleton lane: concurrent callers share one creation.
|
|
962
|
+
const scopedPromise = this.#instantiateAndActivateAsync(
|
|
963
|
+
binding,
|
|
964
|
+
resolutionCtx,
|
|
965
|
+
levelStack,
|
|
966
|
+
levelDepth,
|
|
967
|
+
needsActivation,
|
|
968
|
+
owner,
|
|
969
|
+
).then(
|
|
970
|
+
(activated) => {
|
|
971
|
+
this.#scope.setScoped(binding, activated);
|
|
972
|
+
this.#scope.clearInflight(binding.id);
|
|
973
|
+
return activated;
|
|
974
|
+
},
|
|
975
|
+
(error: unknown) => {
|
|
976
|
+
this.#scope.clearInflight(binding.id);
|
|
977
|
+
throw error;
|
|
978
|
+
},
|
|
979
|
+
);
|
|
980
|
+
this.#scope.setInflight(binding.id, scopedPromise as Promise<unknown>);
|
|
981
|
+
return await scopedPromise;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return await this.#instantiateAndActivateAsync(
|
|
838
985
|
binding,
|
|
839
986
|
resolutionCtx,
|
|
840
|
-
levelPath,
|
|
841
987
|
levelStack,
|
|
842
988
|
levelDepth,
|
|
843
989
|
needsActivation,
|
|
990
|
+
owner,
|
|
844
991
|
);
|
|
845
|
-
|
|
846
|
-
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Settles this resolver's own `postConstruct` answer for a class binding a parent owns.
|
|
996
|
+
*
|
|
997
|
+
* @remarks The owner discovers it on first instantiation, but the plan compiler reads the
|
|
998
|
+
* introspector of whoever is resolving — left unknown, that resolver refuses to compile a plan for
|
|
999
|
+
* this binding on every call, forever.
|
|
1000
|
+
*/
|
|
1001
|
+
#mirrorPostConstructFromOwner(binding: Binding, owner: DependencyResolver): void {
|
|
1002
|
+
if (owner !== this && binding.kind === "class" && this.#classes.knownPostConstruct(binding.target) === undefined) {
|
|
1003
|
+
this.#classes.discoverPostConstruct(binding.target);
|
|
847
1004
|
}
|
|
848
|
-
return activated;
|
|
849
1005
|
}
|
|
850
1006
|
|
|
851
1007
|
async #instantiateAndActivateAsync(
|
|
852
1008
|
binding: Binding,
|
|
853
1009
|
ctx: AsyncLevelContext | undefined,
|
|
854
|
-
resolutionPath: Array<string>,
|
|
855
1010
|
resolutionStack: Array<ResolutionFrame>,
|
|
856
1011
|
branchDepth: BranchDepth,
|
|
857
1012
|
needsActivation: boolean,
|
|
1013
|
+
owner: DependencyResolver,
|
|
858
1014
|
): Promise<unknown> {
|
|
859
|
-
const instance = await this.#instantiateAsync(binding, ctx,
|
|
860
|
-
|
|
1015
|
+
const instance = await this.#instantiateAsync(binding, ctx, resolutionStack, branchDepth);
|
|
1016
|
+
this.#mirrorPostConstructFromOwner(binding, owner);
|
|
1017
|
+
if (!owner.#activation.refreshAfterFirstInstantiation(binding, needsActivation)) {
|
|
861
1018
|
return instance;
|
|
862
1019
|
}
|
|
863
|
-
return
|
|
1020
|
+
return owner.#lifecycle.runActivation(ctx as AsyncLevelContext, binding, instance, owner.#metadataReader);
|
|
864
1021
|
}
|
|
865
1022
|
|
|
866
1023
|
async #instantiateAsync(
|
|
867
1024
|
binding: Binding,
|
|
868
1025
|
ctx: AsyncLevelContext | undefined,
|
|
869
|
-
resolutionPath: Array<string>,
|
|
870
1026
|
resolutionStack: Array<ResolutionFrame>,
|
|
871
1027
|
branchDepth: BranchDepth,
|
|
872
1028
|
): Promise<unknown> {
|
|
@@ -891,24 +1047,25 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
891
1047
|
case "class": {
|
|
892
1048
|
const deps = await this.#resolveDepsAsync(
|
|
893
1049
|
this.#constructorParams(binding.target),
|
|
894
|
-
resolutionPath,
|
|
895
1050
|
resolutionStack,
|
|
896
1051
|
branchDepth,
|
|
897
1052
|
);
|
|
898
|
-
|
|
1053
|
+
// Accessor initializers resolve synchronously, so the branch-owned path serves them directly.
|
|
1054
|
+
return this.#classes.instantiate(
|
|
1055
|
+
binding.target,
|
|
1056
|
+
deps,
|
|
1057
|
+
this.#classes.needsActiveContainer(binding.target) ? this.#ambientResolutionFor(resolutionStack) : undefined,
|
|
1058
|
+
);
|
|
899
1059
|
}
|
|
900
1060
|
|
|
901
1061
|
case "resolved": {
|
|
902
|
-
|
|
903
|
-
throw new InternalError("resolved binding requires resolution context");
|
|
904
|
-
}
|
|
905
|
-
const deps = await this.#resolveDepsAsync(binding.deps, resolutionPath, resolutionStack, branchDepth);
|
|
1062
|
+
const deps = await this.#resolveDepsAsync(binding.deps, resolutionStack, branchDepth);
|
|
906
1063
|
const factoryResult = binding.factory(...deps);
|
|
907
1064
|
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
908
1065
|
}
|
|
909
1066
|
|
|
910
1067
|
case "resolved-async": {
|
|
911
|
-
const deps = await this.#resolveDepsAsync(binding.deps,
|
|
1068
|
+
const deps = await this.#resolveDepsAsync(binding.deps, resolutionStack, branchDepth);
|
|
912
1069
|
return binding.factory(...deps);
|
|
913
1070
|
}
|
|
914
1071
|
|
|
@@ -919,7 +1076,6 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
919
1076
|
|
|
920
1077
|
async #resolveDepsAsync(
|
|
921
1078
|
deps: ReadonlyArray<DependencySlot>,
|
|
922
|
-
resolutionPath: Array<string>,
|
|
923
1079
|
resolutionStack: Array<ResolutionFrame>,
|
|
924
1080
|
branchDepth: BranchDepth,
|
|
925
1081
|
): Promise<Array<unknown>> {
|
|
@@ -928,63 +1084,68 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
928
1084
|
return [];
|
|
929
1085
|
}
|
|
930
1086
|
if (count === 1) {
|
|
931
|
-
return [await this.#resolveDepAsync(deps[0]!,
|
|
1087
|
+
return [await this.#resolveDepAsync(deps[0]!, resolutionStack, branchDepth)];
|
|
932
1088
|
}
|
|
933
1089
|
// Siblings resolve concurrently and each extends the same branch, so the first appends in
|
|
934
1090
|
// place and the rest copy the prefix — no caller has to isolate them.
|
|
935
1091
|
const pending = new Array<Promise<unknown>>(count);
|
|
936
1092
|
for (let index = 0; index < count; index += 1) {
|
|
937
|
-
pending[index] = this.#resolveDepAsync(deps[index]!,
|
|
1093
|
+
pending[index] = this.#resolveDepAsync(deps[index]!, resolutionStack, branchDepth);
|
|
938
1094
|
}
|
|
939
1095
|
return Promise.all(pending);
|
|
940
1096
|
}
|
|
941
1097
|
|
|
942
1098
|
#resolveDepAsync(
|
|
943
1099
|
dep: DependencySlot,
|
|
944
|
-
resolutionPath: Array<string>,
|
|
945
1100
|
resolutionStack: Array<ResolutionFrame>,
|
|
946
1101
|
branchDepth: BranchDepth,
|
|
947
1102
|
): Promise<unknown> {
|
|
948
|
-
const options =
|
|
1103
|
+
const options = resolveOptionsForSlot(dep);
|
|
949
1104
|
if (dep.multi) {
|
|
950
|
-
return this.resolveAllAsync(dep.token, options,
|
|
1105
|
+
return this.resolveAllAsync(dep.token, options, resolutionStack, branchDepth);
|
|
951
1106
|
}
|
|
952
1107
|
if (dep.optional) {
|
|
953
|
-
return this.resolveOptionalAsync(dep.token, options,
|
|
1108
|
+
return this.resolveOptionalAsync(dep.token, options, resolutionStack, branchDepth);
|
|
954
1109
|
}
|
|
955
1110
|
if (options === undefined) {
|
|
956
|
-
return this.resolveAsyncFromContext(dep.token,
|
|
1111
|
+
return this.resolveAsyncFromContext(dep.token, resolutionStack, branchDepth);
|
|
957
1112
|
}
|
|
958
|
-
return this.resolveAsync(dep.token, options,
|
|
1113
|
+
return this.resolveAsync(dep.token, options, resolutionStack, branchDepth);
|
|
959
1114
|
}
|
|
960
1115
|
|
|
961
|
-
async resolveOptionalAsync<
|
|
1116
|
+
async resolveOptionalAsync<Value>(
|
|
962
1117
|
token: Token<Value> | Constructor<Value>,
|
|
963
1118
|
options: ResolveOptions | undefined,
|
|
964
|
-
resolutionPath: Array<string>,
|
|
965
1119
|
resolutionStack: Array<ResolutionFrame>,
|
|
966
1120
|
branchDepth: BranchDepth = UNOWNED_BRANCH,
|
|
967
1121
|
): Promise<Value | undefined> {
|
|
968
|
-
|
|
1122
|
+
const entry = this.#findBinding(token, options, resolutionStack);
|
|
1123
|
+
if (entry === undefined) {
|
|
969
1124
|
return undefined;
|
|
970
1125
|
}
|
|
971
|
-
|
|
1126
|
+
// Same single-evaluation contract as the sync lane: resolve what the probe found.
|
|
1127
|
+
const { binding, owner } = entry;
|
|
1128
|
+
if (binding.kind === "alias") {
|
|
1129
|
+
return this.resolveAsync(token, options, resolutionStack, branchDepth);
|
|
1130
|
+
}
|
|
1131
|
+
if (binding.scope === "singleton" && owner !== this) {
|
|
1132
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner) as Promise<Value>;
|
|
1133
|
+
}
|
|
1134
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner) as Promise<Value>;
|
|
972
1135
|
}
|
|
973
1136
|
|
|
974
|
-
async resolveAllAsync<
|
|
1137
|
+
async resolveAllAsync<Value>(
|
|
975
1138
|
token: Token<Value> | Constructor<Value>,
|
|
976
1139
|
options: ResolveOptions | undefined,
|
|
977
|
-
resolutionPath: Array<string>,
|
|
978
1140
|
resolutionStack: Array<ResolutionFrame>,
|
|
979
1141
|
branchDepth: BranchDepth = UNOWNED_BRANCH,
|
|
980
1142
|
): Promise<Array<Value>> {
|
|
981
|
-
const candidates = this.#candidateBindings(token, options,
|
|
1143
|
+
const candidates = this.#candidateBindings(token, options, resolutionStack);
|
|
982
1144
|
const pending = new Array<Promise<Value>>(candidates.length);
|
|
983
1145
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
984
1146
|
pending[index] = this.#resolveCandidateAsync(
|
|
985
1147
|
candidates[index]!,
|
|
986
1148
|
options,
|
|
987
|
-
resolutionPath,
|
|
988
1149
|
resolutionStack,
|
|
989
1150
|
branchDepth,
|
|
990
1151
|
) as Promise<Value>;
|
|
@@ -992,7 +1153,7 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
992
1153
|
return Promise.all(pending);
|
|
993
1154
|
}
|
|
994
1155
|
|
|
995
|
-
// ── Helpers
|
|
1156
|
+
// ── Helpers ────────────────────────────────────────────────────────────────────────────────────────────────────────
|
|
996
1157
|
|
|
997
1158
|
#allBindingsFromChain(token: Token<unknown> | Constructor): ReadonlyArray<Binding> {
|
|
998
1159
|
const ownBindings = this.#registry.getAll(token);
|
|
@@ -1027,6 +1188,58 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1027
1188
|
return result;
|
|
1028
1189
|
}
|
|
1029
1190
|
|
|
1191
|
+
/**
|
|
1192
|
+
* The candidates an index can name outright, or `null` when the request needs full selection.
|
|
1193
|
+
*
|
|
1194
|
+
* @remarks Kept off `#candidateBindings` so that method stays the size it was: a request neither
|
|
1195
|
+
* index serves must not pay for the two that do.
|
|
1196
|
+
* An index has matched the slot already, but a hit may still carry a predicate, and evaluating
|
|
1197
|
+
* that is the selection path's job.
|
|
1198
|
+
*/
|
|
1199
|
+
#indexedCandidates(
|
|
1200
|
+
token: Token<unknown> | Constructor,
|
|
1201
|
+
options: ResolveOptions,
|
|
1202
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1203
|
+
): ReadonlyArray<Binding> | null {
|
|
1204
|
+
if (isNameOnlyOptions(options)) {
|
|
1205
|
+
const named = this.#namedBindingsFromChain(token, options.name);
|
|
1206
|
+
return anyPredicate(named)
|
|
1207
|
+
? selectAllBindings(named, options, this.#makeConstraintContext(resolutionStack, options))
|
|
1208
|
+
: named;
|
|
1209
|
+
}
|
|
1210
|
+
const singleTag = singleTagOnlyOf(options);
|
|
1211
|
+
if (singleTag === undefined) {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
const tagged = this.#taggedBindingsFromChain(token, singleTag);
|
|
1215
|
+
return anyPredicate(tagged)
|
|
1216
|
+
? selectAllBindings(tagged, options, this.#makeConstraintContext(resolutionStack, options))
|
|
1217
|
+
: tagged;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Every binding the chain's tag indexes hold for one tag, nearest container first.
|
|
1222
|
+
*
|
|
1223
|
+
* @remarks A request for one tag and no name matches exactly the bindings the index keys, so this
|
|
1224
|
+
* is the whole candidate set rather than a prefilter — a named or multi-tag slot cannot satisfy it.
|
|
1225
|
+
*/
|
|
1226
|
+
#taggedBindingsFromChain(token: Token<unknown> | Constructor, tag: BindingTag): Array<Binding> {
|
|
1227
|
+
// A tag matches at most one binding per registry, so a root container's answer is built whole
|
|
1228
|
+
// rather than grown — the shape `#namedBindingsFromChain` takes, for the same reason.
|
|
1229
|
+
const ownBinding = this.#registry.getSimpleTagged(token, tag);
|
|
1230
|
+
if (this.#parent === undefined) {
|
|
1231
|
+
return ownBinding === undefined ? [] : [ownBinding];
|
|
1232
|
+
}
|
|
1233
|
+
const result: Array<Binding> = ownBinding === undefined ? [] : [ownBinding];
|
|
1234
|
+
for (let current: DependencyResolver | undefined = this.#parent; current !== undefined; current = current.#parent) {
|
|
1235
|
+
const binding = current.#registry.getSimpleTagged(token, tag);
|
|
1236
|
+
if (binding !== undefined) {
|
|
1237
|
+
result.push(binding);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return result;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1030
1243
|
/** A constant with no activation anywhere resolves to its value with no pipeline at all. */
|
|
1031
1244
|
#isPlainConstant(binding: Binding): binding is ConstantBinding<unknown> {
|
|
1032
1245
|
return (
|
|
@@ -1054,68 +1267,111 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1054
1267
|
if (!this.#scope.isChild) {
|
|
1055
1268
|
throw new MissingScopeContextError(tokenName(binding.token));
|
|
1056
1269
|
}
|
|
1057
|
-
|
|
1058
|
-
return this.#scope.getScoped(binding.id);
|
|
1059
|
-
}
|
|
1060
|
-
return SCOPED_MISS;
|
|
1270
|
+
return this.#scope.readScoped(binding.id);
|
|
1061
1271
|
}
|
|
1062
1272
|
|
|
1273
|
+
// The shared root context answers every top-level request; building one is the rarer half and
|
|
1274
|
+
// lives outside, so what a selection inlines is the test and not the literal.
|
|
1063
1275
|
#makeConstraintContext(
|
|
1064
|
-
resolutionPath: Array<string>,
|
|
1065
1276
|
resolutionStack: Array<ResolutionFrame>,
|
|
1066
1277
|
options: ResolveOptions | undefined,
|
|
1067
1278
|
): ConstraintContext {
|
|
1068
|
-
if (options === undefined &&
|
|
1279
|
+
if (options === undefined && resolutionStack.length === 0) {
|
|
1069
1280
|
return ROOT_CONSTRAINT_CONTEXT;
|
|
1070
1281
|
}
|
|
1071
|
-
return
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1282
|
+
return buildConstraintContext(resolutionStack, options);
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
/** Selection for a name-less multi-tag request, over the union of the two tag indexes. */
|
|
1286
|
+
#selectMultiTagged(
|
|
1287
|
+
token: Token<unknown> | Constructor,
|
|
1288
|
+
options: ResolveOptions,
|
|
1289
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1290
|
+
): Binding | undefined {
|
|
1291
|
+
const candidates: Array<Binding> = [];
|
|
1292
|
+
this.#gatherTagCandidates(token, options.tag, candidates);
|
|
1293
|
+
const listed = options.tags;
|
|
1294
|
+
if (listed !== undefined) {
|
|
1295
|
+
for (let index = 0; index < listed.length; index += 1) {
|
|
1296
|
+
this.#gatherTagCandidates(token, listed[index], candidates);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (candidates.length === 0) {
|
|
1300
|
+
return undefined;
|
|
1301
|
+
}
|
|
1302
|
+
return selectBinding(candidates, options, this.#makeConstraintContext(resolutionStack, options), tokenName(token));
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/** One request criterion's candidates: its exact single-tag binding, plus its first-tag bucket. */
|
|
1306
|
+
#gatherTagCandidates(
|
|
1307
|
+
token: Token<unknown> | Constructor,
|
|
1308
|
+
criterion: BindingTag | undefined,
|
|
1309
|
+
out: Array<Binding>,
|
|
1310
|
+
): void {
|
|
1311
|
+
if (criterion === undefined) {
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
// The includes probes only guard a request repeating a criterion across its two spellings.
|
|
1315
|
+
const single = this.#registry.getSimpleTagged(token, criterion);
|
|
1316
|
+
if (single !== undefined && !out.includes(single)) {
|
|
1317
|
+
out.push(single);
|
|
1318
|
+
}
|
|
1319
|
+
const bucket = this.#registry.getMultiTagged(token, criterion);
|
|
1320
|
+
if (bucket !== undefined) {
|
|
1321
|
+
for (let index = 0; index < bucket.length; index += 1) {
|
|
1322
|
+
const candidate = bucket[index]!;
|
|
1323
|
+
if (!out.includes(candidate)) {
|
|
1324
|
+
out.push(candidate);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1078
1328
|
}
|
|
1079
1329
|
|
|
1080
1330
|
#matchesBindingFast(
|
|
1081
1331
|
binding: Binding,
|
|
1082
1332
|
options: ResolveOptions | undefined,
|
|
1083
|
-
resolutionPath: Array<string>,
|
|
1084
1333
|
resolutionStack: Array<ResolutionFrame>,
|
|
1085
1334
|
): boolean {
|
|
1086
1335
|
if (!matchesSlot(binding.slot, options)) {
|
|
1087
1336
|
return false;
|
|
1088
1337
|
}
|
|
1089
|
-
|
|
1338
|
+
return this.#satisfiesPredicate(binding, options, resolutionStack);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/** The predicate half of a match, for a lane whose index has already settled the slot. */
|
|
1342
|
+
#satisfiesPredicate(
|
|
1343
|
+
binding: Binding,
|
|
1344
|
+
options: ResolveOptions | undefined,
|
|
1345
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1346
|
+
): boolean {
|
|
1347
|
+
const predicate = binding.predicate;
|
|
1348
|
+
if (predicate === undefined) {
|
|
1090
1349
|
return true;
|
|
1091
1350
|
}
|
|
1092
|
-
return
|
|
1351
|
+
return predicate(this.#makeConstraintContext(resolutionStack, options));
|
|
1093
1352
|
}
|
|
1094
1353
|
|
|
1095
1354
|
#resolveTransientDynamicSyncFromContext(
|
|
1096
1355
|
binding: DynamicBinding<unknown>,
|
|
1097
|
-
resolutionPath: Array<string>,
|
|
1098
1356
|
resolutionStack: Array<ResolutionFrame>,
|
|
1099
1357
|
): unknown {
|
|
1100
1358
|
// One lane at every depth: `binding.inFlight` is O(1), so there is nothing to escape.
|
|
1101
1359
|
const frame = this.#getResolutionFrame(binding);
|
|
1102
1360
|
const tokenDisplayName = frame.tokenName;
|
|
1103
1361
|
if (binding.inFlight) {
|
|
1104
|
-
throw new CircularDependencyError(
|
|
1362
|
+
throw new CircularDependencyError(cycleNamesOf(resolutionStack, tokenDisplayName));
|
|
1105
1363
|
}
|
|
1106
1364
|
binding.inFlight = true;
|
|
1107
|
-
resolutionPath.push(tokenDisplayName);
|
|
1108
1365
|
resolutionStack.push(frame);
|
|
1109
|
-
const resolutionCtx = this.#acquireSyncResolutionContext(
|
|
1366
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionStack, undefined);
|
|
1110
1367
|
try {
|
|
1111
1368
|
const dynamicResult = binding.factory(resolutionCtx);
|
|
1112
1369
|
if (dynamicResult instanceof Promise) {
|
|
1113
|
-
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
1370
|
+
throw new AsyncResolutionError(resolutionStack[0]?.tokenName ?? tokenDisplayName, tokenDisplayName);
|
|
1114
1371
|
}
|
|
1115
1372
|
return dynamicResult;
|
|
1116
1373
|
} finally {
|
|
1117
1374
|
resolutionStack.pop();
|
|
1118
|
-
resolutionPath.pop();
|
|
1119
1375
|
binding.inFlight = false;
|
|
1120
1376
|
}
|
|
1121
1377
|
}
|
|
@@ -1123,22 +1379,20 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1123
1379
|
// Deliberately not `async`: that would allocate a state machine and a promise per level.
|
|
1124
1380
|
#resolveTransientDynamicAsyncFromContext(
|
|
1125
1381
|
binding: DynamicBinding<unknown> | DynamicAsyncBinding<unknown>,
|
|
1126
|
-
resolutionPath: Array<string>,
|
|
1127
1382
|
resolutionStack: Array<ResolutionFrame>,
|
|
1128
1383
|
branchDepth: BranchDepth,
|
|
1129
1384
|
): Promise<unknown> {
|
|
1130
1385
|
const frame = this.#getResolutionFrame(binding);
|
|
1131
|
-
let
|
|
1386
|
+
let levelStack: OwnedBranchStack;
|
|
1132
1387
|
try {
|
|
1133
|
-
|
|
1388
|
+
levelStack = extendResolutionBranch(resolutionStack, branchDepth, frame);
|
|
1134
1389
|
} catch (cycleError) {
|
|
1135
1390
|
// This method is not `async`; keep failures as rejections rather than sync throws.
|
|
1136
1391
|
return Promise.reject(cycleError);
|
|
1137
1392
|
}
|
|
1138
|
-
const levelStack = extendResolutionStackBranch(resolutionStack, branchDepth, frame);
|
|
1139
1393
|
|
|
1140
1394
|
// Nothing this level appended is ever removed, so no level observes its own settlement.
|
|
1141
|
-
const ctx = new AsyncLevelContext(this,
|
|
1395
|
+
const ctx = new AsyncLevelContext(this, levelStack, undefined);
|
|
1142
1396
|
try {
|
|
1143
1397
|
if (binding.kind === "dynamic-async") {
|
|
1144
1398
|
return binding.factory(ctx);
|
|
@@ -1150,17 +1404,17 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1150
1404
|
}
|
|
1151
1405
|
}
|
|
1152
1406
|
|
|
1153
|
-
// ── The cascade lane
|
|
1407
|
+
// ── The cascade lane ───────────────────────────────────────────────────────────────────────────────────────────────
|
|
1154
1408
|
|
|
1155
1409
|
/**
|
|
1156
1410
|
* Entry for a request a factory makes from inside an open synchronous cascade.
|
|
1157
1411
|
*
|
|
1158
1412
|
* @remarks A request arriving with no cascade open came out of a continuation, so its ancestors
|
|
1159
|
-
* are on no call stack — it escapes to the branch lane.
|
|
1413
|
+
* are on no call stack — it escapes to the branch lane.
|
|
1160
1414
|
*/
|
|
1161
1415
|
resolveAsyncFromCascade(token: Token<unknown> | Constructor): Promise<unknown> {
|
|
1162
|
-
if (this.#
|
|
1163
|
-
return this.resolveAsyncFromContext(token, [],
|
|
1416
|
+
if (this.#cascadeStack.length === 0) {
|
|
1417
|
+
return this.resolveAsyncFromContext(token, [], ROOT_BRANCH);
|
|
1164
1418
|
}
|
|
1165
1419
|
return this.#dispatchCascade(token);
|
|
1166
1420
|
}
|
|
@@ -1188,10 +1442,41 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1188
1442
|
if (fastBinding.scope === "singleton" && fastBinding.instance !== NO_INSTANCE) {
|
|
1189
1443
|
return Promise.resolve(fastBinding.instance);
|
|
1190
1444
|
}
|
|
1445
|
+
const planned = this.#plannedCascadeAnswer(fastBinding);
|
|
1446
|
+
if (planned !== null) {
|
|
1447
|
+
return planned;
|
|
1448
|
+
}
|
|
1191
1449
|
}
|
|
1192
1450
|
// Anything else leaves the cascade lane for good, seeded with a snapshot of the ancestors it
|
|
1193
1451
|
// accumulated — so a cycle across the boundary is still on one path.
|
|
1194
|
-
return this.resolveAsyncFromContext(token, [...this.#
|
|
1452
|
+
return this.resolveAsyncFromContext(token, [...this.#cascadeStack], UNOWNED_BRANCH);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* A statically-visible transient graph at a true root answers from its compiled async plan.
|
|
1457
|
+
*
|
|
1458
|
+
* @remarks Kept out of the dispatcher so its size stays inlinable. Inside an open cascade the
|
|
1459
|
+
* graph must escape instead, so its escapes carry the live ancestors — hence the idle gate.
|
|
1460
|
+
*/
|
|
1461
|
+
#plannedCascadeAnswer(fastBinding: Binding): Promise<unknown> | null {
|
|
1462
|
+
if (
|
|
1463
|
+
this.#cascadeStack.length !== 0 ||
|
|
1464
|
+
fastBinding.scope !== "transient" ||
|
|
1465
|
+
(fastBinding.kind !== "class" && fastBinding.kind !== "resolved" && fastBinding.kind !== "resolved-async")
|
|
1466
|
+
) {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
const plan = this.#getAsyncInstantiationPlan(fastBinding);
|
|
1470
|
+
if (plan === null) {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
try {
|
|
1474
|
+
const planned = plan();
|
|
1475
|
+
return planned instanceof Promise ? planned : Promise.resolve(planned);
|
|
1476
|
+
} catch (planError) {
|
|
1477
|
+
// The interpreted lane is async, so a sync throw is a rejection there too.
|
|
1478
|
+
return Promise.reject(planError);
|
|
1479
|
+
}
|
|
1195
1480
|
}
|
|
1196
1481
|
|
|
1197
1482
|
#resolveTransientDynamicAsyncCascade(
|
|
@@ -1202,11 +1487,10 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1202
1487
|
// code does not interleave — so the O(1) flag is exact path membership here, as it is for the
|
|
1203
1488
|
// sync lane. It is cleared when the factory returns its promise, not when that promise settles.
|
|
1204
1489
|
if (binding.inFlight) {
|
|
1205
|
-
return Promise.reject(new CircularDependencyError(
|
|
1490
|
+
return Promise.reject(new CircularDependencyError(cycleNamesOf(this.#cascadeStack, frame.tokenName)));
|
|
1206
1491
|
}
|
|
1207
|
-
const ctx = (this.#cascadeContext ??= new AsyncCascadeContext(this, this.#
|
|
1492
|
+
const ctx = (this.#cascadeContext ??= new AsyncCascadeContext(this, this.#cascadeStack));
|
|
1208
1493
|
binding.inFlight = true;
|
|
1209
|
-
this.#cascadePath.push(frame.tokenName);
|
|
1210
1494
|
this.#cascadeStack.push(frame);
|
|
1211
1495
|
try {
|
|
1212
1496
|
if (binding.kind === "dynamic-async") {
|
|
@@ -1218,7 +1502,6 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1218
1502
|
return Promise.reject(factoryError);
|
|
1219
1503
|
} finally {
|
|
1220
1504
|
this.#cascadeStack.pop();
|
|
1221
|
-
this.#cascadePath.pop();
|
|
1222
1505
|
binding.inFlight = false;
|
|
1223
1506
|
}
|
|
1224
1507
|
}
|
|
@@ -1228,38 +1511,67 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1228
1511
|
#resolveCandidateSync(
|
|
1229
1512
|
binding: Binding,
|
|
1230
1513
|
options: ResolveOptions | undefined,
|
|
1231
|
-
resolutionPath: Array<string>,
|
|
1232
1514
|
resolutionStack: Array<ResolutionFrame>,
|
|
1233
1515
|
): unknown {
|
|
1234
|
-
|
|
1516
|
+
// Fan-outs are dominated by constants: with no activation hook anywhere in the chain, a
|
|
1517
|
+
// hook-free constant is plain no matter which container owns it — skip the owner probe.
|
|
1518
|
+
if (binding.kind === "constant" && binding.onActivation === undefined && this.#chainActivationVersion() === 0) {
|
|
1519
|
+
return binding.value;
|
|
1520
|
+
}
|
|
1521
|
+
const owner = this.#ownerOf(binding);
|
|
1522
|
+
if (owner.#isPlainConstant(binding)) {
|
|
1235
1523
|
return binding.value;
|
|
1236
1524
|
}
|
|
1237
1525
|
if (binding.kind === "alias") {
|
|
1238
|
-
return this.resolve(binding.target, options,
|
|
1526
|
+
return this.resolve(binding.target, options, resolutionStack);
|
|
1239
1527
|
}
|
|
1240
|
-
if (binding.scope === "singleton"
|
|
1241
|
-
|
|
1528
|
+
if (binding.scope === "singleton") {
|
|
1529
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
1530
|
+
return binding.instance;
|
|
1531
|
+
}
|
|
1532
|
+
// Owner-routed like `resolve`: the owner materializes and caches its own singleton.
|
|
1533
|
+
return owner.#resolveBinding(binding, options, resolutionStack, owner);
|
|
1242
1534
|
}
|
|
1243
|
-
return this.#resolveBinding(binding, options,
|
|
1535
|
+
return this.#resolveBinding(binding, options, resolutionStack, owner);
|
|
1244
1536
|
}
|
|
1245
1537
|
|
|
1246
1538
|
#resolveCandidateAsync(
|
|
1247
1539
|
binding: Binding,
|
|
1248
1540
|
options: ResolveOptions | undefined,
|
|
1249
|
-
resolutionPath: Array<string>,
|
|
1250
1541
|
resolutionStack: Array<ResolutionFrame>,
|
|
1251
1542
|
branchDepth: BranchDepth,
|
|
1252
1543
|
): Promise<unknown> {
|
|
1253
|
-
if (this.#
|
|
1544
|
+
if (binding.kind === "constant" && binding.onActivation === undefined && this.#chainActivationVersion() === 0) {
|
|
1545
|
+
return Promise.resolve(binding.value);
|
|
1546
|
+
}
|
|
1547
|
+
const owner = this.#ownerOf(binding);
|
|
1548
|
+
if (owner.#isPlainConstant(binding)) {
|
|
1254
1549
|
return Promise.resolve(binding.value);
|
|
1255
1550
|
}
|
|
1256
1551
|
if (binding.kind === "alias") {
|
|
1257
|
-
return this.resolveAsync(binding.target, options,
|
|
1552
|
+
return this.resolveAsync(binding.target, options, resolutionStack, branchDepth);
|
|
1258
1553
|
}
|
|
1259
|
-
if (binding.scope === "singleton"
|
|
1260
|
-
|
|
1554
|
+
if (binding.scope === "singleton") {
|
|
1555
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
1556
|
+
return Promise.resolve(binding.instance);
|
|
1557
|
+
}
|
|
1558
|
+
return owner.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
1559
|
+
}
|
|
1560
|
+
return this.#resolveBindingAsync(binding, options, resolutionStack, branchDepth, owner);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/** The resolver whose registry holds `binding` — `this` (the common case) when it is own. */
|
|
1564
|
+
#ownerOf(binding: Binding): DependencyResolver {
|
|
1565
|
+
// A root resolver can only hold its own bindings, so the per-candidate id probe is chain-only.
|
|
1566
|
+
if (this.#parent === undefined || this.#registry.getById(binding.id) !== undefined) {
|
|
1567
|
+
return this;
|
|
1568
|
+
}
|
|
1569
|
+
for (let current: DependencyResolver | undefined = this.#parent; current !== undefined; current = current.#parent) {
|
|
1570
|
+
if (current.#registry.getById(binding.id) !== undefined) {
|
|
1571
|
+
return current;
|
|
1572
|
+
}
|
|
1261
1573
|
}
|
|
1262
|
-
return this
|
|
1574
|
+
return this;
|
|
1263
1575
|
}
|
|
1264
1576
|
|
|
1265
1577
|
#getResolutionFrame(binding: Binding): ResolutionFrame {
|
|
@@ -1275,26 +1587,48 @@ export class DependencyResolver implements ResolverCallbacks {
|
|
|
1275
1587
|
return frame;
|
|
1276
1588
|
}
|
|
1277
1589
|
|
|
1590
|
+
// A pool is keyed by the one array pair its contexts hold, so reuse can never re-point a context
|
|
1591
|
+
// a live frame still reads — a nested top-level resolve reaches the same depth while the outer
|
|
1592
|
+
// factory runs, and it must get its own context, not the outer frame's re-bound.
|
|
1278
1593
|
#acquireSyncResolutionContext(
|
|
1279
|
-
resolutionPath: Array<string>,
|
|
1280
1594
|
resolutionStack: Array<ResolutionFrame>,
|
|
1281
1595
|
options: ResolveOptions | undefined,
|
|
1282
1596
|
): DefaultResolutionContext {
|
|
1597
|
+
if (resolutionStack === this.rootStack) {
|
|
1598
|
+
const depth = resolutionStack.length;
|
|
1599
|
+
const existing = this.#syncResolutionContextPool[depth];
|
|
1600
|
+
if (existing !== undefined) {
|
|
1601
|
+
existing.reset(this, resolutionStack, options);
|
|
1602
|
+
return existing;
|
|
1603
|
+
}
|
|
1604
|
+
const created = new DefaultResolutionContext(this, resolutionStack, options);
|
|
1605
|
+
this.#syncResolutionContextPool[depth] = created;
|
|
1606
|
+
return created;
|
|
1607
|
+
}
|
|
1608
|
+
return this.#acquireOffRootSyncContext(resolutionStack, options);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
/** The cascade pair pools separately; a throwaway pair (nested resolve, async snapshot) mints per call. */
|
|
1612
|
+
#acquireOffRootSyncContext(
|
|
1613
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1614
|
+
options: ResolveOptions | undefined,
|
|
1615
|
+
): DefaultResolutionContext {
|
|
1616
|
+
if (resolutionStack !== this.#cascadeStack) {
|
|
1617
|
+
return new DefaultResolutionContext(this, resolutionStack, options);
|
|
1618
|
+
}
|
|
1283
1619
|
const depth = resolutionStack.length;
|
|
1284
|
-
const
|
|
1620
|
+
const pool = (this.#cascadeContextPool ??= []);
|
|
1621
|
+
const existing = pool[depth];
|
|
1285
1622
|
if (existing !== undefined) {
|
|
1286
|
-
existing.reset(this,
|
|
1623
|
+
existing.reset(this, resolutionStack, options);
|
|
1287
1624
|
return existing;
|
|
1288
1625
|
}
|
|
1289
|
-
const created = new DefaultResolutionContext(this,
|
|
1290
|
-
|
|
1626
|
+
const created = new DefaultResolutionContext(this, resolutionStack, options);
|
|
1627
|
+
pool[depth] = created;
|
|
1291
1628
|
return created;
|
|
1292
1629
|
}
|
|
1293
1630
|
}
|
|
1294
1631
|
|
|
1295
|
-
/** Absent scoped entry — distinguishes it from a cached `undefined`. */
|
|
1296
|
-
const SCOPED_MISS: unique symbol = Symbol("di:scoped-miss");
|
|
1297
|
-
|
|
1298
1632
|
function anyPredicate(bindings: ReadonlyArray<Binding>): boolean {
|
|
1299
1633
|
for (let index = 0; index < bindings.length; index += 1) {
|
|
1300
1634
|
if (bindings[index]!.predicate !== undefined) {
|
|
@@ -1304,20 +1638,36 @@ function anyPredicate(bindings: ReadonlyArray<Binding>): boolean {
|
|
|
1304
1638
|
return false;
|
|
1305
1639
|
}
|
|
1306
1640
|
|
|
1641
|
+
function buildConstraintContext(
|
|
1642
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1643
|
+
options: ResolveOptions | undefined,
|
|
1644
|
+
): ConstraintContext {
|
|
1645
|
+
return {
|
|
1646
|
+
// Derived per read, never cached: the stack may be live, and the names must report it as it stands.
|
|
1647
|
+
get resolutionPath(): ReadonlyArray<string> {
|
|
1648
|
+
const names = new Array<string>(resolutionStack.length);
|
|
1649
|
+
for (let index = 0; index < resolutionStack.length; index += 1) {
|
|
1650
|
+
names[index] = resolutionStack[index]!.tokenName;
|
|
1651
|
+
}
|
|
1652
|
+
return names;
|
|
1653
|
+
},
|
|
1654
|
+
resolutionStack,
|
|
1655
|
+
parent: resolutionStack.at(-1),
|
|
1656
|
+
ancestors: resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [],
|
|
1657
|
+
currentResolveOptions: options,
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/** The async-resolution failure for a binding reached on a sync path, naming what to await instead. */
|
|
1662
|
+
function asyncResolutionErrorFor(
|
|
1663
|
+
binding: Binding,
|
|
1664
|
+
resolutionStack: ReadonlyArray<ResolutionFrame>,
|
|
1665
|
+
): AsyncResolutionError {
|
|
1666
|
+
const sourceName = tokenName(binding.token);
|
|
1667
|
+
return new AsyncResolutionError(resolutionStack[0]?.tokenName ?? sourceName, sourceName);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1307
1670
|
/** Only a factory is handed the resolution context; everything else gets its deps directly. */
|
|
1308
1671
|
function requiresResolutionContext(binding: Binding): boolean {
|
|
1309
1672
|
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
1310
1673
|
}
|
|
1311
|
-
|
|
1312
|
-
/**
|
|
1313
|
-
* Whether the tag index's answer is the one `Object.is` would give.
|
|
1314
|
-
*
|
|
1315
|
-
* @remarks An indexed binding has no name, no predicate and exactly one tag, and the request carries
|
|
1316
|
-
* only that tag, so `matchesSlot` reduces to the tag values — and the index matched the key already.
|
|
1317
|
-
* It answers by SameValueZero, which parts from `Object.is` (SPEC §3.5) on exactly one pair: `+0` and
|
|
1318
|
-
* `-0`. So a request whose value is not zero is already exact, and only a zero-valued one is worth
|
|
1319
|
-
* reading the stored value for.
|
|
1320
|
-
*/
|
|
1321
|
-
function matchesIndexedTagValue(binding: Binding, requestedValue: unknown): boolean {
|
|
1322
|
-
return requestedValue !== 0 || Object.is(binding.slot.tags[0]![1], requestedValue);
|
|
1323
|
-
}
|