@loro-dev/streams-crdt 0.11.0 → 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.
- package/README.md +55 -14
- package/dist/flock.d.ts +1 -1
- package/dist/flock.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/loro.d.ts +1 -1
- package/dist/loro.js +1 -1
- package/dist/{stream-id-DAAMJlvT.d.ts → stream-id-DJYQlTE2.d.ts} +68 -3
- package/dist/{stream-id-fOF9ZqCH.js → stream-id-DVVP3k7V.js} +186 -51
- package/dist/stream-id-DVVP3k7V.js.map +1 -0
- package/package.json +2 -2
- package/dist/stream-id-fOF9ZqCH.js.map +0 -1
|
@@ -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
|
-
|
|
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({
|
|
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(
|
|
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(
|
|
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
|
-
|
|
2770
|
-
|
|
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
|
-
|
|
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
|
-
}, {
|
|
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
|
-
}, {
|
|
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, {
|
|
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.
|
|
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-
|
|
3682
|
+
//# sourceMappingURL=stream-id-DVVP3k7V.js.map
|