What are CRDTs?
CRDTs are data structures that can be replicated across multiple devices, modified independently, and merged automatically without conflicts.Key Properties
- Commutative: Operations can be applied in any order
- Associative: Grouping of operations doesn’t matter
- Idempotent: Applying same operation twice has no additional effect
Result
Strong eventual consistency: All clients that have received the same set of updates will converge to the same state, regardless of network delays or order of delivery.Yjs Overview
Yjs is a high-performance CRDT implementation optimized for shared editing of rich text, JSON, and other data structures. GitHub: https://github.com/yjs/yjsDocs: https://docs.yjs.dev
Why Yjs?
- Battle-tested: Used by Notion, Linear, and other production apps
- Efficient: Binary encoding, compact updates
- Rich types: Text, Map, Array, XML
- Language support: JavaScript, Rust, Python, Swift
- Offline-first: Designed for local-first architectures
Brainbox CRDT Package
Location:packages/crdt/
The @brainbox/crdt package wraps Yjs with a high-level API tailored for Brainbox:
Core Functions
Location:packages/crdt/src/index.ts
State Encoding
Update Merging
YDoc Class
TheYDoc class wraps a Yjs document with type-safe operations:
packages/crdt/src/index.ts:22
Update Method
Generates a CRDT update from attribute changes:packages/crdt/src/index.ts:49
Undo/Redo Support
packages/crdt/src/index.ts:97
CRDT Types
Yjs provides several shared types, mapped to JavaScript structures:Y.Text (Strings)
For collaborative string editing with character-level CRDT:packages/crdt/src/index.ts:415
Used for:
- Rich text document content (via TipTap/ProseMirror)
- Node names and simple text fields
Y.Map (Objects)
For collaborative object editing:packages/crdt/src/index.ts:170
Used for:
- Node attributes (page metadata, database config, etc.)
- Structured data within documents
Y.Array (Lists)
For collaborative list editing:packages/crdt/src/index.ts:251
Used for:
- Ordered lists within documents
- Multi-select field values
- Block sequences
Rich Text Collaboration
Brainbox uses TipTap (ProseMirror) for rich text editing, integrated with Yjs:How It Works
- User types: TipTap converts keystrokes to ProseMirror transactions
- Yjs generates update: Collaboration extension creates CRDT update
- Send to server: Update sent via WebSocket sync engine
- Broadcast to peers: Server broadcasts to other editors
- Apply update: Peers apply update to their Yjs document
- TipTap renders: Collaboration extension updates editor state
Conflict Resolution Example
Scenario: Two users edit the same document simultaneouslyNode Attributes CRDT
Brainbox applies CRDTs to node metadata, not just documents:Benefits
- Offline edits merge: Multiple clients can edit node attributes offline
- Last-write-wins avoided: No arbitrary conflict resolution
- Atomic updates: All attribute changes are part of CRDT history
Storage and Sync
Client-Side Storage
Node States Table
Node Updates Table
Server-Side Storage
Node Updates Table (PostgreSQL)
Sync Protocol
- Client generates update: YDoc produces binary update
- Store locally: Insert into
node_updatestable - Send to server: WebSocket message with base64-encoded update
- Server persists: Insert into PostgreSQL with revision number
- Broadcast to peers: Server sends to subscribed clients
- Peers apply: Clients apply update to their YDoc
- Update cursor: Track last synced revision
Performance Optimizations
Update Merging
Multiple small updates can be merged into larger updates:State Snapshots
Periodically store full CRDT state to avoid replaying long update history:Binary Encoding
Yjs uses compact binary encoding:- Update size: Typically 10-100 bytes per edit
- State size: Proportional to document size, not edit history
- Compression: Further compressed with gzip for transmission
Limitations and Trade-offs
CRDT Limitations
- Storage overhead: CRDT metadata adds ~10-20% to document size
- Complexity: More complex than last-write-wins
- Tombstones: Deleted items leave metadata (can be garbage collected)
When Not to Use CRDTs
- Financial data: Use server-authoritative transactions
- Inventory: Requires strong consistency (use locks)
- Sequential operations: Use queues with ordering guarantees
Brainbox Approach
- Rich text: Always use CRDTs (primary use case)
- Node metadata: Use CRDTs for offline support
- Transactional data: Use server-side validation (e.g., workspace billing)
Debugging CRDT Issues
Inspect Document State
Trace Updates
Validate Convergence
Test that clients converge after applying same updates:Further Reading
- Yjs Documentation: https://docs.yjs.dev
- CRDT Papers: https://crdt.tech
- Local-first Software: https://www.inkandswitch.com/local-first/
- Brainbox Sync Engine: Sync Engine
Next Steps
- Sync Engine - WebSocket sync protocol
- Local-First Architecture - Client database design
- Monorepo Structure - Code organization