footprintjs 8.2.0 → 9.0.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/dist/esm/lib/engine/handlers/ContinuationResolver.js +44 -45
- package/dist/esm/lib/engine/handlers/DeciderHandler.js +37 -14
- package/dist/esm/lib/engine/handlers/NodeResolver.js +14 -4
- package/dist/esm/lib/engine/traversal/FlowchartTraverser.js +551 -300
- package/dist/esm/lib/engine/types.js +1 -1
- package/dist/esm/lib/memory/StageContext.js +28 -7
- package/dist/esm/lib/runner/FlowChartExecutor.js +5 -4
- package/dist/lib/engine/handlers/ContinuationResolver.js +44 -45
- package/dist/lib/engine/handlers/DeciderHandler.js +37 -14
- package/dist/lib/engine/handlers/NodeResolver.js +14 -4
- package/dist/lib/engine/traversal/FlowchartTraverser.js +551 -300
- package/dist/lib/engine/types.js +1 -1
- package/dist/lib/memory/StageContext.js +28 -7
- package/dist/lib/runner/FlowChartExecutor.js +5 -4
- package/dist/types/lib/engine/handlers/ContinuationResolver.d.ts +36 -9
- package/dist/types/lib/engine/handlers/DeciderHandler.d.ts +32 -0
- package/dist/types/lib/engine/handlers/NodeResolver.d.ts +9 -1
- package/dist/types/lib/engine/traversal/FlowchartTraverser.d.ts +156 -24
- package/dist/types/lib/engine/types.d.ts +15 -3
- package/dist/types/lib/memory/StageContext.d.ts +3 -0
- package/dist/types/lib/runner/FlowChartExecutor.d.ts +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
* - String ID → reference to existing node (resolve via NodeResolver)
|
|
9
9
|
* - StageNode with fn → truly dynamic node (execute directly)
|
|
10
10
|
* - StageNode without fn → reference by ID (resolve via NodeResolver)
|
|
11
|
+
*
|
|
12
|
+
* Two entry points:
|
|
13
|
+
* - `resolveTarget` — resolves the continuation to `{ node, context }` and
|
|
14
|
+
* fires every side effect (iteration counting, debug logs, `onLoop`
|
|
15
|
+
* narrative) WITHOUT executing. The traverser's trampoline driver uses
|
|
16
|
+
* this to follow loop edges iteratively — flat stack, so the iteration
|
|
17
|
+
* limit (not call-stack depth) is what bounds a loop.
|
|
18
|
+
* - `resolve` — resolveTarget + immediate execution via the provided
|
|
19
|
+
* `executeNode` callback. Kept for direct/advanced callers.
|
|
11
20
|
*/
|
|
12
21
|
import type { StageContext } from '../../memory/StageContext.js';
|
|
13
22
|
import type { StageNode } from '../graph/StageNode.js';
|
|
@@ -16,6 +25,16 @@ import type { HandlerDeps } from '../types.js';
|
|
|
16
25
|
import type { NodeResolver } from './NodeResolver.js';
|
|
17
26
|
import type { ExecuteNodeFn } from './types.js';
|
|
18
27
|
export declare const DEFAULT_MAX_ITERATIONS = 1000;
|
|
28
|
+
/**
|
|
29
|
+
* A resolved continuation target — the node to execute next plus the
|
|
30
|
+
* StageContext to execute it in. All side effects (iteration counting,
|
|
31
|
+
* debug logs, `onLoop` narrative) have already fired by the time this
|
|
32
|
+
* is returned.
|
|
33
|
+
*/
|
|
34
|
+
export interface ResolvedContinuation<TOut = any, TScope = any> {
|
|
35
|
+
node: StageNode<TOut, TScope>;
|
|
36
|
+
context: StageContext;
|
|
37
|
+
}
|
|
19
38
|
export declare class ContinuationResolver<TOut = any, TScope = any> {
|
|
20
39
|
private readonly deps;
|
|
21
40
|
private readonly nodeResolver;
|
|
@@ -28,19 +47,27 @@ export declare class ContinuationResolver<TOut = any, TScope = any> {
|
|
|
28
47
|
private readonly maxIterations;
|
|
29
48
|
constructor(deps: HandlerDeps<TOut, TScope>, nodeResolver: NodeResolver<TOut, TScope>, onIterationUpdate?: (nodeId: string, count: number) => void, maxIterations?: number);
|
|
30
49
|
/**
|
|
31
|
-
* Resolve a dynamic continuation.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
50
|
+
* Resolve a dynamic continuation and execute it immediately.
|
|
51
|
+
* Equivalent to `executeNode(...resolveTarget(...))` — the traverser's
|
|
52
|
+
* driver loop calls `resolveTarget` directly instead so the continuation
|
|
53
|
+
* becomes a flat trampoline hop rather than a retained recursive frame.
|
|
34
54
|
*/
|
|
35
55
|
resolve(dynamicNext: string | StageNode<TOut, TScope>, node: StageNode<TOut, TScope>, context: StageContext, breakFlag: {
|
|
36
56
|
shouldBreak: boolean;
|
|
37
57
|
}, branchPath: string | undefined, executeNode: ExecuteNodeFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<any>;
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a dynamic continuation to its target node + next StageContext
|
|
60
|
+
* WITHOUT executing it. Fires the same side effects `resolve` always did
|
|
61
|
+
* (iteration counting + limit, `dynamicNext*` logs, loop debug message,
|
|
62
|
+
* `onLoop` narrative), in the same order.
|
|
63
|
+
*
|
|
64
|
+
* Three dynamicNext patterns:
|
|
65
|
+
* - StageNode with fn → truly dynamic node, returned as-is (no iteration
|
|
66
|
+
* tracking — it is a fresh node, not a back-edge).
|
|
67
|
+
* - String ID → reference to an existing node, resolved via NodeResolver.
|
|
68
|
+
* - StageNode without fn → reference by ID, resolved via NodeResolver.
|
|
69
|
+
*/
|
|
70
|
+
resolveTarget(dynamicNext: string | StageNode<TOut, TScope>, currentNode: StageNode<TOut, TScope>, context: StageContext, branchPath: string | undefined, traversalContext?: TraversalContext): ResolvedContinuation<TOut, TScope>;
|
|
44
71
|
/**
|
|
45
72
|
* Get the next iteration number for a node and increment.
|
|
46
73
|
* Returns 0 for first visit, 1 for second, etc.
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles scope-based deciders (stage IS the decider, returns branch ID).
|
|
5
5
|
* Logs flow control decisions and narrative sentences.
|
|
6
|
+
*
|
|
7
|
+
* Two entry points:
|
|
8
|
+
* - `prepareDispatch` — runs the decider stage, commits, resolves the chosen
|
|
9
|
+
* branch, fires narrative, and returns the chosen node + branch context
|
|
10
|
+
* WITHOUT executing it. The traverser's trampoline driver uses this so a
|
|
11
|
+
* decider with no continuation of its own can hand the branch to the
|
|
12
|
+
* driver as a flat hop (loop-heavy decider charts stay flat-stacked).
|
|
13
|
+
* - `handleScopeBased` — prepareDispatch + immediate branch execution via
|
|
14
|
+
* the provided `executeNode` callback. Kept for direct/advanced callers
|
|
15
|
+
* and for deciders whose own `.next` must run after the branch completes.
|
|
6
16
|
*/
|
|
7
17
|
import type { StageContext } from '../../memory/StageContext.js';
|
|
8
18
|
import type { StageNode } from '../graph/StageNode.js';
|
|
@@ -10,6 +20,19 @@ import type { TraversalContext } from '../narrative/types.js';
|
|
|
10
20
|
import type { HandlerDeps, StageFunction } from '../types.js';
|
|
11
21
|
import type { ExecuteNodeFn, RunStageFn } from './types.js';
|
|
12
22
|
export type { ExecuteNodeFn, RunStageFn };
|
|
23
|
+
/**
|
|
24
|
+
* Result of `prepareDispatch` — either the decider stage broke (no branch
|
|
25
|
+
* runs; `branchId` is the decider's return value), or a branch was chosen
|
|
26
|
+
* and is ready to execute in `branchContext`.
|
|
27
|
+
*/
|
|
28
|
+
export type DeciderDispatch<TOut = any, TScope = any> = {
|
|
29
|
+
kind: 'break';
|
|
30
|
+
branchId: string;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'dispatch';
|
|
33
|
+
chosen: StageNode<TOut, TScope>;
|
|
34
|
+
branchContext: StageContext;
|
|
35
|
+
};
|
|
13
36
|
export declare class DeciderHandler<TOut = any, TScope = any> {
|
|
14
37
|
private readonly deps;
|
|
15
38
|
constructor(deps: HandlerDeps<TOut, TScope>);
|
|
@@ -21,4 +44,13 @@ export declare class DeciderHandler<TOut = any, TScope = any> {
|
|
|
21
44
|
handleScopeBased(node: StageNode<TOut, TScope>, stageFunc: StageFunction<TOut, TScope>, context: StageContext, breakFlag: {
|
|
22
45
|
shouldBreak: boolean;
|
|
23
46
|
}, branchPath: string | undefined, runStage: RunStageFn<TOut, TScope>, executeNode: ExecuteNodeFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<any>;
|
|
47
|
+
/**
|
|
48
|
+
* Run the decider stage and resolve the chosen branch WITHOUT executing it.
|
|
49
|
+
* Everything up to (and including) the `onDecision`/`onStageExecuted`
|
|
50
|
+
* narrative and the branch context creation happens here — only the
|
|
51
|
+
* branch execution itself is left to the caller.
|
|
52
|
+
*/
|
|
53
|
+
prepareDispatch(node: StageNode<TOut, TScope>, stageFunc: StageFunction<TOut, TScope>, context: StageContext, breakFlag: {
|
|
54
|
+
shouldBreak: boolean;
|
|
55
|
+
}, branchPath: string | undefined, runStage: RunStageFn<TOut, TScope>, traversalContext?: TraversalContext): Promise<DeciderDispatch<TOut, TScope>>;
|
|
24
56
|
}
|
|
@@ -11,7 +11,15 @@ import type { HandlerDeps } from '../types.js';
|
|
|
11
11
|
export declare class NodeResolver<TOut = any, TScope = any> {
|
|
12
12
|
private deps;
|
|
13
13
|
private readonly nodeIdMap;
|
|
14
|
-
|
|
14
|
+
private readonly getEffectiveChildren?;
|
|
15
|
+
constructor(deps: HandlerDeps<TOut, TScope>, nodeIdMap?: Map<string, StageNode<TOut, TScope>>,
|
|
16
|
+
/**
|
|
17
|
+
* Overlay-aware children accessor injected by the traverser. The DFS
|
|
18
|
+
* fallback resolves loop targets against the LIVE runtime shape — a node
|
|
19
|
+
* patched by a dynamic StageNode return carries its children in the
|
|
20
|
+
* traverser-local overlay, not on the shared built-chart node.
|
|
21
|
+
*/
|
|
22
|
+
getEffectiveChildren?: (node: StageNode<TOut, TScope>) => StageNode<TOut, TScope>[] | undefined);
|
|
15
23
|
/**
|
|
16
24
|
* O(1) node lookup via pre-built ID map.
|
|
17
25
|
* Falls back to DFS from startNode (for dynamic nodes added at runtime
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlowchartTraverser — Pre-order DFS traversal of StageNode graph.
|
|
3
3
|
*
|
|
4
|
-
* Unified traversal algorithm for all node shapes
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Unified traversal algorithm for all node shapes. `executeNode` is a
|
|
5
|
+
* TRAMPOLINE driver: it runs `executeNodeStep` (one node, all 7 phases) in
|
|
6
|
+
* a flat loop, following tail continuations (linear `next`, loop edges,
|
|
7
|
+
* dynamic next, flat decider dispatch) iteratively — so chain length and
|
|
8
|
+
* loop iterations never grow the call stack. Only true tree nesting (fork
|
|
9
|
+
* children, with-continuation decider/selector branches, subflow mounts)
|
|
10
|
+
* recurses.
|
|
8
11
|
*
|
|
9
|
-
* For each node,
|
|
12
|
+
* For each node, executeNodeStep follows 7 phases:
|
|
10
13
|
* 0. CLASSIFY — subflow detection, early delegation
|
|
11
14
|
* 1. VALIDATE — node invariants, role markers
|
|
12
15
|
* 2. EXECUTE — run stage fn, commit, break check
|
|
@@ -21,7 +24,7 @@
|
|
|
21
24
|
import type { ScopeProtectionMode } from '../../scope/protection/types.js';
|
|
22
25
|
import { FlowRecorderDispatcher } from '../narrative/FlowRecorderDispatcher.js';
|
|
23
26
|
import type { FlowRecorder, IControlFlowNarrative } from '../narrative/types.js';
|
|
24
|
-
import type { IExecutionRuntime, ILogger, ScopeFactory, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowResult, TraversalResult } from '../types.js';
|
|
27
|
+
import type { IExecutionRuntime, ILogger, ScopeFactory, Selector, SerializedPipelineStructure, StageFunction, StageNode, StreamHandlers, SubflowMountOptions, SubflowResult, TraversalResult } from '../types.js';
|
|
25
28
|
export interface TraverserOptions<TOut = any, TScope = any> {
|
|
26
29
|
root: StageNode<TOut, TScope>;
|
|
27
30
|
stageMap: Map<string, StageFunction<TOut, TScope>>;
|
|
@@ -49,10 +52,17 @@ export interface TraverserOptions<TOut = any, TScope = any> {
|
|
|
49
52
|
*/
|
|
50
53
|
narrativeGenerator?: IControlFlowNarrative;
|
|
51
54
|
/**
|
|
52
|
-
* Maximum
|
|
53
|
-
*
|
|
55
|
+
* Maximum nested executeNode depth (tree nesting — branch/fork dispatch and
|
|
56
|
+
* dynamic recursion, NOT linear chains or loop iterations, which run flat).
|
|
57
|
+
* Defaults to FlowchartTraverser.MAX_EXECUTE_DEPTH (500).
|
|
54
58
|
*/
|
|
55
59
|
maxDepth?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Maximum loop iterations per node (the ContinuationResolver guard).
|
|
62
|
+
* Defaults to DEFAULT_MAX_ITERATIONS (1000). Propagated to subflow
|
|
63
|
+
* traversers. Must be >= 1.
|
|
64
|
+
*/
|
|
65
|
+
maxIterations?: number;
|
|
56
66
|
/**
|
|
57
67
|
* When this traverser runs inside a subflow, set this to the subflow's ID.
|
|
58
68
|
* Propagated to TraversalContext so narrative entries carry the correct subflowId.
|
|
@@ -78,6 +88,37 @@ export interface TraverserOptions<TOut = any, TScope = any> {
|
|
|
78
88
|
*/
|
|
79
89
|
runId: string;
|
|
80
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Traverser-local overlay entry for a node whose stage function returned a
|
|
93
|
+
* StageNode (dynamic continuation). Holds the dynamic values that earlier
|
|
94
|
+
* versions wrote DIRECTLY onto the shared built-chart node — which leaked the
|
|
95
|
+
* dynamic shape into every later run of the same built chart and raced
|
|
96
|
+
* concurrent executors. The overlay keeps the built graph immutable: patches
|
|
97
|
+
* live in a per-traverser Map keyed by `node.id` and die with the run.
|
|
98
|
+
*
|
|
99
|
+
* `next` is intentionally ABSENT: a dynamic `next` only ever applies to the
|
|
100
|
+
* visit that produced it (the old code wrote `node.next` and restored it
|
|
101
|
+
* before anything could observe the write), so it stays a local variable in
|
|
102
|
+
* `executeNode` and is routed through `ContinuationResolver` directly.
|
|
103
|
+
*/
|
|
104
|
+
export interface DynamicNodePatch<TOut = any, TScope = any> {
|
|
105
|
+
/**
|
|
106
|
+
* Subflow mount metadata from a dynamic-subflow return. Grouped so the
|
|
107
|
+
* merged view reproduces the old field-wise overwrite exactly — including
|
|
108
|
+
* `subflowName`/`subflowMountOptions` becoming undefined when the dynamic
|
|
109
|
+
* return omitted them.
|
|
110
|
+
*/
|
|
111
|
+
subflowMeta?: {
|
|
112
|
+
isSubflowRoot: true;
|
|
113
|
+
subflowId: string;
|
|
114
|
+
subflowName: string | undefined;
|
|
115
|
+
subflowMountOptions: SubflowMountOptions | undefined;
|
|
116
|
+
};
|
|
117
|
+
/** Dynamic fork children (replaces the built node's children for this run). */
|
|
118
|
+
children?: StageNode<TOut, TScope>[];
|
|
119
|
+
/** Dynamic output-based selector accompanying dynamic children. */
|
|
120
|
+
nextNodeSelector?: Selector;
|
|
121
|
+
}
|
|
81
122
|
export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
82
123
|
private readonly root;
|
|
83
124
|
private stageMap;
|
|
@@ -112,11 +153,41 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
|
112
153
|
*/
|
|
113
154
|
private readonly resolvedLazySubflows;
|
|
114
155
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
156
|
+
* Per-traverser overlay of dynamic StageNode returns, keyed by `node.id`.
|
|
157
|
+
* Phase 4 writes patches HERE instead of mutating the shared built-chart
|
|
158
|
+
* node objects (same isolation convention as `resolvedLazySubflows`).
|
|
159
|
+
* All engine reads of the patched fields go through the `eff*` accessors
|
|
160
|
+
* below. The map dies with the traverser — one run, one overlay — so a
|
|
161
|
+
* fresh executor over the same built chart always sees the original graph.
|
|
162
|
+
*
|
|
163
|
+
* Keyed by the node OBJECT (WeakMap), not `node.id`: a dynamic child that
|
|
164
|
+
* reuses a built node's id must NOT make the built node inherit the patch
|
|
165
|
+
* (id-keyed lookup caused phantom double-execution). `patchCount` is the
|
|
166
|
+
* fast-path check — WeakMap has no `size`.
|
|
167
|
+
*/
|
|
168
|
+
private readonly dynamicPatches;
|
|
169
|
+
private patchCount;
|
|
170
|
+
/**
|
|
171
|
+
* TREE-nesting depth counter for executeNode (the trampoline driver).
|
|
172
|
+
* Each driver invocation increments this; decrements on exit (try/finally).
|
|
173
|
+
*
|
|
174
|
+
* Linear `next` chains, loop edges, and dynamic continuations are followed
|
|
175
|
+
* ITERATIVELY inside one driver invocation, so they never grow this
|
|
176
|
+
* counter. Only true tree recursion does: fork children, decider/selector
|
|
177
|
+
* branch dispatch (when the decider has its own continuation), and
|
|
178
|
+
* unbounded dynamic recursion. Prevents call-stack overflow on runaway
|
|
179
|
+
* recursive composition.
|
|
118
180
|
*/
|
|
119
181
|
private _executeDepth;
|
|
182
|
+
/**
|
|
183
|
+
* Memoized parent-chain depth per StageContext. The context tree deepens
|
|
184
|
+
* by one per executed stage along a chain, so the naive parent-walk in
|
|
185
|
+
* `computeContextDepth` is O(chain length) per stage — O(n²) per run once
|
|
186
|
+
* the trampoline allows chains of tens of thousands of stages. Contexts
|
|
187
|
+
* are visited parent-before-child, so the memo makes each lookup O(1)
|
|
188
|
+
* amortized. WeakMap — dies with the traverser.
|
|
189
|
+
*/
|
|
190
|
+
private readonly contextDepthCache;
|
|
120
191
|
/**
|
|
121
192
|
* Shared mutable execution counter — monotonic, incremented per stage execution.
|
|
122
193
|
* Shared with child traversers (subflows) so indices are globally unique within a run.
|
|
@@ -127,18 +198,26 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
|
127
198
|
*/
|
|
128
199
|
private readonly _maxDepth;
|
|
129
200
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
|
|
201
|
+
* Per-instance loop-iteration limit forwarded to the ContinuationResolver
|
|
202
|
+
* and propagated to subflow traversers. Undefined → resolver default (1000).
|
|
203
|
+
*/
|
|
204
|
+
private readonly _maxIterations?;
|
|
205
|
+
/**
|
|
206
|
+
* Default maximum nested executeNode depth before an error is thrown.
|
|
133
207
|
*
|
|
134
|
-
* **
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
208
|
+
* **What counts as depth (trampoline model):** `executeNode` is an iterative
|
|
209
|
+
* driver — linear `next` hops, loop edges (`loopTo`/dynamic next), and
|
|
210
|
+
* dynamic-subflow re-entry are followed in a flat loop and consume NO depth.
|
|
211
|
+
* Depth grows only with true tree nesting: one tick per fork child, one per
|
|
212
|
+
* decider/selector branch dispatch that must return to its invoker (decider
|
|
213
|
+
* with its own `next`), one per subflow mount frame in the parent (the
|
|
214
|
+
* subflow body itself runs on a FRESH traverser with its own budget).
|
|
138
215
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
216
|
+
* 500 therefore covers any realistic chart — it bounds recursive
|
|
217
|
+
* COMPOSITION, not chain length or loop count. Loops are bounded by
|
|
218
|
+
* `ContinuationResolver`'s independent iteration limit (default 1000,
|
|
219
|
+
* configurable via `RunOptions.maxIterations`), which is now the binding
|
|
220
|
+
* constraint for loop-heavy pipelines.
|
|
142
221
|
*
|
|
143
222
|
* @remarks Not safe for concurrent `.execute()` calls on the same instance — concurrent
|
|
144
223
|
* executions race on `_executeDepth`. Use a separate `FlowchartTraverser` per concurrent
|
|
@@ -197,19 +276,72 @@ export declare class FlowchartTraverser<TOut = any, TScope = any> {
|
|
|
197
276
|
/**
|
|
198
277
|
* Build an O(1) ID→node map from the root graph.
|
|
199
278
|
* Used by NodeResolver to avoid repeated DFS on every loopTo() call.
|
|
200
|
-
*
|
|
279
|
+
* Iterative worklist (no recursion) so arbitrarily long chains index fully;
|
|
280
|
+
* the `map.has` guard handles cyclic refs. First-visited node wins per ID —
|
|
281
|
+
* worklist order matches the old recursive pre-order (children, then next).
|
|
201
282
|
* Dynamic subflows and lazy-resolved nodes are added to stageMap at runtime but not to this map —
|
|
202
283
|
* those use the DFS fallback in NodeResolver.
|
|
203
284
|
*/
|
|
204
285
|
private buildNodeIdMap;
|
|
205
286
|
private getStageFn;
|
|
287
|
+
private getPatch;
|
|
288
|
+
private getOrCreatePatch;
|
|
289
|
+
/** Effective children: dynamic patch first, then the built node's children. */
|
|
290
|
+
private effChildren;
|
|
291
|
+
/** Effective output-based selector: dynamic patch first, then the built node's. */
|
|
292
|
+
private effSelector;
|
|
293
|
+
/** Effective subflow-root marker (true when a dynamic subflow was patched on). */
|
|
294
|
+
private effIsSubflowRoot;
|
|
295
|
+
/** Effective subflow id (patched verbatim by a dynamic subflow return). */
|
|
296
|
+
private effSubflowId;
|
|
297
|
+
/**
|
|
298
|
+
* Materialize the effective view of a node — field-identical to what the
|
|
299
|
+
* pre-overlay code produced by mutating the shared node. Used where a node
|
|
300
|
+
* is handed to a helper executor (NodeResolver / SubflowExecutor /
|
|
301
|
+
* ChildrenExecutor) so helpers never read stale built fields. Returns the
|
|
302
|
+
* node itself (no allocation) when it carries no patch.
|
|
303
|
+
*/
|
|
304
|
+
private effNode;
|
|
206
305
|
private executeStage;
|
|
207
306
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
307
|
+
* Trampoline driver — pre-order DFS traversal entry point.
|
|
308
|
+
*
|
|
309
|
+
* Runs `executeNodeStep` (one node, all 7 phases) in a flat loop: every
|
|
310
|
+
* TAIL continuation (linear `next`, loop edge, dynamic next / dynamic
|
|
311
|
+
* re-entry, no-continuation decider dispatch) comes back as a
|
|
312
|
+
* `ContinuationHop` and is followed ITERATIVELY — neither the call stack
|
|
313
|
+
* nor the retained promise chain grows with chain length or loop count.
|
|
314
|
+
*
|
|
315
|
+
* Recursion remains ONLY for true tree nesting (each gets a nested driver
|
|
316
|
+
* call): fork children (`ChildrenExecutor`), selector branches (parallel
|
|
317
|
+
* fan-out), decider branch dispatch when the decider has its own `next`
|
|
318
|
+
* (the branch must complete BEFORE the decider's continuation runs), and
|
|
319
|
+
* subflow mounts (fresh traverser; the mount frame stays in the parent).
|
|
320
|
+
* `_executeDepth` therefore counts chart COMPOSITION depth only, guarded
|
|
321
|
+
* by `_maxDepth` (default `MAX_EXECUTE_DEPTH` = 500).
|
|
322
|
+
*
|
|
323
|
+
* PauseSignal: a flat decider dispatch records an `InvokerStamp`; if the
|
|
324
|
+
* continued chain later pauses, the driver stamps the signal during
|
|
325
|
+
* unwind — same invoker context the recursive dispatch's catch used to
|
|
326
|
+
* stamp, innermost (most recent dispatch) first.
|
|
210
327
|
*/
|
|
211
328
|
private executeNode;
|
|
329
|
+
/** Build a flat continuation hop for the driver loop. */
|
|
330
|
+
private hop;
|
|
331
|
+
/**
|
|
332
|
+
* Execute ONE node through all 7 phases — the old recursive `executeNode`
|
|
333
|
+
* body; only the tail calls became `ContinuationHop` returns. Returns the
|
|
334
|
+
* node's result, or a hop for the driver loop to follow.
|
|
335
|
+
*/
|
|
336
|
+
private executeNodeStep;
|
|
212
337
|
private captureDynamicChildrenResult;
|
|
338
|
+
/**
|
|
339
|
+
* Parent-chain length of a StageContext — same value the pre-trampoline
|
|
340
|
+
* walk produced, memoized. The context tree deepens by one per executed
|
|
341
|
+
* stage along a chain, so the naive walk is O(chain length) per stage —
|
|
342
|
+
* O(n²) per run once chains reach trampoline scale. Contexts are visited
|
|
343
|
+
* parent-before-child, so the cached parent makes this O(1) amortized.
|
|
344
|
+
*/
|
|
213
345
|
private computeContextDepth;
|
|
214
346
|
private prefixNodeTree;
|
|
215
347
|
private autoRegisterSubflowDef;
|
|
@@ -322,12 +322,24 @@ export interface RunOptions {
|
|
|
322
322
|
*/
|
|
323
323
|
env?: ExecutionEnv;
|
|
324
324
|
/**
|
|
325
|
-
* Override the maximum
|
|
325
|
+
* Override the maximum nested `executeNode` depth for this run.
|
|
326
326
|
* Defaults to `FlowchartTraverser.MAX_EXECUTE_DEPTH` (500).
|
|
327
|
-
*
|
|
328
|
-
*
|
|
327
|
+
*
|
|
328
|
+
* Depth counts TREE nesting only — fork children, decider/selector branch
|
|
329
|
+
* dispatch, recursive composition. Linear chains and loop iterations run
|
|
330
|
+
* on a flat trampoline and never consume depth, so this rarely needs
|
|
331
|
+
* raising. Must be >= 1.
|
|
329
332
|
*/
|
|
330
333
|
maxDepth?: number;
|
|
334
|
+
/**
|
|
335
|
+
* Override the maximum loop iterations per node for this run (the
|
|
336
|
+
* `ContinuationResolver` infinite-loop guard). Defaults to 1000.
|
|
337
|
+
* This is the binding constraint for loop-heavy pipelines — raise it for
|
|
338
|
+
* legitimately long loops (`loopTo` chains run with a flat stack, so high
|
|
339
|
+
* values are safe; memory for state/narrative still grows per iteration).
|
|
340
|
+
* Propagates to subflows. Must be >= 1.
|
|
341
|
+
*/
|
|
342
|
+
maxIterations?: number;
|
|
331
343
|
}
|
|
332
344
|
export type { FlowControlType, FlowMessage };
|
|
333
345
|
export interface RuntimeStructureMetadata {
|
|
@@ -117,4 +117,7 @@ export declare class StageContext {
|
|
|
117
117
|
}): void;
|
|
118
118
|
getStageId(): string;
|
|
119
119
|
getSnapshot(): StageSnapshot;
|
|
120
|
+
/** Snapshot of THIS context's own fields — `next`/`children` are filled
|
|
121
|
+
* in by the iterative walk in `getSnapshot`. */
|
|
122
|
+
private snapshotSelf;
|
|
120
123
|
}
|
|
@@ -212,7 +212,7 @@ export declare class FlowChartExecutor<TOut = any, TScope = any> {
|
|
|
212
212
|
* const result = await executor.resume(restored, { approved: true });
|
|
213
213
|
* ```
|
|
214
214
|
*/
|
|
215
|
-
resume(checkpoint: FlowchartCheckpoint, resumeInput?: unknown, options?: Pick<RunOptions, 'signal' | 'env' | 'maxDepth'>): Promise<ExecutorResult>;
|
|
215
|
+
resume(checkpoint: FlowchartCheckpoint, resumeInput?: unknown, options?: Pick<RunOptions, 'signal' | 'env' | 'maxDepth' | 'maxIterations'>): Promise<ExecutorResult>;
|
|
216
216
|
/**
|
|
217
217
|
* Build a fully DETACHED checkpoint from a caught PauseSignal.
|
|
218
218
|
*
|
package/package.json
CHANGED