@bian-womp/spark-workbench 0.1.11 → 0.1.13

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
@@ -343,19 +343,9 @@ class GraphRunner {
343
343
  if (!this.runtime)
344
344
  return;
345
345
  // Prevent mid-run churn while wiring changes are applied
346
- try {
347
- this.runtime.pause();
348
- }
349
- catch {
350
- console.error("Failed to pause runtime");
351
- }
346
+ this.runtime.pause();
352
347
  this.runtime.update(def, this.registry);
353
- try {
354
- this.runtime.resume();
355
- }
356
- catch {
357
- console.error("Failed to resume runtime");
358
- }
348
+ this.runtime.resume();
359
349
  this.emit("invalidate", { reason: "graph-updated" });
360
350
  return;
361
351
  }
@@ -409,9 +399,7 @@ class GraphRunner {
409
399
  this.runningKind = opts.engine;
410
400
  this.emit("status", { running: true, engine: this.runningKind });
411
401
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
412
- for (const [handle, value] of Object.entries(map)) {
413
- this.engine.setInput(nodeId, handle, value);
414
- }
402
+ this.engine.setInputs(nodeId, map);
415
403
  }
416
404
  return;
417
405
  }
@@ -439,9 +427,7 @@ class GraphRunner {
439
427
  this.runningKind = "push";
440
428
  this.emit("status", { running: true, engine: this.runningKind });
441
429
  for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
442
- for (const [handle, value] of Object.entries(map)) {
443
- this.engine.setInput(nodeId, handle, value);
444
- }
430
+ this.engine.setInputs(nodeId, map);
445
431
  }
446
432
  });
447
433
  }
@@ -457,6 +443,51 @@ class GraphRunner {
457
443
  this.emit("value", { nodeId, handle, value, io: "input" });
458
444
  }
459
445
  }
446
+ // Batch update multiple inputs on a node and trigger a single run
447
+ setInputs(nodeId, inputs) {
448
+ if (!inputs)
449
+ return;
450
+ if (!this.stagedInputs[nodeId])
451
+ this.stagedInputs[nodeId] = {};
452
+ Object.assign(this.stagedInputs[nodeId], inputs);
453
+ // Local running: pause, set all inputs, resume, schedule a single recompute
454
+ if (this.backend.kind === "local" && this.engine && this.runtime) {
455
+ this.runtime.pause();
456
+ try {
457
+ for (const [handle, value] of Object.entries(inputs)) {
458
+ this.engine.setInput(nodeId, handle, value);
459
+ }
460
+ }
461
+ finally {
462
+ this.runtime.resume();
463
+ try {
464
+ this.runtime.__unsafe_scheduleInputsChanged(nodeId);
465
+ }
466
+ catch { }
467
+ }
468
+ }
469
+ // Remote running: forward inputs individually (no batch API available)
470
+ else if (this.engine && this.backend.kind !== "local") {
471
+ // Prefer batch if supported by remote engine
472
+ if (this.engine instanceof sparkRemote.RemoteEngine) {
473
+ this.engine.setInputs(nodeId, inputs);
474
+ }
475
+ else {
476
+ console.warn("Remote engine does not support setInputs");
477
+ for (const [handle, value] of Object.entries(inputs)) {
478
+ this.engine.setInput(nodeId, handle, value);
479
+ }
480
+ }
481
+ }
482
+ // Not running: emit value events so UI reflects staged values
483
+ else if (!this.engine) {
484
+ // Not running: emit a single synthetic value event per handle; UI will coalesce
485
+ console.warn("Remote engine does not exists");
486
+ for (const [handle, value] of Object.entries(inputs)) {
487
+ this.emit("value", { nodeId, handle, value, io: "input" });
488
+ }
489
+ }
490
+ }
460
491
  async step() {
461
492
  if (this.backend.kind !== "local")
462
493
  return; // unsupported remotely
@@ -817,7 +848,7 @@ function toReactFlow(def, positions, registry, opts) {
817
848
  })
