@codefast/di 0.10.1 → 0.12.0
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 +376 -0
- package/README.md +94 -17
- package/dist/{ambient/active-container.d.ts → ambient-container.d.ts} +7 -2
- package/dist/{ambient/active-container.js → ambient-container.js} +6 -1
- package/dist/container/{binding-builders.d.ts → binding-chain.d.ts} +27 -16
- package/dist/container/binding-chain.js +254 -0
- package/dist/container/container.d.ts +11 -2
- package/dist/container/container.js +293 -73
- package/dist/core/binding-builders.d.ts +114 -0
- package/dist/core/binding-declaration.d.ts +127 -0
- package/dist/core/binding-declaration.js +187 -0
- package/dist/core/binding.d.ts +62 -138
- package/dist/core/binding.js +54 -17
- package/dist/core/module.d.ts +8 -5
- package/dist/core/module.js +17 -3
- package/dist/core/registry.d.ts +17 -28
- package/dist/core/registry.js +92 -133
- package/dist/core/state-epoch.d.ts +18 -1
- package/dist/core/state-epoch.js +17 -0
- package/dist/core/tag.js +1 -1
- package/dist/decorators/inject.js +6 -5
- package/dist/decorators/injectable.js +4 -2
- package/dist/decorators/{lifecycle-decorators.js → lifecycle.js} +9 -3
- package/dist/decorators/metadata-record.d.ts +9 -0
- package/dist/decorators/metadata-record.js +20 -0
- package/dist/{errors/errors.d.ts → errors.d.ts} +84 -9
- package/dist/{errors/errors.js → errors.js} +101 -17
- package/dist/index.d.ts +14 -10
- package/dist/index.js +7 -6
- package/dist/injection/{resolve-options.js → dependency-slot.js} +6 -4
- package/dist/injection/descriptor.d.ts +1 -1
- package/dist/injection/descriptor.js +3 -7
- package/dist/introspection/dependency-graph.d.ts +8 -3
- package/dist/introspection/dependency-graph.js +48 -26
- package/dist/introspection/explanation.d.ts +101 -0
- package/dist/introspection/explanation.js +151 -0
- package/dist/introspection/graph-adapters/reactflow.js +6 -4
- package/dist/introspection/inspector.d.ts +9 -2
- package/dist/introspection/inspector.js +30 -28
- package/dist/lifecycle/{lifecycle-manager.d.ts → hooks.d.ts} +9 -2
- package/dist/lifecycle/{lifecycle-manager.js → hooks.js} +59 -30
- package/dist/lifecycle/{scope-manager.d.ts → scopes.d.ts} +3 -5
- package/dist/lifecycle/{scope-manager.js → scopes.js} +36 -29
- package/dist/metadata/{metadata-reader-token.d.ts → reader-token.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.d.ts → symbol-reader.d.ts} +1 -1
- package/dist/metadata/{symbol-metadata-reader.js → symbol-reader.js} +1 -1
- package/dist/metadata/{metadata-types.d.ts → types.d.ts} +1 -1
- package/dist/metadata/types.js +1 -0
- package/dist/metadata/verify.d.ts +43 -0
- package/dist/metadata/verify.js +133 -0
- package/dist/metadata/{verifying-metadata-reader.d.ts → verifying-reader.d.ts} +5 -4
- package/dist/metadata/verifying-reader.js +53 -0
- package/dist/resolution/async-fan-out.d.ts +12 -0
- package/dist/resolution/async-fan-out.js +26 -0
- package/dist/resolution/cache/activation-need.d.ts +1 -2
- package/dist/resolution/cache/activation-need.js +16 -19
- package/dist/resolution/cache/class-introspector.d.ts +38 -36
- package/dist/resolution/cache/class-introspector.js +79 -159
- package/dist/resolution/cache/{binding-lookup-cache.d.ts → lookup.d.ts} +7 -9
- package/dist/resolution/cache/{binding-lookup-cache.js → lookup.js} +70 -25
- package/dist/resolution/context.d.ts +16 -24
- package/dist/resolution/context.js +47 -56
- package/dist/resolution/path.d.ts +128 -0
- package/dist/resolution/path.js +167 -0
- package/dist/resolution/plan/{plan-codegen.d.ts → codegen.d.ts} +5 -4
- package/dist/resolution/plan/{plan-codegen.js → codegen.js} +58 -32
- package/dist/resolution/plan/{instantiation-plan.d.ts → compiler.d.ts} +1 -1
- package/dist/resolution/plan/{instantiation-plan.js → compiler.js} +64 -24
- package/dist/resolution/resolver.d.ts +11 -12
- package/dist/resolution/resolver.js +573 -356
- package/dist/resolution/select/{binding-select.d.ts → candidates.d.ts} +30 -0
- package/dist/resolution/select/{binding-select.js → candidates.js} +51 -10
- package/dist/resolution/select/constraints.js +1 -1
- package/package.json +87 -67
- package/dist/container/binding-builders.js +0 -305
- package/dist/core/binding-scope.d.ts +0 -12
- package/dist/core/binding-scope.js +0 -12
- package/dist/metadata/verifying-metadata-reader.js +0 -31
- package/dist/resolution/path/resolution-path.d.ts +0 -93
- package/dist/resolution/path/resolution-path.js +0 -116
- /package/dist/{metadata/metadata-types.js → core/binding-builders.js} +0 -0
- /package/dist/decorators/{lifecycle-decorators.d.ts → lifecycle.d.ts} +0 -0
- /package/dist/injection/{resolve-options.d.ts → dependency-slot.d.ts} +0 -0
- /package/dist/{errors → introspection}/diagnostics.d.ts +0 -0
- /package/dist/{errors → introspection}/diagnostics.js +0 -0
- /package/dist/metadata/{metadata-keys.d.ts → keys.d.ts} +0 -0
- /package/dist/metadata/{metadata-keys.js → keys.js} +0 -0
- /package/dist/metadata/{metadata-reader-token.js → reader-token.js} +0 -0
|
@@ -6,12 +6,12 @@ import type { ConstructorInvocation } from "#core/constructor-type";
|
|
|
6
6
|
/**
|
|
7
7
|
* The number of runs a plan's closure makes before the plan is generated as its own function.
|
|
8
8
|
*
|
|
9
|
-
* @remarks
|
|
10
|
-
*
|
|
9
|
+
* @remarks A measured policy: the generated function costs a compile and a warm-up, so it repays only over
|
|
10
|
+
* many runs of one plan in one container, and a cold container or a per-request child keeps the closure.
|
|
11
11
|
*
|
|
12
12
|
* @since 0.10.0
|
|
13
13
|
*/
|
|
14
|
-
export declare const PLAN_CODEGEN_THRESHOLD =
|
|
14
|
+
export declare const PLAN_CODEGEN_THRESHOLD = 1024;
|
|
15
15
|
/**
|
|
16
16
|
* The shape of a compiled sync plan: what its closure does, stated as data the generator can read.
|
|
17
17
|
*
|
|
@@ -49,7 +49,8 @@ export type PlanNode = {
|
|
|
49
49
|
*
|
|
50
50
|
* @remarks A node that `awaits` has a dependency that may yield a promise, so it runs as the
|
|
51
51
|
* interpreted async path does: every dependency starts in order, a sync throw becomes that slot's
|
|
52
|
-
* rejection,
|
|
52
|
+
* rejection, the constructor or factory runs on the settled values, and a failure is reported in
|
|
53
|
+
* declaration order.
|
|
53
54
|
*
|
|
54
55
|
* @since 0.10.0
|
|
55
56
|
*/
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { NO_INSTANCE } from "#core/binding";
|
|
2
|
+
import { settleInOrder } from "#resolution/async-fan-out";
|
|
2
3
|
/**
|
|
3
4
|
* The number of runs a plan's closure makes before the plan is generated as its own function.
|
|
4
5
|
*
|
|
5
|
-
* @remarks
|
|
6
|
-
*
|
|
6
|
+
* @remarks A measured policy: the generated function costs a compile and a warm-up, so it repays only over
|
|
7
|
+
* many runs of one plan in one container, and a cold container or a per-request child keeps the closure.
|
|
7
8
|
*
|
|
8
9
|
* @since 0.10.0
|
|
9
10
|
*/
|
|
10
|
-
export const PLAN_CODEGEN_THRESHOLD =
|
|
11
|
+
export const PLAN_CODEGEN_THRESHOLD = 1024;
|
|
11
12
|
const rejectWith = (error) => Promise.reject(error);
|
|
12
13
|
let codegenAvailable;
|
|
13
14
|
let generatedCount = 0;
|
|
@@ -59,10 +60,10 @@ export function generateAsyncPlan(node) {
|
|
|
59
60
|
const emitter = new PlanEmitter();
|
|
60
61
|
return compileRendered(emitter, emitter.asyncExpression(node));
|
|
61
62
|
}
|
|
62
|
-
function compileRendered(emitter,
|
|
63
|
+
function compileRendered(emitter, result) {
|
|
63
64
|
generatedCount += 1;
|
|
64
65
|
const locals = emitter.locals.length === 0 ? "" : `let ${emitter.locals.join(", ")};`;
|
|
65
|
-
const body = `"use strict";/* plan ${String(generatedCount)} */${emitter.hoisted.join("")}return () => {${locals}return ${
|
|
66
|
+
const body = `"use strict";/* plan ${String(generatedCount)} */${emitter.hoisted.join("")}return () => {${locals}${emitter.statements.join("")}return ${result};};`;
|
|
66
67
|
try {
|
|
67
68
|
// Compiling from source is the mechanism: one function literal per plan is what gives it its own feedback.
|
|
68
69
|
// oxlint-disable-next-line typescript/no-implied-eval
|
|
@@ -74,40 +75,50 @@ function compileRendered(emitter, expression) {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
/**
|
|
77
|
-
* Renders a plan tree as
|
|
78
|
+
* Renders a plan tree as a flat sequence of statements over parameters that carry every value the plan closes over.
|
|
78
79
|
*
|
|
79
|
-
* @remarks
|
|
80
|
-
*
|
|
80
|
+
* @remarks Each node becomes one assignment to a local after its dependencies' assignments, in declaration
|
|
81
|
+
* order, so the generated function evaluates exactly as the nested closure did while nesting nothing: a
|
|
82
|
+
* graph of any depth renders as that many statements, never as an expression that deep. A node that awaits
|
|
83
|
+
* its dependencies renders as an inner function of the same shape, so every plan's awaiting nodes have call
|
|
84
|
+
* sites of their own too.
|
|
81
85
|
*/
|
|
82
86
|
class PlanEmitter {
|
|
83
87
|
names = [];
|
|
84
88
|
values = [];
|
|
85
89
|
hoisted = [];
|
|
86
|
-
#
|
|
90
|
+
#frames = [
|
|
91
|
+
{ locals: [], statements: [] },
|
|
92
|
+
];
|
|
87
93
|
#hoistedCount = 0;
|
|
88
94
|
#slotByValue = new Map();
|
|
89
95
|
/** The plan function's own temporaries. */
|
|
90
96
|
get locals() {
|
|
91
|
-
return this.#
|
|
97
|
+
return this.#frames[0].locals;
|
|
92
98
|
}
|
|
99
|
+
/** The plan function's statements, in evaluation order. */
|
|
100
|
+
get statements() {
|
|
101
|
+
return this.#frames[0].statements;
|
|
102
|
+
}
|
|
103
|
+
/** Emits a node's statements and returns the reference that holds its value. */
|
|
93
104
|
expression(node) {
|
|
94
105
|
switch (node.kind) {
|
|
95
106
|
case "construct":
|
|
96
|
-
return `new ${this.#slot(node.target, "C")}(${this.#list(node.deps)})
|
|
107
|
+
return this.#define(`new ${this.#slot(node.target, "C")}(${this.#list(node.deps)})`);
|
|
97
108
|
case "accessors":
|
|
98
|
-
return `${this.#slot(node.construct, "A")}([${this.#list(node.deps)}])
|
|
109
|
+
return this.#define(`${this.#slot(node.construct, "A")}([${this.#list(node.deps)}])`);
|
|
99
110
|
case "call": {
|
|
100
|
-
// The settle only ever throws, so it runs on the promise branch alone and the plain result
|
|
101
|
-
const local = this.#
|
|
102
|
-
|
|
103
|
-
return
|
|
111
|
+
// The settle only ever throws, so it runs on the promise branch alone and the plain result stands.
|
|
112
|
+
const local = this.#define(`${this.#slot(node.factory, "F")}(${this.#list(node.deps)})`);
|
|
113
|
+
this.#statement(`if (${local} instanceof ${this.#slot(Promise, "P")}) ${local} = ${this.#slot(node.settle, "S")}(${local});`);
|
|
114
|
+
return local;
|
|
104
115
|
}
|
|
105
116
|
case "value":
|
|
106
117
|
return this.#slot(node.value, "V");
|
|
107
118
|
case "singleton":
|
|
108
119
|
return this.#singletonRead(node.binding, node.escape);
|
|
109
120
|
case "thunk":
|
|
110
|
-
return `${this.#slot(node.run, "T")}()
|
|
121
|
+
return this.#define(`${this.#slot(node.run, "T")}()`);
|
|
111
122
|
}
|
|
112
123
|
}
|
|
113
124
|
asyncExpression(node) {
|
|
@@ -116,20 +127,20 @@ class PlanEmitter {
|
|
|
116
127
|
const target = this.#slot(node.target, "C");
|
|
117
128
|
return node.awaits
|
|
118
129
|
? this.#settled(node.deps, (values) => `new ${target}(${values})`)
|
|
119
|
-
: `new ${target}(${this.#asyncList(node.deps)})
|
|
130
|
+
: this.#define(`new ${target}(${this.#asyncList(node.deps)})`);
|
|
120
131
|
}
|
|
121
132
|
case "call": {
|
|
122
133
|
const factory = this.#slot(node.factory, "F");
|
|
123
134
|
return node.awaits
|
|
124
135
|
? this.#settled(node.deps, (values) => `${factory}(${values})`)
|
|
125
|
-
: `${factory}(${this.#asyncList(node.deps)})
|
|
136
|
+
: this.#define(`${factory}(${this.#asyncList(node.deps)})`);
|
|
126
137
|
}
|
|
127
138
|
case "value":
|
|
128
139
|
return this.#slot(node.value, "V");
|
|
129
140
|
case "singleton":
|
|
130
141
|
return this.#singletonRead(node.binding, node.escape);
|
|
131
142
|
case "thunk":
|
|
132
|
-
return `${this.#slot(node.run, "T")}()
|
|
143
|
+
return this.#define(`${this.#slot(node.run, "T")}()`);
|
|
133
144
|
}
|
|
134
145
|
}
|
|
135
146
|
#list(deps) {
|
|
@@ -146,30 +157,45 @@ class PlanEmitter {
|
|
|
146
157
|
const name = `n${String(index)}`;
|
|
147
158
|
const applyName = `a${String(index)}`;
|
|
148
159
|
const reject = this.#slot(rejectWith, "R");
|
|
149
|
-
const
|
|
150
|
-
this.#
|
|
160
|
+
const frame = { locals: [], statements: [] };
|
|
161
|
+
this.#frames.push(frame);
|
|
151
162
|
const pendings = [];
|
|
152
|
-
const statements = [];
|
|
153
163
|
for (let position = 0; position < deps.length; position += 1) {
|
|
154
164
|
const pending = `p${String(position)}`;
|
|
155
165
|
pendings.push(pending);
|
|
156
|
-
statements
|
|
166
|
+
// A dependency's own statements run inside its try, so its sync throw is its rejection alone.
|
|
167
|
+
const mark = frame.statements.length;
|
|
168
|
+
const reference = this.asyncExpression(deps[position]);
|
|
169
|
+
const inner = frame.statements.splice(mark).join("");
|
|
170
|
+
frame.statements.push(`try{${inner}${pending}=${reference};}catch(e){${pending}=${reject}(e);}`);
|
|
157
171
|
}
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
172
|
+
this.#frames.pop();
|
|
173
|
+
const locals = [...pendings, ...frame.locals];
|
|
174
|
+
// One dependency has one outcome, so there is nothing to order and no fan-out to settle.
|
|
175
|
+
const settled = deps.length === 1
|
|
176
|
+
? `const ${applyName}=(v0)=>${apply("v0")};const ${name}=()=>{let ${locals.join(",")};${frame.statements.join("")}return ${this.#slot(Promise, "P")}.resolve(p0).then(${applyName});};`
|
|
177
|
+
: `const ${applyName}=(v)=>${apply(deps.map((_dep, position) => `v[${String(position)}]`).join(","))};const ${name}=()=>{let ${locals.join(",")};${frame.statements.join("")}return ${this.#slot(settleInOrder, "W")}([${pendings.join(",")}],${applyName});};`;
|
|
178
|
+
this.hoisted.push(settled);
|
|
179
|
+
return this.#define(`${name}()`);
|
|
180
|
+
}
|
|
181
|
+
#define(expression) {
|
|
182
|
+
const local = this.#local();
|
|
183
|
+
this.#statement(`${local} = ${expression};`);
|
|
184
|
+
return local;
|
|
185
|
+
}
|
|
186
|
+
#statement(statement) {
|
|
187
|
+
this.#frames.at(-1).statements.push(statement);
|
|
162
188
|
}
|
|
163
189
|
#local() {
|
|
164
|
-
const locals = this.#
|
|
190
|
+
const locals = this.#frames.at(-1).locals;
|
|
165
191
|
const local = `t${String(locals.length)}`;
|
|
166
192
|
locals.push(local);
|
|
167
193
|
return local;
|
|
168
194
|
}
|
|
169
195
|
#singletonRead(binding, escape) {
|
|
170
|
-
const local = this.#
|
|
171
|
-
|
|
172
|
-
return
|
|
196
|
+
const local = this.#define(`${this.#slot(binding, "B")}.instance`);
|
|
197
|
+
this.#statement(`if (${local} === ${this.#slot(NO_INSTANCE, "N")}) ${local} = ${this.#slot(escape, "E")}();`);
|
|
198
|
+
return local;
|
|
173
199
|
}
|
|
174
200
|
// One parameter per distinct value, so a class constructed four times is one constructor with four sites.
|
|
175
201
|
#slot(value, prefix) {
|
|
@@ -8,7 +8,7 @@ import type { Binding } from "#core/binding";
|
|
|
8
8
|
import type { ConstructorInvocation } from "#core/constructor-type";
|
|
9
9
|
import type { Token } from "#core/token";
|
|
10
10
|
import type { Constructor, ResolutionFrame, ResolveOptions } from "#core/types";
|
|
11
|
-
import type { ConstructorMetadata } from "#metadata/
|
|
11
|
+
import type { ConstructorMetadata } from "#metadata/types";
|
|
12
12
|
/**
|
|
13
13
|
* Compilation asked to retry later (class lifecycle metadata not discovered yet).
|
|
14
14
|
*
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { NO_INSTANCE } from "#core/binding";
|
|
2
2
|
import { tokenName } from "#core/token";
|
|
3
|
-
import { AsyncResolutionError } from "#errors
|
|
4
|
-
import { injectionSlotToResolveOptions } from "#injection/
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const PLAN_DEPTH_LIMIT = 32;
|
|
3
|
+
import { AsyncResolutionError } from "#errors";
|
|
4
|
+
import { injectionSlotToResolveOptions } from "#injection/dependency-slot";
|
|
5
|
+
import { settleInOrder } from "#resolution/async-fan-out";
|
|
6
|
+
import { enterSeededPath, leaveSeededPath } from "#resolution/path";
|
|
7
|
+
import { generateAsyncPlan, generatePlan, isPlanCodegenAvailable, PLAN_CODEGEN_THRESHOLD, } from "#resolution/plan/codegen";
|
|
9
8
|
/**
|
|
10
9
|
* Compilation asked to retry later (class lifecycle metadata not discovered yet).
|
|
11
10
|
*
|
|
@@ -21,12 +20,28 @@ function allSynchronous(deps) {
|
|
|
21
20
|
return true;
|
|
22
21
|
}
|
|
23
22
|
/**
|
|
24
|
-
* The promise-aware combinator: run every dep thunk,
|
|
23
|
+
* The promise-aware combinator: run every dep thunk, settle them together, then apply.
|
|
25
24
|
*
|
|
26
25
|
* @remarks A dep's sync throw becomes that slot's rejection so its siblings still start — the
|
|
27
|
-
* interpreted path starts every sibling before
|
|
26
|
+
* interpreted path starts every sibling before any failure is reported, and so does this; a failure
|
|
27
|
+
* is the first in declaration order, as it is on every other lane.
|
|
28
28
|
*/
|
|
29
29
|
function settleThenApply(deps, apply) {
|
|
30
|
+
if (deps.length === 1) {
|
|
31
|
+
// One dependency has one outcome, so there is nothing to order and no fan-out to settle.
|
|
32
|
+
const only = deps[0].run;
|
|
33
|
+
const applyOne = (value) => apply([value]);
|
|
34
|
+
return () => {
|
|
35
|
+
let pending;
|
|
36
|
+
try {
|
|
37
|
+
pending = only();
|
|
38
|
+
}
|
|
39
|
+
catch (dependencyError) {
|
|
40
|
+
pending = Promise.reject(dependencyError);
|
|
41
|
+
}
|
|
42
|
+
return Promise.resolve(pending).then(applyOne);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
30
45
|
return () => {
|
|
31
46
|
const pending = new Array(deps.length);
|
|
32
47
|
for (let index = 0; index < deps.length; index += 1) {
|
|
@@ -37,7 +52,7 @@ function settleThenApply(deps, apply) {
|
|
|
37
52
|
pending[index] = Promise.reject(dependencyError);
|
|
38
53
|
}
|
|
39
54
|
}
|
|
40
|
-
return
|
|
55
|
+
return settleInOrder(pending, apply);
|
|
41
56
|
};
|
|
42
57
|
}
|
|
43
58
|
/**
|
|
@@ -94,24 +109,52 @@ export class InstantiationPlanCompiler {
|
|
|
94
109
|
* root-stack rule one level down: every sync lane pops what it pushes, so the owned array still
|
|
95
110
|
* holds exactly the seed when a call returns. A reentrant call finds it claimed and mints its
|
|
96
111
|
* own copy; a return that did not restore the length hands nothing back, so the next call mints.
|
|
112
|
+
* The plan pushed no frame for the ancestors it inlined, so the seed's bindings are marked in
|
|
113
|
+
* flight around the call: an escaped factory cycling back into one is caught as the interpreted
|
|
114
|
+
* path would catch it.
|
|
97
115
|
*/
|
|
98
116
|
#compileEscapeThunk(token, ancestors, arity = "single", options) {
|
|
99
117
|
const host = this.#host;
|
|
100
118
|
const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
|
|
101
119
|
const depth = frames.length;
|
|
102
120
|
let owned = [...frames];
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
// A plan root's own opaque dependency has one ancestor, and it is the common escape, so its
|
|
122
|
+
// marking is written out: one flag read and at most two writes, no loop and no call.
|
|
123
|
+
const only = ancestors.length === 1 ? ancestors[0] : undefined;
|
|
124
|
+
const run = only === undefined
|
|
125
|
+
? () => {
|
|
126
|
+
const stack = owned ?? [...frames];
|
|
127
|
+
owned = undefined;
|
|
128
|
+
const alreadyInFlight = enterSeededPath(ancestors);
|
|
129
|
+
try {
|
|
130
|
+
return host.resolveEscaped(token, options, arity, stack);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
leaveSeededPath(ancestors, alreadyInFlight);
|
|
134
|
+
if (stack.length === depth) {
|
|
135
|
+
owned = stack;
|
|
136
|
+
}
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
|
-
|
|
139
|
+
: () => {
|
|
140
|
+
const stack = owned ?? [...frames];
|
|
141
|
+
owned = undefined;
|
|
142
|
+
const wasInFlight = only.inFlight;
|
|
143
|
+
if (!wasInFlight) {
|
|
144
|
+
only.inFlight = true;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
return host.resolveEscaped(token, options, arity, stack);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
if (!wasInFlight) {
|
|
151
|
+
only.inFlight = false;
|
|
152
|
+
}
|
|
153
|
+
if (stack.length === depth) {
|
|
154
|
+
owned = stack;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
115
158
|
return { run, node: { kind: "thunk", run } };
|
|
116
159
|
}
|
|
117
160
|
// A resolved binding declares its deps as explicit descriptors — same rules as
|
|
@@ -282,10 +325,7 @@ export class InstantiationPlanCompiler {
|
|
|
282
325
|
node: { kind: "singleton", binding: singletonBinding, escape },
|
|
283
326
|
};
|
|
284
327
|
}
|
|
285
|
-
if (scope === "transient" &&
|
|
286
|
-
binding.kind === "class" &&
|
|
287
|
-
depth < PLAN_DEPTH_LIMIT &&
|
|
288
|
-
!compileStack.has(binding.identifier)) {
|
|
328
|
+
if (scope === "transient" && binding.kind === "class" && !compileStack.has(binding.identifier)) {
|
|
289
329
|
const inlined = this.#compileClassPlan(binding, compileStack, depth + 1, ancestors);
|
|
290
330
|
if (inlined !== null) {
|
|
291
331
|
return inlined;
|
|
@@ -476,7 +516,7 @@ export class InstantiationPlanCompiler {
|
|
|
476
516
|
node: { kind: "singleton", binding: singletonBinding, escape },
|
|
477
517
|
};
|
|
478
518
|
}
|
|
479
|
-
if (scope === "transient" &&
|
|
519
|
+
if (scope === "transient" && !compileStack.has(binding.identifier)) {
|
|
480
520
|
let inlined = null;
|
|
481
521
|
if (binding.kind === "class") {
|
|
482
522
|
inlined = this.#compileAsyncClassNode(binding, compileStack, depth + 1, ancestors);
|
|
@@ -3,13 +3,13 @@ import type { Binding } from "#core/binding";
|
|
|
3
3
|
import type { BindingRegistry } from "#core/registry";
|
|
4
4
|
import type { Token } from "#core/token";
|
|
5
5
|
import type { BindingTag, Constructor, ResolutionFrame, ResolveOptions } from "#core/types";
|
|
6
|
-
import type { ResolutionDiagnostics } from "#
|
|
7
|
-
import type { LifecycleManager } from "#lifecycle/
|
|
8
|
-
import type { ScopeManager } from "#lifecycle/
|
|
9
|
-
import type { MetadataReader } from "#metadata/
|
|
10
|
-
import type { DefaultLookupEntry } from "#resolution/cache/
|
|
6
|
+
import type { ResolutionDiagnostics } from "#introspection/diagnostics";
|
|
7
|
+
import type { LifecycleManager } from "#lifecycle/hooks";
|
|
8
|
+
import type { ScopeManager } from "#lifecycle/scopes";
|
|
9
|
+
import type { MetadataReader } from "#metadata/types";
|
|
10
|
+
import type { DefaultLookupEntry } from "#resolution/cache/lookup";
|
|
11
11
|
import type { ResolverCallbacks } from "#resolution/context";
|
|
12
|
-
import type { BranchDepth } from "#resolution/path
|
|
12
|
+
import type { BranchDepth } from "#resolution/path";
|
|
13
13
|
/**
|
|
14
14
|
* The resolution engine driving binding selection, instantiation, scoping, and lifecycle hooks.
|
|
15
15
|
*
|
|
@@ -51,7 +51,7 @@ export declare class DependencyResolver implements ResolverCallbacks {
|
|
|
51
51
|
* shape it had; the container routes a top-level read with no options here.
|
|
52
52
|
*/
|
|
53
53
|
resolveRootCollection<Value>(token: Token<Value> | Constructor<Value>): ReadonlyArray<Value>;
|
|
54
|
-
/** The async twin of `resolveRootCollection`: a stable value list
|
|
54
|
+
/** The async twin of `resolveRootCollection`: a stable value list answers at once, candidates fan out as usual. */
|
|
55
55
|
resolveRootCollectionAsync<Value>(token: Token<Value> | Constructor<Value>): Promise<ReadonlyArray<Value>>;
|
|
56
56
|
resolveAsyncFromContext<Value>(token: Token<Value> | Constructor<Value>, resolutionStack: Array<ResolutionFrame>, branchDepth: BranchDepth): Promise<Value>;
|
|
57
57
|
/**
|
|
@@ -65,12 +65,11 @@ export declare class DependencyResolver implements ResolverCallbacks {
|
|
|
65
65
|
resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>, branchDepth?: BranchDepth, precomputedCriterion?: BindingTag | null): Promise<Value | undefined>;
|
|
66
66
|
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>, branchDepth?: BranchDepth): Promise<ReadonlyArray<Value>>;
|
|
67
67
|
/**
|
|
68
|
-
* Entry for
|
|
68
|
+
* Entry for an options-less resolve the container starts: the root of a branch of its own.
|
|
69
69
|
*
|
|
70
|
-
* @remarks A
|
|
71
|
-
*
|
|
70
|
+
* @remarks A statically-visible transient graph answers from its compiled async plan; everything
|
|
71
|
+
* else opens a branch over a fresh array, so every level's context keeps its own ancestors for as
|
|
72
|
+
* long as the factory holds it — across an `await` included.
|
|
72
73
|
*/
|
|
73
|
-
resolveAsyncFromCascade(token: Token<unknown> | Constructor): Promise<unknown>;
|
|
74
|
-
/** Entry for a resolve the container starts, which opens the cascade rather than joining one. */
|
|
75
74
|
resolveAsyncFromRoot(token: Token<unknown> | Constructor): Promise<unknown>;
|
|
76
75
|
}
|