@venizia/ignis-docs 0.2.0 → 0.2.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +22 -11
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +6 -2
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +182 -93
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -1,45 +1,98 @@
1
- # WebSocket -- API Reference
2
-
3
- > Architecture, method signatures, internals, and type definitions.
1
+ ---
2
+ title: WebSocket - Full Reference
3
+ description: Complete reference for WebSocketServerHelper and WebSocketEmitter - constructors, every method, the client lifecycle, types, and constants
4
+ difficulty: intermediate
5
+ ---
6
+
7
+ # WebSocket - Full Reference
8
+
9
+ Exhaustive reference for `WebSocketServerHelper` and `WebSocketEmitter`. For a readable introduction and the most common tasks, start with the [WebSocket overview](/extensions/helpers/websocket/).
10
+
11
+ **Files:**
12
+
13
+ - [`packages/helpers/src/modules/socket/websocket/server/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/server/helper.ts) - `WebSocketServerHelper`
14
+ - [`packages/helpers/src/modules/socket/websocket/emitter/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/emitter/helper.ts) - `WebSocketEmitter`
15
+ - [`packages/helpers/src/modules/socket/websocket/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/common/types.ts) - option, callback, and wire types
16
+ - [`packages/helpers/src/modules/socket/websocket/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/common/constants.ts) - `WebSocketEvents`, `WebSocketChannels`, `WebSocketDefaults`, `WebSocketMessageTypes`, `WebSocketClientStates`
17
+
18
+ ## Quick Reference
19
+
20
+ | Class | Extends | Role |
21
+ |-------|---------|------|
22
+ | `WebSocketServerHelper` | `BaseHelper` | Bun-native WebSocket server with auth, rooms, heartbeat, Redis Pub/Sub scaling |
23
+ | `WebSocketEmitter` | `BaseHelper` | Publish messages to WebSocket clients from any process via Redis |
24
+
25
+ ### Import Reference
26
+
27
+ ```typescript
28
+ import { WebSocketServerHelper, WebSocketEmitter } from '@venizia/ignis-helpers';
29
+
30
+ import type {
31
+ IWebSocketServerOptions,
32
+ IWebSocketEmitterOptions,
33
+ IWebSocketClient,
34
+ IWebSocketData,
35
+ IWebSocketMessage,
36
+ IRedisSocketMessage,
37
+ IWebSocket,
38
+ IBunServer,
39
+ IBunWebSocketConfig,
40
+ IBunWebSocketHandler,
41
+ TWebSocketAuthenticateFn,
42
+ TWebSocketValidateRoomFn,
43
+ TWebSocketClientConnectedFn,
44
+ TWebSocketClientDisconnectedFn,
45
+ TWebSocketMessageHandler,
46
+ TWebSocketOutboundTransformer,
47
+ TWebSocketHandshakeFn,
48
+ TWebSocketClientState,
49
+ TWebSocketEvent,
50
+ TWebSocketMessageType,
51
+ } from '@venizia/ignis-helpers';
52
+
53
+ import {
54
+ WebSocketEvents,
55
+ WebSocketChannels,
56
+ WebSocketDefaults,
57
+ WebSocketMessageTypes,
58
+ WebSocketClientStates,
59
+ } from '@venizia/ignis-helpers';
60
+ ```
4
61
 
5
62
  ## Architecture
6
63
 
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
64
+ `Source ->` [`server/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/server/helper.ts), [`emitter/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/emitter/helper.ts)
10
65
 
11
66
  ```
12
67
  WebSocketServerHelper
13
68
  +---------------------------------------------------+
14
- | |
15
69
  | constructor(opts) |
16
- | |-- identifier, path, serverId (UUID) |
17
- | |-- Store callbacks (auth, rooms, messages) |
18
- | |-- Apply defaults (rooms, timeouts) |
19
- | +-- initRedisClients(redisConnection) |
20
- | +-- redisPub = duplicateClient() |
21
- | +-- redisSub = duplicateClient() |
70
+ | |-- identifier, path, serverId (UUID) |
71
+ | |-- Store callbacks (auth, rooms, messages) |
72
+ | |-- Apply defaults (rooms, timeouts) |
73
+ | +-- initRedisClients(redisConnection) |
74
+ | +-- redisPub = duplicateClient() |
75
+ | +-- redisSub = duplicateClient() |
22
76
  | |
23
77
  | 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() |
78
+ | |-- Connect Redis clients (if lazyConnect) |
79
+ | |-- await Redis ready (pub + sub) |
80
+ | |-- setupRedisSubscriptions() |
81
+ | | |-- subscribe(ws:broadcast) |
82
+ | | |-- psubscribe(ws:room:*) |
83
+ | | |-- psubscribe(ws:client:*) |
84
+ | | +-- psubscribe(ws:user:*) |
85
+ | +-- startHeartbeatTimer() |
32
86
  | |
33
87
  | getBunWebSocketHandler() |
34
- | +-- Returns { open, message, close, drain, |
35
- | ...serverOptions } |
36
- | |
88
+ | +-- Returns { open, message, close, drain, |
89
+ | ...serverOptions } |
37
90
  +---------------------------------------------------+
38
91
 
39
92
  WebSocketEmitter
40
93
  +---------------------------------------------------+
41
94
  | constructor(opts) |
42
- | +-- redisPub = duplicateClient() |
95
+ | +-- redisPub = duplicateClient() |
43
96
  | |
44
97
  | configure() [async] |
45
98
  | +-- await Redis ready |
@@ -49,7 +102,7 @@ The WebSocket helper provides two classes: `WebSocketServerHelper` for managing
49
102
  +---------------------------------------------------+
50
103
  ```
51
104
 
52
- #### Client Connection Lifecycle
105
+ ### Client connection lifecycle
53
106
 
54
107
  ```
55
108
  Client connects via WebSocket upgrade
@@ -69,18 +122,18 @@ Client connects via WebSocket upgrade
69
122
  | | +-- [requireEncryption?] -> handshakeFn() -> enableClientEncryption()
70
123
  | | +-- Index by userId
71
124
  | | +-- Subscribe to broadcast topic (unless encrypted)
72
- | | +-- Join default rooms + clientId room
125
+ | | +-- Join clientId room + defaultRooms
73
126
  | | +-- Send 'connected' event
74
127
  | | +-- Call clientConnectedFn()
75
- | +-- Failure:
128
+ | +-- Failure (null/false or throw):
76
129
  | +-- Send 'error' event
77
130
  | +-- Close with code 4003
78
131
  |
79
- +-- Auth timeout expires (if still UNAUTHORIZED)
132
+ +-- Auth timeout expires (still UNAUTHORIZED or AUTHENTICATING)
80
133
  | +-- Close with code 4001
81
134
  |
82
135
  +-- Heartbeat sweep (every heartbeatInterval)
83
- | +-- If now - lastActivity > heartbeatTimeout
136
+ | +-- If now - lastActivity > heartbeatTimeout (AUTHENTICATED clients only)
84
137
  | +-- Close with code 4002
85
138
  |
86
139
  +-- Client disconnects
@@ -92,27 +145,81 @@ Client connects via WebSocket upgrade
92
145
  +-- Call clientDisconnectedFn()
93
146
  ```
94
147
 
95
- #### Redis 2-Client Architecture
148
+ ### Redis two-client architecture
96
149
 
97
150
  ```
98
- RedisSingleHelper (parent -- NOT consumed)
99
- |
100
- +-- duplicateClient() --> redisPub (publishes cross-instance messages)
101
- |
102
- +-- duplicateClient() --> redisSub (subscribes to cross-instance messages)
151
+ Server A Redis Server B
152
+ +-----------+ +----------+ +-----------+
153
+ | WS Server |--redisPub-->| |<--redisPub----| WS Server |
154
+ | |<--redisSub--| Pub/Sub |---redisSub--->| |
155
+ +-----------+ +----------+ +-----------+
103
156
  ```
104
157
 
105
- Both single-instance `Redis` and `Cluster` connections from ioredis are supported. The parent `RedisSingleHelper` connection remains independent.
158
+ - **Duplication.** `WebSocketServerHelper` duplicates its `redisConnection` twice (`redisPub`, `redisSub`); `WebSocketEmitter` duplicates it once (`redisPub` only).
159
+ - **Connection types.** Both single-instance `Redis` and `Cluster` connections from ioredis are supported - the parent helper connection stays independent and unconsumed.
160
+ - **Dedup on receipt.** Every server instance generates a unique `serverId` (UUID) at construction; messages carrying the same `serverId` on receipt are skipped to prevent double delivery to the originating instance.
106
161
 
107
162
  ## Server API
108
163
 
109
- ### `WebSocketServerHelper` Constructor
164
+ `Source ->` [`server/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/server/helper.ts)
165
+
166
+ ### Constructor
110
167
 
111
168
  ```typescript
112
169
  constructor(opts: IWebSocketServerOptions<AuthDataType, MetadataType>)
113
170
  ```
114
171
 
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.
172
+ Generates a unique `serverId` (UUID), stores all options with defaults applied, and initializes two duplicated Redis clients (`redisPub`, `redisSub`). Throws `getError({ statusCode: 500, message: '[WebSocketServerHelper] Invalid redis connection!' })` if `redisConnection` is falsy.
173
+
174
+ #### Generic type parameters
175
+
176
+ `AuthDataType` types the payload passed to `authenticateFn`/`handshakeFn`; `MetadataType` types the value returned as `metadata` and stored on `IWebSocketClient`. Both default to `Record<string, unknown>`.
177
+
178
+ ```typescript
179
+ interface AuthPayload { type: string; token: string; publicKey?: string }
180
+ interface UserMetadata { role: string; permissions: string[] }
181
+
182
+ const helper = new WebSocketServerHelper<AuthPayload, UserMetadata>({
183
+ identifier: 'typed-ws',
184
+ server: bunServer,
185
+ redisConnection: redis,
186
+ authenticateFn: async data => {
187
+ // data is typed as AuthPayload
188
+ const user = await verifyJWT(data.token);
189
+ return user ? { userId: user.id, metadata: { role: user.role, permissions: user.permissions } } : null;
190
+ },
191
+ clientConnectedFn: ({ metadata }) => {
192
+ // metadata is typed as UserMetadata | undefined
193
+ if (metadata?.role === 'admin') console.log('admin connected');
194
+ },
195
+ });
196
+ ```
197
+
198
+ ### `IWebSocketServerOptions`
199
+
200
+ | Option | Type | Required | Default | Description |
201
+ |--------|------|----------|---------|-------------|
202
+ | `identifier` | `string` | Yes | - | Unique name for this instance; also used as the `BaseHelper` logging scope |
203
+ | `path` | `string` | No | `'/ws'` | URL path for WebSocket upgrade requests |
204
+ | `server` | `IBunServer` | Yes | - | Bun server instance (provides `publish()` for native pub/sub) |
205
+ | `redisConnection` | `IRedisHelper` | Yes | - | Redis helper for cross-instance messaging; duplicated twice internally |
206
+ | `defaultRooms` | `string[]` | No | `['ws-default', 'ws-notification']` | Rooms every client auto-joins after authentication |
207
+ | `serverOptions` | `IBunWebSocketConfig` | No | See [Bun native configuration](#bun-native-configuration) | Bun native WebSocket configuration |
208
+ | `authTimeout` | `number` | No | `5000` (5s) | Milliseconds before an unauthenticated client is disconnected (close code `4001`); extended to `authTimeout * 3` while `authenticateFn` is in flight |
209
+ | `heartbeatInterval` | `number` | No | `30000` (30s) | Milliseconds between heartbeat sweeps |
210
+ | `heartbeatTimeout` | `number` | No | `90000` (90s) | Milliseconds of inactivity before an authenticated client is closed (close code `4002`) |
211
+ | `encryptedBatchLimit` | `number` | No | `10` | Max concurrent `outboundTransformer` invocations during room/broadcast delivery |
212
+ | `requireEncryption` | `boolean` | No | `false` | When `true`, clients must complete the handshake during auth or get disconnected (code `4004`) |
213
+ | `authenticateFn` | `TWebSocketAuthenticateFn` | Yes | - | Called on `{ event: 'authenticate' }`. Return `{ userId, metadata }` to accept, `null`/`false` (or throw) to reject |
214
+ | `validateRoomFn` | `TWebSocketValidateRoomFn` | No | - | Called on `{ event: 'join' }`. Return the allowed subset of requested rooms. All joins are rejected when this is not provided |
215
+ | `clientConnectedFn` | `TWebSocketClientConnectedFn` | No | - | Called after successful authentication |
216
+ | `clientDisconnectedFn` | `TWebSocketClientDisconnectedFn` | No | - | Called during disconnect cleanup |
217
+ | `messageHandler` | `TWebSocketMessageHandler` | No | - | Called for events other than `authenticate`, `heartbeat`, `join`, `leave` from authenticated clients |
218
+ | `outboundTransformer` | `TWebSocketOutboundTransformer` | No | - | Intercepts outbound `{ event, data }` before `socket.send()`; enables per-client encryption |
219
+ | `handshakeFn` | `TWebSocketHandshakeFn` | No | - | Required when `requireEncryption` is `true`. Returns `{ serverPublicKey, salt }` to accept, `null`/`false` to reject |
220
+
221
+ - **All callbacks run through `invokeHook()`.** Applies to `authenticateFn`, `validateRoomFn`, `clientConnectedFn`, `clientDisconnectedFn`, `messageHandler`, and `handshakeFn`.
222
+ - **Failures do not crash the process.** A synchronous throw inside a Bun socket handler is caught and logged; rejected promises are logged the same way via `voidExecution`.
116
223
 
117
224
  ### `configure()`
118
225
 
@@ -120,15 +227,13 @@ Creates the server helper, generates a unique `serverId` (UUID), stores all opti
120
227
  configure(): Promise<void>
121
228
  ```
122
229
 
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
230
+ Must be called after construction and before accepting connections.
126
231
 
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)`
232
+ 1. Registers `error` listeners on `redisPub` and `redisSub`.
233
+ 2. Connects both duplicated clients if their status is `'wait'` (`lazyConnect` mode).
234
+ 3. `await Promise.all([waitForRedisReady(redisPub), waitForRedisReady(redisSub)])`.
235
+ 4. Subscribes: `subscribe('ws:broadcast')`, `psubscribe('ws:room:*')`, `psubscribe('ws:client:*')`, `psubscribe('ws:user:*')` - awaited together before proceeding.
236
+ 5. Starts the heartbeat timer: `setInterval(heartbeatAll, heartbeatInterval)`.
132
237
 
133
238
  ### `getBunWebSocketHandler()`
134
239
 
@@ -136,30 +241,29 @@ Initializes Redis connections, sets up pub/sub subscriptions, and starts the hea
136
241
  getBunWebSocketHandler(): IBunWebSocketHandler
137
242
  ```