818
849
  .map((e) => {
819
850
  const st = opts.edgeStatus?.[e.id];
820
- const isRunning = !!st?.running;
851
+ const isRunning = !!st?.activeRuns;
821
852
  const hasError = !!st?.lastError;
822
853
  const isInvalidEdge = !!opts.edgeValidation?.[e.id];
823
854
  const style = hasError || isInvalidEdge
@@ -848,7 +879,7 @@ function getNodeBorderClassNames(args) {
848
879
  const hasError = !!status.lastError;
849
880
  const hasValidationError = issues.some((i) => i?.level === "error");
850
881
  const hasValidationWarning = !hasValidationError && issues.length > 0;
851
- const isRunning = !!status.running;
882
+ const isRunning = !!status.activeRuns;
852
883
  const isInvalid = !!status.invalidated && !isRunning && !hasError;
853
884
  const borderWidth = selected ? "border-2" : "border";
854
885
  const borderStyle = isInvalid ? "border-dashed" : "border-solid";
@@ -963,7 +994,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
963
994
  const nodeId = e?.nodeId;
964
995
  setNodeStatus((s) => ({
965
996
  ...s,
966
- [nodeId]: { ...(s[nodeId] ?? {}), invalidated: true },
997
+ [nodeId]: { ...s[nodeId], invalidated: true },
967
998
  }));
968
999
  }
969
1000
  return add("runner", "value")(e);
@@ -975,7 +1006,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
975
1006
  const edgeId = edgeError.edgeId;
976
1007
  setEdgeStatus((s) => ({
977
1008
  ...s,
978
- [edgeId]: { ...(s[edgeId] ?? {}), lastError: edgeError.err },
1009
+ [edgeId]: { ...s[edgeId], lastError: edgeError.err },
979
1010
  }));
980
1011
  }
981
1012
  else if (nodeError.nodeId) {
@@ -983,7 +1014,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
983
1014
  setNodeStatus((s) => ({
984
1015
  ...s,
985
1016
  [nodeId]: {
986
- ...(s[nodeId] ?? {}),
1017
+ ...s[nodeId],
987
1018
  lastError: nodeError.err,
988
1019
  },
989
1020
  }));
@@ -995,7 +1026,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
995
1026
  setNodeStatus((s) => {
996
1027
  const next = {};
997
1028
  for (const n of wb.export().nodes) {
998
- next[n.nodeId] = { ...(s[n.nodeId] ?? {}), invalidated: true };
1029
+ next[n.nodeId] = { ...s[n.nodeId], invalidated: true };
999
1030
  }
1000
1031
  return next;
1001
1032
  });
@@ -1007,47 +1038,61 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1007
1038
  return;
1008
1039
  if (s.kind === "node-start") {
1009
1040
  const id = s.nodeId;
1010
- setNodeStatus((prev) => ({
1011
- ...prev,
1012
- [id]: {
1013
- ...(prev[id] ?? {}),
1014
- running: true,
1015
- progress: 0,
1016
- invalidated: false,
1017
- },
1018
- }));
1041
+ setNodeStatus((prev) => {
1042
+ const current = prev[id]?.activeRuns ?? 0;
1043
+ return {
1044
+ ...prev,
1045
+ [id]: {
1046
+ ...prev[id],
1047
+ activeRuns: current + 1,
1048
+ progress: 0,
1049
+ invalidated: false,
1050
+ },
1051
+ };
1052
+ });
1019
1053
  }
1020
1054
  else if (s.kind === "node-progress") {
1021
1055
  const id = s.nodeId;
1022
1056
  setNodeStatus((prev) => ({
1023
1057
  ...prev,
1024
1058
  [id]: {
1025
- ...(prev[id] ?? {}),
1026
- running: true,
1059
+ ...prev[id],
1027
1060
  progress: Number(s.progress) || 0,
1028
1061
  },
1029
1062
  }));
1030
1063
  }
1031
1064
  else if (s.kind === "node-done") {
1032
1065
  const id = s.nodeId;
1033
- setNodeStatus((prev) => ({
1034
- ...prev,
1035
- [id]: { ...(prev[id] ?? {}), running: false },
1036
- }));
1066
+ setNodeStatus((prev) => {
1067
+ const current = prev[id]?.activeRuns ?? 0;
1068
+ return {
1069
+ ...prev,
1070
+ [id]: {
1071
+ ...prev[id],
1072
+ activeRuns: current - 1,
1073
+ },
1074
+ };
1075
+ });
1037
1076
  }
1038
1077
  else if (s.kind === "edge-start") {
1039
1078
  const id = s.edgeId;
1040
- setEdgeStatus((prev) => ({
1041
- ...prev,
1042
- [id]: { ...(prev[id] ?? {}), running: true },
1043
- }));
1079
+ setEdgeStatus((prev) => {
1080
+ const current = prev[id]?.activeRuns ?? 0;
1081
+ return {
1082
+ ...prev,
1083
+ [id]: { ...prev[id], activeRuns: current + 1 },
1084
+ };
1085
+ });
1044
1086
  }
1045
1087
  else if (s.kind === "edge-done") {
1046
1088
  const id = s.edgeId;
1047
- setEdgeStatus((prev) => ({
1048
- ...prev,
1049
- [id]: { ...(prev[id] ?? {}), running: false },
1050
- }));
1089
+ setEdgeStatus((prev) => {
1090
+ const current = prev[id]?.activeRuns ?? 0;
1091
+ return {
1092
+ ...prev,
1093
+ [id]: { ...prev[id], activeRuns: current - 1 },
1094
+ };
1095
+ });
1051
1096
  }
1052
1097
  return add("runner", "stats")(s);
1053
1098
  });
@@ -1059,7 +1104,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, children, }) {
1059
1104
  const id = change.nodeId;
1060
1105
  setNodeStatus((s) => ({
1061
1106
  ...s,
1062
- [id]: { ...(s[id] ?? {}), invalidated: true },
1107
+ [id]: { ...s[id], invalidated: true },
1063
1108
  }));
1064
1109
  }
1065
1110
  });
@@ -1411,7 +1456,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1411
1456
  const { typeId, showValues, inputValues, outputValues, toString } = data;
1412
1457
  const inputEntries = data.inputHandles ?? [];
1413
1458
  const outputEntries = data.outputHandles ?? [];
1414
- const status = data.status ?? {};
1459
+ const status = data.status ?? { activeRuns: 0 };
1415
1460
  const validation = data.validation ?? {
1416
1461
  inputs: [],
1417
1462
  outputs: [],
@@ -1426,7 +1471,7 @@ const DefaultNode = React.memo(function DefaultNode({ id, data, selected, isConn
1426
1471
  const hasError = !!status.lastError;
1427
1472
  const hasValidationError = validation.issues.some((i) => i.level === "error");
1428
1473
  const hasValidationWarning = !hasValidationError && validation.issues.length > 0;
1429
- const isRunning = !!status.running;
1474
+ const isRunning = !!status.activeRuns;
1430
1475
  const isInvalid = !!status.invalidated && !isRunning && !hasError;
1431
1476
  // Border color encodes severity; thickness encodes selection; style (dashed) encodes invalidated
1432
1477
  const borderWidth = selected ? "border-2" : "border";