@venizia/ignis-docs 0.2.0 → 0.2.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +22 -11
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +26 -2
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +6 -2
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +182 -93
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +107 -322
  26. package/content/extensions/components/authentication/api.md +454 -603
  27. package/content/extensions/components/authentication/errors.md +121 -498
  28. package/content/extensions/components/authentication/index.md +88 -801
  29. package/content/extensions/components/authentication/usage.md +207 -956
  30. package/content/extensions/components/authorization/api.md +736 -656
  31. package/content/extensions/components/authorization/errors.md +168 -206
  32. package/content/extensions/components/authorization/index.md +82 -797
  33. package/content/extensions/components/authorization/usage.md +194 -527
  34. package/content/extensions/components/health-check.md +71 -243
  35. package/content/extensions/components/mail/api.md +504 -287
  36. package/content/extensions/components/mail/errors.md +73 -61
  37. package/content/extensions/components/mail/index.md +96 -467
  38. package/content/extensions/components/mail/usage.md +130 -172
  39. package/content/extensions/components/request-tracker.md +66 -173
  40. package/content/extensions/components/socket-io/api.md +195 -13
  41. package/content/extensions/components/socket-io/errors.md +3 -3
  42. package/content/extensions/components/socket-io/index.md +50 -337
  43. package/content/extensions/components/socket-io/usage.md +143 -26
  44. package/content/extensions/components/static-asset/api.md +410 -142
  45. package/content/extensions/components/static-asset/errors.md +110 -53
  46. package/content/extensions/components/static-asset/index.md +79 -608
  47. package/content/extensions/components/static-asset/usage.md +180 -300
  48. package/content/extensions/components/websocket/api.md +275 -399
  49. package/content/extensions/components/websocket/errors.md +47 -56
  50. package/content/extensions/components/websocket/index.md +74 -407
  51. package/content/extensions/components/websocket/usage.md +110 -341
  52. package/content/extensions/helpers/cron/index.md +51 -160
  53. package/content/extensions/helpers/crypto/index.md +62 -483
  54. package/content/extensions/helpers/crypto/reference.md +456 -0
  55. package/content/extensions/helpers/env/index.md +60 -178
  56. package/content/extensions/helpers/error/index.md +221 -207
  57. package/content/extensions/helpers/inversion/index.md +65 -556
  58. package/content/extensions/helpers/inversion/reference.md +522 -0
  59. package/content/extensions/helpers/kafka/admin.md +20 -1
  60. package/content/extensions/helpers/kafka/compile-binary.md +41 -35
  61. package/content/extensions/helpers/kafka/consumer.md +54 -20
  62. package/content/extensions/helpers/kafka/examples.md +21 -16
  63. package/content/extensions/helpers/kafka/index.md +80 -610
  64. package/content/extensions/helpers/kafka/producer.md +134 -5
  65. package/content/extensions/helpers/kafka/schema-registry.md +45 -70
  66. package/content/extensions/helpers/logger/hf-logger.md +193 -0
  67. package/content/extensions/helpers/logger/index.md +64 -563
  68. package/content/extensions/helpers/logger/pino.md +85 -0
  69. package/content/extensions/helpers/logger/reference.md +746 -0
  70. package/content/extensions/helpers/network/api.md +241 -195
  71. package/content/extensions/helpers/network/index.md +72 -530
  72. package/content/extensions/helpers/queue/index.md +72 -900
  73. package/content/extensions/helpers/queue/reference.md +467 -0
  74. package/content/extensions/helpers/redis/index.md +73 -645
  75. package/content/extensions/helpers/redis/reference.md +727 -0
  76. package/content/extensions/helpers/secrets/index.md +66 -0
  77. package/content/extensions/helpers/socket-io/api.md +305 -203
  78. package/content/extensions/helpers/socket-io/index.md +66 -432
  79. package/content/extensions/helpers/storage/api.md +564 -462
  80. package/content/extensions/helpers/storage/index.md +77 -573
  81. package/content/extensions/helpers/types/index.md +66 -499
  82. package/content/extensions/helpers/types/reference.md +650 -0
  83. package/content/extensions/helpers/uid/index.md +58 -227
  84. package/content/extensions/helpers/websocket/api.md +329 -216
  85. package/content/extensions/helpers/websocket/index.md +65 -503
  86. package/content/extensions/helpers/worker-thread/index.md +58 -396
  87. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  88. package/content/guides/core-concepts/persistent/models.md +1 -1
  89. package/content/guides/core-concepts/persistent/search-typesense.md +3 -3
  90. package/content/guides/core-concepts/persistent/transactions.md +1 -1
  91. package/content/guides/core-concepts/secrets-vault.md +177 -0
  92. package/content/guides/core-concepts/services.md +1 -1
  93. package/content/guides/migrations/redis-helpers-migration.md +1 -1
  94. package/content/guides/migrations/unified-connectors-migration.md +2 -2
  95. package/content/guides/tutorials/ecommerce-api.md +3 -8
  96. package/content/references/base/application.md +1 -1
  97. package/content/references/base/connectors.md +79 -136
  98. package/content/references/base/datasources-reference.md +599 -0
  99. package/content/references/base/datasources.md +84 -444
  100. package/content/references/base/dependency-injection.md +17 -39
  101. package/content/references/base/filter-system/application-usage.md +69 -121
  102. package/content/references/base/filter-system/array-operators.md +12 -0
  103. package/content/references/base/filter-system/comparison-operators.md +12 -0
  104. package/content/references/base/filter-system/default-filter.md +136 -348
  105. package/content/references/base/filter-system/fields-order-pagination.md +38 -16
  106. package/content/references/base/filter-system/index.md +106 -257
  107. package/content/references/base/filter-system/json-filtering.md +12 -2
  108. package/content/references/base/filter-system/list-operators.md +16 -2
  109. package/content/references/base/filter-system/logical-operators.md +13 -0
  110. package/content/references/base/filter-system/null-operators.md +13 -0
  111. package/content/references/base/filter-system/pattern-matching.md +12 -0
  112. package/content/references/base/filter-system/quick-reference.md +11 -2
  113. package/content/references/base/filter-system/range-operators.md +12 -0
  114. package/content/references/base/filter-system/tips.md +70 -133
  115. package/content/references/base/filter-system/use-cases.md +156 -233
  116. package/content/references/base/middlewares.md +35 -21
  117. package/content/references/base/models-reference.md +886 -0
  118. package/content/references/base/models.md +80 -1452
  119. package/content/references/base/repositories/advanced.md +156 -192
  120. package/content/references/base/repositories/index.md +77 -650
  121. package/content/references/base/repositories/mixins.md +22 -18
  122. package/content/references/base/repositories/relations.md +123 -171
  123. package/content/references/base/repositories/soft-deletable.md +58 -56
  124. package/content/references/base/secrets.md +263 -0
  125. package/content/references/base/services.md +2 -2
  126. package/content/references/configuration/environment-variables.md +48 -4
  127. package/content/references/configuration/index.md +49 -31
  128. package/content/references/quick-reference.md +3 -16
  129. package/content/references/utilities/crypto.md +35 -76
  130. package/content/references/utilities/date.md +33 -73
  131. package/content/references/utilities/index.md +1 -1
  132. package/content/references/utilities/jsx-reference.md +298 -0
  133. package/content/references/utilities/jsx.md +82 -525
  134. package/content/references/utilities/module.md +29 -62
  135. package/content/references/utilities/parse.md +34 -64
  136. package/content/references/utilities/performance.md +33 -58
  137. package/content/references/utilities/promise.md +28 -62
  138. package/content/references/utilities/request.md +57 -218
  139. package/content/references/utilities/schema.md +43 -137
  140. package/content/references/utilities/statuses-reference.md +361 -0
  141. package/content/references/utilities/statuses.md +63 -667
  142. package/package.json +8 -8
