Engines
The CRDT backend behind a document — what a driver owns, how Yjs, Automerge and the edge engines compare, how a document picks one, and what changes in your code when it does.
An engine is the CRDT backend that owns a document: its merge, its wire protocol, its debounced
persistence and its version mechanism. The library wraps each one behind a driver and exposes
the same manager API over all of them — getDocumentState, getDocumentText, createVersion,
restoreVersion, diffVersions — so the engine is a config decision rather than an architectural
one.
Drivers are built lazily, one per engine, on first use. Configuring four engines and only ever opening Yjs documents means only the Yjs driver exists in memory.
At a glance
yjs | automerge | partykit / partyserver | |
|---|---|---|---|
| Where the socket lives | your Adonis process | your Adonis process | Cloudflare Durable Objects |
| Data model | shared types — rich text, maps, arrays | a JSON-like document | same as Yjs, at the edge |
| Rich-text editors | Tiptap, ProseMirror, CodeMirror | none | Tiptap, ProseMirror, CodeMirror |
| Live cursors (awareness) | yes | no | yes |
| History | binary snapshots the driver adds | native change graph | binary snapshots |
| Client hook | useCollabDoc | useAutomergeDoc | useCollabDoc |
| Runs behind >1 instance | with sticky sessions | no | yes, natively |
| Extra deployment | none | none | a Worker you own |
Choosing
Start with yjs. It is the default, it has the editor bindings, it has awareness, and it is
what the rest of these docs assume unless a page says otherwise.
Reach for automerge when the document is structured JSON that several clients mutate — a
form, a config object, a board of records — and you would rather have a native change graph than an
editor binding you were never going to use. It cannot back a rich-text editor.
Reach for the edge engines when latency across continents is the problem you are actually solving, and you are willing to own a Cloudflare Worker and a shared secret to fix it.
A whiteboard is usually still Yjs
The instinct to pick Automerge for a canvas is understandable — a scene is structured JSON — but Excalidraw and tldraw both want awareness for remote cursors, and the shipped editor adapters are built on the Yjs session. Automerge is for data you compute over, not for a surface people point at.
How a document picks its engine
Three ways, in the order the manager resolves them:
// 1. A sweeping function — wins outright, for every document
engineFor: (docName) => (docName.startsWith('archives/') ? 'automerge' : 'yjs'),
// 2. Per declared document type
documents: {
'whiteboards/:id': { engine: 'automerge' },
},
// 3. The global default (and the fallback when nothing else matches)
engine: 'yjs',The client is never told which engine to use — it asks. GET /collaboration/token?doc=…
answers with { token, wsUrl, engine }, and the hook picks the transport that matches. Moving a
document from the embedded Yjs server to a PartyKit worker is a config change plus a deploy; the
editor component does not know it happened.
What stays the same when you switch
Everything except the sync itself:
- Comments live at
manager.commentsand never touch a driver, so they follow the document across engines untouched. - Storage is one
CollaborationStorageimplementation shared by every driver — the same tables, the same rows. - Authorization is resolved by the manager before any driver is involved.
- Versions answer the same four calls, with the mechanism differing underneath.
What does change is the client hook — Automerge documents cannot back a Y.Doc, so they get
their own hook rather than being forced through a Y.Doc-shaped interface. See
Automerge.
Yjs (Hocuspocus)
The default — embedded Hocuspocus, rich text, canvases, awareness and snapshot versions.
Automerge
A JSON-like document with a native change graph, and its own client hook.
Edge (PartyKit / PartyServer)
Move the WebSocket to Cloudflare Durable Objects while Adonis keeps the token and the storage.
Presence
Who is in this document right now — peer-to-peer awareness for live cursors, a Redis-backed roster that spans Node instances, and the different questions each layer can answer.
Yjs (Hocuspocus)
The default engine — an embedded Hocuspocus server sharing the Adonis HTTP port, y-protocols over WebSocket, shared types for text and canvases, awareness for live cursors, and snapshot-based versions.