@loro-dev/streams-crdt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +407 -0
- package/dist/flock.d.ts +36 -0
- package/dist/flock.js +146 -0
- package/dist/flock.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/loro.d.ts +22 -0
- package/dist/loro.js +108 -0
- package/dist/loro.js.map +1 -0
- package/dist/stream-id-1f2mhtmk.d.ts +463 -0
- package/dist/stream-id-CIhmveJD.js +2151 -0
- package/dist/stream-id-CIhmveJD.js.map +1 -0
- package/package.json +76 -0
package/README.md
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# `@loro-dev/streams-crdt`
|
|
2
|
+
|
|
3
|
+
Transport layer for synchronizing CRDT state over
|
|
4
|
+
[Loro Streams](https://loro.dev/docs/streams) (Durable Streams).
|
|
5
|
+
|
|
6
|
+
## Core Concepts
|
|
7
|
+
|
|
8
|
+
### Two-class model
|
|
9
|
+
|
|
10
|
+
| Class | Responsibility |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| `StreamsCrdt` | Transport runtime bound to **one** stream URL. Handles auth, reconnect, bootstrap, SSE/long-poll, binary framing, and remote cursor persistence. |
|
|
13
|
+
| `CrdtAdapter` | Binding for **one** local CRDT instance. Handles snapshot export/import, version tracking, local update export, and remote update application. |
|
|
14
|
+
|
|
15
|
+
### Operations
|
|
16
|
+
|
|
17
|
+
| Method | Purpose |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `createStream()` | Creates the stream on the server. Returns `{ created: true }` on first call, `{ created: false }` if it already exists. |
|
|
20
|
+
| `deleteStream()` | Deletes the stream. Closes any active `join()` first. |
|
|
21
|
+
| `sync()` | One-shot bootstrap/catch-up cycle. Syncs remote state into local CRDT and pushes any local changes to the server. Does **not** enter live mode. |
|
|
22
|
+
| `join()` | Initial sync **plus** live subscription. Internally performs the same sync as `sync()`, then enters a persistent SSE/long-poll read loop and starts forwarding local writes. No need to call `sync()` before `join()`. |
|
|
23
|
+
| `close()` | Closes any active join and releases resources. |
|
|
24
|
+
|
|
25
|
+
> **`join()` includes an initial sync.** You do not need to call `sync()` before
|
|
26
|
+
> `join()`. When `join()` resolves, the local CRDT is already caught up with
|
|
27
|
+
> the server and the live subscription is active.
|
|
28
|
+
|
|
29
|
+
### Error handling — `Result`, not exceptions
|
|
30
|
+
|
|
31
|
+
All transport operations return a `Result<T, TransportError>` instead of
|
|
32
|
+
throwing. Callers **must** check `.ok` before accessing the value:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const result = await transport.join();
|
|
36
|
+
if (!result.ok) {
|
|
37
|
+
// result.error is a TransportError with a discriminated `code` field.
|
|
38
|
+
switch (result.error.code) {
|
|
39
|
+
case "stream_not_found":
|
|
40
|
+
// The stream doesn't exist yet — create it first.
|
|
41
|
+
break;
|
|
42
|
+
case "auth_failed":
|
|
43
|
+
// Token expired or unauthorized.
|
|
44
|
+
break;
|
|
45
|
+
case "protocol_error":
|
|
46
|
+
case "internal_error":
|
|
47
|
+
// Non-retryable: bad server response or local runtime/adapter failure.
|
|
48
|
+
break;
|
|
49
|
+
case "network_error":
|
|
50
|
+
case "timeout":
|
|
51
|
+
// Transient — safe to retry.
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
console.error("transport error:", result.error);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sub = result.value; // TransportSubscription
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Every error carries a `retryable` flag so callers can decide whether to retry
|
|
63
|
+
without hard-coding error codes:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
if (!result.ok && result.error.retryable) {
|
|
67
|
+
// safe to retry after a delay
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> **Why `Result` instead of `throw`?** Transport failures (network errors,
|
|
72
|
+
> auth expiry, missing streams) are expected at runtime, not exceptional.
|
|
73
|
+
> Wrapping them in `Result` makes the error path explicit in the type system
|
|
74
|
+
> so callers cannot accidentally ignore failures with a missing `try/catch`.
|
|
75
|
+
|
|
76
|
+
## Quick Start (Loro)
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { LoroDoc } from "loro-crdt";
|
|
80
|
+
import {
|
|
81
|
+
createStreamUrl,
|
|
82
|
+
createLoroDocAdapter,
|
|
83
|
+
StreamsCrdt,
|
|
84
|
+
} from "@loro-dev/streams-crdt/loro";
|
|
85
|
+
|
|
86
|
+
const doc = new LoroDoc();
|
|
87
|
+
const transport = new StreamsCrdt({
|
|
88
|
+
streamUrl: createStreamUrl({
|
|
89
|
+
bucketId: "my-bucket",
|
|
90
|
+
streamId: "doc-1",
|
|
91
|
+
}),
|
|
92
|
+
auth: async () => "<gateway-jwt>",
|
|
93
|
+
adapter: createLoroDocAdapter(doc),
|
|
94
|
+
// Enable automatic snapshot upload so new readers bootstrap faster.
|
|
95
|
+
snapshotUpload: { canUpload: async () => true },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Create the stream (idempotent).
|
|
99
|
+
const created = await transport.createStream();
|
|
100
|
+
if (!created.ok) throw created.error;
|
|
101
|
+
|
|
102
|
+
// Join enters live collaborative editing mode.
|
|
103
|
+
// This performs an initial sync automatically before entering live mode.
|
|
104
|
+
const joined = await transport.join({
|
|
105
|
+
onStatusChange(status) {
|
|
106
|
+
console.log("room status:", status);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
if (!joined.ok) throw joined.error;
|
|
110
|
+
|
|
111
|
+
const sub = joined.value;
|
|
112
|
+
|
|
113
|
+
// The doc is now syncing in real time.
|
|
114
|
+
// Call sub.unsubscribe() or transport.close() to leave.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`createLoroDocAdapter(doc)` requires an attached `LoroDoc`. If the document was
|
|
118
|
+
checked out or detached, call `checkoutToLatest()` or `attach()` before passing
|
|
119
|
+
it to `StreamsCrdt`.
|
|
120
|
+
|
|
121
|
+
## Quick Start (Flock)
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { Flock } from "@loro-dev/flock-wasm";
|
|
125
|
+
import {
|
|
126
|
+
createStreamUrl,
|
|
127
|
+
createFlockAdapter,
|
|
128
|
+
StreamsCrdt,
|
|
129
|
+
} from "@loro-dev/streams-crdt/flock";
|
|
130
|
+
|
|
131
|
+
const flock = new Flock();
|
|
132
|
+
const transport = new StreamsCrdt({
|
|
133
|
+
streamUrl: createStreamUrl({
|
|
134
|
+
bucketId: "my-bucket",
|
|
135
|
+
streamId: "room-1",
|
|
136
|
+
}),
|
|
137
|
+
auth: async () => "<gateway-jwt>",
|
|
138
|
+
adapter: createFlockAdapter(flock),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await transport.createStream();
|
|
142
|
+
const joined = await transport.join();
|
|
143
|
+
if (!joined.ok) throw joined.error;
|
|
144
|
+
|
|
145
|
+
const sub = joined.value;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## When to Use `sync()` vs `join()`
|
|
149
|
+
|
|
150
|
+
Use **`sync()`** when you need a one-shot catch-up without entering live mode:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
// Offline-first: catch up on app start, then work offline.
|
|
154
|
+
const synced = await transport.sync();
|
|
155
|
+
if (!synced.ok) throw synced.error;
|
|
156
|
+
// synced.value.cursor contains the replay progress after sync.
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Use **`join()`** for real-time collaboration. It does everything `sync()` does
|
|
160
|
+
and then keeps the connection alive for continuous bidirectional sync:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const joined = await transport.join();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Delete Stream
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const deleted = await transport.deleteStream();
|
|
170
|
+
if (!deleted.ok) throw deleted.error;
|
|
171
|
+
|
|
172
|
+
console.log(deleted.value.deleted); // true when deleted now, false when already missing
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`deleteStream()` closes any active join first. When the configured
|
|
176
|
+
`remoteCursorStore` implements `delete(streamUrl)` (the built-in stores do),
|
|
177
|
+
the transport also clears the stored replay cursor for that stream.
|
|
178
|
+
|
|
179
|
+
## Auth
|
|
180
|
+
|
|
181
|
+
`auth` may be:
|
|
182
|
+
|
|
183
|
+
- a static string
|
|
184
|
+
- a sync callback returning a string
|
|
185
|
+
- an async callback returning a string
|
|
186
|
+
|
|
187
|
+
This is intended for refresh-token flows. `StreamsCrdt` asks for a token before
|
|
188
|
+
requests; token refresh itself remains caller-owned.
|
|
189
|
+
|
|
190
|
+
## Remote Cursor Store
|
|
191
|
+
|
|
192
|
+
The `RemoteCursorStore` persists **replay progress metadata only** — it does
|
|
193
|
+
**not** store any CRDT update data. A remote cursor contains:
|
|
194
|
+
|
|
195
|
+
- `nextOffset` — where the transport should resume reading from
|
|
196
|
+
- `serverLowerBoundVersion` — the CRDT version that the server data represents
|
|
197
|
+
- `streamUrl` — identity of the stream
|
|
198
|
+
|
|
199
|
+
### Important: cursor-only persistence is unsafe
|
|
200
|
+
|
|
201
|
+
If `RemoteCursorStore.load()` returns a saved cursor, the transport resumes
|
|
202
|
+
from `nextOffset` and assumes the caller has already restored the matching
|
|
203
|
+
local CRDT state represented by `serverLowerBoundVersion`.
|
|
204
|
+
|
|
205
|
+
**If the local CRDT was reset to empty but the cursor was persisted**, the
|
|
206
|
+
transport will do delta-only catch-up from `nextOffset` — applying those deltas
|
|
207
|
+
against an empty document will fail or produce an invalid state.
|
|
208
|
+
|
|
209
|
+
### Rule of thumb
|
|
210
|
+
|
|
211
|
+
| Local CRDT state | Cursor store | Safe? |
|
|
212
|
+
| --- | --- | --- |
|
|
213
|
+
| Not persisted (ephemeral) | `InMemoryRemoteCursorStore` (default) | Yes |
|
|
214
|
+
| Persisted (IndexedDB, SQLite, etc.) | `IndexedDbRemoteCursorStore` + `beforeRemoteCursorSave` hook | Yes |
|
|
215
|
+
| Not persisted | `IndexedDbRemoteCursorStore` | **No — data loss risk** |
|
|
216
|
+
|
|
217
|
+
If you do not persist the local CRDT payload across reloads, keep the remote
|
|
218
|
+
cursor ephemeral too. The default `InMemoryRemoteCursorStore` is the safe
|
|
219
|
+
choice.
|
|
220
|
+
|
|
221
|
+
## Hooking Local Durability
|
|
222
|
+
|
|
223
|
+
Use `beforeRemoteCursorSave` to couple local payload persistence with remote
|
|
224
|
+
cursor advancement. The transport advances the cursor only after:
|
|
225
|
+
|
|
226
|
+
1. Remote data was applied locally
|
|
227
|
+
2. The durability hook succeeded
|
|
228
|
+
3. The cursor store write succeeded
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
const transport = new StreamsCrdt({
|
|
232
|
+
streamUrl: createStreamUrl({
|
|
233
|
+
bucketId: "my-bucket",
|
|
234
|
+
streamId: "doc-1",
|
|
235
|
+
}),
|
|
236
|
+
adapter: createLoroDocAdapter(doc),
|
|
237
|
+
beforeRemoteCursorSave: async ({ cursor, source }) => {
|
|
238
|
+
// Persist local CRDT state before cursor advances.
|
|
239
|
+
// `source` is "local" | "remote" | "bootstrap".
|
|
240
|
+
await persistLocalPayload(doc);
|
|
241
|
+
console.log(source, cursor.nextOffset);
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
`persistLocalPayload(doc)` must make the local durable state correspond to the
|
|
247
|
+
cursor that is about to be stored. On the next app start, restore that local
|
|
248
|
+
state before calling `sync()` or `join()`.
|
|
249
|
+
|
|
250
|
+
## Persistent Cursor Example
|
|
251
|
+
|
|
252
|
+
Only use a persisted `RemoteCursorStore` such as `IndexedDbRemoteCursorStore`
|
|
253
|
+
when you also persist **and restore** the local CRDT state for the same stream.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { LoroDoc } from "loro-crdt";
|
|
257
|
+
import {
|
|
258
|
+
createStreamUrl,
|
|
259
|
+
createLoroDocAdapter,
|
|
260
|
+
IndexedDbRemoteCursorStore,
|
|
261
|
+
StreamsCrdt,
|
|
262
|
+
} from "@loro-dev/streams-crdt/loro";
|
|
263
|
+
|
|
264
|
+
// Step 1: restore local CRDT state first.
|
|
265
|
+
const doc = (await restorePersistedDoc("doc-1")) ?? new LoroDoc();
|
|
266
|
+
|
|
267
|
+
const transport = new StreamsCrdt({
|
|
268
|
+
streamUrl: createStreamUrl({
|
|
269
|
+
bucketId: "my-bucket",
|
|
270
|
+
streamId: "doc-1",
|
|
271
|
+
}),
|
|
272
|
+
auth: async () => "<gateway-jwt>",
|
|
273
|
+
adapter: createLoroDocAdapter(doc),
|
|
274
|
+
// Step 2: use a persistent cursor store.
|
|
275
|
+
remoteCursorStore: new IndexedDbRemoteCursorStore({
|
|
276
|
+
dbName: "my-app-ds-cursors",
|
|
277
|
+
}),
|
|
278
|
+
// Step 3: persist local state before cursor advances.
|
|
279
|
+
beforeRemoteCursorSave: async () => {
|
|
280
|
+
await persistLocalPayload("doc-1", doc);
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// join() handles initial sync + live mode.
|
|
285
|
+
const joined = await transport.join();
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The required ordering is:
|
|
289
|
+
|
|
290
|
+
1. Restore the local CRDT state first
|
|
291
|
+
2. Let the transport apply remote updates
|
|
292
|
+
3. Persist the updated local CRDT state (via `beforeRemoteCursorSave`)
|
|
293
|
+
4. Only then persist the remote cursor
|
|
294
|
+
|
|
295
|
+
If you cannot provide steps 1 and 3, do not use a persisted remote cursor
|
|
296
|
+
store.
|
|
297
|
+
|
|
298
|
+
## Automatic Snapshot Upload
|
|
299
|
+
|
|
300
|
+
The transport can upload snapshots automatically during `join()` when callers
|
|
301
|
+
opt in with `snapshotUpload`. **This feature is disabled by default** — if you
|
|
302
|
+
do not pass `snapshotUpload`, no snapshots will ever be uploaded.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const transport = new StreamsCrdt({
|
|
306
|
+
streamUrl,
|
|
307
|
+
adapter: createLoroDocAdapter(doc),
|
|
308
|
+
// Enable automatic snapshot upload:
|
|
309
|
+
snapshotUpload: {
|
|
310
|
+
canUpload: async () => true,
|
|
311
|
+
// debounceMs: 10_000, // default: 10 seconds
|
|
312
|
+
// minBytesSinceRemoteSnapshot: 102400, // default: 100 KB
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Configuration
|
|
318
|
+
|
|
319
|
+
| Option | Default | Description |
|
|
320
|
+
| --- | --- | --- |
|
|
321
|
+
| `canUpload` | *(required)* | Authorization gate called before each upload attempt. Return `false` to skip. |
|
|
322
|
+
| `debounceMs` | `10_000` (10 s) | Debounce window after the last local write before attempting upload. |
|
|
323
|
+
| `minBytesSinceRemoteSnapshot` | `102400` (100 KB) | Minimum byte delta between the last remote snapshot offset and the current stream tail. Upload is skipped when the delta is smaller. |
|
|
324
|
+
|
|
325
|
+
### Behavior
|
|
326
|
+
|
|
327
|
+
- Automatic snapshot upload is considered only during `join()`, never `sync()`
|
|
328
|
+
- **The feature is opt-in**: callers must pass `snapshotUpload` with at least
|
|
329
|
+
`canUpload` to enable it
|
|
330
|
+
- Successful local appends while `join()` is active refresh a debounce window
|
|
331
|
+
(default 10 s) for a possible snapshot upload check
|
|
332
|
+
- If a remote apply advances the inferred remote version during that window, the
|
|
333
|
+
transport treats that as another writer becoming active and skips that
|
|
334
|
+
snapshot attempt
|
|
335
|
+
- At debounce expiry, the transport may enqueue a best-effort
|
|
336
|
+
`maybeUploadSnapshot` task onto the same serialized operation queue used for
|
|
337
|
+
local append and remote apply
|
|
338
|
+
- When that task executes, it fetches the latest remote snapshot offset, uses
|
|
339
|
+
the latest confirmed local `cursor.nextOffset` as the candidate
|
|
340
|
+
`snapshot_offset`, and uploads only if
|
|
341
|
+
`current_tail_offset - remote_snapshot_offset` exceeds the configured byte
|
|
342
|
+
threshold
|
|
343
|
+
|
|
344
|
+
## Entry Points
|
|
345
|
+
|
|
346
|
+
| Import path | What it adds |
|
|
347
|
+
| --- | --- |
|
|
348
|
+
| `@loro-dev/streams-crdt` | Transport core, cursor stores, ID helpers, all shared types |
|
|
349
|
+
| `@loro-dev/streams-crdt/loro` | Everything above + `createLoroDocAdapter(doc)` |
|
|
350
|
+
| `@loro-dev/streams-crdt/flock` | Everything above + `createFlockAdapter(flock)` |
|
|
351
|
+
|
|
352
|
+
## Public API Reference
|
|
353
|
+
|
|
354
|
+
### Root Entry Point
|
|
355
|
+
|
|
356
|
+
- `StreamsCrdt` — transport runtime class
|
|
357
|
+
- `StreamsCrdtOptions` — constructor options
|
|
358
|
+
- `CrdtAdapter` — adapter contract interface
|
|
359
|
+
- `CrdtUpdateBatch` — local update batch type
|
|
360
|
+
- `Result` — Rust-style `Ok | Err` result
|
|
361
|
+
- `StreamsAuthProvider` — auth callback type
|
|
362
|
+
- `TransportError` — discriminated error union
|
|
363
|
+
- `TransportCreateStreamSuccess`, `TransportDeleteStreamSuccess`, `TransportSyncSuccess` — result payloads
|
|
364
|
+
- `TransportJoinParams` — `join()` options
|
|
365
|
+
- `TransportSubscription` — active live subscription handle
|
|
366
|
+
- `TransportRoomStatus` — `"joined" | "reconnecting" | "disconnected" | "error"`
|
|
367
|
+
- `SnapshotUploadOptions` — snapshot upload configuration
|
|
368
|
+
- `RemoteCursor` — replay progress metadata
|
|
369
|
+
- `RemoteCursorStore` — abstract cursor storage interface
|
|
370
|
+
- `RemoteCursorSaveSource` — `"local" | "remote" | "bootstrap"`
|
|
371
|
+
- `BeforeRemoteCursorSaveContext`, `BeforeRemoteCursorSaveHook` — durability hook types
|
|
372
|
+
- `InMemoryRemoteCursorStore` — ephemeral cursor store (default, always safe)
|
|
373
|
+
- `IndexedDbRemoteCursorStore` — persistent cursor store (requires local CRDT persistence)
|
|
374
|
+
- `IndexedDbRemoteCursorStoreOptions` — IndexedDB store config
|
|
375
|
+
- `createInitialRemoteCursor(...)` — seed a cursor store
|
|
376
|
+
- `createStreamUrl(...)` — build stream URL from bucket/stream IDs
|
|
377
|
+
- `isValidBucketId(...)`, `isValidRillId(...)` — ID validation helpers
|
|
378
|
+
|
|
379
|
+
### Loro Entry Point
|
|
380
|
+
|
|
381
|
+
`@loro-dev/streams-crdt/loro` re-exports everything from the root plus:
|
|
382
|
+
|
|
383
|
+
- `createLoroDocAdapter(doc)` — creates a `CrdtAdapter` for one `LoroDoc`
|
|
384
|
+
|
|
385
|
+
### Flock Entry Point
|
|
386
|
+
|
|
387
|
+
`@loro-dev/streams-crdt/flock` re-exports everything from the root plus:
|
|
388
|
+
|
|
389
|
+
- `createFlockAdapter(flock)` — creates a `CrdtAdapter` for one `Flock` replica
|
|
390
|
+
- `FlockVersionVector` — Flock-specific version vector type
|
|
391
|
+
|
|
392
|
+
## Notes
|
|
393
|
+
|
|
394
|
+
- `createStreamIfMissing` defaults to `false` and only affects the initial sync/join bootstrap
|
|
395
|
+
- `deleteStream()` returns `{ deleted: false }` when the target stream is already missing
|
|
396
|
+
- `sync()` and `join()` return `Result` objects instead of implicitly creating missing streams
|
|
397
|
+
- `join()` resolves only after the initial replay has succeeded; without a stored remote cursor it prefers bootstrap-live SSE and falls back sticky to long-poll
|
|
398
|
+
- One transport instance maps to one DS stream
|
|
399
|
+
- One adapter instance maps to one local CRDT instance
|
|
400
|
+
- Built-in remote cursor stores implement `delete(streamUrl)` so `deleteStream()` can reset replay state cleanly
|
|
401
|
+
|
|
402
|
+
## Repository-only Scripts
|
|
403
|
+
|
|
404
|
+
The published package only includes built runtime artifacts under `dist/`.
|
|
405
|
+
For repository verification against the hosted backend, run:
|
|
406
|
+
|
|
407
|
+
- `pnpm --dir packages/streams-crdt run test:e2e:streams-api`
|
package/dist/flock.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { C as IndexedDbRemoteCursorStore, D as RemoteCursorStore, E as RemoteCursorSaveSource, O as createInitialRemoteCursor, S as InMemoryRemoteCursorStore, T as RemoteCursor, _ as TransportRoomStatus, a as CrdtAdapter, b as BeforeRemoteCursorSaveContext, c as JsonValue, d as StreamsAuthProvider, f as StreamsCrdtOptions, g as TransportJoinParams, h as TransportError, i as StreamsCrdt, l as Result, m as TransportDeleteStreamSuccess, n as isValidBucketId, o as CrdtUpdateBatch, p as TransportCreateStreamSuccess, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotUploadOptions, v as TransportSubscription, w as IndexedDbRemoteCursorStoreOptions, x as BeforeRemoteCursorSaveHook, y as TransportSyncSuccess } from "./stream-id-1f2mhtmk.js";
|
|
2
|
+
|
|
3
|
+
//#region src/flock.d.ts
|
|
4
|
+
type FlockVersionEntry = {
|
|
5
|
+
logicalCounter: number;
|
|
6
|
+
physicalTime?: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Flock version vector shape used by the built-in Flock adapter.
|
|
10
|
+
*/
|
|
11
|
+
type FlockVersionVector = Record<string, FlockVersionEntry>;
|
|
12
|
+
type FlockExportBundle = {
|
|
13
|
+
readonly version?: number;
|
|
14
|
+
readonly entries: Record<string, unknown>;
|
|
15
|
+
};
|
|
16
|
+
type FlockLike = {
|
|
17
|
+
exportJson(options?: {
|
|
18
|
+
from?: FlockVersionVector;
|
|
19
|
+
peerId?: string;
|
|
20
|
+
}): FlockExportBundle;
|
|
21
|
+
importJson(bundle: FlockExportBundle): unknown;
|
|
22
|
+
merge?(replica: unknown): unknown;
|
|
23
|
+
peerId?(): string;
|
|
24
|
+
version(): unknown;
|
|
25
|
+
exportFile?(): Uint8Array;
|
|
26
|
+
subscribe?(listener: (batch: {
|
|
27
|
+
source?: string;
|
|
28
|
+
}) => void): () => void;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Creates a `CrdtAdapter` for one Flock replica.
|
|
32
|
+
*/
|
|
33
|
+
declare function createFlockAdapter(flock: FlockLike): CrdtAdapter<JsonObject>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, FlockVersionVector, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotUploadOptions, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
36
|
+
//# sourceMappingURL=flock.d.ts.map
|
package/dist/flock.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-CIhmveJD.js";
|
|
2
|
+
//#region src/flock.ts
|
|
3
|
+
function toVersionVector(version) {
|
|
4
|
+
const out = {};
|
|
5
|
+
for (const [peer, raw] of Object.entries(version)) {
|
|
6
|
+
if (typeof raw !== "object" || raw == null || Array.isArray(raw)) continue;
|
|
7
|
+
const logicalCounter = typeof raw.logicalCounter === "number" ? raw.logicalCounter : void 0;
|
|
8
|
+
if (logicalCounter == null || !Number.isFinite(logicalCounter)) continue;
|
|
9
|
+
out[peer] = typeof raw.physicalTime === "number" && Number.isFinite(raw.physicalTime) ? {
|
|
10
|
+
logicalCounter,
|
|
11
|
+
physicalTime: raw.physicalTime
|
|
12
|
+
} : { logicalCounter };
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function fromVersionVector(version) {
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const [peer, entry] of Object.entries(version)) out[peer] = entry.physicalTime === void 0 ? { logicalCounter: entry.logicalCounter } : {
|
|
19
|
+
logicalCounter: entry.logicalCounter,
|
|
20
|
+
physicalTime: entry.physicalTime
|
|
21
|
+
};
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function mergeVersions(base, incoming) {
|
|
25
|
+
const merged = { ...base };
|
|
26
|
+
for (const [peer, entry] of Object.entries(incoming)) {
|
|
27
|
+
const current = merged[peer];
|
|
28
|
+
if (current == null || entry.logicalCounter > current.logicalCounter) {
|
|
29
|
+
merged[peer] = { ...entry };
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (entry.logicalCounter < current.logicalCounter) continue;
|
|
33
|
+
const physicalTime = Math.max(current.physicalTime ?? Number.NEGATIVE_INFINITY, entry.physicalTime ?? Number.NEGATIVE_INFINITY);
|
|
34
|
+
merged[peer] = physicalTime === Number.NEGATIVE_INFINITY ? { logicalCounter: current.logicalCounter } : {
|
|
35
|
+
logicalCounter: current.logicalCounter,
|
|
36
|
+
physicalTime
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return merged;
|
|
40
|
+
}
|
|
41
|
+
function extractVersionFromBundle(bundle) {
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const raw of Object.values(bundle.entries)) {
|
|
44
|
+
if (typeof raw !== "object" || raw == null || Array.isArray(raw)) continue;
|
|
45
|
+
const entry = raw;
|
|
46
|
+
if (typeof entry.peerId !== "string") continue;
|
|
47
|
+
const logicalCounter = typeof entry.logicalCounter === "number" ? entry.logicalCounter : typeof entry.counter === "number" ? entry.counter : void 0;
|
|
48
|
+
if (logicalCounter == null || !Number.isFinite(logicalCounter)) continue;
|
|
49
|
+
out[entry.peerId] = typeof entry.physicalTime === "number" && Number.isFinite(entry.physicalTime) ? {
|
|
50
|
+
logicalCounter,
|
|
51
|
+
physicalTime: entry.physicalTime
|
|
52
|
+
} : { logicalCounter };
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function hasEntries(bundle) {
|
|
57
|
+
return Object.keys(bundle.entries).length > 0;
|
|
58
|
+
}
|
|
59
|
+
function tryMergeSnapshotFromFile(flock, snapshot) {
|
|
60
|
+
if (typeof flock.merge !== "function") return null;
|
|
61
|
+
const ctor = flock.constructor;
|
|
62
|
+
if (ctor == null || typeof ctor.fromFile !== "function") return null;
|
|
63
|
+
try {
|
|
64
|
+
const peerId = typeof flock.peerId === "function" ? flock.peerId() : void 0;
|
|
65
|
+
const replica = ctor.fromFile(snapshot, peerId);
|
|
66
|
+
flock.merge(replica);
|
|
67
|
+
return toVersionVector(flock.version() ?? {});
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Creates a `CrdtAdapter` for one Flock replica.
|
|
74
|
+
*/
|
|
75
|
+
function createFlockAdapter(flock) {
|
|
76
|
+
let lastExportedVersion = {};
|
|
77
|
+
let localExportQueue = Promise.resolve();
|
|
78
|
+
const recordExportFrontier = (version) => {
|
|
79
|
+
lastExportedVersion = mergeVersions(lastExportedVersion, version);
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
emptyVersion: () => ({}),
|
|
83
|
+
mergeVersions: (base, incoming) => fromVersionVector(mergeVersions(toVersionVector(base), toVersionVector(incoming))),
|
|
84
|
+
exportSnapshot: () => {
|
|
85
|
+
if (typeof flock.exportFile === "function") return flock.exportFile();
|
|
86
|
+
return new TextEncoder().encode(JSON.stringify({
|
|
87
|
+
version: 0,
|
|
88
|
+
entries: {}
|
|
89
|
+
}));
|
|
90
|
+
},
|
|
91
|
+
applySnapshot: (snapshot) => {
|
|
92
|
+
if (snapshot.byteLength === 0) return {};
|
|
93
|
+
const mergedVersion = tryMergeSnapshotFromFile(flock, snapshot);
|
|
94
|
+
if (mergedVersion != null) return fromVersionVector(mergedVersion);
|
|
95
|
+
const bundle = JSON.parse(new TextDecoder().decode(snapshot));
|
|
96
|
+
if (hasEntries(bundle)) flock.importJson(bundle);
|
|
97
|
+
return fromVersionVector(extractVersionFromBundle(bundle));
|
|
98
|
+
},
|
|
99
|
+
exportUpdates: (fromVersion) => {
|
|
100
|
+
const from = toVersionVector(fromVersion);
|
|
101
|
+
const bundle = flock.exportJson({ from });
|
|
102
|
+
recordExportFrontier(from);
|
|
103
|
+
if (!hasEntries(bundle)) return;
|
|
104
|
+
const exportedVersion = extractVersionFromBundle(bundle);
|
|
105
|
+
recordExportFrontier(exportedVersion);
|
|
106
|
+
return {
|
|
107
|
+
updates: [new TextEncoder().encode(JSON.stringify(bundle))],
|
|
108
|
+
version: fromVersionVector(exportedVersion)
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
applyRemoteUpdates: (updates, remoterVersion) => {
|
|
112
|
+
let merged = toVersionVector(remoterVersion);
|
|
113
|
+
for (const update of updates) {
|
|
114
|
+
const bundle = JSON.parse(new TextDecoder().decode(update));
|
|
115
|
+
if (!hasEntries(bundle)) continue;
|
|
116
|
+
flock.importJson(bundle);
|
|
117
|
+
merged = mergeVersions(merged, extractVersionFromBundle(bundle));
|
|
118
|
+
}
|
|
119
|
+
return fromVersionVector(merged);
|
|
120
|
+
},
|
|
121
|
+
subscribeLocalUpdates: (listener) => {
|
|
122
|
+
if (typeof flock.subscribe !== "function") return () => void 0;
|
|
123
|
+
return flock.subscribe((batch) => {
|
|
124
|
+
if (batch.source !== "local") return;
|
|
125
|
+
const drain = () => {
|
|
126
|
+
const peerId = typeof flock.peerId === "function" ? flock.peerId() : void 0;
|
|
127
|
+
const batchPayload = flock.exportJson({
|
|
128
|
+
from: lastExportedVersion,
|
|
129
|
+
peerId
|
|
130
|
+
});
|
|
131
|
+
if (!hasEntries(batchPayload)) return;
|
|
132
|
+
recordExportFrontier(toVersionVector(fromVersionVector(flock.version() ?? {})));
|
|
133
|
+
listener({
|
|
134
|
+
updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],
|
|
135
|
+
version: fromVersionVector(extractVersionFromBundle(batchPayload))
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
localExportQueue = localExportQueue.then(drain).catch(() => {});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
export { InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createFlockAdapter, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=flock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flock.js","names":[],"sources":["../src/flock.ts"],"sourcesContent":["import type { JsonObject, CrdtAdapter } from \"./types\";\n\nexport { StreamsCrdt } from \"./transport\";\nexport * from \"./shared\";\n\ntype FlockVersionEntry = {\n logicalCounter: number;\n physicalTime?: number;\n};\n\n/**\n * Flock version vector shape used by the built-in Flock adapter.\n */\nexport type FlockVersionVector = Record<string, FlockVersionEntry>;\n\ntype FlockExportBundle = {\n readonly version?: number;\n readonly entries: Record<string, unknown>;\n};\n\ntype FlockLike = {\n exportJson(options?: {\n from?: FlockVersionVector;\n peerId?: string;\n }): FlockExportBundle;\n importJson(bundle: FlockExportBundle): unknown;\n merge?(replica: unknown): unknown;\n peerId?(): string;\n version(): unknown;\n exportFile?(): Uint8Array;\n subscribe?(listener: (batch: { source?: string }) => void): () => void;\n};\n\nfunction toVersionVector(version: JsonObject): FlockVersionVector {\n const out: FlockVersionVector = {};\n for (const [peer, raw] of Object.entries(version)) {\n if (typeof raw !== \"object\" || raw == null || Array.isArray(raw)) {\n continue;\n }\n const logicalCounter =\n typeof raw.logicalCounter === \"number\" ? raw.logicalCounter : undefined;\n if (logicalCounter == null || !Number.isFinite(logicalCounter)) {\n continue;\n }\n out[peer] =\n typeof raw.physicalTime === \"number\" && Number.isFinite(raw.physicalTime)\n ? { logicalCounter, physicalTime: raw.physicalTime }\n : { logicalCounter };\n }\n return out;\n}\n\nfunction fromVersionVector(version: FlockVersionVector): JsonObject {\n const out: Record<string, JsonObject> = {};\n for (const [peer, entry] of Object.entries(version)) {\n out[peer] =\n entry.physicalTime === undefined\n ? { logicalCounter: entry.logicalCounter }\n : {\n logicalCounter: entry.logicalCounter,\n physicalTime: entry.physicalTime,\n };\n }\n return out;\n}\n\nfunction mergeVersions(\n base: FlockVersionVector,\n incoming: FlockVersionVector,\n): FlockVersionVector {\n const merged: FlockVersionVector = { ...base };\n for (const [peer, entry] of Object.entries(incoming)) {\n const current = merged[peer];\n if (current == null || entry.logicalCounter > current.logicalCounter) {\n merged[peer] = { ...entry };\n continue;\n }\n if (entry.logicalCounter < current.logicalCounter) {\n continue;\n }\n const physicalTime = Math.max(\n current.physicalTime ?? Number.NEGATIVE_INFINITY,\n entry.physicalTime ?? Number.NEGATIVE_INFINITY,\n );\n merged[peer] =\n physicalTime === Number.NEGATIVE_INFINITY\n ? { logicalCounter: current.logicalCounter }\n : { logicalCounter: current.logicalCounter, physicalTime };\n }\n return merged;\n}\n\nfunction extractVersionFromBundle(\n bundle: FlockExportBundle,\n): FlockVersionVector {\n const out: FlockVersionVector = {};\n for (const raw of Object.values(bundle.entries)) {\n if (typeof raw !== \"object\" || raw == null || Array.isArray(raw)) {\n continue;\n }\n const entry = raw as {\n peerId?: unknown;\n logicalCounter?: unknown;\n counter?: unknown;\n physicalTime?: unknown;\n };\n if (typeof entry.peerId !== \"string\") {\n continue;\n }\n const logicalCounter =\n typeof entry.logicalCounter === \"number\"\n ? entry.logicalCounter\n : typeof entry.counter === \"number\"\n ? entry.counter\n : undefined;\n if (logicalCounter == null || !Number.isFinite(logicalCounter)) {\n continue;\n }\n out[entry.peerId] =\n typeof entry.physicalTime === \"number\" &&\n Number.isFinite(entry.physicalTime)\n ? { logicalCounter, physicalTime: entry.physicalTime }\n : { logicalCounter };\n }\n return out;\n}\n\nfunction hasEntries(bundle: FlockExportBundle): boolean {\n return Object.keys(bundle.entries).length > 0;\n}\n\nfunction tryMergeSnapshotFromFile(\n flock: FlockLike,\n snapshot: Uint8Array,\n): FlockVersionVector | null {\n if (typeof flock.merge !== \"function\") {\n return null;\n }\n const ctor = (\n flock as unknown as {\n readonly constructor?: {\n fromFile?: (bytes: Uint8Array, peerId?: string) => unknown;\n };\n }\n ).constructor;\n if (ctor == null || typeof ctor.fromFile !== \"function\") {\n return null;\n }\n try {\n const peerId =\n typeof flock.peerId === \"function\" ? flock.peerId() : undefined;\n const replica = ctor.fromFile(snapshot, peerId);\n flock.merge(replica);\n return toVersionVector((flock.version() as JsonObject) ?? {});\n } catch {\n return null;\n }\n}\n\n/**\n * Creates a `CrdtAdapter` for one Flock replica.\n */\nexport function createFlockAdapter(flock: FlockLike): CrdtAdapter<JsonObject> {\n let lastExportedVersion: FlockVersionVector = {};\n let localExportQueue = Promise.resolve();\n\n const recordExportFrontier = (version: FlockVersionVector): void => {\n lastExportedVersion = mergeVersions(lastExportedVersion, version);\n };\n\n return {\n emptyVersion: () => ({}),\n mergeVersions: (base, incoming) =>\n fromVersionVector(\n mergeVersions(toVersionVector(base), toVersionVector(incoming)),\n ),\n exportSnapshot: () => {\n if (typeof flock.exportFile === \"function\") {\n return flock.exportFile();\n }\n return new TextEncoder().encode(\n JSON.stringify({ version: 0, entries: {} }),\n );\n },\n applySnapshot: (snapshot) => {\n if (snapshot.byteLength === 0) {\n return {};\n }\n const mergedVersion = tryMergeSnapshotFromFile(flock, snapshot);\n if (mergedVersion != null) {\n return fromVersionVector(mergedVersion);\n }\n const bundle = JSON.parse(\n new TextDecoder().decode(snapshot),\n ) as FlockExportBundle;\n if (hasEntries(bundle)) {\n flock.importJson(bundle);\n }\n return fromVersionVector(extractVersionFromBundle(bundle));\n },\n exportUpdates: (fromVersion) => {\n const from = toVersionVector(fromVersion);\n const bundle = flock.exportJson({ from });\n recordExportFrontier(from);\n if (!hasEntries(bundle)) {\n return undefined;\n }\n const exportedVersion = extractVersionFromBundle(bundle);\n recordExportFrontier(exportedVersion);\n return {\n updates: [new TextEncoder().encode(JSON.stringify(bundle))],\n version: fromVersionVector(exportedVersion),\n };\n },\n applyRemoteUpdates: (updates, remoterVersion) => {\n let merged = toVersionVector(remoterVersion);\n for (const update of updates) {\n const bundle = JSON.parse(\n new TextDecoder().decode(update),\n ) as FlockExportBundle;\n if (!hasEntries(bundle)) {\n continue;\n }\n flock.importJson(bundle);\n merged = mergeVersions(merged, extractVersionFromBundle(bundle));\n }\n return fromVersionVector(merged);\n },\n subscribeLocalUpdates: (listener) => {\n if (typeof flock.subscribe !== \"function\") {\n return () => undefined;\n }\n const unsubscribe = flock.subscribe((batch) => {\n if (batch.source !== \"local\") {\n return;\n }\n const drain = () => {\n const peerId =\n typeof flock.peerId === \"function\" ? flock.peerId() : undefined;\n const batchPayload = flock.exportJson({\n from: lastExportedVersion,\n peerId,\n });\n if (!hasEntries(batchPayload)) {\n return;\n }\n const currentVersion = toVersionVector(\n fromVersionVector((flock.version() as FlockVersionVector) ?? {}),\n );\n recordExportFrontier(currentVersion);\n listener({\n updates: [new TextEncoder().encode(JSON.stringify(batchPayload))],\n version: fromVersionVector(extractVersionFromBundle(batchPayload)),\n });\n };\n\n localExportQueue = localExportQueue.then(drain).catch(() => {\n /* swallow to keep queue alive */\n });\n });\n return unsubscribe;\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { C as IndexedDbRemoteCursorStore, D as RemoteCursorStore, E as RemoteCursorSaveSource, O as createInitialRemoteCursor, S as InMemoryRemoteCursorStore, T as RemoteCursor, _ as TransportRoomStatus, a as CrdtAdapter, b as BeforeRemoteCursorSaveContext, c as JsonValue, d as StreamsAuthProvider, f as StreamsCrdtOptions, g as TransportJoinParams, h as TransportError, i as StreamsCrdt, l as Result, m as TransportDeleteStreamSuccess, n as isValidBucketId, o as CrdtUpdateBatch, p as TransportCreateStreamSuccess, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotUploadOptions, v as TransportSubscription, w as IndexedDbRemoteCursorStoreOptions, x as BeforeRemoteCursorSaveHook, y as TransportSyncSuccess } from "./stream-id-1f2mhtmk.js";
|
|
2
|
+
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotUploadOptions, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as InMemoryRemoteCursorStore, i as StreamsCrdt, n as isValidBucketId, o as IndexedDbRemoteCursorStore, r as isValidRillId, s as createInitialRemoteCursor, t as createStreamUrl } from "./stream-id-CIhmveJD.js";
|
|
2
|
+
export { InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, StreamsCrdt, createInitialRemoteCursor, createStreamUrl, isValidBucketId, isValidRillId };
|
package/dist/loro.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { C as IndexedDbRemoteCursorStore, D as RemoteCursorStore, E as RemoteCursorSaveSource, O as createInitialRemoteCursor, S as InMemoryRemoteCursorStore, T as RemoteCursor, _ as TransportRoomStatus, a as CrdtAdapter, b as BeforeRemoteCursorSaveContext, c as JsonValue, d as StreamsAuthProvider, f as StreamsCrdtOptions, g as TransportJoinParams, h as TransportError, i as StreamsCrdt, l as Result, m as TransportDeleteStreamSuccess, n as isValidBucketId, o as CrdtUpdateBatch, p as TransportCreateStreamSuccess, r as isValidRillId, s as JsonObject, t as createStreamUrl, u as SnapshotUploadOptions, v as TransportSubscription, w as IndexedDbRemoteCursorStoreOptions, x as BeforeRemoteCursorSaveHook, y as TransportSyncSuccess } from "./stream-id-1f2mhtmk.js";
|
|
2
|
+
import { LoroDoc } from "loro-crdt";
|
|
3
|
+
|
|
4
|
+
//#region src/loro.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Creates a `CrdtAdapter` for one `LoroDoc`.
|
|
7
|
+
*
|
|
8
|
+
* The `LoroDoc` must be attached (not detached or checked out to a historical
|
|
9
|
+
* version). If the document is detached, call `doc.checkoutToLatest()` or
|
|
10
|
+
* `doc.attach()` before passing it here.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const doc = new LoroDoc();
|
|
15
|
+
* const adapter = createLoroDocAdapter(doc);
|
|
16
|
+
* const transport = new StreamsCrdt({ streamUrl, adapter, auth });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare function createLoroDocAdapter(doc: LoroDoc): CrdtAdapter<JsonObject>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, CrdtUpdateBatch, InMemoryRemoteCursorStore, IndexedDbRemoteCursorStore, IndexedDbRemoteCursorStoreOptions, JsonObject, JsonValue, RemoteCursor, RemoteCursorSaveSource, RemoteCursorStore, Result, SnapshotUploadOptions, StreamsAuthProvider, StreamsCrdt, StreamsCrdtOptions, TransportCreateStreamSuccess, TransportDeleteStreamSuccess, TransportError, TransportJoinParams, TransportRoomStatus, TransportSubscription, TransportSyncSuccess, createInitialRemoteCursor, createLoroDocAdapter, createStreamUrl, isValidBucketId, isValidRillId };
|
|
22
|
+
//# sourceMappingURL=loro.d.ts.map
|