@bian-womp/spark-graph 0.2.10 → 0.2.12

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
@@ -409,6 +409,10 @@ class GraphRuntime {
409
409
  this.listeners = new Map();
410
410
  this.environment = {};
411
411
  this.paused = false;
412
+ // For array-typed target inputs, keep per-edge contributions so successive runs
413
+ // from the same source replace their slice instead of accumulating forever.
414
+ // Structure: nodeId -> handle -> edgeId -> values[]
415
+ this.arrayInputBuckets = new Map();
412
416
  }
413
417
  // Shallow/deep-ish equality to avoid unnecessary runs on identical values
414
418
  valuesEqual(a, b) {
@@ -842,11 +846,31 @@ class GraphRuntime {
842
846
  return;
843
847
  const dstIsArray = typeof e.dstDeclared === "string" && e.dstDeclared.endsWith("[]");
844
848
  let next = v;
845
- // If target input is an array type, append incoming values instead of last-write wins
849
+ // If target input is an array type, merge per-edge contributions deterministically
846
850
  if (dstIsArray) {
847
851
  const toArray = (x) => Array.isArray(x) ? x : x === undefined ? [] : [x];
848
- const prev = dstNode.inputs[e.target.handle];
849
- const merged = [...toArray(prev), ...toArray(v)];
852
+ // Update this edge's contribution
853
+ let forNode = this.arrayInputBuckets.get(e.target.nodeId);
854
+ if (!forNode) {
855
+ forNode = new Map();
856
+ this.arrayInputBuckets.set(e.target.nodeId, forNode);
857
+ }
858
+ let forHandle = forNode.get(e.target.handle);
859
+ if (!forHandle) {
860
+ forHandle = new Map();
861
+ forNode.set(e.target.handle, forHandle);
862
+ }
863
+ forHandle.set(e.id, toArray(v));
864
+ // Compute merged array in the order of current edges list
865
+ const merged = [];
866
+ for (const ed of this.edges) {
867
+ if (ed.target.nodeId === e.target.nodeId &&
868
+ ed.target.handle === e.target.handle) {
869
+ const part = forHandle.get(ed.id);
870
+ if (part && part.length)
871
+ merged.push(...part);
872
+ }
873
+ }
850
874
  next = merged;
851
875
  }
852
876
  const prev = dstNode.inputs[e.target.handle];
@@ -997,6 +1021,7 @@ class GraphRuntime {
997
1021
  this.nodes.clear();
998
1022
  this.edges = [];
999
1023
  this.listeners.clear();
1024
+ this.arrayInputBuckets.clear();
1000
1025
  }
1001
1026
  getNodeIds() {
1002
1027
  return Array.from(this.nodes.keys());
@@ -1054,6 +1079,60 @@ class GraphRuntime {
1054
1079
  __unsafe_scheduleInputsChanged(nodeId) {
1055
1080
  this.scheduleInputsChanged(nodeId);
1056
1081
  }
1082
+ // Hydrate inputs/outputs without triggering computation; optionally re-emit outputs downstream
1083
+ hydrate(payload, opts) {
1084
+ const prevPaused = this.paused;
1085
+ this.paused = true;
1086
+ try {
1087
+ const ins = payload?.inputs || {};
1088
+ for (const [nodeId, map] of Object.entries(ins)) {
1089
+ const node = this.nodes.get(nodeId);
1090
+ if (!node)
1091
+ continue;
1092
+ for (const [h, v] of Object.entries(map || {})) {
1093
+ node.inputs[h] =
1094
+ typeof structuredClone === "function"
1095
+ ? structuredClone(v)
1096
+ : JSON.parse(JSON.stringify(v));
1097
+ // emit input value event
1098
+ this.emit("value", {
1099
+ nodeId,
1100
+ handle: h,
1101
+ value: node.inputs[h],
1102
+ io: "input",
1103
+ runtimeTypeId: getTypedOutputTypeId(node.inputs[h]),
1104
+ });
1105
+ }
1106
+ }
1107
+ const outs = payload?.outputs || {};
1108
+ for (const [nodeId, map] of Object.entries(outs)) {
1109
+ const node = this.nodes.get(nodeId);
1110
+ if (!node)
1111
+ continue;
1112
+ for (const [h, v] of Object.entries(map || {})) {
1113
+ node.outputs[h] =
1114
+ typeof structuredClone === "function"
1115
+ ? structuredClone(v)
1116
+ : JSON.parse(JSON.stringify(v));
1117
+ // emit output value event
1118
+ this.emit("value", {
1119
+ nodeId,
1120
+ handle: h,
1121
+ value: node.outputs[h],
1122
+ io: "output",
1123
+ runtimeTypeId: getTypedOutputTypeId(node.outputs[h]),
1124
+ });
1125
+ }
1126
+ }
1127
+ if (opts?.reemit) {
1128
+ for (const nodeId of this.nodes.keys())
1129
+ this.reemitNodeOutputs(nodeId);
1130
+ }
1131
+ }
1132
+ finally {
1133
+ this.paused = prevPaused;
1134
+ }
1135
+ }
1057
1136
  // Incrementally update nodes/edges to match new definition without full rebuild
1058
1137
  update(def, registry) {
1059
1138
  // Handle node additions and removals
@@ -1070,6 +1149,8 @@ class GraphRuntime {
1070
1149
  setState: (next) => Object.assign(node.state, next),
1071
1150
  });
1072
1151
  this.nodes.delete(nodeId);
1152
+ // Clear any array buckets for this node
1153
+ this.arrayInputBuckets.delete(nodeId);
1073
1154
  }
1074
1155
  }
1075
1156
  // Add or update existing nodes
@@ -1247,6 +1328,16 @@ class GraphRuntime {
1247
1328
  }
1248
1329
  }
1249
1330
  }
