@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,1108 @@
|
|
|
1
|
+
import { NO_INSTANCE } from "#/binding";
|
|
2
|
+
import { AsyncActivationError, AsyncResolutionError, CircularDependencyError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#/errors";
|
|
3
|
+
import { ActivationNeedCache } from "#/resolution/activation-need";
|
|
4
|
+
import { BindingLookupCache } from "#/resolution/binding-lookup-cache";
|
|
5
|
+
import { selectAllBindings, selectBinding } from "#/resolution/binding-select";
|
|
6
|
+
import { ClassIntrospector } from "#/resolution/class-introspector";
|
|
7
|
+
import { buildResolutionFrame, DefaultResolutionContext } from "#/resolution/environment";
|
|
8
|
+
import { InstantiationPlanCompiler, PLAN_RETRY } from "#/resolution/instantiation-plan";
|
|
9
|
+
import { enterResolutionPath, exitResolutionPath } from "#/resolution/resolution-path";
|
|
10
|
+
import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
11
|
+
import { tokenName } from "#/token";
|
|
12
|
+
const EMPTY_STRING_LIST = [];
|
|
13
|
+
const EMPTY_FRAME_LIST = [];
|
|
14
|
+
const ROOT_CONSTRAINT_CONTEXT = {
|
|
15
|
+
resolutionPath: EMPTY_STRING_LIST,
|
|
16
|
+
resolutionStack: EMPTY_FRAME_LIST,
|
|
17
|
+
parent: undefined,
|
|
18
|
+
ancestors: EMPTY_FRAME_LIST,
|
|
19
|
+
currentResolveOptions: undefined,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @since 0.3.16-canary.0
|
|
23
|
+
*/
|
|
24
|
+
export class DependencyResolver {
|
|
25
|
+
#syncResolutionContextPool = [];
|
|
26
|
+
// Pure allocation pool: a chain's own context is threaded through the call, so nothing here
|
|
27
|
+
// identifies a chain.
|
|
28
|
+
#asyncChainContextPool = [];
|
|
29
|
+
// Compiled plans; `null` marks a binding as unplannable under the current cache versions.
|
|
30
|
+
#classPlanByBindingId = new Map();
|
|
31
|
+
#classPlanRegistryVersion = -1;
|
|
32
|
+
#classPlanActivationVersion = -1;
|
|
33
|
+
#registry;
|
|
34
|
+
#scope;
|
|
35
|
+
#lifecycle;
|
|
36
|
+
#metadataReader;
|
|
37
|
+
#parent;
|
|
38
|
+
#lookup;
|
|
39
|
+
#classes;
|
|
40
|
+
#activation;
|
|
41
|
+
constructor(registry, scope, lifecycle, metadataReader, container, parent) {
|
|
42
|
+
this.#registry = registry;
|
|
43
|
+
this.#scope = scope;
|
|
44
|
+
this.#lifecycle = lifecycle;
|
|
45
|
+
this.#metadataReader = metadataReader;
|
|
46
|
+
this.#parent = parent;
|
|
47
|
+
this.#lookup = new BindingLookupCache(registry, this, parent === undefined ? undefined : parent.#lookup);
|
|
48
|
+
this.#classes = new ClassIntrospector(metadataReader, container);
|
|
49
|
+
this.#activation = new ActivationNeedCache(lifecycle, this.#classes);
|
|
50
|
+
}
|
|
51
|
+
/** Structural counts for {@link RESOLUTION_DIAGNOSTICS}; see `resolution/diagnostics.ts`. */
|
|
52
|
+
describeCaches() {
|
|
53
|
+
let compiledPlanCount = 0;
|
|
54
|
+
for (const plan of this.#classPlanByBindingId.values()) {
|
|
55
|
+
if (plan !== null) {
|
|
56
|
+
compiledPlanCount += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
asyncContextPoolSize: this.#asyncChainContextPool.length,
|
|
61
|
+
compiledPlanCount,
|
|
62
|
+
syncContextPoolSize: this.#syncResolutionContextPool.length,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ── Binding lookup ─────────────────────────────────────────────────────────
|
|
66
|
+
#findBinding(token, options, resolutionPath, resolutionStack) {
|
|
67
|
+
if (options === undefined) {
|
|
68
|
+
const fastDefaultBinding = this.#registry.getFastDefault(token);
|
|
69
|
+
if (fastDefaultBinding !== undefined) {
|
|
70
|
+
return { binding: fastDefaultBinding, owner: this };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
74
|
+
const namedBinding = this.#registry.getSimpleNamed(token, options.name);
|
|
75
|
+
if (namedBinding !== undefined &&
|
|
76
|
+
this.#matchesBindingFast(namedBinding, options, resolutionPath, resolutionStack)) {
|
|
77
|
+
return { binding: namedBinding, owner: this };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (options !== undefined &&
|
|
81
|
+
options.name === undefined &&
|
|
82
|
+
options.tag === undefined &&
|
|
83
|
+
(options.tags?.length ?? 0) === 1) {
|
|
84
|
+
const [tagKey, tagValue] = options.tags[0];
|
|
85
|
+
const tagged = this.#registry.getSimpleTagged(token, tagKey, tagValue);
|
|
86
|
+
if (tagged !== undefined) {
|
|
87
|
+
return { binding: tagged, owner: this };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const bindings = this.#registry.getAll(token);
|
|
91
|
+
if (bindings.length > 0) {
|
|
92
|
+
if (bindings.length === 1) {
|
|
93
|
+
const onlyBinding = bindings[0];
|
|
94
|
+
const isDefaultSlot = onlyBinding.slot.name === undefined && onlyBinding.slot.tags.length === 0;
|
|
95
|
+
if (options === undefined && isDefaultSlot && onlyBinding.predicate === undefined) {
|
|
96
|
+
return { binding: onlyBinding, owner: this };
|
|
97
|
+
}
|
|
98
|
+
if (this.#matchesBindingFast(onlyBinding, options, resolutionPath, resolutionStack)) {
|
|
99
|
+
return { binding: onlyBinding, owner: this };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
103
|
+
const binding = selectBinding(bindings, options, ctx, this.#getTokenName(token));
|
|
104
|
+
if (binding !== undefined) {
|
|
105
|
+
return { binding, owner: this };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (this.#parent !== undefined) {
|
|
109
|
+
return this.#parent.#findBinding(token, options, resolutionPath, resolutionStack);
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Binding lookup aligned with `resolve` — used by `Container.validate` without instantiating.
|
|
115
|
+
*/
|
|
116
|
+
peekBindingForValidate(token, options) {
|
|
117
|
+
return this.#findBinding(token, options, [], []);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Mirrors {@link DependencyResolver.resolveAll} candidate selection only (no instantiation).
|
|
121
|
+
*/
|
|
122
|
+
peekCandidateBindingsForValidate(token, options) {
|
|
123
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
124
|
+
return this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
125
|
+
}
|
|
126
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
127
|
+
if (allBindings.length === 0) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const ctx = this.#makeConstraintContext([], [], options);
|
|
131
|
+
return selectAllBindings(allBindings, options, ctx);
|
|
132
|
+
}
|
|
133
|
+
// ── Sync resolve ───────────────────────────────────────────────────────────
|
|
134
|
+
resolveFromContext(token, resolutionPath, resolutionStack) {
|
|
135
|
+
// Hot lane: own-registry fast default. Fall back to the chain-versioned memo
|
|
136
|
+
// (parent-chain walk + alias folding) only on miss or alias.
|
|
137
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
138
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
139
|
+
return this.#resolveDefaultEntry(fastBinding, this, resolutionPath, resolutionStack);
|
|
140
|
+
}
|
|
141
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
142
|
+
if (entry === null) {
|
|
143
|
+
return this.resolve(token, undefined, resolutionPath, resolutionStack);
|
|
144
|
+
}
|
|
145
|
+
return this.#resolveDefaultEntry(entry.binding, entry.owner, resolutionPath, resolutionStack);
|
|
146
|
+
}
|
|
147
|
+
#resolveDefaultEntry(binding, owner, resolutionPath, resolutionStack) {
|
|
148
|
+
if (binding.kind === "constant" &&
|
|
149
|
+
binding.onActivation === undefined &&
|
|
150
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
151
|
+
return binding.value;
|
|
152
|
+
}
|
|
153
|
+
const scope = binding.scope ?? "transient";
|
|
154
|
+
if (scope === "transient") {
|
|
155
|
+
if (binding.kind === "dynamic") {
|
|
156
|
+
const containerHooks = this.#lifecycle.activationVersion === 0 ? undefined : this.#lifecycle.activationHandlersFor(binding.token);
|
|
157
|
+
if (binding.onActivation === undefined && (containerHooks === undefined || containerHooks.length === 0)) {
|
|
158
|
+
return this.#resolveTransientDynamicSyncFromContext(binding, resolutionPath, resolutionStack);
|
|
159
|
+
}
|
|
160
|
+
return this.#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionPath, resolutionStack);
|
|
161
|
+
}
|
|
162
|
+
// Compiled plans only run at the top level — inner levels keep the runtime cycle guard.
|
|
163
|
+
if ((binding.kind === "class" || binding.kind === "resolved") && resolutionPath.length === 0) {
|
|
164
|
+
const plan = this.#getInstantiationPlan(binding);
|
|
165
|
+
if (plan !== null) {
|
|
166
|
+
return plan();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (scope === "singleton") {
|
|
171
|
+
const cachedSingleton = binding.instance;
|
|
172
|
+
if (cachedSingleton !== NO_INSTANCE) {
|
|
173
|
+
return cachedSingleton;
|
|
174
|
+
}
|
|
175
|
+
if (owner !== this) {
|
|
176
|
+
return owner.#resolveBinding(binding, undefined, resolutionPath, resolutionStack);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
if (!this.#scope.isChild) {
|
|
181
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
182
|
+
}
|
|
183
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
184
|
+
return this.#scope.getScoped(binding.id);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return this.#resolveBinding(binding, undefined, resolutionPath, resolutionStack);
|
|
188
|
+
}
|
|
189
|
+
// Lean lane for an activated transient dynamic binding: same observable behavior as the
|
|
190
|
+
// generic #resolveBinding path (guard, frame, ctx, per-binding then container hooks) with
|
|
191
|
+
// the kind/activation dispatch resolved statically.
|
|
192
|
+
#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionPath, resolutionStack) {
|
|
193
|
+
const frame = this.#getResolutionFrame(binding);
|
|
194
|
+
const tokenDisplayName = frame.tokenName;
|
|
195
|
+
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
196
|
+
resolutionStack.push(frame);
|
|
197
|
+
try {
|
|
198
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
199
|
+
const factoryResult = binding.factory(resolutionCtx);
|
|
200
|
+
if (factoryResult instanceof Promise) {
|
|
201
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
202
|
+
}
|
|
203
|
+
let activated = factoryResult;
|
|
204
|
+
if (binding.onActivation !== undefined) {
|
|
205
|
+
const activationResult = binding.onActivation(resolutionCtx, activated);
|
|
206
|
+
if (activationResult instanceof Promise) {
|
|
207
|
+
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
208
|
+
}
|
|
209
|
+
activated = activationResult;
|
|
210
|
+
}
|
|
211
|
+
if (containerHooks !== undefined) {
|
|
212
|
+
for (let index = 0; index < containerHooks.length; index += 1) {
|
|
213
|
+
const activationResult = containerHooks[index](resolutionCtx, activated);
|
|
214
|
+
if (activationResult instanceof Promise) {
|
|
215
|
+
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
216
|
+
}
|
|
217
|
+
activated = activationResult;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return activated;
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
resolutionStack.pop();
|
|
224
|
+
resolutionPath.pop();
|
|
225
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
#getInstantiationPlan(binding) {
|
|
229
|
+
const registryVersion = this.#lookup.chainVersion();
|
|
230
|
+
const activationVersion = this.#lifecycle.activationVersion;
|
|
231
|
+
if (registryVersion !== this.#classPlanRegistryVersion || activationVersion !== this.#classPlanActivationVersion) {
|
|
232
|
+
this.#classPlanByBindingId.clear();
|
|
233
|
+
this.#classPlanRegistryVersion = registryVersion;
|
|
234
|
+
this.#classPlanActivationVersion = activationVersion;
|
|
235
|
+
}
|
|
236
|
+
const cached = this.#classPlanByBindingId.get(binding.id);
|
|
237
|
+
if (cached !== undefined) {
|
|
238
|
+
return cached;
|
|
239
|
+
}
|
|
240
|
+
const compiled = this.#planCompiler.compile(binding);
|
|
241
|
+
if (compiled === PLAN_RETRY) {
|
|
242
|
+
// Lifecycle metadata not discovered yet — the fallback resolve discovers it; retry then.
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
this.#classPlanByBindingId.set(binding.id, compiled);
|
|
246
|
+
return compiled;
|
|
247
|
+
}
|
|
248
|
+
// Compiler behind #getClassPlan — cold path, so the host indirection costs nothing hot.
|
|
249
|
+
#planCompiler = new InstantiationPlanCompiler({
|
|
250
|
+
hasActivationHandlers: (token) => this.#lifecycle.hasActivationHandlers(token),
|
|
251
|
+
knownPostConstruct: (target) => this.#classes.knownPostConstruct(target),
|
|
252
|
+
needsActiveContainer: (target) => this.#classes.needsActiveContainer(target),
|
|
253
|
+
getConstructorMetadata: (target) => this.#classes.constructorMetadata(target),
|
|
254
|
+
lookupDependencyEntry: (token) => {
|
|
255
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
256
|
+
return entry === null ? null : { binding: entry.binding };
|
|
257
|
+
},
|
|
258
|
+
getResolutionFrame: (binding) => this.#getResolutionFrame(binding),
|
|
259
|
+
// Dispatches exactly as #resolveClassDeps does, so an escaped dep is indistinguishable
|
|
260
|
+
// from the same dep on a fully interpreted resolve.
|
|
261
|
+
resolveEscaped: (token, options, arity, resolutionPath, resolutionStack) => {
|
|
262
|
+
if (arity === "all") {
|
|
263
|
+
return this.resolveAll(token, options, resolutionPath, resolutionStack);
|
|
264
|
+
}
|
|
265
|
+
if (arity === "optional") {
|
|
266
|
+
return this.resolveOptional(token, options, resolutionPath, resolutionStack);
|
|
267
|
+
}
|
|
268
|
+
if (options === undefined) {
|
|
269
|
+
return this.resolveFromContext(token, resolutionPath, resolutionStack);
|
|
270
|
+
}
|
|
271
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
resolve(token, options, resolutionPath, resolutionStack) {
|
|
275
|
+
// Name-only fast lane: memoized lookup, dispatching just the shapes whose
|
|
276
|
+
// semantics involve no resolution context (constants, cached singletons).
|
|
277
|
+
if (options !== undefined &&
|
|
278
|
+
options.name !== undefined &&
|
|
279
|
+
options.tag === undefined &&
|
|
280
|
+
(options.tags === undefined || options.tags.length === 0)) {
|
|
281
|
+
const namedEntry = this.#lookup.namedEntry(token, options.name);
|
|
282
|
+
if (namedEntry !== null) {
|
|
283
|
+
const namedBinding = namedEntry.binding;
|
|
284
|
+
if (namedBinding.kind === "constant" &&
|
|
285
|
+
namedBinding.onActivation === undefined &&
|
|
286
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(namedBinding.token))) {
|
|
287
|
+
return namedBinding.value;
|
|
288
|
+
}
|
|
289
|
+
const namedScope = namedBinding.scope ?? "transient";
|
|
290
|
+
if (namedScope === "singleton") {
|
|
291
|
+
if (namedBinding.instance !== NO_INSTANCE) {
|
|
292
|
+
return namedBinding.instance;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Everything else keeps the full path (context, activation, guards).
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
let currentToken = token;
|
|
299
|
+
let visitedAliasTokens;
|
|
300
|
+
let found = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
301
|
+
// Follow aliases iteratively with exact cycle detection — a revisited alias
|
|
302
|
+
// token throws CircularDependencyError instead of overflowing the call stack.
|
|
303
|
+
while (found !== undefined && found.binding.kind === "alias") {
|
|
304
|
+
const target = found.binding.target;
|
|
305
|
+
visitedAliasTokens ??= new Set([currentToken]);
|
|
306
|
+
if (visitedAliasTokens.has(target)) {
|
|
307
|
+
throw new CircularDependencyError([...visitedAliasTokens, target].map((entry) => tokenName(entry)));
|
|
308
|
+
}
|
|
309
|
+
visitedAliasTokens.add(target);
|
|
310
|
+
currentToken = target;
|
|
311
|
+
found = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
312
|
+
}
|
|
313
|
+
if (found === undefined) {
|
|
314
|
+
const ownBindings = this.#registry.getAll(currentToken);
|
|
315
|
+
if (ownBindings.length > 0) {
|
|
316
|
+
throw new NoMatchingBindingError(this.#getTokenName(currentToken), options ?? {}, this.#getAvailableSlots(currentToken));
|
|
317
|
+
}
|
|
318
|
+
throw new TokenNotBoundError(this.#getTokenName(currentToken));
|
|
319
|
+
}
|
|
320
|
+
const { binding, owner } = found;
|
|
321
|
+
const scope = binding.scope ?? "transient";
|
|
322
|
+
// Singleton from a parent resolver: delegate so the parent caches it correctly
|
|
323
|
+
if (scope === "singleton" && owner !== this) {
|
|
324
|
+
return owner.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
325
|
+
}
|
|
326
|
+
// Scoped/transient (or own singleton): resolve with this resolver's container/scope
|
|
327
|
+
return this.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
328
|
+
}
|
|
329
|
+
#resolveBinding(binding, options, resolutionPath, resolutionStack) {
|
|
330
|
+
if (binding.kind === "constant" &&
|
|
331
|
+
binding.onActivation === undefined &&
|
|
332
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
333
|
+
return binding.value;
|
|
334
|
+
}
|
|
335
|
+
const scope = binding.scope ?? "transient";
|
|
336
|
+
// Singleton cache check
|
|
337
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
338
|
+
return binding.instance;
|
|
339
|
+
}
|
|
340
|
+
// Scoped cache check
|
|
341
|
+
if (scope === "scoped") {
|
|
342
|
+
if (!this.#scope.isChild) {
|
|
343
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
344
|
+
}
|
|
345
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
346
|
+
return this.#scope.getScoped(binding.id);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const frame = this.#getResolutionFrame(binding);
|
|
350
|
+
const tokenDisplayName = frame.tokenName;
|
|
351
|
+
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
352
|
+
resolutionStack.push(frame);
|
|
353
|
+
const needsActivation = this.#activation.needsActivation(binding);
|
|
354
|
+
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
355
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options);
|
|
356
|
+
try {
|
|
357
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
358
|
+
if (dynamicResult instanceof Promise) {
|
|
359
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
360
|
+
}
|
|
361
|
+
resolutionStack.pop();
|
|
362
|
+
resolutionPath.pop();
|
|
363
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
364
|
+
return dynamicResult;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
resolutionStack.pop();
|
|
368
|
+
resolutionPath.pop();
|
|
369
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
375
|
+
const resolutionCtx = needsResolutionContext
|
|
376
|
+
? this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options)
|
|
377
|
+
: undefined;
|
|
378
|
+
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
379
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
380
|
+
const activated = shouldActivate
|
|
381
|
+
? this.#lifecycle.runActivationSync(resolutionCtx, binding, instance, this.#metadataReader)
|
|
382
|
+
: instance;
|
|
383
|
+
// Cache by scope
|
|
384
|
+
if (scope === "singleton") {
|
|
385
|
+
this.#scope.setSingleton(binding, activated);
|
|
386
|
+
}
|
|
387
|
+
else if (scope === "scoped") {
|
|
388
|
+
this.#scope.setScoped(binding.id, activated);
|
|
389
|
+
}
|
|
390
|
+
return activated;
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
resolutionStack.pop();
|
|
394
|
+
resolutionPath.pop();
|
|
395
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
#instantiateSync(binding, ctx, resolutionPath, resolutionStack) {
|
|
399
|
+
switch (binding.kind) {
|
|
400
|
+
case "constant":
|
|
401
|
+
return binding.value;
|
|
402
|
+
case "dynamic": {
|
|
403
|
+
if (ctx === undefined) {
|
|
404
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
405
|
+
}
|
|
406
|
+
const factoryResult = binding.factory(ctx);
|
|
407
|
+
if (factoryResult instanceof Promise) {
|
|
408
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
409
|
+
}
|
|
410
|
+
return factoryResult;
|
|
411
|
+
}
|
|
412
|
+
case "dynamic-async":
|
|
413
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
414
|
+
case "class": {
|
|
415
|
+
const deps = this.#resolveClassDeps(binding.target, resolutionPath, resolutionStack);
|
|
416
|
+
const instance = this.#classes.instantiate(binding.target, deps);
|
|
417
|
+
return instance;
|
|
418
|
+
}
|
|
419
|
+
case "resolved": {
|
|
420
|
+
const deps = this.#resolveDescriptorDeps(binding.deps, resolutionPath, resolutionStack);
|
|
421
|
+
const factoryResult = binding.factory(...deps);
|
|
422
|
+
if (factoryResult instanceof Promise) {
|
|
423
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
424
|
+
}
|
|
425
|
+
return factoryResult;
|
|
426
|
+
}
|
|
427
|
+
case "resolved-async":
|
|
428
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
429
|
+
case "alias":
|
|
430
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
#resolveClassDeps(target, resolutionPath, resolutionStack) {
|
|
434
|
+
const meta = this.#classes.constructorMetadata(target);
|
|
435
|
+
if (meta === undefined) {
|
|
436
|
+
if (target.length === 0) {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
throw new MissingMetadataError(target.name);
|
|
440
|
+
}
|
|
441
|
+
if (meta.params.length === 0) {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
if (meta.params.length === 1) {
|
|
445
|
+
const param = meta.params[0];
|
|
446
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
447
|
+
if (param.multi) {
|
|
448
|
+
return [this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
449
|
+
}
|
|
450
|
+
if (param.optional) {
|
|
451
|
+
return [this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
452
|
+
}
|
|
453
|
+
if (paramOptions === undefined) {
|
|
454
|
+
return [this.resolveFromContext(param.token, resolutionPath, resolutionStack)];
|
|
455
|
+
}
|
|
456
|
+
return [this.resolve(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
457
|
+
}
|
|
458
|
+
const deps = new Array(meta.params.length);
|
|
459
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
460
|
+
const param = meta.params[index];
|
|
461
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
462
|
+
if (param.multi) {
|
|
463
|
+
deps[index] = this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (param.optional) {
|
|
467
|
+
deps[index] = this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
deps[index] =
|
|
471
|
+
paramOptions === undefined
|
|
472
|
+
? this.resolveFromContext(param.token, resolutionPath, resolutionStack)
|
|
473
|
+
: this.resolve(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
474
|
+
}
|
|
475
|
+
return deps;
|
|
476
|
+
}
|
|
477
|
+
#resolveDescriptorDeps(deps, resolutionPath, resolutionStack) {
|
|
478
|
+
const resolved = new Array(deps.length);
|
|
479
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
480
|
+
const dep = deps[index];
|
|
481
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
482
|
+
if (dep.multi) {
|
|
483
|
+
resolved[index] = this.resolveAll(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (dep.optional) {
|
|
487
|
+
resolved[index] = this.resolveOptional(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
resolved[index] =
|
|
491
|
+
depOptions === undefined
|
|
492
|
+
? this.resolveFromContext(dep.token, resolutionPath, resolutionStack)
|
|
493
|
+
: this.resolve(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
494
|
+
}
|
|
495
|
+
return resolved;
|
|
496
|
+
}
|
|
497
|
+
resolveOptional(token, options, resolutionPath, resolutionStack) {
|
|
498
|
+
if (this.#findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
502
|
+
}
|
|
503
|
+
resolveAll(token, options, resolutionPath, resolutionStack) {
|
|
504
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
505
|
+
const namedCandidates = this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
506
|
+
if (namedCandidates.length === 0) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
const resolved = new Array(namedCandidates.length);
|
|
510
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
511
|
+
resolved[index] = this.#resolveCandidateSync(namedCandidates[index], options, resolutionPath, resolutionStack);
|
|
512
|
+
}
|
|
513
|
+
return resolved;
|
|
514
|
+
}
|
|
515
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
516
|
+
if (allBindings.length === 0) {
|
|
517
|
+
return [];
|
|
518
|
+
}
|
|
519
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
520
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
521
|
+
const resolved = new Array(candidates.length);
|
|
522
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
523
|
+
resolved[index] = this.#resolveCandidateSync(candidates[index], options, resolutionPath, resolutionStack);
|
|
524
|
+
}
|
|
525
|
+
return resolved;
|
|
526
|
+
}
|
|
527
|
+
// ── Async resolve ──────────────────────────────────────────────────────────
|
|
528
|
+
resolveAsyncFromContext(token, resolutionPath, resolutionStack, callerContext) {
|
|
529
|
+
// Hot lane: own-registry fast default (async chains resolve sibling dynamic bindings).
|
|
530
|
+
// Fall back to the chain-versioned memo only on miss or alias.
|
|
531
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
532
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
533
|
+
// Inline the dominant chain shape — transient dynamic factory with no activation.
|
|
534
|
+
if ((fastBinding.kind === "dynamic-async" || fastBinding.kind === "dynamic") &&
|
|
535
|
+
fastBinding.scope === "transient" &&
|
|
536
|
+
fastBinding.onActivation === undefined &&
|
|
537
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(fastBinding.token))) {
|
|
538
|
+
return this.#resolveTransientDynamicAsyncFromContext(fastBinding, resolutionPath, resolutionStack, callerContext);
|
|
539
|
+
}
|
|
540
|
+
return this.#resolveAsyncDefaultEntry(fastBinding, this, resolutionPath, resolutionStack, callerContext);
|
|
541
|
+
}
|
|
542
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
543
|
+
if (entry === null) {
|
|
544
|
+
return this.resolveAsync(token, undefined, resolutionPath, resolutionStack);
|
|
545
|
+
}
|
|
546
|
+
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner, resolutionPath, resolutionStack, callerContext);
|
|
547
|
+
}
|
|
548
|
+
#resolveAsyncDefaultEntry(binding, owner, resolutionPath, resolutionStack, callerContext) {
|
|
549
|
+
if (binding.kind === "constant" &&
|
|
550
|
+
binding.onActivation === undefined &&
|
|
551
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
552
|
+
return Promise.resolve(binding.value);
|
|
553
|
+
}
|
|
554
|
+
const scope = binding.scope ?? "transient";
|
|
555
|
+
if (scope === "transient") {
|
|
556
|
+
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") &&
|
|
557
|
+
binding.onActivation === undefined &&
|
|
558
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
559
|
+
return this.#resolveTransientDynamicAsyncFromContext(binding, resolutionPath, resolutionStack, callerContext);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else if (scope === "singleton") {
|
|
563
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
564
|
+
return Promise.resolve(binding.instance);
|
|
565
|
+
}
|
|
566
|
+
if (owner !== this) {
|
|
567
|
+
return owner.#resolveBindingAsync(binding, undefined, resolutionPath, resolutionStack);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
if (!this.#scope.isChild) {
|
|
572
|
+
return Promise.reject(new MissingScopeContextError(this.#getTokenName(binding.token)));
|
|
573
|
+
}
|
|
574
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
575
|
+
return Promise.resolve(this.#scope.getScoped(binding.id));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return this.#resolveBindingAsync(binding, undefined, resolutionPath, resolutionStack);
|
|
579
|
+
}
|
|
580
|
+
async resolveAsync(token, options, resolutionPath, resolutionStack) {
|
|
581
|
+
const found = this.#findBinding(token, options, resolutionPath, resolutionStack);
|
|
582
|
+
if (found === undefined) {
|
|
583
|
+
const ownBindings = this.#registry.getAll(token);
|
|
584
|
+
if (ownBindings.length > 0) {
|
|
585
|
+
throw new NoMatchingBindingError(this.#getTokenName(token), options ?? {}, this.#getAvailableSlots(token));
|
|
586
|
+
}
|
|
587
|
+
throw new TokenNotBoundError(this.#getTokenName(token));
|
|
588
|
+
}
|
|
589
|
+
let currentToken = token;
|
|
590
|
+
let visitedAliasTokens;
|
|
591
|
+
let aliasFollowed = found;
|
|
592
|
+
while (aliasFollowed !== undefined && aliasFollowed.binding.kind === "alias") {
|
|
593
|
+
const target = aliasFollowed.binding.target;
|
|
594
|
+
visitedAliasTokens ??= new Set([currentToken]);
|
|
595
|
+
if (visitedAliasTokens.has(target)) {
|
|
596
|
+
throw new CircularDependencyError([...visitedAliasTokens, target].map((entry) => tokenName(entry)));
|
|
597
|
+
}
|
|
598
|
+
visitedAliasTokens.add(target);
|
|
599
|
+
currentToken = target;
|
|
600
|
+
aliasFollowed = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
601
|
+
}
|
|
602
|
+
if (aliasFollowed === undefined) {
|
|
603
|
+
const ownBindings = this.#registry.getAll(currentToken);
|
|
604
|
+
if (ownBindings.length > 0) {
|
|
605
|
+
throw new NoMatchingBindingError(this.#getTokenName(currentToken), options ?? {}, this.#getAvailableSlots(currentToken));
|
|
606
|
+
}
|
|
607
|
+
throw new TokenNotBoundError(this.#getTokenName(currentToken));
|
|
608
|
+
}
|
|
609
|
+
const { binding, owner } = aliasFollowed;
|
|
610
|
+
const scope = binding.scope ?? "transient";
|
|
611
|
+
if (scope === "singleton" && owner !== this) {
|
|
612
|
+
return owner.#resolveBindingAsync(binding, options, resolutionPath, resolutionStack);
|
|
613
|
+
}
|
|
614
|
+
return this.#resolveBindingAsync(binding, options, resolutionPath, resolutionStack);
|
|
615
|
+
}
|
|
616
|
+
async #resolveBindingAsync(binding, options, resolutionPath, resolutionStack) {
|
|
617
|
+
if (binding.kind === "constant" &&
|
|
618
|
+
binding.onActivation === undefined &&
|
|
619
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
620
|
+
return binding.value;
|
|
621
|
+
}
|
|
622
|
+
const scope = binding.scope ?? "transient";
|
|
623
|
+
// Singleton cache
|
|
624
|
+
if (scope === "singleton") {
|
|
625
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
626
|
+
return binding.instance;
|
|
627
|
+
}
|
|
628
|
+
// In-flight dedup
|
|
629
|
+
const inflight = this.#scope.getInflight(binding.id);
|
|
630
|
+
if (inflight !== undefined) {
|
|
631
|
+
return inflight;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
// Scoped cache
|
|
635
|
+
if (scope === "scoped") {
|
|
636
|
+
if (!this.#scope.isChild) {
|
|
637
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
638
|
+
}
|
|
639
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
640
|
+
return this.#scope.getScoped(binding.id);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const frame = this.#getResolutionFrame(binding);
|
|
644
|
+
const frameName = frame.tokenName;
|
|
645
|
+
const resolutionSet = enterResolutionPath(resolutionPath, frameName, false);
|
|
646
|
+
resolutionStack.push(frame);
|
|
647
|
+
const needsActivation = this.#activation.needsActivation(binding);
|
|
648
|
+
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
649
|
+
const resolutionCtx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
650
|
+
try {
|
|
651
|
+
if (binding.kind === "dynamic-async") {
|
|
652
|
+
return await binding.factory(resolutionCtx);
|
|
653
|
+
}
|
|
654
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
655
|
+
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
656
|
+
}
|
|
657
|
+
finally {
|
|
658
|
+
resolutionStack.pop();
|
|
659
|
+
resolutionPath.pop();
|
|
660
|
+
resolutionSet?.delete(frameName);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
664
|
+
const resolutionCtx = needsResolutionContext
|
|
665
|
+
? new DefaultResolutionContext(this, resolutionPath, resolutionStack, options)
|
|
666
|
+
: undefined;
|
|
667
|
+
try {
|
|
668
|
+
if (scope === "singleton") {
|
|
669
|
+
const createSingletonPromise = async () => {
|
|
670
|
+
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
671
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
672
|
+
const activated = shouldActivate
|
|
673
|
+
? await this.#lifecycle.runActivation(resolutionCtx, binding, instance, this.#metadataReader)
|
|
674
|
+
: instance;
|
|
675
|
+
this.#scope.setSingleton(binding, activated);
|
|
676
|
+
binding.instance = activated;
|
|
677
|
+
this.#scope.clearInflight(binding.id);
|
|
678
|
+
return activated;
|
|
679
|
+
};
|
|
680
|
+
const singletonPromise = createSingletonPromise().catch((err) => {
|
|
681
|
+
this.#scope.clearInflight(binding.id);
|
|
682
|
+
throw err;
|
|
683
|
+
});
|
|
684
|
+
this.#scope.setInflight(binding.id, singletonPromise);
|
|
685
|
+
return await singletonPromise;
|
|
686
|
+
}
|
|
687
|
+
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
688
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
689
|
+
const activated = shouldActivate
|
|
690
|
+
? await this.#lifecycle.runActivation(resolutionCtx, binding, instance, this.#metadataReader)
|
|
691
|
+
: instance;
|
|
692
|
+
if (scope === "scoped") {
|
|
693
|
+
this.#scope.setScoped(binding.id, activated);
|
|
694
|
+
}
|
|
695
|
+
return activated;
|
|
696
|
+
}
|
|
697
|
+
finally {
|
|
698
|
+
resolutionStack.pop();
|
|
699
|
+
resolutionPath.pop();
|
|
700
|
+
resolutionSet?.delete(frameName);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async #instantiateAsync(binding, ctx, resolutionPath, resolutionStack) {
|
|
704
|
+
switch (binding.kind) {
|
|
705
|
+
case "constant":
|
|
706
|
+
return binding.value;
|
|
707
|
+
case "dynamic": {
|
|
708
|
+
if (ctx === undefined) {
|
|
709
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
710
|
+
}
|
|
711
|
+
const factoryResult = binding.factory(ctx);
|
|
712
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
713
|
+
}
|
|
714
|
+
case "dynamic-async":
|
|
715
|
+
if (ctx === undefined) {
|
|
716
|
+
throw new InternalError("dynamic-async binding requires resolution context");
|
|
717
|
+
}
|
|
718
|
+
return binding.factory(ctx);
|
|
719
|
+
case "class": {
|
|
720
|
+
const deps = await this.#resolveClassDepsAsync(binding.target, resolutionPath, resolutionStack);
|
|
721
|
+
const instance = this.#classes.instantiate(binding.target, deps);
|
|
722
|
+
return instance;
|
|
723
|
+
}
|
|
724
|
+
case "resolved": {
|
|
725
|
+
if (ctx === undefined) {
|
|
726
|
+
throw new InternalError("resolved binding requires resolution context");
|
|
727
|
+
}
|
|
728
|
+
const deps = await this.#resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
729
|
+
const factoryResult = binding.factory(...deps);
|
|
730
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
731
|
+
}
|
|
732
|
+
case "resolved-async": {
|
|
733
|
+
const deps = await this.#resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
734
|
+
return binding.factory(...deps);
|
|
735
|
+
}
|
|
736
|
+
case "alias":
|
|
737
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
async #resolveClassDepsAsync(target, resolutionPath, resolutionStack) {
|
|
741
|
+
const meta = this.#classes.constructorMetadata(target);
|
|
742
|
+
if (meta === undefined) {
|
|
743
|
+
if (target.length === 0) {
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
throw new MissingMetadataError(target.name);
|
|
747
|
+
}
|
|
748
|
+
if (meta.params.length === 0) {
|
|
749
|
+
return [];
|
|
750
|
+
}
|
|
751
|
+
if (meta.params.length === 1) {
|
|
752
|
+
const param = meta.params[0];
|
|
753
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
754
|
+
if (param.multi) {
|
|
755
|
+
return [await this.resolveAllAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
756
|
+
}
|
|
757
|
+
if (param.optional) {
|
|
758
|
+
return [await this.resolveOptionalAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
759
|
+
}
|
|
760
|
+
if (paramOptions === undefined) {
|
|
761
|
+
return [await this.resolveAsyncFromContext(param.token, resolutionPath, resolutionStack)];
|
|
762
|
+
}
|
|
763
|
+
return [await this.resolveAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
764
|
+
}
|
|
765
|
+
const pending = new Array(meta.params.length);
|
|
766
|
+
const shouldCloneContext = meta.params.length > 1;
|
|
767
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
768
|
+
const param = meta.params[index];
|
|
769
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
770
|
+
if (param.multi) {
|
|
771
|
+
pending[index] = this.resolveAllAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
772
|
+
}
|
|
773
|
+
else if (param.optional) {
|
|
774
|
+
pending[index] = this.resolveOptionalAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
pending[index] =
|
|
778
|
+
paramOptions === undefined
|
|
779
|
+
? this.resolveAsyncFromContext(param.token, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack)
|
|
780
|
+
: this.resolveAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return Promise.all(pending);
|
|
784
|
+
}
|
|
785
|
+
async #resolveDescriptorDepsAsync(deps, resolutionPath, resolutionStack) {
|
|
786
|
+
const pending = new Array(deps.length);
|
|
787
|
+
const shouldCloneContext = deps.length > 1;
|
|
788
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
789
|
+
const dep = deps[index];
|
|
790
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
791
|
+
if (dep.multi) {
|
|
792
|
+
pending[index] = this.resolveAllAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
793
|
+
}
|
|
794
|
+
else if (dep.optional) {
|
|
795
|
+
pending[index] = this.resolveOptionalAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
pending[index] =
|
|
799
|
+
depOptions === undefined
|
|
800
|
+
? this.resolveAsyncFromContext(dep.token, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack)
|
|
801
|
+
: this.resolveAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return Promise.all(pending);
|
|
805
|
+
}
|
|
806
|
+
async resolveOptionalAsync(token, options, resolutionPath, resolutionStack) {
|
|
807
|
+
if (this.#findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
808
|
+
return undefined;
|
|
809
|
+
}
|
|
810
|
+
return this.resolveAsync(token, options, resolutionPath, resolutionStack);
|
|
811
|
+
}
|
|
812
|
+
async resolveAllAsync(token, options, resolutionPath, resolutionStack) {
|
|
813
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
814
|
+
const namedCandidates = this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
815
|
+
if (namedCandidates.length === 0) {
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
const pending = new Array(namedCandidates.length);
|
|
819
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
820
|
+
pending[index] = this.#resolveCandidateAsync(namedCandidates[index], options, resolutionPath, resolutionStack);
|
|
821
|
+
}
|
|
822
|
+
return Promise.all(pending);
|
|
823
|
+
}
|
|
824
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
825
|
+
if (allBindings.length === 0) {
|
|
826
|
+
return [];
|
|
827
|
+
}
|
|
828
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
829
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
830
|
+
const pending = new Array(candidates.length);
|
|
831
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
832
|
+
pending[index] = this.#resolveCandidateAsync(candidates[index], options, resolutionPath, resolutionStack);
|
|
833
|
+
}
|
|
834
|
+
return Promise.all(pending);
|
|
835
|
+
}
|
|
836
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
837
|
+
#getAllBindingsFromChain(token) {
|
|
838
|
+
const ownBindings = this.#registry.getAll(token);
|
|
839
|
+
if (this.#parent === undefined) {
|
|
840
|
+
return ownBindings;
|
|
841
|
+
}
|
|
842
|
+
const result = [...ownBindings];
|
|
843
|
+
let current = this.#parent;
|
|
844
|
+
while (current !== undefined) {
|
|
845
|
+
const own = current.#registry.getAll(token);
|
|
846
|
+
if (own.length > 0) {
|
|
847
|
+
result.push(...own);
|
|
848
|
+
}
|
|
849
|
+
current = current.#parent;
|
|
850
|
+
}
|
|
851
|
+
return result;
|
|
852
|
+
}
|
|
853
|
+
#getSimpleNamedBindingsFromChain(token, name) {
|
|
854
|
+
const ownBinding = this.#registry.getSimpleNamed(token, name);
|
|
855
|
+
if (this.#parent === undefined) {
|
|
856
|
+
return ownBinding !== undefined ? [ownBinding] : [];
|
|
857
|
+
}
|
|
858
|
+
const result = [];
|
|
859
|
+
if (ownBinding !== undefined) {
|
|
860
|
+
result.push(ownBinding);
|
|
861
|
+
}
|
|
862
|
+
let current = this.#parent;
|
|
863
|
+
while (current !== undefined) {
|
|
864
|
+
const binding = current.#registry.getSimpleNamed(token, name);
|
|
865
|
+
if (binding !== undefined) {
|
|
866
|
+
result.push(binding);
|
|
867
|
+
}
|
|
868
|
+
current = current.#parent;
|
|
869
|
+
}
|
|
870
|
+
return result;
|
|
871
|
+
}
|
|
872
|
+
#getAvailableSlots(token) {
|
|
873
|
+
return this.#registry.availableSlotStrings(token);
|
|
874
|
+
}
|
|
875
|
+
#makeConstraintContext(resolutionPath, resolutionStack, options) {
|
|
876
|
+
if (options === undefined && resolutionPath.length === 0 && resolutionStack.length === 0) {
|
|
877
|
+
return ROOT_CONSTRAINT_CONTEXT;
|
|
878
|
+
}
|
|
879
|
+
const parent = resolutionStack.at(-1);
|
|
880
|
+
const ancestors = resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [];
|
|
881
|
+
return {
|
|
882
|
+
resolutionPath,
|
|
883
|
+
resolutionStack,
|
|
884
|
+
parent,
|
|
885
|
+
ancestors,
|
|
886
|
+
currentResolveOptions: options,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
#matchesBindingFast(binding, options, resolutionPath, resolutionStack) {
|
|
890
|
+
if (!this.#matchesSlotFast(binding.slot, options)) {
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
if (binding.predicate === undefined) {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
897
|
+
return binding.predicate(ctx);
|
|
898
|
+
}
|
|
899
|
+
#matchesSlotFast(slot, options) {
|
|
900
|
+
const requestedName = options?.name;
|
|
901
|
+
const requestedTags = options?.tags;
|
|
902
|
+
const singleRequestedTag = options?.tag;
|
|
903
|
+
const hasRequestedTags = (requestedTags?.length ?? 0) > 0 || singleRequestedTag !== undefined;
|
|
904
|
+
if (slot.name !== undefined) {
|
|
905
|
+
if (requestedName === undefined || slot.name !== requestedName) {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
else if (requestedName !== undefined) {
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
912
|
+
if (slot.tags.length > 0) {
|
|
913
|
+
if (!hasRequestedTags) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
for (const [tagKey, tagValue] of slot.tags) {
|
|
917
|
+
if (!this.#matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag)) {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
else if (hasRequestedTags) {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
return true;
|
|
926
|
+
}
|
|
927
|
+
#getTokenName(token) {
|
|
928
|
+
return tokenName(token);
|
|
929
|
+
}
|
|
930
|
+
#matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag) {
|
|
931
|
+
if (singleRequestedTag !== undefined &&
|
|
932
|
+
singleRequestedTag[0] === tagKey &&
|
|
933
|
+
Object.is(singleRequestedTag[1], tagValue)) {
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
if (requestedTags === undefined || requestedTags.length === 0) {
|
|
937
|
+
return false;
|
|
938
|
+
}
|
|
939
|
+
for (let index = 0; index < requestedTags.length; index += 1) {
|
|
940
|
+
const requestedTag = requestedTags[index];
|
|
941
|
+
if (requestedTag[0] === tagKey && Object.is(requestedTag[1], tagValue)) {
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
#resolveTransientDynamicSyncFromContext(binding, resolutionPath, resolutionStack) {
|
|
948
|
+
// One lane at every depth: `binding.inFlight` is O(1), so there is nothing to escape.
|
|
949
|
+
const frame = this.#getResolutionFrame(binding);
|
|
950
|
+
const tokenDisplayName = frame.tokenName;
|
|
951
|
+
if (binding.inFlight) {
|
|
952
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
953
|
+
}
|
|
954
|
+
binding.inFlight = true;
|
|
955
|
+
resolutionPath.push(tokenDisplayName);
|
|
956
|
+
resolutionStack.push(frame);
|
|
957
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
958
|
+
try {
|
|
959
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
960
|
+
if (dynamicResult instanceof Promise) {
|
|
961
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
962
|
+
}
|
|
963
|
+
return dynamicResult;
|
|
964
|
+
}
|
|
965
|
+
finally {
|
|
966
|
+
resolutionStack.pop();
|
|
967
|
+
resolutionPath.pop();
|
|
968
|
+
binding.inFlight = false;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
// Deliberately not `async`: that would allocate a state machine and a promise per level.
|
|
972
|
+
#resolveTransientDynamicAsyncFromContext(binding, resolutionPath, resolutionStack, callerContext) {
|
|
973
|
+
// Path-scoped cycle detection, because async chains interleave — see ARCHITECTURE.md.
|
|
974
|
+
const pool = this.#asyncChainContextPool;
|
|
975
|
+
const frame = this.#getResolutionFrame(binding);
|
|
976
|
+
const tokenDisplayName = frame.tokenName;
|
|
977
|
+
try {
|
|
978
|
+
enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
979
|
+
}
|
|
980
|
+
catch (cycleError) {
|
|
981
|
+
// This method is not `async`; keep failures as rejections rather than sync throws.
|
|
982
|
+
return Promise.reject(cycleError);
|
|
983
|
+
}
|
|
984
|
+
// An inner level reuses the context its caller passed down; only a chain's first level
|
|
985
|
+
// borrows from the pool. Pooling is load-bearing here — see ARCHITECTURE.md.
|
|
986
|
+
const ctx = callerContext !== undefined && callerContext.owner === this
|
|
987
|
+
? callerContext
|
|
988
|
+
: this.#acquireAsyncChainContext(resolutionPath, resolutionStack);
|
|
989
|
+
ctx.chainLevels += 1;
|
|
990
|
+
// Invoke the factory synchronously to get its Promise (or a resolved value for "dynamic").
|
|
991
|
+
let factoryPromise;
|
|
992
|
+
try {
|
|
993
|
+
if (binding.kind === "dynamic-async") {
|
|
994
|
+
factoryPromise = binding.factory(ctx);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
const factoryResult = binding.factory(ctx);
|
|
998
|
+
factoryPromise =
|
|
999
|
+
factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
catch (factoryError) {
|
|
1003
|
+
// Synchronous throw from the factory (rare) — clean up immediately.
|
|
1004
|
+
exitResolutionPath(resolutionPath);
|
|
1005
|
+
ctx.chainLevels -= 1;
|
|
1006
|
+
if (ctx.chainLevels === 0) {
|
|
1007
|
+
pool.push(ctx);
|
|
1008
|
+
}
|
|
1009
|
+
return Promise.reject(factoryError);
|
|
1010
|
+
}
|
|
1011
|
+
// A side listener, not a derived chain: FIFO puts it before the awaiting caller resumes.
|
|
1012
|
+
// It marks a rejection as handled, so callers must await or `.catch` the returned promise.
|
|
1013
|
+
const settle = ctx.chainSettle ??
|
|
1014
|
+
(ctx.chainSettle = () => {
|
|
1015
|
+
exitResolutionPath(resolutionPath);
|
|
1016
|
+
ctx.chainLevels -= 1;
|
|
1017
|
+
if (ctx.chainLevels === 0) {
|
|
1018
|
+
pool.push(ctx);
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
factoryPromise.then(settle, settle);
|
|
1022
|
+
return factoryPromise;
|
|
1023
|
+
}
|
|
1024
|
+
#acquireAsyncChainContext(resolutionPath, resolutionStack) {
|
|
1025
|
+
const pooled = this.#asyncChainContextPool.pop();
|
|
1026
|
+
if (pooled === undefined) {
|
|
1027
|
+
return new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1028
|
+
}
|
|
1029
|
+
pooled.reset(this, resolutionPath, resolutionStack, undefined);
|
|
1030
|
+
return pooled;
|
|
1031
|
+
}
|
|
1032
|
+
#resolveCandidateSync(binding, options, resolutionPath, resolutionStack) {
|
|
1033
|
+
if (binding.kind === "constant" &&
|
|
1034
|
+
binding.onActivation === undefined &&
|
|
1035
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
1036
|
+
return binding.value;
|
|
1037
|
+
}
|
|
1038
|
+
if (binding.kind === "alias") {
|
|
1039
|
+
return this.resolve(binding.target, options, resolutionPath, resolutionStack);
|
|
1040
|
+
}
|
|
1041
|
+
const scope = binding.scope ?? "transient";
|
|
1042
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
1043
|
+
return binding.instance;
|
|
1044
|
+
}
|
|
1045
|
+
if (scope === "scoped") {
|
|
1046
|
+
if (!this.#scope.isChild) {
|
|
1047
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
1048
|
+
}
|
|
1049
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
1050
|
+
return this.#scope.getScoped(binding.id);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return this.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
1054
|
+
}
|
|
1055
|
+
#resolveCandidateAsync(binding, options, resolutionPath, resolutionStack) {
|
|
1056
|
+
if (binding.kind === "constant" &&
|
|
1057
|
+
binding.onActivation === undefined &&
|
|
1058
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
1059
|
+
return Promise.resolve(binding.value);
|
|
1060
|
+
}
|
|
1061
|
+
const isolatedPath = [...resolutionPath];
|
|
1062
|
+
const isolatedStack = [...resolutionStack];
|
|
1063
|
+
if (binding.kind === "alias") {
|
|
1064
|
+
return this.resolveAsync(binding.target, options, isolatedPath, isolatedStack);
|
|
1065
|
+
}
|
|
1066
|
+
const scope = binding.scope ?? "transient";
|
|
1067
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
1068
|
+
return Promise.resolve(binding.instance);
|
|
1069
|
+
}
|
|
1070
|
+
if (scope === "scoped") {
|
|
1071
|
+
if (!this.#scope.isChild) {
|
|
1072
|
+
return Promise.reject(new MissingScopeContextError(this.#getTokenName(binding.token)));
|
|
1073
|
+
}
|
|
1074
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
1075
|
+
return Promise.resolve(this.#scope.getScoped(binding.id));
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return this.#resolveBindingAsync(binding, options, isolatedPath, isolatedStack);
|
|
1079
|
+
}
|
|
1080
|
+
#getResolutionFrame(binding) {
|
|
1081
|
+
// Memoized on the binding rather than in a per-resolver Map: the frame derives only from
|
|
1082
|
+
// immutable binding fields, so it is identical for every resolver, and a field read beats a
|
|
1083
|
+
// Map lookup on every hop of a chain.
|
|
1084
|
+
const existing = binding.frame;
|
|
1085
|
+
if (existing !== undefined) {
|
|
1086
|
+
return existing;
|
|
1087
|
+
}
|
|
1088
|
+
const scope = binding.scope ?? "transient";
|
|
1089
|
+
const frame = buildResolutionFrame(tokenName(binding.token), scope, binding.id, binding.kind, binding.slot);
|
|
1090
|
+
binding.frame = frame;
|
|
1091
|
+
return frame;
|
|
1092
|
+
}
|
|
1093
|
+
#requiresResolutionContext(binding) {
|
|
1094
|
+
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
1095
|
+
}
|
|
1096
|
+
#acquireSyncResolutionContext(resolutionPath, resolutionStack, options) {
|
|
1097
|
+
const depth = resolutionStack.length;
|
|
1098
|
+
const existing = this.#syncResolutionContextPool[depth];
|
|
1099
|
+
if (existing !== undefined) {
|
|
1100
|
+
existing.reset(this, resolutionPath, resolutionStack, options);
|
|
1101
|
+
return existing;
|
|
1102
|
+
}
|
|
1103
|
+
const created = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
1104
|
+
this.#syncResolutionContextPool[depth] = created;
|
|
1105
|
+
return created;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
//# sourceMappingURL=resolver.js.map
|