@bian-womp/spark-workbench 0.1.20 → 0.1.22

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 (29) hide show
  1. package/lib/cjs/index.cjs +69 -58
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/misc/DefaultNode.d.ts.map +1 -1
  4. package/lib/cjs/src/misc/Inspector.d.ts.map +1 -1
  5. package/lib/cjs/src/misc/WorkbenchStudio.d.ts +1 -1
  6. package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
  7. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +1 -0
  8. package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  9. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts +1 -1
  10. package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  11. package/lib/cjs/src/misc/value.d.ts +1 -0
  12. package/lib/cjs/src/misc/value.d.ts.map +1 -1
  13. package/lib/cjs/src/runtime/GraphRunner.d.ts +7 -1
  14. package/lib/cjs/src/runtime/GraphRunner.d.ts.map +1 -1
  15. package/lib/esm/index.js +71 -61
  16. package/lib/esm/index.js.map +1 -1
  17. package/lib/esm/src/misc/DefaultNode.d.ts.map +1 -1
  18. package/lib/esm/src/misc/Inspector.d.ts.map +1 -1
  19. package/lib/esm/src/misc/WorkbenchStudio.d.ts +1 -1
  20. package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
  21. package/lib/esm/src/misc/context/WorkbenchContext.d.ts +1 -0
  22. package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
  23. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts +1 -1
  24. package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
  25. package/lib/esm/src/misc/value.d.ts +1 -0
  26. package/lib/esm/src/misc/value.d.ts.map +1 -1
  27. package/lib/esm/src/runtime/GraphRunner.d.ts +7 -1
  28. package/lib/esm/src/runtime/GraphRunner.d.ts.map +1 -1
  29. package/package.json +4 -4
package/lib/cjs/index.cjs CHANGED
@@ -327,6 +327,9 @@ class GraphRunner {
327
327
  this.backend = { kind: "local" };
328
328
  if (backend)
329
329
  this.backend = backend;
330
+ // Emit initial transport status
331
+ if (this.backend.kind === "local")
332
+ this.emit("transport", { state: "local" });
330
333
  }
331
334
  build(def) {
332
335
  if (this.backend.kind === "local") {
@@ -615,6 +618,13 @@ class GraphRunner {
615
618
  this.runningKind = undefined;
616
619
  this.emit("status", { running: false, engine: undefined });
617
620
  }
621
+ const kind = this.backend.kind === "local"
622
+ ? undefined
623
+ : this.backend.kind;
624
+ this.emit("transport", {
625
+ state: this.backend.kind === "local" ? "local" : "disconnected",
626
+ kind,
627
+ });
618
628
  }
619
629
  isRunning() {
620
630
  return !!this.engine;
@@ -627,6 +637,8 @@ class GraphRunner {
627
637
  if (this.remote)
628
638
  return this.remote;
629
639
  let transport;
640
+ const kind = this.backend.kind === "remote-http" ? "remote-http" : "remote-ws";
641
+ this.emit("transport", { state: "connecting", kind });
630
642
  if (this.backend.kind === "remote-http") {
631
643
  if (!sparkRemote.HttpPollingTransport)
632
644
  throw new Error("HttpPollingTransport not available");
@@ -649,6 +661,7 @@ class GraphRunner {
649
661
  valueCache: new Map(),
650
662
  listenersBound: false,
651
663
  };
664
+ this.emit("transport", { state: "connected", kind });
652
665
  return this.remote;
653
666
  }
654
667
  }
@@ -817,9 +830,25 @@ function useQueryParamString(key, defaultValue) {
817
830
  return [val, set];
818
831
  }
819
832
 
833
+ function formatDataUrlAsLabel(dataUrl) {
834
+ try {
835
+ const semi = dataUrl.indexOf(";");
836
+ const comma = dataUrl.indexOf(",");
837
+ const mime = dataUrl.slice(5, semi > 0 ? semi : undefined).toUpperCase();
838
+ const b64 = comma >= 0 ? dataUrl.slice(comma + 1) : "";
839
+ const bytes = Math.floor((b64.length * 3) / 4);
840
+ return `${mime} Data (${bytes} bytes)`;
841
+ }
842
+ catch {
843
+ return dataUrl.length > 64 ? dataUrl.slice(0, 64) + "…" : dataUrl;
844
+ }
845
+ }
820
846
  function resolveOutputDisplay(raw, declared) {
821
847
  if (sparkGraph.isTypedOutput(raw)) {
822
- return { typeId: String(raw.__spark_type), value: raw.__spark_value };
848
+ return {
849
+ typeId: sparkGraph.getTypedOutputTypeId(raw),
850
+ value: sparkGraph.getTypedOutputValue(raw),
851
+ };
823
852
  }
824
853
  let typeId = undefined;
825
854
  if (Array.isArray(declared)) {
@@ -841,7 +870,7 @@ function preformatValueForDisplay(typeId, value, registry) {
841
870
  return "";
842
871
  // Unwrap typed outputs
843
872
  if (sparkGraph.isTypedOutput(value)) {
844
- return preformatValueForDisplay(String(value.__spark_type), value.__spark_value, registry);
873
+ return preformatValueForDisplay(sparkGraph.getTypedOutputTypeId(value), sparkGraph.getTypedOutputValue(value), registry);
845
874
  }
846
875
  // Enums
847
876
  if (typeId && typeId.includes("enum:") && registry) {
@@ -868,24 +897,13 @@ function preformatValueForDisplay(typeId, value, registry) {
868
897
  function summarizeDeep(value) {
869
898
  // Strings: summarize data URLs and trim extremely long strings
870
899
  if (typeof value === "string") {
871
- if (value.startsWith("data:")) {
872
- try {
873
- const semi = value.indexOf(";");
874
- const comma = value.indexOf(",");
875
- const mime = value.slice(5, semi > 0 ? semi : undefined).toUpperCase();
876
- const b64 = comma >= 0 ? value.slice(comma + 1) : "";
877
- const bytes = Math.floor((b64.length * 3) / 4);
878
- return `${mime} Data (${bytes} bytes)`;
879
- }
880
- catch {
881
- return value.length > 64 ? value.slice(0, 64) + "…" : value;
882
- }
883
- }
900
+ if (value.startsWith("data:"))
901
+ return formatDataUrlAsLabel(value);
884
902
  return value.length > 512 ? value.slice(0, 512) + "…" : value;
885
903
  }
886
904
  // Typed output wrapper
887
905
  if (sparkGraph.isTypedOutput(value)) {
888
- return summarizeDeep(value.__spark_value);
906
+ return summarizeDeep(sparkGraph.getTypedOutputValue(value));
889
907
  }
890
908
  // Arrays
891
909
  if (Array.isArray(value)) {
@@ -900,18 +918,8 @@ function summarizeDeep(value) {
900
918
  if (typeof v === "string" &&
901
919
  k.toLowerCase() === "url" &&
902
920
  v.startsWith("data:")) {
903
- try {
904
- const semi = v.indexOf(";");
905
- const comma = v.indexOf(",");
906
- const mime = v.slice(5, semi > 0 ? semi : undefined).toUpperCase();
907
- const b64 = comma >= 0 ? v.slice(comma + 1) : "";
908
- const bytes = Math.floor((b64.length * 3) / 4);
909
- out[k] = `${mime} Data (${bytes} bytes)`;
910
- continue;
911
- }
912
- catch {
913
- // fallthrough
914
- }
921
+ out[k] = formatDataUrlAsLabel(v);
922
+ continue;
915
923
  }
916
924
  out[k] = summarizeDeep(v);
917
925
  }
@@ -1078,6 +1086,24 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1078
1086
  const def = wb.export();
1079
1087
  const inputsMap = React.useMemo(() => runner.getInputs(def), [runner, def, valuesTick]);
1080
1088
  const outputsMap = React.useMemo(() => runner.getOutputs(def), [runner, def, valuesTick]);
1089
+ const outputTypesMap = React.useMemo(() => {
1090
+ const out = {};
1091
+ // Local: runtimeTypeId is not stored; derive from typed wrapper in outputsMap
1092
+ for (const n of def.nodes) {
1093
+ const outputsDecl = registry.nodes.get(n.typeId)?.outputs ?? {};
1094
+ const handles = Object.keys(outputsDecl);
1095
+ const cur = {};
1096
+ for (const h of handles) {
1097
+ const v = outputsMap[n.nodeId]?.[h];
1098
+ const declared = outputsDecl[h];
1099
+ const { typeId } = resolveOutputDisplay(v, declared);
1100
+ cur[h] = typeId;
1101
+ }
1102
+ if (Object.keys(cur).length > 0)
1103
+ out[n.nodeId] = cur;
1104
+ }
1105
+ return out;
1106
+ }, [def, outputsMap, registry]);
1081
1107
  // Initialize nodes as invalidated by default until first successful run
1082
1108
  React.useEffect(() => {
1083
1109
  setNodeStatus((prev) => {
@@ -1425,6 +1451,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1425
1451
  valuesTick,
1426
1452
  inputsMap,
1427
1453
  outputsMap,
1454
+ outputTypesMap,
1428
1455
  validationByNode,
1429
1456
  validationByEdge,
1430
1457
  validationGlobal,
@@ -1505,14 +1532,6 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1505
1532
  const safeToString = (typeId, value) => {
1506
1533
  try {
1507
1534
  if (typeof toString === "function") {
1508
- // Special-case data URLs for readability
1509
- if (typeof value === "string" && value.startsWith("data:image/")) {
1510
- const comma = value.indexOf(",");
1511
- const b64 = comma >= 0 ? value.slice(comma + 1) : "";
1512
- const bytes = Math.floor((b64.length * 3) / 4);
1513
- const fmt = value.slice(5, value.indexOf(";")) || "image";
1514
- return `${fmt.toUpperCase()} Data (${bytes} bytes)`;
1515
- }
1516
1535
  return toString(typeId, value);
1517
1536
  }
1518
1537
  return String(value ?? "");
@@ -1521,7 +1540,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1521
1540
  return String(value ?? "");
1522
1541
  }
1523
1542
  };
1524
- const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, } = useWorkbenchContext();
1543
+ const { registry, def, selectedNodeId, selectedEdgeId, inputsMap, outputsMap, outputTypesMap, nodeStatus, validationByNode, validationByEdge, validationGlobal, valuesTick, } = useWorkbenchContext();
1525
1544
  const nodeValidationIssues = validationByNode.issues;
1526
1545
  const edgeValidationIssues = validationByEdge.issues;
1527
1546
  const nodeValidationHandles = validationByNode;
@@ -1644,7 +1663,7 @@ function Inspector({ debug, autoScroll, hideWorkbench, onAutoScrollChange, onHid
1644
1663
  if (e.key === "Escape")
1645
1664
  revert();
1646
1665
  }, ...commonProps }))] }, h));
1647
- }))] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsx("label", { className: "w-20", children: h }), jsxRuntime.jsx("div", { className: "flex-1", children: (() => {
1666
+ }))] }), jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "font-semibold mb-1", children: "Outputs" }), outputHandles.length === 0 ? (jsxRuntime.jsx("div", { className: "text-gray-500", children: "No outputs" })) : (outputHandles.map((h) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsxs("label", { className: "w-20 flex flex-col", children: [jsxRuntime.jsx("span", { children: h }), jsxRuntime.jsx("span", { className: "text-gray-500 text-[11px]", children: outputTypesMap[selectedNodeId]?.[h] ?? "" })] }), jsxRuntime.jsx("div", { className: "flex-1", children: (() => {
1648
1667
  const { typeId, value } = resolveOutputDisplay(nodeOutputs[h], selectedDesc?.outputs?.[h]);
1649
1668
  return toElement(typeId, value);
1650
1669
  })() }), (() => {
@@ -1728,7 +1747,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1728
1747
  whiteSpace: "nowrap",
1729
1748
  overflow: "hidden",
1730
1749
  textOverflow: "ellipsis",
1731
- }, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, resolved.typeId && (jsxRuntime.jsxs("span", { className: "ml-1 opacity-60", children: ["(", resolved.typeId, ")"] })), hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: toString(resolved.typeId, resolved.value) }))] })] }, `out-${entry.id}`));
1750
+ }, title: `${entry.id}: ${entry.typeId}`, children: [entry.id, hasAny && (jsxRuntime.jsx(IssueBadge, { level: hasErr ? "error" : "warning", size: 12, className: "ml-1", title: title })), showValues && (jsxRuntime.jsx("span", { className: "ml-1 opacity-60", children: toString(resolved.typeId, resolved.value) }))] })] }, `out-${entry.id}`));
1732
1751
  })] }));
