@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,6 +1,6 @@
|
|
|
1
1
|
/** The `ResolutionContext` a factory is handed, and the callbacks the resolver answers it with. */
|
|
2
2
|
import type { Token } from "#core/token";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ConstraintContext, Constructor, ResolutionFrame, ResolutionContext, ResolveOptions } from "#core/types";
|
|
4
4
|
import type { BranchDepth, OwnedBranchStack } from "#resolution/path/resolution-path";
|
|
5
5
|
/**
|
|
6
6
|
* The engine surface a resolution context calls back into to resolve further dependencies.
|
|
@@ -11,8 +11,6 @@ export interface ResolverCallbacks {
|
|
|
11
11
|
resolveFromContext<Value>(token: Token<Value> | Constructor<Value>, resolutionStack: Array<ResolutionFrame>): Value;
|
|
12
12
|
resolve<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Value;
|
|
13
13
|
resolveAsyncFromContext<Value>(token: Token<Value> | Constructor<Value>, resolutionStack: Array<ResolutionFrame>, branchDepth: BranchDepth): Promise<Value>;
|
|
14
|
-
/** Not one of the eight `Value`-naming entry points: its caller is, and casts once. */
|
|
15
|
-
resolveAsyncFromCascade(token: Token<unknown> | Constructor): Promise<unknown>;
|
|
16
14
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Promise<Value>;
|
|
17
15
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Value | undefined;
|
|
18
16
|
resolveOptionalAsync<Value>(token: Token<Value> | Constructor<Value>, options: ResolveOptions | undefined, resolutionStack: Array<ResolutionFrame>): Promise<Value | undefined>;
|
|
@@ -55,6 +53,8 @@ export declare class AsyncLevelContext implements ResolutionContext {
|
|
|
55
53
|
*/
|
|
56
54
|
constructor(resolver: ResolverCallbacks, resolutionStack: OwnedBranchStack, currentOptions: ResolveOptions | undefined);
|
|
57
55
|
get graph(): ConstraintContext;
|
|
56
|
+
/** This level's own path, fixed for its lifetime, for a caller that names it in an error. */
|
|
57
|
+
get ownPath(): ReadonlyArray<ResolutionFrame>;
|
|
58
58
|
resolve<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value;
|
|
59
59
|
resolveAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<Value>;
|
|
60
60
|
resolveOptional<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Value | undefined;
|
|
@@ -63,27 +63,19 @@ export declare class AsyncLevelContext implements ResolutionContext {
|
|
|
63
63
|
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<ReadonlyArray<Value>>;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* The one context every
|
|
66
|
+
* The one constraint context every predicate reads, whatever lane built it.
|
|
67
67
|
*
|
|
68
|
-
* @remarks
|
|
69
|
-
*
|
|
68
|
+
* @remarks One shape for the shared root, a selection over a live synchronous stack, an async
|
|
69
|
+
* level's prefix and an inspector's probe, so a predicate's call site stays monomorphic.
|
|
70
70
|
*
|
|
71
|
-
* @since 0.
|
|
71
|
+
* @since 0.11.0
|
|
72
72
|
*/
|
|
73
|
-
export declare class
|
|
73
|
+
export declare class DefaultConstraintContext implements ConstraintContext {
|
|
74
74
|
#private;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
resolveAllAsync<Value>(token: Token<Value> | Constructor<Value>, options?: ResolveOptions): Promise<ReadonlyArray<Value>>;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Creates the resolution-stack frame for one in-flight resolve.
|
|
86
|
-
*
|
|
87
|
-
* @since 0.3.16-canary.0
|
|
88
|
-
*/
|
|
89
|
-
export declare function buildResolutionFrame(tokenName: string, scope: BindingScope, bindingId: BindingIdentifier, kind: BindingKind, slot: ResolutionFrame["slot"]): ResolutionFrame;
|
|
75
|
+
readonly resolutionStack: ReadonlyArray<ResolutionFrame>;
|
|
76
|
+
readonly parent: ResolutionFrame | undefined;
|
|
77
|
+
readonly currentResolveOptions: Readonly<ResolveOptions> | undefined;
|
|
78
|
+
constructor(resolutionStack: ReadonlyArray<ResolutionFrame>, currentResolveOptions: ResolveOptions | undefined);
|
|
79
|
+
get resolutionPath(): ReadonlyArray<string>;
|
|
80
|
+
get ancestors(): ReadonlyArray<ResolutionFrame>;
|
|
81
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UNOWNED_BRANCH } from "#resolution/path/resolution-path";
|
|
1
|
+
import { bindingsOf, enterSeededPath, leaveSeededPath, UNOWNED_BRANCH } from "#resolution/path/resolution-path";
|
|
2
2
|
/**
|
|
3
3
|
* The `ResolutionContext` handed to factories and hooks, backed by the engine's callbacks.
|
|
4
4
|
*
|
|
@@ -88,6 +88,7 @@ export class AsyncLevelContext {
|
|
|
88
88
|
}
|
|
89
89
|
#graph;
|
|
90
90
|
#exactStackCache;
|
|
91
|
+
#exactBindingsCache;
|
|
91
92
|
get graph() {
|
|
92
93
|
if (this.#graph === undefined) {
|
|
93
94
|
this.#graph = new DefaultConstraintContext(this.#exactStack(), this.#currentOptions);
|
|
@@ -99,11 +100,30 @@ export class AsyncLevelContext {
|
|
|
99
100
|
#exactStack() {
|
|
100
101
|
return (this.#exactStackCache ??= this.#resolutionStack.slice(0, this.#branchDepth));
|
|
101
102
|
}
|
|
103
|
+
/** This level's own path, fixed for its lifetime, for a caller that names it in an error. */
|
|
104
|
+
get ownPath() {
|
|
105
|
+
return this.#exactStack();
|
|
106
|
+
}
|
|
107
|
+
// The bindings a synchronous call from this level marks in flight, read off the frames once.
|
|
108
|
+
#exactBindings() {
|
|
109
|
+
return (this.#exactBindingsCache ??= bindingsOf(this.#exactStack()));
|
|
110
|
+
}
|
|
111
|
+
// A synchronous call from an async level runs over a path no synchronous frame pushed, so the
|
|
112
|
+
// level's ancestors are marked in flight for its duration — and the level itself, whose factory is
|
|
113
|
+
// the caller: a factory resolving its own token synchronously has closed a cycle.
|
|
102
114
|
resolve(token, options) {
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
const path = this.#exactStack();
|
|
116
|
+
const marked = this.#exactBindings();
|
|
117
|
+
const alreadyInFlight = enterSeededPath(marked);
|
|
118
|
+
try {
|
|
119
|
+
if (options === undefined) {
|
|
120
|
+
return this.#resolver.resolveFromContext(token, path);
|
|
121
|
+
}
|
|
122
|
+
return this.#resolver.resolve(token, options, path);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
leaveSeededPath(marked, alreadyInFlight);
|
|
105
126
|
}
|
|
106
|
-
return this.#resolver.resolve(token, options, this.#exactStack());
|
|
107
127
|
}
|
|
108
128
|
resolveAsync(token, options) {
|
|
109
129
|
if (options === undefined) {
|
|
@@ -113,64 +133,43 @@ export class AsyncLevelContext {
|
|
|
113
133
|
return this.#resolver.resolveAsync(token, options, this.#exactStack());
|
|
114
134
|
}
|
|
115
135
|
resolveOptional(token, options) {
|
|
116
|
-
|
|
136
|
+
const path = this.#exactStack();
|
|
137
|
+
const marked = this.#exactBindings();
|
|
138
|
+
const alreadyInFlight = enterSeededPath(marked);
|
|
139
|
+
try {
|
|
140
|
+
return this.#resolver.resolveOptional(token, options, path);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
leaveSeededPath(marked, alreadyInFlight);
|
|
144
|
+
}
|
|
117
145
|
}
|
|
118
146
|
resolveOptionalAsync(token, options) {
|
|
119
147
|
return this.#resolver.resolveOptionalAsync(token, options, this.#exactStack());
|
|
120
148
|
}
|
|
121
149
|
resolveAll(token, options) {
|
|
122
|
-
|
|
150
|
+
const path = this.#exactStack();
|
|
151
|
+
const marked = this.#exactBindings();
|
|
152
|
+
const alreadyInFlight = enterSeededPath(marked);
|
|
153
|
+
try {
|
|
154
|
+
return this.#resolver.resolveAll(token, options, path);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
leaveSeededPath(marked, alreadyInFlight);
|
|
158
|
+
}
|
|
123
159
|
}
|
|
124
160
|
resolveAllAsync(token, options) {
|
|
125
161
|
return this.#resolver.resolveAllAsync(token, options, this.#exactStack());
|
|
126
162
|
}
|
|
127
163
|
}
|
|
128
164
|
/**
|
|
129
|
-
* The one context every
|
|
165
|
+
* The one constraint context every predicate reads, whatever lane built it.
|
|
130
166
|
*
|
|
131
|
-
* @remarks
|
|
132
|
-
*
|
|
167
|
+
* @remarks One shape for the shared root, a selection over a live synchronous stack, an async
|
|
168
|
+
* level's prefix and an inspector's probe, so a predicate's call site stays monomorphic.
|
|
133
169
|
*
|
|
134
|
-
* @since 0.
|
|
170
|
+
* @since 0.11.0
|
|
135
171
|
*/
|
|
136
|
-
export class
|
|
137
|
-
#resolver;
|
|
138
|
-
#cascadeStack;
|
|
139
|
-
constructor(resolver, cascadeStack) {
|
|
140
|
-
this.#resolver = resolver;
|
|
141
|
-
this.#cascadeStack = cascadeStack;
|
|
142
|
-
}
|
|
143
|
-
get graph() {
|
|
144
|
-
// Not memoized: this context outlives every level, so a cached graph would describe whichever
|
|
145
|
-
// level asked first. The cascade stack is only this level's ancestors while it is open.
|
|
146
|
-
return new DefaultConstraintContext(this.#cascadeStack, undefined);
|
|
147
|
-
}
|
|
148
|
-
resolve(token, options) {
|
|
149
|
-
if (options === undefined) {
|
|
150
|
-
return this.#resolver.resolveFromContext(token, this.#cascadeStack);
|
|
151
|
-
}
|
|
152
|
-
return this.#resolver.resolve(token, options, this.#cascadeStack);
|
|
153
|
-
}
|
|
154
|
-
resolveAsync(token, options) {
|
|
155
|
-
if (options === undefined) {
|
|
156
|
-
return this.#resolver.resolveAsyncFromCascade(token);
|
|
157
|
-
}
|
|
158
|
-
return this.#resolver.resolveAsync(token, options, [...this.#cascadeStack]);
|
|
159
|
-
}
|
|
160
|
-
resolveOptional(token, options) {
|
|
161
|
-
return this.#resolver.resolveOptional(token, options, this.#cascadeStack);
|
|
162
|
-
}
|
|
163
|
-
resolveOptionalAsync(token, options) {
|
|
164
|
-
return this.#resolver.resolveOptionalAsync(token, options, [...this.#cascadeStack]);
|
|
165
|
-
}
|
|
166
|
-
resolveAll(token, options) {
|
|
167
|
-
return this.#resolver.resolveAll(token, options, this.#cascadeStack);
|
|
168
|
-
}
|
|
169
|
-
resolveAllAsync(token, options) {
|
|
170
|
-
return this.#resolver.resolveAllAsync(token, options, [...this.#cascadeStack]);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
class DefaultConstraintContext {
|
|
172
|
+
export class DefaultConstraintContext {
|
|
174
173
|
resolutionStack;
|
|
175
174
|
parent;
|
|
176
175
|
currentResolveOptions;
|
|
@@ -195,12 +194,4 @@ class DefaultConstraintContext {
|
|
|
195
194
|
}
|
|
196
195
|
return this.#ancestors;
|
|
197
196
|
}
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Creates the resolution-stack frame for one in-flight resolve.
|
|
201
|
-
*
|
|
202
|
-
* @since 0.3.16-canary.0
|
|
203
|
-
*/
|
|
204
|
-
export function buildResolutionFrame(tokenName, scope, bindingId, kind, slot) {
|
|
205
|
-
return { tokenName, scope, bindingId, kind, slot };
|
|
206
197
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Binding } from "#core/binding";
|
|
2
|
+
import type { ResolutionFrame } from "#core/types";
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Creates the resolution-stack frame for one in-flight resolve of a binding.
|
|
4
5
|
*
|
|
5
|
-
* @remarks
|
|
6
|
+
* @remarks A literal, because the engine's hot loads read frames: a class instance costs every level
|
|
7
|
+
* a few nanoseconds, and a weak map beside the frames costs every cold container an ephemeron insert
|
|
8
|
+
* per binding. The binding rides under a symbol no caller enumerates.
|
|
6
9
|
*
|
|
7
|
-
* @since 0.
|
|
10
|
+
* @since 0.3.16-canary.0
|
|
8
11
|
*/
|
|
9
|
-
export declare
|
|
12
|
+
export declare function buildResolutionFrame(binding: Binding, tokenName: string): ResolutionFrame;
|
|
13
|
+
/**
|
|
14
|
+
* The bindings behind the resolver-built frames of a path, in path order.
|
|
15
|
+
*
|
|
16
|
+
* @since 0.11.0
|
|
17
|
+
*/
|
|
18
|
+
export declare function bindingsOf(frames: ReadonlyArray<ResolutionFrame>): Array<Binding>;
|
|
10
19
|
/**
|
|
11
20
|
* The token names along a stack plus the frame that closed the cycle — built only to throw.
|
|
12
21
|
*
|
|
@@ -14,18 +23,44 @@ export declare const RESOLUTION_SET_THRESHOLD = 32;
|
|
|
14
23
|
*/
|
|
15
24
|
export declare function cycleNamesOf(resolutionStack: ReadonlyArray<ResolutionFrame>, closingName: string): Array<string>;
|
|
16
25
|
/**
|
|
17
|
-
* Marks a level
|
|
26
|
+
* Marks a level in flight on a synchronous path and pushes its frame, throwing if its binding already is.
|
|
18
27
|
*
|
|
19
|
-
* @remarks
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
28
|
+
* @remarks Synchronous code does not interleave, so the flag on the binding is exact membership in the
|
|
29
|
+
* path one call stack is resolving — at any depth, with no side table and nothing to size. The check
|
|
30
|
+
* keys on the binding, never on a token's display name, and the names an error prints are read off
|
|
31
|
+
* the frames at the throw site. Unmark with {@link leaveSyncPath}. Sync only: the async branch lane
|
|
32
|
+
* never unwinds, so it extends a branch instead; see {@link extendResolutionBranch}.
|
|
23
33
|
*
|
|
24
|
-
* @
|
|
34
|
+
* @since 0.11.0
|
|
35
|
+
*/
|
|
36
|
+
export declare function enterSyncPath(resolutionStack: Array<ResolutionFrame>, binding: Binding, frame: ResolutionFrame): void;
|
|
37
|
+
/**
|
|
38
|
+
* Pops the level {@link enterSyncPath} pushed and clears its binding's flag.
|
|
39
|
+
*
|
|
40
|
+
* @since 0.11.0
|
|
41
|
+
*/
|
|
42
|
+
export declare function leaveSyncPath(resolutionStack: Array<ResolutionFrame>, binding: Binding): void;
|
|
43
|
+
/**
|
|
44
|
+
* Marks every binding of a seeded path in flight for the synchronous call about to run over it.
|
|
45
|
+
*
|
|
46
|
+
* @remarks A seed is a path no synchronous frame pushed — a plan's static ancestors handed to an
|
|
47
|
+
* escape, or an async level's branch handed to a factory's synchronous call — so its bindings carry no
|
|
48
|
+
* flag, and marking them is what keeps the flag the one check. A binding already in flight was
|
|
49
|
+
* flagged by an enclosing synchronous frame that is still running, which is the same fact stated
|
|
50
|
+
* once already, so it is left as it is and left alone on the way out; a cycle is reported where the
|
|
51
|
+
* path re-enters that binding, which is where the frames to name it are.
|
|
52
|
+
*
|
|
53
|
+
* @returns the bindings this call left flagged as it found them, when there were any, for {@link leaveSeededPath}
|
|
54
|
+
*
|
|
55
|
+
* @since 0.11.0
|
|
56
|
+
*/
|
|
57
|
+
export declare function enterSeededPath(bindings: ReadonlyArray<Binding>): Set<Binding> | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Clears the flags {@link enterSeededPath} set, leaving the ones it found already set.
|
|
25
60
|
*
|
|
26
|
-
* @since 0.
|
|
61
|
+
* @since 0.11.0
|
|
27
62
|
*/
|
|
28
|
-
export declare function
|
|
63
|
+
export declare function leaveSeededPath(bindings: ReadonlyArray<Binding>, alreadyInFlight: Set<Binding> | undefined): void;
|
|
29
64
|
declare const BRANCH_BRAND: unique symbol;
|
|
30
65
|
/**
|
|
31
66
|
* A resolution stack one async branch owns, so appending to it cannot disturb another branch.
|
|
@@ -1,14 +1,42 @@
|
|
|
1
1
|
/** Cycle-detection bookkeeping carried on the resolution stack itself. */
|
|
2
2
|
import { CircularDependencyError } from "#errors/errors";
|
|
3
|
-
|
|
3
|
+
/** The key under which a resolver-built frame carries its binding, for marking a seeded path. */
|
|
4
|
+
const FRAME_BINDING = Symbol("di:frame-binding");
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Creates the resolution-stack frame for one in-flight resolve of a binding.
|
|
6
7
|
*
|
|
7
|
-
* @remarks
|
|
8
|
+
* @remarks A literal, because the engine's hot loads read frames: a class instance costs every level
|
|
9
|
+
* a few nanoseconds, and a weak map beside the frames costs every cold container an ephemeron insert
|
|
10
|
+
* per binding. The binding rides under a symbol no caller enumerates.
|
|
8
11
|
*
|
|
9
|
-
* @since 0.
|
|
12
|
+
* @since 0.3.16-canary.0
|
|
10
13
|
*/
|
|
11
|
-
export
|
|
14
|
+
export function buildResolutionFrame(binding, tokenName) {
|
|
15
|
+
const frame = {
|
|
16
|
+
tokenName,
|
|
17
|
+
scope: binding.scope,
|
|
18
|
+
bindingId: binding.identifier,
|
|
19
|
+
kind: binding.kind,
|
|
20
|
+
slot: binding.slot,
|
|
21
|
+
[FRAME_BINDING]: binding,
|
|
22
|
+
};
|
|
23
|
+
return frame;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The bindings behind the resolver-built frames of a path, in path order.
|
|
27
|
+
*
|
|
28
|
+
* @since 0.11.0
|
|
29
|
+
*/
|
|
30
|
+
export function bindingsOf(frames) {
|
|
31
|
+
const bindings = [];
|
|
32
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
33
|
+
const binding = frames[index][FRAME_BINDING];
|
|
34
|
+
if (binding !== undefined) {
|
|
35
|
+
bindings.push(binding);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return bindings;
|
|
39
|
+
}
|
|
12
40
|
/**
|
|
13
41
|
* The token names along a stack plus the frame that closed the cycle — built only to throw.
|
|
14
42
|
*
|
|
@@ -23,47 +51,70 @@ export function cycleNamesOf(resolutionStack, closingName) {
|
|
|
23
51
|
return names;
|
|
24
52
|
}
|
|
25
53
|
/**
|
|
26
|
-
* Marks a level
|
|
54
|
+
* Marks a level in flight on a synchronous path and pushes its frame, throwing if its binding already is.
|
|
27
55
|
*
|
|
28
|
-
* @remarks
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
56
|
+
* @remarks Synchronous code does not interleave, so the flag on the binding is exact membership in the
|
|
57
|
+
* path one call stack is resolving — at any depth, with no side table and nothing to size. The check
|
|
58
|
+
* keys on the binding, never on a token's display name, and the names an error prints are read off
|
|
59
|
+
* the frames at the throw site. Unmark with {@link leaveSyncPath}. Sync only: the async branch lane
|
|
60
|
+
* never unwinds, so it extends a branch instead; see {@link extendResolutionBranch}.
|
|
32
61
|
*
|
|
33
|
-
* @
|
|
34
|
-
*
|
|
35
|
-
* @since 0.5.0-canary.7
|
|
62
|
+
* @since 0.11.0
|
|
36
63
|
*/
|
|
37
|
-
export function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// A live set mirrors the stack exactly, so a size that disagrees means it is holding ids of
|
|
41
|
-
// frames that unwound: the ones already on the stack when it attached were handed no set to
|
|
42
|
-
// delete from. Dropped rather than repaired, because the next deep frame rebuilds it.
|
|
43
|
-
if (resolutionSet !== undefined && resolutionSet.size !== resolutionStack.length) {
|
|
44
|
-
resolutionSet = undefined;
|
|
45
|
-
stackWithSet[RESOLUTION_SET_KEY] = undefined;
|
|
64
|
+
export function enterSyncPath(resolutionStack, binding, frame) {
|
|
65
|
+
if (binding.inFlight) {
|
|
66
|
+
throw new CircularDependencyError(cycleNamesOf(resolutionStack, frame.tokenName));
|
|
46
67
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
binding.inFlight = true;
|
|
69
|
+
resolutionStack.push(frame);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pops the level {@link enterSyncPath} pushed and clears its binding's flag.
|
|
73
|
+
*
|
|
74
|
+
* @since 0.11.0
|
|
75
|
+
*/
|
|
76
|
+
export function leaveSyncPath(resolutionStack, binding) {
|
|
77
|
+
resolutionStack.pop();
|
|
78
|
+
binding.inFlight = false;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Marks every binding of a seeded path in flight for the synchronous call about to run over it.
|
|
82
|
+
*
|
|
83
|
+
* @remarks A seed is a path no synchronous frame pushed — a plan's static ancestors handed to an
|
|
84
|
+
* escape, or an async level's branch handed to a factory's synchronous call — so its bindings carry no
|
|
85
|
+
* flag, and marking them is what keeps the flag the one check. A binding already in flight was
|
|
86
|
+
* flagged by an enclosing synchronous frame that is still running, which is the same fact stated
|
|
87
|
+
* once already, so it is left as it is and left alone on the way out; a cycle is reported where the
|
|
88
|
+
* path re-enters that binding, which is where the frames to name it are.
|
|
89
|
+
*
|
|
90
|
+
* @returns the bindings this call left flagged as it found them, when there were any, for {@link leaveSeededPath}
|
|
91
|
+
*
|
|
92
|
+
* @since 0.11.0
|
|
93
|
+
*/
|
|
94
|
+
export function enterSeededPath(bindings) {
|
|
95
|
+
let alreadyInFlight;
|
|
96
|
+
for (let index = 0; index < bindings.length; index += 1) {
|
|
97
|
+
const binding = bindings[index];
|
|
98
|
+
if (binding.inFlight) {
|
|
99
|
+
(alreadyInFlight ??= new Set()).add(binding);
|
|
100
|
+
continue;
|
|
51
101
|
}
|
|
52
|
-
|
|
102
|
+
binding.inFlight = true;
|
|
53
103
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
104
|
+
return alreadyInFlight;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Clears the flags {@link enterSeededPath} set, leaving the ones it found already set.
|
|
108
|
+
*
|
|
109
|
+
* @since 0.11.0
|
|
110
|
+
*/
|
|
111
|
+
export function leaveSeededPath(bindings, alreadyInFlight) {
|
|
112
|
+
for (let index = 0; index < bindings.length; index += 1) {
|
|
113
|
+
const binding = bindings[index];
|
|
114
|
+
if (alreadyInFlight === undefined || !alreadyInFlight.has(binding)) {
|
|
115
|
+
binding.inFlight = false;
|
|
59
116
|
}
|
|
60
117
|
}
|
|
61
|
-
else if (resolutionSet.has(frame.bindingId)) {
|
|
62
|
-
throw new CircularDependencyError(cycleNamesOf(resolutionStack, frame.tokenName));
|
|
63
|
-
}
|
|
64
|
-
resolutionStack.push(frame);
|
|
65
|
-
resolutionSet?.add(frame.bindingId);
|
|
66
|
-
return resolutionSet;
|
|
67
118
|
}
|
|
68
119
|
/**
|
|
69
120
|
* A stack no async branch owns yet, so its first extension must copy rather than append.
|
|
@@ -2,10 +2,9 @@ import { NO_INSTANCE } from "#core/binding";
|
|
|
2
2
|
import { tokenName } from "#core/token";
|
|
3
3
|
import { AsyncResolutionError } from "#errors/errors";
|
|
4
4
|
import { injectionSlotToResolveOptions } from "#injection/resolve-options";
|
|
5
|
+
import { settleInOrder } from "#resolution/async-fan-out";
|
|
6
|
+
import { enterSeededPath, leaveSeededPath } from "#resolution/path/resolution-path";
|
|
5
7
|
import { generateAsyncPlan, generatePlan, isPlanCodegenAvailable, PLAN_CODEGEN_THRESHOLD, } from "#resolution/plan/plan-codegen";
|
|
6
|
-
// Past this depth a dependency escapes to the runtime path rather than inlining further —
|
|
7
|
-
// compiled closures nest one JS frame per level, and pathological graphs are the runtime's job.
|
|
8
|
-
const PLAN_DEPTH_LIMIT = 32;
|
|
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);
|
|
@@ -6,12 +6,14 @@ 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, not a machine width, a contract value or bind-time data: generating
|
|
10
|
+
* costs some fifty closure runs and the new function runs cold for thirty more, so it repays only
|
|
11
|
+
* over runs in the thousands. Below it a plan stays a closure, which is all a cold container or a
|
|
12
|
+
* per-request child ever runs.
|
|
11
13
|
*
|
|
12
14
|
* @since 0.10.0
|
|
13
15
|
*/
|
|
14
|
-
export declare const PLAN_CODEGEN_THRESHOLD =
|
|
16
|
+
export declare const PLAN_CODEGEN_THRESHOLD = 1024;
|
|
15
17
|
/**
|
|
16
18
|
* The shape of a compiled sync plan: what its closure does, stated as data the generator can read.
|
|
17
19
|
*
|
|
@@ -49,7 +51,8 @@ export type PlanNode = {
|
|
|
49
51
|
*
|
|
50
52
|
* @remarks A node that `awaits` has a dependency that may yield a promise, so it runs as the
|
|
51
53
|
* interpreted async path does: every dependency starts in order, a sync throw becomes that slot's
|
|
52
|
-
* rejection,
|
|
54
|
+
* rejection, the constructor or factory runs on the settled values, and a failure is reported in
|
|
55
|
+
* declaration order.
|
|
53
56
|
*
|
|
54
57
|
* @since 0.10.0
|
|
55
58
|
*/
|