@bian-womp/spark-graph 0.3.7 → 0.3.9

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 (31) hide show
  1. package/lib/cjs/index.cjs +138 -12
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/core/type-utils.d.ts +18 -0
  4. package/lib/cjs/src/core/type-utils.d.ts.map +1 -0
  5. package/lib/cjs/src/core/types.d.ts +0 -6
  6. package/lib/cjs/src/core/types.d.ts.map +1 -1
  7. package/lib/cjs/src/index.d.ts +1 -1
  8. package/lib/cjs/src/index.d.ts.map +1 -1
  9. package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
  10. package/lib/cjs/src/runtime/components/EdgePropagator.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/components/HandleResolver.d.ts.map +1 -1
  12. package/lib/cjs/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/components/graph-utils.d.ts.map +1 -1
  14. package/lib/cjs/src/runtime/components/types.d.ts +3 -1
  15. package/lib/cjs/src/runtime/components/types.d.ts.map +1 -1
  16. package/lib/esm/index.js +137 -13
  17. package/lib/esm/index.js.map +1 -1
  18. package/lib/esm/src/core/type-utils.d.ts +18 -0
  19. package/lib/esm/src/core/type-utils.d.ts.map +1 -0
  20. package/lib/esm/src/core/types.d.ts +0 -6
  21. package/lib/esm/src/core/types.d.ts.map +1 -1
  22. package/lib/esm/src/index.d.ts +1 -1
  23. package/lib/esm/src/index.d.ts.map +1 -1
  24. package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
  25. package/lib/esm/src/runtime/components/EdgePropagator.d.ts.map +1 -1
  26. package/lib/esm/src/runtime/components/HandleResolver.d.ts.map +1 -1
  27. package/lib/esm/src/runtime/components/NodeExecutor.d.ts.map +1 -1
  28. package/lib/esm/src/runtime/components/graph-utils.d.ts.map +1 -1
  29. package/lib/esm/src/runtime/components/types.d.ts +3 -1
  30. package/lib/esm/src/runtime/components/types.d.ts.map +1 -1
  31. package/package.json +2 -2
package/lib/cjs/index.cjs CHANGED
@@ -28,6 +28,46 @@ function isInputPrivate(inputs, handle) {
28
28
  const v = inputs ? inputs[handle] : undefined;
29
29
  return !!(v && typeof v === "object" && v.private);
30
30
  }
31
+ /**
32
+ * Merge two InputHandleDescriptor values, with dynamic taking precedence.
33
+ * If both have metadata, merge the metadata objects (dynamic overrides static).
34
+ */
35
+ function mergeInputHandleDescriptors(staticDesc, dynamicDesc) {
36
+ // If only one exists, return it
37
+ if (!staticDesc)
38
+ return dynamicDesc;
39
+ if (!dynamicDesc)
40
+ return staticDesc;
41
+ // If both are strings, dynamic wins
42
+ if (typeof staticDesc === "string" && typeof dynamicDesc === "string") {
43
+ return dynamicDesc;
44
+ }
45
+ const staticObj = typeof staticDesc === "string" ? { typeId: staticDesc } : staticDesc;
46
+ const dynamicObj = typeof dynamicDesc === "string" ? { typeId: dynamicDesc } : dynamicDesc;
47
+ // Merge: dynamic takes precedence, but merge metadata objects
48
+ const merged = {
49
+ typeId: dynamicObj.typeId ?? staticObj.typeId,
50
+ private: dynamicObj.private ?? staticObj.private,
51
+ };
52
+ // Merge metadata if either has it
53
+ if (staticObj.metadata || dynamicObj.metadata) {
54
+ merged.metadata = {
55
+ ...staticObj.metadata,
56
+ ...dynamicObj.metadata, // Dynamic metadata overrides static
57
+ };
58
+ }
59
+ // Return as InputHandleDescriptor (which accepts object form)
60
+ return merged;
61
+ }
62
+ /**
63
+ * Extract metadata from an InputHandleDescriptor
64
+ */
65
+ function getInputHandleMetadata(inputs, handle) {
66
+ const v = inputs ? inputs[handle] : undefined;
67
+ if (!v || typeof v === "string")
68
+ return undefined;
69
+ return v.metadata;
70
+ }
31
71
 
