@firtoz/websocket-do 6.0.2 → 7.0.1

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 CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  Type-safe WebSocket session management for Cloudflare Durable Objects with Hono integration.
8
8
 
9
+ > **⚠️ 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
+
9
11
  ## Features
10
12
 
11
13
  - 🔒 **Type-safe** - Full TypeScript support with generic types for messages and session data
@@ -67,44 +69,81 @@ interface SessionData {
67
69
  ### 2. Implement Your Session
68
70
 
69
71
  ```typescript
70
- import { BaseSession } from '@firtoz/websocket-do';
72
+ import { BaseSession, type BaseSessionHandlers } from '@firtoz/websocket-do';
71
73
  import type { Context } from 'hono';
72
74
 
73
- class ChatSession extends BaseSession<
74
- Env,
75
+ // Define handlers for your session
76
+ const chatSessionHandlers: BaseSessionHandlers<
75
77
  SessionData,
76
78
  ServerMessage,
77
- ClientMessage
78
- > {
79
- protected createData(ctx: Context<{ Bindings: Env }>): SessionData {
80
- return {
81
- userId: crypto.randomUUID(),
82
- joinedAt: Date.now(),
83
- };
84
- }
85
-
86
- async handleMessage(message: ClientMessage): Promise<void> {
79
+ ClientMessage,
80
+ Env
81
+ > = {
82
+ createData: (ctx: Context<{ Bindings: Env }>) => ({
83
+ userId: crypto.randomUUID(),
84
+ joinedAt: Date.now(),
85
+ }),
86
+
87
+ handleMessage: async (message: ClientMessage) => {
88
+ // 'this' context will be the session instance
87
89
  switch (message.type) {
88
90
  case 'chat':
89
- // Broadcast to all sessions
90
- this.broadcast({
91
- type: 'chat',
92
- message: message.message,
93
- from: this.data.userId,
94
- });
91
+ // Access session via closure or bind
92
+ // Note: handlers receive session context when called
95
93
  break;
96
94
  case 'ping':
97
- this.send({ type: 'welcome', userId: this.data.userId });
95
+ // Send messages
98
96
  break;
99
97
  }
100
- }
98
+ },
101
99
 
102
- async handleBufferMessage(message: ArrayBuffer): Promise<void> {
100
+ handleBufferMessage: async (message: ArrayBuffer) => {
103
101
  // Handle binary messages if needed
104
- }
102
+ },
105
103
 
106
- async handleClose(): Promise<void> {
107
- console.log(`Session closed for user ${this.data.userId}`);
104
+ handleClose: async () => {
105
+ console.log('Session closed');
106
+ },
107
+ };
108
+
109
+ // Create session class (can be extended if needed)
110
+ class ChatSession extends BaseSession<
111
+ SessionData,
112
+ ServerMessage,
113
+ ClientMessage,
114
+ Env
115
+ > {
116
+ constructor(
117
+ websocket: WebSocket,
118
+ sessions: Map<WebSocket, ChatSession>
119
+ ) {
120
+ super(websocket, sessions, {
121
+ createData: (ctx) => ({
122
+ userId: crypto.randomUUID(),
123
+ joinedAt: Date.now(),
124
+ }),
125
+ handleMessage: async (message) => {
126
+ switch (message.type) {
127
+ case 'chat':
128
+ // Broadcast to all sessions
129
+ this.broadcast({
130
+ type: 'chat',
131
+ message: message.message,
132
+ from: this.data.userId,
133
+ });
134
+ break;
135
+ case 'ping':
136
+ this.send({ type: 'welcome', userId: this.data.userId });
137
+ break;
138
+ }
139
+ },
140
+ handleBufferMessage: async (message) => {
141
+ // Handle binary messages if needed
142
+ },
143
+ handleClose: async () => {
144
+ console.log(`Session closed for user ${this.data.userId}`);
145
+ },
146
+ });
108
147
  }
109
148
  }
110
149
  ```
@@ -115,7 +154,7 @@ class ChatSession extends BaseSession<
115
154
  import { BaseWebSocketDO } from '@firtoz/websocket-do';
116
155
  import { Hono } from 'hono';
117
156
 
118
- export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
157
+ export class ChatRoomDO extends BaseWebSocketDO<ChatSession, Env> {
119
158
  app = this.getBaseApp()
120
159
  .get('/info', (ctx) => {
121
160
  return ctx.json({
@@ -123,8 +162,12 @@ export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
123
162
  });
124
163
  });
125
164
 
126
- protected createSession(websocket: WebSocket): ChatSession {
127
- return new ChatSession(websocket, this.sessions);
165
+ constructor(ctx: DurableObjectState, env: Env) {
166
+ super(ctx, env, {
167
+ createSession: (ctx, websocket) => {
168
+ return new ChatSession(websocket, this.sessions);
169
+ },
170
+ });
128
171
  }
129
172
  }
130
173
  ```
@@ -342,39 +385,42 @@ interface SessionData {
342
385
 
343
386
  // Implement validated session
344
387
  class ChatSession extends ZodSession<
345
- Env,
346
388
  SessionData,
347
389
  ServerMessage,
348
- ClientMessage
390
+ ClientMessage,
391
+ Env
349
392
  > {
350
- protected clientSchema = ClientMessageSchema;
351
- protected serverSchema = ServerMessageSchema;
352
-
353
- protected createData(ctx: Context<{ Bindings: Env }>): SessionData {
354
- return { name: 'Anonymous' };
355
- }
356
-
357
- async handleMessage(message: ClientMessage): Promise<void> {
358
- // Message is already validated!
359
- switch (message.type) {
360
- case 'setName':
361
- this.data.name = message.name;
362
- this.update();
363
- this.send({ type: 'nameChanged', newName: message.name });
364
- break;
393
+ constructor(
394
+ websocket: WebSocket,
395
+ sessions: Map<WebSocket, ChatSession>,
396
+ options: ZodSessionOptions<ClientMessage, ServerMessage>
397
+ ) {
398
+ super(websocket, sessions, options, {
399
+ createData: (ctx) => ({ name: 'Anonymous' }),
365
400
 
366
- case 'message':
367
- this.broadcast({
368
- type: 'message',
369
- text: message.text,
370
- from: this.data.name,
371
- });
372
- break;
373
- }
374
- }
375
-
376
- async handleClose(): Promise<void> {
377
- console.log(`${this.data.name} disconnected`);
401
+ handleValidatedMessage: async (message) => {
402
+ // Message is already validated!
403
+ switch (message.type) {
404
+ case 'setName':
405
+ this.data.name = message.name;
406
+ this.update();
407
+ this.send({ type: 'nameChanged', newName: message.name });
408
+ break;
409
+
410
+ case 'message':
411
+ this.broadcast({
412
+ type: 'message',
413
+ text: message.text,
414
+ from: this.data.name,
415
+ });
416
+ break;
417
+ }
418
+ },
419
+
420
+ handleClose: async () => {
421
+ console.log(`${this.data.name} disconnected`);
422
+ },
423
+ });
378
424
  }
379
425
  }
380
426
  ```
@@ -383,34 +429,61 @@ class ChatSession extends ZodSession<
383
429
 
384
430
  ```typescript
385
431
  class ChatSession extends ZodSession<...> {
386
- // Enable buffer mode for msgpack
387
- protected enableBufferMessages = true;
388
-
389
- protected clientSchema = ClientMessageSchema;
390
- protected serverSchema = ServerMessageSchema;
391
-
392
- // Messages automatically decoded from msgpack
393
- async handleMessage(message: ClientMessage): Promise<void> {
394
- // Handle validated message
432
+ constructor(
433
+ websocket: WebSocket,
434
+ sessions: Map<WebSocket, ChatSession>
435
+ ) {
436
+ super(websocket, sessions, {
437
+ clientSchema: ClientMessageSchema,
438
+ serverSchema: ServerMessageSchema,
439
+ enableBufferMessages: true, // Enable buffer mode for msgpack
440
+ }, {
441
+ createData: (ctx) => ({ name: 'Anonymous' }),
442
+ handleValidatedMessage: async (message) => {
443
+ // Messages automatically decoded from msgpack
444
+ // Handle validated message
445
+ },
446
+ handleClose: async () => {
447
+ console.log('Session closed');
448
+ },
449
+ });
395
450
  }
396
451
  }
397
452
  ```
398
453
 
399
454
  ## API Reference
400
455
 
401
- ### `BaseWebSocketDO<TEnv, TSession>`
456
+ ### `BaseWebSocketDO<TSession, TEnv>`
402
457
 
403
- Abstract class for creating WebSocket-enabled Durable Objects.
458
+ Base class for creating WebSocket-enabled Durable Objects. Uses composition instead of inheritance.
404
459
 
405
460
  #### Type Parameters
406
461
 
407
- - `TEnv` - Your Cloudflare Worker environment bindings
408
462
  - `TSession` - Your session class extending `BaseSession`
463
+ - `TEnv` - Your Cloudflare Worker environment bindings
409
464
 
410
- #### Methods
465
+ #### Constructor
466
+
467
+ ```typescript
468
+ constructor(
469
+ ctx: DurableObjectState,
470
+ env: TEnv,
471
+ options: BaseWebSocketDOOptions<TSession, TEnv>
472
+ )
473
+ ```
474
+
475
+ #### Options Type
476
+
477
+ ```typescript
478
+ type BaseWebSocketDOOptions<TSession, TEnv> = {
479
+ createSession: (
480
+ ctx: Context<{ Bindings: TEnv }> | undefined,
481
+ websocket: WebSocket
482
+ ) => TSession | Promise<TSession>;
483
+ };
484
+ ```
411
485
 
412
- - `abstract createSession(websocket: WebSocket): TSession | Promise<TSession>`
413
- - Factory method to create session instances
486
+ #### Methods
414
487
 
415
488
  - `getBaseApp(): Hono`
416
489
  - Returns a base Hono app with `/websocket` endpoint configured
@@ -423,35 +496,44 @@ Abstract class for creating WebSocket-enabled Durable Objects.
423
496
  - `sessions: Map<WebSocket, TSession>` - Map of all active sessions
424
497
  - `app: Hono` - Your Hono application (must be implemented)
425
498
 
426
- ### `BaseSession<TEnv, TData, TServerMessage, TClientMessage>`
499
+ ### `BaseSession<TData, TServerMessage, TClientMessage, TEnv>`
427
500
 
428
- Abstract class for managing individual WebSocket sessions.
501
+ Concrete class for managing individual WebSocket sessions. Uses composition pattern with handlers.
429
502
 
430
503
  #### Type Parameters
431
504
 
432
- - `TEnv` - Your Cloudflare Worker environment bindings
433
505
  - `TData` - Type of data stored in the session
434
506
  - `TServerMessage` - Union type of messages sent to clients
435
507
  - `TClientMessage` - Union type of messages received from clients
508
+ - `TEnv` - Your Cloudflare Worker environment bindings (default: `Cloudflare.Env`)
436
509
 
437
- #### Methods
510
+ #### Constructor
438
511
 
439
- - `abstract createData(ctx: Context): TData`
440
- - Creates initial session data
512
+ ```typescript
513
+ constructor(
514
+ websocket: WebSocket,
515
+ sessions: Map<WebSocket, BaseSession<TData, TServerMessage, TClientMessage, TEnv>>,
516
+ handlers: BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv>
517
+ )
518
+ ```
441
519
 
442
- - `abstract handleMessage(message: TClientMessage): Promise<void>`
443
- - Handles text messages from client
520
+ #### Handlers Type
444
521
 
445
- - `abstract handleBufferMessage(message: ArrayBuffer): Promise<void>`
446
- - Handles binary messages from client
522
+ ```typescript
523
+ type BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv> = {
524
+ createData: (ctx: Context<{ Bindings: TEnv }>) => TData;
525
+ handleMessage: (message: TClientMessage) => Promise<void>;
526
+ handleBufferMessage: (message: ArrayBuffer) => Promise<void>;
527
+ handleClose: () => Promise<void>;
528
+ };
529
+ ```
447
530
 
448
- - `abstract handleClose(): Promise<void>`
449
- - Cleanup when session closes
531
+ #### Methods
450
532
 
451
- - `protected send(message: TServerMessage): void`
533
+ - `send(message: TServerMessage): void`
452
534
  - Send message to this session's client
453
535
 
454
- - `protected broadcast(message: TServerMessage, excludeSelf?: boolean): void`
536
+ - `broadcast(message: TServerMessage, excludeSelf?: boolean): void`
455
537
  - Send message to all connected sessions
456
538
 
457
539
  - `startFresh(ctx: Context): void`
@@ -490,7 +572,7 @@ Low-level wrapper for typed WebSocket operations.
490
572
  You can extend the base app with custom routes:
491
573
 
492
574
  ```typescript
493
- export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
575
+ export class ChatRoomDO extends BaseWebSocketDO<ChatSession, Env> {
494
576
  app = this.getBaseApp()
495
577
  .get('/stats', (ctx) => {
496
578
  const users = Array.from(this.sessions.values()).map(s => ({
@@ -509,6 +591,14 @@ export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
509
591
 
510
592
  return ctx.json({ success: true });
511
593
  });
594
+
595
+ constructor(ctx: DurableObjectState, env: Env) {
596
+ super(ctx, env, {
597
+ createSession: (ctx, websocket) => {
598
+ return new ChatSession(websocket, this.sessions);
599
+ },
600
+ });
601
+ }
512
602
  }
513
603
  ```
514
604
 
@@ -517,25 +607,38 @@ export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
517
607
  Session data is automatically serialized and persists across hibernation:
518
608
 
519
609
  ```typescript
520
- class GameSession extends BaseSession<Env, GameData, ServerMsg, ClientMsg> {
521
- protected createData(ctx: Context): GameData {
522
- return {
523
- playerName: ctx.req.query('name') || 'Anonymous',
524
- score: 0,
525
- inventory: [],
526
- };
527
- }
528
-
529
- async handleMessage(message: ClientMsg): Promise<void> {
530
- if (message.type === 'collectItem') {
531
- this.data.inventory.push(message.item);
532
- this.data.score += 10;
533
-
534
- // Persist changes
535
- this.update();
536
-
537
- this.send({ type: 'scoreUpdate', score: this.data.score });
538
- }
610
+ class GameSession extends BaseSession<GameData, ServerMsg, ClientMsg, Env> {
611
+ constructor(
612
+ websocket: WebSocket,
613
+ sessions: Map<WebSocket, GameSession>
614
+ ) {
615
+ super(websocket, sessions, {
616
+ createData: (ctx) => ({
617
+ playerName: ctx.req.query('name') || 'Anonymous',
618
+ score: 0,
619
+ inventory: [],
620
+ }),
621
+
622
+ handleMessage: async (message) => {
623
+ if (message.type === 'collectItem') {
624
+ this.data.inventory.push(message.item);
625
+ this.data.score += 10;
626
+
627
+ // Persist changes
628
+ this.update();
629
+
630
+ this.send({ type: 'scoreUpdate', score: this.data.score });
631
+ }
632
+ },
633
+
634
+ handleBufferMessage: async (message) => {
635
+ // Handle buffer messages if needed
636
+ },
637
+
638
+ handleClose: async () => {
639
+ console.log('Game session closed');
640
+ },
641
+ });
539
642
  }
540
643
  }
541
644
  ```
@@ -545,21 +648,40 @@ class GameSession extends BaseSession<Env, GameData, ServerMsg, ClientMsg> {
545
648
  Errors in message handlers are caught and logged, but don't crash the connection:
546
649
 
547
650
  ```typescript
548
- async handleMessage(message: ClientMessage): Promise<void> {
549
- try {
550
- // Your logic here
551
- if (message.type === 'dangerous') {
552
- throw new Error('Invalid operation');
553
- }
554
- } catch (error) {
555
- // Send error to client
556
- this.send({
557
- type: 'error',
558
- message: error instanceof Error ? error.message : 'Unknown error'
651
+ class MySession extends BaseSession<...> {
652
+ constructor(
653
+ websocket: WebSocket,
654
+ sessions: Map<WebSocket, MySession>
655
+ ) {
656
+ super(websocket, sessions, {
657
+ createData: (ctx) => ({ /* ... */ }),
658
+
659
+ handleMessage: async (message) => {
660
+ try {
661
+ // Your logic here
662
+ if (message.type === 'dangerous') {
663
+ throw new Error('Invalid operation');
664
+ }
665
+ } catch (error) {
666
+ // Send error to client
667
+ this.send({
668
+ type: 'error',
669
+ message: error instanceof Error ? error.message : 'Unknown error'
670
+ });
671
+
672
+ // Optionally close the connection
673
+ this.websocket.close(1008, 'Policy violation');
674
+ }
675
+ },
676
+
677
+ handleBufferMessage: async (message) => {
678
+ // Handle buffer messages
679
+ },
680
+
681
+ handleClose: async () => {
682
+ console.log('Session closed');
683
+ },
559
684
  });
560
-
561
- // Optionally close the connection
562
- this.websocket.close(1008, 'Policy violation');
563
685
  }
564
686
  }
565
687
  ```
@@ -569,18 +691,25 @@ async handleMessage(message: ClientMessage): Promise<void> {
569
691
  This package exports the following:
570
692
 
571
693
  ### Classes
572
- - `BaseWebSocketDO` - Abstract base class for WebSocket Durable Objects
573
- - `BaseSession` - Abstract base class for WebSocket sessions
694
+ - `BaseWebSocketDO` - Base class for WebSocket Durable Objects (composition-based)
695
+ - `BaseSession` - Concrete session class with handler injection
574
696
  - `ZodWebSocketClient` - Type-safe WebSocket client with Zod validation
575
- - `ZodSession` - Session base class with Zod validation built-in
697
+ - `ZodSession` - Concrete session class with Zod validation and handler injection
698
+ - `ZodWebSocketDO` - Base class for WebSocket DOs with Zod validation
576
699
  - `WebsocketWrapper` - Low-level WebSocket wrapper with typed attachments
577
700
 
701
+ ### Types
702
+ - `BaseSessionHandlers` - Handler interface for `BaseSession`
703
+ - `BaseWebSocketDOOptions` - Options interface for `BaseWebSocketDO`
704
+ - `ZodSessionHandlers` - Handler interface for `ZodSession`
705
+ - `ZodSessionOptions` - Options interface for `ZodSession`
706
+ - `ZodSessionOptionsOrFactory` - Options or factory function for `ZodSession`
707
+ - `ZodWebSocketDOOptions` - Options interface for `ZodWebSocketDO`
708
+ - `ZodWebSocketClientOptions` - Options interface for `ZodWebSocketClient`
709
+
578
710
  ### Utilities
579
711
  - `zodMsgpack` - Msgpack encode/decode with Zod validation
580
712
 
581
- ### Types
582
- All classes export their type parameters and interfaces for custom implementations.
583
-
584
713
  ## Complete Example
585
714
 
586
715
  Here's a full example combining all features:
@@ -605,7 +734,7 @@ export type ClientMessage = z.infer<typeof ClientMessageSchema>;
605
734
  export type ServerMessage = z.infer<typeof ServerMessageSchema>;
606
735
 
607
736
  // do.ts - Server-side (Durable Object)
608
- import { BaseWebSocketDO, ZodSession } from '@firtoz/websocket-do';
737
+ import { BaseWebSocketDO, ZodSession, type ZodSessionOptions } from '@firtoz/websocket-do';
609
738
  import { ClientMessageSchema, ServerMessageSchema } from './schemas';
610
739
 
611
740
  interface SessionData {
@@ -613,45 +742,47 @@ interface SessionData {
613
742
  joinedAt: number;
614
743
  }
615
744
 
616
- class ChatSession extends ZodSession<Env, SessionData, ServerMessage, ClientMessage> {
617
- protected clientSchema = ClientMessageSchema;
618
- protected serverSchema = ServerMessageSchema;
619
- protected enableBufferMessages = true; // Use msgpack for efficiency
620
-
621
- protected createData(): SessionData {
622
- return {
623
- name: 'Anonymous',
624
- joinedAt: Date.now(),
625
- };
626
- }
627
-
628
- async handleMessage(message: ClientMessage): Promise<void> {
629
- switch (message.type) {
630
- case 'setName':
631
- const oldName = this.data.name;
632
- this.data.name = message.name;
633
- this.update();
634
-
635
- this.send({ type: 'nameChanged', newName: message.name });
636
- this.broadcast({ type: 'userJoined', name: message.name }, true);
637
- break;
638
-
639
- case 'message':
640
- this.broadcast({
641
- type: 'message',
642
- text: message.text,
643
- from: this.data.name,
644
- });
645
- break;
646
- }
647
- }
648
-
649
- async handleClose(): Promise<void> {
650
- console.log(`${this.data.name} disconnected`);
745
+ class ChatSession extends ZodSession<SessionData, ServerMessage, ClientMessage, Env> {
746
+ constructor(
747
+ websocket: WebSocket,
748
+ sessions: Map<WebSocket, ChatSession>,
749
+ options: ZodSessionOptions<ClientMessage, ServerMessage>
750
+ ) {
751
+ super(websocket, sessions, options, {
752
+ createData: () => ({
753
+ name: 'Anonymous',
754
+ joinedAt: Date.now(),
755
+ }),
756
+
757
+ handleValidatedMessage: async (message) => {
758
+ switch (message.type) {
759
+ case 'setName':
760
+ const oldName = this.data.name;
761
+ this.data.name = message.name;
762
+ this.update();
763
+
764
+ this.send({ type: 'nameChanged', newName: message.name });
765
+ this.broadcast({ type: 'userJoined', name: message.name }, true);
766
+ break;
767
+
768
+ case 'message':
769
+ this.broadcast({
770
+ type: 'message',
771
+ text: message.text,
772
+ from: this.data.name,
773
+ });
774
+ break;
775
+ }
776
+ },
777
+
778
+ handleClose: async () => {
779
+ console.log(`${this.data.name} disconnected`);
780
+ },
781
+ });
651
782
  }
652
783
  }
653
784
 
654
- export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
785
+ export class ChatRoomDO extends BaseWebSocketDO<ChatSession, Env> {
655
786
  app = this.getBaseApp()
656
787
  .get('/info', (ctx) => {
657
788
  const users = Array.from(this.sessions.values()).map(s => ({
@@ -661,8 +792,16 @@ export class ChatRoomDO extends BaseWebSocketDO<Env, ChatSession> {
661
792
  return ctx.json({ users, count: users.length });
662
793
  });
663
794
 
664
- protected createSession(websocket: WebSocket): ChatSession {
665
- return new ChatSession(websocket, this.sessions);
795
+ constructor(ctx: DurableObjectState, env: Env) {
796
+ super(ctx, env, {
797
+ createSession: (ctx, websocket) => {
798
+ return new ChatSession(websocket, this.sessions, {
799
+ clientSchema: ClientMessageSchema,
800
+ serverSchema: ServerMessageSchema,
801
+ enableBufferMessages: true, // Use msgpack for efficiency
802
+ });
803
+ },
804
+ });
666
805
  }
667
806
  }
668
807
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/websocket-do",
3
- "version": "6.0.2",
3
+ "version": "7.0.1",
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",
@@ -45,14 +45,14 @@
45
45
  "url": "https://github.com/firtoz/fullstack-toolkit/issues"
46
46
  },
47
47
  "peerDependencies": {
48
- "@cloudflare/workers-types": "^4.20251008.0",
49
- "@firtoz/hono-fetcher": "^2.3.1",
50
- "hono": "^4.10.1"
48
+ "@cloudflare/workers-types": "^4.20251228.0",
49
+ "@firtoz/hono-fetcher": "^2.3.2",
50
+ "hono": "^4.11.4"
51
51
  },
52
52
  "optionalDependencies": {
53
- "msgpackr": "^1.11.5",
54
- "react": "^19.2.0",
55
- "zod": "^4.1.12"
53
+ "msgpackr": "^1.11.8",
54
+ "react": "^19.2.3",
55
+ "zod": "^4.3.5"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=18.0.0"
@@ -61,8 +61,8 @@
61
61
  "access": "public"
62
62
  },
63
63
  "devDependencies": {
64
- "@types/react": "^19.2.2",
65
- "bun-types": "^1.3.0",
64
+ "@types/react": "^19.2.8",
65
+ "bun-types": "^1.3.6",
66
66
  "typescript": "^5.9.3"
67
67
  }
68
68
  }
@@ -1,20 +1,73 @@
1
1
  import type { Context } from "hono";
2
2
  import { WebsocketWrapper } from "./WebsocketWrapper";
3
3
 
4
- export type SessionClientMessage<TSession extends BaseSession> =
5
- TSession extends BaseSession<never, never, infer TClientMessage, never>
4
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
5
+ export type SessionData<TSession extends BaseSession<any, any, any, any>> =
6
+ TSession extends BaseSession<
7
+ infer TData,
8
+ infer _TServerMessage,
9
+ infer _TClientMessage,
10
+ infer _TEnv
11
+ >
12
+ ? TData
13
+ : never;
14
+
15
+ export type SessionClientMessage<
16
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
17
+ TSession extends BaseSession<any, any, any, any>,
18
+ > =
19
+ TSession extends BaseSession<
20
+ infer _TData,
21
+ infer _TServerMessage,
22
+ infer TClientMessage,
23
+ infer _TEnv
24
+ >
6
25
  ? TClientMessage
7
26
  : never;
8
27
 
9
- export abstract class BaseSession<
10
- // biome-ignore lint/suspicious/noExplicitAny: Generic type parameter with flexible default
11
- TEnv extends object = any,
12
- // biome-ignore lint/suspicious/noExplicitAny: Generic type parameter with flexible default
13
- TData = any,
14
- // biome-ignore lint/suspicious/noExplicitAny: Generic type parameter with flexible default
15
- TServerMessage = any,
16
- // biome-ignore lint/suspicious/noExplicitAny: Generic type parameter with flexible default
17
- TClientMessage = any,
28
+ export type SessionServerMessage<
29
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
30
+ TSession extends BaseSession<any, any, any, any>,
31
+ > =
32
+ TSession extends BaseSession<
33
+ infer _TData,
34
+ infer TServerMessage,
35
+ infer _TClientMessage,
36
+ infer _TEnv
37
+ >
38
+ ? TServerMessage
39
+ : never;
40
+
41
+ export type SessionEnv<
42
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
43
+ TSession extends BaseSession<any, any, any, any>,
44
+ > =
45
+ TSession extends BaseSession<
46
+ infer _TData,
47
+ infer _TServerMessage,
48
+ infer _TClientMessage,
49
+ infer TEnv extends Cloudflare.Env
50
+ >
51
+ ? TEnv
52
+ : never;
53
+
54
+ export type BaseSessionHandlers<
55
+ TData,
56
+ _TServerMessage,
57
+ TClientMessage,
58
+ TEnv extends object = Cloudflare.Env,
59
+ > = {
60
+ createData: (ctx: Context<{ Bindings: TEnv }>) => TData;
61
+ handleMessage: (message: TClientMessage) => Promise<void>;
62
+ handleBufferMessage: (message: ArrayBuffer) => Promise<void>;
63
+ handleClose: () => Promise<void>;
64
+ };
65
+
66
+ export class BaseSession<
67
+ TData,
68
+ TServerMessage,
69
+ TClientMessage,
70
+ TEnv extends object = Cloudflare.Env,
18
71
  > {
19
72
  private _data!: TData;
20
73
 
@@ -27,19 +80,27 @@ export abstract class BaseSession<
27
80
  }
28
81
 
29
82
  private readonly wrapper: WebsocketWrapper<TData, TServerMessage>;
83
+ protected readonly handlers: BaseSessionHandlers<
84
+ TData,
85
+ TServerMessage,
86
+ TClientMessage,
87
+ TEnv
88
+ >;
30
89
 
31
90
  constructor(
32
91
  public websocket: WebSocket,
33
92
  protected sessions: Map<
34
93
  WebSocket,
35
- BaseSession<TEnv, TData, TServerMessage, TClientMessage>
94
+ BaseSession<TData, TServerMessage, TClientMessage, TEnv>
36
95
  >,
96
+ handlers: BaseSessionHandlers<TData, TServerMessage, TClientMessage, TEnv>,
37
97
  ) {
38
98
  this.wrapper = new WebsocketWrapper<TData, TServerMessage>(websocket);
99
+ this.handlers = handlers;
39
100
  }
40
101
 
41
102
  public startFresh(ctx: Context<{ Bindings: TEnv }>) {
42
- this.data = this.createData(ctx);
103
+ this.data = this.handlers.createData(ctx);
43
104
  this.wrapper.serializeAttachment(this.data);
44
105
  }
45
106
 
@@ -56,20 +117,26 @@ export abstract class BaseSession<
56
117
  this.wrapper.serializeAttachment(this.data);
57
118
  }
58
119
 
59
- protected abstract createData(ctx: Context<{ Bindings: TEnv }>): TData;
60
-
61
- protected broadcast(message: TServerMessage, excludeSelf = false) {
120
+ public broadcast(message: TServerMessage, excludeSelf = false) {
62
121
  for (const session of this.sessions.values()) {
63
122
  if (excludeSelf && session === this) continue;
64
123
  session.send(message);
65
124
  }
66
125
  }
67
126
 
68
- protected send(message: TServerMessage) {
127
+ public send(message: TServerMessage) {
69
128
  this.wrapper.send(message);
70
129
  }
71
130
 
72
- abstract handleMessage(message: TClientMessage): Promise<void>;
73
- abstract handleBufferMessage(message: ArrayBuffer): Promise<void>;
74
- abstract handleClose(): Promise<void>;
131
+ async handleMessage(message: TClientMessage): Promise<void> {
132
+ return this.handlers.handleMessage(message);
133
+ }
134
+
135
+ async handleBufferMessage(message: ArrayBuffer): Promise<void> {
136
+ return this.handlers.handleBufferMessage(message);
137
+ }
138
+
139
+ async handleClose(): Promise<void> {
140
+ return this.handlers.handleClose();
141
+ }
75
142
  }
@@ -1,18 +1,48 @@
1
1
  import { DurableObject } from "cloudflare:workers";
2
2
  import type { DOWithHonoApp } from "@firtoz/hono-fetcher/honoDoFetcher";
3
3
  import { type Context, Hono } from "hono";
4
- import type { BaseSession, SessionClientMessage } from "./BaseSession";
4
+ import type {
5
+ BaseSession,
6
+ SessionClientMessage,
7
+ SessionEnv,
8
+ } from "./BaseSession";
9
+
10
+ export type BaseWebSocketDOOptions<
11
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
12
+ TSession extends BaseSession<any, any, any, any>,
13
+ TEnv extends SessionEnv<TSession>,
14
+ > = {
15
+ createSession: (
16
+ ctx: Context<{ Bindings: TEnv }> | undefined,
17
+ websocket: WebSocket,
18
+ ) => TSession | Promise<TSession>;
19
+ };
5
20
 
6
21
  export abstract class BaseWebSocketDO<
7
- TEnv extends object,
8
- TSession extends BaseSession<TEnv>,
22
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
23
+ TSession extends BaseSession<any, any, any, any> = BaseSession<
24
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
25
+ any,
26
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
27
+ any,
28
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
29
+ any,
30
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
31
+ any
32
+ >,
33
+ TEnv extends SessionEnv<TSession> = SessionEnv<TSession>,
9
34
  >
10
35
  extends DurableObject<TEnv>
11
36
  implements DOWithHonoApp
12
37
  {
13
38
  protected readonly sessions = new Map<WebSocket, TSession>();
39
+ abstract readonly app: Hono<{ Bindings: TEnv }>;
14
40
 
15
- constructor(ctx: DurableObjectState, env: TEnv) {
41
+ constructor(
42
+ ctx: DurableObjectState,
43
+ env: TEnv,
44
+ private readonly options: BaseWebSocketDOOptions<TSession, TEnv>,
45
+ ) {
16
46
  super(ctx, env);
17
47
 
18
48
  this.ctx.blockConcurrencyWhile(async () => {
@@ -20,7 +50,9 @@ export abstract class BaseWebSocketDO<
20
50
  await Promise.all(
21
51
  websockets.map(async (websocket) => {
22
52
  try {
23
- const session = await this.createSession(websocket);
53
+ // For resumed sessions, we don't have a Hono context
54
+ // Pass undefined and let implementers handle it
55
+ const session = await options.createSession(undefined, websocket);
24
56
  session.resume();
25
57
  this.sessions.set(websocket, session);
26
58
  } catch (error) {
@@ -65,19 +97,13 @@ export abstract class BaseWebSocketDO<
65
97
  );
66
98
  }
67
99
 
68
- abstract app: Hono<{ Bindings: TEnv }>;
69
-
70
- protected abstract createSession(
71
- websocket: WebSocket,
72
- ): TSession | Promise<TSession>;
73
-
74
100
  async handleSession(
75
101
  ctx: Context<{ Bindings: TEnv }>,
76
102
  ws: WebSocket,
77
103
  ): Promise<void> {
78
104
  this.ctx.acceptWebSocket(ws);
79
105
  try {
80
- const session = await this.createSession(ws);
106
+ const session = await this.options.createSession(ctx, ws);
81
107
  session.startFresh(ctx);
82
108
  this.sessions.set(ws, session);
83
109
  } catch (error) {
package/src/ZodSession.ts CHANGED
@@ -1,44 +1,77 @@
1
+ import type { Context } from "hono";
1
2
  import type { ZodType } from "zod";
2
3
  import { BaseSession } from "./BaseSession";
3
4
  import { zodMsgpack } from "./zodMsgpack";
4
5
 
5
- export interface ZodSessionOptions<TClientMessage, TServerMessage> {
6
+ export type ZodSessionOptions<TClientMessage, TServerMessage> = {
6
7
  clientSchema: ZodType<TClientMessage>;
7
8
  serverSchema: ZodType<TServerMessage>;
8
9
  enableBufferMessages?: boolean;
9
- }
10
-
11
- export abstract class ZodSession<
12
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the environment
13
- TEnv extends object = any,
14
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the data
15
- TData = any,
16
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the server message
17
- TServerMessage = any,
18
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the client message
19
- TClientMessage = any,
20
- > extends BaseSession<TEnv, TData, TServerMessage, TClientMessage> {
21
- protected readonly clientCodec: ReturnType<typeof zodMsgpack<TClientMessage>>;
22
- protected readonly serverCodec: ReturnType<typeof zodMsgpack<TServerMessage>>;
10
+ sendProtocolError?: (
11
+ websocket: WebSocket,
12
+ errorMessage: string,
13
+ ) => Promise<void>;
14
+ };
15
+
16
+ export type ZodSessionHandlers<
17
+ TData,
18
+ _TServerMessage,
19
+ TClientMessage,
20
+ TEnv extends object,
21
+ > = {
22
+ createData: (ctx: Context<{ Bindings: TEnv }>) => TData;
23
+ handleValidatedMessage: (message: TClientMessage) => Promise<void>;
24
+ handleValidationError?: (
25
+ error: unknown,
26
+ originalMessage: unknown,
27
+ ) => Promise<void>;
28
+ handleClose: () => Promise<void>;
29
+ };
30
+
31
+ export class ZodSession<
32
+ TData,
33
+ TServerMessage,
34
+ TClientMessage,
35
+ TEnv extends object = Cloudflare.Env,
36
+ > extends BaseSession<TData, TServerMessage, TClientMessage, TEnv> {
37
+ private readonly clientCodec: ReturnType<typeof zodMsgpack<TClientMessage>>;
38
+ private readonly serverCodec: ReturnType<typeof zodMsgpack<TServerMessage>>;
23
39
  protected readonly enableBufferMessages: boolean;
24
40
 
25
41
  constructor(
26
42
  websocket: WebSocket,
27
43
  sessions: Map<
28
44
  WebSocket,
29
- ZodSession<TEnv, TData, TServerMessage, TClientMessage>
45
+ ZodSession<TData, TServerMessage, TClientMessage, TEnv>
46
+ >,
47
+ private readonly options: ZodSessionOptions<TClientMessage, TServerMessage>,
48
+ private readonly zodHandlers: ZodSessionHandlers<
49
+ TData,
50
+ TServerMessage,
51
+ TClientMessage,
52
+ TEnv
30
53
  >,
31
- protected options: ZodSessionOptions<TClientMessage, TServerMessage>,
32
54
  ) {
33
- super(websocket, sessions);
55
+ super(websocket, sessions, {
56
+ createData: zodHandlers.createData,
57
+ handleMessage: async (message) => {
58
+ return this._internalHandleMessage(message);
59
+ },
60
+ handleBufferMessage: async (message) => {
61
+ return this._internalHandleBufferMessage(message);
62
+ },
63
+ handleClose: async () => {
64
+ return zodHandlers.handleClose();
65
+ },
66
+ });
34
67
 
35
68
  this.clientCodec = zodMsgpack(options.clientSchema);
36
69
  this.serverCodec = zodMsgpack(options.serverSchema);
37
70
  this.enableBufferMessages = options.enableBufferMessages ?? false;
38
71
  }
39
72
 
40
- // Override the base handleMessage to add validation
41
- async handleMessage(message: TClientMessage): Promise<void> {
73
+ // Internal method used by the base class handlers
74
+ private async _internalHandleMessage(message: TClientMessage): Promise<void> {
42
75
  // If buffer messages are enabled, reject JSON messages
43
76
  if (this.enableBufferMessages) {
44
77
  console.error(
@@ -53,15 +86,17 @@ export abstract class ZodSession<
53
86
  try {
54
87
  // Validate the message using the client schema
55
88
  const validatedMessage = this.options.clientSchema.parse(message);
56
- await this.handleValidatedMessage(validatedMessage);
89
+ await this.zodHandlers.handleValidatedMessage(validatedMessage);
57
90
  } catch (error) {
58
91
  console.error("Invalid client message received:", error);
59
- await this.handleValidationError(error, message);
92
+ await this._internalHandleValidationError(error, message);
60
93
  }
61
94
  }
62
95
 
63
- // Override buffer message handling to support msgpack decoding
64
- async handleBufferMessage(buffer: ArrayBuffer): Promise<void> {
96
+ // Internal method used by the base class handlers
97
+ private async _internalHandleBufferMessage(
98
+ buffer: ArrayBuffer,
99
+ ): Promise<void> {
65
100
  // If buffer messages are disabled, reject buffer messages
66
101
  if (!this.enableBufferMessages) {
67
102
  console.error(
@@ -75,15 +110,33 @@ export abstract class ZodSession<
75
110
  try {
76
111
  const bytes = new Uint8Array(buffer);
77
112
  const decodedMessage = this.clientCodec.decode(bytes);
78
- await this.handleValidatedMessage(decodedMessage);
113
+ await this.zodHandlers.handleValidatedMessage(decodedMessage);
79
114
  } catch (error) {
80
115
  console.error("Failed to decode buffer message:", error);
81
- await this.handleValidationError(error, buffer);
116
+ await this._internalHandleValidationError(error, buffer);
117
+ }
118
+ }
119
+
120
+ // Internal validation error handler
121
+ private async _internalHandleValidationError(
122
+ error: unknown,
123
+ originalMessage: unknown,
124
+ ): Promise<void> {
125
+ if (this.zodHandlers.handleValidationError) {
126
+ await this.zodHandlers.handleValidationError(error, originalMessage);
127
+ } else {
128
+ // Default implementation logs and continues
129
+ console.error(
130
+ "Validation error:",
131
+ error,
132
+ "Original message:",
133
+ originalMessage,
134
+ );
82
135
  }
83
136
  }
84
137
 
85
138
  // Type-safe send method that automatically uses the correct format
86
- protected send(message: TServerMessage): void {
139
+ public send(message: TServerMessage): void {
87
140
  if (this.enableBufferMessages) {
88
141
  this.sendBuffer(message);
89
142
  } else {
@@ -118,12 +171,17 @@ export abstract class ZodSession<
118
171
  }
119
172
  }
120
173
 
121
- // Send a protocol error message (always as JSON for compatibility)
174
+ // Send a protocol error message (always as JSON for compatibility by default)
122
175
  private async sendProtocolError(errorMessage: string): Promise<void> {
123
176
  try {
124
- // Send a simple error object - no schema validation needed
125
- if (this.websocket.readyState !== WebSocket.OPEN) return;
126
- this.websocket.send(JSON.stringify({ error: errorMessage }));
177
+ // Use custom handler if provided, otherwise use default
178
+ if (this.options.sendProtocolError) {
179
+ await this.options.sendProtocolError(this.websocket, errorMessage);
180
+ } else {
181
+ // Default implementation: send a simple error object - no schema validation needed
182
+ if (this.websocket.readyState !== WebSocket.OPEN) return;
183
+ this.websocket.send(JSON.stringify({ error: errorMessage }));
184
+ }
127
185
  } catch (error) {
128
186
  console.error("Failed to send protocol error:", error);
129
187
  }
@@ -131,7 +189,7 @@ export abstract class ZodSession<
131
189
 
132
190
  // Type-safe broadcast that validates server messages
133
191
  // Automatically uses the correct format based on session configuration
134
- protected broadcast(message: TServerMessage, excludeSelf = false): void {
192
+ public broadcast(message: TServerMessage, excludeSelf = false): void {
135
193
  for (const session of this.sessions.values()) {
136
194
  if (excludeSelf && session === this) continue;
137
195
  if (session instanceof ZodSession) {
@@ -139,23 +197,4 @@ export abstract class ZodSession<
139
197
  }
140
198
  }
141
199
  }
142
-
143
- // Abstract methods for implementers
144
- protected abstract handleValidatedMessage(
145
- message: TClientMessage,
146
- ): Promise<void>;
147
-
148
- // Optional error handling - default implementation logs and continues
149
- protected async handleValidationError(
150
- error: unknown,
151
- originalMessage: unknown,
152
- ): Promise<void> {
153
- console.error(
154
- "Validation error:",
155
- error,
156
- "Original message:",
157
- originalMessage,
158
- );
159
- // Implementers can override this to send error responses to clients
160
- }
161
200
  }
@@ -1,27 +1,83 @@
1
+ import type { Context } from "hono";
2
+ import type {
3
+ SessionClientMessage,
4
+ SessionEnv,
5
+ SessionServerMessage,
6
+ } from "./BaseSession";
1
7
  import { BaseWebSocketDO } from "./BaseWebSocketDO";
2
8
  import type { ZodSession, ZodSessionOptions } from "./ZodSession";
3
9
 
4
- export abstract class ZodWebSocketDO<
5
- TEnv extends object,
6
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the session
7
- TSession extends ZodSession<TEnv, any, any, any>,
8
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the client message
9
- TClientMessage = any,
10
- // biome-ignore lint/suspicious/noExplicitAny: We need to allow any for the server message
11
- TServerMessage = any,
12
- > extends BaseWebSocketDO<TEnv, TSession> {
13
- protected abstract getZodOptions(): ZodSessionOptions<
10
+ export type ZodSessionOptionsOrFactory<
11
+ TClientMessage,
12
+ TServerMessage,
13
+ TEnv extends Cloudflare.Env = Cloudflare.Env,
14
+ > =
15
+ | ZodSessionOptions<TClientMessage, TServerMessage>
16
+ | ((
17
+ ctx: Context<{ Bindings: TEnv }> | undefined,
18
+ websocket: WebSocket,
19
+ ) => ZodSessionOptions<TClientMessage, TServerMessage>);
20
+
21
+ export type ZodWebSocketDOOptions<
22
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
23
+ TSession extends ZodSession<any, any, any, any>,
24
+ TClientMessage,
25
+ TServerMessage,
26
+ TEnv extends SessionEnv<TSession>,
27
+ > = {
28
+ zodSessionOptions: ZodSessionOptionsOrFactory<
14
29
  TClientMessage,
15
- TServerMessage
30
+ TServerMessage,
31
+ TEnv
16
32
  >;
33
+ createZodSession: (
34
+ ctx: Context<{ Bindings: TEnv }> | undefined,
35
+ websocket: WebSocket,
36
+ options: ZodSessionOptions<TClientMessage, TServerMessage>,
37
+ ) => TSession | Promise<TSession>;
38
+ };
17
39
 
18
- protected abstract createZodSession(
40
+ export abstract class ZodWebSocketDO<
41
+ // biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.
42
+ TSession extends ZodSession<any, any, any, any>,
43
+ TClientMessage extends
44
+ SessionClientMessage<TSession> = SessionClientMessage<TSession>,
45
+ TServerMessage extends
46
+ SessionServerMessage<TSession> = SessionServerMessage<TSession>,
47
+ TEnv extends SessionEnv<TSession> = SessionEnv<TSession>,
48
+ > extends BaseWebSocketDO<TSession, TEnv> {
49
+ protected readonly zodSessionOptions: ZodSessionOptionsOrFactory<
50
+ TClientMessage,
51
+ TServerMessage,
52
+ TEnv
53
+ >;
54
+ protected readonly createZodSessionFn: (
55
+ ctx: Context<{ Bindings: TEnv }> | undefined,
19
56
  websocket: WebSocket,
20
57
  options: ZodSessionOptions<TClientMessage, TServerMessage>,
21
- ): TSession | Promise<TSession>;
58
+ ) => TSession | Promise<TSession>;
59
+
60
+ constructor(
61
+ ctx: DurableObjectState,
62
+ env: TEnv,
63
+ options: ZodWebSocketDOOptions<
64
+ TSession,
65
+ TClientMessage,
66
+ TServerMessage,
67
+ TEnv
68
+ >,
69
+ ) {
70
+ super(ctx, env, {
71
+ createSession: (ctx, websocket) => {
72
+ const zodOptions =
73
+ typeof options.zodSessionOptions === "function"
74
+ ? options.zodSessionOptions(ctx, websocket)
75
+ : options.zodSessionOptions;
22
76
 
23
- protected createSession(websocket: WebSocket): TSession | Promise<TSession> {
24
- const options = this.getZodOptions();
25
- return this.createZodSession(websocket, options);
77
+ return options.createZodSession(ctx, websocket, zodOptions);
78
+ },
79
+ });
80
+ this.zodSessionOptions = options.zodSessionOptions;
81
+ this.createZodSessionFn = options.createZodSession;
26
82
  }
27
83
  }
package/src/index.ts CHANGED
@@ -1,10 +1,25 @@
1
- export { BaseSession, type SessionClientMessage } from "./BaseSession";
2
- export { BaseWebSocketDO } from "./BaseWebSocketDO";
1
+ export {
2
+ BaseSession,
3
+ type BaseSessionHandlers,
4
+ type SessionClientMessage,
5
+ } from "./BaseSession";
6
+ export {
7
+ BaseWebSocketDO,
8
+ type BaseWebSocketDOOptions,
9
+ } from "./BaseWebSocketDO";
3
10
  export { WebsocketWrapper } from "./WebsocketWrapper";
4
- export { ZodSession, type ZodSessionOptions } from "./ZodSession";
11
+ export {
12
+ ZodSession,
13
+ type ZodSessionHandlers,
14
+ type ZodSessionOptions,
15
+ } from "./ZodSession";
5
16
  export {
6
17
  ZodWebSocketClient,
7
18
  type ZodWebSocketClientOptions,
8
19
  } from "./ZodWebSocketClient";
9
- export { ZodWebSocketDO } from "./ZodWebSocketDO";
20
+ export {
21
+ type ZodSessionOptionsOrFactory,
22
+ ZodWebSocketDO,
23
+ type ZodWebSocketDOOptions,
24
+ } from "./ZodWebSocketDO";
10
25
  export { zodMsgpack } from "./zodMsgpack";