@actuallab/rpc 12.3.85 → 12.4.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.
package/dist/index.d.cts CHANGED
@@ -995,7 +995,7 @@ interface RpcStreamRef {
995
995
  readonly hostId: string;
996
996
  readonly localId: number;
997
997
  readonly ackPeriod: number;
998
- readonly ackAdvance: number;
998
+ readonly bufferSize: number;
999
999
  readonly allowReconnect: boolean;
1000
1000
  readonly isRealTime: boolean;
1001
1001
  }
@@ -1009,13 +1009,13 @@ type RpcStreamSource<T> = AsyncIterable<T> | ((abortSignal: AbortSignal) => Asyn
1009
1009
  /** Configuration options for creating a local (origin-side) RpcStream. */
1010
1010
  interface RpcStreamOptions<T> {
1011
1011
  ackPeriod?: number;
1012
- ackAdvance?: number;
1012
+ bufferSize?: number;
1013
1013
  allowReconnect?: boolean;
1014
1014
  isRealTime?: boolean;
1015
1015
  canSkipTo?: (item: T) => boolean;
1016
1016
  }
1017
1017
  /**
1018
- * Parses a stream reference string of the form "{hostId},{localId},{ackPeriod},{ackAdvance}".
1018
+ * Parses a stream reference string of the form "{hostId},{localId},{ackPeriod},{bufferSize}".
1019
1019
  * Returns null if the value is not a valid stream reference.
1020
1020
  */
1021
1021
  declare function parseStreamRef(value: unknown): RpcStreamRef | null;
@@ -1047,7 +1047,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1047
1047
  readonly isRealTime: boolean;
1048
1048
  readonly canSkipTo: (item: T) => boolean;
1049
1049
  readonly ackPeriod: number;
1050
- readonly ackAdvance: number;
1050
+ readonly bufferSize: number;
1051
1051
  readonly localSource?: RpcStreamSource<T>;
1052
1052
  readonly peer: RpcPeer;
1053
1053
  private _sender?;
@@ -1078,7 +1078,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1078
1078
  * stream reference.
1079
1079
  *
1080
1080
  * For binary peers the ref is a MessagePack-compatible object
1081
- * (`{ SerializedId, AckPeriod, AckAdvance, AllowReconnect }`);
1081
+ * (`{ SerializedId, AckPeriod, BufferSize, AllowReconnect }`);
1082
1082
  * for text peers it is a comma-separated string.
1083
1083
  *
1084
1084
  * Can only be called once per stream.
@@ -1104,7 +1104,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1104
1104
  * Recursively walks a deserialized value and replaces any stream ref strings
1105
1105
  * with live RpcStream<unknown> instances registered on the given peer.
1106
1106
  * This handles nested stream refs inside complex return types (e.g. Table<T>
1107
- * containing RpcStream<Row<T>> fields serialized as "{hostId},{localId},{ackPeriod},{ackAdvance}").
1107
+ * containing RpcStream<Row<T>> fields serialized as "{hostId},{localId},{ackPeriod},{bufferSize}").
1108
1108
  */
1109
1109
  declare function resolveStreamRefs(value: unknown, peer: RpcPeer): unknown;
1110
1110
 
@@ -1123,13 +1123,12 @@ declare function resolveStreamRefs(value: unknown, peer: RpcPeer): unknown;
1123
1123
  * The pump is ACK-driven: the main loop blocks on `_whenAckReady` until a
1124
1124
  * client ACK is queued, processes it (updating `_lastAckedIndex`, rewinding
1125
1125
  * `_index` on mustReset, trimming the replay buffer), then sends items up
1126
- * to `ack.nextIndex + ackAdvance`. While the peer is disconnected no ACKs
1126
+ * to `ack.nextIndex + bufferSize`. While the peer is disconnected no ACKs
1127
1127
  * arrive, so the source is never pulled — the sender naturally pauses
1128
1128
  * instead of spinning.
1129
1129
  *
1130
1130
  * Direct port of .NET `RpcSharedStream<T>` at
1131
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs — including the
1132
- * separate non-real-time / real-time pump variants.
1131
+ * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
1133
1132
  */