138
243
 
139
- Returns the Bun WebSocket handler object containing lifecycle callbacks and native configuration. Pass this to `server.reload({ websocket })`.
140
-
141
- #### Lifecycle Callbacks
244
+ Returns the Bun WebSocket handler object. Pass it to `server.reload({ websocket })`.
142
245
 
143
246
  | Callback | When | Behavior |
144
247
  |----------|------|----------|
145
- | `open` | WebSocket connection established | Extracts `clientId` from `socket.data`, calls `onClientConnect()` |
146
- | `message` | Message received | Updates `lastActivity`, calls `onClientMessage()` for routing |
248
+ | `open` | Connection established | Reads `clientId` from `socket.data`, calls `onClientConnect()` |
249
+ | `message` | Message received | Updates `client.lastActivity`, calls `onClientMessage()` for routing |
147
250
  | `close` | Connection closed | Calls `onClientDisconnect()` for cleanup |
148
251
  | `drain` | Backpressure cleared | Sets `client.backpressured = false` |
149
252
 
150
- #### Bun Native Configuration
253
+ #### Bun native configuration
151
254
 
152
- These values are spread from `serverOptions` into the returned handler:
255
+ - **`serverOptions` is spread into the returned handler.** `WebSocketServerHelper` only applies its own default for `sendPings`, `idleTimeout`, and `maxPayloadLength`.
256
+ - **Everything else falls through to Bun.** The remaining fields are `undefined` unless you set them, and Bun applies its own runtime default in that case.
153
257
 
154
- | Option | Type | Default | Description |
155
- |--------|------|---------|-------------|
156
- | `perMessageDeflate` | `boolean` | `undefined` | Enable per-message compression |
258
+ | Option | Type | Helper default | Description |
259
+ |--------|------|-----------------|--------------|
260
+ | `sendPings` | `boolean` | `true` | Bun transport-level pings |
261
+ | `idleTimeout` | `number` | `60` (seconds) | Bun-level idle timeout (transport layer, separate from `heartbeatTimeout`) |
157
262
  | `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 |
263
+ | `perMessageDeflate` | `boolean` | not set by the helper | Enable per-message compression |
264
+ | `backpressureLimit` | `number` | not set by the helper | Backpressure threshold in bytes |
265
+ | `closeOnBackpressureLimit` | `boolean` | not set by the helper | Close socket when the backpressure limit is exceeded |
266
+ | `publishToSelf` | `boolean` | not set by the helper | Whether `server.publish()` delivers to the publishing socket |
163
267
 
164
268
  ### `getPath()`
165
269
 
@@ -178,7 +282,7 @@ getClients(opts?: { id?: string }):
178
282
  | undefined
179
283
  ```
180
284
 
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`.
285
+ Without `id` (or an empty `opts`), returns the full `Map<string, IWebSocketClient>`. With `{ id }`, returns that client entry or `undefined`.
182
286
 
183
287
  ### `getClientsByUser()`
184
288
 
@@ -186,7 +290,7 @@ When called without arguments or with an empty opts, returns the full `Map<strin
186
290
  getClientsByUser(opts: { userId: string }): IWebSocketClient<MetadataType>[]
187
291
  ```
188
292
 
189
- Returns all clients belonging to the given user ID. Returns an empty array if the user has no active connections.
293
+ Returns every client belonging to `userId`; `[]` if the user has no active connections.
190
294
 
191
295
  ### `getClientsByRoom()`
192
296
 
@@ -194,7 +298,7 @@ Returns all clients belonging to the given user ID. Returns an empty array if th
194
298
  getClientsByRoom(opts: { room: string }): IWebSocketClient<MetadataType>[]
195
299
  ```
