@loro-dev/streams-crdt 0.7.1 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -118,6 +118,103 @@ const sub = joined.value;
118
118
  checked out or detached, call `checkoutToLatest()` or `attach()` before passing
119
119
  it to `StreamsCrdt`.
120
120
 
121
+ ## Quick Start (Loro EphemeralStore)
122
+
123
+ Use a separate `EphemeralStreamCrdt` for presence-like Loro `EphemeralStore`
124
+ data. It bootstraps through the first SSE event, posts local ephemeral updates,
125
+ and never republishes state imported from other peers. If a client needs to keep
126
+ its own presence alive, update that client's own keys with `store.set()` so the
127
+ store emits a fresh local update.
128
+
129
+ Ephemeral live delivery is latest-state oriented. The server may keep only a
130
+ tiny live fanout queue for current SSE subscribers and may disconnect slow
131
+ subscribers instead of buffering every intermediate cursor or presence update.
132
+ The client reconnects and receives a fresh bootstrap, so the local store
133
+ converges to current state without requiring offset replay.
134
+
135
+ ```ts
136
+ import { EphemeralStore } from "loro-crdt";
137
+ import {
138
+ EphemeralStreamCrdt,
139
+ EphemeralStoreAdaptor,
140
+ createStreamUrl,
141
+ } from "@loro-dev/streams-crdt/loro";
142
+
143
+ const store = new EphemeralStore();
144
+ const docStreamUrl = createStreamUrl({
145
+ bucketId: "my-bucket",
146
+ streamId: "doc-1",
147
+ });
148
+
149
+ const presence = new EphemeralStreamCrdt({
150
+ streamUrl: `${docStreamUrl}?ephemeral=presence`,
151
+ auth: async () => "<gateway-jwt>",
152
+ adaptor: EphemeralStoreAdaptor(store),
153
+ });
154
+
155
+ const joinedPresence = await presence.join();
156
+ if (!joinedPresence.ok) throw joinedPresence.error;
157
+ ```
158
+
159
+ ### Ephemeral stream URL format
160
+
161
+ An ephemeral stream URL is the durable stream URL for the same document plus a
162
+ channel query parameter:
163
+
164
+ ```txt
165
+ {baseUrl}/ds/{bucketId}/{streamId}?ephemeral={channel}
166
+ ```
167
+
168
+ For example, if the durable Flock document stream is:
169
+
170
+ ```txt
171
+ https://streams-api.loro.dev/ds/my-bucket/flock-cm9vbS0x
172
+ ```
173
+
174
+ then the matching presence stream is:
175
+
176
+ ```txt
177
+ https://streams-api.loro.dev/ds/my-bucket/flock-cm9vbS0x?ephemeral=presence
178
+ ```
179
+
180
+ Use the same `bucketId` and `streamId` as the durable `StreamsCrdt` room. The
181
+ `ephemeral` value names a side-channel for that room, such as `presence`,
182
+ `cursor`, or `selection`. Different channels under the same durable stream are
183
+ separate in-memory rooms. The compatibility spelling `?awareness=presence` is
184
+ also accepted, but new code should use `?ephemeral=presence`.
185
+
186
+ When an app exposes a room link such as `/rooms/design-review-42`, the link
187
+ normally maps to one durable stream id first, and both durable and ephemeral
188
+ sync derive from that same id:
189
+
190
+ ```ts
191
+ const roomId = "design-review-42";
192
+ const durableStreamUrl = createStreamUrl({
193
+ bucketId: "my-bucket",
194
+ streamId: `flock-${roomId}`,
195
+ });
196
+
197
+ const presenceStreamUrl = new URL(durableStreamUrl);
198
+ presenceStreamUrl.searchParams.set("ephemeral", "presence");
199
+
200
+ const flockTransport = new StreamsCrdt({
201
+ streamUrl: durableStreamUrl,
202
+ auth,
203
+ adapter: createFlockAdapter(flock),
204
+ });
205
+
206
+ const presenceTransport = new EphemeralStreamCrdt({
207
+ streamUrl: presenceStreamUrl.toString(),
208
+ auth,
209
+ adaptor: EphemeralStoreAdaptor(store),
210
+ });
211
+ ```
212
+
213
+ `EphemeralStreamCrdt` does not create or delete the durable stream. It posts
214
+ updates to the ephemeral URL and opens SSE on the same URL with `live=sse`.
215
+ Ephemeral state is not persisted with the Flock document stream; it is scoped by
216
+ the URL path plus channel and is meant for current live state only.
217
+
121
218
  ## Quick Start (Flock)
