@actuallab/rpc 12.3.83 → 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.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;
@@ -2188,6 +2120,17 @@ var defaultConnectionUrlResolver = (peer) => {
2188
2120
  return peer.ref + sep + `clientId=${peer.clientId}&f=${formatKey}`;
2189
2121
  }
2190
2122
  };
2123
+ var sanitizeUrl = (url) => {
2124
+ try {
2125
+ const parsed = new URL(url);
2126
+ if (!parsed.searchParams.has("session"))
2127
+ return url;
2128
+ parsed.searchParams.set("session", "<redacted>");
2129
+ return parsed.toString();
2130
+ } catch {
2131
+ return url.replace(/([?&]session=)[^&]*/i, "$1<redacted>");
2132
+ }
2133
+ };
2191
2134
  function parseFormatFromUrl(url) {
2192
2135
  try {
2193
2136
  const f = new URL(url).searchParams.get("f");
@@ -2635,7 +2578,6 @@ var RpcClientPeer = class extends RpcPeer {
2635
2578
  }
2636
2579
  async _runImpl() {
2637
2580
  if (this._disposed) return;
2638
- const connUrl = this.connectionUrlResolver(this);
2639
2581
  infoLog?.log(`'${this.ref}': Started (Client)`);
2640
2582
  try {
2641
2583
  while (!this._disposed) {
@@ -2656,7 +2598,8 @@ var RpcClientPeer = class extends RpcPeer {
2656
2598
  let lastCloseCode = 0;
2657
2599
  let lastCloseReason = "";
2658
2600
  try {
2659
- infoLog?.log(`'${this.ref}': Connecting to ${connUrl}`);
2601
+ const connUrl = await this.connectionUrlResolver(this);
2602
+ infoLog?.log(`'${this.ref}': Connecting to ${sanitizeUrl(connUrl)}`);
2660
2603
  const ws = this.webSocketFactory?.(connUrl) ?? new WebSocket(connUrl);
2661
2604
  const conn = new RpcWebSocketConnection(
2662
2605
  ws,