1733
1752
  });
1734
1753
  DefaultNode.displayName = "DefaultNode";
@@ -1962,6 +1981,9 @@ const WorkbenchCanvas = React.forwardRef(({ showValues, toString, toElement }, r
1962
1981
 
1963
1982
  function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, example, onExampleChange, engine, onEngineChange, backendKind, onBackendKindChange, httpBaseUrl, onHttpBaseUrlChange, wsUrl, onWsUrlChange, debug, onDebugChange, showValues, onShowValuesChange, hideWorkbench, onHideWorkbenchChange, overrides, onInit, onChange, }) {
1964
1983
  const { wb, runner, registry, def, selectedNodeId, runAutoLayout } = useWorkbenchContext();
1984
+ const [transportStatus, setTransportStatus] = React.useState({
1985
+ state: "local",
1986
+ });
1965
1987
  const selectedNode = def.nodes.find((n) => n.nodeId === selectedNodeId);
1966
1988
  const selectedDesc = selectedNode
1967
1989
  ? registry.nodes.get(selectedNode.typeId)
@@ -2175,6 +2197,10 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2175
2197
  return;
2176
2198
  applyExample(example);
2177
2199
  }, [example, wb]);
2200
+ React.useEffect(() => {
2201
+ const off = runner.on("transport", (s) => setTransportStatus(s));
2202
+ return () => off();
2203
+ }, [runner]);
2178
2204
  React.useEffect(() => {
2179
2205
  if (!engine)
2180
2206
  return;
@@ -2308,7 +2334,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2308
2334
  return "";
2309
2335
  // Normalize typed wrapper
2310
2336
  if (sparkGraph.isTypedOutput(value)) {
2311
- return baseToString(String(value.__spark_type), value.__spark_value);
2337
+ return baseToString(sparkGraph.getTypedOutputTypeId(value), sparkGraph.getTypedOutputValue(value));
2312
2338
  }
2313
2339
  const pre = preformatValueForDisplay(typeId, value, registry);
2314
2340
  if (pre !== undefined)
@@ -2318,23 +2344,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2318
2344
  typeof value.url === "string") {
2319
2345
  const title = value.title || "";
2320
2346
  const url = String(value.url || "");
2321
- if (url.startsWith("data:image/")) {
2322
- try {
2323
- const semi = url.indexOf(";");
2324
- const comma = url.indexOf(",");
2325
- const mime = url
2326
- .slice(5, semi > 0 ? semi : undefined)
2327
- .toUpperCase();
2328
- const b64 = comma >= 0 ? url.slice(comma + 1) : "";
2329
- const bytes = Math.floor((b64.length * 3) / 4);
2330
- return title
2331
- ? `${title} (${mime} ${bytes} bytes)`
2332
- : `${mime} Data (${bytes} bytes)`;
2333
- }
2334
- catch {
2335
- return title || url.slice(0, 32) + (url.length > 32 ? "…" : "");
2336
- }
2337
- }
2347
+ // value.ts handles data URL formatting
2338
2348
  return title || url.slice(0, 32) + (url.length > 32 ? "…" : "");
2339
2349
  }
2340
2350
  if (typeId && typeId.includes("enum:")) {
@@ -2381,7 +2391,7 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
2381
2391
  return overrides.toElement(baseToElement, { registry });
2382
2392
  return baseToElement;
2383
2393
  }, [overrides, baseToElement, registry]);
