@venizia/ignis-docs 0.0.5 → 0.0.6-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.
Files changed (123) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architectural-patterns.md +0 -2
  3. package/wiki/best-practices/architecture-decisions.md +0 -8
  4. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  5. package/wiki/best-practices/code-style-standards/index.md +0 -1
  6. package/wiki/best-practices/code-style-standards/tooling.md +0 -3
  7. package/wiki/best-practices/contribution-workflow.md +12 -12
  8. package/wiki/best-practices/index.md +4 -14
  9. package/wiki/best-practices/performance-optimization.md +3 -3
  10. package/wiki/best-practices/security-guidelines.md +2 -2
  11. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  12. package/wiki/guides/core-concepts/application/bootstrapping.md +6 -7
  13. package/wiki/guides/core-concepts/components-guide.md +1 -1
  14. package/wiki/guides/core-concepts/components.md +2 -2
  15. package/wiki/guides/core-concepts/dependency-injection.md +4 -5
  16. package/wiki/guides/core-concepts/persistent/datasources.md +4 -5
  17. package/wiki/guides/core-concepts/services.md +1 -1
  18. package/wiki/guides/get-started/5-minute-quickstart.md +4 -5
  19. package/wiki/guides/get-started/philosophy.md +12 -24
  20. package/wiki/guides/index.md +2 -9
  21. package/wiki/guides/reference/mcp-docs-server.md +13 -13
  22. package/wiki/guides/tutorials/building-a-crud-api.md +10 -10
  23. package/wiki/guides/tutorials/complete-installation.md +11 -12
  24. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  25. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  26. package/wiki/guides/tutorials/testing.md +4 -5
  27. package/wiki/index.md +8 -14
  28. package/wiki/references/base/bootstrapping.md +0 -3
  29. package/wiki/references/base/components.md +2 -2
  30. package/wiki/references/base/controllers.md +0 -1
  31. package/wiki/references/base/datasources.md +1 -1
  32. package/wiki/references/base/dependency-injection.md +2 -2
  33. package/wiki/references/base/filter-system/default-filter.md +2 -3
  34. package/wiki/references/base/filter-system/index.md +1 -1
  35. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  36. package/wiki/references/base/middlewares.md +0 -8
  37. package/wiki/references/base/providers.md +0 -9
  38. package/wiki/references/base/repositories/advanced.md +1 -1
  39. package/wiki/references/base/repositories/mixins.md +2 -3
  40. package/wiki/references/base/services.md +0 -1
  41. package/wiki/references/components/authentication/api.md +444 -0
  42. package/wiki/references/components/authentication/errors.md +177 -0
  43. package/wiki/references/components/authentication/index.md +571 -0
  44. package/wiki/references/components/authentication/usage.md +781 -0
  45. package/wiki/references/components/health-check.md +292 -103
  46. package/wiki/references/components/index.md +14 -12
  47. package/wiki/references/components/mail/api.md +505 -0
  48. package/wiki/references/components/mail/errors.md +176 -0
  49. package/wiki/references/components/mail/index.md +535 -0
  50. package/wiki/references/components/mail/usage.md +404 -0
  51. package/wiki/references/components/request-tracker.md +229 -25
  52. package/wiki/references/components/socket-io/api.md +1051 -0
  53. package/wiki/references/components/socket-io/errors.md +119 -0
  54. package/wiki/references/components/socket-io/index.md +410 -0
  55. package/wiki/references/components/socket-io/usage.md +322 -0
  56. package/wiki/references/components/static-asset/api.md +261 -0
  57. package/wiki/references/components/static-asset/errors.md +89 -0
  58. package/wiki/references/components/static-asset/index.md +617 -0
  59. package/wiki/references/components/static-asset/usage.md +364 -0
  60. package/wiki/references/components/swagger.md +390 -110
  61. package/wiki/references/components/template/api-page.md +125 -0
  62. package/wiki/references/components/template/errors-page.md +100 -0
  63. package/wiki/references/components/template/index.md +104 -0
  64. package/wiki/references/components/template/setup-page.md +134 -0
  65. package/wiki/references/components/template/single-page.md +132 -0
  66. package/wiki/references/components/template/usage-page.md +127 -0
  67. package/wiki/references/components/websocket/api.md +508 -0
  68. package/wiki/references/components/websocket/errors.md +123 -0
  69. package/wiki/references/components/websocket/index.md +453 -0
  70. package/wiki/references/components/websocket/usage.md +475 -0
  71. package/wiki/references/helpers/cron/index.md +224 -0
  72. package/wiki/references/helpers/crypto/index.md +537 -0
  73. package/wiki/references/helpers/env/index.md +214 -0
  74. package/wiki/references/helpers/error/index.md +232 -0
  75. package/wiki/references/helpers/index.md +16 -15
  76. package/wiki/references/helpers/inversion/index.md +608 -0
  77. package/wiki/references/helpers/logger/index.md +600 -0
  78. package/wiki/references/helpers/network/api.md +986 -0
  79. package/wiki/references/helpers/network/index.md +620 -0
  80. package/wiki/references/helpers/queue/index.md +589 -0
  81. package/wiki/references/helpers/redis/index.md +495 -0
  82. package/wiki/references/helpers/socket-io/api.md +497 -0
  83. package/wiki/references/helpers/socket-io/index.md +513 -0
  84. package/wiki/references/helpers/storage/api.md +705 -0
  85. package/wiki/references/helpers/storage/index.md +583 -0
  86. package/wiki/references/helpers/template/index.md +66 -0
  87. package/wiki/references/helpers/template/single-page.md +126 -0
  88. package/wiki/references/helpers/testing/index.md +510 -0
  89. package/wiki/references/helpers/types/index.md +512 -0
  90. package/wiki/references/helpers/uid/index.md +272 -0
  91. package/wiki/references/helpers/websocket/api.md +736 -0
  92. package/wiki/references/helpers/websocket/index.md +574 -0
  93. package/wiki/references/helpers/worker-thread/index.md +470 -0
  94. package/wiki/references/index.md +2 -9
  95. package/wiki/references/quick-reference.md +3 -18
  96. package/wiki/references/utilities/jsx.md +1 -8
  97. package/wiki/references/utilities/statuses.md +0 -7
  98. package/wiki/references/components/authentication.md +0 -476
  99. package/wiki/references/components/mail.md +0 -687
  100. package/wiki/references/components/socket-io.md +0 -562
  101. package/wiki/references/components/static-asset.md +0 -1277
  102. package/wiki/references/helpers/cron.md +0 -108
  103. package/wiki/references/helpers/crypto.md +0 -132
  104. package/wiki/references/helpers/env.md +0 -83
  105. package/wiki/references/helpers/error.md +0 -97
  106. package/wiki/references/helpers/inversion.md +0 -176
  107. package/wiki/references/helpers/logger.md +0 -296
  108. package/wiki/references/helpers/network.md +0 -396
  109. package/wiki/references/helpers/queue.md +0 -150
  110. package/wiki/references/helpers/redis.md +0 -142
  111. package/wiki/references/helpers/socket-io.md +0 -932
  112. package/wiki/references/helpers/storage.md +0 -665
  113. package/wiki/references/helpers/testing.md +0 -133
  114. package/wiki/references/helpers/types.md +0 -167
  115. package/wiki/references/helpers/uid.md +0 -167
  116. package/wiki/references/helpers/worker-thread.md +0 -178
  117. package/wiki/references/src-details/boot.md +0 -379
  118. package/wiki/references/src-details/core.md +0 -263
  119. package/wiki/references/src-details/dev-configs.md +0 -298
  120. package/wiki/references/src-details/docs.md +0 -71
  121. package/wiki/references/src-details/helpers.md +0 -211
  122. package/wiki/references/src-details/index.md +0 -86
  123. package/wiki/references/src-details/inversion.md +0 -340
