@bian-womp/spark-remote 0.2.94 → 0.3.1

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 (43) hide show
  1. package/lib/cjs/index.cjs +358 -344
  2. package/lib/cjs/index.cjs.map +1 -1
  3. package/lib/cjs/src/RemoteEngine.d.ts +8 -2
  4. package/lib/cjs/src/RemoteEngine.d.ts.map +1 -1
  5. package/lib/cjs/src/RemoteGraphLifecycleApi.d.ts +39 -0
  6. package/lib/cjs/src/RemoteGraphLifecycleApi.d.ts.map +1 -0
  7. package/lib/cjs/src/{RuntimeApiClient.d.ts → RemoteRuntimeClient.d.ts} +22 -38
  8. package/lib/cjs/src/RemoteRuntimeClient.d.ts.map +1 -0
  9. package/lib/cjs/src/examples/shared.d.ts.map +1 -1
  10. package/lib/cjs/src/index.d.ts +6 -3
  11. package/lib/cjs/src/index.d.ts.map +1 -1
  12. package/lib/cjs/src/server/{RuntimeApiServer.d.ts → ServerCommandHandler.d.ts} +5 -5
  13. package/lib/cjs/src/server/ServerCommandHandler.d.ts.map +1 -0
  14. package/lib/cjs/src/server/ServerRuntimeAdapter.d.ts +26 -0
  15. package/lib/cjs/src/server/ServerRuntimeAdapter.d.ts.map +1 -0
  16. package/lib/esm/index.js +356 -343
  17. package/lib/esm/index.js.map +1 -1
  18. package/lib/esm/src/RemoteEngine.d.ts +8 -2
  19. package/lib/esm/src/RemoteEngine.d.ts.map +1 -1
  20. package/lib/esm/src/RemoteGraphLifecycleApi.d.ts +39 -0
  21. package/lib/esm/src/RemoteGraphLifecycleApi.d.ts.map +1 -0
  22. package/lib/esm/src/{RuntimeApiClient.d.ts → RemoteRuntimeClient.d.ts} +22 -38
  23. package/lib/esm/src/RemoteRuntimeClient.d.ts.map +1 -0
  24. package/lib/esm/src/examples/shared.d.ts.map +1 -1
  25. package/lib/esm/src/index.d.ts +6 -3
  26. package/lib/esm/src/index.d.ts.map +1 -1
  27. package/lib/esm/src/server/{RuntimeApiServer.d.ts → ServerCommandHandler.d.ts} +5 -5
  28. package/lib/esm/src/server/ServerCommandHandler.d.ts.map +1 -0
  29. package/lib/esm/src/server/ServerRuntimeAdapter.d.ts +26 -0
  30. package/lib/esm/src/server/ServerRuntimeAdapter.d.ts.map +1 -0
  31. package/package.json +3 -3
  32. package/lib/cjs/src/RuntimeApiClient.d.ts.map +0 -1
  33. package/lib/cjs/src/protocol.d.ts +0 -2
  34. package/lib/cjs/src/protocol.d.ts.map +0 -1
  35. package/lib/cjs/src/server/RuntimeApiServer.d.ts.map +0 -1
  36. package/lib/cjs/src/server/runtime.d.ts +0 -56
  37. package/lib/cjs/src/server/runtime.d.ts.map +0 -1
  38. package/lib/esm/src/RuntimeApiClient.d.ts.map +0 -1
  39. package/lib/esm/src/protocol.d.ts +0 -2
  40. package/lib/esm/src/protocol.d.ts.map +0 -1
  41. package/lib/esm/src/server/RuntimeApiServer.d.ts.map +0 -1
  42. package/lib/esm/src/server/runtime.d.ts +0 -56
  43. package/lib/esm/src/server/runtime.d.ts.map +0 -1
package/lib/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { WebSocket } from 'ws';
2
- import { GraphBuilder, createEngine } from '@bian-womp/spark-graph';
2
+ import { GraphBuilder, UnifiedEngine } from '@bian-womp/spark-graph';
3
3
 