196
300
 
197
- Returns all clients in the given room. Returns an empty array if the room does not exist or is empty.
301
+ Returns every client in `room`; `[]` if the room does not exist or is empty.
198
302
 
199
303
  ### `onClientConnect()`
200
304
 
@@ -202,7 +306,7 @@ Returns all clients in the given room. Returns an empty array if the room does n
202
306
  onClientConnect(opts: { clientId: string; socket: IWebSocket }): void
203
307
  ```
204
308
 
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.
309
+ 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 - and logs `'Client already existed'` - if the client ID already exists.
206
310
 
207
311
  ### `onClientMessage()`
208
312
 
@@ -210,15 +314,14 @@ Handles a new WebSocket connection. Creates an `IWebSocketClient` entry with sta
210
314
  onClientMessage(opts: { clientId: string; raw: string }): void
211
315
  ```
212
316
 
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)
317
+ Routes an incoming message:
220
318
 
221
- Sends an `error` event (`'Invalid message format'`) if JSON parsing fails.
319
+ - Parses `raw` as JSON. On failure, sends `{ event: 'error', data: { message: 'Invalid message format' } }` and returns. A message with no `event` field is logged and dropped.
320
+ - `heartbeat`: consumed silently (`lastActivity` is already updated by the `message` callback before this runs).
321
+ - `authenticate`: delegates to `handleAuthenticate()`.
322
+ - Any other event, when the client is not `AUTHENTICATED`: sends `{ event: 'error', data: { message: 'Not authenticated' } }`.
323
+ - `join` / `leave`: delegate to the internal room handlers.
324
+ - Everything else: delegates to `messageHandler` via `invokeHook()`, if configured; otherwise logged at `debug` and dropped.
222
325
 
223
326
  ### `onClientDisconnect()`
224
327
 
@@ -226,13 +329,7 @@ Sends an `error` event (`'Invalid message format'`) if JSON parsing fails.
226
329
  onClientDisconnect(opts: { clientId: string }): void
227
330
  ```
228
331
 
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
332
+ Cleans up a disconnected client: clears the pending auth timer, removes the client from its user's index, removes it from every joined room, removes it from the clients map, then invokes `clientDisconnectedFn`.
236
333
 
237
334
  ### `joinRoom()`
238
335
 
@@ -240,7 +337,7 @@ Cleans up a disconnected client:
240
337
  joinRoom(opts: { clientId: string; room: string }): void
241
338
  ```
242
339
 
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).
340
+ Adds `clientId` to the room index and to `client.rooms`, and subscribes the socket to the room's Bun native pub/sub topic - unless the client is encrypted, in which case the subscribe step is skipped (delivery goes through the transformer instead). No-op if the client does not exist.
244
341
 
245
342
  ### `leaveRoom()`
246
343
 
@@ -248,7 +345,19 @@ Programmatically joins a client to a room. Adds to the room index, adds to the c
248
345
  leaveRoom(opts: { clientId: string; room: string }): void
249
346
  ```
250
347
 
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.
348
+ Removes `clientId` from the room index and from `client.rooms`, and unsubscribes the socket from the room's topic. No-op if the client does not exist.
349
+
350
+ #### Room name validation (client-initiated `join`/`leave` only)
351
+
352
+ `handleJoin()` filters requested room names before calling `validateRoomFn`:
353
+
354
+ - Must be a non-empty string.
355
+ - Maximum 256 characters.
356
+ - Cannot start with `ws:` (reserved for the internal Redis channel prefix).
357
+
358
+ - **Rejection is silent.** If every requested room is filtered out, `validateRoomFn` is not configured, or it resolves to an empty array, the join is rejected and logged - no error is sent back to the client.
359
+ - **`leave` does not re-filter.** `{ event: 'leave' }` only processes rooms the client is actually a member of (`client.rooms.has(room)`).
360
+ - **Programmatic calls bypass validation.** `joinRoom()`/`leaveRoom()` called directly skip all of the above.
252
361
 
253
362
  ### `enableClientEncryption()`
254
363
 
@@ -256,74 +365,51 @@ Removes a client from a room. Removes from the room index, removes from the clie
256
365
  enableClientEncryption(opts: { clientId: string }): void
257
366
  ```
258
367
 
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.
368
+ Unsubscribes the client from every Bun native pub/sub topic (the broadcast topic plus all joined rooms) so `server.publish()` no longer reaches it; messages are instead delivered individually through `outboundTransformer`. No-op if the client is already encrypted or does not exist.
260
369
 
261
370
  > [!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.
371
+ > Irreversible for the lifetime of the connection. Once a client is encrypted it cannot be switched back to Bun native pub/sub delivery.
263
372
 
264
373
  ### `sendToClient()`
265
374
 
266
375
  ```typescript
267
- sendToClient(opts: {
268
- clientId: string;
269
- event: string;
270
- data: unknown;
271
- doLog?: boolean;
272
- }): void
376
+ sendToClient(opts: { clientId: string; event: string; data: unknown; doLog?: boolean }): void
273
377
  ```
274
378
 
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.
379
+ - **Local delivery only.**
380
+ - **Encrypted client, `outboundTransformer` configured.** The transformer runs (async) before `socket.send()`; a transformer error is logged and the message is dropped.
381
+ - **Otherwise.** Sends `JSON.stringify({ event, data })` directly.
382
+ - **`doLog: true`** emits an info log after delivery.
276
383
 
277
384
  ### `sendToUser()`
278
385
 
279
386
  ```typescript
280
- sendToUser(opts: {
281
- userId: string;
282
- event: string;
283
- data: unknown;
284
- }): void
387
+ sendToUser(opts: { userId: string; event: string; data: unknown }): void
285
388
  ```
286
389
 
287
- Sends a message to all local clients belonging to a user. Iterates the user's client set and calls `sendToClient()` for each.
390
+ Local delivery only. Iterates the user's client set and calls `sendToClient()` for each.
288
391
 
289
392
  ### `sendToRoom()`
290
393
 
291
394
  ```typescript
292
- sendToRoom(opts: {
293
- room: string;
294
- event: string;
295
- data: unknown;
296
- exclude?: string[];
297
- }): void
395
+ sendToRoom(opts: { room: string; event: string; data: unknown; exclude?: string[] }): void
298
396
  ```
299
397
 
300
- Sends a message to all clients in a room (local delivery only).
301
-
302
- #### Delivery Strategy
398
+ Local delivery only.
303
399
 
304
400
  | Condition | Strategy |
305
401
  |-----------|----------|
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) |
402
+ | No `outboundTransformer`, no `exclude` | Bun native `server.publish(room, payload)` - O(1) C++ fan-out |
403
+ | `outboundTransformer` set, no `exclude` | Iterates room clients via `executePromiseWithLimit` (max `encryptedBatchLimit` concurrent) |
404
+ | `exclude` provided | Always iterates clients individually - Bun pub/sub cannot exclude |
309
405
 