@@ -0,0 +1,736 @@
1
+ # WebSocket -- API Reference
2
+
3
+ > Architecture, method signatures, internals, and type definitions.
4
+
5
+ ## Architecture
6
+
7
+ The WebSocket helper provides two classes: `WebSocketServerHelper` for managing a Bun-native WebSocket server with Redis Pub/Sub, and `WebSocketEmitter` for publishing messages from external processes.
8
+
9
+ #### Architecture Diagram
10
+
11
+ ```
12
+ WebSocketServerHelper
13
+ +---------------------------------------------------+
14
+ | |
15
+ | constructor(opts) |
16
+ | |-- identifier, path, serverId (UUID) |
17
+ | |-- Store callbacks (auth, rooms, messages) |
18
+ | |-- Apply defaults (rooms, timeouts) |
19
+ | +-- initRedisClients(redisConnection) |
20
+ | +-- redisPub = client.duplicate() |
21
+ | +-- redisSub = client.duplicate() |
22
+ | |
23
+ | configure() [async] |
24
+ | |-- Connect Redis clients (if lazyConnect) |
25
+ | |-- await Redis ready (pub + sub) |
26
+ | |-- setupRedisSubscriptions() |
27
+ | | |-- subscribe(ws:broadcast) |
28
+ | | |-- psubscribe(ws:room:*) |
29
+ | | |-- psubscribe(ws:client:*) |
30
+ | | +-- psubscribe(ws:user:*) |
31
+ | +-- startHeartbeatTimer() |
32
+ | |
33
+ | getBunWebSocketHandler() |
34
+ | +-- Returns { open, message, close, drain, |
35
+ | ...serverOptions } |
36
+ | |
37
+ +---------------------------------------------------+
38
+
39
+ WebSocketEmitter
40
+ +---------------------------------------------------+
41
+ | constructor(opts) |
42
+ | +-- redisPub = client.duplicate() |
43
+ | |
44
+ | configure() [async] |
45
+ | +-- await Redis ready |
46
+ | |
47
+ | toClient / toUser / toRoom / broadcast |
48
+ | +-- publish(channel, IRedisSocketMessage) |
49
+ +---------------------------------------------------+
50
+ ```
51
+
52
+ #### Client Connection Lifecycle
53
+
54
+ ```
55
+ Client connects via WebSocket upgrade
56
+ |
57
+ +-- onClientConnect({ clientId, socket })
58
+ | +-- Create IWebSocketClient entry (state: UNAUTHORIZED)
59
+ | +-- Subscribe to clientId topic (Bun native pub/sub)
60
+ | +-- Start auth timeout (authTimeout ms)
61
+ |
62
+ +-- Client sends { event: 'authenticate', data: { ... } }
63
+ | +-- handleAuthenticate()
64
+ | +-- Set state: AUTHENTICATING
65
+ | +-- Extended timeout (authTimeout * 3) for async auth
66
+ | +-- Call authenticateFn(data)
67
+ | +-- Success:
68
+ | | +-- Set state: AUTHENTICATED
69
+ | | +-- [requireEncryption?] -> handshakeFn() -> enableClientEncryption()
70
+ | | +-- Index by userId
71
+ | | +-- Subscribe to broadcast topic (unless encrypted)
72
+ | | +-- Join default rooms + clientId room
73
+ | | +-- Send 'connected' event
74
+ | | +-- Call clientConnectedFn()
75
+ | +-- Failure:
76
+ | +-- Send 'error' event
77
+ | +-- Close with code 4003
78
+ |
79
+ +-- Auth timeout expires (if still UNAUTHORIZED)
80
+ | +-- Close with code 4001
81
+ |
82
+ +-- Heartbeat sweep (every heartbeatInterval)
83
+ | +-- If now - lastActivity > heartbeatTimeout
84
+ | +-- Close with code 4002
85
+ |
86
+ +-- Client disconnects
87
+ +-- onClientDisconnect()
88
+ +-- Clear auth timer
89
+ +-- Remove from user index
90
+ +-- Remove from all rooms
91
+ +-- Remove from clients map
92
+ +-- Call clientDisconnectedFn()
93
+ ```
94
+
95
+ #### Redis 2-Client Architecture
96
+
97
+ ```
98
+ RedisHelper (parent -- NOT consumed)
99
+ |
100
+ +-- client.duplicate() --> redisPub (publishes cross-instance messages)
101
+ |
102
+ +-- client.duplicate() --> redisSub (subscribes to cross-instance messages)
103
+ ```
104
+
105
+ Both single-instance `Redis` and `Cluster` connections from ioredis are supported. The parent `RedisHelper` connection remains independent.
106
+
107
+ ## Server API
108
+
109
+ ### `WebSocketServerHelper` Constructor
110
+
111
+ ```typescript
112
+ constructor(opts: IWebSocketServerOptions<AuthDataType, MetadataType>)
113
+ ```
114
+
115
+ Creates the server helper, generates a unique `serverId` (UUID), stores all options with defaults, and initializes two Redis client duplicates. Throws if `redisConnection` is falsy.
116
+
117
+ ### `configure()`
118
+
119
+ ```typescript
120
+ configure(): Promise<void>
121
+ ```
122
+
123
+ Initializes Redis connections, sets up pub/sub subscriptions, and starts the heartbeat timer. Must be called after construction and before accepting connections.
124
+
125
+ #### Internal Flow
126
+
127
+ 1. Register error handlers on `redisPub` and `redisSub`
128
+ 2. Connect duplicated clients if status is `'wait'` (lazyConnect mode)
129
+ 3. `await Promise.all([waitForRedisReady(pub), waitForRedisReady(sub)])`
130
+ 4. Set up Redis subscriptions (direct + pattern subscribe)
131
+ 5. Start heartbeat timer via `setInterval(heartbeatAll, heartbeatInterval)`
132
+
133
+ ### `getBunWebSocketHandler()`
134
+
135
+ ```typescript
136
+ getBunWebSocketHandler(): IBunWebSocketHandler
137
+ ```
138
+
139
+ Returns the Bun WebSocket handler object containing lifecycle callbacks and native configuration. Pass this to `server.reload({ websocket })`.
140
+
141
+ #### Lifecycle Callbacks
142
+
143
+ | Callback | When | Behavior |
144
+ |----------|------|----------|
145
+ | `open` | WebSocket connection established | Extracts `clientId` from `socket.data`, calls `onClientConnect()` |
146
+ | `message` | Message received | Updates `lastActivity`, calls `onClientMessage()` for routing |
147
+ | `close` | Connection closed | Calls `onClientDisconnect()` for cleanup |
148
+ | `drain` | Backpressure cleared | Sets `client.backpressured = false` |
149
+
150
+ #### Bun Native Configuration
151
+
152
+ These values are spread from `serverOptions` into the returned handler:
153
+
154
+ | Option | Type | Default | Description |
155
+ |--------|------|---------|-------------|
156
+ | `perMessageDeflate` | `boolean` | `undefined` | Enable per-message compression |
157
+ | `maxPayloadLength` | `number` | `131072` (128KB) | Maximum incoming message size in bytes |
158
+ | `idleTimeout` | `number` | `60` (seconds) | Bun-level idle timeout (transport layer) |
159
+ | `backpressureLimit` | `number` | `1048576` (1MB) | Backpressure threshold in bytes |
160
+ | `closeOnBackpressureLimit` | `boolean` | `undefined` | Close socket when backpressure limit is exceeded |
161
+ | `sendPings` | `boolean` | `true` | Enable Bun transport-level pings |
162
+ | `publishToSelf` | `boolean` | `false` | Whether `server.publish()` delivers to the publishing socket |
163
+
164
+ ### `getPath()`
165
+
166
+ ```typescript
167
+ getPath(): string
168
+ ```
169
+
170
+ Returns the configured WebSocket path (default: `'/ws'`).
171
+
172
+ ### `getClients()`
173
+
174
+ ```typescript
175
+ getClients(opts?: { id?: string }):
176
+ | IWebSocketClient<MetadataType>
177
+ | Map<string, IWebSocketClient<MetadataType>>
178
+ | undefined
179
+ ```
180
+
181
+ When called without arguments or with an empty opts, returns the full `Map<string, IWebSocketClient>`. When called with `{ id }`, returns the specific client entry or `undefined`.
182
+
183
+ ### `getClientsByUser()`
184
+
185
+ ```typescript
186
+ getClientsByUser(opts: { userId: string }): IWebSocketClient<MetadataType>[]
187
+ ```
188
+
189
+ Returns all clients belonging to the given user ID. Returns an empty array if the user has no active connections.
190
+
191
+ ### `getClientsByRoom()`
192
+
193
+ ```typescript
194
+ getClientsByRoom(opts: { room: string }): IWebSocketClient<MetadataType>[]
195
+ ```
196
+
197
+ Returns all clients in the given room. Returns an empty array if the room does not exist or is empty.
198
+
199
+ ### `onClientConnect()`
200
+
201
+ ```typescript
202
+ onClientConnect(opts: { clientId: string; socket: IWebSocket }): void
203
+ ```
204
+
205
+ Handles a new WebSocket connection. Creates an `IWebSocketClient` entry with state `UNAUTHORIZED`, subscribes the socket to its own `clientId` topic (Bun native pub/sub), and starts the authentication timeout. Returns early if the client ID already exists.
206
+
207
+ ### `onClientMessage()`
208
+
209
+ ```typescript
210
+ onClientMessage(opts: { clientId: string; raw: string }): void
211
+ ```
212
+
213
+ Routes incoming messages. Parses JSON, then:
214
+
215
+ - `heartbeat` events: silently consumed (updates `lastActivity` via the `message` callback)
216
+ - `authenticate` events: delegates to `handleAuthenticate()`
217
+ - Unauthenticated clients sending non-auth events: receives an `error` event (`'Not authenticated'`)
218
+ - `join` / `leave` events: delegates to room handlers
219
+ - All other events: delegates to `messageHandler` (if configured)
220
+
221
+ Sends an `error` event (`'Invalid message format'`) if JSON parsing fails.
222
+
223
+ ### `onClientDisconnect()`
224
+
225
+ ```typescript
226
+ onClientDisconnect(opts: { clientId: string }): void
227
+ ```
228
+
229
+ Cleans up a disconnected client:
230
+
231
+ 1. Clears auth timeout if pending
232
+ 2. Removes from user index
233
+ 3. Removes from all rooms
234
+ 4. Removes from clients map
235
+ 5. Invokes `clientDisconnectedFn` callback
236
+
237
+ ### `joinRoom()`
238
+
239
+ ```typescript
240
+ joinRoom(opts: { clientId: string; room: string }): void
241
+ ```
242
+
243
+ Programmatically joins a client to a room. Adds to the room index, adds to the client's room set, and subscribes the socket to the room's Bun native pub/sub topic (unless the client has encryption enabled).
244
+
245
+ ### `leaveRoom()`
246
+
247
+ ```typescript
248
+ leaveRoom(opts: { clientId: string; room: string }): void
249
+ ```
250
+
251
+ Removes a client from a room. Removes from the room index, removes from the client's room set, and unsubscribes the socket from the Bun native pub/sub topic.
252
+
253
+ ### `enableClientEncryption()`
254
+
255
+ ```typescript
256
+ enableClientEncryption(opts: { clientId: string }): void
257
+ ```
258
+
259
+ Enables encryption for a client. Unsubscribes the client from all Bun native pub/sub topics (broadcast topic + all rooms) so `server.publish()` will not reach them. Messages are instead delivered individually through the `outboundTransformer`. No-op if the client is already encrypted or does not exist.
260
+
261
+ > [!WARNING]
262
+ > This is **irreversible** for the lifetime of the connection. Once encrypted, the client cannot be switched back to Bun native pub/sub delivery.
263
+
264
+ ### `sendToClient()`
265
+
266
+ ```typescript
267
+ sendToClient(opts: {
268
+ clientId: string;
269
+ event: string;
270
+ data: unknown;
271
+ doLog?: boolean;
272
+ }): void
273
+ ```
274
+
275
+ Sends a message to a specific client (local delivery only). If the client has encryption enabled and an `outboundTransformer` is configured, the transformer runs before delivery. Otherwise, sends the raw `{ event, data }` JSON.
276
+
277
+ ### `sendToUser()`
278
+
279
+ ```typescript
280
+ sendToUser(opts: {
281
+ userId: string;
282
+ event: string;
283
+ data: unknown;
284
+ }): void
285
+ ```
286
+
287
+ Sends a message to all local clients belonging to a user. Iterates the user's client set and calls `sendToClient()` for each.
288
+
289
+ ### `sendToRoom()`
290
+
291
+ ```typescript
292
+ sendToRoom(opts: {
293
+ room: string;
294
+ event: string;
295
+ data: unknown;
296
+ exclude?: string[];
297
+ }): void
298
+ ```
299
+
300
+ Sends a message to all clients in a room (local delivery only).
301
+
302
+ #### Delivery Strategy
303
+
304
+ | Condition | Strategy |
305
+ |-----------|----------|
306
+ | No `outboundTransformer`, no `exclude` | Bun native `server.publish()` -- O(1) C++ fan-out |
307
+ | `outboundTransformer` set, no `exclude` | Iterates all room clients via `executePromiseWithLimit` (max `encryptedBatchLimit` concurrent) |
308
+ | `exclude` provided | Always iterates clients individually (cannot exclude from Bun pub/sub) |
309
+
310
+ ### `broadcast()`
311
+
312
+ ```typescript
313
+ broadcast(opts: {
314
+ event: string;
315
+ data: unknown;
316
+ exclude?: string[];
317
+ }): void
318
+ ```
319
+
320
+ Sends a message to all authenticated clients on this instance (local delivery only). Delivery strategy follows the same pattern as `sendToRoom()`:
321
+
322
+ | Condition | Strategy |
323
+ |-----------|----------|
324
+ | No `outboundTransformer`, no `exclude` | Bun native `server.publish()` via broadcast topic |
325
+ | `outboundTransformer` set, no `exclude` | Iterates all authenticated clients with concurrency limit |
326
+ | `exclude` provided | Always iterates clients individually |
327
+
328
+ ### `send()`
329
+
330
+ ```typescript
331
+ send<T = unknown>(opts: {
332
+ destination?: string;
333
+ payload: { topic: string; data: T };
334
+ doLog?: boolean;
335
+ cb?: () => void;
336
+ }): void
337
+ ```
338
+
339
+ Public API for cross-instance messaging. Delivers locally **and** publishes to Redis so other server instances receive the message.
340
+
341
+ Routing logic:
342
+
343
+ | `destination` | Local delivery | Redis channel |
344
+ |---------------|----------------|---------------|
345
+ | Omitted | `broadcast()` | `ws:broadcast` |
346
+ | Matches a local client ID | `sendToClient()` | `ws:client:{clientId}` |
347
+ | Matches a local room name | `sendToRoom()` | `ws:room:{room}` |
348
+ | Neither (remote target) | None | `ws:room:{destination}` |
349
+
350
+ Silent no-op when `payload` is falsy, `payload.topic` is falsy, or `payload.data` is `undefined`.
351
+
352
+ If `cb` is provided, it is executed asynchronously via `setTimeout(cb, 0)`.
353
+
354
+ ### `shutdown()`
355
+
356
+ ```typescript
357
+ shutdown(): Promise<void>
358
+ ```
359
+
360
+ Graceful shutdown:
361
+
362
+ 1. Clear heartbeat timer
363
+ 2. Close all client sockets with code `1001` (`'Server shutting down'`)
364
+ 3. Trigger disconnect callbacks for all tracked clients
365
+ 4. Clear `clients`, `users`, and `rooms` maps
366
+ 5. `await Promise.all([redisPub.quit(), redisSub.quit()])`
367
+
368
+ ## Emitter API
369
+
370
+ ### `WebSocketEmitter` Constructor
371
+
372
+ ```typescript
373
+ constructor(opts: IWebSocketEmitterOptions)
374
+ ```
375
+
376
+ Creates the emitter, duplicates one Redis client from `redisConnection`. Throws if `redisConnection` is falsy.
377
+
378
+ ### `configure()`
379
+
380
+ ```typescript
381
+ configure(): Promise<void>
382
+ ```
383
+
384
+ Connects the Redis client (if in `'wait'` status) and waits for it to reach `ready` status. Must be called before emitting.
385
+
386
+ ### `toClient()`
387
+
388
+ ```typescript
389
+ toClient(opts: {
390
+ clientId: string;
391
+ event: string;
392
+ data: unknown;
393
+ }): Promise<void>
394
+ ```
395
+
396
+ Publishes a message to the `ws:client:{clientId}` Redis channel. All server instances subscribed via `psubscribe('ws:client:*')` will deliver it to the target client if connected locally.
397
+
398
+ ### `toUser()`
399
+
400
+ ```typescript
401
+ toUser(opts: {
402
+ userId: string;
403
+ event: string;
404
+ data: unknown;
405
+ }): Promise<void>
406
+ ```
407
+
408
+ Publishes a message to the `ws:user:{userId}` Redis channel. All server instances deliver it to every session belonging to that user.
409
+
410
+ ### `toRoom()`
411
+
412
+ ```typescript
413
+ toRoom(opts: {
414
+ room: string;
415
+ event: string;
416
+ data: unknown;
417
+ exclude?: string[];
418
+ }): Promise<void>
419
+ ```
420
+
421
+ Publishes a message to the `ws:room:{room}` Redis channel. All server instances deliver it to every client in that room.
422
+
423
+ ### `broadcast()`
424
+
425
+ ```typescript
426
+ broadcast(opts: {
427
+ event: string;
428
+ data: unknown;
429
+ }): Promise<void>
430
+ ```
431
+
432
+ Publishes a message to the `ws:broadcast` Redis channel. All server instances deliver it to every authenticated client.
433
+
434
+ ### `shutdown()`
435
+
436
+ ```typescript
437
+ shutdown(): Promise<void>
438
+ ```
439
+
440
+ Quits the Redis connection.
441
+
442
+ ## Types Reference
443
+
444
+ ### Wire Protocol
445
+
446
+ ```typescript
447
+ /** Client <-> Server message envelope */
448
+ interface IWebSocketMessage<DataType = unknown> {
449
+ event: string;
450
+ data?: DataType;
451
+ id?: string;
452
+ }
453
+
454
+ /** Internal Redis Pub/Sub message envelope */
455
+ interface IRedisSocketMessage<DataType = unknown> {
456
+ serverId: string;
457
+ type: TWebSocketMessageType; // 'client' | 'user' | 'room' | 'broadcast'
458
+ target?: string;
459
+ event: string;
460
+ data: DataType;
461
+ exclude?: string[];
462
+ }
463
+ ```
464
+
465
+ ### Client Tracking
466
+
467
+ ```typescript
468
+ interface IWebSocketClient<
469
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
470
+ > {
471
+ id: string;
472
+ userId?: string;
473
+ socket: IWebSocket;
474
+ state: TWebSocketClientState;
475
+ rooms: Set<string>;
476
+ backpressured: boolean;
477
+ encrypted: boolean;
478
+ connectedAt: number;
479
+ lastActivity: number;
480
+ metadata?: MetadataType;
481
+ serverPublicKey?: string;
482
+ salt?: string;
483
+ authTimer?: ReturnType<typeof setTimeout>;
484
+ }
485
+
486
+ /** Data attached during server.upgrade() */
487
+ interface IWebSocketData<
488
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
489
+ > {
490
+ clientId: string;
491
+ userId?: string;
492
+ metadata?: MetadataType;
493
+ }
494
+ ```
495
+
496
+ ### Bun Interfaces
497
+
498
+ ```typescript
499
+ /** Bun WebSocket handle (defined locally to avoid @types/bun dependency) */
500
+ interface IWebSocket<T = unknown> {
501
+ readonly data: T;
502
+ readonly remoteAddress: string;
503
+ readonly readyState: number;
504
+
505
+ send(data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean): number;
506
+ subscribe(topic: string): void;
507
+ unsubscribe(topic: string): void;
508
+ isSubscribed(topic: string): boolean;
509
+ close(code?: number, reason?: string): void;
510
+ cork(cb: (ws: IWebSocket<T>) => void): void;
511
+ }
512
+
513
+ /** Bun server interface for native pub/sub */
514
+ interface IBunServer {
515
+ readonly pendingWebSockets: number;
516
+ publish(
517
+ topic: string,
518
+ data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
519
+ compress?: boolean,
520
+ ): number;
521
+ }
522
+
523
+ /** Bun native WebSocket configuration */
524
+ interface IBunWebSocketConfig {
525
+ perMessageDeflate?: boolean;
526
+ maxPayloadLength?: number;
527
+ idleTimeout?: number;
528
+ backpressureLimit?: number;
529
+ closeOnBackpressureLimit?: boolean;
530
+ sendPings?: boolean;
531
+ publishToSelf?: boolean;
532
+ }
533
+
534
+ /** Return type of getBunWebSocketHandler() */
535
+ interface IBunWebSocketHandler extends IBunWebSocketConfig {
536
+ open: (socket: IWebSocket) => void;
537
+ message: (socket: IWebSocket, message: string | Buffer) => void;
538
+ close: (socket: IWebSocket, code: number, reason: string) => void;
539
+ drain: (socket: IWebSocket) => void;
540
+ }
541
+ ```
542
+
543
+ ### Server Options
544
+
545
+ ```typescript
546
+ interface IWebSocketServerOptions<
547
+ AuthDataType extends Record<string, unknown> = Record<string, unknown>,
548
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
549
+ > {
550
+ identifier: string;
551
+ path?: string; // Default: '/ws'
552
+ redisConnection: DefaultRedisHelper;
553
+ server: IBunServer;
554
+ defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
555
+ serverOptions?: IBunWebSocketConfig;
556
+ authTimeout?: number; // Default: 5000
557
+ heartbeatInterval?: number; // Default: 30000
558
+ heartbeatTimeout?: number; // Default: 90000
559
+ encryptedBatchLimit?: number; // Default: 10
560
+ requireEncryption?: boolean; // Default: false
561
+
562
+ authenticateFn: TWebSocketAuthenticateFn<AuthDataType, MetadataType>;
563
+ validateRoomFn?: TWebSocketValidateRoomFn;
564
+ clientConnectedFn?: TWebSocketClientConnectedFn<MetadataType>;
565
+ clientDisconnectedFn?: TWebSocketClientDisconnectedFn;
566
+ messageHandler?: TWebSocketMessageHandler;
567
+ outboundTransformer?: TWebSocketOutboundTransformer<unknown, MetadataType>;
568
+ handshakeFn?: TWebSocketHandshakeFn<AuthDataType>;
569
+ }
570
+
571
+ interface IWebSocketEmitterOptions {
572
+ identifier?: string; // Default: 'WebSocketEmitter'
573
+ redisConnection: DefaultRedisHelper;
574
+ }
575
+ ```
576
+
577
+ ### Callback Types
578
+
579
+ ```typescript
580
+ /** Authentication -- return { userId, metadata } on success, null/false to reject */
581
+ type TWebSocketAuthenticateFn<
582
+ AuthDataType extends Record<string, unknown> = Record<string, unknown>,
583
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
584
+ > = (
585
+ opts: AuthDataType,
586
+ ) => ValueOrPromise<{ userId?: string; metadata?: MetadataType } | null | false>;
587
+
588
+ /** ECDH key exchange during auth -- return { serverPublicKey, salt } or null/false */
589
+ type TWebSocketHandshakeFn<
590
+ AuthDataType extends Record<string, unknown> = Record<string, unknown>,
591
+ > = (opts: {
592
+ clientId: string;
593
+ userId?: string;
594
+ data: AuthDataType;
595
+ }) => ValueOrPromise<{ serverPublicKey: string; salt: string } | null | false>;
596
+
597
+ /** Room validation -- return the allowed subset of requested rooms */
598
+ type TWebSocketValidateRoomFn = (opts: {
599
+ clientId: string;
600
+ userId?: string;
601
+ rooms: string[];
602
+ }) => ValueOrPromise<string[]>;
603
+
604
+ /** Post-authentication callback */
605
+ type TWebSocketClientConnectedFn<
606
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
607
+ > = (opts: {
608
+ clientId: string;
609
+ userId?: string;
610
+ metadata?: MetadataType;
611
+ }) => ValueOrPromise<void>;
612
+
613
+ /** Disconnect callback */
614
+ type TWebSocketClientDisconnectedFn = (opts: {
615
+ clientId: string;
616
+ userId?: string;
617
+ }) => ValueOrPromise<void>;
618
+
619
+ /** Custom event handler for non-system events from authenticated clients */
620
+ type TWebSocketMessageHandler = (opts: {
621
+ clientId: string;
622
+ userId?: string;
623
+ message: IWebSocketMessage;
624
+ }) => ValueOrPromise<void>;
625
+
626
+ /** Outbound transformer -- intercepts messages before socket.send() */
627
+ type TWebSocketOutboundTransformer<
628
+ DataType = unknown,
629
+ MetadataType extends Record<string, unknown> = Record<string, unknown>,
630
+ > = (opts: {
631
+ client: IWebSocketClient<MetadataType>;
632
+ event: string;
633
+ data: DataType;
634
+ }) => ValueOrPromise<TNullable<{ event: string; data: DataType }>>;
635
+ ```
636
+
637
+ ### State Types
638
+
639
+ ```typescript
640
+ type TWebSocketClientState = 'unauthorized' | 'authenticating' | 'authenticated' | 'disconnected';
641
+ type TWebSocketEvent = 'authenticate' | 'connected' | 'disconnect' | 'join' | 'leave' | 'error' | 'heartbeat' | 'encrypted';
642
+ type TWebSocketMessageType = 'client' | 'user' | 'room' | 'broadcast';
643
+ ```
644
+
645
+ ## Constants
646
+
647
+ ### `WebSocketEvents`
648
+
649
+ | Constant | Value | Description |
650
+ |----------|-------|-------------|
651
+ | `AUTHENTICATE` | `'authenticate'` | Client -> Server auth request |
652
+ | `CONNECTED` | `'connected'` | Server -> Client auth success |
653
+ | `DISCONNECT` | `'disconnect'` | Disconnection event |
654
+ | `JOIN` | `'join'` | Room join request |
655
+ | `LEAVE` | `'leave'` | Room leave request |
656
+ | `ERROR` | `'error'` | Server -> Client error message |
657
+ | `HEARTBEAT` | `'heartbeat'` | Client -> Server keep-alive |
658
+ | `ENCRYPTED` | `'encrypted'` | Encrypted message wrapper |
659
+
660
+ Utility methods:
661
+
662
+ ```typescript
663
+ WebSocketEvents.isValid('authenticate'); // true
664
+ WebSocketEvents.isValid('invalid'); // false
665
+ WebSocketEvents.SCHEME_SET; // Set of all valid event strings
666
+ ```
667
+
668
+ ### `WebSocketChannels`
669
+
670
+ | Constant / Method | Value | Description |
671
+ |-------------------|-------|-------------|
672
+ | `BROADCAST` | `'ws:broadcast'` | Broadcast channel |
673
+ | `ROOM_PREFIX` | `'ws:room:'` | Room channel prefix |
674
+ | `CLIENT_PREFIX` | `'ws:client:'` | Client channel prefix |
675
+ | `USER_PREFIX` | `'ws:user:'` | User channel prefix |
676
+ | `forRoom({ room })` | `'ws:room:{room}'` | Build room channel |
677
+ | `forClient({ clientId })` | `'ws:client:{clientId}'` | Build client channel |
678
+ | `forUser({ userId })` | `'ws:user:{userId}'` | Build user channel |
679
+ | `forRoomPattern()` | `'ws:room:*'` | Room pattern for `psubscribe` |
680
+ | `forClientPattern()` | `'ws:client:*'` | Client pattern for `psubscribe` |
681
+ | `forUserPattern()` | `'ws:user:*'` | User pattern for `psubscribe` |
682
+
683
+ ### `WebSocketDefaults`
684
+
685
+ | Constant | Value | Description |
686
+ |----------|-------|-------------|
687
+ | `PATH` | `'/ws'` | Default WebSocket path |
688
+ | `ROOM` | `'ws-default'` | Default room |
689
+ | `NOTIFICATION_ROOM` | `'ws-notification'` | Default notification room |
690
+ | `BROADCAST_TOPIC` | `'ws:internal:broadcast'` | Bun pub/sub broadcast topic |
691
+ | `MAX_PAYLOAD_LENGTH` | `131072` (128KB) | Maximum incoming payload size |
692
+ | `IDLE_TIMEOUT` | `60` (seconds) | Bun transport idle timeout |
693
+ | `BACKPRESSURE_LIMIT` | `1048576` (1MB) | Bun backpressure threshold |
694
+ | `SEND_PINGS` | `true` | Bun transport pings enabled |
695
+ | `PUBLISH_TO_SELF` | `false` | Bun pub/sub self-delivery disabled |
696
+ | `AUTH_TIMEOUT` | `5000` (5s) | Authentication timeout |
697
+ | `HEARTBEAT_INTERVAL` | `30000` (30s) | Heartbeat sweep interval |
698
+ | `HEARTBEAT_TIMEOUT` | `90000` (90s) | Heartbeat inactivity threshold |
699
+ | `ENCRYPTED_BATCH_LIMIT` | `10` | Max concurrent encryption operations |
700
+
701
+ ### `WebSocketMessageTypes`
702
+
703
+ | Constant | Value | Description |
704
+ |----------|-------|-------------|
705
+ | `CLIENT` | `'client'` | Message targeted at a specific client |
706
+ | `USER` | `'user'` | Message targeted at all sessions of a user |
707
+ | `ROOM` | `'room'` | Message targeted at a room |
708
+ | `BROADCAST` | `'broadcast'` | Message targeted at all clients |
709
+
710
+ Utility methods:
711
+
712
+ ```typescript
713
+ WebSocketMessageTypes.isValid('room'); // true
714
+ WebSocketMessageTypes.SCHEME_SET; // Set { 'client', 'user', 'room', 'broadcast' }
715
+ ```
716
+
717
+ ### `WebSocketClientStates`
718
+
719
+ | Constant | Value | Description |
720
+ |----------|-------|-------------|
721
+ | `UNAUTHORIZED` | `'unauthorized'` | Initial state after connection |
722
+ | `AUTHENTICATING` | `'authenticating'` | Auth in progress |
723
+ | `AUTHENTICATED` | `'authenticated'` | Successfully authenticated |
724
+ | `DISCONNECTED` | `'disconnected'` | Client has disconnected |
725
+
726
+ Utility methods:
727
+
728
+ ```typescript
729
+ WebSocketClientStates.isValid('authenticated'); // true
730
+ WebSocketClientStates.SCHEME_SET; // Set of all valid state strings
731
+ ```
732
+
733
+ ## See Also
734
+
735
+ - [Setup & Usage](./) -- Getting started, examples, and troubleshooting
736
+ - [Socket.IO Helper](../socket-io/) -- Socket.IO-based alternative with Node.js support