122
219
 
123
220
  ```ts
package/dist/flock.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as StreamsAuthProvider, B as WriteOnlyAppendResult, C as PayloadProtectionScope, D as SnapshotTransformHook, E as SnapshotCodec, F as TransportJoinParams, G as IndexedDbRemoteCursorStoreOptions, H as BeforeRemoteCursorSaveHook, I as TransportRoomStatus, J as RemoteCursorStore, K as RemoteCursor, L as TransportSnapshotUploadSuccess, M as TransportCreateStreamSuccess, N as TransportDeleteStreamSuccess, O as SnapshotUploadOptions, P as TransportError, R as TransportSubscription, S as PayloadProtectionReadPolicy, T as Result, U as InMemoryRemoteCursorStore, V as BeforeRemoteCursorSaveContext, W as IndexedDbRemoteCursorStore, Y as createInitialRemoteCursor, _ as JsonValue, a as StreamsCrdt, b as PayloadProtectionKeyProvider, c as E2eeEncryptionOptions, d as E2eeOptions, f as E2eeReadPolicy, g as JsonObject, h as IsolatedCrdtAdapter, i as PayloadProtectionError, j as StreamsCrdtOptions, k as StreamsAuthContext, l as E2eeKey, m as E2eeWritePolicy, n as isValidBucketId, o as CrdtAdapter, p as E2eeScope, q as RemoteCursorSaveSource, r as isValidRillId, s as CrdtUpdateBatch, t as createStreamUrl, u as E2eeKeyProvider, v as PayloadProtectionEncryptionOptions, w as PayloadProtectionWritePolicy, x as PayloadProtectionOptions, y as PayloadProtectionKey, z as TransportSyncSuccess } from "./stream-id-CS7SzuLW.js";
1
+ import { $ as RemoteCursorStore, A as Result, B as TransportJoinParams, C as PayloadProtectionEncryptionOptions, D as PayloadProtectionReadPolicy, E as PayloadProtectionOptions, F as StreamsAuthProvider, G as WriteOnlyAppendResult, H as TransportSnapshotUploadSuccess, I as StreamsCrdtOptions, J as InMemoryRemoteCursorStore, K as BeforeRemoteCursorSaveContext, L as TransportCreateStreamSuccess, M as SnapshotTransformHook, N as SnapshotUploadOptions, O as PayloadProtectionScope, P as StreamsAuthContext, Q as RemoteCursorSaveSource, R as TransportDeleteStreamSuccess, S as JsonValue, T as PayloadProtectionKeyProvider, U as TransportSubscription, V as TransportRoomStatus, W as TransportSyncSuccess, X as IndexedDbRemoteCursorStoreOptions, Y as IndexedDbRemoteCursorStore, Z as RemoteCursor, _ as EphemeralStreamCrdtOptions, a as EphemeralStreamCrdt, b as IsolatedCrdtAdapter, c as CrdtUpdateBatch, d as E2eeKeyProvider, et as createInitialRemoteCursor, f as E2eeOptions, g as EphemeralStreamAdaptor, h as E2eeWritePolicy, i as PayloadProtectionError, j as SnapshotCodec, k as PayloadProtectionWritePolicy, l as E2eeEncryptionOptions, m as E2eeScope, n as isValidBucketId, o as StreamsCrdt, p as E2eeReadPolicy, q as BeforeRemoteCursorSaveHook, r as isValidRillId, s as CrdtAdapter, t as createStreamUrl, u as E2eeKey, v as EphemeralStreamJoinParams, w as PayloadProtectionKey, x as JsonObject, y as EphemeralStreamSubscription, z as TransportError } from "./stream-id-B6FzWgkM.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, 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, 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 PayloadProtectionError, c as createInitialRemoteCursor, i as StreamsCrdt, n as isValidBucketId, o as InMemoryRemoteCursorStore, r as isValidRillId, s as IndexedDbRemoteCursorStore, t as createStreamUrl } from "./stream-id-D0qcbI5c.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-CCPEBb6G.js";
2
2
  import { Flock } from "@loro-dev/flock-wasm";
3
3
  //#region src/flock.ts
4
4
  function isAfter(entry, current) {
@@ -149,6 +149,6 @@ function createFlockAdapter(flock) {
149
149
  };
150
150
  }
151
151
  //#endregion
152
- export { PayloadProtectionError as E2eeError, PayloadProtectionError, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
152
+ export { PayloadProtectionError as E2eeError, PayloadProtectionError, EphemeralStreamCrdt, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
153
153
 
154
154
  //# sourceMappingURL=flock.js.map
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as StreamsAuthProvider, B as WriteOnlyAppendResult, C as PayloadProtectionScope, D as SnapshotTransformHook, E as SnapshotCodec, F as TransportJoinParams, G as IndexedDbRemoteCursorStoreOptions, H as BeforeRemoteCursorSaveHook, I as TransportRoomStatus, J as RemoteCursorStore, K as RemoteCursor, L as TransportSnapshotUploadSuccess, M as TransportCreateStreamSuccess, N as TransportDeleteStreamSuccess, O as SnapshotUploadOptions, P as TransportError, R as TransportSubscription, S as PayloadProtectionReadPolicy, T as Result, U as InMemoryRemoteCursorStore, V as BeforeRemoteCursorSaveContext, W as IndexedDbRemoteCursorStore, Y as createInitialRemoteCursor, _ as JsonValue, a as StreamsCrdt, b as PayloadProtectionKeyProvider, c as E2eeEncryptionOptions, d as E2eeOptions, f as E2eeReadPolicy, g as JsonObject, h as IsolatedCrdtAdapter, i as PayloadProtectionError, j as StreamsCrdtOptions, k as StreamsAuthContext, l as E2eeKey, m as E2eeWritePolicy, n as isValidBucketId, o as CrdtAdapter, p as E2eeScope, q as RemoteCursorSaveSource, r as isValidRillId, s as CrdtUpdateBatch, t as createStreamUrl, u as E2eeKeyProvider, v as PayloadProtectionEncryptionOptions, w as PayloadProtectionWritePolicy, x as PayloadProtectionOptions, y as PayloadProtectionKey, z as TransportSyncSuccess } from "./stream-id-CS7SzuLW.js";
2
- export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, 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 RemoteCursorStore, A as Result, B as TransportJoinParams, C as PayloadProtectionEncryptionOptions, D as PayloadProtectionReadPolicy, E as PayloadProtectionOptions, F as StreamsAuthProvider, G as WriteOnlyAppendResult, H as TransportSnapshotUploadSuccess, I as StreamsCrdtOptions, J as InMemoryRemoteCursorStore, K as BeforeRemoteCursorSaveContext, L as TransportCreateStreamSuccess, M as SnapshotTransformHook, N as SnapshotUploadOptions, O as PayloadProtectionScope, P as StreamsAuthContext, Q as RemoteCursorSaveSource, R as TransportDeleteStreamSuccess, S as JsonValue, T as PayloadProtectionKeyProvider, U as TransportSubscription, V as TransportRoomStatus, W as TransportSyncSuccess, X as IndexedDbRemoteCursorStoreOptions, Y as IndexedDbRemoteCursorStore, Z as RemoteCursor, _ as EphemeralStreamCrdtOptions, a as EphemeralStreamCrdt, b as IsolatedCrdtAdapter, c as CrdtUpdateBatch, d as E2eeKeyProvider, et as createInitialRemoteCursor, f as E2eeOptions, g as EphemeralStreamAdaptor, h as E2eeWritePolicy, i as PayloadProtectionError, j as SnapshotCodec, k as PayloadProtectionWritePolicy, l as E2eeEncryptionOptions, m as E2eeScope, n as isValidBucketId, o as StreamsCrdt, p as E2eeReadPolicy, q as BeforeRemoteCursorSaveHook, r as isValidRillId, s as CrdtAdapter, t as createStreamUrl, u as E2eeKey, v as EphemeralStreamJoinParams, w as PayloadProtectionKey, x as JsonObject, y as EphemeralStreamSubscription, z as TransportError } from "./stream-id-B6FzWgkM.js";
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 };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as PayloadProtectionError, c as createInitialRemoteCursor, i as StreamsCrdt, n as isValidBucketId, o as InMemoryRemoteCursorStore, r as isValidRillId, s as IndexedDbRemoteCursorStore, t as createStreamUrl } from "./stream-id-D0qcbI5c.js";
2
- export { PayloadProtectionError as E2eeError, PayloadProtectionError, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
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-CCPEBb6G.js";
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 { A as StreamsAuthProvider, B as WriteOnlyAppendResult, C as PayloadProtectionScope, D as SnapshotTransformHook, E as SnapshotCodec, F as TransportJoinParams, G as IndexedDbRemoteCursorStoreOptions, H as BeforeRemoteCursorSaveHook, I as TransportRoomStatus, J as RemoteCursorStore, K as RemoteCursor, L as TransportSnapshotUploadSuccess, M as TransportCreateStreamSuccess, N as TransportDeleteStreamSuccess, O as SnapshotUploadOptions, P as TransportError, R as TransportSubscription, S as PayloadProtectionReadPolicy, T as Result, U as InMemoryRemoteCursorStore, V as BeforeRemoteCursorSaveContext, W as IndexedDbRemoteCursorStore, Y as createInitialRemoteCursor, _ as JsonValue, a as StreamsCrdt, b as PayloadProtectionKeyProvider, c as E2eeEncryptionOptions, d as E2eeOptions, f as E2eeReadPolicy, g as JsonObject, h as IsolatedCrdtAdapter, i as PayloadProtectionError, j as StreamsCrdtOptions, k as StreamsAuthContext, l as E2eeKey, m as E2eeWritePolicy, n as isValidBucketId, o as CrdtAdapter, p as E2eeScope, q as RemoteCursorSaveSource, r as isValidRillId, s as CrdtUpdateBatch, t as createStreamUrl, u as E2eeKeyProvider, v as PayloadProtectionEncryptionOptions, w as PayloadProtectionWritePolicy, x as PayloadProtectionOptions, y as PayloadProtectionKey, z as TransportSyncSuccess } from "./stream-id-CS7SzuLW.js";
1
+ import { $ as RemoteCursorStore, A as Result, B as TransportJoinParams, C as PayloadProtectionEncryptionOptions, D as PayloadProtectionReadPolicy, E as PayloadProtectionOptions, F as StreamsAuthProvider, G as WriteOnlyAppendResult, H as TransportSnapshotUploadSuccess, I as StreamsCrdtOptions, J as InMemoryRemoteCursorStore, K as BeforeRemoteCursorSaveContext, L as TransportCreateStreamSuccess, M as SnapshotTransformHook, N as SnapshotUploadOptions, O as PayloadProtectionScope, P as StreamsAuthContext, Q as RemoteCursorSaveSource, R as TransportDeleteStreamSuccess, S as JsonValue, T as PayloadProtectionKeyProvider, U as TransportSubscription, V as TransportRoomStatus, W as TransportSyncSuccess, X as IndexedDbRemoteCursorStoreOptions, Y as IndexedDbRemoteCursorStore, Z as RemoteCursor, _ as EphemeralStreamCrdtOptions, a as EphemeralStreamCrdt, b as IsolatedCrdtAdapter, c as CrdtUpdateBatch, d as E2eeKeyProvider, et as createInitialRemoteCursor, f as E2eeOptions, g as EphemeralStreamAdaptor, h as E2eeWritePolicy, i as PayloadProtectionError, j as SnapshotCodec, k as PayloadProtectionWritePolicy, l as E2eeEncryptionOptions, m as E2eeScope, n as isValidBucketId, o as StreamsCrdt, p as E2eeReadPolicy, q as BeforeRemoteCursorSaveHook, r as isValidRillId, s as CrdtAdapter, t as createStreamUrl, u as E2eeKey, v as EphemeralStreamJoinParams, w as PayloadProtectionKey, x as JsonObject, y as EphemeralStreamSubscription, z as TransportError } from "./stream-id-B6FzWgkM.js";
2
2
  import { LoroDoc } from "loro-crdt";
3
3
 
4
4
  //#region src/loro.d.ts
@@ -17,6 +17,29 @@ import { LoroDoc } from "loro-crdt";
17
17
  * ```
