每个页面、组件和站点都有一个房间:一个 YRoom Durable Object,它通过 WebSocket 同步 Yjs 文档、维护在线状态、持久化到自己的 SQLite 存储,并向 Postgres 写入检查点。即使你一个人编辑,房间也是编辑器的保存通道。
令牌api GET /auth/realtime-token?siteId= 签发一个短期令牌。
连接WebSocket 连接到 /rooms/page:<id>?token=… ;检查来源和站点。
同步y-websocket 协议;每个更新在保留前都会经过校验。
检查点定时器在 30 秒内把 JSON + Yjs 状态写入 Postgres。
房间
- 名称为
page:<pageId>(页面和组件)或site:<siteId>(页面列表、类、令牌、变量、公式)。 - 令牌按房间检查;查看者能拿到文档和在线状态,但他们的更新会被丢弃。内容编辑者只能修改文本。
- Socket 会休眠,因此空闲房间不产生费用;在线状态保存在 socket 附件中,唤醒后依然保留。
- 限制:每条消息 4 MiB,每个房间 256 个 socket。
ts
import { WebsocketProvider } from 'y-websocket'
const provider = new WebsocketProvider(realtimeUrl + '/rooms', 'page:p_home', ydoc, {
params: { token },
})
服务端校验
每个更新都会先应用到一份影子副本上。它触及的每条记录都会以 JSON 形式读回并检查:节点结构、绑定只出现在可绑定的属性上、$model 只出现在表单字段上、每个 $expr 都能解析、工作流结构正确。未通过校验的更新既不存储也不转发,发送方的连接会以 4422 关闭,这是一个永久性代码,客户端不会重新发送该更新。
协议
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
持久化
- 一份只追加的更新日志加一个快照,在达到 500 行或 1 MB 时以及最后一个 socket 离开时进行压缩。
- 空房间在首次同步前会从 Postgres 加载初始内容,因此客户端绝不会用空文档覆盖真实内容。
- 向 Postgres 写入检查点失败时会带退避重试,最长五分钟;同步从不等待检查点。
可撤销的 AI 和 MCP 编辑
服务端编辑通过 POST /internal/rooms/:room/ops 连同执行操作的用户一起到达房间。房间只应用一次,然后向该用户的 socket 发送一条 agent update(type 100),向其他所有人发送普通更新。编辑器以 ai 或 mcp 来源应用 agent update,撤销管理器会把它作为单独的一步来跟踪。因此,执行操作的用户可以撤销智能体的改动,而任何人都无法撤销别人的改动。