Skip to content
yjs-lens

Does convergence actually hold?

Yjs is the most widely-deployed CRDT in production. Its own debugging surface is thin by design: Y.logUpdate() prints an update's contents, and that's essentially it. Nothing answers the question that actually matters: did this set of updates converge no matter what order they were applied in?

yjs-lens answers one question: did these Yjs updates actually converge to the same result, in every application order?

$ npm install yjs-lens yjs

Fuzz the application order, not just the happy path

Yjs is explicitly designed to accept updates out of causal order -- this is a real code path, not a contrived one. checkConvergence compares materialized content, not raw bytes, since two logically identical docs can have different internal struct representations. Runs are seeded for reproducibility.

src/verify.ts
import { checkConvergence, summarizeUpdate } from "yjs-lens";

const updates = [/* gathered from diverged replicas */];
const result = checkConvergence(updates, { orderings: 20 });

if (!result.converged) {
  throw new Error("convergence violated: " + JSON.stringify(result.divergence));
}

Two functions, both on Yjs's stable API

01

checkConvergence

checkConvergence(updates, options)

Applies updates to fresh docs in many random orders and asserts every ordering converges to identical content.

02

summarizeUpdate

summarizeUpdate(update)

Surfaces Yjs's causal-history structure: which client IDs contributed, and what clock range each covers.

03

updateSummaryToMermaid

updateSummaryToMermaid(summary)

Renders a summary as a Mermaid diagram, pasteable straight into GitHub markdown.

Why this exists

No conflict-explanation feature, unlike this project's Automerge sibling. Automerge exposes a public getConflicts() API; Yjs's equivalent resolution logic lives in internal, unstable struct-store fields that aren't part of its public API contract. Rather than depend on internals that could break on any patch release, this ships only what the stable API actually supports.

order A: u1, u2, u3

order B: u3, u1, u2

order C: u2, u3, u1


checkConvergence() → converged: true

same materialized content, every order

What yjs-lens doesn't do

  • No conflict/winner explanation.
    Not safely buildable on Yjs's stable public API -- see above.
  • No live, interactive UI.
    updateSummaryToMermaid produces static diagram source you render yourself.
  • No Y.Text-specific diffing.
    Beyond what generic content materialization already gives you.

Trust your CRDT, don't just hope it converges

Free and open source under the MIT license.

$ npm install yjs-lens yjs