18
18
  */
19
19
  declare function createLoroDocAdapter(doc: LoroDoc): CrdtAdapter<JsonObject>;
20
+ /**
21
+ * Structural subset of Loro's `EphemeralStore`.
22
+ */
23
+ interface LoroEphemeralStoreLike {
24
+ /** Applies one opaque Loro ephemeral update. */
25
+ apply(update: Uint8Array): void;
26
+ /** Subscribes to locally produced ephemeral updates. */
27
+ subscribeLocalUpdates(listener: (update: Uint8Array) => void): () => void;
28
+ }
29
+ /**
30
+ * Creates an ephemeral stream adaptor for a Loro `EphemeralStore`.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const room = new EphemeralStreamCrdt({
35
+ * streamUrl,
36
+ * auth,
37
+ * adaptor: EphemeralStoreAdaptor(store),
38
+ * });
39
+ * ```
40
+ */
41
+ declare function EphemeralStoreAdaptor(store: LoroEphemeralStoreLike): EphemeralStreamAdaptor;
42
+ declare const createLoroEphemeralStoreAdaptor: typeof EphemeralStoreAdaptor;
20
43
  //#endregion
21
- export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, E2eeEncryptionOptions, PayloadProtectionError as E2eeError, PayloadProtectionError, E2eeKey, E2eeKeyProvider, E2eeOptions, E2eeReadPolicy, E2eeScope, E2eeWritePolicy, 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, createLoroDocAdapter, createStreamUrl, isValidBucketId, isValidRillId };
44
+ 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 };
22
45
  //# sourceMappingURL=loro.d.ts.map
