@bian-womp/spark-graph 0.3.88 → 0.3.89

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.
Files changed (157) hide show
  1. package/lib/src/builder/GraphBuilder.d.ts +43 -0
  2. package/lib/src/builder/GraphBuilder.d.ts.map +1 -0
  3. package/lib/src/builder/GraphBuilder.js +279 -0
  4. package/lib/src/builder/GraphBuilder.js.map +1 -0
  5. package/lib/src/builder/Registry.d.ts +87 -0
  6. package/lib/src/builder/Registry.d.ts.map +1 -0
  7. package/lib/src/builder/Registry.js +390 -0
  8. package/lib/src/builder/Registry.js.map +1 -0
  9. package/lib/src/core/categories.d.ts +22 -0
  10. package/lib/src/core/categories.d.ts.map +1 -0
  11. package/lib/src/core/categories.js +2 -0
  12. package/lib/src/core/categories.js.map +1 -0
  13. package/lib/src/core/order.d.ts +7 -0
  14. package/lib/src/core/order.d.ts.map +1 -0
  15. package/lib/src/core/order.js +66 -0
  16. package/lib/src/core/order.js.map +1 -0
  17. package/lib/src/core/type-utils.d.ts +29 -0
  18. package/lib/src/core/type-utils.d.ts.map +1 -0
  19. package/lib/src/core/type-utils.js +92 -0
  20. package/lib/src/core/type-utils.js.map +1 -0
  21. package/lib/src/core/types.d.ts +95 -0
  22. package/lib/src/core/types.d.ts.map +1 -0
  23. package/lib/src/core/types.js +2 -0
  24. package/lib/src/core/types.js.map +1 -0
  25. package/lib/src/examples/arrays.d.ts +5 -0
  26. package/lib/src/examples/arrays.d.ts.map +1 -0
  27. package/lib/src/examples/arrays.js +49 -0
  28. package/lib/src/examples/arrays.js.map +1 -0
  29. package/lib/src/examples/async.d.ts +5 -0
  30. package/lib/src/examples/async.d.ts.map +1 -0
  31. package/lib/src/examples/async.js +91 -0
  32. package/lib/src/examples/async.js.map +1 -0
  33. package/lib/src/examples/progress.d.ts +5 -0
  34. package/lib/src/examples/progress.d.ts.map +1 -0
  35. package/lib/src/examples/progress.js +51 -0
  36. package/lib/src/examples/progress.js.map +1 -0
  37. package/lib/src/examples/run.d.ts +2 -0
  38. package/lib/src/examples/run.d.ts.map +1 -0
  39. package/lib/src/examples/run.js +32 -0
  40. package/lib/src/examples/run.js.map +1 -0
  41. package/lib/src/examples/runMode.d.ts +2 -0
  42. package/lib/src/examples/runMode.d.ts.map +1 -0
  43. package/lib/src/examples/runMode.js +223 -0
  44. package/lib/src/examples/runMode.js.map +1 -0
  45. package/lib/src/examples/shared.d.ts +5 -0
  46. package/lib/src/examples/shared.d.ts.map +1 -0
  47. package/lib/src/examples/shared.js +47 -0
  48. package/lib/src/examples/shared.js.map +1 -0
  49. package/lib/src/examples/simple.d.ts +5 -0
  50. package/lib/src/examples/simple.d.ts.map +1 -0
  51. package/lib/src/examples/simple.js +79 -0
  52. package/lib/src/examples/simple.js.map +1 -0
  53. package/lib/src/examples/snapshot.d.ts +4 -0
  54. package/lib/src/examples/snapshot.d.ts.map +1 -0
  55. package/lib/src/examples/snapshot.js +58 -0
  56. package/lib/src/examples/snapshot.js.map +1 -0
  57. package/lib/src/examples/validation.d.ts +5 -0
  58. package/lib/src/examples/validation.d.ts.map +1 -0
  59. package/lib/src/examples/validation.js +105 -0
  60. package/lib/src/examples/validation.js.map +1 -0
  61. package/lib/src/index.d.ts +27 -0
  62. package/lib/src/index.d.ts.map +1 -0
  63. package/lib/src/index.js +19 -0
  64. package/lib/src/index.js.map +1 -0
  65. package/lib/src/misc/base.d.ts +51 -0
  66. package/lib/src/misc/base.d.ts.map +1 -0
  67. package/lib/src/misc/base.js +1091 -0
  68. package/lib/src/misc/base.js.map +1 -0
  69. package/lib/src/misc/utils/LevelLogger.d.ts +150 -0
  70. package/lib/src/misc/utils/LevelLogger.d.ts.map +1 -0
  71. package/lib/src/misc/utils/LevelLogger.js +420 -0
  72. package/lib/src/misc/utils/LevelLogger.js.map +1 -0
  73. package/lib/src/misc/utils/LevelLogger.test.d.ts +2 -0
  74. package/lib/src/misc/utils/LevelLogger.test.d.ts.map +1 -0
  75. package/lib/src/misc/utils/LevelLogger.test.js +283 -0
  76. package/lib/src/misc/utils/LevelLogger.test.js.map +1 -0
  77. package/lib/src/misc/utils/json.d.ts +34 -0
  78. package/lib/src/misc/utils/json.d.ts.map +1 -0
  79. package/lib/src/misc/utils/json.js +471 -0
  80. package/lib/src/misc/utils/json.js.map +1 -0
  81. package/lib/src/misc/utils/merge.d.ts +51 -0
  82. package/lib/src/misc/utils/merge.d.ts.map +1 -0
  83. package/lib/src/misc/utils/merge.js +591 -0
  84. package/lib/src/misc/utils/merge.js.map +1 -0
  85. package/lib/src/misc/utils/test-logger-output.d.ts +7 -0
  86. package/lib/src/misc/utils/test-logger-output.d.ts.map +1 -0
  87. package/lib/src/misc/utils/test-logger-output.js +48 -0
  88. package/lib/src/misc/utils/test-logger-output.js.map +1 -0
  89. package/lib/src/plugins/composite.d.ts +22 -0
  90. package/lib/src/plugins/composite.d.ts.map +1 -0
  91. package/lib/src/plugins/composite.js +59 -0
  92. package/lib/src/plugins/composite.js.map +1 -0
  93. package/lib/src/plugins/compute.d.ts +5 -0
  94. package/lib/src/plugins/compute.d.ts.map +1 -0
  95. package/lib/src/plugins/compute.js +39 -0
  96. package/lib/src/plugins/compute.js.map +1 -0
  97. package/lib/src/runtime/Engine.d.ts +26 -0
  98. package/lib/src/runtime/Engine.d.ts.map +1 -0
  99. package/lib/src/runtime/Engine.js +2 -0
  100. package/lib/src/runtime/Engine.js.map +1 -0
  101. package/lib/src/runtime/GraphLifecycleApi.d.ts +46 -0
  102. package/lib/src/runtime/GraphLifecycleApi.d.ts.map +1 -0
  103. package/lib/src/runtime/GraphLifecycleApi.js +2 -0
  104. package/lib/src/runtime/GraphLifecycleApi.js.map +1 -0
  105. package/lib/src/runtime/GraphRuntime.d.ts +111 -0
  106. package/lib/src/runtime/GraphRuntime.d.ts.map +1 -0
  107. package/lib/src/runtime/GraphRuntime.js +791 -0
  108. package/lib/src/runtime/GraphRuntime.js.map +1 -0
  109. package/lib/src/runtime/LocalEngine.d.ts +41 -0
  110. package/lib/src/runtime/LocalEngine.d.ts.map +1 -0
  111. package/lib/src/runtime/LocalEngine.js +89 -0
  112. package/lib/src/runtime/LocalEngine.js.map +1 -0
  113. package/lib/src/runtime/components/EdgePropagator.d.ts +93 -0
  114. package/lib/src/runtime/components/EdgePropagator.d.ts.map +1 -0
  115. package/lib/src/runtime/components/EdgePropagator.js +378 -0
  116. package/lib/src/runtime/components/EdgePropagator.js.map +1 -0
  117. package/lib/src/runtime/components/EventEmitter.d.ts +12 -0
  118. package/lib/src/runtime/components/EventEmitter.d.ts.map +1 -0
  119. package/lib/src/runtime/components/EventEmitter.js +33 -0
  120. package/lib/src/runtime/components/EventEmitter.js.map +1 -0
  121. package/lib/src/runtime/components/Graph.d.ts +208 -0
  122. package/lib/src/runtime/components/Graph.d.ts.map +1 -0
  123. package/lib/src/runtime/components/Graph.js +452 -0
  124. package/lib/src/runtime/components/Graph.js.map +1 -0
  125. package/lib/src/runtime/components/HandleResolver.d.ts +36 -0
  126. package/lib/src/runtime/components/HandleResolver.d.ts.map +1 -0
  127. package/lib/src/runtime/components/HandleResolver.js +229 -0
  128. package/lib/src/runtime/components/HandleResolver.js.map +1 -0
  129. package/lib/src/runtime/components/NodeExecutor.d.ts +116 -0
  130. package/lib/src/runtime/components/NodeExecutor.d.ts.map +1 -0
  131. package/lib/src/runtime/components/NodeExecutor.js +648 -0
  132. package/lib/src/runtime/components/NodeExecutor.js.map +1 -0
  133. package/lib/src/runtime/components/RunContextManager.d.ts +90 -0
  134. package/lib/src/runtime/components/RunContextManager.d.ts.map +1 -0
  135. package/lib/src/runtime/components/RunContextManager.js +329 -0
  136. package/lib/src/runtime/components/RunContextManager.js.map +1 -0
  137. package/lib/src/runtime/components/RuntimeValidatorManager.d.ts +31 -0
  138. package/lib/src/runtime/components/RuntimeValidatorManager.d.ts.map +1 -0
  139. package/lib/src/runtime/components/RuntimeValidatorManager.js +56 -0
  140. package/lib/src/runtime/components/RuntimeValidatorManager.js.map +1 -0
  141. package/lib/src/runtime/components/graph-utils.d.ts +33 -0
  142. package/lib/src/runtime/components/graph-utils.d.ts.map +1 -0
  143. package/lib/src/runtime/components/graph-utils.js +300 -0
  144. package/lib/src/runtime/components/graph-utils.js.map +1 -0
  145. package/lib/src/runtime/components/interfaces.d.ts +59 -0
  146. package/lib/src/runtime/components/interfaces.d.ts.map +1 -0
  147. package/lib/src/runtime/components/interfaces.js +2 -0
  148. package/lib/src/runtime/components/interfaces.js.map +1 -0
  149. package/lib/src/runtime/components/types.d.ts +57 -0
  150. package/lib/src/runtime/components/types.d.ts.map +1 -0
  151. package/lib/src/runtime/components/types.js +2 -0
  152. package/lib/src/runtime/components/types.js.map +1 -0
  153. package/lib/src/runtime/utils.d.ts +21 -0
  154. package/lib/src/runtime/utils.d.ts.map +1 -0
  155. package/lib/src/runtime/utils.js +41 -0
  156. package/lib/src/runtime/utils.js.map +1 -0
  157. package/package.json +2 -2
