@loro-dev/streams-crdt 0.8.1 → 0.8.3
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 +21 -0
- 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 +126 -19
- package/dist/loro.js.map +1 -1
- package/dist/{stream-id-B6FzWgkM.d.ts → stream-id-Bi5rAZfP.d.ts} +34 -6
- package/dist/{stream-id-CCPEBb6G.js → stream-id-BjmGvObf.js} +179 -36
- package/dist/stream-id-BjmGvObf.js.map +1 -0
- package/package.json +3 -3
- package/dist/stream-id-CCPEBb6G.js.map +0 -1
|
@@ -597,6 +597,12 @@ function mergeAbortSignals(...signals) {
|
|
|
597
597
|
function classifyFetchError(err) {
|
|
598
598
|
const message = err instanceof Error ? err.message : String(err);
|
|
599
599
|
const status = err instanceof HttpError ? err.status : void 0;
|
|
600
|
+
if (isCrdtApplyIncompleteError(err)) return {
|
|
601
|
+
code: "apply_incomplete",
|
|
602
|
+
message,
|
|
603
|
+
retriable: true,
|
|
604
|
+
atMs: Date.now()
|
|
605
|
+
};
|
|
600
606
|
if (status === 401 || status === 403) return {
|
|
601
607
|
code: "auth_failed",
|
|
602
608
|
message,
|
|
@@ -650,6 +656,12 @@ function isStreamNotFoundError(error) {
|
|
|
650
656
|
return error instanceof HttpError && error.status === 404;
|
|
651
657
|
}
|
|
652
658
|
function toTransportError(error) {
|
|
659
|
+
if (isCrdtApplyIncompleteError(error)) return {
|
|
660
|
+
code: "apply_incomplete",
|
|
661
|
+
retryable: true,
|
|
662
|
+
reason: "loro_pending_updates",
|
|
663
|
+
message: error instanceof Error ? error.message : String(error)
|
|
664
|
+
};
|
|
653
665
|
if (isStreamNotFoundError(error)) return {
|
|
654
666
|
code: "stream_not_found",
|
|
655
667
|
retryable: false
|
|
@@ -710,6 +722,9 @@ function toTransportError(error) {
|
|
|
710
722
|
message: String(error)
|
|
711
723
|
};
|
|
712
724
|
}
|
|
725
|
+
function isCrdtApplyIncompleteError(error) {
|
|
726
|
+
return error instanceof Error && Array.isArray(error.unresolved) && error.message.includes("CRDT apply incomplete");
|
|
727
|
+
}
|
|
713
728
|
function isReadNoProgress(requestOffset, result) {
|
|
714
729
|
return !result.upToDate && result.updates.length === 0 && result.nextOffset === requestOffset;
|
|
715
730
|
}
|
|
@@ -1714,67 +1729,115 @@ function createInitialRemoteCursor(input) {
|
|
|
1714
1729
|
}
|
|
1715
1730
|
//#endregion
|
|
1716
1731
|
//#region src/transport-cursor.ts
|
|
1732
|
+
var CrdtApplyIncompleteError = class extends Error {
|
|
1733
|
+
unresolved;
|
|
1734
|
+
constructor(unresolved) {
|
|
1735
|
+
super("CRDT apply incomplete: unresolved remote updates remain");
|
|
1736
|
+
this.unresolved = unresolved;
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
function isApplyOutcome(value) {
|
|
1740
|
+
return typeof value === "object" && value != null && "version" in value && "complete" in value && typeof value.complete === "boolean";
|
|
1741
|
+
}
|
|
1742
|
+
function normalizeApplyReturn(value) {
|
|
1743
|
+
if (isApplyOutcome(value)) return value;
|
|
1744
|
+
return {
|
|
1745
|
+
version: value,
|
|
1746
|
+
complete: true
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1717
1749
|
var TransportCursorManager = class {
|
|
1718
1750
|
adapter;
|
|
1719
1751
|
decodeSnapshot;
|
|
1720
1752
|
decodeUpdateItems;
|
|
1721
1753
|
saveRemoteCursor;
|
|
1754
|
+
incompleteCursors = /* @__PURE__ */ new WeakMap();
|
|
1722
1755
|
constructor(options) {
|
|
1723
1756
|
this.adapter = options.adapter;
|
|
1724
1757
|
this.decodeSnapshot = options.decodeSnapshot;
|
|
1725
1758
|
this.decodeUpdateItems = options.decodeUpdateItems;
|
|
1726
1759
|
this.saveRemoteCursor = options.saveRemoteCursor;
|
|
1727
1760
|
}
|
|
1728
|
-
|
|
1761
|
+
isCursorComplete(cursor) {
|
|
1762
|
+
return !this.incompleteCursors.has(cursor);
|
|
1763
|
+
}
|
|
1764
|
+
async applyBootstrapPayload(cursor, snapshot, updates, nextOffset, upToDate = false) {
|
|
1729
1765
|
let nextVersion = cursor.serverLowerBoundVersion;
|
|
1730
|
-
|
|
1766
|
+
let complete = true;
|
|
1767
|
+
let unresolved = [];
|
|
1768
|
+
if (snapshot.byteLength > 0) {
|
|
1769
|
+
const outcome = normalizeApplyReturn(await this.adapter.applySnapshot(this.decodeSnapshot == null ? snapshot : await this.decodeSnapshot(snapshot)));
|
|
1770
|
+
nextVersion = outcome.version;
|
|
1771
|
+
complete = outcome.complete;
|
|
1772
|
+
unresolved = outcome.unresolved ?? [];
|
|
1773
|
+
}
|
|
1731
1774
|
const clearUpdates = await this.decodeRemoteUpdateItems(updates);
|
|
1732
|
-
if (clearUpdates.length > 0)
|
|
1775
|
+
if (clearUpdates.length > 0) {
|
|
1776
|
+
const outcome = normalizeApplyReturn(await this.adapter.applyRemoteUpdates(clearUpdates, nextVersion));
|
|
1777
|
+
nextVersion = outcome.version;
|
|
1778
|
+
complete = outcome.complete;
|
|
1779
|
+
unresolved = outcome.unresolved ?? [];
|
|
1780
|
+
}
|
|
1733
1781
|
return await this.finalizeCursor({
|
|
1734
1782
|
...cursor,
|
|
1735
1783
|
nextOffset,
|
|
1736
1784
|
serverLowerBoundVersion: nextVersion
|
|
1737
|
-
}, "bootstrap");
|
|
1785
|
+
}, "bootstrap", complete, unresolved, upToDate);
|
|
1738
1786
|
}
|
|
1739
|
-
async applyBootstrapItems(cursor, items, nextOffset) {
|
|
1787
|
+
async applyBootstrapItems(cursor, items, nextOffset, upToDate = false) {
|
|
1740
1788
|
const snapshot = items[0] ?? new Uint8Array();
|
|
1741
|
-
return await this.applyBootstrapPayload(cursor, snapshot, items.slice(1), nextOffset);
|
|
1789
|
+
return await this.applyBootstrapPayload(cursor, snapshot, items.slice(1), nextOffset, upToDate);
|
|
1742
1790
|
}
|
|
1743
1791
|
async applyRemoteItems(cursor, items, nextOffset) {
|
|
1744
1792
|
return await this.applyRemotePayload(cursor, [...items], nextOffset, "remote", void 0);
|
|
1745
1793
|
}
|
|
1746
1794
|
async applyCatchupResult(cursor, result, onRemoteVersionApplied) {
|
|
1747
|
-
return await this.applyRemotePayload(cursor, result.updates, result.nextOffset, "remote", onRemoteVersionApplied);
|
|
1795
|
+
return await this.applyRemotePayload(cursor, result.updates, result.nextOffset, "remote", onRemoteVersionApplied, result.upToDate);
|
|
1748
1796
|
}
|
|
1749
1797
|
async applyLiveResult(cursor, requestOffset, result, onRemoteVersionApplied) {
|
|
1750
1798
|
const previousVersion = cursor.serverLowerBoundVersion;
|
|
1751
1799
|
let nextVersion = previousVersion;
|
|
1800
|
+
let complete = this.isCursorComplete(cursor);
|
|
1801
|
+
let unresolved = this.incompleteCursors.get(cursor) ?? [];
|
|
1752
1802
|
const clearUpdates = await this.decodeRemoteUpdateItems(result.updates);
|
|
1753
1803
|
if (clearUpdates.length > 0) {
|
|
1754
|
-
|
|
1804
|
+
const outcome = normalizeApplyReturn(await this.adapter.applyRemoteUpdates(clearUpdates, nextVersion));
|
|
1805
|
+
nextVersion = outcome.version;
|
|
1806
|
+
complete = outcome.complete;
|
|
1807
|
+
unresolved = outcome.unresolved ?? [];
|
|
1755
1808
|
await onRemoteVersionApplied?.(previousVersion, nextVersion);
|
|
1756
1809
|
}
|
|
1757
1810
|
return await this.finalizeCursor({
|
|
1758
1811
|
...cursor,
|
|
1759
1812
|
nextOffset: resolveNextOffsetAfterConcurrentAdvance(requestOffset, cursor.nextOffset, result.nextOffset),
|
|
1760
1813
|
serverLowerBoundVersion: nextVersion
|
|
1761
|
-
}, "remote");
|
|
1814
|
+
}, "remote", complete, unresolved, result.upToDate === true);
|
|
1762
1815
|
}
|
|
1763
|
-
async applyRemotePayload(cursor, updates, nextOffset, source, onRemoteVersionApplied) {
|
|
1816
|
+
async applyRemotePayload(cursor, updates, nextOffset, source, onRemoteVersionApplied, upToDate = false) {
|
|
1764
1817
|
const previousVersion = cursor.serverLowerBoundVersion;
|
|
1765
1818
|
let nextVersion = previousVersion;
|
|
1819
|
+
let complete = this.isCursorComplete(cursor);
|
|
1820
|
+
let unresolved = this.incompleteCursors.get(cursor) ?? [];
|
|
1766
1821
|
const clearUpdates = await this.decodeRemoteUpdateItems(updates);
|
|
1767
1822
|
if (clearUpdates.length > 0) {
|
|
1768
|
-
|
|
1823
|
+
const outcome = normalizeApplyReturn(await this.adapter.applyRemoteUpdates(clearUpdates, nextVersion));
|
|
1824
|
+
nextVersion = outcome.version;
|
|
1825
|
+
complete = outcome.complete;
|
|
1826
|
+
unresolved = outcome.unresolved ?? [];
|
|
1769
1827
|
await onRemoteVersionApplied?.(previousVersion, nextVersion);
|
|
1770
1828
|
}
|
|
1771
1829
|
return await this.finalizeCursor({
|
|
1772
1830
|
...cursor,
|
|
1773
1831
|
nextOffset,
|
|
1774
1832
|
serverLowerBoundVersion: nextVersion
|
|
1775
|
-
}, source);
|
|
1833
|
+
}, source, complete, unresolved, upToDate);
|
|
1776
1834
|
}
|
|
1777
|
-
async finalizeCursor(cursor, source) {
|
|
1835
|
+
async finalizeCursor(cursor, source, complete, unresolved, upToDate) {
|
|
1836
|
+
if (!complete) {
|
|
1837
|
+
if (upToDate) throw new CrdtApplyIncompleteError(unresolved);
|
|
1838
|
+
this.incompleteCursors.set(cursor, unresolved);
|
|
1839
|
+
return cursor;
|
|
1840
|
+
}
|
|
1778
1841
|
return await this.saveRemoteCursor(cursor, source);
|
|
1779
1842
|
}
|
|
1780
1843
|
async decodeRemoteUpdateItems(items) {
|
|
@@ -1807,6 +1870,7 @@ var TransportSnapshotManager = class {
|
|
|
1807
1870
|
logDebug;
|
|
1808
1871
|
logError;
|
|
1809
1872
|
isCurrentState;
|
|
1873
|
+
isCursorComplete;
|
|
1810
1874
|
constructor(options) {
|
|
1811
1875
|
this.adapter = options.adapter;
|
|
1812
1876
|
this.config = options.config;
|
|
@@ -1817,6 +1881,7 @@ var TransportSnapshotManager = class {
|
|
|
1817
1881
|
this.logDebug = options.logDebug;
|
|
1818
1882
|
this.logError = options.logError;
|
|
1819
1883
|
this.isCurrentState = options.isCurrentState;
|
|
1884
|
+
this.isCursorComplete = options.isCursorComplete;
|
|
1820
1885
|
}
|
|
1821
1886
|
dispose(state) {
|
|
1822
1887
|
this.clearDebounceTimer(state.snapshot);
|
|
@@ -1866,6 +1931,10 @@ var TransportSnapshotManager = class {
|
|
|
1866
1931
|
async maybeUpload(state) {
|
|
1867
1932
|
const config = await this.resolveUploadConfig(state);
|
|
1868
1933
|
if (config == null) return;
|
|
1934
|
+
if (!this.isCursorComplete(state.cursor)) {
|
|
1935
|
+
this.logDebug("skip snapshot upload: current cursor is incomplete", { offset: state.cursor.nextOffset });
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1869
1938
|
const tailBytes = parseSnapshotByteOffset(state.cursor.nextOffset);
|
|
1870
1939
|
if (tailBytes == null) {
|
|
1871
1940
|
this.logDebug("skip snapshot upload: non-numeric offset token", { offset: state.cursor.nextOffset });
|
|
@@ -1980,6 +2049,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1980
2049
|
nextProducerSeq = 0;
|
|
1981
2050
|
frozenLocalAppend;
|
|
1982
2051
|
committedPendingCount;
|
|
2052
|
+
committedLocalStreamCursor;
|
|
1983
2053
|
pendingWriteOnly = new Deque();
|
|
1984
2054
|
writeOnlyCursor;
|
|
1985
2055
|
unsubscribeWriteOnlyLocal;
|
|
@@ -2037,7 +2107,8 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2037
2107
|
enqueueExclusive: (task) => this.enqueueExclusive(task),
|
|
2038
2108
|
logDebug: (message, detail) => this.logDebug(message, detail),
|
|
2039
2109
|
logError: (message, error, detail) => this.logError(message, error, detail),
|
|
2040
|
-
isCurrentState: (state) => this.joinState === state
|
|
2110
|
+
isCurrentState: (state) => this.joinState === state,
|
|
2111
|
+
isCursorComplete: (cursor) => this.cursorManager.isCursorComplete(cursor)
|
|
2041
2112
|
});
|
|
2042
2113
|
this.writeOnlyCursor = this.createInitialCursor();
|
|
2043
2114
|
this.unsubscribeWriteOnlyLocal = this.adapter.subscribeLocalUpdates((batch) => {
|
|
@@ -2191,6 +2262,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2191
2262
|
this.nextProducerSeq = 0;
|
|
2192
2263
|
this.frozenLocalAppend = void 0;
|
|
2193
2264
|
this.committedPendingCount = void 0;
|
|
2265
|
+
this.committedLocalStreamCursor = void 0;
|
|
2194
2266
|
return deleted;
|
|
2195
2267
|
}) }
|
|
2196
2268
|
};
|
|
@@ -2426,10 +2498,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2426
2498
|
async syncOnce() {
|
|
2427
2499
|
let cursor = (await this.finalizeDirectLocalAppendIfNeeded(await this.resolveInitialRemoteState())).cursor;
|
|
2428
2500
|
const localBatch = this.exportUpdates(cursor.serverLowerBoundVersion);
|
|
2429
|
-
if (localBatch.batch != null)
|
|
2430
|
-
cursor = await this.appendLocalBatch(cursor, localBatch.batch);
|
|
2431
|
-
cursor = (await this.catchup(cursor)).cursor;
|
|
2432
|
-
}
|
|
2501
|
+
if (localBatch.batch != null) cursor = (await this.appendLocalBatchDurably(cursor, localBatch.batch)).cursor;
|
|
2433
2502
|
return cursor;
|
|
2434
2503
|
}
|
|
2435
2504
|
async performInitialJoinSync(preferredLiveMode) {
|
|
@@ -2441,10 +2510,9 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2441
2510
|
const exportedLocal = this.exportUpdates(localExportRefVersion);
|
|
2442
2511
|
localExportRefVersion = exportedLocal.nextRefVersion;
|
|
2443
2512
|
if (exportedLocal.batch != null) {
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
streamCursor = caughtUp.streamCursor;
|
|
2513
|
+
const committed = await this.appendLocalBatchDurably(cursor, exportedLocal.batch, void 0, "direct", streamCursor);
|
|
2514
|
+
cursor = committed.cursor;
|
|
2515
|
+
streamCursor = committed.streamCursor;
|
|
2448
2516
|
}
|
|
2449
2517
|
localExportRefVersion = cursor.serverLowerBoundVersion;
|
|
2450
2518
|
return {
|
|
@@ -2478,7 +2546,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2478
2546
|
async bootstrapState(cursor, options = {}) {
|
|
2479
2547
|
const cursorManager = options.cursorManager ?? this.cursorManager;
|
|
2480
2548
|
const result = await this.client.bootstrap();
|
|
2481
|
-
let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset);
|
|
2549
|
+
let saved = await cursorManager.applyBootstrapPayload(cursor, result.snapshot, result.updates, result.nextOffset, result.upToDate);
|
|
2482
2550
|
let bootstrapCursor = result.streamCursor;
|
|
2483
2551
|
if (!result.upToDate) {
|
|
2484
2552
|
const caughtUp = await this.catchupOrGone(saved, {
|
|
@@ -2521,7 +2589,16 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2521
2589
|
}
|
|
2522
2590
|
const result = response.value;
|
|
2523
2591
|
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`);
|
|
2524
|
-
|
|
2592
|
+
try {
|
|
2593
|
+
current = await cursorManager.applyCatchupResult(current, result, options.onRemoteVersionApplied);
|
|
2594
|
+
} catch (error) {
|
|
2595
|
+
if (error instanceof CrdtApplyIncompleteError && result.upToDate) return await this.bootstrapState({
|
|
2596
|
+
...current,
|
|
2597
|
+
nextOffset: "-1",
|
|
2598
|
+
serverLowerBoundVersion: this.adapter.emptyVersion()
|
|
2599
|
+
}, { cursorManager });
|
|
2600
|
+
throw error;
|
|
2601
|
+
}
|
|
2525
2602
|
currentStreamCursor = result.streamCursor;
|
|
2526
2603
|
if (result.upToDate) return {
|
|
2527
2604
|
cursor: current,
|
|
@@ -2529,20 +2606,64 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2529
2606
|
};
|
|
2530
2607
|
}
|
|
2531
2608
|
}
|
|
2532
|
-
async
|
|
2609
|
+
async recoverLiveIncompleteRemoteApply(state) {
|
|
2610
|
+
try {
|
|
2611
|
+
const bootstrapped = await this.bootstrapState({
|
|
2612
|
+
...state.cursor,
|
|
2613
|
+
nextOffset: "-1",
|
|
2614
|
+
serverLowerBoundVersion: this.adapter.emptyVersion()
|
|
2615
|
+
});
|
|
2616
|
+
state.cursor = bootstrapped.cursor;
|
|
2617
|
+
state.streamCursor = bootstrapped.streamCursor;
|
|
2618
|
+
state.retryAttempt = 0;
|
|
2619
|
+
state.lastError = void 0;
|
|
2620
|
+
this.setReadSubStatus(state, "ok");
|
|
2621
|
+
if (state.pendingLocal.length > 0) this.flushPendingLocal(state);
|
|
2622
|
+
return true;
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
const classified = classifyFetchError(error);
|
|
2625
|
+
this.logError("live incomplete apply recovery failed", error, {
|
|
2626
|
+
streamUrl: this.streamUrl,
|
|
2627
|
+
code: classified.code
|
|
2628
|
+
});
|
|
2629
|
+
state.lastError = toTransportError(error);
|
|
2630
|
+
this.setReadSubStatus(state, classified.retriable ? "disconnected" : "error");
|
|
2631
|
+
return false;
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
async appendLocalBatch(cursor, batch, pendingCount, streamCursor) {
|
|
2533
2635
|
const source = pendingCount == null ? "direct" : "pending";
|
|
2534
|
-
const committed = await this.appendLocalBatchDurably(cursor, batch, pendingCount, source);
|
|
2636
|
+
const committed = await this.appendLocalBatchDurably(cursor, batch, pendingCount, source, streamCursor);
|
|
2535
2637
|
this.committedPendingCount = committed.pendingCount;
|
|
2638
|
+
this.committedLocalStreamCursor = committed.streamCursor;
|
|
2536
2639
|
return committed.cursor;
|
|
2537
2640
|
}
|
|
2538
|
-
async appendLocalBatchDurably(cursor, batch, pendingCount, source = pendingCount == null ? "direct" : "pending") {
|
|
2641
|
+
async appendLocalBatchDurably(cursor, batch, pendingCount, source = pendingCount == null ? "direct" : "pending", streamCursor) {
|
|
2539
2642
|
const frozen = await this.getOrCreateFrozenLocalAppend(cursor, batch, pendingCount, source);
|
|
2540
2643
|
const appended = await this.appendFrozenLocalBatchRemote(frozen);
|
|
2541
|
-
const
|
|
2644
|
+
const caughtUp = await this.catchupLocalAppend(frozen, streamCursor);
|
|
2542
2645
|
this.commitProducerAck(appended.producerAck);
|
|
2543
2646
|
return {
|
|
2544
|
-
cursor:
|
|
2545
|
-
pendingCount: frozen.pendingCount
|
|
2647
|
+
cursor: caughtUp.cursor,
|
|
2648
|
+
pendingCount: frozen.pendingCount,
|
|
2649
|
+
streamCursor: caughtUp.streamCursor
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
localAppendReadCursor(frozen) {
|
|
2653
|
+
return {
|
|
2654
|
+
...frozen.baseCursor,
|
|
2655
|
+
serverLowerBoundVersion: this.adapter.mergeVersions(frozen.baseCursor.serverLowerBoundVersion, frozen.batch.version)
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
async catchupLocalAppend(frozen, streamCursor) {
|
|
2659
|
+
const caughtUp = await this.catchupOrGone(this.localAppendReadCursor(frozen), {
|
|
2660
|
+
streamCursor,
|
|
2661
|
+
cursorManager: this.volatileCursorManager
|
|
2662
|
+
});
|
|
2663
|
+
if ("kind" in caughtUp) throw new Error("local append catch-up unexpectedly returned gone");
|
|
2664
|
+
return {
|
|
2665
|
+
cursor: await this.saveRemoteCursor(caughtUp.cursor, "local"),
|
|
2666
|
+
streamCursor: caughtUp.streamCursor
|
|
2546
2667
|
};
|
|
2547
2668
|
}
|
|
2548
2669
|
async appendLocalBatchRemote(cursor, batch, pendingCount, source = pendingCount == null ? "direct" : "pending") {
|
|
@@ -2616,9 +2737,9 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2616
2737
|
const frozen = this.frozenLocalAppend;
|
|
2617
2738
|
if (frozen == null || frozen.pendingCount != null) return state;
|
|
2618
2739
|
const appended = await this.appendFrozenLocalBatchRemote(frozen);
|
|
2619
|
-
const
|
|
2740
|
+
const caughtUp = await this.catchupLocalAppend(frozen, state.streamCursor);
|
|
2620
2741
|
this.commitProducerAck(appended.producerAck);
|
|
2621
|
-
return
|
|
2742
|
+
return caughtUp;
|
|
2622
2743
|
}
|
|
2623
2744
|
createCurrentProducerSession() {
|
|
2624
2745
|
return {
|
|
@@ -2643,6 +2764,11 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2643
2764
|
this.committedPendingCount = void 0;
|
|
2644
2765
|
return count;
|
|
2645
2766
|
}
|
|
2767
|
+
consumeCommittedLocalStreamCursor() {
|
|
2768
|
+
const streamCursor = this.committedLocalStreamCursor;
|
|
2769
|
+
this.committedLocalStreamCursor = void 0;
|
|
2770
|
+
return streamCursor;
|
|
2771
|
+
}
|
|
2646
2772
|
/**
|
|
2647
2773
|
* Ensures the target stream exists, creating it if necessary.
|
|
2648
2774
|
* Tolerates 409 Conflict (stream already created concurrently).
|
|
@@ -2779,6 +2905,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2779
2905
|
if (state.closed) return;
|
|
2780
2906
|
state.retryAttempt = 0;
|
|
2781
2907
|
this.setReadSubStatus(state, "ok");
|
|
2908
|
+
if (state.pendingLocal.length > 0) this.flushPendingLocal(state);
|
|
2782
2909
|
await sleep(0);
|
|
2783
2910
|
continue;
|
|
2784
2911
|
}
|
|
@@ -2787,12 +2914,13 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2787
2914
|
let flushChain = Promise.resolve();
|
|
2788
2915
|
resetInactivity();
|
|
2789
2916
|
const sseStartedAtMs = Date.now();
|
|
2790
|
-
const sseResult = await this.client.readFromOffsetSseItems(sseRequestOffset, state.streamCursor, (updates, nextOffset,
|
|
2917
|
+
const sseResult = await this.client.readFromOffsetSseItems(sseRequestOffset, state.streamCursor, (updates, nextOffset, upToDate, streamCursor) => {
|
|
2791
2918
|
if (capturedController?.signal.aborted) return "stop";
|
|
2792
2919
|
resetInactivity();
|
|
2793
2920
|
pendingRemote.push({
|
|
2794
2921
|
updates,
|
|
2795
2922
|
nextOffset,
|
|
2923
|
+
upToDate,
|
|
2796
2924
|
streamCursor
|
|
2797
2925
|
});
|
|
2798
2926
|
flushChain = flushChain.then(async () => {
|
|
@@ -2804,7 +2932,8 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2804
2932
|
if (state.closed) return;
|
|
2805
2933
|
state.cursor = await this.cursorManager.applyLiveResult(state.cursor, sseRequestOffset, {
|
|
2806
2934
|
nextOffset: lastEvent.nextOffset,
|
|
2807
|
-
updates: allUpdates
|
|
2935
|
+
updates: allUpdates,
|
|
2936
|
+
upToDate: lastEvent.upToDate
|
|
2808
2937
|
}, (previousVersion, nextVersion) => {
|
|
2809
2938
|
this.snapshotManager.observeRemoteVersion(state, previousVersion, nextVersion);
|
|
2810
2939
|
});
|
|
@@ -2814,6 +2943,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2814
2943
|
}
|
|
2815
2944
|
});
|
|
2816
2945
|
this.setReadSubStatus(state, "ok");
|
|
2946
|
+
if (state.pendingLocal.length > 0) this.flushPendingLocal(state);
|
|
2817
2947
|
}).catch((flushError) => {
|
|
2818
2948
|
this.logError("sse flush chain error", flushError, { streamUrl: this.streamUrl });
|
|
2819
2949
|
capturedController?.abort(flushError);
|
|
@@ -2857,6 +2987,11 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2857
2987
|
} catch (error) {
|
|
2858
2988
|
clearInactivity();
|
|
2859
2989
|
if (state.closed) return;
|
|
2990
|
+
if (error instanceof CrdtApplyIncompleteError) {
|
|
2991
|
+
if (capturedController?.signal.aborted && state.requestController === capturedController) state.requestController = new AbortController();
|
|
2992
|
+
if (await this.recoverLiveIncompleteRemoteApply(state)) continue;
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2860
2995
|
if (capturedController?.signal.aborted) {
|
|
2861
2996
|
if (inactivityTriggered) {
|
|
2862
2997
|
if (state.requestController === capturedController) state.requestController = new AbortController();
|
|
@@ -2883,6 +3018,11 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2883
3018
|
}
|
|
2884
3019
|
const abortReason = capturedController.signal.reason;
|
|
2885
3020
|
const flushError = abortReason instanceof Error ? abortReason : error;
|
|
3021
|
+
if (flushError instanceof CrdtApplyIncompleteError) {
|
|
3022
|
+
if (state.requestController === capturedController) state.requestController = new AbortController();
|
|
3023
|
+
if (await this.recoverLiveIncompleteRemoteApply(state)) continue;
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
2886
3026
|
if (flushError instanceof Error && !(flushError instanceof DOMException && flushError.name === "AbortError")) {
|
|
2887
3027
|
const flushClassified = classifyFetchError(flushError);
|
|
2888
3028
|
if (!flushClassified.retriable || isAuthOrProtocolError(flushError)) {
|
|
@@ -2969,6 +3109,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2969
3109
|
}
|
|
2970
3110
|
async flushPendingLocal(state) {
|
|
2971
3111
|
if (state.flushingLocal || state.closed) return;
|
|
3112
|
+
if (!this.cursorManager.isCursorComplete(state.cursor)) return;
|
|
2972
3113
|
state.flushingLocal = true;
|
|
2973
3114
|
try {
|
|
2974
3115
|
while (!state.closed) {
|
|
@@ -2976,7 +3117,9 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2976
3117
|
if (drained == null) return;
|
|
2977
3118
|
let attempts = 0;
|
|
2978
3119
|
while (!state.closed) try {
|
|
2979
|
-
state.cursor = await this.enqueueExclusive(async () => this.appendLocalBatch(state.cursor, drained.batch, drained.count));
|
|
3120
|
+
state.cursor = await this.enqueueExclusive(async () => this.appendLocalBatch(state.cursor, drained.batch, drained.count, state.streamCursor));
|
|
3121
|
+
const committedStreamCursor = this.consumeCommittedLocalStreamCursor();
|
|
3122
|
+
if (committedStreamCursor != null) state.streamCursor = committedStreamCursor;
|
|
2980
3123
|
const committedCount = this.consumeCommittedPendingCount() ?? drained.count;
|
|
2981
3124
|
for (let i = 0; i < committedCount; i += 1) state.pendingLocal.popFront();
|
|
2982
3125
|
this.notifySyncWaiters(state, committedCount);
|
|
@@ -3135,4 +3278,4 @@ function createStreamUrl(input) {
|
|
|
3135
3278
|
//#endregion
|
|
3136
3279
|
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 };
|
|
3137
3280
|
|
|
3138
|
-
//# sourceMappingURL=stream-id-
|
|
3281
|
+
//# sourceMappingURL=stream-id-BjmGvObf.js.map
|