package/dist/loro.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as PayloadProtectionError, c as createInitialRemoteCursor, i as StreamsCrdt, n as isValidBucketId, o as InMemoryRemoteCursorStore, r as isValidRillId, s as IndexedDbRemoteCursorStore, t as createStreamUrl } from "./stream-id-D0qcbI5c.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-CCPEBb6G.js";
2
2
  import { LoroDoc, VersionVector, decodeImportBlobMeta } from "loro-crdt";
3
3
  //#region src/loro.ts
4
4
  function batchImportUpdates(doc, updates) {
@@ -125,7 +125,42 @@ function createLoroDocAdapter(doc) {
125
125
  }
126
126
  };
127
127
  }
128
+ /**
129
+ * Creates an ephemeral stream adaptor for a Loro `EphemeralStore`.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const room = new EphemeralStreamCrdt({
134
+ * streamUrl,
135
+ * auth,
136
+ * adaptor: EphemeralStoreAdaptor(store),
137
+ * });
138
+ * ```
139
+ */
140
+ function EphemeralStoreAdaptor(store) {
141
+ let applyingRemoteDepth = 0;
142
+ return {
143
+ applyRemoteUpdates: (updates) => {
144
+ applyingRemoteDepth += 1;
145
+ try {
146
+ for (const update of updates) store.apply(update);
147
+ } finally {
148
+ applyingRemoteDepth -= 1;
149
+ }
150
+ },
151
+ subscribeLocalUpdates: (listener) => {
152
+ const unsubscribeLocalUpdates = store.subscribeLocalUpdates((update) => {
153
+ if (applyingRemoteDepth > 0) return;
154
+ listener(update);
155
+ });
156
+ return () => {
157
+ unsubscribeLocalUpdates();
158
+ };
159
+ }
160
+ };
161
+ }
162
+ const createLoroEphemeralStoreAdaptor = EphemeralStoreAdaptor;
128
163
  //#endregion
