React Starter Kit 的 WebSocket 协议包:基于 WS-Kit 的类型安全实时通信实战 React Starter Kit 的 WebSocket 协议包基于 WS-Kit 的类型安全实时通信实战【免费下载链接】react-starter-kitModern React starter kit with Bun, TypeScript, Tailwind CSS, tRPC, Stripe, and Cloudflare Workers. Production-ready monorepo for building fast web apps.项目地址: https://gitcode.com/gh_mirrors/rea/react-starter-kit导读packages/ws-protocol是 React Starter Kit 仓库中负责实时通信的独立工作区包workspace package它基于 WS-Kit、router.ts、example.ts、index.ts展开说明如何定义消息、如何用message()/rpc()建模请求-响应、如何通过createAppRouter()挂载处理函数与中间件并给出 Bun 原生 WebSocket 服务器与前端类型安全客户端的完整运行方案。读完你就能在自己的应用里跑起一个可校验、可广播、可 RPC 的实时通道。1. 包定位一个协议模板而非业务模块从仓库结构来看packages/ws-protocol在 项目结构文档 中被明确描述为 WebSocket protocol template其职责是沉淀消息协议定义供应用层复用。包内的package.jsonpackages/ws-protocol/package.json做了如下导出声明exports: { .: ./index.ts, ./messages: ./messages.ts, ./router: ./router.ts, ./package.json: ./package.json }这意味着使用者既可以从包入口repo/ws-protocol一次性导入全部协议也可以按需导入repo/ws-protocol/messages仅消息 Schema或repo/ws-protocol/router仅路由器从而保持协议定义与路由实现的解耦。包对外的 peer 依赖是ws-kit/zod^0.10.2与zod^4.4.3开发依赖中还引入了ws-kit/bun、ws-kit/memory、ws-kit/pubsub等配套库覆盖了 Bun 处理器、内存版发布订阅适配器与发布订阅插件三个方向。TS 编译配置packages/ws-protocol/tsconfig.json继承了repo/typescript-config的 node 配置并开启composite与declaration说明该包是 monorepo 中可被引用、可被增量编译的库型工作区。2. 快速开始一条命令跑起示例服务器README 给出的入门命令只有一条bun run example # Run example server对应package.json中的脚本scripts: { typecheck: tsc --noEmit, example: bun run example.ts }example.ts会启动一个监听3000端口的 Bun 服务器同时在/ws路径上提供 WebSocket 接入点。仓库 配方文档 还给出了在 monorepo 根目录下运行的等效写法bun --filter repo/ws-protocol example启动后终端会输出提示可用wscat -c ws://localhost:3000/ws连接然后依次发送PING、ECHO、GET_USER等消息进行验证详见第 5 节。3. 协议核心message 与 rpc 两种消息建模方式README 中的核心示例展示了最基础的用法import { z, message, rpc } from ws-kit/zod; import { createRouter, withZod } from ws-kit/zod; const Ping message(PING, { timestamp: z.number().optional() }); const Pong message(PONG, { timestamp: z.number() }); const router createRouter{ userId?: string }() .plugin(withZod()) .on(Ping, (ctx) { ctx.send(Pong, { timestamp: Date.now() }); });这段代码浓缩了 WS-Kit 的三大设计类型别名message、Zod 校验withZod 插件、链式路由注册on。仓库源码在此基础上给出了更完整的消息集全部遵循统一的信封结构{ type, meta, payload }见 messages.ts 的注释说明。3.1 单向消息messagemessage(type, payloadSchema)定义一个单向消息只有type与payload两级信息。仓库内置的示例包括消息typepayload 字段用途PingPINGtimestamp?: number连接健康检查PongPONGtimestamp?: number对 Ping 的应答EchoECHOtext: string回声测试NotificationNOTIFICATIONlevel: info \| warning \| error,message: string服务端广播通知ErrorMessageERRORcode: INVALID_MESSAGE \| UNAUTHORIZED \| SERVER_ERROR,message: string协议级错误上报其中Ping/Pong用于连接保活探测Notification与ErrorMessage分别覆盖了服务端推送与错误通道两个高频场景level与code都用z.enum()约束为白名单从协议层面杜绝了非法取值。3.2 请求-响应 RPCrpc对于需要一问一答的场景配方文档 与源码都推荐使用rpc()。它的签名同时描述了请求与响应两侧的 Schemaimport { rpc, z } from ws-kit/zod; export const GetMessages rpc( GET_MESSAGES, { channelId: z.string(), limit: z.number().default(50) }, MESSAGES, { messages: z.array(z.object({ id: z.string(), text: z.string() })) }, );仓库内置的 RPC 示例是GetUsermessages.tsexport const GetUser rpc(GET_USER, { id: z.string() }, USER, { id: z.string(), name: z.string(), email: z.string().optional(), });rpc()的四个参数依次是请求 type、请求 payload Schema、响应 type、响应 payload Schema。相比手写两套 message 再自行关联rpc()让请求与响应成为一组有类型的配对客户端可以通过GetUser.response拿到对应的响应消息类型从而获得端到端的类型推导。3.3 类型工具模块末尾还对外导出了三个类型工具export type { InferMessage, InferPayload, InferResponse } from ws-kit/zod;它们分别用于从消息定义中反推消息整体类型、payload 类型与 RPC 响应类型方便在编写泛型组件或类型工具函数时复用协议类型。4. 路由器生命周期钩子 消息处理 RPC 处理4.1 连接数据 AppDatarouter.ts 定义了一个贯穿连接生命周期的数据结构export interface AppData extends Recordstring, unknown { connectedAt?: number; userId?: string; }AppData继承Recordstring, unknown以保持扩展性connectedAt记录连接建立时间userId预留为登录态标识。它会被注入到createRouterAppData()的泛型中使所有处理函数的ctx都能访问这些连接级数据。4.2 createAppRouter 工厂createAppRouter()router.ts是包的核心工厂函数返回一个RouterAppData。其组装过程体现了 WS-Kit 的插件化设计export function createAppRouter(): RouterAppData { const router createRouterAppData() .plugin(withZod()) // Zod 校验插件 .onOpen((ctx) { ... }) // 连接建立钩子 .onClose((ctx) { ... }) // 连接关闭钩子 .onError((error) { ... }) // 错误处理钩子 .on(Ping, (ctx) { ctx.send(Pong, { timestamp: Date.now() }); }) .on(Echo, (ctx) { ctx.send(Echo, { text: ctx.payload.text }); }) .rpc(GetUser, async (ctx) { ctx.reply({ ... }); }); return router; }各部分职责如下withZod()校验插件确保每个入站消息的 payload 在进入处理器之前就按 Zod Schema 完成运行时校验。这是类型安全在运行时层面的落地——TS 类型负责编译期Zod 负责运行时。onOpen/onClose生命周期钩子。onOpen中通过ctx.assignData({ connectedAt: Date.now() })写入连接数据并打印[WS] Client connectedonClose读取ctx.data.connectedAt计算连接时长并打印断开日志。这样每个连接的生命周期都可见、可审计。onError统一错误出口示例中仅打印[WS] Error:日志生产环境可在此接入指标上报或告警。on(Ping, ...)/on(Echo, ...)消息处理器。Ping处理器用ctx.send(Pong, { timestamp: Date.now() })应答Echo处理器把收到的ctx.payload.text原样回传用于连通性测试。rpc(GetUser, async (ctx) ...)RPC 处理器示例中用一个 mock 用户响应演示调用形态注释中展示了真实的数据库查询写法如db.query.users.findFirst说明该处理器可以直接对接仓库db/目录下 Drizzle ORM 的数据层。由于createAppRouter返回的是可继续链式调用的RouterAppData业务侧可以在不修改包代码的前提下追加自己的消息处理器const router createAppRouter(); router.on(CustomMessage, (ctx) { /* ... */ });5. 示例服务器Bun 发布订阅 广播端点5.1 组装发布订阅能力example.ts 展示了如何把 pub/sub 能力加挂到基础路由器上import { createBunHandler } from ws-kit/bun; import { memoryPubSub } from ws-kit/memory; import { withPubSub } from ws-kit/pubsub; import { createAppRouter, Notification } from ./index; const router createAppRouter().plugin( withPubSub({ adapter: memoryPubSub() }), ); const { fetch: handleWebSocket, websocket } createBunHandler(router, { authenticate() { return { connectedAt: Date.now() }; }, });withPubSub({ adapter: memoryPubSub() })注入进程内内存版发布订阅适配器。它是单机演示形态配方文档 明确指出若部署到 Cloudflare Workers应改用ws-kit/cloudflare基于 Durable Objects以获得跨实例的消息分发。createBunHandler(router, { authenticate })把路由器转换为 Bun 原生 WebSocket 处理器。authenticate()的返回值会被写入连接的初始数据示例中返回{ connectedAt: Date.now() }与AppData结构吻合生产环境可在其中校验 token 并返回userId。5.2 Bun.serve 与广播端点const server Bun.serve({ port: 3000, fetch(req, server) { const url new URL(req.url); if (url.pathname /ws) { return handleWebSocket(req, server); } if (url.pathname /broadcast req.method POST) { router.publish(notifications, Notification, { level: info, message: Hello to all connected clients!, }); return new Response(Broadcast sent); } return new Response(WebSocket Server Example, { ... }); }, websocket, });这里有两个关键点路径分流/ws交给 WebSocket 处理器其余 HTTP 请求走普通响应逻辑/broadcast则是一个专用于测试的 HTTP 触发点演示了router.publish(topic, message, payload)的广播能力——服务端可以在任意 HTTP 请求中向订阅了notifications主题的所有客户端推送Notification消息。Bun 运行时依赖Bun.serve 与createBunHandler强绑定 Bun 运行时这与仓库整体 Bun runtime for instant builds 的定位见 docs/index.md一致。5.3 测试消息样本示例服务器的首页响应体直接给出了可发送的测试样本{type: PING, meta: {}, payload: {}} {type: ECHO, meta: {}, payload: {text: Hello}} {type: GET_USER, meta: {correlationId: 1}, payload: {id: 123}}其中GET_USER消息的meta.correlationId展示了 RPC 的关联机制请求方在 meta 中带上 correlationId响应方据此把应答关联回原始请求。广播测试则用一条 curl 命令完成curl -X POST http://localhost:3000/broadcast6. 前端接入原生 WebSocket 与类型安全客户端6.1 原生客户端零依赖配方文档 给出了不依赖任何 WS-Kit 客户端库的接入方式——用浏览器原生WebSocket收发 JSONimport { Ping, Pong, ChatMessage } from repo/ws-protocol; const ws new WebSocket(ws://localhost:3001/ws); ws.addEventListener(message, (event) { const msg JSON.parse(event.data); if (msg.type ChatMessage.type) { console.log(Chat:, msg.payload.text); } }); ws.send( JSON.stringify({ type: CHAT_MESSAGE, meta: {}, payload: { channelId: general, text: Hello!, sentAt: Date.now() }, }), );该方式的要点是发送时严格按{ type, meta, payload }信封结构序列化接收时用ChatMessage.type常量做类型判断——消息 Schema 从服务端包导出保证前后端对 type 字符串的拼写一致。6.2 类型安全客户端推荐example.ts 末尾的注释块给出了基于ws-kit/client/zod的类型安全客户端写法核心能力包括自动重连、类型化消息订阅与RPC 超时请求import { wsClient } from ws-kit/client/zod; import { Ping, Pong, Echo, GetUser } from repo/ws-protocol/messages; const client wsClient({ url: ws://localhost:3000/ws, reconnect: { enabled: true }, }); client.on(Pong, (msg) console.log(Received Pong)); client.on(Echo, (msg) console.log(Echo response:, msg.payload.text)); await client.connect(); client.send(Ping); client.send(Echo, { text: Hello server! }); // RPC带类型的请求与响应 const user await client.request( GetUser, { id: 123 }, GetUser.response, { timeoutMs: 5000 }, ); console.log(User:, user.payload.name); await client.close();wsClient({ reconnect: { enabled: true } })断线自动重连适合移动端网络抖动场景。client.on(Pong, (msg) ...)类型化订阅回调中的msg.payload自动推导为对应 Schema 类型。client.request(GetUser, payload, GetUser.response, { timeoutMs: 5000 })RPC 式调用GetUser.response提供了响应类型timeoutMs控制超时。7. 在项目中新增一个实时功能端到端配方配方文档 把以上要素串成一条完整流程以聊天室为例分四步落地第 1 步定义消息。在 messages.ts 中追加ChatMessagechannelId、text限制 1–2000 字符、sentAt或用rpc()定义GetMessages请求-响应对。第 2 步注册处理器。在 router.ts 的createAppRouter链上追加.on(ChatMessage, (ctx) ctx.publish(\channel:${ctx.payload.channelId}, ChatMessage, {...}))通过ctx.publish把消息广播给订阅了对应频道的所有客户端。注意publish 需要第 3 步的 pub/sub 插件支持。第 3 步启动服务器。createAppRouter()之后.plugin(withPubSub({ adapter: memoryPubSub() }))再用createBunHandlerBun.serve暴露/ws端点生产部署到 Cloudflare 时改用ws-kit/cloudflare Durable Objects。第 4 步前端接入。用原生WebSocket或ws-kit/client/zod的wsClient收发ChatMessage。这套流程的价值在于消息定义、服务端校验、广播分发、客户端订阅全部共享同一份 Schema 与类型前后端任何一方的字段变更都会在编译期暴露从源头消除协议漂移类 bug。8. 小结与扩展阅读packages/ws-protocol以极小的体积提供了一套完整的类型安全实时通信方案message()/rpc()定义协议、withZod()做运行时校验、生命周期钩子与发布订阅插件覆盖连接管理与广播场景、createBunHandler对接 Bun 原生 WebSocket并随包附带可运行示例。它在仓库中的角色是协议模板——业务开发者不必重复设计消息信封与校验逻辑只需按第 7 节的四步配方追加自己的消息与处理器。如果想继续深入可以在当前仓库中找到以下相关材料完整配方docs/recipes/websockets.md协议包目录packages/ws-protocol含 messages.ts、router.ts、example.ts工作区定位说明docs/getting-started/project-structure.md 与 README.md架构边界Worker 与 Durable Objectsdocs/architecture/edge.md面向 HTTP 的 tRPC 端点方案docs/recipes/new-procedure.md与 WebSocket 通道互补【免费下载链接】react-starter-kitModern React starter kit with Bun, TypeScript, Tailwind CSS, tRPC, Stripe, and Cloudflare Workers. Production-ready monorepo for building fast web apps.项目地址: https://gitcode.com/gh_mirrors/rea/react-starter-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考