Agora
Concepts

CRDTs

Why a conflict-free replicated data type removes the conflict instead of resolving it, what Yjs and Automerge each model, and the three consequences that show up in your application code.

A CRDT — conflict-free replicated data type — is a data structure whose merge is defined by the structure itself. Give two replicas the same set of operations in any order, and they end up byte-identical. No coordinator decides who won, because the question never comes up.

The conflict that doesn't happen

Think about the naive version. Two people open the same paragraph. Both send "here is my new text". Whoever's request lands second overwrites the first, and someone's sentence is gone. The usual fixes — an edit lock, an operational-transform server, a "someone else changed this, reload" banner — all exist to manage that collision.

A CRDT removes it. Every edit is an operation with enough identity attached (who made it, where it sits relative to its neighbours) that "insert 'cat' at position 4" and "insert 'dog' at position 4" are not competing claims on position 4 — they are two insertions with a deterministic order. Both survive. Every replica agrees on which came first. Nobody was asked.

The price is that the document is no longer a string you can SET — it is a structure you apply operations to. That is the trade this library is built around, and the reason the shared content is the only thing inside it.

The two engines model different shapes

YjsAutomerge
Shapeshared types — Y.XmlFragment (rich text), Y.Map, Y.Arraya JSON-like document you mutate in a callback
Historynone built in; the library adds binary snapshotsnative — every change carries a hash
Editor bindingsTiptap/ProseMirror, Excalidraw, tldraw, CodeMirrornone for rich text
Awareness (live cursors)yes, part of the protocolno

Yjs is what you want for anything a human types or drags: it has the rich-text type, the editor bindings, and the awareness channel that carries cursors. Automerge is what you want for structured JSON that several clients mutate — a form, a config, a board of records — where a native change graph is worth more than an editor binding you were never going to use. See Engines for the full comparison and how a document picks one.

What it changes in your code

Three consequences you will meet on the first day:

  • Undo is not the editor's job. ProseMirror's history plugin has no idea which operations were yours; it will cheerfully undo the paragraph your colleague just wrote. Disable it (StarterKit.configure({ undoRedo: false }) on Tiptap 3, history: false on Tiptap 2) and let the CRDT's undo manager, which tracks origin, own it.

  • You do not read the document from the database. The authoritative copy is the live in-memory document the driver holds; the stored bytes are a debounced flush of it. Ask the manager (getDocumentState, getDocumentText) and it prefers the live document, falling back to storage only when nobody has it open.

    import collaboration from '@adonis-agora/collaboration/services/main'
    
    // Correct: goes through the driver, which knows about the live document.
    const text = await collaboration.current.getDocumentText({ docName })
  • Merges do not fail, but they can surprise. Two people rewriting the same sentence converge to a document containing both rewrites interleaved. That is the algorithm working as specified, not a bug — and it is why presence matters: showing the other cursor is what stops people from editing the same words in the first place.

Convergence is not authorization

A CRDT guarantees that everyone who receives an operation applies it identically. It says nothing about who is allowed to send one. That is the permission seam's job, and it happens before the socket is open — not inside the merge.

On this page