129
- export { PayloadProtectionError as E2eeError, PayloadProtectionError, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createLoroDocAdapter, createStreamUrl, isValidBucketId, isValidRillId };
164
+ export { PayloadProtectionError as E2eeError, PayloadProtectionError, EphemeralStoreAdaptor, EphemeralStreamCrdt, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createLoroDocAdapter, createLoroEphemeralStoreAdaptor, createStreamUrl, isValidBucketId, isValidRillId };
130
165
 
131
166
  //# sourceMappingURL=loro.js.map
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\";\n\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"],"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 { 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 /** 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":"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"}
@@ -392,6 +392,25 @@ interface TransportSubscription {
392
392
  */
393
393
  readonly waitUntilSynced: () => Promise<void>;
394
394
  }
395
+ /**
396
+ * Adapter contract for CRDT-like ephemeral stores.
397
+ *
398
+ * Ephemeral transports bootstrap through the first SSE event, then continue
399
+ * with live deltas only. They do not expose checkpoints, snapshots, or
400
+ * replayable offset history. Outbound data is limited to updates emitted by
401
+ * `subscribeLocalUpdates()` so imported remote presence is never republished.
402
+ */
403
+ interface EphemeralStreamAdaptor {
404
+ /** Applies updates received from the remote ephemeral stream. */
405
+ applyRemoteUpdates(updates: readonly Uint8Array[]): MaybePromise<void>;
406
+ /** Subscribes to local ephemeral updates. Returns an unsubscribe function. */
407
+ subscribeLocalUpdates(listener: (update: Uint8Array) => void): () => void;
408
+ }
409
+ interface EphemeralStreamJoinParams {
410
+ /** Called when the live ephemeral subscription status changes. */
411
+ readonly onStatusChange?: (status: "connecting" | TransportRoomStatus) => void;
412
+ }
413
+ interface EphemeralStreamSubscription extends TransportSubscription {}
395
414
  /**
396
415
  * Local CRDT updates ready to append to Durable Streams.
397
416
  */
