MallaDocs

Realtime and multiplayer

Yjs rooms in Durable Objects, presence, checkpoints and undoable agent updates.

Developer guide

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.

Tokenapi GET /auth/realtime-token?siteId= signs a short-lived token.
ConnectWebSocket to /rooms/page:<id>?token=… ; origin and site checked.
Syncy-websocket protocol; every update validated before it is kept.
CheckpointAlarm writes JSON + Yjs state to Postgres within 30 s.

Rooms

  • Names are page:<pageId> (pages and components) or site:<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.
ts
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

text
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.