@orkestrel/process 0.0.7 → 0.0.8

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.
@@ -7,8 +7,8 @@ let node_process = require("node:process");
7
7
  let _orkestrel_contract = require("@orkestrel/contract");
8
8
  let _src_core = require("../core/index.cjs");
9
9
  let node_readline = require("node:readline");
10
- let node_string_decoder = require("node:string_decoder");
11
10
  let _orkestrel_emitter = require("@orkestrel/emitter");
11
+ let node_string_decoder = require("node:string_decoder");
12
12
  //#region src/server/helpers.ts
13
13
  /**
14
14
  * Trims a buffer to at most `limit` trailing bytes without splitting a UTF-8 sequence.
@@ -693,87 +693,92 @@ function buildExecuteResult(input) {
693
693
  });
694
694
  }
695
695
  //#endregion
696
- //#region src/server/Process.ts
696
+ //#region src/server/Supervisor.ts
697
697
  /**
698
- * Supervises one child while keeping every observation channel aligned at termination.
698
+ * Supervises one child process and reports each lifecycle moment to the face composing it.
699
699
  *
700
700
  * @remarks
701
- * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read
702
- * the host child directly, so they expose native exit as soon as the host records it. `settled`,
703
- * `exit`, `evidence`, and `lines` reach one terminal moment after the read channels close or the
704
- * bounded `drain` window cuts them off. That window is armed by the child's native exit and by a
705
- * termination this package initiated, so every way a child can end reaches the moment. The terminal
706
- * routine freezes `evidence` and ends `lines` before it destroys stdout, stderr, or the emitter, so
707
- * the push channel never disappears while a pull channel can still change or wait indefinitely.
708
- *
709
- * Standard output is framed through `readline` into a single-consumer stream. Lines already framed
710
- * and queued remain available through the terminal moment, while bytes arriving after a requested
711
- * termination may be cut off. Standard error is forwarded through the live `stderr` event and
712
- * retained as a byte-bounded raw tail until that same moment. `stop` ends the child tree and the
713
- * observation surfaces. `destroy` performs that stop and then destroys the emitter after the
714
- * frozen terminal state exists.
715
- *
716
- * @example
717
- * ```ts
718
- * import { Process } from '@orkestrel/process/server'
719
- *
720
- * const child = new Process({
721
- * command: { file: 'node', arguments: ['--version'] },
722
- * workspace: process.cwd(),
723
- * })
724
- * const exit = await child.exit
725
- * await child.destroy()
726
- * ```
701
+ * This is the engine every supervised-child face shares. It owns the eager spawn, the byte-bounded
702
+ * stderr tail and its decoded chunks, the standard-input channel and its bounded confirmation, the
703
+ * termination sequence, and the progression from the child's native exit through the bounded `drain`
704
+ * window to one terminal moment. It owns no output framing and no observation surface: a face
705
+ * attaches its own consumer to `stdout`, frames bytes however its contract promises, and owns the
706
+ * emitter and event map its consumers subscribe to.
707
+ *
708
+ * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read the
709
+ * host child directly, so they expose native exit as soon as the host records it, and `ending`
710
+ * settles at that same moment. `settled`, `exit`, and `evidence` reach one terminal moment after the
711
+ * read channels close or the bounded `drain` window cuts them off. That window is armed by the
712
+ * child's native exit and by a termination this package initiated, so every way a child can end
713
+ * reaches the moment. The terminal routine hands the face its `close` and `terminal` moments before
714
+ * it destroys stdout and stderr, so a face's own pull surfaces are final while its read ends still
715
+ * exist.
716
+ *
717
+ * The face is a set of callbacks rather than an emitter, because each face publishes a different
718
+ * event map over the same moments. `chunk` and `fault` carry the live stderr text and the child or
719
+ * channel faults, `relieve` reports that a termination began so the face can release whatever
720
+ * backpressure it holds, `close` ends the face's read pipeline at the terminal moment, `terminal`
721
+ * carries the frozen {@link ProcessExit}, and `teardown` releases the face's own surface after the
722
+ * termination completes. `relieve` is optional because a face that never pauses the child's output
723
+ * holds no backpressure to release, and a face with nothing to do there would otherwise declare an
724
+ * empty method to say so.
727
725
  */
