@codefast/di 0.9.0 → 0.10.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 +165 -0
- package/README.md +6 -5
- package/dist/ambient/active-container.d.ts +14 -4
- package/dist/ambient/active-container.js +22 -1
- package/dist/container/binding-builders.d.ts +35 -13
- package/dist/container/binding-builders.js +174 -97
- package/dist/container/container.d.ts +10 -10
- package/dist/container/container.js +53 -36
- package/dist/core/binding-scope.d.ts +2 -2
- package/dist/core/binding.d.ts +42 -49
- package/dist/core/binding.js +12 -38
- package/dist/core/constraint-requirement.d.ts +1 -1
- package/dist/core/module.d.ts +3 -3
- package/dist/core/registry.d.ts +52 -9
- package/dist/core/registry.js +376 -162
- package/dist/core/state-epoch.d.ts +16 -0
- package/dist/core/state-epoch.js +21 -0
- package/dist/core/token.d.ts +1 -1
- package/dist/core/types.d.ts +12 -9
- package/dist/decorators/inject.d.ts +3 -3
- package/dist/decorators/inject.js +5 -5
- package/dist/decorators/injectable.d.ts +2 -2
- package/dist/decorators/injectable.js +2 -2
- package/dist/decorators/lifecycle-decorators.js +2 -2
- package/dist/errors/diagnostics.d.ts +2 -0
- package/dist/errors/errors.d.ts +29 -3
- package/dist/errors/errors.js +34 -2
- package/dist/index.d.ts +35 -35
- package/dist/index.js +19 -19
- package/dist/injection/descriptor.d.ts +9 -7
- package/dist/injection/descriptor.js +3 -1
- package/dist/injection/resolve-options.d.ts +9 -3
- package/dist/injection/resolve-options.js +17 -1
- package/dist/introspection/dependency-graph.d.ts +3 -3
- package/dist/introspection/dependency-graph.js +15 -10
- package/dist/introspection/graph-adapters/cytoscape.d.ts +1 -1
- package/dist/introspection/graph-adapters/dot.d.ts +1 -1
- package/dist/introspection/graph-adapters/mermaid.d.ts +1 -1
- package/dist/introspection/graph-adapters/reactflow.d.ts +1 -1
- package/dist/introspection/inspector.d.ts +7 -5
- package/dist/introspection/inspector.js +13 -27
- package/dist/lifecycle/lifecycle-manager.d.ts +4 -4
- package/dist/lifecycle/lifecycle-manager.js +13 -11
- package/dist/lifecycle/scope-manager.d.ts +2 -2
- package/dist/lifecycle/scope-manager.js +4 -4
- package/dist/metadata/metadata-reader-token.d.ts +2 -2
- package/dist/metadata/metadata-reader-token.js +1 -1
- package/dist/metadata/metadata-types.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.d.ts +3 -3
- package/dist/metadata/symbol-metadata-reader.js +1 -1
- package/dist/metadata/verifying-metadata-reader.d.ts +1 -1
- package/dist/metadata/verifying-metadata-reader.js +2 -2
- package/dist/resolution/cache/activation-need.d.ts +6 -4
- package/dist/resolution/cache/activation-need.js +13 -6
- package/dist/resolution/cache/binding-lookup-cache.d.ts +39 -6
- package/dist/resolution/cache/binding-lookup-cache.js +97 -13
- package/dist/resolution/cache/class-introspector.d.ts +6 -6
- package/dist/resolution/cache/class-introspector.js +82 -69
- package/dist/resolution/context.d.ts +11 -11
- package/dist/resolution/context.js +1 -1
- package/dist/resolution/path/resolution-path.d.ts +1 -1
- package/dist/resolution/path/resolution-path.js +1 -1
- package/dist/resolution/plan/instantiation-plan.d.ts +16 -4
- package/dist/resolution/plan/instantiation-plan.js +160 -69
- package/dist/resolution/plan/plan-codegen.d.ts +100 -0
- package/dist/resolution/plan/plan-codegen.js +185 -0
- package/dist/resolution/resolver.d.ts +26 -16
- package/dist/resolution/resolver.js +401 -147
- package/dist/resolution/select/binding-select.d.ts +6 -5
- package/dist/resolution/select/binding-select.js +17 -11
- package/dist/resolution/select/constraints.d.ts +3 -3
- package/dist/resolution/select/constraints.js +4 -4
- package/package.json +11 -3
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { NO_INSTANCE } from "
|
|
2
|
-
import { tokenName } from "
|
|
3
|
-
import { AsyncResolutionError } from "
|
|
4
|
-
import { injectionSlotToResolveOptions } from "
|
|
1
|
+
import { NO_INSTANCE } from "#core/binding";
|
|
2
|
+
import { tokenName } from "#core/token";
|
|
3
|
+
import { AsyncResolutionError } from "#errors/errors";
|
|
4
|
+
import { injectionSlotToResolveOptions } from "#injection/resolve-options";
|
|
5
|
+
import { generateAsyncPlan, generatePlan, isPlanCodegenAvailable, PLAN_CODEGEN_THRESHOLD, } from "#resolution/plan/plan-codegen";
|
|
5
6
|
// Past this depth a dependency escapes to the runtime path rather than inlining further —
|
|
6
7
|
// compiled closures nest one JS frame per level, and pathological graphs are the runtime's job.
|
|
7
8
|
const PLAN_DEPTH_LIMIT = 32;
|
|
@@ -46,13 +47,45 @@ function settleThenApply(deps, apply) {
|
|
|
46
47
|
*/
|
|
47
48
|
export class InstantiationPlanCompiler {
|
|
48
49
|
#host;
|
|
50
|
+
#generatedPlanCount = 0;
|
|
49
51
|
constructor(host) {
|
|
50
52
|
this.#host = host;
|
|
51
53
|
}
|
|
54
|
+
/** Plans this compiler has generated as functions of their own. */
|
|
55
|
+
get generatedPlanCount() {
|
|
56
|
+
return this.#generatedPlanCount;
|
|
57
|
+
}
|
|
52
58
|
compile(binding) {
|
|
53
|
-
|
|
59
|
+
const compiled = binding.kind === "class"
|
|
54
60
|
? this.#compileClassPlan(binding, new Set(), 0, [])
|
|
55
61
|
: this.#compileResolvedPlan(binding, new Set(), 0, []);
|
|
62
|
+
if (compiled === null || compiled === PLAN_RETRY) {
|
|
63
|
+
return compiled;
|
|
64
|
+
}
|
|
65
|
+
const host = this.#host;
|
|
66
|
+
return this.#tiered(compiled.run, () => generatePlan(compiled.node), (current, next) => host.replacePlan(binding, current, next));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The closure runs first; a plan that keeps running is generated as a function of its own and
|
|
70
|
+
* takes the closure's place in the host's map, so nothing resolved a few times ever compiles one.
|
|
71
|
+
*/
|
|
72
|
+
#tiered(closure, generate, replace) {
|
|
73
|
+
if (!isPlanCodegenAvailable()) {
|
|
74
|
+
return closure;
|
|
75
|
+
}
|
|
76
|
+
let runs = 0;
|
|
77
|
+
const plan = () => {
|
|
78
|
+
runs += 1;
|
|
79
|
+
if (runs === PLAN_CODEGEN_THRESHOLD) {
|
|
80
|
+
const generated = generate();
|
|
81
|
+
if (generated !== null) {
|
|
82
|
+
this.#generatedPlanCount += 1;
|
|
83
|
+
}
|
|
84
|
+
replace(plan, generated ?? closure);
|
|
85
|
+
}
|
|
86
|
+
return closure();
|
|
87
|
+
};
|
|
88
|
+
return plan;
|
|
56
89
|
}
|
|
57
90
|
/**
|
|
58
91
|
* Re-entry into the runtime resolver for a dependency the plan can't see through.
|
|
@@ -67,7 +100,7 @@ export class InstantiationPlanCompiler {
|
|
|
67
100
|
const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
|
|
68
101
|
const depth = frames.length;
|
|
69
102
|
let owned = [...frames];
|
|
70
|
-
|
|
103
|
+
const run = () => {
|
|
71
104
|
const stack = owned ?? [...frames];
|
|
72
105
|
owned = undefined;
|
|
73
106
|
try {
|
|
@@ -79,37 +112,42 @@ export class InstantiationPlanCompiler {
|
|
|
79
112
|
}
|
|
80
113
|
}
|
|
81
114
|
};
|
|
115
|
+
return { run, node: { kind: "thunk", run } };
|
|
82
116
|
}
|
|
83
117
|
// A resolved binding declares its deps as explicit descriptors — same rules as
|
|
84
118
|
// class params, with the factory call (and its sync-only check) in place of `new`.
|
|
85
119
|
#compileResolvedPlan(binding, compileStack, depth, ancestors) {
|
|
86
|
-
if (binding.
|
|
120
|
+
if (binding.activationHook !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
87
121
|
return null;
|
|
88
122
|
}
|
|
89
123
|
const factory = binding.factory;
|
|
90
124
|
const tokenDisplayName = tokenName(binding.token);
|
|
91
|
-
const
|
|
125
|
+
const deps = new Array(binding.deps.length);
|
|
92
126
|
const depAncestors = [...ancestors, binding];
|
|
93
|
-
compileStack.add(binding.
|
|
127
|
+
compileStack.add(binding.identifier);
|
|
94
128
|
try {
|
|
95
129
|
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
96
|
-
const
|
|
97
|
-
if (
|
|
98
|
-
return
|
|
130
|
+
const dep = this.#compileInjectionThunk(binding.deps[index], compileStack, depth, depAncestors);
|
|
131
|
+
if (dep === PLAN_RETRY) {
|
|
132
|
+
return dep;
|
|
99
133
|
}
|
|
100
|
-
|
|
134
|
+
deps[index] = dep;
|
|
101
135
|
}
|
|
102
136
|
}
|
|
103
137
|
finally {
|
|
104
|
-
compileStack.delete(binding.
|
|
138
|
+
compileStack.delete(binding.identifier);
|
|
105
139
|
}
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
const runs = deps.map((dep) => dep.run);
|
|
141
|
+
const settle = (factoryResult) => {
|
|
108
142
|
if (factoryResult instanceof Promise) {
|
|
109
143
|
throw new AsyncResolutionError(tokenDisplayName);
|
|
110
144
|
}
|
|
111
145
|
return factoryResult;
|
|
112
146
|
};
|
|
147
|
+
return {
|
|
148
|
+
run: () => settle(factory(...runs.map((run) => run()))),
|
|
149
|
+
node: { kind: "call", factory, settle, deps: deps.map((dep) => dep.node) },
|
|
150
|
+
};
|
|
113
151
|
}
|
|
114
152
|
/**
|
|
115
153
|
* One dependency of a plan node — a constructor param or a `toResolved` descriptor.
|
|
@@ -141,7 +179,7 @@ export class InstantiationPlanCompiler {
|
|
|
141
179
|
return this.#compileDepThunk(entry, compileStack, depth, ancestors);
|
|
142
180
|
}
|
|
143
181
|
#compileClassPlan(binding, compileStack, depth, ancestors) {
|
|
144
|
-
if (binding.
|
|
182
|
+
if (binding.activationHook !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
145
183
|
return null;
|
|
146
184
|
}
|
|
147
185
|
const target = binding.target;
|
|
@@ -149,77 +187,105 @@ export class InstantiationPlanCompiler {
|
|
|
149
187
|
if (hasPostConstruct === undefined) {
|
|
150
188
|
return PLAN_RETRY;
|
|
151
189
|
}
|
|
152
|
-
if (hasPostConstruct
|
|
190
|
+
if (hasPostConstruct) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
// An accessor-injected class compiles only as the plan's root: its accessors resolve during
|
|
194
|
+
// construction with the class's own frame on the path, and a root has no static ancestors to replay.
|
|
195
|
+
const withAccessors = this.#host.needsActiveContainer(target);
|
|
196
|
+
if (withAccessors && depth !== 0) {
|
|
153
197
|
return null;
|
|
154
198
|
}
|
|
155
199
|
const invokable = target;
|
|
156
200
|
const meta = this.#host.getConstructorMetadata(target);
|
|
157
201
|
if (meta === undefined) {
|
|
158
202
|
// Metadata-less classes with required params throw on the runtime path — keep them there.
|
|
159
|
-
|
|
203
|
+
if (target.length !== 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return this.#classNode(binding, invokable, withAccessors, []);
|
|
160
207
|
}
|
|
161
208
|
const params = meta.params;
|
|
162
209
|
if (params.length === 0) {
|
|
163
|
-
return (
|
|
210
|
+
return this.#classNode(binding, invokable, withAccessors, []);
|
|
164
211
|
}
|
|
165
|
-
const
|
|
212
|
+
const deps = new Array(params.length);
|
|
166
213
|
const depAncestors = [...ancestors, binding];
|
|
167
|
-
compileStack.add(binding.
|
|
214
|
+
compileStack.add(binding.identifier);
|
|
168
215
|
try {
|
|
169
216
|
for (let index = 0; index < params.length; index += 1) {
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
217
|
+
const dep = this.#compileInjectionThunk(params[index], compileStack, depth, depAncestors);
|
|
218
|
+
if (dep === PLAN_RETRY) {
|
|
219
|
+
return dep;
|
|
173
220
|
}
|
|
174
|
-
|
|
221
|
+
deps[index] = dep;
|
|
175
222
|
}
|
|
176
223
|
}
|
|
177
224
|
finally {
|
|
178
|
-
compileStack.delete(binding.
|
|
225
|
+
compileStack.delete(binding.identifier);
|
|
179
226
|
}
|
|
180
|
-
|
|
227
|
+
return this.#classNode(binding, invokable, withAccessors, deps);
|
|
228
|
+
}
|
|
229
|
+
// The closure is hand-written per arity up to three so the common shapes spread nothing.
|
|
230
|
+
#classNode(binding, invokable, withAccessors, deps) {
|
|
231
|
+
const nodes = deps.map((dep) => dep.node);
|
|
232
|
+
if (withAccessors) {
|
|
233
|
+
const host = this.#host;
|
|
234
|
+
const construct = (values) => host.constructWithAccessors(binding, invokable, values);
|
|
235
|
+
const runs = deps.map((dep) => dep.run);
|
|
236
|
+
return { run: () => construct(runs.map((run) => run())), node: { kind: "accessors", construct, deps: nodes } };
|
|
237
|
+
}
|
|
238
|
+
const node = { kind: "construct", target: invokable, deps: nodes };
|
|
239
|
+
switch (deps.length) {
|
|
240
|
+
case 0:
|
|
241
|
+
return { run: () => new invokable(), node };
|
|
181
242
|
case 1: {
|
|
182
|
-
const dep0 =
|
|
183
|
-
return () => new invokable(dep0());
|
|
243
|
+
const dep0 = deps[0].run;
|
|
244
|
+
return { run: () => new invokable(dep0()), node };
|
|
184
245
|
}
|
|
185
246
|
case 2: {
|
|
186
|
-
const dep0 =
|
|
187
|
-
const dep1 =
|
|
188
|
-
return () => new invokable(dep0(), dep1());
|
|
247
|
+
const dep0 = deps[0].run;
|
|
248
|
+
const dep1 = deps[1].run;
|
|
249
|
+
return { run: () => new invokable(dep0(), dep1()), node };
|
|
189
250
|
}
|
|
190
251
|
case 3: {
|
|
191
|
-
const dep0 =
|
|
192
|
-
const dep1 =
|
|
193
|
-
const dep2 =
|
|
194
|
-
return () => new invokable(dep0(), dep1(), dep2());
|
|
252
|
+
const dep0 = deps[0].run;
|
|
253
|
+
const dep1 = deps[1].run;
|
|
254
|
+
const dep2 = deps[2].run;
|
|
255
|
+
return { run: () => new invokable(dep0(), dep1(), dep2()), node };
|
|
256
|
+
}
|
|
257
|
+
default: {
|
|
258
|
+
const runs = deps.map((dep) => dep.run);
|
|
259
|
+
return { run: () => new invokable(...runs.map((run) => run())), node };
|
|
195
260
|
}
|
|
196
|
-
default:
|
|
197
|
-
return () => new invokable(...depThunks.map((thunk) => thunk()));
|
|
198
261
|
}
|
|
199
262
|
}
|
|
200
263
|
#compileDepThunk(entry, compileStack, depth, ancestors, options) {
|
|
201
264
|
const { binding } = entry;
|
|
202
|
-
if (binding.kind === "constant" && binding.
|
|
265
|
+
if (binding.kind === "constant" && binding.activationHook === undefined) {
|
|
203
266
|
if (!this.#host.hasActivationHandlers(binding)) {
|
|
204
267
|
const value = binding.value;
|
|
205
|
-
return () => value;
|
|
268
|
+
return { run: () => value, node: { kind: "value", value } };
|
|
206
269
|
}
|
|
207
270
|
}
|
|
208
271
|
const scope = binding.scope;
|
|
209
272
|
if (scope === "singleton") {
|
|
210
273
|
// Cached-singleton read; the first materialization escapes so it sees the same ancestors
|
|
211
274
|
// (and therefore the same cycle detection) the interpreted path would have built.
|
|
212
|
-
const escape = this.#compileEscapeThunk(binding.token, ancestors, "single", options);
|
|
275
|
+
const escape = this.#compileEscapeThunk(binding.token, ancestors, "single", options).run;
|
|
213
276
|
const singletonBinding = binding;
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
|
|
277
|
+
return {
|
|
278
|
+
run: () => {
|
|
279
|
+
const cached = singletonBinding.instance;
|
|
280
|
+
return cached === NO_INSTANCE ? escape() : cached;
|
|
281
|
+
},
|
|
282
|
+
node: { kind: "singleton", binding: singletonBinding, escape },
|
|
217
283
|
};
|
|
218
284
|
}
|
|
219
285
|
if (scope === "transient" &&
|
|
220
286
|
binding.kind === "class" &&
|
|
221
287
|
depth < PLAN_DEPTH_LIMIT &&
|
|
222
|
-
!compileStack.has(binding.
|
|
288
|
+
!compileStack.has(binding.identifier)) {
|
|
223
289
|
const inlined = this.#compileClassPlan(binding, compileStack, depth + 1, ancestors);
|
|
224
290
|
if (inlined !== null) {
|
|
225
291
|
return inlined;
|
|
@@ -244,18 +310,17 @@ export class InstantiationPlanCompiler {
|
|
|
244
310
|
if (node === null || node === PLAN_RETRY) {
|
|
245
311
|
return node;
|
|
246
312
|
}
|
|
247
|
-
|
|
313
|
+
const host = this.#host;
|
|
314
|
+
return this.#tiered(node.run, () => generateAsyncPlan(node.node), (current, next) => host.replaceAsyncPlan(binding, current, next));
|
|
248
315
|
}
|
|
249
316
|
#compileAsyncEscapeThunk(token, ancestors, arity = "single", options) {
|
|
250
317
|
const host = this.#host;
|
|
251
318
|
const frames = ancestors.map((ancestor) => host.getResolutionFrame(ancestor));
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
promiseShape: "always",
|
|
255
|
-
};
|
|
319
|
+
const run = () => host.resolveEscapedAsync(token, options, arity, [...frames]);
|
|
320
|
+
return { run, promiseShape: "always", node: { kind: "thunk", run } };
|
|
256
321
|
}
|
|
257
322
|
#compileAsyncClassNode(binding, compileStack, depth, ancestors) {
|
|
258
|
-
if (binding.
|
|
323
|
+
if (binding.activationHook !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
259
324
|
return null;
|
|
260
325
|
}
|
|
261
326
|
const target = binding.target;
|
|
@@ -268,17 +333,22 @@ export class InstantiationPlanCompiler {
|
|
|
268
333
|
}
|
|
269
334
|
const invokable = target;
|
|
270
335
|
const meta = this.#host.getConstructorMetadata(target);
|
|
336
|
+
const leaf = {
|
|
337
|
+
run: () => new invokable(),
|
|
338
|
+
promiseShape: "never",
|
|
339
|
+
node: { kind: "construct", target: invokable, deps: [], awaits: false },
|
|
340
|
+
};
|
|
271
341
|
if (meta === undefined) {
|
|
272
342
|
// Metadata-less classes with required params throw on the runtime path — keep them there.
|
|
273
|
-
return target.length === 0 ?
|
|
343
|
+
return target.length === 0 ? leaf : null;
|
|
274
344
|
}
|
|
275
345
|
const params = meta.params;
|
|
276
346
|
if (params.length === 0) {
|
|
277
|
-
return
|
|
347
|
+
return leaf;
|
|
278
348
|
}
|
|
279
349
|
const depThunks = new Array(params.length);
|
|
280
350
|
const depAncestors = [...ancestors, binding];
|
|
281
|
-
compileStack.add(binding.
|
|
351
|
+
compileStack.add(binding.identifier);
|
|
282
352
|
try {
|
|
283
353
|
for (let index = 0; index < params.length; index += 1) {
|
|
284
354
|
const thunk = this.#compileAsyncInjectionThunk(params[index], compileStack, depth, depAncestors);
|
|
@@ -289,36 +359,43 @@ export class InstantiationPlanCompiler {
|
|
|
289
359
|
}
|
|
290
360
|
}
|
|
291
361
|
finally {
|
|
292
|
-
compileStack.delete(binding.
|
|
362
|
+
compileStack.delete(binding.identifier);
|
|
293
363
|
}
|
|
364
|
+
const nodes = depThunks.map((thunk) => thunk.node);
|
|
294
365
|
if (allSynchronous(depThunks)) {
|
|
366
|
+
const node = { kind: "construct", target: invokable, deps: nodes, awaits: false };
|
|
295
367
|
switch (depThunks.length) {
|
|
296
368
|
case 1: {
|
|
297
369
|
const dep0 = depThunks[0].run;
|
|
298
|
-
return { run: () => new invokable(dep0()), promiseShape: "never" };
|
|
370
|
+
return { run: () => new invokable(dep0()), promiseShape: "never", node };
|
|
299
371
|
}
|
|
300
372
|
case 2: {
|
|
301
373
|
const dep0 = depThunks[0].run;
|
|
302
374
|
const dep1 = depThunks[1].run;
|
|
303
|
-
return { run: () => new invokable(dep0(), dep1()), promiseShape: "never" };
|
|
375
|
+
return { run: () => new invokable(dep0(), dep1()), promiseShape: "never", node };
|
|
304
376
|
}
|
|
305
377
|
default:
|
|
306
378
|
return {
|
|
307
379
|
run: () => new invokable(...depThunks.map((thunk) => thunk.run())),
|
|
308
380
|
promiseShape: "never",
|
|
381
|
+
node,
|
|
309
382
|
};
|
|
310
383
|
}
|
|
311
384
|
}
|
|
312
|
-
return {
|
|
385
|
+
return {
|
|
386
|
+
run: settleThenApply(depThunks, (values) => new invokable(...values)),
|
|
387
|
+
promiseShape: "always",
|
|
388
|
+
node: { kind: "construct", target: invokable, deps: nodes, awaits: true },
|
|
389
|
+
};
|
|
313
390
|
}
|
|
314
391
|
#compileAsyncResolvedNode(binding, compileStack, depth, ancestors) {
|
|
315
|
-
if (binding.
|
|
392
|
+
if (binding.activationHook !== undefined || this.#host.hasActivationHandlers(binding)) {
|
|
316
393
|
return null;
|
|
317
394
|
}
|
|
318
395
|
const factory = binding.factory;
|
|
319
396
|
const depThunks = new Array(binding.deps.length);
|
|
320
397
|
const depAncestors = [...ancestors, binding];
|
|
321
|
-
compileStack.add(binding.
|
|
398
|
+
compileStack.add(binding.identifier);
|
|
322
399
|
try {
|
|
323
400
|
for (let index = 0; index < binding.deps.length; index += 1) {
|
|
324
401
|
const thunk = this.#compileAsyncInjectionThunk(binding.deps[index], compileStack, depth, depAncestors);
|
|
@@ -329,15 +406,24 @@ export class InstantiationPlanCompiler {
|
|
|
329
406
|
}
|
|
330
407
|
}
|
|
331
408
|
finally {
|
|
332
|
-
compileStack.delete(binding.
|
|
409
|
+
compileStack.delete(binding.identifier);
|
|
333
410
|
}
|
|
334
411
|
// A sync-kind factory may still hand back a promise on this path — the async entry awaits it,
|
|
335
412
|
// so the consumer is told to as well.
|
|
336
413
|
const directShape = binding.kind === "resolved-async" ? "always" : "maybe";
|
|
414
|
+
const nodes = depThunks.map((thunk) => thunk.node);
|
|
337
415
|
if (allSynchronous(depThunks)) {
|
|
338
|
-
return {
|
|
416
|
+
return {
|
|
417
|
+
run: () => factory(...depThunks.map((thunk) => thunk.run())),
|
|
418
|
+
promiseShape: directShape,
|
|
419
|
+
node: { kind: "call", factory, deps: nodes, awaits: false },
|
|
420
|
+
};
|
|
339
421
|
}
|
|
340
|
-
return {
|
|
422
|
+
return {
|
|
423
|
+
run: settleThenApply(depThunks, (values) => factory(...values)),
|
|
424
|
+
promiseShape: "always",
|
|
425
|
+
node: { kind: "call", factory, deps: nodes, awaits: true },
|
|
426
|
+
};
|
|
341
427
|
}
|
|
342
428
|
/** Mirrors {@link InstantiationPlanCompiler.#compileInjectionThunk}, escaping through the async dispatch. */
|
|
343
429
|
#compileAsyncInjectionThunk(descriptor, compileStack, depth, ancestors) {
|
|
@@ -364,28 +450,33 @@ export class InstantiationPlanCompiler {
|
|
|
364
450
|
}
|
|
365
451
|
#compileAsyncDepThunk(entry, compileStack, depth, ancestors, options) {
|
|
366
452
|
const { binding } = entry;
|
|
367
|
-
if (binding.kind === "constant" && binding.
|
|
453
|
+
if (binding.kind === "constant" && binding.activationHook === undefined) {
|
|
368
454
|
if (!this.#host.hasActivationHandlers(binding)) {
|
|
369
455
|
const value = binding.value;
|
|
370
456
|
// The interpreted path funnels every dependency through an await, which unwraps a
|
|
371
457
|
// promise-valued constant — so one compiles as needing that same await.
|
|
372
|
-
return {
|
|
458
|
+
return {
|
|
459
|
+
run: () => value,
|
|
460
|
+
promiseShape: value instanceof Promise ? "always" : "never",
|
|
461
|
+
node: { kind: "value", value },
|
|
462
|
+
};
|
|
373
463
|
}
|
|
374
464
|
}
|
|
375
465
|
const scope = binding.scope;
|
|
376
466
|
if (scope === "singleton") {
|
|
377
467
|
// Cached-singleton read; the cold materialization escapes with the same criteria.
|
|
378
|
-
const escape = this.#compileAsyncEscapeThunk(binding.token, ancestors, "single", options);
|
|
468
|
+
const escape = this.#compileAsyncEscapeThunk(binding.token, ancestors, "single", options).run;
|
|
379
469
|
const singletonBinding = binding;
|
|
380
470
|
return {
|
|
381
471
|
run: () => {
|
|
382
472
|
const cached = singletonBinding.instance;
|
|
383
|
-
return cached === NO_INSTANCE ? escape
|
|
473
|
+
return cached === NO_INSTANCE ? escape() : cached;
|
|
384
474
|
},
|
|
385
475
|
promiseShape: "maybe",
|
|
476
|
+
node: { kind: "singleton", binding: singletonBinding, escape },
|
|
386
477
|
};
|
|
387
478
|
}
|
|
388
|
-
if (scope === "transient" && depth < PLAN_DEPTH_LIMIT && !compileStack.has(binding.
|
|
479
|
+
if (scope === "transient" && depth < PLAN_DEPTH_LIMIT && !compileStack.has(binding.identifier)) {
|
|
389
480
|
let inlined = null;
|
|
390
481
|
if (binding.kind === "class") {
|
|
391
482
|
inlined = this.#compileAsyncClassNode(binding, compileStack, depth + 1, ancestors);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a hot instantiation plan as a function of its own, so its call sites answer for one plan only.
|
|
3
|
+
*/
|
|
4
|
+
import type { Binding } from "#core/binding";
|
|
5
|
+
import type { ConstructorInvocation } from "#core/constructor-type";
|
|
6
|
+
/**
|
|
7
|
+
* The number of runs a plan's closure makes before the plan is generated as its own function.
|
|
8
|
+
*
|
|
9
|
+
* @remarks Below it a plan stays a closure, which is all a cold container or a per-request child
|
|
10
|
+
* ever runs; above it a plan pays one compile for call sites nothing else feeds.
|
|
11
|
+
*
|
|
12
|
+
* @since 0.10.0
|
|
13
|
+
*/
|
|
14
|
+
export declare const PLAN_CODEGEN_THRESHOLD = 32;
|
|
15
|
+
/**
|
|
16
|
+
* The shape of a compiled sync plan: what its closure does, stated as data the generator can read.
|
|
17
|
+
*
|
|
18
|
+
* @remarks Every leaf the compiler could not see through is a `thunk` and stays opaque — generated
|
|
19
|
+
* code calls it exactly as the closure did, so an escape keeps its frames, its dispatch and its errors.
|
|
20
|
+
*
|
|
21
|
+
* @since 0.10.0
|
|
22
|
+
*/
|
|
23
|
+
export type PlanNode = {
|
|
24
|
+
readonly kind: "construct";
|
|
25
|
+
readonly target: ConstructorInvocation;
|
|
26
|
+
readonly deps: ReadonlyArray<PlanNode>;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "accessors";
|
|
29
|
+
readonly construct: (deps: Array<unknown>) => unknown;
|
|
30
|
+
readonly deps: ReadonlyArray<PlanNode>;
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "call";
|
|
33
|
+
readonly factory: (...args: Array<unknown>) => unknown;
|
|
34
|
+
readonly settle: (result: unknown) => unknown;
|
|
35
|
+
readonly deps: ReadonlyArray<PlanNode>;
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: "value";
|
|
38
|
+
readonly value: unknown;
|
|
39
|
+
} | {
|
|
40
|
+
readonly kind: "singleton";
|
|
41
|
+
readonly binding: Binding;
|
|
42
|
+
readonly escape: () => unknown;
|
|
43
|
+
} | {
|
|
44
|
+
readonly kind: "thunk";
|
|
45
|
+
readonly run: () => unknown;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The shape of a compiled async plan.
|
|
49
|
+
*
|
|
50
|
+
* @remarks A node that `awaits` has a dependency that may yield a promise, so it runs as the
|
|
51
|
+
* interpreted async path does: every dependency starts in order, a sync throw becomes that slot's
|
|
52
|
+
* rejection, and the constructor or factory runs on the settled values.
|
|
53
|
+
*
|
|
54
|
+
* @since 0.10.0
|
|
55
|
+
*/
|
|
56
|
+
export type AsyncPlanNode = {
|
|
57
|
+
readonly kind: "construct";
|
|
58
|
+
readonly target: ConstructorInvocation;
|
|
59
|
+
readonly deps: ReadonlyArray<AsyncPlanNode>;
|
|
60
|
+
readonly awaits: boolean;
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: "call";
|
|
63
|
+
readonly factory: (...args: Array<unknown>) => unknown;
|
|
64
|
+
readonly deps: ReadonlyArray<AsyncPlanNode>;
|
|
65
|
+
readonly awaits: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
readonly kind: "value";
|
|
68
|
+
readonly value: unknown;
|
|
69
|
+
} | {
|
|
70
|
+
readonly kind: "singleton";
|
|
71
|
+
readonly binding: Binding;
|
|
72
|
+
readonly escape: () => unknown;
|
|
73
|
+
} | {
|
|
74
|
+
readonly kind: "thunk";
|
|
75
|
+
readonly run: () => unknown;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Whether this runtime lets the engine compile a function from source.
|
|
79
|
+
*
|
|
80
|
+
* @remarks A Content Security Policy without `unsafe-eval` refuses the `Function` constructor; every
|
|
81
|
+
* plan then stays a closure, which behaves identically.
|
|
82
|
+
*
|
|
83
|
+
* @since 0.10.0
|
|
84
|
+
*/
|
|
85
|
+
export declare function isPlanCodegenAvailable(): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Generates a plan as a function of its own, or `null` when the runtime refuses to compile one.
|
|
88
|
+
*
|
|
89
|
+
* @remarks The source carries a serial so no two plans share a compilation-cache entry: V8 keys
|
|
90
|
+
* type feedback by function literal, and one literal per plan is the whole point.
|
|
91
|
+
*
|
|
92
|
+
* @since 0.10.0
|
|
93
|
+
*/
|
|
94
|
+
export declare function generatePlan(node: PlanNode): (() => unknown) | null;
|
|
95
|
+
/**
|
|
96
|
+
* Generates an async plan as a function of its own, or `null` when the runtime refuses to compile one.
|
|
97
|
+
*
|
|
98
|
+
* @since 0.10.0
|
|
99
|
+
*/
|
|
100
|
+
export declare function generateAsyncPlan(node: AsyncPlanNode): (() => unknown) | null;
|