@@ -1,51 +1,21 @@
1
- # Socket.IO
2
-
3
- Runtime-agnostic Socket.IO server and client helpers with built-in authentication flow, room management, Redis adapter for horizontal scaling, and automatic heartbeat keep-alive.
4
-
5
- ## Quick Reference
1
+ ---
2
+ title: Socket.IO
3
+ description: Runtime-agnostic Socket.IO server and client helpers with a built-in authentication handshake and Redis-backed horizontal scaling
4
+ difficulty: intermediate
5
+ ---
6
6
 
7
- | Class | Extends | Role |
8
- |-------|---------|------|
9
- | `SocketIOServerHelper` | `BaseHelper` | Manages a Socket.IO server with authentication, rooms, ping intervals, and Redis-backed messaging |
10
- | `SocketIOClientHelper` | `BaseHelper` | Manages a Socket.IO client connection with authentication, event subscriptions, and room operations |
11
-
12
- #### Import Paths
13
-
14
- ```typescript
15
- import {
16
- SocketIOServerHelper,
17
- SocketIOClientHelper,
18
- SocketIOConstants,
19
- SocketIOClientStates,
20
- } from '@venizia/ignis-helpers/socket-io';
21
-
22
- import type {
23
- TSocketIOServerOptions,
24
- ISocketIOServerBaseOptions,
25
- ISocketIOServerNodeOptions,
26
- ISocketIOServerBunOptions,
27
- ISocketIOClientOptions,
28
- IOptions,
29
- ISocketIOClient,
30
- IHandshake,
31
- TSocketIOAuthenticateFn,
32
- TSocketIOValidateRoomFn,
33
- TSocketIOClientConnectedFn,
34
- TSocketIOEventHandler,
35
- TSocketIOClientState,
36
- } from '@venizia/ignis-helpers/socket-io';
37
- ```
7
+ # Socket.IO
38
8
 
