@firtoz/websocket-do 10.0.0 → 13.0.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/README.md +68 -61
- package/dist/BaseSession.d.ts +41 -0
- package/dist/BaseSession.js +5 -0
- package/dist/BaseSession.js.map +1 -0
- package/dist/BaseWebSocketDO.d.ts +42 -0
- package/dist/BaseWebSocketDO.js +4 -0
- package/dist/BaseWebSocketDO.js.map +1 -0
- package/dist/StandardSchemaSession.d.ts +41 -0
- package/dist/StandardSchemaSession.js +8 -0
- package/dist/StandardSchemaSession.js.map +1 -0
- package/dist/StandardSchemaWebSocketClient.d.ts +45 -0
- package/dist/StandardSchemaWebSocketClient.js +5 -0
- package/dist/StandardSchemaWebSocketClient.js.map +1 -0
- package/dist/StandardSchemaWebSocketDO.d.ts +28 -0
- package/dist/StandardSchemaWebSocketDO.js +5 -0
- package/dist/StandardSchemaWebSocketDO.js.map +1 -0
- package/dist/WebsocketWrapper.d.ts +9 -0
- package/dist/WebsocketWrapper.js +4 -0
- package/dist/WebsocketWrapper.js.map +1 -0
- package/dist/chunk-3C77OSOD.js +54 -0
- package/dist/chunk-3C77OSOD.js.map +1 -0
- package/dist/chunk-3LWVEY3R.js +130 -0
- package/dist/chunk-3LWVEY3R.js.map +1 -0
- package/dist/chunk-53MFRNQS.js +153 -0
- package/dist/chunk-53MFRNQS.js.map +1 -0
- package/dist/chunk-CAX4POIL.js +13 -0
- package/dist/chunk-CAX4POIL.js.map +1 -0
- package/dist/chunk-KCPOB32E.js +20 -0
- package/dist/chunk-KCPOB32E.js.map +1 -0
- package/dist/chunk-NOUFNU2O.js +10 -0
- package/dist/chunk-NOUFNU2O.js.map +1 -0
- package/dist/chunk-QMGIRIHJ.js +18 -0
- package/dist/chunk-QMGIRIHJ.js.map +1 -0
- package/dist/chunk-ULGH6X42.js +23 -0
- package/dist/chunk-ULGH6X42.js.map +1 -0
- package/dist/chunk-WJIQBI6I.js +35 -0
- package/dist/chunk-WJIQBI6I.js.map +1 -0
- package/dist/chunk-XFB6C3NZ.js +134 -0
- package/dist/chunk-XFB6C3NZ.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/parseStandardSchema.d.ts +9 -0
- package/dist/parseStandardSchema.js +4 -0
- package/dist/parseStandardSchema.js.map +1 -0
- package/dist/standardSchemaMsgpack.d.ts +8 -0
- package/dist/standardSchemaMsgpack.js +5 -0
- package/dist/standardSchemaMsgpack.js.map +1 -0
- package/dist/standardSchemaRpc.d.ts +30 -0
- package/dist/standardSchemaRpc.js +6 -0
- package/dist/standardSchemaRpc.js.map +1 -0
- package/dist/standardSchemaRpcReact.d.ts +27 -0
- package/dist/standardSchemaRpcReact.js +54 -0
- package/dist/standardSchemaRpcReact.js.map +1 -0
- package/package.json +36 -18
- package/src/BaseSession.ts +15 -5
- package/src/{ZodSession.ts → StandardSchemaSession.ts} +71 -70
- package/src/{ZodWebSocketClient.ts → StandardSchemaWebSocketClient.ts} +30 -50
- package/src/{ZodWebSocketDO.ts → StandardSchemaWebSocketDO.ts} +29 -22
- package/src/index.ts +15 -12
- package/src/parseStandardSchema.ts +17 -0
- package/src/standardSchemaMsgpack.ts +17 -0
- package/src/standardSchemaRpc.ts +83 -0
- package/src/standardSchemaRpcReact.ts +107 -0
- package/src/zodMsgpack.ts +0 -13
package/README.md
CHANGED
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@firtoz/websocket-do)
|
|
5
5
|
[](https://github.com/firtoz/fullstack-toolkit/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://developers.cloudflare.com/durable-objects/)
|
|
9
|
+
[](https://hono.dev)
|
|
10
|
+
|
|
11
|
+
**WebSocket sessions on Durable Objects** — Standard Schema message validation, broadcasting, and Hono integration.
|
|
8
12
|
|
|
9
13
|
> **⚠️ Early WIP Notice:** This package is in very early development and is **not production-ready**. It is TypeScript-only and may have breaking changes. While I (the maintainer) have limited time, I'm open to PRs for features, bug fixes, or additional support (like JS builds). Please feel free to try it out and contribute! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|
|
10
14
|
|
|
11
15
|
## Features
|
|
12
16
|
|
|
13
17
|
- 🔒 **Type-safe** - Full TypeScript support with generic types for messages and session data
|
|
14
|
-
- ✨ **
|
|
18
|
+
- ✨ **Standard Schema validation** - Runtime message validation with `StandardSchemaWebSocketClient` and `StandardSchemaSession` (Zod, Valibot, ArkType, …)
|
|
15
19
|
- 🌐 **WebSocket Management** - Built on Cloudflare Durable Objects for stateful WebSocket connections
|
|
16
20
|
- 🎯 **Session-based** - Abstract session class for easy implementation of custom WebSocket logic
|
|
17
21
|
- 🔄 **State Persistence** - Automatic serialization/deserialization of session data
|
|
@@ -34,10 +38,7 @@ This package requires the following peer dependencies:
|
|
|
34
38
|
bun add hono @firtoz/hono-fetcher
|
|
35
39
|
```
|
|
36
40
|
|
|
37
|
-
**For
|
|
38
|
-
```bash
|
|
39
|
-
bun add zod msgpackr
|
|
40
|
-
```
|
|
41
|
+
**For schemas:** use any [Standard Schema v1](https://standardschema.dev/) library (e.g. Zod, Valibot). **`msgpackr`** is a normal dependency of this package (buffer / msgpack mode); you do not add it separately unless your bundler needs it hoisted.
|
|
41
42
|
|
|
42
43
|
For TypeScript support, use `wrangler types` to generate accurate types from your `wrangler.jsonc`:
|
|
43
44
|
|
|
@@ -101,8 +102,8 @@ const chatSessionHandlers: BaseSessionHandlers<
|
|
|
101
102
|
// Handle binary messages if needed
|
|
102
103
|
},
|
|
103
104
|
|
|
104
|
-
handleClose: async () => {
|
|
105
|
-
console.log('Session closed');
|
|
105
|
+
handleClose: async (session) => {
|
|
106
|
+
console.log('Session closed', session.data);
|
|
106
107
|
},
|
|
107
108
|
};
|
|
108
109
|
|
|
@@ -140,8 +141,8 @@ class ChatSession extends BaseSession<
|
|
|
140
141
|
handleBufferMessage: async (message) => {
|
|
141
142
|
// Handle binary messages if needed
|
|
142
143
|
},
|
|
143
|
-
handleClose: async () => {
|
|
144
|
-
console.log(`Session closed for user ${
|
|
144
|
+
handleClose: async (session) => {
|
|
145
|
+
console.log(`Session closed for user ${session.data.userId}`);
|
|
145
146
|
},
|
|
146
147
|
});
|
|
147
148
|
}
|
|
@@ -214,13 +215,13 @@ export default {
|
|
|
214
215
|
};
|
|
215
216
|
```
|
|
216
217
|
|
|
217
|
-
##
|
|
218
|
+
## StandardSchemaWebSocketClient (Type-Safe Client)
|
|
218
219
|
|
|
219
|
-
`
|
|
220
|
+
`StandardSchemaWebSocketClient` provides a type-safe WebSocket client with automatic validation (Standard Schema v1) for both incoming and outgoing messages.
|
|
220
221
|
|
|
221
222
|
### Features
|
|
222
223
|
|
|
223
|
-
- ✅ **Automatic validation** - All messages validated with
|
|
224
|
+
- ✅ **Automatic validation** - All messages validated with your Standard Schema–compatible schemas
|
|
224
225
|
- 🎯 **Full type inference** - TypeScript types automatically inferred from schemas
|
|
225
226
|
- 📦 **Dual mode** - Supports both JSON and msgpack (buffer) serialization
|
|
226
227
|
- 🔗 **DO Integration** - Works seamlessly with `honoDoFetcher` WebSocket connections
|
|
@@ -229,7 +230,7 @@ export default {
|
|
|
229
230
|
### Basic Usage
|
|
230
231
|
|
|
231
232
|
```typescript
|
|
232
|
-
import {
|
|
233
|
+
import { StandardSchemaWebSocketClient } from '@firtoz/websocket-do';
|
|
233
234
|
import { z } from 'zod';
|
|
234
235
|
|
|
235
236
|
// Define your message schemas
|
|
@@ -249,8 +250,8 @@ type ServerMessage = z.infer<typeof ServerMessageSchema>;
|
|
|
249
250
|
// Create WebSocket connection (regular or via honoDoFetcher)
|
|
250
251
|
const ws = new WebSocket('wss://example.com/chat');
|
|
251
252
|
|
|
252
|
-
// Wrap with
|
|
253
|
-
const client = new
|
|
253
|
+
// Wrap with StandardSchemaWebSocketClient
|
|
254
|
+
const client = new StandardSchemaWebSocketClient({
|
|
254
255
|
webSocket: ws, // Can also use 'url' instead
|
|
255
256
|
clientSchema: ClientMessageSchema,
|
|
256
257
|
serverSchema: ServerMessageSchema,
|
|
@@ -272,7 +273,7 @@ Perfect for connecting to Durable Objects:
|
|
|
272
273
|
|
|
273
274
|
```typescript
|
|
274
275
|
import { honoDoFetcherWithName } from '@firtoz/hono-fetcher';
|
|
275
|
-
import {
|
|
276
|
+
import { StandardSchemaWebSocketClient } from '@firtoz/websocket-do';
|
|
276
277
|
|
|
277
278
|
// 1. Connect to DO via honoDoFetcher
|
|
278
279
|
const api = honoDoFetcherWithName(env.CHAT_ROOM, 'room-1');
|
|
@@ -281,8 +282,8 @@ const wsResp = await api.websocket({
|
|
|
281
282
|
config: { autoAccept: false }, // Let client control acceptance
|
|
282
283
|
});
|
|
283
284
|
|
|
284
|
-
// 2. Wrap with
|
|
285
|
-
const client = new
|
|
285
|
+
// 2. Wrap with StandardSchemaWebSocketClient for type safety!
|
|
286
|
+
const client = new StandardSchemaWebSocketClient({
|
|
286
287
|
webSocket: wsResp.webSocket,
|
|
287
288
|
clientSchema: ClientMessageSchema,
|
|
288
289
|
serverSchema: ServerMessageSchema,
|
|
@@ -307,7 +308,7 @@ client.send({ type: 'chat', text: 'Hello from typed client!' });
|
|
|
307
308
|
For better performance and smaller payloads, use buffer mode with msgpack:
|
|
308
309
|
|
|
309
310
|
```typescript
|
|
310
|
-
const client = new
|
|
311
|
+
const client = new StandardSchemaWebSocketClient({
|
|
311
312
|
webSocket: ws,
|
|
312
313
|
clientSchema: ClientMessageSchema,
|
|
313
314
|
serverSchema: ServerMessageSchema,
|
|
@@ -327,14 +328,16 @@ client.send({ type: 'chat', text: 'Efficient binary message!' });
|
|
|
327
328
|
#### Constructor Options
|
|
328
329
|
|
|
329
330
|
```typescript
|
|
330
|
-
|
|
331
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
332
|
+
|
|
333
|
+
interface StandardSchemaWebSocketClientOptions<TClientMessage, TServerMessage> {
|
|
331
334
|
// Connection (provide one)
|
|
332
335
|
url?: string; // Create new WebSocket
|
|
333
336
|
webSocket?: WebSocket; // Use existing WebSocket (e.g., from honoDoFetcher)
|
|
334
337
|
|
|
335
338
|
// Schemas (required)
|
|
336
|
-
clientSchema:
|
|
337
|
-
serverSchema:
|
|
339
|
+
clientSchema: StandardSchemaV1<unknown, TClientMessage>;
|
|
340
|
+
serverSchema: StandardSchemaV1<unknown, TServerMessage>;
|
|
338
341
|
|
|
339
342
|
// Serialization
|
|
340
343
|
enableBufferMessages?: boolean; // Use msgpack instead of JSON (default: false)
|
|
@@ -350,18 +353,18 @@ interface ZodWebSocketClientOptions<TClientMessage, TServerMessage> {
|
|
|
350
353
|
|
|
351
354
|
#### Methods
|
|
352
355
|
|
|
353
|
-
- `send(message: TClientMessage): void
|
|
356
|
+
- `send(message: TClientMessage): Promise<void>` - Send a validated message (async Standard Schema validation)
|
|
354
357
|
- `close(code?: number, reason?: string): void` - Close the connection
|
|
355
358
|
- `waitForOpen(): Promise<void>` - Wait for connection to open
|
|
356
359
|
|
|
357
|
-
##
|
|
360
|
+
## StandardSchemaSession (Validated Sessions)
|
|
358
361
|
|
|
359
|
-
`
|
|
362
|
+
`StandardSchemaSession` extends `BaseSession` with automatic validation for incoming messages.
|
|
360
363
|
|
|
361
364
|
### Basic Usage
|
|
362
365
|
|
|
363
366
|
```typescript
|
|
364
|
-
import {
|
|
367
|
+
import { StandardSchemaSession } from '@firtoz/websocket-do';
|
|
365
368
|
import { z } from 'zod';
|
|
366
369
|
|
|
367
370
|
// Define schemas
|
|
@@ -384,7 +387,7 @@ interface SessionData {
|
|
|
384
387
|
}
|
|
385
388
|
|
|
386
389
|
// Implement validated session
|
|
387
|
-
class ChatSession extends
|
|
390
|
+
class ChatSession extends StandardSchemaSession<
|
|
388
391
|
SessionData,
|
|
389
392
|
ServerMessage,
|
|
390
393
|
ClientMessage,
|
|
@@ -393,7 +396,7 @@ class ChatSession extends ZodSession<
|
|
|
393
396
|
constructor(
|
|
394
397
|
websocket: WebSocket,
|
|
395
398
|
sessions: Map<WebSocket, ChatSession>,
|
|
396
|
-
options:
|
|
399
|
+
options: StandardSchemaSessionOptions<ClientMessage, ServerMessage>
|
|
397
400
|
) {
|
|
398
401
|
super(websocket, sessions, options, {
|
|
399
402
|
createData: (ctx) => ({ name: 'Anonymous' }),
|
|
@@ -417,18 +420,18 @@ class ChatSession extends ZodSession<
|
|
|
417
420
|
}
|
|
418
421
|
},
|
|
419
422
|
|
|
420
|
-
handleClose: async () => {
|
|
421
|
-
console.log(`${
|
|
423
|
+
handleClose: async (session) => {
|
|
424
|
+
console.log(`${session.data.name} disconnected`);
|
|
422
425
|
},
|
|
423
426
|
});
|
|
424
427
|
}
|
|
425
428
|
}
|
|
426
429
|
```
|
|
427
430
|
|
|
428
|
-
### Buffer Mode with
|
|
431
|
+
### Buffer Mode with StandardSchemaSession
|
|
429
432
|
|
|
430
433
|
```typescript
|
|
431
|
-
class ChatSession extends
|
|
434
|
+
class ChatSession extends StandardSchemaSession<...> {
|
|
432
435
|
constructor(
|
|
433
436
|
websocket: WebSocket,
|
|
434
437
|
sessions: Map<WebSocket, ChatSession>
|
|
@@ -443,8 +446,8 @@ class ChatSession extends ZodSession<...> {
|
|
|
443
446
|
// Messages automatically decoded from msgpack
|
|
444
447
|
// Handle validated message
|
|
445
448
|
},
|
|
446
|
-
handleClose: async () => {
|
|
447
|
-
console.log('Session closed');
|
|
449
|
+
handleClose: async (session) => {
|
|
450
|
+
console.log('Session closed', session.data);
|
|
448
451
|
},
|
|
449
452
|
});
|
|
450
453
|
}
|
|
@@ -521,13 +524,17 @@ constructor(
|
|
|
521
524
|
|
|
522
525
|
```typescript
|
|
523
526
|
type BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv> = {
|
|
524
|
-
createData
|
|
527
|
+
createData?: (ctx: Context<{ Bindings: TEnv }>) => TData;
|
|
525
528
|
handleMessage: (message: TClientMessage) => Promise<void>;
|
|
526
529
|
handleBufferMessage: (message: ArrayBuffer) => Promise<void>;
|
|
527
|
-
handleClose: (
|
|
530
|
+
handleClose: (
|
|
531
|
+
session: BaseSession<TData, TServerMessage, TClientMessage, TEnv>,
|
|
532
|
+
) => Promise<void>;
|
|
528
533
|
};
|
|
529
534
|
```
|
|
530
535
|
|
|
536
|
+
If `createData` is omitted, `startFresh` sets `data` to `{}` (use an empty `TData` such as `Record<string, never>`).
|
|
537
|
+
|
|
531
538
|
#### Methods
|
|
532
539
|
|
|
533
540
|
- `send(message: TServerMessage): void`
|
|
@@ -537,7 +544,7 @@ type BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv> = {
|
|
|
537
544
|
- Send message to all connected sessions
|
|
538
545
|
|
|
539
546
|
- `startFresh(ctx: Context): void`
|
|
540
|
-
- Initialize new session (called automatically)
|
|
547
|
+
- Initialize new session (called automatically). Uses `createData` when provided; otherwise `{}`.
|
|
541
548
|
|
|
542
549
|
- `resume(): void`
|
|
543
550
|
- Resume existing session after hibernation (called automatically)
|
|
@@ -635,8 +642,8 @@ class GameSession extends BaseSession<GameData, ServerMsg, ClientMsg, Env> {
|
|
|
635
642
|
// Handle buffer messages if needed
|
|
636
643
|
},
|
|
637
644
|
|
|
638
|
-
handleClose: async () => {
|
|
639
|
-
console.log('Game session closed');
|
|
645
|
+
handleClose: async (session) => {
|
|
646
|
+
console.log('Game session closed', session.data);
|
|
640
647
|
},
|
|
641
648
|
});
|
|
642
649
|
}
|
|
@@ -678,8 +685,8 @@ class MySession extends BaseSession<...> {
|
|
|
678
685
|
// Handle buffer messages
|
|
679
686
|
},
|
|
680
687
|
|
|
681
|
-
handleClose: async () => {
|
|
682
|
-
console.log('Session closed');
|
|
688
|
+
handleClose: async (session) => {
|
|
689
|
+
console.log('Session closed', session.data);
|
|
683
690
|
},
|
|
684
691
|
});
|
|
685
692
|
}
|
|
@@ -693,22 +700,22 @@ This package exports the following:
|
|
|
693
700
|
### Classes
|
|
694
701
|
- `BaseWebSocketDO` - Base class for WebSocket Durable Objects (composition-based)
|
|
695
702
|
- `BaseSession` - Concrete session class with handler injection
|
|
696
|
-
- `
|
|
697
|
-
- `
|
|
698
|
-
- `
|
|
703
|
+
- `StandardSchemaWebSocketClient` - Type-safe WebSocket client with Standard Schema validation
|
|
704
|
+
- `StandardSchemaSession` - Concrete session class with validation and handler injection
|
|
705
|
+
- `StandardSchemaWebSocketDO` - Base class for WebSocket DOs with Standard Schema sessions
|
|
699
706
|
- `WebsocketWrapper` - Low-level WebSocket wrapper with typed attachments
|
|
700
707
|
|
|
701
708
|
### Types
|
|
702
709
|
- `BaseSessionHandlers` - Handler interface for `BaseSession`
|
|
703
710
|
- `BaseWebSocketDOOptions` - Options interface for `BaseWebSocketDO`
|
|
704
|
-
- `
|
|
705
|
-
- `
|
|
706
|
-
- `
|
|
707
|
-
- `
|
|
708
|
-
- `
|
|
711
|
+
- `StandardSchemaSessionHandlers` - Handler interface for `StandardSchemaSession`
|
|
712
|
+
- `StandardSchemaSessionOptions` - Options interface for `StandardSchemaSession`
|
|
713
|
+
- `StandardSchemaSessionOptionsOrFactory` - Options or factory function for `StandardSchemaSession`
|
|
714
|
+
- `StandardSchemaWebSocketDOOptions` - Options interface for `StandardSchemaWebSocketDO`
|
|
715
|
+
- `StandardSchemaWebSocketClientOptions` - Options interface for `StandardSchemaWebSocketClient`
|
|
709
716
|
|
|
710
717
|
### Utilities
|
|
711
|
-
- `
|
|
718
|
+
- `standardSchemaMsgpack` - Msgpack encode/decode with Standard Schema validation
|
|
712
719
|
|
|
713
720
|
## Complete Example
|
|
714
721
|
|
|
@@ -734,7 +741,7 @@ export type ClientMessage = z.infer<typeof ClientMessageSchema>;
|
|
|
734
741
|
export type ServerMessage = z.infer<typeof ServerMessageSchema>;
|
|
735
742
|
|
|
736
743
|
// do.ts - Server-side (Durable Object)
|
|
737
|
-
import { BaseWebSocketDO,
|
|
744
|
+
import { BaseWebSocketDO, StandardSchemaSession, type StandardSchemaSessionOptions } from '@firtoz/websocket-do';
|
|
738
745
|
import { ClientMessageSchema, ServerMessageSchema } from './schemas';
|
|
739
746
|
|
|
740
747
|
interface SessionData {
|
|
@@ -742,11 +749,11 @@ interface SessionData {
|
|
|
742
749
|
joinedAt: number;
|
|
743
750
|
}
|
|
744
751
|
|
|
745
|
-
class ChatSession extends
|
|
752
|
+
class ChatSession extends StandardSchemaSession<SessionData, ServerMessage, ClientMessage, Env> {
|
|
746
753
|
constructor(
|
|
747
754
|
websocket: WebSocket,
|
|
748
755
|
sessions: Map<WebSocket, ChatSession>,
|
|
749
|
-
options:
|
|
756
|
+
options: StandardSchemaSessionOptions<ClientMessage, ServerMessage>
|
|
750
757
|
) {
|
|
751
758
|
super(websocket, sessions, options, {
|
|
752
759
|
createData: () => ({
|
|
@@ -775,8 +782,8 @@ class ChatSession extends ZodSession<SessionData, ServerMessage, ClientMessage,
|
|
|
775
782
|
}
|
|
776
783
|
},
|
|
777
784
|
|
|
778
|
-
handleClose: async () => {
|
|
779
|
-
console.log(`${
|
|
785
|
+
handleClose: async (session) => {
|
|
786
|
+
console.log(`${session.data.name} disconnected`);
|
|
780
787
|
},
|
|
781
788
|
});
|
|
782
789
|
}
|
|
@@ -806,7 +813,7 @@ export class ChatRoomDO extends BaseWebSocketDO<ChatSession, Env> {
|
|
|
806
813
|
}
|
|
807
814
|
|
|
808
815
|
// client.ts - Client-side
|
|
809
|
-
import {
|
|
816
|
+
import { StandardSchemaWebSocketClient } from '@firtoz/websocket-do';
|
|
810
817
|
import { honoDoFetcherWithName } from '@firtoz/hono-fetcher';
|
|
811
818
|
import { ClientMessageSchema, ServerMessageSchema } from './schemas';
|
|
812
819
|
|
|
@@ -818,8 +825,8 @@ async function connectToChat(env: Env, roomName: string) {
|
|
|
818
825
|
config: { autoAccept: false },
|
|
819
826
|
});
|
|
820
827
|
|
|
821
|
-
// 2. Wrap with
|
|
822
|
-
const client = new
|
|
828
|
+
// 2. Wrap with StandardSchemaWebSocketClient
|
|
829
|
+
const client = new StandardSchemaWebSocketClient({
|
|
823
830
|
webSocket: wsResp.webSocket,
|
|
824
831
|
clientSchema: ClientMessageSchema,
|
|
825
832
|
serverSchema: ServerMessageSchema,
|
|
@@ -868,8 +875,8 @@ This package includes comprehensive integration tests in a separate test package
|
|
|
868
875
|
- ✅ Real-time WebSocket message exchange
|
|
869
876
|
- ✅ WebSocket session management
|
|
870
877
|
- ✅ Type-safe DO client integration
|
|
871
|
-
- ✅
|
|
872
|
-
- ✅ Integration between honoDoFetcher and
|
|
878
|
+
- ✅ Standard Schema validation in both JSON and msgpack modes
|
|
879
|
+
- ✅ Integration between honoDoFetcher and StandardSchemaWebSocketClient
|
|
873
880
|
|
|
874
881
|
For detailed information about testing capabilities, example implementations, comprehensive test coverage, and setup instructions, see the [websocket-do-test](../../tests/websocket-do-test/) package.
|
|
875
882
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
|
|
3
|
+
type SessionData<TSession extends BaseSession<any, any, any, any>> = TSession extends BaseSession<infer TData, infer _TServerMessage, infer _TClientMessage, infer _TEnv> ? TData : never;
|
|
4
|
+
type SessionClientMessage<TSession extends BaseSession<any, any, any, any>> = TSession extends BaseSession<infer _TData, infer _TServerMessage, infer TClientMessage, infer _TEnv> ? TClientMessage : never;
|
|
5
|
+
type SessionServerMessage<TSession extends BaseSession<any, any, any, any>> = TSession extends BaseSession<infer _TData, infer TServerMessage, infer _TClientMessage, infer _TEnv> ? TServerMessage : never;
|
|
6
|
+
type SessionEnv<TSession extends BaseSession<any, any, any, any>> = TSession extends BaseSession<infer _TData, infer _TServerMessage, infer _TClientMessage, infer TEnv extends Cloudflare.Env> ? TEnv : never;
|
|
7
|
+
type BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv extends object = Cloudflare.Env> = {
|
|
8
|
+
/**
|
|
9
|
+
* Per-connection state. If omitted, `startFresh` initializes `data` as `{}`
|
|
10
|
+
* (use empty `TData`, e.g. `Record<string, never>`).
|
|
11
|
+
*/
|
|
12
|
+
createData?: (ctx: Context<{
|
|
13
|
+
Bindings: TEnv;
|
|
14
|
+
}>) => TData;
|
|
15
|
+
handleMessage: (message: TClientMessage) => Promise<void>;
|
|
16
|
+
handleBufferMessage: (message: ArrayBuffer) => Promise<void>;
|
|
17
|
+
/** Called when this connection closes; `session` is this {@link BaseSession} instance. */
|
|
18
|
+
handleClose: (session: BaseSession<TData, TServerMessage, TClientMessage, TEnv>) => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
declare class BaseSession<TData, TServerMessage, TClientMessage, TEnv extends object = Cloudflare.Env> {
|
|
21
|
+
websocket: WebSocket;
|
|
22
|
+
protected sessions: Map<WebSocket, BaseSession<TData, TServerMessage, TClientMessage, TEnv>>;
|
|
23
|
+
private _data;
|
|
24
|
+
get data(): TData;
|
|
25
|
+
private set data(value);
|
|
26
|
+
private readonly wrapper;
|
|
27
|
+
protected readonly handlers: BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv>;
|
|
28
|
+
constructor(websocket: WebSocket, sessions: Map<WebSocket, BaseSession<TData, TServerMessage, TClientMessage, TEnv>>, handlers: BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv>);
|
|
29
|
+
startFresh(ctx: Context<{
|
|
30
|
+
Bindings: TEnv;
|
|
31
|
+
}>): void;
|
|
32
|
+
resume(): void;
|
|
33
|
+
update(): void;
|
|
34
|
+
broadcast(message: TServerMessage, excludeSelf?: boolean): void;
|
|
35
|
+
send(message: TServerMessage): void;
|
|
36
|
+
handleMessage(message: TClientMessage): Promise<void>;
|
|
37
|
+
handleBufferMessage(message: ArrayBuffer): Promise<void>;
|
|
38
|
+
handleClose(): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { BaseSession, type BaseSessionHandlers, type SessionClientMessage, type SessionData, type SessionEnv, type SessionServerMessage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"BaseSession.js"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as hono_hono_base from 'hono/hono-base';
|
|
2
|
+
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
3
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
4
|
+
import { DOWithHonoApp } from '@firtoz/hono-fetcher/honoDoFetcher';
|
|
5
|
+
import { Context, Hono } from 'hono';
|
|
6
|
+
import { BaseSession, SessionEnv } from './BaseSession.js';
|
|
7
|
+
|
|
8
|
+
type BaseWebSocketDOOptions<TSession extends BaseSession<any, any, any, any>, TEnv extends SessionEnv<TSession>> = {
|
|
9
|
+
createSession: (ctx: Context<{
|
|
10
|
+
Bindings: TEnv;
|
|
11
|
+
}> | undefined, websocket: WebSocket) => TSession | Promise<TSession>;
|
|
12
|
+
};
|
|
13
|
+
declare abstract class BaseWebSocketDO<TSession extends BaseSession<any, any, any, any> = BaseSession<any, any, any, any>, TEnv extends SessionEnv<TSession> = SessionEnv<TSession>> extends DurableObject<TEnv> implements DOWithHonoApp {
|
|
14
|
+
#private;
|
|
15
|
+
private readonly options;
|
|
16
|
+
protected readonly sessions: Map<WebSocket, TSession>;
|
|
17
|
+
abstract readonly app: Hono<{
|
|
18
|
+
Bindings: TEnv;
|
|
19
|
+
}>;
|
|
20
|
+
constructor(ctx: DurableObjectState, env: TEnv, options: BaseWebSocketDOOptions<TSession, TEnv>);
|
|
21
|
+
protected getBaseApp(): hono_hono_base.HonoBase<{
|
|
22
|
+
Bindings: TEnv;
|
|
23
|
+
}, {
|
|
24
|
+
"/websocket": {
|
|
25
|
+
$get: {
|
|
26
|
+
input: {};
|
|
27
|
+
output: {};
|
|
28
|
+
outputFormat: string;
|
|
29
|
+
status: hono_utils_http_status.StatusCode;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}, "/", "/websocket">;
|
|
33
|
+
handleSession(ctx: Context<{
|
|
34
|
+
Bindings: TEnv;
|
|
35
|
+
}>, ws: WebSocket): Promise<void>;
|
|
36
|
+
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
|
|
37
|
+
webSocketClose(ws: WebSocket, _code: number, _reason: string, _wasClean: boolean): Promise<void>;
|
|
38
|
+
webSocketError(ws: WebSocket, error: unknown): Promise<void>;
|
|
39
|
+
fetch(request: Request): Response | Promise<Response>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { BaseWebSocketDO, type BaseWebSocketDOOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"BaseWebSocketDO.js"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
import { BaseSession } from './BaseSession.js';
|
|
4
|
+
|
|
5
|
+
type StandardSchemaSessionOptions<TClientMessage, TServerMessage> = {
|
|
6
|
+
clientSchema: StandardSchemaV1<unknown, TClientMessage>;
|
|
7
|
+
serverSchema: StandardSchemaV1<unknown, TServerMessage>;
|
|
8
|
+
serializeJson?: (value: unknown) => string;
|
|
9
|
+
deserializeJson?: (raw: string) => unknown;
|
|
10
|
+
enableBufferMessages?: boolean;
|
|
11
|
+
sendProtocolError?: (websocket: WebSocket, errorMessage: string) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
type StandardSchemaSessionHandlers<TData, TServerMessage, TClientMessage, TEnv extends object> = {
|
|
14
|
+
createData: (ctx: Context<{
|
|
15
|
+
Bindings: TEnv;
|
|
16
|
+
}>) => TData;
|
|
17
|
+
handleValidatedMessage: (message: TClientMessage) => Promise<void>;
|
|
18
|
+
handleValidationError?: (error: unknown, originalMessage: unknown) => Promise<void>;
|
|
19
|
+
handleClose: (session: StandardSchemaSession<TData, TServerMessage, TClientMessage, TEnv>) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
declare class StandardSchemaSession<TData, TServerMessage, TClientMessage, TEnv extends object = Cloudflare.Env> extends BaseSession<TData, TServerMessage, TClientMessage, TEnv> {
|
|
22
|
+
private readonly options;
|
|
23
|
+
private readonly schemaHandlers;
|
|
24
|
+
private readonly clientCodec;
|
|
25
|
+
private readonly serverCodec;
|
|
26
|
+
protected readonly enableBufferMessages: boolean;
|
|
27
|
+
constructor(websocket: WebSocket, sessions: Map<WebSocket, StandardSchemaSession<TData, TServerMessage, TClientMessage, TEnv>>, options: StandardSchemaSessionOptions<TClientMessage, TServerMessage>, schemaHandlers: StandardSchemaSessionHandlers<TData, TServerMessage, TClientMessage, TEnv>);
|
|
28
|
+
handleRawMessage(rawMessage: string): Promise<void>;
|
|
29
|
+
private _internalHandleMessage;
|
|
30
|
+
private _internalHandleBufferMessage;
|
|
31
|
+
private _internalHandleValidationError;
|
|
32
|
+
send(message: TServerMessage): void;
|
|
33
|
+
private sendJsonAsync;
|
|
34
|
+
private sendBufferAsync;
|
|
35
|
+
private sendProtocolError;
|
|
36
|
+
private serializeJson;
|
|
37
|
+
private deserializeJson;
|
|
38
|
+
broadcast(message: TServerMessage, excludeSelf?: boolean): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { StandardSchemaSession, type StandardSchemaSessionHandlers, type StandardSchemaSessionOptions };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { StandardSchemaSession } from './chunk-53MFRNQS.js';
|
|
2
|
+
import './chunk-QMGIRIHJ.js';
|
|
3
|
+
import './chunk-3C77OSOD.js';
|
|
4
|
+
import './chunk-KCPOB32E.js';
|
|
5
|
+
import './chunk-CAX4POIL.js';
|
|
6
|
+
import './chunk-NOUFNU2O.js';
|
|
7
|
+
//# sourceMappingURL=StandardSchemaSession.js.map
|
|
8
|
+
//# sourceMappingURL=StandardSchemaSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"StandardSchemaSession.js"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
|
|
3
|
+
interface StandardSchemaWebSocketClientOptions<TClientMessage, TServerMessage> {
|
|
4
|
+
/**
|
|
5
|
+
* URL to connect to (required if webSocket not provided)
|
|
6
|
+
*/
|
|
7
|
+
url?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Existing WebSocket to wrap (alternative to url)
|
|
10
|
+
* Useful when getting a WebSocket from honoDoFetcher
|
|
11
|
+
*/
|
|
12
|
+
webSocket?: WebSocket;
|
|
13
|
+
clientSchema: StandardSchemaV1<unknown, TClientMessage>;
|
|
14
|
+
serverSchema: StandardSchemaV1<unknown, TServerMessage>;
|
|
15
|
+
serializeJson?: (value: unknown) => string;
|
|
16
|
+
deserializeJson?: (raw: string) => unknown;
|
|
17
|
+
enableBufferMessages?: boolean;
|
|
18
|
+
onMessage?: (message: TServerMessage) => void;
|
|
19
|
+
onOpen?: (event: Event) => void;
|
|
20
|
+
onClose?: (event: CloseEvent) => void;
|
|
21
|
+
onError?: (event: Event) => void;
|
|
22
|
+
onValidationError?: (error: Error, rawMessage: unknown) => void;
|
|
23
|
+
}
|
|
24
|
+
declare class StandardSchemaWebSocketClient<TClientMessage, TServerMessage> {
|
|
25
|
+
private ws;
|
|
26
|
+
private readonly clientSchema;
|
|
27
|
+
private readonly serverSchema;
|
|
28
|
+
private readonly serializeJson;
|
|
29
|
+
private readonly deserializeJson;
|
|
30
|
+
private readonly enableBufferMessages;
|
|
31
|
+
private readonly onMessageCallback?;
|
|
32
|
+
private readonly onValidationError?;
|
|
33
|
+
constructor(options: StandardSchemaWebSocketClientOptions<TClientMessage, TServerMessage>);
|
|
34
|
+
private handleMessageEvent;
|
|
35
|
+
/**
|
|
36
|
+
* Send a message (automatically encodes based on mode).
|
|
37
|
+
*/
|
|
38
|
+
send(message: TClientMessage): Promise<void>;
|
|
39
|
+
close(code?: number, reason?: string): void;
|
|
40
|
+
get readyState(): number;
|
|
41
|
+
get socket(): WebSocket;
|
|
42
|
+
waitForOpen(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { StandardSchemaWebSocketClient, type StandardSchemaWebSocketClientOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"StandardSchemaWebSocketClient.js"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
import { SessionEnv, SessionClientMessage, SessionServerMessage } from './BaseSession.js';
|
|
3
|
+
import { BaseWebSocketDO } from './BaseWebSocketDO.js';
|
|
4
|
+
import { StandardSchemaSessionOptions, StandardSchemaSession } from './StandardSchemaSession.js';
|
|
5
|
+
import 'hono/hono-base';
|
|
6
|
+
import 'hono/utils/http-status';
|
|
7
|
+
import 'cloudflare:workers';
|
|
8
|
+
import '@firtoz/hono-fetcher/honoDoFetcher';
|
|
9
|
+
import '@standard-schema/spec';
|
|
10
|
+
|
|
11
|
+
type StandardSchemaSessionOptionsOrFactory<TClientMessage, TServerMessage, TEnv extends Cloudflare.Env = Cloudflare.Env> = StandardSchemaSessionOptions<TClientMessage, TServerMessage> | ((ctx: Context<{
|
|
12
|
+
Bindings: TEnv;
|
|
13
|
+
}> | undefined, websocket: WebSocket) => StandardSchemaSessionOptions<TClientMessage, TServerMessage>);
|
|
14
|
+
type StandardSchemaWebSocketDOOptions<TSession extends StandardSchemaSession<any, any, any, any>, TClientMessage, TServerMessage, TEnv extends SessionEnv<TSession>> = {
|
|
15
|
+
standardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<TClientMessage, TServerMessage, TEnv>;
|
|
16
|
+
createStandardSchemaSession: (ctx: Context<{
|
|
17
|
+
Bindings: TEnv;
|
|
18
|
+
}> | undefined, websocket: WebSocket, options: StandardSchemaSessionOptions<TClientMessage, TServerMessage>) => TSession | Promise<TSession>;
|
|
19
|
+
};
|
|
20
|
+
declare abstract class StandardSchemaWebSocketDO<TSession extends StandardSchemaSession<any, any, any, any>, TClientMessage extends SessionClientMessage<TSession> = SessionClientMessage<TSession>, TServerMessage extends SessionServerMessage<TSession> = SessionServerMessage<TSession>, TEnv extends SessionEnv<TSession> = SessionEnv<TSession>> extends BaseWebSocketDO<TSession, TEnv> {
|
|
21
|
+
protected readonly standardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<TClientMessage, TServerMessage, TEnv>;
|
|
22
|
+
protected readonly createStandardSchemaSessionFn: (ctx: Context<{
|
|
23
|
+
Bindings: TEnv;
|
|
24
|
+
}> | undefined, websocket: WebSocket, options: StandardSchemaSessionOptions<TClientMessage, TServerMessage>) => TSession | Promise<TSession>;
|
|
25
|
+
constructor(ctx: DurableObjectState, env: TEnv, options: StandardSchemaWebSocketDOOptions<TSession, TClientMessage, TServerMessage, TEnv>);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { type StandardSchemaSessionOptionsOrFactory, StandardSchemaWebSocketDO, type StandardSchemaWebSocketDOOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"StandardSchemaWebSocketDO.js"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
declare class WebsocketWrapper<TAttachment, TMessage> {
|
|
2
|
+
webSocket: WebSocket;
|
|
3
|
+
constructor(webSocket: WebSocket);
|
|
4
|
+
send(message: TMessage): void;
|
|
5
|
+
deserializeAttachment(): TAttachment;
|
|
6
|
+
serializeAttachment(attachment: TAttachment): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { WebsocketWrapper };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"WebsocketWrapper.js"}
|