Every page, component and site has a room: one YRoom Durable Object that syncs a Yjs document over WebSockets, keeps presence, persists to its own SQLite storage and checkpoints to Postgres. The room is also the editor’s save path, even when you edit alone.
Rooms
- Names are
page:<pageId>(pages and components) orsite:<siteId>(pages list, classes, tokens, variables, formulas). - Tokens are checked per room; viewers get the document and presence but their updates are dropped. Content editors may only change text.
- Sockets hibernate, so idle rooms cost nothing; presence survives a wake-up in socket attachments.
- Limits: 4 MiB per message, 256 sockets per room.
import { WebsocketProvider } from 'y-websocket'
const provider = new WebsocketProvider(realtimeUrl + '/rooms', 'page:p_home', ydoc, {
params: { token },
})
Server-side validation
Every update is first applied to a shadow copy. Each record it touched is read back as JSON and checked: node schema, bindings only on bindable props, $model only on form fields, every $expr parses, workflow shapes. A failing update is neither stored nor relayed, and the sender is closed with 4422, a permanent code so the client does not resend it.
Protocol
type 0 sync step1 / step2 / update (relayed to other sockets)
type 1 awareness cursors, selections (relayed to every socket)
type 3 query awareness
type 100 agent update Malla extension: origin byte (0 = ai, 1 = mcp) + Yjs update,
sent only to the sockets of the user an AI/MCP edit acted for
Persistence
- An append-only update log plus a snapshot, compacted at 500 rows or 1 MB and when the last socket leaves.
- Empty rooms are seeded from Postgres before the first sync, so a client never syncs an empty document over real content.
- Checkpoints to Postgres retry with backoff up to five minutes; sync never waits on them.
Undoable AI and MCP edits
Server-side edits reach the room through POST /internal/rooms/:room/ops with the acting user. The room applies them once, sends that user’s sockets an agent update (type 100) and everyone else a plain update. The editor applies agent updates with origin ai or mcp, which its undo manager tracks as a separate step. So the acting user can undo the agent’s change, and nobody can undo someone else’s.