@bian-womp/spark-graph 0.1.27 → 0.1.29

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,22 @@ class GraphRuntime {
409
409
  this.environment = {};
410
410
  this.paused = false;
411
411
  }
412
+ // Shallow/deep-ish equality to avoid unnecessary runs on identical values
413
+ valuesEqual(a, b) {
414
+ if (a === b)
415
+ return true;
416
+ if (typeof a !== typeof b)
417
+ return false;
418
+ if (a && b && typeof a === "object") {
419
+ try {
420
+ return JSON.stringify(a) === JSON.stringify(b);
421
+ }
422
+ catch {
423
+ return false;
424
+ }
425
+ }
426
+ return false;
427
+ }
412
428
  static create(def, registry, opts) {
413
429
  const gr = new GraphRuntime();
414
430
  gr.environment = opts?.environment ?? {};
@@ -545,17 +561,23 @@ class GraphRuntime {
545
561
  const node = this.nodes.get(nodeId);
546
562
  if (!node)
547
563
  throw new Error(`Node not found: ${nodeId}`);
564
+ let anyChanged = false;
548
565
  for (const [handle, value] of Object.entries(inputs)) {
549
566
  const hasInbound = this.edges.some((e) => e.target.nodeId === nodeId && e.target.handle === handle);
550
567
  if (hasInbound)
551
568
  continue;
552
- node.inputs[handle] = value;
553
- // Emit value event for input updates
554
- this.emit("value", { nodeId, handle, value, io: "input" });
569
+ const prev = node.inputs[handle];
570
+ const same = this.valuesEqual(prev, value);
571
+ if (!same) {
572
+ node.inputs[handle] = value;
573
+ // Emit value event for input updates
574
+ this.emit("value", { nodeId, handle, value, io: "input" });
575
+ anyChanged = true;
576
+ }
555
577
  }
556
578
  if (!this.paused) {
557
579
  // Only schedule if all inbound inputs are present (or there are none)
558
- if (this.allInboundHaveValue(nodeId))
580
+ if (anyChanged && this.allInboundHaveValue(nodeId))
559
581
  this.scheduleInputsChanged(nodeId);
560
582
  }
561
583
  }
@@ -580,7 +602,7 @@ class GraphRuntime {
580
602
  throw new Error(`Typed output required for union source; allowed: ${srcTypes.join("|")}`);
581
603
  if (!srcTypes.includes(typeId))
582
604
  throw new Error(`Invalid typed output ${typeId}; allowed: ${srcTypes.join("|")}`);
583
- const payload = typeId;
605
+ const payload = getTypedOutputValue(v);
584
606
  const res = registry.resolveCoercion(typeId, dstDeclared);
585
607
  if (!res)
586
608
  return payload;
@@ -596,7 +618,7 @@ class GraphRuntime {
596
618
  throw new Error(`Typed output required for union source; allowed: ${srcTypes.join("|")}`);
597
619
  if (!srcTypes.includes(typeId))
598
620
  throw new Error(`Invalid typed output ${typeId}; allowed: ${srcTypes.join("|")}`);
599
- const payload = typeId;
621
+ const payload = getTypedOutputValue(v);
600
622
  const res = registry.resolveCoercion(typeId, dstDeclared);
601
623
  if (!res)
602
624
  return payload;
@@ -812,17 +834,21 @@ class GraphRuntime {
812
834
  const dstNode = this.nodes.get(e.target.nodeId);
813
835
  if (!dstNode)
814
836
  return;
815
- dstNode.inputs[e.target.handle] = v;
816
- // Emit value event for input updates
817
- this.emit("value", {
818
- nodeId: e.target.nodeId,
819
- handle: e.target.handle,
820
- value: v,
821
- io: "input",
822
- runtimeTypeId: getTypedOutputTypeId(v),
823
- });
824
- if (!this.paused && this.allInboundHaveValue(e.target.nodeId))
825
- this.scheduleInputsChanged(e.target.nodeId);
837
+ const prev = dstNode.inputs[e.target.handle];
838
+ const same = this.valuesEqual(prev, v);
839
+ if (!same) {
840
+ dstNode.inputs[e.target.handle] = v;
841
+ // Emit value event for input updates
842
+ this.emit("value", {
843
+ nodeId: e.target.nodeId,
844
+ handle: e.target.handle,
845
+ value: v,
846
+ io: "input",
847
+ runtimeTypeId: getTypedOutputTypeId(v),
848
+ });
849
+ if (!this.paused && this.allInboundHaveValue(e.target.nodeId))
850
+ this.scheduleInputsChanged(e.target.nodeId);
851
+ }
826
852
  };
827
853
  if (e.convertAsync) {
828
854
  // emit edge-start before launching async conversion
@@ -1102,6 +1128,13 @@ class GraphRuntime {
1102
1128
  set.add(e.target.handle);
1103
1129
  prevInbound.set(e.target.nodeId, set);
1104
1130
  }
1131
+ // Capture previous outgoing map before rebuilding edges
1132
+ const prevOutgoing = new Map();
1133
+ for (const e of this.edges) {
1134
+ const set = prevOutgoing.get(e.source.nodeId) ?? new Set();
1135
+ set.add(e.source.handle);
1136
+ prevOutgoing.set(e.source.nodeId, set);
1137
+ }
1105
1138
  // Rebuild edges mapping with coercions
1106
1139
  this.edges = def.edges.map((e) => {
1107
1140
  const srcNode = def.nodes.find((nn) => nn.nodeId === e.source.nodeId);
@@ -1191,9 +1224,34 @@ class GraphRuntime {
1191
1224
  this.scheduleInputsChanged(nodeId);
1192
1225
  }
1193
1226
  }
1194
- // Re-emit existing outputs to populate new edges
1195
- for (const nodeId of this.nodes.keys()) {
1196
- this.reemitNodeOutputs(nodeId);
1227
+ // Re-emit outputs only for sources whose outgoing edge set changed
1228
+ const nextOutgoing = new Map();
1229
+ for (const e of this.edges) {
1230
+ const set = nextOutgoing.get(e.source.nodeId) ?? new Set();
1231
+ set.add(e.source.handle);
1232
+ nextOutgoing.set(e.source.nodeId, set);
1233
+ }
1234
+ const setsEqual = (a, b) => {
1235
+ if (!a && !b)
1236
+ return true;
1237
+ if (!a || !b)
1238
+ return false;
1239
+ if (a.size !== b.size)
1240
+ return false;
1241
+ for (const v of a)
1242
+ if (!b.has(v))
1243
+ return false;
1244
+ return true;
1245
+ };
1246
+ const reemitCandidates = new Set([
1247
+ ...Array.from(prevOutgoing.keys()),
1248
+ ...Array.from(nextOutgoing.keys()),
1249
+ ]);
1250
+ for (const nodeId of reemitCandidates) {
1251
+ const prev = prevOutgoing.get(nodeId);
1252
+ const next = nextOutgoing.get(nodeId);
1253
+ if (!setsEqual(prev, next))
1254
+ this.reemitNodeOutputs(nodeId);
1197
1255
  }
1198
1256
  }
1199
1257
  }