2384
- 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()
2394
+ 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.jsxs("span", { className: "ml-2 flex items-center gap-1 text-xs", title: transportStatus.kind || undefined, children: [transportStatus.state === "local" && (jsxRuntime.jsx(react.PlugsConnectedIcon, { size: 14, className: "text-gray-500" })), transportStatus.state === "connecting" && (jsxRuntime.jsx(react.ClockClockwiseIcon, { size: 14, className: "text-amber-600 animate-pulse" })), transportStatus.state === "connected" && (jsxRuntime.jsx(react.WifiHighIcon, { size: 14, className: "text-green-600" })), transportStatus.state === "disconnected" && (jsxRuntime.jsx(react.WifiSlashIcon, { size: 14, className: "text-red-600" })), transportStatus.state === "retrying" && (jsxRuntime.jsx(react.ClockClockwiseIcon, { size: 14, className: "text-amber-700 animate-pulse" }))] }), 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()
2385
2395
  ? "Stop engine before switching example"
2386
2396
  : undefined, children: [jsxRuntime.jsx("option", { value: "", children: "Select Example\u2026" }), 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()
2387
2397
  ? "Stop engine before switching backend"
@@ -2434,6 +2444,7 @@ exports.WorkbenchCanvas = WorkbenchCanvas;
2434
2444
  exports.WorkbenchContext = WorkbenchContext;
2435
2445
  exports.WorkbenchProvider = WorkbenchProvider;
2436
2446
  exports.WorkbenchStudio = WorkbenchStudio;
2447
+ exports.formatDataUrlAsLabel = formatDataUrlAsLabel;
2437
2448
  exports.formatDeclaredTypeSignature = formatDeclaredTypeSignature;
2438
2449
  exports.getNodeBorderClassNames = getNodeBorderClassNames;
2439
2450
  exports.preformatValueForDisplay = preformatValueForDisplay;