Core
@homeostate/core
The sync engine that keeps a Zustand, Redux, MobX or other store in sync with a Yjs, Loro or Automerge document, plus document persistence.
The sync engine. It keeps a store, reached through a StoreAdapter, in sync with a
CrdtBackend such as Yjs,
Loro or
Automerge. Concepts explains
how the pieces fit together.
Install
npm install @homeostate/core @homeostate/crdt-yjs yjs@homeostate/core has no runtime dependencies.
Usage
import { createSyncEngine } from "@homeostate/core";
import { createYjsBackend } from "@homeostate/crdt-yjs";
const engine = createSyncEngine(createYjsBackend(doc, "shared"), adapter, {
omit: ["draft"],
seed: "if-empty",
onError: (error, source) =>
console.error(`Sync failed in the ${source}`, error),
});
engine.connect();
engine.isConnected(); // true
engine.disconnect();Here, doc is your Yjs document and adapter comes from your state manager's store-*
package. omit keeps draft on this device, and functions such as actions stay local by
default. On connect() the backend's values win over the store's. Once connected, what the
store or the backend throws goes to onError instead of the other side's notifier. See the
sync engine reference for all options, lifecycle methods and adapter
contracts, or Connecting for the reconciliation model.
API reference
| Page | APIs and types |
|---|---|
| Sync engine | createSyncEngine, SyncEngineConfig, SeedStrategy, SetupOptions, SyncErrorSource, WriteLoopError, SyncEngine, SyncEvent, defaultSyncFilter, StoreAdapter, CrdtBackend, createCrdtBackend, CrdtBackendParts, CrdtBackendOptions, JsonObject, JsonObjectInput, Unsubscribe |
| Persistence | createPersistence, PersistenceConfig, Persistence, PersistenceStats, PersistableDoc, PersistenceAdapter, StoredUpdates |
| Diff and apply | getChanges, Diffable, DiffOptions, TextPolicy, Change, RecordChange, ListChange, TextChange, EditScript, ChangeType, toJsonValue, jsonEqual, keepIdentity, applyChanges, applyStringChanges, ApplyOps, ContainerKind |
| Testing | createMemoryBackend, MemoryBackend, createMemoryPersistenceAdapter, MemoryPersistenceAdapter from @homeostate/core/testing |
createPersistence keeps the document in browser storage through
@homeostate/persist-indexeddb or
@homeostate/persist-local-storage; the
persistence guide shows the setup.
Writing a backend
Build it with createCrdtBackend, from the parts that
differ by CRDT library: read for the synced subtree as plain JSON, transact to run one edit
tagged as the backend's own, watch to hear of every change and say whether it was its own,
and revision. Core does the rest: it applies the default text policy, diffs each write against
previous or the document with applyChanges and json: true, and leaves the backend's own
writes out of subscribe.
transacthands its edit yourApplyOps, which read and write your library's maps, lists and texts.createCrdtBackendgivesbuildthe text policy: insetandsplice, store the strings it marks in your library's text type and every other string as a plain value; text is then edited througheditText.- Implement
revision()from something your library updates with every change, reported or not, such as a version vector or an op count, and returnundefinedwhile it cannot tell. Without it, the engine reads your document before every local change to check thatpreviousis still current. - Delete in
spliceandeditTextwith one library call for the wholedeleteCount, such asY.Text.delete(index, length): clearing a list or deleting a paragraph arrives as one run.
The diff and apply reference covers the edit script format, the
RecordChange, ListChange and TextChange steps, and has a complete ApplyOps example.