4
4
  class SeqGenerator {
5
5
  constructor() {
@@ -370,7 +370,7 @@ function serializeError(err) {
370
370
  }
371
371
  }
372
372
 
373
- async function createRuntimeAdapter(id, createRegistry, send, extensions) {
373
+ async function createServerRuntimeAdapter(id, createRegistry, send, extensions) {
374
374
  const registry = await createRegistry(id);
375
375
  const builder = new GraphBuilder(registry);
376
376
  let graphRuntime;
@@ -385,7 +385,7 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
385
385
  extData,
386
386
  });
387
387
  // Original implementations - define as separate functions first to allow cross-references
388
- const originalApi = {
388
+ const originalAdapter = {
389
389
  coerce: async (from, to, value) => {
390
390
  const resolved = registry.resolveCoercion(from, to);
391
391
  if (!resolved)
@@ -395,7 +395,7 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
395
395
  const ac = new AbortController();
396
396
  return await resolved.convertAsync(value, ac.signal);
397
397
  },
398
- getEnvironment: () => {
398
+ getEnvironment: async () => {
399
399
  return graphRuntime?.getEnvironment?.() ?? {};
400
400
  },
401
401
  applyRegistry: async (deltas) => {
@@ -443,14 +443,14 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
443
443
  });
444
444
  },
445
445
  build: async (def, opts) => {
446
- const env = opts || {};
446
+ const env = opts?.environment || {};
447
447
  graphRuntime = builder.build(def, { environment: env });
448
448
  graphRuntime.on("value", (p) => send({ message: { type: "value", payload: p } }));
449
449
  graphRuntime.on("invalidate", (p) => send({ message: { type: "invalidate", payload: p } }));
450
450
  graphRuntime.on("error", (p) => send({ message: { type: "error", payload: p } }));
451
451
  graphRuntime.on("stats", (p) => send({ message: { type: "stats", payload: p } }));
452
452
  },
453
- setExtData: (data) => {
453
+ setExtData: async (data) => {
454
454
  if (!data || typeof data !== "object") {
455
455
  // Clear all extData when called with non-object (backward-compatible)
456
456
  extData = {};
@@ -460,10 +460,10 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
460
460
  // (e.g., { ui } or { runtime }) without dropping the other ones.
461
461
  extData = { ...extData, ...data };
462
462
  },
463
- getExtData: () => {
463
+ getExtData: async () => {
464
464
  return extData;
465
465
  },
466
- snapshot: () => {
466
+ snapshot: async () => {
467
467
  const inputs = {};
468
468
  const outputs = {};
469
469
  if (!graphRuntime)
@@ -480,9 +480,9 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
480
480
  }
481
481
  return { inputs, outputs };
482
482
  },
483
- snapshotFull: () => {
484
- const snap = originalApi.snapshot();
485
- const extData = originalApi.getExtData();
483
+ snapshotFull: async () => {
484
+ const snap = await originalAdapter.snapshot();
485
+ const extData = await originalAdapter.getExtData();
486
486
  const env = graphRuntime?.getEnvironment?.() ?? {};
487
487
  const def = graphRuntime?.getGraphDef();
488
488
  return {
@@ -499,27 +499,31 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
499
499
  return;
500
500
  // Only build if skipBuild is not true
501
501
  if (!options?.skipBuild) {
502
- await originalApi.build(def, payload.environment);
502
+ await originalAdapter.build(def, { environment: payload.environment });
503
503
  }
504
504
  else if (!graphRuntime) {
505
505
  // If skipping build but no runtime exists, we still need to build
506
506
  console.info("Skipping build is set but no runtime exists, building anyway");
507
- await originalApi.build(def, payload.environment);
507
+ await originalAdapter.build(def, { environment: payload.environment });
508
508
  }
509
509
  // Hydrate inputs/outputs exactly, then re-emit outputs without scheduling runs
510
510
  graphRuntime?.hydrate({
511
511
  inputs: payload.inputs,
512
512
  outputs: payload.outputs,
513
513
  });
514
- originalApi.setExtData(payload.extData || {});
514
+ await originalAdapter.setExtData(payload.extData || {});
515
515
  },
516
516
  cancelNodeRuns: (nodeIds) => {
517
- graphRuntime?.cancelNodeRuns(nodeIds);
517
+ if (!graphRuntime)
518
+ return;
519
+ graphRuntime.cancelNodeRuns(nodeIds);
520
+ },
521
+ commit: async (reason) => {
522
+ return undefined;
518
523
  },
519
- commit: async (reason) => { },
520
524
  undo: async () => false,
521
525
  redo: async () => false,
522
- describeRegistry: () => {
526
+ describeRegistry: async () => {
523
527
  // types (include enum options when available)
524
528
  const types = Array.from(registry.types.entries()).map(([id, d]) => {
525
529
  const en = registry.enums.get(id);
@@ -568,7 +572,7 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
568
572
  graphRuntime.resume();
569
573
  }
570
574
  },
571
- setEnvironment: (env, opts) => {
575
+ setEnvironment: async (env, opts) => {
572
576
  if (!graphRuntime)
573
577
  return;
574
578
  const wasPaused = graphRuntime.isPaused();
@@ -590,54 +594,21 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
590
594
  }
591
595
  },
592
596
  setInputs: (nodeId, inputs, options) => {
593
- if (!graphRuntime)
597
+ if (!engine)
594
598
  return;
595
- const wasPaused = graphRuntime.isPaused();
596
- if (options?.dry && !wasPaused)
597
- graphRuntime.pause();
598
- try {
599
- if (engine) {
600
- engine.setInputs(nodeId, inputs);
601
- }
602
- else {
603
- graphRuntime.setInputs(nodeId, inputs);
604
- }
605
- }
606
- finally {
607
- if (options?.dry && !wasPaused)
608
- graphRuntime.resume();
609
- }
599
+ engine.setInputs(nodeId, inputs, options);
610
600
  },
611
601
  copyOutputs: (fromNodeId, toNodeId, options) => {
612
- if (!graphRuntime)
613
- return;
614
- // Get outputs from source node
615
- const fromNode = graphRuntime.getNodeData(fromNodeId);
616
- if (!fromNode?.outputs)
602
+ if (!engine)
617
603
  return;
618
- // Copy outputs to target node using hydrate
619
- graphRuntime.hydrate({ outputs: { [toNodeId]: { ...fromNode.outputs } } }, { reemit: !options?.dry });
604
+ engine.copyOutputs(fromNodeId, toNodeId, options);
620
605
  },
621
606
  triggerExternal: (nodeId, event, options) => {
622
- if (!graphRuntime)
607
+ if (!engine)
623
608
  return;
624
- const wasPaused = graphRuntime.isPaused();
625
- if (options?.dry && !wasPaused)
626
- graphRuntime.pause();
627
- try {
628
- if (engine) {
629
- engine.triggerExternal(nodeId, event);
630
- }
631
- else {
632
- graphRuntime.triggerExternal(nodeId, event);
633
- }
634
- }
635
- finally {
636
- if (options?.dry && !wasPaused)
637
- graphRuntime.resume();
638
- }
609
+ engine.triggerExternal(nodeId, event, options);
639
610
  },
640
- launch: (opts) => {
611
+ launch: async (opts) => {
641
612
  if (!graphRuntime) {
642
613
  throw new Error("Cannot launch: graph runtime not built");
643
614
  }
@@ -647,49 +618,52 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
647
618
  engine = undefined;
648
619
  }
649
620
  // Create new engine using shared factory
650
- engine = createEngine(graphRuntime, opts);
651
- // Launch the engine
652
- if (engine) {
653
- engine.launch(opts?.invalidate);
654
- }
621
+ engine = new UnifiedEngine(graphRuntime, opts?.runMode);
622
+ engine.launch(opts?.invalidate);
655
623
  },
656
- step: async () => {
624
+ setRunMode: (runMode) => {
657
625
  if (!engine)
658
626
  return;
659
- // Type guard for StepEngine
660
- if ("step" in engine && typeof engine.step === "function") {
661
- await engine.step();
662
- }
627
+ engine.setRunMode(runMode);
663
628
  },
664
- computeNode: async (nodeId) => {
629
+ computeNode: async (nodeId, options) => {
665
630
  if (!engine)
666
631
  return;
667
- // Type guard for PullEngine
668
- if ("computeNode" in engine && typeof engine.computeNode === "function") {
669
- await engine.computeNode(nodeId);
670
- }
632
+ await engine.computeNode(nodeId, options);
671
633
  },
672
- flush: async () => {
634
+ runFromHere: async (nodeId) => {
673
635
  if (!engine)
674
636
  return;
675
- // Type guard for BatchedEngine
676
- if ("flush" in engine && typeof engine.flush === "function") {
677
- await engine.flush();
678
- }
637
+ await engine.runFromHere(nodeId);
679
638
  },
680
- whenIdle: () => {
681
- if (engine) {
682
- return engine.whenIdle();
683
- }
684
- return graphRuntime?.whenIdle?.() ?? Promise.resolve();
639
+ whenIdle: async () => {
640
+ if (!engine)
641
+ return;
642
+ await engine.whenIdle();
685
643
  },
686
644
  dispose: () => {
687
645
  if (engine) {
688
646
  engine.dispose();
689
647
  engine = undefined;
690
648
  }
691
- graphRuntime?.dispose?.();
692
- graphRuntime = undefined;
649
+ if (graphRuntime) {
650
+ graphRuntime.dispose();
651
+ graphRuntime = undefined;
652
+ }
653
+ },
654
+ // Engine methods
655
+ getOutput: (nodeId, output) => {
656
+ return engine?.getOutput(nodeId, output);
657
+ },
658
+ on: (event, handler) => {
659
+ if (!engine)
660
+ return () => { };
661
+ return engine.on(event, handler);
662
+ },
663
+ // GraphLifecycleApi methods
664
+ setViewport: async (viewport) => {
665
+ // Viewport is UI-specific, server doesn't need to handle it
666
+ // But we implement it to satisfy the interface
693
667
  },
694
668
  };
695
669
  // Helper to wrap a method with extension support
@@ -707,34 +681,39 @@ async function createRuntimeAdapter(id, createRegistry, send, extensions) {
707
681
  });
708
682
  };
709
683
  // Create API with extensions applied
710
- const extendedApi = {
711
- coerce: wrapMethod("coerce", originalApi.coerce),
712
- getEnvironment: wrapMethod("getEnvironment", originalApi.getEnvironment),
713
- applyRegistry: wrapMethod("applyRegistry", originalApi.applyRegistry),
714
- build: wrapMethod("build", originalApi.build),
715
- setExtData: wrapMethod("setExtData", originalApi.setExtData),
716
- getExtData: wrapMethod("getExtData", originalApi.getExtData),
717
- snapshot: wrapMethod("snapshot", originalApi.snapshot),
718
- snapshotFull: wrapMethod("snapshotFull", originalApi.snapshotFull),
719
- applySnapshotFull: wrapMethod("applySnapshotFull", originalApi.applySnapshotFull),
720
- describeRegistry: wrapMethod("describeRegistry", originalApi.describeRegistry),
721
- update: wrapMethod("update", originalApi.update),
722
- setEnvironment: wrapMethod("setEnvironment", originalApi.setEnvironment),
723
- setInputs: wrapMethod("setInputs", originalApi.setInputs),
724
- copyOutputs: wrapMethod("copyOutputs", originalApi.copyOutputs),
725
- triggerExternal: wrapMethod("triggerExternal", originalApi.triggerExternal),
726
- step: wrapMethod("step", originalApi.step),
727
- computeNode: wrapMethod("computeNode", originalApi.computeNode),
728
- flush: wrapMethod("flush", originalApi.flush),
729
- launch: wrapMethod("launch", originalApi.launch),
730
- whenIdle: wrapMethod("whenIdle", originalApi.whenIdle),
731
- dispose: wrapMethod("dispose", originalApi.dispose),
732
- commit: wrapMethod("commit", originalApi.commit),
733
- cancelNodeRuns: wrapMethod("cancelNodeRuns", originalApi.cancelNodeRuns),
734
- undo: wrapMethod("undo", originalApi.undo),
735
- redo: wrapMethod("redo", originalApi.redo),
684
+ const extendedAdapter = {
685
+ // GraphLifecycleApi methods
686
+ coerce: wrapMethod("coerce", originalAdapter.coerce),
687
+ getEnvironment: wrapMethod("getEnvironment", originalAdapter.getEnvironment),
688
+ applyRegistry: wrapMethod("applyRegistry", originalAdapter.applyRegistry),
689
+ build: wrapMethod("build", originalAdapter.build),
690
+ setExtData: wrapMethod("setExtData", originalAdapter.setExtData),
691
+ getExtData: wrapMethod("getExtData", originalAdapter.getExtData),
692
+ snapshot: wrapMethod("snapshot", originalAdapter.snapshot),
693
+ snapshotFull: wrapMethod("snapshotFull", originalAdapter.snapshotFull),
694
+ applySnapshotFull: wrapMethod("applySnapshotFull", originalAdapter.applySnapshotFull),
695
+ describeRegistry: wrapMethod("describeRegistry", originalAdapter.describeRegistry),
696
+ update: wrapMethod("update", originalAdapter.update),
697
+ setEnvironment: wrapMethod("setEnvironment", originalAdapter.setEnvironment),
698
+ setViewport: wrapMethod("setViewport", originalAdapter.setViewport),
699
+ commit: wrapMethod("commit", originalAdapter.commit),
700
+ undo: wrapMethod("undo", originalAdapter.undo),
701
+ redo: wrapMethod("redo", originalAdapter.redo),
702
+ launch: wrapMethod("launch", originalAdapter.launch),
703
+ // Engine methods
704
+ setInputs: wrapMethod("setInputs", originalAdapter.setInputs),
705
+ copyOutputs: wrapMethod("copyOutputs", originalAdapter.copyOutputs),
706
+ triggerExternal: wrapMethod("triggerExternal", originalAdapter.triggerExternal),
707
+ computeNode: wrapMethod("computeNode", originalAdapter.computeNode),
708
+ runFromHere: wrapMethod("runFromHere", originalAdapter.runFromHere),
709
+ setRunMode: wrapMethod("setRunMode", originalAdapter.setRunMode),
710
+ whenIdle: wrapMethod("whenIdle", originalAdapter.whenIdle),
711
+ dispose: wrapMethod("dispose", originalAdapter.dispose),
712
+ cancelNodeRuns: wrapMethod("cancelNodeRuns", originalAdapter.cancelNodeRuns),
713
+ getOutput: wrapMethod("getOutput", originalAdapter.getOutput),
714
+ on: wrapMethod("on", originalAdapter.on),
736
715
  };
737
- return extendedApi;
716
+ return extendedAdapter;
738
717
  }
739
718
 
740
719
  const UNSET_MARKER = { __spark_unset: true };
@@ -788,11 +767,19 @@ class RemoteEngine {
788
767
  this.emit("stats", msg.payload);
789
768
  }
790
769
  }
791
- launch(invalidate, engineConfig) {
770
+ launch(invalidate, runMode) {
792
771
  this.transport.send({
793
772
  message: {
794
773
  type: "Launch",
795
- payload: { invalidate, ...engineConfig },
774
+ payload: { runMode, invalidate },
775
+ },
776
+ });
777
+ }
778
+ setRunMode(runMode) {
779
+ this.transport.send({
780
+ message: {
781
+ type: "SetRunMode",
782
+ payload: { runMode },
796
783
  },
797
784
  });
798
785
  }
@@ -843,11 +830,187 @@ class RemoteEngine {
843
830
  async whenIdle() {
844
831
  await this.transport.request({ message: { type: "WhenIdle" } });
845
832
  }
833
+ async computeNode(nodeId, options) {
834
+ await this.transport.request({
835
+ message: { type: "ComputeNode", payload: { nodeId, ...options } },
836
+ });
837
+ }
838
+ async runFromHere(nodeId) {
839
+ await this.transport.request({
840
+ message: { type: "RunFromHere", payload: { nodeId } },
841
+ });
842
+ }
843
+ async cancelNodeRuns(nodeIds) {
844
+ await this.transport.request({
845
+ message: { type: "CancelNodeRuns", payload: { nodeIds } },
846
+ });
847
+ }
846
848
  dispose() {
847
849
  this.transport.send({ message: { type: "Dispose" } });
848
850
  }
849
851
  }
850
852
 
853
+ /**
854
+ * RemoteGraphLifecycleApi implements GraphLifecycleApi by sending commands over transport.
855
+ * This handles all non-execution operations (graph lifecycle, snapshots, registry, etc.)
856
+ */
857
+ class RemoteGraphLifecycleApi {
858
+ constructor(transport) {
859
+ this.transport = transport;
860
+ }
861
+ async ensureConnected() {
862
+ if (!this.transport) {
863
+ throw new Error("Transport not connected");
864
+ }
865
+ return this.transport;
866
+ }
867
+ async build(def, opts) {
868
+ const transport = await this.ensureConnected();
869
+ await transport.request({
870
+ message: {
871
+ type: "Build",
872
+ payload: { def, environment: opts?.environment },
873
+ },
874
+ });
875
+ }
876
+ async update(def, options) {
877
+ const transport = await this.ensureConnected();
878
+ await transport.request({
879
+ message: { type: "Update", payload: { def }, dry: options?.dry },
880
+ });
881
+ }
882
+ async launch(opts) {
883
+ const transport = await this.ensureConnected();
884
+ await transport.request({
885
+ message: {
886
+ type: "Launch",
887
+ payload: opts,
888
+ },
889
+ });
890
+ }
891
+ async snapshot() {
892
+ const transport = await this.ensureConnected();
893
+ const res = await transport.request({
894
+ message: { type: "Snapshot" },
895
+ });
896
+ const payload = res?.message || {};
897
+ return payload.snapshot || { inputs: {}, outputs: {} };
898
+ }
899
+ async snapshotFull() {
900
+ const transport = await this.ensureConnected();
901
+ const res = await transport.request({
902
+ message: { type: "SnapshotFull" },
903
+ });
904
+ const payload = res?.message || {};
905
+ return (payload.snapshot || {
906
+ def: undefined,
907
+ environment: {},
908
+ inputs: {},
909
+ outputs: {},
910
+ });
911
+ }
912
+ async applySnapshotFull(payload, options) {
913
+ const transport = await this.ensureConnected();
914
+ await transport.request({
915
+ message: {
916
+ type: "ApplySnapshotFull",
917
+ payload,
918
+ skipBuild: options?.skipBuild,
919
+ },
920
+ });
921
+ }
922
+ async describeRegistry() {
923
+ const transport = await this.ensureConnected();
924
+ const res = await transport.request({
925
+ message: { type: "DescribeRegistry" },
926
+ });
927
+ const payload = res?.message || {};
928
+ return (payload.registry || {
929
+ types: [],
930
+ categories: [],
931
+ nodes: [],
932
+ coercions: [],
933
+ schemaVersion: 4,
934
+ });
935
+ }
936
+ async applyRegistry(deltas) {
937
+ const transport = await this.ensureConnected();
938
+ await transport.request({
939
+ message: { type: "RegistryApply", payload: { deltas } },
940
+ });
941
+ }
942
+ async setEnvironment(environment, opts) {
943
+ const transport = await this.ensureConnected();
944
+ await transport.request({
945
+ message: {
946
+ type: "SetEnvironment",
947
+ payload: { environment, merge: opts?.merge },
948
+ dry: opts?.dry,
949
+ },
950
+ });
951
+ }
952
+ async getEnvironment() {
953
+ const transport = await this.ensureConnected();
954
+ const res = await transport.request({
955
+ message: { type: "GetEnvironment" },
956
+ });
957
+ const payload = res?.message || {};
958
+ return payload.environment || {};
959
+ }
960
+ async coerce(from, to, value) {
961
+ const transport = await this.ensureConnected();
962
+ const res = await transport.request({
963
+ message: { type: "Coerce", payload: { from, to, value } },
964
+ });
965
+ const payload = res?.message || {};
966
+ return payload.value;
967
+ }
968
+ async setExtData(data) {
969
+ const transport = await this.ensureConnected();
970
+ await transport.request({
971
+ message: { type: "SetExtData", payload: data },
972
+ });
973
+ }
974
+ async getExtData() {
975
+ const transport = await this.ensureConnected();
976
+ const res = await transport.request({
977
+ message: { type: "GetExtData" },
978
+ });
979
+ const payload = res?.message || {};
980
+ return payload.extData || {};
981
+ }
982
+ async setViewport(viewport) {
983
+ const transport = await this.ensureConnected();
984
+ await transport.send({
985
+ message: { type: "SetViewport", payload: { viewport } },
986
+ });
987
+ }
988
+ async commit(reason) {
989
+ const transport = await this.ensureConnected();
990
+ const res = await transport.request({
991
+ message: { type: "Commit", payload: { reason } },
992
+ });
993
+ const payload = res?.message || {};
994
+ return payload.history;
995
+ }
996
+ async undo() {
997
+ const transport = await this.ensureConnected();
998
+ const res = await transport.request({
999
+ message: { type: "Undo" },
1000
+ });
1001
+ const payload = res?.message || {};
1002
+ return payload.success ?? false;
1003
+ }
1004
+ async redo() {
1005
+ const transport = await this.ensureConnected();
1006
+ const res = await transport.request({
1007
+ message: { type: "Redo" },
1008
+ });
1009
+ const payload = res?.message || {};
1010
+ return payload.success ?? false;
1011
+ }
1012
+ }
1013
+
851
1014
  // Node-only transport using a UNIX domain socket.
852
1015
  // Import directly from path: "@bian-womp/spark-remote/transport/UnixSocketTransport" in Node/Electron code.
853
1016
  // Do not re-export from the browser index to avoid bundling in web builds.
@@ -943,7 +1106,33 @@ class UnixSocketTransport {
943
1106
  }
944
1107
  }
945
1108
 
946
- class RuntimeApiClient {
1109
+ /**
1110
+ * RemoteRuntimeClient manages the connection to a remote runtime.
1111
+ * It provides access to:
1112
+ * - engine: RemoteEngine for execution operations
1113
+ * - api: GraphLifecycleApi for graph lifecycle operations
1114
+ */
1115
+ class RemoteRuntimeClient {
1116
+ /**
1117
+ * Get the Engine instance for execution operations.
1118
+ * Engine provides unified interface for local/remote execution.
1119
+ */
1120
+ get engine() {
1121
+ if (!this._engine) {
1122
+ throw new Error("Engine not available: call connect() first");
1123
+ }
1124
+ return this._engine;
1125
+ }
1126
+ /**
1127
+ * Get the GraphLifecycleApi instance for non-execution operations.
1128
+ * GraphLifecycleApi handles graph lifecycle, snapshots, registry, environment, etc.
1129
+ */
1130
+ get api() {
1131
+ if (!this._api) {
1132
+ throw new Error("GraphLifecycleApi not available: call connect() first");
1133
+ }
1134
+ return this._api;
1135
+ }
947
1136
  constructor(config, options) {
948
1137
  this.customEventListeners = new Set();
949
1138
  this.transportEventListeners = new Set();
@@ -974,7 +1163,7 @@ class RuntimeApiClient {
974
1163
  return new WebSocketTransport(this.config.url, {
975
1164
  onConnectionLost: () => {
976
1165
  this.dispose().catch((err) => {
977
- console.warn("[RuntimeApiClient] Error disposing on connection loss:", err);
1166
+ console.warn("[RemoteRuntimeClient] Error disposing on connection loss:", err);
978
1167
  });
979
1168
  },
980
1169
  connector: this.config.connectOptions?.connector,
@@ -995,7 +1184,7 @@ class RuntimeApiClient {
995
1184
  */
996
1185
  async connect() {
997
1186
  if (this.disposed) {
998
- throw new Error("Cannot connect: RuntimeApiClient has been disposed");
1187
+ throw new Error("Cannot connect: RemoteRuntimeClient has been disposed");
999
1188
  }
1000
1189
  if (this.transport) {
1001
1190
  // Already connected
@@ -1020,8 +1209,9 @@ class RuntimeApiClient {
1020
1209
  this.transportUnsubscribe = transport.subscribe((event) => {
1021
1210
  this.handleTransportEvent(event);
1022
1211
  });
1023
- // Create engine with connected transport
1024
- this.engine = new RemoteEngine(transport);
1212
+ // Create engine and api with connected transport
1213
+ this._engine = new RemoteEngine(transport);
1214
+ this._api = new RemoteGraphLifecycleApi(transport);
1025
1215
  this.emitTransportStatus({
1026
1216
  state: "connected",
1027
1217
  kind: this.config.kind,
@@ -1087,184 +1277,6 @@ class RuntimeApiClient {
1087
1277
  listener(status);
1088
1278
  }
1089
1279
  }
1090
- /**
1091
- * Ensure transport is connected. Called automatically by API methods.
1092
- * Returns the transport instance, throwing if connection fails or client is disposed.
1093
- */
1094
- async ensureConnected() {
1095
- if (this.disposed) {
1096
- throw new Error("Cannot ensure connection: RuntimeApiClient has been disposed");
1097
- }
1098
- if (!this.transport) {
1099
- await this.connect();
1100
- }
1101
- // After connect(), transport is guaranteed to be set
1102
- if (!this.transport) {
1103
- throw new Error("Failed to establish transport connection");
1104
- }
1105
- return this.transport;
1106
- }
1107
- async build(def, opts) {
1108
- const transport = await this.ensureConnected();
1109
- await transport.request({
1110
- message: {
1111
- type: "Build",
1112
- payload: { def, environment: opts?.environment },
1113
- },
1114
- });
1115
- }
1116
- async update(def, options) {
1117
- const transport = await this.ensureConnected();
1118
- await transport.request({
1119
- message: { type: "Update", payload: { def }, dry: options?.dry },
1120
- });
1121
- }
1122
- async describeRegistry() {
1123
- const transport = await this.ensureConnected();
1124
- const res = await transport.request({
1125
- message: { type: "DescribeRegistry" },
1126
- });
1127
- const payload = res?.message || {};
1128
- return (payload.registry || {
1129
- types: [],
1130
- categories: [],
1131
- nodes: [],
1132
- coercions: [],
1133
- schemaVersion: 4,
1134
- });
1135
- }
1136
- async applyRegistry(deltas) {
1137
- const transport = await this.ensureConnected();
1138
- await transport.request({
1139
- message: { type: "RegistryApply", payload: { deltas } },
1140
- });
1141
- }
1142
- async snapshot() {
1143
- const transport = await this.ensureConnected();
1144
- const res = await transport.request({
1145
- message: { type: "Snapshot" },
1146
- });
1147
- const payload = res?.message || {};
1148
- return payload.snapshot || { inputs: {}, outputs: {} };
1149
- }
1150
- async snapshotFull() {
1151
- const transport = await this.ensureConnected();
1152
- const res = await transport.request({
1153
- message: { type: "SnapshotFull" },
1154
- });
1155
- const payload = res?.message || {};
1156
- return (payload.snapshot || {
1157
- def: undefined,
1158
- environment: {},
1159
- inputs: {},
1160
- outputs: {},
1161
- });
1162
- }
1163
- async applySnapshotFull(payload, options) {
1164
- const transport = await this.ensureConnected();
1165
- await transport.request({
1166
- message: {
1167
- type: "ApplySnapshotFull",
1168
- payload,
1169
- skipBuild: options?.skipBuild,
1170
- },
1171
- });
1172
- }
1173
- async setEnvironment(environment, opts) {
1174
- const transport = await this.ensureConnected();
1175
- await transport.request({
1176
- message: {
1177
- type: "SetEnvironment",
1178
- payload: { environment, merge: opts?.merge },
1179
- dry: opts?.dry,
1180
- },
1181
- });
1182
- }
1183
- async getEnvironment() {
1184
- const transport = await this.ensureConnected();
1185
- const res = await transport.request({
1186
- message: { type: "GetEnvironment" },
1187
- });
1188
- const payload = res?.message || {};
1189
- return payload.environment || {};
1190
- }
1191
- async coerce(from, to, value) {
1192
- const transport = await this.ensureConnected();
1193
- const res = await transport.request({
1194
- message: { type: "Coerce", payload: { from, to, value } },
1195
- });
1196
- const payload = res?.message || {};
1197
- return payload.value;
1198
- }
1199
- getEngine() {
1200
- if (!this.engine) {
1201
- throw new Error("Engine not available: call connect() first");
1202
- }
1203
- return this.engine;
1204
- }
1205
- async launch(opts) {
1206
- const transport = await this.ensureConnected();
1207
- await transport.request({
1208
- message: {
1209
- type: "Launch",
1210
- payload: opts,
1211
- },
1212
- });
1213
- }
1214
- async step() {
1215
- const transport = await this.ensureConnected();
1216
- await transport.request({
1217
- message: { type: "Step" },
1218
- });
1219
- }
1220
- async computeNode(nodeId) {
1221
- const transport = await this.ensureConnected();
1222
- await transport.request({
1223
- message: { type: "ComputeNode", payload: { nodeId } },
1224
- });
1225
- }
1226
- async flush() {
1227
- const transport = await this.ensureConnected();
1228
- await transport.request({
1229
- message: { type: "Flush" },
1230
- });
1231
- }
1232
- async setExtData(data) {
1233
- const transport = await this.ensureConnected();
1234
- await transport.request({
1235
- message: { type: "SetExtData", payload: data },
1236
- });
1237
- }
1238
- async commit(reason) {
1239
- const transport = await this.ensureConnected();
1240
- const res = await transport.request({
1241
- message: { type: "Commit", payload: { reason } },
1242
- });
1243
- const payload = res?.message || {};
1244
- return payload.history;
1245
- }
1246
- async undo() {
1247
- const transport = await this.ensureConnected();
1248
- const res = await transport.request({
1249
- message: { type: "Undo" },
1250
- });
1251
- const payload = res?.message || {};
1252
- return payload.success ?? false;
1253
- }
1254
- async redo() {
1255
- const transport = await this.ensureConnected();
1256
- const res = await transport.request({
1257
- message: { type: "Redo" },
1258
- });
1259
- const payload = res?.message || {};
1260
- return payload.success ?? false;
1261
- }
1262
- async setViewport(viewport) {
1263
- const transport = await this.ensureConnected();
1264
- await transport.send({
1265
- message: { type: "SetViewport", payload: { viewport } },
1266
- });
1267
- }
1268
1280
  /**
1269
1281
  * Dispose the client and close the transport connection.
1270
1282
  * Idempotent: safe to call multiple times.
@@ -1284,9 +1296,10 @@ class RuntimeApiClient {
1284
1296
  if (this.transport) {
1285
1297
  const transportToClose = this.transport;
1286
1298
  this.transport = undefined;
1287
- this.engine = undefined; // Clear engine reference
1299
+ this._engine = undefined; // Clear engine reference
1300
+ this._api = undefined; // Clear api reference
1288
1301
  await transportToClose.close().catch((err) => {
1289
- console.warn("[RuntimeApiClient] Error closing transport:", err);
1302
+ console.warn("[RemoteRuntimeClient] Error closing transport:", err);
1290
1303
  });
1291
1304
  }
1292
1305
  // Clear listeners
@@ -1300,9 +1313,9 @@ class RuntimeApiClient {
1300
1313
  }
1301
1314
  }
1302
1315
 
1303
- class RuntimeApiServer {
1304
- constructor(runtimeApi, options) {
1305
- this.runtimeApi = runtimeApi;
1316
+ class ServerCommandHandler {
1317
+ constructor(adapter, options) {
1318
+ this.adapter = adapter;
1306
1319
  this.options = options;
1307
1320
  this.middlewares = [];
1308
1321
  }
@@ -1354,7 +1367,7 @@ class RuntimeApiServer {
1354
1367
  edges: msg.payload.def.edges?.length ?? 0,
1355
1368
  envKeys: Object.keys(msg.payload.environment ?? {}).join(","),
1356
1369
  });
1357
- await this.runtimeApi.build(msg.payload.def, msg.payload.environment);
1370
+ await this.adapter.build(msg.payload.def, msg.payload.environment);
1358
1371
  ack();
1359
1372
  break;
1360
1373
  }
@@ -1365,14 +1378,14 @@ class RuntimeApiServer {
1365
1378
  edges: msg.payload.def.edges?.length ?? 0,
1366
1379
  dry: !!dry,
1367
1380
  });
1368
- await this.runtimeApi.update(msg.payload.def, { dry });
1381
+ await this.adapter.update(msg.payload.def, { dry });
1369
1382
  ack();
1370
1383
  break;
1371
1384
  }
1372
1385
  case "SetEnvironment": {
1373
1386
  const dry = msg.dry;
1374
1387
  this.logCommand("SetEnvironment", env, dry ? { dry: true } : undefined);
1375
- this.runtimeApi.setEnvironment(msg.payload.environment, {
1388
+ await this.adapter.setEnvironment(msg.payload.environment, {
1376
1389
  merge: Boolean(msg.payload.merge),
1377
1390
  dry,
1378
1391
  });
@@ -1388,7 +1401,7 @@ class RuntimeApiServer {
1388
1401
  });
1389
1402
  const nodeId = msg.payload.nodeId;
1390
1403
  const inputs = decodeInputsFromTransport(msg.payload.inputs);
1391
- this.runtimeApi.setInputs(nodeId, inputs, { dry });
1404
+ await this.adapter.setInputs(nodeId, inputs, { dry });
1392
1405
  ack();
1393
1406
  break;
1394
1407
  }
@@ -1399,7 +1412,9 @@ class RuntimeApiServer {
1399
1412
  toNodeId: msg.payload.toNodeId,
1400
1413
  dry: !!dry,
1401
1414
  });
1402
- this.runtimeApi.copyOutputs(msg.payload.fromNodeId, msg.payload.toNodeId, { dry });
1415
+ await this.adapter.copyOutputs(msg.payload.fromNodeId, msg.payload.toNodeId, {
1416
+ dry,
1417
+ });
1403
1418
  ack();
1404
1419
  break;
1405
1420
  }
@@ -1410,7 +1425,7 @@ class RuntimeApiServer {
1410
1425
  event: summarize(msg.payload.event),
1411
1426
  dry: !!dry,
1412
1427
  });
1413
- this.runtimeApi.triggerExternal(msg.payload.nodeId, msg.payload.event, {
1428
+ await this.adapter.triggerExternal(msg.payload.nodeId, msg.payload.event, {
1414
1429
  dry,
1415
1430
  });
1416
1431
  ack();
@@ -1421,72 +1436,70 @@ class RuntimeApiServer {
1421
1436
  case "Launch": {
1422
1437
  const launchPayload = msg.payload;
1423
1438
  this.logCommand("Launch", env, {
1424
- engine: launchPayload.engine,
1425
- invalidate: launchPayload.invalidate,
1426
- });
1427
- this.runtimeApi.launch({
1439
+ runMode: launchPayload.runMode,
1428
1440
  invalidate: launchPayload.invalidate,
1429
- engine: launchPayload.engine,
1430
- batched: launchPayload.batched,
1431
- hybrid: launchPayload.hybrid,
1432
1441
  });
1442
+ await this.adapter.launch(launchPayload);
1433
1443
  ack();
1434
1444
  break;
1435
1445
  }
1436
- case "Step": {
1437
- this.logCommand("Step", env);
1438
- if (this.runtimeApi.step) {
1439
- await this.runtimeApi.step();
1440
- }
1446
+ case "SetRunMode": {
1447
+ const setRunModePayload = msg.payload;
1448
+ this.logCommand("SetRunMode", env, setRunModePayload);
1449
+ await this.adapter.setRunMode(setRunModePayload.runMode);
1441
1450
  ack();
1442
1451
  break;
1443
1452
  }
1444
1453
  case "ComputeNode": {
1445
- this.logCommand("ComputeNode", env, {
1454
+ const computeNodePayload = msg.payload;
1455
+ this.logCommand("ComputeNode", env, computeNodePayload);
1456
+ await this.adapter.computeNode(computeNodePayload.nodeId, computeNodePayload);
1457
+ ack();
1458
+ break;
1459
+ }
1460
+ case "RunFromHere": {
1461
+ this.logCommand("RunFromHere", env, {
1446
1462
  nodeId: msg.payload.nodeId,
1447
1463
  });
1448
- if (this.runtimeApi.computeNode) {
1449
- await this.runtimeApi.computeNode(msg.payload.nodeId);
1450
- }
1464
+ await this.adapter.runFromHere(msg.payload.nodeId);
1451
1465
  ack();
1452
1466
  break;
1453
1467
  }
1454
- case "Flush": {
1455
- this.logCommand("Flush", env);
1456
- if (this.runtimeApi.flush) {
1457
- await this.runtimeApi.flush();
1458
- }
1468
+ case "CancelNodeRuns": {
1469
+ const cancelPayload = msg.payload;
1470
+ this.logCommand("CancelNodeRuns", env, cancelPayload);
1471
+ await this.adapter.cancelNodeRuns(cancelPayload.nodeIds);
1459
1472
  ack();
1460
1473
  break;
1461
1474
  }
1462
1475
  case "WhenIdle": {
1463
1476
  this.logCommand("WhenIdle", env);
1464
- await this.runtimeApi.whenIdle();
1477
+ await this.adapter.whenIdle();
1465
1478
  ack();
1466
1479
  break;
1467
1480
  }
1468
1481
  case "Snapshot": {
1469
1482
  this.logCommand("Snapshot", env);
1470
- const snap = this.runtimeApi.snapshot();
1483
+ const snap = await this.adapter.snapshot();
1471
1484
  ack({ snapshot: snap });
1472
1485
  break;
1473
1486
  }
1474
1487
  case "SnapshotFull": {
1475
1488
  this.logCommand("SnapshotFull", env);
1476
- const snap = this.runtimeApi.snapshotFull();
1489
+ const snap = await this.adapter.snapshotFull();
1477
1490
  ack({ snapshot: snap });
1478
1491
  break;
1479
1492
  }
1480
1493
  case "GetEnvironment": {
1481
1494
  this.logCommand("GetEnvironment", env);
1482
- const environment = this.runtimeApi.getEnvironment();
1495
+ const environment = await this.adapter.getEnvironment();
1483
1496
  ack({ environment });
1484
1497
  break;
1485
1498
  }
1486
1499
  case "ApplySnapshotFull": {
1487
1500
  const skipBuild = msg.skipBuild;
1488
1501
  this.logCommand("ApplySnapshotFull", env, skipBuild ? { skipBuild: true } : undefined);
1489
- await this.runtimeApi.applySnapshotFull(msg.payload, { skipBuild });
1502
+ await this.adapter.applySnapshotFull(msg.payload, { skipBuild });
1490
1503
  ack();
1491
1504
  break;
1492
1505
  }
@@ -1495,48 +1508,48 @@ class RuntimeApiServer {
1495
1508
  from: msg.payload.from,
1496
1509
  to: msg.payload.to,
1497
1510
  });
1498
- const value = await this.runtimeApi.coerce(msg.payload.from, msg.payload.to, msg.payload.value);
1511
+ const value = await this.adapter.coerce(msg.payload.from, msg.payload.to, msg.payload.value);
1499
1512
  ack({ value });
1500
1513
  break;
1501
1514
  }
1502
1515
  case "DescribeRegistry": {
1503
1516
  this.logCommand("DescribeRegistry", env);
1504
- const desc = this.runtimeApi.describeRegistry();
1517
+ const desc = await this.adapter.describeRegistry();
1505
1518
  ack({ registry: desc });
1506
1519
  break;
1507
1520
  }
1508
1521
  case "RegistryApply": {
1509
1522
  this.logCommand("RegistryApply", env);
1510
- await this.runtimeApi.applyRegistry(msg.payload.deltas || []);
1523
+ await this.adapter.applyRegistry(msg.payload.deltas || []);
1511
1524
  ack();
1512
1525
  break;
1513
1526
  }
1514
1527
  case "SetExtData": {
1515
- this.runtimeApi.setExtData(msg.payload);
1528
+ await this.adapter.setExtData(msg.payload);
1516
1529
  ack();
1517
1530
  break;
1518
1531
  }
1519
1532
  case "Dispose": {
1520
1533
  this.logCommand("Dispose", env);
1521
- this.runtimeApi.dispose();
1534
+ await this.adapter.dispose();
1522
1535
  ack();
1523
1536
  break;
1524
1537
  }
1525
1538
  case "Commit": {
1526
1539
  this.logCommand("Commit", env);
1527
- const history = await this.runtimeApi.commit(msg.payload.reason);
1540
+ const history = await this.adapter.commit(msg.payload.reason);
1528
1541
  ack(history ? { history } : undefined);
1529
1542
  break;
1530
1543
  }
1531
1544
  case "Undo": {
1532
1545
  this.logCommand("Undo", env);
1533
- const success = await this.runtimeApi.undo();
1546
+ const success = await this.adapter.undo();
1534
1547
  ack({ success });
1535
1548
  break;
1536
1549
  }
1537
1550
  case "Redo": {
1538
1551
  this.logCommand("Redo", env);
1539
- const success = await this.runtimeApi.redo();
1552
+ const success = await this.adapter.redo();
1540
1553
  ack({ success });
1541
1554
  break;
1542
1555
  }
@@ -1602,5 +1615,5 @@ class RuntimeApiServer {
1602
1615
  }
1603
1616
  }
1604
1617
 
1605
- export { HttpPollingTransport, RemoteEngine, RuntimeApiClient, RuntimeApiServer, WebSocketTransport, createRuntimeAdapter, serializeError, summarize };
1618
+ export { HttpPollingTransport, RemoteEngine, RemoteGraphLifecycleApi, RemoteRuntimeClient, ServerCommandHandler, WebSocketTransport, createServerRuntimeAdapter, serializeError, summarize };
1606
1619
  //# sourceMappingURL=index.js.map