@codefast/di 0.5.0-canary.6 → 0.5.0-canary.8
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 +123 -0
- package/README.md +15 -4
- package/dist/binding.d.ts +78 -19
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +46 -2
- package/dist/binding.js.map +1 -1
- package/dist/constructor-type.d.ts +4 -5
- package/dist/constructor-type.d.ts.map +1 -1
- package/dist/container/binding-builders.d.ts +56 -0
- package/dist/container/binding-builders.d.ts.map +1 -0
- package/dist/container/binding-builders.js +196 -0
- package/dist/container/binding-builders.js.map +1 -0
- package/dist/{container.d.ts → container/container.d.ts} +2 -2
- package/dist/container/container.d.ts.map +1 -0
- package/dist/container/container.js +624 -0
- package/dist/container/container.js.map +1 -0
- package/dist/decorators/inject.d.ts +10 -1
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js +2 -2
- package/dist/decorators/inject.js.map +1 -1
- package/dist/decorators/injectable.js +3 -3
- package/dist/decorators/injectable.js.map +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +13 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/introspection/dependency-graph.d.ts.map +1 -0
- package/dist/{dependency-graph.js → introspection/dependency-graph.js} +1 -1
- package/dist/introspection/dependency-graph.js.map +1 -0
- package/dist/{graph-adapters → introspection/graph-adapters}/cytoscape.d.ts +1 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -0
- package/dist/introspection/graph-adapters/dot.d.ts +6 -0
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/dot.js.map +1 -0
- package/dist/{graph-adapters → introspection/graph-adapters}/reactflow.d.ts +2 -2
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -0
- package/dist/{graph-adapters → introspection/graph-adapters}/reactflow.js +11 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -0
- package/dist/{inspector.d.ts → introspection/inspector.d.ts} +3 -8
- package/dist/introspection/inspector.d.ts.map +1 -0
- package/dist/{inspector.js → introspection/inspector.js} +23 -23
- package/dist/introspection/inspector.js.map +1 -0
- package/dist/metadata/metadata-keys.d.ts +3 -6
- package/dist/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/metadata/metadata-keys.js +3 -6
- package/dist/metadata/metadata-keys.js.map +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.js +4 -4
- package/dist/metadata/symbol-metadata-reader.js.map +1 -1
- package/dist/module.d.ts +9 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +9 -2
- package/dist/module.js.map +1 -1
- package/dist/registry.d.ts +17 -13
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +94 -63
- package/dist/registry.js.map +1 -1
- package/dist/resolution/activation-need.d.ts +25 -0
- package/dist/resolution/activation-need.d.ts.map +1 -0
- package/dist/resolution/activation-need.js +64 -0
- package/dist/resolution/activation-need.js.map +1 -0
- package/dist/resolution/binding-lookup-cache.d.ts +41 -0
- package/dist/resolution/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/binding-lookup-cache.js +102 -0
- package/dist/resolution/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/binding-scope.d.ts.map +1 -0
- package/dist/resolution/binding-scope.js.map +1 -0
- package/dist/resolution/binding-select.d.ts.map +1 -0
- package/dist/{binding-select.js → resolution/binding-select.js} +17 -7
- package/dist/resolution/binding-select.js.map +1 -0
- package/dist/resolution/class-introspector.d.ts +27 -0
- package/dist/resolution/class-introspector.d.ts.map +1 -0
- package/dist/resolution/class-introspector.js +60 -0
- package/dist/resolution/class-introspector.js.map +1 -0
- package/dist/resolution/constraints.d.ts.map +1 -0
- package/dist/resolution/constraints.js.map +1 -0
- package/dist/resolution/diagnostics.d.ts +41 -0
- package/dist/resolution/diagnostics.d.ts.map +1 -0
- package/dist/resolution/diagnostics.js +18 -0
- package/dist/resolution/diagnostics.js.map +1 -0
- package/dist/{environment.d.ts → resolution/environment.d.ts} +24 -7
- package/dist/resolution/environment.d.ts.map +1 -0
- package/dist/resolution/environment.js +126 -0
- package/dist/resolution/environment.js.map +1 -0
- package/dist/resolution/instantiation-plan.d.ts +67 -0
- package/dist/resolution/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/instantiation-plan.js +183 -0
- package/dist/resolution/instantiation-plan.js.map +1 -0
- package/dist/{lifecycle.d.ts → resolution/lifecycle.d.ts} +5 -3
- package/dist/resolution/lifecycle.d.ts.map +1 -0
- package/dist/{lifecycle.js → resolution/lifecycle.js} +23 -14
- package/dist/resolution/lifecycle.js.map +1 -0
- package/dist/resolution/resolution-path.d.ts +22 -0
- package/dist/resolution/resolution-path.d.ts.map +1 -0
- package/dist/resolution/resolution-path.js +40 -0
- package/dist/resolution/resolution-path.js.map +1 -0
- package/dist/resolution/resolve-options.d.ts.map +1 -0
- package/dist/resolution/resolve-options.js.map +1 -0
- package/dist/resolution/resolver.d.ts +40 -0
- package/dist/resolution/resolver.d.ts.map +1 -0
- package/dist/resolution/resolver.js +1108 -0
- package/dist/resolution/resolver.js.map +1 -0
- package/dist/{scope.d.ts → resolution/scope.d.ts} +8 -9
- package/dist/resolution/scope.d.ts.map +1 -0
- package/dist/resolution/scope.js +80 -0
- package/dist/resolution/scope.js.map +1 -0
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +0 -3
- package/dist/token.js.map +1 -1
- package/package.json +19 -94
- package/src/binding.ts +137 -22
- package/src/constructor-type.ts +4 -5
- package/src/container/binding-builders.ts +294 -0
- package/src/container/container.ts +825 -0
- package/src/decorators/inject.ts +15 -4
- package/src/decorators/injectable.ts +3 -3
- package/src/errors.ts +21 -0
- package/src/index.ts +15 -7
- package/src/{dependency-graph.ts → introspection/dependency-graph.ts} +1 -1
- package/src/{graph-adapters → introspection/graph-adapters}/cytoscape.ts +1 -1
- package/src/{graph-adapters → introspection/graph-adapters}/dot.ts +1 -1
- package/src/{graph-adapters → introspection/graph-adapters}/reactflow.ts +13 -2
- package/src/{inspector.ts → introspection/inspector.ts} +26 -21
- package/src/metadata/metadata-keys.ts +3 -6
- package/src/metadata/symbol-metadata-reader.ts +4 -4
- package/src/module.ts +14 -6
- package/src/registry.ts +97 -64
- package/src/resolution/activation-need.ts +81 -0
- package/src/resolution/binding-lookup-cache.ts +132 -0
- package/src/{binding-select.ts → resolution/binding-select.ts} +17 -7
- package/src/resolution/class-introspector.ts +74 -0
- package/src/resolution/diagnostics.ts +43 -0
- package/src/{environment.ts → resolution/environment.ts} +65 -35
- package/src/resolution/instantiation-plan.ts +292 -0
- package/src/{lifecycle.ts → resolution/lifecycle.ts} +26 -14
- package/src/resolution/resolution-path.ts +62 -0
- package/src/resolution/resolver.ts +1526 -0
- package/src/resolution/scope.ts +95 -0
- package/src/token.ts +0 -3
- package/dist/binding-scope.d.ts.map +0 -1
- package/dist/binding-scope.js.map +0 -1
- package/dist/binding-select.d.ts.map +0 -1
- package/dist/binding-select.js.map +0 -1
- package/dist/constraints.d.ts.map +0 -1
- package/dist/constraints.js.map +0 -1
- package/dist/container.d.ts.map +0 -1
- package/dist/container.js +0 -853
- package/dist/container.js.map +0 -1
- package/dist/dependency-graph.d.ts.map +0 -1
- package/dist/dependency-graph.js.map +0 -1
- package/dist/environment.d.ts.map +0 -1
- package/dist/environment.js +0 -100
- package/dist/environment.js.map +0 -1
- package/dist/graph-adapters/cytoscape.d.ts.map +0 -1
- package/dist/graph-adapters/cytoscape.js.map +0 -1
- package/dist/graph-adapters/dot.d.ts +0 -6
- package/dist/graph-adapters/dot.d.ts.map +0 -1
- package/dist/graph-adapters/dot.js.map +0 -1
- package/dist/graph-adapters/reactflow.d.ts.map +0 -1
- package/dist/graph-adapters/reactflow.js.map +0 -1
- package/dist/inspector.d.ts.map +0 -1
- package/dist/inspector.js.map +0 -1
- package/dist/lifecycle.d.ts.map +0 -1
- package/dist/lifecycle.js.map +0 -1
- package/dist/resolve-options.d.ts.map +0 -1
- package/dist/resolve-options.js.map +0 -1
- package/dist/resolver.d.ts +0 -90
- package/dist/resolver.d.ts.map +0 -1
- package/dist/resolver.js +0 -1208
- package/dist/resolver.js.map +0 -1
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -61
- package/dist/scope.js.map +0 -1
- package/src/container.ts +0 -1141
- package/src/resolver.ts +0 -1626
- package/src/scope.ts +0 -77
- /package/dist/{dependency-graph.d.ts → introspection/dependency-graph.d.ts} +0 -0
- /package/dist/{graph-adapters → introspection/graph-adapters}/cytoscape.js +0 -0
- /package/dist/{graph-adapters → introspection/graph-adapters}/dot.js +0 -0
- /package/dist/{binding-scope.d.ts → resolution/binding-scope.d.ts} +0 -0
- /package/dist/{binding-scope.js → resolution/binding-scope.js} +0 -0
- /package/dist/{binding-select.d.ts → resolution/binding-select.d.ts} +0 -0
- /package/dist/{constraints.d.ts → resolution/constraints.d.ts} +0 -0
- /package/dist/{constraints.js → resolution/constraints.js} +0 -0
- /package/dist/{resolve-options.d.ts → resolution/resolve-options.d.ts} +0 -0
- /package/dist/{resolve-options.js → resolution/resolve-options.js} +0 -0
- /package/src/{binding-scope.ts → resolution/binding-scope.ts} +0 -0
- /package/src/{constraints.ts → resolution/constraints.ts} +0 -0
- /package/src/{resolve-options.ts → resolution/resolve-options.ts} +0 -0
|
@@ -0,0 +1,1526 @@
|
|
|
1
|
+
import type { Binding, BindingSlot } from "#/binding";
|
|
2
|
+
import { NO_INSTANCE } from "#/binding";
|
|
3
|
+
import type { Container } from "#/container/container";
|
|
4
|
+
import type { InjectionDescriptor } from "#/decorators/inject";
|
|
5
|
+
import {
|
|
6
|
+
AsyncActivationError,
|
|
7
|
+
AsyncResolutionError,
|
|
8
|
+
CircularDependencyError,
|
|
9
|
+
InternalError,
|
|
10
|
+
MissingMetadataError,
|
|
11
|
+
MissingScopeContextError,
|
|
12
|
+
NoMatchingBindingError,
|
|
13
|
+
TokenNotBoundError,
|
|
14
|
+
} from "#/errors";
|
|
15
|
+
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
16
|
+
import type { BindingRegistry } from "#/registry";
|
|
17
|
+
import { ActivationNeedCache } from "#/resolution/activation-need";
|
|
18
|
+
import type { DefaultLookupEntry } from "#/resolution/binding-lookup-cache";
|
|
19
|
+
import { BindingLookupCache } from "#/resolution/binding-lookup-cache";
|
|
20
|
+
import { selectAllBindings, selectBinding } from "#/resolution/binding-select";
|
|
21
|
+
import { ClassIntrospector } from "#/resolution/class-introspector";
|
|
22
|
+
import type { ResolutionDiagnostics } from "#/resolution/diagnostics";
|
|
23
|
+
import type { ResolverCallbacks } from "#/resolution/environment";
|
|
24
|
+
import { buildResolutionFrame, DefaultResolutionContext } from "#/resolution/environment";
|
|
25
|
+
import { InstantiationPlanCompiler, PLAN_RETRY } from "#/resolution/instantiation-plan";
|
|
26
|
+
import type { LifecycleManager } from "#/resolution/lifecycle";
|
|
27
|
+
import { enterResolutionPath, exitResolutionPath } from "#/resolution/resolution-path";
|
|
28
|
+
import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
29
|
+
import type { ScopeManager } from "#/resolution/scope";
|
|
30
|
+
import type { Token } from "#/token";
|
|
31
|
+
import { tokenName } from "#/token";
|
|
32
|
+
import type {
|
|
33
|
+
ActivationHandler,
|
|
34
|
+
BindingIdentifier,
|
|
35
|
+
BindingScope,
|
|
36
|
+
BindingTag,
|
|
37
|
+
ConstraintContext,
|
|
38
|
+
Constructor,
|
|
39
|
+
ResolutionFrame,
|
|
40
|
+
ResolveOptions,
|
|
41
|
+
} from "#/types";
|
|
42
|
+
|
|
43
|
+
type BindingWithScope = Binding & { scope: BindingScope };
|
|
44
|
+
const EMPTY_STRING_LIST: ReadonlyArray<string> = [];
|
|
45
|
+
const EMPTY_FRAME_LIST: ReadonlyArray<ResolutionFrame> = [];
|
|
46
|
+
const ROOT_CONSTRAINT_CONTEXT = {
|
|
47
|
+
resolutionPath: EMPTY_STRING_LIST,
|
|
48
|
+
resolutionStack: EMPTY_FRAME_LIST,
|
|
49
|
+
parent: undefined,
|
|
50
|
+
ancestors: EMPTY_FRAME_LIST,
|
|
51
|
+
currentResolveOptions: undefined,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @since 0.3.16-canary.0
|
|
56
|
+
*/
|
|
57
|
+
export class DependencyResolver implements ResolverCallbacks {
|
|
58
|
+
readonly #syncResolutionContextPool: Array<DefaultResolutionContext> = [];
|
|
59
|
+
// Pure allocation pool: a chain's own context is threaded through the call, so nothing here
|
|
60
|
+
// identifies a chain.
|
|
61
|
+
readonly #asyncChainContextPool: Array<DefaultResolutionContext> = [];
|
|
62
|
+
// Compiled plans; `null` marks a binding as unplannable under the current cache versions.
|
|
63
|
+
readonly #classPlanByBindingId = new Map<BindingIdentifier, (() => unknown) | null>();
|
|
64
|
+
#classPlanRegistryVersion = -1;
|
|
65
|
+
#classPlanActivationVersion = -1;
|
|
66
|
+
|
|
67
|
+
readonly #registry: BindingRegistry;
|
|
68
|
+
readonly #scope: ScopeManager;
|
|
69
|
+
readonly #lifecycle: LifecycleManager;
|
|
70
|
+
readonly #metadataReader: MetadataReader;
|
|
71
|
+
readonly #parent: DependencyResolver | undefined;
|
|
72
|
+
readonly #lookup: BindingLookupCache<DependencyResolver>;
|
|
73
|
+
readonly #classes: ClassIntrospector;
|
|
74
|
+
readonly #activation: ActivationNeedCache;
|
|
75
|
+
|
|
76
|
+
constructor(
|
|
77
|
+
registry: BindingRegistry,
|
|
78
|
+
scope: ScopeManager,
|
|
79
|
+
lifecycle: LifecycleManager,
|
|
80
|
+
metadataReader: MetadataReader,
|
|
81
|
+
container: Container,
|
|
82
|
+
parent: DependencyResolver | undefined,
|
|
83
|
+
) {
|
|
84
|
+
this.#registry = registry;
|
|
85
|
+
this.#scope = scope;
|
|
86
|
+
this.#lifecycle = lifecycle;
|
|
87
|
+
this.#metadataReader = metadataReader;
|
|
88
|
+
this.#parent = parent;
|
|
89
|
+
this.#lookup = new BindingLookupCache<DependencyResolver>(
|
|
90
|
+
registry,
|
|
91
|
+
this,
|
|
92
|
+
parent === undefined ? undefined : parent.#lookup,
|
|
93
|
+
);
|
|
94
|
+
this.#classes = new ClassIntrospector(metadataReader, container);
|
|
95
|
+
this.#activation = new ActivationNeedCache(lifecycle, this.#classes);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Structural counts for {@link RESOLUTION_DIAGNOSTICS}; see `resolution/diagnostics.ts`. */
|
|
99
|
+
describeCaches(): Pick<ResolutionDiagnostics, "asyncContextPoolSize" | "compiledPlanCount" | "syncContextPoolSize"> {
|
|
100
|
+
let compiledPlanCount = 0;
|
|
101
|
+
for (const plan of this.#classPlanByBindingId.values()) {
|
|
102
|
+
if (plan !== null) {
|
|
103
|
+
compiledPlanCount += 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
asyncContextPoolSize: this.#asyncChainContextPool.length,
|
|
108
|
+
compiledPlanCount,
|
|
109
|
+
syncContextPoolSize: this.#syncResolutionContextPool.length,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Binding lookup ─────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
#findBinding(
|
|
116
|
+
token: Token<unknown> | Constructor,
|
|
117
|
+
options: ResolveOptions | undefined,
|
|
118
|
+
resolutionPath: Array<string>,
|
|
119
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
120
|
+
): { binding: Binding; owner: DependencyResolver } | undefined {
|
|
121
|
+
if (options === undefined) {
|
|
122
|
+
const fastDefaultBinding = this.#registry.getFastDefault(token);
|
|
123
|
+
if (fastDefaultBinding !== undefined) {
|
|
124
|
+
return { binding: fastDefaultBinding, owner: this };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
129
|
+
const namedBinding = this.#registry.getSimpleNamed(token, options.name);
|
|
130
|
+
if (
|
|
131
|
+
namedBinding !== undefined &&
|
|
132
|
+
this.#matchesBindingFast(namedBinding, options, resolutionPath, resolutionStack)
|
|
133
|
+
) {
|
|
134
|
+
return { binding: namedBinding, owner: this };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (
|
|
139
|
+
options !== undefined &&
|
|
140
|
+
options.name === undefined &&
|
|
141
|
+
options.tag === undefined &&
|
|
142
|
+
(options.tags?.length ?? 0) === 1
|
|
143
|
+
) {
|
|
144
|
+
const [tagKey, tagValue] = options.tags![0]!;
|
|
145
|
+
const tagged = this.#registry.getSimpleTagged(token, tagKey, tagValue);
|
|
146
|
+
if (tagged !== undefined) {
|
|
147
|
+
return { binding: tagged, owner: this };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const bindings = this.#registry.getAll(token);
|
|
152
|
+
if (bindings.length > 0) {
|
|
153
|
+
if (bindings.length === 1) {
|
|
154
|
+
const onlyBinding = bindings[0]!;
|
|
155
|
+
const isDefaultSlot = onlyBinding.slot.name === undefined && onlyBinding.slot.tags.length === 0;
|
|
156
|
+
if (options === undefined && isDefaultSlot && onlyBinding.predicate === undefined) {
|
|
157
|
+
return { binding: onlyBinding, owner: this };
|
|
158
|
+
}
|
|
159
|
+
if (this.#matchesBindingFast(onlyBinding, options, resolutionPath, resolutionStack)) {
|
|
160
|
+
return { binding: onlyBinding, owner: this };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
164
|
+
const binding = selectBinding(bindings, options, ctx, this.#getTokenName(token));
|
|
165
|
+
if (binding !== undefined) {
|
|
166
|
+
return { binding, owner: this };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (this.#parent !== undefined) {
|
|
170
|
+
return this.#parent.#findBinding(token, options, resolutionPath, resolutionStack);
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Binding lookup aligned with `resolve` — used by `Container.validate` without instantiating.
|
|
177
|
+
*/
|
|
178
|
+
peekBindingForValidate(
|
|
179
|
+
token: Token<unknown> | Constructor,
|
|
180
|
+
options: ResolveOptions | undefined,
|
|
181
|
+
): { binding: Binding; owner: DependencyResolver } | undefined {
|
|
182
|
+
return this.#findBinding(token, options, [], []);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mirrors {@link DependencyResolver.resolveAll} candidate selection only (no instantiation).
|
|
187
|
+
*/
|
|
188
|
+
peekCandidateBindingsForValidate(
|
|
189
|
+
token: Token<unknown> | Constructor,
|
|
190
|
+
options: ResolveOptions | undefined,
|
|
191
|
+
): Array<Binding> {
|
|
192
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
193
|
+
return this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
194
|
+
}
|
|
195
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
196
|
+
if (allBindings.length === 0) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const ctx = this.#makeConstraintContext([], [], options);
|
|
200
|
+
return selectAllBindings(allBindings, options, ctx);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Sync resolve ───────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
resolveFromContext<const Value>(
|
|
206
|
+
token: Token<Value> | Constructor<Value>,
|
|
207
|
+
resolutionPath: Array<string>,
|
|
208
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
209
|
+
): Value {
|
|
210
|
+
// Hot lane: own-registry fast default. Fall back to the chain-versioned memo
|
|
211
|
+
// (parent-chain walk + alias folding) only on miss or alias.
|
|
212
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
213
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
214
|
+
return this.#resolveDefaultEntry<Value>(fastBinding, this, resolutionPath, resolutionStack);
|
|
215
|
+
}
|
|
216
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
217
|
+
if (entry === null) {
|
|
218
|
+
return this.resolve(token, undefined, resolutionPath, resolutionStack);
|
|
219
|
+
}
|
|
220
|
+
return this.#resolveDefaultEntry<Value>(entry.binding, entry.owner, resolutionPath, resolutionStack);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#resolveDefaultEntry<const Value>(
|
|
224
|
+
binding: Binding,
|
|
225
|
+
owner: DependencyResolver,
|
|
226
|
+
resolutionPath: Array<string>,
|
|
227
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
228
|
+
): Value {
|
|
229
|
+
if (
|
|
230
|
+
binding.kind === "constant" &&
|
|
231
|
+
binding.onActivation === undefined &&
|
|
232
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))
|
|
233
|
+
) {
|
|
234
|
+
return binding.value as Value;
|
|
235
|
+
}
|
|
236
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
237
|
+
if (scope === "transient") {
|
|
238
|
+
if (binding.kind === "dynamic") {
|
|
239
|
+
const containerHooks =
|
|
240
|
+
this.#lifecycle.activationVersion === 0 ? undefined : this.#lifecycle.activationHandlersFor(binding.token);
|
|
241
|
+
if (binding.onActivation === undefined && (containerHooks === undefined || containerHooks.length === 0)) {
|
|
242
|
+
return this.#resolveTransientDynamicSyncFromContext(
|
|
243
|
+
binding as Binding<Value> & { kind: "dynamic" },
|
|
244
|
+
resolutionPath,
|
|
245
|
+
resolutionStack,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return this.#resolveTransientDynamicActivatedSync(
|
|
249
|
+
binding as Binding<Value> & { kind: "dynamic" },
|
|
250
|
+
containerHooks,
|
|
251
|
+
resolutionPath,
|
|
252
|
+
resolutionStack,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
// Compiled plans only run at the top level — inner levels keep the runtime cycle guard.
|
|
256
|
+
if ((binding.kind === "class" || binding.kind === "resolved") && resolutionPath.length === 0) {
|
|
257
|
+
const plan = this.#getInstantiationPlan(binding);
|
|
258
|
+
if (plan !== null) {
|
|
259
|
+
return plan() as Value;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else if (scope === "singleton") {
|
|
263
|
+
const cachedSingleton = binding.instance;
|
|
264
|
+
if (cachedSingleton !== NO_INSTANCE) {
|
|
265
|
+
return cachedSingleton as Value;
|
|
266
|
+
}
|
|
267
|
+
if (owner !== this) {
|
|
268
|
+
return owner.#resolveBinding(binding as Binding<Value>, undefined, resolutionPath, resolutionStack);
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
if (!this.#scope.isChild) {
|
|
272
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
273
|
+
}
|
|
274
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
275
|
+
return this.#scope.getScoped<Value>(binding.id);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return this.#resolveBinding(binding as Binding<Value>, undefined, resolutionPath, resolutionStack);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Lean lane for an activated transient dynamic binding: same observable behavior as the
|
|
282
|
+
// generic #resolveBinding path (guard, frame, ctx, per-binding then container hooks) with
|
|
283
|
+
// the kind/activation dispatch resolved statically.
|
|
284
|
+
#resolveTransientDynamicActivatedSync<const Value>(
|
|
285
|
+
binding: Binding<Value> & { kind: "dynamic" },
|
|
286
|
+
containerHooks: ReadonlyArray<ActivationHandler<unknown>> | undefined,
|
|
287
|
+
resolutionPath: Array<string>,
|
|
288
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
289
|
+
): Value {
|
|
290
|
+
const frame = this.#getResolutionFrame(binding);
|
|
291
|
+
const tokenDisplayName = frame.tokenName;
|
|
292
|
+
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
293
|
+
resolutionStack.push(frame);
|
|
294
|
+
try {
|
|
295
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
296
|
+
const factoryResult = binding.factory(resolutionCtx);
|
|
297
|
+
if (factoryResult instanceof Promise) {
|
|
298
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
299
|
+
}
|
|
300
|
+
let activated = factoryResult;
|
|
301
|
+
if (binding.onActivation !== undefined) {
|
|
302
|
+
const activationResult = binding.onActivation(resolutionCtx, activated);
|
|
303
|
+
if (activationResult instanceof Promise) {
|
|
304
|
+
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
305
|
+
}
|
|
306
|
+
activated = activationResult;
|
|
307
|
+
}
|
|
308
|
+
if (containerHooks !== undefined) {
|
|
309
|
+
for (let index = 0; index < containerHooks.length; index += 1) {
|
|
310
|
+
const activationResult = containerHooks[index]!(resolutionCtx, activated);
|
|
311
|
+
if (activationResult instanceof Promise) {
|
|
312
|
+
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
313
|
+
}
|
|
314
|
+
activated = activationResult as Value;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return activated;
|
|
318
|
+
} finally {
|
|
319
|
+
resolutionStack.pop();
|
|
320
|
+
resolutionPath.pop();
|
|
321
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
#getInstantiationPlan(binding: Binding & { kind: "class" | "resolved" }): (() => unknown) | null {
|
|
326
|
+
const registryVersion = this.#lookup.chainVersion();
|
|
327
|
+
const activationVersion = this.#lifecycle.activationVersion;
|
|
328
|
+
if (registryVersion !== this.#classPlanRegistryVersion || activationVersion !== this.#classPlanActivationVersion) {
|
|
329
|
+
this.#classPlanByBindingId.clear();
|
|
330
|
+
this.#classPlanRegistryVersion = registryVersion;
|
|
331
|
+
this.#classPlanActivationVersion = activationVersion;
|
|
332
|
+
}
|
|
333
|
+
const cached = this.#classPlanByBindingId.get(binding.id);
|
|
334
|
+
if (cached !== undefined) {
|
|
335
|
+
return cached;
|
|
336
|
+
}
|
|
337
|
+
const compiled = this.#planCompiler.compile(binding);
|
|
338
|
+
if (compiled === PLAN_RETRY) {
|
|
339
|
+
// Lifecycle metadata not discovered yet — the fallback resolve discovers it; retry then.
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
this.#classPlanByBindingId.set(binding.id, compiled);
|
|
343
|
+
return compiled;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Compiler behind #getClassPlan — cold path, so the host indirection costs nothing hot.
|
|
347
|
+
readonly #planCompiler = new InstantiationPlanCompiler({
|
|
348
|
+
hasActivationHandlers: (token) => this.#lifecycle.hasActivationHandlers(token),
|
|
349
|
+
knownPostConstruct: (target) => this.#classes.knownPostConstruct(target),
|
|
350
|
+
needsActiveContainer: (target) => this.#classes.needsActiveContainer(target),
|
|
351
|
+
getConstructorMetadata: (target) => this.#classes.constructorMetadata(target),
|
|
352
|
+
lookupDependencyEntry: (token) => {
|
|
353
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
354
|
+
return entry === null ? null : { binding: entry.binding };
|
|
355
|
+
},
|
|
356
|
+
getResolutionFrame: (binding) => this.#getResolutionFrame(binding),
|
|
357
|
+
// Dispatches exactly as #resolveClassDeps does, so an escaped dep is indistinguishable
|
|
358
|
+
// from the same dep on a fully interpreted resolve.
|
|
359
|
+
resolveEscaped: (token, options, arity, resolutionPath, resolutionStack) => {
|
|
360
|
+
if (arity === "all") {
|
|
361
|
+
return this.resolveAll(token, options, resolutionPath, resolutionStack);
|
|
362
|
+
}
|
|
363
|
+
if (arity === "optional") {
|
|
364
|
+
return this.resolveOptional(token, options, resolutionPath, resolutionStack);
|
|
365
|
+
}
|
|
366
|
+
if (options === undefined) {
|
|
367
|
+
return this.resolveFromContext(token, resolutionPath, resolutionStack);
|
|
368
|
+
}
|
|
369
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
resolve<const Value>(
|
|
374
|
+
token: Token<Value> | Constructor<Value>,
|
|
375
|
+
options: ResolveOptions | undefined,
|
|
376
|
+
resolutionPath: Array<string>,
|
|
377
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
378
|
+
): Value {
|
|
379
|
+
// Name-only fast lane: memoized lookup, dispatching just the shapes whose
|
|
380
|
+
// semantics involve no resolution context (constants, cached singletons).
|
|
381
|
+
if (
|
|
382
|
+
options !== undefined &&
|
|
383
|
+
options.name !== undefined &&
|
|
384
|
+
options.tag === undefined &&
|
|
385
|
+
(options.tags === undefined || options.tags.length === 0)
|
|
386
|
+
) {
|
|
387
|
+
const namedEntry = this.#lookup.namedEntry(token, options.name);
|
|
388
|
+
if (namedEntry !== null) {
|
|
389
|
+
const namedBinding = namedEntry.binding;
|
|
390
|
+
if (
|
|
391
|
+
namedBinding.kind === "constant" &&
|
|
392
|
+
namedBinding.onActivation === undefined &&
|
|
393
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(namedBinding.token))
|
|
394
|
+
) {
|
|
395
|
+
return namedBinding.value as Value;
|
|
396
|
+
}
|
|
397
|
+
const namedScope = (namedBinding as BindingWithScope).scope ?? "transient";
|
|
398
|
+
if (namedScope === "singleton") {
|
|
399
|
+
if (namedBinding.instance !== NO_INSTANCE) {
|
|
400
|
+
return namedBinding.instance as Value;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Everything else keeps the full path (context, activation, guards).
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let currentToken: Token<unknown> | Constructor = token;
|
|
408
|
+
let visitedAliasTokens: Set<Token<unknown> | Constructor> | undefined;
|
|
409
|
+
let found = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
410
|
+
|
|
411
|
+
// Follow aliases iteratively with exact cycle detection — a revisited alias
|
|
412
|
+
// token throws CircularDependencyError instead of overflowing the call stack.
|
|
413
|
+
while (found !== undefined && found.binding.kind === "alias") {
|
|
414
|
+
const target = found.binding.target;
|
|
415
|
+
visitedAliasTokens ??= new Set([currentToken]);
|
|
416
|
+
if (visitedAliasTokens.has(target)) {
|
|
417
|
+
throw new CircularDependencyError([...visitedAliasTokens, target].map((entry) => tokenName(entry)));
|
|
418
|
+
}
|
|
419
|
+
visitedAliasTokens.add(target);
|
|
420
|
+
currentToken = target;
|
|
421
|
+
found = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (found === undefined) {
|
|
425
|
+
const ownBindings = this.#registry.getAll(currentToken);
|
|
426
|
+
if (ownBindings.length > 0) {
|
|
427
|
+
throw new NoMatchingBindingError(
|
|
428
|
+
this.#getTokenName(currentToken),
|
|
429
|
+
options ?? {},
|
|
430
|
+
this.#getAvailableSlots(currentToken),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
throw new TokenNotBoundError(this.#getTokenName(currentToken));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const { binding, owner } = found;
|
|
437
|
+
|
|
438
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
439
|
+
|
|
440
|
+
// Singleton from a parent resolver: delegate so the parent caches it correctly
|
|
441
|
+
if (scope === "singleton" && owner !== this) {
|
|
442
|
+
return owner.#resolveBinding(binding as Binding<Value>, options, resolutionPath, resolutionStack);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Scoped/transient (or own singleton): resolve with this resolver's container/scope
|
|
446
|
+
return this.#resolveBinding(binding as Binding<Value>, options, resolutionPath, resolutionStack);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
#resolveBinding<const Value>(
|
|
450
|
+
binding: Binding<Value>,
|
|
451
|
+
options: ResolveOptions | undefined,
|
|
452
|
+
resolutionPath: Array<string>,
|
|
453
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
454
|
+
): Value {
|
|
455
|
+
if (
|
|
456
|
+
binding.kind === "constant" &&
|
|
457
|
+
binding.onActivation === undefined &&
|
|
458
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))
|
|
459
|
+
) {
|
|
460
|
+
return binding.value;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
464
|
+
|
|
465
|
+
// Singleton cache check
|
|
466
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
467
|
+
return binding.instance as Value;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Scoped cache check
|
|
471
|
+
if (scope === "scoped") {
|
|
472
|
+
if (!this.#scope.isChild) {
|
|
473
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
474
|
+
}
|
|
475
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
476
|
+
return this.#scope.getScoped<Value>(binding.id);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const frame = this.#getResolutionFrame(binding);
|
|
481
|
+
const tokenDisplayName = frame.tokenName;
|
|
482
|
+
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
483
|
+
resolutionStack.push(frame);
|
|
484
|
+
const needsActivation = this.#activation.needsActivation(binding);
|
|
485
|
+
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
486
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options);
|
|
487
|
+
try {
|
|
488
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
489
|
+
if (dynamicResult instanceof Promise) {
|
|
490
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
491
|
+
}
|
|
492
|
+
resolutionStack.pop();
|
|
493
|
+
resolutionPath.pop();
|
|
494
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
495
|
+
return dynamicResult;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
resolutionStack.pop();
|
|
498
|
+
resolutionPath.pop();
|
|
499
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
506
|
+
const resolutionCtx = needsResolutionContext
|
|
507
|
+
? this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options)
|
|
508
|
+
: undefined;
|
|
509
|
+
|
|
510
|
+
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
511
|
+
|
|
512
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
513
|
+
const activated = shouldActivate
|
|
514
|
+
? this.#lifecycle.runActivationSync(
|
|
515
|
+
resolutionCtx as DefaultResolutionContext,
|
|
516
|
+
binding,
|
|
517
|
+
instance,
|
|
518
|
+
this.#metadataReader,
|
|
519
|
+
)
|
|
520
|
+
: instance;
|
|
521
|
+
|
|
522
|
+
// Cache by scope
|
|
523
|
+
if (scope === "singleton") {
|
|
524
|
+
this.#scope.setSingleton(binding, activated);
|
|
525
|
+
} else if (scope === "scoped") {
|
|
526
|
+
this.#scope.setScoped(binding.id, activated);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return activated;
|
|
530
|
+
} finally {
|
|
531
|
+
resolutionStack.pop();
|
|
532
|
+
resolutionPath.pop();
|
|
533
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#instantiateSync<const Value>(
|
|
538
|
+
binding: Binding<Value>,
|
|
539
|
+
ctx: DefaultResolutionContext | undefined,
|
|
540
|
+
resolutionPath: Array<string>,
|
|
541
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
542
|
+
): Value {
|
|
543
|
+
switch (binding.kind) {
|
|
544
|
+
case "constant":
|
|
545
|
+
return binding.value;
|
|
546
|
+
|
|
547
|
+
case "dynamic": {
|
|
548
|
+
if (ctx === undefined) {
|
|
549
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
550
|
+
}
|
|
551
|
+
const factoryResult = binding.factory(ctx);
|
|
552
|
+
if (factoryResult instanceof Promise) {
|
|
553
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
554
|
+
}
|
|
555
|
+
return factoryResult;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
case "dynamic-async":
|
|
559
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
560
|
+
|
|
561
|
+
case "class": {
|
|
562
|
+
const deps = this.#resolveClassDeps(binding.target, resolutionPath, resolutionStack);
|
|
563
|
+
const instance = this.#classes.instantiate(binding.target, deps);
|
|
564
|
+
return instance as Value;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
case "resolved": {
|
|
568
|
+
const deps = this.#resolveDescriptorDeps(binding.deps, resolutionPath, resolutionStack);
|
|
569
|
+
const factoryResult = binding.factory(...deps);
|
|
570
|
+
if (factoryResult instanceof Promise) {
|
|
571
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
572
|
+
}
|
|
573
|
+
return factoryResult;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
case "resolved-async":
|
|
577
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
578
|
+
|
|
579
|
+
case "alias":
|
|
580
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#resolveClassDeps(
|
|
585
|
+
target: Constructor,
|
|
586
|
+
resolutionPath: Array<string>,
|
|
587
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
588
|
+
): Array<unknown> {
|
|
589
|
+
const meta = this.#classes.constructorMetadata(target);
|
|
590
|
+
if (meta === undefined) {
|
|
591
|
+
if (target.length === 0) {
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
throw new MissingMetadataError(target.name);
|
|
595
|
+
}
|
|
596
|
+
if (meta.params.length === 0) {
|
|
597
|
+
return [];
|
|
598
|
+
}
|
|
599
|
+
if (meta.params.length === 1) {
|
|
600
|
+
const param = meta.params[0]!;
|
|
601
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
602
|
+
if (param.multi) {
|
|
603
|
+
return [this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
604
|
+
}
|
|
605
|
+
if (param.optional) {
|
|
606
|
+
return [this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
607
|
+
}
|
|
608
|
+
if (paramOptions === undefined) {
|
|
609
|
+
return [this.resolveFromContext(param.token, resolutionPath, resolutionStack)];
|
|
610
|
+
}
|
|
611
|
+
return [this.resolve(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
612
|
+
}
|
|
613
|
+
const deps = new Array<unknown>(meta.params.length);
|
|
614
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
615
|
+
const param = meta.params[index]!;
|
|
616
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
617
|
+
if (param.multi) {
|
|
618
|
+
deps[index] = this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (param.optional) {
|
|
622
|
+
deps[index] = this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
deps[index] =
|
|
626
|
+
paramOptions === undefined
|
|
627
|
+
? this.resolveFromContext(param.token, resolutionPath, resolutionStack)
|
|
628
|
+
: this.resolve(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
629
|
+
}
|
|
630
|
+
return deps;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
#resolveDescriptorDeps(
|
|
634
|
+
deps: ReadonlyArray<InjectionDescriptor>,
|
|
635
|
+
resolutionPath: Array<string>,
|
|
636
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
637
|
+
): Array<unknown> {
|
|
638
|
+
const resolved = new Array<unknown>(deps.length);
|
|
639
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
640
|
+
const dep = deps[index]!;
|
|
641
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
642
|
+
if (dep.multi) {
|
|
643
|
+
resolved[index] = this.resolveAll(
|
|
644
|
+
dep.token as Token<unknown> | Constructor,
|
|
645
|
+
depOptions,
|
|
646
|
+
resolutionPath,
|
|
647
|
+
resolutionStack,
|
|
648
|
+
);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (dep.optional) {
|
|
652
|
+
resolved[index] = this.resolveOptional(
|
|
653
|
+
dep.token as Token<unknown> | Constructor,
|
|
654
|
+
depOptions,
|
|
655
|
+
resolutionPath,
|
|
656
|
+
resolutionStack,
|
|
657
|
+
);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
resolved[index] =
|
|
661
|
+
depOptions === undefined
|
|
662
|
+
? this.resolveFromContext(dep.token as Token<unknown> | Constructor, resolutionPath, resolutionStack)
|
|
663
|
+
: this.resolve(dep.token as Token<unknown> | Constructor, depOptions, resolutionPath, resolutionStack);
|
|
664
|
+
}
|
|
665
|
+
return resolved;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
resolveOptional<const Value>(
|
|
669
|
+
token: Token<Value> | Constructor<Value>,
|
|
670
|
+
options: ResolveOptions | undefined,
|
|
671
|
+
resolutionPath: Array<string>,
|
|
672
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
673
|
+
): Value | undefined {
|
|
674
|
+
if (this.#findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
resolveAll<const Value>(
|
|
681
|
+
token: Token<Value> | Constructor<Value>,
|
|
682
|
+
options: ResolveOptions | undefined,
|
|
683
|
+
resolutionPath: Array<string>,
|
|
684
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
685
|
+
): Array<Value> {
|
|
686
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
687
|
+
const namedCandidates = this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
688
|
+
if (namedCandidates.length === 0) {
|
|
689
|
+
return [];
|
|
690
|
+
}
|
|
691
|
+
const resolved = new Array<Value>(namedCandidates.length);
|
|
692
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
693
|
+
resolved[index] = this.#resolveCandidateSync(
|
|
694
|
+
namedCandidates[index] as Binding<Value>,
|
|
695
|
+
options,
|
|
696
|
+
resolutionPath,
|
|
697
|
+
resolutionStack,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
return resolved;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
704
|
+
if (allBindings.length === 0) {
|
|
705
|
+
return [];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
709
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
710
|
+
|
|
711
|
+
const resolved = new Array<Value>(candidates.length);
|
|
712
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
713
|
+
resolved[index] = this.#resolveCandidateSync(
|
|
714
|
+
candidates[index] as Binding<Value>,
|
|
715
|
+
options,
|
|
716
|
+
resolutionPath,
|
|
717
|
+
resolutionStack,
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
return resolved;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// ── Async resolve ──────────────────────────────────────────────────────────
|
|
724
|
+
|
|
725
|
+
resolveAsyncFromContext<const Value>(
|
|
726
|
+
token: Token<Value> | Constructor<Value>,
|
|
727
|
+
resolutionPath: Array<string>,
|
|
728
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
729
|
+
callerContext?: DefaultResolutionContext,
|
|
730
|
+
): Promise<Value> {
|
|
731
|
+
// Hot lane: own-registry fast default (async chains resolve sibling dynamic bindings).
|
|
732
|
+
// Fall back to the chain-versioned memo only on miss or alias.
|
|
733
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
734
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
735
|
+
// Inline the dominant chain shape — transient dynamic factory with no activation.
|
|
736
|
+
if (
|
|
737
|
+
(fastBinding.kind === "dynamic-async" || fastBinding.kind === "dynamic") &&
|
|
738
|
+
fastBinding.scope === "transient" &&
|
|
739
|
+
fastBinding.onActivation === undefined &&
|
|
740
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(fastBinding.token))
|
|
741
|
+
) {
|
|
742
|
+
return this.#resolveTransientDynamicAsyncFromContext(
|
|
743
|
+
fastBinding as Binding<Value> & { kind: "dynamic" | "dynamic-async" },
|
|
744
|
+
resolutionPath,
|
|
745
|
+
resolutionStack,
|
|
746
|
+
callerContext,
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return this.#resolveAsyncDefaultEntry<Value>(fastBinding, this, resolutionPath, resolutionStack, callerContext);
|
|
750
|
+
}
|
|
751
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
752
|
+
if (entry === null) {
|
|
753
|
+
return this.resolveAsync(token, undefined, resolutionPath, resolutionStack);
|
|
754
|
+
}
|
|
755
|
+
return this.#resolveAsyncDefaultEntry<Value>(
|
|
756
|
+
entry.binding,
|
|
757
|
+
entry.owner,
|
|
758
|
+
resolutionPath,
|
|
759
|
+
resolutionStack,
|
|
760
|
+
callerContext,
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
#resolveAsyncDefaultEntry<const Value>(
|
|
765
|
+
binding: Binding,
|
|
766
|
+
owner: DependencyResolver,
|
|
767
|
+
resolutionPath: Array<string>,
|
|
768
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
769
|
+
callerContext?: DefaultResolutionContext,
|
|
770
|
+
): Promise<Value> {
|
|
771
|
+
if (
|
|
772
|
+
binding.kind === "constant" &&
|
|
773
|
+
binding.onActivation === undefined &&
|
|
774
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))
|
|
775
|
+
) {
|
|
776
|
+
return Promise.resolve(binding.value as Value);
|
|
777
|
+
}
|
|
778
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
779
|
+
if (scope === "transient") {
|
|
780
|
+
if (
|
|
781
|
+
(binding.kind === "dynamic" || binding.kind === "dynamic-async") &&
|
|
782
|
+
binding.onActivation === undefined &&
|
|
783
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))
|
|
784
|
+
) {
|
|
785
|
+
return this.#resolveTransientDynamicAsyncFromContext(
|
|
786
|
+
binding as Binding<Value> & { kind: "dynamic" | "dynamic-async" },
|
|
787
|
+
resolutionPath,
|
|
788
|
+
resolutionStack,
|
|
789
|
+
callerContext,
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
} else if (scope === "singleton") {
|
|
793
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
794
|
+
return Promise.resolve(binding.instance as Value);
|
|
795
|
+
}
|
|
796
|
+
if (owner !== this) {
|
|
797
|
+
return owner.#resolveBindingAsync(binding as Binding<Value>, undefined, resolutionPath, resolutionStack);
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
if (!this.#scope.isChild) {
|
|
801
|
+
return Promise.reject(new MissingScopeContextError(this.#getTokenName(binding.token)));
|
|
802
|
+
}
|
|
803
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
804
|
+
return Promise.resolve(this.#scope.getScoped<Value>(binding.id));
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return this.#resolveBindingAsync(binding as Binding<Value>, undefined, resolutionPath, resolutionStack);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async resolveAsync<const Value>(
|
|
811
|
+
token: Token<Value> | Constructor<Value>,
|
|
812
|
+
options: ResolveOptions | undefined,
|
|
813
|
+
resolutionPath: Array<string>,
|
|
814
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
815
|
+
): Promise<Value> {
|
|
816
|
+
const found = this.#findBinding(token, options, resolutionPath, resolutionStack);
|
|
817
|
+
|
|
818
|
+
if (found === undefined) {
|
|
819
|
+
const ownBindings = this.#registry.getAll(token);
|
|
820
|
+
if (ownBindings.length > 0) {
|
|
821
|
+
throw new NoMatchingBindingError(this.#getTokenName(token), options ?? {}, this.#getAvailableSlots(token));
|
|
822
|
+
}
|
|
823
|
+
throw new TokenNotBoundError(this.#getTokenName(token));
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
let currentToken: Token<unknown> | Constructor = token;
|
|
827
|
+
let visitedAliasTokens: Set<Token<unknown> | Constructor> | undefined;
|
|
828
|
+
let aliasFollowed: DefaultLookupEntry<DependencyResolver> | undefined = found;
|
|
829
|
+
while (aliasFollowed !== undefined && aliasFollowed.binding.kind === "alias") {
|
|
830
|
+
const target = aliasFollowed.binding.target;
|
|
831
|
+
visitedAliasTokens ??= new Set([currentToken]);
|
|
832
|
+
if (visitedAliasTokens.has(target)) {
|
|
833
|
+
throw new CircularDependencyError([...visitedAliasTokens, target].map((entry) => tokenName(entry)));
|
|
834
|
+
}
|
|
835
|
+
visitedAliasTokens.add(target);
|
|
836
|
+
currentToken = target;
|
|
837
|
+
aliasFollowed = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
838
|
+
}
|
|
839
|
+
if (aliasFollowed === undefined) {
|
|
840
|
+
const ownBindings = this.#registry.getAll(currentToken);
|
|
841
|
+
if (ownBindings.length > 0) {
|
|
842
|
+
throw new NoMatchingBindingError(
|
|
843
|
+
this.#getTokenName(currentToken),
|
|
844
|
+
options ?? {},
|
|
845
|
+
this.#getAvailableSlots(currentToken),
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
throw new TokenNotBoundError(this.#getTokenName(currentToken));
|
|
849
|
+
}
|
|
850
|
+
const { binding, owner } = aliasFollowed;
|
|
851
|
+
|
|
852
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
853
|
+
|
|
854
|
+
if (scope === "singleton" && owner !== this) {
|
|
855
|
+
return owner.#resolveBindingAsync(binding as Binding<Value>, options, resolutionPath, resolutionStack);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return this.#resolveBindingAsync(binding as Binding<Value>, options, resolutionPath, resolutionStack);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
async #resolveBindingAsync<const Value>(
|
|
862
|
+
binding: Binding<Value>,
|
|
863
|
+
options: ResolveOptions | undefined,
|
|
864
|
+
resolutionPath: Array<string>,
|
|
865
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
866
|
+
): Promise<Value> {
|
|
867
|
+
if (
|
|
868
|
+
binding.kind === "constant" &&
|
|
869
|
+
binding.onActivation === undefined &&
|
|
870
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)
|
|
871
|
+
) {
|
|
872
|
+
return binding.value;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
876
|
+
|
|
877
|
+
// Singleton cache
|
|
878
|
+
if (scope === "singleton") {
|
|
879
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
880
|
+
return binding.instance as Value;
|
|
881
|
+
}
|
|
882
|
+
// In-flight dedup
|
|
883
|
+
const inflight = this.#scope.getInflight(binding.id);
|
|
884
|
+
if (inflight !== undefined) {
|
|
885
|
+
return inflight as Promise<Value>;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Scoped cache
|
|
890
|
+
if (scope === "scoped") {
|
|
891
|
+
if (!this.#scope.isChild) {
|
|
892
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
893
|
+
}
|
|
894
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
895
|
+
return this.#scope.getScoped<Value>(binding.id);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const frame = this.#getResolutionFrame(binding);
|
|
900
|
+
const frameName = frame.tokenName;
|
|
901
|
+
const resolutionSet = enterResolutionPath(resolutionPath, frameName, false);
|
|
902
|
+
resolutionStack.push(frame);
|
|
903
|
+
const needsActivation = this.#activation.needsActivation(binding);
|
|
904
|
+
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
905
|
+
const resolutionCtx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
906
|
+
try {
|
|
907
|
+
if (binding.kind === "dynamic-async") {
|
|
908
|
+
return await binding.factory(resolutionCtx);
|
|
909
|
+
}
|
|
910
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
911
|
+
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
912
|
+
} finally {
|
|
913
|
+
resolutionStack.pop();
|
|
914
|
+
resolutionPath.pop();
|
|
915
|
+
resolutionSet?.delete(frameName);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
920
|
+
const resolutionCtx = needsResolutionContext
|
|
921
|
+
? new DefaultResolutionContext(this, resolutionPath, resolutionStack, options)
|
|
922
|
+
: undefined;
|
|
923
|
+
|
|
924
|
+
try {
|
|
925
|
+
if (scope === "singleton") {
|
|
926
|
+
const createSingletonPromise = async (): Promise<Value> => {
|
|
927
|
+
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
928
|
+
|
|
929
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
930
|
+
const activated = shouldActivate
|
|
931
|
+
? await this.#lifecycle.runActivation(
|
|
932
|
+
resolutionCtx as DefaultResolutionContext,
|
|
933
|
+
binding,
|
|
934
|
+
instance,
|
|
935
|
+
this.#metadataReader,
|
|
936
|
+
)
|
|
937
|
+
: instance;
|
|
938
|
+
|
|
939
|
+
this.#scope.setSingleton(binding, activated);
|
|
940
|
+
binding.instance = activated;
|
|
941
|
+
this.#scope.clearInflight(binding.id);
|
|
942
|
+
return activated;
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
const singletonPromise = createSingletonPromise().catch((err: unknown) => {
|
|
946
|
+
this.#scope.clearInflight(binding.id);
|
|
947
|
+
throw err;
|
|
948
|
+
});
|
|
949
|
+
this.#scope.setInflight(binding.id, singletonPromise as Promise<unknown>);
|
|
950
|
+
return await singletonPromise;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
954
|
+
|
|
955
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
956
|
+
const activated = shouldActivate
|
|
957
|
+
? await this.#lifecycle.runActivation(
|
|
958
|
+
resolutionCtx as DefaultResolutionContext,
|
|
959
|
+
binding,
|
|
960
|
+
instance,
|
|
961
|
+
this.#metadataReader,
|
|
962
|
+
)
|
|
963
|
+
: instance;
|
|
964
|
+
|
|
965
|
+
if (scope === "scoped") {
|
|
966
|
+
this.#scope.setScoped(binding.id, activated);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
return activated;
|
|
970
|
+
} finally {
|
|
971
|
+
resolutionStack.pop();
|
|
972
|
+
resolutionPath.pop();
|
|
973
|
+
resolutionSet?.delete(frameName);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async #instantiateAsync<const Value>(
|
|
978
|
+
binding: Binding<Value>,
|
|
979
|
+
ctx: DefaultResolutionContext | undefined,
|
|
980
|
+
resolutionPath: Array<string>,
|
|
981
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
982
|
+
): Promise<Value> {
|
|
983
|
+
switch (binding.kind) {
|
|
984
|
+
case "constant":
|
|
985
|
+
return binding.value;
|
|
986
|
+
|
|
987
|
+
case "dynamic": {
|
|
988
|
+
if (ctx === undefined) {
|
|
989
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
990
|
+
}
|
|
991
|
+
const factoryResult = binding.factory(ctx);
|
|
992
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
case "dynamic-async":
|
|
996
|
+
if (ctx === undefined) {
|
|
997
|
+
throw new InternalError("dynamic-async binding requires resolution context");
|
|
998
|
+
}
|
|
999
|
+
return binding.factory(ctx);
|
|
1000
|
+
|
|
1001
|
+
case "class": {
|
|
1002
|
+
const deps = await this.#resolveClassDepsAsync(binding.target, resolutionPath, resolutionStack);
|
|
1003
|
+
const instance = this.#classes.instantiate(binding.target, deps);
|
|
1004
|
+
return instance as Value;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
case "resolved": {
|
|
1008
|
+
if (ctx === undefined) {
|
|
1009
|
+
throw new InternalError("resolved binding requires resolution context");
|
|
1010
|
+
}
|
|
1011
|
+
const deps = await this.#resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
1012
|
+
const factoryResult = binding.factory(...deps);
|
|
1013
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
case "resolved-async": {
|
|
1017
|
+
const deps = await this.#resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
1018
|
+
return binding.factory(...deps);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
case "alias":
|
|
1022
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
async #resolveClassDepsAsync(
|
|
1027
|
+
target: Constructor,
|
|
1028
|
+
resolutionPath: Array<string>,
|
|
1029
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1030
|
+
): Promise<Array<unknown>> {
|
|
1031
|
+
const meta = this.#classes.constructorMetadata(target);
|
|
1032
|
+
if (meta === undefined) {
|
|
1033
|
+
if (target.length === 0) {
|
|
1034
|
+
return [];
|
|
1035
|
+
}
|
|
1036
|
+
throw new MissingMetadataError(target.name);
|
|
1037
|
+
}
|
|
1038
|
+
if (meta.params.length === 0) {
|
|
1039
|
+
return [];
|
|
1040
|
+
}
|
|
1041
|
+
if (meta.params.length === 1) {
|
|
1042
|
+
const param = meta.params[0]!;
|
|
1043
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
1044
|
+
if (param.multi) {
|
|
1045
|
+
return [await this.resolveAllAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
1046
|
+
}
|
|
1047
|
+
if (param.optional) {
|
|
1048
|
+
return [await this.resolveOptionalAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
1049
|
+
}
|
|
1050
|
+
if (paramOptions === undefined) {
|
|
1051
|
+
return [await this.resolveAsyncFromContext(param.token, resolutionPath, resolutionStack)];
|
|
1052
|
+
}
|
|
1053
|
+
return [await this.resolveAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
1054
|
+
}
|
|
1055
|
+
const pending = new Array<Promise<unknown>>(meta.params.length);
|
|
1056
|
+
const shouldCloneContext = meta.params.length > 1;
|
|
1057
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
1058
|
+
const param = meta.params[index]!;
|
|
1059
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
1060
|
+
if (param.multi) {
|
|
1061
|
+
pending[index] = this.resolveAllAsync(
|
|
1062
|
+
param.token,
|
|
1063
|
+
paramOptions,
|
|
1064
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1065
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1066
|
+
);
|
|
1067
|
+
} else if (param.optional) {
|
|
1068
|
+
pending[index] = this.resolveOptionalAsync(
|
|
1069
|
+
param.token,
|
|
1070
|
+
paramOptions,
|
|
1071
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1072
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1073
|
+
);
|
|
1074
|
+
} else {
|
|
1075
|
+
pending[index] =
|
|
1076
|
+
paramOptions === undefined
|
|
1077
|
+
? this.resolveAsyncFromContext(
|
|
1078
|
+
param.token,
|
|
1079
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1080
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1081
|
+
)
|
|
1082
|
+
: this.resolveAsync(
|
|
1083
|
+
param.token,
|
|
1084
|
+
paramOptions,
|
|
1085
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1086
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return Promise.all(pending);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
async #resolveDescriptorDepsAsync(
|
|
1094
|
+
deps: ReadonlyArray<InjectionDescriptor>,
|
|
1095
|
+
resolutionPath: Array<string>,
|
|
1096
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1097
|
+
): Promise<Array<unknown>> {
|
|
1098
|
+
const pending = new Array<Promise<unknown>>(deps.length);
|
|
1099
|
+
const shouldCloneContext = deps.length > 1;
|
|
1100
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
1101
|
+
const dep = deps[index]!;
|
|
1102
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
1103
|
+
if (dep.multi) {
|
|
1104
|
+
pending[index] = this.resolveAllAsync(
|
|
1105
|
+
dep.token as Token<unknown> | Constructor,
|
|
1106
|
+
depOptions,
|
|
1107
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1108
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1109
|
+
);
|
|
1110
|
+
} else if (dep.optional) {
|
|
1111
|
+
pending[index] = this.resolveOptionalAsync(
|
|
1112
|
+
dep.token as Token<unknown> | Constructor,
|
|
1113
|
+
depOptions,
|
|
1114
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1115
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1116
|
+
);
|
|
1117
|
+
} else {
|
|
1118
|
+
pending[index] =
|
|
1119
|
+
depOptions === undefined
|
|
1120
|
+
? this.resolveAsyncFromContext(
|
|
1121
|
+
dep.token as Token<unknown> | Constructor,
|
|
1122
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1123
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1124
|
+
)
|
|
1125
|
+
: this.resolveAsync(
|
|
1126
|
+
dep.token as Token<unknown> | Constructor,
|
|
1127
|
+
depOptions,
|
|
1128
|
+
shouldCloneContext ? [...resolutionPath] : resolutionPath,
|
|
1129
|
+
shouldCloneContext ? [...resolutionStack] : resolutionStack,
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
return Promise.all(pending);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
async resolveOptionalAsync<const Value>(
|
|
1137
|
+
token: Token<Value> | Constructor<Value>,
|
|
1138
|
+
options: ResolveOptions | undefined,
|
|
1139
|
+
resolutionPath: Array<string>,
|
|
1140
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1141
|
+
): Promise<Value | undefined> {
|
|
1142
|
+
if (this.#findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
1143
|
+
return undefined;
|
|
1144
|
+
}
|
|
1145
|
+
return this.resolveAsync(token, options, resolutionPath, resolutionStack);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
async resolveAllAsync<const Value>(
|
|
1149
|
+
token: Token<Value> | Constructor<Value>,
|
|
1150
|
+
options: ResolveOptions | undefined,
|
|
1151
|
+
resolutionPath: Array<string>,
|
|
1152
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1153
|
+
): Promise<Array<Value>> {
|
|
1154
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
1155
|
+
const namedCandidates = this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
1156
|
+
if (namedCandidates.length === 0) {
|
|
1157
|
+
return [];
|
|
1158
|
+
}
|
|
1159
|
+
const pending = new Array<Promise<Value>>(namedCandidates.length);
|
|
1160
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
1161
|
+
pending[index] = this.#resolveCandidateAsync(
|
|
1162
|
+
namedCandidates[index] as Binding<Value>,
|
|
1163
|
+
options,
|
|
1164
|
+
resolutionPath,
|
|
1165
|
+
resolutionStack,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
return Promise.all(pending);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
1172
|
+
if (allBindings.length === 0) {
|
|
1173
|
+
return [];
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
1177
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
1178
|
+
|
|
1179
|
+
const pending = new Array<Promise<Value>>(candidates.length);
|
|
1180
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
1181
|
+
pending[index] = this.#resolveCandidateAsync(
|
|
1182
|
+
candidates[index] as Binding<Value>,
|
|
1183
|
+
options,
|
|
1184
|
+
resolutionPath,
|
|
1185
|
+
resolutionStack,
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
return Promise.all(pending);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
1192
|
+
|
|
1193
|
+
#getAllBindingsFromChain(token: Token<unknown> | Constructor): ReadonlyArray<Binding> {
|
|
1194
|
+
const ownBindings = this.#registry.getAll(token);
|
|
1195
|
+
if (this.#parent === undefined) {
|
|
1196
|
+
return ownBindings;
|
|
1197
|
+
}
|
|
1198
|
+
const result: Array<Binding> = [...ownBindings];
|
|
1199
|
+
let current: DependencyResolver | undefined = this.#parent;
|
|
1200
|
+
while (current !== undefined) {
|
|
1201
|
+
const own = current.#registry.getAll(token);
|
|
1202
|
+
if (own.length > 0) {
|
|
1203
|
+
result.push(...own);
|
|
1204
|
+
}
|
|
1205
|
+
current = current.#parent;
|
|
1206
|
+
}
|
|
1207
|
+
return result;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
#getSimpleNamedBindingsFromChain(token: Token<unknown> | Constructor, name: string): Array<Binding> {
|
|
1211
|
+
const ownBinding = this.#registry.getSimpleNamed(token, name);
|
|
1212
|
+
if (this.#parent === undefined) {
|
|
1213
|
+
return ownBinding !== undefined ? [ownBinding] : [];
|
|
1214
|
+
}
|
|
1215
|
+
const result: Array<Binding> = [];
|
|
1216
|
+
if (ownBinding !== undefined) {
|
|
1217
|
+
result.push(ownBinding);
|
|
1218
|
+
}
|
|
1219
|
+
let current: DependencyResolver | undefined = this.#parent;
|
|
1220
|
+
while (current !== undefined) {
|
|
1221
|
+
const binding = current.#registry.getSimpleNamed(token, name);
|
|
1222
|
+
if (binding !== undefined) {
|
|
1223
|
+
result.push(binding);
|
|
1224
|
+
}
|
|
1225
|
+
current = current.#parent;
|
|
1226
|
+
}
|
|
1227
|
+
return result;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
#getAvailableSlots(token: Token<unknown> | Constructor): Array<string> {
|
|
1231
|
+
return this.#registry.availableSlotStrings(token);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
#makeConstraintContext(
|
|
1235
|
+
resolutionPath: Array<string>,
|
|
1236
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1237
|
+
options: ResolveOptions | undefined,
|
|
1238
|
+
): ConstraintContext {
|
|
1239
|
+
if (options === undefined && resolutionPath.length === 0 && resolutionStack.length === 0) {
|
|
1240
|
+
return ROOT_CONSTRAINT_CONTEXT;
|
|
1241
|
+
}
|
|
1242
|
+
const parent = resolutionStack.at(-1);
|
|
1243
|
+
const ancestors = resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [];
|
|
1244
|
+
return {
|
|
1245
|
+
resolutionPath,
|
|
1246
|
+
resolutionStack,
|
|
1247
|
+
parent,
|
|
1248
|
+
ancestors,
|
|
1249
|
+
currentResolveOptions: options,
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
#matchesBindingFast(
|
|
1254
|
+
binding: Binding,
|
|
1255
|
+
options: ResolveOptions | undefined,
|
|
1256
|
+
resolutionPath: Array<string>,
|
|
1257
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1258
|
+
): boolean {
|
|
1259
|
+
if (!this.#matchesSlotFast(binding.slot, options)) {
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
if (binding.predicate === undefined) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
1266
|
+
return binding.predicate(ctx);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
#matchesSlotFast(slot: BindingSlot, options: ResolveOptions | undefined): boolean {
|
|
1270
|
+
const requestedName = options?.name;
|
|
1271
|
+
const requestedTags = options?.tags;
|
|
1272
|
+
const singleRequestedTag = options?.tag;
|
|
1273
|
+
const hasRequestedTags = (requestedTags?.length ?? 0) > 0 || singleRequestedTag !== undefined;
|
|
1274
|
+
|
|
1275
|
+
if (slot.name !== undefined) {
|
|
1276
|
+
if (requestedName === undefined || slot.name !== requestedName) {
|
|
1277
|
+
return false;
|
|
1278
|
+
}
|
|
1279
|
+
} else if (requestedName !== undefined) {
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (slot.tags.length > 0) {
|
|
1284
|
+
if (!hasRequestedTags) {
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
for (const [tagKey, tagValue] of slot.tags) {
|
|
1288
|
+
if (!this.#matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag)) {
|
|
1289
|
+
return false;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
} else if (hasRequestedTags) {
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
#getTokenName(token: Token<unknown> | Constructor): string {
|
|
1300
|
+
return tokenName(token);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
#matchesRequestedTag(
|
|
1304
|
+
tagKey: string,
|
|
1305
|
+
tagValue: unknown,
|
|
1306
|
+
requestedTags: ReadonlyArray<BindingTag> | undefined,
|
|
1307
|
+
singleRequestedTag: BindingTag | undefined,
|
|
1308
|
+
): boolean {
|
|
1309
|
+
if (
|
|
1310
|
+
singleRequestedTag !== undefined &&
|
|
1311
|
+
singleRequestedTag[0] === tagKey &&
|
|
1312
|
+
Object.is(singleRequestedTag[1], tagValue)
|
|
1313
|
+
) {
|
|
1314
|
+
return true;
|
|
1315
|
+
}
|
|
1316
|
+
if (requestedTags === undefined || requestedTags.length === 0) {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
for (let index = 0; index < requestedTags.length; index += 1) {
|
|
1320
|
+
const requestedTag = requestedTags[index]!;
|
|
1321
|
+
if (requestedTag[0] === tagKey && Object.is(requestedTag[1], tagValue)) {
|
|
1322
|
+
return true;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
#resolveTransientDynamicSyncFromContext<const Value>(
|
|
1329
|
+
binding: Binding<Value> & { kind: "dynamic" },
|
|
1330
|
+
resolutionPath: Array<string>,
|
|
1331
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1332
|
+
): Value {
|
|
1333
|
+
// One lane at every depth: `binding.inFlight` is O(1), so there is nothing to escape.
|
|
1334
|
+
const frame = this.#getResolutionFrame(binding);
|
|
1335
|
+
const tokenDisplayName = frame.tokenName;
|
|
1336
|
+
if (binding.inFlight) {
|
|
1337
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
1338
|
+
}
|
|
1339
|
+
binding.inFlight = true;
|
|
1340
|
+
resolutionPath.push(tokenDisplayName);
|
|
1341
|
+
resolutionStack.push(frame);
|
|
1342
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
1343
|
+
try {
|
|
1344
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
1345
|
+
if (dynamicResult instanceof Promise) {
|
|
1346
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
1347
|
+
}
|
|
1348
|
+
return dynamicResult;
|
|
1349
|
+
} finally {
|
|
1350
|
+
resolutionStack.pop();
|
|
1351
|
+
resolutionPath.pop();
|
|
1352
|
+
binding.inFlight = false;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Deliberately not `async`: that would allocate a state machine and a promise per level.
|
|
1357
|
+
#resolveTransientDynamicAsyncFromContext<const Value>(
|
|
1358
|
+
binding: Binding<Value> & { kind: "dynamic" | "dynamic-async" },
|
|
1359
|
+
resolutionPath: Array<string>,
|
|
1360
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1361
|
+
callerContext?: DefaultResolutionContext,
|
|
1362
|
+
): Promise<Value> {
|
|
1363
|
+
// Path-scoped cycle detection, because async chains interleave — see ARCHITECTURE.md.
|
|
1364
|
+
const pool = this.#asyncChainContextPool;
|
|
1365
|
+
const frame = this.#getResolutionFrame(binding);
|
|
1366
|
+
const tokenDisplayName = frame.tokenName;
|
|
1367
|
+
try {
|
|
1368
|
+
enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
1369
|
+
} catch (cycleError) {
|
|
1370
|
+
// This method is not `async`; keep failures as rejections rather than sync throws.
|
|
1371
|
+
return Promise.reject(cycleError);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// An inner level reuses the context its caller passed down; only a chain's first level
|
|
1375
|
+
// borrows from the pool. Pooling is load-bearing here — see ARCHITECTURE.md.
|
|
1376
|
+
const ctx =
|
|
1377
|
+
callerContext !== undefined && callerContext.owner === this
|
|
1378
|
+
? callerContext
|
|
1379
|
+
: this.#acquireAsyncChainContext(resolutionPath, resolutionStack);
|
|
1380
|
+
ctx.chainLevels += 1;
|
|
1381
|
+
|
|
1382
|
+
// Invoke the factory synchronously to get its Promise (or a resolved value for "dynamic").
|
|
1383
|
+
let factoryPromise: Promise<Value>;
|
|
1384
|
+
try {
|
|
1385
|
+
if (binding.kind === "dynamic-async") {
|
|
1386
|
+
factoryPromise = binding.factory(ctx);
|
|
1387
|
+
} else {
|
|
1388
|
+
const factoryResult = binding.factory(ctx);
|
|
1389
|
+
factoryPromise =
|
|
1390
|
+
factoryResult instanceof Promise ? (factoryResult as Promise<Value>) : Promise.resolve(factoryResult);
|
|
1391
|
+
}
|
|
1392
|
+
} catch (factoryError) {
|
|
1393
|
+
// Synchronous throw from the factory (rare) — clean up immediately.
|
|
1394
|
+
exitResolutionPath(resolutionPath);
|
|
1395
|
+
ctx.chainLevels -= 1;
|
|
1396
|
+
if (ctx.chainLevels === 0) {
|
|
1397
|
+
pool.push(ctx);
|
|
1398
|
+
}
|
|
1399
|
+
return Promise.reject(factoryError);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// A side listener, not a derived chain: FIFO puts it before the awaiting caller resumes.
|
|
1403
|
+
// It marks a rejection as handled, so callers must await or `.catch` the returned promise.
|
|
1404
|
+
const settle =
|
|
1405
|
+
ctx.chainSettle ??
|
|
1406
|
+
(ctx.chainSettle = (): void => {
|
|
1407
|
+
exitResolutionPath(resolutionPath);
|
|
1408
|
+
ctx.chainLevels -= 1;
|
|
1409
|
+
if (ctx.chainLevels === 0) {
|
|
1410
|
+
pool.push(ctx);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
factoryPromise.then(settle, settle);
|
|
1414
|
+
return factoryPromise;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
#acquireAsyncChainContext(
|
|
1418
|
+
resolutionPath: Array<string>,
|
|
1419
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1420
|
+
): DefaultResolutionContext {
|
|
1421
|
+
const pooled = this.#asyncChainContextPool.pop();
|
|
1422
|
+
if (pooled === undefined) {
|
|
1423
|
+
return new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1424
|
+
}
|
|
1425
|
+
pooled.reset(this, resolutionPath, resolutionStack, undefined);
|
|
1426
|
+
return pooled;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
#resolveCandidateSync<const Value>(
|
|
1430
|
+
binding: Binding<Value>,
|
|
1431
|
+
options: ResolveOptions | undefined,
|
|
1432
|
+
resolutionPath: Array<string>,
|
|
1433
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1434
|
+
): Value {
|
|
1435
|
+
if (
|
|
1436
|
+
binding.kind === "constant" &&
|
|
1437
|
+
binding.onActivation === undefined &&
|
|
1438
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)
|
|
1439
|
+
) {
|
|
1440
|
+
return binding.value;
|
|
1441
|
+
}
|
|
1442
|
+
if (binding.kind === "alias") {
|
|
1443
|
+
return this.resolve(binding.target, options, resolutionPath, resolutionStack);
|
|
1444
|
+
}
|
|
1445
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
1446
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
1447
|
+
return binding.instance as Value;
|
|
1448
|
+
}
|
|
1449
|
+
if (scope === "scoped") {
|
|
1450
|
+
if (!this.#scope.isChild) {
|
|
1451
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
1452
|
+
}
|
|
1453
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
1454
|
+
return this.#scope.getScoped<Value>(binding.id);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return this.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
#resolveCandidateAsync<const Value>(
|
|
1461
|
+
binding: Binding<Value>,
|
|
1462
|
+
options: ResolveOptions | undefined,
|
|
1463
|
+
resolutionPath: Array<string>,
|
|
1464
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1465
|
+
): Promise<Value> {
|
|
1466
|
+
if (
|
|
1467
|
+
binding.kind === "constant" &&
|
|
1468
|
+
binding.onActivation === undefined &&
|
|
1469
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)
|
|
1470
|
+
) {
|
|
1471
|
+
return Promise.resolve(binding.value);
|
|
1472
|
+
}
|
|
1473
|
+
const isolatedPath = [...resolutionPath];
|
|
1474
|
+
const isolatedStack = [...resolutionStack];
|
|
1475
|
+
if (binding.kind === "alias") {
|
|
1476
|
+
return this.resolveAsync(binding.target, options, isolatedPath, isolatedStack);
|
|
1477
|
+
}
|
|
1478
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
1479
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
1480
|
+
return Promise.resolve(binding.instance as Value);
|
|
1481
|
+
}
|
|
1482
|
+
if (scope === "scoped") {
|
|
1483
|
+
if (!this.#scope.isChild) {
|
|
1484
|
+
return Promise.reject(new MissingScopeContextError(this.#getTokenName(binding.token)));
|
|
1485
|
+
}
|
|
1486
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
1487
|
+
return Promise.resolve(this.#scope.getScoped<Value>(binding.id));
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return this.#resolveBindingAsync(binding, options, isolatedPath, isolatedStack);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
#getResolutionFrame<const Value>(binding: Binding<Value>): ResolutionFrame {
|
|
1494
|
+
// Memoized on the binding rather than in a per-resolver Map: the frame derives only from
|
|
1495
|
+
// immutable binding fields, so it is identical for every resolver, and a field read beats a
|
|
1496
|
+
// Map lookup on every hop of a chain.
|
|
1497
|
+
const existing = binding.frame;
|
|
1498
|
+
if (existing !== undefined) {
|
|
1499
|
+
return existing;
|
|
1500
|
+
}
|
|
1501
|
+
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
1502
|
+
const frame = buildResolutionFrame(tokenName(binding.token), scope, binding.id, binding.kind, binding.slot);
|
|
1503
|
+
binding.frame = frame;
|
|
1504
|
+
return frame;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
#requiresResolutionContext<const Value>(binding: Binding<Value>): boolean {
|
|
1508
|
+
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
#acquireSyncResolutionContext(
|
|
1512
|
+
resolutionPath: Array<string>,
|
|
1513
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1514
|
+
options: ResolveOptions | undefined,
|
|
1515
|
+
): DefaultResolutionContext {
|
|
1516
|
+
const depth = resolutionStack.length;
|
|
1517
|
+
const existing = this.#syncResolutionContextPool[depth];
|
|
1518
|
+
if (existing !== undefined) {
|
|
1519
|
+
existing.reset(this, resolutionPath, resolutionStack, options);
|
|
1520
|
+
return existing;
|
|
1521
|
+
}
|
|
1522
|
+
const created = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
1523
|
+
this.#syncResolutionContextPool[depth] = created;
|
|
1524
|
+
return created;
|
|
1525
|
+
}
|
|
1526
|
+
}
|