Skip to content
Homeostate

Loro

@homeostate/crdt-loro

Keep a Zustand, Redux, MobX or other store in sync with a Loro document, mapping state onto LoroMap and LoroList with fine-grained updates.

@homeostate/crdt-loro@0.3.0npmSource

Loro backend for @homeostate/core. It maps the synced state onto a LoroMap (objects become LoroMap, arrays LoroList, and strings plain values or, where you choose, LoroText) and commits fine-grained operations as one transaction per write, so a middle-of-array delete, a toggle or a keystroke each produce one small update.

Install

npm install @homeostate/core @homeostate/crdt-loro loro-crdt

loro-crdt ships WebAssembly; for bundler setup see Loro's documentation.

Usage

import { LoroDoc } from "loro-crdt";
import { createSyncEngine } from "@homeostate/core";
import { createLoroBackend } from "@homeostate/crdt-loro";

const doc = new LoroDoc();
const engine = createSyncEngine(createLoroBackend(doc, "shared"), adapter);
engine.connect();

The synced state lives in doc.getMap("shared"). Replication is yours to wire, for example:

doc.subscribeLocalUpdates((update) => send(update));
onMessage((update) => doc.import(update));

The engine picks up every commit that did not come through its own write, imports and local edits alike. An edit not committed yet is picked up when the store next changes: the engine applies it first, and it wins over what that change did to synced keys.

undefined and functions are not JSON, so the backend leaves out object entries holding either and stores such array items as null, as JSON.stringify does.

Strings and text

Strings are plain values by default, so two peers changing an id, a status or a timestamp at once keep one of the two values. Pass text to store chosen strings as LoroText, whose concurrent edits merge character by character:

const backend = createLoroBackend(doc, "shared", {
  text: (path) => path[0] === "todos" && path[2] === "title",
});

text receives the path from the synced map, such as ["todos", 0, "title"]. read() returns plain strings either way. Give every backend writing to the document the same policy. A string held as the other kind, such as a LoroText written by an earlier release, reads as usual and is stored as the configured kind when it next changes. See Strings and text.

Persistence

Pass createLoroPersistable(doc) to createPersistence. It stores a snapshot plus the update of each commit or import since. See the persistence guide.