@loro-dev/streams-crdt 0.11.1 → 0.12.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.
@@ -135,6 +135,8 @@ const DEFAULT_RECONNECT_CONFIG = {
135
135
  jitterFraction: .2,
136
136
  maxAttempts: Infinity,
137
137
  connectTimeoutMs: 1e4,
138
+ initialSyncSlowAfterMs: 3e4,
139
+ initialSyncHardTimeoutMs: 12e4,
138
140
  pollTimeoutMs: 3e4,
139
141
  liveInactivityTimeoutMs: 45e3
140
142
  };
@@ -503,6 +505,17 @@ var TimeoutError = class extends Error {
503
505
  this.timeoutMs = timeoutMs;
504
506
  }
505
507
  };
508
+ var InitialSyncTimeoutError = class extends Error {
509
+ operation;
510
+ timeoutMs;
511
+ elapsedMs;
512
+ constructor(operation, timeoutMs, elapsedMs) {
513
+ super(`initial ${operation} did not complete within ${timeoutMs}ms; the in-flight bootstrap/catch-up request was aborted at the hard timeout`);
514
+ this.operation = operation;
515
+ this.timeoutMs = timeoutMs;
516
+ this.elapsedMs = elapsedMs;
517
+ }
518
+ };
506
519
  var ProtocolError = class extends Error {
507
520
  status;
508
521
  constructor(message, status) {
@@ -673,6 +686,12 @@ function classifyFetchError(err) {
673
686
  retriable: true,
674
687
  atMs: Date.now()
675
688
  };
689
+ if (err instanceof InitialSyncTimeoutError) return {
690
+ code: "initial_sync_timeout",
691
+ message,
692
+ retriable: true,
693
+ atMs: Date.now()
694
+ };
676
695
  if (err instanceof TypeError) return {
677
696
  code: "network",
678
697
  message,
@@ -748,6 +767,14 @@ function toTransportError(error) {
748
767
  phase: error.timeoutType,
749
768
  message: error.message
750
769
  };
770
+ if (error instanceof InitialSyncTimeoutError) return {
771
+ code: "initial_sync_timeout",
772
+ retryable: true,
773
+ operation: error.operation,
774
+ timeoutMs: error.timeoutMs,
775
+ elapsedMs: error.elapsedMs,
776
+ message: error.message
777
+ };
751
778
  if (error instanceof HttpError && error.status === 410) return {
752
779
  code: "gone",
753
780
  retryable: true,
@@ -903,9 +930,10 @@ function normalizeShardUrlsOptions(options) {
903
930
  bootstrap: normalizeShardOrigins(options.bootstrap, "shardUrls.bootstrap"),
904
931
  catchup: normalizeShardOrigins(options.catchup, "shardUrls.catchup"),
905
932
  largePost: normalizeShardOrigins(options.largePost, "shardUrls.largePost"),
933
+ other: normalizeShardOrigins(options.other, "shardUrls.other"),
906
934
  largePostMinBytes
907
935
  };
908
- if (normalized.bootstrap == null && normalized.catchup == null && normalized.largePost == null) return;
936
+ if (normalized.bootstrap == null && normalized.catchup == null && normalized.largePost == null && normalized.other == null) return;
909
937
  return normalized;
910
938
  }
911
939
  function requestBodyByteLength(body) {
@@ -940,7 +968,9 @@ function normalizedStreamPath(streamUrl) {
940
968
  var StreamsTransportClient = class {
941
969
  streamUrl;
942
970
  adapter;
971
+ streamTtlSeconds;
943
972
  client;
973
+ initialSyncClient;
944
974
  fetchImpl;
945
975
  authProvider;
946
976
  reconnectConfig;
@@ -950,6 +980,7 @@ var StreamsTransportClient = class {
950
980
  constructor(options) {
951
981
  this.streamUrl = options.streamUrl;
952
982
  this.adapter = options.adapter;
983
+ this.streamTtlSeconds = normalizeStreamTtlSeconds(options.streamTtlSeconds);
953
984
  this.fetchImpl = options.fetchImpl;
954
985
  this.shardUrls = normalizeShardUrlsOptions(options.shardUrls);
955
986
  this.client = new StreamsClient({
@@ -962,6 +993,16 @@ var StreamsTransportClient = class {
962
993
  pollTimeoutMs: options.reconnectConfig.pollTimeoutMs
963
994
  }
964
995
  });
996
+ this.initialSyncClient = new StreamsClient({
997
+ url: options.streamUrl,
998
+ auth: options.authProvider,
999
+ fetch: this.fetchRouted.bind(this),
1000
+ retry: { maxAttempts: 0 },
1001
+ timeout: {
1002
+ connectTimeoutMs: 0,
1003
+ pollTimeoutMs: options.reconnectConfig.pollTimeoutMs
1004
+ }
1005
+ });
965
1006
  this.authProvider = options.authProvider;
966
1007
  this.reconnectConfig = options.reconnectConfig;
967
1008
  }
@@ -972,17 +1013,20 @@ var StreamsTransportClient = class {
972
1013
  const shardUrls = this.shardUrls;
973
1014
  if (shardUrls == null) return input;
974
1015
  const operation = this.classifyShardOperation(input, init, shardUrls);
975
- if (operation == null) return input;
976
- const origins = operation === "bootstrap" ? shardUrls.bootstrap : operation === "catchup" ? shardUrls.catchup : shardUrls.largePost;
1016
+ const origins = this.originsForShardOperation(operation, shardUrls);
977
1017
  if (origins == null || origins.length === 0) return input;
978
1018
  const target = new URL(input);
979
- const shardOrigin = new URL(this.nextShardOrigin(operation, origins));
1019
+ const shardOrigin = new URL(this.nextShardOrigin(operation ?? "other", origins));
980
1020
  target.protocol = shardOrigin.protocol;
981
1021
  target.host = shardOrigin.host;
982
1022
  target.username = "";
983
1023
  target.password = "";
984
1024
  return target.toString();
985
1025
  }
1026
+ originsForShardOperation(operation, shardUrls) {
1027
+ if (operation == null) return shardUrls.other;
1028
+ return (operation === "bootstrap" ? shardUrls.bootstrap : operation === "catchup" ? shardUrls.catchup : shardUrls.largePost) ?? shardUrls.other;
1029
+ }
986
1030
  nextShardOrigin(operation, origins) {
987
1031
  const currentIndex = this.shardCursorIndexes[operation] ?? createRandomShardStart(origins.length);
988
1032
  this.shardCursorIndexes[operation] = (currentIndex + 1) % origins.length;
@@ -1002,7 +1046,10 @@ var StreamsTransportClient = class {
1002
1046
  }
1003
1047
  async createStreamRequest() {
1004
1048
  const adapterHeaders = this.adapter.getCreateStreamHeaders ? await this.adapter.getCreateStreamHeaders() : void 0;
1005
- const result = await this.client.create({ headers: adapterHeaders });
1049
+ const result = await this.client.create({
1050
+ headers: adapterHeaders,
1051
+ ttlSeconds: this.streamTtlSeconds
1052
+ });
1006
1053
  if (result.ok) return result.result.created;
1007
1054
  throw this.toTransportFailure(result.result);
1008
1055
  }
@@ -1014,8 +1061,8 @@ var StreamsTransportClient = class {
1014
1061
  async readFromOffsetSseItems(offset, cursor, onControl, onOpen, signal, onActivity) {
1015
1062
  return this.readItemsSse(offset, cursor, onControl, onOpen, signal, onActivity);
1016
1063
  }
1017
- async readFromOffset(offset, cursor, live, signal) {
1018
- const result = await this.client.readOnce({
1064
+ async readFromOffset(offset, cursor, live, signal, timeoutMode = "default") {
1065
+ const result = await (live == null && timeoutMode === "initial" ? this.initialSyncClient : this.client).readOnce({
1019
1066
  offset,
1020
1067
  cursor,
1021
1068
  live,
@@ -1036,8 +1083,8 @@ var StreamsTransportClient = class {
1036
1083
  }
1037
1084
  };
1038
1085
  }
1039
- async bootstrap() {
1040
- const result = await this.client.bootstrap();
1086
+ async bootstrap(signal, timeoutMode = "default") {
1087
+ const result = await (timeoutMode === "initial" ? this.initialSyncClient : this.client).bootstrap({ signal });
1041
1088
  if (!result.ok) throw this.toTransportFailure(result.result);
1042
1089
  const snapshot = result.result.snapshotOffset === "-1" ? new Uint8Array() : result.result.snapshot?.body ?? new Uint8Array();
1043
1090
  return {
@@ -1178,15 +1225,16 @@ var StreamsTransportClient = class {
1178
1225
  }
1179
1226
  }
1180
1227
  async fetchAuthorized(input, init, timeoutMs, phase) {
1228
+ const requestUrl = this.routeRequestUrl(input, init);
1181
1229
  const firstToken = await this.resolveToken({ reason: "request" });
1182
- const firstResponse = await this.fetchWithTimeout(input, {
1230
+ const firstResponse = await this.fetchWithTimeout(requestUrl, {
1183
1231
  ...init,
1184
1232
  headers: this.attachAuthorization(init.headers, firstToken)
1185
1233
  }, timeoutMs, phase);
1186
1234
  if (this.authProvider == null || firstResponse.status !== 401 && firstResponse.status !== 403) return firstResponse;
1187
1235
  await firstResponse.body?.cancel().catch(() => {});
1188
1236
  const refreshedToken = await this.refreshTokenAfterFailure(firstResponse.status, firstToken);
1189
- return this.fetchWithTimeout(input, {
1237
+ return this.fetchWithTimeout(requestUrl, {
1190
1238
  ...init,
1191
1239
  headers: this.attachAuthorization(init.headers, refreshedToken)
1192
1240
  }, timeoutMs, phase);
@@ -1375,6 +1423,11 @@ var StreamsTransportClient = class {
1375
1423
  };
1376
1424
  }
1377
1425
  };
1426
+ function normalizeStreamTtlSeconds(value) {
1427
+ if (value == null) return;
1428
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error("streamTtlSeconds must be a non-negative safe integer");
1429
+ return value;
1430
+ }
1378
1431
  const REQUEST_ID_HEADER_NAMES = [
1379
1432
  "x-request-id",
1380
1433
  "x-amzn-trace-id",
@@ -1466,7 +1519,8 @@ var EphemeralStreamCrdt = class {
1466
1519
  adapter: createNoopAdapter(),
1467
1520
  authProvider: normalizeAuthProvider(options.auth),
1468
1521
  fetchImpl: options.fetch ?? globalThis.fetch.bind(globalThis),
1469
- reconnectConfig: this.reconnectConfig
1522
+ reconnectConfig: this.reconnectConfig,
1523
+ shardUrls: options.shardUrls
1470
1524
  });
1471
1525
  }
1472
1526
  async join(params) {
@@ -2295,6 +2349,7 @@ var StreamsCrdt = class StreamsCrdt {
2295
2349
  this.client = new StreamsTransportClient({
2296
2350
  streamUrl: this.streamUrl,
2297
2351
  adapter: this.adapter,
2352
+ streamTtlSeconds: options.streamTtlSeconds,
2298
2353
  authProvider,
2299
2354
  fetchImpl,
2300
2355
  reconnectConfig: this.reconnectConfig,
@@ -2417,7 +2472,7 @@ var StreamsCrdt = class StreamsCrdt {
2417
2472
  async uploadSnapshotForTesting() {
2418
2473
  try {
2419
2474
  await this.adapter.flushLocalExports?.();
2420
- const cursor = await this.syncOnce();
2475
+ const cursor = await this.syncOnce("sync");
2421
2476
  const rawSnapshot = this.adapter.exportSnapshot();
2422
2477
  const snapshot = this.encodeSnapshot == null ? rawSnapshot : await this.encodeSnapshot(rawSnapshot);
2423
2478
  await this.client.putSnapshotAt(cursor.nextOffset, snapshot);
@@ -2485,7 +2540,7 @@ var StreamsCrdt = class StreamsCrdt {
2485
2540
  this.disableWriteOnlyMode("appendWriteOnly cannot be used after sync() on the same transport");
2486
2541
  return {
2487
2542
  ok: true,
2488
- value: { cursor: await this.syncOnce() }
2543
+ value: { cursor: await this.syncOnce("sync") }
2489
2544
  };
2490
2545
  } catch (error) {
2491
2546
  return {
@@ -2509,11 +2564,11 @@ var StreamsCrdt = class StreamsCrdt {
2509
2564
  const state = this.joinState;
2510
2565
  if (state != null && !state.closed) return {
2511
2566
  ok: true,
2512
- value: { cursor: await this.catchupJoinState(state, params) }
2567
+ value: { cursor: await this.catchupJoinState(state, params, state.requestController?.signal) }
2513
2568
  };
2514
2569
  return {
2515
2570
  ok: true,
2516
- value: { cursor: (await this.resolvePullOnlyRemoteState()).cursor }
2571
+ value: { cursor: (await this.resolvePullOnlyRemoteState("catchup")).cursor }
2517
2572
  };
2518
2573
  } catch (error) {
2519
2574
  return {
@@ -2692,10 +2747,46 @@ var StreamsCrdt = class StreamsCrdt {
2692
2747
  }
2693
2748
  });
2694
2749
  }
2750
+ async runInitialSyncWithDeadline(operation, task, parentSignal) {
2751
+ const startedAtMs = Date.now();
2752
+ const hardController = new AbortController();
2753
+ const signal = mergeAbortSignals(parentSignal, hardController.signal);
2754
+ const slowAfterMs = this.reconnectConfig.initialSyncSlowAfterMs;
2755
+ const hardTimeoutMs = this.reconnectConfig.initialSyncHardTimeoutMs;
2756
+ let slowTimer;
2757
+ let hardTimer;
2758
+ let hardTimeoutError;
2759
+ let hardTimeoutPromise;
2760
+ if (typeof slowAfterMs === "number" && Number.isFinite(slowAfterMs) && slowAfterMs > 0) slowTimer = setTimeout(() => {
2761
+ this.logDebug("initial sync still pending", {
2762
+ streamUrl: this.streamUrl,
2763
+ operation,
2764
+ elapsedMs: Date.now() - startedAtMs,
2765
+ slowAfterMs
2766
+ });
2767
+ }, slowAfterMs);
2768
+ if (typeof hardTimeoutMs === "number" && Number.isFinite(hardTimeoutMs) && hardTimeoutMs > 0) hardTimeoutPromise = new Promise((_, reject) => {
2769
+ hardTimer = setTimeout(() => {
2770
+ hardTimeoutError = new InitialSyncTimeoutError(operation, hardTimeoutMs, Date.now() - startedAtMs);
2771
+ hardController.abort(hardTimeoutError);
2772
+ reject(hardTimeoutError);
2773
+ }, hardTimeoutMs);
2774
+ });
2775
+ try {
2776
+ const taskPromise = task(signal);
2777
+ return hardTimeoutPromise == null ? await taskPromise : await Promise.race([taskPromise, hardTimeoutPromise]);
2778
+ } catch (error) {
2779
+ if (hardTimeoutError != null) throw hardTimeoutError;
2780
+ throw error;
2781
+ } finally {
2782
+ if (slowTimer != null) clearTimeout(slowTimer);
2783
+ if (hardTimer != null) clearTimeout(hardTimer);
2784
+ }
2785
+ }
2695
2786
  async startJoin(state) {
2696
2787
  try {
2697
2788
  state.requestController ??= new AbortController();
2698
- const joined = await this.performInitialJoinSync(state.liveMode);
2789
+ const joined = await this.performInitialJoinSync(state.liveMode, state.requestController?.signal);
2699
2790
  state.cursor = joined.cursor;
2700
2791
  state.streamCursor = joined.streamCursor;
2701
2792
  state.liveMode = joined.liveMode;
@@ -2727,14 +2818,14 @@ var StreamsCrdt = class StreamsCrdt {
2727
2818
  throw error;
2728
2819
  }
2729
2820
  }
2730
- async syncOnce() {
2731
- let cursor = (await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState())).cursor;
2821
+ async syncOnce(operation, parentSignal) {
2822
+ let cursor = (await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState(operation, parentSignal))).cursor;
2732
2823
  const localBatch = this.exportUpdates(cursor.serverLowerBoundVersion);
2733
2824
  if (localBatch.batch != null) cursor = (await this.appendLocalBatchDurably(cursor, localBatch.batch)).cursor;
2734
2825
  return cursor;
2735
2826
  }
2736
- async performInitialJoinSync(preferredLiveMode) {
2737
- const initial = await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState());
2827
+ async performInitialJoinSync(preferredLiveMode, parentSignal) {
2828
+ const initial = await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState("join", parentSignal));
2738
2829
  let cursor = initial.cursor;
2739
2830
  let localExportRefVersion = cursor.serverLowerBoundVersion;
2740
2831
  let streamCursor = initial.streamCursor;
@@ -2761,33 +2852,51 @@ var StreamsCrdt = class StreamsCrdt {
2761
2852
  serverLowerBoundVersion: this.adapter.emptyVersion()
2762
2853
  });
2763
2854
  }
2764
- async resolveInitialRemoteState() {
2855
+ async resolveInitialRemoteState(operation, parentSignal) {
2765
2856
  const loaded = await this.remoteCursorStore.load(this.streamUrl);
2766
2857
  const cursor = loaded ?? this.createInitialCursor();
2858
+ const runInitial = (task) => this.runInitialSyncWithDeadline(operation, task, parentSignal);
2767
2859
  try {
2768
- if (loaded == null) return await this.bootstrapState(cursor);
2769
- const caughtUp = await this.catchupOrGone(cursor, { goneStrategy: "return-gone" });
2770
- if ("kind" in caughtUp) return await this.recoverFromGoneWithLocalBootstrap(cursor);
2860
+ if (loaded == null) return await runInitial((signal) => this.bootstrapState(cursor, {
2861
+ signal,
2862
+ timeoutMode: "initial"
2863
+ }));
2864
+ const caughtUp = await runInitial((signal) => this.catchupOrGone(cursor, {
2865
+ goneStrategy: "return-gone",
2866
+ signal,
2867
+ timeoutMode: "initial"
2868
+ }));
2869
+ if ("kind" in caughtUp) return await this.recoverFromGoneWithLocalBootstrap(cursor, operation, parentSignal);
2771
2870
  return caughtUp;
2772
2871
  } catch (error) {
2773
2872
  if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) throw error;
2774
2873
  await this.ensureStreamExists();
2775
- return await this.bootstrapState(this.createInitialCursor());
2874
+ return await runInitial((signal) => this.bootstrapState(this.createInitialCursor(), {
2875
+ signal,
2876
+ timeoutMode: "initial"
2877
+ }));
2776
2878
  }
2777
2879
  }
2778
- async resolvePullOnlyRemoteState() {
2880
+ async resolvePullOnlyRemoteState(operation, parentSignal) {
2779
2881
  const loaded = await this.remoteCursorStore.load(this.streamUrl);
2780
2882
  const cursor = loaded ?? this.createInitialCursor();
2883
+ const runInitial = (task) => this.runInitialSyncWithDeadline(operation, task, parentSignal);
2781
2884
  try {
2782
- if (loaded == null) return await this.bootstrapState(cursor);
2783
- return await this.catchupFromCursor(cursor);
2885
+ if (loaded == null) return await runInitial((signal) => this.bootstrapState(cursor, {
2886
+ signal,
2887
+ timeoutMode: "initial"
2888
+ }));
2889
+ return await runInitial((signal) => this.catchupFromCursor(cursor, void 0, void 0, void 0, signal, "initial"));
2784
2890
  } catch (error) {
2785
2891
  if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) throw error;
2786
2892
  await this.ensureStreamExists();
2787
- return await this.bootstrapState(this.createInitialCursor());
2893
+ return await runInitial((signal) => this.bootstrapState(this.createInitialCursor(), {
2894
+ signal,
2895
+ timeoutMode: "initial"
2896
+ }));
2788
2897
  }
2789
2898
  }
2790
- async catchupJoinState(state, params) {
2899
+ async catchupJoinState(state, params, parentSignal) {
2791
2900
  if (state.readSubStatus === "connecting") {
2792
2901
  await state.started;
2793
2902
  return state.cursor;
@@ -2795,9 +2904,9 @@ var StreamsCrdt = class StreamsCrdt {
2795
2904
  if (state.readSubStatus === "ok" && params.force !== true) return state.cursor;
2796
2905
  const cursor = await this.enqueueExclusive(async () => {
2797
2906
  if (state.closed) throw new Error("subscription closed");
2798
- const caughtUp = await this.catchupFromCursor(state.cursor, void 0, state.streamCursor, (previousVersion, nextVersion) => {
2907
+ const caughtUp = await this.runInitialSyncWithDeadline("catchup", (signal) => this.catchupFromCursor(state.cursor, void 0, state.streamCursor, (previousVersion, nextVersion) => {
2799
2908
  this.snapshotManager.observeRemoteVersion(state, previousVersion, nextVersion);
2800
- });
2909
+ }, signal, "initial"), parentSignal);
2801
2910
  state.cursor = caughtUp.cursor;
2802
2911
  state.streamCursor = caughtUp.streamCursor;
2803
2912
  return state.cursor;
@@ -2811,13 +2920,15 @@ var StreamsCrdt = class StreamsCrdt {
2811
2920
  }
2812
2921
  async bootstrapState(cursor, options = {}) {
2813
2922
  const cursorManager = options.cursorManager ?? this.cursorManager;
2814
- const result = await this.client.bootstrap();
2923
+ const result = await this.client.bootstrap(options.signal, options.timeoutMode);
2815
2924
  let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset, result.upToDate);
2816
2925
  let bootstrapCursor = result.streamCursor;
2817
2926
  if (!result.upToDate) {
2818
2927
  const caughtUp = await this.catchupOrGone(saved, {
2819
2928
  streamCursor: bootstrapCursor,
2820
- cursorManager
2929
+ cursorManager,
2930
+ signal: options.signal,
2931
+ timeoutMode: options.timeoutMode
2821
2932
  });
2822
2933
  if ("kind" in caughtUp) throw new Error("bootstrap catch-up unexpectedly returned gone");
2823
2934
  saved = caughtUp.cursor;
@@ -2828,11 +2939,13 @@ var StreamsCrdt = class StreamsCrdt {
2828
2939
  streamCursor: bootstrapCursor
2829
2940
  };
2830
2941
  }
2831
- async catchupFromCursor(cursor, live, streamCursor, onRemoteVersionApplied) {
2942
+ async catchupFromCursor(cursor, live, streamCursor, onRemoteVersionApplied, signal, timeoutMode) {
2832
2943
  const result = await this.catchupOrGone(cursor, {
2833
2944
  live,
2834
2945
  streamCursor,
2835
- onRemoteVersionApplied
2946
+ onRemoteVersionApplied,
2947
+ signal,
2948
+ timeoutMode
2836
2949
  });
2837
2950
  if ("kind" in result) throw new Error("catchup unexpectedly returned gone");
2838
2951
  return result;
@@ -2844,14 +2957,18 @@ var StreamsCrdt = class StreamsCrdt {
2844
2957
  const cursorManager = options.cursorManager ?? this.cursorManager;
2845
2958
  while (true) {
2846
2959
  const requestOffset = current.nextOffset;
2847
- const response = await this.client.readFromOffset(requestOffset, currentStreamCursor, options.live);
2960
+ const response = await this.client.readFromOffset(requestOffset, currentStreamCursor, options.live, options.signal, options.timeoutMode);
2848
2961
  if (response.kind === "gone") {
2849
2962
  if (goneStrategy === "return-gone") return StreamsCrdt.GONE_CATCHUP;
2850
2963
  return await this.bootstrapState({
2851
2964
  ...current,
2852
2965
  nextOffset: "-1",
2853
2966
  serverLowerBoundVersion: this.adapter.emptyVersion()
2854
- }, { cursorManager });
2967
+ }, {
2968
+ cursorManager,
2969
+ signal: options.signal,
2970
+ timeoutMode: options.timeoutMode
2971
+ });
2855
2972
  }
2856
2973
  const result = response.value;
2857
2974
  if (isReadNoProgress(requestOffset, result)) throw new Error(`catch-up made no progress at offset ${requestOffset}; server returned empty updates and unchanged next offset with up_to_date=false`);
@@ -2862,7 +2979,11 @@ var StreamsCrdt = class StreamsCrdt {
2862
2979
  ...current,
2863
2980
  nextOffset: "-1",
2864
2981
  serverLowerBoundVersion: this.adapter.emptyVersion()
2865
- }, { cursorManager });
2982
+ }, {
2983
+ cursorManager,
2984
+ signal: options.signal,
2985
+ timeoutMode: options.timeoutMode
2986
+ });
2866
2987
  throw error;
2867
2988
  }
2868
2989
  currentStreamCursor = result.streamCursor;
@@ -2878,7 +2999,7 @@ var StreamsCrdt = class StreamsCrdt {
2878
2999
  ...state.cursor,
2879
3000
  nextOffset: "-1",
2880
3001
  serverLowerBoundVersion: this.adapter.emptyVersion()
2881
- });
3002
+ }, { signal: state.requestController?.signal });
2882
3003
  state.cursor = bootstrapped.cursor;
2883
3004
  state.streamCursor = bootstrapped.streamCursor;
2884
3005
  state.retryAttempt = 0;
@@ -3080,8 +3201,12 @@ var StreamsCrdt = class StreamsCrdt {
3080
3201
  * exports and appends any local delta, then re-bootstraps to converge
3081
3202
  * persisted cursor and local CRDT state on server truth.
3082
3203
  */
3083
- async inferRemoteAndAppendLocal(bootstrapCursor, cursorManager) {
3084
- const inferredRemote = await this.bootstrapState(bootstrapCursor, { cursorManager });
3204
+ async inferRemoteAndAppendLocal(bootstrapCursor, cursorManager, operation, parentSignal) {
3205
+ const inferredRemote = await this.runInitialSyncWithDeadline(operation, (signal) => this.bootstrapState(bootstrapCursor, {
3206
+ cursorManager,
3207
+ signal,
3208
+ timeoutMode: "initial"
3209
+ }), parentSignal);
3085
3210
  let pendingAppend;
3086
3211
  if (this.frozenLocalAppend != null) {
3087
3212
  if (this.frozenLocalAppend.pendingCount != null) throw new Error("pending local append must be finalized by pending flush");
@@ -3090,22 +3215,29 @@ var StreamsCrdt = class StreamsCrdt {
3090
3215
  const localBatch = this.exportUpdates(inferredRemote.cursor.serverLowerBoundVersion);
3091
3216
  if (localBatch.batch != null) pendingAppend = await this.appendLocalBatchRemote(inferredRemote.cursor, localBatch.batch);
3092
3217
  }
3093
- const result = await this.bootstrapState(bootstrapCursor);
3218
+ const result = await this.runInitialSyncWithDeadline(operation, (signal) => this.bootstrapState(bootstrapCursor, {
3219
+ signal,
3220
+ timeoutMode: "initial"
3221
+ }), parentSignal);
3094
3222
  if (pendingAppend != null) this.commitProducerAck(pendingAppend.producerAck);
3095
3223
  return result;
3096
3224
  }
3097
- async recoverFromGoneWithLocalBootstrap(cursor) {
3225
+ async recoverFromGoneWithLocalBootstrap(cursor, operation, parentSignal) {
3098
3226
  const isolatedCursorManager = await this.createIsolatedCursorManager();
3099
3227
  const bootstrapCursor = {
3100
3228
  ...cursor,
3101
3229
  nextOffset: "-1",
3102
3230
  serverLowerBoundVersion: this.adapter.emptyVersion()
3103
3231
  };
3104
- if (isolatedCursorManager != null) return await this.inferRemoteAndAppendLocal(bootstrapCursor, isolatedCursorManager);
3232
+ if (isolatedCursorManager != null) return await this.inferRemoteAndAppendLocal(bootstrapCursor, isolatedCursorManager, operation, parentSignal);
3105
3233
  const preservedLocalSnapshot = this.adapter.exportSnapshot();
3106
3234
  let restoredLocalSnapshot = false;
3107
3235
  try {
3108
- const inferredRemote = await this.bootstrapState(bootstrapCursor, { cursorManager: this.volatileCursorManager });
3236
+ const inferredRemote = await this.runInitialSyncWithDeadline(operation, (signal) => this.bootstrapState(bootstrapCursor, {
3237
+ cursorManager: this.volatileCursorManager,
3238
+ signal,
3239
+ timeoutMode: "initial"
3240
+ }), parentSignal);
3109
3241
  await this.adapter.applySnapshot(preservedLocalSnapshot);
3110
3242
  restoredLocalSnapshot = true;
3111
3243
  let pendingAppend;
@@ -3116,7 +3248,10 @@ var StreamsCrdt = class StreamsCrdt {
3116
3248
  const localBatch = this.exportUpdates(inferredRemote.cursor.serverLowerBoundVersion);
3117
3249
  if (localBatch.batch != null) pendingAppend = await this.appendLocalBatchRemote(inferredRemote.cursor, localBatch.batch);
3118
3250
  }
3119
- const result = await this.bootstrapState(bootstrapCursor);
3251
+ const result = await this.runInitialSyncWithDeadline(operation, (signal) => this.bootstrapState(bootstrapCursor, {
3252
+ signal,
3253
+ timeoutMode: "initial"
3254
+ }), parentSignal);
3120
3255
  if (pendingAppend != null) this.commitProducerAck(pendingAppend.producerAck);
3121
3256
  return result;
3122
3257
  } catch (error) {
@@ -3153,7 +3288,7 @@ var StreamsCrdt = class StreamsCrdt {
3153
3288
  ...state.cursor,
3154
3289
  nextOffset: "-1",
3155
3290
  serverLowerBoundVersion: this.adapter.emptyVersion()
3156
- });
3291
+ }, { signal: capturedController?.signal });
3157
3292
  state.cursor = bootstrapped.cursor;
3158
3293
  state.streamCursor = bootstrapped.streamCursor;
3159
3294
  state.retryAttempt = 0;
@@ -3246,7 +3381,7 @@ var StreamsCrdt = class StreamsCrdt {
3246
3381
  ...state.cursor,
3247
3382
  nextOffset: "-1",
3248
3383
  serverLowerBoundVersion: this.adapter.emptyVersion()
3249
- });
3384
+ }, { signal: capturedController?.signal });
3250
3385
  state.cursor = bootstrapped.cursor;
3251
3386
  state.streamCursor = bootstrapped.streamCursor;
3252
3387
  state.retryAttempt = 0;
@@ -3544,4 +3679,4 @@ function createStreamUrl(input) {
3544
3679
  //#endregion
3545
3680
  export { InMemoryRemoteCursorStore as a, EphemeralStreamCrdt as c, StreamsCrdt as i, PayloadProtectionError as l, isValidBucketId as n, IndexedDbRemoteCursorStore as o, isValidRillId as r, createInitialRemoteCursor as s, createStreamUrl as t };
3546
3681
 
3547
- //# sourceMappingURL=stream-id-fOF9ZqCH.js.map
3682
+ //# sourceMappingURL=stream-id-DVVP3k7V.js.map