@@ -462,6 +481,29 @@ interface CrdtAdapter<TVersion extends JsonObject> {
462
481
  /** Optional headers to include when creating the stream (e.g. retention config). */
463
482
  getCreateStreamHeaders?(): MaybePromise<Record<string, string>>;
464
483
  }
484
+ interface EphemeralStreamCrdtOptions {
485
+ /**
486
+ * Full ephemeral stream URL.
487
+ *
488
+ * The URL must include the channel query, usually
489
+ * `?ephemeral=presence` or the compatibility alias `?awareness=presence`.
490
+ */
491
+ readonly streamUrl: string;
492
+ /**
493
+ * Ephemeral store adaptor.
494
+ *
495
+ * The public spelling intentionally matches Loro's "adaptor" examples.
496
+ */
497
+ readonly adaptor: EphemeralStreamAdaptor;
498
+ /** Auth token source used for ephemeral POST and SSE requests. */
499
+ readonly auth?: StreamsAuthProvider;
500
+ /** Optional fetch implementation for tests, workers, or custom runtimes. */
501
+ readonly fetch?: (input: string, init?: RequestInit) => Promise<Response>;
502
+ /** Reconnect policy for the SSE read loop and local POST retry loop. */
503
+ readonly reconnectConfig?: Partial<ReconnectConfig>;
504
+ /** Enables diagnostic console logging for terminal read/write failures. */
505
+ readonly debug?: boolean;
506
+ }
465
507
  /**
466
508
  * Construction options for one `StreamsCrdt` instance.
467
509
  *
@@ -585,6 +627,8 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
585
627
  private readonly producerId;
586
628
  private producerEpoch;
587
629
  private nextProducerSeq;
630
+ private frozenLocalAppend?;
631
+ private committedPendingCount?;
588
632
  private readonly pendingWriteOnly;
589
633
  private writeOnlyCursor;
590
634
  private unsubscribeWriteOnlyLocal?;
@@ -663,6 +707,15 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
663
707
  private catchup;
664
708
  private catchupOrGone;
665
709
  private appendLocalBatch;
710
+ private appendLocalBatchDurably;
711
+ private appendLocalBatchRemote;
712
+ private getOrCreateFrozenLocalAppend;
713
+ private appendFrozenLocalBatchRemote;
714
+ private finalizeDirectLocalAppendIfNeeded;
715
+ private createCurrentProducerSession;
716
+ private commitProducerAck;
717
+ private abandonFrozenLocalAppend;
718
+ private consumeCommittedPendingCount;
666
719
  /**
667
720
  * Ensures the target stream exists, creating it if necessary.
668
721
  * Tolerates 409 Conflict (stream already created concurrently).
@@ -712,6 +765,57 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
712
765
  private logError;
713
766
  }
714
767
  //#endregion
768
+ //#region src/ephemeral-runtime.d.ts
769
+ /**
770
+ * Realtime transport for one ephemeral CRDT stream.
771
+ *
772
+ * This is intentionally separate from `StreamsCrdt`: it bootstraps through
773
+ * the first SSE event, then tails live deltas, and never stores a replay
774
+ * offset, snapshot checkpoint, or remote cursor.
775
+ */
776
+ declare class EphemeralStreamCrdt {
777
+ /** Full ephemeral stream URL, including `?ephemeral=` or `?awareness=`. */
778
+ readonly streamUrl: string;
779
+ /** Store-specific adapter used to encode local state and apply remote bytes. */
780
+ readonly adaptor: EphemeralStreamAdaptor;
781
+ /** Low-level HTTP/SSE transport. It is reused only for auth, fetch, and parsing helpers. */
782
+ private readonly client;
783
+ /** Retry policy shared by the read loop and local write queue. */
784
+ private readonly reconnectConfig;
785
+ /** Enables diagnostic console errors for failed read/write loops. */
786
+ private readonly debug;
787
+ /** Current shared subscription state, if the stream is joined. */
788
+ private joinState?;
789
+ constructor(options: EphemeralStreamCrdtOptions);
790
+ join(params?: EphemeralStreamJoinParams): Promise<{
791
+ readonly ok: true;
792
+ readonly value: EphemeralStreamSubscription;
793
+ } | {
794
+ readonly ok: false;
795
+ readonly error: TransportError;
796
+ }>;
797
+ rejoin(): void;
798
+ close(): Promise<void>;
799
+ private createJoinState;
800
+ private startJoin;
801
+ private waitForFirstOpen;
802
+ private createSubscription;
803
+ private onStatusChange;
804
+ private disposeJoinState;
805
+ private resolveFirstOpen;
806
+ private rejectFirstOpen;
807
+ private runReadLoop;
808
+ private enqueueLocal;
809
+ private flushPendingLocal;
810
+ private notifySyncWaiters;
811
+ private rejectSyncWaiters;
812
+ private setReadStatus;
813
+ private setWriteStatus;
814
+ private recomputeStatus;
815
+ private computeStatus;
816
+ private logError;
817
+ }
818
+ //#endregion
715
819
  //#region src/payload-protection.d.ts
