{"_rev":"6-0d656dcd298c9713398bb670e75e4058","time":{"created":"2026-01-08T06:58:42.346Z","modified":"2026-01-08T06:58:42.868Z","1.0.0":"2026-01-07T10:33:23.846Z","1.2.0":"2026-01-08T06:58:42.570Z"},"_id":"@an-epiphany/websocket-json-stream","name":"@an-epiphany/websocket-json-stream","dist-tags":{"latest":"1.2.0"},"versions":{"1.2.0":{"name":"@an-epiphany/websocket-json-stream","version":"1.2.0","description":"A TypeScript Duplex stream wrapper for WebSocket with automatic JSON serialization. Supports ws, SockJS, and Socket.IO.","type":"module","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"keywords":["websocket","json","stream","duplex","typescript","sockjs","socket.io","socketio","ws","realtime","socket"],"author":{"name":"Pengap","email":"penganpingprivte@gmail.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/an-epiphany/websocket-json-stream.git"},"homepage":"https://github.com/an-epiphany/websocket-json-stream#readme","bugs":{"url":"https://github.com/an-epiphany/websocket-json-stream/issues"},"publishConfig":{"access":"public"},"devDependencies":{"@types/node":"^22.18.0","@types/sockjs":"^0.3.36","@types/sockjs-client":"^1.5.4","@types/ws":"^8.18.1","@vitest/coverage-v8":"^2.1.8","socket.io":"^4.8.1","socket.io-client":"^4.8.1","sockjs":"^0.3.24","sockjs-client":"^1.6.1","tsx":"^4.21.0","typescript":"^5.7.2","unbuild":"^3.3.1","vitest":"^2.1.8","ws":"^8.18.0"},"engines":{"node":">=18","pnpm":">=9","npm":"please-use-pnpm"},"scripts":{"build":"unbuild","dev":"unbuild --stub","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","typecheck":"tsc --noEmit","benchmark":"tsx benchmark/throughput.ts"},"_id":"@an-epiphany/websocket-json-stream@1.2.0","_integrity":"sha512-bITAXUrZMPw+PIDX96vnrB187FHaPHdmnkPr2Fy4wSR+UboCG6fcCfvNDof0zsb7hsQToVIgZI/Q3RZD4Fxziw==","_resolved":"/private/var/folders/dv/0278gk194678_k_x0jh27p1c0000gn/T/ae01ed551f6610beb1c972ce790d9e23/an-epiphany-websocket-json-stream-1.2.0.tgz","_from":"file:an-epiphany-websocket-json-stream-1.2.0.tgz","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-bITAXUrZMPw+PIDX96vnrB187FHaPHdmnkPr2Fy4wSR+UboCG6fcCfvNDof0zsb7hsQToVIgZI/Q3RZD4Fxziw==","shasum":"cd68296c58e9b64b6d60181d920cf42c95f04f3b","tarball":"https://registry.npmjs.org/@an-epiphany/websocket-json-stream/-/websocket-json-stream-1.2.0.tgz","fileCount":9,"unpackedSize":77373,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID9KOsI0q0W81Qn/iyWbKytAjEGjseH57aSW8lIZOjCAAiAu8+rw+nPbtl0A8TlAtE4qQX79XaRhY5iHc9TA/jr++w=="}]},"_npmUser":{"name":"pengap","email":"penganpingprivte@gmail.com"},"directories":{},"maintainers":[{"name":"pengap","email":"penganpingprivte@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/websocket-json-stream_1.2.0_1767855522447_0.1322557075081383"},"_hasShrinkwrap":false}},"maintainers":[{"name":"pengap","email":"penganpingprivte@gmail.com"}],"description":"A TypeScript Duplex stream wrapper for WebSocket with automatic JSON serialization. Supports ws, SockJS, and Socket.IO.","homepage":"https://github.com/an-epiphany/websocket-json-stream#readme","keywords":["websocket","json","stream","duplex","typescript","sockjs","socket.io","socketio","ws","realtime","socket"],"repository":{"type":"git","url":"git+https://github.com/an-epiphany/websocket-json-stream.git"},"author":{"name":"Pengap","email":"penganpingprivte@gmail.com"},"bugs":{"url":"https://github.com/an-epiphany/websocket-json-stream/issues"},"license":"MIT","readme":"<div align=\"center\">\n\n# websocket-json-stream\n\n[![license](https://img.shields.io/npm/l/websocket-json-stream?color=blue)](./LICENSE)\n[![typescript](https://img.shields.io/badge/TypeScript-5.0+-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)\n[![node](https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white)](https://nodejs.org/)\n\nNode.js Duplex 流封装，用于 WebSocket 连接的自动 JSON 序列化。\n\n支持 Node.js WebSockets (ws)、**SockJS** 和 **Socket.IO**。\n\n[English](./README.md) | [中文](./README.zh-CN.md)\n\n</div>\n\n---\n\n## 特性\n\n- **TypeScript 优先** - 完整的类型定义和泛型支持\n- **双包支持** - 同时支持 ESM 和 CommonJS\n- **自定义序列化器** - 可插拔的序列化方案（JSON、MessagePack 等）\n- **SockJS 适配器** - 内置 SockJS 支持，带 HTTP 降级\n- **Socket.IO 适配器** - 内置 Socket.IO 支持，带自动重连\n- **零依赖** - 仅需 WebSocket 库作为对等依赖\n- **类型安全消息** - 泛型类型实现编译时消息校验\n\n## 安装\n\n```bash\nnpm install @an-epiphany/websocket-json-stream\n# 或\npnpm add @an-epiphany/websocket-json-stream\n# 或\nyarn add @an-epiphany/websocket-json-stream\n```\n\n## 快速开始\n\n### 服务端\n\n```typescript\nimport { WebSocketJSONStream } from '@an-epiphany/websocket-json-stream'\nimport { WebSocketServer } from 'ws'\n\nconst wss = new WebSocketServer({ port: 8080 })\n\nwss.on('connection', (ws) => {\n  const stream = new WebSocketJSONStream(ws)\n\n  stream.on('data', (data) => {\n    console.log('收到:', data)\n    stream.write({ echo: data })\n  })\n})\n```\n\n### 客户端（原生 WebSocket）\n\n```typescript\nimport { WebSocket } from 'ws'\n\nconst ws = new WebSocket('ws://localhost:8080')\n\nws.on('open', () => {\n  ws.send(JSON.stringify({ message: '你好！' }))\n})\n\nws.on('message', (data) => {\n  const message = JSON.parse(data.toString())\n  console.log('收到:', message)\n})\n```\n\n## 类型安全消息\n\n```typescript\ninterface ChatMessage {\n  type: 'message' | 'join' | 'leave'\n  user: string\n  content?: string\n}\n\nconst stream = new WebSocketJSONStream<ChatMessage>(ws)\n\nstream.on('data', (msg) => {\n  // msg 被类型化为 ChatMessage\n  switch (msg.type) {\n    case 'message':\n      console.log(`${msg.user}: ${msg.content}`)\n      break\n    case 'join':\n      console.log(`${msg.user} 加入了`)\n      break\n  }\n})\n\nstream.write({ type: 'message', user: 'Alice', content: '你好！' })\n```\n\n## 自定义序列化器\n\n默认情况下，流使用 JSON 进行序列化。你可以提供自定义序列化器以获得更好的性能或使用不同的格式。\n\n### 使用选项对象\n\n```typescript\nimport { WebSocketJSONStream, type Serializer } from '@an-epiphany/websocket-json-stream'\n\n// 带前缀的自定义序列化器（示例）\nconst customSerializer: Serializer<MyData> = {\n  serialize: (value) => `PREFIX:${JSON.stringify(value)}`,\n  deserialize: (data) => JSON.parse(data.replace('PREFIX:', '')),\n}\n\nconst stream = new WebSocketJSONStream(ws, {\n  adapterType: 'ws',\n  serializer: customSerializer,\n})\n```\n\n### MessagePack 示例\n\n[MessagePack](https://msgpack.org/) 是一种二进制格式，比 JSON 更快更小。\n\n```typescript\nimport { WebSocketJSONStream, type Serializer } from '@an-epiphany/websocket-json-stream'\nimport { encode, decode } from '@msgpack/msgpack'\n\nconst msgpackSerializer: Serializer<MyData> = {\n  serialize: (value) => Buffer.from(encode(value)).toString('base64'),\n  deserialize: (data) => decode(Buffer.from(data, 'base64')) as MyData,\n}\n\nconst stream = new WebSocketJSONStream(ws, {\n  serializer: msgpackSerializer,\n})\n```\n\n### Base64 编码示例\n\n```typescript\nconst base64Serializer: Serializer<unknown> = {\n  serialize: (value) => Buffer.from(JSON.stringify(value)).toString('base64'),\n  deserialize: (data) => JSON.parse(Buffer.from(data, 'base64').toString('utf-8')),\n}\n\nconst stream = new WebSocketJSONStream(ws, {\n  serializer: base64Serializer,\n})\n```\n\n### 默认 JSON 序列化器\n\n你也可以导入默认序列化器用于参考或扩展：\n\n```typescript\nimport { jsonSerializer } from '@an-epiphany/websocket-json-stream'\n\n// jsonSerializer.serialize(value) - 转换为 JSON 字符串\n// jsonSerializer.deserialize(data) - 解析 JSON 字符串\n```\n\n## SockJS 支持\n\nSockJS 提供类似 WebSocket 的 API，当 WebSocket 不可用时自动降级到 HTTP 传输。\n\n### 服务端 (sockjs-node)\n\n```typescript\nimport { WebSocketJSONStream } from '@an-epiphany/websocket-json-stream'\nimport sockjs from 'sockjs'\nimport http from 'http'\n\nconst server = sockjs.createServer()\n\nserver.on('connection', (conn) => {\n  // 服务端连接使用 'sockjs-node' 适配器\n  const stream = new WebSocketJSONStream(conn, 'sockjs-node')\n\n  stream.on('data', (data) => {\n    stream.write({ echo: data })\n  })\n})\n\nconst httpServer = http.createServer()\nserver.installHandlers(httpServer, { prefix: '/sockjs' })\nhttpServer.listen(8080)\n```\n\n### 客户端 (sockjs-client)\n\n```typescript\nimport SockJS from 'sockjs-client'\n\nconst sock = new SockJS('http://localhost:8080/sockjs')\n\nsock.onopen = () => {\n  sock.send(JSON.stringify({ message: '通过 SockJS 发送！' }))\n}\n\nsock.onmessage = (e) => {\n  const message = JSON.parse(e.data)\n  console.log('收到:', message)\n}\n```\n\n### 为什么选择 SockJS？\n\n| 场景 | 解决方案 |\n|------|----------|\n| WebSocket 被防火墙/代理阻止 | 自动降级到 XHR streaming |\n| 企业网络环境 | 降级到长轮询 |\n| WebSocket 连接不稳定 | 多种传输选项 |\n\n## Socket.IO 支持\n\nSocket.IO 提供实时双向事件通信，支持自动重连和 HTTP 降级。\n\n### 服务端 (socket.io)\n\n```typescript\nimport { WebSocketJSONStream } from '@an-epiphany/websocket-json-stream'\nimport { Server as SocketIOServer } from 'socket.io'\nimport http from 'http'\n\nconst httpServer = http.createServer()\nconst io = new SocketIOServer(httpServer)\n\nio.on('connection', (socket) => {\n  // 使用 'socketio' 适配器\n  const stream = new WebSocketJSONStream(socket, 'socketio')\n\n  stream.on('data', (data) => {\n    stream.write({ echo: data })\n  })\n})\n\nhttpServer.listen(8080)\n```\n\n### 客户端 (socket.io-client)\n\n```typescript\nimport { io } from 'socket.io-client'\n\nconst socket = io('http://localhost:8080')\n\nsocket.on('connect', () => {\n  // 通过 'message' 事件发送 JSON（对应服务端的 WebSocketJSONStream）\n  socket.emit('message', JSON.stringify({ message: '通过 Socket.IO 发送！' }))\n})\n\nsocket.on('message', (data: string) => {\n  const message = JSON.parse(data)\n  console.log('收到:', message)\n})\n```\n\n### 为什么选择 Socket.IO？\n\n| 场景 | 解决方案 |\n|------|----------|\n| 需要自动重连 | 内置重连和退避机制 |\n| WebSocket 不可用 | 自动降级到 HTTP 长轮询 |\n| 需要房间/命名空间支持 | 原生房间和命名空间 |\n| 跨浏览器兼容性 | 包含 polyfill 和降级方案 |\n\n## API 参考\n\n### 构造函数\n\n```typescript\n// 新的选项对象 API（推荐）\nnew WebSocketJSONStream<T>(ws: AdaptableWebSocket, options?: WebSocketJSONStreamOptions<T>)\n\n// 旧版 API（仍然支持）\nnew WebSocketJSONStream<T>(ws: AdaptableWebSocket, adapterType?: AdapterType)\n```\n\n#### 选项对象\n\n| 属性 | 类型 | 默认值 | 描述 |\n|------|------|--------|------|\n| `adapterType` | `AdapterType` | `'ws'` | WebSocket 实现的适配器类型 |\n| `serializer` | `Serializer<T>` | `jsonSerializer` | 自定义序列化器 |\n\n#### 参数\n\n| 参数 | 类型 | 默认值 | 描述 |\n|------|------|--------|------|\n| `ws` | `AdaptableWebSocket` | - | WebSocket、SockJS 或 Socket.IO 连接 |\n| `T` | 泛型 | `unknown` | 消息类型 |\n\n### 事件\n\n| 事件 | 载荷 | 描述 |\n|------|------|------|\n| `data` | `T` | 收到 JSON 消息 |\n| `error` | `Error` | 解析/写入错误 |\n| `close` | - | 流已关闭 |\n| `finish` | - | 写入端已结束 |\n\n### 方法\n\n| 方法 | 描述 |\n|------|------|\n| `write(data: T)` | 发送 JSON 消息 |\n| `end()` | 以码 1000 关闭 |\n| `destroy(error?)` | 强制关闭 |\n\n## 关闭连接\n\n```typescript\n// 正常关闭 (码: 1000)\nstream.end()\n\n// 无状态码关闭 (码: 1005)\nstream.destroy()\n\n// 带错误关闭 (码: 1011)\nstream.destroy(new Error('出错了'))\n\n// 自定义关闭码 (3000-4999)\nconst error = new Error('自定义') as StreamError\nerror.closeCode = 4000\nerror.closeReason = '自定义原因'\nstream.destroy(error)\n```\n\n## 错误处理\n\n```typescript\n// 处理 WebSocket 错误（流不处理）\nws.on('error', (error) => {\n  console.error('WebSocket 错误:', error)\n})\n\n// 处理流错误\nstream.on('error', (error) => {\n  console.error('流错误:', error)\n})\n```\n\n## 高级：适配器工具\n\n```typescript\nimport {\n  adaptWebSocket,\n  isWebSocketLike,\n  isSockJSNodeConnection,\n  isSocketIOSocket,\n  SockJSNodeAdapter,\n  SocketIOAdapter,\n} from '@an-epiphany/websocket-json-stream'\n\n// 类型检查\nif (isSockJSNodeConnection(conn)) {\n  console.log('SockJS Node 连接')\n}\n\nif (isSocketIOSocket(socket)) {\n  console.log('Socket.IO 连接')\n}\n\n// 手动适配\nconst adapted = adaptWebSocket(conn, 'auto')\n```\n\n## 类型\n\n```typescript\ninterface Serializer<T = unknown> {\n  serialize(value: T): string\n  deserialize(data: string): T\n}\n\ninterface WebSocketJSONStreamOptions<T = unknown> {\n  adapterType?: AdapterType\n  serializer?: Serializer<T>\n}\n\ninterface WebSocketLike {\n  readonly readyState: number\n  send(data: string, callback?: (error?: Error) => void): void\n  close(code?: number, reason?: string): void\n  addEventListener(type: string, listener: Function): void\n  removeEventListener(type: string, listener: Function): void\n}\n\ninterface SockJSNodeConnection {\n  readonly readyState: number\n  write(data: string): boolean\n  close(code?: number, reason?: string): void\n  on(event: 'data' | 'close', listener: Function): this\n  off(event: 'data' | 'close', listener: Function): this\n}\n\ninterface SocketIOSocket {\n  readonly id: string\n  readonly connected: boolean\n  emit(event: string, ...args: unknown[]): this\n  on(event: string, listener: Function): this\n  off(event: string, listener: Function): this\n  disconnect(close?: boolean): this\n}\n\ninterface StreamError extends Error {\n  closeCode?: number\n  closeReason?: string\n}\n\ntype AdaptableWebSocket = WebSocketLike | SockJSNodeConnection | SocketIOSocket\ntype AdapterType = 'ws' | 'sockjs-node' | 'socketio' | 'auto'\n```\n\n## 许可证\n\n[MIT](./LICENSE)\n\n## 致谢\n\n基于 Greg Kubisa 的 [@teamwork/websocket-json-stream](https://github.com/Teamwork/websocket-json-stream) 重写的 TypeScript 版本。\n","readmeFilename":"README.zh-CN.md"}