@loro-dev/streams-crdt 0.8.0 → 0.8.2
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 +83 -2
- 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 -4
- package/dist/loro.js +142 -22
- package/dist/loro.js.map +1 -1
- package/dist/{stream-id-BXJvwX_O.d.ts → stream-id-Bi5rAZfP.d.ts} +36 -19
- package/dist/{stream-id-CF1egQDz.js → stream-id-BjmGvObf.js} +183 -63
- package/dist/stream-id-BjmGvObf.js.map +1 -0
- package/package.json +1 -1
- package/dist/stream-id-CF1egQDz.js.map +0 -1
package/README.md
CHANGED
|
@@ -118,12 +118,30 @@ const sub = joined.value;
|
|
|
118
118
|
checked out or detached, call `checkoutToLatest()` or `attach()` before passing
|
|
119
119
|
it to `StreamsCrdt`.
|
|
120
120
|
|
|
121
|
+
### Loro pending imports and cursor safety
|
|
122
|
+
|
|
123
|
+
Loro can accept an update whose dependencies are missing and keep it as a
|
|
124
|
+
pending import. Pending imports are not represented in `doc.version()` and are
|
|
125
|
+
not included in snapshots exported from that document. For that reason, the Loro
|
|
126
|
+
adapter must not persist a `RemoteCursor` until all pending imports are resolved
|
|
127
|
+
and the local document is complete at the cursor offset.
|
|
128
|
+
|
|
129
|
+
The transport may continue reading with an in-memory volatile cursor to find the
|
|
130
|
+
missing dependencies. If the server reports up-to-date while Loro still has
|
|
131
|
+
unresolved imports, the client must bootstrap again and only save the bootstrap
|
|
132
|
+
cursor after the batch import is clean. Snapshot upload is skipped while Loro has
|
|
133
|
+
unresolved imports.
|
|
134
|
+
|
|
135
|
+
See [`specs/loro-pending.md`](../../specs/loro-pending.md) for the full behavior
|
|
136
|
+
and implementation checklist.
|
|
137
|
+
|
|
121
138
|
## Quick Start (Loro EphemeralStore)
|
|
122
139
|
|
|
123
140
|
Use a separate `EphemeralStreamCrdt` for presence-like Loro `EphemeralStore`
|
|
124
141
|
data. It bootstraps through the first SSE event, posts local ephemeral updates,
|
|
125
|
-
and
|
|
126
|
-
|
|
142
|
+
and never republishes state imported from other peers. If a client needs to keep
|
|
143
|
+
its own presence alive, update that client's own keys with `store.set()` so the
|
|
144
|
+
store emits a fresh local update.
|
|
127
145
|
|
|
128
146
|
Ephemeral live delivery is latest-state oriented. The server may keep only a
|
|
129
147
|
tiny live fanout queue for current SSE subscribers and may disconnect slow
|
|
@@ -155,6 +173,65 @@ const joinedPresence = await presence.join();
|
|
|
155
173
|
if (!joinedPresence.ok) throw joinedPresence.error;
|
|
156
174
|
```
|
|
157
175
|
|
|
176
|
+
### Ephemeral stream URL format
|
|
177
|
+
|
|
178
|
+
An ephemeral stream URL is the durable stream URL for the same document plus a
|
|
179
|
+
channel query parameter:
|
|
180
|
+
|
|
181
|
+
```txt
|
|
182
|
+
{baseUrl}/ds/{bucketId}/{streamId}?ephemeral={channel}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
For example, if the durable Flock document stream is:
|
|
186
|
+
|
|
187
|
+
```txt
|
|
188
|
+
https://streams-api.loro.dev/ds/my-bucket/flock-cm9vbS0x
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
then the matching presence stream is:
|
|
192
|
+
|
|
193
|
+
```txt
|
|
194
|
+
https://streams-api.loro.dev/ds/my-bucket/flock-cm9vbS0x?ephemeral=presence
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Use the same `bucketId` and `streamId` as the durable `StreamsCrdt` room. The
|
|
198
|
+
`ephemeral` value names a side-channel for that room, such as `presence`,
|
|
199
|
+
`cursor`, or `selection`. Different channels under the same durable stream are
|
|
200
|
+
separate in-memory rooms. The compatibility spelling `?awareness=presence` is
|
|
201
|
+
also accepted, but new code should use `?ephemeral=presence`.
|
|
202
|
+
|
|
203
|
+
When an app exposes a room link such as `/rooms/design-review-42`, the link
|
|
204
|
+
normally maps to one durable stream id first, and both durable and ephemeral
|
|
205
|
+
sync derive from that same id:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const roomId = "design-review-42";
|
|
209
|
+
const durableStreamUrl = createStreamUrl({
|
|
210
|
+
bucketId: "my-bucket",
|
|
211
|
+
streamId: `flock-${roomId}`,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const presenceStreamUrl = new URL(durableStreamUrl);
|
|
215
|
+
presenceStreamUrl.searchParams.set("ephemeral", "presence");
|
|
216
|
+
|
|
217
|
+
const flockTransport = new StreamsCrdt({
|
|
218
|
+
streamUrl: durableStreamUrl,
|
|
219
|
+
auth,
|
|
220
|
+
adapter: createFlockAdapter(flock),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const presenceTransport = new EphemeralStreamCrdt({
|
|
224
|
+
streamUrl: presenceStreamUrl.toString(),
|
|
225
|
+
auth,
|
|
226
|
+
adaptor: EphemeralStoreAdaptor(store),
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`EphemeralStreamCrdt` does not create or delete the durable stream. It posts
|
|
231
|
+
updates to the ephemeral URL and opens SSE on the same URL with `live=sse`.
|
|
232
|
+
Ephemeral state is not persisted with the Flock document stream; it is scoped by
|
|
233
|
+
the URL path plus channel and is meant for current live state only.
|
|
234
|
+
|
|
158
235
|
## Quick Start (Flock)
|
|
159
236
|
|
|
160
237
|
```ts
|
|
@@ -799,9 +876,13 @@ const transport = new StreamsCrdt({
|
|
|
799
876
|
- `CrdtAdapter` — adapter contract interface
|
|
800
877
|
- `IsolatedCrdtAdapter` — optional apply-only adapter used for safe 410 recovery
|
|
801
878
|
- `CrdtUpdateBatch` — local update batch type
|
|
879
|
+
- `CrdtApplyOutcome`, `CrdtApplyReturn`, `CrdtUnresolvedSpan` — adapter apply
|
|
880
|
+
result types used to report incomplete imports such as Loro pending updates
|
|
802
881
|
- `Result` — Rust-style `Ok | Err` result
|
|
803
882
|
- `StreamsAuthProvider` — auth callback type
|
|
804
883
|
- `TransportError` — discriminated error union
|
|
884
|
+
`apply_incomplete` is returned when remote data was read but the CRDT adapter
|
|
885
|
+
still has unresolved dependencies and the cursor cannot be saved.
|
|
805
886
|
- `TransportCreateStreamSuccess`, `TransportDeleteStreamSuccess`, `TransportSyncSuccess` — result payloads
|
|
806
887
|
- `TransportJoinParams` — `join()` options
|
|
807
888
|
- `TransportSubscription` — active live subscription handle
|
package/dist/flock.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
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-Bi5rAZfP.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, 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, 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-BjmGvObf.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, 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 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-Bi5rAZfP.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 };
|
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-BjmGvObf.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 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-Bi5rAZfP.js";
|
|
2
2
|
import { LoroDoc } from "loro-crdt";
|
|
3
3
|
|
|
4
4
|
//#region src/loro.d.ts
|
|
@@ -21,8 +21,6 @@ declare function createLoroDocAdapter(doc: LoroDoc): CrdtAdapter<JsonObject>;
|
|
|
21
21
|
* Structural subset of Loro's `EphemeralStore`.
|
|
22
22
|
*/
|
|
23
23
|
interface LoroEphemeralStoreLike {
|
|
24
|
-
/** Encodes the current non-expired store state for bootstrap/lease publish. */
|
|
25
|
-
encodeAll(): Uint8Array;
|
|
26
24
|
/** Applies one opaque Loro ephemeral update. */
|
|
27
25
|
apply(update: Uint8Array): void;
|
|
28
26
|
/** Subscribes to locally produced ephemeral updates. */
|
|
@@ -43,5 +41,5 @@ interface LoroEphemeralStoreLike {
|
|
|
43
41
|
declare function EphemeralStoreAdaptor(store: LoroEphemeralStoreLike): EphemeralStreamAdaptor;
|
|
44
42
|
declare const createLoroEphemeralStoreAdaptor: typeof EphemeralStoreAdaptor;
|
|
45
43
|
//#endregion
|
|
46
|
-
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, 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, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSnapshotUploadSuccess, TransportSubscription, TransportSyncSuccess, WriteOnlyAppendResult, createInitialRemoteCursor, createLoroDocAdapter, createLoroEphemeralStoreAdaptor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
47
45
|
//# sourceMappingURL=loro.d.ts.map
|
package/dist/loro.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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-BjmGvObf.js";
|
|
2
2
|
import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
|
|
3
3
|
//#region src/loro.ts
|
|
4
4
|
function batchImportUpdates(doc, updates) {
|
|
5
|
-
if ("batchImport" in doc && typeof doc.batchImport === "function") doc.batchImport(updates);
|
|
6
|
-
|
|
5
|
+
if ("batchImport" in doc && typeof doc.batchImport === "function") return doc.batchImport(updates);
|
|
6
|
+
return doc.importBatch(updates);
|
|
7
7
|
}
|
|
8
8
|
function toDocVersionRecord(version) {
|
|
9
9
|
const out = {};
|
|
@@ -39,6 +39,106 @@ function assertDocAttached(doc) {
|
|
|
39
39
|
if (!doc.isDetached()) return;
|
|
40
40
|
throw new Error("LoroDoc must be attached before using streams-crdt; call checkoutToLatest() or attach() first");
|
|
41
41
|
}
|
|
42
|
+
function normalizePeerId(peer) {
|
|
43
|
+
return `${peer}`;
|
|
44
|
+
}
|
|
45
|
+
function isValidSpan(span) {
|
|
46
|
+
return Number.isSafeInteger(span.start) && Number.isSafeInteger(span.end) && span.start < span.end;
|
|
47
|
+
}
|
|
48
|
+
var UnresolvedSpanTracker = class {
|
|
49
|
+
spans = /* @__PURE__ */ new Map();
|
|
50
|
+
addAll(pending) {
|
|
51
|
+
if (pending == null) return;
|
|
52
|
+
for (const [peer, span] of pending) this.add(normalizePeerId(peer), span);
|
|
53
|
+
}
|
|
54
|
+
resolveAll(success) {
|
|
55
|
+
for (const [peer, span] of success) this.resolve(normalizePeerId(peer), span);
|
|
56
|
+
}
|
|
57
|
+
isEmpty() {
|
|
58
|
+
return this.spans.size === 0;
|
|
59
|
+
}
|
|
60
|
+
toArray() {
|
|
61
|
+
const out = [];
|
|
62
|
+
for (const [peer, spans] of this.spans) for (const span of spans) out.push({
|
|
63
|
+
peer,
|
|
64
|
+
start: span.start,
|
|
65
|
+
end: span.end
|
|
66
|
+
});
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
reset() {
|
|
70
|
+
this.spans.clear();
|
|
71
|
+
}
|
|
72
|
+
add(peer, incoming) {
|
|
73
|
+
if (!isValidSpan(incoming)) return;
|
|
74
|
+
const existing = this.spans.get(peer) ?? [];
|
|
75
|
+
existing.push({
|
|
76
|
+
start: incoming.start,
|
|
77
|
+
end: incoming.end
|
|
78
|
+
});
|
|
79
|
+
existing.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
80
|
+
const merged = [];
|
|
81
|
+
for (const span of existing) {
|
|
82
|
+
const last = merged[merged.length - 1];
|
|
83
|
+
if (last == null || span.start > last.end) merged.push({
|
|
84
|
+
start: span.start,
|
|
85
|
+
end: span.end
|
|
86
|
+
});
|
|
87
|
+
else merged[merged.length - 1] = {
|
|
88
|
+
start: last.start,
|
|
89
|
+
end: Math.max(last.end, span.end)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
this.spans.set(peer, merged);
|
|
93
|
+
}
|
|
94
|
+
resolve(peer, applied) {
|
|
95
|
+
if (!isValidSpan(applied)) return;
|
|
96
|
+
const existing = this.spans.get(peer);
|
|
97
|
+
if (existing == null) return;
|
|
98
|
+
const remaining = [];
|
|
99
|
+
for (const span of existing) {
|
|
100
|
+
if (applied.end <= span.start || applied.start >= span.end) {
|
|
101
|
+
remaining.push(span);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (span.start < applied.start) remaining.push({
|
|
105
|
+
start: span.start,
|
|
106
|
+
end: applied.start
|
|
107
|
+
});
|
|
108
|
+
if (applied.end < span.end) remaining.push({
|
|
109
|
+
start: applied.end,
|
|
110
|
+
end: span.end
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (remaining.length === 0) this.spans.delete(peer);
|
|
114
|
+
else this.spans.set(peer, remaining);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
function trackImportStatus(tracker, status) {
|
|
118
|
+
tracker.addAll(status.pending);
|
|
119
|
+
tracker.resolveAll(status.success);
|
|
120
|
+
}
|
|
121
|
+
function versionFromImportSuccess(status) {
|
|
122
|
+
const version = {};
|
|
123
|
+
for (const [peer, span] of status.success) {
|
|
124
|
+
if (!isValidSpan(span)) continue;
|
|
125
|
+
const peerId = normalizePeerId(peer);
|
|
126
|
+
version[peerId] = Math.max(version[peerId] ?? 0, span.end);
|
|
127
|
+
}
|
|
128
|
+
return version;
|
|
129
|
+
}
|
|
130
|
+
function versionFromSnapshotImport(snapshot, status, tracker) {
|
|
131
|
+
if (!tracker.isEmpty()) return versionFromImportSuccess(status);
|
|
132
|
+
return extractVersionFromUpdate(snapshot) ?? versionFromImportSuccess(status);
|
|
133
|
+
}
|
|
134
|
+
function applyOutcome(version, tracker) {
|
|
135
|
+
const unresolved = tracker.toArray();
|
|
136
|
+
return {
|
|
137
|
+
version,
|
|
138
|
+
complete: unresolved.length === 0,
|
|
139
|
+
...unresolved.length === 0 ? {} : { unresolved }
|
|
140
|
+
};
|
|
141
|
+
}
|
|
42
142
|
/**
|
|
43
143
|
* Creates a `CrdtAdapter` for one `LoroDoc`.
|
|
44
144
|
*
|
|
@@ -54,6 +154,7 @@ function assertDocAttached(doc) {
|
|
|
54
154
|
* ```
|
|
55
155
|
*/
|
|
56
156
|
function createLoroDocAdapter(doc) {
|
|
157
|
+
const unresolved = new UnresolvedSpanTracker();
|
|
57
158
|
return {
|
|
58
159
|
emptyVersion: () => ({}),
|
|
59
160
|
mergeVersions: (base, incoming) => mergeDocVersions(base, incoming),
|
|
@@ -63,8 +164,10 @@ function createLoroDocAdapter(doc) {
|
|
|
63
164
|
},
|
|
64
165
|
applySnapshot: (snapshot) => {
|
|
65
166
|
assertDocAttached(doc);
|
|
66
|
-
doc.import(snapshot);
|
|
67
|
-
|
|
167
|
+
const status = doc.import(snapshot);
|
|
168
|
+
unresolved.reset();
|
|
169
|
+
trackImportStatus(unresolved, status);
|
|
170
|
+
return applyOutcome(versionFromSnapshotImport(snapshot, status, unresolved), unresolved);
|
|
68
171
|
},
|
|
69
172
|
exportUpdates: (fromVersion) => {
|
|
70
173
|
assertDocAttached(doc);
|
|
@@ -86,30 +189,34 @@ function createLoroDocAdapter(doc) {
|
|
|
86
189
|
},
|
|
87
190
|
applyRemoteUpdates: (updates, remoterVersion) => {
|
|
88
191
|
assertDocAttached(doc);
|
|
89
|
-
if (updates.length > 0)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (next != null) version = mergeDocVersions(version, next);
|
|
192
|
+
if (updates.length > 0) {
|
|
193
|
+
const status = batchImportUpdates(doc, updates);
|
|
194
|
+
trackImportStatus(unresolved, status);
|
|
195
|
+
return applyOutcome(mergeDocVersions(remoterVersion, versionFromImportSuccess(status)), unresolved);
|
|
94
196
|
}
|
|
95
|
-
return
|
|
197
|
+
return applyOutcome(remoterVersion, unresolved);
|
|
96
198
|
},
|
|
97
199
|
createIsolatedAdapter: () => {
|
|
98
200
|
let isolatedDoc = new LoroDoc();
|
|
201
|
+
const isolatedUnresolved = new UnresolvedSpanTracker();
|
|
99
202
|
return {
|
|
100
203
|
applySnapshot: (snapshot) => {
|
|
101
204
|
isolatedDoc = new LoroDoc();
|
|
102
|
-
|
|
103
|
-
|
|
205
|
+
isolatedUnresolved.reset();
|
|
206
|
+
if (snapshot.byteLength > 0) {
|
|
207
|
+
const status = isolatedDoc.import(snapshot);
|
|
208
|
+
trackImportStatus(isolatedUnresolved, status);
|
|
209
|
+
return applyOutcome(versionFromSnapshotImport(snapshot, status, isolatedUnresolved), isolatedUnresolved);
|
|
210
|
+
}
|
|
211
|
+
return applyOutcome({}, isolatedUnresolved);
|
|
104
212
|
},
|
|
105
213
|
applyRemoteUpdates: (updates, remoterVersion) => {
|
|
106
|
-
if (updates.length > 0)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (next != null) version = mergeDocVersions(version, next);
|
|
214
|
+
if (updates.length > 0) {
|
|
215
|
+
const status = batchImportUpdates(isolatedDoc, updates);
|
|
216
|
+
trackImportStatus(isolatedUnresolved, status);
|
|
217
|
+
return applyOutcome(mergeDocVersions(remoterVersion, versionFromImportSuccess(status)), isolatedUnresolved);
|
|
111
218
|
}
|
|
112
|
-
return
|
|
219
|
+
return applyOutcome(remoterVersion, isolatedUnresolved);
|
|
113
220
|
}
|
|
114
221
|
};
|
|
115
222
|
},
|
|
@@ -138,12 +245,25 @@ function createLoroDocAdapter(doc) {
|
|
|
138
245
|
* ```
|
|
139
246
|
*/
|
|
140
247
|
function EphemeralStoreAdaptor(store) {
|
|
248
|
+
let applyingRemoteDepth = 0;
|
|
141
249
|
return {
|
|
142
|
-
encodeAll: () => store.encodeAll(),
|
|
143
250
|
applyRemoteUpdates: (updates) => {
|
|
144
|
-
|
|
251
|
+
applyingRemoteDepth += 1;
|
|
252
|
+
try {
|
|
253
|
+
for (const update of updates) store.apply(update);
|
|
254
|
+
} finally {
|
|
255
|
+
applyingRemoteDepth -= 1;
|
|
256
|
+
}
|
|
145
257
|
},
|
|
146
|
-
subscribeLocalUpdates: (listener) =>
|
|
258
|
+
subscribeLocalUpdates: (listener) => {
|
|
259
|
+
const unsubscribeLocalUpdates = store.subscribeLocalUpdates((update) => {
|
|
260
|
+
if (applyingRemoteDepth > 0) return;
|
|
261
|
+
listener(update);
|
|
262
|
+
});
|
|
263
|
+
return () => {
|
|
264
|
+
unsubscribeLocalUpdates();
|
|
265
|
+
};
|
|
266
|
+
}
|
|
147
267
|
};
|
|
148
268
|
}
|
|
149
269
|
const createLoroEphemeralStoreAdaptor = EphemeralStoreAdaptor;
|
package/dist/loro.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loro.js","names":[],"sources":["../src/loro.ts"],"sourcesContent":["import {\n decodeImportBlobMeta,\n LoroDoc,\n VersionVector,\n} from \"loro-crdt\";\n\nimport type { CrdtAdapter, JsonObject } from \"./types\";\nimport type { EphemeralStreamAdaptor } from \"./types\";\n\nexport { EphemeralStreamCrdt } from \"./ephemeral-runtime\";\nexport { StreamsCrdt } from \"./transport-runtime\";\nexport * from \"./shared\";\n\nfunction batchImportUpdates(doc: LoroDoc, updates: Uint8Array[]): void {\n if (\"batchImport\" in doc && typeof doc.batchImport === \"function\") {\n doc.batchImport(updates);\n } else {\n doc.importBatch(updates);\n }\n}\n\ntype PeerId = `${number}`;\ntype DocVersionRecord = Record<PeerId, number>;\n\nfunction toDocVersionRecord(version: VersionVector): DocVersionRecord {\n const out = {} as DocVersionRecord;\n for (const [peer, counter] of version.toJSON()) {\n out[peer as PeerId] = counter;\n }\n return out;\n}\n\nfunction toDocVersionMap(version: JsonObject): Map<PeerId, number> {\n const out = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(version)) {\n if (typeof counter === \"number\" && Number.isFinite(counter)) {\n out.set(peer as PeerId, counter);\n }\n }\n return out;\n}\n\nfunction mergeDocVersions(\n base: DocVersionRecord,\n incoming: DocVersionRecord,\n): DocVersionRecord {\n const merged = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(base)) {\n merged.set(peer as PeerId, counter);\n }\n for (const [peer, counter] of Object.entries(incoming)) {\n const peerId = peer as PeerId;\n merged.set(peerId, Math.max(merged.get(peerId) ?? 0, counter));\n }\n return toDocVersionRecord(VersionVector.parseJSON(merged));\n}\n\nfunction hasUpdatesSince(\n fromVersion: DocVersionRecord,\n currentVersion: DocVersionRecord,\n): boolean {\n for (const [peer, counter] of Object.entries(currentVersion)) {\n if (counter > (fromVersion[peer as PeerId] ?? 0)) {\n return true;\n }\n }\n return false;\n}\n\nfunction extractVersionFromUpdate(update: Uint8Array): DocVersionRecord | null {\n try {\n const meta = decodeImportBlobMeta(update, false);\n return toDocVersionRecord(meta.partialEndVersionVector);\n } catch {\n return null;\n }\n}\n\nfunction assertDocAttached(doc: LoroDoc): void {\n if (!doc.isDetached()) {\n return;\n }\n throw new Error(\n \"LoroDoc must be attached before using streams-crdt; \" +\n \"call checkoutToLatest() or attach() first\",\n );\n}\n\n/**\n * Creates a `CrdtAdapter` for one `LoroDoc`.\n *\n * The `LoroDoc` must be attached (not detached or checked out to a historical\n * version). If the document is detached, call `doc.checkoutToLatest()` or\n * `doc.attach()` before passing it here.\n *\n * @example\n * ```ts\n * const doc = new LoroDoc();\n * const adapter = createLoroDocAdapter(doc);\n * const transport = new StreamsCrdt({ streamUrl, adapter, auth });\n * ```\n */\nexport function createLoroDocAdapter(\n doc: LoroDoc,\n): CrdtAdapter<JsonObject> {\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base, incoming) =>\n mergeDocVersions(base as DocVersionRecord, incoming as DocVersionRecord),\n exportSnapshot: () => {\n assertDocAttached(doc);\n return doc.export({ mode: \"snapshot\" });\n },\n applySnapshot: (snapshot) => {\n assertDocAttached(doc);\n doc.import(snapshot);\n return toDocVersionRecord(doc.version());\n },\n exportUpdates: (fromVersion) => {\n assertDocAttached(doc);\n const currentVersion = toDocVersionRecord(doc.version());\n if (\n !hasUpdatesSince(\n fromVersion as DocVersionRecord,\n currentVersion,\n )\n ) {\n return undefined;\n }\n const updates = [\n doc.export({\n mode: \"update\",\n from: VersionVector.parseJSON(toDocVersionMap(fromVersion)),\n }),\n ].filter((update) => update.byteLength > 0);\n if (updates.length === 0) {\n return undefined;\n }\n let version: DocVersionRecord = {};\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return {\n updates,\n version,\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n assertDocAttached(doc);\n if (updates.length > 0) {\n batchImportUpdates(doc, updates);\n }\n let version = remoterVersion as DocVersionRecord;\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return version;\n },\n createIsolatedAdapter: () => {\n let isolatedDoc = new LoroDoc();\n return {\n applySnapshot: (snapshot) => {\n isolatedDoc = new LoroDoc();\n if (snapshot.byteLength > 0) {\n isolatedDoc.import(snapshot);\n }\n return toDocVersionRecord(isolatedDoc.version());\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n if (updates.length > 0) {\n batchImportUpdates(isolatedDoc, updates);\n }\n let version = remoterVersion as DocVersionRecord;\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return version;\n },\n };\n },\n subscribeLocalUpdates: (listener) => {\n assertDocAttached(doc);\n return doc.subscribeLocalUpdates((update) => {\n const version = extractVersionFromUpdate(update) ?? {};\n listener({ updates: [update], version });\n });\n },\n };\n}\n\n/**\n * Structural subset of Loro's `EphemeralStore`.\n */\nexport interface LoroEphemeralStoreLike {\n /** Encodes the current non-expired store state for bootstrap/lease publish. */\n encodeAll(): Uint8Array;\n /** Applies one opaque Loro ephemeral update. */\n apply(update: Uint8Array): void;\n /** Subscribes to locally produced ephemeral updates. */\n subscribeLocalUpdates(listener: (update: Uint8Array) => void): () => void;\n}\n\n/**\n * Creates an ephemeral stream adaptor for a Loro `EphemeralStore`.\n *\n * @example\n * ```ts\n * const room = new EphemeralStreamCrdt({\n * streamUrl,\n * auth,\n * adaptor: EphemeralStoreAdaptor(store),\n * });\n * ```\n */\nexport function EphemeralStoreAdaptor(\n store: LoroEphemeralStoreLike,\n): EphemeralStreamAdaptor {\n return {\n encodeAll: () => store.encodeAll(),\n applyRemoteUpdates: (updates) => {\n for (const update of updates) {\n store.apply(update);\n }\n },\n subscribeLocalUpdates: (listener) =>\n store.subscribeLocalUpdates(listener),\n };\n}\n\nexport const createLoroEphemeralStoreAdaptor = EphemeralStoreAdaptor;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"loro.js","names":[],"sources":["../src/loro.ts"],"sourcesContent":["import {\n decodeImportBlobMeta,\n LoroDoc,\n VersionVector,\n} from \"loro-crdt\";\n\nimport type {\n CrdtAdapter,\n CrdtApplyOutcome,\n CrdtUnresolvedSpan,\n JsonObject,\n} from \"./types\";\nimport type { EphemeralStreamAdaptor } from \"./types\";\n\nexport { EphemeralStreamCrdt } from \"./ephemeral-runtime\";\nexport { StreamsCrdt } from \"./transport-runtime\";\nexport * from \"./shared\";\n\ntype CounterSpan = {\n readonly start: number;\n readonly end: number;\n};\n\ntype LoroImportStatus = {\n readonly success: Map<string | number | bigint, CounterSpan>;\n readonly pending: Map<string | number | bigint, CounterSpan> | null;\n};\n\nfunction batchImportUpdates(\n doc: LoroDoc,\n updates: Uint8Array[],\n): LoroImportStatus {\n if (\"batchImport\" in doc && typeof doc.batchImport === \"function\") {\n return doc.batchImport(updates) as LoroImportStatus;\n }\n return doc.importBatch(updates) as LoroImportStatus;\n}\n\ntype PeerId = `${number}`;\ntype DocVersionRecord = Record<PeerId, number>;\n\nfunction toDocVersionRecord(version: VersionVector): DocVersionRecord {\n const out = {} as DocVersionRecord;\n for (const [peer, counter] of version.toJSON()) {\n out[peer as PeerId] = counter;\n }\n return out;\n}\n\nfunction toDocVersionMap(version: JsonObject): Map<PeerId, number> {\n const out = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(version)) {\n if (typeof counter === \"number\" && Number.isFinite(counter)) {\n out.set(peer as PeerId, counter);\n }\n }\n return out;\n}\n\nfunction mergeDocVersions(\n base: DocVersionRecord,\n incoming: DocVersionRecord,\n): DocVersionRecord {\n const merged = new Map<PeerId, number>();\n for (const [peer, counter] of Object.entries(base)) {\n merged.set(peer as PeerId, counter);\n }\n for (const [peer, counter] of Object.entries(incoming)) {\n const peerId = peer as PeerId;\n merged.set(peerId, Math.max(merged.get(peerId) ?? 0, counter));\n }\n return toDocVersionRecord(VersionVector.parseJSON(merged));\n}\n\nfunction hasUpdatesSince(\n fromVersion: DocVersionRecord,\n currentVersion: DocVersionRecord,\n): boolean {\n for (const [peer, counter] of Object.entries(currentVersion)) {\n if (counter > (fromVersion[peer as PeerId] ?? 0)) {\n return true;\n }\n }\n return false;\n}\n\nfunction extractVersionFromUpdate(update: Uint8Array): DocVersionRecord | null {\n try {\n const meta = decodeImportBlobMeta(update, false);\n return toDocVersionRecord(meta.partialEndVersionVector);\n } catch {\n return null;\n }\n}\n\nfunction assertDocAttached(doc: LoroDoc): void {\n if (!doc.isDetached()) {\n return;\n }\n throw new Error(\n \"LoroDoc must be attached before using streams-crdt; \" +\n \"call checkoutToLatest() or attach() first\",\n );\n}\n\nfunction normalizePeerId(peer: string | number | bigint): PeerId {\n return `${peer}` as PeerId;\n}\n\nfunction isValidSpan(span: CounterSpan): boolean {\n return (\n Number.isSafeInteger(span.start) &&\n Number.isSafeInteger(span.end) &&\n span.start < span.end\n );\n}\n\nclass UnresolvedSpanTracker {\n private readonly spans = new Map<PeerId, CounterSpan[]>();\n\n addAll(pending: LoroImportStatus[\"pending\"]): void {\n if (pending == null) {\n return;\n }\n for (const [peer, span] of pending) {\n this.add(normalizePeerId(peer), span);\n }\n }\n\n resolveAll(success: LoroImportStatus[\"success\"]): void {\n for (const [peer, span] of success) {\n this.resolve(normalizePeerId(peer), span);\n }\n }\n\n isEmpty(): boolean {\n return this.spans.size === 0;\n }\n\n toArray(): CrdtUnresolvedSpan[] {\n const out: CrdtUnresolvedSpan[] = [];\n for (const [peer, spans] of this.spans) {\n for (const span of spans) {\n out.push({ peer, start: span.start, end: span.end });\n }\n }\n return out;\n }\n\n reset(): void {\n this.spans.clear();\n }\n\n private add(peer: PeerId, incoming: CounterSpan): void {\n if (!isValidSpan(incoming)) {\n return;\n }\n const existing = this.spans.get(peer) ?? [];\n existing.push({ start: incoming.start, end: incoming.end });\n existing.sort((a, b) => a.start - b.start || a.end - b.end);\n const merged: CounterSpan[] = [];\n for (const span of existing) {\n const last = merged[merged.length - 1];\n if (last == null || span.start > last.end) {\n merged.push({ start: span.start, end: span.end });\n } else {\n merged[merged.length - 1] = {\n start: last.start,\n end: Math.max(last.end, span.end),\n };\n }\n }\n this.spans.set(peer, merged);\n }\n\n private resolve(peer: PeerId, applied: CounterSpan): void {\n if (!isValidSpan(applied)) {\n return;\n }\n const existing = this.spans.get(peer);\n if (existing == null) {\n return;\n }\n const remaining: CounterSpan[] = [];\n for (const span of existing) {\n if (applied.end <= span.start || applied.start >= span.end) {\n remaining.push(span);\n continue;\n }\n if (span.start < applied.start) {\n remaining.push({ start: span.start, end: applied.start });\n }\n if (applied.end < span.end) {\n remaining.push({ start: applied.end, end: span.end });\n }\n }\n if (remaining.length === 0) {\n this.spans.delete(peer);\n } else {\n this.spans.set(peer, remaining);\n }\n }\n}\n\nfunction trackImportStatus(\n tracker: UnresolvedSpanTracker,\n status: LoroImportStatus,\n): void {\n tracker.addAll(status.pending);\n tracker.resolveAll(status.success);\n}\n\nfunction versionFromImportSuccess(status: LoroImportStatus): DocVersionRecord {\n const version: DocVersionRecord = {};\n for (const [peer, span] of status.success) {\n if (!isValidSpan(span)) {\n continue;\n }\n const peerId = normalizePeerId(peer);\n version[peerId] = Math.max(version[peerId] ?? 0, span.end);\n }\n return version;\n}\n\nfunction versionFromSnapshotImport(\n snapshot: Uint8Array,\n status: LoroImportStatus,\n tracker: UnresolvedSpanTracker,\n): DocVersionRecord {\n if (!tracker.isEmpty()) {\n return versionFromImportSuccess(status);\n }\n return extractVersionFromUpdate(snapshot) ?? versionFromImportSuccess(status);\n}\n\nfunction applyOutcome(\n version: DocVersionRecord,\n tracker: UnresolvedSpanTracker,\n): CrdtApplyOutcome<JsonObject> {\n const unresolved = tracker.toArray();\n return {\n version,\n complete: unresolved.length === 0,\n ...(unresolved.length === 0 ? {} : { unresolved }),\n };\n}\n\n/**\n * Creates a `CrdtAdapter` for one `LoroDoc`.\n *\n * The `LoroDoc` must be attached (not detached or checked out to a historical\n * version). If the document is detached, call `doc.checkoutToLatest()` or\n * `doc.attach()` before passing it here.\n *\n * @example\n * ```ts\n * const doc = new LoroDoc();\n * const adapter = createLoroDocAdapter(doc);\n * const transport = new StreamsCrdt({ streamUrl, adapter, auth });\n * ```\n */\nexport function createLoroDocAdapter(\n doc: LoroDoc,\n): CrdtAdapter<JsonObject> {\n const unresolved = new UnresolvedSpanTracker();\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base, incoming) =>\n mergeDocVersions(base as DocVersionRecord, incoming as DocVersionRecord),\n exportSnapshot: () => {\n assertDocAttached(doc);\n return doc.export({ mode: \"snapshot\" });\n },\n applySnapshot: (snapshot) => {\n assertDocAttached(doc);\n const status = doc.import(snapshot) as LoroImportStatus;\n unresolved.reset();\n trackImportStatus(unresolved, status);\n return applyOutcome(\n versionFromSnapshotImport(snapshot, status, unresolved),\n unresolved,\n );\n },\n exportUpdates: (fromVersion) => {\n assertDocAttached(doc);\n const currentVersion = toDocVersionRecord(doc.version());\n if (\n !hasUpdatesSince(\n fromVersion as DocVersionRecord,\n currentVersion,\n )\n ) {\n return undefined;\n }\n const updates = [\n doc.export({\n mode: \"update\",\n from: VersionVector.parseJSON(toDocVersionMap(fromVersion)),\n }),\n ].filter((update) => update.byteLength > 0);\n if (updates.length === 0) {\n return undefined;\n }\n let version: DocVersionRecord = {};\n for (const update of updates) {\n const next = extractVersionFromUpdate(update);\n if (next != null) {\n version = mergeDocVersions(version, next);\n }\n }\n return {\n updates,\n version,\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n assertDocAttached(doc);\n if (updates.length > 0) {\n const status = batchImportUpdates(doc, updates);\n trackImportStatus(unresolved, status);\n const version = mergeDocVersions(\n remoterVersion as DocVersionRecord,\n versionFromImportSuccess(status),\n );\n return applyOutcome(version, unresolved);\n }\n return applyOutcome(remoterVersion as DocVersionRecord, unresolved);\n },\n createIsolatedAdapter: () => {\n let isolatedDoc = new LoroDoc();\n const isolatedUnresolved = new UnresolvedSpanTracker();\n return {\n applySnapshot: (snapshot) => {\n isolatedDoc = new LoroDoc();\n isolatedUnresolved.reset();\n if (snapshot.byteLength > 0) {\n const status = isolatedDoc.import(snapshot) as LoroImportStatus;\n trackImportStatus(isolatedUnresolved, status);\n return applyOutcome(\n versionFromSnapshotImport(snapshot, status, isolatedUnresolved),\n isolatedUnresolved,\n );\n }\n return applyOutcome({}, isolatedUnresolved);\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n if (updates.length > 0) {\n const status = batchImportUpdates(isolatedDoc, updates);\n trackImportStatus(isolatedUnresolved, status);\n const version = mergeDocVersions(\n remoterVersion as DocVersionRecord,\n versionFromImportSuccess(status),\n );\n return applyOutcome(version, isolatedUnresolved);\n }\n return applyOutcome(\n remoterVersion as DocVersionRecord,\n isolatedUnresolved,\n );\n },\n };\n },\n subscribeLocalUpdates: (listener) => {\n assertDocAttached(doc);\n return doc.subscribeLocalUpdates((update) => {\n const version = extractVersionFromUpdate(update) ?? {};\n listener({ updates: [update], version });\n });\n },\n };\n}\n\n/**\n * Structural subset of Loro's `EphemeralStore`.\n */\nexport interface LoroEphemeralStoreLike {\n /** Applies one opaque Loro ephemeral update. */\n apply(update: Uint8Array): void;\n /** Subscribes to locally produced ephemeral updates. */\n subscribeLocalUpdates(listener: (update: Uint8Array) => void): () => void;\n}\n\n/**\n * Creates an ephemeral stream adaptor for a Loro `EphemeralStore`.\n *\n * @example\n * ```ts\n * const room = new EphemeralStreamCrdt({\n * streamUrl,\n * auth,\n * adaptor: EphemeralStoreAdaptor(store),\n * });\n * ```\n */\nexport function EphemeralStoreAdaptor(\n store: LoroEphemeralStoreLike,\n): EphemeralStreamAdaptor {\n let applyingRemoteDepth = 0;\n return {\n applyRemoteUpdates: (updates) => {\n applyingRemoteDepth += 1;\n try {\n for (const update of updates) {\n store.apply(update);\n }\n } finally {\n applyingRemoteDepth -= 1;\n }\n },\n subscribeLocalUpdates: (listener) => {\n const unsubscribeLocalUpdates = store.subscribeLocalUpdates((update) => {\n if (applyingRemoteDepth > 0) {\n return;\n }\n listener(update);\n });\n return () => {\n unsubscribeLocalUpdates();\n };\n },\n };\n}\n\nexport const createLoroEphemeralStoreAdaptor = EphemeralStoreAdaptor;\n"],"mappings":";;;AA4BA,SAAS,mBACP,KACA,SACkB;AAClB,KAAI,iBAAiB,OAAO,OAAO,IAAI,gBAAgB,WACrD,QAAO,IAAI,YAAY,QAAQ;AAEjC,QAAO,IAAI,YAAY,QAAQ;;AAMjC,SAAS,mBAAmB,SAA0C;CACpE,MAAM,MAAM,EAAE;AACd,MAAK,MAAM,CAAC,MAAM,YAAY,QAAQ,QAAQ,CAC5C,KAAI,QAAkB;AAExB,QAAO;;AAGT,SAAS,gBAAgB,SAA0C;CACjE,MAAM,sBAAM,IAAI,KAAqB;AACrC,MAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,QAAQ,CACnD,KAAI,OAAO,YAAY,YAAY,OAAO,SAAS,QAAQ,CACzD,KAAI,IAAI,MAAgB,QAAQ;AAGpC,QAAO;;AAGT,SAAS,iBACP,MACA,UACkB;CAClB,MAAM,yBAAS,IAAI,KAAqB;AACxC,MAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,KAAK,CAChD,QAAO,IAAI,MAAgB,QAAQ;AAErC,MAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,SAAS,EAAE;EACtD,MAAM,SAAS;AACf,SAAO,IAAI,QAAQ,KAAK,IAAI,OAAO,IAAI,OAAO,IAAI,GAAG,QAAQ,CAAC;;AAEhE,QAAO,mBAAmB,cAAc,UAAU,OAAO,CAAC;;AAG5D,SAAS,gBACP,aACA,gBACS;AACT,MAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,eAAe,CAC1D,KAAI,WAAW,YAAY,SAAmB,GAC5C,QAAO;AAGX,QAAO;;AAGT,SAAS,yBAAyB,QAA6C;AAC7E,KAAI;AAEF,SAAO,mBADM,qBAAqB,QAAQ,MAAM,CACjB,wBAAwB;SACjD;AACN,SAAO;;;AAIX,SAAS,kBAAkB,KAAoB;AAC7C,KAAI,CAAC,IAAI,YAAY,CACnB;AAEF,OAAM,IAAI,MACR,gGAED;;AAGH,SAAS,gBAAgB,MAAwC;AAC/D,QAAO,GAAG;;AAGZ,SAAS,YAAY,MAA4B;AAC/C,QACE,OAAO,cAAc,KAAK,MAAM,IAChC,OAAO,cAAc,KAAK,IAAI,IAC9B,KAAK,QAAQ,KAAK;;AAItB,IAAM,wBAAN,MAA4B;CAC1B,wBAAyB,IAAI,KAA4B;CAEzD,OAAO,SAA4C;AACjD,MAAI,WAAW,KACb;AAEF,OAAK,MAAM,CAAC,MAAM,SAAS,QACzB,MAAK,IAAI,gBAAgB,KAAK,EAAE,KAAK;;CAIzC,WAAW,SAA4C;AACrD,OAAK,MAAM,CAAC,MAAM,SAAS,QACzB,MAAK,QAAQ,gBAAgB,KAAK,EAAE,KAAK;;CAI7C,UAAmB;AACjB,SAAO,KAAK,MAAM,SAAS;;CAG7B,UAAgC;EAC9B,MAAM,MAA4B,EAAE;AACpC,OAAK,MAAM,CAAC,MAAM,UAAU,KAAK,MAC/B,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK;GAAE;GAAM,OAAO,KAAK;GAAO,KAAK,KAAK;GAAK,CAAC;AAGxD,SAAO;;CAGT,QAAc;AACZ,OAAK,MAAM,OAAO;;CAGpB,IAAY,MAAc,UAA6B;AACrD,MAAI,CAAC,YAAY,SAAS,CACxB;EAEF,MAAM,WAAW,KAAK,MAAM,IAAI,KAAK,IAAI,EAAE;AAC3C,WAAS,KAAK;GAAE,OAAO,SAAS;GAAO,KAAK,SAAS;GAAK,CAAC;AAC3D,WAAS,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI;EAC3D,MAAM,SAAwB,EAAE;AAChC,OAAK,MAAM,QAAQ,UAAU;GAC3B,MAAM,OAAO,OAAO,OAAO,SAAS;AACpC,OAAI,QAAQ,QAAQ,KAAK,QAAQ,KAAK,IACpC,QAAO,KAAK;IAAE,OAAO,KAAK;IAAO,KAAK,KAAK;IAAK,CAAC;OAEjD,QAAO,OAAO,SAAS,KAAK;IAC1B,OAAO,KAAK;IACZ,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI;IAClC;;AAGL,OAAK,MAAM,IAAI,MAAM,OAAO;;CAG9B,QAAgB,MAAc,SAA4B;AACxD,MAAI,CAAC,YAAY,QAAQ,CACvB;EAEF,MAAM,WAAW,KAAK,MAAM,IAAI,KAAK;AACrC,MAAI,YAAY,KACd;EAEF,MAAM,YAA2B,EAAE;AACnC,OAAK,MAAM,QAAQ,UAAU;AAC3B,OAAI,QAAQ,OAAO,KAAK,SAAS,QAAQ,SAAS,KAAK,KAAK;AAC1D,cAAU,KAAK,KAAK;AACpB;;AAEF,OAAI,KAAK,QAAQ,QAAQ,MACvB,WAAU,KAAK;IAAE,OAAO,KAAK;IAAO,KAAK,QAAQ;IAAO,CAAC;AAE3D,OAAI,QAAQ,MAAM,KAAK,IACrB,WAAU,KAAK;IAAE,OAAO,QAAQ;IAAK,KAAK,KAAK;IAAK,CAAC;;AAGzD,MAAI,UAAU,WAAW,EACvB,MAAK,MAAM,OAAO,KAAK;MAEvB,MAAK,MAAM,IAAI,MAAM,UAAU;;;AAKrC,SAAS,kBACP,SACA,QACM;AACN,SAAQ,OAAO,OAAO,QAAQ;AAC9B,SAAQ,WAAW,OAAO,QAAQ;;AAGpC,SAAS,yBAAyB,QAA4C;CAC5E,MAAM,UAA4B,EAAE;AACpC,MAAK,MAAM,CAAC,MAAM,SAAS,OAAO,SAAS;AACzC,MAAI,CAAC,YAAY,KAAK,CACpB;EAEF,MAAM,SAAS,gBAAgB,KAAK;AACpC,UAAQ,UAAU,KAAK,IAAI,QAAQ,WAAW,GAAG,KAAK,IAAI;;AAE5D,QAAO;;AAGT,SAAS,0BACP,UACA,QACA,SACkB;AAClB,KAAI,CAAC,QAAQ,SAAS,CACpB,QAAO,yBAAyB,OAAO;AAEzC,QAAO,yBAAyB,SAAS,IAAI,yBAAyB,OAAO;;AAG/E,SAAS,aACP,SACA,SAC8B;CAC9B,MAAM,aAAa,QAAQ,SAAS;AACpC,QAAO;EACL;EACA,UAAU,WAAW,WAAW;EAChC,GAAI,WAAW,WAAW,IAAI,EAAE,GAAG,EAAE,YAAY;EAClD;;;;;;;;;;;;;;;;AAiBH,SAAgB,qBACd,KACyB;CACzB,MAAM,aAAa,IAAI,uBAAuB;AAC9C,QAAO;EACL,qBAAqB,EAAE;EACvB,gBAAgB,MAAM,aACpB,iBAAiB,MAA0B,SAA6B;EAC1E,sBAAsB;AACpB,qBAAkB,IAAI;AACtB,UAAO,IAAI,OAAO,EAAE,MAAM,YAAY,CAAC;;EAEzC,gBAAgB,aAAa;AAC3B,qBAAkB,IAAI;GACtB,MAAM,SAAS,IAAI,OAAO,SAAS;AACnC,cAAW,OAAO;AAClB,qBAAkB,YAAY,OAAO;AACrC,UAAO,aACL,0BAA0B,UAAU,QAAQ,WAAW,EACvD,WACD;;EAEH,gBAAgB,gBAAgB;AAC9B,qBAAkB,IAAI;AAEtB,OACE,CAAC,gBACC,aAHmB,mBAAmB,IAAI,SAAS,CAAC,CAKrD,CAED;GAEF,MAAM,UAAU,CACd,IAAI,OAAO;IACT,MAAM;IACN,MAAM,cAAc,UAAU,gBAAgB,YAAY,CAAC;IAC5D,CAAC,CACH,CAAC,QAAQ,WAAW,OAAO,aAAa,EAAE;AAC3C,OAAI,QAAQ,WAAW,EACrB;GAEF,IAAI,UAA4B,EAAE;AAClC,QAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,OAAO,yBAAyB,OAAO;AAC7C,QAAI,QAAQ,KACV,WAAU,iBAAiB,SAAS,KAAK;;AAG7C,UAAO;IACL;IACA;IACD;;EAEH,qBAAqB,SAAS,mBAAmB;AAC/C,qBAAkB,IAAI;AACtB,OAAI,QAAQ,SAAS,GAAG;IACtB,MAAM,SAAS,mBAAmB,KAAK,QAAQ;AAC/C,sBAAkB,YAAY,OAAO;AAKrC,WAAO,aAJS,iBACd,gBACA,yBAAyB,OAAO,CACjC,EAC4B,WAAW;;AAE1C,UAAO,aAAa,gBAAoC,WAAW;;EAErE,6BAA6B;GAC3B,IAAI,cAAc,IAAI,SAAS;GAC/B,MAAM,qBAAqB,IAAI,uBAAuB;AACtD,UAAO;IACL,gBAAgB,aAAa;AAC3B,mBAAc,IAAI,SAAS;AAC3B,wBAAmB,OAAO;AAC1B,SAAI,SAAS,aAAa,GAAG;MAC3B,MAAM,SAAS,YAAY,OAAO,SAAS;AAC3C,wBAAkB,oBAAoB,OAAO;AAC7C,aAAO,aACL,0BAA0B,UAAU,QAAQ,mBAAmB,EAC/D,mBACD;;AAEH,YAAO,aAAa,EAAE,EAAE,mBAAmB;;IAE7C,qBAAqB,SAAS,mBAAmB;AAC/C,SAAI,QAAQ,SAAS,GAAG;MACtB,MAAM,SAAS,mBAAmB,aAAa,QAAQ;AACvD,wBAAkB,oBAAoB,OAAO;AAK7C,aAAO,aAJS,iBACd,gBACA,yBAAyB,OAAO,CACjC,EAC4B,mBAAmB;;AAElD,YAAO,aACL,gBACA,mBACD;;IAEJ;;EAEH,wBAAwB,aAAa;AACnC,qBAAkB,IAAI;AACtB,UAAO,IAAI,uBAAuB,WAAW;IAC3C,MAAM,UAAU,yBAAyB,OAAO,IAAI,EAAE;AACtD,aAAS;KAAE,SAAS,CAAC,OAAO;KAAE;KAAS,CAAC;KACxC;;EAEL;;;;;;;;;;;;;;AAyBH,SAAgB,sBACd,OACwB;CACxB,IAAI,sBAAsB;AAC1B,QAAO;EACL,qBAAqB,YAAY;AAC/B,0BAAuB;AACvB,OAAI;AACF,SAAK,MAAM,UAAU,QACnB,OAAM,MAAM,OAAO;aAEb;AACR,2BAAuB;;;EAG3B,wBAAwB,aAAa;GACnC,MAAM,0BAA0B,MAAM,uBAAuB,WAAW;AACtE,QAAI,sBAAsB,EACxB;AAEF,aAAS,OAAO;KAChB;AACF,gBAAa;AACX,6BAAyB;;;EAG9B;;AAGH,MAAa,kCAAkC"}
|
|
@@ -176,6 +176,7 @@ type TransportRoomStatus = "joined" | "reconnecting" | "disconnected" | "error";
|
|
|
176
176
|
* - `network_error` — fetch-level failure (DNS, connection refused, etc.).
|
|
177
177
|
* - `timeout` — connect or poll deadline exceeded.
|
|
178
178
|
* - `gone` — the server indicated the stream was deleted or moved (410).
|
|
179
|
+
* - `apply_incomplete` — a CRDT adapter could not fully apply remote data.
|
|
179
180
|
* - `internal_error` — adapter or runtime logic failed locally.
|
|
180
181
|
* - `unknown` — catch-all for unexpected errors.
|
|
181
182
|
*/
|
|
@@ -205,6 +206,11 @@ type TransportError = {
|
|
|
205
206
|
readonly code: "gone";
|
|
206
207
|
readonly retryable: true;
|
|
207
208
|
readonly message: string;
|
|
209
|
+
} | {
|
|
210
|
+
readonly code: "apply_incomplete";
|
|
211
|
+
readonly retryable: true;
|
|
212
|
+
readonly reason: "loro_pending_updates";
|
|
213
|
+
readonly message: string;
|
|
208
214
|
} | {
|
|
209
215
|
readonly code: "internal_error";
|
|
210
216
|
readonly retryable: false;
|
|
@@ -397,12 +403,10 @@ interface TransportSubscription {
|
|
|
397
403
|
*
|
|
398
404
|
* Ephemeral transports bootstrap through the first SSE event, then continue
|
|
399
405
|
* with live deltas only. They do not expose checkpoints, snapshots, or
|
|
400
|
-
* replayable offset history.
|
|
401
|
-
* so
|
|
406
|
+
* replayable offset history. Outbound data is limited to updates emitted by
|
|
407
|
+
* `subscribeLocalUpdates()` so imported remote presence is never republished.
|
|
402
408
|
*/
|
|
403
409
|
interface EphemeralStreamAdaptor {
|
|
404
|
-
/** Encodes all currently live local ephemeral state. */
|
|
405
|
-
encodeAll(): MaybePromise<Uint8Array | null | undefined>;
|
|
406
410
|
/** Applies updates received from the remote ephemeral stream. */
|
|
407
411
|
applyRemoteUpdates(updates: readonly Uint8Array[]): MaybePromise<void>;
|
|
408
412
|
/** Subscribes to local ephemeral updates. Returns an unsubscribe function. */
|
|
@@ -421,6 +425,23 @@ interface CrdtUpdateBatch<TVersion extends JsonObject> {
|
|
|
421
425
|
readonly version: TVersion;
|
|
422
426
|
}
|
|
423
427
|
type ExportedCrdtUpdateBatch<TVersion extends JsonObject> = CrdtUpdateBatch<TVersion> | null | undefined;
|
|
428
|
+
interface CrdtUnresolvedSpan {
|
|
429
|
+
/** CRDT peer/client id that still has an unresolved counter span. */
|
|
430
|
+
readonly peer: string;
|
|
431
|
+
/** Inclusive start counter. */
|
|
432
|
+
readonly start: number;
|
|
433
|
+
/** Exclusive end counter. */
|
|
434
|
+
readonly end: number;
|
|
435
|
+
}
|
|
436
|
+
interface CrdtApplyOutcome<TVersion extends JsonObject> {
|
|
437
|
+
/** Inferred remote version that is actually applied to local durable CRDT state. */
|
|
438
|
+
readonly version: TVersion;
|
|
439
|
+
/** `false` means remote bytes were seen but cannot yet be durably checkpointed. */
|
|
440
|
+
readonly complete: boolean;
|
|
441
|
+
/** Adapter-specific unresolved dependency spans, when known. */
|
|
442
|
+
readonly unresolved?: readonly CrdtUnresolvedSpan[];
|
|
443
|
+
}
|
|
444
|
+
type CrdtApplyReturn<TVersion extends JsonObject> = TVersion | CrdtApplyOutcome<TVersion>;
|
|
424
445
|
/**
|
|
425
446
|
* Isolated apply-only adapter used for 410/bootstrap recovery.
|
|
426
447
|
*
|
|
@@ -428,9 +449,9 @@ type ExportedCrdtUpdateBatch<TVersion extends JsonObject> = CrdtUpdateBatch<TVer
|
|
|
428
449
|
*/
|
|
429
450
|
interface IsolatedCrdtAdapter<TVersion extends JsonObject> {
|
|
430
451
|
/** Imports a full snapshot into isolated state and returns its version. */
|
|
431
|
-
applySnapshot(snapshot: Uint8Array): MaybePromise<TVersion
|
|
452
|
+
applySnapshot(snapshot: Uint8Array): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
432
453
|
/** Applies retained remote updates into isolated state and returns the new version. */
|
|
433
|
-
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<TVersion
|
|
454
|
+
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
434
455
|
}
|
|
435
456
|
/**
|
|
436
457
|
* Adapter contract between `StreamsCrdt` and a concrete CRDT runtime.
|
|
@@ -443,7 +464,7 @@ interface CrdtAdapter<TVersion extends JsonObject> {
|
|
|
443
464
|
/** Exports a full snapshot of the current local CRDT state. */
|
|
444
465
|
exportSnapshot(): Uint8Array;
|
|
445
466
|
/** Imports a full snapshot, replacing local state. Returns the version after import. */
|
|
446
|
-
applySnapshot(snapshot: Uint8Array): MaybePromise<TVersion
|
|
467
|
+
applySnapshot(snapshot: Uint8Array): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
447
468
|
/** Exports incremental updates since `fromVersion`. Returns `null`/`undefined` when there are no new updates. */
|
|
448
469
|
exportUpdates(fromVersion: TVersion): ExportedCrdtUpdateBatch<TVersion>;
|
|
449
470
|
/**
|
|
@@ -460,7 +481,7 @@ interface CrdtAdapter<TVersion extends JsonObject> {
|
|
|
460
481
|
* invariant to distinguish self-replay from another writer advancing the
|
|
461
482
|
* remote version.
|
|
462
483
|
*/
|
|
463
|
-
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<TVersion
|
|
484
|
+
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
464
485
|
/**
|
|
465
486
|
* Creates an isolated apply-only adapter for 410 recovery.
|
|
466
487
|
*
|
|
@@ -503,11 +524,6 @@ interface EphemeralStreamCrdtOptions {
|
|
|
503
524
|
readonly fetch?: (input: string, init?: RequestInit) => Promise<Response>;
|
|
504
525
|
/** Reconnect policy for the SSE read loop and local POST retry loop. */
|
|
505
526
|
readonly reconnectConfig?: Partial<ReconnectConfig>;
|
|
506
|
-
/**
|
|
507
|
-
* Interval for resending `adaptor.encodeAll()`. Set to `false` to disable.
|
|
508
|
-
* Defaults to 15 seconds, matching presence-style leases.
|
|
509
|
-
*/
|
|
510
|
-
readonly heartbeatMs?: number | false;
|
|
511
527
|
/** Enables diagnostic console logging for terminal read/write failures. */
|
|
512
528
|
readonly debug?: boolean;
|
|
513
529
|
}
|
|
@@ -636,6 +652,7 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
636
652
|
private nextProducerSeq;
|
|
637
653
|
private frozenLocalAppend?;
|
|
638
654
|
private committedPendingCount?;
|
|
655
|
+
private committedLocalStreamCursor?;
|
|
639
656
|
private readonly pendingWriteOnly;
|
|
640
657
|
private writeOnlyCursor;
|
|
641
658
|
private unsubscribeWriteOnlyLocal?;
|
|
@@ -713,8 +730,11 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
713
730
|
private bootstrapState;
|
|
714
731
|
private catchup;
|
|
715
732
|
private catchupOrGone;
|
|
733
|
+
private recoverLiveIncompleteRemoteApply;
|
|
716
734
|
private appendLocalBatch;
|
|
717
735
|
private appendLocalBatchDurably;
|
|
736
|
+
private localAppendReadCursor;
|
|
737
|
+
private catchupLocalAppend;
|
|
718
738
|
private appendLocalBatchRemote;
|
|
719
739
|
private getOrCreateFrozenLocalAppend;
|
|
720
740
|
private appendFrozenLocalBatchRemote;
|
|
@@ -723,6 +743,7 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
723
743
|
private commitProducerAck;
|
|
724
744
|
private abandonFrozenLocalAppend;
|
|
725
745
|
private consumeCommittedPendingCount;
|
|
746
|
+
private consumeCommittedLocalStreamCursor;
|
|
726
747
|
/**
|
|
727
748
|
* Ensures the target stream exists, creating it if necessary.
|
|
728
749
|
* Tolerates 409 Conflict (stream already created concurrently).
|
|
@@ -789,8 +810,6 @@ declare class EphemeralStreamCrdt {
|
|
|
789
810
|
private readonly client;
|
|
790
811
|
/** Retry policy shared by the read loop and local write queue. */
|
|
791
812
|
private readonly reconnectConfig;
|
|
792
|
-
/** Period for republishing full local ephemeral state, or `false` to disable it. */
|
|
793
|
-
private readonly heartbeatMs;
|
|
794
813
|
/** Enables diagnostic console errors for failed read/write loops. */
|
|
795
814
|
private readonly debug;
|
|
796
815
|
/** Current shared subscription state, if the stream is joined. */
|
|
@@ -814,8 +833,6 @@ declare class EphemeralStreamCrdt {
|
|
|
814
833
|
private resolveFirstOpen;
|
|
815
834
|
private rejectFirstOpen;
|
|
816
835
|
private runReadLoop;
|
|
817
|
-
private startHeartbeat;
|
|
818
|
-
private enqueueFullState;
|
|
819
836
|
private enqueueLocal;
|
|
820
837
|
private flushPendingLocal;
|
|
821
838
|
private notifySyncWaiters;
|
|
@@ -856,5 +873,5 @@ declare function createStreamUrl(input: {
|
|
|
856
873
|
baseUrl?: string;
|
|
857
874
|
}): string;
|
|
858
875
|
//#endregion
|
|
859
|
-
export {
|
|
860
|
-
//# sourceMappingURL=stream-id-
|
|
876
|
+
export { IndexedDbRemoteCursorStoreOptions as $, PayloadProtectionReadPolicy as A, TransportCreateStreamSuccess as B, IsolatedCrdtAdapter as C, PayloadProtectionKey as D, PayloadProtectionEncryptionOptions as E, SnapshotTransformHook as F, TransportSnapshotUploadSuccess as G, TransportError as H, SnapshotUploadOptions as I, WriteOnlyAppendResult as J, TransportSubscription as K, StreamsAuthContext as L, PayloadProtectionWritePolicy as M, Result as N, PayloadProtectionKeyProvider as O, SnapshotCodec as P, IndexedDbRemoteCursorStore as Q, StreamsAuthProvider as R, EphemeralStreamSubscription as S, JsonValue as T, TransportJoinParams as U, TransportDeleteStreamSuccess as V, TransportRoomStatus as W, BeforeRemoteCursorSaveHook as X, BeforeRemoteCursorSaveContext as Y, InMemoryRemoteCursorStore as Z, E2eeScope as _, EphemeralStreamCrdt as a, EphemeralStreamCrdtOptions as b, CrdtApplyOutcome as c, CrdtUpdateBatch as d, RemoteCursor as et, E2eeEncryptionOptions as f, E2eeReadPolicy as g, E2eeOptions as h, PayloadProtectionError as i, PayloadProtectionScope as j, PayloadProtectionOptions as k, CrdtApplyReturn as l, E2eeKeyProvider as m, isValidBucketId as n, RemoteCursorStore as nt, StreamsCrdt as o, E2eeKey as p, TransportSyncSuccess as q, isValidRillId as r, createInitialRemoteCursor as rt, CrdtAdapter as s, createStreamUrl as t, RemoteCursorSaveSource as tt, CrdtUnresolvedSpan as u, E2eeWritePolicy as v, JsonObject as w, EphemeralStreamJoinParams as x, EphemeralStreamAdaptor as y, StreamsCrdtOptions as z };
|
|
877
|
+
//# sourceMappingURL=stream-id-Bi5rAZfP.d.ts.map
|