@codefast/di 1.0.0-canary.6 → 1.0.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 +2 -0
- package/README.md +67 -499
- package/dist/binding-scope.d.ts +10 -0
- package/dist/binding-scope.d.ts.map +1 -0
- package/dist/binding-scope.js +24 -0
- package/dist/binding-scope.js.map +1 -0
- package/dist/binding-select.d.ts +16 -0
- package/dist/binding-select.d.ts.map +1 -0
- package/dist/binding-select.js +130 -0
- package/dist/binding-select.js.map +1 -0
- package/dist/binding.d.ts +194 -0
- package/dist/binding.d.ts.map +1 -0
- package/dist/binding.js +46 -0
- package/dist/binding.js.map +1 -0
- package/dist/constraints.d.ts +51 -0
- package/dist/constraints.d.ts.map +1 -0
- package/dist/constraints.js +88 -0
- package/dist/constraints.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.d.ts +58 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +853 -0
- package/dist/container.js.map +1 -0
- package/dist/decorators/inject.d.ts +46 -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/dependency-graph.d.ts +40 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +66 -0
- package/dist/dependency-graph.js.map +1 -0
- package/dist/environment.d.ts +51 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +100 -0
- package/dist/environment.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/graph-adapters/cytoscape.d.ts +33 -0
- package/dist/graph-adapters/cytoscape.d.ts.map +1 -0
- package/dist/graph-adapters/cytoscape.js +30 -0
- package/dist/graph-adapters/cytoscape.js.map +1 -0
- package/dist/graph-adapters/dot.d.ts +6 -0
- package/dist/graph-adapters/dot.d.ts.map +1 -0
- package/dist/graph-adapters/dot.js +18 -0
- package/dist/graph-adapters/dot.js.map +1 -0
- package/dist/graph-adapters/reactflow.d.ts +38 -0
- package/dist/graph-adapters/reactflow.d.ts.map +1 -0
- package/dist/graph-adapters/reactflow.js +23 -0
- package/dist/graph-adapters/reactflow.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +44 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +82 -0
- package/dist/inspector.js.map +1 -0
- package/dist/lifecycle.d.ts +21 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +174 -0
- package/dist/lifecycle.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 +60 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +47 -0
- package/dist/module.js.map +1 -0
- package/dist/registry.d.ts +41 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +208 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolve-options.d.ts +19 -0
- package/dist/resolve-options.d.ts.map +1 -0
- package/dist/resolve-options.js +32 -0
- package/dist/resolve-options.js.map +1 -0
- package/dist/resolver.d.ts +90 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +1208 -0
- package/dist/resolver.js.map +1 -0
- package/dist/scope.d.ts +25 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +61 -0
- package/dist/scope.js.map +1 -0
- package/dist/token.d.ts +23 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +25 -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 +89 -80
- 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/dist/resolver.js
ADDED
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
import { selectAllBindings, selectBinding } from "#/binding-select";
|
|
2
|
+
import { buildResolutionFrame, DefaultResolutionContext, runWithContainer } from "#/environment";
|
|
3
|
+
import { AsyncResolutionError, CircularDependencyError, InternalError, MissingMetadataError, MissingScopeContextError, NoMatchingBindingError, TokenNotBoundError, } from "#/errors";
|
|
4
|
+
import { injectionSlotToResolveOptions } from "#/resolve-options";
|
|
5
|
+
import { tokenName } from "#/token";
|
|
6
|
+
const RESOLUTION_SET_KEY = Symbol("di:resolution-set");
|
|
7
|
+
const RESOLUTION_SET_THRESHOLD = 32;
|
|
8
|
+
const EMPTY_STRING_LIST = [];
|
|
9
|
+
const EMPTY_FRAME_LIST = [];
|
|
10
|
+
const ROOT_CONSTRAINT_CONTEXT = {
|
|
11
|
+
resolutionPath: EMPTY_STRING_LIST,
|
|
12
|
+
resolutionStack: EMPTY_FRAME_LIST,
|
|
13
|
+
parent: undefined,
|
|
14
|
+
ancestors: EMPTY_FRAME_LIST,
|
|
15
|
+
currentResolveOptions: undefined,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* @since 0.3.16-canary.0
|
|
19
|
+
*/
|
|
20
|
+
export class DependencyResolver {
|
|
21
|
+
_registry;
|
|
22
|
+
_scope;
|
|
23
|
+
_lifecycle;
|
|
24
|
+
_metadataReader;
|
|
25
|
+
_container;
|
|
26
|
+
_parent;
|
|
27
|
+
_frameByBindingId = new Map();
|
|
28
|
+
_syncResolutionContextPool = [];
|
|
29
|
+
// Deep-path state for _resolveTransientDynamicSyncFromContext (depth ≥ RESOLUTION_SET_THRESHOLD):
|
|
30
|
+
//
|
|
31
|
+
// Cycle detection — generation-based marking (replaces Set<BindingIdentifier>):
|
|
32
|
+
// _deepCycleMarks: Map<BindingIdentifier, generation> records which bindings are "in flight"
|
|
33
|
+
// during the current deep chain. A binding is considered "active" when its recorded
|
|
34
|
+
// generation matches _deepCycleGen.
|
|
35
|
+
// _deepCycleGen: monotonically incremented at the start of each new deep chain. This acts
|
|
36
|
+
// as an implicit bulk-clear: old marks from a previous chain have a stale generation number
|
|
37
|
+
// and are treated as absent — no explicit Map.delete or Map.clear is needed at all.
|
|
38
|
+
// Eliminating the per-level Map.delete call (was ~10 ns × 480 levels) is the primary
|
|
39
|
+
// motivation for this design.
|
|
40
|
+
//
|
|
41
|
+
// _deepActiveLevels: tracks how many deep levels are currently on the call stack so we know
|
|
42
|
+
// when the deep portion has fully unwound and can reset _deepSyncCtxPath.
|
|
43
|
+
//
|
|
44
|
+
// _deepSyncCtx / _deepSyncCtxPath: single shared context for the deep chain; avoids one
|
|
45
|
+
// per-depth pool reset (5 property writes) for every level beyond the threshold.
|
|
46
|
+
// resolutionStack is NOT pushed in the deep path — no GC write-barriers for object arrays.
|
|
47
|
+
// Trade-off: ctx.graph.resolutionStack only reflects the first RESOLUTION_SET_THRESHOLD
|
|
48
|
+
// frames; code relying on ctx.graph.parent inside a deep transient-dynamic factory will see
|
|
49
|
+
// the frame at the threshold boundary, not the current depth.
|
|
50
|
+
_deepCycleMarks = new Map();
|
|
51
|
+
_deepCycleGen = 0;
|
|
52
|
+
_deepActiveLevels = 0;
|
|
53
|
+
_deepSyncCtx;
|
|
54
|
+
_deepSyncCtxPath;
|
|
55
|
+
// Async shared-context state for _resolveTransientDynamicAsyncFromContext (shallow path):
|
|
56
|
+
//
|
|
57
|
+
// For a SEQUENTIAL async chain (depth < RESOLUTION_SET_THRESHOLD), all levels share the same
|
|
58
|
+
// resolutionPath and resolutionStack arrays (passed by reference through ctx.resolveAsync).
|
|
59
|
+
// Because the context stores references rather than snapshots, a single DefaultResolutionContext
|
|
60
|
+
// can serve the entire chain without any per-level allocation or reset — the arrays reflect the
|
|
61
|
+
// current chain state automatically as we push/pop.
|
|
62
|
+
//
|
|
63
|
+
// _deepAsyncCtx: the shared context, lazily created on the first chain entry and reset
|
|
64
|
+
// (5 property writes) at the start of each new root call. Levels 2-N of the same chain
|
|
65
|
+
// reuse it with ZERO setup cost.
|
|
66
|
+
//
|
|
67
|
+
// _deepAsyncCtxPath: identity pointer of the resolutionPath array that "owns" the shared
|
|
68
|
+
// context. Used to distinguish two cases:
|
|
69
|
+
// • same reference → inner level of the owning chain → reuse ctx with no setup
|
|
70
|
+
// • different reference → concurrent chain (e.g. Promise.all) → fall back to a fresh
|
|
71
|
+
// DefaultResolutionContext allocation for that call
|
|
72
|
+
//
|
|
73
|
+
// _deepAsyncActiveLevels: counts active levels of the OWNING chain so we know when to
|
|
74
|
+
// release the path pointer (set _deepAsyncCtxPath = undefined).
|
|
75
|
+
// Concurrent fallback calls are NOT counted — they don't interfere with the owner.
|
|
76
|
+
//
|
|
77
|
+
// resolutionStack is NOT pushed in this path (same trade-off as the deep sync path):
|
|
78
|
+
// ctx.graph.resolutionStack will always be empty for async transient-dynamic chains.
|
|
79
|
+
// Factories that inspect ctx.graph.parent should use the conventional sync binding approach.
|
|
80
|
+
//
|
|
81
|
+
// The function is NOT declared async so that V8 does not create an AsyncGeneratorObject and
|
|
82
|
+
// an implicit Promise on every invocation; instead each level calls factoryPromise.then(cleanup)
|
|
83
|
+
// which chains natively without the extra async state-machine overhead.
|
|
84
|
+
_deepAsyncCtx;
|
|
85
|
+
_deepAsyncCtxPath;
|
|
86
|
+
_deepAsyncActiveLevels = 0;
|
|
87
|
+
_classHasPostConstruct = new WeakMap();
|
|
88
|
+
_classNeedsActiveContainer = new WeakMap();
|
|
89
|
+
_classConstructorMetadata = new WeakMap();
|
|
90
|
+
_activationNeedByBindingId = new Map();
|
|
91
|
+
_activationCacheVersion = -1;
|
|
92
|
+
constructor(_registry, _scope, _lifecycle, _metadataReader, _container, _parent) {
|
|
93
|
+
this._registry = _registry;
|
|
94
|
+
this._scope = _scope;
|
|
95
|
+
this._lifecycle = _lifecycle;
|
|
96
|
+
this._metadataReader = _metadataReader;
|
|
97
|
+
this._container = _container;
|
|
98
|
+
this._parent = _parent;
|
|
99
|
+
}
|
|
100
|
+
// ── Binding lookup ─────────────────────────────────────────────────────────
|
|
101
|
+
_findBinding(token, options, resolutionPath, resolutionStack) {
|
|
102
|
+
if (options === undefined) {
|
|
103
|
+
const fastDefaultBinding = this._registry.getFastDefault(token);
|
|
104
|
+
if (fastDefaultBinding !== undefined) {
|
|
105
|
+
return { binding: fastDefaultBinding, owner: this };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
109
|
+
const namedBinding = this._registry.getSimpleNamed(token, options.name);
|
|
110
|
+
if (namedBinding !== undefined &&
|
|
111
|
+
this._matchesBindingFast(namedBinding, options, resolutionPath, resolutionStack)) {
|
|
112
|
+
return { binding: namedBinding, owner: this };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (options !== undefined &&
|
|
116
|
+
options.name === undefined &&
|
|
117
|
+
options.tag === undefined &&
|
|
118
|
+
(options.tags?.length ?? 0) === 1) {
|
|
119
|
+
const [tagKey, tagValue] = options.tags[0];
|
|
120
|
+
const tagged = this._registry.getSimpleTagged(token, tagKey, tagValue);
|
|
121
|
+
if (tagged !== undefined) {
|
|
122
|
+
return { binding: tagged, owner: this };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const bindings = this._registry.getAll(token);
|
|
126
|
+
if (bindings.length > 0) {
|
|
127
|
+
if (bindings.length === 1) {
|
|
128
|
+
const onlyBinding = bindings[0];
|
|
129
|
+
const isDefaultSlot = onlyBinding.slot.name === undefined && onlyBinding.slot.tags.length === 0;
|
|
130
|
+
if (options === undefined && isDefaultSlot && onlyBinding.predicate === undefined) {
|
|
131
|
+
return { binding: onlyBinding, owner: this };
|
|
132
|
+
}
|
|
133
|
+
if (this._matchesBindingFast(onlyBinding, options, resolutionPath, resolutionStack)) {
|
|
134
|
+
return { binding: onlyBinding, owner: this };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const ctx = this._makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
138
|
+
const binding = selectBinding(bindings, options, ctx, this._getTokenName(token));
|
|
139
|
+
if (binding !== undefined) {
|
|
140
|
+
return { binding, owner: this };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (this._parent !== undefined) {
|
|
144
|
+
return this._parent._findBinding(token, options, resolutionPath, resolutionStack);
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Binding lookup aligned with `resolve` — used by `Container.validate` without instantiating.
|
|
150
|
+
*/
|
|
151
|
+
peekBindingForValidate(token, options) {
|
|
152
|
+
return this._findBinding(token, options, [], []);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Mirrors {@link DependencyResolver.resolveAll} candidate selection only (no instantiation).
|
|
156
|
+
*/
|
|
157
|
+
peekCandidateBindingsForValidate(token, options) {
|
|
158
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
159
|
+
return this._getSimpleNamedBindingsFromChain(token, options.name);
|
|
160
|
+
}
|
|
161
|
+
const allBindings = this._getAllBindingsFromChain(token);
|
|
162
|
+
if (allBindings.length === 0) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const ctx = this._makeConstraintContext([], [], options);
|
|
166
|
+
return selectAllBindings(allBindings, options, ctx);
|
|
167
|
+
}
|
|
168
|
+
// ── Sync resolve ───────────────────────────────────────────────────────────
|
|
169
|
+
resolveFromContext(token, resolutionPath, resolutionStack) {
|
|
170
|
+
const fastBinding = this._registry.getFastDefault(token);
|
|
171
|
+
if (fastBinding !== undefined) {
|
|
172
|
+
if (fastBinding.kind === "alias") {
|
|
173
|
+
return this.resolveFromContext(fastBinding.target, resolutionPath, resolutionStack);
|
|
174
|
+
}
|
|
175
|
+
const scope = fastBinding.scope ?? "transient";
|
|
176
|
+
if (scope === "transient" &&
|
|
177
|
+
fastBinding.kind === "dynamic" &&
|
|
178
|
+
fastBinding.onActivation === undefined &&
|
|
179
|
+
(this._lifecycle.activationVersion === 0 || !this._lifecycle.hasActivationHandlers(fastBinding.token))) {
|
|
180
|
+
return this._resolveTransientDynamicSyncFromContext(fastBinding, resolutionPath, resolutionStack);
|
|
181
|
+
}
|
|
182
|
+
if (scope === "singleton" && this._scope.hasSingleton(fastBinding.id)) {
|
|
183
|
+
return this._scope.getSingleton(fastBinding.id);
|
|
184
|
+
}
|
|
185
|
+
if (scope === "scoped") {
|
|
186
|
+
if (!this._scope.isChild) {
|
|
187
|
+
throw new MissingScopeContextError(this._getTokenName(fastBinding.token));
|
|
188
|
+
}
|
|
189
|
+
if (this._scope.hasScoped(fastBinding.id)) {
|
|
190
|
+
return this._scope.getScoped(fastBinding.id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return this._resolveBinding(fastBinding, undefined, resolutionPath, resolutionStack);
|
|
194
|
+
}
|
|
195
|
+
return this.resolve(token, undefined, resolutionPath, resolutionStack);
|
|
196
|
+
}
|
|
197
|
+
resolve(token, options, resolutionPath, resolutionStack) {
|
|
198
|
+
const found = this._findBinding(token, options, resolutionPath, resolutionStack);
|
|
199
|
+
if (found === undefined) {
|
|
200
|
+
const ownBindings = this._registry.getAll(token);
|
|
201
|
+
if (ownBindings.length > 0) {
|
|
202
|
+
throw new NoMatchingBindingError(this._getTokenName(token), options ?? {}, this._getAvailableSlots(token));
|
|
203
|
+
}
|
|
204
|
+
throw new TokenNotBoundError(this._getTokenName(token));
|
|
205
|
+
}
|
|
206
|
+
const { binding, owner } = found;
|
|
207
|
+
// Follow alias
|
|
208
|
+
if (binding.kind === "alias") {
|
|
209
|
+
return this.resolve(binding.target, options, resolutionPath, resolutionStack);
|
|
210
|
+
}
|
|
211
|
+
const scope = binding.scope ?? "transient";
|
|
212
|
+
// Singleton from a parent resolver: delegate so the parent caches it correctly
|
|
213
|
+
if (scope === "singleton" && owner !== this) {
|
|
214
|
+
return owner._resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
215
|
+
}
|
|
216
|
+
// Scoped/transient (or own singleton): resolve with this resolver's container/scope
|
|
217
|
+
return this._resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
218
|
+
}
|
|
219
|
+
_resolveBinding(binding, options, resolutionPath, resolutionStack) {
|
|
220
|
+
if (binding.kind === "constant" &&
|
|
221
|
+
binding.onActivation === undefined &&
|
|
222
|
+
!this._lifecycle.hasActivationHandlers(binding.token)) {
|
|
223
|
+
return binding.value;
|
|
224
|
+
}
|
|
225
|
+
const scope = binding.scope ?? "transient";
|
|
226
|
+
// Singleton cache check
|
|
227
|
+
if (scope === "singleton") {
|
|
228
|
+
if (this._scope.hasSingleton(binding.id)) {
|
|
229
|
+
return this._scope.getSingleton(binding.id);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Scoped cache check
|
|
233
|
+
if (scope === "scoped") {
|
|
234
|
+
if (!this._scope.isChild) {
|
|
235
|
+
throw new MissingScopeContextError(this._getTokenName(binding.token));
|
|
236
|
+
}
|
|
237
|
+
if (this._scope.hasScoped(binding.id)) {
|
|
238
|
+
return this._scope.getScoped(binding.id);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const frame = this._getResolutionFrame(binding);
|
|
242
|
+
const tokenDisplayName = frame.tokenName;
|
|
243
|
+
const pathWithSet = resolutionPath;
|
|
244
|
+
let resolutionSet = pathWithSet[RESOLUTION_SET_KEY];
|
|
245
|
+
if (resolutionSet === undefined && resolutionPath.length >= RESOLUTION_SET_THRESHOLD) {
|
|
246
|
+
resolutionSet = new Set(resolutionPath);
|
|
247
|
+
pathWithSet[RESOLUTION_SET_KEY] = resolutionSet;
|
|
248
|
+
}
|
|
249
|
+
// Circular dependency detection
|
|
250
|
+
if (resolutionSet !== undefined ? resolutionSet.has(tokenDisplayName) : resolutionPath.includes(tokenDisplayName)) {
|
|
251
|
+
const cycle = [...resolutionPath, tokenDisplayName];
|
|
252
|
+
throw new CircularDependencyError(cycle);
|
|
253
|
+
}
|
|
254
|
+
resolutionPath.push(tokenDisplayName);
|
|
255
|
+
resolutionSet?.add(tokenDisplayName);
|
|
256
|
+
resolutionStack.push(frame);
|
|
257
|
+
const needsActivation = this._needsActivation(binding);
|
|
258
|
+
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
259
|
+
const resolutionCtx = this._acquireSyncResolutionContext(resolutionPath, resolutionStack, options);
|
|
260
|
+
try {
|
|
261
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
262
|
+
if (dynamicResult instanceof Promise) {
|
|
263
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
264
|
+
}
|
|
265
|
+
resolutionStack.pop();
|
|
266
|
+
resolutionPath.pop();
|
|
267
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
268
|
+
return dynamicResult;
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
resolutionStack.pop();
|
|
272
|
+
resolutionPath.pop();
|
|
273
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const needsResolutionContext = needsActivation || this._requiresResolutionContext(binding);
|
|
279
|
+
const resolutionCtx = needsResolutionContext
|
|
280
|
+
? this._acquireSyncResolutionContext(resolutionPath, resolutionStack, options)
|
|
281
|
+
: undefined;
|
|
282
|
+
const instance = this._instantiateSync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
283
|
+
const shouldActivate = this._refreshActivationCacheIfNeeded(binding, needsActivation);
|
|
284
|
+
const activated = shouldActivate
|
|
285
|
+
? this._lifecycle.runActivationSync(resolutionCtx, binding, instance, this._metadataReader)
|
|
286
|
+
: instance;
|
|
287
|
+
// Cache by scope
|
|
288
|
+
if (scope === "singleton") {
|
|
289
|
+
this._scope.setSingleton(binding.id, activated);
|
|
290
|
+
}
|
|
291
|
+
else if (scope === "scoped") {
|
|
292
|
+
this._scope.setScoped(binding.id, activated);
|
|
293
|
+
}
|
|
294
|
+
return activated;
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
resolutionStack.pop();
|
|
298
|
+
resolutionPath.pop();
|
|
299
|
+
resolutionSet?.delete(tokenDisplayName);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
_instantiateSync(binding, ctx, resolutionPath, resolutionStack) {
|
|
303
|
+
switch (binding.kind) {
|
|
304
|
+
case "constant":
|
|
305
|
+
return binding.value;
|
|
306
|
+
case "dynamic": {
|
|
307
|
+
if (ctx === undefined) {
|
|
308
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
309
|
+
}
|
|
310
|
+
const factoryResult = binding.factory(ctx);
|
|
311
|
+
if (factoryResult instanceof Promise) {
|
|
312
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
313
|
+
}
|
|
314
|
+
return factoryResult;
|
|
315
|
+
}
|
|
316
|
+
case "dynamic-async":
|
|
317
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
318
|
+
case "class": {
|
|
319
|
+
const deps = this._resolveClassDeps(binding.target, resolutionPath, resolutionStack);
|
|
320
|
+
const instance = this._instantiateClass(binding.target, deps);
|
|
321
|
+
return instance;
|
|
322
|
+
}
|
|
323
|
+
case "resolved": {
|
|
324
|
+
const deps = this._resolveDescriptorDeps(binding.deps, resolutionPath, resolutionStack);
|
|
325
|
+
const factoryResult = binding.factory(...deps);
|
|
326
|
+
if (factoryResult instanceof Promise) {
|
|
327
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
328
|
+
}
|
|
329
|
+
return factoryResult;
|
|
330
|
+
}
|
|
331
|
+
case "resolved-async":
|
|
332
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
333
|
+
case "alias":
|
|
334
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
_resolveClassDeps(target, resolutionPath, resolutionStack) {
|
|
338
|
+
const meta = this._getConstructorMetadata(target);
|
|
339
|
+
if (meta === undefined) {
|
|
340
|
+
if (target.length === 0) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
throw new MissingMetadataError(target.name);
|
|
344
|
+
}
|
|
345
|
+
if (meta.params.length === 0) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
if (meta.params.length === 1) {
|
|
349
|
+
const param = meta.params[0];
|
|
350
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
351
|
+
if (param.multi) {
|
|
352
|
+
return [this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
353
|
+
}
|
|
354
|
+
if (param.optional) {
|
|
355
|
+
return [this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
356
|
+
}
|
|
357
|
+
if (paramOptions === undefined) {
|
|
358
|
+
return [this.resolveFromContext(param.token, resolutionPath, resolutionStack)];
|
|
359
|
+
}
|
|
360
|
+
return [this.resolve(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
361
|
+
}
|
|
362
|
+
const deps = new Array(meta.params.length);
|
|
363
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
364
|
+
const param = meta.params[index];
|
|
365
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
366
|
+
if (param.multi) {
|
|
367
|
+
deps[index] = this.resolveAll(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (param.optional) {
|
|
371
|
+
deps[index] = this.resolveOptional(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
deps[index] =
|
|
375
|
+
paramOptions === undefined
|
|
376
|
+
? this.resolveFromContext(param.token, resolutionPath, resolutionStack)
|
|
377
|
+
: this.resolve(param.token, paramOptions, resolutionPath, resolutionStack);
|
|
378
|
+
}
|
|
379
|
+
return deps;
|
|
380
|
+
}
|
|
381
|
+
_resolveDescriptorDeps(deps, resolutionPath, resolutionStack) {
|
|
382
|
+
const resolved = new Array(deps.length);
|
|
383
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
384
|
+
const dep = deps[index];
|
|
385
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
386
|
+
if (dep.multi) {
|
|
387
|
+
resolved[index] = this.resolveAll(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (dep.optional) {
|
|
391
|
+
resolved[index] = this.resolveOptional(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
resolved[index] =
|
|
395
|
+
depOptions === undefined
|
|
396
|
+
? this.resolveFromContext(dep.token, resolutionPath, resolutionStack)
|
|
397
|
+
: this.resolve(dep.token, depOptions, resolutionPath, resolutionStack);
|
|
398
|
+
}
|
|
399
|
+
return resolved;
|
|
400
|
+
}
|
|
401
|
+
resolveOptional(token, options, resolutionPath, resolutionStack) {
|
|
402
|
+
if (this._findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
406
|
+
}
|
|
407
|
+
resolveAll(token, options, resolutionPath, resolutionStack) {
|
|
408
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
409
|
+
const namedCandidates = this._getSimpleNamedBindingsFromChain(token, options.name);
|
|
410
|
+
if (namedCandidates.length === 0) {
|
|
411
|
+
return [];
|
|
412
|
+
}
|
|
413
|
+
const resolved = new Array(namedCandidates.length);
|
|
414
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
415
|
+
resolved[index] = this._resolveCandidateSync(namedCandidates[index], options, resolutionPath, resolutionStack);
|
|
416
|
+
}
|
|
417
|
+
return resolved;
|
|
418
|
+
}
|
|
419
|
+
const allBindings = this._getAllBindingsFromChain(token);
|
|
420
|
+
if (allBindings.length === 0) {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
const ctx = this._makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
424
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
425
|
+
const resolved = new Array(candidates.length);
|
|
426
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
427
|
+
resolved[index] = this._resolveCandidateSync(candidates[index], options, resolutionPath, resolutionStack);
|
|
428
|
+
}
|
|
429
|
+
return resolved;
|
|
430
|
+
}
|
|
431
|
+
// ── Async resolve ──────────────────────────────────────────────────────────
|
|
432
|
+
resolveAsyncFromContext(token, resolutionPath, resolutionStack) {
|
|
433
|
+
const fastBinding = this._registry.getFastDefault(token);
|
|
434
|
+
if (fastBinding !== undefined) {
|
|
435
|
+
if (fastBinding.kind === "alias") {
|
|
436
|
+
return this.resolveAsyncFromContext(fastBinding.target, resolutionPath, resolutionStack);
|
|
437
|
+
}
|
|
438
|
+
const scope = fastBinding.scope ?? "transient";
|
|
439
|
+
if (scope === "transient" &&
|
|
440
|
+
(fastBinding.kind === "dynamic" || fastBinding.kind === "dynamic-async") &&
|
|
441
|
+
fastBinding.onActivation === undefined &&
|
|
442
|
+
(this._lifecycle.activationVersion === 0 || !this._lifecycle.hasActivationHandlers(fastBinding.token))) {
|
|
443
|
+
return this._resolveTransientDynamicAsyncFromContext(fastBinding, resolutionPath, resolutionStack);
|
|
444
|
+
}
|
|
445
|
+
if (scope === "singleton" && this._scope.hasSingleton(fastBinding.id)) {
|
|
446
|
+
return Promise.resolve(this._scope.getSingleton(fastBinding.id));
|
|
447
|
+
}
|
|
448
|
+
if (scope === "scoped") {
|
|
449
|
+
if (!this._scope.isChild) {
|
|
450
|
+
return Promise.reject(new MissingScopeContextError(this._getTokenName(fastBinding.token)));
|
|
451
|
+
}
|
|
452
|
+
if (this._scope.hasScoped(fastBinding.id)) {
|
|
453
|
+
return Promise.resolve(this._scope.getScoped(fastBinding.id));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return this._resolveBindingAsync(fastBinding, undefined, resolutionPath, resolutionStack);
|
|
457
|
+
}
|
|
458
|
+
return this.resolveAsync(token, undefined, resolutionPath, resolutionStack);
|
|
459
|
+
}
|
|
460
|
+
async resolveAsync(token, options, resolutionPath, resolutionStack) {
|
|
461
|
+
const found = this._findBinding(token, options, resolutionPath, resolutionStack);
|
|
462
|
+
if (found === undefined) {
|
|
463
|
+
const ownBindings = this._registry.getAll(token);
|
|
464
|
+
if (ownBindings.length > 0) {
|
|
465
|
+
throw new NoMatchingBindingError(this._getTokenName(token), options ?? {}, this._getAvailableSlots(token));
|
|
466
|
+
}
|
|
467
|
+
throw new TokenNotBoundError(this._getTokenName(token));
|
|
468
|
+
}
|
|
469
|
+
const { binding, owner } = found;
|
|
470
|
+
if (binding.kind === "alias") {
|
|
471
|
+
return this.resolveAsync(binding.target, options, resolutionPath, resolutionStack);
|
|
472
|
+
}
|
|
473
|
+
const scope = binding.scope ?? "transient";
|
|
474
|
+
if (scope === "singleton" && owner !== this) {
|
|
475
|
+
return owner._resolveBindingAsync(binding, options, resolutionPath, resolutionStack);
|
|
476
|
+
}
|
|
477
|
+
return this._resolveBindingAsync(binding, options, resolutionPath, resolutionStack);
|
|
478
|
+
}
|
|
479
|
+
async _resolveBindingAsync(binding, options, resolutionPath, resolutionStack) {
|
|
480
|
+
if (binding.kind === "constant" &&
|
|
481
|
+
binding.onActivation === undefined &&
|
|
482
|
+
!this._lifecycle.hasActivationHandlers(binding.token)) {
|
|
483
|
+
return binding.value;
|
|
484
|
+
}
|
|
485
|
+
const scope = binding.scope ?? "transient";
|
|
486
|
+
// Singleton cache
|
|
487
|
+
if (scope === "singleton") {
|
|
488
|
+
if (this._scope.hasSingleton(binding.id)) {
|
|
489
|
+
return this._scope.getSingleton(binding.id);
|
|
490
|
+
}
|
|
491
|
+
// In-flight dedup
|
|
492
|
+
const inflight = this._scope.getInflight(binding.id);
|
|
493
|
+
if (inflight !== undefined) {
|
|
494
|
+
return inflight;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Scoped cache
|
|
498
|
+
if (scope === "scoped") {
|
|
499
|
+
if (!this._scope.isChild) {
|
|
500
|
+
throw new MissingScopeContextError(this._getTokenName(binding.token));
|
|
501
|
+
}
|
|
502
|
+
if (this._scope.hasScoped(binding.id)) {
|
|
503
|
+
return this._scope.getScoped(binding.id);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const frame = this._getResolutionFrame(binding);
|
|
507
|
+
const frameName = frame.tokenName;
|
|
508
|
+
const pathWithSet = resolutionPath;
|
|
509
|
+
let resolutionSet = pathWithSet[RESOLUTION_SET_KEY];
|
|
510
|
+
if (resolutionSet === undefined && resolutionPath.length >= RESOLUTION_SET_THRESHOLD) {
|
|
511
|
+
resolutionSet = new Set(resolutionPath);
|
|
512
|
+
pathWithSet[RESOLUTION_SET_KEY] = resolutionSet;
|
|
513
|
+
}
|
|
514
|
+
if (resolutionSet !== undefined ? resolutionSet.has(frameName) : resolutionPath.includes(frameName)) {
|
|
515
|
+
throw new CircularDependencyError([...resolutionPath, frameName]);
|
|
516
|
+
}
|
|
517
|
+
resolutionPath.push(frameName);
|
|
518
|
+
resolutionSet?.add(frameName);
|
|
519
|
+
resolutionStack.push(frame);
|
|
520
|
+
const needsActivation = this._needsActivation(binding);
|
|
521
|
+
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
522
|
+
const resolutionCtx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
523
|
+
try {
|
|
524
|
+
if (binding.kind === "dynamic-async") {
|
|
525
|
+
return await binding.factory(resolutionCtx);
|
|
526
|
+
}
|
|
527
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
528
|
+
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
resolutionStack.pop();
|
|
532
|
+
resolutionPath.pop();
|
|
533
|
+
resolutionSet?.delete(frameName);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const needsResolutionContext = needsActivation || this._requiresResolutionContext(binding);
|
|
537
|
+
const resolutionCtx = needsResolutionContext
|
|
538
|
+
? new DefaultResolutionContext(this, resolutionPath, resolutionStack, options)
|
|
539
|
+
: undefined;
|
|
540
|
+
try {
|
|
541
|
+
if (scope === "singleton") {
|
|
542
|
+
const createSingletonPromise = async () => {
|
|
543
|
+
const instance = await this._instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
544
|
+
const shouldActivate = this._refreshActivationCacheIfNeeded(binding, needsActivation);
|
|
545
|
+
const activated = shouldActivate
|
|
546
|
+
? await this._lifecycle.runActivation(resolutionCtx, binding, instance, this._metadataReader)
|
|
547
|
+
: instance;
|
|
548
|
+
this._scope.setSingleton(binding.id, activated);
|
|
549
|
+
this._scope.clearInflight(binding.id);
|
|
550
|
+
return activated;
|
|
551
|
+
};
|
|
552
|
+
const singletonPromise = createSingletonPromise().catch((err) => {
|
|
553
|
+
this._scope.clearInflight(binding.id);
|
|
554
|
+
throw err;
|
|
555
|
+
});
|
|
556
|
+
this._scope.setInflight(binding.id, singletonPromise);
|
|
557
|
+
return await singletonPromise;
|
|
558
|
+
}
|
|
559
|
+
const instance = await this._instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
560
|
+
const shouldActivate = this._refreshActivationCacheIfNeeded(binding, needsActivation);
|
|
561
|
+
const activated = shouldActivate
|
|
562
|
+
? await this._lifecycle.runActivation(resolutionCtx, binding, instance, this._metadataReader)
|
|
563
|
+
: instance;
|
|
564
|
+
if (scope === "scoped") {
|
|
565
|
+
this._scope.setScoped(binding.id, activated);
|
|
566
|
+
}
|
|
567
|
+
return activated;
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
resolutionStack.pop();
|
|
571
|
+
resolutionPath.pop();
|
|
572
|
+
resolutionSet?.delete(frameName);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async _instantiateAsync(binding, ctx, resolutionPath, resolutionStack) {
|
|
576
|
+
switch (binding.kind) {
|
|
577
|
+
case "constant":
|
|
578
|
+
return binding.value;
|
|
579
|
+
case "dynamic": {
|
|
580
|
+
if (ctx === undefined) {
|
|
581
|
+
throw new InternalError("dynamic binding requires resolution context");
|
|
582
|
+
}
|
|
583
|
+
const factoryResult = binding.factory(ctx);
|
|
584
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
585
|
+
}
|
|
586
|
+
case "dynamic-async":
|
|
587
|
+
if (ctx === undefined) {
|
|
588
|
+
throw new InternalError("dynamic-async binding requires resolution context");
|
|
589
|
+
}
|
|
590
|
+
return binding.factory(ctx);
|
|
591
|
+
case "class": {
|
|
592
|
+
const deps = await this._resolveClassDepsAsync(binding.target, resolutionPath, resolutionStack);
|
|
593
|
+
const instance = this._instantiateClass(binding.target, deps);
|
|
594
|
+
return instance;
|
|
595
|
+
}
|
|
596
|
+
case "resolved": {
|
|
597
|
+
if (ctx === undefined) {
|
|
598
|
+
throw new InternalError("resolved binding requires resolution context");
|
|
599
|
+
}
|
|
600
|
+
const deps = await this._resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
601
|
+
const factoryResult = binding.factory(...deps);
|
|
602
|
+
return factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
603
|
+
}
|
|
604
|
+
case "resolved-async": {
|
|
605
|
+
const deps = await this._resolveDescriptorDepsAsync(binding.deps, resolutionPath, resolutionStack);
|
|
606
|
+
return binding.factory(...deps);
|
|
607
|
+
}
|
|
608
|
+
case "alias":
|
|
609
|
+
throw new InternalError("alias should have been followed before instantiation");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
async _resolveClassDepsAsync(target, resolutionPath, resolutionStack) {
|
|
613
|
+
const meta = this._getConstructorMetadata(target);
|
|
614
|
+
if (meta === undefined) {
|
|
615
|
+
if (target.length === 0) {
|
|
616
|
+
return [];
|
|
617
|
+
}
|
|
618
|
+
throw new MissingMetadataError(target.name);
|
|
619
|
+
}
|
|
620
|
+
if (meta.params.length === 0) {
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
if (meta.params.length === 1) {
|
|
624
|
+
const param = meta.params[0];
|
|
625
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
626
|
+
if (param.multi) {
|
|
627
|
+
return [await this.resolveAllAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
628
|
+
}
|
|
629
|
+
if (param.optional) {
|
|
630
|
+
return [await this.resolveOptionalAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
631
|
+
}
|
|
632
|
+
if (paramOptions === undefined) {
|
|
633
|
+
return [await this.resolveAsyncFromContext(param.token, resolutionPath, resolutionStack)];
|
|
634
|
+
}
|
|
635
|
+
return [await this.resolveAsync(param.token, paramOptions, resolutionPath, resolutionStack)];
|
|
636
|
+
}
|
|
637
|
+
const pending = new Array(meta.params.length);
|
|
638
|
+
const shouldCloneContext = meta.params.length > 1;
|
|
639
|
+
for (let index = 0; index < meta.params.length; index += 1) {
|
|
640
|
+
const param = meta.params[index];
|
|
641
|
+
const paramOptions = injectionSlotToResolveOptions(param);
|
|
642
|
+
if (param.multi) {
|
|
643
|
+
pending[index] = this.resolveAllAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
644
|
+
}
|
|
645
|
+
else if (param.optional) {
|
|
646
|
+
pending[index] = this.resolveOptionalAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
pending[index] =
|
|
650
|
+
paramOptions === undefined
|
|
651
|
+
? this.resolveAsyncFromContext(param.token, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack)
|
|
652
|
+
: this.resolveAsync(param.token, paramOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return Promise.all(pending);
|
|
656
|
+
}
|
|
657
|
+
async _resolveDescriptorDepsAsync(deps, resolutionPath, resolutionStack) {
|
|
658
|
+
const pending = new Array(deps.length);
|
|
659
|
+
const shouldCloneContext = deps.length > 1;
|
|
660
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
661
|
+
const dep = deps[index];
|
|
662
|
+
const depOptions = injectionSlotToResolveOptions(dep);
|
|
663
|
+
if (dep.multi) {
|
|
664
|
+
pending[index] = this.resolveAllAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
665
|
+
}
|
|
666
|
+
else if (dep.optional) {
|
|
667
|
+
pending[index] = this.resolveOptionalAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
pending[index] =
|
|
671
|
+
depOptions === undefined
|
|
672
|
+
? this.resolveAsyncFromContext(dep.token, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack)
|
|
673
|
+
: this.resolveAsync(dep.token, depOptions, shouldCloneContext ? [...resolutionPath] : resolutionPath, shouldCloneContext ? [...resolutionStack] : resolutionStack);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return Promise.all(pending);
|
|
677
|
+
}
|
|
678
|
+
async resolveOptionalAsync(token, options, resolutionPath, resolutionStack) {
|
|
679
|
+
if (this._findBinding(token, options, resolutionPath, resolutionStack) === undefined) {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
return this.resolveAsync(token, options, resolutionPath, resolutionStack);
|
|
683
|
+
}
|
|
684
|
+
async resolveAllAsync(token, options, resolutionPath, resolutionStack) {
|
|
685
|
+
if (options?.name !== undefined && options.tag === undefined && (options.tags?.length ?? 0) === 0) {
|
|
686
|
+
const namedCandidates = this._getSimpleNamedBindingsFromChain(token, options.name);
|
|
687
|
+
if (namedCandidates.length === 0) {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
const pending = new Array(namedCandidates.length);
|
|
691
|
+
for (let index = 0; index < namedCandidates.length; index += 1) {
|
|
692
|
+
pending[index] = this._resolveCandidateAsync(namedCandidates[index], options, resolutionPath, resolutionStack);
|
|
693
|
+
}
|
|
694
|
+
return Promise.all(pending);
|
|
695
|
+
}
|
|
696
|
+
const allBindings = this._getAllBindingsFromChain(token);
|
|
697
|
+
if (allBindings.length === 0) {
|
|
698
|
+
return [];
|
|
699
|
+
}
|
|
700
|
+
const ctx = this._makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
701
|
+
const candidates = selectAllBindings(allBindings, options, ctx);
|
|
702
|
+
const pending = new Array(candidates.length);
|
|
703
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
704
|
+
pending[index] = this._resolveCandidateAsync(candidates[index], options, resolutionPath, resolutionStack);
|
|
705
|
+
}
|
|
706
|
+
return Promise.all(pending);
|
|
707
|
+
}
|
|
708
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
709
|
+
_getAllBindingsFromChain(token) {
|
|
710
|
+
const ownBindings = this._registry.getAll(token);
|
|
711
|
+
if (this._parent === undefined) {
|
|
712
|
+
return ownBindings;
|
|
713
|
+
}
|
|
714
|
+
const result = [...ownBindings];
|
|
715
|
+
let current = this._parent;
|
|
716
|
+
while (current !== undefined) {
|
|
717
|
+
const own = current._registry.getAll(token);
|
|
718
|
+
if (own.length > 0) {
|
|
719
|
+
result.push(...own);
|
|
720
|
+
}
|
|
721
|
+
current = current._parent;
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
_getSimpleNamedBindingsFromChain(token, name) {
|
|
726
|
+
const ownBinding = this._registry.getSimpleNamed(token, name);
|
|
727
|
+
if (this._parent === undefined) {
|
|
728
|
+
return ownBinding !== undefined ? [ownBinding] : [];
|
|
729
|
+
}
|
|
730
|
+
const result = [];
|
|
731
|
+
if (ownBinding !== undefined) {
|
|
732
|
+
result.push(ownBinding);
|
|
733
|
+
}
|
|
734
|
+
let current = this._parent;
|
|
735
|
+
while (current !== undefined) {
|
|
736
|
+
const binding = current._registry.getSimpleNamed(token, name);
|
|
737
|
+
if (binding !== undefined) {
|
|
738
|
+
result.push(binding);
|
|
739
|
+
}
|
|
740
|
+
current = current._parent;
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
744
|
+
_getAvailableSlots(token) {
|
|
745
|
+
return this._registry.availableSlotStrings(token);
|
|
746
|
+
}
|
|
747
|
+
_makeConstraintContext(resolutionPath, resolutionStack, options) {
|
|
748
|
+
if (options === undefined && resolutionPath.length === 0 && resolutionStack.length === 0) {
|
|
749
|
+
return ROOT_CONSTRAINT_CONTEXT;
|
|
750
|
+
}
|
|
751
|
+
const parent = resolutionStack.at(-1);
|
|
752
|
+
const ancestors = resolutionStack.length > 1 ? resolutionStack.slice(0, -1) : [];
|
|
753
|
+
return {
|
|
754
|
+
resolutionPath,
|
|
755
|
+
resolutionStack,
|
|
756
|
+
parent,
|
|
757
|
+
ancestors,
|
|
758
|
+
currentResolveOptions: options,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
_matchesBindingFast(binding, options, resolutionPath, resolutionStack) {
|
|
762
|
+
if (!this._matchesSlotFast(binding.slot, options)) {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
if (binding.predicate === undefined) {
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
const ctx = this._makeConstraintContext(resolutionPath, resolutionStack, options);
|
|
769
|
+
return binding.predicate(ctx);
|
|
770
|
+
}
|
|
771
|
+
_matchesSlotFast(slot, options) {
|
|
772
|
+
const requestedName = options?.name;
|
|
773
|
+
const requestedTags = options?.tags;
|
|
774
|
+
const singleRequestedTag = options?.tag;
|
|
775
|
+
const hasRequestedTags = (requestedTags?.length ?? 0) > 0 || singleRequestedTag !== undefined;
|
|
776
|
+
if (slot.name !== undefined) {
|
|
777
|
+
if (requestedName === undefined || slot.name !== requestedName) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
else if (requestedName !== undefined) {
|
|
782
|
+
return false;
|
|
783
|
+
}
|
|
784
|
+
if (slot.tags.length > 0) {
|
|
785
|
+
if (!hasRequestedTags) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
for (const [tagKey, tagValue] of slot.tags) {
|
|
789
|
+
if (!this._matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag)) {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
else if (hasRequestedTags) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
return true;
|
|
798
|
+
}
|
|
799
|
+
_getTokenName(token) {
|
|
800
|
+
return tokenName(token);
|
|
801
|
+
}
|
|
802
|
+
_getConstructorMetadata(target) {
|
|
803
|
+
const cached = this._classConstructorMetadata.get(target);
|
|
804
|
+
if (cached !== undefined) {
|
|
805
|
+
return cached === null ? undefined : cached;
|
|
806
|
+
}
|
|
807
|
+
const metadata = this._metadataReader.getConstructorMetadata(target);
|
|
808
|
+
this._classConstructorMetadata.set(target, metadata ?? null);
|
|
809
|
+
return metadata;
|
|
810
|
+
}
|
|
811
|
+
_instantiateClass(target, deps) {
|
|
812
|
+
let needsActiveContainer = this._classNeedsActiveContainer.get(target);
|
|
813
|
+
if (needsActiveContainer === undefined) {
|
|
814
|
+
const accessorMetadata = this._metadataReader.getAccessorMetadata?.(target);
|
|
815
|
+
needsActiveContainer = (accessorMetadata?.length ?? 0) > 0;
|
|
816
|
+
this._classNeedsActiveContainer.set(target, needsActiveContainer);
|
|
817
|
+
}
|
|
818
|
+
const invokable = target;
|
|
819
|
+
if (!needsActiveContainer) {
|
|
820
|
+
return new invokable(...deps);
|
|
821
|
+
}
|
|
822
|
+
return runWithContainer(this._container, () => new invokable(...deps));
|
|
823
|
+
}
|
|
824
|
+
_matchesRequestedTag(tagKey, tagValue, requestedTags, singleRequestedTag) {
|
|
825
|
+
if (singleRequestedTag !== undefined &&
|
|
826
|
+
singleRequestedTag[0] === tagKey &&
|
|
827
|
+
Object.is(singleRequestedTag[1], tagValue)) {
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
if (requestedTags === undefined || requestedTags.length === 0) {
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
for (let index = 0; index < requestedTags.length; index += 1) {
|
|
834
|
+
const requestedTag = requestedTags[index];
|
|
835
|
+
if (requestedTag[0] === tagKey && Object.is(requestedTag[1], tagValue)) {
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
_resolveTransientDynamicSyncFromContext(binding, resolutionPath, resolutionStack) {
|
|
842
|
+
// ── Shallow path (depth < RESOLUTION_SET_THRESHOLD) ──────────────────────────────────
|
|
843
|
+
// Use resolutionPath.includes for cycle detection: for tiny arrays (depth 0–31) this is
|
|
844
|
+
// faster than a Set lookup. Keep resolutionStack push/pop and the per-depth pool so
|
|
845
|
+
// ctx.graph reflects correct state for shallow-chain factories.
|
|
846
|
+
if (resolutionPath.length < RESOLUTION_SET_THRESHOLD) {
|
|
847
|
+
const frame = this._getResolutionFrame(binding);
|
|
848
|
+
const tokenDisplayName = frame.tokenName;
|
|
849
|
+
if (resolutionPath.includes(tokenDisplayName)) {
|
|
850
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
851
|
+
}
|
|
852
|
+
resolutionPath.push(tokenDisplayName);
|
|
853
|
+
resolutionStack.push(frame);
|
|
854
|
+
const resolutionCtx = this._acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
855
|
+
try {
|
|
856
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
857
|
+
if (dynamicResult instanceof Promise) {
|
|
858
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
859
|
+
}
|
|
860
|
+
return dynamicResult;
|
|
861
|
+
}
|
|
862
|
+
finally {
|
|
863
|
+
resolutionStack.pop();
|
|
864
|
+
resolutionPath.pop();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// ── Deep path (depth >= RESOLUTION_SET_THRESHOLD) ────────────────────────────────────
|
|
868
|
+
// At this depth the O(N) array scan becomes expensive. Switch to a class-level
|
|
869
|
+
// Set<BindingIdentifier> for O(1) cycle detection.
|
|
870
|
+
//
|
|
871
|
+
// Performance decisions vs. shallow path:
|
|
872
|
+
// 1. _getResolutionFrame is NOT called: frameName is only needed for resolutionPath.push
|
|
873
|
+
// and error messages. Both are handled below without the frame Map lookup.
|
|
874
|
+
// 2. resolutionPath.push / pop is SKIPPED: cycle detection uses _deepCycleIds (binding IDs),
|
|
875
|
+
// so path membership tracking through the string array is unnecessary. Eliminating
|
|
876
|
+
// ~480 array writes per 512-chain avoids GC write-barriers on every level.
|
|
877
|
+
// Trade-off: CircularDependencyError thrown for a deep cycle (depth > 32) will only
|
|
878
|
+
// include the first 32 path elements in its message; levels 32+ are omitted.
|
|
879
|
+
// 3. The shared context is set up ONCE (when _deepActiveLevels === 0) rather than
|
|
880
|
+
// re-checked on every level — saves two property reads + a reference comparison
|
|
881
|
+
// for each of the ~480 subsequent deep-chain levels.
|
|
882
|
+
//
|
|
883
|
+
// Reentrancy: if a *different* deep chain is currently active (factory called
|
|
884
|
+
// container.resolve() internally and that inner chain also reached the threshold),
|
|
885
|
+
// fall back to the slow path to avoid cross-chain Set pollution.
|
|
886
|
+
if (this._deepActiveLevels > 0 && this._deepSyncCtxPath !== resolutionPath) {
|
|
887
|
+
return this._resolveTransientDynamicSyncSlow(binding, resolutionPath, resolutionStack);
|
|
888
|
+
}
|
|
889
|
+
// First deep level: bump the generation (implicitly clearing all stale cycle marks from
|
|
890
|
+
// previous chains), seed the marks with the shallow-path frames already on the
|
|
891
|
+
// resolution stack, then initialise (or reset) the shared context once for the
|
|
892
|
+
// whole deep chain.
|
|
893
|
+
if (this._deepActiveLevels === 0) {
|
|
894
|
+
const gen = ++this._deepCycleGen;
|
|
895
|
+
for (const stackFrame of resolutionStack) {
|
|
896
|
+
this._deepCycleMarks.set(stackFrame.bindingId, gen);
|
|
897
|
+
}
|
|
898
|
+
let ctx = this._deepSyncCtx;
|
|
899
|
+
if (ctx === undefined) {
|
|
900
|
+
ctx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
901
|
+
this._deepSyncCtx = ctx;
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
ctx.reset(this, resolutionPath, resolutionStack, undefined);
|
|
905
|
+
}
|
|
906
|
+
this._deepSyncCtxPath = resolutionPath;
|
|
907
|
+
}
|
|
908
|
+
if (this._deepCycleMarks.get(binding.id) === this._deepCycleGen) {
|
|
909
|
+
throw new CircularDependencyError([...resolutionPath, tokenName(binding.token)]);
|
|
910
|
+
}
|
|
911
|
+
this._deepCycleMarks.set(binding.id, this._deepCycleGen);
|
|
912
|
+
this._deepActiveLevels++;
|
|
913
|
+
try {
|
|
914
|
+
// Use the pre-initialised context directly — no local variable needed.
|
|
915
|
+
const dynamicResult = binding.factory(this._deepSyncCtx);
|
|
916
|
+
if (dynamicResult instanceof Promise) {
|
|
917
|
+
throw new AsyncResolutionError(tokenName(binding.token), tokenName(binding.token));
|
|
918
|
+
}
|
|
919
|
+
return dynamicResult;
|
|
920
|
+
}
|
|
921
|
+
finally {
|
|
922
|
+
// No Map.delete needed: the generation counter makes old marks invisible.
|
|
923
|
+
// Only reset the path pointer when the last deep level unwinds.
|
|
924
|
+
if (--this._deepActiveLevels === 0) {
|
|
925
|
+
this._deepSyncCtxPath = undefined;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
_resolveTransientDynamicSyncSlow(binding, resolutionPath, resolutionStack) {
|
|
930
|
+
// Rare fallback used when deep-chain reentrancy is detected (a factory called
|
|
931
|
+
// container.resolve() directly and the resulting chain also reached the threshold).
|
|
932
|
+
const frame = this._getResolutionFrame(binding);
|
|
933
|
+
const tokenDisplayName = frame.tokenName;
|
|
934
|
+
const pathWithSet = resolutionPath;
|
|
935
|
+
let resolutionSet = pathWithSet[RESOLUTION_SET_KEY];
|
|
936
|
+
if (resolutionSet === undefined) {
|
|
937
|
+
resolutionSet = new Set(resolutionPath);
|
|
938
|
+
pathWithSet[RESOLUTION_SET_KEY] = resolutionSet;
|
|
939
|
+
}
|
|
940
|
+
if (resolutionSet.has(tokenDisplayName)) {
|
|
941
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
942
|
+
}
|
|
943
|
+
resolutionPath.push(tokenDisplayName);
|
|
944
|
+
resolutionSet.add(tokenDisplayName);
|
|
945
|
+
resolutionStack.push(frame);
|
|
946
|
+
const resolutionCtx = this._acquireSyncResolutionContext(resolutionPath, resolutionStack, undefined);
|
|
947
|
+
try {
|
|
948
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
949
|
+
if (dynamicResult instanceof Promise) {
|
|
950
|
+
throw new AsyncResolutionError(tokenDisplayName, tokenDisplayName);
|
|
951
|
+
}
|
|
952
|
+
return dynamicResult;
|
|
953
|
+
}
|
|
954
|
+
finally {
|
|
955
|
+
resolutionStack.pop();
|
|
956
|
+
resolutionPath.pop();
|
|
957
|
+
resolutionSet.delete(tokenDisplayName);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
// NOT declared `async` — avoids creating a JSAsyncGeneratorObject + implicit Promise wrapper on
|
|
961
|
+
// every invocation. Cleanup is handled via .then(onFulfilled, onRejected) so the behaviour is
|
|
962
|
+
// identical to a try/finally but without the async machinery overhead.
|
|
963
|
+
_resolveTransientDynamicAsyncFromContext(binding, resolutionPath, resolutionStack) {
|
|
964
|
+
// ── Shallow async path (depth < RESOLUTION_SET_THRESHOLD) ─────────────────────────────
|
|
965
|
+
// For the common case of a sequential async chain (each factory awaits one dependency at a
|
|
966
|
+
// time), all levels share the same resolutionPath/resolutionStack arrays. A single
|
|
967
|
+
// DefaultResolutionContext can therefore serve the entire chain:
|
|
968
|
+
// • levels 2-N of the owning chain: zero allocation, zero reset writes
|
|
969
|
+
// • concurrent chains (Promise.all roots): detected by path-identity mismatch → fallback
|
|
970
|
+
// to a fresh DefaultResolutionContext for that level only
|
|
971
|
+
// resolutionStack is NOT pushed here — see class-level comment for the trade-off.
|
|
972
|
+
if (resolutionPath.length < RESOLUTION_SET_THRESHOLD) {
|
|
973
|
+
const tokenDisplayName = tokenName(binding.token);
|
|
974
|
+
if (resolutionPath.includes(tokenDisplayName)) {
|
|
975
|
+
return Promise.reject(new CircularDependencyError([...resolutionPath, tokenDisplayName]));
|
|
976
|
+
}
|
|
977
|
+
resolutionPath.push(tokenDisplayName);
|
|
978
|
+
// Determine which context to use and whether this level owns the shared context.
|
|
979
|
+
let ctx;
|
|
980
|
+
let isOwnerLevel;
|
|
981
|
+
if (this._deepAsyncCtxPath === resolutionPath) {
|
|
982
|
+
// Inner level of the owning chain — reuse the shared context with NO setup overhead.
|
|
983
|
+
ctx = this._deepAsyncCtx;
|
|
984
|
+
isOwnerLevel = true;
|
|
985
|
+
}
|
|
986
|
+
else if (this._deepAsyncCtxPath === undefined) {
|
|
987
|
+
// Root of a new chain — take ownership and initialise (or reset) the shared context.
|
|
988
|
+
const existing = this._deepAsyncCtx;
|
|
989
|
+
if (existing === undefined) {
|
|
990
|
+
ctx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
991
|
+
this._deepAsyncCtx = ctx;
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
existing.reset(this, resolutionPath, resolutionStack, undefined);
|
|
995
|
+
ctx = existing;
|
|
996
|
+
}
|
|
997
|
+
this._deepAsyncCtxPath = resolutionPath;
|
|
998
|
+
isOwnerLevel = true;
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
// Concurrent chain (e.g. Promise.all) — allocate a dedicated context and do NOT
|
|
1002
|
+
// interfere with the owning chain's state.
|
|
1003
|
+
ctx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1004
|
+
isOwnerLevel = false;
|
|
1005
|
+
}
|
|
1006
|
+
if (isOwnerLevel) {
|
|
1007
|
+
this._deepAsyncActiveLevels++;
|
|
1008
|
+
}
|
|
1009
|
+
// Invoke the factory synchronously to get its Promise (or a resolved value for "dynamic").
|
|
1010
|
+
let factoryPromise;
|
|
1011
|
+
try {
|
|
1012
|
+
if (binding.kind === "dynamic-async") {
|
|
1013
|
+
factoryPromise = binding.factory(ctx);
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
const factoryResult = binding.factory(ctx);
|
|
1017
|
+
factoryPromise =
|
|
1018
|
+
factoryResult instanceof Promise ? factoryResult : Promise.resolve(factoryResult);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch (err) {
|
|
1022
|
+
// Synchronous throw from the factory (rare) — clean up immediately.
|
|
1023
|
+
resolutionPath.pop();
|
|
1024
|
+
if (isOwnerLevel && --this._deepAsyncActiveLevels === 0) {
|
|
1025
|
+
this._deepAsyncCtxPath = undefined;
|
|
1026
|
+
}
|
|
1027
|
+
return Promise.reject(err);
|
|
1028
|
+
}
|
|
1029
|
+
// Chain cleanup onto the Promise so it runs whether the factory resolves or rejects.
|
|
1030
|
+
return factoryPromise.then((value) => {
|
|
1031
|
+
resolutionPath.pop();
|
|
1032
|
+
if (isOwnerLevel && --this._deepAsyncActiveLevels === 0) {
|
|
1033
|
+
this._deepAsyncCtxPath = undefined;
|
|
1034
|
+
}
|
|
1035
|
+
return value;
|
|
1036
|
+
}, (err) => {
|
|
1037
|
+
resolutionPath.pop();
|
|
1038
|
+
if (isOwnerLevel && --this._deepAsyncActiveLevels === 0) {
|
|
1039
|
+
this._deepAsyncCtxPath = undefined;
|
|
1040
|
+
}
|
|
1041
|
+
// Re-throw as the rejected value; the `never` cast suppresses the TS return-type
|
|
1042
|
+
// mismatch that arises because `throw` has type `never` in an expression context.
|
|
1043
|
+
throw err;
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
// ── Deep async path (depth ≥ RESOLUTION_SET_THRESHOLD) ────────────────────────────────
|
|
1047
|
+
// Fall back to the fully-correct slow implementation.
|
|
1048
|
+
return this._resolveTransientDynamicAsyncSlow(binding, resolutionPath, resolutionStack);
|
|
1049
|
+
}
|
|
1050
|
+
async _resolveTransientDynamicAsyncSlow(binding, resolutionPath, resolutionStack) {
|
|
1051
|
+
const frame = this._getResolutionFrame(binding);
|
|
1052
|
+
const tokenDisplayName = frame.tokenName;
|
|
1053
|
+
const pathWithSet = resolutionPath;
|
|
1054
|
+
let resolutionSet = pathWithSet[RESOLUTION_SET_KEY];
|
|
1055
|
+
if (resolutionSet === undefined) {
|
|
1056
|
+
resolutionSet = new Set(resolutionPath);
|
|
1057
|
+
pathWithSet[RESOLUTION_SET_KEY] = resolutionSet;
|
|
1058
|
+
}
|
|
1059
|
+
if (resolutionSet.has(tokenDisplayName)) {
|
|
1060
|
+
throw new CircularDependencyError([...resolutionPath, tokenDisplayName]);
|
|
1061
|
+
}
|
|
1062
|
+
resolutionPath.push(tokenDisplayName);
|
|
1063
|
+
resolutionSet.add(tokenDisplayName);
|
|
1064
|
+
resolutionStack.push(frame);
|
|
1065
|
+
const resolutionCtx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1066
|
+
try {
|
|
1067
|
+
if (binding.kind === "dynamic-async") {
|
|
1068
|
+
return await binding.factory(resolutionCtx);
|
|
1069
|
+
}
|
|
1070
|
+
const dynamicResult = binding.factory(resolutionCtx);
|
|
1071
|
+
return dynamicResult instanceof Promise ? await dynamicResult : dynamicResult;
|
|
1072
|
+
}
|
|
1073
|
+
finally {
|
|
1074
|
+
resolutionStack.pop();
|
|
1075
|
+
resolutionPath.pop();
|
|
1076
|
+
resolutionSet.delete(tokenDisplayName);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
_resolveCandidateSync(binding, options, resolutionPath, resolutionStack) {
|
|
1080
|
+
if (binding.kind === "constant" &&
|
|
1081
|
+
binding.onActivation === undefined &&
|
|
1082
|
+
!this._lifecycle.hasActivationHandlers(binding.token)) {
|
|
1083
|
+
return binding.value;
|
|
1084
|
+
}
|
|
1085
|
+
if (binding.kind === "alias") {
|
|
1086
|
+
return this.resolve(binding.target, options, resolutionPath, resolutionStack);
|
|
1087
|
+
}
|
|
1088
|
+
const scope = binding.scope ?? "transient";
|
|
1089
|
+
if (scope === "singleton" && this._scope.hasSingleton(binding.id)) {
|
|
1090
|
+
return this._scope.getSingleton(binding.id);
|
|
1091
|
+
}
|
|
1092
|
+
if (scope === "scoped") {
|
|
1093
|
+
if (!this._scope.isChild) {
|
|
1094
|
+
throw new MissingScopeContextError(this._getTokenName(binding.token));
|
|
1095
|
+
}
|
|
1096
|
+
if (this._scope.hasScoped(binding.id)) {
|
|
1097
|
+
return this._scope.getScoped(binding.id);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return this._resolveBinding(binding, options, resolutionPath, resolutionStack);
|
|
1101
|
+
}
|
|
1102
|
+
_resolveCandidateAsync(binding, options, resolutionPath, resolutionStack) {
|
|
1103
|
+
if (binding.kind === "constant" &&
|
|
1104
|
+
binding.onActivation === undefined &&
|
|
1105
|
+
!this._lifecycle.hasActivationHandlers(binding.token)) {
|
|
1106
|
+
return Promise.resolve(binding.value);
|
|
1107
|
+
}
|
|
1108
|
+
const isolatedPath = [...resolutionPath];
|
|
1109
|
+
const isolatedStack = [...resolutionStack];
|
|
1110
|
+
if (binding.kind === "alias") {
|
|
1111
|
+
return this.resolveAsync(binding.target, options, isolatedPath, isolatedStack);
|
|
1112
|
+
}
|
|
1113
|
+
const scope = binding.scope ?? "transient";
|
|
1114
|
+
if (scope === "singleton" && this._scope.hasSingleton(binding.id)) {
|
|
1115
|
+
return Promise.resolve(this._scope.getSingleton(binding.id));
|
|
1116
|
+
}
|
|
1117
|
+
if (scope === "scoped") {
|
|
1118
|
+
if (!this._scope.isChild) {
|
|
1119
|
+
return Promise.reject(new MissingScopeContextError(this._getTokenName(binding.token)));
|
|
1120
|
+
}
|
|
1121
|
+
if (this._scope.hasScoped(binding.id)) {
|
|
1122
|
+
return Promise.resolve(this._scope.getScoped(binding.id));
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return this._resolveBindingAsync(binding, options, isolatedPath, isolatedStack);
|
|
1126
|
+
}
|
|
1127
|
+
_getResolutionFrame(binding) {
|
|
1128
|
+
const existing = this._frameByBindingId.get(binding.id);
|
|
1129
|
+
if (existing !== undefined) {
|
|
1130
|
+
return existing;
|
|
1131
|
+
}
|
|
1132
|
+
const scope = binding.scope ?? "transient";
|
|
1133
|
+
const frame = buildResolutionFrame(tokenName(binding.token), scope, binding.id, binding.kind, binding.slot);
|
|
1134
|
+
this._frameByBindingId.set(binding.id, frame);
|
|
1135
|
+
return frame;
|
|
1136
|
+
}
|
|
1137
|
+
_needsActivation(binding) {
|
|
1138
|
+
const lifecycleVersion = this._lifecycle.activationVersion;
|
|
1139
|
+
if (lifecycleVersion === 0 &&
|
|
1140
|
+
binding.kind !== "class" &&
|
|
1141
|
+
binding.kind !== "alias" &&
|
|
1142
|
+
binding.onActivation === undefined) {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
1145
|
+
if (this._activationCacheVersion !== lifecycleVersion) {
|
|
1146
|
+
this._activationNeedByBindingId.clear();
|
|
1147
|
+
this._activationCacheVersion = lifecycleVersion;
|
|
1148
|
+
}
|
|
1149
|
+
const cached = this._activationNeedByBindingId.get(binding.id);
|
|
1150
|
+
if (cached !== undefined) {
|
|
1151
|
+
return cached;
|
|
1152
|
+
}
|
|
1153
|
+
if (binding.kind === "class") {
|
|
1154
|
+
let hasActivation = this._lifecycle.hasActivationHandlers(binding.token) || binding.onActivation !== undefined;
|
|
1155
|
+
const cachedPostConstruct = this._classHasPostConstruct.get(binding.target);
|
|
1156
|
+
// Unknown class lifecycle metadata: activate once, then cache after first instantiation.
|
|
1157
|
+
if (cachedPostConstruct === undefined) {
|
|
1158
|
+
hasActivation = true;
|
|
1159
|
+
}
|
|
1160
|
+
else if (cachedPostConstruct) {
|
|
1161
|
+
hasActivation = true;
|
|
1162
|
+
}
|
|
1163
|
+
this._activationNeedByBindingId.set(binding.id, hasActivation);
|
|
1164
|
+
return hasActivation;
|
|
1165
|
+
}
|
|
1166
|
+
let hasActivation = false;
|
|
1167
|
+
if (binding.kind !== "alias" && binding.onActivation !== undefined) {
|
|
1168
|
+
hasActivation = true;
|
|
1169
|
+
}
|
|
1170
|
+
else if (this._lifecycle.hasActivationHandlers(binding.token)) {
|
|
1171
|
+
hasActivation = true;
|
|
1172
|
+
}
|
|
1173
|
+
this._activationNeedByBindingId.set(binding.id, hasActivation);
|
|
1174
|
+
return hasActivation;
|
|
1175
|
+
}
|
|
1176
|
+
_refreshClassPostConstructCache(target) {
|
|
1177
|
+
const lifecycle = this._metadataReader.getLifecycleMetadata(target);
|
|
1178
|
+
const hasPostConstruct = lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0;
|
|
1179
|
+
this._classHasPostConstruct.set(target, hasPostConstruct);
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Refreshes the post-construct cache for class bindings on first instantiation and
|
|
1183
|
+
* returns the (possibly updated) shouldActivate flag.
|
|
1184
|
+
*/
|
|
1185
|
+
_refreshActivationCacheIfNeeded(binding, needsActivation) {
|
|
1186
|
+
if (binding.kind === "class" && this._classHasPostConstruct.get(binding.target) === undefined) {
|
|
1187
|
+
this._refreshClassPostConstructCache(binding.target);
|
|
1188
|
+
this._activationNeedByBindingId.delete(binding.id);
|
|
1189
|
+
return this._needsActivation(binding);
|
|
1190
|
+
}
|
|
1191
|
+
return needsActivation;
|
|
1192
|
+
}
|
|
1193
|
+
_requiresResolutionContext(binding) {
|
|
1194
|
+
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
1195
|
+
}
|
|
1196
|
+
_acquireSyncResolutionContext(resolutionPath, resolutionStack, options) {
|
|
1197
|
+
const depth = resolutionStack.length;
|
|
1198
|
+
const existing = this._syncResolutionContextPool[depth];
|
|
1199
|
+
if (existing !== undefined) {
|
|
1200
|
+
existing.reset(this, resolutionPath, resolutionStack, options);
|
|
1201
|
+
return existing;
|
|
1202
|
+
}
|
|
1203
|
+
const created = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
1204
|
+
this._syncResolutionContextPool[depth] = created;
|
|
1205
|
+
return created;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
//# sourceMappingURL=resolver.js.map
|