1331
+ // Clear buckets for handles that lost inbound
1332
+ const bucketsForNode = this.arrayInputBuckets.get(nodeId);
1333
+ if (bucketsForNode) {
1334
+ for (const handle of Array.from(prevSet)) {
1335
+ if (!currSet.has(handle))
1336
+ bucketsForNode.delete(handle);
1337
+ }
1338
+ if (bucketsForNode.size === 0)
1339
+ this.arrayInputBuckets.delete(nodeId);
1340
+ }
1250
1341
  this.scheduleInputsChanged(nodeId);
1251
1342
  }
1252
1343
  }
@@ -1294,6 +1385,29 @@ class GraphRuntime {
1294
1385
  }
1295
1386
  }
1296
1387
  }
1388
+ // Prune array bucket contributions for edges that no longer exist
1389
+ const validPerTarget = new Map();
1390
+ for (const ed of this.edges) {
1391
+ const m = validPerTarget.get(ed.target.nodeId) ?? new Map();
1392
+ const s = m.get(ed.target.handle) ?? new Set();
1393
+ s.add(ed.id);
1394
+ m.set(ed.target.handle, s);
1395
+ validPerTarget.set(ed.target.nodeId, m);
1396
+ }
1397
+ for (const [nodeId, byHandle] of Array.from(this.arrayInputBuckets)) {
1398
+ const validHandles = validPerTarget.get(nodeId) ?? new Map();
1399
+ for (const [handle, perEdge] of Array.from(byHandle)) {
1400
+ const validEdgeIds = validHandles.get(handle) ?? new Set();
1401
+ for (const edgeId of Array.from(perEdge.keys())) {
1402
+ if (!validEdgeIds.has(edgeId))
1403
+ perEdge.delete(edgeId);
1404
+ }
1405
+ if (perEdge.size === 0)
1406
+ byHandle.delete(handle);
1407
+ }
1408
+ if (byHandle.size === 0)
1409
+ this.arrayInputBuckets.delete(nodeId);
1410
+ }
1297
1411
  }
1298
1412
  }
1299
1413