@firtoz/websocket-do 7.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/websocket-do",
3
- "version": "7.0.1",
3
+ "version": "7.1.0",
4
4
  "description": "Type-safe WebSocket session management for Cloudflare Durable Objects with Hono integration",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -10,6 +10,11 @@
10
10
  "types": "./src/index.ts",
11
11
  "import": "./src/index.ts",
12
12
  "require": "./src/index.ts"
13
+ },
14
+ "./zod-client": {
15
+ "types": "./src/ZodWebSocketClient.ts",
16
+ "import": "./src/ZodWebSocketClient.ts",
17
+ "require": "./src/ZodWebSocketClient.ts"
13
18
  }
14
19
  },
15
20
  "files": [
@@ -18,7 +23,7 @@
18
23
  "README.md"
19
24
  ],
20
25
  "scripts": {
21
- "typecheck": "tsc --noEmit -p ./tsconfig.json",
26
+ "typecheck": "tsgo --noEmit -p ./tsconfig.json",
22
27
  "lint": "biome check --write src",
23
28
  "lint:ci": "biome ci src",
24
29
  "format": "biome format src --write"
@@ -45,14 +50,14 @@
45
50
  "url": "https://github.com/firtoz/fullstack-toolkit/issues"
46
51
  },
47
52
  "peerDependencies": {
48
- "@cloudflare/workers-types": "^4.20251228.0",
53
+ "@cloudflare/workers-types": "^4.20260329.1",
49
54
  "@firtoz/hono-fetcher": "^2.3.2",
50
- "hono": "^4.11.4"
55
+ "hono": "^4.12.9"
51
56
  },
52
57
  "optionalDependencies": {
53
- "msgpackr": "^1.11.8",
54
- "react": "^19.2.3",
55
- "zod": "^4.3.5"
58
+ "msgpackr": "^1.11.9",
59
+ "react": "^19.2.4",
60
+ "zod": "^4.3.6"
56
61
  },
57
62
  "engines": {
58
63
  "node": ">=18.0.0"
@@ -61,8 +66,8 @@
61
66
  "access": "public"
62
67
  },
63
68
  "devDependencies": {
64
- "@types/react": "^19.2.8",
65
- "bun-types": "^1.3.6",
66
- "typescript": "^5.9.3"
69
+ "@types/react": "^19.2.14",
70
+ "bun-types": "^1.3.11",
71
+ "typescript": "^6.0.2"
67
72
  }
68
73
  }
@@ -125,6 +125,19 @@ export abstract class BaseWebSocketDO<
125
125
  return;
126
126
  }
127
127
 
128
+ const rawMessageSession = session as BaseSession<
129
+ unknown,
130
+ unknown,
131
+ unknown,
132
+ TEnv
133
+ > & {
134
+ handleRawMessage?: (rawMessage: string) => Promise<void>;
135
+ };
136
+ if (rawMessageSession.handleRawMessage) {
137
+ await rawMessageSession.handleRawMessage(message);
138
+ return;
139
+ }
140
+
128
141
  const parsed = JSON.parse(message) as SessionClientMessage<TSession>;
129
142
  await session.handleMessage(parsed);
130
143
  } catch (error) {
package/src/ZodSession.ts CHANGED
@@ -6,6 +6,8 @@ import { zodMsgpack } from "./zodMsgpack";
6
6
  export type ZodSessionOptions<TClientMessage, TServerMessage> = {
7
7
  clientSchema: ZodType<TClientMessage>;
8
8
  serverSchema: ZodType<TServerMessage>;
9
+ serializeJson?: (value: unknown) => string;
10
+ deserializeJson?: (raw: string) => unknown;
9
11
  enableBufferMessages?: boolean;
10
12
  sendProtocolError?: (
11
13
  websocket: WebSocket,
@@ -70,6 +72,28 @@ export class ZodSession<
70
72
  this.enableBufferMessages = options.enableBufferMessages ?? false;
71
73
  }
72
74
 
75
+ public async handleRawMessage(rawMessage: string): Promise<void> {
76
+ // If buffer messages are enabled, reject string messages
77
+ if (this.enableBufferMessages) {
78
+ console.error(
79
+ "String messages not allowed when buffer messages are enabled",
80
+ );
81
+ await this.sendProtocolError(
82
+ "String messages are not allowed. Please use buffer messages.",
83
+ );
84
+ return;
85
+ }
86
+
87
+ try {
88
+ const parsed = this.deserializeJson(rawMessage);
89
+ const validatedMessage = this.options.clientSchema.parse(parsed);
90
+ await this.zodHandlers.handleValidatedMessage(validatedMessage);
91
+ } catch (error) {
92
+ console.error("Invalid client message received:", error);
93
+ await this._internalHandleValidationError(error, rawMessage);
94
+ }
95
+ }
96
+
73
97
  // Internal method used by the base class handlers
74
98
  private async _internalHandleMessage(message: TClientMessage): Promise<void> {
75
99
  // If buffer messages are enabled, reject JSON messages
@@ -152,7 +176,7 @@ export class ZodSession<
152
176
 
153
177
  if (this.websocket.readyState !== WebSocket.OPEN) return;
154
178
 
155
- this.websocket.send(JSON.stringify(validatedMessage));
179
+ this.websocket.send(this.serializeJson(validatedMessage));
156
180
  } catch (error) {
157
181
  console.error("Invalid server message to send:", error);
158
182
  }
@@ -180,13 +204,25 @@ export class ZodSession<
180
204
  } else {
181
205
  // Default implementation: send a simple error object - no schema validation needed
182
206
  if (this.websocket.readyState !== WebSocket.OPEN) return;
183
- this.websocket.send(JSON.stringify({ error: errorMessage }));
207
+ this.websocket.send(this.serializeJson({ error: errorMessage }));
184
208
  }
185
209
  } catch (error) {
186
210
  console.error("Failed to send protocol error:", error);
187
211
  }
