@loro-dev/streams-crdt 0.8.1 → 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 +21 -0
- package/dist/flock.d.ts +2 -2
- package/dist/flock.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/loro.d.ts +2 -2
- package/dist/loro.js +126 -19
- package/dist/loro.js.map +1 -1
- package/dist/{stream-id-B6FzWgkM.d.ts → stream-id-Bi5rAZfP.d.ts} +34 -6
- package/dist/{stream-id-CCPEBb6G.js → stream-id-BjmGvObf.js} +179 -36
- package/dist/stream-id-BjmGvObf.js.map +1 -0
- package/package.json +1 -1
- package/dist/stream-id-CCPEBb6G.js.map +0 -1
package/README.md
CHANGED
|
@@ -118,6 +118,23 @@ 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`
|
|
@@ -859,9 +876,13 @@ const transport = new StreamsCrdt({
|
|
|
859
876
|
- `CrdtAdapter` — adapter contract interface
|
|
860
877
|
- `IsolatedCrdtAdapter` — optional apply-only adapter used for safe 410 recovery
|
|
861
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
|
|
862
881
|
- `Result` — Rust-style `Ok | Err` result
|
|
863
882
|
- `StreamsAuthProvider` — auth callback type
|
|
864
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.
|
|
865
886
|
- `TransportCreateStreamSuccess`, `TransportDeleteStreamSuccess`, `TransportSyncSuccess` — result payloads
|
|
866
887
|
- `TransportJoinParams` — `join()` options
|
|
867
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
|
|
@@ -41,5 +41,5 @@ interface LoroEphemeralStoreLike {
|
|
|
41
41
|
declare function EphemeralStoreAdaptor(store: LoroEphemeralStoreLike): EphemeralStreamAdaptor;
|
|
42
42
|
declare const createLoroEphemeralStoreAdaptor: typeof EphemeralStoreAdaptor;
|
|
43
43
|
//#endregion
|
|
44
|
-
export { BeforeRemoteCursorSaveContext, BeforeRemoteCursorSaveHook, CrdtAdapter, 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 };
|
|
45
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
|
},
|
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 /** 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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"}
|
|
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":"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"}
|
|
@@ -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;
|
|
@@ -419,6 +425,23 @@ interface CrdtUpdateBatch<TVersion extends JsonObject> {
|
|
|
419
425
|
readonly version: TVersion;
|
|
420
426
|
}
|
|
421
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>;
|
|
422
445
|
/**
|
|
423
446
|
* Isolated apply-only adapter used for 410/bootstrap recovery.
|
|
424
447
|
*
|
|
@@ -426,9 +449,9 @@ type ExportedCrdtUpdateBatch<TVersion extends JsonObject> = CrdtUpdateBatch<TVer
|
|
|
426
449
|
*/
|
|
427
450
|
interface IsolatedCrdtAdapter<TVersion extends JsonObject> {
|
|
428
451
|
/** Imports a full snapshot into isolated state and returns its version. */
|
|
429
|
-
applySnapshot(snapshot: Uint8Array): MaybePromise<TVersion
|
|
452
|
+
applySnapshot(snapshot: Uint8Array): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
430
453
|
/** Applies retained remote updates into isolated state and returns the new version. */
|
|
431
|
-
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<TVersion
|
|
454
|
+
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
432
455
|
}
|
|
433
456
|
/**
|
|
434
457
|
* Adapter contract between `StreamsCrdt` and a concrete CRDT runtime.
|
|
@@ -441,7 +464,7 @@ interface CrdtAdapter<TVersion extends JsonObject> {
|
|
|
441
464
|
/** Exports a full snapshot of the current local CRDT state. */
|
|
442
465
|
exportSnapshot(): Uint8Array;
|
|
443
466
|
/** Imports a full snapshot, replacing local state. Returns the version after import. */
|
|
444
|
-
applySnapshot(snapshot: Uint8Array): MaybePromise<TVersion
|
|
467
|
+
applySnapshot(snapshot: Uint8Array): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
445
468
|
/** Exports incremental updates since `fromVersion`. Returns `null`/`undefined` when there are no new updates. */
|
|
446
469
|
exportUpdates(fromVersion: TVersion): ExportedCrdtUpdateBatch<TVersion>;
|
|
447
470
|
/**
|
|
@@ -458,7 +481,7 @@ interface CrdtAdapter<TVersion extends JsonObject> {
|
|
|
458
481
|
* invariant to distinguish self-replay from another writer advancing the
|
|
459
482
|
* remote version.
|
|
460
483
|
*/
|
|
461
|
-
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<TVersion
|
|
484
|
+
applyRemoteUpdates(updates: Uint8Array[], remoterVersion: TVersion): MaybePromise<CrdtApplyReturn<TVersion>>;
|
|
462
485
|
/**
|
|
463
486
|
* Creates an isolated apply-only adapter for 410 recovery.
|
|
464
487
|
*
|
|
@@ -629,6 +652,7 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
629
652
|
private nextProducerSeq;
|
|
630
653
|
private frozenLocalAppend?;
|
|
631
654
|
private committedPendingCount?;
|
|
655
|
+
private committedLocalStreamCursor?;
|
|
632
656
|
private readonly pendingWriteOnly;
|
|
633
657
|
private writeOnlyCursor;
|
|
634
658
|
private unsubscribeWriteOnlyLocal?;
|
|
@@ -706,8 +730,11 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
706
730
|
private bootstrapState;
|
|
707
731
|
private catchup;
|
|
708
732
|
private catchupOrGone;
|
|
733
|
+
private recoverLiveIncompleteRemoteApply;
|
|
709
734
|
private appendLocalBatch;
|
|
710
735
|
private appendLocalBatchDurably;
|
|
736
|
+
private localAppendReadCursor;
|
|
737
|
+
private catchupLocalAppend;
|
|
711
738
|
private appendLocalBatchRemote;
|
|
712
739
|
private getOrCreateFrozenLocalAppend;
|
|
713
740
|
private appendFrozenLocalBatchRemote;
|
|
@@ -716,6 +743,7 @@ declare class StreamsCrdt<TVersion extends JsonObject> implements StreamsCrdtLik
|
|
|
716
743
|
private commitProducerAck;
|
|
717
744
|
private abandonFrozenLocalAppend;
|
|
718
745
|
private consumeCommittedPendingCount;
|
|
746
|
+
private consumeCommittedLocalStreamCursor;
|
|
719
747
|
/**
|
|
720
748
|
* Ensures the target stream exists, creating it if necessary.
|
|
721
749
|
* Tolerates 409 Conflict (stream already created concurrently).
|
|
@@ -845,5 +873,5 @@ declare function createStreamUrl(input: {
|
|
|
845
873
|
baseUrl?: string;
|
|
846
874
|
}): string;
|
|
847
875
|
//#endregion
|
|
848
|
-
export {
|
|
849
|
-
//# 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
|