39
- ## Creating an Instance
9
+ `SocketIOServerHelper` and `SocketIOClientHelper` wrap `socket.io` with a mandatory post-connection authentication handshake, room management, and a Redis adapter so events reach clients no matter which server instance they are connected to.
40
10
 
41
- ### Server
11
+ ## In one example
42
12
 
43
- `SocketIOServerHelper` requires a Redis connection for the pub/sub adapter, an HTTP server (Node.js) or Bun engine instance, and an authentication function.
13
+ The smallest working server: construct with a Redis connection and an `authenticateFn`, then `configure()`.
44
14
 
45
15
  ```typescript
46
- import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
47
- import { RedisSingleHelper } from '@venizia/ignis-helpers';
48
16
  import { createServer } from 'node:http';
17
+ import { RedisSingleHelper } from '@venizia/ignis-helpers';
18
+ import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
49
19
 
50
20
  const httpServer = createServer();
51
21
 
@@ -61,457 +31,121 @@ const socketServer = new SocketIOServerHelper({
61
31
  runtime: 'node',
62
32
  server: httpServer,
63
33
  redisConnection: redisHelper,
64
- serverOptions: {
65
- cors: { origin: '*' },
66
- path: '/socket.io',
67
- },
68
- authenticateFn: async (handshake) => {
69
- const token = handshake.auth?.token;
70
- return !!token; // Return true to accept, false to reject
71
- },
72
- validateRoomFn: async ({ socket, rooms }) => {
73
- // Return only the rooms the client is allowed to join
74
- return rooms.filter(r => r.startsWith('public-'));
75
- },
76
- clientConnectedFn: async ({ socket }) => {
77
- console.log('Client authenticated:', socket.id);
78
- },
79
- defaultRooms: ['io-default', 'io-notification'],
80
- authenticateTimeout: 10000,
81
- pingInterval: 30000,
82
- });
83
- ```
84
-
85
- #### `TSocketIOServerOptions`
86
-
87
- A discriminated union based on the `runtime` field:
88
-
89
- | Option | Type | Default | Description |
90
- |--------|------|---------|-------------|
91
- | `identifier` | `string` | -- | Unique identifier for this server instance (used as logger scope) |
92
- | `runtime` | `'node' \| 'bun'` | -- | Runtime environment. Determines which server field is required |
93
- | `server` | `HTTPServer` | -- | Node.js HTTP server instance. **Required when `runtime` is `'node'`** |
94
- | `engine` | `any` | -- | `@socket.io/bun-engine` Server instance. **Required when `runtime` is `'bun'`** |
95
- | `serverOptions` | `Partial<ServerOptions>` | -- | **Required by the type.** Socket.IO `ServerOptions` (cors, path, transports, etc.). Pass `{}` when no options are needed; nullish values fall back to `{}` at runtime |
96
- | `redisConnection` | `IRedisHelper` | -- | **Required.** Redis helper used to create pub, sub, and emitter clients |
97
- | `authenticateFn` | `TSocketIOAuthenticateFn` | -- | **Required.** Called with the client's handshake data. Return `true` to accept, `false` to reject |
98
- | `validateRoomFn` | `TSocketIOValidateRoomFn` | `undefined` | Called when a client requests to join rooms. Return the allowed subset |
99
- | `clientConnectedFn` | `TSocketIOClientConnectedFn` | `undefined` | Called after a client is fully authenticated and has joined default rooms |
100
- | `defaultRooms` | `string[]` | `['io-default', 'io-notification']` | Rooms that every authenticated client joins automatically |
101
- | `authenticateTimeout` | `number` | `10000` (10 s) | Milliseconds before an unauthenticated client is disconnected |
102
- | `pingInterval` | `number` | `30000` (30 s) | Interval in milliseconds between heartbeat pings to authenticated clients |
103
-
104
- > [!WARNING]
105
- > If no `validateRoomFn` is provided, **all room join requests are rejected** with a warning log. You must provide this callback if you want clients to join custom rooms beyond the `defaultRooms`.
106
-
107
- ### Client
108
-
109
- `SocketIOClientHelper` connects to a Socket.IO server. Configuration is done entirely via the constructor -- `configure()` is called automatically.
110
-
111
- ```typescript
112
- import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
113
-
114
- const socketClient = new SocketIOClientHelper({
115
- identifier: 'my-client',
116
- host: 'http://localhost:3000',
117
- options: {
118
- path: '/socket.io',
119
- extraHeaders: {
120
- Authorization: 'Bearer my-jwt-token',
121
- },
122
- },
123
- onConnected: () => {
124
- console.log('Connected to server');
125
- socketClient.authenticate();
126
- },
127
- onDisconnected: (reason) => {
128
- console.log('Disconnected:', reason);
129
- },
130
- onError: (error) => {
131
- console.error('Connection error:', error);
132
- },
133
- onAuthenticated: () => {
134
- console.log('Successfully authenticated');
135
- },
136
- onUnauthenticated: (message) => {
137
- console.warn('Authentication failed:', message);
138
- },
139
- });
140
- ```
141
-
142
- #### `ISocketIOClientOptions`
143
-
144
- | Option | Type | Default | Description |
145
- |--------|------|---------|-------------|
146
- | `identifier` | `string` | -- | Unique identifier for this client (used as logger scope) |
147
- | `host` | `string` | -- | Server URL to connect to (e.g., `'http://localhost:3000'`) |
148
- | `options` | `IOptions` | -- | Socket.IO client options (extends `SocketOptions` with `path` and `extraHeaders`) |
149
- | `onConnected` | `() => ValueOrPromise<void>` | `undefined` | Called when the transport connection is established |
150
- | `onDisconnected` | `(reason: string) => ValueOrPromise<void>` | `undefined` | Called when disconnected. The client state resets to `'unauthorized'` |
151
- | `onError` | `(error: Error) => ValueOrPromise<void>` | `undefined` | Called on connection errors |
152
- | `onAuthenticated` | `() => ValueOrPromise<void>` | `undefined` | Called when the server sends an `authenticated` event |
153
- | `onUnauthenticated` | `(message: string) => ValueOrPromise<void>` | `undefined` | Called when the server rejects authentication |
154
-
155
- #### `IOptions`
156
-
157
- Extends `SocketOptions` from `socket.io-client`:
158
-
159
- | Option | Type | Description |
160
- |--------|------|-------------|
161
- | `path` | `string` | Socket.IO server path (e.g., `'/socket.io'`) |
162
- | `extraHeaders` | `Record<string \| symbol \| number, any>` | Additional headers sent with the connection request |
163
-
164
- ## Usage
165
-
166
- ### Server Setup
167
-
168
- After constructing the server helper, call `configure()` to initialize the Socket.IO server, set up the Redis adapter, and start listening for connections.
169
-
170
- ```typescript
171
- const socketServer = new SocketIOServerHelper({
172
- identifier: 'my-server',
173
- runtime: 'node',
174
- server: httpServer,
175
- redisConnection: redisHelper,
176
34
  serverOptions: { cors: { origin: '*' } },
177
- authenticateFn: async (handshake) => {
178
- return verifyToken(handshake.auth?.token);
179
- },
35
+ authenticateFn: async handshake => !!handshake.auth?.token,
180
36
  });
181
37
 
182
38
  await socketServer.configure();
183
- // Server is now ready and listening for connections
184
-
185
- httpServer.listen(3000, () => {
186
- console.log('HTTP + Socket.IO server running on port 3000');
187
- });
39
+ httpServer.listen(3000);
188
40
  ```
