Skip to content
Homeostate

Automerge

@homeostate/crdt-automerge

Automerge CRDT backend for @homeostate/core

@homeostate/crdt-automerge@0.1.1npmSource
Warning

Draft / placeholder release. 0.0.0 reserves the name while the API is still being designed. Nothing here is stable — do not depend on it yet.

Automerge backend for @homeostate/core. It maps the synced state onto one key of an Automerge document (nested objects become maps, arrays lists, strings text) and writes fine-grained operations derived from getChanges, one Automerge change per write.

Install

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

@automerge/automerge 3.x is a peer dependency.

Usage

import * as A from "@automerge/automerge";
import { createSyncEngine } from "@homeostate/core";
import {
  createAutomergeBackend,
  createAutomergeHandle,
} from "@homeostate/crdt-automerge";

const handle = createAutomergeHandle(A.init());
const engine = createSyncEngine(
  createAutomergeBackend(handle, "shared"),
  adapter,
);
engine.connect();

Automerge documents are immutable values: every change returns a new document and leaves the old one behind. createAutomergeHandle holds the current document so the backend and your replication code share it:

  • handle.doc() returns the current document.
  • handle.change(fn) applies a local change through A.change.
  • handle.update((doc) => ...) replaces the document with the result of A.merge, A.applyChanges, A.loadIncremental, A.receiveSyncMessage, or any other Automerge call.
  • handle.subscribe(({ doc, local }) => ...) fires whenever the heads change; local is true for change and false for update.

The synced state lives in handle.doc().shared; a middle-of-array delete, a toggle, or a keystroke each produce one small change. Replication is yours to wire, for example:

handle.subscribe(({ doc, local }) => {
  if (local) send(A.getLastLocalChange(doc));
});
onMessage((change) => handle.update((doc) => A.applyChanges(doc, [change])[0]));

The engine hears about every change that did not come through its own write, imports and local edits alike.

read() returns plain JSON. Automerge keeps every object that the last change did not touch, so the backend caches copies per source object and a read after a toggle copies only the containers on the path to the toggled item. Snapshots are shared between reads; treat them as immutable.

undefined is not JSON and Automerge rejects it, so the backend drops object entries whose value is undefined and stores undefined array items as null, as JSON.stringify does.

@automerge/automerge ships WebAssembly. Node loads it directly; for bundlers see Automerge's documentation, or import @automerge/automerge/slim and initialize the module yourself.