188
212
  }
189
213
 
214
+ private serializeJson(value: unknown): string {
215
+ return this.options.serializeJson
216
+ ? this.options.serializeJson(value)
217
+ : JSON.stringify(value);
218
+ }
219
+
220
+ private deserializeJson(raw: string): unknown {
221
+ return this.options.deserializeJson
222
+ ? this.options.deserializeJson(raw)
223
+ : JSON.parse(raw);
224
+ }
225
+
190
226
  // Type-safe broadcast that validates server messages
191
227
  // Automatically uses the correct format based on session configuration
192
228
  public broadcast(message: TServerMessage, excludeSelf = false): void {
@@ -13,6 +13,8 @@ export interface ZodWebSocketClientOptions<TClientMessage, TServerMessage> {
13
13
  webSocket?: WebSocket;
14
14
  clientSchema: ZodType<TClientMessage>;
15
15
  serverSchema: ZodType<TServerMessage>;
16
+ serializeJson?: (value: unknown) => string;
17
+ deserializeJson?: (raw: string) => unknown;
16
18
  enableBufferMessages?: boolean;
17
19
  onMessage?: (message: TServerMessage) => void;
18
20
  onOpen?: (event: Event) => void;
@@ -25,6 +27,8 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
25
27
  private ws: WebSocket;
26
28
  private readonly clientSchema: ZodType<TClientMessage>;
27
29
  private readonly serverSchema: ZodType<TServerMessage>;
30
+ private readonly serializeJson: (value: unknown) => string;
31
+ private readonly deserializeJson: (raw: string) => unknown;
28
32
  private readonly enableBufferMessages: boolean;
29
33
  private readonly onMessageCallback?: (message: TServerMessage) => void;
30
34
  private readonly onValidationError?: (
@@ -37,6 +41,8 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
37
41
  ) {
38
42
  this.clientSchema = options.clientSchema;
39
43
  this.serverSchema = options.serverSchema;
44
+ this.serializeJson = options.serializeJson ?? JSON.stringify;
45
+ this.deserializeJson = options.deserializeJson ?? JSON.parse;
40
46
  this.enableBufferMessages = options.enableBufferMessages ?? false;
41
47
  this.onMessageCallback = options.onMessage;
42
48
  this.onValidationError = options.onValidationError;
@@ -108,7 +114,7 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
108
114
  }
109
115
 
110
116
  // Parse and validate
111
- const parsed = JSON.parse(event.data);
117
+ const parsed = this.deserializeJson(event.data);
112
118
  parsedMessage = this.serverSchema.parse(parsed);
113
119
  }
114
120
 
@@ -132,12 +138,12 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
132
138
  const validatedMessage = this.clientSchema.parse(message);
133
139
 
134
140
  if (this.enableBufferMessages) {
135
- // Encode as msgpack
141
+ // Encode as msgpack (ensure ArrayBufferView for WebSocket.send)
136
142
  const packed = pack(validatedMessage);
137
- this.ws.send(packed);
143
+ this.ws.send(new Uint8Array(packed));
138
144
  } else {
139
145
  // Encode as JSON
140
- this.ws.send(JSON.stringify(validatedMessage));
146
+ this.ws.send(this.serializeJson(validatedMessage));
141
147
  }
142
148
  } catch (error) {
143
149
  console.error("Failed to send message:", error);