310
406
  ### `broadcast()`
311
407
 
312
408
  ```typescript
313
- broadcast(opts: {
314
- event: string;
315
- data: unknown;
316
- exclude?: string[];
317
- }): void
409
+ broadcast(opts: { event: string; data: unknown; exclude?: string[] }): void
318
410
  ```
319
411
 
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 |
412
+ Local delivery only, to `AUTHENTICATED` clients. Same delivery strategy as `sendToRoom()`, publishing to `WebSocketDefaults.BROADCAST_TOPIC` (`'ws:internal:broadcast'`) instead of a room topic when no transformer/exclude applies.
327
413
 
328
414
  ### `send()`
329
415
 
@@ -332,24 +418,20 @@ send<T = unknown>(opts: {
332
418
  destination?: string;
333
419
  payload: { topic: string; data: T };
334
420
  doLog?: boolean;
335
- cb?: () => void;
421
+ callback?: () => void;
336
422
  }): void
337
423
  ```
338
424
 
339
- Public API for cross-instance messaging. Delivers locally **and** publishes to Redis so other server instances receive the message.
340
-
341
- Routing logic:
425
+ Cross-instance messaging: delivers locally **and** publishes to Redis so other server instances receive it.
342
426
 
343
427
  | `destination` | Local delivery | Redis channel |
344
- |---------------|----------------|---------------|
428
+ |---------------|----------------|----------------|
345
429
  | 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}` |
430
+ | Matches a locally-tracked client ID | `sendToClient()` | `ws:client:{clientId}` |
431
+ | Matches a locally-tracked room name | `sendToRoom()` | `ws:room:{room}` |
432
+ | Matches neither (remote target) | None | `ws:room:{destination}` |
349
433
 
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)`.
434
+ Silent no-op when `payload` is falsy, `payload.topic` is falsy, or `payload.data` is `undefined`. When `callback` is provided it runs via `setTimeout(callback, 0)` regardless of delivery outcome.
353
435
 
354
436
  ### `shutdown()`
355
437
 
@@ -357,23 +439,33 @@ If `cb` is provided, it is executed asynchronously via `setTimeout(cb, 0)`.
357
439
  shutdown(): Promise<void>
358
440
  ```
359
441
 
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()])`
442
+ 1. Clears the heartbeat timer.
443
+ 2. Closes every client socket with code `1001` (`'Server shutting down'`).
444
+ 3. Calls `onClientDisconnect()` for every tracked client (clears auth timers, removes from indexes, invokes `clientDisconnectedFn`).
445
+ 4. Clears the `clients`, `users`, and `rooms` maps.
446
+ 5. `await Promise.all([redisPub.quit(), redisSub.quit()])`.
367
447
 
368
448
  ## Emitter API
369
449
 
370
- ### `WebSocketEmitter` Constructor
450
+ `Source ->` [`emitter/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/emitter/helper.ts)
451
+
452
+ - **Role.** A Redis-only publisher for processes with no WebSocket server of their own - background workers, other microservices, cron jobs.
453
+ - **No self-skip.** It always publishes with `serverId: 'emitter'`, so every server instance processes the message; there is no dedup skip on the sending side.
454
+
455
+ ### Constructor
371
456
 
372
457
  ```typescript
373
458
  constructor(opts: IWebSocketEmitterOptions)
374
459
  ```
375
460
 
376
- Creates the emitter, duplicates one Redis client from `redisConnection`. Throws if `redisConnection` is falsy.
461
+ Duplicates one Redis client from `redisConnection`. Throws `getError({ statusCode: 500, message: '[WebSocketEmitter] Invalid redis connection!' })` if `redisConnection` is falsy.
462
+
463
+ ### `IWebSocketEmitterOptions`
464
+
465
+ | Field | Type | Required | Default | Description |
466
+ |-------|------|----------|---------|-------------|
467
+ | `identifier` | `string` | No | `'WebSocketEmitter'` | Instance name; also the `BaseHelper` logging scope |
468
+ | `redisConnection` | `IRedisHelper` | Yes | - | Redis helper; duplicated once internally |
377
469
 
378
470
  ### `configure()`
379
471
 
@@ -381,55 +473,39 @@ Creates the emitter, duplicates one Redis client from `redisConnection`. Throws
381
473
  configure(): Promise<void>
382
474
  ```
383
475
 
384
- Connects the Redis client (if in `'wait'` status) and waits for it to reach `ready` status. Must be called before emitting.
476
+ Connects the Redis client (if status is `'wait'`) and `await`s it reaching `'ready'`. Must be called before emitting.
385
477
 
386
478
  ### `toClient()`
387
479
 
388
480
  ```typescript
389
- toClient(opts: {
390
- clientId: string;
391
- event: string;
392
- data: unknown;
393
- }): Promise<void>
481
+ toClient(opts: { clientId: string; event: string; data: unknown }): Promise<void>
394
482
  ```
395
483
 
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.
484
+ Publishes to `ws:client:{clientId}`. Every server instance subscribed via `psubscribe('ws:client:*')` delivers it to that client if connected locally.
397
485
 
398
486
  ### `toUser()`
399
487
 
400
488
  ```typescript
401
- toUser(opts: {
402
- userId: string;
403
- event: string;
404
- data: unknown;
405
- }): Promise<void>
489
+ toUser(opts: { userId: string; event: string; data: unknown }): Promise<void>
406
490
  ```
407
491
 
408
- Publishes a message to the `ws:user:{userId}` Redis channel. All server instances deliver it to every session belonging to that user.
492
+ Publishes to `ws:user:{userId}`. Every server instance delivers it to every session belonging to that user.
409
493
 
410
494
  ### `toRoom()`
411
495
 
412
496
  ```typescript
