@actuallab/rpc 13.0.163 → 14.0.17

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.cjs CHANGED
@@ -1077,6 +1077,7 @@ var import_core5 = require("@actuallab/core");
1077
1077
  var { warnLog: warnLog3 } = getLogs("RpcSharedStream");
1078
1078
  var DEFAULT_ACK_PERIOD = 256;
1079
1079
  var DEFAULT_ACK_ADVANCE = 128;
1080
+ var RTT_WINDOW_MS = 2e4;
1080
1081
  var RpcStreamSender = class _RpcStreamSender {
1081
1082
  static _emptyGuid = "00000000-0000-0000-0000-000000000000";
1082
1083
  id;
@@ -1118,6 +1119,9 @@ var RpcStreamSender = class _RpcStreamSender {
1118
1119
  _lastAckedIndex = 0;
1119
1120
  _abortController = new AbortController();
1120
1121
  _iterator = null;
1122
+ // -- RTT sampling (send → matching ack round trips, windowed minimum) --
1123
+ _pendingSendTimes = [];
1124
+ _rttSamples = [];
1121
1125
  // -- Recorder-controller metrics (Step 9.1) --
1122
1126
  /** Total items skipped via real-time `canSkipTo` compaction. */
1123
1127
  _skipCount = 0;
@@ -1142,6 +1146,21 @@ var RpcStreamSender = class _RpcStreamSender {
1142
1146
  get skipCount() {
1143
1147
  return this._skipCount;
1144
1148
  }
1149
+ // Windowed-MIN ack round trip (ms, -1 until sampled): converges on propagation
1150
+ // RTT — unlike a mean/EMA it is not inflated by self-induced queuing.
1151
+ get minRttMs() {
1152
+ const cutoffMs = Date.now() - RTT_WINDOW_MS;
1153
+ while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
1154
+ this._rttSamples.shift();
1155
+ if (this._rttSamples.length === 0)
1156
+ return -1;
1157
+ let min = this._rttSamples[0].rttMs;
1158
+ for (const s of this._rttSamples) {
1159
+ if (s.rttMs < min)
1160
+ min = s.rttMs;
1161
+ }
1162
+ return min;
1163
+ }
1145
1164
  // -- ACK queue (analog of .NET's Channel<(long, bool)>) --
1146
1165
  _acks = [];
1147
1166
  _whenAckReady = null;
@@ -1195,6 +1214,7 @@ var RpcStreamSender = class _RpcStreamSender {
1195
1214
  this._sendDisconnect();
1196
1215
  return;
1197
1216
  }
1217
+ this._sampleRtt(nextIndex, mustReset);
1198
1218
  this._acks.push({ nextIndex, mustReset });
1199
1219
  if (this._whenAckReady) {
1200
1220
  this._whenAckReady.resolve();
@@ -1233,6 +1253,7 @@ var RpcStreamSender = class _RpcStreamSender {
1233
1253
  this._nextIndex,
1234
1254
  item
1235
1255
  );
1256
+ this._pendingSendTimes.push({ index: this._nextIndex, sentAtMs: Date.now() });
1236
1257
  }
1237
1258
  this._nextIndex++;
1238
1259
  }
@@ -1451,13 +1472,18 @@ var RpcStreamSender = class _RpcStreamSender {
1451
1472
  }
1452
1473
  }
1453
1474
  /* eslint-enable @typescript-eslint/no-unnecessary-condition */
1454
- /** Drain all queued ACKs, returning the most recent one (or null if none). */
1475
+ /** Drain all queued ACKs, returning the most recent one (or null if none).
1476
+ * A reconnect's reset ACK may be drained in one batch with regular
1477
+ * consumption ACKs the client sends right after it, so `mustReset` is
1478
+ * OR-ed over the whole batch — the last ACK alone can't represent it. */
1455
1479
  _tryProcessAcks() {
1456
1480
  if (this._acks.length === 0) return null;
1457
- let last = null;
1481
+ let nextIndex = 0;
1482
+ let mustReset = false;
1458
1483
  while (this._acks.length > 0) {
1459
1484
  const a = this._acks.shift();
1460
- last = a;
1485
+ nextIndex = a.nextIndex;
1486
+ mustReset ||= a.mustReset;
1461
1487
  if (a.mustReset || this._nextIndex < a.nextIndex) {
1462
1488
  this._nextIndex = a.nextIndex;
1463
1489
  }
@@ -1466,7 +1492,7 @@ var RpcStreamSender = class _RpcStreamSender {
1466
1492
  this.onAckProcessed?.();
1467
1493
  } catch {
1468
1494
  }
1469
- return last;
1495
+ return { nextIndex, mustReset };
1470
1496
  }
1471
1497
  /** Wait for the ACK queue to have at least one entry (or `_ended`). */
1472
1498
  async _waitAckReady() {
@@ -1475,6 +1501,26 @@ var RpcStreamSender = class _RpcStreamSender {
1475
1501
  await this._whenAckReady;
1476
1502
  this._whenAckReady = null;
1477
1503
  }
1504
+ // One RTT sample per ack: time from sending the newest acked item to the
1505
+ // ack's arrival (a slight upper bound — includes the server's ack cadence).
1506
+ _sampleRtt(ackNextIndex, mustReset) {
1507
+ if (mustReset) {
1508
+ this._pendingSendTimes.length = 0;
1509
+ return;
1510
+ }
1511
+ let sentAtMs = -1;
1512
+ while (this._pendingSendTimes.length > 0 && this._pendingSendTimes[0].index < ackNextIndex) {
1513
+ sentAtMs = this._pendingSendTimes[0].sentAtMs;
1514
+ this._pendingSendTimes.shift();
1515
+ }
1516
+ if (sentAtMs < 0)
1517
+ return;
1518
+ const nowMs = Date.now();
1519
+ const cutoffMs = nowMs - RTT_WINDOW_MS;
1520
+ while (this._rttSamples.length > 0 && this._rttSamples[0].atMs < cutoffMs)
1521
+ this._rttSamples.shift();
1522
+ this._rttSamples.push({ atMs: nowMs, rttMs: nowMs - sentAtMs });
1523
+ }
1478
1524
  // -- IRpcObject --
1479
1525
  reconnect() {
1480
1526
  }