@venizia/ignis-docs 0.0.5 → 0.0.6-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.
Files changed (98) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architecture-decisions.md +0 -8
  3. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  4. package/wiki/best-practices/performance-optimization.md +3 -3
  5. package/wiki/best-practices/security-guidelines.md +2 -2
  6. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  7. package/wiki/guides/core-concepts/components-guide.md +1 -1
  8. package/wiki/guides/core-concepts/components.md +2 -2
  9. package/wiki/guides/core-concepts/dependency-injection.md +1 -1
  10. package/wiki/guides/core-concepts/services.md +1 -1
  11. package/wiki/guides/tutorials/building-a-crud-api.md +1 -1
  12. package/wiki/guides/tutorials/ecommerce-api.md +2 -2
  13. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  14. package/wiki/guides/tutorials/testing.md +1 -1
  15. package/wiki/references/base/bootstrapping.md +0 -2
  16. package/wiki/references/base/components.md +2 -2
  17. package/wiki/references/base/controllers.md +0 -1
  18. package/wiki/references/base/datasources.md +1 -1
  19. package/wiki/references/base/dependency-injection.md +1 -1
  20. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  21. package/wiki/references/base/middlewares.md +0 -8
  22. package/wiki/references/base/providers.md +0 -9
  23. package/wiki/references/base/services.md +0 -1
  24. package/wiki/references/components/authentication/api.md +444 -0
  25. package/wiki/references/components/authentication/errors.md +177 -0
  26. package/wiki/references/components/authentication/index.md +571 -0
  27. package/wiki/references/components/authentication/usage.md +781 -0
  28. package/wiki/references/components/health-check.md +292 -103
  29. package/wiki/references/components/index.md +14 -12
  30. package/wiki/references/components/mail/api.md +505 -0
  31. package/wiki/references/components/mail/errors.md +176 -0
  32. package/wiki/references/components/mail/index.md +535 -0
  33. package/wiki/references/components/mail/usage.md +404 -0
  34. package/wiki/references/components/request-tracker.md +229 -25
  35. package/wiki/references/components/socket-io/api.md +1051 -0
  36. package/wiki/references/components/socket-io/errors.md +119 -0
  37. package/wiki/references/components/socket-io/index.md +410 -0
  38. package/wiki/references/components/socket-io/usage.md +322 -0
  39. package/wiki/references/components/static-asset/api.md +261 -0
  40. package/wiki/references/components/static-asset/errors.md +89 -0
  41. package/wiki/references/components/static-asset/index.md +617 -0
  42. package/wiki/references/components/static-asset/usage.md +364 -0
  43. package/wiki/references/components/swagger.md +390 -110
  44. package/wiki/references/components/template/api-page.md +125 -0
  45. package/wiki/references/components/template/errors-page.md +100 -0
  46. package/wiki/references/components/template/index.md +104 -0
  47. package/wiki/references/components/template/setup-page.md +134 -0
  48. package/wiki/references/components/template/single-page.md +132 -0
  49. package/wiki/references/components/template/usage-page.md +127 -0
  50. package/wiki/references/components/websocket/api.md +508 -0
  51. package/wiki/references/components/websocket/errors.md +123 -0
  52. package/wiki/references/components/websocket/index.md +453 -0
  53. package/wiki/references/components/websocket/usage.md +475 -0
  54. package/wiki/references/helpers/cron/index.md +224 -0
  55. package/wiki/references/helpers/crypto/index.md +537 -0
  56. package/wiki/references/helpers/env/index.md +214 -0
  57. package/wiki/references/helpers/error/index.md +232 -0
  58. package/wiki/references/helpers/index.md +16 -15
  59. package/wiki/references/helpers/inversion/index.md +608 -0
  60. package/wiki/references/helpers/logger/index.md +600 -0
  61. package/wiki/references/helpers/network/api.md +986 -0
  62. package/wiki/references/helpers/network/index.md +620 -0
  63. package/wiki/references/helpers/queue/index.md +589 -0
  64. package/wiki/references/helpers/redis/index.md +495 -0
  65. package/wiki/references/helpers/socket-io/api.md +497 -0
  66. package/wiki/references/helpers/socket-io/index.md +513 -0
  67. package/wiki/references/helpers/storage/api.md +705 -0
  68. package/wiki/references/helpers/storage/index.md +583 -0
  69. package/wiki/references/helpers/template/index.md +66 -0
  70. package/wiki/references/helpers/template/single-page.md +126 -0
  71. package/wiki/references/helpers/testing/index.md +510 -0
  72. package/wiki/references/helpers/types/index.md +512 -0
  73. package/wiki/references/helpers/uid/index.md +272 -0
  74. package/wiki/references/helpers/websocket/api.md +736 -0
  75. package/wiki/references/helpers/websocket/index.md +574 -0
  76. package/wiki/references/helpers/worker-thread/index.md +470 -0
  77. package/wiki/references/quick-reference.md +3 -18
  78. package/wiki/references/utilities/jsx.md +1 -8
  79. package/wiki/references/utilities/statuses.md +0 -7
  80. package/wiki/references/components/authentication.md +0 -476
  81. package/wiki/references/components/mail.md +0 -687
  82. package/wiki/references/components/socket-io.md +0 -562
  83. package/wiki/references/components/static-asset.md +0 -1277
  84. package/wiki/references/helpers/cron.md +0 -108
  85. package/wiki/references/helpers/crypto.md +0 -132
  86. package/wiki/references/helpers/env.md +0 -83
  87. package/wiki/references/helpers/error.md +0 -97
  88. package/wiki/references/helpers/inversion.md +0 -176
  89. package/wiki/references/helpers/logger.md +0 -296
  90. package/wiki/references/helpers/network.md +0 -396
  91. package/wiki/references/helpers/queue.md +0 -150
  92. package/wiki/references/helpers/redis.md +0 -142
  93. package/wiki/references/helpers/socket-io.md +0 -932
  94. package/wiki/references/helpers/storage.md +0 -665
  95. package/wiki/references/helpers/testing.md +0 -133
  96. package/wiki/references/helpers/types.md +0 -167
  97. package/wiki/references/helpers/uid.md +0 -167
  98. package/wiki/references/helpers/worker-thread.md +0 -178