32
72
  class CategoryRegistry {
33
73
  constructor() {
@@ -752,11 +792,34 @@ function tryHandleResolving(def, registry, environment) {
752
792
  // ignore dynamic resolution errors at this stage
753
793
  }
754
794
  // Merge base with dynamic and overrides (allow partial resolvedHandles)
755
- const inputs = {
756
- ...desc.inputs,
757
- ...dyn.inputs,
758
- ...overrideInputs,
759
- };
795
+ // Merge inputs properly, handling metadata
796
+ const inputs = {};
797
+ // First, add all static handles
798
+ if (desc.inputs) {
799
+ for (const [handle, staticDesc] of Object.entries(desc.inputs)) {
800
+ inputs[handle] = staticDesc;
801
+ }
802
+ }
803
+ // Then, merge dynamic handles
804
+ if (dyn.inputs) {
805
+ for (const [handle, dynamicDesc] of Object.entries(dyn.inputs)) {
806
+ const staticDesc = desc.inputs?.[handle];
807
+ const merged = mergeInputHandleDescriptors(staticDesc, dynamicDesc);
808
+ if (merged) {
809
+ inputs[handle] = merged;
810
+ }
811
+ }
812
+ }
813
+ // Finally, merge overrides
814
+ if (overrideInputs) {
815
+ for (const [handle, overrideDesc] of Object.entries(overrideInputs)) {
816
+ const existingDesc = inputs[handle];
817
+ const merged = mergeInputHandleDescriptors(existingDesc, overrideDesc);
818
+ if (merged) {
819
+ inputs[handle] = merged;
820
+ }
821
+ }
822
+ }
760
823
  const outputs = {
761
824
  ...desc.outputs,
762
825
  ...dyn.outputs,
@@ -1022,7 +1085,24 @@ class HandleResolver {
1022
1085
  const nodeDesc = this.registry.nodes.get(node.typeId);
1023
1086
  if (!nodeDesc)
1024
1087
  return;
1025
- const inputs = { ...nodeDesc.inputs, ...resolved?.inputs };
1088
+ // Merge inputs properly, handling metadata
1089
+ const inputs = {};
1090
+ // First, add all static handles
1091
+ if (nodeDesc.inputs) {
1092
+ for (const [handle, staticDesc] of Object.entries(nodeDesc.inputs)) {
1093
+ inputs[handle] = staticDesc;
1094
+ }
1095
+ }
1096
+ // Then, merge dynamic handles
1097
+ if (resolved?.inputs) {
1098
+ for (const [handle, dynamicDesc] of Object.entries(resolved.inputs)) {
1099
+ const staticDesc = nodeDesc.inputs?.[handle];
1100
+ const merged = mergeInputHandleDescriptors(staticDesc, dynamicDesc);
1101
+ if (merged) {
1102
+ inputs[handle] = merged;
1103
+ }
1104
+ }
1105
+ }
1026
1106
  const outputs = { ...nodeDesc.outputs, ...resolved?.outputs };
1027
1107
  const inputDefaults = {
1028
1108
  ...nodeDesc.inputDefaults,
@@ -1238,14 +1318,35 @@ class EdgePropagator {
1238
1318
  const processedValue = this.processArrayInput(edge, value);
1239
1319
  // Check if value changed
1240
1320
  const prev = dstNode.inputs[edge.target.handle];
1241
- if (valuesEqual(prev, processedValue)) {
1242
- return; // No change
1321
+ const valueChanged = !valuesEqual(prev, processedValue);
1322
+ // Check if we should execute even if value is same:
1323
+ // 1. If node has rerunOnSameInput policy (from node params or registry)
1324
+ // 2. If input was set after last successful run (stale input detection)
1325
+ const registry = this.graph.getRegistry();
1326
+ const desc = registry?.nodes.get(dstNode.typeId);
1327
+ const nodeRerunPolicy = dstNode.policy?.rerunOnSameInput === true;
1328
+ const descRerunPolicy = desc?.policy?.rerunOnSameInput === true;
1329
+ const shouldRerunOnSameInput = nodeRerunPolicy || descRerunPolicy;
1330
+ const inputWasSetAfterLastRun = dstNode.lastInputAt?.[edge.target.handle] &&
1331
+ dstNode.lastSuccessAt &&
1332
+ dstNode.lastInputAt[edge.target.handle] > dstNode.lastSuccessAt;
1333
+ const shouldExecute = valueChanged || shouldRerunOnSameInput || inputWasSetAfterLastRun;
1334
+ if (!shouldExecute) {
1335
+ return; // No change and no reason to rerun
1243
1336
  }
1244
1337
  // Set input value (respecting skipPropagateValues)
1245
1338
  const shouldSetValue = this.shouldSetInputValue(effectiveRunContexts);
1246
- if (shouldSetValue) {
1339
+ if (shouldSetValue && valueChanged) {
1247
1340
  this.setTargetInput(edge, dstNode, processedValue);
1248
1341
  }
1342
+ else if (shouldSetValue && !valueChanged) {
1343
+ // Even if value didn't change, update timestamp if we're forcing execution
1344
+ const now = Date.now();
1345
+ if (!dstNode.lastInputAt) {
1346
+ dstNode.lastInputAt = {};
1347
+ }
1348
+ dstNode.lastInputAt[edge.target.handle] = now;
1349
+ }
1249
1350
  // Schedule downstream execution
1250
1351
  this.executeDownstream(edge.target.nodeId, effectiveRunContexts);
1251
1352
  }
@@ -1302,7 +1403,13 @@ class EdgePropagator {
1302
1403
  * Set target input value and emit event
1303
1404
  */
1304
1405
  setTargetInput(edge, dstNode, value) {
1406
+ const now = Date.now();
1305
1407
  dstNode.inputs[edge.target.handle] = value;
1408
+ // Track when this input was set
1409
+ if (!dstNode.lastInputAt) {
1410
+ dstNode.lastInputAt = {};
1411
+ }
1412
+ dstNode.lastInputAt[edge.target.handle] = now;
1306
1413
  this.eventEmitter.emit("value", {
1307
1414
  nodeId: edge.target.nodeId,
1308
1415
  handle: edge.target.handle,
@@ -1879,11 +1986,15 @@ class NodeExecutor {
1879
1986
  : undefined;
1880
1987
  if (!hadError)
1881
1988
  node.stats.lastError = undefined;
1882
- // Only emit node-done if not cancelled (cancellation events emitted separately)
1989
+ // Track successful completion time (for detecting stale inputs)
1883
1990
  const isCancelled = controller.signal.aborted &&
1884
1991
  (controller.signal.reason === "snapshot" ||
1885
1992
  controller.signal.reason === "node-deleted" ||
1886
1993
  controller.signal.reason === "user-cancelled");
1994
+ if (!hadError && !isCancelled) {
1995
+ node.lastSuccessAt = Date.now();
1996
+ }
1997
+ // Only emit node-done if not cancelled (cancellation events emitted separately)
1887
1998
  if (!isCancelled) {
1888
1999
  this.eventEmitter.emit("stats", {
1889
2000
  kind: "node-done",
@@ -2051,6 +2162,8 @@ class GraphRuntime {
2051
2162
  progress: 0,
2052
2163
  },
2053
2164
  activeRunContextIds: new Set(),
2165
+ lastInputAt: {},
2166
+ lastSuccessAt: undefined,
2054
2167
  };
2055
2168
  gr.graph.setNode(n.nodeId, rn);
2056
2169
  }
@@ -2404,6 +2517,8 @@ class GraphRuntime {
2404
2517
  progress: 0,
2405
2518
  },
2406
2519
  activeRunContextIds: new Set(),
2520
+ lastInputAt: {},
2521
+ lastSuccessAt: undefined,
2407
2522
  };
2408
2523
  this.graph.setNode(n.nodeId, newNode);
2409
2524
  const effectiveInputs = this.nodeExecutor.getEffectiveInputs(newNode.nodeId);
@@ -2550,8 +2665,17 @@ class GraphRuntime {
2550
2665
  const afterTargetSet = afterMap.get(handle) ?? new Set();
2551
2666
  if (!setsEqual(beforeTargetSet, afterTargetSet)) {
2552
2667
  const val = this.getOutput(nodeId, handle);
2553
- if (val !== undefined)
2554
- this.propagate(nodeId, handle, val);
2668
+ if (val !== undefined) {
2669
+ let runContextIdsToUse = undefined;
2670
+ if (this.runMode === "manual") {
2671
+ runContextIdsToUse = new Set([
2672
+ this.runContextManager.createRunContext(nodeId, undefined, {
2673
+ propagate: false,
2674
+ }),
2675
+ ]);
2676
+ }
2677
+ this.propagate(nodeId, handle, val, runContextIdsToUse);
2678
+ }
2555
2679
  }
2556
2680
  }
2557
2681
  }
@@ -5043,6 +5167,7 @@ exports.createValidationGraphDef = createValidationGraphDef;
5043
5167
  exports.createValidationGraphRegistry = createValidationGraphRegistry;
5044
5168
  exports.findMatchingPaths = findMatchingPaths;
5045
5169
  exports.generateId = generateId;
5170
+ exports.getInputHandleMetadata = getInputHandleMetadata;
5046
5171
  exports.getInputTypeId = getInputTypeId;
5047
5172
  exports.getTypedOutputTypeId = getTypedOutputTypeId;
5048
5173
  exports.getTypedOutputValue = getTypedOutputValue;
@@ -5051,6 +5176,7 @@ exports.installLogging = installLogging;
5051
5176
  exports.isInputPrivate = isInputPrivate;
5052
5177
  exports.isTypedOutput = isTypedOutput;
5053
5178
  exports.mergeGraphDefinitions = mergeGraphDefinitions;
5179
+ exports.mergeInputHandleDescriptors = mergeInputHandleDescriptors;
5054
5180
  exports.mergeInputsOutputs = mergeInputsOutputs;
5055
5181
  exports.mergeRuntimeState = mergeRuntimeState;
5056
5182
  exports.mergeSnapshotData = mergeSnapshotData;