413
- toRoom(opts: {
414
- room: string;
415
- event: string;
416
- data: unknown;
417
- exclude?: string[];
418
- }): Promise<void>
497
+ toRoom(opts: { room: string; event: string; data: unknown; exclude?: string[] }): Promise<void>
419
498
  ```
420
499
 
421
- Publishes a message to the `ws:room:{room}` Redis channel. All server instances deliver it to every client in that room.
500
+ Publishes to `ws:room:{room}`. Every server instance delivers it to every client in that room.
422
501
 
423
502
  ### `broadcast()`
424
503
 
425
504
  ```typescript
426
- broadcast(opts: {
427
- event: string;
428
- data: unknown;
429
- }): Promise<void>
505
+ broadcast(opts: { event: string; data: unknown }): Promise<void>
430
506
  ```
431
507
 
432
- Publishes a message to the `ws:broadcast` Redis channel. All server instances deliver it to every authenticated client.
508
+ Publishes to `ws:broadcast`. Every server instance delivers it to every authenticated client.
433
509
 
434
510
  ### `shutdown()`
435
511
 
@@ -437,11 +513,13 @@ Publishes a message to the `ws:broadcast` Redis channel. All server instances de
437
513
  shutdown(): Promise<void>
438
514
  ```
439
515
 
440
- Quits the Redis connection.
516
+ Quits the duplicated Redis connection.
441
517
 
442
518
  ## Types Reference
443
519
 
