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