@loro-dev/streams-crdt 0.9.0 → 0.10.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 CHANGED
@@ -189,6 +189,47 @@ const sub = joined.value;
189
189
  checked out or detached, call `checkoutToLatest()` or `attach()` before passing
190
190
  it to `StreamsCrdt`.
191
191
 
192
+ ### Request shard origins
193
+
194
+ Browsers can still show head-of-line blocking symptoms when large
195
+ bootstrap/catch-up downloads and normal app requests contend on the same
196
+ connection. `shardUrls` lets the transport replace only the request origin for
197
+ selected operations while preserving the stream path and query:
198
+
199
+ ```ts
200
+ const transport = new StreamsCrdt({
201
+ streamUrl,
202
+ auth: async () => "<gateway-jwt>",
203
+ adapter: createLoroDocAdapter(doc),
204
+ shardUrls: {
205
+ bootstrap: [
206
+ "https://control-a.streams-api-proxy.loro.dev",
207
+ "https://control-b.streams-api-proxy.loro.dev",
208
+ ],
209
+ catchup: [
210
+ "https://control-a.streams-api-proxy.loro.dev",
211
+ "https://control-b.streams-api-proxy.loro.dev",
212
+ ],
213
+ largePost: [
214
+ "https://write-a.streams-api-proxy.loro.dev",
215
+ "https://write-b.streams-api-proxy.loro.dev",
216
+ ],
217
+ largePostMinBytes: 64 * 1024,
218
+ },
219
+ });
220
+ ```
221
+
222
+ Shard entries must be origin URLs with no path, query, or hash. Each transport
223
+ instance rotates through the configured origins per operation, with a randomized
224
+ starting point so multiple tabs are less likely to all begin on the same shard.
225
+ Bootstrap applies to `GET <stream>/bootstrap`; catch-up applies to non-SSE
226
+ offset reads, including long-poll fallback; SSE live reads stay on the original
227
+ `streamUrl`. `largePost` applies to non-empty append POST bodies at or above
228
+ `largePostMinBytes` bytes. The default threshold is `0`, so every non-empty
229
+ append POST can use `largePost` when that pool is configured. Verify actual
230
+ connection separation with browser NetLog: browsers may coalesce compatible
231
+ HTTP/2 or HTTP/3 origins when DNS, certificate, and transport settings allow it.
232
+
192
233
  ### Loro pending imports and cursor safety
193
234
 
194
235
  Loro can accept an update whose dependencies are missing and keep it as a
