@loro-dev/streams-crdt 0.15.1 → 0.16.0

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.
@@ -261,27 +261,29 @@ var PayloadProtectionRuntime = class {
261
261
  }
262
262
  return out;
263
263
  }
264
- async encodeSnapshot(snapshot) {
264
+ async encodeSnapshot(snapshot, continuationOffset) {
265
+ checkContinuationOffset(continuationOffset);
265
266
  if (this.writePolicy === "plaintext") return snapshot;
266
- return await this.sealPayload("snapshot", snapshot);
267
+ return await this.sealPayload("snapshot", snapshot, continuationOffset);
267
268
  }
268
- async decodeSnapshot(snapshot) {
269
- if (isEncryptedEnvelope(snapshot)) return await this.openPayload("snapshot", snapshot);
269
+ async decodeSnapshot(snapshot, continuationOffset) {
270
+ checkContinuationOffset(continuationOffset);
271
+ if (isEncryptedEnvelope(snapshot)) return await this.openPayload("snapshot", snapshot, continuationOffset);
270
272
  this.assertPlaintextAllowed("snapshot");
271
273
  return snapshot;
272
274
  }
273
- async sealPayload(kind, plaintext) {
274
- return await this.sealProviderPayload(kind, plaintext);
275
+ async sealPayload(kind, plaintext, continuationOffset) {
276
+ return await this.sealProviderPayload(kind, plaintext, continuationOffset);
275
277
  }
276
- async openPayload(expectedKind, envelope) {
278
+ async openPayload(expectedKind, envelope, continuationOffset) {
277
279
  const version = envelope[4];
278
- if (version === PROVIDER_ENVELOPE_VERSION) return await this.openProviderPayload(expectedKind, envelope);
280
+ if (version === PROVIDER_ENVELOPE_VERSION) return await this.openProviderPayload(expectedKind, envelope, continuationOffset);
279
281
  throw new PayloadProtectionError("invalid_envelope", `unsupported streams-crdt protected payload envelope version '${String(version)}'`);
280
282
  }
281
- async sealProviderPayload(kind, plaintext) {
283
+ async sealProviderPayload(kind, plaintext, continuationOffset) {
282
284
  const provider = this.provider;
283
285
  if (provider == null) throw new PayloadProtectionError("encrypt_failed", "provider-backed E2EE writes require a provider");
284
- const context = createProviderContext(kind);
286
+ const context = createProviderContext(kind, continuationOffset);
285
287
  let authenticatedHeader;
286
288
  let additionalDataCalls = 0;
287
289
  let result;
@@ -314,12 +316,12 @@ var PayloadProtectionRuntime = class {
314
316
  throw new PayloadProtectionError(reason, payloadProtectionFailureMessage(reason));
315
317
  }
316
318
  }
317
- async openProviderPayload(expectedKind, envelope) {
319
+ async openProviderPayload(expectedKind, envelope, continuationOffset) {
318
320
  const parsed = parseProviderEnvelope(envelope);
319
321
  if (parsed.kind !== expectedKind) throw new PayloadProtectionError("wrong_payload_kind", `expected ${expectedKind} payload, got ${parsed.kind}`);
320
322
  const provider = this.provider;
321
323
  if (provider == null) throw new PayloadProtectionError("missing_read_key", "provider-backed E2EE reads require a provider");
322
- const context = createProviderContext(parsed.kind);
324
+ const context = createProviderContext(parsed.kind, continuationOffset);
323
325
  try {
324
326
  const plaintext = await provider.open({
325
327
  sealed: parsed.sealed,
@@ -370,13 +372,26 @@ function payloadProtectionFailureMessage(reason) {
370
372
  function isEncryptedEnvelope(payload) {
371
373
  return payload.byteLength >= MAGIC.byteLength && MAGIC.every((byte, index) => payload[index] === byte);
372
374
  }
373
- function createProviderContext(kind) {
375
+ function createProviderContext(kind, continuationOffset) {
376
+ if (kind === "snapshot") {
377
+ checkContinuationOffset(continuationOffset);
378
+ return {
379
+ protocol: PAYLOAD_PROTECTION_PROTOCOL,
380
+ version: PROVIDER_ENVELOPE_VERSION,
381
+ kind,
382
+ continuationOffset
383
+ };
384
+ }
374
385
  return {
375
386
  protocol: PAYLOAD_PROTECTION_PROTOCOL,
376
387
  version: PROVIDER_ENVELOPE_VERSION,
377
388
  kind
378
389
  };
379
390
  }
391
+ /** Opaque offset token only. Not a timestamp, Org, bucket, or role. */
392
+ function checkContinuationOffset(value) {
393
+ if (typeof value !== "string" || value.length === 0 || value.length > 1024 || value === "now") throw new PayloadProtectionError("invalid_envelope", "invalid continuation offset");
394
+ }
380
395
  function createProviderEnvelopeHeader(kind, providerHeader) {
381
396
  const normalizedHeader = normalizeProviderHeader(providerHeader);
382
397
  const header = new Uint8Array(PROVIDER_PREFIX_BYTES + normalizedHeader.byteLength);
@@ -1083,11 +1098,13 @@ var StreamsTransportClient = class {
1083
1098
  async bootstrap(signal, timeoutMode = "default") {
1084
1099
  const result = await (timeoutMode === "initial" ? this.initialSyncClient : this.client).bootstrap({ signal });
1085
1100
  if (!result.ok) throw this.toTransportFailure(result.result);
1086
- const snapshot = result.result.snapshotOffset === "-1" ? new Uint8Array() : result.result.snapshot?.body ?? new Uint8Array();
1101
+ const snapshotOffset = result.result.snapshotOffset;
1102
+ const snapshot = snapshotOffset === "-1" ? new Uint8Array() : result.result.snapshot?.body ?? new Uint8Array();
1087
1103
  return {
1088
1104
  nextOffset: result.result.nextOffset,
1089
1105
  upToDate: result.result.upToDate,
1090
1106
  snapshot,
1107
+ snapshotOffset,
1091
1108
  updates: result.result.updates.flatMap((part) => decodeMultipartPartItems(part.body)),
1092
1109
  streamCursor: result.result.cursor
1093
1110
  };
@@ -1265,14 +1282,14 @@ var StreamsTransportClient = class {
1265
1282
  }
1266
1283
  async fetchAuthorized(input, init, timeoutMs, phase) {
1267
1284
  const requestUrl = this.routeRequestUrl(input, init);
1268
- const firstToken = await this.resolveToken({ reason: "request" });
1285
+ const firstToken = this.authProvider == null ? void 0 : await withTokenTimeout(this.resolveToken({ reason: "request" }), timeoutMs, phase);
1269
1286
  const firstResponse = await this.fetchWithTimeout(requestUrl, {
1270
1287
  ...init,
1271
1288
  headers: this.attachAuthorization(init.headers, firstToken)
1272
1289
  }, timeoutMs, phase);
1273
1290
  if (this.authProvider == null || firstResponse.status !== 401 && firstResponse.status !== 403) return firstResponse;
1274
1291
  await firstResponse.body?.cancel().catch(() => {});
1275
- const refreshedToken = await this.refreshTokenAfterFailure(firstResponse.status, firstToken);
1292
+ const refreshedToken = await this.refreshTokenAfterFailure(firstResponse.status, firstToken, timeoutMs, phase);
1276
1293
  return this.fetchWithTimeout(requestUrl, {
1277
1294
  ...init,
1278
1295
  headers: this.attachAuthorization(init.headers, refreshedToken)
@@ -1299,16 +1316,25 @@ var StreamsTransportClient = class {
1299
1316
  async resolveToken(context) {
1300
1317
  return await this.authProvider?.(context);
1301
1318
  }
1302
- async refreshTokenAfterFailure(status, previousToken) {
1319
+ async refreshTokenAfterFailure(status, previousToken, timeoutMs, phase) {
1303
1320
  if (this.authProvider == null) return;
1304
- if (this.authRefreshInFlight == null) this.authRefreshInFlight = this.resolveToken({
1305
- reason: "unauthorized",
1306
- status,
1307
- previousToken
1308
- }).finally(() => {
1309
- this.authRefreshInFlight = void 0;
1310
- });
1311
- return await this.authRefreshInFlight;
1321
+ if (this.authRefreshInFlight == null) {
1322
+ const inFlight = this.resolveToken({
1323
+ reason: "unauthorized",
1324
+ status,
1325
+ previousToken
1326
+ }).finally(() => {
1327
+ if (this.authRefreshInFlight === inFlight) this.authRefreshInFlight = void 0;
1328
+ });
1329
+ this.authRefreshInFlight = inFlight;
1330
+ }
1331
+ const inFlight = this.authRefreshInFlight;
1332
+ try {
1333
+ return await withTokenTimeout(inFlight, timeoutMs, phase);
1334
+ } catch (error) {
1335
+ if (error instanceof TimeoutError && this.authRefreshInFlight === inFlight) this.authRefreshInFlight = void 0;
1336
+ throw error;
1337
+ }
1312
1338
  }
1313
1339
  attachAuthorization(base, token) {
1314
1340
  const headers = new Headers(base);
@@ -1502,8 +1528,26 @@ function parseRetryAfterMs(value) {
1502
1528
  const delta = parsed - Date.now();
1503
1529
  return delta > 0 ? delta : void 0;
1504
1530
  }
1531
+ async function withTokenTimeout(promise, timeoutMs, phase) {
1532
+ if (!(timeoutMs > 0)) return await promise;
1533
+ let timer;
1534
+ try {
1535
+ return await Promise.race([promise, new Promise((_, reject) => {
1536
+ timer = setTimeout(() => {
1537
+ reject(new TimeoutError(phase, timeoutMs));
1538
+ }, timeoutMs);
1539
+ })]);
1540
+ } finally {
1541
+ clearTimeout(timer);
1542
+ }
1543
+ }
1505
1544
  //#endregion
1506
1545
  //#region src/ephemeral-runtime.ts
1546
+ /**
1547
+ * Superseded entries are skipped lazily; once they outnumber live entries
1548
+ * (and exceed this floor) the queue is rebuilt so it stays O(live entries).
1549
+ */
1550
+ const PENDING_LOCAL_REBUILD_MIN_SUPERSEDED = 64;
1507
1551
  function createNoopAdapter() {
1508
1552
  return {
1509
1553
  emptyVersion: () => ({}),
@@ -1624,6 +1668,11 @@ var EphemeralStreamCrdt = class {
1624
1668
  writeStatus: "connecting",
1625
1669
  statusListeners: /* @__PURE__ */ new Set(),
1626
1670
  pendingLocal: new Deque(),
1671
+ pendingLocalByKey: /* @__PURE__ */ new Map(),
1672
+ pendingLocalSuperseded: 0,
1673
+ pendingLocalCount: 0,
1674
+ lastChainId: 0,
1675
+ liveChains: 0,
1627
1676
  syncWaiters: [],
1628
1677
  flushingLocal: false,
1629
1678
  retryAttempt: 0,
@@ -1674,15 +1723,17 @@ var EphemeralStreamCrdt = class {
1674
1723
  return state.status;
1675
1724
  },
1676
1725
  get pendingLocalCount() {
1677
- return state.pendingLocal.length;
1726
+ return state.pendingLocalCount;
1678
1727
  },
1679
1728
  onStatusChange: (listener) => this.onStatusChange(state, listener),
1680
1729
  waitUntilSynced: async () => {
1681
1730
  if (state.closed) throw closedSubscriptionError();
1682
- const remaining = state.pendingLocal.length;
1731
+ const remaining = state.liveChains;
1732
+ const lastChainId = state.lastChainId;
1683
1733
  if (remaining === 0) return;
1684
1734
  return new Promise((resolve, reject) => {
1685
1735
  state.syncWaiters.push({
1736
+ lastChainId,
1686
1737
  remaining,
1687
1738
  resolve,
1688
1739
  reject
@@ -1812,7 +1863,33 @@ var EphemeralStreamCrdt = class {
1812
1863
  }
1813
1864
  enqueueLocal(state, update) {
1814
1865
  if (state.closed || update == null || update.byteLength === 0) return;
1815
- state.pendingLocal.pushBack(update);
1866
+ const key = this.localUpdateKey(update);
1867
+ const previous = key == null ? void 0 : state.pendingLocalByKey.get(key);
1868
+ let chainId;
1869
+ if (previous == null) {
1870
+ state.lastChainId += 1;
1871
+ state.liveChains += 1;
1872
+ chainId = state.lastChainId;
1873
+ } else chainId = previous.chainId;
1874
+ const entry = {
1875
+ update,
1876
+ key,
1877
+ count: 1,
1878
+ chainId,
1879
+ superseded: false,
1880
+ inFlight: false
1881
+ };
1882
+ if (previous != null) {
1883
+ previous.superseded = true;
1884
+ previous.update = null;
1885
+ entry.count += previous.count;
1886
+ previous.count = 0;
1887
+ state.pendingLocalSuperseded += 1;
1888
+ }
1889
+ if (key != null) state.pendingLocalByKey.set(key, entry);
1890
+ state.pendingLocal.pushBack(entry);
1891
+ state.pendingLocalCount += 1;
1892
+ this.maybeRebuildPendingLocal(state);
1816
1893
  if (state.writeStatus !== "connecting") this.flushPendingLocal(state);
1817
1894
  }
1818
1895
  async flushPendingLocal(state) {
@@ -1820,42 +1897,94 @@ var EphemeralStreamCrdt = class {
1820
1897
  state.flushingLocal = true;
1821
1898
  try {
1822
1899
  while (!state.closed) {
1823
- const update = state.pendingLocal.peekFront();
1824
- if (update == null) return;
1825
1900
  let attempt = 0;
1826
- while (!state.closed) try {
1827
- await this.client.appendEphemeralUpdate(update);
1828
- state.pendingLocal.popFront();
1829
- this.notifySyncWaiters(state, 1);
1830
- this.setWriteStatus(state, "ok");
1831
- break;
1832
- } catch (error) {
1833
- const classified = classifyFetchError(error);
1834
- if (!classified.retriable || isAuthOrProtocolError(error)) {
1835
- this.rejectSyncWaiters(state, /* @__PURE__ */ new Error(`ephemeral append failed: ${classified.code}`));
1836
- this.setWriteStatus(state, "error");
1837
- return;
1838
- }
1839
- if (attempt >= this.reconnectConfig.maxAttempts) {
1840
- this.rejectSyncWaiters(state, /* @__PURE__ */ new Error("ephemeral append retry budget exhausted"));
1841
- this.setWriteStatus(state, "error");
1842
- return;
1901
+ while (!state.closed) {
1902
+ const entry = this.nextPendingLocal(state);
1903
+ if (entry == null) return;
1904
+ try {
1905
+ await this.client.appendEphemeralUpdate(entry.update);
1906
+ state.pendingLocal.popFront();
1907
+ state.pendingLocalCount -= entry.count;
1908
+ state.liveChains -= 1;
1909
+ this.notifyChainAcknowledged(state, entry.chainId);
1910
+ this.setWriteStatus(state, "ok");
1911
+ break;
1912
+ } catch (error) {
1913
+ const classified = classifyFetchError(error);
1914
+ if (!classified.retriable || isAuthOrProtocolError(error)) {
1915
+ this.rejectSyncWaiters(state, /* @__PURE__ */ new Error(`ephemeral append failed: ${classified.code}`));
1916
+ this.setWriteStatus(state, "error");
1917
+ return;
1918
+ }
1919
+ if (attempt >= this.reconnectConfig.maxAttempts) {
1920
+ this.rejectSyncWaiters(state, /* @__PURE__ */ new Error("ephemeral append retry budget exhausted"));
1921
+ this.setWriteStatus(state, "error");
1922
+ return;
1923
+ }
1924
+ this.setWriteStatus(state, "reconnecting");
1925
+ const delay = computeRetryDelay(attempt, this.reconnectConfig);
1926
+ attempt += 1;
1927
+ await sleep(delay, state.retrySleepController.signal);
1843
1928
  }
1844
- this.setWriteStatus(state, "reconnecting");
1845
- const delay = computeRetryDelay(attempt, this.reconnectConfig);
1846
- attempt += 1;
1847
- await sleep(delay, state.retrySleepController.signal);
1848
1929
  }
1849
1930
  }
1850
1931
  } finally {
1851
1932
  state.flushingLocal = false;
1852
1933
  }
1853
1934
  }
1854
- notifySyncWaiters(state, flushedCount) {
1935
+ localUpdateKey(update) {
1936
+ const adaptor = this.adaptor;
1937
+ if (adaptor.localUpdateKey == null) return;
1938
+ try {
1939
+ return adaptor.localUpdateKey(update) ?? void 0;
1940
+ } catch (error) {
1941
+ this.logError("ephemeral local update key lookup failed", error);
1942
+ return;
1943
+ }
1944
+ }
1945
+ /**
1946
+ * Pops superseded entries off the head and returns the next entry to send,
1947
+ * marking it in-flight so later same-key updates queue behind it.
1948
+ */
1949
+ nextPendingLocal(state) {
1950
+ for (;;) {
1951
+ const head = state.pendingLocal.peekFront();
1952
+ if (head == null) return;
1953
+ if (head.superseded) {
1954
+ state.pendingLocal.popFront();
1955
+ state.pendingLocalSuperseded -= 1;
1956
+ continue;
1957
+ }
1958
+ if (!head.inFlight) {
1959
+ head.inFlight = true;
1960
+ if (head.key != null && state.pendingLocalByKey.get(head.key) === head) state.pendingLocalByKey.delete(head.key);
1961
+ }
1962
+ return head;
1963
+ }
1964
+ }
1965
+ /** Drops superseded markers once they dominate the queue (amortized O(1)). */
1966
+ maybeRebuildPendingLocal(state) {
1967
+ if (state.pendingLocalSuperseded < PENDING_LOCAL_REBUILD_MIN_SUPERSEDED || state.pendingLocalSuperseded * 2 <= state.pendingLocal.length) return;
1968
+ const live = state.pendingLocal.toArray().filter((entry) => !entry.superseded);
1969
+ state.pendingLocal.clear();
1970
+ for (const entry of live) state.pendingLocal.pushBack(entry);
1971
+ state.pendingLocalSuperseded = 0;
1972
+ }
1973
+ /**
1974
+ * Completes waiters that tracked `chainId`. Each chain is acknowledged
1975
+ * exactly once (its entry is never superseded after it goes in flight), so
1976
+ * a waiter resolves only after every chain live at call time has had its
1977
+ * latest value acknowledged.
1978
+ */
1979
+ notifyChainAcknowledged(state, chainId) {
1855
1980
  let i = 0;
1856
1981
  while (i < state.syncWaiters.length) {
1857
1982
  const waiter = state.syncWaiters[i];
1858
- waiter.remaining -= flushedCount;
1983
+ if (chainId > waiter.lastChainId) {
1984
+ i += 1;
1985
+ continue;
1986
+ }
1987
+ waiter.remaining -= 1;
1859
1988
  if (waiter.remaining <= 0) {
1860
1989
  state.syncWaiters.splice(i, 1);
1861
1990
  waiter.resolve();
@@ -2135,12 +2264,13 @@ var TransportCursorManager = class {
2135
2264
  markCursorIncomplete(cursor, unresolved) {
2136
2265
  this.incompleteCursors.set(cursor, unresolved);
2137
2266
  }
2138
- async applyBootstrapPayload(cursor, snapshot, updates, nextOffset, upToDate = false) {
2267
+ async applyBootstrapPayload(cursor, snapshot, updates, nextOffset, upToDate = false, snapshotOffset = "-1") {
2139
2268
  let nextVersion = cursor.serverLowerBoundVersion;
2140
2269
  let complete = true;
2141
2270
  let unresolved = [];
2142
2271
  if (snapshot.byteLength > 0) {
2143
- const outcome = normalizeApplyReturn(await this.adapter.applySnapshot(this.decodeSnapshot == null ? snapshot : await this.decodeSnapshot(snapshot)));
2272
+ if (typeof snapshotOffset !== "string" || snapshotOffset.length === 0 || snapshotOffset.length > 1024 || snapshotOffset === "now" || snapshotOffset === "-1") throw new Error("bootstrap snapshot offset does not match an admitted continuation");
2273
+ const outcome = normalizeApplyReturn(await this.adapter.applySnapshot(this.decodeSnapshot == null ? snapshot : await this.decodeSnapshot(snapshot, snapshotOffset)));
2144
2274
  nextVersion = outcome.version;
2145
2275
  complete = outcome.complete;
2146
2276
  unresolved = outcome.unresolved ?? [];
@@ -2158,9 +2288,9 @@ var TransportCursorManager = class {
2158
2288
  serverLowerBoundVersion: nextVersion
2159
2289
  }, "bootstrap", complete, unresolved, upToDate);
2160
2290
  }
2161
- async applyBootstrapItems(cursor, items, nextOffset, upToDate = false) {
2291
+ async applyBootstrapItems(cursor, items, nextOffset, upToDate = false, snapshotOffset = "-1") {
2162
2292
  const snapshot = items[0] ?? new Uint8Array();
2163
- return await this.applyBootstrapPayload(cursor, snapshot, items.slice(1), nextOffset, upToDate);
2293
+ return await this.applyBootstrapPayload(cursor, snapshot, items.slice(1), nextOffset, upToDate, snapshotOffset);
2164
2294
  }
2165
2295
  async applyRemoteItems(cursor, items, nextOffset) {
2166
2296
  return await this.applyRemotePayload(cursor, [...items], nextOffset, "remote", void 0);
@@ -2324,7 +2454,7 @@ var TransportSnapshotManager = class {
2324
2454
  if (tailBytes <= snapshotBytes) return;
2325
2455
  if (tailBytes - snapshotBytes < config.minBytesSinceRemoteSnapshot) return;
2326
2456
  const snapshot = this.adapter.exportSnapshot();
2327
- await this.putSnapshotAt(state.cursor.nextOffset, this.encodeSnapshot == null ? snapshot : await this.encodeSnapshot(snapshot));
2457
+ await this.putSnapshotAt(state.cursor.nextOffset, this.encodeSnapshot == null ? snapshot : await this.encodeSnapshot(snapshot, state.cursor.nextOffset));
2328
2458
  } catch (error) {
2329
2459
  this.logError("snapshot upload failed", error);
2330
2460
  }
@@ -2506,16 +2636,16 @@ var StreamsCrdt = class StreamsCrdt {
2506
2636
  }
2507
2637
  composeSnapshotDecoder(payloadProtection, decompressSnapshot) {
2508
2638
  if (payloadProtection == null && decompressSnapshot == null) return;
2509
- return async (snapshot) => {
2510
- const clearSnapshot = payloadProtection == null ? snapshot : await payloadProtection.decodeSnapshot(snapshot);
2639
+ return async (snapshot, continuationOffset) => {
2640
+ const clearSnapshot = payloadProtection == null ? snapshot : await payloadProtection.decodeSnapshot(snapshot, continuationOffset);
2511
2641
  return decompressSnapshot == null ? clearSnapshot : await decompressSnapshot(clearSnapshot);
2512
2642
  };
2513
2643
  }
2514
2644
  composeSnapshotEncoder(payloadProtection, compressSnapshot) {
2515
2645
  if (payloadProtection == null && compressSnapshot == null) return;
2516
- return async (snapshot) => {
2646
+ return async (snapshot, continuationOffset) => {
2517
2647
  const compressedSnapshot = compressSnapshot == null ? snapshot : await compressSnapshot(snapshot);
2518
- return payloadProtection == null ? compressedSnapshot : await payloadProtection.encodeSnapshot(compressedSnapshot);
2648
+ return payloadProtection == null ? compressedSnapshot : await payloadProtection.encodeSnapshot(compressedSnapshot, continuationOffset);
2519
2649
  };
2520
2650
  }
2521
2651
  /**
@@ -2624,7 +2754,7 @@ var StreamsCrdt = class StreamsCrdt {
2624
2754
  await this.adapter.flushLocalExports?.();
2625
2755
  const cursor = await this.syncOnce("sync");
2626
2756
  const rawSnapshot = this.adapter.exportSnapshot();
2627
- const snapshot = this.encodeSnapshot == null ? rawSnapshot : await this.encodeSnapshot(rawSnapshot);
2757
+ const snapshot = this.encodeSnapshot == null ? rawSnapshot : await this.encodeSnapshot(rawSnapshot, cursor.nextOffset);
2628
2758
  await this.client.putSnapshotAt(cursor.nextOffset, snapshot);
2629
2759
  return {
2630
2760
  ok: true,
@@ -3150,7 +3280,7 @@ var StreamsCrdt = class StreamsCrdt {
3150
3280
  async bootstrapState(cursor, options = {}) {
3151
3281
  const cursorManager = options.cursorManager ?? this.cursorManager;
3152
3282
  const result = await this.client.bootstrap(options.signal, options.timeoutMode);
3153
- let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset, result.upToDate);
3283
+ let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset, result.upToDate, result.snapshotOffset);
3154
3284
  let bootstrapCursor = result.streamCursor;
3155
3285
  if (!result.upToDate) {
3156
3286
  const caughtUp = await this.catchupOrGone(saved, {
@@ -4071,4 +4201,4 @@ function isValidRillId(id) {
4071
4201
  //#endregion
4072
4202
  export { createInitialRemoteCursor as a, PayloadProtectionError as c, IndexedDbRemoteCursorStore as i, StreamsCrdt as n, EphemeralStreamCrdt as o, InMemoryRemoteCursorStore as r, LocalAppendFailedError as s, isValidRillId as t };
4073
4203
 
4074
- //# sourceMappingURL=stream-id-BygPTNvQ.js.map
4204
+ //# sourceMappingURL=stream-id-DwQujw_H.js.map