189
41
 
190
- #### Bun Runtime
191
-
192
- For Bun, pass the `@socket.io/bun-engine` instance instead of an HTTP server:
42
+ A client connects at the transport level, then must explicitly authenticate before it can join rooms or exchange events:
193
43
 
194
44
  ```typescript
195
- import { SocketIOServerHelper } from '@venizia/ignis-helpers/socket-io';
196
-
197
- const socketServer = new SocketIOServerHelper({
198
- identifier: 'my-bun-server',
199
- runtime: 'bun',
200
- engine: bunEngineInstance,
201
- redisConnection: redisHelper,
202
- serverOptions: {},
203
- authenticateFn: async (handshake) => {
204
- return verifyToken(handshake.auth?.token);
205
- },
206
- });
207
-
208
- await socketServer.configure();
209
- ```
210
-
211
- ### Client Connection
212
-
213
- The client connects automatically on construction. Call `authenticate()` after the connection is established to trigger the server-side authentication flow.
45
+ import { SocketIOClientHelper } from '@venizia/ignis-helpers/socket-io';
214
46
 
215
- ```typescript
216
47
  const client = new SocketIOClientHelper({
217
48
  identifier: 'app-client',
218
49
  host: 'http://localhost:3000',
219
50
  options: {
220
51
  path: '/socket.io',
221
- extraHeaders: { Authorization: 'Bearer my-token' },
222
- },
223
- onConnected: () => {
224
- // Connection established -- initiate authentication
225
- client.authenticate();
226
- },
227
- onAuthenticated: () => {
228
- // Now safe to subscribe and emit
229
- client.joinRooms({ rooms: ['chat-room-1'] });
52
+ extraHeaders: { Authorization: 'Bearer my-jwt-token' },
230
53
  },
54
+ onConnected: () => client.authenticate(),
55
+ onAuthenticated: () => client.emit({ topic: 'ready', data: {} }),
231
56
  });
232
57
  ```
