@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,64 +1,103 @@
1
- # Socket.IO -- API Reference
1
+ ---
2
+ title: Socket.IO - Full Reference
3
+ description: Complete reference for SocketIOServerHelper and SocketIOClientHelper - every option, method signature, type, constant, and error case
4
+ difficulty: intermediate
5
+ ---
2
6
 
3
- > Full method signatures, type definitions, and constants for `SocketIOServerHelper` and `SocketIOClientHelper`.
7
+ # Socket.IO - Full Reference
8
+
9
+ Exhaustive reference for `SocketIOServerHelper` and `SocketIOClientHelper`. For a readable introduction and the common tasks, start with the [Socket.IO overview](/extensions/helpers/socket-io/).
10
+
11
+ **Files:**
12
+
13
+ - [`packages/helpers/src/modules/socket/socket-io/server/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/server/helper.ts) - `SocketIOServerHelper`
14
+ - [`packages/helpers/src/modules/socket/socket-io/client/helper.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/client/helper.ts) - `SocketIOClientHelper`
15
+ - [`packages/helpers/src/modules/socket/socket-io/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/common/types.ts) - option and callback types
16
+ - [`packages/helpers/src/modules/socket/socket-io/common/constants.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/common/constants.ts) - `SocketIOConstants`, `SocketIOClientStates`
17
+ - [`packages/helpers/src/modules/socket/socket-io/index.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/helpers/src/modules/socket/socket-io/index.ts) - barrel export (`@venizia/ignis-helpers/socket-io`)
18
+
19
+ ## Find what you need
20
+
21
+ | You want to | Go to |
22
+ |---|---|
23
+ | See the full server and client call sequence | [Architecture](#architecture) |
24
+ | Look up a server method (`send()`, `on()`, `getClients()`, ...) | [Server API](#server-api) |
25
+ | Look up a client method (`emit()`, `subscribe()`, `joinRooms()`, ...) | [Client API](#client-api) |
26
+ | Follow the auth handshake step by step | [Authentication Protocol](#authentication-protocol) |
27
+ | Understand the three Redis connections | [Redis Adapter](#redis-adapter) |
28
+ | Look up a type, option, or callback signature | [Types Reference](#types-reference) |
29
+ | Look up an event name, room default, or client state | [Constants](#constants) |
30
+ | Match an error message to its cause | [Common Errors](#common-errors) |
4
31
 
5
32
  ## Architecture
6
33
 
7
34
  ```
8
35
  SocketIOServerHelper (extends BaseHelper)
9
36
  |
10
- |-- configure()
11
- | |-- waitForRedisReady() x3 (pub, sub, emitter)
12
- | |-- initIOServer() (Node.js HTTPServer or Bun engine)
37
+ |-- constructor(opts)
38
+ | |-- setRuntime(opts) # validates `server` (Node) or `engine` (Bun)
39
+ | +-- initRedisClients(opts.redisConnection)
40
+ | |-- redisPub = duplicateClient()
41
+ | |-- redisSub = duplicateClient()
42
+ | +-- redisEmitter = duplicateClient()
43
+ |
44
+ |-- configure() [async]
45
+ | |-- ensureRedisClientsConnecting([redisPub, redisSub, redisEmitter])
46
+ | |-- await Promise.all(waitForRedisReady x3) # 30s timeout per client
47
+ | |-- initIOServer() # new IOServer(server, serverOptions) | new IOServer() + io.bind(engine)
13
48
  | |-- io.adapter(createAdapter(redisPub, redisSub))
14
- | |-- new Emitter(redisEmitter)
15
- | +-- io.on('connection', onClientConnect)
49
+ | |-- emitter = new Emitter(redisEmitter)
50
+ | +-- io.on('connection', socket => onClientConnect({ socket }))
16
51
  |
17
52
  |-- onClientConnect({ socket })
18
- | |-- Create ISocketIOClient (state: UNAUTHORIZED)
53
+ | |-- Create ISocketIOClient entry (state: UNAUTHORIZED)
19
54
  | |-- Start authenticateTimeout timer
20
55
  | |-- Register 'disconnect' handler
21
- | +-- Register 'authenticate' handler
22
- | |-- authenticateFn(handshake)
23
- | +-- onClientAuthenticated({ socket })
24
- | |-- State -> AUTHENTICATED
25
- | |-- Join defaultRooms
26
- | |-- Register room handlers (join/leave)
27
- | |-- Start ping interval
28
- | |-- Emit 'authenticated' to client
29
- | +-- Invoke clientConnectedFn
56
+ | +-- registerAuthHandler({ socket, handshake, clientId })
57
+ | +-- socket.on('authenticate', ...)
58
+ | |-- state -> AUTHENTICATING
59
+ | |-- authenticateFn(handshake)
60
+ | |-- true -> onClientAuthenticated({ socket })
61
+ | +-- false or throw -> emit 'unauthenticated', disconnect
30
62
  |
31
- |-- send({ destination?, payload, doLog?, cb? })
32
- | +-- emitter.compress(true).to(destination).emit(topic, data)
63
+ |-- onClientAuthenticated({ socket })
64
+ | |-- state -> AUTHENTICATED, clear authenticateTimeout
65
+ | |-- ping({ socket, doIgnoreAuth: true }) # immediate first ping
66
+ | |-- Join all defaultRooms
67
+ | |-- registerRoomHandlers({ socket, clientId }) # 'join' / 'leave'
68
+ | |-- interval = setInterval(ping, pingInterval)
69
+ | |-- send 'authenticated' to socket.id
70
+ | +-- invokeHook(clientConnectedFn)
33
71
  |
34
- +-- shutdown()
35
- |-- Disconnect all clients
36
- |-- io.close()
37
- +-- Quit Redis clients (pub, sub, emitter)
72
+ |-- send({ destination?, payload, doLog?, callback? })
73
+ | +-- emitter.compress(true).to(destination)?.emit(topic, data)
74
+ |
75
+ +-- shutdown() [async]
76
+ |-- Disconnect all tracked clients (clears their timers)
77
+ |-- close() # io.close()
78
+ +-- Promise.all([redisPub.quit(), redisSub.quit(), redisEmitter.quit()])
38
79
 
39
80
 
40
81
  SocketIOClientHelper (extends BaseHelper)
41
82
  |
42
83
  |-- constructor -> configure()
43
- | |-- io(host, options)
44
- | |-- Register 'connect' handler
45
- | |-- Register 'disconnect' handler
46
- | |-- Register 'connect_error' handler
47
- | |-- Register 'authenticated' handler
48
- | |-- Register 'unauthenticated' handler
49
- | +-- Register 'ping' handler
84
+ | |-- client = io(host, options)
85
+ | |-- Register 'connect' -> onConnected
86
+ | |-- Register 'disconnect' -> state UNAUTHORIZED, onDisconnected
87
+ | |-- Register 'connect_error' -> onError
88
+ | |-- Register 'authenticated' -> state AUTHENTICATED, onAuthenticated
89
+ | |-- Register 'unauthenticated' -> state UNAUTHORIZED, onUnauthenticated
90
+ | +-- Register 'ping' -> debug log
50
91
  |
51
- |-- authenticate()
52
- | +-- client.emit('authenticate')
53
- |
54
- |-- subscribe({ event, handler, ignoreDuplicate? })
55
- |-- emit({ topic, data, doLog?, cb? })
56
- |-- joinRooms({ rooms }) -> client.emit('join', { rooms })
92
+ |-- authenticate() -> state AUTHENTICATING, client.emit('authenticate')
93
+ |-- subscribe({ event, handler, ignoreDuplicate? }) # wraps handler, tracked in wrappedHandlers
94
+ |-- emit({ topic, data, doLog?, callback? })
95
+ |-- joinRooms({ rooms }) -> client.emit('join', { rooms })
57
96
  |-- leaveRooms({ rooms }) -> client.emit('leave', { rooms })
58
97
  |
59
98
  +-- shutdown()
60
99
  |-- client.removeAllListeners()
61
- |-- client.disconnect()
100
+ |-- disconnect() if connected
62
101
  +-- state -> UNAUTHORIZED
63
102
  ```
64
103
 
@@ -66,25 +105,35 @@ SocketIOClientHelper (extends BaseHelper)
66
105
 
67
106
  ### `SocketIOServerHelper`
68
107
 
69
- Extends `BaseHelper`. Manages a Socket.IO server with Redis adapter, authentication, room management, and heartbeat pings.
108
+ Extends `BaseHelper`. Manages a `socket.io` server with a Redis adapter, authentication, room management, and heartbeat pings.
70
109
 
71
110
  #### `constructor(opts: TSocketIOServerOptions)`
72
111
 
73
- Creates the server helper. Validates the runtime-specific server/engine and initializes three Redis client connections by duplicating the provided `redisConnection`.
112
+ Validates the runtime-specific `server` (Node) or `engine` (Bun) field. Duplicates the provided `redisConnection` into three independent clients (`redisPub`, `redisSub`, `redisEmitter`). Does **not** start the IO server - call `configure()` to complete initialization.
113
+
114
+ **Throws:**
74
115
 
75
- Does **not** start the IO server -- call `configure()` to complete initialization.
116
+ | Message | Condition |
117
+ |---------|-----------|
118
+ | `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!` | `runtime: 'node'` and `server` is falsy |
119
+ | `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!` | `runtime: 'bun'` and `engine` is falsy |
120
+ | `[SocketIOServerHelper] Unsupported runtime!` | `runtime` is neither `'node'` nor `'bun'` |
121
+ | `Invalid redis connection to config socket.io adapter!` | `redisConnection` is falsy |
76
122
 
77
123
  #### `configure(): Promise<void>`
78
124
 
79
- Initializes the Socket.IO server and sets up Redis infrastructure:
125
+ 1. Registers `error` listeners on all three duplicated Redis clients (logged, not thrown)
126
+ 2. Kicks off `connect()` on any client still in `'wait'` status
127
+ 3. `await Promise.all([waitForRedisReady(redisPub), waitForRedisReady(redisSub), waitForRedisReady(redisEmitter)])`
128
+ 4. Creates the `IOServer` - `new IOServer(server, serverOptions)` for Node.js, or `new IOServer()` followed by `io.bind(engine)` for Bun
129
+ 5. Attaches the Redis adapter via `@socket.io/redis-adapter`
130
+ 6. Creates the Redis emitter via `@socket.io/redis-emitter`
131
+ 7. Registers the `'connection'` handler
80
132
 
81
- 1. Ensures all three Redis clients (pub, sub, emitter) are connected and ready
82
- 2. Creates the `IOServer` based on runtime (`new IOServer(httpServer, serverOptions)` for Node.js, or `new IOServer()` with `io.bind(engine)` for Bun)
83
- 3. Attaches the Redis adapter via `@socket.io/redis-adapter`
84
- 4. Creates a Redis emitter via `@socket.io/redis-emitter`
85
- 5. Registers the `'connection'` event handler
133
+ Must be called before `on()`, `send()`, or any server operation.
86
134
 
87
- Must be called before `on()`, `send()`, or any server operations.
135
+ > [!NOTE]
136
+ > `waitForRedisReady` rejects after **30 seconds** if a client never reaches `ready` (or immediately on that client's `error` event). A broken Redis connection fails `configure()` instead of hanging boot indefinitely.
88
137
 
89
138
  #### `getIOServer(): IOServer`
90
139
 
@@ -92,144 +141,153 @@ Returns the underlying `socket.io` `Server` instance for direct access.
92
141
 
93
142
  ```typescript
94
143
  const io = socketServer.getIOServer();
95
- io.of('/admin').on('connection', (socket) => { /* ... */ });
144
+ io.of('/admin').on('connection', socket => {
145
+ /* ... */
146
+ });
96
147
  ```
97
148
 
98
149
  #### `getEngine(): any`
99
150
 
100
- Returns the Bun engine instance. Throws if the runtime is not `'bun'`.
151
+ Returns the Bun engine instance.
101
152
 
102
- ```typescript
103
- // Error: '[getEngine] Engine is only available for Bun runtime!'
104
- ```
153
+ **Throws:** `'[getEngine] Engine is only available for Bun runtime!'` - `runtime` is not `'bun'`
105
154
 
106
155
  #### `getClients(opts?: { id?: string }): ISocketIOClient | Map<string, ISocketIOClient> | undefined`
107
156
 
108
- Returns client information.
157
+ Without `id`, returns the full `Map<string, ISocketIOClient>`. With `{ id }`, returns that client's entry or `undefined`.
109
158
 
110
159
  ```typescript
111
- // Get all clients
112
160
  const allClients = socketServer.getClients() as Map<string, ISocketIOClient>;
113
-
114
- // Get a specific client by socket ID
115
161
  const client = socketServer.getClients({ id: 'socket-id' }) as ISocketIOClient | undefined;
116
162
  ```
117
163
 
118
- #### `on<HandlerArgsType, HandlerReturnType>(opts: { topic: string; handler: (...args: HandlerArgsType) => ValueOrPromise<HandlerReturnType> }): void`
164
+ #### `on<HandlerArgsType extends unknown[] = unknown[], HandlerReturnType = void>(opts: { topic: string; handler: (...args: HandlerArgsType) => ValueOrPromise<HandlerReturnType> }): void`
119
165
 
120
- Registers an event handler on the IO server instance.
166
+ Registers an event handler directly on the IO server instance (`io.on(topic, handler)`).
121
167
 
122
168
  ```typescript
123
169
  socketServer.on({
124
170
  topic: 'custom-event',
125
- handler: (data: { userId: string }) => {
126
- console.log('Received:', data);
127
- },
171
+ handler: (data: { userId: string }) => console.log('received:', data),
128
172
  });
129
173
  ```
130
174
 
131
175
  **Throws:**
132
- - `'[on] Invalid topic to start binding handler'` -- if `topic` is empty/falsy
133
- - `'[on] Invalid event handler | topic: {topic}'` -- if `handler` is missing
134
- - `'[on] IOServer is not initialized yet!'` -- if called before `configure()`
176
+
177
+ | Message | Condition |
178
+ |---------|-----------|
179
+ | `[on] Invalid topic to start binding handler` | `topic` is empty/falsy |
180
+ | `[on] Invalid event handler \| topic: {topic}` | `handler` is missing |
181
+ | `[on] IOServer is not initialized yet!` | called before `configure()` completes |
135
182
 
136
183
  #### `onClientConnect(opts: { socket: IOSocket }): void`
137
184
 
138
- Handles a new socket connection. Called automatically by the `'connection'` event. Can also be called manually.
185
+ Handles a new socket connection. Invoked automatically by the `'connection'` event, but can also be called manually.
139
186
 
140
187
  1. Creates an `ISocketIOClient` entry with state `UNAUTHORIZED`
141
188
  2. Starts the `authenticateTimeout` timer
142
189
  3. Registers `'disconnect'` and `'authenticate'` handlers on the socket
143
190
 
144
- Returns early (no-op) if `socket` is falsy or the client ID already exists.
191
+ No-op if `socket` is falsy or the client ID already exists in the registry.
145
192
 
146
193
  #### `onClientAuthenticated(opts: { socket: IOSocket }): void`
147
194
 
148
- Called after successful authentication. Can also be called manually to programmatically authenticate a client.
195
+ Runs after successful authentication. Can also be called manually to authenticate a client programmatically.
149
196
 
150
- 1. Sets client state to `AUTHENTICATED`
151
- 2. Sends an initial ping
152
- 3. Joins all `defaultRooms`
153
- 4. Registers room handlers (`join`, `leave`)
154
- 5. Starts the periodic ping interval
155
- 6. Emits `'authenticated'` event to the client with `{ id, time }`
156
- 7. Invokes `clientConnectedFn` callback (errors caught and logged)
197
+ 1. Sets state to `AUTHENTICATED`
198
+ 2. Clears and unsets the `authenticateTimeout` timer
199
+ 3. Sends an immediate ping (`doIgnoreAuth: true`)
200
+ 4. Joins all `defaultRooms`
201
+ 5. Registers room handlers (`'join'`, `'leave'`)
202
+ 6. Starts the periodic ping `interval`
203
+ 7. Emits `'authenticated'` to the client with `{ id, time }`
204
+ 8. Invokes `clientConnectedFn` - errors are caught and logged, never surfaced to the caller
205
+
206
+ If the client entry no longer exists (disconnected mid-flow), logs and calls `disconnect({ socket })` instead.
157
207
 
158
208
  #### `ping(opts: { socket: IOSocket; doIgnoreAuth: boolean }): void`
159
209
 
160
- Sends a `'ping'` event to the client with `{ time: ISO string }`.
210
+ Emits `'ping'` to the client with `{ time: ISO string }`.
161
211
 
162
- - If `doIgnoreAuth` is `false` and the client is not in `AUTHENTICATED` state, the client is disconnected
163
- - If the socket or client is not found, returns silently
212
+ - If `doIgnoreAuth` is `false` and the client is not `AUTHENTICATED`, the client is disconnected instead of pinged
213
+ - No-op if the socket or the tracked client is not found
164
214
 
165
215
  #### `disconnect(opts: { socket: IOSocket }): void`
166
216
 
167
- Disconnects a client and cleans up internal state:
168
-
169
217
  1. Clears the ping `interval` timer
170
218
  2. Clears the `authenticateTimeout` timer
171
- 3. Removes the client from the `clients` map
219
+ 3. Removes the client from the registry
172
220
  4. Calls `socket.disconnect()`
173
221
 
174
- #### `send(opts: { destination?: string; payload: { topic: string; data: any }; doLog?: boolean; cb?: () => void }): void`
222
+ No-op if `socket` is falsy.
223
+
224
+ #### `send(opts: { destination?: string; payload: { topic: string; data: any }; doLog?: boolean; callback?: () => void }): void`
175
225
 
176
- Emits a message via the Redis emitter with compression enabled.
226
+ Emits through the Redis emitter with compression enabled (`emitter.compress(true)`), reaching clients on **any** server instance.
177
227
 
178
228
  | Parameter | Type | Description |
179
229
  |-----------|------|-------------|
180
- | `destination` | `string \| undefined` | Socket ID or room name. If omitted, broadcasts to all |
230
+ | `destination` | `string \| undefined` | Socket ID or room name. Omit to broadcast to all clients |
181
231
  | `payload.topic` | `string` | Event name |
182
232
  | `payload.data` | `any` | Event payload |
183
- | `doLog` | `boolean` | If `true`, logs the message details. Default: `false` |
184
- | `cb` | `() => void` | Callback invoked asynchronously via `setImmediate` after emission |
233
+ | `doLog` | `boolean` | Logs the message details. Default: `false` |
234
+ | `callback` | `() => void` | Invoked via `setImmediate` after the emit call returns |
185
235
 
186
- Returns early (no-op) if `payload`, `topic`, or `data` is falsy.
236
+ No-op if `payload`, `payload.topic`, or `payload.data` is falsy. Logs an error and returns if called before `configure()` (the Redis emitter does not exist yet).
187
237
 
188
- #### `shutdown(): Promise<void>`
238
+ ```typescript
239
+ socketServer.send({
240
+ destination: 'some-room',
241
+ payload: { topic: 'update', data: { value: 42 } },
242
+ callback: () => console.log('queued'),
243
+ });
244
+ ```
189
245
 
190
- Gracefully shuts down the server:
246
+ #### `shutdown(): Promise<void>`
191
247
 
192
- 1. Disconnects all tracked clients (clears their intervals and timeouts)
248
+ 1. Disconnects every tracked client, clearing its `interval` and `authenticateTimeout`
193
249
  2. Clears the `clients` map
194
- 3. Closes the IO server
195
- 4. Quits all three Redis connections (pub, sub, emitter)
250
+ 3. Closes the IO server (no-op if `configure()` never ran)
251
+ 4. `await Promise.all([redisPub.quit(), redisSub.quit(), redisEmitter.quit()])` - runs even if closing the IO server rejects, so the three Redis sockets never leak
196
252
 
197
253
  ## Client API
198
254
 
199
255
  ### `SocketIOClientHelper`
200
256
 
201
- Extends `BaseHelper`. Manages a Socket.IO client connection with authentication, event subscriptions, and room operations.
257
+ Extends `BaseHelper`. Manages a `socket.io-client` connection with authentication, event subscriptions, and room operations.
202
258
 
203
259
  #### `constructor(opts: ISocketIOClientOptions)`
204
260
 
205
- Creates and immediately configures the client. The constructor calls `configure()` internally, which establishes the connection and registers lifecycle event handlers.
261
+ Stores the callbacks and calls `configure()` internally. The client starts connecting immediately.
206
262
 
207
263
  #### `configure(): void`
208
264
 
209
- Initializes the `socket.io-client` connection and registers internal event handlers. Called automatically by the constructor. If called again when a client already exists, returns early (no-op).
265
+ Creates the `socket.io-client` connection (`io(host, options)`) and registers the internal lifecycle handlers below. Called automatically by the constructor. A second call is a no-op if a client instance already exists.
266
+
267
+ | Event | Behavior |
268
+ |-------|----------|
269
+ | `'connect'` | Invokes `onConnected` |
270
+ | `'disconnect'` | State -> `UNAUTHORIZED`, invokes `onDisconnected(reason)` |
271
+ | `'connect_error'` | Invokes `onError(error)` |
272
+ | `'authenticated'` | State -> `AUTHENTICATED`, invokes `onAuthenticated` |
273
+ | `'unauthenticated'` | State -> `UNAUTHORIZED`, invokes `onUnauthenticated(message)` |
274
+ | `'ping'` | Logs a debug message only |
210
275
 
211
- Registered handlers:
212
- - `'connect'` -- invokes `onConnected` callback
213
- - `'disconnect'` -- resets state to `UNAUTHORIZED`, invokes `onDisconnected` callback
214
- - `'connect_error'` -- invokes `onError` callback
215
- - `'authenticated'` -- sets state to `AUTHENTICATED`, invokes `onAuthenticated` callback
216
- - `'unauthenticated'` -- resets state to `UNAUTHORIZED`, invokes `onUnauthenticated` callback
217
- - `'ping'` -- logs debug message
276
+ Every callback invocation is wrapped so a rejected promise is caught and logged, never thrown.
218
277
 
219
278
  #### `getState(): TSocketIOClientState`
220
279
 
221
- Returns the current authentication state: `'unauthorized'`, `'authenticating'`, or `'authenticated'`.
280
+ Returns `'unauthorized'`, `'authenticating'`, or `'authenticated'`.
222
281
 
223
282
  ```typescript
224
- const state = client.getState();
225
- if (state === 'authenticated') {
283
+ if (client.getState() === 'authenticated') {
226
284
  client.emit({ topic: 'message', data: { text: 'hello' } });
227
285
  }
228
286
  ```
229
287
 
230
288
  #### `getSocketClient(): Socket`
231
289
 
232
- Returns the underlying `socket.io-client` `Socket` instance for direct access.
290
+ Returns the underlying `socket.io-client` `Socket` instance.
233
291
 
234
292
  ```typescript
235
293
  const rawSocket = client.getSocketClient();
@@ -238,143 +296,177 @@ rawSocket.io.opts.reconnection = false;
238
296
 
239
297
  #### `authenticate(): void`
240
298
 
241
- Initiates the authentication handshake by emitting the `'authenticate'` event to the server.
299
+ Emits `'authenticate'` to the server to start the handshake.
242
300
 
243
- - Does nothing if the client is not connected
244
- - Does nothing if the current state is not `'unauthorized'`
301
+ - No-op if the client is not connected
302
+ - No-op if the current state is not `'unauthorized'`
245
303
  - Sets state to `AUTHENTICATING` before emitting
246
304
 
247
305
  ```typescript
248
- // Typically called inside the onConnected callback
249
306
  const client = new SocketIOClientHelper({
250
307
  // ...
251
- onConnected: () => {
252
- client.authenticate();
253
- },
308
+ onConnected: () => client.authenticate(),
254
309
  });
255
310
  ```
256
311
 
257
312
  #### `subscribe<T>(opts: { event: string; handler: TSocketIOEventHandler<T>; ignoreDuplicate?: boolean }): void`
258
313
 
259
- Registers an event handler on the client socket. The handler is automatically wrapped with error handling (catches both sync throws and async rejections).
314
+ Registers a handler on the client socket. The handler is wrapped with error handling that catches both sync throws and async rejections. That wrapper is tracked in an internal `wrappedHandlers` map, so `unsubscribe()` can find and remove it.
260
315
 
261
316
  | Parameter | Type | Default | Description |
262
317
  |-----------|------|---------|-------------|
263
- | `event` | `string` | -- | Event name to listen for |
264
- | `handler` | `TSocketIOEventHandler<T>` | -- | Callback receiving the event data |
265
- | `ignoreDuplicate` | `boolean` | `true` | If `true` and a handler already exists for this event, skips registration |
318
+ | `event` | `string` | - | Event name to listen for |
319
+ | `handler` | `TSocketIOEventHandler<T>` | - | Callback receiving the event data |
320
+ | `ignoreDuplicate` | `boolean` | `true` | Skip registration if a handler already exists for this event |
266
321
 
267
322
  #### `subscribeMany(opts: { events: Record<string, TSocketIOEventHandler>; ignoreDuplicate?: boolean }): void`
268
323
 
269
- Batch-registers multiple event handlers. Calls `subscribe()` for each entry.
324
+ Calls `subscribe()` for each entry in `events`.
270
325
 
271
326
  ```typescript
272
327
  client.subscribeMany({
273
328
  events: {
274
- 'event-a': (data) => { /* ... */ },
275
- 'event-b': (data) => { /* ... */ },
329
+ 'user-joined': data => console.log('joined:', data),
330
+ 'user-left': data => console.log('left:', data),
276
331
  },
277
- ignoreDuplicate: false,
278
332
  });
279
333
  ```
280
334
 
281
335
  #### `unsubscribe(opts: { event: string; handler?: TSocketIOEventHandler }): void`
282
336
 
283
- Removes event handlers from the client socket.
284
-
285
- - If `handler` is provided, removes only that specific handler
286
- - If `handler` is omitted, removes **all** handlers for the event
287
- - If no listeners exist for the event, returns early (no-op)
337
+ - With `handler`, removes only that specific handler (looked up in `wrappedHandlers`). No-op if that handler was not registered through this helper
338
+ - Without `handler`, removes **all** handlers for the event
339
+ - No-op if the event has no listeners
288
340
 
289
341
  #### `unsubscribeMany(opts: { events: string[] }): void`
290
342
 
291
- Batch-unsubscribes from multiple events. Removes all handlers for each event.
292
-
293
- ```typescript
294
- client.unsubscribeMany({ events: ['event-a', 'event-b'] });
295
- ```
343
+ Calls `unsubscribe()` for each event, removing all handlers for each.
296
344
 
297
345
  #### `connect(): void`
298
346
 
299
- Manually connects the client socket. Useful after a manual `disconnect()`.
300
-
301
- Returns early if the client instance does not exist.
347
+ Manually reconnects the client socket. No-op if the client instance does not exist (i.e. `configure()` never ran).
302
348
 
303
349
  #### `disconnect(): void`
304
350
 
305
- Manually disconnects the client socket without cleaning up listeners or resetting state.
306
-
307
- Returns early if the client instance does not exist.
351
+ Manually disconnects the client socket without removing listeners or resetting state. No-op if the client instance does not exist.
308
352
 
309
353
  > [!TIP]
310
- > Use `shutdown()` instead of `disconnect()` for a full cleanup that also removes listeners and resets the authentication state.
354
+ > Use `shutdown()` for a full cleanup that also removes listeners and resets the authentication state.
311
355
 
312
- #### `emit<T>(opts: { topic: string; data: T; doLog?: boolean; cb?: () => void }): void`
356
+ #### `emit<T>(opts: { topic: string; data: T; doLog?: boolean; callback?: () => void }): void`
313
357
 
314
358
  Emits an event to the server.
315
359
 
316
360
  | Parameter | Type | Default | Description |
317
361
  |-----------|------|---------|-------------|
318
- | `topic` | `string` | -- | Event name |
319
- | `data` | `T` | -- | Event payload |
320
- | `doLog` | `boolean` | `false` | If `true`, logs the emission details |
321
- | `cb` | `() => void` | `undefined` | Callback invoked asynchronously via `setImmediate` |
362
+ | `topic` | `string` | - | Event name |
363
+ | `data` | `T` | - | Event payload |
364
+ | `doLog` | `boolean` | `false` | Logs the emission details |
365
+ | `callback` | `() => void` | `undefined` | Invoked via `setImmediate` after the emit call |
322
366
 
323
- **Throws:**
324
- - `'Invalid socket client state to emit'` (status 400) -- if the client is not connected
325
- - `'Topic is required to emit'` (status 400) -- if `topic` is empty/falsy
367
+ **Throws** (both status `400`):
326
368
 
327
- #### `joinRooms(opts: { rooms: string[] }): void`
369
+ | Message | Condition |
370
+ |---------|-----------|
371
+ | `Invalid socket client state to emit` | the client is not connected |
372
+ | `Topic is required to emit` | `topic` is empty/falsy |
328
373
 
329
- Requests to join rooms by emitting a `'join'` event to the server with `{ rooms }`. The server validates the request through its `validateRoomFn`.
374
+ #### `joinRooms(opts: { rooms: string[] }): void`
330
375
 
331
- Logs a warning and returns early if the client is not connected.
376
+ Emits `'join'` with `{ rooms }`. The server validates the request through its `validateRoomFn`. Logs a warning and no-ops if the client is not connected.
332
377
 
333
378
  #### `leaveRooms(opts: { rooms: string[] }): void`
334
379
 
335
- Requests to leave rooms by emitting a `'leave'` event to the server with `{ rooms }`.
380
+ Emits `'leave'` with `{ rooms }`. Logs a warning and no-ops if the client is not connected.
336
381
 
337
- Logs a warning and returns early if the client is not connected.
382
+ > [!NOTE]
383
+ > Unlike `joinRooms()`, leave requests are **not** validated server-side. The client can request to leave any room name, including ones it never joined. `socket.leave()` on a room the socket isn't in is a no-op.
338
384
 
339
385
  #### `shutdown(): void`
340
386
 
341
- Fully shuts down the client:
342
-
343
387
  1. Removes all event listeners (`removeAllListeners()`)
344
388
  2. Disconnects if currently connected
345
389
  3. Resets state to `UNAUTHORIZED`
346
390
 
391
+ ## Authentication Protocol
392
+
393
+ ```
394
+ Client connects (transport only)
395
+ |
396
+ v
397
+ Server creates client entry (state: UNAUTHORIZED)
398
+ |-- Starts authenticateTimeout timer (default: 10s)
399
+ |-- Registers 'disconnect' handler
400
+ |
401
+ Client emits 'authenticate'
402
+ |
403
+ v
404
+ State -> AUTHENTICATING
405
+ Server calls authenticateFn(handshake)
406
+ |
407
+ +-- Resolves true:
408
+ | |-- State -> AUTHENTICATED, authenticateTimeout cleared
409
+ | |-- Immediate ping, join defaultRooms, start ping interval
410
+ | |-- Emit 'authenticated' { id, time } to client
411
+ | +-- Invoke clientConnectedFn (errors caught and logged)
412
+ |
413
+ +-- Resolves false:
414
+ | |-- State -> UNAUTHORIZED
415
+ | |-- Emit 'unauthenticated' { message: 'Invalid token to authenticate! Please login again!', time }
416
+ | +-- Disconnect (via the emit callback)
417
+ |
418
+ +-- Throws / rejects:
419
+ | |-- State -> UNAUTHORIZED
420
+ | |-- Emit 'unauthenticated' { message: 'Failed to authenticate connection! Please login again!', time }
421
+ | +-- Disconnect (via the emit callback), logged with doLog: true
422
+ |
423
+ +-- authenticateTimeout elapses first:
424
+ +-- Disconnect - including a client whose authenticateFn is still pending;
425
+ its eventual resolution finds the client already removed and is a no-op
426
+ ```
427
+
428
+ A client that re-emits `'authenticate'` while already `AUTHENTICATING` or `AUTHENTICATED` is ignored (logged as a warning, `authenticateFn` is not called again).
429
+
430
+ ## Redis Adapter
431
+
432
+ The server uses `@socket.io/redis-adapter` and `@socket.io/redis-emitter` for horizontal scaling. Three Redis connections are created by duplicating the provided `redisConnection`:
433
+
434
+ | Client | Purpose |
435
+ |--------|---------|
436
+ | `redisPub` | Publishes adapter messages (room membership, cross-instance socket.io internals) |
437
+ | `redisSub` | Subscribes to adapter messages |
438
+ | `redisEmitter` | Powers `send()` for cross-instance message delivery |
439
+
440
+ - **Initialized during `configure()`.** All three clients are created and awaited before the IO server starts.
441
+ - **Lazy connect handled explicitly.** Duplicated clients inherit `lazyConnect` but never dial on their own. If the parent `redisConnection` uses `lazyConnect` (`autoConnect: false`), `configure()` kicks each duplicated client into `connect()` explicitly.
442
+
347
443
  ## Types Reference
348
444
 
349
445
  ### `IHandshake`
350
446
 
351
- Represents the client handshake data available during authentication.
352
-
353
447
  ```typescript
354
448
  interface IHandshake {
355
- headers: IncomingHttpHeaders; // HTTP headers from the initial request
356
- time: string; // Connection time as ISO string
357
- address: string; // Client IP address
358
- xdomain: boolean; // Whether the connection is cross-domain
359
- secure: boolean; // Whether the connection uses TLS
360
- issued: number; // Timestamp when the handshake was issued
361
- url: string; // Request URL
362
- query: ParsedUrlQuery; // Parsed query string parameters
363
- auth: { [key: string]: any }; // Authentication payload sent by the client
449
+ headers: IncomingHttpHeaders;
450
+ time: string;
451
+ address: string;
452
+ xdomain: boolean;
453
+ secure: boolean;
454
+ issued: number;
455
+ url: string;
456
+ query: ParsedUrlQuery;
457
+ auth: { [key: string]: any };
364
458
  }
365
459
  ```
366
460
 
367
461
  ### `ISocketIOClient`
368
462
 
369
- Internal representation of a connected client tracked by the server.
370
-
371
463
  ```typescript
372
464
  interface ISocketIOClient {
373
- id: string; // Socket ID
374
- socket: IOSocket; // The socket.io Socket instance
375
- state: TSocketIOClientState; // 'unauthorized' | 'authenticating' | 'authenticated'
376
- interval?: NodeJS.Timeout; // Ping interval timer (set after authentication)
377
- authenticateTimeout: NodeJS.Timeout; // Auth timeout timer (cleared after authentication)
465
+ id: string;
466
+ socket: IOSocket;
467
+ state: TSocketIOClientState;
468
+ interval?: NodeJS.Timeout; // Ping interval timer, set after authentication
469
+ authenticateTimeout?: NodeJS.Timeout; // Cleared (set to undefined) after authentication
378
470
  }
379
471
  ```
380
472
 
@@ -386,44 +478,31 @@ type TSocketIOClientState = 'unauthorized' | 'authenticating' | 'authenticated';
386
478
 
387
479
  ### `TSocketIOAuthenticateFn`
388
480
 
389
- Server-side authentication callback. Receives the handshake data and returns a boolean indicating whether to accept or reject the connection.
390
-
391
481
  ```typescript
392
482
  type TSocketIOAuthenticateFn = (args: IHandshake) => ValueOrPromise<boolean>;
393
483
  ```
394
484
 
395
485
  ### `TSocketIOValidateRoomFn`
396
486
 
397
- Server-side room validation callback. Receives the socket and requested rooms, returns the subset of rooms the client is allowed to join.
398
-
399
487
  ```typescript
400
- type TSocketIOValidateRoomFn = (opts: {
401
- socket: IOSocket;
402
- rooms: string[];
403
- }) => ValueOrPromise<string[]>;
488
+ type TSocketIOValidateRoomFn = (opts: { socket: IOSocket; rooms: string[] }) => ValueOrPromise<string[]>;
404
489
  ```
405
490
 
406
491
  ### `TSocketIOClientConnectedFn`
407
492
 
408
- Server-side callback invoked after a client is fully authenticated and has joined default rooms.
409
-
410
493
  ```typescript
411
- type TSocketIOClientConnectedFn = (opts: {
412
- socket: IOSocket;
413
- }) => ValueOrPromise<void>;
494
+ type TSocketIOClientConnectedFn = (opts: { socket: IOSocket }) => ValueOrPromise<void>;
414
495
  ```
415
496
 
416
497
  ### `TSocketIOEventHandler<T>`
417
498
 
418
- Client-side event handler type.
419
-
420
499
  ```typescript
421
500
  type TSocketIOEventHandler<T = unknown> = (data: T) => ValueOrPromise<void>;
422
501
  ```
423
502
 
424
503
  ### `IOptions`
425
504
 
426
- Client connection options. Extends `SocketOptions` from `socket.io-client`.
505
+ Client connection options. Extends `SocketOptions` from `socket.io-client`; `path` and `extraHeaders` are required by the type.
427
506
 
428
507
  ```typescript
429
508
  interface IOptions extends SocketOptions {
@@ -432,24 +511,53 @@ interface IOptions extends SocketOptions {
432
511
  }
433
512
  ```
434
513
 
514
+ ### `ISocketIOClientOptions`
515
+
516
+ | Option | Type | Default | Description |
517
+ |--------|------|---------|-------------|
518
+ | `identifier` | `string` | - | Unique identifier for this client (used as logger scope) |
519
+ | `host` | `string` | - | Server URL to connect to (e.g. `'http://localhost:3000'`) |
520
+ | `options` | `IOptions` | - | Socket.IO client options |
521
+ | `onConnected` | `() => ValueOrPromise<void>` | `undefined` | Called when the transport connection is established |
522
+ | `onDisconnected` | `(reason: string) => ValueOrPromise<void>` | `undefined` | Called on disconnect; state resets to `'unauthorized'` first |
523
+ | `onError` | `(error: Error) => ValueOrPromise<void>` | `undefined` | Called on `'connect_error'` |
524
+ | `onAuthenticated` | `() => ValueOrPromise<void>` | `undefined` | Called when the server confirms authentication |
525
+ | `onUnauthenticated` | `(message: string) => ValueOrPromise<void>` | `undefined` | Called when the server rejects authentication |
526
+
435
527
  ### `TSocketIOServerOptions`
436
528
 
437
- Discriminated union for server constructor options:
529
+ Discriminated union on `runtime`.
438
530
 
439
531
  ```typescript
440
532
  type TSocketIOServerOptions = ISocketIOServerNodeOptions | ISocketIOServerBunOptions;
441
533
 
534
+ interface ISocketIOServerBaseOptions {
535
+ identifier: string;
536
+ serverOptions: Partial<ServerOptions>; // socket.io ServerOptions - required by the type, pass {} if unused
537
+ redisConnection: IRedisHelper;
538
+ defaultRooms?: string[]; // Default: ['io-default', 'io-notification']
539
+ authenticateTimeout?: number; // Default: 10000 (10s)
540
+ pingInterval?: number; // Default: 30000 (30s)
541
+
542
+ authenticateFn: TSocketIOAuthenticateFn;
543
+ validateRoomFn?: TSocketIOValidateRoomFn;
544
+ clientConnectedFn?: TSocketIOClientConnectedFn;
545
+ }
546
+
442
547
  interface ISocketIOServerNodeOptions extends ISocketIOServerBaseOptions {
443
548
  runtime: 'node';
444
- server: HTTPServer;
549
+ server: HTTPServer; // node:http Server
445
550
  }
446
551
 
447
552
  interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
448
553
  runtime: 'bun';
449
- engine: any;
554
+ engine: any; // @socket.io/bun-engine Server instance - typed `any` since it's an optional peer dep
450
555
  }
451
556
  ```
452
557
 
558
+ > [!WARNING]
559
+ > If no `validateRoomFn` is provided, **all** custom room join requests are rejected with a warning log. Clients still get `defaultRooms` automatically.
560
+
453
561
  ## Constants
454
562
 
455
563
  ### `SocketIOConstants`
@@ -458,12 +566,12 @@ interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
458
566
  |----------|-------|-------------|
459
567
  | `EVENT_PING` | `'ping'` | Heartbeat event emitted by the server at `pingInterval` |
460
568
  | `EVENT_CONNECT` | `'connection'` | Server-side connection event |
461
- | `EVENT_DISCONNECT` | `'disconnect'` | Disconnect event (both server and client) |
569
+ | `EVENT_DISCONNECT` | `'disconnect'` | Disconnect event (server and client) |
462
570
  | `EVENT_JOIN` | `'join'` | Room join request event |
463
571
  | `EVENT_LEAVE` | `'leave'` | Room leave request event |
464
- | `EVENT_AUTHENTICATE` | `'authenticate'` | Client-to-server authentication request |
465
- | `EVENT_AUTHENTICATED` | `'authenticated'` | Server-to-client authentication success |
466
- | `EVENT_UNAUTHENTICATE` | `'unauthenticated'` | Server-to-client authentication failure |
572
+ | `EVENT_AUTHENTICATE` | `'authenticate'` | Client -> server authentication request |
573
+ | `EVENT_AUTHENTICATED` | `'authenticated'` | Server -> client authentication success |
574
+ | `EVENT_UNAUTHENTICATE` | `'unauthenticated'` | Server -> client authentication failure |
467
575
  | `ROOM_DEFAULT` | `'io-default'` | Default room name |
468
576
  | `ROOM_NOTIFICATION` | `'io-notification'` | Default notification room name |
469
577
 
@@ -475,23 +583,36 @@ interface ISocketIOServerBunOptions extends ISocketIOServerBaseOptions {
475
583
  | `AUTHENTICATING` | `'authenticating'` | Authentication in progress |
476
584
  | `AUTHENTICATED` | `'authenticated'` | Successfully authenticated |
477
585
 
478
- #### `SocketIOClientStates.isValid(input: string): boolean`
479
-
480
- Static method that checks whether a string is a valid client state value.
586
+ `SocketIOClientStates.isValid(input: string): input is TConstValue<typeof SocketIOClientStates>` - checks membership against the backing `SCHEME_SET`.
481
587
 
482
588
  ```typescript
483
589
  SocketIOClientStates.isValid('authenticated'); // true
484
590
  SocketIOClientStates.isValid('invalid'); // false
485
591
  ```
486
592
 
487
- ### Internal Defaults
593
+ ### Internal defaults
488
594
 
489
595
  | Constant | Value | Description |
490
596
  |----------|-------|-------------|
491
- | `CLIENT_AUTHENTICATE_TIMEOUT` | `10000` (10 s) | Default timeout before disconnecting unauthenticated clients |
492
- | `CLIENT_PING_INTERVAL` | `30000` (30 s) | Default interval between heartbeat pings |
493
-
494
- ## See Also
495
-
496
- - [Setup & Usage](./) -- Getting started, constructor options, and examples
497
- - [WebSocket Helper](../websocket/) -- Bun-native WebSocket alternative
597
+ | `CLIENT_AUTHENTICATE_TIMEOUT` | `10_000` (10s) | Default `authenticateTimeout` |
598
+ | `CLIENT_PING_INTERVAL` | `30_000` (30s) | Default `pingInterval` |
599
+
600
+ ## Common Errors
601
+
602
+ | Error | Cause | Fix |
603
+ |-------|-------|-----|
604
+ | `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!` | `server` missing/falsy with `runtime: 'node'` | Pass a valid `http.Server` instance |
605
+ | `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!` | `engine` missing/falsy with `runtime: 'bun'` | Pass a valid `@socket.io/bun-engine` `Server` instance |
606
+ | `[SocketIOServerHelper] Unsupported runtime!` | `runtime` is neither `'node'` nor `'bun'` | Use `RuntimeModules.NODE`, `RuntimeModules.BUN`, or `RuntimeModules.detect()` |
607
+ | `Invalid redis connection to config socket.io adapter!` | `redisConnection` missing, `null`, or `undefined` | Pass a valid `IRedisHelper` (e.g. `RedisSingleHelper`, `RedisClusterHelper`) |
608
+ | `[on] Invalid topic to start binding handler` | Empty/falsy `topic` passed to `on()` | Provide a non-empty string topic |
609
+ | `[on] IOServer is not initialized yet!` | `on()` called before `configure()` completed | `await configure()` before registering handlers |
610
+ | `Invalid socket client state to emit` (client) | `emit()` called while not connected | Check `getState()` / connection status, or emit inside `onConnected` |
611
+ | `Topic is required to emit` (client) | `emit()` called with an empty/falsy `topic` | Provide a non-empty string topic |
612
+
613
+ ## See also
614
+
615
+ - [Socket.IO overview](/extensions/helpers/socket-io/) - getting started, constructor options, and common tasks
616
+ - [Socket.IO Component](/extensions/components/socket-io/) - DI-managed lifecycle wrapper around this helper
617
+ - [WebSocket Helper](../websocket/) - Bun-native alternative with no `socket.io` dependency
618
+ - [Redis Helper](/extensions/helpers/redis/reference) - `duplicateClient()` and the topology classes used as `redisConnection`