@codefast/di 0.5.0 → 0.6.1
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 +712 -0
- package/README.md +119 -30
- package/dist/ambient/active-container.d.ts +37 -0
- package/dist/ambient/active-container.d.ts.map +1 -0
- package/dist/ambient/active-container.js +41 -0
- package/dist/ambient/active-container.js.map +1 -0
- package/dist/container/binding-builders.d.ts +12 -8
- package/dist/container/binding-builders.d.ts.map +1 -1
- package/dist/container/binding-builders.js +62 -31
- package/dist/container/binding-builders.js.map +1 -1
- package/dist/container/container.d.ts +33 -19
- package/dist/container/container.d.ts.map +1 -1
- package/dist/container/container.js +281 -118
- package/dist/container/container.js.map +1 -1
- package/dist/{resolution → core}/binding-scope.d.ts +2 -2
- package/dist/core/binding-scope.d.ts.map +1 -0
- package/dist/core/binding-scope.js.map +1 -0
- package/dist/{binding.d.ts → core/binding.d.ts} +70 -15
- package/dist/core/binding.d.ts.map +1 -0
- package/dist/{binding.js → core/binding.js} +27 -12
- package/dist/core/binding.js.map +1 -0
- package/dist/core/constraint-requirement.d.ts +53 -0
- package/dist/core/constraint-requirement.d.ts.map +1 -0
- package/dist/core/constraint-requirement.js +62 -0
- package/dist/core/constraint-requirement.js.map +1 -0
- package/dist/{constructor-type.d.ts → core/constructor-type.d.ts} +1 -1
- package/dist/core/constructor-type.d.ts.map +1 -0
- package/dist/core/constructor-type.js.map +1 -0
- package/dist/{module.d.ts → core/module.d.ts} +5 -5
- package/dist/core/module.d.ts.map +1 -0
- package/dist/{module.js → core/module.js} +4 -4
- package/dist/core/module.js.map +1 -0
- package/dist/{registry.d.ts → core/registry.d.ts} +27 -7
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/{registry.js → core/registry.js} +125 -52
- package/dist/core/registry.js.map +1 -0
- package/dist/core/tag.d.ts +80 -0
- package/dist/core/tag.d.ts.map +1 -0
- package/dist/core/tag.js +96 -0
- package/dist/core/tag.js.map +1 -0
- package/dist/{token.d.ts → core/token.d.ts} +2 -6
- package/dist/core/token.d.ts.map +1 -0
- package/dist/core/token.js +13 -0
- package/dist/core/token.js.map +1 -0
- package/dist/{types.d.ts → core/types.d.ts} +27 -21
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js.map +1 -0
- package/dist/decorators/inject.d.ts +5 -49
- package/dist/decorators/inject.d.ts.map +1 -1
- package/dist/decorators/inject.js +34 -104
- package/dist/decorators/inject.js.map +1 -1
- package/dist/decorators/injectable.d.ts +25 -5
- package/dist/decorators/injectable.d.ts.map +1 -1
- package/dist/decorators/injectable.js +2 -3
- package/dist/decorators/injectable.js.map +1 -1
- package/dist/decorators/lifecycle-decorators.d.ts +4 -2
- package/dist/decorators/lifecycle-decorators.d.ts.map +1 -1
- package/dist/decorators/lifecycle-decorators.js +19 -25
- package/dist/decorators/lifecycle-decorators.js.map +1 -1
- package/dist/{resolution → errors}/diagnostics.d.ts +2 -0
- package/dist/errors/diagnostics.d.ts.map +1 -0
- package/dist/errors/diagnostics.js.map +1 -0
- package/dist/{errors.d.ts → errors/errors.d.ts} +89 -4
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/{errors.js → errors/errors.js} +129 -7
- package/dist/errors/errors.js.map +1 -0
- package/dist/index.d.ts +21 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -7
- package/dist/index.js.map +1 -1
- package/dist/injection/descriptor.d.ts +79 -0
- package/dist/injection/descriptor.d.ts.map +1 -0
- package/dist/injection/descriptor.js +118 -0
- package/dist/injection/descriptor.js.map +1 -0
- package/dist/{resolution → injection}/resolve-options.d.ts +21 -11
- package/dist/injection/resolve-options.d.ts.map +1 -0
- package/dist/injection/resolve-options.js +101 -0
- package/dist/injection/resolve-options.js.map +1 -0
- package/dist/introspection/dependency-graph.d.ts +16 -5
- package/dist/introspection/dependency-graph.d.ts.map +1 -1
- package/dist/introspection/dependency-graph.js +138 -57
- package/dist/introspection/dependency-graph.js.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.d.ts +6 -3
- package/dist/introspection/graph-adapters/cytoscape.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/cytoscape.js +3 -0
- package/dist/introspection/graph-adapters/cytoscape.js.map +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/dot.js +12 -5
- package/dist/introspection/graph-adapters/dot.js.map +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +9 -0
- package/dist/introspection/graph-adapters/mermaid.d.ts.map +1 -0
- package/dist/introspection/graph-adapters/mermaid.js +47 -0
- package/dist/introspection/graph-adapters/mermaid.js.map +1 -0
- package/dist/introspection/graph-adapters/reactflow.d.ts +6 -3
- package/dist/introspection/graph-adapters/reactflow.d.ts.map +1 -1
- package/dist/introspection/graph-adapters/reactflow.js +5 -1
- package/dist/introspection/graph-adapters/reactflow.js.map +1 -1
- package/dist/introspection/inspector.d.ts +4 -4
- package/dist/introspection/inspector.d.ts.map +1 -1
- package/dist/introspection/inspector.js +10 -6
- package/dist/introspection/inspector.js.map +1 -1
- package/dist/lifecycle/lifecycle-manager.d.ts +27 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -0
- package/dist/{resolution/lifecycle.js → lifecycle/lifecycle-manager.js} +39 -9
- package/dist/lifecycle/lifecycle-manager.js.map +1 -0
- package/dist/lifecycle/scope-manager.d.ts +48 -0
- package/dist/lifecycle/scope-manager.d.ts.map +1 -0
- package/dist/lifecycle/scope-manager.js +135 -0
- package/dist/lifecycle/scope-manager.js.map +1 -0
- package/dist/metadata/metadata-reader-token.d.ts +1 -1
- package/dist/metadata/metadata-reader-token.d.ts.map +1 -1
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-reader-token.js.map +1 -1
- package/dist/metadata/metadata-types.d.ts +9 -9
- package/dist/metadata/metadata-types.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.d.ts +19 -6
- package/dist/metadata/symbol-metadata-reader.d.ts.map +1 -1
- package/dist/metadata/symbol-metadata-reader.js +89 -9
- package/dist/metadata/symbol-metadata-reader.js.map +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +16 -0
- package/dist/metadata/verifying-metadata-reader.d.ts.map +1 -0
- package/dist/metadata/verifying-metadata-reader.js +32 -0
- package/dist/metadata/verifying-metadata-reader.js.map +1 -0
- package/dist/resolution/{activation-need.d.ts → cache/activation-need.d.ts} +5 -5
- package/dist/resolution/cache/activation-need.d.ts.map +1 -0
- package/dist/resolution/cache/activation-need.js.map +1 -0
- package/dist/resolution/{binding-lookup-cache.d.ts → cache/binding-lookup-cache.d.ts} +9 -5
- package/dist/resolution/cache/binding-lookup-cache.d.ts.map +1 -0
- package/dist/resolution/{binding-lookup-cache.js → cache/binding-lookup-cache.js} +55 -0
- package/dist/resolution/cache/binding-lookup-cache.js.map +1 -0
- package/dist/resolution/cache/class-introspector.d.ts +66 -0
- package/dist/resolution/cache/class-introspector.d.ts.map +1 -0
- package/dist/resolution/cache/class-introspector.js +194 -0
- package/dist/resolution/cache/class-introspector.js.map +1 -0
- package/dist/resolution/context.d.ts +84 -0
- package/dist/resolution/context.d.ts.map +1 -0
- package/dist/resolution/{environment.js → context.js} +54 -80
- package/dist/resolution/context.js.map +1 -0
- package/dist/resolution/path/resolution-path.d.ts +94 -0
- package/dist/resolution/path/resolution-path.d.ts.map +1 -0
- package/dist/resolution/path/resolution-path.js +117 -0
- package/dist/resolution/path/resolution-path.js.map +1 -0
- package/dist/resolution/{instantiation-plan.d.ts → plan/instantiation-plan.d.ts} +37 -7
- package/dist/resolution/plan/instantiation-plan.d.ts.map +1 -0
- package/dist/resolution/plan/instantiation-plan.js +419 -0
- package/dist/resolution/plan/instantiation-plan.js.map +1 -0
- package/dist/resolution/resolver.d.ts +28 -28
- package/dist/resolution/resolver.d.ts.map +1 -1
- package/dist/resolution/resolver.js +581 -239
- package/dist/resolution/resolver.js.map +1 -1
- package/dist/resolution/select/binding-select.d.ts +34 -0
- package/dist/resolution/select/binding-select.d.ts.map +1 -0
- package/dist/resolution/select/binding-select.js +175 -0
- package/dist/resolution/select/binding-select.js.map +1 -0
- package/dist/resolution/{constraints.d.ts → select/constraints.d.ts} +13 -12
- package/dist/resolution/select/constraints.d.ts.map +1 -0
- package/dist/resolution/{constraints.js → select/constraints.js} +33 -22
- package/dist/resolution/select/constraints.js.map +1 -0
- package/package.json +160 -20
- package/src/ambient/active-container.ts +65 -0
- package/src/container/binding-builders.ts +79 -44
- package/src/container/container.ts +357 -170
- package/src/{resolution → core}/binding-scope.ts +2 -2
- package/src/{binding.ts → core/binding.ts} +101 -39
- package/src/core/constraint-requirement.ts +94 -0
- package/src/{constructor-type.ts → core/constructor-type.ts} +1 -1
- package/src/{module.ts → core/module.ts} +10 -10
- package/src/core/registry.ts +365 -0
- package/src/core/tag.ts +158 -0
- package/src/{token.ts → core/token.ts} +2 -14
- package/src/{types.ts → core/types.ts} +37 -34
- package/src/decorators/inject.ts +39 -175
- package/src/decorators/injectable.ts +40 -10
- package/src/decorators/lifecycle-decorators.ts +22 -34
- package/src/{resolution → errors}/diagnostics.ts +2 -0
- package/src/{errors.ts → errors/errors.ts} +152 -8
- package/src/index.ts +43 -15
- package/src/injection/descriptor.ts +206 -0
- package/src/injection/resolve-options.ts +144 -0
- package/src/introspection/dependency-graph.ts +223 -63
- package/src/introspection/graph-adapters/cytoscape.ts +9 -3
- package/src/introspection/graph-adapters/dot.ts +13 -5
- package/src/introspection/graph-adapters/mermaid.ts +61 -0
- package/src/introspection/graph-adapters/reactflow.ts +11 -4
- package/src/introspection/inspector.ts +16 -12
- package/src/{resolution/lifecycle.ts → lifecycle/lifecycle-manager.ts} +68 -32
- package/src/lifecycle/scope-manager.ts +156 -0
- package/src/metadata/metadata-reader-token.ts +2 -2
- package/src/metadata/metadata-types.ts +9 -9
- package/src/metadata/symbol-metadata-reader.ts +95 -18
- package/src/metadata/verifying-metadata-reader.ts +41 -0
- package/src/resolution/{activation-need.ts → cache/activation-need.ts} +8 -8
- package/src/resolution/{binding-lookup-cache.ts → cache/binding-lookup-cache.ts} +63 -5
- package/src/resolution/cache/class-introspector.ts +251 -0
- package/src/resolution/{environment.ts → context.ts} +84 -145
- package/src/resolution/path/resolution-path.ts +167 -0
- package/src/resolution/plan/instantiation-plan.ts +615 -0
- package/src/resolution/resolver.ts +663 -313
- package/src/resolution/select/binding-select.ts +210 -0
- package/src/resolution/select/constraints.ts +130 -0
- package/dist/binding.d.ts.map +0 -1
- package/dist/binding.js.map +0 -1
- package/dist/constructor-type.d.ts.map +0 -1
- package/dist/constructor-type.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/module.d.ts.map +0 -1
- package/dist/module.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/resolution/activation-need.d.ts.map +0 -1
- package/dist/resolution/activation-need.js.map +0 -1
- package/dist/resolution/binding-lookup-cache.d.ts.map +0 -1
- package/dist/resolution/binding-lookup-cache.js.map +0 -1
- package/dist/resolution/binding-scope.d.ts.map +0 -1
- package/dist/resolution/binding-scope.js.map +0 -1
- package/dist/resolution/binding-select.d.ts +0 -23
- package/dist/resolution/binding-select.d.ts.map +0 -1
- package/dist/resolution/binding-select.js +0 -121
- package/dist/resolution/binding-select.js.map +0 -1
- package/dist/resolution/class-introspector.d.ts +0 -27
- package/dist/resolution/class-introspector.d.ts.map +0 -1
- package/dist/resolution/class-introspector.js +0 -60
- package/dist/resolution/class-introspector.js.map +0 -1
- package/dist/resolution/constraints.d.ts.map +0 -1
- package/dist/resolution/constraints.js.map +0 -1
- package/dist/resolution/diagnostics.d.ts.map +0 -1
- package/dist/resolution/diagnostics.js.map +0 -1
- package/dist/resolution/environment.d.ts +0 -94
- package/dist/resolution/environment.d.ts.map +0 -1
- package/dist/resolution/environment.js.map +0 -1
- package/dist/resolution/instantiation-plan.d.ts.map +0 -1
- package/dist/resolution/instantiation-plan.js +0 -183
- package/dist/resolution/instantiation-plan.js.map +0 -1
- package/dist/resolution/lifecycle.d.ts +0 -23
- package/dist/resolution/lifecycle.d.ts.map +0 -1
- package/dist/resolution/lifecycle.js.map +0 -1
- package/dist/resolution/resolution-path.d.ts +0 -98
- package/dist/resolution/resolution-path.d.ts.map +0 -1
- package/dist/resolution/resolution-path.js +0 -98
- package/dist/resolution/resolution-path.js.map +0 -1
- package/dist/resolution/resolve-options.d.ts.map +0 -1
- package/dist/resolution/resolve-options.js +0 -56
- package/dist/resolution/resolve-options.js.map +0 -1
- package/dist/resolution/scope.d.ts +0 -28
- package/dist/resolution/scope.d.ts.map +0 -1
- package/dist/resolution/scope.js +0 -88
- package/dist/resolution/scope.js.map +0 -1
- package/dist/token.d.ts.map +0 -1
- package/dist/token.js +0 -22
- package/dist/token.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/registry.ts +0 -282
- package/src/resolution/binding-select.ts +0 -154
- package/src/resolution/class-introspector.ts +0 -74
- package/src/resolution/constraints.ts +0 -121
- package/src/resolution/instantiation-plan.ts +0 -292
- package/src/resolution/resolution-path.ts +0 -156
- package/src/resolution/resolve-options.ts +0 -89
- package/src/resolution/scope.ts +0 -105
- /package/dist/{resolution → core}/binding-scope.js +0 -0
- /package/dist/{constructor-type.js → core/constructor-type.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
- /package/dist/{resolution → errors}/diagnostics.js +0 -0
- /package/dist/resolution/{activation-need.js → cache/activation-need.js} +0 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles a transient class or resolved-factory binding into a nested-constructor closure.
|
|
3
|
+
*
|
|
4
|
+
* @remarks A dependency the compiler cannot inline escapes to a resolver callback instead; the
|
|
5
|
+
* closure must stay callable for it, which is what bounds what may be inlined.
|
|
6
|
+
*/
|
|
7
|
+
import type { Binding } from "#/core/binding";
|
|
8
|
+
import { NO_INSTANCE } from "#/core/binding";
|
|
9
|
+
import type { ConstructorInvocation } from "#/core/constructor-type";
|
|
10
|
+
import type { Token } from "#/core/token";
|
|
11
|
+
import { tokenName } from "#/core/token";
|
|
12
|
+
import type { Constructor, ResolutionFrame, ResolveOptions } from "#/core/types";
|
|
13
|
+
import { AsyncResolutionError } from "#/errors/errors";
|
|
14
|
+
import type { DependencySlot } from "#/injection/resolve-options";
|
|
15
|
+
import { injectionSlotToResolveOptions, isNameOnlyOptions } from "#/injection/resolve-options";
|
|
16
|
+
import type { ConstructorMetadata } from "#/metadata/metadata-types";
|
|
17
|
+
|
|
18
|
+
// Past this depth a dependency escapes to the runtime path rather than inlining further —
|
|
19
|
+
// compiled closures nest one JS frame per level, and pathological graphs are the runtime's job.
|
|
20
|
+
const PLAN_DEPTH_LIMIT = 32;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Compilation asked to retry later (class lifecycle metadata not discovered yet).
|
|
24
|
+
*
|
|
25
|
+
* @since 0.5.0-canary.7
|
|
26
|
+
*/
|
|
27
|
+
export const PLAN_RETRY: unique symbol = Symbol("di:plan-retry");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A compiled plan, `null` for "not plannable under the current cache versions",
|
|
31
|
+
* or {@link PLAN_RETRY} when a first runtime resolve must discover metadata first.
|
|
32
|
+
*
|
|
33
|
+
* @since 0.5.0-canary.7
|
|
34
|
+
*/
|
|
35
|
+
export type InstantiationPlanCompileResult = (() => unknown) | null | typeof PLAN_RETRY;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What compiling one *dependency* can yield.
|
|
39
|
+
*
|
|
40
|
+
* @remarks No `null`: a dependency escapes rather than failing, so "no plan" is only ever a
|
|
41
|
+
* verdict on a plan's root.
|
|
42
|
+
*/
|
|
43
|
+
type DependencyCompileResult = (() => unknown) | typeof PLAN_RETRY;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether a compiled async thunk's return needs awaiting: never, at runtime's discretion, or always.
|
|
47
|
+
*
|
|
48
|
+
* @remarks Settled at compile time so a fully synchronous subtree touches no promise, while anything
|
|
49
|
+
* that may yield one routes through `Promise.all` — the interpreted async path awaits every dependency.
|
|
50
|
+
*/
|
|
51
|
+
type ThunkPromiseShape = "never" | "maybe" | "always";
|
|
52
|
+
|
|
53
|
+
/** One compiled async dependency: its thunk plus what its return needs from the consumer. */
|
|
54
|
+
interface AsyncNodeThunk {
|
|
55
|
+
readonly run: () => unknown;
|
|
56
|
+
readonly promiseShape: ThunkPromiseShape;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type AsyncDependencyCompileResult = AsyncNodeThunk | typeof PLAN_RETRY;
|
|
60
|
+
|
|
61
|
+
function allSynchronous(deps: ReadonlyArray<AsyncNodeThunk>): boolean {
|
|
62
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
63
|
+
if (deps[index]!.promiseShape !== "never") {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The promise-aware combinator: run every dep thunk, await them together, then apply.
|
|
72
|
+
*
|
|
73
|
+
* @remarks A dep's sync throw becomes that slot's rejection so its siblings still start — the
|
|
74
|
+
* interpreted path starts every sibling before the first rejection propagates, and so does this.
|
|
75
|
+
*/
|
|
76
|
+
function settleThenApply(
|
|
77
|
+
deps: ReadonlyArray<AsyncNodeThunk>,
|
|
78
|
+
apply: (values: Array<unknown>) => unknown,
|
|
79
|
+
): () => Promise<unknown> {
|
|
80
|
+
return () => {
|
|
81
|
+
const pending = new Array<unknown>(deps.length);
|
|
82
|
+
for (let index = 0; index < deps.length; index += 1) {
|
|
83
|
+
try {
|
|
84
|
+
pending[index] = deps[index]!.run();
|
|
85
|
+
} catch (dependencyError) {
|
|
86
|
+
pending[index] = Promise.reject(dependencyError);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return Promise.all(pending).then(apply);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A dependency's terminal binding — all a compiled thunk needs.
|
|
95
|
+
*
|
|
96
|
+
* @since 0.5.0-canary.7
|
|
97
|
+
*/
|
|
98
|
+
export interface InstantiationPlanDependencyEntry {
|
|
99
|
+
readonly binding: Binding;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Everything the compiler needs from its resolver, expressed as behavior so the
|
|
104
|
+
* compiler stays independently testable and free of resolver internals.
|
|
105
|
+
*
|
|
106
|
+
* @since 0.5.0-canary.7
|
|
107
|
+
*/
|
|
108
|
+
export interface InstantiationPlanHost {
|
|
109
|
+
/** Owner-aware: container-level hooks belong to the binding's owner, which may be a parent. */
|
|
110
|
+
hasActivationHandlers(binding: Binding): boolean;
|
|
111
|
+
/** Cached postConstruct presence — `undefined` until a runtime resolve discovers it. */
|
|
112
|
+
knownPostConstruct(target: Constructor): boolean | undefined;
|
|
113
|
+
needsActiveContainer(target: Constructor): boolean;
|
|
114
|
+
getConstructorMetadata(target: Constructor): ConstructorMetadata | undefined;
|
|
115
|
+
/** Options-less lookup with alias hops folded; `null` when the fast lane can't answer. */
|
|
116
|
+
lookupDependencyEntry(token: Token<unknown> | Constructor): InstantiationPlanDependencyEntry | null;
|
|
117
|
+
/**
|
|
118
|
+
* A name-only lookup a plan may bake in, or `null` when the answer is not the compiler's to make.
|
|
119
|
+
*
|
|
120
|
+
* @remarks Selection for a named request is an index hit *and* a predicate, and a predicate reads
|
|
121
|
+
* the resolution path — so only a candidate carrying none of one can be decided ahead of time.
|
|
122
|
+
*/
|
|
123
|
+
lookupPathIndependentNamedEntry(
|
|
124
|
+
token: Token<unknown> | Constructor,
|
|
125
|
+
options: ResolveOptions & { name: string },
|
|
126
|
+
): InstantiationPlanDependencyEntry | null;
|
|
127
|
+
/**
|
|
128
|
+
* The named lookup's single-tag twin, or `null` under the same rule.
|
|
129
|
+
*
|
|
130
|
+
* @remarks Optional so a host predating it stays a valid host — a compiler given none simply
|
|
131
|
+
* escapes the dependency, which is exactly the pre-settlement behavior.
|
|
132
|
+
*/
|
|
133
|
+
lookupPathIndependentTaggedEntry?(
|
|
134
|
+
token: Token<unknown> | Constructor,
|
|
135
|
+
options: ResolveOptions,
|
|
136
|
+
): InstantiationPlanDependencyEntry | null;
|
|
137
|
+
/** The frame the interpreted path pushes for this binding, so escapes can replay it. */
|
|
138
|
+
getResolutionFrame(binding: Binding): ResolutionFrame;
|
|
139
|
+
/** Runtime resolve for an escaped dependency, seeded with the ancestor frames above it. */
|
|
140
|
+
resolveEscaped(
|
|
141
|
+
token: Token<unknown> | Constructor,
|
|
142
|
+
options: ResolveOptions | undefined,
|
|
143
|
+
arity: EscapeArity,
|
|
144
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
145
|
+
): unknown;
|
|
146
|
+
/** The async counterpart of {@link InstantiationPlanHost.resolveEscaped}, replaying the async dispatch. */
|
|
147
|
+
resolveEscapedAsync(
|
|
148
|
+
token: Token<unknown> | Constructor,
|
|
149
|
+
options: ResolveOptions | undefined,
|
|
150
|
+
arity: EscapeArity,
|
|
151
|
+
resolutionStack: Array<ResolutionFrame>,
|
|
152
|
+
): Promise<unknown>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Which resolve an escaped dependency replays — mirrors how the interpreted path dispatches
|
|
157
|
+
* a constructor param.
|
|
158
|
+
*
|
|
159
|
+
* @since 0.5.0-canary.8
|
|
160
|
+
*/
|
|
161
|
+
export type EscapeArity = "all" | "optional" | "single";
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @since 0.3.16-canary.1
|
|
165
|
+
*/
|
|
166
|
+
export class InstantiationPlanCompiler {
|
|
167
|
+
readonly #host: InstantiationPlanHost;
|
|
168
|
+
|
|
169
|
+
constructor(host: InstantiationPlanHost) {
|
|
170
|
+
this.#host = host;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
compile(binding: Binding & { kind: "class" | "resolved" }): InstantiationPlanCompileResult {
|
|
174
|
+
return binding.kind === "class"
|
|
175
|
+
? this.#compileClassPlan(binding, new Set(), 0, [])
|
|
176
|
+
: this.#compileResolvedPlan(binding, new Set(), 0, []);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Re-entry into the runtime resolver for a dependency the plan can't see through.
|
|
181
|
+
*
|
|
182
|
+
* The ancestors are fixed at compile time, so the seed is built once and lent per call — the
|
|
183
|
+
* root-stack rule one level down: every sync lane pops what it pushes, so the owned array still
|
|
184
|
+
* holds exactly the seed when a call returns. A reentrant call finds it claimed and mints its
|
|
185
|
+
* own copy; a return that did not restore the length hands nothing back, so the next call mints.
|
|
186
|
+
*/
|
|
187
|
+
#compileEscapeThunk(
|
|
188
|
+
token: Token<unknown> | Constructor,
|
|
189
|
+
ancestors: ReadonlyArray<Binding>,
|
|
190
|
+
arity: EscapeArity = "single",
|
|
191
|
+
options?: ResolveOptions,
|
|
192
|
+
): () => unknown {
|
|
193
|
+
const host = this.#host;
|
|
194
|
+
const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
|
|
195
|
+
const depth = frames.length;
|
|
196
|
+
let owned: Array<ResolutionFrame> | undefined = [...frames];
|
|
197
|
+
return () => {
|
|
198
|
+
const stack = owned ?? [...frames];
|
|
199
|
+
owned = undefined;
|
|
200
|
+
try {
|
|
201
|
+
return host.resolveEscaped(token, options, arity, stack);
|
|
202
|
+
} finally {
|
|
203
|
+
if (stack.length === depth) {
|
|
204
|
+
owned = stack;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A resolved binding declares its deps as explicit descriptors — same rules as
|
|
211
|
+
// class params, with the factory call (and its sync-only check) in place of `new`.
|
|
212
|
+
#compileResolvedPlan(
|
|
213
|
+
binding: Binding & { kind: "resolved" },
|
|
214
|
+
compileStack: Set<Binding["id"]>,
|
|
215
|
+
depth: number,
|
|
216
|
+
ancestors: ReadonlyArray<Binding>,
|
|
217
|
+
): InstantiationPlanCompileResult {
|
|
218
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const factory = binding.factory;
|
|
222
|
+
const tokenDisplayName = tokenName(binding.token);
|
|
223
|
+
const depThunks = new Array<() => unknown>(binding.deps.length);
|
|
224
|
+
const depAncestors = [...ancestors, binding];
|
|
225
|
+
compileStack.add(binding.id);
|
|
226
|
+
try {
|
|
227
|
+
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
228
|
+
const thunk = this.#compileInjectionThunk(binding.deps[index]!, compileStack, depth, depAncestors);
|
|
229
|
+
if (thunk === PLAN_RETRY) {
|
|
230
|
+
return thunk;
|
|
231
|
+
}
|
|
232
|
+
depThunks[index] = thunk;
|
|
233
|
+
}
|
|
234
|
+
} finally {
|
|
235
|
+
compileStack.delete(binding.id);
|
|
236
|
+
}
|
|
237
|
+
return () => {
|
|
238
|
+
const factoryResult = factory(...depThunks.map((thunk) => thunk()));
|
|
239
|
+
if (factoryResult instanceof Promise) {
|
|
240
|
+
throw new AsyncResolutionError(tokenDisplayName);
|
|
241
|
+
}
|
|
242
|
+
return factoryResult;
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* One dependency of a plan node — a constructor param or a `toResolved` descriptor.
|
|
248
|
+
*
|
|
249
|
+
* @remarks Anything but a plain required single dependency escapes to the runtime path.
|
|
250
|
+
*/
|
|
251
|
+
#compileInjectionThunk(
|
|
252
|
+
descriptor: DependencySlot,
|
|
253
|
+
compileStack: Set<Binding["id"]>,
|
|
254
|
+
depth: number,
|
|
255
|
+
ancestors: ReadonlyArray<Binding>,
|
|
256
|
+
): DependencyCompileResult {
|
|
257
|
+
const token = descriptor.token;
|
|
258
|
+
const options = injectionSlotToResolveOptions(descriptor);
|
|
259
|
+
if (descriptor.multi) {
|
|
260
|
+
return this.#compileEscapeThunk(token, ancestors, "all", options);
|
|
261
|
+
}
|
|
262
|
+
if (descriptor.optional) {
|
|
263
|
+
return this.#compileEscapeThunk(token, ancestors, "optional", options);
|
|
264
|
+
}
|
|
265
|
+
if (options !== undefined) {
|
|
266
|
+
// A name the registry can settle without reading a path is a dependency like any other: it
|
|
267
|
+
// escapes only because it carries a criterion, not because anything about it is opaque.
|
|
268
|
+
if (isNameOnlyOptions(options)) {
|
|
269
|
+
const named = this.#host.lookupPathIndependentNamedEntry(token, options);
|
|
270
|
+
if (named !== null) {
|
|
271
|
+
return this.#compileDepThunk(named, compileStack, depth, ancestors, options);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
// The host owns the whole single-tag decision, including whether the options qualify.
|
|
275
|
+
const tagged = this.#host.lookupPathIndependentTaggedEntry?.(token, options);
|
|
276
|
+
if (tagged !== null && tagged !== undefined) {
|
|
277
|
+
return this.#compileDepThunk(tagged, compileStack, depth, ancestors, options);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return this.#compileEscapeThunk(token, ancestors, "single", options);
|
|
281
|
+
}
|
|
282
|
+
const entry = this.#host.lookupDependencyEntry(token);
|
|
283
|
+
if (entry === null) {
|
|
284
|
+
return this.#compileEscapeThunk(token, ancestors);
|
|
285
|
+
}
|
|
286
|
+
return this.#compileDepThunk(entry, compileStack, depth, ancestors);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#compileClassPlan(
|
|
290
|
+
binding: Binding & { kind: "class" },
|
|
291
|
+
compileStack: Set<Binding["id"]>,
|
|
292
|
+
depth: number,
|
|
293
|
+
ancestors: ReadonlyArray<Binding>,
|
|
294
|
+
): InstantiationPlanCompileResult {
|
|
295
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const target = binding.target;
|
|
299
|
+
const hasPostConstruct = this.#host.knownPostConstruct(target);
|
|
300
|
+
if (hasPostConstruct === undefined) {
|
|
301
|
+
return PLAN_RETRY;
|
|
302
|
+
}
|
|
303
|
+
if (hasPostConstruct || this.#host.needsActiveContainer(target)) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
const invokable = target as ConstructorInvocation;
|
|
307
|
+
const meta = this.#host.getConstructorMetadata(target);
|
|
308
|
+
if (meta === undefined) {
|
|
309
|
+
// Metadata-less classes with required params throw on the runtime path — keep them there.
|
|
310
|
+
return target.length === 0 ? () => new invokable() : null;
|
|
311
|
+
}
|
|
312
|
+
const params = meta.params;
|
|
313
|
+
if (params.length === 0) {
|
|
314
|
+
return () => new invokable();
|
|
315
|
+
}
|
|
316
|
+
const depThunks = new Array<() => unknown>(params.length);
|
|
317
|
+
const depAncestors = [...ancestors, binding];
|
|
318
|
+
compileStack.add(binding.id);
|
|
319
|
+
try {
|
|
320
|
+
for (let index = 0; index < params.length; index += 1) {
|
|
321
|
+
const thunk = this.#compileInjectionThunk(params[index]!, compileStack, depth, depAncestors);
|
|
322
|
+
if (thunk === PLAN_RETRY) {
|
|
323
|
+
return thunk;
|
|
324
|
+
}
|
|
325
|
+
depThunks[index] = thunk;
|
|
326
|
+
}
|
|
327
|
+
} finally {
|
|
328
|
+
compileStack.delete(binding.id);
|
|
329
|
+
}
|
|
330
|
+
switch (depThunks.length) {
|
|
331
|
+
case 1: {
|
|
332
|
+
const dep0 = depThunks[0]!;
|
|
333
|
+
return () => new invokable(dep0());
|
|
334
|
+
}
|
|
335
|
+
case 2: {
|
|
336
|
+
const dep0 = depThunks[0]!;
|
|
337
|
+
const dep1 = depThunks[1]!;
|
|
338
|
+
return () => new invokable(dep0(), dep1());
|
|
339
|
+
}
|
|
340
|
+
case 3: {
|
|
341
|
+
const dep0 = depThunks[0]!;
|
|
342
|
+
const dep1 = depThunks[1]!;
|
|
343
|
+
const dep2 = depThunks[2]!;
|
|
344
|
+
return () => new invokable(dep0(), dep1(), dep2());
|
|
345
|
+
}
|
|
346
|
+
default:
|
|
347
|
+
return () => new invokable(...depThunks.map((thunk) => thunk()));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#compileDepThunk(
|
|
352
|
+
entry: InstantiationPlanDependencyEntry,
|
|
353
|
+
compileStack: Set<Binding["id"]>,
|
|
354
|
+
depth: number,
|
|
355
|
+
ancestors: ReadonlyArray<Binding>,
|
|
356
|
+
options?: ResolveOptions,
|
|
357
|
+
): DependencyCompileResult {
|
|
358
|
+
const { binding } = entry;
|
|
359
|
+
if (binding.kind === "constant" && binding.onActivation === undefined) {
|
|
360
|
+
if (!this.#host.hasActivationHandlers(binding)) {
|
|
361
|
+
const value = binding.value;
|
|
362
|
+
return () => value;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const scope = binding.scope;
|
|
366
|
+
if (scope === "singleton") {
|
|
367
|
+
// Cached-singleton read; the first materialization escapes so it sees the same ancestors
|
|
368
|
+
// (and therefore the same cycle detection) the interpreted path would have built.
|
|
369
|
+
const escape = this.#compileEscapeThunk(binding.token, ancestors, "single", options);
|
|
370
|
+
const singletonBinding = binding;
|
|
371
|
+
return () => {
|
|
372
|
+
const cached = singletonBinding.instance;
|
|
373
|
+
return cached === NO_INSTANCE ? escape() : cached;
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (
|
|
377
|
+
scope === "transient" &&
|
|
378
|
+
binding.kind === "class" &&
|
|
379
|
+
depth < PLAN_DEPTH_LIMIT &&
|
|
380
|
+
!compileStack.has(binding.id)
|
|
381
|
+
) {
|
|
382
|
+
const inlined = this.#compileClassPlan(
|
|
383
|
+
binding as Binding & { kind: "class" },
|
|
384
|
+
compileStack,
|
|
385
|
+
depth + 1,
|
|
386
|
+
ancestors,
|
|
387
|
+
);
|
|
388
|
+
if (inlined !== null) {
|
|
389
|
+
return inlined;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Anything opaque — a factory, a scoped binding, an activation hook, a class the compiler
|
|
393
|
+
// declined — runs on the runtime path, seeded with this plan's ancestors.
|
|
394
|
+
return this.#compileEscapeThunk(binding.token, ancestors, "single", options);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ── The async lane ─────────────────────────────────────────────────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Compiles a transient binding whose graph is statically visible, for the async entry point.
|
|
401
|
+
*
|
|
402
|
+
* @remarks Same gates as the sync lane; the differences are downstream — escapes replay the async
|
|
403
|
+
* dispatch, and a node whose dependencies may yield promises awaits them together, exactly as the
|
|
404
|
+
* interpreted async path does.
|
|
405
|
+
*/
|
|
406
|
+
compileAsync(binding: Binding & { kind: "class" | "resolved" | "resolved-async" }): InstantiationPlanCompileResult {
|
|
407
|
+
const node =
|
|
408
|
+
binding.kind === "class"
|
|
409
|
+
? this.#compileAsyncClassNode(binding, new Set(), 0, [])
|
|
410
|
+
: this.#compileAsyncResolvedNode(binding, new Set(), 0, []);
|
|
411
|
+
if (node === null || node === PLAN_RETRY) {
|
|
412
|
+
return node;
|
|
413
|
+
}
|
|
414
|
+
return node.run;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#compileAsyncEscapeThunk(
|
|
418
|
+
token: Token<unknown> | Constructor,
|
|
419
|
+
ancestors: ReadonlyArray<Binding>,
|
|
420
|
+
arity: EscapeArity = "single",
|
|
421
|
+
options?: ResolveOptions,
|
|
422
|
+
): AsyncNodeThunk {
|
|
423
|
+
const host = this.#host;
|
|
424
|
+
const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
|
|
425
|
+
return {
|
|
426
|
+
run: () => host.resolveEscapedAsync(token, options, arity, [...frames]),
|
|
427
|
+
promiseShape: "always",
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
#compileAsyncClassNode(
|
|
432
|
+
binding: Binding & { kind: "class" },
|
|
433
|
+
compileStack: Set<Binding["id"]>,
|
|
434
|
+
depth: number,
|
|
435
|
+
ancestors: ReadonlyArray<Binding>,
|
|
436
|
+
): AsyncNodeThunk | null | typeof PLAN_RETRY {
|
|
437
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const target = binding.target;
|
|
441
|
+
const hasPostConstruct = this.#host.knownPostConstruct(target);
|
|
442
|
+
if (hasPostConstruct === undefined) {
|
|
443
|
+
return PLAN_RETRY;
|
|
444
|
+
}
|
|
445
|
+
if (hasPostConstruct || this.#host.needsActiveContainer(target)) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
const invokable = target as ConstructorInvocation;
|
|
449
|
+
const meta = this.#host.getConstructorMetadata(target);
|
|
450
|
+
if (meta === undefined) {
|
|
451
|
+
// Metadata-less classes with required params throw on the runtime path — keep them there.
|
|
452
|
+
return target.length === 0 ? { run: () => new invokable(), promiseShape: "never" } : null;
|
|
453
|
+
}
|
|
454
|
+
const params = meta.params;
|
|
455
|
+
if (params.length === 0) {
|
|
456
|
+
return { run: () => new invokable(), promiseShape: "never" };
|
|
457
|
+
}
|
|
458
|
+
const depThunks = new Array<AsyncNodeThunk>(params.length);
|
|
459
|
+
const depAncestors = [...ancestors, binding];
|
|
460
|
+
compileStack.add(binding.id);
|
|
461
|
+
try {
|
|
462
|
+
for (let index = 0; index < params.length; index += 1) {
|
|
463
|
+
const thunk = this.#compileAsyncInjectionThunk(params[index]!, compileStack, depth, depAncestors);
|
|
464
|
+
if (thunk === PLAN_RETRY) {
|
|
465
|
+
return thunk;
|
|
466
|
+
}
|
|
467
|
+
depThunks[index] = thunk;
|
|
468
|
+
}
|
|
469
|
+
} finally {
|
|
470
|
+
compileStack.delete(binding.id);
|
|
471
|
+
}
|
|
472
|
+
if (allSynchronous(depThunks)) {
|
|
473
|
+
switch (depThunks.length) {
|
|
474
|
+
case 1: {
|
|
475
|
+
const dep0 = depThunks[0]!.run;
|
|
476
|
+
return { run: () => new invokable(dep0()), promiseShape: "never" };
|
|
477
|
+
}
|
|
478
|
+
case 2: {
|
|
479
|
+
const dep0 = depThunks[0]!.run;
|
|
480
|
+
const dep1 = depThunks[1]!.run;
|
|
481
|
+
return { run: () => new invokable(dep0(), dep1()), promiseShape: "never" };
|
|
482
|
+
}
|
|
483
|
+
default:
|
|
484
|
+
return {
|
|
485
|
+
run: () => new invokable(...depThunks.map((thunk) => thunk.run())),
|
|
486
|
+
promiseShape: "never",
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { run: settleThenApply(depThunks, (values) => new invokable(...values)), promiseShape: "always" };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#compileAsyncResolvedNode(
|
|
494
|
+
binding: Binding & { kind: "resolved" | "resolved-async" },
|
|
495
|
+
compileStack: Set<Binding["id"]>,
|
|
496
|
+
depth: number,
|
|
497
|
+
ancestors: ReadonlyArray<Binding>,
|
|
498
|
+
): AsyncNodeThunk | null | typeof PLAN_RETRY {
|
|
499
|
+
if (binding.onActivation !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
const factory = binding.factory;
|
|
503
|
+
const depThunks = new Array<AsyncNodeThunk>(binding.deps.length);
|
|
504
|
+
const depAncestors = [...ancestors, binding];
|
|
505
|
+
compileStack.add(binding.id);
|
|
506
|
+
try {
|
|
507
|
+
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
508
|
+
const thunk = this.#compileAsyncInjectionThunk(binding.deps[index]!, compileStack, depth, depAncestors);
|
|
509
|
+
if (thunk === PLAN_RETRY) {
|
|
510
|
+
return thunk;
|
|
511
|
+
}
|
|
512
|
+
depThunks[index] = thunk;
|
|
513
|
+
}
|
|
514
|
+
} finally {
|
|
515
|
+
compileStack.delete(binding.id);
|
|
516
|
+
}
|
|
517
|
+
// A sync-kind factory may still hand back a promise on this path — the async entry awaits it,
|
|
518
|
+
// so the consumer is told to as well.
|
|
519
|
+
const directShape: ThunkPromiseShape = binding.kind === "resolved-async" ? "always" : "maybe";
|
|
520
|
+
if (allSynchronous(depThunks)) {
|
|
521
|
+
return { run: () => factory(...depThunks.map((thunk) => thunk.run())), promiseShape: directShape };
|
|
522
|
+
}
|
|
523
|
+
return { run: settleThenApply(depThunks, (values) => factory(...values)), promiseShape: "always" };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** Mirrors {@link InstantiationPlanCompiler.#compileInjectionThunk}, escaping through the async dispatch. */
|
|
527
|
+
#compileAsyncInjectionThunk(
|
|
528
|
+
descriptor: DependencySlot,
|
|
529
|
+
compileStack: Set<Binding["id"]>,
|
|
530
|
+
depth: number,
|
|
531
|
+
ancestors: ReadonlyArray<Binding>,
|
|
532
|
+
): AsyncDependencyCompileResult {
|
|
533
|
+
const token = descriptor.token;
|
|
534
|
+
const options = injectionSlotToResolveOptions(descriptor);
|
|
535
|
+
if (descriptor.multi) {
|
|
536
|
+
return this.#compileAsyncEscapeThunk(token, ancestors, "all", options);
|
|
537
|
+
}
|
|
538
|
+
if (descriptor.optional) {
|
|
539
|
+
return this.#compileAsyncEscapeThunk(token, ancestors, "optional", options);
|
|
540
|
+
}
|
|
541
|
+
if (options !== undefined) {
|
|
542
|
+
if (isNameOnlyOptions(options)) {
|
|
543
|
+
const named = this.#host.lookupPathIndependentNamedEntry(token, options);
|
|
544
|
+
if (named !== null) {
|
|
545
|
+
return this.#compileAsyncDepThunk(named, compileStack, depth, ancestors, options);
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
// The host owns the whole single-tag decision, including whether the options qualify.
|
|
549
|
+
const tagged = this.#host.lookupPathIndependentTaggedEntry?.(token, options);
|
|
550
|
+
if (tagged !== null && tagged !== undefined) {
|
|
551
|
+
return this.#compileAsyncDepThunk(tagged, compileStack, depth, ancestors, options);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return this.#compileAsyncEscapeThunk(token, ancestors, "single", options);
|
|
555
|
+
}
|
|
556
|
+
const entry = this.#host.lookupDependencyEntry(token);
|
|
557
|
+
if (entry === null) {
|
|
558
|
+
return this.#compileAsyncEscapeThunk(token, ancestors);
|
|
559
|
+
}
|
|
560
|
+
return this.#compileAsyncDepThunk(entry, compileStack, depth, ancestors);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
#compileAsyncDepThunk(
|
|
564
|
+
entry: InstantiationPlanDependencyEntry,
|
|
565
|
+
compileStack: Set<Binding["id"]>,
|
|
566
|
+
depth: number,
|
|
567
|
+
ancestors: ReadonlyArray<Binding>,
|
|
568
|
+
options?: ResolveOptions,
|
|
569
|
+
): AsyncDependencyCompileResult {
|
|
570
|
+
const { binding } = entry;
|
|
571
|
+
if (binding.kind === "constant" && binding.onActivation === undefined) {
|
|
572
|
+
if (!this.#host.hasActivationHandlers(binding)) {
|
|
573
|
+
const value = binding.value;
|
|
574
|
+
// The interpreted path funnels every dependency through an await, which unwraps a
|
|
575
|
+
// promise-valued constant — so one compiles as needing that same await.
|
|
576
|
+
return { run: () => value, promiseShape: value instanceof Promise ? "always" : "never" };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const scope = binding.scope;
|
|
580
|
+
if (scope === "singleton") {
|
|
581
|
+
// Cached-singleton read; the cold materialization escapes with the same criteria.
|
|
582
|
+
const escape = this.#compileAsyncEscapeThunk(binding.token, ancestors, "single", options);
|
|
583
|
+
const singletonBinding = binding;
|
|
584
|
+
return {
|
|
585
|
+
run: () => {
|
|
586
|
+
const cached = singletonBinding.instance;
|
|
587
|
+
return cached === NO_INSTANCE ? escape.run() : cached;
|
|
588
|
+
},
|
|
589
|
+
promiseShape: "maybe",
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (scope === "transient" && depth < PLAN_DEPTH_LIMIT && !compileStack.has(binding.id)) {
|
|
593
|
+
let inlined: AsyncNodeThunk | null | typeof PLAN_RETRY = null;
|
|
594
|
+
if (binding.kind === "class") {
|
|
595
|
+
inlined = this.#compileAsyncClassNode(
|
|
596
|
+
binding as Binding & { kind: "class" },
|
|
597
|
+
compileStack,
|
|
598
|
+
depth + 1,
|
|
599
|
+
ancestors,
|
|
600
|
+
);
|
|
601
|
+
} else if (binding.kind === "resolved" || binding.kind === "resolved-async") {
|
|
602
|
+
inlined = this.#compileAsyncResolvedNode(
|
|
603
|
+
binding as Binding & { kind: "resolved" | "resolved-async" },
|
|
604
|
+
compileStack,
|
|
605
|
+
depth + 1,
|
|
606
|
+
ancestors,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
if (inlined !== null) {
|
|
610
|
+
return inlined;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return this.#compileAsyncEscapeThunk(binding.token, ancestors, "single", options);
|
|
614
|
+
}
|
|
615
|
+
}
|