233
58
 
234
- ### Emitting Events
235
-
236
- #### From the Server
59
+ ## How it works
237
60
 
238
- Use `send()` to emit events through the Redis emitter. Messages can target a specific socket ID, a room, or broadcast to all clients.
61
+ - **Two independent helpers.** `SocketIOServerHelper` wraps a `socket.io` `Server`; `SocketIOClientHelper` wraps a `socket.io-client` `Socket`. Both extend `BaseHelper`, and the client can talk to any `socket.io` server, not only this one.
62
+ - **Runtime-agnostic server.** Pass a Node.js `http.Server` for `runtime: 'node'`, or an `@socket.io/bun-engine` instance for `runtime: 'bun'`.
63
+ - **Redis is mandatory server-side.** `configure()` duplicates the parent `redisConnection` into three dedicated clients: `redisPub`/`redisSub` power `@socket.io/redis-adapter` for cross-instance room broadcast, and `redisEmitter` powers `@socket.io/redis-emitter` for `send()`.
64
+ - **Boot fails fast on a broken Redis connection.** `configure()` waits for all three clients to reach `ready` and rejects after 30 seconds if any never do, so a broken Redis connection fails boot instead of hanging it.
65
+ - **Authentication is a step separate from connecting.** A client connects at the transport level in state `UNAUTHORIZED`, then must emit `'authenticate'`. The server calls `authenticateFn(handshake)`; only `true` moves the client to `AUTHENTICATED` and joins it to `defaultRooms`.
66
+ - **Unauthenticated clients time out.** A client that never authenticates within `authenticateTimeout` is disconnected - including one whose `authenticateFn` is still pending when the timeout fires.
67
+ - **Heartbeat has no pong check.** Once authenticated, the server pings on `pingInterval` as a keep-alive; a silently dead connection is only caught when the underlying transport itself notices.
68
+ - **Custom rooms need `validateRoomFn`.** Room joins beyond `defaultRooms` are rejected unless you supply it - without it, every custom join request is dropped.
239
69
 
