@bian-womp/spark-graph 0.3.7 → 0.3.8

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/lib/cjs/index.cjs CHANGED
@@ -1238,14 +1238,35 @@ class EdgePropagator {
1238
1238
  const processedValue = this.processArrayInput(edge, value);
1239
1239
  // Check if value changed
1240
1240
  const prev = dstNode.inputs[edge.target.handle];
1241
- if (valuesEqual(prev, processedValue)) {
1242
- return; // No change
1241
+ const valueChanged = !valuesEqual(prev, processedValue);
1242
+ // Check if we should execute even if value is same:
1243
+ // 1. If node has rerunOnSameInput policy (from node params or registry)
1244
+ // 2. If input was set after last successful run (stale input detection)
1245
+ const registry = this.graph.getRegistry();
1246
+ const desc = registry?.nodes.get(dstNode.typeId);
1247
+ const nodeRerunPolicy = dstNode.policy?.rerunOnSameInput === true;
1248
+ const descRerunPolicy = desc?.policy?.rerunOnSameInput === true;
1249
+ const shouldRerunOnSameInput = nodeRerunPolicy || descRerunPolicy;
1250
+ const inputWasSetAfterLastRun = dstNode.lastInputAt?.[edge.target.handle] &&
1251
+ dstNode.lastSuccessAt &&
1252
+ dstNode.lastInputAt[edge.target.handle] > dstNode.lastSuccessAt;
1253
+ const shouldExecute = valueChanged || shouldRerunOnSameInput || inputWasSetAfterLastRun;
1254
+ if (!shouldExecute) {
1255
+ return; // No change and no reason to rerun
1243
1256
  }
1244
1257
  // Set input value (respecting skipPropagateValues)
1245
1258
  const shouldSetValue = this.shouldSetInputValue(effectiveRunContexts);
1246
- if (shouldSetValue) {
1259
+ if (shouldSetValue && valueChanged) {
1247
1260
  this.setTargetInput(edge, dstNode, processedValue);
1248
1261
  }
1262
+ else if (shouldSetValue && !valueChanged) {
1263
+ // Even if value didn't change, update timestamp if we're forcing execution
1264
+ const now = Date.now();
1265
+ if (!dstNode.lastInputAt) {
1266
+ dstNode.lastInputAt = {};
1267
+ }
1268
+ dstNode.lastInputAt[edge.target.handle] = now;
1269
+ }
1249
1270
  // Schedule downstream execution
1250
1271
  this.executeDownstream(edge.target.nodeId, effectiveRunContexts);
1251
1272
  }
@@ -1302,7 +1323,13 @@ class EdgePropagator {
1302
1323
  * Set target input value and emit event
1303
1324
  */
1304
1325
  setTargetInput(edge, dstNode, value) {
1326
+ const now = Date.now();
1305
1327
  dstNode.inputs[edge.target.handle] = value;
1328
+ // Track when this input was set
1329
+ if (!dstNode.lastInputAt) {
1330
+ dstNode.lastInputAt = {};
1331
+ }
1332
+ dstNode.lastInputAt[edge.target.handle] = now;
1306
1333
  this.eventEmitter.emit("value", {
1307
1334
  nodeId: edge.target.nodeId,
1308
1335
  handle: edge.target.handle,
@@ -1879,11 +1906,15 @@ class NodeExecutor {
1879
1906
  : undefined;
1880
1907
  if (!hadError)
1881
1908
  node.stats.lastError = undefined;
1882
- // Only emit node-done if not cancelled (cancellation events emitted separately)
1909
+ // Track successful completion time (for detecting stale inputs)
1883
1910
  const isCancelled = controller.signal.aborted &&
1884
1911
  (controller.signal.reason === "snapshot" ||
1885
1912
  controller.signal.reason === "node-deleted" ||
1886
1913
  controller.signal.reason === "user-cancelled");
1914
+ if (!hadError && !isCancelled) {
1915
+ node.lastSuccessAt = Date.now();
1916
+ }
1917
+ // Only emit node-done if not cancelled (cancellation events emitted separately)
1887
1918
  if (!isCancelled) {
1888
1919
  this.eventEmitter.emit("stats", {
1889
1920
  kind: "node-done",
@@ -2051,6 +2082,8 @@ class GraphRuntime {
2051
2082
  progress: 0,
2052
2083
  },
2053
2084
  activeRunContextIds: new Set(),
2085
+ lastInputAt: {},
2086
+ lastSuccessAt: undefined,
2054
2087
  };
2055
2088
  gr.graph.setNode(n.nodeId, rn);
2056
2089
  }
@@ -2404,6 +2437,8 @@ class GraphRuntime {
2404
2437
  progress: 0,
2405
2438
  },
2406
2439
  activeRunContextIds: new Set(),
2440
+ lastInputAt: {},
2441
+ lastSuccessAt: undefined,
2407
2442
  };
2408
2443
  this.graph.setNode(n.nodeId, newNode);
2409
2444
  const effectiveInputs = this.nodeExecutor.getEffectiveInputs(newNode.nodeId);
@@ -2550,8 +2585,17 @@ class GraphRuntime {
2550
2585
  const afterTargetSet = afterMap.get(handle) ?? new Set();
2551
2586
  if (!setsEqual(beforeTargetSet, afterTargetSet)) {
2552
2587
  const val = this.getOutput(nodeId, handle);
2553
- if (val !== undefined)
2554
- this.propagate(nodeId, handle, val);
2588
+ if (val !== undefined) {
2589
+ let runContextIdsToUse = undefined;
2590
+ if (this.runMode === "manual") {
2591
+ runContextIdsToUse = new Set([
2592
+ this.runContextManager.createRunContext(nodeId, undefined, {
2593
+ propagate: false,
2594
+ }),
2595
+ ]);
2596
+ }
2597
+ this.propagate(nodeId, handle, val, runContextIdsToUse);
2598
+ }
2555
2599
  }
2556
2600
  }
2557
2601
  }