@loro-dev/streams-crdt 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/flock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-CvJLq-0s.js";
|
|
2
2
|
import { Flock } from "@loro-dev/flock-wasm";
|
|
3
3
|
//#region src/flock.ts
|
|
4
4
|
function isAfter(entry, current) {
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-CvJLq-0s.js";
|
|
2
2
|
export { PayloadProtectionError as E2eeError, PayloadProtectionError, EphemeralStreamCrdt, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/loro.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, c as EphemeralStreamCrdt, i as StreamsCrdt, l as PayloadProtectionError, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-CvJLq-0s.js";
|
|
2
2
|
import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
|
|
3
3
|
//#region src/loro.ts
|
|
4
4
|
function batchImportUpdates(doc, updates) {
|
|
@@ -1112,6 +1112,11 @@ var StreamsTransportClient = class {
|
|
|
1112
1112
|
const snapshotOffset = result.result.snapshotOffset;
|
|
1113
1113
|
return snapshotOffset == null || snapshotOffset.length === 0 ? null : snapshotOffset;
|
|
1114
1114
|
}
|
|
1115
|
+
async streamSupportsAppendCas() {
|
|
1116
|
+
const result = await this.client.streamSupportsAppendCas();
|
|
1117
|
+
if (result.ok) return result.result;
|
|
1118
|
+
throw this.toTransportFailure(result.result);
|
|
1119
|
+
}
|
|
1115
1120
|
async putSnapshotAt(snapshotOffset, snapshot) {
|
|
1116
1121
|
const result = await this.client.putSnapshot({
|
|
1117
1122
|
offset: snapshotOffset,
|
|
@@ -1151,6 +1156,43 @@ var StreamsTransportClient = class {
|
|
|
1151
1156
|
};
|
|
1152
1157
|
throw this.toTransportFailure(error);
|
|
1153
1158
|
}
|
|
1159
|
+
async appendUpdatesCas(expectedOffset, updates, producerSession) {
|
|
1160
|
+
const result = await this.client.appendCas({
|
|
1161
|
+
expectedOffset,
|
|
1162
|
+
part: {
|
|
1163
|
+
contentType: BINARY_CONTENT_TYPE,
|
|
1164
|
+
body: encodeItems(updates)
|
|
1165
|
+
},
|
|
1166
|
+
producer: {
|
|
1167
|
+
producerId: producerSession.producerId,
|
|
1168
|
+
epoch: producerSession.producerEpoch,
|
|
1169
|
+
seq: producerSession.nextProducerSeq
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
if (result.ok) {
|
|
1173
|
+
if (result.result.kind === "mismatch") return {
|
|
1174
|
+
kind: "cas-mismatch",
|
|
1175
|
+
expectedOffset: result.result.expectedOffset,
|
|
1176
|
+
currentOffset: result.result.currentOffset
|
|
1177
|
+
};
|
|
1178
|
+
return {
|
|
1179
|
+
kind: "ok",
|
|
1180
|
+
ackEpoch: result.result.value.producer?.epoch ?? producerSession.producerEpoch,
|
|
1181
|
+
ackSeq: result.result.value.producer?.seq ?? producerSession.nextProducerSeq,
|
|
1182
|
+
nextOffset: result.result.value.nextOffset
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
const error = result.result;
|
|
1186
|
+
if (error.code === "conflict" && error.detail?.kind === "producer_seq_gap") return {
|
|
1187
|
+
kind: "seq-gap",
|
|
1188
|
+
expectedSeq: error.detail.expectedSeq
|
|
1189
|
+
};
|
|
1190
|
+
if (error.code === "forbidden" && error.detail?.kind === "stale_producer_epoch") return {
|
|
1191
|
+
kind: "stale-epoch",
|
|
1192
|
+
serverEpoch: error.detail.serverEpoch
|
|
1193
|
+
};
|
|
1194
|
+
throw this.toTransportFailure(error);
|
|
1195
|
+
}
|
|
1154
1196
|
async appendItems(updates) {
|
|
1155
1197
|
const result = await this.client.append({ part: {
|
|
1156
1198
|
contentType: BINARY_CONTENT_TYPE,
|
|
@@ -1177,7 +1219,7 @@ var StreamsTransportClient = class {
|
|
|
1177
1219
|
* the first event must be `bootstrap`, and all following events must be live
|
|
1178
1220
|
* `data` updates.
|
|
1179
1221
|
*/
|
|
1180
|
-
async readEphemeralSse(onBootstrap, onData, signal) {
|
|
1222
|
+
async readEphemeralSse(onBootstrap, onData, signal, onActivity) {
|
|
1181
1223
|
const requestUrl = this.ephemeralReadEndpoint();
|
|
1182
1224
|
const response = await this.fetchAuthorized(requestUrl, {
|
|
1183
1225
|
method: "GET",
|
|
@@ -1198,7 +1240,7 @@ var StreamsTransportClient = class {
|
|
|
1198
1240
|
const dataEncoding = response.headers.get("Stream-SSE-Data-Encoding") ?? void 0;
|
|
1199
1241
|
const consume = async () => {
|
|
1200
1242
|
let sawBootstrap = false;
|
|
1201
|
-
for await (const event of readSseEvents(response.body)) {
|
|
1243
|
+
for await (const event of readSseEvents(response.body, { onActivity })) {
|
|
1202
1244
|
if (!sawBootstrap) {
|
|
1203
1245
|
if (event.event !== "bootstrap") throw new ProtocolError(`ephemeral sse must start with bootstrap, got '${event.event}'`);
|
|
1204
1246
|
await onBootstrap(decodeSsePayload(event.data, dataEncoding));
|
|
@@ -1689,15 +1731,35 @@ var EphemeralStreamCrdt = class {
|
|
|
1689
1731
|
state.requestController ??= new AbortController();
|
|
1690
1732
|
const capturedController = state.requestController;
|
|
1691
1733
|
let sseOpenedAtMs = 0;
|
|
1734
|
+
let inactivityTimer = null;
|
|
1735
|
+
let inactivityTriggered = false;
|
|
1736
|
+
const clearInactivity = () => {
|
|
1737
|
+
if (inactivityTimer != null) {
|
|
1738
|
+
clearTimeout(inactivityTimer);
|
|
1739
|
+
inactivityTimer = null;
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
const resetInactivity = () => {
|
|
1743
|
+
clearInactivity();
|
|
1744
|
+
const timeoutMs = this.reconnectConfig.liveInactivityTimeoutMs;
|
|
1745
|
+
if (timeoutMs != null && timeoutMs > 0 && !state.closed) inactivityTimer = setTimeout(() => {
|
|
1746
|
+
inactivityTriggered = true;
|
|
1747
|
+
capturedController.abort();
|
|
1748
|
+
}, timeoutMs);
|
|
1749
|
+
};
|
|
1692
1750
|
try {
|
|
1751
|
+
resetInactivity();
|
|
1693
1752
|
const result = await this.client.readEphemeralSse(async (bootstrap) => {
|
|
1753
|
+
resetInactivity();
|
|
1694
1754
|
if (bootstrap.byteLength > 0) await this.adaptor.applyRemoteUpdates([bootstrap]);
|
|
1695
1755
|
sseOpenedAtMs = Date.now();
|
|
1696
1756
|
this.setReadStatus(state, "ok");
|
|
1697
1757
|
this.resolveFirstOpen(state);
|
|
1698
1758
|
}, async (update) => {
|
|
1759
|
+
resetInactivity();
|
|
1699
1760
|
if (update.byteLength > 0) await this.adaptor.applyRemoteUpdates([update]);
|
|
1700
|
-
}, capturedController.signal);
|
|
1761
|
+
}, capturedController.signal, resetInactivity);
|
|
1762
|
+
clearInactivity();
|
|
1701
1763
|
if (state.closed) return;
|
|
1702
1764
|
if (result.kind === "ok") {
|
|
1703
1765
|
state.retryAttempt = 0;
|
|
@@ -1708,10 +1770,25 @@ var EphemeralStreamCrdt = class {
|
|
|
1708
1770
|
if (result.kind === "unsupported") throw new Error("ephemeral stream requires SSE support");
|
|
1709
1771
|
if (result.kind === "not-found") throw new HttpError("ephemeral stream not found", 404);
|
|
1710
1772
|
} catch (error) {
|
|
1773
|
+
clearInactivity();
|
|
1711
1774
|
if (state.closed) return;
|
|
1712
1775
|
if (capturedController.signal.aborted) {
|
|
1713
1776
|
state.requestController = new AbortController();
|
|
1714
1777
|
this.setReadStatus(state, "reconnecting");
|
|
1778
|
+
if (!inactivityTriggered) continue;
|
|
1779
|
+
if (sseOpenedAtMs > 0 && Date.now() - sseOpenedAtMs >= 5e3) state.retryAttempt = 0;
|
|
1780
|
+
if (state.retryAttempt >= this.reconnectConfig.maxAttempts) {
|
|
1781
|
+
this.logError("ephemeral live reconnect exhausted (inactivity)", error, {
|
|
1782
|
+
streamUrl: this.streamUrl,
|
|
1783
|
+
attempts: state.retryAttempt
|
|
1784
|
+
});
|
|
1785
|
+
this.setReadStatus(state, "disconnected");
|
|
1786
|
+
this.rejectFirstOpen(state, toError(error));
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const delay = computeRetryDelay(state.retryAttempt, this.reconnectConfig);
|
|
1790
|
+
state.retryAttempt += 1;
|
|
1791
|
+
await sleep(delay, state.retrySleepController.signal);
|
|
1715
1792
|
continue;
|
|
1716
1793
|
}
|
|
1717
1794
|
const classified = classifyFetchError(error);
|
|
@@ -3122,6 +3199,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
3122
3199
|
};
|
|
3123
3200
|
continue;
|
|
3124
3201
|
}
|
|
3202
|
+
if (result.kind === "cas-mismatch") throw new Error("unexpected CAS mismatch from unconditional append");
|
|
3125
3203
|
frozen.producerSession = {
|
|
3126
3204
|
...frozen.producerSession,
|
|
3127
3205
|
producerEpoch: result.serverEpoch + 1,
|
|
@@ -3692,4 +3770,4 @@ function createStreamUrl(input) {
|
|
|
3692
3770
|
//#endregion
|
|
3693
3771
|
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 };
|
|
3694
3772
|
|
|
3695
|
-
//# sourceMappingURL=stream-id-
|
|
3773
|
+
//# sourceMappingURL=stream-id-CvJLq-0s.js.map
|