@bian-womp/spark-workbench 0.1.12 → 0.1.14

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
@@ -399,9 +399,7 @@ class GraphRunner {
399
399
  this.runningKind = opts.engine;
400
400
  this.emit("status", { running: true, engine: this.runningKind });
401
401
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
402
- for (const [handle, value] of Object.entries(map)) {
403
- this.engine.setInput(nodeId, handle, value);
404
- }
402
+ this.engine.setInputs(nodeId, map);
405
403
  }
406
404
  return;
407
405
  }
@@ -410,6 +408,31 @@ class GraphRunner {
410
408
  await rc.runner.build(def);
411
409
  // Signal UI after remote build as well
412
410
  this.emit("invalidate", { reason: "graph-built" });
411
+ // Hydrate current remote inputs/outputs (including defaults) into cache
412
+ try {
413
+ const snap = await rc.runner.snapshot();
414
+ for (const [nodeId, map] of Object.entries(snap.inputs || {})) {
415
+ for (const [handle, value] of Object.entries(map || {})) {
416
+ rc.valueCache.set(`${nodeId}.${handle}`, {
417
+ io: "input",
418
+ value,
419
+ });
420
+ this.emit("value", { nodeId, handle, value, io: "input" });
421
+ }
422
+ }
423
+ for (const [nodeId, map] of Object.entries(snap.outputs || {})) {
424
+ for (const [handle, value] of Object.entries(map || {})) {
425
+ rc.valueCache.set(`${nodeId}.${handle}`, {
426
+ io: "output",
427
+ value,
428
+ });
429
+ this.emit("value", { nodeId, handle, value, io: "output" });
430
+ }
431
+ }
432
+ }
433
+ catch {
434
+ console.error("Failed to hydrate remote inputs/outputs");
435
+ }
413
436
  const eng = rc.runner.getEngine();
414
437
  if (!rc.listenersBound) {
415
438
  eng.on("value", (e) => {
@@ -429,9 +452,7 @@ class GraphRunner {
429
452
  this.runningKind = "push";
430
453
  this.emit("status", { running: true, engine: this.runningKind });
431
454
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
432
- for (const [handle, value] of Object.entries(map)) {
433
- this.engine.setInput(nodeId, handle, value);
434
- }
455
+ this.engine.setInputs(nodeId, map);
435
456
  }
436
457
  });
437
458
  }
@@ -456,32 +477,13 @@ class GraphRunner {
456
477
  Object.assign(this.stagedInputs[nodeId], inputs);
457
478
  // Local running: pause, set all inputs, resume, schedule a single recompute
458
479
  if (this.backend.kind === "local" && this.engine && this.runtime) {
459
- this.runtime.pause();
460
- try {
461
- for (const [handle, value] of Object.entries(inputs)) {
462
- this.engine.setInput(nodeId, handle, value);
463
- }
464
- }
465
- finally {
466
- this.runtime.resume();
467
- try {
468
- this.runtime.__unsafe_scheduleInputsChanged(nodeId);
469
- }
470
- catch { }
471
- }
480
+ this.engine.setInputs(nodeId, inputs);
472
481
  }
473
482
  // Remote running: forward inputs individually (no batch API available)
474
- else if (this.engine && this.backend.kind !== "local") {
475
- // Prefer batch if supported by remote engine
476
- if (this.engine instanceof sparkRemote.RemoteEngine) {
477
- this.engine.setInputs(nodeId, inputs);
478
- }
479
- else {
480
- console.warn("Remote engine does not support setInputs");
481
- for (const [handle, value] of Object.entries(inputs)) {
482
- this.engine.setInput(nodeId, handle, value);
483
- }
484
- }
483
+ else if (this.engine &&
484
+ this.backend.kind !== "local" &&
485
+ this.engine instanceof sparkRemote.RemoteEngine) {
486
+ this.engine.setInputs(nodeId, inputs);
485
487
  }
486
488
  // Not running: emit value events so UI reflects staged values
487
489
  else if (!this.engine) {
@@ -556,7 +558,7 @@ class GraphRunner {
556
558
  for (const n of def.nodes) {
557
559
  const staged = this.stagedInputs[n.nodeId] ?? {};
558
560
  const runtimeInputs = this.runtime
559
- ? this.runtime.__unsafe_getNodeData?.(n.nodeId)?.inputs ?? {}
561
+ ? this.runtime.getNodeData?.(n.nodeId)?.inputs ?? {}
560
562
  : {};
561
563
  if (this.isRunning()) {
562
564
  out[n.nodeId] = runtimeInputs;
@@ -881,7 +883,7 @@ function getNodeBorderClassNames(args) {
881
883
  const status = args.status || {};
882
884
  const issues = args.validation?.issues ?? [];
883
885
  const hasError = !!status.lastError;
884
- const hasValidationError = issues.some((i) => i?.level === "error");
886
+ const hasValidationError = issues.some((i) => i.level === "error");
885
887
  const hasValidationWarning = !hasValidationError && issues.length > 0;
886
888
  const isRunning = !!status.activeRuns;
887
889
  const isInvalid = !!status.invalidated && !isRunning && !hasError;
@@ -983,7 +985,8 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
983
985
  const add = (source, type) => (payload) => setEvents((prev) => {
984
986
  if (source === "workbench" &&
985
987
  (type === "graphChanged" || type === "graphUiChanged")) {
986
- const changeType = payload?.change?.type;
988
+ const changeType = payload
989
+ .change?.type;
987
990
  if (changeType === "moveNode" || changeType === "moveNodes")
988
991
  return prev;
989
992
  }
@@ -998,7 +1001,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
998
1001
  const nodeId = e?.nodeId;
999
1002
  setNodeStatus((s) => ({
1000
1003
  ...s,
1001
- [nodeId]: { ...(s[nodeId] ?? {}), invalidated: true },
1004
+ [nodeId]: { ...s[nodeId], invalidated: true },
1002
1005
  }));
1003
1006
  }
1004
1007
  return add("runner", "value")(e);
@@ -1010,7 +1013,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1010
1013
  const edgeId = edgeError.edgeId;
1011
1014
  setEdgeStatus((s) => ({
1012
1015
  ...s,
1013
- [edgeId]: { ...(s[edgeId] ?? {}), lastError: edgeError.err },
1016
+ [edgeId]: { ...s[edgeId], lastError: edgeError.err },
1014
1017
  }));
1015
1018
  }
1016
1019
  else if (nodeError.nodeId) {
@@ -1018,7 +1021,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1018
1021
  setNodeStatus((s) => ({
1019
1022
  ...s,
1020
1023
  [nodeId]: {
1021
- ...(s[nodeId] ?? {}),
1024
+ ...s[nodeId],
1022
1025
  lastError: nodeError.err,
1023
1026
  },
1024
1027
  }));
@@ -1030,7 +1033,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1030
1033
  setNodeStatus((s) => {
1031
1034
  const next = {};
1032
1035
  for (const n of wb.export().nodes) {
1033
- next[n.nodeId] = { ...(s[n.nodeId] ?? {}), invalidated: true };
1036
+ next[n.nodeId] = { ...s[n.nodeId], invalidated: true };
1034
1037
  }
1035
1038
  return next;
1036
1039
  });
@@ -1043,12 +1046,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1043
1046
  if (s.kind === "node-start") {
1044
1047
  const id = s.nodeId;
1045
1048
  setNodeStatus((prev) => {
1046
- const active = Math.max(1, (prev[id]?.activeRuns ?? 0) + 1);
1049
+ const current = prev[id]?.activeRuns ?? 0;
1047
1050
  return {
1048
1051
  ...prev,
1049
1052
  [id]: {
1050
- ...(prev[id] ?? {}),
1051
- activeRuns: active,
1053
+ ...prev[id],
1054
+ activeRuns: current + 1,
1052
1055
  progress: 0,
1053
1056
  invalidated: false,
1054
1057
  },
@@ -1060,7 +1063,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1060
1063
  setNodeStatus((prev) => ({
1061
1064
  ...prev,
1062
1065
  [id]: {
1063
- ...(prev[id] ?? {}),
1066
+ ...prev[id],
1064
1067
  progress: Number(s.progress) || 0,
1065
1068
  },
1066
1069
  }));
@@ -1068,13 +1071,12 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1068
1071
  else if (s.kind === "node-done") {
1069
1072
  const id = s.nodeId;
1070
1073
  setNodeStatus((prev) => {
1071
- const current = prev[id]?.activeRuns ?? 1;
1072
- const nextActive = Math.max(0, current - 1);
1074
+ const current = prev[id]?.activeRuns ?? 0;
1073
1075
  return {
1074
1076
  ...prev,
1075
1077
  [id]: {
1076
- ...(prev[id] ?? {}),
1077
- activeRuns: nextActive,
1078
+ ...prev[id],
1079
+ activeRuns: current - 1,
1078
1080
  },
1079
1081
  };
1080
1082
  });
@@ -1082,22 +1084,20 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1082
1084
  else if (s.kind === "edge-start") {
1083
1085
  const id = s.edgeId;
1084
1086
  setEdgeStatus((prev) => {
1085
- const current = prev[id]?.activeRuns ?? 1;
1086
- const nextActive = Math.max(0, current + 1);
1087
+ const current = prev[id]?.activeRuns ?? 0;
1087
1088
  return {
1088
1089
  ...prev,
1089
- [id]: { ...(prev[id] ?? {}), activeRuns: nextActive },
1090
+ [id]: { ...prev[id], activeRuns: current + 1 },
1090
1091
  };
1091
1092
  });
1092
1093
  }
1093
1094
  else if (s.kind === "edge-done") {
1094
1095
  const id = s.edgeId;
1095
1096
  setEdgeStatus((prev) => {
1096
- const current = prev[id]?.activeRuns ?? 1;
1097
- const nextActive = Math.max(0, current - 1);
1097
+ const current = prev[id]?.activeRuns ?? 0;
1098
1098
  return {
1099
1099
  ...prev,
1100
- [id]: { ...(prev[id] ?? {}), activeRuns: nextActive },
1100
+ [id]: { ...prev[id], activeRuns: current - 1 },
1101
1101
  };
1102
1102
  });
1103
1103
  }
@@ -1111,7 +1111,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1111
1111
  const id = change.nodeId;
1112
1112
  setNodeStatus((s) => ({
1113
1113
  ...s,
1114
- [id]: { ...(s[id] ?? {}), invalidated: true },
1114
+ [id]: { ...s[id], invalidated: true },
1115
1115
  }));
1116
1116
  }
1117
1117
  });
@@ -1463,7 +1463,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1463
1463
  const { typeId, showValues, inputValues, outputValues, toString } = data;
1464
1464
  const inputEntries = data.inputHandles ?? [];
1465
1465
  const outputEntries = data.outputHandles ?? [];
1466
- const status = data.status ?? {};
1466
+ const status = data.status ?? { activeRuns: 0 };
1467
1467
  const validation = data.validation ?? {
1468
1468
  inputs: [],
1469
1469
  outputs: [],
@@ -1743,6 +1743,45 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
1743
1743
  ? registry.nodes.get(selectedNode.typeId)
1744
1744
  : undefined;
1745
1745
  const [exampleState, setExampleState] = React.useState(example ?? "simple");
1746
+ const defaultExamples = React.useMemo(() => [
1747
+ {
1748
+ id: "simple",
1749
+ label: "Simple",
1750
+ load: async () => ({
1751
+ registry: sparkGraph.createSimpleGraphRegistry(),
1752
+ def: sparkGraph.createSimpleGraphDef(),
1753
+ }),
1754
+ },
1755
+ {
1756
+ id: "async",
1757
+ label: "Async Chain",
1758
+ load: async () => ({
1759
+ registry: sparkGraph.createAsyncGraphRegistry(),
1760
+ def: sparkGraph.createAsyncGraphDef(),
1761
+ }),
1762
+ },
1763
+ {
1764
+ id: "progress",
1765
+ label: "Progress + Errors",
1766
+ load: async () => ({
1767
+ registry: sparkGraph.createProgressGraphRegistry(),
1768
+ def: sparkGraph.createProgressGraphDef(),
1769
+ }),
1770
+ },
1771
+ {
1772
+ id: "validation",
1773
+ label: "Validation",
1774
+ load: async () => ({
1775
+ registry: sparkGraph.createValidationGraphRegistry(),
1776
+ def: sparkGraph.createValidationGraphDef(),
1777
+ }),
1778
+ },
1779
+ ], []);
1780
+ const examples = React.useMemo(() => {
1781
+ if (overrides?.getExamples)
1782
+ return overrides.getExamples(defaultExamples);
1783
+ return defaultExamples;
1784
+ }, [overrides, defaultExamples]);
1746
1785
  const lastAutoLaunched = React.useRef(undefined);
1747
1786
  const autoLayoutRan = React.useRef(false);
1748
1787
  const applyExample = React.useCallback(async (key) => {
@@ -1750,46 +1789,21 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
1750
1789
  alert(`Stop engine before switching example.`);
1751
1790
  return;
1752
1791
  }
1753
- switch (key) {
1754
- case "simple": {
1755
- const r = sparkGraph.createSimpleGraphRegistry();
1756
- setRegistry(r);
1757
- wb.setRegistry(r);
1758
- await wb.load(sparkGraph.createSimpleGraphDef());
1759
- break;
1760
- }
1761
- case "async": {
1762
- const r = sparkGraph.createAsyncGraphRegistry();
1763
- setRegistry(r);
1764
- wb.setRegistry(r);
1765
- await wb.load(sparkGraph.createAsyncGraphDef());
1766
- break;
1767
- }
1768
- case "progress": {
1769
- const r = sparkGraph.createProgressGraphRegistry();
1770
- setRegistry(r);
1771
- wb.setRegistry(r);
1772
- await wb.load(sparkGraph.createProgressGraphDef());
1773
- break;
1774
- }
1775
- case "validation": {
1776
- const r = sparkGraph.createValidationGraphRegistry();
1777
- setRegistry(r);
1778
- wb.setRegistry(r);
1779
- await wb.load(sparkGraph.createValidationGraphDef());
1780
- break;
1781
- }
1782
- default: {
1783
- const r = sparkGraph.createSimpleGraphRegistry();
1784
- setRegistry(r);
1785
- wb.setRegistry(r);
1786
- await wb.load(sparkGraph.createSimpleGraphDef());
1787
- }
1792
+ const ex = examples.find((e) => e.id === key) ?? examples[0];
1793
+ if (!ex)
1794
+ return;
1795
+ const { registry: r, def } = await ex.load();
1796
+ if (r) {
1797
+ setRegistry(r);
1798
+ wb.setRegistry(r);
1788
1799
  }
1800
+ await wb.load(def);
1801
+ // Build a local runtime so seeded defaults are visible pre-run
1802
+ runner.build(wb.export());
1789
1803
  runAutoLayout();
1790
1804
  setExampleState(key);
1791
1805
  onExampleChange?.(key);
1792
- }, [runner, wb, onExampleChange, runAutoLayout]);
1806
+ }, [runner, wb, onExampleChange, runAutoLayout, examples, setRegistry]);
1793
1807
  const hydrateFromBackend = React.useCallback(async (kind, base) => {
1794
1808
  try {
1795
1809
  const transport = kind === "remote-http"
@@ -2040,9 +2054,9 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2040
2054
  return overrides.toElement(baseToElement, { registry });
2041
2055
  return baseToElement;
2042
2056
  }, [overrides, baseToElement, registry]);
2043
- return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [runner.isRunning() ? (jsxRuntime.jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runner.getRunningEngine()] })) : (jsxRuntime.jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxRuntime.jsx("label", { className: "ml-2 text-sm", children: "Example:" }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
2057
+ return (jsxRuntime.jsxs("div", { className: "w-full h-screen flex flex-col", children: [jsxRuntime.jsxs("div", { className: "p-2 border-b border-gray-300 flex gap-2 items-center", children: [runner.isRunning() ? (jsxRuntime.jsxs("span", { className: "ml-2 text-sm text-green-700", children: ["Running: ", runner.getRunningEngine()] })) : (jsxRuntime.jsx("span", { className: "ml-2 text-sm text-gray-500", children: "Stopped" })), jsxRuntime.jsx("label", { className: "ml-2 text-sm", children: "Example:" }), jsxRuntime.jsx("select", { className: "border border-gray-300 rounded px-2 py-1", value: exampleState, onChange: (e) => applyExample(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
2044
2058
  ? "Stop engine before switching example"
2045
- : undefined, children: [jsxRuntime.jsx("option", { value: "simple", children: "Simple" }), jsxRuntime.jsx("option", { value: "async", children: "Async Chain" }), jsxRuntime.jsx("option", { value: "progress", children: "Progress + Errors" }), jsxRuntime.jsx("option", { value: "validation", children: "Validation" })] }), jsxRuntime.jsx("label", { className: "ml-2 text-sm", children: "Backend:" }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
2059
+ : undefined, children: examples.map((ex) => (jsxRuntime.jsx("option", { value: ex.id, children: ex.label }, ex.id))) }), jsxRuntime.jsx("label", { className: "ml-2 text-sm", children: "Backend:" }), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: backendKind, onChange: (e) => onBackendKindChange(e.target.value), disabled: runner.isRunning(), title: runner.isRunning()
2046
2060
  ? "Stop engine before switching backend"
2047
2061
  : undefined, children: [jsxRuntime.jsx("option", { value: "local", children: "Local" }), jsxRuntime.jsx("option", { value: "remote-http", children: "Remote (HTTP)" }), jsxRuntime.jsx("option", { value: "remote-ws", children: "Remote (WebSocket)" })] }), backendKind === "remote-http" && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "http://127.0.0.1:18080", value: httpBaseUrl, onChange: (e) => onHttpBaseUrlChange(e.target.value) })), backendKind === "remote-ws" && (jsxRuntime.jsx("input", { className: "ml-2 border border-gray-300 rounded px-2 py-1 w-72", placeholder: "ws://127.0.0.1:18081", value: wsUrl, onChange: (e) => onWsUrlChange(e.target.value) })), jsxRuntime.jsxs("select", { className: "border border-gray-300 rounded px-2 py-1", value: runner.getRunningEngine() ?? engine ?? "", onChange: (e) => {
2048
2062
  const kind = e.target.value || undefined;