@bian-womp/spark-remote 0.2.93 → 0.3.0

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