@codefast/di 0.10.1 → 0.11.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 +265 -0
- package/README.md +69 -6
- package/dist/ambient/active-container.d.ts +7 -2
- package/dist/ambient/active-container.js +6 -1
- package/dist/container/binding-builders.d.ts +19 -1
- package/dist/container/binding-builders.js +75 -16
- package/dist/container/container.js +224 -47
- package/dist/core/binding-declaration.d.ts +120 -0
- package/dist/core/binding-declaration.js +186 -0
- package/dist/core/binding.d.ts +57 -5
- package/dist/core/binding.js +48 -1
- package/dist/core/module.d.ts +7 -4
- package/dist/core/module.js +17 -3
- package/dist/core/registry.d.ts +11 -7
- package/dist/core/registry.js +122 -38
- 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/decorator-metadata.d.ts +9 -0
- package/dist/decorators/decorator-metadata.js +20 -0
- package/dist/decorators/inject.js +2 -1
- package/dist/decorators/injectable.js +3 -1
- package/dist/decorators/lifecycle-decorators.js +8 -2
- package/dist/errors/errors.d.ts +77 -3
- package/dist/errors/errors.js +93 -10
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/injection/descriptor.js +3 -7
- package/dist/injection/resolve-options.js +6 -4
- package/dist/introspection/dependency-graph.d.ts +7 -2
- package/dist/introspection/dependency-graph.js +46 -23
- package/dist/introspection/graph-adapters/reactflow.js +6 -4
- package/dist/introspection/inspector.js +6 -9
- package/dist/lifecycle/lifecycle-manager.js +14 -2
- package/dist/lifecycle/scope-manager.js +28 -10
- package/dist/metadata/verifying-metadata-reader.d.ts +4 -3
- package/dist/metadata/verifying-metadata-reader.js +28 -6
- 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 +0 -1
- package/dist/resolution/cache/activation-need.js +11 -18
- package/dist/resolution/cache/binding-lookup-cache.d.ts +0 -7
- package/dist/resolution/cache/binding-lookup-cache.js +30 -17
- package/dist/resolution/cache/class-introspector.d.ts +11 -0
- package/dist/resolution/cache/class-introspector.js +18 -0
- package/dist/resolution/context.d.ts +15 -23
- package/dist/resolution/context.js +47 -56
- package/dist/resolution/path/resolution-path.d.ts +48 -13
- package/dist/resolution/path/resolution-path.js +89 -38
- package/dist/resolution/plan/instantiation-plan.js +61 -21
- package/dist/resolution/plan/plan-codegen.d.ts +7 -4
- package/dist/resolution/plan/plan-codegen.js +60 -32
- package/dist/resolution/resolver.d.ts +4 -5
- package/dist/resolution/resolver.js +288 -258
- package/package.json +14 -2
|
@@ -1,13 +1,16 @@
|
|
|
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, not a machine width, a contract value or bind-time data: generating
|
|
7
|
+
* costs some fifty closure runs and the new function runs cold for thirty more, so it repays only
|
|
8
|
+
* over runs in the thousands. Below it a plan stays a closure, which is all a cold container or a
|
|
9
|
+
* per-request child ever runs.
|
|
7
10
|
*
|
|
8
11
|
* @since 0.10.0
|
|
9
12
|
*/
|
|
10
|
-
export const PLAN_CODEGEN_THRESHOLD =
|
|
13
|
+
export const PLAN_CODEGEN_THRESHOLD = 1024;
|
|
11
14
|
const rejectWith = (error) => Promise.reject(error);
|
|
12
15
|
let codegenAvailable;
|
|
13
16
|
let generatedCount = 0;
|
|
@@ -59,10 +62,10 @@ export function generateAsyncPlan(node) {
|
|
|
59
62
|
const emitter = new PlanEmitter();
|
|
60
63
|
return compileRendered(emitter, emitter.asyncExpression(node));
|
|
61
64
|
}
|
|
62
|
-
function compileRendered(emitter,
|
|
65
|
+
function compileRendered(emitter, result) {
|
|
63
66
|
generatedCount += 1;
|
|
64
67
|
const locals = emitter.locals.length === 0 ? "" : `let ${emitter.locals.join(", ")};`;
|
|
65
|
-
const body = `"use strict";/* plan ${String(generatedCount)} */${emitter.hoisted.join("")}return () => {${locals}return ${
|
|
68
|
+
const body = `"use strict";/* plan ${String(generatedCount)} */${emitter.hoisted.join("")}return () => {${locals}${emitter.statements.join("")}return ${result};};`;
|
|
66
69
|
try {
|
|
67
70
|
// Compiling from source is the mechanism: one function literal per plan is what gives it its own feedback.
|
|
68
71
|
// oxlint-disable-next-line typescript/no-implied-eval
|
|
@@ -74,40 +77,50 @@ function compileRendered(emitter, expression) {
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
/**
|
|
77
|
-
* Renders a plan tree as
|
|
80
|
+
* Renders a plan tree as a flat sequence of statements over parameters that carry every value the plan closes over.
|
|
78
81
|
*
|
|
79
|
-
* @remarks
|
|
80
|
-
*
|
|
82
|
+
* @remarks Each node becomes one assignment to a local after its dependencies' assignments, in declaration
|
|
83
|
+
* order, so the generated function evaluates exactly as the nested closure did while nesting nothing: a
|
|
84
|
+
* graph of any depth renders as that many statements, never as an expression that deep. A node that awaits
|
|
85
|
+
* its dependencies renders as an inner function of the same shape, so every plan's awaiting nodes have call
|
|
86
|
+
* sites of their own too.
|
|
81
87
|
*/
|
|
82
88
|
class PlanEmitter {
|
|
83
89
|
names = [];
|
|
84
90
|
values = [];
|
|
85
91
|
hoisted = [];
|
|
86
|
-
#
|
|
92
|
+
#frames = [
|
|
93
|
+
{ locals: [], statements: [] },
|
|
94
|
+
];
|
|
87
95
|
#hoistedCount = 0;
|
|
88
96
|
#slotByValue = new Map();
|
|
89
97
|
/** The plan function's own temporaries. */
|
|
90
98
|
get locals() {
|
|
91
|
-
return this.#
|
|
99
|
+
return this.#frames[0].locals;
|
|
92
100
|
}
|
|
101
|
+
/** The plan function's statements, in evaluation order. */
|
|
102
|
+
get statements() {
|
|
103
|
+
return this.#frames[0].statements;
|
|
104
|
+
}
|
|
105
|
+
/** Emits a node's statements and returns the reference that holds its value. */
|
|
93
106
|
expression(node) {
|
|
94
107
|
switch (node.kind) {
|
|
95
108
|
case "construct":
|
|
96
|
-
return `new ${this.#slot(node.target, "C")}(${this.#list(node.deps)})
|
|
109
|
+
return this.#define(`new ${this.#slot(node.target, "C")}(${this.#list(node.deps)})`);
|
|
97
110
|
case "accessors":
|
|
98
|
-
return `${this.#slot(node.construct, "A")}([${this.#list(node.deps)}])
|
|
111
|
+
return this.#define(`${this.#slot(node.construct, "A")}([${this.#list(node.deps)}])`);
|
|
99
112
|
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
|
|
113
|
+
// The settle only ever throws, so it runs on the promise branch alone and the plain result stands.
|
|
114
|
+
const local = this.#define(`${this.#slot(node.factory, "F")}(${this.#list(node.deps)})`);
|
|
115
|
+
this.#statement(`if (${local} instanceof ${this.#slot(Promise, "P")}) ${local} = ${this.#slot(node.settle, "S")}(${local});`);
|
|
116
|
+
return local;
|
|
104
117
|
}
|
|
105
118
|
case "value":
|
|
106
119
|
return this.#slot(node.value, "V");
|
|
107
120
|
case "singleton":
|
|
108
121
|
return this.#singletonRead(node.binding, node.escape);
|
|
109
122
|
case "thunk":
|
|
110
|
-
return `${this.#slot(node.run, "T")}()
|
|
123
|
+
return this.#define(`${this.#slot(node.run, "T")}()`);
|
|
111
124
|
}
|
|
112
125
|
}
|
|
113
126
|
asyncExpression(node) {
|
|
@@ -116,20 +129,20 @@ class PlanEmitter {
|
|
|
116
129
|
const target = this.#slot(node.target, "C");
|
|
117
130
|
return node.awaits
|
|
118
131
|
? this.#settled(node.deps, (values) => `new ${target}(${values})`)
|
|
119
|
-
: `new ${target}(${this.#asyncList(node.deps)})
|
|
132
|
+
: this.#define(`new ${target}(${this.#asyncList(node.deps)})`);
|
|
120
133
|
}
|
|
121
134
|
case "call": {
|
|
122
135
|
const factory = this.#slot(node.factory, "F");
|
|
123
136
|
return node.awaits
|
|
124
137
|
? this.#settled(node.deps, (values) => `${factory}(${values})`)
|
|
125
|
-
: `${factory}(${this.#asyncList(node.deps)})
|
|
138
|
+
: this.#define(`${factory}(${this.#asyncList(node.deps)})`);
|
|
126
139
|
}
|
|
127
140
|
case "value":
|
|
128
141
|
return this.#slot(node.value, "V");
|
|
129
142
|
case "singleton":
|
|
130
143
|
return this.#singletonRead(node.binding, node.escape);
|
|
131
144
|
case "thunk":
|
|
132
|
-
return `${this.#slot(node.run, "T")}()
|
|
145
|
+
return this.#define(`${this.#slot(node.run, "T")}()`);
|
|
133
146
|
}
|
|
134
147
|
}
|
|
135
148
|
#list(deps) {
|
|
@@ -146,30 +159,45 @@ class PlanEmitter {
|
|
|
146
159
|
const name = `n${String(index)}`;
|
|
147
160
|
const applyName = `a${String(index)}`;
|
|
148
161
|
const reject = this.#slot(rejectWith, "R");
|
|
149
|
-
const
|
|
150
|
-
this.#
|
|
162
|
+
const frame = { locals: [], statements: [] };
|
|
163
|
+
this.#frames.push(frame);
|
|
151
164
|
const pendings = [];
|
|
152
|
-
const statements = [];
|
|
153
165
|
for (let position = 0; position < deps.length; position += 1) {
|
|
154
166
|
const pending = `p${String(position)}`;
|
|
155
167
|
pendings.push(pending);
|
|
156
|
-
statements
|
|
168
|
+
// A dependency's own statements run inside its try, so its sync throw is its rejection alone.
|
|
169
|
+
const mark = frame.statements.length;
|
|
170
|
+
const reference = this.asyncExpression(deps[position]);
|
|
171
|
+
const inner = frame.statements.splice(mark).join("");
|
|
172
|
+
frame.statements.push(`try{${inner}${pending}=${reference};}catch(e){${pending}=${reject}(e);}`);
|
|
157
173
|
}
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
174
|
+
this.#frames.pop();
|
|
175
|
+
const locals = [...pendings, ...frame.locals];
|
|
176
|
+
// One dependency has one outcome, so there is nothing to order and no fan-out to settle.
|
|
177
|
+
const settled = deps.length === 1
|
|
178
|
+
? `const ${applyName}=(v0)=>${apply("v0")};const ${name}=()=>{let ${locals.join(",")};${frame.statements.join("")}return ${this.#slot(Promise, "P")}.resolve(p0).then(${applyName});};`
|
|
179
|
+
: `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});};`;
|
|
180
|
+
this.hoisted.push(settled);
|
|
181
|
+
return this.#define(`${name}()`);
|
|
182
|
+
}
|
|
183
|
+
#define(expression) {
|
|
184
|
+
const local = this.#local();
|
|
185
|
+
this.#statement(`${local} = ${expression};`);
|
|
186
|
+
return local;
|
|
187
|
+
}
|
|
188
|
+
#statement(statement) {
|
|
189
|
+
this.#frames.at(-1).statements.push(statement);
|
|
162
190
|
}
|
|
163
191
|
#local() {
|
|
164
|
-
const locals = this.#
|
|
192
|
+
const locals = this.#frames.at(-1).locals;
|
|
165
193
|
const local = `t${String(locals.length)}`;
|
|
166
194
|
locals.push(local);
|
|
167
195
|
return local;
|
|
168
196
|
}
|
|
169
197
|
#singletonRead(binding, escape) {
|
|
170
|
-
const local = this.#
|
|
171
|
-
|
|
172
|
-
return
|
|
198
|
+
const local = this.#define(`${this.#slot(binding, "B")}.instance`);
|
|
199
|
+
this.#statement(`if (${local} === ${this.#slot(NO_INSTANCE, "N")}) ${local} = ${this.#slot(escape, "E")}();`);
|
|
200
|
+
return local;
|
|
173
201
|
}
|
|
174
202
|
// One parameter per distinct value, so a class constructed four times is one constructor with four sites.
|
|
175
203
|
#slot(value, prefix) {
|
|
@@ -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
|
}
|