@@ -0,0 +1,453 @@
1
+ # WebSocket -- Setup & Configuration
2
+
3
+ > Bun-native real-time, bidirectional communication using pure WebSocket -- with Redis Pub/Sub for horizontal scaling, application-level heartbeat, and post-connection authentication.
4
+
5
+ > [!IMPORTANT]
6
+ > **Bun only.** The WebSocket component will throw an error if the runtime is Node.js. For Node.js support, use the [Socket.IO Component](../socket-io/) instead.
7
+
8
+ ## Quick Reference
9
+
10
+ | Item | Value |
11
+ |------|-------|
12
+ | **Package** | `@venizia/ignis` (core component) + `@venizia/ignis-helpers` (helper classes) |
13
+ | **Component** | `WebSocketComponent` |
14
+ | **Server Helper** | [`WebSocketServerHelper`](/references/helpers/websocket/) |
15
+ | **Emitter Helper** | `WebSocketEmitter` (standalone Redis publisher) |
16
+ | **Runtimes** | Bun only (throws on Node.js) |
17
+ | **Scaling** | Redis Pub/Sub (ioredis -- single or Cluster) |
18
+
19
+ #### Import Paths
20
+ ```typescript
21
+ // From core -- component + binding keys only
22
+ import {
23
+ WebSocketComponent,
24
+ WebSocketBindingKeys,
25
+ } from '@venizia/ignis';
26
+
27
+ // From helpers -- types, helpers, constants
28
+ import {
29
+ WebSocketServerHelper,
30
+ WebSocketEmitter,
31
+ WebSocketDefaults,
32
+ WebSocketEvents,
33
+ WebSocketChannels,
34
+ WebSocketClientStates,
35
+ WebSocketMessageTypes,
36
+ } from '@venizia/ignis-helpers';
37
+
38
+ import type {
39
+ IWebSocketServerOptions,
40
+ IWebSocketEmitterOptions,
41
+ IWebSocketClient,
42
+ IWebSocketMessage,
43
+ IRedisSocketMessage,
44
+ IBunWebSocketConfig,
45
+ TWebSocketAuthenticateFn,
46
+ TWebSocketValidateRoomFn,
47
+ TWebSocketClientConnectedFn,
48
+ TWebSocketClientDisconnectedFn,
49
+ TWebSocketMessageHandler,
50
+ TWebSocketOutboundTransformer,
51
+ TWebSocketHandshakeFn,
52
+ } from '@venizia/ignis-helpers';
53
+ ```
54
+
55
+ > [!NOTE]
56
+ > `IServerOptions` (the core component's subset type) is **not** exported from `@venizia/ignis`. Only `WebSocketBindingKeys` and `WebSocketComponent` are exported from the core package. All helper types, constants, and classes are imported from `@venizia/ignis-helpers`.
57
+
58
+ ### Use Cases
59
+
60
+ - Live notifications and alerts
61
+ - Real-time chat and messaging
62
+ - Collaborative editing (docs, whiteboards)
63
+ - Live data streams (dashboards, monitoring)
64
+ - Multiplayer game state synchronization
65
+ - IoT device communication
66
+ - Background job progress updates (via `WebSocketEmitter`)
67
+ - Cross-service event broadcasting (via `WebSocketEmitter`)
68
+
69
+ ## Setup
70
+
71
+ ### Step 1: Install Dependencies
72
+
73
+ ```bash
74
+ # Core dependency (already included via @venizia/ignis)
75
+ # ioredis is required for Redis Pub/Sub
76
+ bun add ioredis
77
+ ```
78
+
79
+ ### Step 2: Bind Required Services
80
+
81
+ In your application's `preConfigure()` method, bind the required services and register the component:
82
+
83
+ #### Full Setup Example
84
+ ```typescript
85
+ import {
86
+ BaseApplication,
87
+ WebSocketComponent,
88
+ WebSocketBindingKeys,
89
+ } from '@venizia/ignis';
90
+ import {
91
+ RedisHelper,
92
+ } from '@venizia/ignis-helpers';
93
+ import type {
94
+ TWebSocketAuthenticateFn,
95
+ TWebSocketValidateRoomFn,
96
+ TWebSocketClientConnectedFn,
97
+ TWebSocketClientDisconnectedFn,
98
+ TWebSocketMessageHandler,
99
+ TWebSocketOutboundTransformer,
100
+ TWebSocketHandshakeFn,
101
+ IWebSocketServerOptions,
102
+ IBunWebSocketConfig,
103
+ ValueOrPromise,
104
+ } from '@venizia/ignis-helpers';
105
+
106
+ export class Application extends BaseApplication {
107
+ private redisHelper: RedisHelper;
108
+
109
+ preConfigure(): ValueOrPromise<void> {
110
+ this.setupWebSocket();
111
+ // ... other setup
112
+ }
113
+
114
+ setupWebSocket() {
115
+ // 1. Redis connection (required for cross-instance messaging)
116
+ this.redisHelper = new RedisHelper({
117
+ name: 'websocket-redis',
118
+ host: process.env.REDIS_HOST ?? 'localhost',
119
+ port: +(process.env.REDIS_PORT ?? 6379),
120
+ password: process.env.REDIS_PASSWORD,
121
+ autoConnect: false,
122
+ });
123
+
124
+ this.bind<RedisHelper>({
125
+ key: WebSocketBindingKeys.REDIS_CONNECTION,
126
+ }).toValue(this.redisHelper);
127
+
128
+ // 2. Authentication handler (required)
129
+ const authenticateFn: TWebSocketAuthenticateFn = async (data) => {
130
+ const token = data.token as string;
131
+ if (!token) return null;
132
+
133
+ const user = await verifyJWT(token);
134
+ if (!user) return null;
135
+
136
+ return { userId: user.id, metadata: { role: user.role } };
137
+ };
138
+
139
+ this.bind<TWebSocketAuthenticateFn>({
140
+ key: WebSocketBindingKeys.AUTHENTICATE_HANDLER,
141
+ }).toValue(authenticateFn);
142
+
143
+ // 3. Room validation handler (optional -- joins rejected without this)
144
+ const validateRoomFn: TWebSocketValidateRoomFn = ({ clientId, userId, rooms }) => {
145
+ return rooms.filter(room => room.startsWith('public-'));
146
+ };
147
+
148
+ this.bind<TWebSocketValidateRoomFn>({
149
+ key: WebSocketBindingKeys.VALIDATE_ROOM_HANDLER,
150
+ }).toValue(validateRoomFn);
151
+
152
+ // 4. Client connected handler (optional)
153
+ const clientConnectedFn: TWebSocketClientConnectedFn = ({ clientId, userId }) => {
154
+ console.log('Client connected:', clientId, userId);
155
+ };
156
+
157
+ this.bind<TWebSocketClientConnectedFn>({
158
+ key: WebSocketBindingKeys.CLIENT_CONNECTED_HANDLER,
159
+ }).toValue(clientConnectedFn);
160
+
161
+ // 5. Client disconnected handler (optional)
162
+ const clientDisconnectedFn: TWebSocketClientDisconnectedFn = ({ clientId, userId }) => {
163
+ console.log('Client disconnected:', clientId, userId);
164
+ };
165
+
166
+ this.bind<TWebSocketClientDisconnectedFn>({
167
+ key: WebSocketBindingKeys.CLIENT_DISCONNECTED_HANDLER,
168
+ }).toValue(clientDisconnectedFn);
169
+
170
+ // 6. Message handler (optional -- for custom events)
171
+ const messageHandler: TWebSocketMessageHandler = ({ clientId, userId, message }) => {
172
+ console.log('Custom event:', message.event, message.data);
173
+ };
174
+
175
+ this.bind<TWebSocketMessageHandler>({
176
+ key: WebSocketBindingKeys.MESSAGE_HANDLER,
177
+ }).toValue(messageHandler);
178
+
179
+ // 7. Outbound transformer (optional -- for per-client encryption)
180
+ const outboundTransformer: TWebSocketOutboundTransformer = async ({ client, event, data }) => {
181
+ if (!client.encrypted) return null;
182
+ // Encrypt using client's derived AES key (from ECDH handshake)
183
+ const encrypted = await encryptForClient(client.id, JSON.stringify({ event, data }));
184
+ return { event: 'encrypted', data: encrypted };
185
+ };
186
+
187
+ this.bind<TWebSocketOutboundTransformer>({
188
+ key: WebSocketBindingKeys.OUTBOUND_TRANSFORMER,
189
+ }).toValue(outboundTransformer);
190
+
191
+ // 8. Handshake handler (optional -- required when requireEncryption is true)
192
+ const handshakeFn: TWebSocketHandshakeFn = async ({ clientId, data }) => {
193
+ const clientPubKey = data.publicKey as string;
194
+ if (!clientPubKey) return null; // Reject -- no public key provided
195
+ const salt = crypto.getRandomValues(new Uint8Array(32));
196
+ const saltB64 = Buffer.from(salt).toString('base64');
197
+ const aesKey = await deriveSharedSecret(clientPubKey, salt);
198
+ storeClientKey(clientId, aesKey);
199
+ return { serverPublicKey: serverPublicKeyB64, salt: saltB64 };
200
+ };
201
+
202
+ this.bind<TWebSocketHandshakeFn>({
203
+ key: WebSocketBindingKeys.HANDSHAKE_HANDLER,
204
+ }).toValue(handshakeFn);
205
+
206
+ // 9. Server options (optional -- customize defaults)
207
+ this.bind<Partial<IWebSocketServerOptions>>({
208
+ key: WebSocketBindingKeys.SERVER_OPTIONS,
209
+ }).toValue({
210
+ identifier: 'my-app-websocket',
211
+ requireEncryption: true,
212
+ });
213
+
214
+ // 10. Register the component
215
+ this.component(WebSocketComponent);
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## Configuration
221
+
222
+ The core component's `IServerOptions` interface controls the WebSocket server setup. Default values come from `DEFAULT_SERVER_OPTIONS` and `WebSocketDefaults`:
223
+
224
+ ```typescript
225
+ {
226
+ identifier: 'WEBSOCKET_SERVER',
227
+ path: '/ws', // WebSocketDefaults.PATH
228
+ defaultRooms: [ // Joined automatically after auth
229
+ 'ws-default', // WebSocketDefaults.ROOM
230
+ 'ws-notification', // WebSocketDefaults.NOTIFICATION_ROOM
231
+ ],
232
+ heartbeatInterval: 30000, // 30 seconds (WebSocketDefaults.HEARTBEAT_INTERVAL)
233
+ heartbeatTimeout: 90000, // 90 seconds (WebSocketDefaults.HEARTBEAT_TIMEOUT)
234
+ requireEncryption: false,
235
+ serverOptions: { // Bun native WebSocket config (IBunWebSocketConfig)
236
+ sendPings: true, // WebSocketDefaults.SEND_PINGS
237
+ idleTimeout: 60, // WebSocketDefaults.IDLE_TIMEOUT (seconds)
238
+ maxPayloadLength: 131072, // WebSocketDefaults.MAX_PAYLOAD_LENGTH (128 KB)
239
+ },
240
+ }
241
+ ```
242
+
243
+ To customize options, bind a partial options object before registering the component:
244
+
245
+ #### Custom Server Options Example
246
+ ```typescript
247
+ import { WebSocketBindingKeys } from '@venizia/ignis';
248
+
249
+ this.bind({
250
+ key: WebSocketBindingKeys.SERVER_OPTIONS,
251
+ }).toValue({
252
+ identifier: 'my-app-websocket',
253
+ path: '/realtime',
254
+ defaultRooms: ['general', 'announcements'], // Override default rooms
255
+ heartbeatInterval: 20000, // More frequent heartbeats
256
+ heartbeatTimeout: 60000, // Shorter timeout
257
+ requireEncryption: true, // Require ECDH handshake
258
+ serverOptions: {
259
+ maxPayloadLength: 2097152, // 2 MB max payload
260
+ backpressureLimit: 2097152, // 2 MB backpressure limit
261
+ },
262
+ });
263
+ ```
264
+
265
+ > [!NOTE]
266
+ > `authTimeout` and `encryptedBatchLimit` are properties of the helper's `IWebSocketServerOptions`, not the core component's `IServerOptions`. The component uses the helper defaults for those (`5000` ms and `10` respectively). If you need to customize them, you must set them on the helper directly (not via binding keys).
267
+
268
+ ### `WebSocketDefaults` Constants
269
+
270
+ All tunable defaults are defined in the `WebSocketDefaults` class. The helper falls back to these when no explicit value is provided.
271
+
272
+ | Constant | Value | Description |
273
+ |----------|-------|-------------|
274
+ | `PATH` | `'/ws'` | Default WebSocket endpoint path |
275
+ | `ROOM` | `'ws-default'` | Default room name |
276
+ | `NOTIFICATION_ROOM` | `'ws-notification'` | Default notification room name |
277
+ | `BROADCAST_TOPIC` | `'ws:internal:broadcast'` | Internal Bun pub/sub broadcast topic |
278
+ | `MAX_PAYLOAD_LENGTH` | `131072` (128 KB) | Maximum message payload size |
279
+ | `IDLE_TIMEOUT` | `60` | Bun idle timeout in seconds |
280
+ | `BACKPRESSURE_LIMIT` | `1048576` (1 MB) | Bun backpressure limit |
281
+ | `SEND_PINGS` | `true` | Enable WebSocket pings |
282
+ | `PUBLISH_TO_SELF` | `false` | Whether server receives its own publishes |
283
+ | `AUTH_TIMEOUT` | `5000` (5 s) | Time to authenticate before disconnect |
284
+ | `HEARTBEAT_INTERVAL` | `30000` (30 s) | Interval between heartbeat sweeps |
285
+ | `HEARTBEAT_TIMEOUT` | `90000` (90 s) | Disconnect after 3 missed heartbeats |
286
+ | `ENCRYPTED_BATCH_LIMIT` | `10` | Max concurrent encryption operations |
287
+
288
+ > [!TIP]
289
+ > `MAX_PAYLOAD_LENGTH`, `IDLE_TIMEOUT`, `BACKPRESSURE_LIMIT`, `SEND_PINGS`, and `PUBLISH_TO_SELF` are Bun-native WebSocket settings passed via `serverOptions` inside `IServerOptions`. The rest are application-level settings on `IWebSocketServerOptions` (the helper constructor options).
290
+
291
+ #### Full `IBunWebSocketConfig` Interface
292
+ ```typescript
293
+ /** Bun WebSocket native configuration options */
294
+ interface IBunWebSocketConfig {
295
+ perMessageDeflate?: boolean;
296
+ maxPayloadLength?: number; // Default: 128 KB (131072)
297
+ idleTimeout?: number; // Default: 60 s
298
+ backpressureLimit?: number; // Default: 1 MB (1048576)
299
+ closeOnBackpressureLimit?: boolean;
300
+ sendPings?: boolean; // Default: true
301
+ publishToSelf?: boolean; // Default: false
302
+ }
303
+ ```
304
+
305
+ These options are passed directly to Bun's native WebSocket handler. Set them via `serverOptions` inside the options bound to `WebSocketBindingKeys.SERVER_OPTIONS`.
306
+
307
+ #### Full `IServerOptions` Interface (Core Component)
308
+ ```typescript
309
+ interface IServerOptions {
310
+ identifier: string; // Default: 'WEBSOCKET_SERVER'
311
+ path?: string; // Default: '/ws' (from WebSocketDefaults.PATH)
312
+ defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
313
+ serverOptions?: IBunWebSocketConfig; // Bun native WebSocket config
314
+ heartbeatInterval?: number; // Default: 30000 (30 s)
315
+ heartbeatTimeout?: number; // Default: 90000 (90 s)
316
+ requireEncryption?: boolean; // Default: false
317
+ }
318
+ ```
319
+
320
+ > [!NOTE]
321
+ > `IServerOptions` is the **core component's** options type. It is a subset of the helper's `IWebSocketServerOptions`, which additionally includes `server`, `redisConnection`, callback functions, `authTimeout`, and `encryptedBatchLimit`. The component fills in those extra fields from the DI container before constructing the helper.
322
+
323
+ ## Binding Keys
324
+
325
+ | Binding Key | Constant | Type | Required | Default |
326
+ |------------|----------|------|----------|---------|
327
+ | `@app/websocket/server-options` | `WebSocketBindingKeys.SERVER_OPTIONS` | `Partial<IServerOptions>` | No | See [Configuration](#configuration) |
328
+ | `@app/websocket/redis-connection` | `WebSocketBindingKeys.REDIS_CONNECTION` | `DefaultRedisHelper` | **Yes** | `null` |
329
+ | `@app/websocket/authenticate-handler` | `WebSocketBindingKeys.AUTHENTICATE_HANDLER` | `TWebSocketAuthenticateFn` | **Yes** | `null` |
330
+ | `@app/websocket/validate-room-handler` | `WebSocketBindingKeys.VALIDATE_ROOM_HANDLER` | `TWebSocketValidateRoomFn` | No | `null` |
331
+ | `@app/websocket/client-connected-handler` | `WebSocketBindingKeys.CLIENT_CONNECTED_HANDLER` | `TWebSocketClientConnectedFn` | No | `null` |
332
+ | `@app/websocket/client-disconnected-handler` | `WebSocketBindingKeys.CLIENT_DISCONNECTED_HANDLER` | `TWebSocketClientDisconnectedFn` | No | `null` |
333
+ | `@app/websocket/message-handler` | `WebSocketBindingKeys.MESSAGE_HANDLER` | `TWebSocketMessageHandler` | No | `null` |
334
+ | `@app/websocket/outbound-transformer` | `WebSocketBindingKeys.OUTBOUND_TRANSFORMER` | `TWebSocketOutboundTransformer` | No | `null` |
335
+ | `@app/websocket/handshake-handler` | `WebSocketBindingKeys.HANDSHAKE_HANDLER` | `TWebSocketHandshakeFn` | No* | `null` |
336
+ | `@app/websocket/instance` | `WebSocketBindingKeys.WEBSOCKET_INSTANCE` | `WebSocketServerHelper` | -- | *Set by component* |
337
+
338
+ > [!NOTE]
339
+ > `HANDSHAKE_HANDLER` is required when `IServerOptions.requireEncryption` is `true`. It performs ECDH key exchange during authentication.
340
+
341
+ > [!NOTE]
342
+ > `WEBSOCKET_INSTANCE` is **not** set by you -- the component creates and binds it automatically after the server starts. Inject it in services/controllers to interact with WebSocket.
343
+
344
+ ### Callback Type Signatures
345
+
346
+ | Binding Key | Callback Type | Required | Description |
347
+ |-------------|--------------|----------|-------------|
348
+ | `AUTHENTICATE_HANDLER` | `TWebSocketAuthenticateFn` | **Yes** | Returns <code v-pre>{ userId, metadata }</code> or `null`/`false` to reject |
349
+ | `VALIDATE_ROOM_HANDLER` | `TWebSocketValidateRoomFn` | No | Filters requested rooms, returns allowed rooms |
350
+ | `CLIENT_CONNECTED_HANDLER` | `TWebSocketClientConnectedFn` | No | Called after successful authentication |
351
+ | `CLIENT_DISCONNECTED_HANDLER` | `TWebSocketClientDisconnectedFn` | No | Called on disconnect (after cleanup) |
352
+ | `MESSAGE_HANDLER` | `TWebSocketMessageHandler` | No | Handles non-system messages from authenticated clients |
353
+ | `OUTBOUND_TRANSFORMER` | `TWebSocketOutboundTransformer` | No | Transforms outbound messages (e.g., per-client encryption) |
354
+ | `HANDSHAKE_HANDLER` | `TWebSocketHandshakeFn` | When `requireEncryption: true` | Returns <code v-pre>{ serverPublicKey, salt }</code> or `null`/`false` to reject |
355
+
356
+ #### `TWebSocketAuthenticateFn`
357
+ ```typescript
358
+ type TWebSocketAuthenticateFn<
359
+ AuthDataType extends Record<string, unknown> = Record<string, unknown>,
360
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
361
+ > = (
362
+ opts: AuthDataType,
363
+ ) => ValueOrPromise<{ userId?: string; metadata?: MetadataType } | null | false>;
364
+ ```
365
+
366
+ Receives the `data` field from the client's `authenticate` event. Return <code v-pre>{ userId, metadata }</code> on success, or `null`/`false` to reject (closes with code `4003`).
367
+
368
+ #### `TWebSocketValidateRoomFn`
369
+ ```typescript
370
+ type TWebSocketValidateRoomFn = (opts: {
371
+ clientId: string;
372
+ userId?: string;
373
+ rooms: string[];
374
+ }) => ValueOrPromise<string[]>;
375
+ ```
376
+
377
+ Called when a client sends a `join` event. Receives the sanitized room list (internal `ws:` prefix rooms are already filtered out). Return the subset of rooms the client is allowed to join.
378
+
379
+ > [!WARNING]
380
+ > If no `validateRoomFn` is bound, **all join requests are rejected**. You must bind this handler if you want clients to join custom rooms.
381
+
382
+ #### `TWebSocketClientConnectedFn`
383
+ ```typescript
384
+ type TWebSocketClientConnectedFn<
385
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
386
+ > = (opts: {
387
+ clientId: string;
388
+ userId?: string;
389
+ metadata?: MetadataType;
390
+ }) => ValueOrPromise<void>;
391
+ ```
392
+
393
+ Called after a client has been fully authenticated, joined default rooms, and received the `connected` event. Errors thrown here are caught and logged -- they do not disconnect the client.
394
+
395
+ #### `TWebSocketClientDisconnectedFn`
396
+ ```typescript
397
+ type TWebSocketClientDisconnectedFn = (opts: {
398
+ clientId: string;
399
+ userId?: string;
400
+ }) => ValueOrPromise<void>;
401
+ ```
402
+
403
+ Called after internal cleanup (auth timer cleared, removed from user/room indexes, removed from clients map). Errors thrown here are caught and logged.
404
+
405
+ #### `TWebSocketMessageHandler`
406
+ ```typescript
407
+ type TWebSocketMessageHandler = (opts: {
408
+ clientId: string;
409
+ userId?: string;
410
+ message: IWebSocketMessage;
411
+ }) => ValueOrPromise<void>;
412
+ ```
413
+
414
+ Called for any message from an authenticated client whose `event` is not a system event (`authenticate`, `connected`, `disconnect`, `join`, `leave`, `error`, `heartbeat`, `encrypted`). If no handler is bound, non-system messages are silently dropped.
415
+
416
+ #### `TWebSocketOutboundTransformer`
417
+ ```typescript
418
+ type TWebSocketOutboundTransformer<
419
+ DataType = unknown,
420
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
421
+ > = (opts: {
422
+ client: IWebSocketClient<MetadataType>;
423
+ event: string;
424
+ data: DataType;
425
+ }) => ValueOrPromise<TNullable<{ event: string; data: DataType }>>;
426
+ ```
427
+
428
+ Intercepts every outbound message **to encrypted clients only** before `socket.send()`. Return `null` to send the original <code v-pre>{ event, data }</code> unchanged, or return a transformed <code v-pre>{ event, data }</code> (e.g., <code v-pre>{ event: 'encrypted', data: ciphertext }</code>).
429
+
430
+ > [!NOTE]
431
+ > The transformer is only called for clients where `client.encrypted === true`. Non-encrypted clients bypass this entirely (zero overhead).
432
+
433
+ #### `TWebSocketHandshakeFn`
434
+ ```typescript
435
+ type TWebSocketHandshakeFn<
436
+ AuthDataType extends Record<string, unknown> = Record<string, unknown>,
437
+ > = (opts: {
438
+ clientId: string;
439
+ userId?: string;
440
+ data: AuthDataType;
441
+ }) => ValueOrPromise<{ serverPublicKey: string; salt: string } | null | false>;
442
+ ```
443
+
444
+ Called during authentication when `requireEncryption` is `true`. Receives the same `data` payload as `authenticateFn`. Return <code v-pre>{ serverPublicKey, salt }</code> on success -- these are included in the `connected` event sent to the client. Return `null`/`false` to reject (closes with code `4004`).
445
+
446
+ ## See Also
447
+
448
+ - [Usage & Examples](./usage) - Server-side usage, emitter, wire protocol, client tracking, and delivery strategy
449
+ - [API Reference](./api) - Architecture, WebSocketEmitter API, and internals
450
+ - [Error Reference](./errors) - Error conditions table and troubleshooting
451
+ - [WebSocketServerHelper](/references/helpers/websocket/) - Helper API documentation
452
+ - [Socket.IO Component](../socket-io/) - Node.js-compatible alternative with Socket.IO
453
+ - [Bun WebSocket Documentation](https://bun.sh/docs/api/websockets) - Official Bun WebSocket API reference