Skip to content
Homeostate

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.

@homeostate/core@0.4.0npmSource

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

PageAPIs and types
Sync enginecreateSyncEngine, SyncEngineConfig, SeedStrategy, SetupOptions, SyncErrorSource, WriteLoopError, SyncEngine, SyncEvent, defaultSyncFilter, StoreAdapter, CrdtBackend, createCrdtBackend, CrdtBackendParts, CrdtBackendOptions, JsonObject, JsonObjectInput, Unsubscribe
PersistencecreatePersistence, PersistenceConfig, Persistence, PersistenceStats, PersistableDoc, PersistenceAdapter, StoredUpdates
Diff and applygetChanges, Diffable, DiffOptions, TextPolicy, Change, RecordChange, ListChange, TextChange, EditScript, ChangeType, toJsonValue, jsonEqual, keepIdentity, applyChanges, applyStringChanges, ApplyOps, ContainerKind
TestingcreateMemoryBackend, 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.

  • transact hands its edit your ApplyOps, which read and write your library's maps, lists and texts. createCrdtBackend gives build the text policy: in set and splice, store the strings it marks in your library's text type and every other string as a plain value; text is then edited through editText.
  • Implement revision() from something your library updates with every change, reported or not, such as a version vector or an op count, and return undefined while it cannot tell. Without it, the engine reads your document before every local change to check that previous is still current.
  • Delete in splice and editText with one library call for the whole deleteCount, such as Y.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.