444
- ### Wire Protocol
520
+ `Source ->` [`common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/common/types.ts)
521
+
522
+ ### Wire protocol
445
523
 
446
524
  ```typescript
447
525
  /** Client <-> Server message envelope */
@@ -454,7 +532,7 @@ interface IWebSocketMessage<DataType = unknown> {
454
532
  /** Internal Redis Pub/Sub message envelope */
455
533
  interface IRedisSocketMessage<DataType = unknown> {
456
534
  serverId: string;
457
- type: TWebSocketMessageType; // 'client' | 'user' | 'room' | 'broadcast'
535
+ type: TWebSocketMessageType; // 'client' | 'user' | 'room' | 'broadcast'
458
536
  target?: string;
459
537
  event: string;
460
538
  data: DataType;
@@ -462,7 +540,7 @@ interface IRedisSocketMessage<DataType = unknown> {
462
540
  }
463
541
  ```
464
542
 
465
- ### Client Tracking
543
+ ### Client tracking
466
544
 
467
545
  ```typescript
468
546
  interface IWebSocketClient<
@@ -493,10 +571,10 @@ interface IWebSocketData<
493
571
  }
494
572
  ```
495
573
 
496
- ### Bun Interfaces
574
+ ### Bun interfaces
497
575
 
498
576
  ```typescript
499
- /** Bun WebSocket handle (defined locally to avoid @types/bun dependency) */
577
+ /** Bun WebSocket handle - defined locally to avoid an @types/bun dependency */
500
578
  interface IWebSocket<T = unknown> {
501
579
  readonly data: T;
502
580
  readonly remoteAddress: string;
@@ -507,7 +585,7 @@ interface IWebSocket<T = unknown> {
507
585
  unsubscribe(topic: string): void;
508
586
  isSubscribed(topic: string): boolean;
509
587
  close(code?: number, reason?: string): void;
510
- cork(cb: (ws: IWebSocket<T>) => void): void;
588
+ cork(callback: (ws: IWebSocket<T>) => void): void;
511
589
  }
512
590
 
513
591
  /** Bun server interface for native pub/sub */
@@ -540,7 +618,7 @@ interface IBunWebSocketHandler extends IBunWebSocketConfig {
540
618
  }
541
619
  ```
542
620
 
543
- ### Server Options
621
+ ### Server and emitter options
544
622
 
545
623
  ```typescript
546
624
  interface IWebSocketServerOptions<
@@ -548,16 +626,16 @@ interface IWebSocketServerOptions<
548
626
  MetadataType extends Record<string, unknown> = Record<string, unknown>,
549
627
  > {
550
628
  identifier: string;
551
- path?: string; // Default: '/ws'
629
+ path?: string; // Default: '/ws'
552
630
  redisConnection: IRedisHelper;
553
631
  server: IBunServer;
554
- defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
632
+ defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
555
633
  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
634
+ authTimeout?: number; // Default: 5_000
635
+ heartbeatInterval?: number; // Default: 30_000
636
+ heartbeatTimeout?: number; // Default: 90_000
637
+ encryptedBatchLimit?: number; // Default: 10
638
+ requireEncryption?: boolean; // Default: false
561
639
 
562
640
  authenticateFn: TWebSocketAuthenticateFn<AuthDataType, MetadataType>;
563
641
  validateRoomFn?: TWebSocketValidateRoomFn;
@@ -565,19 +643,19 @@ interface IWebSocketServerOptions<
565
643
  clientDisconnectedFn?: TWebSocketClientDisconnectedFn;
566
644
  messageHandler?: TWebSocketMessageHandler;
567
645
  outboundTransformer?: TWebSocketOutboundTransformer<unknown, MetadataType>;
568
- handshakeFn?: TWebSocketHandshakeFn<AuthDataType>;
646
+ handshakeFn?: TWebSocketHandshakeFn<AuthDataType>; // Required when requireEncryption is true
569
647
  }
570
648
 
571
649
  interface IWebSocketEmitterOptions {
572
- identifier?: string; // Default: 'WebSocketEmitter'
650
+ identifier?: string; // Default: 'WebSocketEmitter'
573
651
  redisConnection: IRedisHelper;
574
652
  }
575
653
  ```
576
654
 
577
- ### Callback Types
655
+ ### Callback types
578
656
 
579
657
  ```typescript
580
- /** Authentication -- return { userId, metadata } on success, null/false to reject */
658
+ /** Authentication - return { userId, metadata } to accept, null/false to reject */
581
659
  type TWebSocketAuthenticateFn<
582
660
  AuthDataType extends Record<string, unknown> = Record<string, unknown>,
583
661
  MetadataType extends Record<string, unknown> = Record<string, unknown>,
@@ -585,7 +663,7 @@ type TWebSocketAuthenticateFn<
585
663
  opts: AuthDataType,
586
664
  ) => ValueOrPromise<{ userId?: string; metadata?: MetadataType } | null | false>;
587
665
 
588
- /** ECDH key exchange during auth -- return { serverPublicKey, salt } or null/false */
666
+ /** Handshake during auth - return { serverPublicKey, salt } to accept, null/false to reject */
589
667
  type TWebSocketHandshakeFn<
590
668
  AuthDataType extends Record<string, unknown> = Record<string, unknown>,
591
669
  > = (opts: {
@@ -594,7 +672,7 @@ type TWebSocketHandshakeFn<
594
672
  data: AuthDataType;
595
673
  }) => ValueOrPromise<{ serverPublicKey: string; salt: string } | null | false>;
596
674
 
597
- /** Room validation -- return the allowed subset of requested rooms */
675
+ /** Room validation - return the allowed subset of requested rooms */
598
676
  type TWebSocketValidateRoomFn = (opts: {
599
677
  clientId: string;
600
678
  userId?: string;
@@ -604,11 +682,7 @@ type TWebSocketValidateRoomFn = (opts: {
604
682
  /** Post-authentication callback */
605
683
  type TWebSocketClientConnectedFn<
606
684
  MetadataType extends Record<string, unknown> = Record<string, unknown>,
607
- > = (opts: {
608
- clientId: string;
609
- userId?: string;
610
- metadata?: MetadataType;
611
- }) => ValueOrPromise<void>;
685
+ > = (opts: { clientId: string; userId?: string; metadata?: MetadataType }) => ValueOrPromise<void>;
612
686
 
613
687
  /** Disconnect callback */
614
688
  type TWebSocketClientDisconnectedFn = (opts: {
@@ -616,14 +690,14 @@ type TWebSocketClientDisconnectedFn = (opts: {
616
690
  userId?: string;
617
691
  }) => ValueOrPromise<void>;
618
692
 
619
- /** Custom event handler for non-system events from authenticated clients */
693
+ /** Custom event handler for events other than authenticate/heartbeat/join/leave */
620
694
  type TWebSocketMessageHandler = (opts: {
621
695
  clientId: string;
622
696
  userId?: string;
623
697
  message: IWebSocketMessage;
624
698
  }) => ValueOrPromise<void>;
625
699
 
626
- /** Outbound transformer -- intercepts messages before socket.send() */
700
+ /** Outbound transformer - intercepts messages before socket.send() */
627
701
  type TWebSocketOutboundTransformer<
628
702
  DataType = unknown,
629
703
  MetadataType extends Record<string, unknown> = Record<string, unknown>,
@@ -634,7 +708,7 @@ type TWebSocketOutboundTransformer<
634
708
  }) => ValueOrPromise<TNullable<{ event: string; data: DataType }>>;
635
709
  ```
636
710
 
637
- ### State Types
711
+ ### State types
638
712
 
639
713
  ```typescript
640
714
  type TWebSocketClientState = 'unauthorized' | 'authenticating' | 'authenticated' | 'disconnected';
@@ -644,6 +718,8 @@ type TWebSocketMessageType = 'client' | 'user' | 'room' | 'broadcast';
644
718
 
645
719
  ## Constants
646
720
 
721
+ `Source ->` [`common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/websocket/common/constants.ts)
722
+
647
723
  ### `WebSocketEvents`
648
724
 
649
725
  | Constant | Value | Description |
@@ -657,12 +733,10 @@ type TWebSocketMessageType = 'client' | 'user' | 'room' | 'broadcast';
657
733
  | `HEARTBEAT` | `'heartbeat'` | Client -> Server keep-alive |
658
734
  | `ENCRYPTED` | `'encrypted'` | Encrypted message wrapper |
659
735
 
660
- Utility methods:
661
-
662
736
  ```typescript
663
737
  WebSocketEvents.isValid('authenticate'); // true
664
738
  WebSocketEvents.isValid('invalid'); // false
665
- WebSocketEvents.SCHEME_SET; // Set of all valid event strings
739
+ WebSocketEvents.SCHEME_SET; // Set of all valid event strings
666
740
  ```
667
741
 
668
742
  ### `WebSocketChannels`
@@ -673,9 +747,9 @@ WebSocketEvents.SCHEME_SET; // Set of all valid event strings
673
747
  | `ROOM_PREFIX` | `'ws:room:'` | Room channel prefix |
674
748
  | `CLIENT_PREFIX` | `'ws:client:'` | Client channel prefix |
675
749
  | `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 |
750
+ | `forRoom({ room })` | `'ws:room:{room}'` | Build a room channel name |
751
+ | `forClient({ clientId })` | `'ws:client:{clientId}'` | Build a client channel name |
752
+ | `forUser({ userId })` | `'ws:user:{userId}'` | Build a user channel name |
679
753
  | `forRoomPattern()` | `'ws:room:*'` | Room pattern for `psubscribe` |
680
754
  | `forClientPattern()` | `'ws:client:*'` | Client pattern for `psubscribe` |
681
755
  | `forUserPattern()` | `'ws:user:*'` | User pattern for `psubscribe` |
@@ -690,13 +764,13 @@ WebSocketEvents.SCHEME_SET; // Set of all valid event strings
690
764
  | `BROADCAST_TOPIC` | `'ws:internal:broadcast'` | Bun pub/sub broadcast topic |
691
765
  | `MAX_PAYLOAD_LENGTH` | `131072` (128KB) | Maximum incoming payload size |
692
766
  | `IDLE_TIMEOUT` | `60` (seconds) | Bun transport idle timeout |
693
- | `BACKPRESSURE_LIMIT` | `1048576` (1MB) | Bun backpressure threshold |
767
+ | `BACKPRESSURE_LIMIT` | `1048576` (1MB) | Bun backpressure threshold (Bun's own default, not applied by the helper) |
694
768
  | `SEND_PINGS` | `true` | Bun transport pings enabled |
695
- | `PUBLISH_TO_SELF` | `false` | Bun pub/sub self-delivery disabled |
769
+ | `PUBLISH_TO_SELF` | `false` | Bun pub/sub self-delivery disabled (Bun's own default, not applied by the helper) |
696
770
  | `AUTH_TIMEOUT` | `5000` (5s) | Authentication timeout |
697
771
  | `HEARTBEAT_INTERVAL` | `30000` (30s) | Heartbeat sweep interval |
698
- | `HEARTBEAT_TIMEOUT` | `90000` (90s) | Heartbeat inactivity threshold |
699
- | `ENCRYPTED_BATCH_LIMIT` | `10` | Max concurrent encryption operations |
772
+ | `HEARTBEAT_TIMEOUT` | `90000` (90s, 3x interval) | Heartbeat inactivity threshold |
773
+ | `ENCRYPTED_BATCH_LIMIT` | `10` | Max concurrent `outboundTransformer` invocations |
700
774
 
701
775
  ### `WebSocketMessageTypes`
702
776
 
@@ -707,11 +781,9 @@ WebSocketEvents.SCHEME_SET; // Set of all valid event strings
707
781
  | `ROOM` | `'room'` | Message targeted at a room |
708
782
  | `BROADCAST` | `'broadcast'` | Message targeted at all clients |
709
783
 
710
- Utility methods:
711
-
712
784
  ```typescript
713
785
  WebSocketMessageTypes.isValid('room'); // true
714
- WebSocketMessageTypes.SCHEME_SET; // Set { 'client', 'user', 'room', 'broadcast' }
786
+ WebSocketMessageTypes.SCHEME_SET; // Set { 'client', 'user', 'room', 'broadcast' }
715
787
  ```
716
788
 
717
789
  ### `WebSocketClientStates`
@@ -719,18 +791,59 @@ WebSocketMessageTypes.SCHEME_SET; // Set { 'client', 'user', 'room', 'broadc
719
791
  | Constant | Value | Description |
720
792
  |----------|-------|-------------|
721
793
  | `UNAUTHORIZED` | `'unauthorized'` | Initial state after connection |
722
- | `AUTHENTICATING` | `'authenticating'` | Auth in progress |
723
- | `AUTHENTICATED` | `'authenticated'` | Successfully authenticated |
794
+ | `AUTHENTICATING` | `'authenticating'` | `authenticate` event received, awaiting `authenticateFn` |
795
+ | `AUTHENTICATED` | `'authenticated'` | Successfully authenticated, fully operational |
724
796
  | `DISCONNECTED` | `'disconnected'` | Client has disconnected |
725
797
 
726
- Utility methods:
727
-
728
798
  ```typescript
729
799
  WebSocketClientStates.isValid('authenticated'); // true
730
800
  WebSocketClientStates.SCHEME_SET; // Set of all valid state strings
731
801
  ```
732
802
 
733
- ## See Also
803
+ ### Close codes
804
+
805
+ | Code | Meaning | Trigger |
806
+ |------|---------|---------|
807
+ | `4001` | Authentication timeout | Client did not authenticate within `authTimeout` (or `authTimeout * 3` once `authenticateFn` started) |
808
+ | `4002` | Heartbeat timeout | No activity for `heartbeatTimeout` |
809
+ | `4003` | Authentication failed | `authenticateFn` returned `null`/`false` or threw |
810
+ | `4004` | Encryption required | `requireEncryption` is `true` and `handshakeFn` is missing or rejected |
811
+ | `1001` | Going away | Server shutting down gracefully (`shutdown()`) |
812
+
813
+ ## Troubleshooting
814
+
815
+ ### Client disconnects immediately with close code 4001
816
+
817
+ - **Symptom.** The client connects but is closed before it can interact - it did not send `{ event: 'authenticate', data: { ... } }` within `authTimeout` (default 5s).
818
+ - **Wrong message shape.** e.g. sending `{ type: 'auth' }` instead of `{ event: 'authenticate' }`.
819
+ - **Client waits for the server first.** The server sends nothing after upgrade - the client must initiate.
820
+ - **Slow token retrieval.** Pushes the auth message past the timeout window.
821
+
822
+ > [!TIP]
823
+ > Send `{ event: 'authenticate', data: { token: '...' } }` immediately in the client's `onopen` handler. If token retrieval is slow, raise `authTimeout`.
824
+
825
+ ### `helper.send()` delivers locally but other instances never receive it
826
+
827
+ - **Topology mismatch.** A single-instance Redis client against a Redis Cluster deployment will not route correctly.
828
+ - **Configure ordering.** `await helper.configure()` must run to completion before you start accepting connections - subscriptions are set up asynchronously.
829
+ - **Network/ACL.** No firewall or ACL should block `SUBSCRIBE`/`PSUBSCRIBE` on the duplicated clients.
830
+
831
+ ### `requireEncryption` is true but clients get disconnected with code 4004
832
+
833
+ - **`handshakeFn` missing.** The server logs `"requireEncryption is true but no handshakeFn configured"` and closes the client.
834
+ - **`handshakeFn` rejected.** It returned `null`/`false` - typically because required key-exchange data (e.g. `publicKey`) was missing from the authenticate payload.
835
+
836
+ ### `[WebSocketServerHelper] Invalid redis connection!` / `[WebSocketEmitter] Invalid redis connection!`
837
+
838
+ Thrown synchronously during construction when `redisConnection` is `null`/`undefined`. Pass a valid `IRedisHelper` instance (e.g. `RedisSingleHelper`).
839
+
840
+ ### `Redis client did not become ready within 30000ms`
841
+
842
+ Thrown during `configure()` when a duplicated Redis client fails to reach `'ready'` status. Check that the Redis server is reachable and the underlying `IRedisHelper` instance is configured correctly.
843
+
844
+ ## See also
734
845
 
735
- - [Setup & Usage](./) -- Getting started, examples, and troubleshooting
736
- - [Socket.IO Helper](../socket-io/) -- Socket.IO-based alternative with Node.js support
846
+ - [WebSocket overview](/extensions/helpers/websocket/) - getting started and the most common tasks
847
+ - [Socket.IO Helper](/extensions/helpers/socket-io/) - Socket.IO-based alternative with Node.js support
848
+ - [Redis Helper](/extensions/helpers/redis/) - `RedisSingleHelper` / `RedisClusterHelper` used for cross-instance messaging
849
+ - [WebSocket Component](/extensions/components/websocket/) - component-level lifecycle integration