package/dist/flock.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-JuJ1JqCX.js";
1
+ import { $ as IndexedDbRemoteCursorStore, A as PayloadProtectionReadPolicy, B as StreamsCrdtShardUrlsOptions, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportRoomStatus, H as TransportDeleteStreamSuccess, I as SnapshotUploadOptions, J as TransportSyncSuccess, K as TransportSnapshotUploadSuccess, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as InMemoryRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportError, V as TransportCreateStreamSuccess, W as TransportJoinParams, X as BeforeRemoteCursorSaveContext, Y as WriteOnlyAppendResult, Z as BeforeRemoteCursorSaveHook, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as IndexedDbRemoteCursorStoreOptions, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, it as createInitialRemoteCursor, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorSaveSource, o as StreamsCrdt, p as E2eeKey, q as TransportSubscription, r as isValidRillId, rt as RemoteCursorStore, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursor, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-BP6BR7jn.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, 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-BBE3LRAQ.js";
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-AfFikxB2.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 IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-JuJ1JqCX.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, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
1
+ import { $ as IndexedDbRemoteCursorStore, A as PayloadProtectionReadPolicy, B as StreamsCrdtShardUrlsOptions, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportRoomStatus, H as TransportDeleteStreamSuccess, I as SnapshotUploadOptions, J as TransportSyncSuccess, K as TransportSnapshotUploadSuccess, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as InMemoryRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportError, V as TransportCreateStreamSuccess, W as TransportJoinParams, X as BeforeRemoteCursorSaveContext, Y as WriteOnlyAppendResult, Z as BeforeRemoteCursorSaveHook, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as IndexedDbRemoteCursorStoreOptions, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, it as createInitialRemoteCursor, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorSaveSource, o as StreamsCrdt, p as E2eeKey, q as TransportSubscription, r as isValidRillId, rt as RemoteCursorStore, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursor, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-BP6BR7jn.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, 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-BBE3LRAQ.js";
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-AfFikxB2.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 IndexedDbRemoteCursorStoreOptions, A as PayloadProtectionReadPolicy, B as TransportCreateStreamSuccess, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportSnapshotUploadSuccess, H as TransportError, I as SnapshotUploadOptions, J as WriteOnlyAppendResult, K as TransportSubscription, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as IndexedDbRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportJoinParams, V as TransportDeleteStreamSuccess, W as TransportRoomStatus, X as BeforeRemoteCursorSaveHook, Y as BeforeRemoteCursorSaveContext, Z as InMemoryRemoteCursorStore, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as RemoteCursor, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorStore, o as StreamsCrdt, p as E2eeKey, q as TransportSyncSuccess, r as isValidRillId, rt as createInitialRemoteCursor, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursorSaveSource, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-JuJ1JqCX.js";
1
+ import { $ as IndexedDbRemoteCursorStore, A as PayloadProtectionReadPolicy, B as StreamsCrdtShardUrlsOptions, C as IsolatedCrdtAdapter, D as PayloadProtectionKey, E as PayloadProtectionEncryptionOptions, F as SnapshotTransformHook, G as TransportRoomStatus, H as TransportDeleteStreamSuccess, I as SnapshotUploadOptions, J as TransportSyncSuccess, K as TransportSnapshotUploadSuccess, L as StreamsAuthContext, M as PayloadProtectionWritePolicy, N as Result, O as PayloadProtectionKeyProvider, P as SnapshotCodec, Q as InMemoryRemoteCursorStore, R as StreamsAuthProvider, S as EphemeralStreamSubscription, T as JsonValue, U as TransportError, V as TransportCreateStreamSuccess, W as TransportJoinParams, X as BeforeRemoteCursorSaveContext, Y as WriteOnlyAppendResult, Z as BeforeRemoteCursorSaveHook, _ as E2eeScope, a as EphemeralStreamCrdt, b as EphemeralStreamCrdtOptions, c as CrdtApplyOutcome, d as CrdtUpdateBatch, et as IndexedDbRemoteCursorStoreOptions, f as E2eeEncryptionOptions, g as E2eeReadPolicy, h as E2eeOptions, i as PayloadProtectionError, it as createInitialRemoteCursor, j as PayloadProtectionScope, k as PayloadProtectionOptions, l as CrdtApplyReturn, m as E2eeKeyProvider, n as isValidBucketId, nt as RemoteCursorSaveSource, o as StreamsCrdt, p as E2eeKey, q as TransportSubscription, r as isValidRillId, rt as RemoteCursorStore, s as CrdtAdapter, t as createStreamUrl, tt as RemoteCursor, u as CrdtUnresolvedSpan, v as E2eeWritePolicy, w as JsonObject, x as EphemeralStreamJoinParams, y as EphemeralStreamAdaptor, z as StreamsCrdtOptions } from "./stream-id-BP6BR7jn.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, 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-BBE3LRAQ.js";
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-AfFikxB2.js";
2
2
  import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
3
3
  //#region src/loro.ts
4
4
  function batchImportUpdates(doc, updates) {
@@ -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: options.fetchImpl,
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 });
@@ -951,10 +1039,11 @@ var StreamsTransportClient = class {
951
1039
  async bootstrap() {
952
1040
  const result = await this.client.bootstrap();
953
1041
  if (!result.ok) throw this.toTransportFailure(result.result);
1042
+ const snapshot = result.result.snapshotOffset === "-1" ? new Uint8Array() : result.result.snapshot?.body ?? new Uint8Array();
954
1043
  return {
955
1044
  nextOffset: result.result.nextOffset,
956
1045
  upToDate: result.result.upToDate,
957
- snapshot: result.result.snapshot?.body ?? new Uint8Array(),
1046
+ snapshot,
958
1047
  updates: result.result.updates.flatMap((part) => decodeMultipartPartItems(part.body)),
959
1048
  streamCursor: result.result.cursor
960
1049
  };
@@ -2208,7 +2297,8 @@ var StreamsCrdt = class StreamsCrdt {
2208
2297
  adapter: this.adapter,
2209
2298
  authProvider,
2210
2299
  fetchImpl,
2211
- reconnectConfig: this.reconnectConfig
2300
+ reconnectConfig: this.reconnectConfig,
2301
+ shardUrls: options.shardUrls
2212
2302
  });
2213
2303
  this.cursorManager = this.createCursorManager(this.adapter, (cursor, source) => this.saveRemoteCursor(cursor, source));
2214
2304
  this.volatileCursorManager = this.createCursorManager(this.adapter, async (cursor) => cursor);
@@ -3392,4 +3482,4 @@ function createStreamUrl(input) {
3392
3482
  //#endregion
3393
3483
  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 };
3394
3484
 
3395
- //# sourceMappingURL=stream-id-BBE3LRAQ.js.map
3485
+ //# sourceMappingURL=stream-id-AfFikxB2.js.map