@loro-dev/streams-crdt 0.1.2 → 0.2.1
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 +26 -3
- package/dist/flock.d.ts +2 -2
- package/dist/flock.js +4 -3
- package/dist/flock.js.map +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-BfuQIfcB.d.ts → stream-id-BxGVitUa.d.ts} +30 -9
- package/dist/{stream-id-YOFZ4I6Y.js → stream-id-kM9JM1F7.js} +78 -244
- package/dist/stream-id-kM9JM1F7.js.map +1 -0
- package/package.json +1 -1
- package/dist/stream-id-YOFZ4I6Y.js.map +0 -1
|
@@ -360,24 +360,28 @@ function parseHeaderU64(value) {
|
|
|
360
360
|
return parsed;
|
|
361
361
|
}
|
|
362
362
|
function decodeBase64Bytes(encoded) {
|
|
363
|
-
|
|
363
|
+
const normalized = encoded.replace(/[\r\n]/g, "");
|
|
364
|
+
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(normalized, "base64"));
|
|
364
365
|
if (typeof atob === "function") {
|
|
365
|
-
const binary = atob(
|
|
366
|
+
const binary = atob(normalized);
|
|
366
367
|
const out = new Uint8Array(binary.length);
|
|
367
368
|
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
|
|
368
369
|
return out;
|
|
369
370
|
}
|
|
370
371
|
throw new Error("base64 decoding is unavailable");
|
|
371
372
|
}
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
373
|
+
function normalizeSseDataEncoding(dataEncoding) {
|
|
374
|
+
const normalized = dataEncoding?.trim().toLowerCase();
|
|
375
|
+
if (normalized == null || normalized.length === 0) return;
|
|
376
|
+
if (normalized === "base64") return "base64";
|
|
377
|
+
throw new ProtocolError(`unsupported stream-sse-data-encoding '${dataEncoding}'`);
|
|
378
|
+
}
|
|
379
|
+
function decodeSseRawPayload(data, dataEncoding) {
|
|
380
|
+
if (dataEncoding === "base64") return decodeBase64Bytes(data);
|
|
377
381
|
return new TextEncoder().encode(data);
|
|
378
382
|
}
|
|
379
|
-
function decodeSseItemPayloads(data) {
|
|
380
|
-
const payload = decodeSseRawPayload(data);
|
|
383
|
+
function decodeSseItemPayloads(data, dataEncoding) {
|
|
384
|
+
const payload = decodeSseRawPayload(data, normalizeSseDataEncoding(dataEncoding));
|
|
381
385
|
if (payload.byteLength === 0) return [payload];
|
|
382
386
|
try {
|
|
383
387
|
return decodeItems(payload);
|
|
@@ -571,8 +575,8 @@ function normalizeAuthProvider(auth) {
|
|
|
571
575
|
const token = auth.trim();
|
|
572
576
|
return async () => token.length === 0 ? void 0 : token;
|
|
573
577
|
}
|
|
574
|
-
return async () => {
|
|
575
|
-
const token = (await auth())?.trim() ?? "";
|
|
578
|
+
return async (context) => {
|
|
579
|
+
const token = (await auth(context))?.trim() ?? "";
|
|
576
580
|
return token.length === 0 ? void 0 : token;
|
|
577
581
|
};
|
|
578
582
|
}
|
|
@@ -650,6 +654,7 @@ var StreamsTransportClient = class {
|
|
|
650
654
|
authProvider;
|
|
651
655
|
fetchImpl;
|
|
652
656
|
reconnectConfig;
|
|
657
|
+
authRefreshInFlight;
|
|
653
658
|
constructor(options) {
|
|
654
659
|
this.streamUrl = options.streamUrl;
|
|
655
660
|
this.adapter = options.adapter;
|
|
@@ -659,21 +664,18 @@ var StreamsTransportClient = class {
|
|
|
659
664
|
}
|
|
660
665
|
async createStreamRequest() {
|
|
661
666
|
const adapterHeaders = this.adapter.getCreateStreamHeaders ? await this.adapter.getCreateStreamHeaders() : void 0;
|
|
662
|
-
const response = await this.
|
|
667
|
+
const response = await this.fetchAuthorized(this.streamUrl, {
|
|
663
668
|
method: "PUT",
|
|
664
|
-
headers:
|
|
669
|
+
headers: {
|
|
665
670
|
"Content-Type": BINARY_CONTENT_TYPE,
|
|
666
671
|
...adapterHeaders
|
|
667
|
-
}
|
|
672
|
+
}
|
|
668
673
|
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
669
674
|
if (response.status === 200 || response.status === 201) return response.status === 201;
|
|
670
675
|
throw new HttpError(`create stream failed with status ${response.status}`, response.status);
|
|
671
676
|
}
|
|
672
677
|
async deleteStreamRequest() {
|
|
673
|
-
const response = await this.
|
|
674
|
-
method: "DELETE",
|
|
675
|
-
headers: await this.resolveHeaders()
|
|
676
|
-
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
678
|
+
const response = await this.fetchAuthorized(this.streamUrl, { method: "DELETE" }, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
677
679
|
if (response.status === 200 || response.status === 204) return true;
|
|
678
680
|
if (response.status === 404) return false;
|
|
679
681
|
throw new HttpError(`delete stream failed with status ${response.status}`, response.status);
|
|
@@ -685,49 +687,14 @@ var StreamsTransportClient = class {
|
|
|
685
687
|
});
|
|
686
688
|
return this.readItemsSse(withCursorQuery(`${this.streamUrl}?${query.toString()}`, cursor), offset, onControl, onOpen, signal);
|
|
687
689
|
}
|
|
688
|
-
async openBootstrapSseSession(signal) {
|
|
689
|
-
const query = new URLSearchParams({ live: "sse" });
|
|
690
|
-
return await this.openItemsSseSession(`${this.streamUrl}/bootstrap?${query.toString()}`, "-1", void 0, signal);
|
|
691
|
-
}
|
|
692
|
-
async readBootstrapLongPoll(cursor, signal) {
|
|
693
|
-
const query = new URLSearchParams({ live: "long-poll" });
|
|
694
|
-
if (cursor != null && cursor.length > 0) query.set("cursor", cursor);
|
|
695
|
-
const response = await this.fetchWithTimeout(`${this.streamUrl}/bootstrap?${query.toString()}`, {
|
|
696
|
-
method: "GET",
|
|
697
|
-
headers: await this.resolveHeaders(),
|
|
698
|
-
signal
|
|
699
|
-
}, this.reconnectConfig.pollTimeoutMs, "poll");
|
|
700
|
-
if (response.status === 410) return { kind: "gone" };
|
|
701
|
-
if (!response.ok) throw new HttpError(`bootstrap long-poll failed with status ${response.status}`, response.status);
|
|
702
|
-
const nextOffset = getHeader(response.headers, "stream-next-offset", "Stream-Next-Offset");
|
|
703
|
-
if (nextOffset == null) throw missingRequiredHeaderError("bootstrap", "Stream-Next-Offset", response.headers);
|
|
704
|
-
const upToDate = getHeader(response.headers, "stream-up-to-date", "Stream-Up-To-Date") === "true";
|
|
705
|
-
const streamCursor = getHeader(response.headers, "stream-cursor", "Stream-Cursor") ?? void 0;
|
|
706
|
-
const body = new Uint8Array(await response.arrayBuffer());
|
|
707
|
-
const boundary = extractBoundary(response.headers.get("Content-Type") ?? "");
|
|
708
|
-
let items;
|
|
709
|
-
if (boundary != null && body.byteLength > 0) items = decodeMultipartMixed(boundary, body).flatMap((part) => decodeMultipartPartItems(part.body));
|
|
710
|
-
else if (body.byteLength === 0) items = [];
|
|
711
|
-
else throw new ProtocolError("bootstrap long-poll returned a non-multipart body");
|
|
712
|
-
return {
|
|
713
|
-
kind: "ok",
|
|
714
|
-
value: {
|
|
715
|
-
nextOffset,
|
|
716
|
-
upToDate,
|
|
717
|
-
items,
|
|
718
|
-
streamCursor
|
|
719
|
-
}
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
690
|
async readFromOffset(offset, cursor, live, signal) {
|
|
723
691
|
const query = new URLSearchParams({ offset });
|
|
724
692
|
if (live != null) query.set("live", live);
|
|
725
693
|
if (cursor != null && cursor.length > 0) query.set("cursor", cursor);
|
|
726
694
|
const timeoutMs = live === "long-poll" ? this.reconnectConfig.pollTimeoutMs : this.reconnectConfig.connectTimeoutMs;
|
|
727
695
|
const timeoutType = live === "long-poll" ? "poll" : "connect";
|
|
728
|
-
const response = await this.
|
|
696
|
+
const response = await this.fetchAuthorized(`${this.streamUrl}?${query.toString()}`, {
|
|
729
697
|
method: "GET",
|
|
730
|
-
headers: await this.resolveHeaders(),
|
|
731
698
|
signal
|
|
732
699
|
}, timeoutMs, timeoutType);
|
|
733
700
|
if (response.status === 410) return { kind: "gone" };
|
|
@@ -748,10 +715,7 @@ var StreamsTransportClient = class {
|
|
|
748
715
|
};
|
|
749
716
|
}
|
|
750
717
|
async bootstrap() {
|
|
751
|
-
const response = await this.
|
|
752
|
-
method: "GET",
|
|
753
|
-
headers: await this.resolveHeaders()
|
|
754
|
-
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
718
|
+
const response = await this.fetchAuthorized(`${this.streamUrl}/bootstrap`, { method: "GET" }, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
755
719
|
if (!response.ok) throw new HttpError(`bootstrap failed with status ${response.status}`, response.status);
|
|
756
720
|
const nextOffset = getHeader(response.headers, "stream-next-offset", "Stream-Next-Offset");
|
|
757
721
|
if (nextOffset == null) throw missingRequiredHeaderError("bootstrap", "Stream-Next-Offset", response.headers);
|
|
@@ -779,35 +743,32 @@ var StreamsTransportClient = class {
|
|
|
779
743
|
};
|
|
780
744
|
}
|
|
781
745
|
async readSnapshotOffsetValue() {
|
|
782
|
-
const response = await this.
|
|
783
|
-
method: "HEAD",
|
|
784
|
-
headers: await this.resolveHeaders()
|
|
785
|
-
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
746
|
+
const response = await this.fetchAuthorized(this.streamUrl, { method: "HEAD" }, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
786
747
|
if (response.status === 404) return null;
|
|
787
748
|
if (!response.ok) throw new HttpError(`head snapshot offset failed with status ${response.status}`, response.status);
|
|
788
749
|
const snapshotOffset = getHeader(response.headers, "stream-snapshot-offset", "Stream-Snapshot-Offset");
|
|
789
750
|
return snapshotOffset == null || snapshotOffset.length === 0 ? null : snapshotOffset;
|
|
790
751
|
}
|
|
791
752
|
async putSnapshotAt(snapshotOffset, snapshot) {
|
|
792
|
-
const response = await this.
|
|
753
|
+
const response = await this.fetchAuthorized(`${this.streamUrl}/snapshot/${encodeURIComponent(snapshotOffset)}`, {
|
|
793
754
|
method: "PUT",
|
|
794
|
-
headers:
|
|
755
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
795
756
|
body: bytesToArrayBuffer(snapshot)
|
|
796
757
|
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
797
758
|
if (response.status === 200 || response.status === 204) return;
|
|
798
759
|
throw new HttpError(`snapshot upload failed with status ${response.status}`, response.status);
|
|
799
760
|
}
|
|
800
761
|
async appendUpdates(updates, producerSession) {
|
|
801
|
-
const response = await this.
|
|
762
|
+
const response = await this.fetchAuthorized(this.streamUrl, {
|
|
802
763
|
method: "POST",
|
|
803
|
-
headers:
|
|
764
|
+
headers: {
|
|
804
765
|
"Content-Type": BINARY_CONTENT_TYPE,
|
|
805
766
|
"Producer-Id": producerSession.producerId,
|
|
806
767
|
"Producer-Epoch": producerSession.producerEpoch.toString(),
|
|
807
768
|
"Producer-Seq": producerSession.nextProducerSeq.toString()
|
|
808
|
-
}
|
|
769
|
+
},
|
|
809
770
|
body: bytesToArrayBuffer(encodeItems(updates))
|
|
810
|
-
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
771
|
+
}, this.reconnectConfig.connectTimeoutMs, "connect", { shouldRetryAuthFailure: (response) => response.status === 401 || response.status === 403 && parseHeaderU64(getHeader(response.headers, "producer-epoch", "Producer-Epoch")) == null });
|
|
811
772
|
if (response.status === 200 || response.status === 204) {
|
|
812
773
|
const nextOffset = getHeader(response.headers, "stream-next-offset", "Stream-Next-Offset");
|
|
813
774
|
if (nextOffset == null) throw missingRequiredHeaderError("append", "Stream-Next-Offset", response.headers);
|
|
@@ -834,10 +795,42 @@ var StreamsTransportClient = class {
|
|
|
834
795
|
}
|
|
835
796
|
throw new HttpError(`append failed with status ${response.status}`, response.status);
|
|
836
797
|
}
|
|
837
|
-
async
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
798
|
+
async resolveToken(context) {
|
|
799
|
+
if (this.authProvider == null) return;
|
|
800
|
+
return await this.authProvider(context);
|
|
801
|
+
}
|
|
802
|
+
async refreshTokenAfterAuthFailure(status, previousToken) {
|
|
803
|
+
if (this.authProvider == null) return;
|
|
804
|
+
if (this.authRefreshInFlight == null) this.authRefreshInFlight = this.resolveToken({
|
|
805
|
+
reason: "unauthorized",
|
|
806
|
+
status,
|
|
807
|
+
previousToken
|
|
808
|
+
}).finally(() => {
|
|
809
|
+
this.authRefreshInFlight = void 0;
|
|
810
|
+
});
|
|
811
|
+
return await this.authRefreshInFlight;
|
|
812
|
+
}
|
|
813
|
+
async fetchAuthorized(input, init, timeoutMs, timeoutType, options) {
|
|
814
|
+
const firstToken = await this.resolveToken({ reason: "request" });
|
|
815
|
+
const firstResponse = await this.fetchWithTimeout(input, {
|
|
816
|
+
...init,
|
|
817
|
+
headers: this.attachAuthorizationHeader(init.headers, firstToken)
|
|
818
|
+
}, timeoutMs, timeoutType);
|
|
819
|
+
const shouldRetryAuthFailure = options?.shouldRetryAuthFailure ?? ((response) => response.status === 401 || response.status === 403);
|
|
820
|
+
if (this.authProvider == null || !shouldRetryAuthFailure(firstResponse)) return firstResponse;
|
|
821
|
+
const refreshedToken = await this.refreshTokenAfterAuthFailure(firstResponse.status, firstToken);
|
|
822
|
+
return await this.fetchWithTimeout(input, {
|
|
823
|
+
...init,
|
|
824
|
+
headers: this.attachAuthorizationHeader(init.headers, refreshedToken)
|
|
825
|
+
}, timeoutMs, timeoutType);
|
|
826
|
+
}
|
|
827
|
+
attachAuthorizationHeader(base, token) {
|
|
828
|
+
const headers = new Headers(base);
|
|
829
|
+
if (token == null || token.length === 0) {
|
|
830
|
+
headers.delete("Authorization");
|
|
831
|
+
return headers;
|
|
832
|
+
}
|
|
833
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
841
834
|
return headers;
|
|
842
835
|
}
|
|
843
836
|
async fetchWithTimeout(input, init, timeoutMs, timeoutType) {
|
|
@@ -871,9 +864,8 @@ var StreamsTransportClient = class {
|
|
|
871
864
|
};
|
|
872
865
|
}
|
|
873
866
|
async openItemsSseSession(url, initialOffset, onOpen, signal) {
|
|
874
|
-
const response = await this.
|
|
867
|
+
const response = await this.fetchAuthorized(url, {
|
|
875
868
|
method: "GET",
|
|
876
|
-
headers: await this.resolveHeaders(),
|
|
877
869
|
signal
|
|
878
870
|
}, this.reconnectConfig.connectTimeoutMs, "connect");
|
|
879
871
|
if (response.status === 410) return { kind: "gone" };
|
|
@@ -885,6 +877,7 @@ var StreamsTransportClient = class {
|
|
|
885
877
|
}
|
|
886
878
|
if (!response.ok) throw new HttpError(`sse read failed with status ${response.status}`, response.status);
|
|
887
879
|
if (!(response.headers.get("Content-Type") ?? "").toLowerCase().includes("text/event-stream")) throw new ProtocolError("sse read response must use text/event-stream");
|
|
880
|
+
const sseDataEncoding = response.headers.get("stream-sse-data-encoding");
|
|
888
881
|
onOpen?.();
|
|
889
882
|
if (response.body == null) return {
|
|
890
883
|
kind: "ok",
|
|
@@ -934,7 +927,7 @@ var StreamsTransportClient = class {
|
|
|
934
927
|
if (eventType !== "control") return;
|
|
935
928
|
const control = parseSseControlData(data);
|
|
936
929
|
const updates = [];
|
|
937
|
-
for (const eventData of pendingDataEvents) for (const payload of decodeSseItemPayloads(eventData)) updates.push(payload);
|
|
930
|
+
for (const eventData of pendingDataEvents) for (const payload of decodeSseItemPayloads(eventData, sseDataEncoding)) updates.push(payload);
|
|
938
931
|
pendingDataEvents = [];
|
|
939
932
|
const handlerResult = await nextOnControl(updates, control.nextOffset, control.upToDate, control.streamCursor);
|
|
940
933
|
latestOffset = control.nextOffset;
|
|
@@ -1335,8 +1328,6 @@ function createJoinState(cursor, liveMode = "sse") {
|
|
|
1335
1328
|
async function disposeJoinState(state, options) {
|
|
1336
1329
|
state.closed = true;
|
|
1337
1330
|
options.snapshotManager.dispose(state);
|
|
1338
|
-
const liveSseSession = state.liveSseSession;
|
|
1339
|
-
state.liveSseSession = void 0;
|
|
1340
1331
|
const requestController = state.requestController;
|
|
1341
1332
|
state.requestController = void 0;
|
|
1342
1333
|
const unsubscribeLocal = state.unsubscribeLocal;
|
|
@@ -1344,7 +1335,6 @@ async function disposeJoinState(state, options) {
|
|
|
1344
1335
|
requestController?.abort();
|
|
1345
1336
|
unsubscribeLocal?.();
|
|
1346
1337
|
options.onDisposed?.();
|
|
1347
|
-
await liveSseSession?.cancel();
|
|
1348
1338
|
}
|
|
1349
1339
|
//#endregion
|
|
1350
1340
|
//#region src/transport-runtime.ts
|
|
@@ -1568,11 +1558,10 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1568
1558
|
async startJoin(state) {
|
|
1569
1559
|
try {
|
|
1570
1560
|
state.requestController ??= new AbortController();
|
|
1571
|
-
const joined = await this.performInitialJoinSync(state.liveMode
|
|
1561
|
+
const joined = await this.performInitialJoinSync(state.liveMode);
|
|
1572
1562
|
state.cursor = joined.cursor;
|
|
1573
1563
|
state.streamCursor = joined.streamCursor;
|
|
1574
1564
|
state.liveMode = joined.liveMode;
|
|
1575
|
-
state.liveSseSession = joined.liveSseSession;
|
|
1576
1565
|
const pendingLocal = this.exportUpdates(joined.localExportRefVersion);
|
|
1577
1566
|
const hasQueuedPreSubscribeBatch = pendingLocal.batch != null;
|
|
1578
1567
|
if (pendingLocal.batch != null) state.pendingLocal.pushBack(pendingLocal.batch);
|
|
@@ -1622,7 +1611,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1622
1611
|
}
|
|
1623
1612
|
return cursor;
|
|
1624
1613
|
}
|
|
1625
|
-
async performInitialJoinSync(preferredLiveMode
|
|
1614
|
+
async performInitialJoinSync(preferredLiveMode) {
|
|
1626
1615
|
const loaded = await this.remoteCursorStore.load(this.streamUrl);
|
|
1627
1616
|
let cursor = loaded ?? createInitialRemoteCursor({
|
|
1628
1617
|
streamUrl: this.streamUrl,
|
|
@@ -1632,15 +1621,9 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1632
1621
|
let localExportRefVersion = cursor.serverLowerBoundVersion;
|
|
1633
1622
|
let streamCursor;
|
|
1634
1623
|
let liveMode = preferredLiveMode;
|
|
1635
|
-
let liveSseSession;
|
|
1636
1624
|
try {
|
|
1637
|
-
if (loaded == null)
|
|
1638
|
-
|
|
1639
|
-
cursor = joined.cursor;
|
|
1640
|
-
streamCursor = joined.streamCursor;
|
|
1641
|
-
liveMode = joined.liveMode;
|
|
1642
|
-
liveSseSession = joined.liveSseSession;
|
|
1643
|
-
} else {
|
|
1625
|
+
if (loaded == null) cursor = await this.bootstrapIntoState(cursor);
|
|
1626
|
+
else {
|
|
1644
1627
|
const caughtUp = await this.catchup(cursor);
|
|
1645
1628
|
cursor = caughtUp.cursor;
|
|
1646
1629
|
streamCursor = caughtUp.streamCursor;
|
|
@@ -1648,38 +1631,26 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1648
1631
|
} catch (error) {
|
|
1649
1632
|
if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) throw error;
|
|
1650
1633
|
await this.createStreamRequest();
|
|
1651
|
-
|
|
1634
|
+
cursor = await this.bootstrapIntoState({
|
|
1652
1635
|
...cursor,
|
|
1653
1636
|
nextOffset: "-1",
|
|
1654
1637
|
serverLowerBoundVersion: this.adapter.emptyVersion()
|
|
1655
|
-
}
|
|
1656
|
-
cursor = joined.cursor;
|
|
1657
|
-
streamCursor = joined.streamCursor;
|
|
1658
|
-
liveMode = joined.liveMode;
|
|
1659
|
-
liveSseSession = joined.liveSseSession;
|
|
1638
|
+
});
|
|
1660
1639
|
}
|
|
1661
1640
|
localExportRefVersion = cursor.serverLowerBoundVersion;
|
|
1662
1641
|
const exportedLocal = this.exportUpdates(localExportRefVersion);
|
|
1663
1642
|
localExportRefVersion = exportedLocal.nextRefVersion;
|
|
1664
1643
|
if (exportedLocal.batch != null) {
|
|
1665
1644
|
cursor = await this.appendLocalBatch(cursor, exportedLocal.batch);
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
streamCursor = continued.streamCursor;
|
|
1670
|
-
liveSseSession = continued.liveSseSession;
|
|
1671
|
-
} else {
|
|
1672
|
-
const caughtUp = await this.catchup(cursor);
|
|
1673
|
-
cursor = caughtUp.cursor;
|
|
1674
|
-
streamCursor = caughtUp.streamCursor;
|
|
1675
|
-
}
|
|
1645
|
+
const caughtUp = await this.catchup(cursor);
|
|
1646
|
+
cursor = caughtUp.cursor;
|
|
1647
|
+
streamCursor = caughtUp.streamCursor;
|
|
1676
1648
|
}
|
|
1677
1649
|
localExportRefVersion = cursor.serverLowerBoundVersion;
|
|
1678
1650
|
return {
|
|
1679
1651
|
cursor,
|
|
1680
1652
|
streamCursor,
|
|
1681
1653
|
liveMode,
|
|
1682
|
-
liveSseSession,
|
|
1683
1654
|
localExportRefVersion
|
|
1684
1655
|
};
|
|
1685
1656
|
}
|
|
@@ -1689,111 +1660,6 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1689
1660
|
if (!result.upToDate) return (await this.catchup(saved)).cursor;
|
|
1690
1661
|
return saved;
|
|
1691
1662
|
}
|
|
1692
|
-
async bootstrapIntoJoinedState(cursor, preferredLiveMode, signal) {
|
|
1693
|
-
if (preferredLiveMode === "sse") {
|
|
1694
|
-
const sseResult = await this.bootstrapIntoJoinedStateViaSse(cursor, signal);
|
|
1695
|
-
if (sseResult.kind === "ok") return {
|
|
1696
|
-
...sseResult,
|
|
1697
|
-
liveMode: "sse"
|
|
1698
|
-
};
|
|
1699
|
-
}
|
|
1700
|
-
return {
|
|
1701
|
-
...await this.bootstrapIntoJoinedStateViaLongPoll(cursor),
|
|
1702
|
-
liveMode: "long-poll"
|
|
1703
|
-
};
|
|
1704
|
-
}
|
|
1705
|
-
async bootstrapIntoJoinedStateViaSse(cursor, signal) {
|
|
1706
|
-
let current = cursor;
|
|
1707
|
-
let currentStreamCursor;
|
|
1708
|
-
let expectBootstrapFrame = true;
|
|
1709
|
-
let caughtUp = false;
|
|
1710
|
-
const opened = await this.openBootstrapSseSession(this.streamUrl, signal);
|
|
1711
|
-
if (opened.kind === "unsupported") return { kind: "unsupported" };
|
|
1712
|
-
if (opened.kind === "not-found") throw new HttpError("bootstrap live sse failed with status 404", 404);
|
|
1713
|
-
if (opened.kind === "gone") return {
|
|
1714
|
-
kind: "ok",
|
|
1715
|
-
cursor: await this.bootstrapIntoState({
|
|
1716
|
-
...current,
|
|
1717
|
-
nextOffset: "-1",
|
|
1718
|
-
serverLowerBoundVersion: this.adapter.emptyVersion()
|
|
1719
|
-
})
|
|
1720
|
-
};
|
|
1721
|
-
const result = await opened.session.continue(async (items, nextOffset, upToDate, streamCursor) => {
|
|
1722
|
-
if (expectBootstrapFrame) {
|
|
1723
|
-
current = await this.applyBootstrapItems(current, items, nextOffset);
|
|
1724
|
-
expectBootstrapFrame = false;
|
|
1725
|
-
} else current = await this.applyRemoteItems(current, items, nextOffset);
|
|
1726
|
-
if (streamCursor != null) currentStreamCursor = streamCursor;
|
|
1727
|
-
if (upToDate) {
|
|
1728
|
-
caughtUp = true;
|
|
1729
|
-
return "pause";
|
|
1730
|
-
}
|
|
1731
|
-
});
|
|
1732
|
-
if (!caughtUp || !result.paused) {
|
|
1733
|
-
const resumed = await this.catchup(current, void 0, currentStreamCursor);
|
|
1734
|
-
await opened.session.cancel();
|
|
1735
|
-
return {
|
|
1736
|
-
kind: "ok",
|
|
1737
|
-
cursor: resumed.cursor,
|
|
1738
|
-
streamCursor: resumed.streamCursor
|
|
1739
|
-
};
|
|
1740
|
-
}
|
|
1741
|
-
return {
|
|
1742
|
-
kind: "ok",
|
|
1743
|
-
cursor: current,
|
|
1744
|
-
streamCursor: currentStreamCursor,
|
|
1745
|
-
liveSseSession: opened.session
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
async continueJoinedSseSessionUntilCaughtUp(session, cursor) {
|
|
1749
|
-
let current = cursor;
|
|
1750
|
-
let currentStreamCursor;
|
|
1751
|
-
let caughtUp = false;
|
|
1752
|
-
const result = await session.continue(async (items, nextOffset, upToDate, streamCursor) => {
|
|
1753
|
-
current = await this.applyRemoteItems(current, items, nextOffset);
|
|
1754
|
-
if (streamCursor != null) currentStreamCursor = streamCursor;
|
|
1755
|
-
if (upToDate) {
|
|
1756
|
-
caughtUp = true;
|
|
1757
|
-
return "pause";
|
|
1758
|
-
}
|
|
1759
|
-
});
|
|
1760
|
-
if (!caughtUp || !result.paused) {
|
|
1761
|
-
const resumed = await this.catchup(current, void 0, currentStreamCursor);
|
|
1762
|
-
await session.cancel();
|
|
1763
|
-
return {
|
|
1764
|
-
cursor: resumed.cursor,
|
|
1765
|
-
streamCursor: resumed.streamCursor
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
return {
|
|
1769
|
-
cursor: current,
|
|
1770
|
-
streamCursor: currentStreamCursor,
|
|
1771
|
-
liveSseSession: session
|
|
1772
|
-
};
|
|
1773
|
-
}
|
|
1774
|
-
async bootstrapIntoJoinedStateViaLongPoll(cursor) {
|
|
1775
|
-
let current = cursor;
|
|
1776
|
-
let currentStreamCursor;
|
|
1777
|
-
let expectBootstrapFrame = true;
|
|
1778
|
-
while (true) {
|
|
1779
|
-
const response = await this.readBootstrapLongPoll(this.streamUrl, currentStreamCursor);
|
|
1780
|
-
if (response.kind === "gone") return { cursor: await this.bootstrapIntoState({
|
|
1781
|
-
...current,
|
|
1782
|
-
nextOffset: "-1",
|
|
1783
|
-
serverLowerBoundVersion: this.adapter.emptyVersion()
|
|
1784
|
-
}) };
|
|
1785
|
-
const result = response.value;
|
|
1786
|
-
if (expectBootstrapFrame) {
|
|
1787
|
-
current = await this.applyBootstrapItems(current, result.items, result.nextOffset);
|
|
1788
|
-
expectBootstrapFrame = false;
|
|
1789
|
-
} else current = await this.applyRemoteItems(current, result.items, result.nextOffset);
|
|
1790
|
-
currentStreamCursor = result.streamCursor;
|
|
1791
|
-
if (result.upToDate) return {
|
|
1792
|
-
cursor: current,
|
|
1793
|
-
streamCursor: currentStreamCursor
|
|
1794
|
-
};
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
1663
|
async catchup(cursor, live, streamCursor, onRemoteVersionApplied) {
|
|
1798
1664
|
let current = cursor;
|
|
1799
1665
|
let currentStreamCursor = streamCursor;
|
|
@@ -1895,29 +1761,6 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1895
1761
|
await sleep(0);
|
|
1896
1762
|
continue;
|
|
1897
1763
|
}
|
|
1898
|
-
if (state.liveSseSession != null) {
|
|
1899
|
-
const session = state.liveSseSession;
|
|
1900
|
-
const sseRequestOffset = state.cursor.nextOffset;
|
|
1901
|
-
const sseResult = await session.continue(async (updates, nextOffset, _upToDate, streamCursor) => {
|
|
1902
|
-
await this.enqueueExclusive(async () => {
|
|
1903
|
-
if (state.closed) return;
|
|
1904
|
-
state.cursor = await this.cursorManager.applyLiveResult(state.cursor, sseRequestOffset, {
|
|
1905
|
-
nextOffset,
|
|
1906
|
-
updates
|
|
1907
|
-
}, (previousVersion, nextVersion) => {
|
|
1908
|
-
this.snapshotManager.observeRemoteVersion(state, previousVersion, nextVersion);
|
|
1909
|
-
});
|
|
1910
|
-
if (streamCursor != null) state.streamCursor = streamCursor;
|
|
1911
|
-
});
|
|
1912
|
-
this.setJoinStatus(state, "joined");
|
|
1913
|
-
});
|
|
1914
|
-
if (state.closed) return;
|
|
1915
|
-
state.liveSseSession = void 0;
|
|
1916
|
-
state.retryAttempt = 0;
|
|
1917
|
-
state.streamCursor = sseResult.latestCursor;
|
|
1918
|
-
await sleep(0);
|
|
1919
|
-
continue;
|
|
1920
|
-
}
|
|
1921
1764
|
const sseRequestOffset = state.cursor.nextOffset;
|
|
1922
1765
|
const sseResult = await this.readFromOffsetSseItems(this.streamUrl, sseRequestOffset, state.streamCursor, async (updates, nextOffset, _upToDate, streamCursor) => {
|
|
1923
1766
|
await this.enqueueExclusive(async () => {
|
|
@@ -1963,9 +1806,6 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
1963
1806
|
state.retryAttempt = 0;
|
|
1964
1807
|
} catch (error) {
|
|
1965
1808
|
if (state.closed) return;
|
|
1966
|
-
const liveSseSession = state.liveSseSession;
|
|
1967
|
-
state.liveSseSession = void 0;
|
|
1968
|
-
await liveSseSession?.cancel();
|
|
1969
1809
|
const classified = classifyFetchError(error);
|
|
1970
1810
|
if (!classified.retriable || isAuthOrProtocolError(error)) {
|
|
1971
1811
|
this.logError("live read failed", error, {
|
|
@@ -2088,12 +1928,6 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2088
1928
|
async readFromOffsetSseItems(_streamUrl, offset, cursor, onControl, onOpen, signal) {
|
|
2089
1929
|
return await this.client.readFromOffsetSseItems(offset, cursor, onControl, onOpen, signal);
|
|
2090
1930
|
}
|
|
2091
|
-
async openBootstrapSseSession(_streamUrl, signal) {
|
|
2092
|
-
return await this.client.openBootstrapSseSession(signal);
|
|
2093
|
-
}
|
|
2094
|
-
async readBootstrapLongPoll(_streamUrl, cursor, signal) {
|
|
2095
|
-
return await this.client.readBootstrapLongPoll(cursor, signal);
|
|
2096
|
-
}
|
|
2097
1931
|
async readFromOffset(_streamUrl, offset, cursor, live, signal) {
|
|
2098
1932
|
return await this.client.readFromOffset(offset, cursor, live, signal);
|
|
2099
1933
|
}
|
|
@@ -2167,4 +2001,4 @@ function createStreamUrl(input) {
|
|
|
2167
2001
|
//#endregion
|
|
2168
2002
|
export { InMemoryRemoteCursorStore as a, StreamsCrdt as i, isValidBucketId as n, IndexedDbRemoteCursorStore as o, isValidRillId as r, createInitialRemoteCursor as s, createStreamUrl as t };
|
|
2169
2003
|
|
|
2170
|
-
//# sourceMappingURL=stream-id-
|
|
2004
|
+
//# sourceMappingURL=stream-id-kM9JM1F7.js.map
|