CRDT Collaboration Architecture in Macro: Loro and Cloudflare Durable Objects
Macro implements real-time collaborative editing by hosting a single Loro CRDT document inside a Cloudflare Durable Object per document, ensuring strong consistency through serialized WebSocket message processing while using Loro's built-in vector clocks for automatic conflict resolution.
The macro-inc/macro repository powers a high-performance collaborative editor that combines Loro's conflict-free replicated data types (CRDTs) with Cloudflare's edge infrastructure. This CRDT collaboration architecture routes all editing sessions for a given document through a single Durable Object, guaranteeing that concurrent operations merge deterministically while maintaining low-latency synchronization across global regions.
Four-Layer Architecture
Macro's collaboration stack operates through four tightly coupled layers:
- Document Core: Holds the authoritative
LoroDocand awareness data within theDocumentSyncSessionDurable Object defined inservices/sync-service/src/durable_object.rs - Sync Engine: Serialized WebSocket message handling and CRDT operations managed by
DocumentStateinservices/sync-service/src/state.rs - Persistence: Shallow snapshots to Durable KV and full snapshots to S3-compatible storage via
services/sync-service/src/storage/mod.rsandservices/sync-service/src/storage/backends/durable_kv.rs - Cloudflare Edge: HTTP/WebSocket entry points, periodic autosave alarms, and cross-region coordination implemented in the Durable Object trait implementation
Per-Document Durable Objects and Strong Consistency
When a client connects to /document/{document_id}/connect, Cloudflare instantiates or reuses a DocumentSyncSession Durable Object. This guarantees that all traffic for a specific document ID routes to the same edge instance, creating a single-writer consistency model for the underlying CRDT.
The Durable Object stores one LoroDoc instance in memory along with an EphemeralStore for awareness data:
#[durable_object]
pub struct DocumentSyncSession {
awareness: EphemeralStore,
// ...
}
Because Cloudflare serializes all inbound WebSocket messages through the websocket_message method before they touch the CRDT, the system avoids race conditions while maintaining the performance benefits of edge computing.
Loro Integration and CRDT State Management
The core CRDT document lives inside DocumentState in services/sync-service/src/state.rs. The system initializes each document with LoroDoc::new() and maintains this instance for the Durable Object's entire lifetime.
pub struct DocumentState {
pub loro_doc: LoroDoc,
}
Operations arrive as binary Loro updates from clients. The Durable Object deserializes these via websocket::deserialize_message and applies them using apply_update(). Loro automatically merges concurrent edits using its internal version vectors and oplog frontiers, ensuring that divergent client states converge deterministically without server-side locking.
Real-Time Synchronization Protocol
The WebSocket message handler in services/sync-service/src/durable_object.rs processes all document mutations. When a binary update arrives, the system applies it to the shared LoroDoc and broadcasts the delta to connected peers:
async fn websocket_message(&self, ws: WebSocket, msg: WebSocketIncomingMessage) -> Result<()> {
let binary = match msg {
WebSocketIncomingMessage::Binary(b) => b,
_ => return Ok(()),
};
let update = websocket::deserialize_message(&binary)?;
self.document_state().await?.loro_doc.apply_update(update)?;
websocket::broadcast_update(&ws, &self.state.get_websockets(), update, self.msg_buffer.clone())?;
bump_alarm(&self.state).await?;
Ok(())
}
This serialized processing path ensures that even with thousands of concurrent connections, the CRDT state transitions remain atomic and ordered.
Awareness and Presence Handling
Macro uses Loro's EphemeralStore to manage transient user state such as cursor positions and text selections. Stored in the awareness field of DocumentSyncSession, this data expires automatically (configured to 5000ms) and broadcasts updates when peers join or disconnect.
When a client disconnects, the Durable Object removes the peer from the store and propagates the change:
self.awareness.delete(&peer_id.to_string());
let update = self.awareness.encode(&peer_id.to_string());
websocket::broadcast_awareness(&ws, …);
Unlike document content, awareness data does not persist to storage, ensuring that presence information remains lightweight and ephemeral.
Persistence and Snapshot Strategy
The architecture implements a tiered persistence strategy to balance durability with performance. The alarm method runs periodically or upon client disconnect to evaluate state.should_save():
- Shallow snapshots: State vectors cached for search indexing and sent to the Document Search Service (DSS)
- Full snapshots: Binary exports stored in Durable KV and S3-compatible backends via
store_snapshot()
async fn alarm(&self) -> Result<Response> {
let state = self.document_state().await?;
if state.should_save() {
let sess = self.session_storage().await?;
sess.store_snapshot(&state).await?;
sess.clear_applied_ops().await?;
self.state.wait_until(async move {
if let Ok(snapshot) = state.export_shallow_snapshot() {
report_new_doc_state(&doc_id, &snapshot, &env).await;
}
});
}
if !self.state.get_websockets().is_empty() {
bump_alarm(&self.state).await?;
}
Response::ok("ok")
}
This fire-and-forget pattern for side effects ensures the main request path remains low-latency while background tasks handle durability.
Versioning and Historical State
Clients can request document snapshots at specific versions using VersionIndicator parameters. The Durable Object constructs an ExportMode::StateOnly using the requested VersionVector, allowing Loro to reconstruct historical states deterministically:
let frontiers = body.and_then(|b| {
b.version_id.map(|vid| {
let id = loro::ID::new(vid.peer.parse::<u64>().unwrap(), vid.counter);
ExportMode::StateOnly(Some(Cow::Owned(loro::Frontiers::ID(id))))
})
});
let snapshot = self.document_state().await?
.export_snapshot(frontiers)
.context("Couldn't export snapshot")?;
This capability enables features like version history and time-travel debugging while maintaining the cryptographic consistency guarantees of the underlying CRDT.
Summary
Macro's CRDT collaboration architecture delivers real-time synchronization through these key mechanisms:
- Single Durable Object per document guarantees strong consistency and serialized message processing at the edge
- Loro CRDT provides deterministic conflict resolution through version vectors and oplog frontiers without requiring operational transformation locks
- EphemeralStore handles transient awareness data separately from document persistence
- Tiered persistence combines Durable KV for rapid recovery with S3-compatible storage for long-term durability
- Alarm-based autosave optimizes storage writes while ensuring data safety during connection drops
Frequently Asked Questions
How does Macro handle conflicting edits from simultaneous users?
Macro relies on Loro's CRDT properties rather than operational transformation. When concurrent updates arrive through the Durable Object's serialized WebSocket handler, Loro applies them using vector clock comparison and oplog merging. The LoroDoc in services/sync-service/src/state.rs automatically reconciles conflicts so that all clients eventually converge to the same state regardless of network latency or message ordering.
What happens when the last user disconnects from a document?
The DocumentSyncSession Durable Object detects empty WebSocket collections during the alarm tick. If state.should_save() returns true, the system persists a full snapshot via store_snapshot(), clears the applied operations list with clear_applied_ops(), and reports the final state to downstream services. The Durable Object then hibernates until the next connection, with state reconstructed from the persisted snapshot in services/sync-service/src/storage/backends/durable_kv.rs.
Why does Macro use Cloudflare Durable Objects instead of traditional servers?
Cloudflare Durable Objects provide co-location of compute and state at the network edge, ensuring that all clients editing the same document connect to the same physical instance. This eliminates the need for distributed locking or consensus protocols between regions. As implemented in services/sync-service/src/durable_object.rs, the single-instance model guarantees that the LoroDoc remains authoritative while maintaining sub-100ms latency for global users through Cloudflare's edge network.
How are document versions and history maintained?
The system stores periodic snapshots and maintains the full oplog within the Loro document structure. When exporting, the export_snapshot method accepts an ExportMode::StateOnly parameter constructed from a VersionVector, allowing precise reconstruction of any historical state. The crates/documents/src/domain/ports/markdown.rs integration shows how initial snapshots are generated from Markdown, while subsequent edits append to the versioned history stored in the Durable Object's memory and periodically flushed to the snapshot storage backend.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →