@bian-womp/spark-graph 0.2.30 → 0.2.32

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 (33) hide show
  1. package/lib/cjs/index.cjs +83 -54
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/examples/async.d.ts +1 -1
  4. package/lib/cjs/src/examples/async.d.ts.map +1 -1
  5. package/lib/cjs/src/plugins/composite.d.ts +1 -1
  6. package/lib/cjs/src/plugins/composite.d.ts.map +1 -1
  7. package/lib/cjs/src/runtime/AbstractEngine.d.ts +1 -1
  8. package/lib/cjs/src/runtime/AbstractEngine.d.ts.map +1 -1
  9. package/lib/cjs/src/runtime/Engine.d.ts +1 -1
  10. package/lib/cjs/src/runtime/Engine.d.ts.map +1 -1
  11. package/lib/cjs/src/runtime/GraphRuntime.d.ts +1 -1
  12. package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/LocalRunner.d.ts +1 -1
  14. package/lib/cjs/src/runtime/LocalRunner.d.ts.map +1 -1
  15. package/lib/cjs/src/runtime/RunnerControl.d.ts +1 -1
  16. package/lib/cjs/src/runtime/RunnerControl.d.ts.map +1 -1
  17. package/lib/esm/index.js +83 -54
  18. package/lib/esm/index.js.map +1 -1
  19. package/lib/esm/src/examples/async.d.ts +1 -1
  20. package/lib/esm/src/examples/async.d.ts.map +1 -1
  21. package/lib/esm/src/plugins/composite.d.ts +1 -1
  22. package/lib/esm/src/plugins/composite.d.ts.map +1 -1
  23. package/lib/esm/src/runtime/AbstractEngine.d.ts +1 -1
  24. package/lib/esm/src/runtime/AbstractEngine.d.ts.map +1 -1
  25. package/lib/esm/src/runtime/Engine.d.ts +1 -1
  26. package/lib/esm/src/runtime/Engine.d.ts.map +1 -1
  27. package/lib/esm/src/runtime/GraphRuntime.d.ts +1 -1
  28. package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
  29. package/lib/esm/src/runtime/LocalRunner.d.ts +1 -1
  30. package/lib/esm/src/runtime/LocalRunner.d.ts.map +1 -1
  31. package/lib/esm/src/runtime/RunnerControl.d.ts +1 -1
  32. package/lib/esm/src/runtime/RunnerControl.d.ts.map +1 -1
  33. package/package.json +2 -2
package/lib/cjs/index.cjs CHANGED
@@ -573,57 +573,67 @@ class GraphRuntime {
573
573
  return node?.outputs[output];
574
574
  }
575
575
  static buildEdgeConverters(srcDeclared, dstDeclared, registry) {
576
- let convert;
577
- let convertAsync;
578
- if (dstDeclared && srcDeclared) {
579
- if (Array.isArray(srcDeclared)) {
580
- const srcTypes = srcDeclared;
581
- const anyAsync = srcTypes.some((s) => {
582
- const res = registry.resolveCoercion(s, dstDeclared);
583
- return res?.kind === "async";
584
- });
585
- if (anyAsync) {
586
- convertAsync = async (v, signal) => {
587
- const typeId = getTypedOutputTypeId(v);
588
- if (!typeId)
589
- throw new Error(`Typed output required for union source; allowed: ${srcTypes.join("|")}`);
590
- if (!srcTypes.includes(typeId))
591
- throw new Error(`Invalid typed output ${typeId}; allowed: ${srcTypes.join("|")}`);
592
- const payload = getTypedOutputValue(v);
593
- const res = registry.resolveCoercion(typeId, dstDeclared);
594
- if (!res)
595
- return payload;
596
- if (res.kind === "async")
597
- return await res.convertAsync(payload, signal);
598
- return res.convert(payload);
599
- };
576
+ if (!dstDeclared || !srcDeclared) {
577
+ return {};
578
+ }
579
+ const isUnion = Array.isArray(srcDeclared);
580
+ const srcTypes = isUnion ? srcDeclared : [srcDeclared];
581
+ // Helper to get the coercion for a specific type
582
+ const getCoercion = (typeId) => {
583
+ return registry.resolveCoercion(typeId, dstDeclared);
584
+ };
585
+ // Resolve coercions for all source types
586
+ const coercions = srcTypes.map(getCoercion);
587
+ const hasAsync = coercions.some((r) => r?.kind === "async");
588
+ // Helper to extract and validate typed output for unions
589
+ const extractPayload = (v) => {
590
+ const typeId = getTypedOutputTypeId(v);
591
+ const payload = getTypedOutputValue(v);
592
+ if (isUnion) {
593
+ if (!typeId) {
594
+ throw new Error(`Typed output required for union source; allowed: ${srcTypes.join("|")}`);
600
595
  }
601
- else {
602
- convert = (v) => {
603
- const typeId = getTypedOutputTypeId(v);
604
- if (!typeId)
605
- throw new Error(`Typed output required for union source; allowed: ${srcTypes.join("|")}`);
606
- if (!srcTypes.includes(typeId))
607
- throw new Error(`Invalid typed output ${typeId}; allowed: ${srcTypes.join("|")}`);
608
- const payload = getTypedOutputValue(v);
609
- const res = registry.resolveCoercion(typeId, dstDeclared);
610
- if (!res)
611
- return payload;
612
- if (res.kind === "async")
613
- throw new Error("Async coercion required but convert used");
614
- return res.convert(payload);
615
- };
596
+ if (!srcTypes.includes(typeId)) {
597
+ throw new Error(`Invalid typed output ${typeId}; allowed: ${srcTypes.join("|")}`);
616
598
  }
617
599
  }
618
- else {
619
- const res = registry.resolveCoercion(srcDeclared, dstDeclared);
620
- if (res?.kind === "async")
621
- convertAsync = res.convertAsync;
622
- else if (res?.kind === "sync")
623
- convert = res.convert;
600
+ else if (typeId) {
601
+ // Warn if typed output is used for non-union source
602
+ console.warn(`Typed output ${typeId} is fed even though source is not union: ${srcDeclared} -> ${dstDeclared}`);
624
603
  }
604
+ return { typeId: typeId || srcTypes[0], payload };
605
+ };
606
+ if (hasAsync) {
607
+ return {
608
+ convertAsync: async (v, signal) => {
609
+ const { typeId, payload } = extractPayload(v);
610
+ const res = getCoercion(typeId);
611
+ if (!res)
612
+ return payload;
613
+ if (res.kind === "async") {
614
+ return await res.convertAsync(payload, signal);
615
+ }
616
+ return res.convert(payload);
617
+ },
618
+ };
625
619
  }
626
- return { convert, convertAsync };
620
+ // Sync path
621
+ const firstCoercion = coercions.find((r) => r?.kind === "sync");
622
+ if (!firstCoercion) {
623
+ return {};
624
+ }
625
+ return {
626
+ convert: (v) => {
627
+ const { typeId, payload } = extractPayload(v);
628
+ const res = getCoercion(typeId);
629
+ if (!res)
630
+ return payload;
631
+ if (res.kind === "async") {
632
+ throw new Error("Async coercion required but convert used");
633
+ }
634
+ return res.convert(payload);
635
+ },
636
+ };
627
637
  }
628
638
  scheduleInputsChanged(nodeId) {
629
639
  const node = this.nodes.get(nodeId);
@@ -827,6 +837,9 @@ class GraphRuntime {
827
837
  const dstNode = this.nodes.get(e.target.nodeId);
828
838
  if (!dstNode)
829
839
  return;
840
+ // Skip writing to unresolved handles - wait for handle resolution
841
+ if (e.dstDeclared === undefined)
842
+ return;
830
843
  const dstIsArray = typeof e.dstDeclared === "string" && e.dstDeclared.endsWith("[]");
831
844
  let next = v;
832
845
  // If target input is an array type, merge per-edge contributions deterministically
@@ -1035,6 +1048,7 @@ class GraphRuntime {
1035
1048
  for (const e of this.edges) {
1036
1049
  let srcDeclared = e.effectiveTypeId; // Use effectiveTypeId as fallback
1037
1050
  let dstDeclared = e.dstDeclared;
1051
+ const oldDstDeclared = dstDeclared; // Track old value to detect resolution
1038
1052
  if (e.source.nodeId === nodeId) {
1039
1053
  const resolved = this.resolvedByNode.get(nodeId);
1040
1054
  srcDeclared = resolved
@@ -1057,11 +1071,24 @@ class GraphRuntime {
1057
1071
  const conv = GraphRuntime.buildEdgeConverters(srcDeclared, dstDeclared, registry);
1058
1072
  e.convert = conv.convert;
1059
1073
  e.convertAsync = conv.convertAsync;
1074
+ // If target handle was just resolved (was undefined, now has a type), re-propagate values
1075
+ if (e.target.nodeId === nodeId &&
1076
+ oldDstDeclared === undefined &&
1077
+ dstDeclared !== undefined) {
1078
+ const srcNode = this.nodes.get(e.source.nodeId);
1079
+ if (srcNode) {
1080
+ const srcValue = srcNode.outputs[e.source.handle];
1081
+ if (srcValue !== undefined) {
1082
+ // Re-propagate through the now-resolved edge converter
1083
+ this.propagate(e.source.nodeId, e.source.handle, srcValue);
1084
+ }
1085
+ }
1086
+ }
1060
1087
  }
1061
1088
  // Invalidate downstream for this node so UI refreshes
1062
1089
  this.invalidateDownstream(nodeId);
1063
1090
  }
1064
- launch() {
1091
+ launch(invalidate = false) {
1065
1092
  // call onActivated for nodes that implement it
1066
1093
  for (const node of this.nodes.values()) {
1067
1094
  const ctrl = new AbortController();
@@ -1085,10 +1112,12 @@ class GraphRuntime {
1085
1112
  });
1086
1113
  node.runtime.onActivated?.(ctx);
1087
1114
  }
1088
- // After activation, schedule nodes that have all inbound inputs present
1089
- for (const nodeId of this.nodes.keys()) {
1090
- if (this.allInboundHaveValue(nodeId))
1091
- this.scheduleInputsChanged(nodeId);
1115
+ if (invalidate) {
1116
+ // After activation, schedule nodes that have all inbound inputs present
1117
+ for (const nodeId of this.nodes.keys()) {
1118
+ if (this.allInboundHaveValue(nodeId))
1119
+ this.scheduleInputsChanged(nodeId);
1120
+ }
1092
1121
  }
1093
1122
  }
1094
1123
  triggerExternal(nodeId, event) {
@@ -1874,11 +1903,11 @@ class AbstractEngine {
1874
1903
  constructor(graphRuntime) {
1875
1904
  this.graphRuntime = graphRuntime;
1876
1905
  }
1877
- launch() {
1878
- this.graphRuntime.launch();
1906
+ launch(invalidate = false) {
1907
+ this.graphRuntime.launch(invalidate);
1879
1908
  }
1880
1909
  setInput(nodeId, handle, value) {
1881
- this.setInputs(nodeId, { [handle]: value });
1910
+ this.graphRuntime.setInputs(nodeId, { [handle]: value });
1882
1911
  }
1883
1912
  setInputs(nodeId, inputs) {
1884
1913
  this.graphRuntime.setInputs(nodeId, inputs);