240
- ```typescript
241
- // Send to a specific client
242
- socketServer.send({
243
- destination: 'client-socket-id',
244
- payload: {
245
- topic: 'notification',
246
- data: { message: 'Hello!' },
247
- },
248
- });
70
+ **Defaults**
249
71
 
250
- // Broadcast to all connected clients (no destination)
251
- socketServer.send({
252
- payload: {
253
- topic: 'announcement',
254
- data: { message: 'Server update in 5 minutes' },
255
- },
256
- });
72
+ | Option | Default | Purpose |
73
+ |--------|---------|---------|
74
+ | `authenticateTimeout` | 10s | Disconnects a client that never authenticates |
75
+ | `pingInterval` | 30s | Server heartbeat interval once authenticated |
257
76
 
258
- // Send with logging and callback
259
- socketServer.send({
260
- destination: 'some-room',
261
- payload: {
262
- topic: 'room-event',
263
- data: { action: 'update' },
264
- },
265
- doLog: true,
266
- cb: () => {
267
- console.log('Message queued');
268
- },
269
- });
270
- ```
77
+ ## Common tasks
271
78
 
272
- #### From the Client
79
+ ### Send a message from the server
273
80
 
274
- Use `emit()` to send events to the server.
81
+ `send()` goes through the Redis emitter, so it reaches the target on any server instance. Omit `destination` to broadcast to everyone.
275
82
 
276
83
  ```typescript
277
- client.emit({
278
- topic: 'chat-message',
279
- data: { text: 'Hello, world!' },
280
- });
281
-
282
- // With logging enabled
283
- client.emit({
284
- topic: 'user-action',
285
- data: { action: 'click', target: 'button-1' },
286
- doLog: true,
287
- });
288
-
289
- // With callback
290
- client.emit({
291
- topic: 'upload-complete',
292
- data: { fileId: '123' },
293
- cb: () => {
294
- console.log('Emit completed');
295
- },
84
+ socketServer.send({
85
+ destination: 'some-room', // socket ID, room name, or omitted to broadcast
86
+ payload: { topic: 'notification', data: { message: 'Hello!' } },
87
+ callback: () => console.log('queued'),
296
88
  });
297
89
  ```
298
90
 
299
- ### Listening for Events
300
-
301
- #### Server-Side Event Binding
91
+ ### Listen for a custom event
302
92
 
303
- Use `on()` to register event handlers on the IO server instance.
93
+ Register on the server with `on()`; subscribe on the client with `subscribe()`.
304
94
 
305
95
  ```typescript
306
96
  socketServer.on({
307
97
  topic: 'custom-event',
308
- handler: (data) => {
309
- console.log('Received:', data);
310
- },
98
+ handler: (data: { userId: string }) => console.log('received:', data),
311
99
  });
312
- ```
313
-
314
- #### Client-Side Event Subscription
315
100
 
