@codefast/di 0.5.0-canary.7 → 0.5.0-canary.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/README.md +3 -1
- package/dist/binding.d.ts +47 -5
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +44 -0
- package/dist/binding.js.map +1 -1
- package/dist/constructor-type.d.ts +4 -5
- package/dist/constructor-type.d.ts.map +1 -1
- package/dist/container/binding-builders.d.ts +32 -11
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +140 -191
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +75 -92
- package/dist/container/container.js.map +1 -1
- package/dist/decorators/inject.d.ts +2 -4
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js.map +1 -1
- package/dist/errors.d.ts +14 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/introspection/inspector.js +1 -1
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/metadata/metadata-keys.d.ts +3 -6
- package/dist/metadata/metadata-keys.d.ts.map +1 -1
- package/dist/metadata/metadata-keys.js +3 -6
- package/dist/metadata/metadata-keys.js.map +1 -1
- package/dist/registry.d.ts +14 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +35 -45
- package/dist/registry.js.map +1 -1
- package/dist/resolution/activation-need.d.ts +25 -0
- package/dist/resolution/activation-need.d.ts.map +1 -0
- package/dist/resolution/activation-need.js +64 -0
- package/dist/resolution/activation-need.js.map +1 -0
- package/dist/resolution/binding-lookup-cache.d.ts +41 -0
- package/dist/resolution/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/binding-lookup-cache.js +102 -0
- package/dist/resolution/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/binding-select.js +1 -4
- package/dist/resolution/binding-select.js.map +1 -1
- package/dist/resolution/class-introspector.d.ts +27 -0
- package/dist/resolution/class-introspector.d.ts.map +1 -0
- package/dist/resolution/class-introspector.js +60 -0
- package/dist/resolution/class-introspector.js.map +1 -0
- package/dist/resolution/diagnostics.d.ts +41 -0
- package/dist/resolution/diagnostics.d.ts.map +1 -0
- package/dist/resolution/diagnostics.js +18 -0
- package/dist/resolution/diagnostics.js.map +1 -0
- package/dist/resolution/environment.d.ts +22 -1
- package/dist/resolution/environment.d.ts.map +1 -1
- package/dist/resolution/environment.js +27 -1
- package/dist/resolution/environment.js.map +1 -1
- package/dist/resolution/instantiation-plan.d.ts +15 -15
- package/dist/resolution/instantiation-plan.d.ts.map +1 -1
- package/dist/resolution/instantiation-plan.js +68 -48
- package/dist/resolution/instantiation-plan.js.map +1 -1
- package/dist/resolution/lifecycle.d.ts +2 -0
- package/dist/resolution/lifecycle.d.ts.map +1 -1
- package/dist/resolution/lifecycle.js +16 -11
- package/dist/resolution/lifecycle.js.map +1 -1
- package/dist/resolution/resolution-path.d.ts +4 -13
- package/dist/resolution/resolution-path.d.ts.map +1 -1
- package/dist/resolution/resolution-path.js +3 -16
- package/dist/resolution/resolution-path.js.map +1 -1
- package/dist/resolution/resolver.d.ts +7 -2
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +116 -328
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/scope.d.ts +7 -15
- package/dist/resolution/scope.d.ts.map +1 -1
- package/dist/resolution/scope.js +48 -44
- package/dist/resolution/scope.js.map +1 -1
- package/package.json +7 -97
- package/src/binding.ts +104 -5
- package/src/constructor-type.ts +4 -5
- package/src/container/binding-builders.ts +180 -283
- package/src/container/container.ts +90 -103
- package/src/decorators/inject.ts +3 -5
- package/src/errors.ts +21 -0
- package/src/index.ts +1 -0
- package/src/introspection/inspector.ts +1 -1
- package/src/metadata/metadata-keys.ts +3 -6
- package/src/registry.ts +38 -59
- package/src/resolution/activation-need.ts +81 -0
- package/src/resolution/binding-lookup-cache.ts +132 -0
- package/src/resolution/binding-select.ts +1 -4
- package/src/resolution/class-introspector.ts +74 -0
- package/src/resolution/diagnostics.ts +43 -0
- package/src/resolution/environment.ts +31 -1
- package/src/resolution/instantiation-plan.ts +113 -61
- package/src/resolution/lifecycle.ts +16 -11
- package/src/resolution/resolution-path.ts +5 -20
- package/src/resolution/resolver.ts +142 -371
- package/src/resolution/scope.ts +50 -49
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Binding, BindingSlot } from "#/binding";
|
|
2
|
-
import
|
|
2
|
+
import { NO_INSTANCE } from "#/binding";
|
|
3
3
|
import type { Container } from "#/container/container";
|
|
4
4
|
import type { InjectionDescriptor } from "#/decorators/inject";
|
|
5
5
|
import {
|
|
@@ -12,17 +12,21 @@ import {
|
|
|
12
12
|
NoMatchingBindingError,
|
|
13
13
|
TokenNotBoundError,
|
|
14
14
|
} from "#/errors";
|
|
15
|
-
import type {
|
|
15
|
+
import type { MetadataReader } from "#/metadata/metadata-types";
|
|
16
16
|
import type { BindingRegistry } from "#/registry";
|
|
17
|
+
import { ActivationNeedCache } from "#/resolution/activation-need";
|
|
18
|
+
import type { DefaultLookupEntry } from "#/resolution/binding-lookup-cache";
|
|
19
|
+
import { BindingLookupCache } from "#/resolution/binding-lookup-cache";
|
|
17
20
|
import { selectAllBindings, selectBinding } from "#/resolution/binding-select";
|
|
21
|
+
import { ClassIntrospector } from "#/resolution/class-introspector";
|
|
22
|
+
import type { ResolutionDiagnostics } from "#/resolution/diagnostics";
|
|
18
23
|
import type { ResolverCallbacks } from "#/resolution/environment";
|
|
19
|
-
import { buildResolutionFrame, DefaultResolutionContext
|
|
24
|
+
import { buildResolutionFrame, DefaultResolutionContext } from "#/resolution/environment";
|
|
20
25
|
import { InstantiationPlanCompiler, PLAN_RETRY } from "#/resolution/instantiation-plan";
|
|
21
26
|
import type { LifecycleManager } from "#/resolution/lifecycle";
|
|
22
27
|
import { enterResolutionPath, exitResolutionPath } from "#/resolution/resolution-path";
|
|
23
28
|
import { injectionSlotToResolveOptions } from "#/resolution/resolve-options";
|
|
24
29
|
import type { ScopeManager } from "#/resolution/scope";
|
|
25
|
-
import { SINGLETON_MISS } from "#/resolution/scope";
|
|
26
30
|
import type { Token } from "#/token";
|
|
27
31
|
import { tokenName } from "#/token";
|
|
28
32
|
import type {
|
|
@@ -36,16 +40,6 @@ import type {
|
|
|
36
40
|
ResolveOptions,
|
|
37
41
|
} from "#/types";
|
|
38
42
|
|
|
39
|
-
// Terminal result of the options-less lookup fast lane — alias hops already folded.
|
|
40
|
-
interface DefaultLookupEntry {
|
|
41
|
-
readonly binding: Binding;
|
|
42
|
-
readonly owner: DependencyResolver;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Fast-lane alias folding gives up past this many hops and defers to the resolve()
|
|
46
|
-
// loop, whose Set-based traversal detects genuine cycles exactly (no arbitrary cap).
|
|
47
|
-
const ALIAS_HOP_LIMIT = 32;
|
|
48
|
-
|
|
49
43
|
type BindingWithScope = Binding & { scope: BindingScope };
|
|
50
44
|
const EMPTY_STRING_LIST: ReadonlyArray<string> = [];
|
|
51
45
|
const EMPTY_FRAME_LIST: ReadonlyArray<ResolutionFrame> = [];
|
|
@@ -60,53 +54,12 @@ const ROOT_CONSTRAINT_CONTEXT = {
|
|
|
60
54
|
/**
|
|
61
55
|
* @since 0.3.16-canary.0
|
|
62
56
|
*/
|
|
63
|
-
export class DependencyResolver {
|
|
57
|
+
export class DependencyResolver implements ResolverCallbacks {
|
|
64
58
|
readonly #syncResolutionContextPool: Array<DefaultResolutionContext> = [];
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// Every level of a SEQUENTIAL async chain shares the same resolutionPath and resolutionStack
|
|
71
|
-
// arrays (passed by reference through ctx.resolveAsync), and the context stores references
|
|
72
|
-
// rather than snapshots, so one DefaultResolutionContext can serve the whole chain — the arrays
|
|
73
|
-
// reflect the current state automatically as levels push and pop.
|
|
74
|
-
//
|
|
75
|
-
// #asyncChainCtx: the shared context, created on first use and reset at each new root call.
|
|
76
|
-
// Inner levels of the same chain reuse it with zero setup.
|
|
77
|
-
// #asyncChainCtxPath: identity of the resolutionPath array owning the shared context — same
|
|
78
|
-
// reference means an inner level of that chain, a different one means a concurrent chain
|
|
79
|
-
// (e.g. Promise.all) which gets its own context instead.
|
|
80
|
-
// #asyncChainActiveLevels: active levels of the OWNING chain, so the path pointer is released
|
|
81
|
-
// when the last one settles. Concurrent fallback calls are not counted.
|
|
82
|
-
//
|
|
83
|
-
// The method is NOT declared async: that would allocate an async state machine and an implicit
|
|
84
|
-
// promise per level, where a `.then(settle, settle)` side listener costs neither.
|
|
85
|
-
#asyncChainCtx: DefaultResolutionContext | undefined;
|
|
86
|
-
#asyncChainCtxPath: Array<string> | undefined;
|
|
87
|
-
#asyncChainActiveLevels = 0;
|
|
88
|
-
// Settle callback shared by every level of the owning async chain: all of them pop the same
|
|
89
|
-
// resolutionPath and decrement the same counter, so one closure serves the whole chain instead
|
|
90
|
-
// of allocating one per level (the async lane's dominant per-level allocation).
|
|
91
|
-
#asyncChainSettle: (() => void) | undefined;
|
|
92
|
-
readonly #classHasPostConstruct = new WeakMap<Constructor, boolean>();
|
|
93
|
-
readonly #classNeedsActiveContainer = new WeakMap<Constructor, boolean>();
|
|
94
|
-
readonly #classConstructorMetadata = new WeakMap<Constructor, ConstructorMetadata | null>();
|
|
95
|
-
readonly #activationNeedByBindingId = new Map<BindingIdentifier, boolean>();
|
|
96
|
-
#activationCacheVersion = -1;
|
|
97
|
-
// Options-less lookup memo across the parent chain: token → terminal {binding, owner}
|
|
98
|
-
// (alias hops folded). `null` = token must take the slow lookup path. Invalidated when
|
|
99
|
-
// any registry in the chain mutates (monotonic version sum).
|
|
100
|
-
readonly #defaultLookupByToken = new Map<Token<unknown> | Constructor, DefaultLookupEntry | null>();
|
|
101
|
-
#defaultLookupVersion = -1;
|
|
102
|
-
// Name-only lookup memo (token → name → entry) with the same chain-version
|
|
103
|
-
// invalidation. `null` = shape needs the full selection path.
|
|
104
|
-
readonly #namedLookupByToken = new Map<Token<unknown> | Constructor, Map<string, DefaultLookupEntry | null>>();
|
|
105
|
-
#namedLookupVersion = -1;
|
|
106
|
-
// Compiled transient-class plans (Dagger-style): a pure-static subgraph (class/constant/
|
|
107
|
-
// cached-singleton deps only) compiles once into a nested-constructor closure — cycle
|
|
108
|
-
// checking happens at compile time, so execution skips all per-resolve bookkeeping.
|
|
109
|
-
// `null` = binding is not plannable under the current versions.
|
|
59
|
+
// Pure allocation pool: a chain's own context is threaded through the call, so nothing here
|
|
60
|
+
// identifies a chain.
|
|
61
|
+
readonly #asyncChainContextPool: Array<DefaultResolutionContext> = [];
|
|
62
|
+
// Compiled plans; `null` marks a binding as unplannable under the current cache versions.
|
|
110
63
|
readonly #classPlanByBindingId = new Map<BindingIdentifier, (() => unknown) | null>();
|
|
111
64
|
#classPlanRegistryVersion = -1;
|
|
112
65
|
#classPlanActivationVersion = -1;
|
|
@@ -115,8 +68,10 @@ export class DependencyResolver {
|
|
|
115
68
|
readonly #scope: ScopeManager;
|
|
116
69
|
readonly #lifecycle: LifecycleManager;
|
|
117
70
|
readonly #metadataReader: MetadataReader;
|
|
118
|
-
readonly #container: Container;
|
|
119
71
|
readonly #parent: DependencyResolver | undefined;
|
|
72
|
+
readonly #lookup: BindingLookupCache<DependencyResolver>;
|
|
73
|
+
readonly #classes: ClassIntrospector;
|
|
74
|
+
readonly #activation: ActivationNeedCache;
|
|
120
75
|
|
|
121
76
|
constructor(
|
|
122
77
|
registry: BindingRegistry,
|
|
@@ -130,8 +85,29 @@ export class DependencyResolver {
|
|
|
130
85
|
this.#scope = scope;
|
|
131
86
|
this.#lifecycle = lifecycle;
|
|
132
87
|
this.#metadataReader = metadataReader;
|
|
133
|
-
this.#container = container;
|
|
134
88
|
this.#parent = parent;
|
|
89
|
+
this.#lookup = new BindingLookupCache<DependencyResolver>(
|
|
90
|
+
registry,
|
|
91
|
+
this,
|
|
92
|
+
parent === undefined ? undefined : parent.#lookup,
|
|
93
|
+
);
|
|
94
|
+
this.#classes = new ClassIntrospector(metadataReader, container);
|
|
95
|
+
this.#activation = new ActivationNeedCache(lifecycle, this.#classes);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Structural counts for {@link RESOLUTION_DIAGNOSTICS}; see `resolution/diagnostics.ts`. */
|
|
99
|
+
describeCaches(): Pick<ResolutionDiagnostics, "asyncContextPoolSize" | "compiledPlanCount" | "syncContextPoolSize"> {
|
|
100
|
+
let compiledPlanCount = 0;
|
|
101
|
+
for (const plan of this.#classPlanByBindingId.values()) {
|
|
102
|
+
if (plan !== null) {
|
|
103
|
+
compiledPlanCount += 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
asyncContextPoolSize: this.#asyncChainContextPool.length,
|
|
108
|
+
compiledPlanCount,
|
|
109
|
+
syncContextPoolSize: this.#syncResolutionContextPool.length,
|
|
110
|
+
};
|
|
135
111
|
}
|
|
136
112
|
|
|
137
113
|
// ── Binding lookup ─────────────────────────────────────────────────────────
|
|
@@ -237,7 +213,7 @@ export class DependencyResolver {
|
|
|
237
213
|
if (fastBinding !== undefined && fastBinding.kind !== "alias") {
|
|
238
214
|
return this.#resolveDefaultEntry<Value>(fastBinding, this, resolutionPath, resolutionStack);
|
|
239
215
|
}
|
|
240
|
-
const entry = this.#
|
|
216
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
241
217
|
if (entry === null) {
|
|
242
218
|
return this.resolve(token, undefined, resolutionPath, resolutionStack);
|
|
243
219
|
}
|
|
@@ -284,8 +260,8 @@ export class DependencyResolver {
|
|
|
284
260
|
}
|
|
285
261
|
}
|
|
286
262
|
} else if (scope === "singleton") {
|
|
287
|
-
const cachedSingleton =
|
|
288
|
-
if (cachedSingleton !==
|
|
263
|
+
const cachedSingleton = binding.instance;
|
|
264
|
+
if (cachedSingleton !== NO_INSTANCE) {
|
|
289
265
|
return cachedSingleton as Value;
|
|
290
266
|
}
|
|
291
267
|
if (owner !== this) {
|
|
@@ -346,89 +322,8 @@ export class DependencyResolver {
|
|
|
346
322
|
}
|
|
347
323
|
}
|
|
348
324
|
|
|
349
|
-
#chainRegistryVersion(): number {
|
|
350
|
-
let version = this.#registry.version;
|
|
351
|
-
for (let resolver = this.#parent; resolver !== undefined; resolver = resolver.#parent) {
|
|
352
|
-
version += resolver.#registry.version;
|
|
353
|
-
}
|
|
354
|
-
return version;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
#lookupDefaultEntry(token: Token<unknown> | Constructor): DefaultLookupEntry | null {
|
|
358
|
-
const version = this.#chainRegistryVersion();
|
|
359
|
-
if (version !== this.#defaultLookupVersion) {
|
|
360
|
-
this.#defaultLookupByToken.clear();
|
|
361
|
-
this.#defaultLookupVersion = version;
|
|
362
|
-
}
|
|
363
|
-
let entry = this.#defaultLookupByToken.get(token);
|
|
364
|
-
if (entry === undefined) {
|
|
365
|
-
entry = this.#computeDefaultEntry(token);
|
|
366
|
-
this.#defaultLookupByToken.set(token, entry);
|
|
367
|
-
}
|
|
368
|
-
return entry;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
#computeDefaultEntry(token: Token<unknown> | Constructor): DefaultLookupEntry | null {
|
|
372
|
-
let current = token;
|
|
373
|
-
for (let hop = 0; hop < ALIAS_HOP_LIMIT; hop += 1) {
|
|
374
|
-
const entry = this.#findDefaultEntryInChain(current);
|
|
375
|
-
if (entry === null) {
|
|
376
|
-
return null;
|
|
377
|
-
}
|
|
378
|
-
if (entry.binding.kind === "alias") {
|
|
379
|
-
current = entry.binding.target;
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
return entry;
|
|
383
|
-
}
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
#lookupNamedEntry(token: Token<unknown> | Constructor, name: string): DefaultLookupEntry | null {
|
|
388
|
-
const version = this.#chainRegistryVersion();
|
|
389
|
-
if (version !== this.#namedLookupVersion) {
|
|
390
|
-
this.#namedLookupByToken.clear();
|
|
391
|
-
this.#namedLookupVersion = version;
|
|
392
|
-
}
|
|
393
|
-
// ✓ TS6.0: Map.getOrInsert (ES2025)
|
|
394
|
-
const entriesByName = this.#namedLookupByToken.getOrInsert(token, new Map<string, DefaultLookupEntry | null>());
|
|
395
|
-
let entry = entriesByName.get(name);
|
|
396
|
-
if (entry === undefined) {
|
|
397
|
-
entry = this.#findNamedEntryInChain(token, name);
|
|
398
|
-
entriesByName.set(name, entry);
|
|
399
|
-
}
|
|
400
|
-
return entry;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
#findNamedEntryInChain(token: Token<unknown> | Constructor, name: string): DefaultLookupEntry | null {
|
|
404
|
-
const named = this.#registry.getSimpleNamed(token, name);
|
|
405
|
-
if (named !== undefined) {
|
|
406
|
-
// Predicates need a live context; aliases carry options through the full path.
|
|
407
|
-
if (named.predicate !== undefined || named.kind === "alias") {
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
return { binding: named, owner: this };
|
|
411
|
-
}
|
|
412
|
-
if (this.#registry.has(token)) {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
return this.#parent === undefined ? null : this.#parent.#findNamedEntryInChain(token, name);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
#findDefaultEntryInChain(token: Token<unknown> | Constructor): DefaultLookupEntry | null {
|
|
419
|
-
const fast = this.#registry.getFastDefault(token);
|
|
420
|
-
if (fast !== undefined) {
|
|
421
|
-
return { binding: fast, owner: this };
|
|
422
|
-
}
|
|
423
|
-
// A level with non-fast bindings (multi-slot / predicate) needs full selection — bail.
|
|
424
|
-
if (this.#registry.has(token)) {
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
return this.#parent === undefined ? null : this.#parent.#findDefaultEntryInChain(token);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
325
|
#getInstantiationPlan(binding: Binding & { kind: "class" | "resolved" }): (() => unknown) | null {
|
|
431
|
-
const registryVersion = this.#
|
|
326
|
+
const registryVersion = this.#lookup.chainVersion();
|
|
432
327
|
const activationVersion = this.#lifecycle.activationVersion;
|
|
433
328
|
if (registryVersion !== this.#classPlanRegistryVersion || activationVersion !== this.#classPlanActivationVersion) {
|
|
434
329
|
this.#classPlanByBindingId.clear();
|
|
@@ -451,22 +346,28 @@ export class DependencyResolver {
|
|
|
451
346
|
// Compiler behind #getClassPlan — cold path, so the host indirection costs nothing hot.
|
|
452
347
|
readonly #planCompiler = new InstantiationPlanCompiler({
|
|
453
348
|
hasActivationHandlers: (token) => this.#lifecycle.hasActivationHandlers(token),
|
|
454
|
-
knownPostConstruct: (target) => this.#
|
|
455
|
-
needsActiveContainer: (target) =>
|
|
456
|
-
|
|
457
|
-
if (needsActiveContainer === undefined) {
|
|
458
|
-
const accessorMetadata = this.#metadataReader.getAccessorMetadata?.(target);
|
|
459
|
-
needsActiveContainer = (accessorMetadata?.length ?? 0) > 0;
|
|
460
|
-
this.#classNeedsActiveContainer.set(target, needsActiveContainer);
|
|
461
|
-
}
|
|
462
|
-
return needsActiveContainer;
|
|
463
|
-
},
|
|
464
|
-
getConstructorMetadata: (target) => this.#getConstructorMetadata(target),
|
|
349
|
+
knownPostConstruct: (target) => this.#classes.knownPostConstruct(target),
|
|
350
|
+
needsActiveContainer: (target) => this.#classes.needsActiveContainer(target),
|
|
351
|
+
getConstructorMetadata: (target) => this.#classes.constructorMetadata(target),
|
|
465
352
|
lookupDependencyEntry: (token) => {
|
|
466
|
-
const entry = this.#
|
|
467
|
-
return entry === null ? null : { binding: entry.binding
|
|
353
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
354
|
+
return entry === null ? null : { binding: entry.binding };
|
|
355
|
+
},
|
|
356
|
+
getResolutionFrame: (binding) => this.#getResolutionFrame(binding),
|
|
357
|
+
// Dispatches exactly as #resolveClassDeps does, so an escaped dep is indistinguishable
|
|
358
|
+
// from the same dep on a fully interpreted resolve.
|
|
359
|
+
resolveEscaped: (token, options, arity, resolutionPath, resolutionStack) => {
|
|
360
|
+
if (arity === "all") {
|
|
361
|
+
return this.resolveAll(token, options, resolutionPath, resolutionStack);
|
|
362
|
+
}
|
|
363
|
+
if (arity === "optional") {
|
|
364
|
+
return this.resolveOptional(token, options, resolutionPath, resolutionStack);
|
|
365
|
+
}
|
|
366
|
+
if (options === undefined) {
|
|
367
|
+
return this.resolveFromContext(token, resolutionPath, resolutionStack);
|
|
368
|
+
}
|
|
369
|
+
return this.resolve(token, options, resolutionPath, resolutionStack);
|
|
468
370
|
},
|
|
469
|
-
resolveFallback: (token) => this.resolve(token, undefined, [], []),
|
|
470
371
|
});
|
|
471
372
|
|
|
472
373
|
resolve<const Value>(
|
|
@@ -483,7 +384,7 @@ export class DependencyResolver {
|
|
|
483
384
|
options.tag === undefined &&
|
|
484
385
|
(options.tags === undefined || options.tags.length === 0)
|
|
485
386
|
) {
|
|
486
|
-
const namedEntry = this.#
|
|
387
|
+
const namedEntry = this.#lookup.namedEntry(token, options.name);
|
|
487
388
|
if (namedEntry !== null) {
|
|
488
389
|
const namedBinding = namedEntry.binding;
|
|
489
390
|
if (
|
|
@@ -495,9 +396,8 @@ export class DependencyResolver {
|
|
|
495
396
|
}
|
|
496
397
|
const namedScope = (namedBinding as BindingWithScope).scope ?? "transient";
|
|
497
398
|
if (namedScope === "singleton") {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return cachedSingleton as Value;
|
|
399
|
+
if (namedBinding.instance !== NO_INSTANCE) {
|
|
400
|
+
return namedBinding.instance as Value;
|
|
501
401
|
}
|
|
502
402
|
}
|
|
503
403
|
// Everything else keeps the full path (context, activation, guards).
|
|
@@ -563,10 +463,8 @@ export class DependencyResolver {
|
|
|
563
463
|
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
564
464
|
|
|
565
465
|
// Singleton cache check
|
|
566
|
-
if (scope === "singleton") {
|
|
567
|
-
|
|
568
|
-
return this.#scope.getSingleton<Value>(binding.id);
|
|
569
|
-
}
|
|
466
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
467
|
+
return binding.instance as Value;
|
|
570
468
|
}
|
|
571
469
|
|
|
572
470
|
// Scoped cache check
|
|
@@ -583,7 +481,7 @@ export class DependencyResolver {
|
|
|
583
481
|
const tokenDisplayName = frame.tokenName;
|
|
584
482
|
const resolutionSet = enterResolutionPath(resolutionPath, tokenDisplayName, false);
|
|
585
483
|
resolutionStack.push(frame);
|
|
586
|
-
const needsActivation = this.#needsActivation(binding);
|
|
484
|
+
const needsActivation = this.#activation.needsActivation(binding);
|
|
587
485
|
if (!needsActivation && scope === "transient" && binding.kind === "dynamic") {
|
|
588
486
|
const resolutionCtx = this.#acquireSyncResolutionContext(resolutionPath, resolutionStack, options);
|
|
589
487
|
try {
|
|
@@ -611,7 +509,7 @@ export class DependencyResolver {
|
|
|
611
509
|
|
|
612
510
|
const instance = this.#instantiateSync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
613
511
|
|
|
614
|
-
const shouldActivate = this.#
|
|
512
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
615
513
|
const activated = shouldActivate
|
|
616
514
|
? this.#lifecycle.runActivationSync(
|
|
617
515
|
resolutionCtx as DefaultResolutionContext,
|
|
@@ -623,7 +521,7 @@ export class DependencyResolver {
|
|
|
623
521
|
|
|
624
522
|
// Cache by scope
|
|
625
523
|
if (scope === "singleton") {
|
|
626
|
-
this.#scope.setSingleton(binding
|
|
524
|
+
this.#scope.setSingleton(binding, activated);
|
|
627
525
|
} else if (scope === "scoped") {
|
|
628
526
|
this.#scope.setScoped(binding.id, activated);
|
|
629
527
|
}
|
|
@@ -662,7 +560,7 @@ export class DependencyResolver {
|
|
|
662
560
|
|
|
663
561
|
case "class": {
|
|
664
562
|
const deps = this.#resolveClassDeps(binding.target, resolutionPath, resolutionStack);
|
|
665
|
-
const instance = this.#
|
|
563
|
+
const instance = this.#classes.instantiate(binding.target, deps);
|
|
666
564
|
return instance as Value;
|
|
667
565
|
}
|
|
668
566
|
|
|
@@ -688,7 +586,7 @@ export class DependencyResolver {
|
|
|
688
586
|
resolutionPath: Array<string>,
|
|
689
587
|
resolutionStack: Array<ResolutionFrame>,
|
|
690
588
|
): Array<unknown> {
|
|
691
|
-
const meta = this.#
|
|
589
|
+
const meta = this.#classes.constructorMetadata(target);
|
|
692
590
|
if (meta === undefined) {
|
|
693
591
|
if (target.length === 0) {
|
|
694
592
|
return [];
|
|
@@ -828,6 +726,7 @@ export class DependencyResolver {
|
|
|
828
726
|
token: Token<Value> | Constructor<Value>,
|
|
829
727
|
resolutionPath: Array<string>,
|
|
830
728
|
resolutionStack: Array<ResolutionFrame>,
|
|
729
|
+
callerContext?: DefaultResolutionContext,
|
|
831
730
|
): Promise<Value> {
|
|
832
731
|
// Hot lane: own-registry fast default (async chains resolve sibling dynamic bindings).
|
|
833
732
|
// Fall back to the chain-versioned memo only on miss or alias.
|
|
@@ -844,15 +743,22 @@ export class DependencyResolver {
|
|
|
844
743
|
fastBinding as Binding<Value> & { kind: "dynamic" | "dynamic-async" },
|
|
845
744
|
resolutionPath,
|
|
846
745
|
resolutionStack,
|
|
746
|
+
callerContext,
|
|
847
747
|
);
|
|
848
748
|
}
|
|
849
|
-
return this.#resolveAsyncDefaultEntry<Value>(fastBinding, this, resolutionPath, resolutionStack);
|
|
749
|
+
return this.#resolveAsyncDefaultEntry<Value>(fastBinding, this, resolutionPath, resolutionStack, callerContext);
|
|
850
750
|
}
|
|
851
|
-
const entry = this.#
|
|
751
|
+
const entry = this.#lookup.defaultEntry(token);
|
|
852
752
|
if (entry === null) {
|
|
853
753
|
return this.resolveAsync(token, undefined, resolutionPath, resolutionStack);
|
|
854
754
|
}
|
|
855
|
-
return this.#resolveAsyncDefaultEntry<Value>(
|
|
755
|
+
return this.#resolveAsyncDefaultEntry<Value>(
|
|
756
|
+
entry.binding,
|
|
757
|
+
entry.owner,
|
|
758
|
+
resolutionPath,
|
|
759
|
+
resolutionStack,
|
|
760
|
+
callerContext,
|
|
761
|
+
);
|
|
856
762
|
}
|
|
857
763
|
|
|
858
764
|
#resolveAsyncDefaultEntry<const Value>(
|
|
@@ -860,6 +766,7 @@ export class DependencyResolver {
|
|
|
860
766
|
owner: DependencyResolver,
|
|
861
767
|
resolutionPath: Array<string>,
|
|
862
768
|
resolutionStack: Array<ResolutionFrame>,
|
|
769
|
+
callerContext?: DefaultResolutionContext,
|
|
863
770
|
): Promise<Value> {
|
|
864
771
|
if (
|
|
865
772
|
binding.kind === "constant" &&
|
|
@@ -879,12 +786,12 @@ export class DependencyResolver {
|
|
|
879
786
|
binding as Binding<Value> & { kind: "dynamic" | "dynamic-async" },
|
|
880
787
|
resolutionPath,
|
|
881
788
|
resolutionStack,
|
|
789
|
+
callerContext,
|
|
882
790
|
);
|
|
883
791
|
}
|
|
884
792
|
} else if (scope === "singleton") {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
return Promise.resolve(cachedSingleton as Value);
|
|
793
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
794
|
+
return Promise.resolve(binding.instance as Value);
|
|
888
795
|
}
|
|
889
796
|
if (owner !== this) {
|
|
890
797
|
return owner.#resolveBindingAsync(binding as Binding<Value>, undefined, resolutionPath, resolutionStack);
|
|
@@ -918,7 +825,7 @@ export class DependencyResolver {
|
|
|
918
825
|
|
|
919
826
|
let currentToken: Token<unknown> | Constructor = token;
|
|
920
827
|
let visitedAliasTokens: Set<Token<unknown> | Constructor> | undefined;
|
|
921
|
-
let aliasFollowed: DefaultLookupEntry | undefined = found;
|
|
828
|
+
let aliasFollowed: DefaultLookupEntry<DependencyResolver> | undefined = found;
|
|
922
829
|
while (aliasFollowed !== undefined && aliasFollowed.binding.kind === "alias") {
|
|
923
830
|
const target = aliasFollowed.binding.target;
|
|
924
831
|
visitedAliasTokens ??= new Set([currentToken]);
|
|
@@ -969,8 +876,8 @@ export class DependencyResolver {
|
|
|
969
876
|
|
|
970
877
|
// Singleton cache
|
|
971
878
|
if (scope === "singleton") {
|
|
972
|
-
if (
|
|
973
|
-
return
|
|
879
|
+
if (binding.instance !== NO_INSTANCE) {
|
|
880
|
+
return binding.instance as Value;
|
|
974
881
|
}
|
|
975
882
|
// In-flight dedup
|
|
976
883
|
const inflight = this.#scope.getInflight(binding.id);
|
|
@@ -993,14 +900,9 @@ export class DependencyResolver {
|
|
|
993
900
|
const frameName = frame.tokenName;
|
|
994
901
|
const resolutionSet = enterResolutionPath(resolutionPath, frameName, false);
|
|
995
902
|
resolutionStack.push(frame);
|
|
996
|
-
const needsActivation = this.#needsActivation(binding);
|
|
903
|
+
const needsActivation = this.#activation.needsActivation(binding);
|
|
997
904
|
if (!needsActivation && scope === "transient" && (binding.kind === "dynamic" || binding.kind === "dynamic-async")) {
|
|
998
|
-
const resolutionCtx = new DefaultResolutionContext(
|
|
999
|
-
this as unknown as ResolverCallbacks,
|
|
1000
|
-
resolutionPath,
|
|
1001
|
-
resolutionStack,
|
|
1002
|
-
options,
|
|
1003
|
-
);
|
|
905
|
+
const resolutionCtx = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
1004
906
|
try {
|
|
1005
907
|
if (binding.kind === "dynamic-async") {
|
|
1006
908
|
return await binding.factory(resolutionCtx);
|
|
@@ -1016,7 +918,7 @@ export class DependencyResolver {
|
|
|
1016
918
|
|
|
1017
919
|
const needsResolutionContext = needsActivation || this.#requiresResolutionContext(binding);
|
|
1018
920
|
const resolutionCtx = needsResolutionContext
|
|
1019
|
-
? new DefaultResolutionContext(this
|
|
921
|
+
? new DefaultResolutionContext(this, resolutionPath, resolutionStack, options)
|
|
1020
922
|
: undefined;
|
|
1021
923
|
|
|
1022
924
|
try {
|
|
@@ -1024,7 +926,7 @@ export class DependencyResolver {
|
|
|
1024
926
|
const createSingletonPromise = async (): Promise<Value> => {
|
|
1025
927
|
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
1026
928
|
|
|
1027
|
-
const shouldActivate = this.#
|
|
929
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
1028
930
|
const activated = shouldActivate
|
|
1029
931
|
? await this.#lifecycle.runActivation(
|
|
1030
932
|
resolutionCtx as DefaultResolutionContext,
|
|
@@ -1034,7 +936,8 @@ export class DependencyResolver {
|
|
|
1034
936
|
)
|
|
1035
937
|
: instance;
|
|
1036
938
|
|
|
1037
|
-
this.#scope.setSingleton(binding
|
|
939
|
+
this.#scope.setSingleton(binding, activated);
|
|
940
|
+
binding.instance = activated;
|
|
1038
941
|
this.#scope.clearInflight(binding.id);
|
|
1039
942
|
return activated;
|
|
1040
943
|
};
|
|
@@ -1049,7 +952,7 @@ export class DependencyResolver {
|
|
|
1049
952
|
|
|
1050
953
|
const instance = await this.#instantiateAsync(binding, resolutionCtx, resolutionPath, resolutionStack);
|
|
1051
954
|
|
|
1052
|
-
const shouldActivate = this.#
|
|
955
|
+
const shouldActivate = this.#activation.refreshAfterFirstInstantiation(binding, needsActivation);
|
|
1053
956
|
const activated = shouldActivate
|
|
1054
957
|
? await this.#lifecycle.runActivation(
|
|
1055
958
|
resolutionCtx as DefaultResolutionContext,
|
|
@@ -1097,7 +1000,7 @@ export class DependencyResolver {
|
|
|
1097
1000
|
|
|
1098
1001
|
case "class": {
|
|
1099
1002
|
const deps = await this.#resolveClassDepsAsync(binding.target, resolutionPath, resolutionStack);
|
|
1100
|
-
const instance = this.#
|
|
1003
|
+
const instance = this.#classes.instantiate(binding.target, deps);
|
|
1101
1004
|
return instance as Value;
|
|
1102
1005
|
}
|
|
1103
1006
|
|
|
@@ -1125,7 +1028,7 @@ export class DependencyResolver {
|
|
|
1125
1028
|
resolutionPath: Array<string>,
|
|
1126
1029
|
resolutionStack: Array<ResolutionFrame>,
|
|
1127
1030
|
): Promise<Array<unknown>> {
|
|
1128
|
-
const meta = this.#
|
|
1031
|
+
const meta = this.#classes.constructorMetadata(target);
|
|
1129
1032
|
if (meta === undefined) {
|
|
1130
1033
|
if (target.length === 0) {
|
|
1131
1034
|
return [];
|
|
@@ -1397,30 +1300,6 @@ export class DependencyResolver {
|
|
|
1397
1300
|
return tokenName(token);
|
|
1398
1301
|
}
|
|
1399
1302
|
|
|
1400
|
-
#getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined {
|
|
1401
|
-
const cached = this.#classConstructorMetadata.get(target);
|
|
1402
|
-
if (cached !== undefined) {
|
|
1403
|
-
return cached === null ? undefined : cached;
|
|
1404
|
-
}
|
|
1405
|
-
const metadata = this.#metadataReader.getConstructorMetadata(target);
|
|
1406
|
-
this.#classConstructorMetadata.set(target, metadata ?? null);
|
|
1407
|
-
return metadata;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
#instantiateClass(target: Constructor, deps: Array<unknown>): unknown {
|
|
1411
|
-
let needsActiveContainer = this.#classNeedsActiveContainer.get(target);
|
|
1412
|
-
if (needsActiveContainer === undefined) {
|
|
1413
|
-
const accessorMetadata = this.#metadataReader.getAccessorMetadata?.(target);
|
|
1414
|
-
needsActiveContainer = (accessorMetadata?.length ?? 0) > 0;
|
|
1415
|
-
this.#classNeedsActiveContainer.set(target, needsActiveContainer);
|
|
1416
|
-
}
|
|
1417
|
-
const invokable = target as ConstructorInvocation;
|
|
1418
|
-
if (!needsActiveContainer) {
|
|
1419
|
-
return new invokable(...deps);
|
|
1420
|
-
}
|
|
1421
|
-
return runWithContainer(this.#container, () => new invokable(...deps));
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
1303
|
#matchesRequestedTag(
|
|
1425
1304
|
tagKey: string,
|
|
1426
1305
|
tagValue: unknown,
|
|
@@ -1451,10 +1330,7 @@ export class DependencyResolver {
|
|
|
1451
1330
|
resolutionPath: Array<string>,
|
|
1452
1331
|
resolutionStack: Array<ResolutionFrame>,
|
|
1453
1332
|
): Value {
|
|
1454
|
-
// One lane at every depth.
|
|
1455
|
-
// an O(depth) `resolutionPath.includes()` scan, which had to be escaped past ~32 levels; with
|
|
1456
|
-
// the O(1) `binding.inFlight` mark there is nothing to escape, so the depth split — and the
|
|
1457
|
-
// divergent behaviour it caused — is gone.
|
|
1333
|
+
// One lane at every depth: `binding.inFlight` is O(1), so there is nothing to escape.
|
|
1458
1334
|
const frame = this.#getResolutionFrame(binding);
|
|
1459
1335
|
const tokenDisplayName = frame.tokenName;
|
|
1460
1336
|
if (binding.inFlight) {
|
|
@@ -1477,23 +1353,15 @@ export class DependencyResolver {
|
|
|
1477
1353
|
}
|
|
1478
1354
|
}
|
|
1479
1355
|
|
|
1480
|
-
//
|
|
1481
|
-
// every invocation. Cleanup is handled via .then(onFulfilled, onRejected) so the behaviour is
|
|
1482
|
-
// identical to a try/finally but without the async machinery overhead.
|
|
1356
|
+
// Deliberately not `async`: that would allocate a state machine and a promise per level.
|
|
1483
1357
|
#resolveTransientDynamicAsyncFromContext<const Value>(
|
|
1484
1358
|
binding: Binding<Value> & { kind: "dynamic" | "dynamic-async" },
|
|
1485
1359
|
resolutionPath: Array<string>,
|
|
1486
1360
|
resolutionStack: Array<ResolutionFrame>,
|
|
1361
|
+
callerContext?: DefaultResolutionContext,
|
|
1487
1362
|
): Promise<Value> {
|
|
1488
|
-
//
|
|
1489
|
-
|
|
1490
|
-
// and adapts on its own: a linear scan while the path is short, an attached Set past
|
|
1491
|
-
// RESOLUTION_SET_THRESHOLD. That removes the depth split, and with it a silent change of
|
|
1492
|
-
// behaviour (context identity, stack frames, promise shape) at the old threshold.
|
|
1493
|
-
//
|
|
1494
|
-
// For a sequential chain every level shares one resolutionPath/resolutionStack, so a single
|
|
1495
|
-
// DefaultResolutionContext serves the whole chain: inner levels of the owning chain allocate
|
|
1496
|
-
// nothing. A concurrent chain is detected by path identity and gets its own context.
|
|
1363
|
+
// Path-scoped cycle detection, because async chains interleave — see ARCHITECTURE.md.
|
|
1364
|
+
const pool = this.#asyncChainContextPool;
|
|
1497
1365
|
const frame = this.#getResolutionFrame(binding);
|
|
1498
1366
|
const tokenDisplayName = frame.tokenName;
|
|
1499
1367
|
try {
|
|
@@ -1503,40 +1371,13 @@ export class DependencyResolver {
|
|
|
1503
1371
|
return Promise.reject(cycleError);
|
|
1504
1372
|
}
|
|
1505
1373
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
if (existing === undefined) {
|
|
1514
|
-
ctx = new DefaultResolutionContext(
|
|
1515
|
-
this as unknown as ResolverCallbacks,
|
|
1516
|
-
resolutionPath,
|
|
1517
|
-
resolutionStack,
|
|
1518
|
-
undefined,
|
|
1519
|
-
);
|
|
1520
|
-
this.#asyncChainCtx = ctx;
|
|
1521
|
-
} else {
|
|
1522
|
-
existing.reset(this as unknown as ResolverCallbacks, resolutionPath, resolutionStack, undefined);
|
|
1523
|
-
ctx = existing;
|
|
1524
|
-
}
|
|
1525
|
-
this.#asyncChainCtxPath = resolutionPath;
|
|
1526
|
-
isOwnerLevel = true;
|
|
1527
|
-
} else {
|
|
1528
|
-
ctx = new DefaultResolutionContext(
|
|
1529
|
-
this as unknown as ResolverCallbacks,
|
|
1530
|
-
resolutionPath,
|
|
1531
|
-
resolutionStack,
|
|
1532
|
-
undefined,
|
|
1533
|
-
);
|
|
1534
|
-
isOwnerLevel = false;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
if (isOwnerLevel) {
|
|
1538
|
-
this.#asyncChainActiveLevels++;
|
|
1539
|
-
}
|
|
1374
|
+
// An inner level reuses the context its caller passed down; only a chain's first level
|
|
1375
|
+
// borrows from the pool. Pooling is load-bearing here — see ARCHITECTURE.md.
|
|
1376
|
+
const ctx =
|
|
1377
|
+
callerContext !== undefined && callerContext.owner === this
|
|
1378
|
+
? callerContext
|
|
1379
|
+
: this.#acquireAsyncChainContext(resolutionPath, resolutionStack);
|
|
1380
|
+
ctx.chainLevels += 1;
|
|
1540
1381
|
|
|
1541
1382
|
// Invoke the factory synchronously to get its Promise (or a resolved value for "dynamic").
|
|
1542
1383
|
let factoryPromise: Promise<Value>;
|
|
@@ -1551,41 +1392,40 @@ export class DependencyResolver {
|
|
|
1551
1392
|
} catch (factoryError) {
|
|
1552
1393
|
// Synchronous throw from the factory (rare) — clean up immediately.
|
|
1553
1394
|
exitResolutionPath(resolutionPath);
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1395
|
+
ctx.chainLevels -= 1;
|
|
1396
|
+
if (ctx.chainLevels === 0) {
|
|
1397
|
+
pool.push(ctx);
|
|
1557
1398
|
}
|
|
1558
1399
|
return Promise.reject(factoryError);
|
|
1559
1400
|
}
|
|
1560
1401
|
|
|
1561
|
-
//
|
|
1562
|
-
//
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
// await (or .catch) the returned promise.
|
|
1567
|
-
//
|
|
1568
|
-
// Every level of the owning chain unwinds identically, so one closure serves them all.
|
|
1569
|
-
let settle: () => void;
|
|
1570
|
-
if (isOwnerLevel) {
|
|
1571
|
-
settle =
|
|
1572
|
-
this.#asyncChainSettle ??
|
|
1573
|
-
(this.#asyncChainSettle = (): void => {
|
|
1574
|
-
exitResolutionPath(resolutionPath);
|
|
1575
|
-
if (--this.#asyncChainActiveLevels === 0) {
|
|
1576
|
-
this.#asyncChainCtxPath = undefined;
|
|
1577
|
-
this.#asyncChainSettle = undefined;
|
|
1578
|
-
}
|
|
1579
|
-
});
|
|
1580
|
-
} else {
|
|
1581
|
-
settle = (): void => {
|
|
1402
|
+
// A side listener, not a derived chain: FIFO puts it before the awaiting caller resumes.
|
|
1403
|
+
// It marks a rejection as handled, so callers must await or `.catch` the returned promise.
|
|
1404
|
+
const settle =
|
|
1405
|
+
ctx.chainSettle ??
|
|
1406
|
+
(ctx.chainSettle = (): void => {
|
|
1582
1407
|
exitResolutionPath(resolutionPath);
|
|
1583
|
-
|
|
1584
|
-
|
|
1408
|
+
ctx.chainLevels -= 1;
|
|
1409
|
+
if (ctx.chainLevels === 0) {
|
|
1410
|
+
pool.push(ctx);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1585
1413
|
factoryPromise.then(settle, settle);
|
|
1586
1414
|
return factoryPromise;
|
|
1587
1415
|
}
|
|
1588
1416
|
|
|
1417
|
+
#acquireAsyncChainContext(
|
|
1418
|
+
resolutionPath: Array<string>,
|
|
1419
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
1420
|
+
): DefaultResolutionContext {
|
|
1421
|
+
const pooled = this.#asyncChainContextPool.pop();
|
|
1422
|
+
if (pooled === undefined) {
|
|
1423
|
+
return new DefaultResolutionContext(this, resolutionPath, resolutionStack, undefined);
|
|
1424
|
+
}
|
|
1425
|
+
pooled.reset(this, resolutionPath, resolutionStack, undefined);
|
|
1426
|
+
return pooled;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1589
1429
|
#resolveCandidateSync<const Value>(
|
|
1590
1430
|
binding: Binding<Value>,
|
|
1591
1431
|
options: ResolveOptions | undefined,
|
|
@@ -1603,8 +1443,8 @@ export class DependencyResolver {
|
|
|
1603
1443
|
return this.resolve(binding.target, options, resolutionPath, resolutionStack);
|
|
1604
1444
|
}
|
|
1605
1445
|
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
1606
|
-
if (scope === "singleton" &&
|
|
1607
|
-
return
|
|
1446
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
1447
|
+
return binding.instance as Value;
|
|
1608
1448
|
}
|
|
1609
1449
|
if (scope === "scoped") {
|
|
1610
1450
|
if (!this.#scope.isChild) {
|
|
@@ -1636,8 +1476,8 @@ export class DependencyResolver {
|
|
|
1636
1476
|
return this.resolveAsync(binding.target, options, isolatedPath, isolatedStack);
|
|
1637
1477
|
}
|
|
1638
1478
|
const scope = (binding as BindingWithScope).scope ?? "transient";
|
|
1639
|
-
if (scope === "singleton" &&
|
|
1640
|
-
return Promise.resolve(
|
|
1479
|
+
if (scope === "singleton" && binding.instance !== NO_INSTANCE) {
|
|
1480
|
+
return Promise.resolve(binding.instance as Value);
|
|
1641
1481
|
}
|
|
1642
1482
|
if (scope === "scoped") {
|
|
1643
1483
|
if (!this.#scope.isChild) {
|
|
@@ -1664,70 +1504,6 @@ export class DependencyResolver {
|
|
|
1664
1504
|
return frame;
|
|
1665
1505
|
}
|
|
1666
1506
|
|
|
1667
|
-
#needsActivation<const Value>(binding: Binding<Value>): boolean {
|
|
1668
|
-
const lifecycleVersion = this.#lifecycle.activationVersion;
|
|
1669
|
-
if (
|
|
1670
|
-
lifecycleVersion === 0 &&
|
|
1671
|
-
binding.kind !== "class" &&
|
|
1672
|
-
binding.kind !== "alias" &&
|
|
1673
|
-
binding.onActivation === undefined
|
|
1674
|
-
) {
|
|
1675
|
-
return false;
|
|
1676
|
-
}
|
|
1677
|
-
if (this.#activationCacheVersion !== lifecycleVersion) {
|
|
1678
|
-
this.#activationNeedByBindingId.clear();
|
|
1679
|
-
this.#activationCacheVersion = lifecycleVersion;
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
const cached = this.#activationNeedByBindingId.get(binding.id);
|
|
1683
|
-
if (cached !== undefined) {
|
|
1684
|
-
return cached;
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
if (binding.kind === "class") {
|
|
1688
|
-
let hasActivation = this.#lifecycle.hasActivationHandlers(binding.token) || binding.onActivation !== undefined;
|
|
1689
|
-
const cachedPostConstruct = this.#classHasPostConstruct.get(binding.target);
|
|
1690
|
-
// Unknown class lifecycle metadata: activate once, then cache after first instantiation.
|
|
1691
|
-
if (cachedPostConstruct === undefined) {
|
|
1692
|
-
hasActivation = true;
|
|
1693
|
-
} else if (cachedPostConstruct) {
|
|
1694
|
-
hasActivation = true;
|
|
1695
|
-
}
|
|
1696
|
-
this.#activationNeedByBindingId.set(binding.id, hasActivation);
|
|
1697
|
-
return hasActivation;
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
let hasActivation = false;
|
|
1701
|
-
if (binding.kind !== "alias" && binding.onActivation !== undefined) {
|
|
1702
|
-
hasActivation = true;
|
|
1703
|
-
} else if (this.#lifecycle.hasActivationHandlers(binding.token)) {
|
|
1704
|
-
hasActivation = true;
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
this.#activationNeedByBindingId.set(binding.id, hasActivation);
|
|
1708
|
-
return hasActivation;
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
#refreshClassPostConstructCache(target: Constructor): void {
|
|
1712
|
-
const lifecycle = this.#metadataReader.getLifecycleMetadata(target);
|
|
1713
|
-
const hasPostConstruct =
|
|
1714
|
-
lifecycle !== undefined && lifecycle.postConstruct !== undefined && lifecycle.postConstruct.length > 0;
|
|
1715
|
-
this.#classHasPostConstruct.set(target, hasPostConstruct);
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
/**
|
|
1719
|
-
* Refreshes the post-construct cache for class bindings on first instantiation and
|
|
1720
|
-
* returns the (possibly updated) shouldActivate flag.
|
|
1721
|
-
*/
|
|
1722
|
-
#refreshActivationCacheIfNeeded<Value>(binding: Binding<Value>, needsActivation: boolean): boolean {
|
|
1723
|
-
if (binding.kind === "class" && this.#classHasPostConstruct.get(binding.target) === undefined) {
|
|
1724
|
-
this.#refreshClassPostConstructCache(binding.target);
|
|
1725
|
-
this.#activationNeedByBindingId.delete(binding.id);
|
|
1726
|
-
return this.#needsActivation(binding);
|
|
1727
|
-
}
|
|
1728
|
-
return needsActivation;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
1507
|
#requiresResolutionContext<const Value>(binding: Binding<Value>): boolean {
|
|
1732
1508
|
return binding.kind === "dynamic" || binding.kind === "dynamic-async";
|
|
1733
1509
|
}
|
|
@@ -1740,15 +1516,10 @@ export class DependencyResolver {
|
|
|
1740
1516
|
const depth = resolutionStack.length;
|
|
1741
1517
|
const existing = this.#syncResolutionContextPool[depth];
|
|
1742
1518
|
if (existing !== undefined) {
|
|
1743
|
-
existing.reset(this
|
|
1519
|
+
existing.reset(this, resolutionPath, resolutionStack, options);
|
|
1744
1520
|
return existing;
|
|
1745
1521
|
}
|
|
1746
|
-
const created = new DefaultResolutionContext(
|
|
1747
|
-
this as unknown as ResolverCallbacks,
|
|
1748
|
-
resolutionPath,
|
|
1749
|
-
resolutionStack,
|
|
1750
|
-
options,
|
|
1751
|
-
);
|
|
1522
|
+
const created = new DefaultResolutionContext(this, resolutionPath, resolutionStack, options);
|
|
1752
1523
|
this.#syncResolutionContextPool[depth] = created;
|
|
1753
1524
|
return created;
|
|
1754
1525
|
}
|