728
- var Process = class {
729
- #emitter;
726
+ var Supervisor = class {
727
+ #chunk;
728
+ #fault;
729
+ #relieve;
730
+ #close;
731
+ #terminal;
732
+ #teardown;
730
733
  #child;
731
- #reader;
732
734
  #grace;
733
735
  #drain;
734
736
  #evidence;
735
- #backlog;
736
737
  #delivery;
737
738
  #decoder = new node_string_decoder.StringDecoder("utf8");
738
739
  #exit = Promise.withResolvers();
739
- #lines;
740
- #queue = [];
741
- #waiters = [];
740
+ #ending = Promise.withResolvers();
742
741
  #signal;
743
742
  #abort;
744
743
  #writes = /* @__PURE__ */ new Map();
745
744
  #tail = node_buffer.Buffer.alloc(0);
746
- #head = 0;
747
- #pending = 0;
748
- #requested = false;
749
- #paused = false;
750
- #truncated = false;
751
745
  #stopping = false;
752
746
  #settled = false;
753
- #ended = false;
754
747
  #input = 0;
755
748
  #failure;
756
749
  #waiting;
750
+ #closure;
757
751
  #termination;
758
- #ending;
752
+ #destruction;
759
753
  /**
760
- * Spawn one child process and begin stream capture.
754
+ * Spawn one child process and begin standard-error capture.
761
755
  *
762
- * @param options - Command, workspace, termination, capture, stdin, and observation settings
756
+ * @remarks
757
+ * The face's callbacks are captured before anything is read or spawned, so the first moment the
758
+ * child can produce already has somewhere to go. `options` supplies the command, workspace,
759
+ * termination, capture, and standard-input settings; a face's own settings, such as an output
760
+ * retention bound, are read and validated by that face before it constructs this engine, so an
761
+ * invalid one refuses construction while nothing has been spawned.
762
+ *
763
+ * @param options - Command, workspace, termination, capture, and stdin settings
764
+ * @param face - The composing face's callbacks for each lifecycle moment
763
765
  * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
764
766
  */
765
- constructor(options) {
767
+ constructor(options, face) {
768
+ this.#chunk = face.chunk;
769
+ this.#fault = face.fault;
770
+ this.#relieve = face.relieve;
771
+ this.#close = face.close;
772
+ this.#terminal = face.terminal;
773
+ this.#teardown = face.teardown;
766
774
  const source = options.command;
767
775
  const workspace = options.workspace;
768
776
  const grace = options.grace;
769
777
  const drain = options.drain;
770
778
  const evidence = options.evidence;
771
- const backlog = options.backlog;
772
779
  const delivery = options.delivery;
773
780
  const writable = options.writable;
774
781
  const signal = options.signal;
775
- const on = options.on;
776
- const error = options.error;
777
782
  const command = snapshotCommand(source);
778
783
  const input = command.input;
779
784
  validateCommand(command);
@@ -782,18 +787,11 @@ var Process = class {
782
787
  validateTimer(drain, "option 'drain'");
783
788
  validateTimer(delivery, "option 'delivery'");
784
789
  validateBytes(evidence, "option 'evidence'", 0);
785
- validateBytes(backlog, "option 'backlog'", 1);
786
- this.#emitter = new _orkestrel_emitter.Emitter({
787
- ...on === void 0 ? {} : { on },
788
- ...error === void 0 ? {} : { error }
789
- });
790
790
  this.#grace = grace ?? _src_core.PROCESS_GRACE;
791
791
  this.#drain = drain ?? _src_core.PROCESS_DRAIN;
792
792
  this.#evidence = evidence ?? _src_core.PROCESS_EVIDENCE;
793
- this.#backlog = backlog ?? _src_core.PROCESS_BACKLOG;
794
793
  this.#delivery = delivery ?? 0;
795
794
  this.#signal = signal;
796
- this.#lines = Object.freeze({ [Symbol.asyncIterator]: this.#iterate.bind(this) });
797
795
  const childEnvironment = mergeEnvironment(command.isolated === true, command.environment);
798
796
  const plan = buildSpawn(command, {
799
797
  workspace,
@@ -811,16 +809,10 @@ var Process = class {
811
809
  windowsHide: true,
812
810
  windowsVerbatimArguments: plan.verbatim
813
811
  });
814
- this.#reader = (0, node_readline.createInterface)({
815
- input: this.#child.stdout,
816
- crlfDelay: Infinity
817
- });
818
- this.#reader.on("line", this.#push.bind(this));
819
- this.#reader.once("close", this.#finish.bind(this));
820
- this.#child.once("error", (cause) => this.#emitter.emit("error", cause));
812
+ this.#child.once("error", this.#fault);
821
813
  this.#child.once("exit", this.#expire.bind(this));
822
- this.#child.once("close", this.#close.bind(this));
823
- this.#child.stdin.on("error", (cause) => this.#failInputStream(cause));
814
+ this.#child.once("close", this.#complete.bind(this));
815
+ this.#child.stdin.on("error", this.#failInputStream.bind(this));
824
816
  this.#child.stderr.on("data", this.#retain.bind(this));
825
817
  if (input !== void 0) {
826
818
  this.#input += 1;
@@ -836,6 +828,10 @@ var Process = class {
836
828
  if (signal.aborted) this.stop();
837
829
  }
838
830
  }
831
+ /** The child's standard-output stream, for the composing face to attach its own consumer to. */
832
+ get stdout() {
833
+ return this.#child.stdout;
834
+ }
839
835
  /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
840
836
  get pid() {
841
837
  return this.#child.pid;
@@ -848,22 +844,10 @@ var Process = class {
848
844
  get signal() {
849
845
  return this.#child.signalCode;
850
846
  }
851
- /** The typed lifecycle observation surface. */
852
- get emitter() {
853
- return this.#emitter;
854
- }
855
- /** The captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
856
- get lines() {
857
- return this.#lines;
858
- }
859
847
  /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
860
848
  get evidence() {
861
849
  return this.#tail.toString("utf8");
862
850
  }
863
- /** True when the `lines` stream omitted output after a retention bound was reached. */
864
- get truncated() {
865
- return this.#truncated;
866
- }
867
851
  /** True after the terminal moment arrived and `exit` settled. */
868
852
  get settled() {
869
853
  return this.#settled;
@@ -872,34 +856,43 @@ var Process = class {
872
856
  get stopping() {
873
857
  return this.#stopping;
874
858
  }
859
+ /**
860
+ * The child's own ending, awaited without the terminal moment's drain window.
861
+ *
862
+ * @remarks
863
+ * Never rejects, and resolves no value: `code` and `signal` carry the terminal facts as soon as
864
+ * the host records them. It settles at the native exit, and at the terminal moment for a child
865
+ * whose spawn produced no native exit at all, so it can never outlive the supervision.
866
+ */
867
+ get ending() {
868
+ return this.#ending.promise;
869
+ }
875
870
  /** The terminal child state, observed once after stream close or the drain cutoff. */
876
871
  get exit() {
877
872
  return this.#exit.promise;
878
873
  }
879
874
  /**
880
- * Write one line to the open standard-input channel.
875
+ * Writes raw bytes to the open standard-input channel.
881
876
  *
882
877
  * @remarks
883
- * Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
884
- * prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
885
- * full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
886
- * terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
887
- * 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
888
- * the write can settle `true` without a callback error or a stream error while the child remains
889
- * alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
890
- * that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
891
- * for bytes the package is about to discard.
878
+ * Never rejects, and adds no framing: a face composes whatever terminator its contract promises
879
+ * before it calls this. `true` means the host accepted the bytes without reporting a fault; it
880
+ * does not prove that the child read them. An ordinary write settles when the kernel accepts it.
881
+ * Only a full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and
882
+ * every terminal teardown path settles pending writes. After `stop` or `destroy` begins, a later
883
+ * call settles `false`, because teardown cannot confirm delivery for bytes it is about to
884
+ * discard.
892
885
  *
893
- * @param text - The line text without its trailing newline
886
+ * @param bytes - The payload to write, already framed by the caller
894
887
  * @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
895
888
  */
896
- send(text) {
889
+ deliver(bytes) {
897
890
  const stdin = this.#child.stdin;
898
891
  if (this.#settled || this.#stopping || this.#failure !== void 0 || !stdin.writable) return Promise.resolve(false);
899
892
  const settled = Promise.withResolvers();
900
893
  this.#writes.set(settled, void 0);
901
894
  try {
902
- stdin.write(`${text}\n`, this.#confirmWrite.bind(this, settled));
895
+ stdin.write(bytes, this.#confirmWrite.bind(this, settled));
903
896
  } catch {
904
897
  this.#settleWrite(settled, false);
905
898
  return settled.promise;
@@ -912,6 +905,28 @@ var Process = class {
912
905
  return settled.promise;
913
906
  }
914
907
  /**
908
+ * Closes the standard-input channel while leaving the child running.
909
+ *
910
+ * @remarks
911
+ * Never rejects, and terminates nothing: no signal is sent, `stopping` stays false, no drain
912
+ * window is armed, and the terminal moment does not arrive. The child observes end of input and
913
+ * decides for itself what to do about it, which is what separates this from `stop`.
914
+ *
915
+ * Every call shares one barrier, and it resolves after the host flushes the writes it had already
916
+ * accepted, or reports that it cannot. A `deliver` after this call resolves `false`, because the
917
+ * host stops reporting the channel writable the moment it is ended. That same flag keeps the
918
+ * ended channel quiet for its remaining life, so a later host fault on it settles pending writes
919
+ * and reaches no face. After `stop` or `destroy` has begun, or after the terminal moment, the
920
+ * call resolves and changes nothing, because the channel is already being discarded.
921
+ *
922
+ * @returns The stable barrier shared by every call
923
+ */
924
+ end() {
925
+ if (this.#closure !== void 0) return this.#closure;
926
+ this.#closure = this.#endInput();
927
+ return this.#closure;
928
+ }
929
+ /**
915
930
  * Terminates the child process tree and reaches the terminal observation moment.
916
931
  *
917
932
  * @remarks
@@ -928,7 +943,7 @@ var Process = class {
928
943
  return this.#termination;
929
944
  }
930
945
  /**
931
- * Stops the child and destroys the observation emitter after the terminal state freezes.
946
+ * Stops the child and releases the composing face after the terminal state freezes.
932
947
  *
933
948
  * @remarks
934
949
  * Always resolves, including when termination was never confirmed. Every call shares one barrier.
@@ -936,95 +951,22 @@ var Process = class {
936
951
  * @returns The stable barrier shared by every call
937
952
  */
938
953
  destroy() {
939
- if (this.#ending !== void 0) return this.#ending;
940
- this.#ending = this.#end();
941
- return this.#ending;
942
- }
943
- #iterate() {
944
- this.#requested = true;
945
- return Object.freeze({ next: this.#next.bind(this) });
946
- }
947
- #next() {
948
- const line = this.#take();
949
- if (line !== void 0) return Promise.resolve(Object.freeze({
950
- done: false,
951
- value: line
952
- }));
953
- if (this.#ended) return Promise.resolve(Object.freeze({
954
- done: true,
955
- value: void 0
956
- }));
957
- const waiter = Promise.withResolvers();
958
- this.#waiters.push(waiter);
959
- return waiter.promise;
960
- }
961
- #take() {
962
- if (this.#head >= this.#queue.length) return void 0;
963
- const line = this.#queue[this.#head];
964
- this.#queue[this.#head] = "";
965
- this.#head += 1;
966
- if (this.#head >= this.#queue.length) {
967
- this.#queue.length = 0;
968
- this.#head = 0;
969
- }
970
- if (line === void 0) return void 0;
971
- this.#pending -= node_buffer.Buffer.byteLength(line) + 1;
972
- this.#relieve();
973
- return line;
974
- }
975
- #push(line) {
976
- if (this.#ended) return;
977
- const waiter = this.#waiters.shift();
978
- if (waiter !== void 0) {
979
- waiter.resolve(Object.freeze({
980
- done: false,
981
- value: line
982
- }));
983
- return;
984
- }
985
- const bytes = node_buffer.Buffer.byteLength(line) + 1;
986
- const limit = this.#stopping ? this.#backlog * 2 : this.#backlog;
987
- if ((!this.#requested || this.#stopping) && this.#pending + bytes > limit) {
988
- this.#truncated = true;
989
- return;
990
- }
991
- this.#queue.push(line);
992
- this.#pending += bytes;
993
- this.#restrain();
994
- }
995
- #restrain() {
996
- if (this.#paused || this.#stopping || !this.#requested) return;
997
- if (this.#pending < this.#backlog) return;
998
- this.#paused = true;
999
- this.#reader.pause();
1000
- }
1001
- #relieve() {
1002
- if (!this.#paused || this.#ended) return;
1003
- if (!this.#stopping && this.#pending > this.#backlog / 2) return;
1004
- this.#paused = false;
1005
- this.#reader.resume();
1006
- }
1007
- #finish() {
1008
- if (this.#ended) return;
1009
- this.#ended = true;
1010
- this.#paused = false;
1011
- for (const waiter of this.#waiters) waiter.resolve(Object.freeze({
1012
- done: true,
1013
- value: void 0
1014
- }));
1015
- this.#waiters.length = 0;
954
+ if (this.#destruction !== void 0) return this.#destruction;
955
+ this.#destruction = this.#conclude();
956
+ return this.#destruction;
1016
957
  }
1017
958
  #retain(chunk) {
1018
959
  if (!node_buffer.Buffer.isBuffer(chunk) || this.#settled) return;
1019
960
  this.#tail = trimTail(node_buffer.Buffer.concat([this.#tail, chunk]), this.#evidence);
1020
961
  const text = this.#decoder.write(chunk);
1021
- if (text.length > 0) this.#emitter.emit("stderr", text);
962
+ if (text.length > 0) this.#chunk(text);
1022
963
  }
1023
- #close() {
964
+ #complete() {
1024
965
  if (this.#settled) return;
1025
966
  this.#settle(true);
1026
967
  }
1027
968
  #expire() {
969
+ this.#ending.resolve();
1028
970
  if (this.#settled) return;
1029
971
  this.#wait();
1030
972
  }
@@ -1036,17 +978,18 @@ var Process = class {
1036
978
  }
1037
979
  #settle(drained) {
1038
980
  const suffix = this.#decoder.end();
1039
- if (suffix.length > 0) this.#emitter.emit("stderr", suffix);
981
+ if (suffix.length > 0) this.#chunk(suffix);
1040
982
  this.#settled = true;
1041
983
  this.#removeAbortListener();
1042
- this.#reader.close();
984
+ this.#close();
1043
985
  const exit = Object.freeze({
1044
986
  code: this.code,
1045
987
  signal: this.signal,
1046
988
  drained
1047
989
  });
1048
990
  this.#exit.resolve(exit);
1049
- this.#emitter.emit("exit", exit);
991
+ this.#ending.resolve();
992
+ this.#terminal(exit);
1050
993
  this.#child.stdout.destroy();
1051
994
  this.#child.stderr.destroy();
1052
995
  }
@@ -1067,6 +1010,13 @@ var Process = class {
1067
1010
  #completeInput() {
1068
1011
  this.#input -= 1;
1069
1012
  }
1013
+ #endInput() {
1014
+ const stdin = this.#child.stdin;
1015
+ if (this.#stopping || this.#settled || stdin.writableEnded || stdin.destroyed) return Promise.resolve();
1016
+ const ended = Promise.withResolvers();
1017
+ stdin.end(() => ended.resolve());
1018
+ return ended.promise;
1019
+ }
1070
1020
  #settleWrite(settled, accepted) {
1071
1021
  if (!this.#writes.has(settled)) return;
1072
1022
  const timer = this.#writes.get(settled);
@@ -1078,34 +1028,28 @@ var Process = class {
1078
1028
  for (const settled of this.#writes.keys()) this.#settleWrite(settled, false);
1079
1029
  }
1080
1030
  #failInputStream(cause) {
1081
- if (this.#child.stdin.writableEnded || this.#input > 0) {
1031
+ if (this.#input > 0) {
1082
1032
  this.#settleWrites();
1083
1033
  return;
1084
1034
  }
1085
1035
  this.#failInputCallback(cause);
1086
1036
  }
1087
1037
  #failInputCallback(cause) {
1038
+ if (this.#child.stdin.writableEnded) {
1039
+ this.#settleWrites();
1040
+ return;
1041
+ }
1088
1042
  if (this.#failure !== void 0 || this.#stopping) return;
1089
1043
  this.#failure = cause;
1090
1044
  this.#settleWrites();
1091
- this.#emitter.emit("error", new _src_core.ProcessError("The standard-input channel failed", {
1045
+ this.#fault(new _src_core.ProcessError("The standard-input channel failed", {
1092
1046
  code: "protocol",
1093
1047
  cause
1094
1048
  }));
1095
1049
  }
1096
- #capBacklog() {
1097
- const limit = this.#backlog * 2;
1098
- while (this.#pending > limit && this.#queue.length > this.#head) {
1099
- const line = this.#queue.pop();
1100
- if (line === void 0) break;
1101
- this.#pending -= node_buffer.Buffer.byteLength(line) + 1;
1102
- this.#truncated = true;
1103
- }
1104
- }
1105
1050
  async #kill() {
1106
1051
  this.#stopping = true;
1107
- this.#capBacklog();
1108
- this.#relieve();
1052
+ this.#relieve?.();
1109
1053
  const confirmed = await stopChild(this.#child, this.#grace, _src_core.PROCESS_CONFIRMATION);
1110
1054
  this.#settleWrites();
1111
1055
  this.#child.stdin.destroy();
@@ -1113,54 +1057,343 @@ var Process = class {
1113
1057
  if (!this.#settled) this.#settle(false);
1114
1058
  return confirmed;
1115
1059
  }
1116
- async #end() {
1060
+ async #conclude() {
1117
1061
  await this.stop();
1118
- this.#emitter.destroy();
1062
+ this.#teardown();
1119
1063
  }
1120
1064
  };
1121
1065
  //#endregion
1122
- //#region src/server/ProcessManager.ts
1066
+ //#region src/server/Process.ts
1123
1067
  /**
1124
- * A keyed registry of live supervised child processes.
1068
+ * Supervises one child while keeping every observation channel aligned at termination.
1125
1069
  *
1126
1070
  * @remarks
1127
- * A child launched under an id joins the registry and emits `launch`; when it settles it removes
1128
- * itself and emits `exit`, so `count` and `processes` reflect only live children. The id is reserved
1129
- * before the child is constructed and released when construction throws, so a refused launch never
1130
- * strands its key. Eviction follows the child's own `exit` promise, which no listener can forge, so
1131
- * it lands one microtask after the child's public `exit` event: a listener on that event still sees
1132
- * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
1133
- * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
1134
- * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
1135
- * `exit`, never from `launch`. `destroy` awaits every child's own teardown, including the teardown
1136
- * of a child a lost race refused, which destroys each child's observation emitter and leaves every
1137
- * subscription on it silently inert, then destroys the registry emitter last.
1071
+ * The child's ending and the supervision's ending are distinct. `pid`, `code`, and `signal` read
1072
+ * the host child directly, so they expose native exit as soon as the host records it. `settled`,
1073
+ * `exit`, `evidence`, and `lines` reach one terminal moment after the read channels close or the
1074
+ * bounded `drain` window cuts them off. That window is armed by the child's native exit and by a
1075
+ * termination this package initiated, so every way a child can end reaches the moment. The terminal
1076
+ * routine freezes `evidence` and ends `lines` before it destroys stdout, stderr, or the emitter, so
1077
+ * the push channel never disappears while a pull channel can still change or wait indefinitely.
1078
+ *
1079
+ * Standard output is framed through `readline` into a single-consumer stream. Lines already framed
1080
+ * and queued remain available through the terminal moment, while bytes arriving after a requested
1081
+ * termination may be cut off. Standard error is forwarded through the live `stderr` event and
1082
+ * retained as a byte-bounded raw tail until that same moment. `stop` ends the child tree and the
1083
+ * observation surfaces. `destroy` performs that stop and then destroys the emitter after the
1084
+ * frozen terminal state exists.
1138
1085
  *
1139
1086
  * @example
1140
1087
  * ```ts
1141
- * import { ProcessManager } from '@orkestrel/process/server'
1088
+ * import { Process } from '@orkestrel/process/server'
1142
1089
  *
1143
- * const manager = new ProcessManager()
1144
- * const child = manager.launch('build', {
1145
- * command: { file: 'node', arguments: ['build.js'] },
1090
+ * const child = new Process({
1091
+ * command: { file: 'node', arguments: ['--version'] },
1146
1092
  * workspace: process.cwd(),
1147
1093
  * })
1148
- * await child.exit
1149
- * await manager.destroy()
1094
+ * const exit = await child.exit
1095
+ * await child.destroy()
1150
1096
  * ```
1151
1097
  */
1152
- var ProcessManager = class {
1098
+ var Process = class {
1153
1099
  #emitter;
1154
- #children = /* @__PURE__ */ new Map();
1155
- #ids = /* @__PURE__ */ new Set();
1156
- #orphans = /* @__PURE__ */ new Set();
1157
- #destroying = false;
1158
- #ending;
1100
+ #engine;
1101
+ #reader;
1102
+ #backlog;
1103
+ #lines;
1104
+ #queue = [];
1105
+ #waiters = [];
1106
+ #head = 0;
1107
+ #pending = 0;
1108
+ #requested = false;
1109
+ #paused = false;
1110
+ #truncated = false;
1111
+ #ended = false;
1159
1112
  /**
1160
- * Construct a process registry.
1113
+ * Spawn one child process and begin stream capture.
1161
1114
  *
1162
- * @param options - Initial fleet-level observation hooks and listener-error handling
1163
- */
1115
+ * @param options - Command, workspace, termination, capture, stdin, and observation settings
1116
+ * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
1117
+ */
1118
+ constructor(options) {
1119
+ const source = options.command;
1120
+ const workspace = options.workspace;
1121
+ const grace = options.grace;
1122
+ const drain = options.drain;
1123
+ const evidence = options.evidence;
1124
+ const backlog = options.backlog;
1125
+ const delivery = options.delivery;
1126
+ const writable = options.writable;
1127
+ const signal = options.signal;
1128
+ const on = options.on;
1129
+ const error = options.error;
1130
+ const command = snapshotCommand(source);
1131
+ validateCommand(command);
1132
+ validateWorkspace(workspace);
1133
+ validateTimer(grace, "option 'grace'");
1134
+ validateTimer(drain, "option 'drain'");
1135
+ validateTimer(delivery, "option 'delivery'");
1136
+ validateBytes(evidence, "option 'evidence'", 0);
1137
+ validateBytes(backlog, "option 'backlog'", 1);
1138
+ this.#emitter = new _orkestrel_emitter.Emitter({
1139
+ ...on === void 0 ? {} : { on },
1140
+ ...error === void 0 ? {} : { error }
1141
+ });
1142
+ this.#backlog = backlog ?? _src_core.PROCESS_BACKLOG;
1143
+ this.#lines = Object.freeze({ [Symbol.asyncIterator]: this.#iterate.bind(this) });
1144
+ this.#engine = new Supervisor({
1145
+ command,
1146
+ workspace,
1147
+ ...grace === void 0 ? {} : { grace },
1148
+ ...drain === void 0 ? {} : { drain },
1149
+ ...evidence === void 0 ? {} : { evidence },
1150
+ ...delivery === void 0 ? {} : { delivery },
1151
+ ...writable === void 0 ? {} : { writable },
1152
+ ...signal === void 0 ? {} : { signal }
1153
+ }, {
1154
+ chunk: this.#reportStderr.bind(this),
1155
+ fault: this.#reportFault.bind(this),
1156
+ relieve: this.#releaseBackpressure.bind(this),
1157
+ close: this.#closeReader.bind(this),
1158
+ terminal: this.#reportExit.bind(this),
1159
+ teardown: this.#emitter.destroy.bind(this.#emitter)
1160
+ });
1161
+ this.#reader = (0, node_readline.createInterface)({
1162
+ input: this.#engine.stdout,
1163
+ crlfDelay: Infinity
1164
+ });
1165
+ this.#reader.on("line", this.#push.bind(this));
1166
+ this.#reader.once("close", this.#finish.bind(this));
1167
+ }
1168
+ /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
1169
+ get pid() {
1170
+ return this.#engine.pid;
1171
+ }
1172
+ /** The exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
1173
+ get code() {
1174
+ return this.#engine.code;
1175
+ }
1176
+ /** The terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
1177
+ get signal() {
1178
+ return this.#engine.signal;
1179
+ }
1180
+ /** The typed lifecycle observation surface. */
1181
+ get emitter() {
1182
+ return this.#emitter;
1183
+ }
1184
+ /** The captured stdout lines, in arrival order, ending after queued lines at the terminal moment. */
1185
+ get lines() {
1186
+ return this.#lines;
1187
+ }
1188
+ /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
1189
+ get evidence() {
1190
+ return this.#engine.evidence;
1191
+ }
1192
+ /** True when the `lines` stream omitted output after a retention bound was reached. */
1193
+ get truncated() {
1194
+ return this.#truncated;
1195
+ }
1196
+ /** True after the terminal moment arrived and `exit` settled. */
1197
+ get settled() {
1198
+ return this.#engine.settled;
1199
+ }
1200
+ /** True after termination began, including after the terminal moment. */
1201
+ get stopping() {
1202
+ return this.#engine.stopping;
1203
+ }
1204
+ /** The terminal child state, observed once after stream close or the drain cutoff. */
1205
+ get exit() {
1206
+ return this.#engine.exit;
1207
+ }
1208
+ /**
1209
+ * Write one line to the open standard-input channel.
1210
+ *
1211
+ * @remarks
1212
+ * Never rejects. `true` means the host accepted the bytes without reporting a fault; it does not
1213
+ * prove that the child read them. An ordinary write settles when the kernel accepts it. Only a
1214
+ * full pipe can hold the write unconfirmed. The `delivery` option can bound that wait, and every
1215
+ * terminal teardown path settles pending writes. On Windows 11 with Node v24.18.1, measured on
1216
+ * 2026-08-21, a child that closes its own file descriptor 0 can leave the parent pipe writable:
1217
+ * the write can settle `true` without a callback error or a stream error while the child remains
1218
+ * alive. After `stop` or `destroy` begins, a later call settles `false`. Version 0.0.4 could settle
1219
+ * that call `true` before teardown destroyed the pipe; returning `false` avoids claiming delivery
1220
+ * for bytes the package is about to discard.
1221
+ *
1222
+ * @param text - The line text without its trailing newline
1223
+ * @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
1224
+ */
1225
+ send(text) {
1226
+ return this.#engine.deliver(node_buffer.Buffer.from(`${text}\n`, "utf8"));
1227
+ }
1228
+ /**
1229
+ * Terminates the child process tree and reaches the terminal observation moment.
1230
+ *
1231
+ * @remarks
1232
+ * Never rejects, and every call shares one termination. After the termination sequence returns,
1233
+ * confirmed or not, it waits at most `drain` for the read channels to close, then cuts them off
1234
+ * and settles `exit` when they remain open. A cutoff reached before the native exit reports the
1235
+ * `code` and `signal` the host had recorded by then, which is `null` for a child still running.
1236
+ *
1237
+ * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
1238
+ */
1239
+ stop() {
1240
+ return this.#engine.stop();
1241
+ }
1242
+ /**
1243
+ * Stops the child and destroys the observation emitter after the terminal state freezes.
1244
+ *
1245
+ * @remarks
1246
+ * Always resolves, including when termination was never confirmed. Every call shares one barrier.
1247
+ *
1248
+ * @returns The stable barrier shared by every call
1249
+ */
1250
+ destroy() {
1251
+ return this.#engine.destroy();
1252
+ }
1253
+ #iterate() {
1254
+ this.#requested = true;
1255
+ return Object.freeze({ next: this.#next.bind(this) });
1256
+ }
1257
+ #next() {
1258
+ const line = this.#take();
1259
+ if (line !== void 0) return Promise.resolve(Object.freeze({
1260
+ done: false,
1261
+ value: line
1262
+ }));
1263
+ if (this.#ended) return Promise.resolve(Object.freeze({
1264
+ done: true,
1265
+ value: void 0
1266
+ }));
1267
+ const waiter = Promise.withResolvers();
1268
+ this.#waiters.push(waiter);
1269
+ return waiter.promise;
1270
+ }
1271
+ #take() {
1272
+ if (this.#head >= this.#queue.length) return void 0;
1273
+ const line = this.#queue[this.#head];
1274
+ this.#queue[this.#head] = "";
1275
+ this.#head += 1;
1276
+ if (this.#head >= this.#queue.length) {
1277
+ this.#queue.length = 0;
1278
+ this.#head = 0;
1279
+ }
1280
+ if (line === void 0) return void 0;
1281
+ this.#pending -= node_buffer.Buffer.byteLength(line) + 1;
1282
+ this.#relieve();
1283
+ return line;
1284
+ }
1285
+ #push(line) {
1286
+ if (this.#ended) return;
1287
+ const waiter = this.#waiters.shift();
1288
+ if (waiter !== void 0) {
1289
+ waiter.resolve(Object.freeze({
1290
+ done: false,
1291
+ value: line
1292
+ }));
1293
+ return;
1294
+ }
1295
+ const bytes = node_buffer.Buffer.byteLength(line) + 1;
1296
+ const stopping = this.#engine.stopping;
1297
+ const limit = stopping ? this.#backlog * 2 : this.#backlog;
1298
+ if ((!this.#requested || stopping) && this.#pending + bytes > limit) {
1299
+ this.#truncated = true;
1300
+ return;
1301
+ }
1302
+ this.#queue.push(line);
1303
+ this.#pending += bytes;
1304
+ this.#restrain();
1305
+ }
1306
+ #restrain() {
1307
+ if (this.#paused || this.#engine.stopping || !this.#requested) return;
1308
+ if (this.#pending < this.#backlog) return;
1309
+ this.#paused = true;
1310
+ this.#reader.pause();
1311
+ }
1312
+ #relieve() {
1313
+ if (!this.#paused || this.#ended) return;
1314
+ if (!this.#engine.stopping && this.#pending > this.#backlog / 2) return;
1315
+ this.#paused = false;
1316
+ this.#reader.resume();
1317
+ }
1318
+ #finish() {
1319
+ if (this.#ended) return;
1320
+ this.#ended = true;
1321
+ this.#paused = false;
1322
+ for (const waiter of this.#waiters) waiter.resolve(Object.freeze({
1323
+ done: true,
1324
+ value: void 0
1325
+ }));
1326
+ this.#waiters.length = 0;
1327
+ }
1328
+ #reportStderr(chunk) {
1329
+ this.#emitter.emit("stderr", chunk);
1330
+ }
1331
+ #reportFault(cause) {
1332
+ this.#emitter.emit("error", cause);
1333
+ }
1334
+ #reportExit(exit) {
1335
+ this.#emitter.emit("exit", exit);
1336
+ }
1337
+ #closeReader() {
1338
+ this.#reader.close();
1339
+ }
1340
+ #releaseBackpressure() {
1341
+ this.#capBacklog();
1342
+ this.#relieve();
1343
+ }
1344
+ #capBacklog() {
1345
+ const limit = this.#backlog * 2;
1346
+ while (this.#pending > limit && this.#queue.length > this.#head) {
1347
+ const line = this.#queue.pop();
1348
+ if (line === void 0) break;
1349
+ this.#pending -= node_buffer.Buffer.byteLength(line) + 1;
1350
+ this.#truncated = true;
1351
+ }
1352
+ }
1353
+ };
1354
+ //#endregion
1355
+ //#region src/server/ProcessManager.ts
1356
+ /**
1357
+ * A keyed registry of live supervised child processes.
1358
+ *
1359
+ * @remarks
1360
+ * A child launched under an id joins the registry and emits `launch`; when it settles it removes
1361
+ * itself and emits `exit`, so `count` and `processes` reflect only live children. The id is reserved
1362
+ * before the child is constructed and released when construction throws, so a refused launch never
1363
+ * strands its key. Eviction follows the child's own `exit` promise, which no listener can forge, so
1364
+ * it lands one microtask after the child's public `exit` event: a listener on that event still sees
1365
+ * the child registered. `launch` throws a {@link createDuplicateError} when the id is already live
1366
+ * and a {@link createProtocolError} after `destroy` has begun, including when the caller's own option
1367
+ * getter begins that teardown mid-construction — a spawn fault surfaces through the returned child's
1368
+ * `exit`, never from `launch`. `destroy` awaits every child's own teardown, including the teardown
1369
+ * of a child a lost race refused, which destroys each child's observation emitter and leaves every
1370
+ * subscription on it silently inert, then destroys the registry emitter last.
1371
+ *
1372
+ * @example
1373
+ * ```ts
1374
+ * import { ProcessManager } from '@orkestrel/process/server'
1375
+ *
1376
+ * const manager = new ProcessManager()
1377
+ * const child = manager.launch('build', {
1378
+ * command: { file: 'node', arguments: ['build.js'] },
1379
+ * workspace: process.cwd(),
1380
+ * })
1381
+ * await child.exit
1382
+ * await manager.destroy()
1383
+ * ```
1384
+ */
1385
+ var ProcessManager = class {
1386
+ #emitter;
1387
+ #children = /* @__PURE__ */ new Map();
1388
+ #ids = /* @__PURE__ */ new Set();
1389
+ #orphans = /* @__PURE__ */ new Set();
1390
+ #destroying = false;
1391
+ #ending;
1392
+ /**
1393
+ * Construct a process registry.
1394
+ *
1395
+ * @param options - Initial fleet-level observation hooks and listener-error handling
1396
+ */
1164
1397
  constructor(options) {
1165
1398
  const on = options?.on;
1166
1399
  const error = options?.error;
@@ -1286,6 +1519,185 @@ var ProcessManager = class {
1286
1519
  }
1287
1520
  };
1288
1521
  //#endregion
1522
+ //#region src/server/Session.ts
1523
+ /**
1524
+ * Supervises one child and publishes its standard output as raw bytes.
1525
+ *
1526
+ * @remarks
1527
+ * This is the transport face of the supervision engine `Process` also composes. It frames nothing,
1528
+ * retains nothing, and decodes nothing on the output side: every chunk the host delivers becomes one
1529
+ * `stdout` event carrying that chunk's bytes, in arrival order, so a consumer speaking a
1530
+ * length-prefixed or delimiter-framed protocol reads exactly what the child wrote and frames it
1531
+ * itself. Standard error is decoded and forwarded as `stderr`, with the same byte-bounded `evidence`
1532
+ * tail a `Process` retains, because a diagnostic stream is read rather than parsed.
1533
+ *
1534
+ * Each emitted chunk is this session's own copy, and a plain `Uint8Array` rather than the host buffer
1535
+ * the read produced, so a consumer keeps it, mutates it, concatenates it, and reads its whole backing
1536
+ * buffer without reaching memory the host still manages and without depending on a host type.
1537
+ *
1538
+ * The standard-input channel is open from the spawn, and `end` closes it without terminating
1539
+ * anything. That is the member a cooperative protocol reaches for: the child observes end of input
1540
+ * and exits on its own, which `ending` reports. `stop` and `destroy` remain the terminations, and
1541
+ * `exit` remains the terminal moment — the native exit plus at most `drain` while a descendant holds
1542
+ * the inherited read ends.
1543
+ *
1544
+ * @example
1545
+ * ```ts
1546
+ * import { Session } from '@orkestrel/process/server'
1547
+ *
1548
+ * const session = new Session({
1549
+ * command: { file: 'node', arguments: ['-e', 'process.stdin.pipe(process.stdout)'] },
1550
+ * workspace: process.cwd(),
1551
+ * })
1552
+ * ```
1553
+ */
1554
+ var Session = class {
1555
+ #emitter;
1556
+ #engine;
1557
+ #output;
1558
+ /**
1559
+ * Spawn one child process and begin byte capture.
1560
+ *
1561
+ * @param options - Command, workspace, termination, evidence, stdin, and observation settings
1562
+ * @throws A {@link ProcessError} coded `invalid` when an option or command string is malformed
1563
+ */
1564
+ constructor(options) {
1565
+ const command = options.command;
1566
+ const workspace = options.workspace;
1567
+ const grace = options.grace;
1568
+ const drain = options.drain;
1569
+ const evidence = options.evidence;
1570
+ const delivery = options.delivery;
1571
+ const signal = options.signal;
1572
+ const on = options.on;
1573
+ const error = options.error;
1574
+ this.#emitter = new _orkestrel_emitter.Emitter({
1575
+ ...on === void 0 ? {} : { on },
1576
+ ...error === void 0 ? {} : { error }
1577
+ });
1578
+ this.#output = this.#publish.bind(this);
1579
+ this.#engine = new Supervisor({
1580
+ command,
1581
+ workspace,
1582
+ writable: true,
1583
+ ...grace === void 0 ? {} : { grace },
1584
+ ...drain === void 0 ? {} : { drain },
1585
+ ...evidence === void 0 ? {} : { evidence },
1586
+ ...delivery === void 0 ? {} : { delivery },
1587
+ ...signal === void 0 ? {} : { signal }
1588
+ }, {
1589
+ chunk: this.#reportStderr.bind(this),
1590
+ fault: this.#reportFault.bind(this),
1591
+ close: this.#closeOutput.bind(this),
1592
+ terminal: this.#reportExit.bind(this),
1593
+ teardown: this.#emitter.destroy.bind(this.#emitter)
1594
+ });
1595
+ this.#engine.stdout.on("data", this.#output);
1596
+ }
1597
+ /** The host process id, fixed when construction returns, or `undefined` when the spawn produced none. */
1598
+ get pid() {
1599
+ return this.#engine.pid;
1600
+ }
1601
+ /** The exit code the host recorded, or `null` while the child has not exited and when a signal ended it. */
1602
+ get code() {
1603
+ return this.#engine.code;
1604
+ }
1605
+ /** The terminating signal name the host recorded, or `null` while the child has not exited and when it exited on its own. */
1606
+ get signal() {
1607
+ return this.#engine.signal;
1608
+ }
1609
+ /** The typed lifecycle observation surface. */
1610
+ get emitter() {
1611
+ return this.#emitter;
1612
+ }
1613
+ /** The decoded byte-bounded stderr tail, frozen at the terminal moment. */
1614
+ get evidence() {
1615
+ return this.#engine.evidence;
1616
+ }
1617
+ /** True after the terminal moment arrived and `exit` settled. */
1618
+ get settled() {
1619
+ return this.#engine.settled;
1620
+ }
1621
+ /** True after termination began, including after the terminal moment. `end` never turns it true. */
1622
+ get stopping() {
1623
+ return this.#engine.stopping;
1624
+ }
1625
+ /** The child's own ending, awaited without the terminal moment's drain window. */
1626
+ get ending() {
1627
+ return this.#engine.ending;
1628
+ }
1629
+ /** The terminal child state, observed once after stream close or the drain cutoff. */
1630
+ get exit() {
1631
+ return this.#engine.exit;
1632
+ }
1633
+ /**
1634
+ * Writes raw bytes to the open standard-input channel.
1635
+ *
1636
+ * @remarks
1637
+ * Never rejects, and appends no terminator: the child receives exactly these bytes. `true` means
1638
+ * the host accepted them without reporting a fault; it does not prove that the child read them.
1639
+ * The channel can queue the payload, so treat the array as owned by the channel until the
1640
+ * returned promise settles.
1641
+ *
1642
+ * @param bytes - The payload to write, already framed by the caller
1643
+ * @returns True when the host accepted the bytes without reporting a fault; false when the channel was closed, destroyed, ended, failed, or remained unconfirmed through `delivery`
1644
+ */
1645
+ write(bytes) {
1646
+ return this.#engine.deliver(bytes);
1647
+ }
1648
+ /**
1649
+ * Closes the standard-input channel and leaves the child running.
1650
+ *
1651
+ * @remarks
1652
+ * Never rejects and terminates nothing: `stopping` stays false and the terminal moment does not
1653
+ * arrive. Every call shares one barrier, which resolves after the host flushes the writes it had
1654
+ * already accepted. A later `write` resolves `false`.
1655
+ *
1656
+ * @returns The stable barrier shared by every call
1657
+ */
1658
+ end() {
1659
+ return this.#engine.end();
1660
+ }
1661
+ /**
1662
+ * Terminates the child process tree and reaches the terminal observation moment.
1663
+ *
1664
+ * @remarks
1665
+ * Never rejects, and every call shares one termination bounded by `grace` and `drain`.
1666
+ *
1667
+ * @returns True when the child's native exit was observed; false when the confirmation deadline elapsed without it
1668
+ */
1669
+ stop() {
1670
+ return this.#engine.stop();
1671
+ }
1672
+ /**
1673
+ * Stops the child and destroys the observation emitter after the terminal state freezes.
1674
+ *
1675
+ * @remarks
1676
+ * Always resolves, including when termination was never confirmed. Every call shares one barrier.
1677
+ *
1678
+ * @returns The stable barrier shared by every call
1679
+ */
1680
+ destroy() {
1681
+ return this.#engine.destroy();
1682
+ }
1683
+ #publish(chunk) {
1684
+ if (!node_buffer.Buffer.isBuffer(chunk) || this.#engine.settled) return;
1685
+ this.#emitter.emit("stdout", new Uint8Array(chunk));
1686
+ }
1687
+ #reportStderr(chunk) {
1688
+ this.#emitter.emit("stderr", chunk);
1689
+ }
1690
+ #reportFault(cause) {
1691
+ this.#emitter.emit("error", cause);
1692
+ }
1693
+ #reportExit(exit) {
1694
+ this.#emitter.emit("exit", exit);
1695
+ }
1696
+ #closeOutput() {
1697
+ this.#engine.stdout.off("data", this.#output);
1698
+ }
1699
+ };
1700
+ //#endregion
1289
1701
  //#region src/server/factories.ts
