Sync
Two documents with concurrent edits can be synchronized by just two message exchanges.
Below is an example of synchronization between two documents:
const = new ();
const = new ();
const : = .("list");
.(0, "A");
.(1, "B");
.(2, "C");
// B import the ops from A
const : = .({ : "update" });
// The data can be sent to B through the network
.();
(.()).({
: ["A", "B", "C"],
});
const : = .("list");
.(1, 1);
// `doc.export({mode: "update", from: version})` can encode all the ops from the version to the latest version
// `version` is the version vector of another document
const = .({
: "update",
: .(),
});
.();
(.()).({
: ["A", "C"],
});
(.()).(.());Real-time Collaboration
Due to CRDT properties, document consistency is guaranteed when peers receive the same updates, regardless of order or duplicates.
Consistency does not remove the need to choose the right child container
identity. If two offline peers create regular child containers under the same
Map key, sync will converge to one visible Map value and the other child can be
hidden by the Map conflict rule. For dynamic child containers that should be
shared by logical key, use ensureMergeable* when modeling the document.
Sync Strategies
-
First Sync (Initial synchronization between peers):
- New peers can exchange their Version Vectors to determine missing updates
- Use
doc.export({ mode: "update", from: versionVector })to get updates since the peer’s last known state. You may as well send the whole history bydoc.export({ mode: "update" })as shown in the example above. - Example shows basic first sync scenario
-
Realtime Sync (Continuous updates):
- Subscribe to local updates
- Broadcast updates directly to all other peers
- No need for version comparison after initial sync
- As long as updates reach all peers, consistency is maintained
Example
Here’s how two peers can establish realtime sync when one comes online with offline changes:
- Both peers exchange their version information
- Each peer shares their missing updates:
doc2gets updates it’s missing fromdoc1doc1gets updates it’s missing fromdoc2
- Both peers establish realtime sync to stay connected
const = new ();
.("text").(0, "Hello");
// Peer2 joins the network
const = new ();
// ... doc2 may import its local snapshot
// 1. Exchange version information
const = .();
const = .();
// 2. Request missing updates from existing peers
const = .({
: "update",
: ,
});
.();
const = .({
: "update",
: ,
});
.();
// 3. Establish realtime sync
.(() => {
// websocket.send(update);
});
.(() => {
// websocket.send(update);
});
// Now both peers are in sync and can collaborateUnderstanding the import() Return Value
The import() method in both Loro’s WASM binding and pure TypeScript implementation returns an ImportStatus with the following structure:
interface ImportStatus {
: <, >;
: <, > | null;
}
type = `${number}`;
type = {
: number; // Start counter (inclusive)
: number; // End counter (exclusive)
};Fields Explained:
-
success(Map<PeerID, CounterSpan>)- Description: This field is always present and details the ranges of operations integrated into history by this import, including earlier pending operations whose dependencies just arrived.
- Structure: It’s a JavaScript
Mapwhere:- Each key is a
stringrepresenting aPeerID(the unique identifier of a collaborator or a source of changes). - Each value is an object
{ start: number, end: number }defining a continuous range of operation counters for that specific peer.start: The starting counter of the successfully imported range (inclusive).end: The ending counter of the successfully imported range (exclusive). This means operations fromstartup to, but not including,endwere processed.
- Each key is a
- Purpose: Helps understand which parts of the provided update data have been integrated into the local document’s state.
- Example:
// Assuming importResult is the return value of doc.import(bytes) console.log(importResult.success); // Example output: // Map(2) { // "1" => { start: 0, end: 50 }, // "2" => { start: 120, end: 150 } // } // This means operations from peer "1" (counters 0-49) and // peer "2" (counters 120-149) were successfully imported.
-
pending(Map<PeerID, CounterSpan> | null)- Description: This field is always present. It is a
Mapof operation ranges waiting for missing causal dependencies, ornullwhen the import reports no pending operations. - Structure: When non-null, a JavaScript
MapfromPeerIDstrings to{ start: number, end: number }counter ranges, likesuccess. - Purpose: Informs the application that certain changes are known but are “on hold” awaiting their prerequisites. To apply these pending changes, the missing prerequisite operations must be imported first. This is crucial for maintaining data consistency in collaborative scenarios.
- Example:
// Assuming importResult is the return value of doc.import(bytes) if (importResult.pending !== null && importResult.pending.size > 0) { console.log(importResult.pending); // Example output: // Map(2) { // "1" => { start: 50, end: 60 }, // "2" => { start: 10, end: 25 } // } // This means operations from peer "1" (counters 50-59) and // peer "2" (counters 10-24) are pending due to missing dependencies. }
- Description: This field is always present. It is a
ImportStatus reports an import result, not a complete query of the document’s
pending queue; pending === null does not guarantee that no earlier imports
remain pending. Use exportPendingUpdates()
to export all currently received but unintegrated operations; undefined means
that queue is empty, not that the document is synced.
How to Use This Information:
- Use
success.get(peerId)or iterate oversuccessto inspect integrated ranges; use.sizeto check whether a Map is empty.Object.keys()does not enumerate Map entries. - If
pending !== null && pending.size > 0, further updates (dependencies) are required to integrate those pending operations. Request these missing updates from other peers or a central server. - To preserve pending imports across restarts, see persisting pending imports with the
exportPendingUpdates()API. A normal snapshot alone excludes these operations; saving them does not establish that the document is synced.