@@ -0,0 +1,648 @@
1
+ import { getEffectiveInputs } from "./graph-utils";
2
+ import { formatNodeRef } from "../utils";
3
+ import { LevelLogger } from "../../misc/utils/LevelLogger";
4
+ /**
5
+ * NodeExecutor component - handles node execution scheduling and lifecycle
6
+ */
7
+ export class NodeExecutor {
8
+ constructor(graph, eventEmitter, runContextManager, handleResolver, edgePropagator, runtime, environment) {
9
+ this.graph = graph;
10
+ this.eventEmitter = eventEmitter;
11
+ this.runContextManager = runContextManager;
12
+ this.handleResolver = handleResolver;
13
+ this.edgePropagator = edgePropagator;
14
+ this.runtime = runtime;
15
+ this.environment = {};
16
+ this.customNodeData = {};
17
+ this.environment = environment ?? {};
18
+ }
19
+ setEnvironment(environment) {
20
+ this.environment = environment;
21
+ }
22
+ setCustomNodeData(customNodeData) {
23
+ this.customNodeData = customNodeData;
24
+ }
25
+ /**
26
+ * Compute effective inputs for a node by merging real inputs with defaults
27
+ */
28
+ getEffectiveInputs(nodeId) {
29
+ const registry = this.graph.getRegistry();
30
+ if (!registry)
31
+ return {};
32
+ return getEffectiveInputs(nodeId, this.graph, registry);
33
+ }
34
+ /**
35
+ * Create an execution context for a node
36
+ */
37
+ createExecutionContext(nodeId, inputs, runId, abortSignal, runContextIds, options) {
38
+ const emitHandler = options?.emitHandler ??
39
+ ((handle, value) => {
40
+ this.edgePropagator.propagate(nodeId, handle, value, runContextIds);
41
+ });
42
+ const reportProgress = options?.reportProgress ??
43
+ ((p) => {
44
+ this.graph.updateNodeStats(nodeId, {
45
+ progress: Math.max(0, Math.min(1, Number(p) || 0)),
46
+ });
47
+ });
48
+ // Create log function that respects node's logLevel using LevelLogger
49
+ const node = this.graph.getNode(nodeId);
50
+ const nodeLogLevel = node?.logLevel ?? "info";
51
+ const logger = new LevelLogger(nodeLogLevel, `[node:${formatNodeRef(this.graph, nodeId)}:${runId}]`);
52
+ const log = (level, message, context) => {
53
+ logger.log(level, message, context);
54
+ };
55
+ return {
56
+ nodeId,
57
+ state: node?.state,
58
+ setState: (next) => this.graph.updateNodeState(nodeId, next),
59
+ emit: emitHandler,
60
+ invalidateDownstream: () => {
61
+ this.edgePropagator.invalidateDownstream(nodeId);
62
+ },
63
+ execute: (opts) => {
64
+ if (this.graph.allInboundHaveValue(nodeId)) {
65
+ let runContextIdsToUse = this.runtime.getRunMode() === "auto" ? undefined : runContextIds;
66
+ let runContextIdToRelease;
67
+ if (this.runtime.getRunMode() === "manual" && (!runContextIds || runContextIds.size === 0)) {
68
+ runContextIdToRelease = this.runContextManager.createRunContext(nodeId, opts);
69
+ runContextIdsToUse = new Set([runContextIdToRelease]);
70
+ }
71
+ try {
72
+ this.execute(nodeId, {
73
+ runContextIds: runContextIdsToUse,
74
+ reason: opts?.reason ?? "executeFromContext",
75
+ });
76
+ }
77
+ finally {
78
+ if (runContextIdToRelease) {
79
+ this.runContextManager.releaseScheduling(runContextIdToRelease);
80
+ }
81
+ }
82
+ }
83
+ },
84
+ getInput: (handle) => inputs[handle],
85
+ environment: this.environment,
86
+ customNodeData: this.customNodeData[nodeId],
87
+ runId,
88
+ abortSignal,
89
+ reportProgress,
90
+ setCustomData: (data) => {
91
+ this.eventEmitter.emit("stats", {
92
+ kind: "node-custom-data",
93
+ nodeId,
94
+ typeId: node?.typeId ?? "",
95
+ runId,
96
+ data,
97
+ });
98
+ },
99
+ log,
100
+ };
101
+ }
102
+ /**
103
+ * Internal method for executing inputs changed (also used by GraphRuntime)
104
+ */
105
+ execute(nodeId, opts) {
106
+ let { runContextIds, canSkipHandleResolution, reason = "" } = opts ?? {};
107
+ let autoCreatedRunContextId;
108
+ const node = this.graph.getNode(nodeId);
109
+ if (!node)
110
+ return;
111
+ const runMode = this.runtime.getRunMode();
112
+ if (!runMode) {
113
+ console.trace(`NodeExecutor.execute[${formatNodeRef(this.graph, nodeId)}:${reason}]: no runMode, skipping execution`);
114
+ return;
115
+ }
116
+ // In manual mode, require runContextIds unless autoRun policy is set
117
+ if (runMode === "manual" && (!runContextIds || runContextIds.size === 0)) {
118
+ // If autoRun is true, auto-generate a run context (similar to createExecutionContext pattern)
119
+ if (node.policy?.autoRun === true) {
120
+ autoCreatedRunContextId = this.runContextManager.createRunContext(nodeId, { propagate: false });
121
+ runContextIds = new Set([autoCreatedRunContextId]);
122
+ }
123
+ else {
124
+ console.trace(`NodeExecutor.execute[${formatNodeRef(this.graph, nodeId)}:${reason}]: no runContextIds provided in manual mode, skipping execution`);
125
+ return;
126
+ }
127
+ }
128
+ try {
129
+ if (runMode === "auto" && runContextIds && runContextIds.size > 0) {
130
+ console.trace(`NodeExecutor.execute[${formatNodeRef(this.graph, nodeId)}:${reason}]: runContextIds provided in auto mode, ignoring`);
131
+ runContextIds = undefined;
132
+ }
133
+ // Early validation for auto-mode paused state
134
+ if (this.runtime.isPaused())
135
+ return;
136
+ // Check runtime validators (check current state, not just graph definition)
137
+ const runtimeValidationError = this.runtime.hasRuntimeValidationBlock(nodeId);
138
+ if (runtimeValidationError) {
139
+ this.eventEmitter.emit("error", {
140
+ kind: "system",
141
+ message: runtimeValidationError.message,
142
+ code: runtimeValidationError.code || "RUNTIME_VALIDATION_BLOCKED",
143
+ details: {
144
+ nodeId,
145
+ nodeTypeId: node?.typeId,
146
+ ...runtimeValidationError.details,
147
+ },
148
+ });
149
+ return;
150
+ }
151
+ // Attach run-context IDs if provided - do this BEFORE checking for pending resolution
152
+ // so that handle resolution can track these run contexts
153
+ if (runContextIds) {
154
+ this.graph.addNodeRunContextIds(nodeId, runContextIds);
155
+ }
156
+ if (!canSkipHandleResolution && !this.handleResolver.getPendingResolution(nodeId)) {
157
+ this.handleResolver.scheduleRecomputeHandles(nodeId);
158
+ }
159
+ // Check if handles are being resolved - wait for resolution before executing
160
+ // Do this AFTER setting up run contexts so handle resolution can track them
161
+ const pendingResolution = this.handleResolver.getPendingResolution(nodeId);
162
+ if (pendingResolution) {
163
+ if (runContextIds && runContextIds.size > 0) {
164
+ for (const id of runContextIds) {
165
+ this.runContextManager.startHandleResolution(id, nodeId);
166
+ }
167
+ }
168
+ // Wait for resolution to complete, then re-execute
169
+ pendingResolution.then(() => {
170
+ // Re-check node still exists and conditions
171
+ const nodeAfter = this.graph.getNode(nodeId);
172
+ if (nodeAfter) {
173
+ this.execute(nodeId, {
174
+ runContextIds,
175
+ canSkipHandleResolution: true,
176
+ reason: opts?.reason,
177
+ });
178
+ }
179
+ if (runContextIds && runContextIds.size > 0) {
180
+ for (const id of runContextIds) {
181
+ this.runContextManager.finishHandleResolution(id, nodeId);
182
+ }
183
+ }
184
+ });
185
+ return;
186
+ }
187
+ // Handle debouncing
188
+ const now = Date.now();
189
+ if (this.shouldDebounce(nodeId, now)) {
190
+ this.handleDebouncedSchedule(nodeId, now, runContextIds, reason);
191
+ return;
192
+ }
193
+ // Prepare execution plan
194
+ const executionPlan = this.prepareExecutionPlan(nodeId, runContextIds, now, reason);
195
+ // Route to appropriate concurrency handler
196
+ this.routeToConcurrencyHandler(nodeId, executionPlan);
197
+ }
198
+ finally {
199
+ if (autoCreatedRunContextId) {
200
+ this.runContextManager.releaseScheduling(autoCreatedRunContextId);
201
+ }
202
+ }
203
+ }
204
+ /**
205
+ * Check if execution should be debounced
206
+ */
207
+ shouldDebounce(nodeId, now) {
208
+ const node = this.graph.getNode(nodeId);
209
+ if (!node)
210
+ return false;
211
+ const policy = node.policy ?? {};
212
+ const lastScheduledAt = node.lastScheduledAt;
213
+ return !!(policy.debounceMs && lastScheduledAt && now - lastScheduledAt < policy.debounceMs);
214
+ }
215
+ /**
216
+ * Handle debounced scheduling by replacing the latest queued item
217
+ */
218
+ handleDebouncedSchedule(nodeId, now, runContextIds, reason) {
219
+ const node = this.graph.getNode(nodeId);
220
+ if (!node)
221
+ return;
222
+ // Decrement pendingQueued for any existing queued items before replacing
223
+ if (node.queue.length > 0) {
224
+ this.decrementQueuedForPlans(node.queue, nodeId);
225
+ }
226
+ const executionPlan = this.prepareExecutionPlan(nodeId, runContextIds, now, reason);
227
+ this.graph.replaceNodeQueue(nodeId, [executionPlan]);
228
+ }
229
+ /**
230
+ * Prepare execution plan with all necessary information
231
+ */
232
+ prepareExecutionPlan(nodeId, runContextIds, now, reason) {
233
+ const node = this.graph.getNode(nodeId);
234
+ this.graph.setNodeLastScheduledAt(nodeId, now);
235
+ const runSeq = this.graph.incrementNodeRunSeq(nodeId);
236
+ const runId = `${nodeId}:${runSeq}:${now}`;
237
+ this.graph.setNodeLatestRunId(nodeId, runId);
238
+ const effectiveInputs = this.getEffectiveInputs(nodeId);
239
+ // Take a shallow snapshot of the current policy for this run
240
+ const policySnapshot = node?.policy ? { ...node.policy } : undefined;
241
+ return {
242
+ runId,
243
+ effectiveInputs,
244
+ runContextIdsForRun: runContextIds,
245
+ timestamp: now,
246
+ policy: policySnapshot,
247
+ reason,
248
+ };
249
+ }
250
+ /**
251
+ * Route execution to appropriate concurrency handler
252
+ */
253
+ routeToConcurrencyHandler(nodeId, plan) {
254
+ const mode = plan.policy?.asyncConcurrency ?? "switch";
255
+ switch (mode) {
256
+ case "drop":
257
+ this.handleDropMode(nodeId, plan);
258
+ break;
259
+ case "queue":
260
+ this.handleQueueMode(nodeId, plan);
261
+ break;
262
+ case "switch":
263
+ case "merge":
264
+ default:
265
+ this.startRun(nodeId, plan);
266
+ break;
267
+ }
268
+ }
269
+ /**
270
+ * Handle drop mode - drop execution if node is already running, otherwise start run
271
+ */
272
+ handleDropMode(nodeId, plan) {
273
+ const node = this.graph.getNode(nodeId);
274
+ if (!node)
275
+ return;
276
+ // Drop if node is already running
277
+ if (node.activeControllers.size > 0) {
278
+ return; // Don't increment pendingCount if we're dropping this run
279
+ }
280
+ // Start run if node is not running
281
+ this.startRun(nodeId, plan);
282
+ }
283
+ /**
284
+ * Handle queue mode - add to queue and process sequentially
285
+ */
286
+ handleQueueMode(nodeId, plan) {
287
+ const maxQ = plan.policy?.maxQueue ?? 8;
288
+ const node = this.graph.getNode(nodeId);
289
+ if (!node)
290
+ return;
291
+ // Keep the originating run-context alive while work is queued by
292
+ // incrementing queued counters for the plan's run-contexts.
293
+ if (plan.runContextIdsForRun) {
294
+ for (const rcId of plan.runContextIdsForRun) {
295
+ this.runContextManager.incrementQueued(rcId, nodeId);
296
+ }
297
+ }
298
+ this.graph.addToNodeQueue(nodeId, plan);
299
+ if (node.queue.length > maxQ) {
300
+ const dropped = this.graph.shiftNodeQueue(nodeId);
301
+ if (dropped) {
302
+ this.decrementQueuedForPlans(dropped, nodeId);
303
+ }
304
+ }
305
+ this.processQueue(nodeId);
306
+ }
307
+ /**
308
+ * Process queued executions sequentially
309
+ */
310
+ processQueue(nodeId) {
311
+ const processNext = () => {
312
+ const node = this.graph.getNode(nodeId);
313
+ if (!node)
314
+ return;
315
+ if (node.activeControllers.size > 0)
316
+ return;
317
+ const next = this.graph.shiftNodeQueue(nodeId);
318
+ if (!next)
319
+ return;
320
+ this.graph.setNodeLatestRunId(nodeId, next.runId);
321
+ // Start the run first (which increments pendingNodes), then decrement
322
+ // pendingQueued to ensure the run context stays alive.
323
+ this.startRun(nodeId, next, () => {
324
+ setTimeout(processNext, 0);
325
+ });
326
+ this.decrementQueuedForPlans(next, nodeId);
327
+ };
328
+ processNext();
329
+ }
330
+ /**
331
+ * Start a node execution run
332
+ */
333
+ startRun(nodeId, plan, onDone) {
334
+ // Track run-contexts
335
+ this.trackRunContextStart(nodeId, plan.runContextIdsForRun);
336
+ // Setup execution controller
337
+ const controller = this.createExecutionController(nodeId, plan.runId);
338
+ // Handle concurrency mode
339
+ this.applyConcurrencyMode(nodeId, controller, plan);
340
+ // Setup timeout if needed
341
+ const timeoutId = this.setupTimeout(controller, plan);
342
+ // Create execution context
343
+ const execCtx = this.createExecutionContext(nodeId, plan.effectiveInputs, plan.runId, controller.signal, plan.runContextIdsForRun, this.createEmitAndProgressHandlers(nodeId, plan));
344
+ // Execute
345
+ this.executeNode(nodeId, execCtx, plan, controller, timeoutId, onDone);
346
+ }
347
+ /**
348
+ * Track run-context start for pending nodes
349
+ */
350
+ trackRunContextStart(nodeId, runContextIdsForRun) {
351
+ if (runContextIdsForRun && runContextIdsForRun.size > 0) {
352
+ for (const id of runContextIdsForRun) {
353
+ this.runContextManager.startNodeRun(id, nodeId);
354
+ }
355
+ }
356
+ }
357
+ /**
358
+ * Decrement pendingQueued counters for plans' run-contexts.
359
+ * Used when queued work is being started, dropped, or cancelled.
360
+ */
361
+ decrementQueuedForPlans(plans, nodeId) {
362
+ const plansArray = Array.isArray(plans) ? plans : [plans];
363
+ for (const plan of plansArray) {
364
+ if (plan.runContextIdsForRun) {
365
+ for (const rcId of plan.runContextIdsForRun) {
366
+ this.runContextManager.decrementQueued(rcId, nodeId);
367
+ }
368
+ }
369
+ }
370
+ }
371
+ /**
372
+ * Create execution controller and update node stats
373
+ */
374
+ createExecutionController(nodeId, runId) {
375
+ const controller = new AbortController();
376
+ const now = Date.now();
377
+ const node = this.graph.getNode(nodeId);
378
+ this.graph.updateNodeStats(nodeId, {
379
+ runs: (node?.stats.runs ?? 0) + 1,
380
+ active: (node?.stats.active ?? 0) + 1,
381
+ lastStartAt: now,
382
+ progress: 0,
383
+ });
384
+ this.graph.addNodeController(nodeId, controller, runId);
385
+ return controller;
386
+ }
387
+ /**
388
+ * Apply concurrency mode (switch mode aborts other controllers)
389
+ */
390
+ applyConcurrencyMode(nodeId, controller, plan) {
391
+ const mode = plan.policy?.asyncConcurrency ?? "switch";
392
+ if (mode === "switch") {
393
+ const controllers = this.graph.getNodeControllers(nodeId);
394
+ for (const c of controllers) {
395
+ if (c !== controller)
396
+ c.abort("switch");
397
+ }
398
+ }
399
+ }
400
+ /**
401
+ * Setup timeout for execution if configured
402
+ */
403
+ setupTimeout(controller, plan) {
404
+ const policy = plan.policy ?? {};
405
+ if (policy.timeoutMs && policy.timeoutMs > 0) {
406
+ return setTimeout(() => controller.abort("timeout"), policy.timeoutMs);
407
+ }
408
+ return undefined;
409
+ }
410
+ /**
411
+ * Create emit and progress handlers for execution context
412
+ */
413
+ createEmitAndProgressHandlers(nodeId, plan) {
414
+ const policy = plan.policy ?? {};
415
+ return {
416
+ emitHandler: (handle, value) => {
417
+ const node = this.graph.getNode(nodeId);
418
+ if (!node)
419
+ return;
420
+ const m = policy.asyncConcurrency ?? "switch";
421
+ // Drop emits from runs that were explicitly cancelled due to a
422
+ // snapshot/undo/redo operation, regardless of asyncConcurrency.
423
+ if (node.snapshotCancelledRunIds?.has(plan.runId))
424
+ return;
425
+ if (m !== "merge" && plan.runId !== node.latestRunId)
426
+ return;
427
+ this.edgePropagator.propagate(nodeId, handle, value, plan.runContextIdsForRun);
428
+ },
429
+ reportProgress: (p) => {
430
+ const progress = Math.max(0, Math.min(1, Number(p) || 0));
431
+ this.graph.updateNodeStats(nodeId, { progress });
432
+ const node = this.graph.getNode(nodeId);
433
+ if (!node)
434
+ return;
435
+ this.eventEmitter.emit("stats", {
436
+ kind: "node-progress",
437
+ nodeId,
438
+ typeId: node.typeId,
439
+ runId: plan.runId,
440
+ progress,
441
+ });
442
+ },
443
+ };
444
+ }
445
+ /**
446
+ * Execute the node with retry logic and cleanup
447
+ */
448
+ executeNode(nodeId, ctx, plan, controller, timeoutId, onDone) {
449
+ const node = this.graph.getNode(nodeId);
450
+ if (!node)
451
+ return;
452
+ // Fire node-start event
453
+ this.eventEmitter.emit("stats", {
454
+ kind: "node-start",
455
+ nodeId,
456
+ typeId: node.typeId,
457
+ runId: plan.runId,
458
+ });
459
+ ctx.log("debug", "node-start", {
460
+ inputs: node.inputs,
461
+ effectiveInputs: plan.effectiveInputs,
462
+ reason: plan.reason,
463
+ });
464
+ const exec = async (attempt) => {
465
+ let hadError = false;
466
+ try {
467
+ if (node.lifecycle?.prepare) {
468
+ ctx.log("debug", "prepare-start");
469
+ node.lifecycle.prepare(node.params ?? {}, ctx);
470
+ ctx.log("debug", "prepare-done");
471
+ }
472
+ await node.runtime.onInputsChanged?.(plan.effectiveInputs, ctx);
473
+ }
474
+ catch (err) {
475
+ // Suppress errors caused by expected cancellations
476
+ if (controller.signal.aborted) {
477
+ const reason = controller.signal.reason;
478
+ if (reason === "switch" ||
479
+ reason === "snapshot" ||
480
+ reason === "node-deleted" ||
481
+ reason === "user-cancelled") {
482
+ return; // Cancellation events are emitted separately, skip error handling
483
+ }
484
+ }
485
+ hadError = true;
486
+ this.graph.updateNodeStats(nodeId, { lastError: err });
487
+ const retry = plan.policy?.retry;
488
+ if (retry && attempt < (retry.attempts ?? 0)) {
489
+ const delay = retry.backoffMs ? retry.backoffMs(attempt) : 0;
490
+ await new Promise((r) => setTimeout(r, delay));
491
+ return exec(attempt + 1);
492
+ }
493
+ this.eventEmitter.emit("error", {
494
+ kind: "node-run",
495
+ nodeId,
496
+ nodeTypeId: node.typeId,
497
+ runId: plan.runId,
498
+ err,
499
+ });
500
+ }
501
+ finally {
502
+ this.cleanupExecution(nodeId, ctx, plan, controller, timeoutId, hadError, onDone);
503
+ }
504
+ };
505
+ exec(0);
506
+ }
507
+ /**
508
+ * Cleanup after execution completes
509
+ */
510
+ cleanupExecution(nodeId, ctx, plan, controller, timeoutId, hadError, onDone) {
511
+ // Decrement pendingNodes count for all relevant run-contexts
512
+ if (plan.runContextIdsForRun && plan.runContextIdsForRun.size > 0) {
513
+ for (const id of plan.runContextIdsForRun) {
514
+ this.runContextManager.finishNodeRun(id, nodeId);
515
+ }
516
+ }
517
+ // Skip cleanup if node was deleted (cleanup already handled)
518
+ if (!this.graph.hasNode(nodeId)) {
519
+ return;
520
+ }
521
+ if (timeoutId)
522
+ clearTimeout(timeoutId);
523
+ this.graph.removeNodeController(nodeId, controller);
524
+ const node = this.graph.getNode(nodeId);
525
+ if (!node)
526
+ return;
527
+ const controllers = this.graph.getNodeControllers(nodeId);
528
+ const lastEndAt = Date.now();
529
+ const lastDurationMs = node.stats.lastStartAt && lastEndAt ? lastEndAt - node.stats.lastStartAt : undefined;
530
+ this.graph.updateNodeStats(nodeId, {
531
+ active: Math.max(0, controllers.size),
532
+ lastEndAt,
533
+ lastDurationMs,
534
+ lastError: hadError ? node.stats.lastError : undefined,
535
+ });
536
+ // Track successful completion time (for detecting stale inputs)
537
+ const isCancelled = controller.signal.aborted &&
538
+ (controller.signal.reason === "snapshot" ||
539
+ controller.signal.reason === "node-deleted" ||
540
+ controller.signal.reason === "user-cancelled");
541
+ if (!hadError && !isCancelled) {
542
+ this.graph.setNodeLastSuccessAt(nodeId, Date.now());
543
+ }
544
+ // Only emit node-done if not cancelled (cancellation events emitted separately)
545
+ if (!isCancelled) {
546
+ if (node) {
547
+ this.eventEmitter.emit("stats", {
548
+ kind: "node-done",
549
+ nodeId,
550
+ typeId: node.typeId,
551
+ runId: plan.runId,
552
+ durationMs: node.stats.lastDurationMs,
553
+ });
554
+ }
555
+ }
556
+ if (node) {
557
+ ctx.log("debug", "node-done", {
558
+ durationMs: node.stats.lastDurationMs,
559
+ outputs: node.outputs,
560
+ hadError,
561
+ reason: plan.reason,
562
+ });
563
+ }
564
+ if (onDone)
565
+ onDone();
566
+ }
567
+ /**
568
+ * Cancel all active runs for a node
569
+ */
570
+ cancelNodeActiveRuns(nodeId, reason) {
571
+ const node = this.graph.getNode(nodeId);
572
+ if (!node)
573
+ return;
574
+ const controllers = this.graph.getNodeControllers(nodeId);
575
+ for (const controller of controllers) {
576
+ const runId = node.controllerRunIds.get(controller);
577
+ if (runId) {
578
+ // Track cancelled runIds for snapshot and user-cancelled operations
579
+ // (to drop emits from cancelled runs)
580
+ if (reason === "snapshot" || reason === "user-cancelled") {
581
+ this.graph.addSnapshotCancelledRunId(nodeId, runId);
582
+ }
583
+ // Emit cancellation event
584
+ this.eventEmitter.emit("stats", {
585
+ kind: "node-done",
586
+ nodeId: nodeId,
587
+ typeId: node.typeId,
588
+ runId,
589
+ cancelled: true,
590
+ });
591
+ }
592
+ try {
593
+ controller.abort(reason);
594
+ }
595
+ catch {
596
+ // ignore abort errors
597
+ }
598
+ }
599
+ this.graph.clearNodeControllers(nodeId);
600
+ this.graph.updateNodeStats(nodeId, { active: 0 });
601
+ // Decrement pendingQueued for any queued items before clearing the queue
602
+ if (node.queue.length > 0) {
603
+ this.decrementQueuedForPlans(node.queue, nodeId);
604
+ }
605
+ this.graph.clearNodeQueue(nodeId);
606
+ }
607
+ /**
608
+ * Cancel runs for multiple nodes.
609
+ * Can be called for snapshot/undo/redo operations or user-initiated cancellation.
610
+ */
611
+ cancelNodeRuns(nodeIds, reason = "user-cancelled") {
612
+ if (nodeIds.length === 0)
613
+ return;
614
+ const toCancel = new Set(nodeIds);
615
+ const visited = new Set();
616
+ const queue = [...nodeIds];
617
+ // Collect all downstream nodes to cancel
618
+ for (let i = 0; i < queue.length; i++) {
619
+ const nodeId = queue[i];
620
+ if (visited.has(nodeId))
621
+ continue;
622
+ visited.add(nodeId);
623
+ this.graph.forEachEdge((edge) => {
624
+ if (edge.source.nodeId === nodeId) {
625
+ const targetId = edge.target.nodeId;
626
+ if (!visited.has(targetId)) {
627
+ toCancel.add(targetId);
628
+ queue.push(targetId);
629
+ }
630
+ }
631
+ });
632
+ }
633
+ // Cancel runs for all affected nodes
634
+ for (const nodeId of toCancel) {
635
+ this.cancelNodeActiveRuns(nodeId, reason);
636
+ const runSeq = this.graph.incrementNodeRunSeq(nodeId);
637
+ const now = Date.now();
638
+ const suffix = reason === "snapshot" ? "snapshot" : "cancelled";
639
+ this.graph.setNodeLatestRunId(nodeId, `${nodeId}:${runSeq}:${now}:${suffix}`);
640
+ }
641
+ // Cancel nodes in run-contexts (exclude them from active run-contexts)
642
+ for (const nodeId of toCancel) {
643
+ // includeDownstream = false (already collected above)
644
+ this.runContextManager.cancelNodeInRunContexts(nodeId, false);
645
+ }
646
+ }
647
+ }
648
+ //# sourceMappingURL=NodeExecutor.js.map