@actuallab/rpc 13.0.163 → 13.0.167

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
@@ -1266,6 +1266,8 @@ declare class RpcStreamSender<T> implements IRpcObject {
1266
1266
  private _lastAckedIndex;
1267
1267
  private _abortController;
1268
1268
  private _iterator;
1269
+ private _pendingSendTimes;
1270
+ private _rttSamples;
1269
1271
  /** Total items skipped via real-time `canSkipTo` compaction. */
1270
1272
  private _skipCount;
1271
1273
  /** Fires after a $sys.Ack has been processed (post-compaction). The
@@ -1283,6 +1285,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1283
1285
  get nextIndex(): number;
1284
1286
  get lastAckIndex(): number;
1285
1287
  get skipCount(): number;
1288
+ get minRttMs(): number;
1286
1289
  private _acks;
1287
1290
  private _whenAckReady;
1288
1291
  constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean, bufferSize?: number);
@@ -1333,6 +1336,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1333
1336
  private _tryProcessAcks;
1334
1337
  /** Wait for the ACK queue to have at least one entry (or `_ended`). */
1335
1338
  private _waitAckReady;
1339
+ private _sampleRtt;
1336
1340
  reconnect(): void;
1337
1341
  disconnect(): void;
1338
1342
  private _forceCloseIterator;
package/dist/index.d.ts CHANGED
@@ -1266,6 +1266,8 @@ declare class RpcStreamSender<T> implements IRpcObject {
1266
1266
  private _lastAckedIndex;
1267
1267
  private _abortController;
1268
1268
  private _iterator;
1269
+ private _pendingSendTimes;
1270
+ private _rttSamples;
1269
1271
  /** Total items skipped via real-time `canSkipTo` compaction. */
1270
1272
  private _skipCount;
1271
1273
  /** Fires after a $sys.Ack has been processed (post-compaction). The
@@ -1283,6 +1285,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1283
1285
  get nextIndex(): number;
1284
1286
  get lastAckIndex(): number;
1285
1287
  get skipCount(): number;
1288
+ get minRttMs(): number;
1286
1289
  private _acks;
1287
1290
  private _whenAckReady;
1288
1291
  constructor(peer: RpcPeer, ackPeriod?: number, ackAdvance?: number, allowReconnect?: boolean, isRealTime?: boolean, canSkipTo?: (item: T) => boolean, sourceUsesAbortSignal?: boolean, bufferSize?: number);
@@ -1333,6 +1336,7 @@ declare class RpcStreamSender<T> implements IRpcObject {
1333
1336
  private _tryProcessAcks;
1334
1337
  /** Wait for the ACK queue to have at least one entry (or `_ended`). */
1335
1338
  private _waitAckReady;
1339
+ private _sampleRtt;
1336
1340
  reconnect(): void;
1337
1341
  disconnect(): void;
1338
1342
  private _forceCloseIterator;
package/dist/index.js CHANGED
@@ -967,6 +967,7 @@ import { PromiseSource as PromiseSource3, RingBuffer } from "@actuallab/core";
967
967
  var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
968
968
  var DEFAULT_ACK_PERIOD = 256;
969
969
  var DEFAULT_ACK_ADVANCE = 128;
970
+ var RTT_WINDOW_MS = 2e4;
970
971
  var RpcStreamSender = class _RpcStreamSender {
971
972
  static _emptyGuid = "00000000-0000-0000-0000-000000000000";
972
973
  id;
@@ -1008,6 +1009,9 @@ var RpcStreamSender = class _RpcStreamSender {
1008
1009
  _lastAckedIndex = 0;
1009
1010
  _abortController = new AbortController();
1010
1011
  _iterator = null;
1012
+ // -- RTT sampling (send → matching ack round trips, windowed minimum) --
1013
+ _pendingSendTimes = [];
1014
+ _rttSamples = [];
1011
1015
  // -- Recorder-controller metrics (Step 9.1) --
1012
1016
  /** Total items skipped via real-time `canSkipTo` compaction. */
1013
1017
  _skipCount = 0;
@@ -1032,6 +1036,21 @@ var RpcStreamSender = class _RpcStreamSender {
1032
1036
  get skipCount() {
1033
1037
  return this._skipCount;
1034
1038
  }
1039
+ // Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation
1040
+ // RTT — unlike a mean/EMA it is not inflated by self-induced queuing.
1041
+ get minRttMs() {
1042
+ const cutoffMs = Date.now() - RTT_WINDOW_MS;
1043
+ while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
1044
+ this._rttSamples.shift();
1045
+ if (this._rttSamples.length === 0)
1046
+ return -1;
1047
+ let min = this._rttSamples[0].rttMs;
1048
+ for (const s of this._rttSamples) {
1049
+ if (s.rttMs < min)
1050
+ min = s.rttMs;
1051
+ }
1052
+ return min;
1053
+ }
1035
1054
  // -- ACK queue (analog of .NET's Channel<(long, bool)>) --
1036
1055
  _acks = [];
1037
1056
  _whenAckReady = null;
@@ -1085,6 +1104,7 @@ var RpcStreamSender = class _RpcStreamSender {
1085
1104
  this._sendDisconnect();
1086
1105
  return;
1087
1106
  }
1107
+ this._sampleRtt(nextIndex, mustReset);
1088
1108
  this._acks.push({ nextIndex, mustReset });
1089
1109
  if (this._whenAckReady) {
1090
1110
  this._whenAckReady.resolve();
@@ -1123,6 +1143,7 @@ var RpcStreamSender = class _RpcStreamSender {
1123
1143
  this._nextIndex,
1124
1144
  item
1125
1145
  );
1146
+ this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });
1126
1147
  }
1127
1148
  this._nextIndex++;
1128
1149
  }
@@ -1365,6 +1386,26 @@ var RpcStreamSender = class _RpcStreamSender {
1365
1386
  await this._whenAckReady;
1366
1387
  this._whenAckReady = null;
1367
1388
  }
1389
+ // One RTT sample per ack: time from sending the newest acked item to the
1390
+ // ack's arrival (a slight upper bound — includes the server's ack cadence).
1391
+ _sampleRtt(ackNextIndex, mustReset) {
1392
+ if (mustReset) {
1393
+ this._pendingSendTimes.length = 0;
1394
+ return;
1395
+ }
1396
+ let sentAtMs = -1;
1397
+ while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {
1398
+ sentAtMs = this._pendingSendTimes[0].sentAtMs;
1399
+ this._pendingSendTimes.shift();
1400
+ }
1401
+ if (sentAtMs < 0)
1402
+ return;
1403
+ const nowMs = Date.now();
1404
+ const cutoffMs = nowMs - RTT_WINDOW_MS;
1405
+ while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
1406
+ this._rttSamples.shift();
1407
+ this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });
1408
+ }
1368
1409
  // -- IRpcObject --
1369
1410
  reconnect() {
1370
1411
  }