@loro-dev/streams-crdt 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -5
- 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-JuJ1JqCX.d.ts → stream-id-DAAMJlvT.d.ts} +76 -3
- package/dist/{stream-id-2eJsM9CJ.js → stream-id-fOF9ZqCH.js} +156 -5
- package/dist/stream-id-fOF9ZqCH.js.map +1 -0
- package/package.json +1 -1
- package/dist/stream-id-2eJsM9CJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ Transport layer for synchronizing CRDT state over
|
|
|
19
19
|
| `createStream()` | Creates the stream on the server. Returns `{ created: true }` on first call, `{ created: false }` if it already exists. |
|
|
20
20
|
| `deleteStream()` | Deletes the stream. Closes any active `join()` first. |
|
|
21
21
|
| `sync()` | One-shot bootstrap/catch-up cycle. Syncs remote state into local CRDT and pushes any local changes to the server. Does **not** enter live mode. |
|
|
22
|
+
| `catchup()` | Pull-only remote catch-up. Applies remote stream updates to the local CRDT but does not intentionally append new local updates. If live reads from `join()` are already healthy, returns immediately unless `{ force: true }` is passed. |
|
|
22
23
|
| `join()` | Initial sync **plus** live subscription. Internally performs the same sync as `sync()`, then enters a persistent SSE/long-poll read loop and starts forwarding local writes. No need to call `sync()` before `join()`. |
|
|
23
24
|
| `close()` | Closes any active join and releases resources. If this instance was using `appendWriteOnly()`, that write-only path is also torn down and cannot be used again on the same instance. |
|
|
24
25
|
|
|
@@ -107,7 +108,8 @@ if (err.code === "server_error") {
|
|
|
107
108
|
|
|
108
109
|
#### Operation-level retries vs. live-mode retries
|
|
109
110
|
|
|
110
|
-
- `sync()`, `
|
|
111
|
+
- `sync()`, `catchup()`, `createStream()`, `deleteStream()`,
|
|
112
|
+
`appendWriteOnly()` return
|
|
111
113
|
after a **single** attempt. The application is responsible for retrying
|
|
112
114
|
`retryable` errors.
|
|
113
115
|
- After `join()` succeeds, the live read/write loops automatically reconnect
|
|
@@ -189,6 +191,47 @@ const sub = joined.value;
|
|
|
189
191
|
checked out or detached, call `checkoutToLatest()` or `attach()` before passing
|
|
190
192
|
it to `StreamsCrdt`.
|
|
191
193
|
|
|
194
|
+
### Request shard origins
|
|
195
|
+
|
|
196
|
+
Browsers can still show head-of-line blocking symptoms when large
|
|
197
|
+
bootstrap/catch-up downloads and normal app requests contend on the same
|
|
198
|
+
connection. `shardUrls` lets the transport replace only the request origin for
|
|
199
|
+
selected operations while preserving the stream path and query:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
const transport = new StreamsCrdt({
|
|
203
|
+
streamUrl,
|
|
204
|
+
auth: async () => "<gateway-jwt>",
|
|
205
|
+
adapter: createLoroDocAdapter(doc),
|
|
206
|
+
shardUrls: {
|
|
207
|
+
bootstrap: [
|
|
208
|
+
"https://control-a.streams-api-proxy.loro.dev",
|
|
209
|
+
"https://control-b.streams-api-proxy.loro.dev",
|
|
210
|
+
],
|
|
211
|
+
catchup: [
|
|
212
|
+
"https://control-a.streams-api-proxy.loro.dev",
|
|
213
|
+
"https://control-b.streams-api-proxy.loro.dev",
|
|
214
|
+
],
|
|
215
|
+
largePost: [
|
|
216
|
+
"https://write-a.streams-api-proxy.loro.dev",
|
|
217
|
+
"https://write-b.streams-api-proxy.loro.dev",
|
|
218
|
+
],
|
|
219
|
+
largePostMinBytes: 64 * 1024,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Shard entries must be origin URLs with no path, query, or hash. Each transport
|
|
225
|
+
instance rotates through the configured origins per operation, with a randomized
|
|
226
|
+
starting point so multiple tabs are less likely to all begin on the same shard.
|
|
227
|
+
Bootstrap applies to `GET <stream>/bootstrap`; catch-up applies to non-SSE
|
|
228
|
+
offset reads, including long-poll fallback; SSE live reads stay on the original
|
|
229
|
+
`streamUrl`. `largePost` applies to non-empty append POST bodies at or above
|
|
230
|
+
`largePostMinBytes` bytes. The default threshold is `0`, so every non-empty
|
|
231
|
+
append POST can use `largePost` when that pool is configured. Verify actual
|
|
232
|
+
connection separation with browser NetLog: browsers may coalesce compatible
|
|
233
|
+
HTTP/2 or HTTP/3 origins when DNS, certificate, and transport settings allow it.
|
|
234
|
+
|
|
192
235
|
### Loro pending imports and cursor safety
|
|
193
236
|
|
|
194
237
|
Loro can accept an update whose dependencies are missing and keep it as a
|
|
@@ -330,17 +373,47 @@ if (!joined.ok) throw joined.error;
|
|
|
330
373
|
const sub = joined.value;
|
|
331
374
|
```
|
|
332
375
|
|
|
333
|
-
## When to Use `sync()
|
|
376
|
+
## When to Use `catchup()`, `sync()`, and `join()`
|
|
377
|
+
|
|
378
|
+
Use **`catchup()`** when you only want to pull remote updates into the local
|
|
379
|
+
CRDT:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
// Pull remote changes without uploading local pending changes.
|
|
383
|
+
const caughtUp = await transport.catchup();
|
|
384
|
+
if (!caughtUp.ok) throw caughtUp.error;
|
|
385
|
+
// caughtUp.value.cursor contains the replay progress after the pull.
|
|
386
|
+
```
|
|
334
387
|
|
|
335
|
-
|
|
388
|
+
`catchup()` is safe to call while a room is live. When `join()` has a healthy
|
|
389
|
+
SSE/long-poll read loop, `catchup()` returns immediately because the live loop
|
|
390
|
+
is already catching up continuously. Pass `{ force: true }` to issue one
|
|
391
|
+
explicit non-SSE offset read anyway, for example after app foreground or device
|
|
392
|
+
wake:
|
|
336
393
|
|
|
337
394
|
```ts
|
|
338
|
-
|
|
395
|
+
await transport.catchup({ force: true });
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
If the live read side of the room is reconnecting, disconnected, or in error,
|
|
399
|
+
`catchup()` skips the live backoff path and tries one immediate non-SSE
|
|
400
|
+
catch-up read from the room's current cursor. A successful call updates the
|
|
401
|
+
local cursor and nudges the live loops to reconnect, but it does not by itself
|
|
402
|
+
guarantee that local pending writes have reached the server.
|
|
403
|
+
|
|
404
|
+
Use **`sync()`** when you need a one-shot bidirectional sync without entering
|
|
405
|
+
live mode:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
// Offline-first: pull remote changes, upload local changes, then work offline.
|
|
339
409
|
const synced = await transport.sync();
|
|
340
410
|
if (!synced.ok) throw synced.error;
|
|
341
411
|
// synced.value.cursor contains the replay progress after sync.
|
|
342
412
|
```
|
|
343
413
|
|
|
414
|
+
Use `sync()` or `subscription.waitUntilSynced()` when the caller needs local
|
|
415
|
+
pending changes uploaded to the server.
|
|
416
|
+
|
|
344
417
|
Use **`join()`** for real-time collaboration. It does everything `sync()` does
|
|
345
418
|
and then keeps the connection alive for continuous bidirectional sync:
|
|
346
419
|
|
|
@@ -980,7 +1053,9 @@ const transport = new StreamsCrdt({
|
|
|
980
1053
|
- `TransportError` — discriminated error union
|
|
981
1054
|
`apply_incomplete` is returned when remote data was read but the CRDT adapter
|
|
982
1055
|
still has unresolved dependencies and the cursor cannot be saved.
|
|
983
|
-
- `
|
|
1056
|
+
- `TransportCatchupParams` — `catchup()` options
|
|
1057
|
+
- `TransportCreateStreamSuccess`, `TransportDeleteStreamSuccess`,
|
|
1058
|
+
`TransportCatchupSuccess`, `TransportSyncSuccess` — result payloads
|
|
984
1059
|
- `TransportJoinParams` — `join()` options
|
|
985
1060
|
- `TransportSubscription` — active live subscription handle
|
|
986
1061
|
- `TransportRoomStatus` — `"joined" | "reconnecting" | "disconnected" | "error"`
|
package/dist/flock.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as BeforeRemoteCursorSaveHook, A as PayloadProtectionReadPolicy, B as StreamsCrdtShardUrlsOptions, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportError, H as TransportCatchupSuccess, I as SnapshotUploadOptions, J as TransportSnapshotUploadSuccess, K as TransportJoinParams, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as BeforeRemoteCursorSaveContext, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportCreateStreamSuccess, V as TransportCatchupParams, W as TransportDeleteStreamSuccess, X as TransportSyncSuccess, Y as TransportSubscription, Z as WriteOnlyAppendResult, _ as E2eeScope, a as EphemeralStreamCrdt, at as RemoteCursorStore, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as InMemoryRemoteCursorStore, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, it as RemoteCursorSaveSource, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as IndexedDbRemoteCursorStoreOptions, o as StreamsCrdt, ot as createInitialRemoteCursor, p as E2eeKey, q as TransportRoomStatus, r as isValidRillId, rt as RemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as IndexedDbRemoteCursorStore, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-DAAMJlvT.js";
|
|
2
2
|
import { ExportBundle, Flock, VersionVector, VersionVector as VersionVector$1 } from "@loro-dev/flock-wasm";
|
|
3
3
|
|
|
4
4
|
//#region src/flock.d.ts
|
|
@@ -7,5 +7,5 @@ import { ExportBundle, Flock, VersionVector, VersionVector as VersionVector$1 }
|
|
|
7
7
|
*/
|
|
8
8
|
declare function createFlockAdapter(flock: Flock): CrdtAdapter<VersionVector$1>;
|
|
9
9
|
//#endregion
|
|
10
|
-
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, type ExportBundle, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, type VersionVector, WriteOnlyAppendResult, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
10
|
+
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, type ExportBundle, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, StreamsCrdtShardUrlsOptions, TransportCatchupParams, TransportCatchupSuccess, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, type VersionVector, WriteOnlyAppendResult, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
11
11
|
//# sourceMappingURL=flock.d.ts.map
|
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-fOF9ZqCH.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.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
1
|
+
import { $ as BeforeRemoteCursorSaveHook, A as PayloadProtectionReadPolicy, B as StreamsCrdtShardUrlsOptions, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportError, H as TransportCatchupSuccess, I as SnapshotUploadOptions, J as TransportSnapshotUploadSuccess, K as TransportJoinParams, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as BeforeRemoteCursorSaveContext, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportCreateStreamSuccess, V as TransportCatchupParams, W as TransportDeleteStreamSuccess, X as TransportSyncSuccess, Y as TransportSubscription, Z as WriteOnlyAppendResult, _ as E2eeScope, a as EphemeralStreamCrdt, at as RemoteCursorStore, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as InMemoryRemoteCursorStore, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, it as RemoteCursorSaveSource, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as IndexedDbRemoteCursorStoreOptions, o as StreamsCrdt, ot as createInitialRemoteCursor, p as E2eeKey, q as TransportRoomStatus, r as isValidRillId, rt as RemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as IndexedDbRemoteCursorStore, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-DAAMJlvT.js";
|
|
2
|
+
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, StreamsCrdtShardUrlsOptions, TransportCatchupParams, TransportCatchupSuccess, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
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-fOF9ZqCH.js";
|
|
2
2
|
export { PayloadProtectionError as E2eeError, PayloadProtectionError, EphemeralStreamCrdt, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/loro.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as BeforeRemoteCursorSaveHook, A as PayloadProtectionReadPolicy, B as StreamsCrdtShardUrlsOptions, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportError, H as TransportCatchupSuccess, I as SnapshotUploadOptions, J as TransportSnapshotUploadSuccess, K as TransportJoinParams, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as BeforeRemoteCursorSaveContext, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportCreateStreamSuccess, V as TransportCatchupParams, W as TransportDeleteStreamSuccess, X as TransportSyncSuccess, Y as TransportSubscription, Z as WriteOnlyAppendResult, _ as E2eeScope, a as EphemeralStreamCrdt, at as RemoteCursorStore, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as InMemoryRemoteCursorStore, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, it as RemoteCursorSaveSource, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as IndexedDbRemoteCursorStoreOptions, o as StreamsCrdt, ot as createInitialRemoteCursor, p as E2eeKey, q as TransportRoomStatus, r as isValidRillId, rt as RemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as IndexedDbRemoteCursorStore, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-DAAMJlvT.js";
|
|
2
2
|
import { LoroDoc } from "loro-crdt";
|
|
3
3
|
|
|
4
4
|
//#region src/loro.d.ts
|
|
@@ -41,5 +41,5 @@ interface LoroEphemeralStoreLike {
|
|
|
41
41
|
declare function EphemeralStoreAdaptor(store: LoroEphemeralStoreLike): EphemeralStreamAdaptor;
|
|
42
42
|
declare const createLoroEphemeralStoreAdaptor: typeof EphemeralStoreAdaptor;
|
|
43
43
|
//#endregion
|
|
44
|
-
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStoreAdaptor, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, LoroEphemeralStoreLike, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createLoroDocAdapter, createLoroEphemeralStoreAdaptor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
44
|
+
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtApplyOutcome, CrdtApplyReturn, CrdtUnresolvedSpan, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, EphemeralStoreAdaptor, EphemeralStreamAdaptor, EphemeralStreamCrdt, EphemeralStreamCrdtOptions, EphemeralStreamJoinParams, EphemeralStreamSubscription, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, IsolatedCrdtAdapter, JsonObject, JsonValue, LoroEphemeralStoreLike, PayloadProtectionEncryptionOptions, PayloadProtectionKey, PayloadProtectionKeyProvider, PayloadProtectionOptions, PayloadProtectionReadPolicy, PayloadProtectionScope, PayloadProtectionWritePolicy, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotCodec, SnapshotTransformHook, SnapshotUploadOptions, StreamsAuthContext, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, StreamsCrdtShardUrlsOptions, TransportCatchupParams, TransportCatchupSuccess, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createLoroDocAdapter, createLoroEphemeralStoreAdaptor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
45
45
|
//# sourceMappingURL=loro.d.ts.map
|
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-fOF9ZqCH.js";
|
|
2
2
|
import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
|
|
3
3
|
//#region src/loro.ts
|
|
4
4
|
function batchImportUpdates(doc, updates) {
|
|
@@ -356,6 +356,24 @@ interface TransportDeleteStreamSuccess {
|
|
|
356
356
|
interface TransportSyncSuccess<TVersion extends JsonObject = JsonObject> {
|
|
357
357
|
readonly cursor: RemoteCursor<TVersion>;
|
|
358
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* Optional parameters for a `catchup()` call.
|
|
361
|
+
*/
|
|
362
|
+
interface TransportCatchupParams {
|
|
363
|
+
/**
|
|
364
|
+
* When `false` or omitted, an active healthy live read loop is treated as
|
|
365
|
+
* already catching up and `catchup()` returns immediately with the current
|
|
366
|
+
* cursor. When `true`, the transport performs one non-SSE offset read even
|
|
367
|
+
* if live SSE/long-poll is currently healthy.
|
|
368
|
+
*/
|
|
369
|
+
readonly force?: boolean;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Result payload returned by `catchup()`.
|
|
373
|
+
*/
|
|
374
|
+
interface TransportCatchupSuccess<TVersion extends JsonObject = JsonObject> {
|
|
375
|
+
readonly cursor: RemoteCursor<TVersion>;
|
|
376
|
+
}
|
|
359
377
|
/**
|
|
360
378
|
* Result payload returned by the internal snapshot upload test hook.
|
|
361
379
|
*/
|
|
@@ -395,6 +413,32 @@ interface SnapshotUploadOptions {
|
|
|
395
413
|
/** Minimum byte delta since the last remote snapshot before uploading. */
|
|
396
414
|
readonly minBytesSinceRemoteSnapshot?: number;
|
|
397
415
|
}
|
|
416
|
+
interface StreamsCrdtShardUrlsOptions {
|
|
417
|
+
/**
|
|
418
|
+
* Origin URLs used for `GET <stream>/bootstrap`.
|
|
419
|
+
*
|
|
420
|
+
* Each entry should be an origin URL such as
|
|
421
|
+
* `https://control-a.streams-api-proxy.loro.dev`. The transport keeps the
|
|
422
|
+
* stream path and query from `streamUrl` and only replaces the request origin.
|
|
423
|
+
*/
|
|
424
|
+
readonly bootstrap?: readonly string[];
|
|
425
|
+
/**
|
|
426
|
+
* Origin URLs used for non-SSE catch-up reads:
|
|
427
|
+
* `GET <stream>?offset=...` and long-poll fallback reads.
|
|
428
|
+
*/
|
|
429
|
+
readonly catchup?: readonly string[];
|
|
430
|
+
/**
|
|
431
|
+
* Origin URLs used for large `POST <stream>` append requests.
|
|
432
|
+
*/
|
|
433
|
+
readonly largePost?: readonly string[];
|
|
434
|
+
/**
|
|
435
|
+
* Minimum request body size before `largePost` routing applies.
|
|
436
|
+
*
|
|
437
|
+
* Defaults to 0, meaning every non-empty append POST can use `largePost`
|
|
438
|
+
* when that shard pool is configured.
|
|
439
|
+
*/
|
|
440
|
+
readonly largePostMinBytes?: number;
|
|
441
|
+
}
|
|
398
442
|
/**
|
|
399
443
|
* Transforms a remote snapshot payload during upload or bootstrap.
|
|
400
444
|
*/
|
|
@@ -697,6 +741,8 @@ interface StreamsCrdtOptions<TVersion extends JsonObject> {
|
|
|
697
741
|
*/
|
|
698
742
|
readonly payloadProtection?: PayloadProtectionOptions;
|
|
699
743
|
readonly snapshotUpload?: SnapshotUploadOptions;
|
|
744
|
+
/** Optional origin pools for routing bootstrap/catch-up and large append requests. */
|
|
745
|
+
readonly shardUrls?: StreamsCrdtShardUrlsOptions;
|
|
700
746
|
readonly reconnectConfig?: Partial<ReconnectConfig>;
|
|
701
747
|
readonly debug?: boolean;
|
|
702
748
|
}
|
|
@@ -704,6 +750,21 @@ interface StreamsCrdtLike<TVersion extends JsonObject> {
|
|
|
704
750
|
createStream(): Promise<Result<TransportCreateStreamSuccess, TransportError>>;
|
|
705
751
|
deleteStream(): Promise<Result<TransportDeleteStreamSuccess, TransportError>>;
|
|
706
752
|
sync(): Promise<Result<TransportSyncSuccess<TVersion>, TransportError>>;
|
|
753
|
+
/**
|
|
754
|
+
* Pulls remote stream updates into the local CRDT without intentionally
|
|
755
|
+
* appending new local updates.
|
|
756
|
+
*
|
|
757
|
+
* If a healthy `join()` live read loop is active, this is a fast no-op by
|
|
758
|
+
* default because the loop is already catching up continuously. Pass
|
|
759
|
+
* `{ force: true }` to issue one explicit non-SSE offset read anyway.
|
|
760
|
+
*
|
|
761
|
+
* If the live read side of a join is reconnecting, disconnected, or in
|
|
762
|
+
* error, `catchup()` attempts one immediate non-SSE offset read from the
|
|
763
|
+
* room's current cursor and then nudges the live loops to reconnect. Use
|
|
764
|
+
* `sync()` or `subscription.waitUntilSynced()` when the caller also needs
|
|
765
|
+
* local pending changes uploaded to the server.
|
|
766
|
+
*/
|
|
767
|
+
catchup(params?: TransportCatchupParams): Promise<Result<TransportCatchupSuccess<TVersion>, TransportError>>;
|
|
707
768
|
/**
|
|
708
769
|
* Appends only locally observed batches without reading remote state first.
|
|
709
770
|
*
|
|
@@ -812,6 +873,16 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
812
873
|
* Runs one bootstrap/catch-up cycle without entering live mode.
|
|
813
874
|
*/
|
|
814
875
|
sync(): Promise<Result<TransportSyncSuccess<TVersion>, TransportError>>;
|
|
876
|
+
/**
|
|
877
|
+
* Pulls remote updates into the local CRDT without intentionally appending
|
|
878
|
+
* new local updates.
|
|
879
|
+
*
|
|
880
|
+
* Healthy live reads already catch up continuously, so an active `join()`
|
|
881
|
+
* returns immediately unless `force: true` is passed. Reconnecting or
|
|
882
|
+
* terminal live reads perform one immediate non-SSE offset read and then
|
|
883
|
+
* nudge the room to reconnect.
|
|
884
|
+
*/
|
|
885
|
+
catchup(params?: TransportCatchupParams): Promise<Result<TransportCatchupSuccess<TVersion>, TransportError>>;
|
|
815
886
|
/**
|
|
816
887
|
* Appends local CRDT updates without reading the stream first.
|
|
817
888
|
*
|
|
@@ -831,8 +902,10 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
831
902
|
private performInitialJoinSync;
|
|
832
903
|
private createInitialCursor;
|
|
833
904
|
private resolveInitialRemoteState;
|
|
905
|
+
private resolvePullOnlyRemoteState;
|
|
906
|
+
private catchupJoinState;
|
|
834
907
|
private bootstrapState;
|
|
835
|
-
private
|
|
908
|
+
private catchupFromCursor;
|
|
836
909
|
private catchupOrGone;
|
|
837
910
|
private recoverLiveIncompleteRemoteApply;
|
|
838
911
|
private appendLocalBatch;
|
|
@@ -977,5 +1050,5 @@ declare function createStreamUrl(input: {
|
|
|
977
1050
|
baseUrl?: string;
|
|
978
1051
|
}): string;
|
|
979
1052
|
//#endregion
|
|
980
|
-
export {
|
|
981
|
-
//# sourceMappingURL=stream-id-
|
|
1053
|
+
export { BeforeRemoteCursorSaveHook as $, PayloadProtectionReadPolicy as A, StreamsCrdtShardUrlsOptions as B, IsolatedCrdtAdapter as C, PayloadProtectionKey as D, PayloadProtectionEncryptionOptions as E, SnapshotTransformHook as F, TransportError as G, TransportCatchupSuccess as H, SnapshotUploadOptions as I, TransportSnapshotUploadSuccess as J, TransportJoinParams as K, StreamsAuthContext as L, PayloadProtectionWritePolicy as M, Result as N, PayloadProtectionKeyProvider as O, SnapshotCodec as P, BeforeRemoteCursorSaveContext as Q, StreamsAuthProvider as R, EphemeralStreamSubscription as S, JsonValue as T, TransportCreateStreamSuccess as U, TransportCatchupParams as V, TransportDeleteStreamSuccess as W, TransportSyncSuccess as X, TransportSubscription as Y, WriteOnlyAppendResult as Z, E2eeScope as _, EphemeralStreamCrdt as a, RemoteCursorStore as at, EphemeralStreamCrdtOptions as b, CrdtApplyOutcome as c, CrdtUpdateBatch as d, InMemoryRemoteCursorStore as et, E2eeEncryptionOptions as f, E2eeReadPolicy as g, E2eeOptions as h, PayloadProtectionError as i, RemoteCursorSaveSource as it, PayloadProtectionScope as j, PayloadProtectionOptions as k, CrdtApplyReturn as l, E2eeKeyProvider as m, isValidBucketId as n, IndexedDbRemoteCursorStoreOptions as nt, StreamsCrdt as o, createInitialRemoteCursor as ot, E2eeKey as p, TransportRoomStatus as q, isValidRillId as r, RemoteCursor as rt, CrdtAdapter as s, createStreamUrl as t, IndexedDbRemoteCursorStore as tt, CrdtUnresolvedSpan as u, E2eeWritePolicy as v, JsonObject as w, EphemeralStreamJoinParams as x, EphemeralStreamAdaptor as y, StreamsCrdtOptions as z };
|
|
1054
|
+
//# sourceMappingURL=stream-id-DAAMJlvT.d.ts.map
|
|
@@ -881,6 +881,56 @@ function resolveNextOffsetAfterConcurrentAdvance(requestOffset, currentOffset, r
|
|
|
881
881
|
}
|
|
882
882
|
//#endregion
|
|
883
883
|
//#region src/transport-client.ts
|
|
884
|
+
const bodySizeEncoder = new TextEncoder();
|
|
885
|
+
function normalizeShardOrigins(urls, label) {
|
|
886
|
+
if (urls == null) return;
|
|
887
|
+
const out = [];
|
|
888
|
+
for (const raw of urls) {
|
|
889
|
+
const trimmed = raw.trim();
|
|
890
|
+
if (trimmed.length === 0) continue;
|
|
891
|
+
const url = new URL(trimmed);
|
|
892
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error(`${label} shard URL must use http or https`);
|
|
893
|
+
if (url.pathname !== "/" || url.search !== "" || url.hash !== "") throw new Error(`${label} shard URL must be an origin URL`);
|
|
894
|
+
out.push(url.origin);
|
|
895
|
+
}
|
|
896
|
+
return out.length === 0 ? void 0 : out;
|
|
897
|
+
}
|
|
898
|
+
function normalizeShardUrlsOptions(options) {
|
|
899
|
+
if (options == null) return;
|
|
900
|
+
const largePostMinBytes = options.largePostMinBytes ?? 0;
|
|
901
|
+
if (!Number.isSafeInteger(largePostMinBytes) || largePostMinBytes < 0) throw new Error("shardUrls.largePostMinBytes must be a non-negative safe integer");
|
|
902
|
+
const normalized = {
|
|
903
|
+
bootstrap: normalizeShardOrigins(options.bootstrap, "shardUrls.bootstrap"),
|
|
904
|
+
catchup: normalizeShardOrigins(options.catchup, "shardUrls.catchup"),
|
|
905
|
+
largePost: normalizeShardOrigins(options.largePost, "shardUrls.largePost"),
|
|
906
|
+
largePostMinBytes
|
|
907
|
+
};
|
|
908
|
+
if (normalized.bootstrap == null && normalized.catchup == null && normalized.largePost == null) return;
|
|
909
|
+
return normalized;
|
|
910
|
+
}
|
|
911
|
+
function requestBodyByteLength(body) {
|
|
912
|
+
if (body == null) return 0;
|
|
913
|
+
if (typeof body === "string") return bodySizeEncoder.encode(body).byteLength;
|
|
914
|
+
if (body instanceof ArrayBuffer) return body.byteLength;
|
|
915
|
+
if (ArrayBuffer.isView(body)) return body.byteLength;
|
|
916
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) return body.size;
|
|
917
|
+
if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) return bodySizeEncoder.encode(body.toString()).byteLength;
|
|
918
|
+
}
|
|
919
|
+
function createRandomShardStart(originCount) {
|
|
920
|
+
if (originCount <= 1) return 0;
|
|
921
|
+
try {
|
|
922
|
+
const crypto = globalThis.crypto;
|
|
923
|
+
if (crypto != null) {
|
|
924
|
+
const values = new Uint32Array(1);
|
|
925
|
+
crypto.getRandomValues(values);
|
|
926
|
+
return (values[0] ?? 0) % originCount;
|
|
927
|
+
}
|
|
928
|
+
} catch {}
|
|
929
|
+
return Math.floor(Math.random() * originCount);
|
|
930
|
+
}
|
|
931
|
+
function normalizedStreamPath(streamUrl) {
|
|
932
|
+
return streamUrl.pathname.endsWith("/") ? streamUrl.pathname.slice(0, -1) : streamUrl.pathname;
|
|
933
|
+
}
|
|
884
934
|
/**
|
|
885
935
|
* Low-level HTTP and SSE client for one stream.
|
|
886
936
|
*
|
|
@@ -894,24 +944,62 @@ var StreamsTransportClient = class {
|
|
|
894
944
|
fetchImpl;
|
|
895
945
|
authProvider;
|
|
896
946
|
reconnectConfig;
|
|
947
|
+
shardUrls;
|
|
948
|
+
shardCursorIndexes = {};
|
|
897
949
|
authRefreshInFlight;
|
|
898
950
|
constructor(options) {
|
|
899
951
|
this.streamUrl = options.streamUrl;
|
|
900
952
|
this.adapter = options.adapter;
|
|
953
|
+
this.fetchImpl = options.fetchImpl;
|
|
954
|
+
this.shardUrls = normalizeShardUrlsOptions(options.shardUrls);
|
|
901
955
|
this.client = new StreamsClient({
|
|
902
956
|
url: options.streamUrl,
|
|
903
957
|
auth: options.authProvider,
|
|
904
|
-
fetch:
|
|
958
|
+
fetch: this.fetchRouted.bind(this),
|
|
905
959
|
retry: { maxAttempts: 0 },
|
|
906
960
|
timeout: {
|
|
907
961
|
connectTimeoutMs: options.reconnectConfig.connectTimeoutMs,
|
|
908
962
|
pollTimeoutMs: options.reconnectConfig.pollTimeoutMs
|
|
909
963
|
}
|
|
910
964
|
});
|
|
911
|
-
this.fetchImpl = options.fetchImpl;
|
|
912
965
|
this.authProvider = options.authProvider;
|
|
913
966
|
this.reconnectConfig = options.reconnectConfig;
|
|
914
967
|
}
|
|
968
|
+
fetchRouted(input, init) {
|
|
969
|
+
return this.fetchImpl(this.routeRequestUrl(input, init), init);
|
|
970
|
+
}
|
|
971
|
+
routeRequestUrl(input, init) {
|
|
972
|
+
const shardUrls = this.shardUrls;
|
|
973
|
+
if (shardUrls == null) return input;
|
|
974
|
+
const operation = this.classifyShardOperation(input, init, shardUrls);
|
|
975
|
+
if (operation == null) return input;
|
|
976
|
+
const origins = operation === "bootstrap" ? shardUrls.bootstrap : operation === "catchup" ? shardUrls.catchup : shardUrls.largePost;
|
|
977
|
+
if (origins == null || origins.length === 0) return input;
|
|
978
|
+
const target = new URL(input);
|
|
979
|
+
const shardOrigin = new URL(this.nextShardOrigin(operation, origins));
|
|
980
|
+
target.protocol = shardOrigin.protocol;
|
|
981
|
+
target.host = shardOrigin.host;
|
|
982
|
+
target.username = "";
|
|
983
|
+
target.password = "";
|
|
984
|
+
return target.toString();
|
|
985
|
+
}
|
|
986
|
+
nextShardOrigin(operation, origins) {
|
|
987
|
+
const currentIndex = this.shardCursorIndexes[operation] ?? createRandomShardStart(origins.length);
|
|
988
|
+
this.shardCursorIndexes[operation] = (currentIndex + 1) % origins.length;
|
|
989
|
+
return origins[currentIndex % origins.length];
|
|
990
|
+
}
|
|
991
|
+
classifyShardOperation(input, init, shardUrls) {
|
|
992
|
+
const method = (init?.method ?? "GET").toUpperCase();
|
|
993
|
+
const requestUrl = new URL(input);
|
|
994
|
+
const streamUrl = new URL(this.streamUrl);
|
|
995
|
+
const streamPath = normalizedStreamPath(streamUrl);
|
|
996
|
+
if (method === "GET" && requestUrl.pathname === `${streamPath}/bootstrap`) return "bootstrap";
|
|
997
|
+
if (method === "GET" && requestUrl.pathname === streamUrl.pathname && requestUrl.searchParams.has("offset") && requestUrl.searchParams.get("live") !== "sse") return "catchup";
|
|
998
|
+
if (method !== "POST" || requestUrl.pathname !== streamUrl.pathname) return;
|
|
999
|
+
const bodyLength = requestBodyByteLength(init?.body);
|
|
1000
|
+
if (bodyLength == null || bodyLength === 0 || bodyLength < shardUrls.largePostMinBytes) return;
|
|
1001
|
+
return "large-post";
|
|
1002
|
+
}
|
|
915
1003
|
async createStreamRequest() {
|
|
916
1004
|
const adapterHeaders = this.adapter.getCreateStreamHeaders ? await this.adapter.getCreateStreamHeaders() : void 0;
|
|
917
1005
|
const result = await this.client.create({ headers: adapterHeaders });
|
|
@@ -2209,7 +2297,8 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2209
2297
|
adapter: this.adapter,
|
|
2210
2298
|
authProvider,
|
|
2211
2299
|
fetchImpl,
|
|
2212
|
-
reconnectConfig: this.reconnectConfig
|
|
2300
|
+
reconnectConfig: this.reconnectConfig,
|
|
2301
|
+
shardUrls: options.shardUrls
|
|
2213
2302
|
});
|
|
2214
2303
|
this.cursorManager = this.createCursorManager(this.adapter, (cursor, source) => this.saveRemoteCursor(cursor, source));
|
|
2215
2304
|
this.volatileCursorManager = this.createCursorManager(this.adapter, async (cursor) => cursor);
|
|
@@ -2406,6 +2495,34 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2406
2495
|
}
|
|
2407
2496
|
}
|
|
2408
2497
|
/**
|
|
2498
|
+
* Pulls remote updates into the local CRDT without intentionally appending
|
|
2499
|
+
* new local updates.
|
|
2500
|
+
*
|
|
2501
|
+
* Healthy live reads already catch up continuously, so an active `join()`
|
|
2502
|
+
* returns immediately unless `force: true` is passed. Reconnecting or
|
|
2503
|
+
* terminal live reads perform one immediate non-SSE offset read and then
|
|
2504
|
+
* nudge the room to reconnect.
|
|
2505
|
+
*/
|
|
2506
|
+
async catchup(params = {}) {
|
|
2507
|
+
try {
|
|
2508
|
+
this.disableWriteOnlyMode("appendWriteOnly cannot be used after catchup() on the same transport");
|
|
2509
|
+
const state = this.joinState;
|
|
2510
|
+
if (state != null && !state.closed) return {
|
|
2511
|
+
ok: true,
|
|
2512
|
+
value: { cursor: await this.catchupJoinState(state, params) }
|
|
2513
|
+
};
|
|
2514
|
+
return {
|
|
2515
|
+
ok: true,
|
|
2516
|
+
value: { cursor: (await this.resolvePullOnlyRemoteState()).cursor }
|
|
2517
|
+
};
|
|
2518
|
+
} catch (error) {
|
|
2519
|
+
return {
|
|
2520
|
+
ok: false,
|
|
2521
|
+
error: toTransportError(error)
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2409
2526
|
* Appends local CRDT updates without reading the stream first.
|
|
2410
2527
|
*
|
|
2411
2528
|
* Use this for write-only actors whose auth token may POST to the stream but
|
|
@@ -2658,6 +2775,40 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2658
2775
|
return await this.bootstrapState(this.createInitialCursor());
|
|
2659
2776
|
}
|
|
2660
2777
|
}
|
|
2778
|
+
async resolvePullOnlyRemoteState() {
|
|
2779
|
+
const loaded = await this.remoteCursorStore.load(this.streamUrl);
|
|
2780
|
+
const cursor = loaded ?? this.createInitialCursor();
|
|
2781
|
+
try {
|
|
2782
|
+
if (loaded == null) return await this.bootstrapState(cursor);
|
|
2783
|
+
return await this.catchupFromCursor(cursor);
|
|
2784
|
+
} catch (error) {
|
|
2785
|
+
if (!isStreamNotFoundError(error) || !this.createStreamIfMissing) throw error;
|
|
2786
|
+
await this.ensureStreamExists();
|
|
2787
|
+
return await this.bootstrapState(this.createInitialCursor());
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
async catchupJoinState(state, params) {
|
|
2791
|
+
if (state.readSubStatus === "connecting") {
|
|
2792
|
+
await state.started;
|
|
2793
|
+
return state.cursor;
|
|
2794
|
+
}
|
|
2795
|
+
if (state.readSubStatus === "ok" && params.force !== true) return state.cursor;
|
|
2796
|
+
const cursor = await this.enqueueExclusive(async () => {
|
|
2797
|
+
if (state.closed) throw new Error("subscription closed");
|
|
2798
|
+
const caughtUp = await this.catchupFromCursor(state.cursor, void 0, state.streamCursor, (previousVersion, nextVersion) => {
|
|
2799
|
+
this.snapshotManager.observeRemoteVersion(state, previousVersion, nextVersion);
|
|
2800
|
+
});
|
|
2801
|
+
state.cursor = caughtUp.cursor;
|
|
2802
|
+
state.streamCursor = caughtUp.streamCursor;
|
|
2803
|
+
return state.cursor;
|
|
2804
|
+
});
|
|
2805
|
+
if (state.closed) return cursor;
|
|
2806
|
+
state.retryAttempt = 0;
|
|
2807
|
+
state.lastError = void 0;
|
|
2808
|
+
if (state.readSubStatus !== "ok" || state.writeSubStatus === "error") this.rejoin();
|
|
2809
|
+
else if (state.pendingLocal.length > 0) this.flushPendingLocal(state);
|
|
2810
|
+
return cursor;
|
|
2811
|
+
}
|
|
2661
2812
|
async bootstrapState(cursor, options = {}) {
|
|
2662
2813
|
const cursorManager = options.cursorManager ?? this.cursorManager;
|
|
2663
2814
|
const result = await this.client.bootstrap();
|
|
@@ -2677,7 +2828,7 @@ var StreamsCrdt = class StreamsCrdt {
|
|
|
2677
2828
|
streamCursor: bootstrapCursor
|
|
2678
2829
|
};
|
|
2679
2830
|
}
|
|
2680
|
-
async
|
|
2831
|
+
async catchupFromCursor(cursor, live, streamCursor, onRemoteVersionApplied) {
|
|
2681
2832
|
const result = await this.catchupOrGone(cursor, {
|
|
2682
2833
|
live,
|
|
2683
2834
|
streamCursor,
|
|
@@ -3393,4 +3544,4 @@ function createStreamUrl(input) {
|
|
|
3393
3544
|
//#endregion
|
|
3394
3545
|
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 };
|
|
3395
3546
|
|
|
3396
|
-
//# sourceMappingURL=stream-id-
|
|
3547
|
+
//# sourceMappingURL=stream-id-fOF9ZqCH.js.map
|