1134
1133
  declare class RpcStreamSender<T> implements IRpcObject {
1135
1134
  private static readonly _emptyGuid;
@@ -1140,7 +1139,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1140
1139
  readonly canSkipTo: (item: T) => boolean;
1141
1140
  readonly peer: RpcPeer;
1142
1141
  readonly ackPeriod: number;
1143
- readonly ackAdvance: number;
1142
+ readonly bufferSize: number;
1144
1143
  /**
1145
1144
  * If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
1146
1145
  * and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
@@ -1161,7 +1160,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1161
1160
  private _iterator;
1162
1161
  private _acks;
1163
1162
  private _whenAckReady;
1164
- constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
1163
+ constructor(peer: RpcPeer, ackPeriod?: number, bufferSize?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
1165
1164
  /** AbortSignal that is aborted when the sender is disconnected. */
1166
1165
  get abortSignal(): AbortSignal;
1167
1166
  /** Returns the stream reference string for the $sys.Ok response. */
@@ -1192,25 +1191,16 @@ declare class RpcStreamSender<T> implements IRpcObject {
1192
1191
  /**
1193
1192
  * Consume an AsyncIterable and send all items to the client.
1194
1193
  *
1195
- * Dispatches to the non-real-time or real-time pump, matching .NET
1196
- * `RpcSharedStream<T>.OnRun` at
1197
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:105-108.
1194
+ * Matches .NET `RpcSharedStream<T>.OnRun` at
1195
+ * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
1198
1196
  */
1199
1197
  writeFrom(source: AsyncIterable<T>): Promise<void>;
1200
1198
  /**
1201
- * Non-real-time pump — back-pressure through buffered ACKs.
1202
- *
1203
- * Direct port of .NET `OnRunNonRealTime` at
1204
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:110-217.
1205
- */
1206
- private _runNonRealTime;
1207
- /**
1208
- * Real-time pump — drops stale items to stay close to the source's head.
1209
- *
1210
- * Direct port of .NET `OnRunRealTime` at
1211
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:219-420.
1199
+ * ACK-driven pump. Non-real-time streams apply normal backpressure at
1200
+ * `bufferSize`; real-time streams may compact the already-buffered unsent
1201
+ * suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
1212
1202
  */
1213
- private _runRealTime;
1203
+ private _run;
1214
1204
  /** Drain all queued ACKs, returning the most recent one (or null if none). */
1215
1205
  private _tryProcessAcks;
1216
1206
  /** Wait for the ACK queue to have at least one entry (or `_ended`). */
package/dist/index.d.ts CHANGED
@@ -995,7 +995,7 @@ interface RpcStreamRef {
995
995
  readonly hostId: string;
996
996
  readonly localId: number;
997
997
  readonly ackPeriod: number;
998
- readonly ackAdvance: number;
998
+ readonly bufferSize: number;
999
999
  readonly allowReconnect: boolean;
1000
1000
  readonly isRealTime: boolean;
1001
1001
  }
@@ -1009,13 +1009,13 @@ type RpcStreamSource<T> = AsyncIterable<T> | ((abortSignal: AbortSignal) => Asyn
1009
1009
  /** Configuration options for creating a local (origin-side) RpcStream. */
1010
1010
  interface RpcStreamOptions<T> {
1011
1011
  ackPeriod?: number;
1012
- ackAdvance?: number;
1012
+ bufferSize?: number;
1013
1013
  allowReconnect?: boolean;
1014
1014
  isRealTime?: boolean;
1015
1015
  canSkipTo?: (item: T) => boolean;
1016
1016
  }
1017
1017
  /**
1018
- * Parses a stream reference string of the form "{hostId},{localId},{ackPeriod},{ackAdvance}".
1018
+ * Parses a stream reference string of the form "{hostId},{localId},{ackPeriod},{bufferSize}".
1019
1019
  * Returns null if the value is not a valid stream reference.
1020
1020
  */
1021
1021
  declare function parseStreamRef(value: unknown): RpcStreamRef | null;
@@ -1047,7 +1047,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1047
1047
  readonly isRealTime: boolean;
1048
1048
  readonly canSkipTo: (item: T) => boolean;
1049
1049
  readonly ackPeriod: number;
1050
- readonly ackAdvance: number;
1050
+ readonly bufferSize: number;
1051
1051
  readonly localSource?: RpcStreamSource<T>;
1052
1052
  readonly peer: RpcPeer;
1053
1053
  private _sender?;
@@ -1078,7 +1078,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1078
1078
  * stream reference.
1079
1079
  *
1080
1080
  * For binary peers the ref is a MessagePack-compatible object
1081
- * (`{ SerializedId, AckPeriod, AckAdvance, AllowReconnect }`);
1081
+ * (`{ SerializedId, AckPeriod, BufferSize, AllowReconnect }`);
1082
1082
  * for text peers it is a comma-separated string.
1083
1083
  *
1084
1084
  * Can only be called once per stream.
@@ -1104,7 +1104,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1104
1104
  * Recursively walks a deserialized value and replaces any stream ref strings
1105
1105
  * with live RpcStream<unknown> instances registered on the given peer.
1106
1106
  * This handles nested stream refs inside complex return types (e.g. Table<T>
1107
- * containing RpcStream<Row<T>> fields serialized as "{hostId},{localId},{ackPeriod},{ackAdvance}").
1107
+ * containing RpcStream<Row<T>> fields serialized as "{hostId},{localId},{ackPeriod},{bufferSize}").
1108
1108
  */
1109
1109
  declare function resolveStreamRefs(value: unknown, peer: RpcPeer): unknown;
1110
1110
 
@@ -1123,13 +1123,12 @@ declare function resolveStreamRefs(value: unknown, peer: RpcPeer): unknown;
1123
1123
  * The pump is ACK-driven: the main loop blocks on `_whenAckReady` until a
1124
1124
  * client ACK is queued, processes it (updating `_lastAckedIndex`, rewinding
1125
1125
  * `_index` on mustReset, trimming the replay buffer), then sends items up
1126
- * to `ack.nextIndex + ackAdvance`. While the peer is disconnected no ACKs
1126
+ * to `ack.nextIndex + bufferSize`. While the peer is disconnected no ACKs
1127
1127
  * arrive, so the source is never pulled — the sender naturally pauses
1128
1128
  * instead of spinning.
1129
1129
  *
1130
1130
  * Direct port of .NET `RpcSharedStream<T>` at
1131
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs — including the
1132
- * separate non-real-time / real-time pump variants.
1131
+ * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
1133
1132
  */
1134
1133
  declare class RpcStreamSender<T> implements IRpcObject {
1135
1134
  private static readonly _emptyGuid;
@@ -1140,7 +1139,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1140
1139
  readonly canSkipTo: (item: T) => boolean;
1141
1140
  readonly peer: RpcPeer;
1142
1141
  readonly ackPeriod: number;
1143
- readonly ackAdvance: number;
1142
+ readonly bufferSize: number;
1144
1143
  /**
1145
1144
  * If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
1146
1145
  * and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
@@ -1161,7 +1160,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1161
1160
  private _iterator;
1162
1161
  private _acks;
1163
1162
  private _whenAckReady;
1164
- constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
1163
+ constructor(peer: RpcPeer, ackPeriod?: number, bufferSize?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean);
1165
1164
  /** AbortSignal that is aborted when the sender is disconnected. */
1166
1165
  get abortSignal(): AbortSignal;
1167
1166
  /** Returns the stream reference string for the $sys.Ok response. */
@@ -1192,25 +1191,16 @@ declare class RpcStreamSender<T> implements IRpcObject {
1192
1191
  /**
1193
1192
  * Consume an AsyncIterable and send all items to the client.
1194
1193
  *
1195
- * Dispatches to the non-real-time or real-time pump, matching .NET
1196
- * `RpcSharedStream<T>.OnRun` at
1197
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:105-108.
1194
+ * Matches .NET `RpcSharedStream<T>.OnRun` at
1195
+ * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
1198
1196
  */
1199
1197
  writeFrom(source: AsyncIterable<T>): Promise<void>;
1200
1198
  /**
1201
- * Non-real-time pump — back-pressure through buffered ACKs.
1202
- *
1203
- * Direct port of .NET `OnRunNonRealTime` at
1204
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:110-217.
1205
- */
1206
- private _runNonRealTime;
1207
- /**
1208
- * Real-time pump — drops stale items to stay close to the source's head.
1209
- *
1210
- * Direct port of .NET `OnRunRealTime` at
1211
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:219-420.
1199
+ * ACK-driven pump. Non-real-time streams apply normal backpressure at
1200
+ * `bufferSize`; real-time streams may compact the already-buffered unsent
1201
+ * suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
1212
1202
  */
1213
- private _runRealTime;
1203
+ private _run;
1214
1204
  /** Drain all queued ACKs, returning the most recent one (or null if none). */
1215
1205
  private _tryProcessAcks;
1216
1206
  /** Wait for the ACK queue to have at least one entry (or `_ended`). */
package/dist/index.js CHANGED
@@ -870,7 +870,7 @@ var RpcObjectKind = { Local: 0, Remote: 1 };
870
870
  import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
871
871
  var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
872
872
  var DEFAULT_ACK_PERIOD = 256;
873
- var DEFAULT_ACK_ADVANCE = 128;
873
+ var DEFAULT_BUFFER_SIZE = 128;
874
874
  var RpcStreamSender = class _RpcStreamSender {
875
875
  static _emptyGuid = "00000000-0000-0000-0000-000000000000";
876
876
  id;
@@ -880,7 +880,7 @@ var RpcStreamSender = class _RpcStreamSender {
880
880
  canSkipTo;
881
881
  peer;
882
882
  ackPeriod;
883
- ackAdvance;
883
+ bufferSize;
884
884
  /**
885
885
  * If true, the source was produced by a factory `(abortSignal) => AsyncIterable<T>`
886
886
  * and may honor the AbortSignal to exit gracefully. `disconnect()` first aborts
@@ -903,7 +903,7 @@ var RpcStreamSender = class _RpcStreamSender {
903
903
  // -- ACK queue (analog of .NET's Channel<(long, bool)>) --
904
904
  _acks = [];
905
905
  _whenAckReady = null;
906
- constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, ackAdvance = DEFAULT_ACK_ADVANCE, allowReconnect = true, isRealTime = false, canSkipTo = () => true, sourceUsesAbortSignal = false) {
906
+ constructor(peer, ackPeriod = DEFAULT_ACK_PERIOD, bufferSize = DEFAULT_BUFFER_SIZE, allowReconnect = true, isRealTime = false, canSkipTo = () => true, sourceUsesAbortSignal = false) {
907
907
  const localId = peer.sharedObjects.nextId();
908
908
  this.id = { hostId: peer.hub.hubId, localId };
909
909
  this.allowReconnect = allowReconnect;
@@ -911,7 +911,7 @@ var RpcStreamSender = class _RpcStreamSender {
911
911
  this.canSkipTo = canSkipTo;
912
912
  this.peer = peer;
913
913
  this.ackPeriod = ackPeriod;
914
- this.ackAdvance = ackAdvance;
914
+ this.bufferSize = bufferSize;
915
915
  this.sourceUsesAbortSignal = sourceUsesAbortSignal;
916
916
  }
917
917
  /** AbortSignal that is aborted when the sender is disconnected. */
@@ -920,7 +920,7 @@ var RpcStreamSender = class _RpcStreamSender {
920
920
  }
921
921
  /** Returns the stream reference string for the $sys.Ok response. */
922
922
  toRef() {
923
- return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.ackAdvance},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
923
+ return `${this.id.hostId},${this.id.localId},${this.ackPeriod},${this.bufferSize},${this.allowReconnect ? "1" : "0"},${this.isRealTime ? "1" : "0"}`;
924
924
  }
925
925
  /**
926
926
  * Called by system call handler when $sys.Ack is received from the client.
@@ -997,9 +997,8 @@ var RpcStreamSender = class _RpcStreamSender {
997
997
  /**
998
998
  * Consume an AsyncIterable and send all items to the client.
999
999
  *
1000
- * Dispatches to the non-real-time or real-time pump, matching .NET
1001
- * `RpcSharedStream<T>.OnRun` at
1002
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:105-108.
1000
+ * Matches .NET `RpcSharedStream<T>.OnRun` at
1001
+ * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs.
1003
1002
  */
1004
1003
  async writeFrom(source) {
1005
1004
  await this._started.promise;
@@ -1008,11 +1007,7 @@ var RpcStreamSender = class _RpcStreamSender {
1008
1007
  this._iterator = iterator;
1009
1008
  const state = { iteratorDone: false };
1010
1009
  try {
1011
- if (this.isRealTime) {
1012
- await this._runRealTime(iterator, state);
1013
- } else {
1014
- await this._runNonRealTime(iterator, state);
1015
- }
1010
+ await this._run(iterator, state);
1016
1011
  } finally {
1017
1012
  if (state.iteratorDone && this._iterator === iterator) {
1018
1013
  this._iterator = null;
@@ -1020,90 +1015,59 @@ var RpcStreamSender = class _RpcStreamSender {
1020
1015
  }
1021
1016
  }
1022
1017
  /**
1023
- * Non-real-time pump — back-pressure through buffered ACKs.
1024
- *
1025
- * Direct port of .NET `OnRunNonRealTime` at
1026
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:110-217.
1018
+ * ACK-driven pump. Non-real-time streams apply normal backpressure at
1019
+ * `bufferSize`; real-time streams may compact the already-buffered unsent
1020
+ * suffix to the latest buffered `canSkipTo` item after the consumer ACKs.
1027
1021
  */
1028
1022
  /* eslint-disable @typescript-eslint/no-unnecessary-condition -- _ended changes across awaits */
1029
- async _runNonRealTime(iterator, state) {
1030
- const buffer = new RingBuffer(this.ackAdvance + 1);
1023
+ async _run(iterator, state) {
1024
+ const buffer = new RingBuffer(this.bufferSize + 1);
1025
+ const isRealTime = this.isRealTime;
1031
1026
  let bufferStart = 0;
1032
1027
  let isFullyBuffered = false;
1033
- while (true) {
1034
- let ack = this._tryProcessAcks();
1035
- if (!ack) {
1036
- await this._waitAckReady();
1037
- if (this._ended) return;
1038
- ack = this._tryProcessAcks();
1039
- if (!ack) {
1040
- warnLog3?.log("Something is off: couldn't read an acknowledgement");
1041
- return;
1042
- }
1043
- }
1044
- this._lastAckedIndex = ack.nextIndex;
1045
- const shift = _clamp(ack.nextIndex - bufferStart, 0, buffer.count);
1046
- if (shift > 0) {
1047
- buffer.moveHead(shift);
1048
- bufferStart += shift;
1049
- }
1050
- if (this._nextIndex < bufferStart) {
1051
- if (!this._ended)
1052
- this.sendEnd(new Error("Stream position unavailable."));
1053
- return;
1054
- }
1055
- let bufferIndex = this._nextIndex - bufferStart;
1056
- const maxIndex = ack.nextIndex + this.ackAdvance;
1057
- while (this._nextIndex < maxIndex) {
1058
- if (this._ended) return;
1059
- if (bufferIndex < buffer.count) {
1060
- const item = buffer.get(bufferIndex++);
1061
- if (item.kind === "value") {
1062
- this.sendItem(item.value);
1063
- } else if (item.kind === "end") {
1064
- if (!this._ended) this.sendEnd();
1065
- return;
1066
- } else {
1067
- if (!this._ended) this.sendEnd(item.error);
1068
- return;
1069
- }
1070
- continue;
1071
- }
1072
- if (isFullyBuffered) break;
1073
- if (!buffer.hasRemainingCapacity) break;
1074
- let pulled;
1075
- try {
1076
- const r = await iterator.next();
1077
- if (this._ended) return;
1078
- if (r.done) {
1079
- pulled = _endItem;
1080
- isFullyBuffered = true;
1081
- state.iteratorDone = true;
1082
- } else {
1083
- pulled = { kind: "value", value: r.value };
1084
- }
1085
- } catch (e) {
1086
- state.iteratorDone = true;
1028
+ let whenMovedNext = null;
1029
+ const pending = /* @__PURE__ */ Symbol("pending");
1030
+ const readNext = async () => {
1031
+ const next = whenMovedNext ?? iterator.next();
1032
+ whenMovedNext = null;
1033
+ return await next;
1034
+ };
1035
+ const tryReadReady = async () => {
1036
+ whenMovedNext ??= iterator.next();
1037
+ return await Promise.race([
1038
+ whenMovedNext,
1039
+ Promise.resolve(pending)
1040
+ ]);
1041
+ };
1042
+ const bufferNext = async (onlyIfReady, prefetchNext) => {
1043
+ let r;
1044
+ try {
1045
+ r = onlyIfReady ? await tryReadReady() : await readNext();
1046
+ if (r === pending)
1047
+ return false;
1048
+ whenMovedNext = null;
1049
+ if (this._ended) return false;
1050
+ if (r.done) {
1051
+ buffer.pushTail(_endItem);
1087
1052
  isFullyBuffered = true;
1088
- pulled = {
1089
- kind: "error",
1090
- error: e instanceof Error ? e : new Error(String(e))
1091
- };
1053
+ state.iteratorDone = true;
1054
+ } else {
1055
+ buffer.pushTail({ kind: "value", value: r.value });
1056
+ if (prefetchNext)
1057
+ whenMovedNext = iterator.next();
1092
1058
  }
1093
- buffer.pushTail(pulled);
1059
+ return true;
1060
+ } catch (e) {
1061
+ whenMovedNext = null;
1062
+ state.iteratorDone = true;
1063
+ isFullyBuffered = true;
1064
+ buffer.pushTail({
1065
+ kind: "error",
1066
+ error: e instanceof Error ? e : new Error(String(e))
1067
+ });
1068
+ return true;
1094
1069
  }
1095
- }
1096
- }
1097
- /**
1098
- * Real-time pump — drops stale items to stay close to the source's head.
1099
- *
1100
- * Direct port of .NET `OnRunRealTime` at
1101
- * src/ActualLab.Rpc/Infrastructure/RpcSharedStream.cs:219-420.
1102
- */
1103
- async _runRealTime(iterator, state) {
1104
- const buffer = new RingBuffer(this.ackAdvance + 1);
1105
- let bufferStart = 0;
1106
- let isFullyBuffered = false;
1070
+ };
1107
1071
  while (true) {
1108
1072
  let ack = this._tryProcessAcks();
1109
1073
  if (!ack) {
@@ -1116,7 +1080,7 @@ var RpcStreamSender = class _RpcStreamSender {
1116
1080
  }
1117
1081
  }
1118
1082
  this._lastAckedIndex = ack.nextIndex;
1119
- if (ack.mustReset && !isFullyBuffered) {
1083
+ if (isRealTime && ack.mustReset && !isFullyBuffered) {
1120
1084
  buffer.clear();
1121
1085
  bufferStart = this._nextIndex;
1122
1086
  while (true) {
@@ -1124,7 +1088,7 @@ var RpcStreamSender = class _RpcStreamSender {
1124
1088
  let item;
1125
1089
  let accepted = false;
1126
1090
  try {
1127
- const r = await iterator.next();
1091
+ const r = await readNext();
1128
1092
  if (this._ended) return;
1129
1093
  if (r.done) {
1130
1094
  item = _endItem;
@@ -1159,11 +1123,22 @@ var RpcStreamSender = class _RpcStreamSender {
1159
1123
  return;
1160
1124
  }
1161
1125
  let bufferIndex = this._nextIndex - bufferStart;
1162
- const maxIndex = ack.nextIndex + this.ackAdvance;
1126
+ const maxIndex = ack.nextIndex + this.bufferSize;
1163
1127
  while (this._nextIndex < maxIndex) {
1164
1128
  if (this._ended) return;
1165
- if (this._acks.length > 0) break;
1166
- if (bufferIndex < buffer.count) {
1129
+ if (isRealTime) {
1130
+ while (buffer.hasRemainingCapacity && !isFullyBuffered && this._acks.length === 0) {
1131
+ if (!await bufferNext(true, true))
1132
+ break;
1133
+ }
1134
+ if (bufferIndex >= buffer.count && !isFullyBuffered && buffer.hasRemainingCapacity && this._acks.length === 0) {
1135
+ await bufferNext(false, true);
1136
+ }
1137
+ if (ack.nextIndex > 0)
1138
+ bufferIndex = _compactBufferedUnsentSuffix(buffer, bufferIndex, this.canSkipTo);
1139
+ if (this._acks.length > 0) break;
1140
+ }
1141
+ while (this._nextIndex < maxIndex && bufferIndex < buffer.count) {
1167
1142
  const item = buffer.get(bufferIndex++);
1168
1143
  if (item.kind === "value") {
1169
1144
  this.sendItem(item.value);
@@ -1174,73 +1149,13 @@ var RpcStreamSender = class _RpcStreamSender {
1174
1149
  if (!this._ended) this.sendEnd(item.error);
1175
1150
  return;
1176
1151
  }
1177
- continue;
1178
1152
  }
1153
+ if (this._nextIndex >= maxIndex)
1154
+ break;
1179
1155
  if (isFullyBuffered) break;
1180
1156
  if (!buffer.hasRemainingCapacity) break;
1181
- let pulled;
1182
- try {
1183
- const r = await iterator.next();
1184
- if (this._ended) return;
1185
- if (r.done) {
1186
- pulled = _endItem;
1187
- isFullyBuffered = true;
1188
- state.iteratorDone = true;
1189
- } else {
1190
- pulled = { kind: "value", value: r.value };
1191
- }
1192
- } catch (e) {
1193
- state.iteratorDone = true;
1194
- isFullyBuffered = true;
1195
- pulled = {
1196
- kind: "error",
1197
- error: e instanceof Error ? e : new Error(String(e))
1198
- };
1199
- }
1200
- buffer.pushTail(pulled);
1201
- }
1202
- if (this._nextIndex >= maxIndex && !isFullyBuffered && !this._ended) {
1203
- let latestValue;
1204
- let haveLatest = false;
1205
- let exitReason = "ack";
1206
- let exitError = null;
1207
- drain: while (!this._ended) {
1208
- if (this._acks.length > 0) {
1209
- exitReason = "ack";
1210
- break drain;
1211
- }
1212
- let r;
1213
- try {
1214
- r = await iterator.next();
1215
- } catch (e) {
1216
- state.iteratorDone = true;
1217
- isFullyBuffered = true;
1218
- exitReason = "error";
1219
- exitError = e instanceof Error ? e : new Error(String(e));
1220
- break drain;
1221
- }
1222
- if (this._ended) return;
1223
- if (r.done) {
1224
- state.iteratorDone = true;
1225
- isFullyBuffered = true;
1226
- exitReason = "end";
1227
- break drain;
1228
- }
1229
- if (this.canSkipTo(r.value)) {
1230
- latestValue = r.value;
1231
- haveLatest = true;
1232
- }
1233
- }
1234
- if (exitReason === "end" || exitReason === "error") {
1235
- if (haveLatest && !this._ended) this.sendItem(latestValue);
1236
- if (!this._ended) this.sendEnd(exitError);
1237
- return;
1238
- }
1239
- if (haveLatest) {
1240
- const wasFull = buffer.isFull;
1241
- buffer.pushTailAndMoveHeadIfFull({ kind: "value", value: latestValue });
1242
- if (wasFull) bufferStart++;
1243
- }
1157
+ if (!isRealTime)
1158
+ await bufferNext(false, false);
1244
1159
  }
1245
1160
  }
1246
1161
  }
@@ -1307,11 +1222,28 @@ var _endItem = { kind: "end" };
1307
1222
  function _clamp(value, min, max) {
1308
1223
  return value < min ? min : value > max ? max : value;
1309
1224
  }
1225
+ function _compactBufferedUnsentSuffix(buffer, firstUnsentIndex, canSkipTo) {
1226
+ let skipToIndex = -1;
1227
+ for (let i = firstUnsentIndex; i < buffer.count; i++) {
1228
+ const item = buffer.get(i);
1229
+ if (item.kind === "value" && canSkipTo(item.value))
1230
+ skipToIndex = i;
1231
+ }
1232
+ if (skipToIndex <= firstUnsentIndex)
1233
+ return firstUnsentIndex;
1234
+ const items = buffer.toArray();
1235
+ buffer.clear();
1236
+ for (let i = 0; i < firstUnsentIndex; i++)
1237
+ buffer.pushTail(items[i]);
1238
+ for (let i = skipToIndex; i < items.length; i++)
1239
+ buffer.pushTail(items[i]);
1240
+ return firstUnsentIndex;
1241
+ }
1310
1242
 
1311
1243
  // src/rpc-stream.ts
1312
1244
  var { warnLog: warnLog4 } = getLogs("RpcStream");
1313
1245
  var DEFAULT_ACK_PERIOD2 = 30;
1314
- var DEFAULT_ACK_ADVANCE2 = 61;
1246
+ var DEFAULT_BUFFER_SIZE2 = 61;
1315
1247
  function parseStreamRef(value) {
1316
1248
  if (typeof value === "string") {
1317
1249
  const parts = value.split(",");
@@ -1319,12 +1251,12 @@ function parseStreamRef(value) {
1319
1251
  const hostId = parts[0];
1320
1252
  const localId = parseInt(parts[1], 10);
1321
1253
  const ackPeriod = parseInt(parts[2], 10);
1322
- const ackAdvance = parseInt(parts[3], 10);
1323
- if (isNaN(localId) || isNaN(ackPeriod) || isNaN(ackAdvance))
1254
+ const bufferSize = parseInt(parts[3], 10);
1255
+ if (isNaN(localId) || isNaN(ackPeriod) || isNaN(bufferSize))
1324
1256
  return null;
1325
1257
  const allowReconnect = parts.length < 5 || parts[4] !== "0";
1326
1258
  const isRealTime = parts.length >= 6 && parts[5] === "1";
1327
- return { hostId, localId, ackPeriod, ackAdvance, allowReconnect, isRealTime };
1259
+ return { hostId, localId, ackPeriod, bufferSize, allowReconnect, isRealTime };
1328
1260
  }
1329
1261
  if (typeof value === "object" && value !== null) {
1330
1262
  const obj = value;
@@ -1335,10 +1267,10 @@ function parseStreamRef(value) {
1335
1267
  const hostId = String(rawHostId);
1336
1268
  const localId = Number(serializedId[1]);
1337
1269
  const ackPeriod = Number(obj.AckPeriod ?? 256);
1338
- const ackAdvance = Number(obj.AckAdvance ?? 128);
1270
+ const bufferSize = Number(obj.BufferSize ?? 128);
1339
1271
  const allowReconnect = obj.AllowReconnect !== false;
1340
1272
  const isRealTime = obj.IsRealTime === true;
1341
- return { hostId, localId, ackPeriod, ackAdvance, allowReconnect, isRealTime };
1273
+ return { hostId, localId, ackPeriod, bufferSize, allowReconnect, isRealTime };
1342
1274
  }
1343
1275
  return null;
1344
1276
  }
@@ -1358,7 +1290,7 @@ var RpcStream = class _RpcStream {
1358
1290
  isRealTime;
1359
1291
  canSkipTo;
1360
1292
  ackPeriod;
1361
- ackAdvance;
1293
+ bufferSize;
1362
1294
  // Local (origin) mode
1363
1295
  localSource;
1364
1296
  // Remote (target) mode
@@ -1388,7 +1320,7 @@ var RpcStream = class _RpcStream {
1388
1320
  this.isRealTime = options.isRealTime ?? false;
1389
1321
  this.canSkipTo = options.canSkipTo ?? (() => true);
1390
1322
  this.ackPeriod = options.ackPeriod ?? DEFAULT_ACK_PERIOD2;
1391
- this.ackAdvance = options.ackAdvance ?? DEFAULT_ACK_ADVANCE2;
1323
+ this.bufferSize = options.bufferSize ?? DEFAULT_BUFFER_SIZE2;
1392
1324
  } else {
1393
1325
  const ref = sourceOrRef;
1394
1326
  const peer = optionsOrPeer;
@@ -1399,7 +1331,7 @@ var RpcStream = class _RpcStream {
1399
1331
  this.isRealTime = ref.isRealTime;
1400
1332
  this.canSkipTo = () => true;
1401
1333
  this.ackPeriod = ref.ackPeriod;
1402
- this.ackAdvance = ref.ackAdvance;
1334
+ this.bufferSize = ref.bufferSize;
1403
1335
  }
1404
1336
  }
1405
1337
  /**
@@ -1417,7 +1349,7 @@ var RpcStream = class _RpcStream {
1417
1349
  * stream reference.
1418
1350
  *
1419
1351
  * For binary peers the ref is a MessagePack-compatible object
1420
- * (`{ SerializedId, AckPeriod, AckAdvance, AllowReconnect }`);
1352
+ * (`{ SerializedId, AckPeriod, BufferSize, AllowReconnect }`);
1421
1353
  * for text peers it is a comma-separated string.
1422
1354
  *
1423
1355
  * Can only be called once per stream.
@@ -1434,7 +1366,7 @@ var RpcStream = class _RpcStream {
1434
1366
  const sender = new RpcStreamSender(
1435
1367
  peer,
1436
1368
  this.ackPeriod,
1437
- this.ackAdvance,
1369
+ this.bufferSize,
1438
1370
  this.allowReconnect,
1439
1371
  this.isRealTime,
1440
1372
  this.canSkipTo,
@@ -1447,7 +1379,7 @@ var RpcStream = class _RpcStream {
1447
1379
  this._toRefValue = peer.serializationFormat.isBinary ? {
1448
1380
  SerializedId: [sender.id.hostId, sender.id.localId],
1449
1381
  AckPeriod: sender.ackPeriod,
1450
- AckAdvance: sender.ackAdvance,
1382
+ BufferSize: sender.bufferSize,
1451
1383
  AllowReconnect: sender.allowReconnect
1452
1384
  } : sender.toRef();
1453
1385
  return this._toRefValue;