@loro-dev/streams-crdt 0.16.0 → 0.16.2
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 +24 -4
- package/dist/flock.d.ts +2 -2
- package/dist/flock.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/loro.d.ts +2 -2
- package/dist/loro.js +1 -1
- package/dist/{stream-id-BEEukuNT.d.ts → shared-C5Dyx541.d.ts} +35 -10
- package/dist/{stream-id-DwQujw_H.js → stream-id-Dlnc1PNH.js} +376 -102
- package/dist/stream-id-Dlnc1PNH.js.map +1 -0
- package/package.json +2 -2
- package/dist/stream-id-DwQujw_H.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StreamsClient, decodeSsePayload, readSseEvents } from "@loro-dev/streams-client";
|
|
1
|
+
import { StreamsClient, decodeSsePayload, readSseEvents, thrownToStreamError } from "@loro-dev/streams-client";
|
|
2
2
|
//#region src/deque.ts
|
|
3
3
|
const DEFAULT_COMPACT_THRESHOLD = 64;
|
|
4
4
|
const DEFAULT_MIN_CAPACITY = 16;
|
|
@@ -171,6 +171,51 @@ function computeRetryDelay(attempt, config = DEFAULT_RECONNECT_CONFIG, minDelayM
|
|
|
171
171
|
return applyJitter(typeof minDelayMs === "number" && minDelayMs > scheduled ? minDelayMs : scheduled, jitterFraction);
|
|
172
172
|
}
|
|
173
173
|
//#endregion
|
|
174
|
+
//#region src/error-context.ts
|
|
175
|
+
const contexts = /* @__PURE__ */ new WeakMap();
|
|
176
|
+
var ThrownValue = class extends Error {
|
|
177
|
+
constructor(original) {
|
|
178
|
+
super("Local operation failed");
|
|
179
|
+
this.original = original;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function errorContext(error) {
|
|
183
|
+
return typeof error === "object" && error != null ? contexts.get(error) : void 0;
|
|
184
|
+
}
|
|
185
|
+
function originalThrown(error) {
|
|
186
|
+
return error instanceof ThrownValue ? error.original : error;
|
|
187
|
+
}
|
|
188
|
+
function recordErrorContext(error, context, replace = false) {
|
|
189
|
+
const tagged = typeof error === "object" && error != null ? error : new ThrownValue(error);
|
|
190
|
+
contexts.set(tagged, replace ? context : {
|
|
191
|
+
...context,
|
|
192
|
+
...contexts.get(tagged)
|
|
193
|
+
});
|
|
194
|
+
return tagged;
|
|
195
|
+
}
|
|
196
|
+
async function atErrorBoundary(context, task) {
|
|
197
|
+
const startedAt = Date.now();
|
|
198
|
+
try {
|
|
199
|
+
return await task();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw recordErrorContext(error, {
|
|
202
|
+
...context,
|
|
203
|
+
elapsedMs: Math.max(0, Date.now() - startedAt)
|
|
204
|
+
}, context.source != null);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function atSyncErrorBoundary(context, task) {
|
|
208
|
+
const startedAt = Date.now();
|
|
209
|
+
try {
|
|
210
|
+
return task();
|
|
211
|
+
} catch (error) {
|
|
212
|
+
throw recordErrorContext(error, {
|
|
213
|
+
...context,
|
|
214
|
+
elapsedMs: Math.max(0, Date.now() - startedAt)
|
|
215
|
+
}, context.source != null);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
174
219
|
//#region src/items.ts
|
|
175
220
|
const BINARY_CONTENT_TYPE = "application/octet-stream";
|
|
176
221
|
function encodeItems(items) {
|
|
@@ -698,7 +743,7 @@ function classifyFetchError(err) {
|
|
|
698
743
|
retriable: true,
|
|
699
744
|
atMs: Date.now()
|
|
700
745
|
};
|
|
701
|
-
if (err
|
|
746
|
+
if (errorContext(err)?.originalCode === "network_error") return {
|
|
702
747
|
code: "network",
|
|
703
748
|
message,
|
|
704
749
|
retriable: true,
|
|
@@ -720,7 +765,33 @@ function classifyFetchError(err) {
|
|
|
720
765
|
function isStreamNotFoundError(error) {
|
|
721
766
|
return error instanceof HttpError && error.status === 404;
|
|
722
767
|
}
|
|
723
|
-
function toTransportError(error) {
|
|
768
|
+
function toTransportError(error, operation = "unknown") {
|
|
769
|
+
const evidence = errorContext(error);
|
|
770
|
+
const result = classifyTransportError(originalThrown(error), evidence?.originalCode === "network_error");
|
|
771
|
+
const source = isPayloadProtectionError(error) ? "payload_protection" : evidence?.source ?? (error instanceof AuthProviderError ? "auth" : error instanceof HttpError ? "http" : "unknown");
|
|
772
|
+
return {
|
|
773
|
+
...result,
|
|
774
|
+
context: {
|
|
775
|
+
stage: "unknown",
|
|
776
|
+
originalCode: result.code,
|
|
777
|
+
...error instanceof HttpError && { status: error.status },
|
|
778
|
+
...error instanceof TimeoutError && {
|
|
779
|
+
phase: error.timeoutType,
|
|
780
|
+
timeoutMs: error.timeoutMs
|
|
781
|
+
},
|
|
782
|
+
...error instanceof InitialSyncTimeoutError && {
|
|
783
|
+
stage: "initial_sync",
|
|
784
|
+
timeoutMs: error.timeoutMs,
|
|
785
|
+
elapsedMs: error.elapsedMs
|
|
786
|
+
},
|
|
787
|
+
...evidence,
|
|
788
|
+
source,
|
|
789
|
+
retryable: result.retryable,
|
|
790
|
+
operation
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function classifyTransportError(error, fromFetch) {
|
|
724
795
|
if (isCrdtApplyIncompleteError(error)) return {
|
|
725
796
|
code: "apply_incomplete",
|
|
726
797
|
retryable: true,
|
|
@@ -810,10 +881,10 @@ function toTransportError(error) {
|
|
|
810
881
|
reason: error.reason,
|
|
811
882
|
message: payloadProtectionFailureMessage(error.reason)
|
|
812
883
|
};
|
|
813
|
-
if (
|
|
884
|
+
if (fromFetch) return {
|
|
814
885
|
code: "network_error",
|
|
815
886
|
retryable: true,
|
|
816
|
-
message: error.message
|
|
887
|
+
message: error instanceof Error ? error.message : "Fetch failed"
|
|
817
888
|
};
|
|
818
889
|
if (error instanceof Error) return {
|
|
819
890
|
code: "internal_error",
|
|
@@ -1057,7 +1128,10 @@ var StreamsTransportClient = class {
|
|
|
1057
1128
|
return "large-post";
|
|
1058
1129
|
}
|
|
1059
1130
|
async createStreamRequest() {
|
|
1060
|
-
const adapterHeaders = this.adapter.getCreateStreamHeaders ? await
|
|
1131
|
+
const adapterHeaders = this.adapter.getCreateStreamHeaders ? await atErrorBoundary({
|
|
1132
|
+
source: "crdt",
|
|
1133
|
+
stage: "crdt_export"
|
|
1134
|
+
}, this.adapter.getCreateStreamHeaders.bind(this.adapter)) : void 0;
|
|
1061
1135
|
const result = await this.client.create({
|
|
1062
1136
|
headers: adapterHeaders,
|
|
1063
1137
|
ttlSeconds: this.streamTtlSeconds
|
|
@@ -1246,7 +1320,7 @@ var StreamsTransportClient = class {
|
|
|
1246
1320
|
const dataEncoding = response.headers.get("Stream-SSE-Data-Encoding") ?? void 0;
|
|
1247
1321
|
const consume = async () => {
|
|
1248
1322
|
let sawBootstrap = false;
|
|
1249
|
-
for await (const event of readSseEvents(response.body, { onActivity })) {
|
|
1323
|
+
for await (const event of this.translateSseEvents(readSseEvents(response.body, { onActivity }))) {
|
|
1250
1324
|
if (!sawBootstrap) {
|
|
1251
1325
|
if (event.event !== "bootstrap") throw new ProtocolError(`ephemeral sse must start with bootstrap, got '${event.event}'`);
|
|
1252
1326
|
await onBootstrap(decodeSsePayload(event.data, dataEncoding));
|
|
@@ -1302,10 +1376,13 @@ var StreamsTransportClient = class {
|
|
|
1302
1376
|
controller.abort(new TimeoutError(phase, timeoutMs));
|
|
1303
1377
|
}, timeoutMs);
|
|
1304
1378
|
try {
|
|
1305
|
-
return await
|
|
1379
|
+
return await atErrorBoundary({
|
|
1380
|
+
source: "fetch",
|
|
1381
|
+
originalCode: "network_error"
|
|
1382
|
+
}, () => this.fetchImpl(input, {
|
|
1306
1383
|
...init,
|
|
1307
1384
|
signal
|
|
1308
|
-
});
|
|
1385
|
+
}));
|
|
1309
1386
|
} catch (error) {
|
|
1310
1387
|
if (controller.signal.aborted && controller.signal.reason instanceof TimeoutError) throw controller.signal.reason;
|
|
1311
1388
|
throw error;
|
|
@@ -1358,7 +1435,30 @@ var StreamsTransportClient = class {
|
|
|
1358
1435
|
target.searchParams.set("live", "sse");
|
|
1359
1436
|
return target.toString();
|
|
1360
1437
|
}
|
|
1438
|
+
async *translateSseEvents(events) {
|
|
1439
|
+
try {
|
|
1440
|
+
yield* events;
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
throw this.toTransportFailure(thrownToStreamError(error, "live-sse"));
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1361
1445
|
toTransportFailure(error) {
|
|
1446
|
+
const failure = this.convertTransportFailure(error);
|
|
1447
|
+
if (!(failure instanceof InitialSyncTimeoutError)) {
|
|
1448
|
+
const { operation, ...context } = error.context ?? {
|
|
1449
|
+
source: "unknown",
|
|
1450
|
+
operation: "unknown",
|
|
1451
|
+
originalCode: error.code
|
|
1452
|
+
};
|
|
1453
|
+
recordErrorContext(failure, {
|
|
1454
|
+
...context,
|
|
1455
|
+
requestOperation: operation,
|
|
1456
|
+
stage: operation === "bootstrap" ? "bootstrap" : operation === "read-once" ? "catch_up" : operation === "append" || operation === "append-cas" ? "upload" : "unknown"
|
|
1457
|
+
}, true);
|
|
1458
|
+
}
|
|
1459
|
+
return failure;
|
|
1460
|
+
}
|
|
1461
|
+
convertTransportFailure(error) {
|
|
1362
1462
|
switch (error.code) {
|
|
1363
1463
|
case "bad_request":
|
|
1364
1464
|
case "unauthorized":
|
|
@@ -1366,13 +1466,16 @@ var StreamsTransportClient = class {
|
|
|
1366
1466
|
case "not_found":
|
|
1367
1467
|
case "conflict":
|
|
1368
1468
|
case "gone":
|
|
1369
|
-
case "rate_limited": return new HttpError(error.message, error.status, {
|
|
1370
|
-
|
|
1371
|
-
|
|
1469
|
+
case "rate_limited": return new HttpError(error.message, error.status, {
|
|
1470
|
+
body: error.bodyText,
|
|
1471
|
+
requestId: error.context?.requestId
|
|
1472
|
+
});
|
|
1473
|
+
case "timeout": return new TimeoutError(error.phase, error.context?.timeoutMs ?? (error.phase === "poll" ? this.reconnectConfig.pollTimeoutMs : this.reconnectConfig.connectTimeoutMs));
|
|
1474
|
+
case "network_error": return error.cause instanceof Error ? error.cause : new TypeError(error.message);
|
|
1372
1475
|
case "protocol_error": return new ProtocolError(error.message, error.status);
|
|
1373
1476
|
case "unknown":
|
|
1374
1477
|
if (typeof error.status === "number") return new HttpError(error.message, error.status, {
|
|
1375
|
-
requestId: extractRequestIdFromHeaders(error.headers),
|
|
1478
|
+
requestId: error.context?.requestId ?? extractRequestIdFromHeaders(error.headers),
|
|
1376
1479
|
retryAfterMs: parseRetryAfterMs(error.headers?.["retry-after"]),
|
|
1377
1480
|
body: error.bodyText
|
|
1378
1481
|
});
|
|
@@ -1441,7 +1544,7 @@ var StreamsTransportClient = class {
|
|
|
1441
1544
|
latestCursor,
|
|
1442
1545
|
paused: false
|
|
1443
1546
|
};
|
|
1444
|
-
for await (const event of session.events) {
|
|
1547
|
+
for await (const event of this.translateSseEvents(session.events)) {
|
|
1445
1548
|
if (event.event === "data") {
|
|
1446
1549
|
pendingDataEvents.push(event.data);
|
|
1447
1550
|
continue;
|
|
@@ -2036,11 +2139,17 @@ async function persistRemoteCursor(input) {
|
|
|
2036
2139
|
...input.cursor,
|
|
2037
2140
|
updatedAtMs: (input.now ?? Date.now)()
|
|
2038
2141
|
};
|
|
2039
|
-
if (input.beforeRemoteCursorSave != null) await
|
|
2142
|
+
if (input.beforeRemoteCursorSave != null) await atErrorBoundary({
|
|
2143
|
+
source: "durability_hook",
|
|
2144
|
+
stage: "durability_hook"
|
|
2145
|
+
}, input.beforeRemoteCursorSave.bind(void 0, {
|
|
2040
2146
|
cursor: next,
|
|
2041
2147
|
source: input.source
|
|
2042
|
-
});
|
|
2043
|
-
await
|
|
2148
|
+
}));
|
|
2149
|
+
await atErrorBoundary({
|
|
2150
|
+
source: "cursor_store",
|
|
2151
|
+
stage: "cursor_save"
|
|
2152
|
+
}, () => input.remoteCursorStore.save(next));
|
|
2044
2153
|
return next;
|
|
2045
2154
|
}
|
|
2046
2155
|
function key(streamUrl) {
|
|
@@ -2270,14 +2379,24 @@ var TransportCursorManager = class {
|
|
|
2270
2379
|
let unresolved = [];
|
|
2271
2380
|
if (snapshot.byteLength > 0) {
|
|
2272
2381
|
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
|
|
2382
|
+
const clearSnapshot = this.decodeSnapshot == null ? snapshot : await atErrorBoundary({
|
|
2383
|
+
source: "local",
|
|
2384
|
+
stage: "payload_decode"
|
|
2385
|
+
}, this.decodeSnapshot.bind(this, snapshot, snapshotOffset));
|
|
2386
|
+
const outcome = normalizeApplyReturn(await atErrorBoundary({
|
|
2387
|
+
source: "crdt",
|
|
2388
|
+
stage: "crdt_apply"
|
|
2389
|
+
}, () => this.adapter.applySnapshot(clearSnapshot)));
|
|
2274
2390
|
nextVersion = outcome.version;
|
|
2275
2391
|
complete = outcome.complete;
|
|
2276
2392
|
unresolved = outcome.unresolved ?? [];
|
|
2277
2393
|
}
|
|
2278
2394
|
const clearUpdates = await this.decodeRemoteUpdateItems(updates);
|
|
2279
2395
|
if (clearUpdates.length > 0) {
|
|
2280
|
-
const outcome = normalizeApplyReturn(await
|
|
2396
|
+
const outcome = normalizeApplyReturn(await atErrorBoundary({
|
|
2397
|
+
source: "crdt",
|
|
2398
|
+
stage: "crdt_apply"
|
|
2399
|
+
}, async () => this.adapter.applyRemoteUpdates(clearUpdates, nextVersion)));
|
|
2281
2400
|
nextVersion = outcome.version;
|
|
2282
2401
|
complete = outcome.complete;
|
|
2283
2402
|
unresolved = outcome.unresolved ?? [];
|
|
@@ -2305,7 +2424,10 @@ var TransportCursorManager = class {
|
|
|
2305
2424
|
let unresolved = this.incompleteCursors.get(cursor) ?? [];
|
|
2306
2425
|
const clearUpdates = await this.decodeRemoteUpdateItems(result.updates);
|
|
2307
2426
|
if (clearUpdates.length > 0) {
|
|
2308
|
-
const outcome = normalizeApplyReturn(await
|
|
2427
|
+
const outcome = normalizeApplyReturn(await atErrorBoundary({
|
|
2428
|
+
source: "crdt",
|
|
2429
|
+
stage: "crdt_apply"
|
|
2430
|
+
}, async () => this.adapter.applyRemoteUpdates(clearUpdates, nextVersion)));
|
|
2309
2431
|
nextVersion = outcome.version;
|
|
2310
2432
|
complete = outcome.complete;
|
|
2311
2433
|
unresolved = outcome.unresolved ?? [];
|
|
@@ -2324,7 +2446,10 @@ var TransportCursorManager = class {
|
|
|
2324
2446
|
let unresolved = this.incompleteCursors.get(cursor) ?? [];
|
|
2325
2447
|
const clearUpdates = await this.decodeRemoteUpdateItems(updates);
|
|
2326
2448
|
if (clearUpdates.length > 0) {
|
|
2327
|
-
const outcome = normalizeApplyReturn(await
|
|
2449
|
+
const outcome = normalizeApplyReturn(await atErrorBoundary({
|
|
2450
|
+
source: "crdt",
|
|
2451
|
+
stage: "crdt_apply"
|
|
2452
|
+
}, async () => this.adapter.applyRemoteUpdates(clearUpdates, nextVersion)));
|
|
2328
2453
|
nextVersion = outcome.version;
|
|
2329
2454
|
complete = outcome.complete;
|
|
2330
2455
|
unresolved = outcome.unresolved ?? [];
|
|
@@ -2346,7 +2471,10 @@ var TransportCursorManager = class {
|
|
|
2346
2471
|
}
|
|
2347
2472
|
async decodeRemoteUpdateItems(items) {
|
|
2348
2473
|
if (items.length === 0) return [];
|
|
2349
|
-
return this.decodeUpdateItems == null ? [...items] : await
|
|
2474
|
+
return this.decodeUpdateItems == null ? [...items] : await atErrorBoundary({
|
|
2475
|
+
source: "local",
|
|
2476
|
+
stage: "payload_decode"
|
|
2477
|
+
}, this.decodeUpdateItems.bind(this, items));
|
|
2350
2478
|
}
|
|
2351
2479
|
};
|
|
2352
2480
|
//#endregion
|
|
@@ -2453,8 +2581,14 @@ var TransportSnapshotManager = class {
|
|
|
2453
2581
|
}
|
|
2454
2582
|
if (tailBytes <= snapshotBytes) return;
|
|
2455
2583
|
if (tailBytes - snapshotBytes < config.minBytesSinceRemoteSnapshot) return;
|
|
2456
|
-
const snapshot =
|
|
2457
|
-
|
|
2584
|
+
const snapshot = atSyncErrorBoundary({
|
|
2585
|
+
source: "crdt",
|
|
2586
|
+
stage: "crdt_export"
|
|
2587
|
+
}, () => this.adapter.exportSnapshot());
|
|
2588
|
+
await this.putSnapshotAt(state.cursor.nextOffset, this.encodeSnapshot == null ? snapshot : await atErrorBoundary({
|
|
2589
|
+
source: "local",
|
|
2590
|
+
stage: "payload_encode"
|
|
2591
|
+
}, this.encodeSnapshot.bind(this, snapshot, state.cursor.nextOffset)));
|
|
2458
2592
|
} catch (error) {
|
|
2459
2593
|
this.logError("snapshot upload failed", error);
|
|
2460
2594
|
}
|
|
@@ -2681,7 +2815,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2681
2815
|
state.started = this.startJoin(state);
|
|
2682
2816
|
state.started.catch((error) => {
|
|
2683
2817
|
if (state.closed) return;
|
|
2684
|
-
const transportError = toTransportError(error);
|
|
2818
|
+
const transportError = toTransportError(error, "live");
|
|
2685
2819
|
if (transportError.retryable) {
|
|
2686
2820
|
state.initialSyncFailed = true;
|
|
2687
2821
|
state.lastError = transportError;
|
|
@@ -2739,7 +2873,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2739
2873
|
} catch (error) {
|
|
2740
2874
|
return {
|
|
2741
2875
|
ok: false,
|
|
2742
|
-
error: toTransportError(error)
|
|
2876
|
+
error: toTransportError(error, "snapshot")
|
|
2743
2877
|
};
|
|
2744
2878
|
}
|
|
2745
2879
|
}
|
|
@@ -2751,10 +2885,19 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2751
2885
|
*/
|
|
2752
2886
|
async uploadSnapshotForTesting() {
|
|
2753
2887
|
try {
|
|
2754
|
-
await
|
|
2888
|
+
await atErrorBoundary({
|
|
2889
|
+
source: "crdt",
|
|
2890
|
+
stage: "crdt_export"
|
|
2891
|
+
}, () => this.adapter.flushLocalExports?.());
|
|
2755
2892
|
const cursor = await this.syncOnce("sync");
|
|
2756
|
-
const rawSnapshot =
|
|
2757
|
-
|
|
2893
|
+
const rawSnapshot = atSyncErrorBoundary({
|
|
2894
|
+
source: "crdt",
|
|
2895
|
+
stage: "crdt_export"
|
|
2896
|
+
}, () => this.adapter.exportSnapshot());
|
|
2897
|
+
const snapshot = this.encodeSnapshot == null ? rawSnapshot : await atErrorBoundary({
|
|
2898
|
+
source: "local",
|
|
2899
|
+
stage: "payload_encode"
|
|
2900
|
+
}, this.encodeSnapshot.bind(this, rawSnapshot, cursor.nextOffset));
|
|
2758
2901
|
await this.client.putSnapshotAt(cursor.nextOffset, snapshot);
|
|
2759
2902
|
return {
|
|
2760
2903
|
ok: true,
|
|
@@ -2766,7 +2909,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2766
2909
|
} catch (error) {
|
|
2767
2910
|
return {
|
|
2768
2911
|
ok: false,
|
|
2769
|
-
error: toTransportError(error)
|
|
2912
|
+
error: toTransportError(error, "snapshot")
|
|
2770
2913
|
};
|
|
2771
2914
|
}
|
|
2772
2915
|
}
|
|
@@ -2782,7 +2925,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2782
2925
|
} catch (error) {
|
|
2783
2926
|
return {
|
|
2784
2927
|
ok: false,
|
|
2785
|
-
error: toTransportError(error)
|
|
2928
|
+
error: toTransportError(error, "create")
|
|
2786
2929
|
};
|
|
2787
2930
|
}
|
|
2788
2931
|
}
|
|
@@ -2796,7 +2939,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2796
2939
|
ok: true,
|
|
2797
2940
|
value: { deleted: await this.enqueueExclusive(async () => {
|
|
2798
2941
|
const deleted = await this.client.deleteStreamRequest();
|
|
2799
|
-
await
|
|
2942
|
+
await atErrorBoundary({
|
|
2943
|
+
source: "cursor_store",
|
|
2944
|
+
stage: "cursor_delete"
|
|
2945
|
+
}, () => this.remoteCursorStore.delete?.(this.streamUrl));
|
|
2800
2946
|
this.producerEpoch = 0;
|
|
2801
2947
|
this.nextProducerSeq = 0;
|
|
2802
2948
|
this.frozenLocalAppend = void 0;
|
|
@@ -2808,7 +2954,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2808
2954
|
} catch (error) {
|
|
2809
2955
|
return {
|
|
2810
2956
|
ok: false,
|
|
2811
|
-
error: toTransportError(error)
|
|
2957
|
+
error: toTransportError(error, "delete")
|
|
2812
2958
|
};
|
|
2813
2959
|
}
|
|
2814
2960
|
}
|
|
@@ -2825,7 +2971,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2825
2971
|
} catch (error) {
|
|
2826
2972
|
return {
|
|
2827
2973
|
ok: false,
|
|
2828
|
-
error: toTransportError(error)
|
|
2974
|
+
error: toTransportError(error, "sync")
|
|
2829
2975
|
};
|
|
2830
2976
|
}
|
|
2831
2977
|
}
|
|
@@ -2853,7 +2999,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2853
2999
|
} catch (error) {
|
|
2854
3000
|
return {
|
|
2855
3001
|
ok: false,
|
|
2856
|
-
error: toTransportError(error)
|
|
3002
|
+
error: toTransportError(error, "catchup")
|
|
2857
3003
|
};
|
|
2858
3004
|
}
|
|
2859
3005
|
}
|
|
@@ -2868,7 +3014,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2868
3014
|
return await this.enqueueExclusive(async () => {
|
|
2869
3015
|
try {
|
|
2870
3016
|
if (this.writeOnlyDisabledReason != null) throw new Error(this.writeOnlyDisabledReason);
|
|
2871
|
-
await
|
|
3017
|
+
await atErrorBoundary({
|
|
3018
|
+
source: "crdt",
|
|
3019
|
+
stage: "crdt_export"
|
|
3020
|
+
}, () => this.adapter.flushLocalExports?.());
|
|
2872
3021
|
const drained = await this.drainPendingWriteOnlyBatch();
|
|
2873
3022
|
if (drained == null) return {
|
|
2874
3023
|
ok: true,
|
|
@@ -2893,7 +3042,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2893
3042
|
} catch (error) {
|
|
2894
3043
|
return {
|
|
2895
3044
|
ok: false,
|
|
2896
|
-
error: toTransportError(error)
|
|
3045
|
+
error: toTransportError(error, "upload")
|
|
2897
3046
|
};
|
|
2898
3047
|
}
|
|
2899
3048
|
});
|
|
@@ -2918,14 +3067,17 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2918
3067
|
} catch (error) {
|
|
2919
3068
|
return {
|
|
2920
3069
|
ok: false,
|
|
2921
|
-
error: toTransportError(error)
|
|
3070
|
+
error: toTransportError(error, "join")
|
|
2922
3071
|
};
|
|
2923
3072
|
}
|
|
2924
3073
|
}
|
|
2925
3074
|
const state = createJoinState(createInitialRemoteCursor({
|
|
2926
3075
|
streamUrl: this.streamUrl,
|
|
2927
3076
|
nextOffset: "-1",
|
|
2928
|
-
serverLowerBoundVersion:
|
|
3077
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3078
|
+
source: "crdt",
|
|
3079
|
+
stage: "crdt_export"
|
|
3080
|
+
}, () => this.adapter.emptyVersion())
|
|
2929
3081
|
}));
|
|
2930
3082
|
const statusListener = params?.onStatusChange;
|
|
2931
3083
|
if (statusListener != null) state.statusListeners.add(statusListener);
|
|
@@ -2938,7 +3090,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2938
3090
|
value: this.createSubscription(state, params)
|
|
2939
3091
|
};
|
|
2940
3092
|
} catch (error) {
|
|
2941
|
-
const transportError = toTransportError(error);
|
|
3093
|
+
const transportError = toTransportError(error, "join");
|
|
2942
3094
|
if (transportError.retryable && !state.closed) {
|
|
2943
3095
|
state.initialSyncFailed = true;
|
|
2944
3096
|
state.lastError = transportError;
|
|
@@ -3008,7 +3160,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3008
3160
|
if (state.readSubStatus === "connecting") try {
|
|
3009
3161
|
await state.started;
|
|
3010
3162
|
} catch (error) {
|
|
3011
|
-
const te = toTransportError(error);
|
|
3163
|
+
const te = toTransportError(error, "join");
|
|
3012
3164
|
throw new Error(te.code ? `initial sync failed: ${te.code}` : "initial sync failed");
|
|
3013
3165
|
}
|
|
3014
3166
|
if (state.writeSubStatus === "error") {
|
|
@@ -3016,7 +3168,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3016
3168
|
const msg = state.lastError?.code ? `local append failed: ${state.lastError.code}` : "local append failed";
|
|
3017
3169
|
throw new Error(msg);
|
|
3018
3170
|
}
|
|
3019
|
-
await
|
|
3171
|
+
await atErrorBoundary({
|
|
3172
|
+
source: "crdt",
|
|
3173
|
+
stage: "crdt_export"
|
|
3174
|
+
}, () => this.adapter.flushLocalExports?.());
|
|
3020
3175
|
if (state.closed) throw new Error("subscription closed");
|
|
3021
3176
|
const remaining = state.pendingLocal.length;
|
|
3022
3177
|
if (remaining === 0) return;
|
|
@@ -3059,6 +3214,11 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3059
3214
|
if (typeof hardTimeoutMs === "number" && Number.isFinite(hardTimeoutMs) && hardTimeoutMs > 0) hardTimeoutPromise = new Promise((_, reject) => {
|
|
3060
3215
|
hardTimer = setTimeout(() => {
|
|
3061
3216
|
hardTimeoutError = new InitialSyncTimeoutError(operation, hardTimeoutMs, Date.now() - startedAtMs);
|
|
3217
|
+
recordErrorContext(hardTimeoutError, {
|
|
3218
|
+
source: "deadline",
|
|
3219
|
+
stage: "initial_sync",
|
|
3220
|
+
originalCode: "initial_sync_timeout"
|
|
3221
|
+
});
|
|
3062
3222
|
hardController.abort(hardTimeoutError);
|
|
3063
3223
|
reject(hardTimeoutError);
|
|
3064
3224
|
}, hardTimeoutMs);
|
|
@@ -3107,7 +3267,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3107
3267
|
} catch (error) {
|
|
3108
3268
|
if (state.closed) return;
|
|
3109
3269
|
this.snapshotManager.dispose(state);
|
|
3110
|
-
state.lastError = toTransportError(error);
|
|
3270
|
+
state.lastError = toTransportError(error, "join");
|
|
3111
3271
|
this.logError("join first sync failed", error, { streamUrl: this.streamUrl });
|
|
3112
3272
|
state.readSubStatus = "error";
|
|
3113
3273
|
state.writeSubStatus = "error";
|
|
@@ -3155,7 +3315,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3155
3315
|
this.commitProducerAck(ack.producerAck);
|
|
3156
3316
|
didAppend = true;
|
|
3157
3317
|
}
|
|
3158
|
-
await
|
|
3318
|
+
await atErrorBoundary({
|
|
3319
|
+
source: "crdt",
|
|
3320
|
+
stage: "crdt_export"
|
|
3321
|
+
}, () => this.adapter.flushLocalExports?.());
|
|
3159
3322
|
const localBatch = this.exportUpdates(appliedVersion);
|
|
3160
3323
|
if (localBatch.batch != null) {
|
|
3161
3324
|
const appendedBatch = await this.appendLocalBatchRemote({
|
|
@@ -3175,12 +3338,49 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3175
3338
|
}
|
|
3176
3339
|
}
|
|
3177
3340
|
async syncOnce(operation, parentSignal) {
|
|
3178
|
-
|
|
3341
|
+
return this.enqueueExclusive(async () => {
|
|
3342
|
+
await atErrorBoundary({
|
|
3343
|
+
source: "crdt",
|
|
3344
|
+
stage: "crdt_export"
|
|
3345
|
+
}, () => this.adapter.flushLocalExports?.());
|
|
3346
|
+
return this.syncOnceExclusive(operation, parentSignal);
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3349
|
+
/** Caller owns opQueue; never wait for the background flush from here. */
|
|
3350
|
+
async syncOnceExclusive(operation, parentSignal) {
|
|
3351
|
+
const state = this.joinState;
|
|
3352
|
+
if (state != null && !state.closed) while (!state.closed && state.pendingLocal.length > 0) await this.flushPendingLocalBatchExclusive(state);
|
|
3353
|
+
if ((state == null || state.closed) && this.frozenLocalAppend?.source === "pending") {
|
|
3354
|
+
const frozen = this.frozenLocalAppend;
|
|
3355
|
+
const appended = await this.appendFrozenLocalBatchRemote(frozen);
|
|
3356
|
+
await this.catchupLocalAppend(frozen);
|
|
3357
|
+
this.commitProducerAck(appended.producerAck);
|
|
3358
|
+
}
|
|
3359
|
+
const initial = await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState(operation, parentSignal));
|
|
3360
|
+
let cursor = initial.cursor;
|
|
3361
|
+
if (state != null && !state.closed) {
|
|
3362
|
+
state.cursor = cursor;
|
|
3363
|
+
state.streamCursor = initial.streamCursor;
|
|
3364
|
+
while (!state.closed && state.pendingLocal.length > 0) {
|
|
3365
|
+
await this.flushPendingLocalBatchExclusive(state);
|
|
3366
|
+
cursor = state.cursor;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3179
3369
|
const localBatch = this.exportUpdates(cursor.serverLowerBoundVersion);
|
|
3180
|
-
if (localBatch.batch != null)
|
|
3370
|
+
if (localBatch.batch != null) {
|
|
3371
|
+
const appended = await this.appendLocalBatchDurably(cursor, localBatch.batch);
|
|
3372
|
+
cursor = appended.cursor;
|
|
3373
|
+
if (state != null && !state.closed) {
|
|
3374
|
+
state.cursor = cursor;
|
|
3375
|
+
state.streamCursor = appended.streamCursor;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3181
3378
|
return cursor;
|
|
3182
3379
|
}
|
|
3183
3380
|
async performInitialJoinSync(preferredLiveMode, parentSignal) {
|
|
3381
|
+
return this.enqueueExclusive(() => this.performInitialJoinSyncExclusive(preferredLiveMode, parentSignal));
|
|
3382
|
+
}
|
|
3383
|
+
async performInitialJoinSyncExclusive(preferredLiveMode, parentSignal) {
|
|
3184
3384
|
const initial = await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState("join", parentSignal));
|
|
3185
3385
|
let cursor = initial.cursor;
|
|
3186
3386
|
let localExportRefVersion = cursor.serverLowerBoundVersion;
|
|
@@ -3190,9 +3390,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3190
3390
|
localExportRefVersion = exportedLocal.nextRefVersion;
|
|
3191
3391
|
const exportedBatch = exportedLocal.batch;
|
|
3192
3392
|
if (exportedBatch != null) {
|
|
3193
|
-
const
|
|
3194
|
-
const appendStreamCursor = streamCursor;
|
|
3195
|
-
const committed = await this.enqueueExclusive(() => this.appendLocalBatchDurably(appendCursor, exportedBatch, void 0, "direct", appendStreamCursor));
|
|
3393
|
+
const committed = await this.appendLocalBatchDurably(cursor, exportedBatch, void 0, "direct", streamCursor);
|
|
3196
3394
|
cursor = committed.cursor;
|
|
3197
3395
|
streamCursor = committed.streamCursor;
|
|
3198
3396
|
}
|
|
@@ -3208,11 +3406,17 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3208
3406
|
return createInitialRemoteCursor({
|
|
3209
3407
|
streamUrl: this.streamUrl,
|
|
3210
3408
|
nextOffset: "-1",
|
|
3211
|
-
serverLowerBoundVersion:
|
|
3409
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3410
|
+
source: "crdt",
|
|
3411
|
+
stage: "crdt_export"
|
|
3412
|
+
}, () => this.adapter.emptyVersion())
|
|
3212
3413
|
});
|
|
3213
3414
|
}
|
|
3214
3415
|
async resolveInitialRemoteState(operation, parentSignal) {
|
|
3215
|
-
const loaded = await
|
|
3416
|
+
const loaded = await atErrorBoundary({
|
|
3417
|
+
source: "cursor_store",
|
|
3418
|
+
stage: "cursor_load"
|
|
3419
|
+
}, () => this.remoteCursorStore.load(this.streamUrl));
|
|
3216
3420
|
const cursor = loaded ?? this.createInitialCursor();
|
|
3217
3421
|
const runInitial = (task) => this.runInitialSyncWithDeadline(operation, task, parentSignal);
|
|
3218
3422
|
try {
|
|
@@ -3237,7 +3441,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3237
3441
|
}
|
|
3238
3442
|
}
|
|
3239
3443
|
async resolvePullOnlyRemoteState(operation, parentSignal) {
|
|
3240
|
-
const loaded = await
|
|
3444
|
+
const loaded = await atErrorBoundary({
|
|
3445
|
+
source: "cursor_store",
|
|
3446
|
+
stage: "cursor_load"
|
|
3447
|
+
}, () => this.remoteCursorStore.load(this.streamUrl));
|
|
3241
3448
|
const cursor = loaded ?? this.createInitialCursor();
|
|
3242
3449
|
const runInitial = (task) => this.runInitialSyncWithDeadline(operation, task, parentSignal);
|
|
3243
3450
|
try {
|
|
@@ -3279,7 +3486,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3279
3486
|
}
|
|
3280
3487
|
async bootstrapState(cursor, options = {}) {
|
|
3281
3488
|
const cursorManager = options.cursorManager ?? this.cursorManager;
|
|
3282
|
-
const result = await this.client.bootstrap(options.signal, options.timeoutMode);
|
|
3489
|
+
const result = await atErrorBoundary({ stage: "bootstrap" }, () => this.client.bootstrap(options.signal, options.timeoutMode));
|
|
3283
3490
|
let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset, result.upToDate, result.snapshotOffset);
|
|
3284
3491
|
let bootstrapCursor = result.streamCursor;
|
|
3285
3492
|
if (!result.upToDate) {
|
|
@@ -3316,13 +3523,16 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3316
3523
|
const cursorManager = options.cursorManager ?? this.cursorManager;
|
|
3317
3524
|
while (true) {
|
|
3318
3525
|
const requestOffset = current.nextOffset;
|
|
3319
|
-
const response = await this.client.readFromOffset(requestOffset, currentStreamCursor, options.live, options.signal, options.timeoutMode);
|
|
3526
|
+
const response = await atErrorBoundary({ stage: "catch_up" }, () => this.client.readFromOffset(requestOffset, currentStreamCursor, options.live, options.signal, options.timeoutMode));
|
|
3320
3527
|
if (response.kind === "gone") {
|
|
3321
3528
|
if (goneStrategy === "return-gone") return StreamsCrdt.GONE_CATCHUP;
|
|
3322
3529
|
return await this.bootstrapState({
|
|
3323
3530
|
...current,
|
|
3324
3531
|
nextOffset: "-1",
|
|
3325
|
-
serverLowerBoundVersion:
|
|
3532
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3533
|
+
source: "crdt",
|
|
3534
|
+
stage: "crdt_export"
|
|
3535
|
+
}, () => this.adapter.emptyVersion())
|
|
3326
3536
|
}, {
|
|
3327
3537
|
cursorManager,
|
|
3328
3538
|
signal: options.signal,
|
|
@@ -3337,7 +3547,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3337
3547
|
if (error instanceof CrdtApplyIncompleteError && result.upToDate) return await this.bootstrapState({
|
|
3338
3548
|
...current,
|
|
3339
3549
|
nextOffset: "-1",
|
|
3340
|
-
serverLowerBoundVersion:
|
|
3550
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3551
|
+
source: "crdt",
|
|
3552
|
+
stage: "crdt_export"
|
|
3553
|
+
}, () => this.adapter.emptyVersion())
|
|
3341
3554
|
}, {
|
|
3342
3555
|
cursorManager,
|
|
3343
3556
|
signal: options.signal,
|
|
@@ -3358,7 +3571,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3358
3571
|
const bootstrapped = await this.bootstrapState({
|
|
3359
3572
|
...state.cursor,
|
|
3360
3573
|
nextOffset: "-1",
|
|
3361
|
-
serverLowerBoundVersion:
|
|
3574
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3575
|
+
source: "crdt",
|
|
3576
|
+
stage: "crdt_export"
|
|
3577
|
+
}, () => this.adapter.emptyVersion())
|
|
3362
3578
|
}, { signal: state.requestController?.signal });
|
|
3363
3579
|
state.cursor = bootstrapped.cursor;
|
|
3364
3580
|
state.streamCursor = bootstrapped.streamCursor;
|
|
@@ -3374,7 +3590,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3374
3590
|
streamUrl: this.streamUrl,
|
|
3375
3591
|
code: classified.code
|
|
3376
3592
|
});
|
|
3377
|
-
state.lastError = toTransportError(error);
|
|
3593
|
+
state.lastError = toTransportError(error, "live");
|
|
3378
3594
|
if (state.pendingLocal.length > 0) this.flushPendingLocal(state);
|
|
3379
3595
|
if (classified.retriable && !isAuthOrProtocolError(error)) {
|
|
3380
3596
|
state.incompleteRecoveryAttempt += 1;
|
|
@@ -3407,7 +3623,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3407
3623
|
localAppendReadCursor(frozen) {
|
|
3408
3624
|
return {
|
|
3409
3625
|
...frozen.baseCursor,
|
|
3410
|
-
serverLowerBoundVersion:
|
|
3626
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3627
|
+
source: "crdt",
|
|
3628
|
+
stage: "crdt_export"
|
|
3629
|
+
}, () => this.adapter.mergeVersions(frozen.baseCursor.serverLowerBoundVersion, frozen.batch.version))
|
|
3411
3630
|
};
|
|
3412
3631
|
}
|
|
3413
3632
|
async catchupLocalAppend(frozen, streamCursor) {
|
|
@@ -3439,7 +3658,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3439
3658
|
baseCursor: cursor,
|
|
3440
3659
|
batch,
|
|
3441
3660
|
pendingCount,
|
|
3442
|
-
wireUpdates: this.encodeUpdateItems == null ? batch.updates : await
|
|
3661
|
+
wireUpdates: this.encodeUpdateItems == null ? batch.updates : await atErrorBoundary({
|
|
3662
|
+
source: "local",
|
|
3663
|
+
stage: "payload_encode"
|
|
3664
|
+
}, this.encodeUpdateItems.bind(this, batch.updates)),
|
|
3443
3665
|
producerSession: this.createCurrentProducerSession()
|
|
3444
3666
|
};
|
|
3445
3667
|
this.frozenLocalAppend = frozen;
|
|
@@ -3450,7 +3672,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3450
3672
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
3451
3673
|
let result;
|
|
3452
3674
|
try {
|
|
3453
|
-
result = await this.client.appendUpdates(frozen.wireUpdates, frozen.producerSession);
|
|
3675
|
+
result = await atErrorBoundary({ stage: "upload" }, () => this.client.appendUpdates(frozen.wireUpdates, frozen.producerSession));
|
|
3454
3676
|
} catch (error) {
|
|
3455
3677
|
if (isStreamNotFoundError(error) && this.createStreamIfMissing) {
|
|
3456
3678
|
await this.ensureStreamExists();
|
|
@@ -3463,7 +3685,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3463
3685
|
cursor: {
|
|
3464
3686
|
...frozen.baseCursor,
|
|
3465
3687
|
nextOffset: result.nextOffset,
|
|
3466
|
-
serverLowerBoundVersion:
|
|
3688
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3689
|
+
source: "crdt",
|
|
3690
|
+
stage: "crdt_export"
|
|
3691
|
+
}, () => this.adapter.mergeVersions(frozen.baseCursor.serverLowerBoundVersion, frozen.batch.version))
|
|
3467
3692
|
},
|
|
3468
3693
|
producerAck: {
|
|
3469
3694
|
epoch: result.ackEpoch,
|
|
@@ -3538,9 +3763,15 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3538
3763
|
}
|
|
3539
3764
|
}
|
|
3540
3765
|
exportUpdates(fromVersion) {
|
|
3541
|
-
const exported =
|
|
3766
|
+
const exported = atSyncErrorBoundary({
|
|
3767
|
+
source: "crdt",
|
|
3768
|
+
stage: "crdt_export"
|
|
3769
|
+
}, () => this.adapter.exportUpdates(fromVersion));
|
|
3542
3770
|
if (exported == null) return { nextRefVersion: fromVersion };
|
|
3543
|
-
const nextRefVersion =
|
|
3771
|
+
const nextRefVersion = atSyncErrorBoundary({
|
|
3772
|
+
source: "crdt",
|
|
3773
|
+
stage: "crdt_export"
|
|
3774
|
+
}, () => this.adapter.mergeVersions(fromVersion, exported.version));
|
|
3544
3775
|
if (exported.updates.length === 0) return { nextRefVersion };
|
|
3545
3776
|
return {
|
|
3546
3777
|
batch: exported,
|
|
@@ -3597,10 +3828,16 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3597
3828
|
const bootstrapCursor = {
|
|
3598
3829
|
...cursor,
|
|
3599
3830
|
nextOffset: "-1",
|
|
3600
|
-
serverLowerBoundVersion:
|
|
3831
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3832
|
+
source: "crdt",
|
|
3833
|
+
stage: "crdt_export"
|
|
3834
|
+
}, () => this.adapter.emptyVersion())
|
|
3601
3835
|
};
|
|
3602
3836
|
if (isolatedCursorManager != null) return await this.inferRemoteAndAppendLocal(bootstrapCursor, isolatedCursorManager, operation, parentSignal);
|
|
3603
|
-
const preservedLocalSnapshot =
|
|
3837
|
+
const preservedLocalSnapshot = atSyncErrorBoundary({
|
|
3838
|
+
source: "crdt",
|
|
3839
|
+
stage: "crdt_export"
|
|
3840
|
+
}, () => this.adapter.exportSnapshot());
|
|
3604
3841
|
let restoredLocalSnapshot = false;
|
|
3605
3842
|
try {
|
|
3606
3843
|
const inferredRemote = await this.runInitialSyncWithDeadline(operation, (signal) => this.bootstrapState(bootstrapCursor, {
|
|
@@ -3608,7 +3845,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3608
3845
|
signal,
|
|
3609
3846
|
timeoutMode: "initial"
|
|
3610
3847
|
}), parentSignal);
|
|
3611
|
-
await
|
|
3848
|
+
await atErrorBoundary({
|
|
3849
|
+
source: "crdt",
|
|
3850
|
+
stage: "crdt_apply"
|
|
3851
|
+
}, async () => this.adapter.applySnapshot(preservedLocalSnapshot));
|
|
3612
3852
|
restoredLocalSnapshot = true;
|
|
3613
3853
|
let pendingAppend;
|
|
3614
3854
|
if (this.frozenLocalAppend != null) {
|
|
@@ -3625,7 +3865,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3625
3865
|
if (pendingAppend != null) this.commitProducerAck(pendingAppend.producerAck);
|
|
3626
3866
|
return result;
|
|
3627
3867
|
} catch (error) {
|
|
3628
|
-
if (!restoredLocalSnapshot) await
|
|
3868
|
+
if (!restoredLocalSnapshot) await atErrorBoundary({
|
|
3869
|
+
source: "crdt",
|
|
3870
|
+
stage: "crdt_apply"
|
|
3871
|
+
}, async () => this.adapter.applySnapshot(preservedLocalSnapshot));
|
|
3629
3872
|
throw error;
|
|
3630
3873
|
}
|
|
3631
3874
|
}
|
|
@@ -3712,7 +3955,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3712
3955
|
streamUrl: this.streamUrl,
|
|
3713
3956
|
attempts: state.retryAttempt
|
|
3714
3957
|
});
|
|
3715
|
-
state.lastError = toTransportError(error);
|
|
3958
|
+
state.lastError = toTransportError(error, "live");
|
|
3716
3959
|
this.setReadSubStatus(state, "disconnected");
|
|
3717
3960
|
return false;
|
|
3718
3961
|
}
|
|
@@ -3745,12 +3988,15 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3745
3988
|
try {
|
|
3746
3989
|
if (state.liveMode === "long-poll") {
|
|
3747
3990
|
const requestOffset = state.cursor.nextOffset;
|
|
3748
|
-
const response = await this.client.readFromOffset(requestOffset, state.streamCursor, "long-poll", capturedController?.signal);
|
|
3991
|
+
const response = await atErrorBoundary({ stage: "catch_up" }, () => this.client.readFromOffset(requestOffset, state.streamCursor, "long-poll", capturedController?.signal));
|
|
3749
3992
|
if (response.kind === "gone") {
|
|
3750
3993
|
const bootstrapped = await this.bootstrapState({
|
|
3751
3994
|
...state.cursor,
|
|
3752
3995
|
nextOffset: "-1",
|
|
3753
|
-
serverLowerBoundVersion:
|
|
3996
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
3997
|
+
source: "crdt",
|
|
3998
|
+
stage: "crdt_export"
|
|
3999
|
+
}, () => this.adapter.emptyVersion())
|
|
3754
4000
|
}, { signal: capturedController?.signal });
|
|
3755
4001
|
state.cursor = bootstrapped.cursor;
|
|
3756
4002
|
state.streamCursor = bootstrapped.streamCursor;
|
|
@@ -3837,14 +4083,17 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3837
4083
|
streamUrl: this.streamUrl,
|
|
3838
4084
|
code: "stream_not_found"
|
|
3839
4085
|
});
|
|
3840
|
-
state.lastError = toTransportError(notFoundError);
|
|
4086
|
+
state.lastError = toTransportError(notFoundError, "live");
|
|
3841
4087
|
this.setReadSubStatus(state, "error");
|
|
3842
4088
|
return;
|
|
3843
4089
|
}
|
|
3844
4090
|
const bootstrapped = await this.bootstrapState({
|
|
3845
4091
|
...state.cursor,
|
|
3846
4092
|
nextOffset: "-1",
|
|
3847
|
-
serverLowerBoundVersion:
|
|
4093
|
+
serverLowerBoundVersion: atSyncErrorBoundary({
|
|
4094
|
+
source: "crdt",
|
|
4095
|
+
stage: "crdt_export"
|
|
4096
|
+
}, () => this.adapter.emptyVersion())
|
|
3848
4097
|
}, { signal: capturedController?.signal });
|
|
3849
4098
|
state.cursor = bootstrapped.cursor;
|
|
3850
4099
|
state.streamCursor = bootstrapped.streamCursor;
|
|
@@ -3901,7 +4150,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3901
4150
|
streamUrl: this.streamUrl,
|
|
3902
4151
|
code: flushClassified.code
|
|
3903
4152
|
});
|
|
3904
|
-
state.lastError = toTransportError(flushError);
|
|
4153
|
+
state.lastError = toTransportError(flushError, "live");
|
|
3905
4154
|
this.setReadSubStatus(state, "error");
|
|
3906
4155
|
return;
|
|
3907
4156
|
}
|
|
@@ -3916,7 +4165,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3916
4165
|
streamUrl: this.streamUrl,
|
|
3917
4166
|
code: classified.code
|
|
3918
4167
|
});
|
|
3919
|
-
state.lastError = toTransportError(error);
|
|
4168
|
+
state.lastError = toTransportError(error, "live");
|
|
3920
4169
|
this.setReadSubStatus(state, "error");
|
|
3921
4170
|
return;
|
|
3922
4171
|
}
|
|
@@ -3949,7 +4198,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3949
4198
|
const candidateUpdates = [...allUpdates, ...next.updates];
|
|
3950
4199
|
if (await this.measureAppendBodyByteLength(candidateUpdates) > maxBytes) break;
|
|
3951
4200
|
allUpdates.push(...next.updates);
|
|
3952
|
-
mergedVersion =
|
|
4201
|
+
mergedVersion = atSyncErrorBoundary({
|
|
4202
|
+
source: "crdt",
|
|
4203
|
+
stage: "crdt_export"
|
|
4204
|
+
}, () => this.adapter.mergeVersions(mergedVersion, next.version));
|
|
3953
4205
|
count += 1;
|
|
3954
4206
|
}
|
|
3955
4207
|
if (count === 1) return {
|
|
@@ -3971,6 +4223,50 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3971
4223
|
return this.drainQueuedBatch(this.pendingWriteOnly);
|
|
3972
4224
|
}
|
|
3973
4225
|
/**
|
|
4226
|
+
* One FIFO transaction, called only while holding opQueue. Selecting the
|
|
4227
|
+
* head and committing its ACK/count must share the lock: sync may drain
|
|
4228
|
+
* the queue while a background writer is queued or sleeping between retries.
|
|
4229
|
+
*/
|
|
4230
|
+
async flushPendingLocalBatchExclusive(state) {
|
|
4231
|
+
while (!state.closed) {
|
|
4232
|
+
const drained = await this.drainPendingBatch(state);
|
|
4233
|
+
if (drained == null || state.closed) return false;
|
|
4234
|
+
try {
|
|
4235
|
+
let committedCount;
|
|
4236
|
+
const initial = await this.finalizeDirectLocalAppendIfNeeded({
|
|
4237
|
+
cursor: state.cursor,
|
|
4238
|
+
streamCursor: state.streamCursor
|
|
4239
|
+
});
|
|
4240
|
+
state.cursor = initial.cursor;
|
|
4241
|
+
state.streamCursor = initial.streamCursor;
|
|
4242
|
+
if (this.cursorManager.isCursorComplete(state.cursor)) {
|
|
4243
|
+
state.cursor = await this.appendLocalBatch(state.cursor, drained.batch, drained.count, state.streamCursor);
|
|
4244
|
+
const streamCursor = this.consumeCommittedLocalStreamCursor();
|
|
4245
|
+
if (streamCursor != null) state.streamCursor = streamCursor;
|
|
4246
|
+
committedCount = this.consumeCommittedPendingCount() ?? drained.count;
|
|
4247
|
+
if (!state.closed) this.snapshotManager.schedule(state);
|
|
4248
|
+
} else {
|
|
4249
|
+
const appended = await this.appendLocalBatchRemote(state.cursor, drained.batch, drained.count, "pending");
|
|
4250
|
+
this.commitProducerAck(appended.producerAck);
|
|
4251
|
+
committedCount = appended.pendingCount ?? drained.count;
|
|
4252
|
+
}
|
|
4253
|
+
for (let i = 0; i < committedCount; i += 1) state.pendingLocal.popFront();
|
|
4254
|
+
this.notifySyncWaiters(state, committedCount);
|
|
4255
|
+
state.flushHeadBatchOnly = false;
|
|
4256
|
+
state.lastWriteError = void 0;
|
|
4257
|
+
this.setWriteSubStatus(state, "ok");
|
|
4258
|
+
return true;
|
|
4259
|
+
} catch (error) {
|
|
4260
|
+
if (isPayloadTooLargeError(error) && drained.count > 1 && this.abandonPendingFrozenLocalAppend()) {
|
|
4261
|
+
state.flushHeadBatchOnly = true;
|
|
4262
|
+
continue;
|
|
4263
|
+
}
|
|
4264
|
+
throw error;
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
return false;
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
3974
4270
|
* Drains `pendingLocal` to the server in strict FIFO order.
|
|
3975
4271
|
*
|
|
3976
4272
|
* FIFO invariant: batches are only popped after a durable append, and a
|
|
@@ -3995,36 +4291,14 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3995
4291
|
this.clearWriteRetryTimer(state);
|
|
3996
4292
|
state.flushingLocal = true;
|
|
3997
4293
|
try {
|
|
3998
|
-
|
|
3999
|
-
const drained = await this.drainPendingBatch(state);
|
|
4000
|
-
if (drained == null) return;
|
|
4294
|
+
while (!state.closed) {
|
|
4001
4295
|
let attempts = 0;
|
|
4002
4296
|
while (!state.closed) try {
|
|
4003
|
-
|
|
4004
|
-
if (this.cursorManager.isCursorComplete(state.cursor)) {
|
|
4005
|
-
state.cursor = await this.enqueueExclusive(async () => this.appendLocalBatch(state.cursor, drained.batch, drained.count, state.streamCursor));
|
|
4006
|
-
const committedStreamCursor = this.consumeCommittedLocalStreamCursor();
|
|
4007
|
-
if (committedStreamCursor != null) state.streamCursor = committedStreamCursor;
|
|
4008
|
-
committedCount = this.consumeCommittedPendingCount() ?? drained.count;
|
|
4009
|
-
this.snapshotManager.schedule(state);
|
|
4010
|
-
} else {
|
|
4011
|
-
const appended = await this.enqueueExclusive(async () => this.appendLocalBatchRemote(state.cursor, drained.batch, drained.count, "pending"));
|
|
4012
|
-
this.commitProducerAck(appended.producerAck);
|
|
4013
|
-
committedCount = appended.pendingCount ?? drained.count;
|
|
4014
|
-
}
|
|
4015
|
-
for (let i = 0; i < committedCount; i += 1) state.pendingLocal.popFront();
|
|
4016
|
-
this.notifySyncWaiters(state, committedCount);
|
|
4017
|
-
state.flushHeadBatchOnly = false;
|
|
4018
|
-
state.lastWriteError = void 0;
|
|
4019
|
-
this.setWriteSubStatus(state, "ok");
|
|
4297
|
+
if (!await this.enqueueExclusive(() => this.flushPendingLocalBatchExclusive(state))) return;
|
|
4020
4298
|
break;
|
|
4021
4299
|
} catch (error) {
|
|
4022
|
-
if (isPayloadTooLargeError(error) && drained.count > 1 && this.abandonPendingFrozenLocalAppend()) {
|
|
4023
|
-
state.flushHeadBatchOnly = true;
|
|
4024
|
-
continue outer;
|
|
4025
|
-
}
|
|
4026
4300
|
const classified = classifyFetchError(error);
|
|
4027
|
-
const transportError = toTransportError(error);
|
|
4301
|
+
const transportError = toTransportError(error, "upload");
|
|
4028
4302
|
if (!classified.retriable || isAuthOrProtocolError(error)) {
|
|
4029
4303
|
if (isPayloadTooLargeError(error)) state.flushHeadBatchOnly = true;
|
|
4030
4304
|
this.logError("local append failed", error, {
|
|
@@ -4201,4 +4475,4 @@ function isValidRillId(id) {
|
|
|
4201
4475
|
//#endregion
|
|
4202
4476
|
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 };
|
|
4203
4477
|
|
|
4204
|
-
//# sourceMappingURL=stream-id-
|
|
4478
|
+
//# sourceMappingURL=stream-id-Dlnc1PNH.js.map
|