716
820
  type PayloadProtectionFailureReason = "plaintext_forbidden" | "missing_read_key" | "decrypt_failed" | "invalid_envelope" | "wrong_payload_kind" | "encrypt_failed";
717
821
  declare class PayloadProtectionError extends Error {
@@ -741,5 +845,5 @@ declare function createStreamUrl(input: {
741
845
  baseUrl?: string;
742
846
  }): string;
743
847
  //#endregion
744
- export { StreamsAuthProvider as A, WriteOnlyAppendResult as B, PayloadProtectionScope as C, SnapshotTransformHook as D, SnapshotCodec as E, TransportJoinParams as F, IndexedDbRemoteCursorStoreOptions as G, BeforeRemoteCursorSaveHook as H, TransportRoomStatus as I, RemoteCursorStore as J, RemoteCursor as K, TransportSnapshotUploadSuccess as L, TransportCreateStreamSuccess as M, TransportDeleteStreamSuccess as N, SnapshotUploadOptions as O, TransportError as P, TransportSubscription as R, PayloadProtectionReadPolicy as S, Result as T, InMemoryRemoteCursorStore as U, BeforeRemoteCursorSaveContext as V, IndexedDbRemoteCursorStore as W, createInitialRemoteCursor as Y, JsonValue as _, StreamsCrdt as a, PayloadProtectionKeyProvider as b, E2eeEncryptionOptions as c, E2eeOptions as d, E2eeReadPolicy as f, JsonObject as g, IsolatedCrdtAdapter as h, PayloadProtectionError as i, StreamsCrdtOptions as j, StreamsAuthContext as k, E2eeKey as l, E2eeWritePolicy as m, isValidBucketId as n, CrdtAdapter as o, E2eeScope as p, RemoteCursorSaveSource as q, isValidRillId as r, CrdtUpdateBatch as s, createStreamUrl as t, E2eeKeyProvider as u, PayloadProtectionEncryptionOptions as v, PayloadProtectionWritePolicy as w, PayloadProtectionOptions as x, PayloadProtectionKey as y, TransportSyncSuccess as z };
745
- //# sourceMappingURL=stream-id-CS7SzuLW.d.ts.map
848
+ export { RemoteCursorStore as $, Result as A, TransportJoinParams as B, PayloadProtectionEncryptionOptions as C, PayloadProtectionReadPolicy as D, PayloadProtectionOptions as E, StreamsAuthProvider as F, WriteOnlyAppendResult as G, TransportSnapshotUploadSuccess as H, StreamsCrdtOptions as I, InMemoryRemoteCursorStore as J, BeforeRemoteCursorSaveContext as K, TransportCreateStreamSuccess as L, SnapshotTransformHook as M, SnapshotUploadOptions as N, PayloadProtectionScope as O, StreamsAuthContext as P, RemoteCursorSaveSource as Q, TransportDeleteStreamSuccess as R, JsonValue as S, PayloadProtectionKeyProvider as T, TransportSubscription as U, TransportRoomStatus as V, TransportSyncSuccess as W, IndexedDbRemoteCursorStoreOptions as X, IndexedDbRemoteCursorStore as Y, RemoteCursor as Z, EphemeralStreamCrdtOptions as _, EphemeralStreamCrdt as a, IsolatedCrdtAdapter as b, CrdtUpdateBatch as c, E2eeKeyProvider as d, createInitialRemoteCursor as et, E2eeOptions as f, EphemeralStreamAdaptor as g, E2eeWritePolicy as h, PayloadProtectionError as i, SnapshotCodec as j, PayloadProtectionWritePolicy as k, E2eeEncryptionOptions as l, E2eeScope as m, isValidBucketId as n, StreamsCrdt as o, E2eeReadPolicy as p, BeforeRemoteCursorSaveHook as q, isValidRillId as r, CrdtAdapter as s, createStreamUrl as t, E2eeKey as u, EphemeralStreamJoinParams as v, PayloadProtectionKey as w, JsonObject as x, EphemeralStreamSubscription as y, TransportError as z };
849
+ //# sourceMappingURL=stream-id-B6FzWgkM.d.ts.map