@bian-womp/spark-workbench 0.2.46 → 0.2.48
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 +153 -86
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts +2 -2
- package/lib/cjs/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/cjs/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts +6 -3
- package/lib/cjs/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/IGraphRunner.d.ts +4 -14
- package/lib/cjs/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts +3 -3
- package/lib/cjs/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts +5 -3
- package/lib/cjs/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/lib/esm/index.js +154 -87
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/misc/WorkbenchStudio.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts +2 -2
- package/lib/esm/src/misc/context/WorkbenchContext.d.ts.map +1 -1
- package/lib/esm/src/misc/context/WorkbenchContext.provider.d.ts.map +1 -1
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts +6 -3
- package/lib/esm/src/runtime/AbstractGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/IGraphRunner.d.ts +4 -14
- package/lib/esm/src/runtime/IGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts +3 -3
- package/lib/esm/src/runtime/LocalGraphRunner.d.ts.map +1 -1
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts +5 -3
- package/lib/esm/src/runtime/RemoteGraphRunner.d.ts.map +1 -1
- package/package.json +4 -4
package/lib/cjs/index.cjs
CHANGED
|
@@ -336,8 +336,9 @@ class AbstractGraphRunner {
|
|
|
336
336
|
this.stagedInputs = {};
|
|
337
337
|
}
|
|
338
338
|
launch(def, opts) {
|
|
339
|
+
// Auto-stop if engine is already running
|
|
339
340
|
if (this.engine) {
|
|
340
|
-
|
|
341
|
+
this.stop();
|
|
341
342
|
}
|
|
342
343
|
}
|
|
343
344
|
setInput(nodeId, handle, value) {
|
|
@@ -389,6 +390,39 @@ class AbstractGraphRunner {
|
|
|
389
390
|
async whenIdle() {
|
|
390
391
|
await this.engine?.whenIdle();
|
|
391
392
|
}
|
|
393
|
+
stop() {
|
|
394
|
+
if (!this.engine)
|
|
395
|
+
return;
|
|
396
|
+
// Dispose engine (cleans up timers, listeners, etc.)
|
|
397
|
+
this.engine.dispose();
|
|
398
|
+
this.engine = undefined;
|
|
399
|
+
// Emit status but keep runtime alive
|
|
400
|
+
if (this.runningKind) {
|
|
401
|
+
this.runningKind = undefined;
|
|
402
|
+
this.emit("status", { running: false, engine: undefined });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async switchEngine(opts) {
|
|
406
|
+
if (!this.engine || !this.runtime) {
|
|
407
|
+
throw new Error("No engine running to switch from");
|
|
408
|
+
}
|
|
409
|
+
// Wait for current engine to be idle
|
|
410
|
+
await this.whenIdle();
|
|
411
|
+
// Capture current state
|
|
412
|
+
const currentInputs = { ...this.stagedInputs };
|
|
413
|
+
// Stop current engine
|
|
414
|
+
this.stop();
|
|
415
|
+
// Ensure runtime is in a clean state (resumed)
|
|
416
|
+
this.runtime.resume();
|
|
417
|
+
// Create and launch new engine (to be implemented by subclasses)
|
|
418
|
+
await this.createAndLaunchEngine(opts);
|
|
419
|
+
// Re-apply staged inputs to new engine
|
|
420
|
+
for (const [nodeId, map] of Object.entries(currentInputs)) {
|
|
421
|
+
if (this.engine) {
|
|
422
|
+
this.engine.setInputs(nodeId, map);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
392
426
|
on(event, handler) {
|
|
393
427
|
if (!this.listeners.has(event))
|
|
394
428
|
this.listeners.set(event, new Set());
|
|
@@ -460,37 +494,30 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
460
494
|
this.build(def);
|
|
461
495
|
if (!this.runtime)
|
|
462
496
|
throw new Error("Runtime not built");
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
break;
|
|
482
|
-
case "step":
|
|
483
|
-
this.engine = new sparkGraph.StepEngine(rt);
|
|
484
|
-
break;
|
|
485
|
-
default:
|
|
486
|
-
throw new Error("Unknown engine kind");
|
|
487
|
-
}
|
|
497
|
+
// Use the async method to create engine
|
|
498
|
+
this.createAndLaunchEngine(opts).catch((err) => {
|
|
499
|
+
console.error("Failed to launch engine:", err);
|
|
500
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
501
|
+
this.emit("error", {
|
|
502
|
+
kind: "system",
|
|
503
|
+
message: errorMessage,
|
|
504
|
+
err: err instanceof Error ? err : new Error(errorMessage),
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
async createAndLaunchEngine(opts) {
|
|
509
|
+
if (!this.runtime)
|
|
510
|
+
throw new Error("Runtime not built");
|
|
511
|
+
// Use shared engine factory
|
|
512
|
+
this.engine = sparkGraph.createEngine(this.runtime, opts);
|
|
513
|
+
if (!this.engine)
|
|
514
|
+
throw new Error("Failed to create engine");
|
|
488
515
|
this.engine.on("value", (e) => this.emit("value", e));
|
|
489
516
|
this.engine.on("error", (e) => this.emit("error", e));
|
|
490
517
|
this.engine.on("invalidate", (e) => this.emit("invalidate", e));
|
|
491
518
|
this.engine.on("stats", (e) => this.emit("stats", e));
|
|
492
|
-
this.engine.launch(opts
|
|
493
|
-
this.runningKind = opts
|
|
519
|
+
this.engine.launch(opts?.invalidate);
|
|
520
|
+
this.runningKind = opts?.engine ?? "push";
|
|
494
521
|
this.emit("status", { running: true, engine: this.runningKind });
|
|
495
522
|
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
496
523
|
this.engine.setInputs(nodeId, map);
|
|
@@ -498,18 +525,18 @@ class LocalGraphRunner extends AbstractGraphRunner {
|
|
|
498
525
|
}
|
|
499
526
|
async step() {
|
|
500
527
|
const eng = this.engine;
|
|
501
|
-
if (eng instanceof sparkGraph.StepEngine)
|
|
528
|
+
if (eng && eng instanceof sparkGraph.StepEngine)
|
|
502
529
|
await eng.step();
|
|
503
530
|
}
|
|
504
531
|
async computeNode(nodeId) {
|
|
505
532
|
const eng = this.engine;
|
|
506
|
-
if (eng instanceof sparkGraph.PullEngine)
|
|
533
|
+
if (eng && eng instanceof sparkGraph.PullEngine)
|
|
507
534
|
await eng.computeNode(nodeId);
|
|
508
535
|
}
|
|
509
|
-
flush() {
|
|
536
|
+
async flush() {
|
|
510
537
|
const eng = this.engine;
|
|
511
|
-
if (eng instanceof sparkGraph.BatchedEngine)
|
|
512
|
-
eng.flush();
|
|
538
|
+
if (eng && eng instanceof sparkGraph.BatchedEngine)
|
|
539
|
+
await eng.flush();
|
|
513
540
|
}
|
|
514
541
|
getOutputs(def) {
|
|
515
542
|
const out = {};
|
|
@@ -905,30 +932,36 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
905
932
|
catch {
|
|
906
933
|
console.error("Failed to hydrate remote inputs/outputs");
|
|
907
934
|
}
|
|
908
|
-
|
|
909
|
-
if (!this.listenersBound) {
|
|
910
|
-
eng.on("value", (e) => {
|
|
911
|
-
this.valueCache.set(`${e.nodeId}.${e.handle}`, {
|
|
912
|
-
io: e.io,
|
|
913
|
-
value: e.value,
|
|
914
|
-
runtimeTypeId: e.runtimeTypeId,
|
|
915
|
-
});
|
|
916
|
-
this.emit("value", e);
|
|
917
|
-
});
|
|
918
|
-
eng.on("error", (e) => this.emit("error", e));
|
|
919
|
-
eng.on("invalidate", (e) => this.emit("invalidate", e));
|
|
920
|
-
eng.on("stats", (e) => this.emit("stats", e));
|
|
921
|
-
this.listenersBound = true;
|
|
922
|
-
}
|
|
923
|
-
this.engine = eng;
|
|
924
|
-
this.engine.launch(opts.invalidate);
|
|
925
|
-
this.runningKind = "push";
|
|
926
|
-
this.emit("status", { running: true, engine: this.runningKind });
|
|
927
|
-
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
928
|
-
this.engine.setInputs(nodeId, map);
|
|
929
|
-
}
|
|
935
|
+
await this.createAndLaunchEngine(opts);
|
|
930
936
|
});
|
|
931
937
|
}
|
|
938
|
+
async createAndLaunchEngine(opts) {
|
|
939
|
+
const client = await this.ensureClient();
|
|
940
|
+
// Configure and launch engine on the backend
|
|
941
|
+
await client.launch(opts);
|
|
942
|
+
// Get the remote engine proxy and wire up event listeners
|
|
943
|
+
const eng = client.getEngine();
|
|
944
|
+
if (!this.listenersBound) {
|
|
945
|
+
eng.on("value", (e) => {
|
|
946
|
+
this.valueCache.set(`${e.nodeId}.${e.handle}`, {
|
|
947
|
+
io: e.io,
|
|
948
|
+
value: e.value,
|
|
949
|
+
runtimeTypeId: e.runtimeTypeId,
|
|
950
|
+
});
|
|
951
|
+
this.emit("value", e);
|
|
952
|
+
});
|
|
953
|
+
eng.on("error", (e) => this.emit("error", e));
|
|
954
|
+
eng.on("invalidate", (e) => this.emit("invalidate", e));
|
|
955
|
+
eng.on("stats", (e) => this.emit("stats", e));
|
|
956
|
+
this.listenersBound = true;
|
|
957
|
+
}
|
|
958
|
+
this.engine = eng;
|
|
959
|
+
this.runningKind = opts?.engine ?? "push";
|
|
960
|
+
this.emit("status", { running: true, engine: this.runningKind });
|
|
961
|
+
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
962
|
+
this.engine.setInputs(nodeId, map);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
932
965
|
/**
|
|
933
966
|
* Launch using an existing backend runtime that has already been built and hydrated.
|
|
934
967
|
* This is used when resuming from a snapshot where the backend has already applied
|
|
@@ -944,34 +977,44 @@ class RemoteGraphRunner extends AbstractGraphRunner {
|
|
|
944
977
|
// has already been built and hydrated via ApplySnapshotFull.
|
|
945
978
|
// Calling build() would create a new runtime and lose the restored state.
|
|
946
979
|
this.lastDef = def;
|
|
947
|
-
|
|
948
|
-
const eng = client.getEngine();
|
|
949
|
-
if (!this.listenersBound) {
|
|
950
|
-
eng.on("value", (e) => {
|
|
951
|
-
this.valueCache.set(`${e.nodeId}.${e.handle}`, {
|
|
952
|
-
io: e.io,
|
|
953
|
-
value: e.value,
|
|
954
|
-
runtimeTypeId: e.runtimeTypeId,
|
|
955
|
-
});
|
|
956
|
-
this.emit("value", e);
|
|
957
|
-
});
|
|
958
|
-
eng.on("error", (e) => this.emit("error", e));
|
|
959
|
-
eng.on("invalidate", (e) => this.emit("invalidate", e));
|
|
960
|
-
eng.on("stats", (e) => this.emit("stats", e));
|
|
961
|
-
this.listenersBound = true;
|
|
962
|
-
}
|
|
963
|
-
this.engine = eng;
|
|
964
|
-
this.engine.launch(opts.invalidate);
|
|
965
|
-
this.runningKind = "push";
|
|
966
|
-
this.emit("status", { running: true, engine: this.runningKind });
|
|
967
|
-
for (const [nodeId, map] of Object.entries(this.stagedInputs)) {
|
|
968
|
-
this.engine.setInputs(nodeId, map);
|
|
969
|
-
}
|
|
980
|
+
await this.createAndLaunchEngine(opts);
|
|
970
981
|
});
|
|
971
982
|
}
|
|
972
|
-
async
|
|
973
|
-
|
|
974
|
-
|
|
983
|
+
async switchEngine(opts) {
|
|
984
|
+
if (!this.engine) {
|
|
985
|
+
throw new Error("No engine running to switch from");
|
|
986
|
+
}
|
|
987
|
+
// Wait for current engine to be idle
|
|
988
|
+
await this.whenIdle();
|
|
989
|
+
// Capture current state
|
|
990
|
+
const currentInputs = { ...this.stagedInputs };
|
|
991
|
+
// Stop current engine
|
|
992
|
+
this.stop();
|
|
993
|
+
// Create and launch new engine with the specified kind
|
|
994
|
+
const client = await this.ensureClient();
|
|
995
|
+
await client.launch(opts);
|
|
996
|
+
// Get the remote engine proxy
|
|
997
|
+
const eng = client.getEngine();
|
|
998
|
+
this.engine = eng;
|
|
999
|
+
this.runningKind = opts?.engine ?? "push";
|
|
1000
|
+
this.emit("status", { running: true, engine: this.runningKind });
|
|
1001
|
+
// Re-apply staged inputs to new engine
|
|
1002
|
+
for (const [nodeId, map] of Object.entries(currentInputs)) {
|
|
1003
|
+
this.engine.setInputs(nodeId, map);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
async step() {
|
|
1007
|
+
const client = await this.ensureClient();
|
|
1008
|
+
await client.step();
|
|
1009
|
+
}
|
|
1010
|
+
async computeNode(nodeId) {
|
|
1011
|
+
const client = await this.ensureClient();
|
|
1012
|
+
await client.computeNode(nodeId);
|
|
1013
|
+
}
|
|
1014
|
+
async flush() {
|
|
1015
|
+
const client = await this.ensureClient();
|
|
1016
|
+
await client.flush();
|
|
1017
|
+
}
|
|
975
1018
|
triggerExternal(nodeId, event) {
|
|
976
1019
|
this.ensureClient().then(async (client) => {
|
|
977
1020
|
try {
|
|
@@ -2327,7 +2370,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2327
2370
|
offRunnerRegistry();
|
|
2328
2371
|
offRunnerTransport();
|
|
2329
2372
|
};
|
|
2330
|
-
}, [runner, wb]);
|
|
2373
|
+
}, [runner, wb, setRegistry]);
|
|
2331
2374
|
// Push incremental updates into running engine without full reload
|
|
2332
2375
|
React.useEffect(() => {
|
|
2333
2376
|
if (runner.isRunning()) {
|
|
@@ -2408,7 +2451,7 @@ function WorkbenchProvider({ wb, runner, registry, setRegistry, overrides, uiVer
|
|
|
2408
2451
|
}
|
|
2409
2452
|
catch { }
|
|
2410
2453
|
}, [runner, wb]);
|
|
2411
|
-
const stop = React.useCallback(() => runner.
|
|
2454
|
+
const stop = React.useCallback(() => runner.stop(), [runner]);
|
|
2412
2455
|
const step = React.useCallback(() => runner.step(), [runner]);
|
|
2413
2456
|
const flush = React.useCallback(() => runner.flush(), [runner]);
|
|
2414
2457
|
const value = React.useMemo(() => ({
|
|
@@ -3994,10 +4037,34 @@ function WorkbenchStudioCanvas({ setRegistry, autoScroll, onAutoScrollChange, ex
|
|
|
3994
4037
|
? "Stop engine before switching example"
|
|
3995
4038
|
: 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.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()
|
|
3996
4039
|
? "Stop engine before switching backend"
|
|
3997
|
-
: 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" && !!onHttpBaseUrlChange && (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" && !!onWsUrlChange && (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) => {
|
|
4040
|
+
: 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" && !!onHttpBaseUrlChange && (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" && !!onWsUrlChange && (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: async (e) => {
|
|
3998
4041
|
const kind = e.target.value || undefined;
|
|
3999
|
-
|
|
4000
|
-
|
|
4042
|
+
const currentEngine = runner.getRunningEngine();
|
|
4043
|
+
// If engine is running and user selected a different engine, switch it
|
|
4044
|
+
if (runner.isRunning() &&
|
|
4045
|
+
currentEngine &&
|
|
4046
|
+
kind &&
|
|
4047
|
+
kind !== currentEngine) {
|
|
4048
|
+
try {
|
|
4049
|
+
await runner.switchEngine({
|
|
4050
|
+
engine: kind,
|
|
4051
|
+
batched: { flushIntervalMs: 0 },
|
|
4052
|
+
hybrid: { windowMs: 250, batchThreshold: 3 },
|
|
4053
|
+
});
|
|
4054
|
+
onEngineChange?.(kind);
|
|
4055
|
+
}
|
|
4056
|
+
catch (err) {
|
|
4057
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4058
|
+
alert(`Failed to switch engine: ${message}`);
|
|
4059
|
+
// Reset dropdown to current engine
|
|
4060
|
+
e.target.value = currentEngine;
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
else {
|
|
4064
|
+
// Normal change when not running
|
|
4065
|
+
onEngineChange?.(kind);
|
|
4066
|
+
}
|
|
4067
|
+
}, children: [jsxRuntime.jsx("option", { value: "", children: "Select Engine\u2026" }), jsxRuntime.jsx("option", { value: "push", children: "Push" }), jsxRuntime.jsx("option", { value: "batched", children: "Batched" }), jsxRuntime.jsx("option", { value: "pull", children: "Pull" }), jsxRuntime.jsx("option", { value: "hybrid", children: "Hybrid" }), jsxRuntime.jsx("option", { value: "step", children: "Step" })] }), runner.getRunningEngine() === "step" && (jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => runner.step(), disabled: !runner.isRunning(), title: "Step", children: jsxRuntime.jsx(react$1.PlayPauseIcon, { size: 24 }) })), runner.getRunningEngine() === "batched" && (jsxRuntime.jsx("button", { className: "ml-2 border border-gray-300 rounded p-1", onClick: () => runner.flush(), disabled: !runner.isRunning(), title: "Flush", children: jsxRuntime.jsx(react$1.LightningIcon, { size: 24 }) })), runner.isRunning() ? (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-red-700 border-red-600 flex items-center gap-1", onClick: () => runner.stop(), title: "Stop engine", children: [jsxRuntime.jsx(react$1.StopIcon, { size: 16, weight: "fill" }), jsxRuntime.jsx("span", { className: "font-medium ml-1", children: "Stop" })] })) : (jsxRuntime.jsxs("button", { className: "border rounded px-2 py-1.5 text-green-700 border-green-600 flex items-center gap-1 disabled:text-gray-400 disabled:border-gray-300", onClick: (evt) => {
|
|
4001
4068
|
const kind = engine;
|
|
4002
4069
|
if (!kind)
|
|
4003
4070
|
return alert("Select an engine first.");
|