@codefast/di 0.5.0-canary.5 → 0.5.0-canary.7
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 +65 -0
- package/README.md +73 -496
- package/dist/binding.d.ts +211 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +46 -0
- package/dist/binding.js.map +1 -0
- package/dist/{constructor-type.d.mts → constructor-type.d.ts} +3 -5
- package/dist/constructor-type.d.ts.map +1 -0
- package/dist/constructor-type.js +2 -0
- package/dist/constructor-type.js.map +1 -0
- package/dist/container/binding-builders.d.ts +35 -0
- package/dist/container/binding-builders.d.ts.map +1 -0
- package/dist/container/binding-builders.js +247 -0
- package/dist/container/binding-builders.js.map +1 -0
- package/dist/container/container.d.ts +58 -0
- package/dist/container/container.d.ts.map +1 -0
- package/dist/container/container.js +641 -0
- package/dist/container/container.js.map +1 -0
- package/dist/decorators/inject.d.ts +57 -0
- package/dist/decorators/inject.d.ts.map +1 -0
- package/dist/decorators/inject.js +145 -0
- package/dist/decorators/inject.js.map +1 -0
- package/dist/decorators/injectable.d.ts +29 -0
- package/dist/decorators/injectable.d.ts.map +1 -0
- package/dist/decorators/injectable.js +53 -0
- package/dist/decorators/injectable.js.map +1 -0
- package/dist/decorators/lifecycle-decorators.d.ts +9 -0
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -0
- package/dist/decorators/lifecycle-decorators.js +40 -0
- package/dist/decorators/lifecycle-decorators.js.map +1 -0
- package/dist/errors.d.ts +150 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +197 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/introspection/dependency-graph.d.ts +40 -0
- package/dist/introspection/dependency-graph.d.ts.map +1 -0
- package/dist/introspection/dependency-graph.js +66 -0
- package/dist/introspection/dependency-graph.js.map +1 -0
- package/dist/introspection/graph-adapters/cytoscape.d.ts +33 -0
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/cytoscape.js +30 -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 +18 -0
- package/dist/introspection/graph-adapters/dot.js.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts +38 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.js +33 -0
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -0
- package/dist/introspection/inspector.d.ts +39 -0
- package/dist/introspection/inspector.d.ts.map +1 -0
- package/dist/introspection/inspector.js +82 -0
- package/dist/introspection/inspector.js.map +1 -0
- package/dist/metadata/{metadata-keys.d.mts → metadata-keys.d.ts} +5 -7
- package/dist/metadata/metadata-keys.d.ts.map +1 -0
- package/dist/metadata/metadata-keys.js +25 -0
- package/dist/metadata/metadata-keys.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +7 -0
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -0
- package/dist/metadata/metadata-reader-token.js +6 -0
- package/dist/metadata/metadata-reader-token.js.map +1 -0
- package/dist/metadata/metadata-types.d.ts +48 -0
- package/dist/metadata/metadata-types.d.ts.map +1 -0
- package/dist/metadata/metadata-types.js +2 -0
- package/dist/metadata/metadata-types.js.map +1 -0
- package/dist/metadata/symbol-metadata-reader.d.ts +20 -0
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/symbol-metadata-reader.js +34 -0
- package/dist/metadata/symbol-metadata-reader.js.map +1 -0
- package/dist/module.d.ts +67 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +54 -0
- package/dist/module.js.map +1 -0
- package/dist/registry.d.ts +33 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +249 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolution/binding-scope.d.ts +10 -0
- package/dist/resolution/binding-scope.d.ts.map +1 -0
- package/dist/resolution/binding-scope.js +24 -0
- package/dist/resolution/binding-scope.js.map +1 -0
- package/dist/resolution/binding-select.d.ts +16 -0
- package/dist/resolution/binding-select.d.ts.map +1 -0
- package/dist/resolution/binding-select.js +143 -0
- package/dist/resolution/binding-select.js.map +1 -0
- package/dist/resolution/constraints.d.ts +51 -0
- package/dist/resolution/constraints.d.ts.map +1 -0
- package/dist/resolution/constraints.js +88 -0
- package/dist/resolution/constraints.js.map +1 -0
- package/dist/resolution/environment.d.ts +47 -0
- package/dist/resolution/environment.d.ts.map +1 -0
- package/dist/resolution/environment.js +100 -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 +163 -0
- package/dist/resolution/instantiation-plan.js.map +1 -0
- package/dist/resolution/lifecycle.d.ts +21 -0
- package/dist/resolution/lifecycle.d.ts.map +1 -0
- package/dist/resolution/lifecycle.js +178 -0
- package/dist/resolution/lifecycle.js.map +1 -0
- package/dist/resolution/resolution-path.d.ts +31 -0
- package/dist/resolution/resolution-path.d.ts.map +1 -0
- package/dist/resolution/resolution-path.js +53 -0
- package/dist/resolution/resolution-path.js.map +1 -0
- package/dist/resolution/resolve-options.d.ts +19 -0
- package/dist/resolution/resolve-options.d.ts.map +1 -0
- package/dist/resolution/resolve-options.js +32 -0
- package/dist/resolution/resolve-options.js.map +1 -0
- package/dist/resolution/resolver.d.ts +35 -0
- package/dist/resolution/resolver.d.ts.map +1 -0
- package/dist/resolution/resolver.js +1320 -0
- package/dist/resolution/resolver.js.map +1 -0
- package/dist/resolution/scope.d.ts +32 -0
- package/dist/resolution/scope.d.ts.map +1 -0
- package/dist/resolution/scope.js +76 -0
- package/dist/resolution/scope.js.map +1 -0
- package/dist/token.d.ts +23 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +22 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +143 -120
- package/src/binding.ts +38 -22
- package/src/container/binding-builders.ts +397 -0
- package/src/container/container.ts +838 -0
- package/src/decorators/inject.ts +16 -3
- package/src/decorators/injectable.ts +3 -3
- package/src/index.ts +14 -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/symbol-metadata-reader.ts +4 -4
- package/src/module.ts +14 -6
- package/src/registry.ts +115 -61
- package/src/{binding-select.ts → resolution/binding-select.ts} +16 -3
- package/src/{environment.ts → resolution/environment.ts} +35 -35
- package/src/resolution/instantiation-plan.ts +240 -0
- package/src/{lifecycle.ts → resolution/lifecycle.ts} +20 -13
- package/src/resolution/resolution-path.ts +77 -0
- package/src/resolution/resolver.ts +1755 -0
- package/src/{scope.ts → resolution/scope.ts} +35 -18
- package/src/token.ts +0 -3
- package/dist/binding-scope.d.mts +0 -13
- package/dist/binding-scope.mjs +0 -21
- package/dist/binding-select.d.mts +0 -19
- package/dist/binding-select.mjs +0 -72
- package/dist/binding.d.mts +0 -191
- package/dist/binding.mjs +0 -36
- package/dist/constraints.d.mts +0 -55
- package/dist/constraints.mjs +0 -87
- package/dist/constructor-type.mjs +0 -1
- package/dist/container.d.mts +0 -62
- package/dist/container.mjs +0 -741
- package/dist/decorators/inject.d.mts +0 -49
- package/dist/decorators/inject.mjs +0 -136
- package/dist/decorators/injectable.d.mts +0 -32
- package/dist/decorators/injectable.mjs +0 -57
- package/dist/decorators/lifecycle-decorators.d.mts +0 -11
- package/dist/decorators/lifecycle-decorators.mjs +0 -38
- package/dist/dependency-graph.d.mts +0 -43
- package/dist/dependency-graph.mjs +0 -63
- package/dist/environment.d.mts +0 -55
- package/dist/environment.mjs +0 -98
- package/dist/errors.d.mts +0 -153
- package/dist/errors.mjs +0 -197
- package/dist/graph-adapters/cytoscape.d.mts +0 -36
- package/dist/graph-adapters/cytoscape.mjs +0 -26
- package/dist/graph-adapters/dot.d.mts +0 -9
- package/dist/graph-adapters/dot.mjs +0 -20
- package/dist/graph-adapters/reactflow.d.mts +0 -41
- package/dist/graph-adapters/reactflow.mjs +0 -29
- package/dist/index.d.mts +0 -18
- package/dist/index.mjs +0 -12
- package/dist/inspector.d.mts +0 -48
- package/dist/inspector.mjs +0 -71
- package/dist/lifecycle.d.mts +0 -25
- package/dist/lifecycle.mjs +0 -118
- package/dist/metadata/metadata-keys.mjs +0 -27
- package/dist/metadata/metadata-reader-token.d.mts +0 -10
- package/dist/metadata/metadata-reader-token.mjs +0 -8
- package/dist/metadata/metadata-types.d.mts +0 -51
- package/dist/metadata/metadata-types.mjs +0 -1
- package/dist/metadata/symbol-metadata-reader.d.mts +0 -23
- package/dist/metadata/symbol-metadata-reader.mjs +0 -29
- package/dist/module.d.mts +0 -62
- package/dist/module.mjs +0 -42
- package/dist/registry.d.mts +0 -45
- package/dist/registry.mjs +0 -163
- package/dist/resolve-options.d.mts +0 -22
- package/dist/resolve-options.mjs +0 -28
- package/dist/resolver.d.mts +0 -94
- package/dist/resolver.mjs +0 -767
- package/dist/scope.d.mts +0 -28
- package/dist/scope.mjs +0 -58
- package/dist/token.d.mts +0 -25
- package/dist/token.mjs +0 -22
- package/dist/types.d.mts +0 -92
- package/dist/types.mjs +0 -1
- package/src/container.ts +0 -1141
- package/src/resolver.ts +0 -1626
- /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,1320 @@
|
|
|
1
|
+
import { AsyncActivationError, AsyncResolutionError, CircularDependencyError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#/errors";
|
|
2
|
+
import { selectAllBindings, selectBinding } from "#/resolution/binding-select";
|
|
3
|
+
import { buildResolutionFrame, DefaultResolutionContext, runWithContainer } from "#/resolution/environment";
|
|
4
|
+
import { InstantiationPlanCompiler, PLAN_RETRY } from "#/resolution/instantiation-plan";
|
|
5
|
+
import { enterResolutionPath, exitResolutionPath } from "#/resolution/resolution-path";
|
|
6
|
+
import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
7
|
+
import { SINGLETON_MISS } from "#/resolution/scope";
|
|
8
|
+
import { tokenName } from "#/token";
|
|
9
|
+
// Fast-lane alias folding gives up past this many hops and defers to the resolve()
|
|
10
|
+
// loop, whose Set-based traversal detects genuine cycles exactly (no arbitrary cap).
|
|
11
|
+
const ALIAS_HOP_LIMIT = 32;
|
|
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
|
+
// Cycle detection for the sync transient-dynamic lane lives on `binding.inFlight` — see the
|
|
27
|
+
// field's doc comment in binding.ts. It is an O(1) field read with no hashing, no path scan and
|
|
28
|
+
// no side table to allocate or grow, so the lane needs no depth split.
|
|
29
|
+
// Shared-context state for the async transient-dynamic lane.
|
|
30
|
+
//
|
|
31
|
+
// Every level of a SEQUENTIAL async chain shares the same resolutionPath and resolutionStack
|
|
32
|
+
// arrays (passed by reference through ctx.resolveAsync), and the context stores references
|
|
33
|
+
// rather than snapshots, so one DefaultResolutionContext can serve the whole chain — the arrays
|
|
34
|
+
// reflect the current state automatically as levels push and pop.
|
|
35
|
+
//
|
|
36
|
+
// #asyncChainCtx: the shared context, created on first use and reset at each new root call.
|
|
37
|
+
// Inner levels of the same chain reuse it with zero setup.
|
|
38
|
+
// #asyncChainCtxPath: identity of the resolutionPath array owning the shared context — same
|
|
39
|
+
// reference means an inner level of that chain, a different one means a concurrent chain
|
|
40
|
+
// (e.g. Promise.all) which gets its own context instead.
|
|
41
|
+
// #asyncChainActiveLevels: active levels of the OWNING chain, so the path pointer is released
|
|
42
|
+
// when the last one settles. Concurrent fallback calls are not counted.
|
|
43
|
+
//
|
|
44
|
+
// The method is NOT declared async: that would allocate an async state machine and an implicit
|
|
45
|
+
// promise per level, where a `.then(settle, settle)` side listener costs neither.
|
|
46
|
+
#asyncChainCtx;
|
|
47
|
+
#asyncChainCtxPath;
|
|
48
|
+
#asyncChainActiveLevels = 0;
|
|
49
|
+
// Settle callback shared by every level of the owning async chain: all of them pop the same
|
|
50
|
+
// resolutionPath and decrement the same counter, so one closure serves the whole chain instead
|
|
51
|
+
// of allocating one per level (the async lane's dominant per-level allocation).
|
|
52
|
+
#asyncChainSettle;
|
|
53
|
+
#classHasPostConstruct = new WeakMap();
|
|
54
|
+
#classNeedsActiveContainer = new WeakMap();
|
|
55
|
+
#classConstructorMetadata = new WeakMap();
|
|
56
|
+
#activationNeedByBindingId = new Map();
|
|
57
|
+
#activationCacheVersion = -1;
|
|
58
|
+
// Options-less lookup memo across the parent chain: token → terminal {binding, owner}
|
|
59
|
+
// (alias hops folded). `null` = token must take the slow lookup path. Invalidated when
|
|
60
|
+
// any registry in the chain mutates (monotonic version sum).
|
|
61
|
+
#defaultLookupByToken = new Map();
|
|
62
|
+
#defaultLookupVersion = -1;
|
|
63
|
+
// Name-only lookup memo (token → name → entry) with the same chain-version
|
|
64
|
+
// invalidation. `null` = shape needs the full selection path.
|
|
65
|
+
#namedLookupByToken = new Map();
|
|
66
|
+
#namedLookupVersion = -1;
|
|
67
|
+
// Compiled transient-class plans (Dagger-style): a pure-static subgraph (class/constant/
|
|
68
|
+
// cached-singleton deps only) compiles once into a nested-constructor closure — cycle
|
|
69
|
+
// checking happens at compile time, so execution skips all per-resolve bookkeeping.
|
|
70
|
+
// `null` = binding is not plannable under the current versions.
|
|
71
|
+
#classPlanByBindingId = new Map();
|
|
72
|
+
#classPlanRegistryVersion = -1;
|
|
73
|
+
#classPlanActivationVersion = -1;
|
|
74
|
+
#registry;
|
|
75
|
+
#scope;
|
|
76
|
+
#lifecycle;
|
|
77
|
+
#metadataReader;
|
|
78
|
+
#container;
|
|
79
|
+
#parent;
|
|
80
|
+
constructor(registry, scope, lifecycle, metadataReader, container, parent) {
|
|
81
|
+
this.#registry = registry;
|
|
82
|
+
this.#scope = scope;
|
|
83
|
+
this.#lifecycle = lifecycle;
|
|
84
|
+
this.#metadataReader = metadataReader;
|
|
85
|
+
this.#container = container;
|
|
86
|
+
this.#parent = parent;
|
|
87
|
+
}
|
|
88
|
+
// ── Binding lookup ─────────────────────────────────────────────────────────
|
|
89
|
+
#findBinding(token, options, resolutionPath, resolutionStack) {
|
|
90
|
+
if (options === undefined) {
|
|
91
|
+
const fastDefaultBinding = this.#registry.getFastDefault(token);
|
|
92
|
+
if (fastDefaultBinding !== undefined) {
|
|
93
|
+
return { binding: fastDefaultBinding, owner: this };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
97
|
+
const namedBinding = this.#registry.getSimpleNamed(token, options.name);
|
|
98
|
+
if (namedBinding !== undefined &&
|
|
99
|
+
this.#matchesBindingFast(namedBinding, options, resolutionPath, resolutionStack)) {
|
|
100
|
+
return { binding: namedBinding, owner: this };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (options !== undefined &&
|
|
104
|
+
options.name === undefined &&
|
|
105
|
+
options.tag === undefined &&
|
|
106
|
+
(options.tags?.length ?? 0) === 1) {
|
|
107
|
+
const [tagKey, tagValue] = options.tags[0];
|
|
108
|
+
const tagged = this.#registry.getSimpleTagged(token, tagKey, tagValue);
|
|
109
|
+
if (tagged !== undefined) {
|
|
110
|
+
return { binding: tagged, owner: this };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const bindings = this.#registry.getAll(token);
|
|
114
|
+
if (bindings.length > 0) {
|
|
115
|
+
if (bindings.length === 1) {
|
|
116
|
+
const onlyBinding = bindings[0];
|
|
117
|
+
const isDefaultSlot = onlyBinding.slot.name === undefined && onlyBinding.slot.tags.length === 0;
|
|
118
|
+
if (options === undefined && isDefaultSlot && onlyBinding.predicate === undefined) {
|
|
119
|
+
return { binding: onlyBinding, owner: this };
|
|
120
|
+
}
|
|
121
|
+
if (this.#matchesBindingFast(onlyBinding, options, resolutionPath, resolutionStack)) {
|
|
122
|
+
return { binding: onlyBinding, owner: this };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
126
|
+
const binding = selectBinding(bindings, options, ctx, this.#getTokenName(token));
|
|
127
|
+
if (binding !== undefined) {
|
|
128
|
+
return { binding, owner: this };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (this.#parent !== undefined) {
|
|
132
|
+
return this.#parent.#findBinding(token, options, resolutionPath, resolutionStack);
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Binding lookup aligned with `resolve` — used by `Container.validate` without instantiating.
|
|
138
|
+
*/
|
|
139
|
+
peekBindingForValidate(token, options) {
|
|
140
|
+
return this.#findBinding(token, options, [], []);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Mirrors {@link DependencyResolver.resolveAll} candidate selection only (no instantiation).
|
|
144
|
+
*/
|
|
145
|
+
peekCandidateBindingsForValidate(token, options) {
|
|
146
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
147
|
+
return this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
148
|
+
}
|
|
149
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
150
|
+
if (allBindings.length === 0) {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const ctx = this.#makeConstraintContext([], [], options);
|
|
154
|
+
return selectAllBindings(allBindings, options, ctx);
|
|
155
|
+
}
|
|
156
|
+
// ── Sync resolve ───────────────────────────────────────────────────────────
|
|
157
|
+
resolveFromContext(token, resolutionPath, resolutionStack) {
|
|
158
|
+
// Hot lane: own-registry fast default. Fall back to the chain-versioned memo
|
|
159
|
+
// (parent-chain walk + alias folding) only on miss or alias.
|
|
160
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
161
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
162
|
+
return this.#resolveDefaultEntry(fastBinding, this, resolutionPath, resolutionStack);
|
|
163
|
+
}
|
|
164
|
+
const entry = this.#lookupDefaultEntry(token);
|
|
165
|
+
if (entry === null) {
|
|
166
|
+
return this.resolve(token, undefined, resolutionPath, resolutionStack);
|
|
167
|
+
}
|
|
168
|
+
return this.#resolveDefaultEntry(entry.binding, entry.owner, resolutionPath, resolutionStack);
|
|
169
|
+
}
|
|
170
|
+
#resolveDefaultEntry(binding, owner, resolutionPath, resolutionStack) {
|
|
171
|
+
if (binding.kind === "constant" &&
|
|
172
|
+
binding.onActivation === undefined &&
|
|
173
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
174
|
+
return binding.value;
|
|
175
|
+
}
|
|
176
|
+
const scope = binding.scope ?? "transient";
|
|
177
|
+
if (scope === "transient") {
|
|
178
|
+
if (binding.kind === "dynamic") {
|
|
179
|
+
const containerHooks = this.#lifecycle.activationVersion === 0 ? undefined : this.#lifecycle.activationHandlersFor(binding.token);
|
|
180
|
+
if (binding.onActivation === undefined && (containerHooks === undefined || containerHooks.length === 0)) {
|
|
181
|
+
return this.#resolveTransientDynamicSyncFromContext(binding, resolutionPath, resolutionStack);
|
|
182
|
+
}
|
|
183
|
+
return this.#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionPath, resolutionStack);
|
|
184
|
+
}
|
|
185
|
+
// Compiled plans only run at the top level — inner levels keep the runtime cycle guard.
|
|
186
|
+
if ((binding.kind === "class" || binding.kind === "resolved") && resolutionPath.length === 0) {
|
|
187
|
+
const plan = this.#getInstantiationPlan(binding);
|
|
188
|
+
if (plan !== null) {
|
|
189
|
+
return plan();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (scope === "singleton") {
|
|
194
|
+
const cachedSingleton = owner.#scope.peekSingleton(binding.id);
|
|
195
|
+
if (cachedSingleton !== SINGLETON_MISS) {
|
|
196
|
+
return cachedSingleton;
|
|
197
|
+
}
|
|
198
|
+
if (owner !== this) {
|
|
199
|
+
return owner.#resolveBinding(binding, undefined, resolutionPath, resolutionStack);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
if (!this.#scope.isChild) {
|
|
204
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
205
|
+
}
|
|
206
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
207
|
+
return this.#scope.getScoped(binding.id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return this.#resolveBinding(binding, undefined, resolutionPath, resolutionStack);
|
|
211
|
+
}
|
|
212
|
+
// Lean lane for an activated transient dynamic binding: same observable behavior as the
|
|
213
|
+
// generic #resolveBinding path (guard, frame, ctx, per-binding then container hooks) with
|
|
214
|
+
// the kind/activation dispatch resolved statically.
|
|
215
|
+
#resolveTransientDynamicActivatedSync(binding, containerHooks, resolutionPath, resolutionStack) {
|
|
216
|
+
const frame = this.#getResolutionFrame(binding);
|
|
217
|
+
const tokenDisplayName = frame.tokenName;
|
|
218
|
+
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
219
|
+
resolutionStack.push(frame);
|
|
220
|
+
try {
|
|
221
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
222
|
+
const factoryResult = binding.factory(resolutionCtx);
|
|
223
|
+
if (factoryResult instanceof Promise) {
|
|
224
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
225
|
+
}
|
|
226
|
+
let activated = factoryResult;
|
|
227
|
+
if (binding.onActivation !== undefined) {
|
|
228
|
+
const activationResult = binding.onActivation(resolutionCtx, activated);
|
|
229
|
+
if (activationResult instanceof Promise) {
|
|
230
|
+
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
231
|
+
}
|
|
232
|
+
activated = activationResult;
|
|
233
|
+
}
|
|
234
|
+
if (containerHooks !== undefined) {
|
|
235
|
+
for (let index = 0; index < containerHooks.length; index += 1) {
|
|
236
|
+
const activationResult = containerHooks[index](resolutionCtx, activated);
|
|
237
|
+
if (activationResult instanceof Promise) {
|
|
238
|
+
throw new AsyncActivationError(tokenDisplayName, "onActivation");
|
|
239
|
+
}
|
|
240
|
+
activated = activationResult;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return activated;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
resolutionStack.pop();
|
|
247
|
+
resolutionPath.pop();
|
|
248
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
#chainRegistryVersion() {
|
|
252
|
+
let version = this.#registry.version;
|
|
253
|
+
for (let resolver = this.#parent; resolver !== undefined; resolver = resolver.#parent) {
|
|
254
|
+
version += resolver.#registry.version;
|
|
255
|
+
}
|
|
256
|
+
return version;
|
|
257
|
+
}
|
|
258
|
+
#lookupDefaultEntry(token) {
|
|
259
|
+
const version = this.#chainRegistryVersion();
|
|
260
|
+
if (version !== this.#defaultLookupVersion) {
|
|
261
|
+
this.#defaultLookupByToken.clear();
|
|
262
|
+
this.#defaultLookupVersion = version;
|
|
263
|
+
}
|
|
264
|
+
let entry = this.#defaultLookupByToken.get(token);
|
|
265
|
+
if (entry === undefined) {
|
|
266
|
+
entry = this.#computeDefaultEntry(token);
|
|
267
|
+
this.#defaultLookupByToken.set(token, entry);
|
|
268
|
+
}
|
|
269
|
+
return entry;
|
|
270
|
+
}
|
|
271
|
+
#computeDefaultEntry(token) {
|
|
272
|
+
let current = token;
|
|
273
|
+
for (let hop = 0; hop < ALIAS_HOP_LIMIT; hop += 1) {
|
|
274
|
+
const entry = this.#findDefaultEntryInChain(current);
|
|
275
|
+
if (entry === null) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
if (entry.binding.kind === "alias") {
|
|
279
|
+
current = entry.binding.target;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
return entry;
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
#lookupNamedEntry(token, name) {
|
|
287
|
+
const version = this.#chainRegistryVersion();
|
|
288
|
+
if (version !== this.#namedLookupVersion) {
|
|
289
|
+
this.#namedLookupByToken.clear();
|
|
290
|
+
this.#namedLookupVersion = version;
|
|
291
|
+
}
|
|
292
|
+
// ✓ TS6.0: Map.getOrInsert (ES2025)
|
|
293
|
+
const entriesByName = this.#namedLookupByToken.getOrInsert(token, new Map());
|
|
294
|
+
let entry = entriesByName.get(name);
|
|
295
|
+
if (entry === undefined) {
|
|
296
|
+
entry = this.#findNamedEntryInChain(token, name);
|
|
297
|
+
entriesByName.set(name, entry);
|
|
298
|
+
}
|
|
299
|
+
return entry;
|
|
300
|
+
}
|
|
301
|
+
#findNamedEntryInChain(token, name) {
|
|
302
|
+
const named = this.#registry.getSimpleNamed(token, name);
|
|
303
|
+
if (named !== undefined) {
|
|
304
|
+
// Predicates need a live context; aliases carry options through the full path.
|
|
305
|
+
if (named.predicate !== undefined || named.kind === "alias") {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return { binding: named, owner: this };
|
|
309
|
+
}
|
|
310
|
+
if (this.#registry.has(token)) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
return this.#parent === undefined ? null : this.#parent.#findNamedEntryInChain(token, name);
|
|
314
|
+
}
|
|
315
|
+
#findDefaultEntryInChain(token) {
|
|
316
|
+
const fast = this.#registry.getFastDefault(token);
|
|
317
|
+
if (fast !== undefined) {
|
|
318
|
+
return { binding: fast, owner: this };
|
|
319
|
+
}
|
|
320
|
+
// A level with non-fast bindings (multi-slot / predicate) needs full selection — bail.
|
|
321
|
+
if (this.#registry.has(token)) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
return this.#parent === undefined ? null : this.#parent.#findDefaultEntryInChain(token);
|
|
325
|
+
}
|
|
326
|
+
#getInstantiationPlan(binding) {
|
|
327
|
+
const registryVersion = this.#chainRegistryVersion();
|
|
328
|
+
const activationVersion = this.#lifecycle.activationVersion;
|
|
329
|
+
if (registryVersion !== this.#classPlanRegistryVersion || activationVersion !== this.#classPlanActivationVersion) {
|
|
330
|
+
this.#classPlanByBindingId.clear();
|
|
331
|
+
this.#classPlanRegistryVersion = registryVersion;
|
|
332
|
+
this.#classPlanActivationVersion = activationVersion;
|
|
333
|
+
}
|
|
334
|
+
const cached = this.#classPlanByBindingId.get(binding.id);
|
|
335
|
+
if (cached !== undefined) {
|
|
336
|
+
return cached;
|
|
337
|
+
}
|
|
338
|
+
const compiled = this.#planCompiler.compile(binding);
|
|
339
|
+
if (compiled === PLAN_RETRY) {
|
|
340
|
+
// Lifecycle metadata not discovered yet — the fallback resolve discovers it; retry then.
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
this.#classPlanByBindingId.set(binding.id, compiled);
|
|
344
|
+
return compiled;
|
|
345
|
+
}
|
|
346
|
+
// Compiler behind #getClassPlan — cold path, so the host indirection costs nothing hot.
|
|
347
|
+
#planCompiler = new InstantiationPlanCompiler({
|
|
348
|
+
hasActivationHandlers: (token) => this.#lifecycle.hasActivationHandlers(token),
|
|
349
|
+
knownPostConstruct: (target) => this.#classHasPostConstruct.get(target),
|
|
350
|
+
needsActiveContainer: (target) => {
|
|
351
|
+
let needsActiveContainer = this.#classNeedsActiveContainer.get(target);
|
|
352
|
+
if (needsActiveContainer === undefined) {
|
|
353
|
+
const accessorMetadata = this.#metadataReader.getAccessorMetadata?.(target);
|
|
354
|
+
needsActiveContainer = (accessorMetadata?.length ?? 0) > 0;
|
|
355
|
+
this.#classNeedsActiveContainer.set(target, needsActiveContainer);
|
|
356
|
+
}
|
|
357
|
+
return needsActiveContainer;
|
|
358
|
+
},
|
|
359
|
+
getConstructorMetadata: (target) => this.#getConstructorMetadata(target),
|
|
360
|
+
lookupDependencyEntry: (token) => {
|
|
361
|
+
const entry = this.#lookupDefaultEntry(token);
|
|
362
|
+
return entry === null ? null : { binding: entry.binding, ownerScope: entry.owner.#scope };
|
|
363
|
+
},
|
|
364
|
+
resolveFallback: (token) => this.resolve(token, undefined, [], []),
|
|
365
|
+
});
|
|
366
|
+
resolve(token, options, resolutionPath, resolutionStack) {
|
|
367
|
+
// Name-only fast lane: memoized lookup, dispatching just the shapes whose
|
|
368
|
+
// semantics involve no resolution context (constants, cached singletons).
|
|
369
|
+
if (options !== undefined &&
|
|
370
|
+
options.name !== undefined &&
|
|
371
|
+
options.tag === undefined &&
|
|
372
|
+
(options.tags === undefined || options.tags.length === 0)) {
|
|
373
|
+
const namedEntry = this.#lookupNamedEntry(token, options.name);
|
|
374
|
+
if (namedEntry !== null) {
|
|
375
|
+
const namedBinding = namedEntry.binding;
|
|
376
|
+
if (namedBinding.kind === "constant" &&
|
|
377
|
+
namedBinding.onActivation === undefined &&
|
|
378
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(namedBinding.token))) {
|
|
379
|
+
return namedBinding.value;
|
|
380
|
+
}
|
|
381
|
+
const namedScope = namedBinding.scope ?? "transient";
|
|
382
|
+
if (namedScope === "singleton") {
|
|
383
|
+
const cachedSingleton = namedEntry.owner.#scope.peekSingleton(namedBinding.id);
|
|
384
|
+
if (cachedSingleton !== SINGLETON_MISS) {
|
|
385
|
+
return cachedSingleton;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Everything else keeps the full path (context, activation, guards).
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
let currentToken = token;
|
|
392
|
+
let visitedAliasTokens;
|
|
393
|
+
let found = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
394
|
+
// Follow aliases iteratively with exact cycle detection — a revisited alias
|
|
395
|
+
// token throws CircularDependencyError instead of overflowing the call stack.
|
|
396
|
+
while (found !== undefined && found.binding.kind === "alias") {
|
|
397
|
+
const target = found.binding.target;
|
|
398
|
+
visitedAliasTokens ??= new Set([currentToken]);
|
|
399
|
+
if (visitedAliasTokens.has(target)) {
|
|
400
|
+
throw new CircularDependencyError([...visitedAliasTokens, target].map((entry) => tokenName(entry)));
|
|
401
|
+
}
|
|
402
|
+
visitedAliasTokens.add(target);
|
|
403
|
+
currentToken = target;
|
|
404
|
+
found = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
405
|
+
}
|
|
406
|
+
if (found === undefined) {
|
|
407
|
+
const ownBindings = this.#registry.getAll(currentToken);
|
|
408
|
+
if (ownBindings.length > 0) {
|
|
409
|
+
throw new NoMatchingBindingError(this.#getTokenName(currentToken), options ?? {}, this.#getAvailableSlots(currentToken));
|
|
410
|
+
}
|
|
411
|
+
throw new TokenNotBoundError(this.#getTokenName(currentToken));
|
|
412
|
+
}
|
|
413
|
+
const { binding, owner } = found;
|
|
414
|
+
const scope = binding.scope ?? "transient";
|
|
415
|
+
// Singleton from a parent resolver: delegate so the parent caches it correctly
|
|
416
|
+
if (scope === "singleton" && owner !== this) {
|
|
417
|
+
return owner.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
418
|
+
}
|
|
419
|
+
// Scoped/transient (or own singleton): resolve with this resolver's container/scope
|
|
420
|
+
return this.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
421
|
+
}
|
|
422
|
+
#resolveBinding(binding, options, resolutionPath, resolutionStack) {
|
|
423
|
+
if (binding.kind === "constant" &&
|
|
424
|
+
binding.onActivation === undefined &&
|
|
425
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
426
|
+
return binding.value;
|
|
427
|
+
}
|
|
428
|
+
const scope = binding.scope ?? "transient";
|
|
429
|
+
// Singleton cache check
|
|
430
|
+
if (scope === "singleton") {
|
|
431
|
+
if (this.#scope.hasSingleton(binding.id)) {
|
|
432
|
+
return this.#scope.getSingleton(binding.id);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Scoped cache check
|
|
436
|
+
if (scope === "scoped") {
|
|
437
|
+
if (!this.#scope.isChild) {
|
|
438
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
439
|
+
}
|
|
440
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
441
|
+
return this.#scope.getScoped(binding.id);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const frame = this.#getResolutionFrame(binding);
|
|
445
|
+
const tokenDisplayName = frame.tokenName;
|
|
446
|
+
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
447
|
+
resolutionStack.push(frame);
|
|
448
|
+
const needsActivation = this.#needsActivation(binding);
|
|
449
|
+
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
450
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options);
|
|
451
|
+
try {
|
|
452
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
453
|
+
if (dynamicResult instanceof Promise) {
|
|
454
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
455
|
+
}
|
|
456
|
+
resolutionStack.pop();
|
|
457
|
+
resolutionPath.pop();
|
|
458
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
459
|
+
return dynamicResult;
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
resolutionStack.pop();
|
|
463
|
+
resolutionPath.pop();
|
|
464
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
470
|
+
const resolutionCtx = needsResolutionContext
|
|
471
|
+
? this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options)
|
|
472
|
+
: undefined;
|
|
473
|
+
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
474
|
+
const shouldActivate = this.#refreshActivationCacheIfNeeded(binding, needsActivation);
|
|
475
|
+
const activated = shouldActivate
|
|
476
|
+
? this.#lifecycle.runActivationSync(resolutionCtx, binding, instance, this.#metadataReader)
|
|
477
|
+
: instance;
|
|
478
|
+
// Cache by scope
|
|
479
|
+
if (scope === "singleton") {
|
|
480
|
+
this.#scope.setSingleton(binding.id, activated);
|
|
481
|
+
}
|
|
482
|
+
else if (scope === "scoped") {
|
|
483
|
+
this.#scope.setScoped(binding.id, activated);
|
|
484
|
+
}
|
|
485
|
+
return activated;
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
resolutionStack.pop();
|
|
489
|
+
resolutionPath.pop();
|
|
490
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
#instantiateSync(binding, ctx, resolutionPath, resolutionStack) {
|
|
494
|
+
switch (binding.kind) {
|
|
495
|
+
case "constant":
|
|
496
|
+
return binding.value;
|
|
497
|
+
case "dynamic": {
|
|
498
|
+
if (ctx === undefined) {
|
|
499
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
500
|
+
}
|
|
501
|
+
const factoryResult = binding.factory(ctx);
|
|
502
|
+
if (factoryResult instanceof Promise) {
|
|
503
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
504
|
+
}
|
|
505
|
+
return factoryResult;
|
|
506
|
+
}
|
|
507
|
+
case "dynamic-async":
|
|
508
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
509
|
+
case "class": {
|
|
510
|
+
const deps = this.#resolveClassDeps(binding.target, resolutionPath, resolutionStack);
|
|
511
|
+
const instance = this.#instantiateClass(binding.target, deps);
|
|
512
|
+
return instance;
|
|
513
|
+
}
|
|
514
|
+
case "resolved": {
|
|
515
|
+
const deps = this.#resolveDescriptorDeps(binding.deps, resolutionPath, resolutionStack);
|
|
516
|
+
const factoryResult = binding.factory(...deps);
|
|
517
|
+
if (factoryResult instanceof Promise) {
|
|
518
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
519
|
+
}
|
|
520
|
+
return factoryResult;
|
|
521
|
+
}
|
|
522
|
+
case "resolved-async":
|
|
523
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
524
|
+
case "alias":
|
|
525
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
#resolveClassDeps(target, resolutionPath, resolutionStack) {
|
|
529
|
+
const meta = this.#getConstructorMetadata(target);
|
|
530
|
+
if (meta === undefined) {
|
|
531
|
+
if (target.length === 0) {
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
throw new MissingMetadataError(target.name);
|
|
535
|
+
}
|
|
536
|
+
if (meta.params.length === 0) {
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
if (meta.params.length === 1) {
|
|
540
|
+
const param = meta.params[0];
|
|
541
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
542
|
+
if (param.multi) {
|
|
543
|
+
return [this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
544
|
+
}
|
|
545
|
+
if (param.optional) {
|
|
546
|
+
return [this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
547
|
+
}
|
|
548
|
+
if (paramOptions === undefined) {
|
|
549
|
+
return [this.resolveFromContext(param.token, resolutionPath, resolutionStack)];
|
|
550
|
+
}
|
|
551
|
+
return [this.resolve(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
552
|
+
}
|
|
553
|
+
const deps = new Array(meta.params.length);
|
|
554
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
555
|
+
const param = meta.params[index];
|
|
556
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
557
|
+
if (param.multi) {
|
|
558
|
+
deps[index] = this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (param.optional) {
|
|
562
|
+
deps[index] = this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
deps[index] =
|
|
566
|
+
paramOptions === undefined
|
|
567
|
+
? this.resolveFromContext(param.token, resolutionPath, resolutionStack)
|
|
568
|
+
: this.resolve(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
569
|
+
}
|
|
570
|
+
return deps;
|
|
571
|
+
}
|
|
572
|
+
#resolveDescriptorDeps(deps, resolutionPath, resolutionStack) {
|
|
573
|
+
const resolved = new Array(deps.length);
|
|
574
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
575
|
+
const dep = deps[index];
|
|
576
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
577
|
+
if (dep.multi) {
|
|
578
|
+
resolved[index] = this.resolveAll(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (dep.optional) {
|
|
582
|
+
resolved[index] = this.resolveOptional(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
resolved[index] =
|
|
586
|
+
depOptions === undefined
|
|
587
|
+
? this.resolveFromContext(dep.token, resolutionPath, resolutionStack)
|
|
588
|
+
: this.resolve(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
589
|
+
}
|
|
590
|
+
return resolved;
|
|
591
|
+
}
|
|
592
|
+
resolveOptional(token, options, resolutionPath, resolutionStack) {
|
|
593
|
+
if (this.#findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
597
|
+
}
|
|
598
|
+
resolveAll(token, options, resolutionPath, resolutionStack) {
|
|
599
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
600
|
+
const namedCandidates = this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
601
|
+
if (namedCandidates.length === 0) {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
const resolved = new Array(namedCandidates.length);
|
|
605
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
606
|
+
resolved[index] = this.#resolveCandidateSync(namedCandidates[index], options, resolutionPath, resolutionStack);
|
|
607
|
+
}
|
|
608
|
+
return resolved;
|
|
609
|
+
}
|
|
610
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
611
|
+
if (allBindings.length === 0) {
|
|
612
|
+
return [];
|
|
613
|
+
}
|
|
614
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
615
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
616
|
+
const resolved = new Array(candidates.length);
|
|
617
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
618
|
+
resolved[index] = this.#resolveCandidateSync(candidates[index], options, resolutionPath, resolutionStack);
|
|
619
|
+
}
|
|
620
|
+
return resolved;
|
|
621
|
+
}
|
|
622
|
+
// ── Async resolve ──────────────────────────────────────────────────────────
|
|
623
|
+
resolveAsyncFromContext(token, resolutionPath, resolutionStack) {
|
|
624
|
+
// Hot lane: own-registry fast default (async chains resolve sibling dynamic bindings).
|
|
625
|
+
// Fall back to the chain-versioned memo only on miss or alias.
|
|
626
|
+
const fastBinding = this.#registry.getFastDefault(token);
|
|
627
|
+
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
628
|
+
// Inline the dominant chain shape — transient dynamic factory with no activation.
|
|
629
|
+
if ((fastBinding.kind === "dynamic-async" || fastBinding.kind === "dynamic") &&
|
|
630
|
+
fastBinding.scope === "transient" &&
|
|
631
|
+
fastBinding.onActivation === undefined &&
|
|
632
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(fastBinding.token))) {
|
|
633
|
+
return this.#resolveTransientDynamicAsyncFromContext(fastBinding, resolutionPath, resolutionStack);
|
|
634
|
+
}
|
|
635
|
+
return this.#resolveAsyncDefaultEntry(fastBinding, this, resolutionPath, resolutionStack);
|
|
636
|
+
}
|
|
637
|
+
const entry = this.#lookupDefaultEntry(token);
|
|
638
|
+
if (entry === null) {
|
|
639
|
+
return this.resolveAsync(token, undefined, resolutionPath, resolutionStack);
|
|
640
|
+
}
|
|
641
|
+
return this.#resolveAsyncDefaultEntry(entry.binding, entry.owner, resolutionPath, resolutionStack);
|
|
642
|
+
}
|
|
643
|
+
#resolveAsyncDefaultEntry(binding, owner, resolutionPath, resolutionStack) {
|
|
644
|
+
if (binding.kind === "constant" &&
|
|
645
|
+
binding.onActivation === undefined &&
|
|
646
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
647
|
+
return Promise.resolve(binding.value);
|
|
648
|
+
}
|
|
649
|
+
const scope = binding.scope ?? "transient";
|
|
650
|
+
if (scope === "transient") {
|
|
651
|
+
if ((binding.kind === "dynamic" || binding.kind === "dynamic-async") &&
|
|
652
|
+
binding.onActivation === undefined &&
|
|
653
|
+
(this.#lifecycle.activationVersion === 0 || !this.#lifecycle.hasActivationHandlers(binding.token))) {
|
|
654
|
+
return this.#resolveTransientDynamicAsyncFromContext(binding, resolutionPath, resolutionStack);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else if (scope === "singleton") {
|
|
658
|
+
const cachedSingleton = owner.#scope.peekSingleton(binding.id);
|
|
659
|
+
if (cachedSingleton !== SINGLETON_MISS) {
|
|
660
|
+
return Promise.resolve(cachedSingleton);
|
|
661
|
+
}
|
|
662
|
+
if (owner !== this) {
|
|
663
|
+
return owner.#resolveBindingAsync(binding, undefined, resolutionPath, resolutionStack);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
if (!this.#scope.isChild) {
|
|
668
|
+
return Promise.reject(new MissingScopeContextError(this.#getTokenName(binding.token)));
|
|
669
|
+
}
|
|
670
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
671
|
+
return Promise.resolve(this.#scope.getScoped(binding.id));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return this.#resolveBindingAsync(binding, undefined, resolutionPath, resolutionStack);
|
|
675
|
+
}
|
|
676
|
+
async resolveAsync(token, options, resolutionPath, resolutionStack) {
|
|
677
|
+
const found = this.#findBinding(token, options, resolutionPath, resolutionStack);
|
|
678
|
+
if (found === undefined) {
|
|
679
|
+
const ownBindings = this.#registry.getAll(token);
|
|
680
|
+
if (ownBindings.length > 0) {
|
|
681
|
+
throw new NoMatchingBindingError(this.#getTokenName(token), options ?? {}, this.#getAvailableSlots(token));
|
|
682
|
+
}
|
|
683
|
+
throw new TokenNotBoundError(this.#getTokenName(token));
|
|
684
|
+
}
|
|
685
|
+
let currentToken = token;
|
|
686
|
+
let visitedAliasTokens;
|
|
687
|
+
let aliasFollowed = found;
|
|
688
|
+
while (aliasFollowed !== undefined && aliasFollowed.binding.kind === "alias") {
|
|
689
|
+
const target = aliasFollowed.binding.target;
|
|
690
|
+
visitedAliasTokens ??= new Set([currentToken]);
|
|
691
|
+
if (visitedAliasTokens.has(target)) {
|
|
692
|
+
throw new CircularDependencyError([...visitedAliasTokens, target].map((entry) => tokenName(entry)));
|
|
693
|
+
}
|
|
694
|
+
visitedAliasTokens.add(target);
|
|
695
|
+
currentToken = target;
|
|
696
|
+
aliasFollowed = this.#findBinding(currentToken, options, resolutionPath, resolutionStack);
|
|
697
|
+
}
|
|
698
|
+
if (aliasFollowed === undefined) {
|
|
699
|
+
const ownBindings = this.#registry.getAll(currentToken);
|
|
700
|
+
if (ownBindings.length > 0) {
|
|
701
|
+
throw new NoMatchingBindingError(this.#getTokenName(currentToken), options ?? {}, this.#getAvailableSlots(currentToken));
|
|
702
|
+
}
|
|
703
|
+
throw new TokenNotBoundError(this.#getTokenName(currentToken));
|
|
704
|
+
}
|
|
705
|
+
const { binding, owner } = aliasFollowed;
|
|
706
|
+
const scope = binding.scope ?? "transient";
|
|
707
|
+
if (scope === "singleton" && owner !== this) {
|
|
708
|
+
return owner.#resolveBindingAsync(binding, options, resolutionPath, resolutionStack);
|
|
709
|
+
}
|
|
710
|
+
return this.#resolveBindingAsync(binding, options, resolutionPath, resolutionStack);
|
|
711
|
+
}
|
|
712
|
+
async #resolveBindingAsync(binding, options, resolutionPath, resolutionStack) {
|
|
713
|
+
if (binding.kind === "constant" &&
|
|
714
|
+
binding.onActivation === undefined &&
|
|
715
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
716
|
+
return binding.value;
|
|
717
|
+
}
|
|
718
|
+
const scope = binding.scope ?? "transient";
|
|
719
|
+
// Singleton cache
|
|
720
|
+
if (scope === "singleton") {
|
|
721
|
+
if (this.#scope.hasSingleton(binding.id)) {
|
|
722
|
+
return this.#scope.getSingleton(binding.id);
|
|
723
|
+
}
|
|
724
|
+
// In-flight dedup
|
|
725
|
+
const inflight = this.#scope.getInflight(binding.id);
|
|
726
|
+
if (inflight !== undefined) {
|
|
727
|
+
return inflight;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
// Scoped cache
|
|
731
|
+
if (scope === "scoped") {
|
|
732
|
+
if (!this.#scope.isChild) {
|
|
733
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
734
|
+
}
|
|
735
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
736
|
+
return this.#scope.getScoped(binding.id);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const frame = this.#getResolutionFrame(binding);
|
|
740
|
+
const frameName = frame.tokenName;
|
|
741
|
+
const resolutionSet = enterResolutionPath(resolutionPath, frameName, false);
|
|
742
|
+
resolutionStack.push(frame);
|
|
743
|
+
const needsActivation = this.#needsActivation(binding);
|
|
744
|
+
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
745
|
+
const resolutionCtx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
746
|
+
try {
|
|
747
|
+
if (binding.kind === "dynamic-async") {
|
|
748
|
+
return await binding.factory(resolutionCtx);
|
|
749
|
+
}
|
|
750
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
751
|
+
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
752
|
+
}
|
|
753
|
+
finally {
|
|
754
|
+
resolutionStack.pop();
|
|
755
|
+
resolutionPath.pop();
|
|
756
|
+
resolutionSet?.delete(frameName);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
760
|
+
const resolutionCtx = needsResolutionContext
|
|
761
|
+
? new DefaultResolutionContext(this, resolutionPath, resolutionStack, options)
|
|
762
|
+
: undefined;
|
|
763
|
+
try {
|
|
764
|
+
if (scope === "singleton") {
|
|
765
|
+
const createSingletonPromise = async () => {
|
|
766
|
+
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
767
|
+
const shouldActivate = this.#refreshActivationCacheIfNeeded(binding, needsActivation);
|
|
768
|
+
const activated = shouldActivate
|
|
769
|
+
? await this.#lifecycle.runActivation(resolutionCtx, binding, instance, this.#metadataReader)
|
|
770
|
+
: instance;
|
|
771
|
+
this.#scope.setSingleton(binding.id, activated);
|
|
772
|
+
this.#scope.clearInflight(binding.id);
|
|
773
|
+
return activated;
|
|
774
|
+
};
|
|
775
|
+
const singletonPromise = createSingletonPromise().catch((err) => {
|
|
776
|
+
this.#scope.clearInflight(binding.id);
|
|
777
|
+
throw err;
|
|
778
|
+
});
|
|
779
|
+
this.#scope.setInflight(binding.id, singletonPromise);
|
|
780
|
+
return await singletonPromise;
|
|
781
|
+
}
|
|
782
|
+
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
783
|
+
const shouldActivate = this.#refreshActivationCacheIfNeeded(binding, needsActivation);
|
|
784
|
+
const activated = shouldActivate
|
|
785
|
+
? await this.#lifecycle.runActivation(resolutionCtx, binding, instance, this.#metadataReader)
|
|
786
|
+
: instance;
|
|
787
|
+
if (scope === "scoped") {
|
|
788
|
+
this.#scope.setScoped(binding.id, activated);
|
|
789
|
+
}
|
|
790
|
+
return activated;
|
|
791
|
+
}
|
|
792
|
+
finally {
|
|
793
|
+
resolutionStack.pop();
|
|
794
|
+
resolutionPath.pop();
|
|
795
|
+
resolutionSet?.delete(frameName);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
async #instantiateAsync(binding, ctx, resolutionPath, resolutionStack) {
|
|
799
|
+
switch (binding.kind) {
|
|
800
|
+
case "constant":
|
|
801
|
+
return binding.value;
|
|
802
|
+
case "dynamic": {
|
|
803
|
+
if (ctx === undefined) {
|
|
804
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
805
|
+
}
|
|
806
|
+
const factoryResult = binding.factory(ctx);
|
|
807
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
808
|
+
}
|
|
809
|
+
case "dynamic-async":
|
|
810
|
+
if (ctx === undefined) {
|
|
811
|
+
throw new InternalError("dynamic-async binding requires resolution context");
|
|
812
|
+
}
|
|
813
|
+
return binding.factory(ctx);
|
|
814
|
+
case "class": {
|
|
815
|
+
const deps = await this.#resolveClassDepsAsync(binding.target, resolutionPath, resolutionStack);
|
|
816
|
+
const instance = this.#instantiateClass(binding.target, deps);
|
|
817
|
+
return instance;
|
|
818
|
+
}
|
|
819
|
+
case "resolved": {
|
|
820
|
+
if (ctx === undefined) {
|
|
821
|
+
throw new InternalError("resolved binding requires resolution context");
|
|
822
|
+
}
|
|
823
|
+
const deps = await this.#resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
824
|
+
const factoryResult = binding.factory(...deps);
|
|
825
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
826
|
+
}
|
|
827
|
+
case "resolved-async": {
|
|
828
|
+
const deps = await this.#resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
829
|
+
return binding.factory(...deps);
|
|
830
|
+
}
|
|
831
|
+
case "alias":
|
|
832
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async #resolveClassDepsAsync(target, resolutionPath, resolutionStack) {
|
|
836
|
+
const meta = this.#getConstructorMetadata(target);
|
|
837
|
+
if (meta === undefined) {
|
|
838
|
+
if (target.length === 0) {
|
|
839
|
+
return [];
|
|
840
|
+
}
|
|
841
|
+
throw new MissingMetadataError(target.name);
|
|
842
|
+
}
|
|
843
|
+
if (meta.params.length === 0) {
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
if (meta.params.length === 1) {
|
|
847
|
+
const param = meta.params[0];
|
|
848
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
849
|
+
if (param.multi) {
|
|
850
|
+
return [await this.resolveAllAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
851
|
+
}
|
|
852
|
+
if (param.optional) {
|
|
853
|
+
return [await this.resolveOptionalAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
854
|
+
}
|
|
855
|
+
if (paramOptions === undefined) {
|
|
856
|
+
return [await this.resolveAsyncFromContext(param.token, resolutionPath, resolutionStack)];
|
|
857
|
+
}
|
|
858
|
+
return [await this.resolveAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
859
|
+
}
|
|
860
|
+
const pending = new Array(meta.params.length);
|
|
861
|
+
const shouldCloneContext = meta.params.length > 1;
|
|
862
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
863
|
+
const param = meta.params[index];
|
|
864
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
865
|
+
if (param.multi) {
|
|
866
|
+
pending[index] = this.resolveAllAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
867
|
+
}
|
|
868
|
+
else if (param.optional) {
|
|
869
|
+
pending[index] = this.resolveOptionalAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
pending[index] =
|
|
873
|
+
paramOptions === undefined
|
|
874
|
+
? this.resolveAsyncFromContext(param.token, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack)
|
|
875
|
+
: this.resolveAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return Promise.all(pending);
|
|
879
|
+
}
|
|
880
|
+
async #resolveDescriptorDepsAsync(deps, resolutionPath, resolutionStack) {
|
|
881
|
+
const pending = new Array(deps.length);
|
|
882
|
+
const shouldCloneContext = deps.length > 1;
|
|
883
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
884
|
+
const dep = deps[index];
|
|
885
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
886
|
+
if (dep.multi) {
|
|
887
|
+
pending[index] = this.resolveAllAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
888
|
+
}
|
|
889
|
+
else if (dep.optional) {
|
|
890
|
+
pending[index] = this.resolveOptionalAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
pending[index] =
|
|
894
|
+
depOptions === undefined
|
|
895
|
+
? this.resolveAsyncFromContext(dep.token, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack)
|
|
896
|
+
: this.resolveAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return Promise.all(pending);
|
|
900
|
+
}
|
|
901
|
+
async resolveOptionalAsync(token, options, resolutionPath, resolutionStack) {
|
|
902
|
+
if (this.#findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
return this.resolveAsync(token, options, resolutionPath, resolutionStack);
|
|
906
|
+
}
|
|
907
|
+
async resolveAllAsync(token, options, resolutionPath, resolutionStack) {
|
|
908
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
909
|
+
const namedCandidates = this.#getSimpleNamedBindingsFromChain(token, options.name);
|
|
910
|
+
if (namedCandidates.length === 0) {
|
|
911
|
+
return [];
|
|
912
|
+
}
|
|
913
|
+
const pending = new Array(namedCandidates.length);
|
|
914
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
915
|
+
pending[index] = this.#resolveCandidateAsync(namedCandidates[index], options, resolutionPath, resolutionStack);
|
|
916
|
+
}
|
|
917
|
+
return Promise.all(pending);
|
|
918
|
+
}
|
|
919
|
+
const allBindings = this.#getAllBindingsFromChain(token);
|
|
920
|
+
if (allBindings.length === 0) {
|
|
921
|
+
return [];
|
|
922
|
+
}
|
|
923
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
924
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
925
|
+
const pending = new Array(candidates.length);
|
|
926
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
927
|
+
pending[index] = this.#resolveCandidateAsync(candidates[index], options, resolutionPath, resolutionStack);
|
|
928
|
+
}
|
|
929
|
+
return Promise.all(pending);
|
|
930
|
+
}
|
|
931
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
932
|
+
#getAllBindingsFromChain(token) {
|
|
933
|
+
const ownBindings = this.#registry.getAll(token);
|
|
934
|
+
if (this.#parent === undefined) {
|
|
935
|
+
return ownBindings;
|
|
936
|
+
}
|
|
937
|
+
const result = [...ownBindings];
|
|
938
|
+
let current = this.#parent;
|
|
939
|
+
while (current !== undefined) {
|
|
940
|
+
const own = current.#registry.getAll(token);
|
|
941
|
+
if (own.length > 0) {
|
|
942
|
+
result.push(...own);
|
|
943
|
+
}
|
|
944
|
+
current = current.#parent;
|
|
945
|
+
}
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
#getSimpleNamedBindingsFromChain(token, name) {
|
|
949
|
+
const ownBinding = this.#registry.getSimpleNamed(token, name);
|
|
950
|
+
if (this.#parent === undefined) {
|
|
951
|
+
return ownBinding !== undefined ? [ownBinding] : [];
|
|
952
|
+
}
|
|
953
|
+
const result = [];
|
|
954
|
+
if (ownBinding !== undefined) {
|
|
955
|
+
result.push(ownBinding);
|
|
956
|
+
}
|
|
957
|
+
let current = this.#parent;
|
|
958
|
+
while (current !== undefined) {
|
|
959
|
+
const binding = current.#registry.getSimpleNamed(token, name);
|
|
960
|
+
if (binding !== undefined) {
|
|
961
|
+
result.push(binding);
|
|
962
|
+
}
|
|
963
|
+
current = current.#parent;
|
|
964
|
+
}
|
|
965
|
+
return result;
|
|
966
|
+
}
|
|
967
|
+
#getAvailableSlots(token) {
|
|
968
|
+
return this.#registry.availableSlotStrings(token);
|
|
969
|
+
}
|
|
970
|
+
#makeConstraintContext(resolutionPath, resolutionStack, options) {
|
|
971
|
+
if (options === undefined && resolutionPath.length === 0 && resolutionStack.length === 0) {
|
|
972
|
+
return ROOT_CONSTRAINT_CONTEXT;
|
|
973
|
+
}
|
|
974
|
+
const parent = resolutionStack.at(-1);
|
|
975
|
+
const ancestors = resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [];
|
|
976
|
+
return {
|
|
977
|
+
resolutionPath,
|
|
978
|
+
resolutionStack,
|
|
979
|
+
parent,
|
|
980
|
+
ancestors,
|
|
981
|
+
currentResolveOptions: options,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
#matchesBindingFast(binding, options, resolutionPath, resolutionStack) {
|
|
985
|
+
if (!this.#matchesSlotFast(binding.slot, options)) {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
if (binding.predicate === undefined) {
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
const ctx = this.#makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
992
|
+
return binding.predicate(ctx);
|
|
993
|
+
}
|
|
994
|
+
#matchesSlotFast(slot, options) {
|
|
995
|
+
const requestedName = options?.name;
|
|
996
|
+
const requestedTags = options?.tags;
|
|
997
|
+
const singleRequestedTag = options?.tag;
|
|
998
|
+
const hasRequestedTags = (requestedTags?.length ?? 0) > 0 || singleRequestedTag !== undefined;
|
|
999
|
+
if (slot.name !== undefined) {
|
|
1000
|
+
if (requestedName === undefined || slot.name !== requestedName) {
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
else if (requestedName !== undefined) {
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
if (slot.tags.length > 0) {
|
|
1008
|
+
if (!hasRequestedTags) {
|
|
1009
|
+
return false;
|
|
1010
|
+
}
|
|
1011
|
+
for (const [tagKey, tagValue] of slot.tags) {
|
|
1012
|
+
if (!this.#matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag)) {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
else if (hasRequestedTags) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
#getTokenName(token) {
|
|
1023
|
+
return tokenName(token);
|
|
1024
|
+
}
|
|
1025
|
+
#getConstructorMetadata(target) {
|
|
1026
|
+
const cached = this.#classConstructorMetadata.get(target);
|
|
1027
|
+
if (cached !== undefined) {
|
|
1028
|
+
return cached === null ? undefined : cached;
|
|
1029
|
+
}
|
|
1030
|
+
const metadata = this.#metadataReader.getConstructorMetadata(target);
|
|
1031
|
+
this.#classConstructorMetadata.set(target, metadata ?? null);
|
|
1032
|
+
return metadata;
|
|
1033
|
+
}
|
|
1034
|
+
#instantiateClass(target, deps) {
|
|
1035
|
+
let needsActiveContainer = this.#classNeedsActiveContainer.get(target);
|
|
1036
|
+
if (needsActiveContainer === undefined) {
|
|
1037
|
+
const accessorMetadata = this.#metadataReader.getAccessorMetadata?.(target);
|
|
1038
|
+
needsActiveContainer = (accessorMetadata?.length ?? 0) > 0;
|
|
1039
|
+
this.#classNeedsActiveContainer.set(target, needsActiveContainer);
|
|
1040
|
+
}
|
|
1041
|
+
const invokable = target;
|
|
1042
|
+
if (!needsActiveContainer) {
|
|
1043
|
+
return new invokable(...deps);
|
|
1044
|
+
}
|
|
1045
|
+
return runWithContainer(this.#container, () => new invokable(...deps));
|
|
1046
|
+
}
|
|
1047
|
+
#matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag) {
|
|
1048
|
+
if (singleRequestedTag !== undefined &&
|
|
1049
|
+
singleRequestedTag[0] === tagKey &&
|
|
1050
|
+
Object.is(singleRequestedTag[1], tagValue)) {
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
if (requestedTags === undefined || requestedTags.length === 0) {
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
for (let index = 0; index < requestedTags.length; index += 1) {
|
|
1057
|
+
const requestedTag = requestedTags[index];
|
|
1058
|
+
if (requestedTag[0] === tagKey && Object.is(requestedTag[1], tagValue)) {
|
|
1059
|
+
return true;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
return false;
|
|
1063
|
+
}
|
|
1064
|
+
#resolveTransientDynamicSyncFromContext(binding, resolutionPath, resolutionStack) {
|
|
1065
|
+
// One lane at every depth. The separate deep lane existed because cycle detection used to be
|
|
1066
|
+
// an O(depth) `resolutionPath.includes()` scan, which had to be escaped past ~32 levels; with
|
|
1067
|
+
// the O(1) `binding.inFlight` mark there is nothing to escape, so the depth split — and the
|
|
1068
|
+
// divergent behaviour it caused — is gone.
|
|
1069
|
+
const frame = this.#getResolutionFrame(binding);
|
|
1070
|
+
const tokenDisplayName = frame.tokenName;
|
|
1071
|
+
if (binding.inFlight) {
|
|
1072
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
1073
|
+
}
|
|
1074
|
+
binding.inFlight = true;
|
|
1075
|
+
resolutionPath.push(tokenDisplayName);
|
|
1076
|
+
resolutionStack.push(frame);
|
|
1077
|
+
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
1078
|
+
try {
|
|
1079
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
1080
|
+
if (dynamicResult instanceof Promise) {
|
|
1081
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
1082
|
+
}
|
|
1083
|
+
return dynamicResult;
|
|
1084
|
+
}
|
|
1085
|
+
finally {
|
|
1086
|
+
resolutionStack.pop();
|
|
1087
|
+
resolutionPath.pop();
|
|
1088
|
+
binding.inFlight = false;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
// NOT declared `async` — avoids creating a JSAsyncGeneratorObject + implicit Promise wrapper on
|
|
1092
|
+
// every invocation. Cleanup is handled via .then(onFulfilled, onRejected) so the behaviour is
|
|
1093
|
+
// identical to a try/finally but without the async machinery overhead.
|
|
1094
|
+
#resolveTransientDynamicAsyncFromContext(binding, resolutionPath, resolutionStack) {
|
|
1095
|
+
// One lane at every depth. Cycle detection goes through `enterResolutionPath`, which is
|
|
1096
|
+
// path-scoped — the only mechanism that stays correct when chains interleave (Promise.all) —
|
|
1097
|
+
// and adapts on its own: a linear scan while the path is short, an attached Set past
|
|
1098
|
+
// RESOLUTION_SET_THRESHOLD. That removes the depth split, and with it a silent change of
|
|
1099
|
+
// behaviour (context identity, stack frames, promise shape) at the old threshold.
|
|
1100
|
+
//
|
|
1101
|
+
// For a sequential chain every level shares one resolutionPath/resolutionStack, so a single
|
|
1102
|
+
// DefaultResolutionContext serves the whole chain: inner levels of the owning chain allocate
|
|
1103
|
+
// nothing. A concurrent chain is detected by path identity and gets its own context.
|
|
1104
|
+
const frame = this.#getResolutionFrame(binding);
|
|
1105
|
+
const tokenDisplayName = frame.tokenName;
|
|
1106
|
+
try {
|
|
1107
|
+
enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
1108
|
+
}
|
|
1109
|
+
catch (cycleError) {
|
|
1110
|
+
// This method is not `async`; keep failures as rejections rather than sync throws.
|
|
1111
|
+
return Promise.reject(cycleError);
|
|
1112
|
+
}
|
|
1113
|
+
let ctx;
|
|
1114
|
+
let isOwnerLevel;
|
|
1115
|
+
if (this.#asyncChainCtxPath === resolutionPath) {
|
|
1116
|
+
ctx = this.#asyncChainCtx;
|
|
1117
|
+
isOwnerLevel = true;
|
|
1118
|
+
}
|
|
1119
|
+
else if (this.#asyncChainCtxPath === undefined) {
|
|
1120
|
+
const existing = this.#asyncChainCtx;
|
|
1121
|
+
if (existing === undefined) {
|
|
1122
|
+
ctx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1123
|
+
this.#asyncChainCtx = ctx;
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
existing.reset(this, resolutionPath, resolutionStack, undefined);
|
|
1127
|
+
ctx = existing;
|
|
1128
|
+
}
|
|
1129
|
+
this.#asyncChainCtxPath = resolutionPath;
|
|
1130
|
+
isOwnerLevel = true;
|
|
1131
|
+
}
|
|
1132
|
+
else {
|
|
1133
|
+
ctx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1134
|
+
isOwnerLevel = false;
|
|
1135
|
+
}
|
|
1136
|
+
if (isOwnerLevel) {
|
|
1137
|
+
this.#asyncChainActiveLevels++;
|
|
1138
|
+
}
|
|
1139
|
+
// Invoke the factory synchronously to get its Promise (or a resolved value for "dynamic").
|
|
1140
|
+
let factoryPromise;
|
|
1141
|
+
try {
|
|
1142
|
+
if (binding.kind === "dynamic-async") {
|
|
1143
|
+
factoryPromise = binding.factory(ctx);
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
const factoryResult = binding.factory(ctx);
|
|
1147
|
+
factoryPromise =
|
|
1148
|
+
factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
catch (factoryError) {
|
|
1152
|
+
// Synchronous throw from the factory (rare) — clean up immediately.
|
|
1153
|
+
exitResolutionPath(resolutionPath);
|
|
1154
|
+
if (isOwnerLevel && --this.#asyncChainActiveLevels === 0) {
|
|
1155
|
+
this.#asyncChainCtxPath = undefined;
|
|
1156
|
+
this.#asyncChainSettle = undefined;
|
|
1157
|
+
}
|
|
1158
|
+
return Promise.reject(factoryError);
|
|
1159
|
+
}
|
|
1160
|
+
// Cleanup runs as a SIDE listener on the factory promise instead of a derived-promise chain:
|
|
1161
|
+
// registered synchronously here, it is FIFO-guaranteed to run before the awaiting caller
|
|
1162
|
+
// resumes, so ordering is identical while saving one intermediate promise and one microtask
|
|
1163
|
+
// hop per level. Trade-off: the settle handler marks a rejection as handled, so an unawaited
|
|
1164
|
+
// failing resolveAsync no longer surfaces as an unhandledRejection — callers are expected to
|
|
1165
|
+
// await (or .catch) the returned promise.
|
|
1166
|
+
//
|
|
1167
|
+
// Every level of the owning chain unwinds identically, so one closure serves them all.
|
|
1168
|
+
let settle;
|
|
1169
|
+
if (isOwnerLevel) {
|
|
1170
|
+
settle =
|
|
1171
|
+
this.#asyncChainSettle ??
|
|
1172
|
+
(this.#asyncChainSettle = () => {
|
|
1173
|
+
exitResolutionPath(resolutionPath);
|
|
1174
|
+
if (--this.#asyncChainActiveLevels === 0) {
|
|
1175
|
+
this.#asyncChainCtxPath = undefined;
|
|
1176
|
+
this.#asyncChainSettle = undefined;
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
else {
|
|
1181
|
+
settle = () => {
|
|
1182
|
+
exitResolutionPath(resolutionPath);
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
factoryPromise.then(settle, settle);
|
|
1186
|
+
return factoryPromise;
|
|
1187
|
+
}
|
|
1188
|
+
#resolveCandidateSync(binding, options, resolutionPath, resolutionStack) {
|
|
1189
|
+
if (binding.kind === "constant" &&
|
|
1190
|
+
binding.onActivation === undefined &&
|
|
1191
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
1192
|
+
return binding.value;
|
|
1193
|
+
}
|
|
1194
|
+
if (binding.kind === "alias") {
|
|
1195
|
+
return this.resolve(binding.target, options, resolutionPath, resolutionStack);
|
|
1196
|
+
}
|
|
1197
|
+
const scope = binding.scope ?? "transient";
|
|
1198
|
+
if (scope === "singleton" && this.#scope.hasSingleton(binding.id)) {
|
|
1199
|
+
return this.#scope.getSingleton(binding.id);
|
|
1200
|
+
}
|
|
1201
|
+
if (scope === "scoped") {
|
|
1202
|
+
if (!this.#scope.isChild) {
|
|
1203
|
+
throw new MissingScopeContextError(this.#getTokenName(binding.token));
|
|
1204
|
+
}
|
|
1205
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
1206
|
+
return this.#scope.getScoped(binding.id);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return this.#resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
1210
|
+
}
|
|
1211
|
+
#resolveCandidateAsync(binding, options, resolutionPath, resolutionStack) {
|
|
1212
|
+
if (binding.kind === "constant" &&
|
|
1213
|
+
binding.onActivation === undefined &&
|
|
1214
|
+
!this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
1215
|
+
return Promise.resolve(binding.value);
|
|
1216
|
+
}
|
|
1217
|
+
const isolatedPath = [...resolutionPath];
|
|
1218
|
+
const isolatedStack = [...resolutionStack];
|
|
1219
|
+
if (binding.kind === "alias") {
|
|
1220
|
+
return this.resolveAsync(binding.target, options, isolatedPath, isolatedStack);
|
|
1221
|
+
}
|
|
1222
|
+
const scope = binding.scope ?? "transient";
|
|
1223
|
+
if (scope === "singleton" && this.#scope.hasSingleton(binding.id)) {
|
|
1224
|
+
return Promise.resolve(this.#scope.getSingleton(binding.id));
|
|
1225
|
+
}
|
|
1226
|
+
if (scope === "scoped") {
|
|
1227
|
+
if (!this.#scope.isChild) {
|
|
1228
|
+
return Promise.reject(new MissingScopeContextError(this.#getTokenName(binding.token)));
|
|
1229
|
+
}
|
|
1230
|
+
if (this.#scope.hasScoped(binding.id)) {
|
|
1231
|
+
return Promise.resolve(this.#scope.getScoped(binding.id));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return this.#resolveBindingAsync(binding, options, isolatedPath, isolatedStack);
|
|
1235
|
+
}
|
|
1236
|
+
#getResolutionFrame(binding) {
|
|
1237
|
+
// Memoized on the binding rather than in a per-resolver Map: the frame derives only from
|
|
1238
|
+
// immutable binding fields, so it is identical for every resolver, and a field read beats a
|
|
1239
|
+
// Map lookup on every hop of a chain.
|
|
1240
|
+
const existing = binding.frame;
|
|
1241
|
+
if (existing !== undefined) {
|
|
1242
|
+
return existing;
|
|
1243
|
+
}
|
|
1244
|
+
const scope = binding.scope ?? "transient";
|
|
1245
|
+
const frame = buildResolutionFrame(tokenName(binding.token), scope, binding.id, binding.kind, binding.slot);
|
|
1246
|
+
binding.frame = frame;
|
|
1247
|
+
return frame;
|
|
1248
|
+
}
|
|
1249
|
+
#needsActivation(binding) {
|
|
1250
|
+
const lifecycleVersion = this.#lifecycle.activationVersion;
|
|
1251
|
+
if (lifecycleVersion === 0 &&
|
|
1252
|
+
binding.kind !== "class" &&
|
|
1253
|
+
binding.kind !== "alias" &&
|
|
1254
|
+
binding.onActivation === undefined) {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
if (this.#activationCacheVersion !== lifecycleVersion) {
|
|
1258
|
+
this.#activationNeedByBindingId.clear();
|
|
1259
|
+
this.#activationCacheVersion = lifecycleVersion;
|
|
1260
|
+
}
|
|
1261
|
+
const cached = this.#activationNeedByBindingId.get(binding.id);
|
|
1262
|
+
if (cached !== undefined) {
|
|
1263
|
+
return cached;
|
|
1264
|
+
}
|
|
1265
|
+
if (binding.kind === "class") {
|
|
1266
|
+
let hasActivation = this.#lifecycle.hasActivationHandlers(binding.token) || binding.onActivation !== undefined;
|
|
1267
|
+
const cachedPostConstruct = this.#classHasPostConstruct.get(binding.target);
|
|
1268
|
+
// Unknown class lifecycle metadata: activate once, then cache after first instantiation.
|
|
1269
|
+
if (cachedPostConstruct === undefined) {
|
|
1270
|
+
hasActivation = true;
|
|
1271
|
+
}
|
|
1272
|
+
else if (cachedPostConstruct) {
|
|
1273
|
+
hasActivation = true;
|
|
1274
|
+
}
|
|
1275
|
+
this.#activationNeedByBindingId.set(binding.id, hasActivation);
|
|
1276
|
+
return hasActivation;
|
|
1277
|
+
}
|
|
1278
|
+
let hasActivation = false;
|
|
1279
|
+
if (binding.kind !== "alias" && binding.onActivation !== undefined) {
|
|
1280
|
+
hasActivation = true;
|
|
1281
|
+
}
|
|
1282
|
+
else if (this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
1283
|
+
hasActivation = true;
|
|
1284
|
+
}
|
|
1285
|
+
this.#activationNeedByBindingId.set(binding.id, hasActivation);
|
|
1286
|
+
return hasActivation;
|
|
1287
|
+
}
|
|
1288
|
+
#refreshClassPostConstructCache(target) {
|
|
1289
|
+
const lifecycle = this.#metadataReader.getLifecycleMetadata(target);
|
|
1290
|
+
const hasPostConstruct = lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0;
|
|
1291
|
+
this.#classHasPostConstruct.set(target, hasPostConstruct);
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Refreshes the post-construct cache for class bindings on first instantiation and
|
|
1295
|
+
* returns the (possibly updated) shouldActivate flag.
|
|
1296
|
+
*/
|
|
1297
|
+
#refreshActivationCacheIfNeeded(binding, needsActivation) {
|
|
1298
|
+
if (binding.kind === "class" && this.#classHasPostConstruct.get(binding.target) === undefined) {
|
|
1299
|
+
this.#refreshClassPostConstructCache(binding.target);
|
|
1300
|
+
this.#activationNeedByBindingId.delete(binding.id);
|
|
1301
|
+
return this.#needsActivation(binding);
|
|
1302
|
+
}
|
|
1303
|
+
return needsActivation;
|
|
1304
|
+
}
|
|
1305
|
+
#requiresResolutionContext(binding) {
|
|
1306
|
+
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
1307
|
+
}
|
|
1308
|
+
#acquireSyncResolutionContext(resolutionPath, resolutionStack, options) {
|
|
1309
|
+
const depth = resolutionStack.length;
|
|
1310
|
+
const existing = this.#syncResolutionContextPool[depth];
|
|
1311
|
+
if (existing !== undefined) {
|
|
1312
|
+
existing.reset(this, resolutionPath, resolutionStack, options);
|
|
1313
|
+
return existing;
|
|
1314
|
+
}
|
|
1315
|
+
const created = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
1316
|
+
this.#syncResolutionContextPool[depth] = created;
|
|
1317
|
+
return created;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
//# sourceMappingURL=resolver.js.map
|