Skip to content

Repository files navigation

Birga

Many people, one document, no conflicts. A real-time collaborative editor built on CRDTs — including a CRDT written from scratch.

CI tests MIT


Birga is a collaborative editor where several people edit the same document at once — live cursors, instant updates, and edits that merge without conflicts, even offline. Two people typing in the same place never clobber each other; a client that reconnects after an hour catches up cleanly.

The interesting part is not the editor UI — it's the synchronisation. Birga contains a small CRDT (Conflict-free Replicated Data Type) implemented from scratch for sequences (text), so the merge logic is understood and owned, not imported. On top of that, a production-grade editor uses a battle-tested CRDT library, so the app is real, not a demo.

Why this project

Real-time collaboration is where "I know Socket.io" ends and "I understand distributed state" begins. CRDTs are the hard, senior part: convergence, causal ordering, tombstones, offline reconciliation. Building one from scratch — and being able to explain why it converges — is a signal almost no junior portfolio carries.

What it does (target)

  • Collaborative editing of rich text — multiple users, one doc, live.
  • Presence / awareness — live cursors, selections, who's here.
  • Offline-first — edit offline, reconnect, converge automatically.
  • Persistence — documents survive server restarts; late joiners load fast (snapshots).
  • From-scratch CRDT — a documented sequence CRDT (RGA/Logoot-style) with a test suite proving convergence, used to teach the concept; the full editor runs on a mature CRDT for reliability.

Stack

TypeScript · a hand-written CRDT core (own package) · WebSocket sync server (Node, ws) · Next.js + TipTap editor · Yjs for the rich-text path · Redis for presence/fan-out · Postgres for snapshots.

Architecture

graph LR
  A[browser A] -- ops + awareness --> S[["@birga/server<br/>WebSocket relay"]]
  B[browser B] -- ops + awareness --> S
  S --- PG[(Postgres<br/>snapshots + op log)]
  S --- R[(Redis<br/>pub/sub + presence)]
Loading

Each browser runs an editor bound to a CRDT — the from-scratch @birga/crdt for plain-text mode, Yjs for rich text — persists locally to IndexedDB (offline-first), and syncs ops through a CRDT-agnostic relay that stores them and fans out across instances.

Quickstart

pnpm install
pnpm build && pnpm test          # 264 tests green (incl. the CRDT property suite)

# run the app (builds libs, then server + web in parallel)
pnpm dev                         # web on :3000, sync server on :8080, API on :8787

Open http://localhost:3000, sign up (or continue as a guest), create a document, open the same URL in a second window, and type in both — including with one window offline. They converge.

No database is required to run it. Documents, accounts, presence and the op log live in memory and the server says so at boot; everything works — sign-up, sharing, permissions, both editing modes — it simply does not survive a restart.

Durable / multi-instance:

pnpm db:up                       # Postgres + Redis via docker compose
DATABASE_URL=postgres://birga:birga@localhost:5432/birga \
REDIS_URL=redis://localhost:6379 \
SESSION_SECRET=$(openssl rand -base64 32) \
  pnpm dev:server                # persistence, shared rooms, TTL'd presence

Permission enforcement on the WebSocket relay is on by default; set ENFORCE_PERMISSIONS=0 only if you deliberately want an open relay. SESSION_SECRET is required in production, and random-per-process otherwise.

Seeded demo (a populated, role-based tour):

pnpm db:up
export DATABASE_URL=postgres://birga:birga@localhost:5432/birga
export SESSION_SECRET=demo-secret
pnpm seed                        # 5 documents, 3 personas, plain + rich content

DATABASE_URL=$DATABASE_URL SESSION_SECRET=$SESSION_SECRET \
DEMO_ACCOUNTS=1 pnpm dev:server
pnpm dev:web

On the sign-in screen, click Ada, Ben, or Carol — each sees the document list with their own owner/editor/viewer roles, real content in both editing modes, and (as a viewer) a live read-only editor that rolls the edit back and says so.

Status

Every phase of the spec's MVP scope is implemented and tested — the full pipeline (build · typecheck · lint · test · web build) passes, and CI (ci.yml) runs all of it on every push. Full technical spec: docs/TZ.md.

package what it is tests
@birga/crdt from-scratch RGA sequence CRDT + convergence property suite 25
@birga/protocol CRDT-agnostic wire protocol
@birga/server WS relay · Redis fan-out + TTL'd presence · Postgres persistence + auto-compaction (both surfaces) · REST API (accounts, docs, share links, revocation) · access control on by default 138
@birga/client offline-first sync engine (CRDT ⇆ protocol) · reconnect · rollback 18
@birga/web Next.js editor — plain (@birga/crdt) + rich (Yjs) · presence · doc list · sharing 83

How it maps to the spec's Definition of Done (§9): the CRDT converges under thousands of randomised interleavings (a green property suite); two-window live edit and offline-edit-then-reconnect converge; live cursors + presence; late joiners load from a snapshot on long docs (periodic compaction); and the README explains why the CRDT converges with a diagram — see the portfolio write-up, docs/CRDT.md.

Beyond the spec: real email/password accounts alongside guest identities, revocable sharing, live "N here now" on the document list, and an app-wide dark theme. Remaining polish: a richer rich-text schema, document export, and search.

License

MIT.

About

Real-time collaborative document editor — from-scratch CRDT, offline-first, live presence

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages