ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

使用 y-partykit 在 PartyKit 上搭建 Yjs 协同后端:服务端接入、客户端 Provider 与持久化全攻略

使用 y-partykit 在 PartyKit 上搭建 Yjs 协同后端:服务端接入、客户端 Provider 与持久化全攻略 后端【免费下载链接】partykitPartyKit simplifies developing multiplayer applications项目地址https://gitcode.com/gh_mirrors/pa/partykit点击查看免费下载y-partykit是 PartyKit 官方仓库中为 Yjs 打造的插件库addon library它让你用几行代码就能在 PartyKit 房间Party/Room上托管一个完整的 Yjs 协同后端并内置了文档持久化、外部存储同步、只读访问与大消息分块等能力。读完本文你将掌握如何在 PartyKit 服务端通过onConnect接入 Yjs 协议如何用YPartyKitProvider与useYProvider从客户端含 React连接房间以及如何配置persist、load、callback等高级选项把协同文档安全地落地到 PartyKit 存储或你自己的数据库。一、y-partykit 是什么把 Yjs 协同搬到 PartyKitYjs 是一套高性能的 CRDT 数据结构库常用于构建协作编辑类应用多人在线文档、画布、评论等。它天然需要一台「同步服务器」来中转文档更新与感知awareness状态。y-partykit正是扮演这个角色的服务端插件——它深度 fork 了y-websocket把 Yjs 的同步协议y-protocols/sync、y-protocols/awareness直接跑在 PartyKit 的房间模型上。从仓库结构看这个包位于 packages/y-partykit核心源码文件如下src/index.ts服务端入口导出onConnect与unstable_getYDocsrc/provider.ts客户端 Provider导出默认的YPartyKitProvidersrc/react.tsReact Hook 版 Provider导出useYProvidersrc/storage.ts基于 PartyKit 房间存储的持久化层YPartyKitStoragesrc/chunking.ts超过 1MB 的 WebSocket 消息分块与重组。根据 package.json 中的exports字段该包对外提供四个子路径主入口y-partykit、y-partykit/provider、y-partykit/storage与y-partykit/react其运行时依赖lib0与lodash.debounce并将yjs^13.6.16、y-protocols^1.0.6、react列为 peerDependencies其中 react 可选。二、安装在项目根目录执行npm install y-partykit由于yjs、y-protocols是 peerDependencies如果你的项目还没有安装它们需要一并安装npm install yjs y-protocols若在浏览器端使用 React Hook还需安装 react但它是可选的 peer 依赖。三、服务端最小接入三行代码跑通一个 Yjs 房间y-partykit的核心导出是onConnect(conn, room, options)。在你的 PartyKit 服务端入口通常是src/server.ts里这样写// server.ts import { onConnect } from y-partykit; export default { async onConnect(conn, room, context) { return onConnect(conn, room); } };仓库中的示例 examples/yjs/src/server.ts 用的是类写法效果等价import type * as Party from partykit/server; import { onConnect } from y-partykit; export default class YjsServer implements Party.Server { constructor(public room: Party.Room) {} onConnect(conn: Party.Connection) { return onConnect(conn, this.room); } }这背后发生了什么从 src/index.ts 的onConnect实现可以看到它的完整工作流以room.id为键查找或创建一个WSSharedDoc继承自Y.Doc的共享文档实例并保证同一房间的并发连接复用同一个文档实例通过getYDocPromises缓存 Promise 避免重复初始化把当前连接注册进doc.conns同时为message与close事件挂上监听连接建立后立即向客户端发送sync step 1携带服务端当前的状态向量并广播当前所有在线用户的 awareness 状态。消息处理部分src/index.ts遵循 Yjs 标准协议messageSync类型 0用于同步文档增量messageAwareness类型 1用于同步光标、在线状态等感知数据。任何客户端产生update时服务端都会把它广播给房间内所有其他连接updateHandler从而保证多方编辑实时收敛。四、客户端接入YPartyKitProvider服务端就绪后客户端用 Provider 建立 WebSocket 连接并同步文档import YPartyKitProvider from y-partykit/provider; import * as Y from yjs; const yDoc new Y.Doc(); const provider new YPartyKitProvider( localhost:1999, // PartyKit host my-document-name, // 房间名文档名 yDoc );构造函数签名为new YPartyKitProvider(host, room, doc?, options?)。从 src/provider.ts 的实现可以看到几个关键细节自动选择协议如果host是localhost:、127.0.0.1:、192.168.、10.或私有网段172.16-31.开头默认使用ws://否则使用wss://也可以通过options.protocol强制指定URL 构造默认拼接为{ws|wss}://{host}/parties/{party|main}/{room}其中party可自定义默认mainprefix可完全覆盖路由前缀查询参数options.params会被拼接到 WebSocket 的 query string 上同时自动附带_pk连接 ID常用于携带鉴权 token延迟连接构造函数内部会先以connect: false初始化再在connect()中更新 URL 参数后真正建立连接。Provider 可用选项const provider new YPartyKitProvider( localhost:1999, my-document-name, yDoc, { readOnly: false, // 只读模式仅允许读取文档禁止写入默认 false connect: false, // 不立即连接之后手动调用 provider.connect() params: { token: my-secret-token }, // 追加到 WebSocket 连接 query string awareness: new awarenessProtocol.Awareness(yDoc) // 自定义 Yjs awareness 实例 } );各选项说明选项类型默认值说明connectbooleantrue是否在构造时立即连接设为false后可手动provider.connect()readOnlybooleanfalse只读模式服务端会忽略该连接的写入类消息paramsobject | () object | Promise{}附加到连接 URL 的查询参数支持函数/异步函数动态取值如异步获取 tokenawarenessAwareness自动创建使用自己的 Yjs awareness 实例protocolws \| wss自动判断强制指定 WebSocket 协议partystringmain指定连接的 Party 名称prefixstring无自定义 URL 前缀覆盖默认/parties/:party路由connectionIdstring随机 UUID自定义连接 ID会作为_pk参数出现在 URL 中WebSocketPolyfillclass全局 WebSocket在无原生 WebSocket 的环境如 Node中提供 polyfillresyncIntervalnumber-1定期重发 sync step 1 的间隔毫秒-1表示关闭maxBackoffTimenumber2500断线重连的最大退避等待时间毫秒disableBcboolean非浏览器环境为true是否禁用跨标签页 BroadcastChannel 同步此外params支持异步函数形式——这也是仓库示例 examples/yjs/src/client.ts 的用法它用getToken()异步返回{ token }并传入params。当params为函数时connect()会先await其返回值过滤掉null/undefined后再拼装 URL见 src/provider.ts。Provider 同时继承了 y-websocket 的Observable事件模型你可以监听sync、synced、status、connection-error等事件示例中就用provider.on(sync, ...)来切换界面连接状态。五、React 场景useYProvider Hook如果你在用 React可以直接使用 Hook 版本import useYProvider from y-partykit/react; function App() { const provider useYProvider({ host: localhost:1999, // 可选默认取 window.location.host room: my-document-name, doc: yDoc, // 可选不传则内部创建新 Y.Doc options }); }从 src/react.ts 的实现看useYProvider在useState中惰性创建 Providerconnect: false随后在useEffect中调用provider.connect()并在组件卸载时provider.disconnect()从而把连接生命周期与组件生命周期绑定。当host未提供时它会回退到window.location.host非浏览器环境则用占位域名方便部署到与前端同源的 PartyKit 域名。六、服务端高级配置持久化、外部存储与回调除了最小接入onConnect的第三个参数支持一整套选项用来构建更复杂的后端// server.ts import { onConnect } from y-partykit; export default { async onConnect(conn, room, context) { return await onConnect(conn, room, { // 实验性把文档持久化到 PartyKit 房间存储 persist: true, // 或者从你自己的数据库/存储中加载文档 load() { // 从数据库或远程资源加载文档 // 返回 Y.Doc 实例无文档则返回 null }, callback: { async handler(yDoc) { // 编辑后每隔几秒被调用一次 // 可在这里写入数据库或外部存储 }, // 用这些选项控制 handler 的调用频率 debounceWait: 10000, // 默认 2000 ms debounceMaxWait: 20000, // 默认 10000 ms timeout: 5000 // 默认 5000 ms } }); } };完整的选项类型YPartyKitOptions定义在 src/index.ts包括gc、persist、callback、load、readOnly。官方文档 apps/docs/src/content/docs/reference/y-partykit-api.md 对其中的「持久化」有更详细的阐述下面逐一展开。6.1 默认行为不持久化时文档随会话结束而消失默认情况下persist为false或未设置PartyKit 只在至少一个客户端在线时于内存中维护一份 Yjs 文档副本当所有客户端断开连接文档状态就可能丢失。如果你的应用需要跨会话保留文档比如多人文档编辑器、白板就必须开启持久化。6.2 持久化模式一snapshot 快照推荐onConnect(conn, room, { persist: { mode: snapshot } });在snapshot模式下PartyKit 在会话期间把增量更新逐条存入存储当最后一个连接断开、编辑会话结束时把所有更新合并为一份完整快照写入。它适合绝大多数不需要支持长期离线编辑的应用。6.3 持久化模式二history 编辑历史进阶onConnect(conn, room, { persist: { mode: history } });history模式保存文档的完整编辑历史适合「多个客户端离线各自修改、之后再合并同步」的场景。但长期存活的文档历史会无限增长最终触及单实例的实际容量上限。因此 PartyKit 对编辑历史施加了10MB 上限你也可以自定义这两个阈值onConnect(conn, room, { persist: { mode: history, // 历史总大小上限字节。可设为小于 10MB10_000_000的任意值 maxBytes: 10_000_000, // 更新条数上限。默认不设上限历史一直增长直到达到 maxBytes maxUpdates: 10_000 } });一旦任一上限被触达文档会被快照压缩然后重新开始记录历史。在源码 src/index.ts 中可以看到maxBytes超出 10MB 时会打印警告并回退到默认值类型YPartyKitPersistenceStrategysrc/index.ts定义了这两种模式的合法形态。6.4persist: true已废弃旧版本只有一个持久化开关onConnect(conn, room, { persist: true });它功能上等同于{ mode: history }。源码中遇到persist: true时会打印弃用警告并自动转换为 history 模式src/index.ts。该写法仅为向后兼容保留未来版本会移除请优先使用显式的snapshot/history。6.5 持久化背后的存储实现持久化层由 src/storage.ts 实现核心是YPartyKitStorage类。值得注意的实现细节128KB 分块存储PartyKit 房间存储对单个值有 128KB 上限因此levelPut会把超过 128KB 的更新切成 128KB 一块、按前缀#序号键写入src/storage.ts读取时再按序拼接levelGet更新日志与时钟每个文档维护单调递增的 update clock[v1, docName, update, clock]storeUpdate负责追加写getCurrentUpdateClock读取当前序号压缩更新日志compactUpdateLog在更新条数或总字节超过阈值时把全部更新合并mergeUpdates为一条状态快照并清空旧日志——这正是「history 达到上限后自动快照」的底层机制连接关闭落盘当房间最后一个连接断开且开启了持久化时服务端会调用compactUpdateLog并销毁内存中的文档实例src/index.ts确保状态不丢失。6.6 对接外部数据库load callback 组合如果你的业务数据存放在自己的数据库如 Postgres、Supabase、Redis而不是 PartyKit 存储可以用loadcallback组合实现外部持久化return onConnect(conn, this.room, { async load() { return await fetchDataFromExternalService(); // 返回 Y.Doc 或 null }, callback: { async handler(yDoc) { return sendDataToExternalService(yDoc); // 写回外部存储 }, // 编辑停止后 2 秒保存默认值 debounceWait: 2000, // 若更新持续不断至少每 10 秒保存一次默认值 debounceMaxWait: 10000 } });load在房间的第一个连接建立时被调用一次返回值会被applyUpdate到共享文档null表示没有历史文档。从 src/index.ts 看load的null检查0.0.28 版本加入能安全处理空文档场景文档加载后常驻内存直到会话结束。callback接受handler与url两种互斥形态类型定义见 src/index.ts二者不能同时出现。它的触发由lodash.debounce驱动默认参数为参数默认值含义debounceWait2000ms停止编辑后延迟多久调用 handlerdebounceMaxWait10000ms持续编辑时最多间隔多久必须调用一次timeout5000msurl模式下 POST 请求的超时时间当配置了callback.url时服务端会把{ room, data }以 JSON POST 到该 URL其中data内容由callback.objects指定形如{ 共享对象名: Array | Map | Text | XmlFragment | XmlElement }对应类型由 getContent 解析若配置了callback.handler则直接收到Y.Doc实例。保存失败会通过console.error(failed to persist:, ...)输出便于排查。七、只读模式与 GC两个容易被忽略的开关readOnly: true会让该连接只同步文档状态、不接受任何写入。在服务端消息分发处src/index.ts只读连接会跳过messageYjsSyncStep2与messageYjsUpdate两类写入处理只处理同步请求与 awareness。适合「游客围观」或「预览模式」场景。gc垃圾回收用于控制 Yjs 内部结构的 GC 开关。源码中有两条默认逻辑src/index.ts不开启持久化时gc默认true让服务端内存得到更好利用开启持久化时gc默认false因为 GC 会丢弃历史结构与持久化冲突若显式同时设置gc: true与persist会直接抛出Cannot use gc and persist at the same time错误。八、大文档支持1MB 消息分块机制PartyKit以及底层的 Workers 平台将单条 WebSocket 消息限制为 1MB而大型 Yjs 文档的同步消息很容易超过这个值。src/chunking.ts 因此实现了透明的消息分块发送端sendChunked消息 ≤ 1MB 时原样发送超过 1MB 时先发一条start标记含 id、size、count随后按 1MB 分片逐个发送最后发end标记首次触发会打印一条实验性警告接收端handleChunked服务端在收到消息时若为分片则缓存到收到end标记校验 start/end 的 id、count、size 一致后重组为完整 ArrayBuffer 再交给 Yjs 处理src/index.ts 中onConnect就是用handleChunked包裹消息监听的。这意味着即使文档超过 1MB客户端与服务端之间的同步依然可用分块对上层 Yjs 代码完全透明。九、unstable_getYDoc服务端直接操作文档如果你需要在服务端主动读取/修改某个房间的文档例如在 HTTP 请求中返回文档内容可以借助unstable_getYDoc(room, options)逃生舱import type * as Party from partykit/server; import type { YPartyKitOptions } from y-partykit; import { onConnect, unstable_getYDoc } from y-partykit; // options 必须与调用 onConnect 时保持一致 const opts: YPartyKitOptions { persist: { mode: snapshot } }; export default class YjsServer implements Party.Server { constructor(public room: Party.Room) {} async onRequest() { const doc await unstable_getYDoc(this.room, opts); return new Response(doc.getText(message)?.toJSON()); } onConnect(conn: Party.Connection) { return onConnect(conn, this.room, opts); } }需要注意该 API 标记为unstable传入的 options 必须与onConnect一致。文档一旦初始化后续传入的不同 options 会被忽略源码会通过hashOptions对比并打印警告src/index.ts。十、与 Yjs 生态的无缝兼容y-partykit是y-websocket的完整 fork 与改造见 CHANGELOG.md 中 fully fork y-websocket 的说明因此 Yjs 官方文档中所有基于y-websocket的示例都能平滑迁移——只需把y-websocket替换为y-partykit/provider。任何接受「Yjs Provider」的编辑器绑定如 Lexical、TipTap、ProseMirror 等都可以直接使用。仓库中的 examples/lexical/src/client.tsx 就是一个现成的例子它把YPartyKitProvider传给 Lexical 的CollaborationPlugin服务端则只用 examples/lexical/src/server.ts 里的四行代码即可驱动整个协同编辑。完整的可运行示例还可在 examples/yjs 找到其 partykit.json 配置了服务端入口src/server.ts与静态托管public目录 src/client.ts构建配合npm install后运行npx partykit dev即可在本地体验多人文本广播与在线人数感知。总结y-partykit用极小的接入成本把 Yjs 的文档同步、awareness 感知、断线重连、跨标签页同步等能力与 PartyKit 的多房间、持久化存储、WebSocket 运行时深度绑定。本文覆盖了从最小服务端、客户端 Provider、React Hook到 snapshot/history 持久化、外部存储对接、只读、GC、大消息分块与unstable_getYDoc的完整实践路径。想要深入源码可以继续阅读 packages/y-partykit/src/index.ts、src/storage.ts 与官方参考文档 apps/docs/src/content/docs/reference/y-partykit-api.md或直接运行仓库中的 examples/yjs 与 examples/lexical 示例体验效果。赞分享后端【免费下载链接】partykitPartyKit simplifies developing multiplayer applications项目地址https://gitcode.com/gh_mirrors/pa/partykit点击查看免费下载相关推荐PartyKit 协同后端实战指南使用 y-partykit 为 Yjs 搭建实时多人协作服务PartyKit 协同后端实战指南使用 y partykit 为 Yjs 搭建实时多人协作服务 y partykit 是 PartyKit 官方推出的附加库后端PartyKit 上的 Yjs 协同后端y-partykit 核心机制与持久化方案深度解析PartyKit 上的 Yjs 协同后端y partykit 核心机制与持久化方案深度解析 y partykit 是 PartyKit 官方为 Yjs htt后端Gemma-4-E4B-it-8bit API使用手册开发者必知的7个核心功能Gemma 4 E4B it 8bit API使用手册开发者必知的7个核心功能 Gemma 4 E4B it 8bit是一款专为Apple Silicon优化上一篇AI for Beginners 深度学习课使用 OpenAI Gym 与策略梯度/演员-评论家算法训练 CartPole 平衡智能体下一篇Linux 内核 i.MX8 DDR 性能监测单元PMU详解从 perf 事件体系到 AXI ID 过滤创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进