1290
1702
  /**
1291
1703
  * Creates one supervised child process.
@@ -1308,6 +1720,26 @@ function createProcess(options) {
1308
1720
  return new Process(options);
1309
1721
  }
1310
1722
  /**
1723
+ * Creates one raw byte session over a supervised child process.
1724
+ *
1725
+ * @param options - Command, workspace, termination, evidence, stdin, and observation settings
1726
+ * @returns The launched {@link SessionInterface}
1727
+ *
1728
+ * @example
1729
+ * ```ts
1730
+ * import { createSession } from '@orkestrel/process/server'
1731
+ *
1732
+ * const session = createSession({
1733
+ * command: { file: 'node', arguments: ['server.js', '--stdio'] },
1734
+ * workspace: process.cwd(),
1735
+ * grace: 5000,
1736
+ * })
1737
+ * ```
1738
+ */
1739
+ function createSession(options) {
1740
+ return new Session(options);
1741
+ }
1742
+ /**
1311
1743
  * Creates one keyed registry of supervised child processes.
1312
1744
  *
1313
1745
  * @param options - Initial fleet-level observation hooks and listener-error handling
@@ -1664,12 +2096,14 @@ function detach(command, options) {
1664
2096
  exports.Process = Process;
1665
2097
  exports.ProcessManager = ProcessManager;
1666
2098
  exports.Retention = Retention;
2099
+ exports.Session = Session;
1667
2100
  exports.buildExecutableCandidates = buildExecutableCandidates;
1668
2101
  exports.buildExecuteResult = buildExecuteResult;
1669
2102
  exports.buildPlatformSpawn = buildPlatformSpawn;
1670
2103
  exports.buildSpawn = buildSpawn;
1671
2104
  exports.createProcess = createProcess;
1672
2105
  exports.createProcessManager = createProcessManager;
2106
+ exports.createSession = createSession;
1673
2107
  exports.detach = detach;
1674
2108
  exports.execute = execute;
1675
2109
  exports.executeSync = executeSync;