316
- Use `subscribe()` for single events and `subscribeMany()` for batch registration.
317
-
318
- ```typescript
319
- // Single event
320
101
  client.subscribe({
321
102
  event: 'notification',
322
- handler: (data) => {
323
- console.log('Notification:', data);
324
- },
325
- });
326
-
327
- // Prevent duplicate handlers (default behavior)
328
- client.subscribe({
329
- event: 'notification',
330
- handler: (data) => { /* ... */ },
331
- ignoreDuplicate: true, // Default: true -- skips if handler already exists
332
- });
333
-
334
- // Allow multiple handlers for same event
335
- client.subscribe({
336
- event: 'chat-message',
337
- handler: handler1,
338
- ignoreDuplicate: false,
339
- });
340
-
341
- // Batch subscribe
342
- client.subscribeMany({
343
- events: {
344
- 'user-joined': (data) => console.log('Joined:', data),
345
- 'user-left': (data) => console.log('Left:', data),
346
- 'typing': (data) => console.log('Typing:', data),
347
- },
103
+ handler: data => console.log('notification:', data),
348
104
  });
349
-
350
- // Unsubscribe from a specific event (removes all handlers)
351
- client.unsubscribe({ event: 'notification' });
352
-
353
- // Unsubscribe from multiple events
354
- client.unsubscribeMany({ events: ['user-joined', 'user-left'] });
355
- ```
356
-
357
- ### Rooms
358
-
359
- #### Client-Side Room Operations
360
-
361
- ```typescript
362
- // Join rooms (validated by server's validateRoomFn)
363
- client.joinRooms({ rooms: ['chat-room-1', 'notifications'] });
364
-
365
- // Leave rooms
366
- client.leaveRooms({ rooms: ['chat-room-1'] });
367
105
  ```
368
106
 
369
- #### Server-Side Room Behavior
107
+ ### Manage rooms
370
108
 
371
- Authenticated clients automatically join the `defaultRooms` (by default, `'io-default'` and `'io-notification'`). Custom room join requests are validated through `validateRoomFn` before the client is allowed to join.
109
+ Clients request rooms with `joinRooms()` / `leaveRooms()`; the server filters join requests through `validateRoomFn`.
372
110
 
373
111
  ```typescript
374
112
  const socketServer = new SocketIOServerHelper({
375
113
  // ...
376
114
  defaultRooms: ['general', 'announcements'],
377
- validateRoomFn: async ({ socket, rooms }) => {
378
- // Only allow rooms the user has permission for
379
- const userPermissions = await getUserPermissions(socket.id);
380
- return rooms.filter(room => userPermissions.includes(room));
381
- },
115
+ validateRoomFn: async ({ socket, rooms }) => rooms.filter(room => room.startsWith('public-')),
382
116
  });
383
- ```
384
-
385
- ### Authentication Flow
386
-
387
- The server enforces a post-connection authentication protocol:
388
117
 
118
+ client.joinRooms({ rooms: ['public-chat'] });
119
+ client.leaveRooms({ rooms: ['public-chat'] });
389
120
  ```
390
- Client connects
391
- |
392
- v
393
- Server creates client entry (state: UNAUTHORIZED)
394
- |-- Starts authenticateTimeout timer (default: 10s)
395
- |-- Registers disconnect handler
396
- |
397
- Client emits 'authenticate' event
398
- |
399
- v
400
- Server calls authenticateFn(handshake)
401
- |
402
- +-- Returns true:
403
- | |-- State -> AUTHENTICATED
404
- | |-- Clear auth timeout
405
- | |-- Join default rooms
406
- | |-- Start ping interval
407
- | |-- Emit 'authenticated' to client
408
- | +-- Invoke clientConnectedFn
409
- |
410
- +-- Returns false:
411
- | |-- State -> UNAUTHORIZED
412
- | |-- Emit 'unauthenticated' to client
413
- | +-- Disconnect
414
- |
415
- +-- Timeout (no auth within authenticateTimeout):
416
- +-- Disconnect
417
- ```
418
-
419
- ### Redis Adapter
420
-
421
- 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` client:
422
121
 
423
- - **redisPub** -- Publishes adapter messages
424
- - **redisSub** -- Subscribes to adapter messages
425
- - **redisEmitter** -- Powers `send()` for cross-instance message delivery
426
-
427
- All three connections are initialized and awaited during `configure()`. If the parent client uses `lazyConnect`, the duplicated clients will connect automatically.
428
-
429
- > [!NOTE]
430
- > `configure()` fails fast: if any of the three Redis clients never reaches the `ready` state, it rejects after **30 seconds** rather than hanging boot indefinitely.
122
+ ### Emit from the client
431
123
 
432
124
  ```typescript
433
- // Messages sent via send() use the Redis emitter,
434
- // so they reach clients on ANY server instance
435
- socketServer.send({
436
- destination: 'some-room',
437
- payload: {
438
- topic: 'update',
439
- data: { value: 42 },
440
- },
125
+ client.emit({
126
+ topic: 'chat-message',
127
+ data: { text: 'Hello, world!' },
128
+ callback: () => console.log('emit completed'),
441
129
  });
442
130
  ```
443
131
 
444
- ### Graceful Shutdown
445
-
446
- #### Server
447
-
448
- ```typescript
449
- await socketServer.shutdown();
450
- // 1. Disconnects all clients (clears intervals and timeouts)
451
- // 2. Closes the IO server
452
- // 3. Quits all three Redis connections (pub, sub, emitter)
453
- ```
454
-
455
- #### Client
132
+ ### Shut down cleanly
456
133
 
457
134
  ```typescript
458
- client.shutdown();
459
- // 1. Removes all event listeners
460
- // 2. Disconnects if connected
461
- // 3. Resets state to 'unauthorized'
135
+ await socketServer.shutdown(); // disconnects clients, closes IO server, quits all 3 Redis clients
136
+ client.shutdown(); // removes listeners, disconnects, resets state
462
137
  ```
463
138
 
464
- ## Troubleshooting
465
-
466
- ### `[SocketIOServerHelper] Invalid HTTP server for Node.js runtime!`
467
-
468
- **Cause:** The `server` option is missing or falsy when `runtime` is `'node'`.
469
-
470
- **Fix:** Pass a valid `http.Server` instance.
471
-
472
- ### `[SocketIOServerHelper] Invalid @socket.io/bun-engine instance for Bun runtime!`
473
-
474
- **Cause:** The `engine` option is missing or falsy when `runtime` is `'bun'`.
475
-
476
- **Fix:** Pass a valid `@socket.io/bun-engine` Server instance.
477
-
478
- ### `[SocketIOServerHelper] Unsupported runtime!`
479
-
480
- **Cause:** The `runtime` value is neither `'node'` nor `'bun'`.
481
-
482
- **Fix:** Use `RuntimeModules.NODE` or `RuntimeModules.BUN`.
483
-
484
- ### `Invalid redis connection to config socket.io adapter!`
485
-
486
- **Cause:** The `redisConnection` option is missing, `null`, or `undefined`.
487
-
488
- **Fix:** Pass a valid `IRedisHelper` instance (e.g., `RedisSingleHelper` or `RedisClusterHelper`).
489
-
490
- ### `[on] Invalid topic to start binding handler`
491
-
492
- **Cause:** An empty or falsy `topic` was passed to `on()`.
493
-
494
- **Fix:** Provide a non-empty string topic.
495
-
496
- ### `[on] IOServer is not initialized yet!`
497
-
498
- **Cause:** `on()` was called before `configure()` completed.
499
-
500
- **Fix:** Await `configure()` before registering event handlers.
501
-
502
- ### `Invalid socket client state to emit`
503
-
504
- **Cause (client):** `emit()` was called when the client is not connected.
505
-
506
- **Fix:** Check that the client is connected before emitting, or emit inside the `onConnected` callback.
507
-
508
- ### `Topic is required to emit`
509
-
510
- **Cause (client):** `emit()` was called with an empty or falsy `topic`.
139
+ ## See also
511
140
 
512
- **Fix:** Provide a non-empty string topic.
141
+ - [Full reference](./api) - every method signature, type, constant, and error case
142
+ - [Socket.IO Component](/extensions/components/socket-io/) - DI-managed lifecycle wrapper around this helper
143
+ - [WebSocket Helper](../websocket/) - Bun-native alternative with no `socket.io` dependency
144
+ - [Redis Helper](../redis/) - `RedisSingleHelper` / `RedisClusterHelper` used as `redisConnection`
513
145
 
514
- ## See Also
146
+ **Files:**
515
147
 
516
- - [API Reference](./api) -- Full method signatures, types, and constants
517
- - [WebSocket Helper](../websocket/) -- Bun-native WebSocket alternative
148
+ - [`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`
149
+ - [`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`
150
+ - [`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
151
+ - [`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`