@venizia/ignis-docs 0.0.4 → 0.0.5

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.
@@ -1,145 +1,562 @@
1
1
  # Socket.IO Component
2
2
 
3
- Real-time, bidirectional, event-based communication using Socket.IO.
3
+ Real-time, bidirectional, event-based communication using Socket.IO — with automatic runtime detection for both **Node.js** and **Bun**.
4
4
 
5
5
  ## Quick Reference
6
6
 
7
- | Component | Purpose |
8
- |-----------|---------|
9
- | **SocketIOComponent** | Sets up Socket.IO server and bindings |
10
- | **SocketIOServerHelper** | Encapsulates Socket.IO server instance |
11
- | **Redis Adapter** | Enables horizontal scaling with Redis |
12
- | **Redis Emitter** | Emit events from other processes/services |
7
+ | Item | Value |
8
+ |------|-------|
9
+ | **Package** | `@venizia/ignis` (core) |
10
+ | **Class** | `SocketIOComponent` |
11
+ | **Helper** | [`SocketIOServerHelper`](/references/helpers/socket-io) |
12
+ | **Runtimes** | Node.js (`@hono/node-server`) and Bun (native) |
13
+ | **Scaling** | `@socket.io/redis-adapter` + `@socket.io/redis-emitter` |
13
14
 
14
- ### Required Bindings
15
+ ### Binding Keys
15
16
 
16
- | Binding Key | Type | Purpose |
17
- |-------------|------|---------|
18
- | `SERVER_OPTIONS` | `ISocketIOOptions` | Socket.IO server configuration (optional) |
19
- | `REDIS_CONNECTION` | `DefaultRedisHelper` | Redis instance for scaling |
20
- | `AUTHENTICATE_HANDLER` | `Function` | Authenticate socket connections |
21
- | `CLIENT_CONNECTED_HANDLER` | `Function` | Handle successful connections (optional) |
17
+ | Binding Key | Constant | Type | Required | Default |
18
+ |------------|----------|------|----------|---------|
19
+ | `@app/socket-io/server-options` | `SocketIOBindingKeys.SERVER_OPTIONS` | `Partial<IServerOptions>` | No | See [Default Options](#default-server-options) |
20
+ | `@app/socket-io/redis-connection` | `SocketIOBindingKeys.REDIS_CONNECTION` | `RedisHelper` / `DefaultRedisHelper` | **Yes** | `null` |
21
+ | `@app/socket-io/authenticate-handler` | `SocketIOBindingKeys.AUTHENTICATE_HANDLER` | `TSocketIOAuthenticateFn` | **Yes** | `null` |
22
+ | `@app/socket-io/validate-room-handler` | `SocketIOBindingKeys.VALIDATE_ROOM_HANDLER` | `TSocketIOValidateRoomFn` | No | `null` |
23
+ | `@app/socket-io/client-connected-handler` | `SocketIOBindingKeys.CLIENT_CONNECTED_HANDLER` | `TSocketIOClientConnectedFn` | No | `null` |
24
+ | `@app/socket-io/instance` | `SocketIOBindingKeys.SOCKET_IO_INSTANCE` | `SocketIOServerHelper` | — | *Set by component* |
25
+
26
+ > [!NOTE]
27
+ > `SOCKET_IO_INSTANCE` is **not** set by you — the component creates and binds it automatically after the server starts. Inject it in services/controllers to interact with Socket.IO.
22
28
 
23
29
  ### Use Cases
24
30
 
25
- - Live notifications
26
- - Real-time chat
27
- - Collaborative editing
28
- - Live data streams
31
+ - Live notifications and alerts
32
+ - Real-time chat and messaging
33
+ - Collaborative editing (docs, whiteboards)
34
+ - Live data streams (dashboards, monitoring)
35
+ - Multiplayer game state synchronization
29
36
 
30
- ## Architecture Components
37
+ ---
31
38
 
32
- - **`SocketIOComponent`**: Sets up Socket.IO server, binds `SocketIOServerHelper` to DI
33
- - **`SocketIOServerHelper`**: Wraps Socket.IO server, provides interaction methods
34
- - **`@socket.io/redis-adapter`**: Scales Socket.IO with Redis
35
- - **`@socket.io/redis-emitter`**: Emits events from external processes
36
- - **Integration**: Works with HTTP server and authentication system
39
+ ## Architecture Overview
37
40
 
38
- ## Implementation Details
41
+ ```
42
+ SocketIOComponent
43
+ ┌──────────────────────────────────────────┐
44
+ │ │
45
+ │ binding() │
46
+ │ ├── resolveBindings() │
47
+ │ │ ├── SERVER_OPTIONS │
48
+ │ │ ├── REDIS_CONNECTION │
49
+ │ │ ├── AUTHENTICATE_HANDLER │
50
+ │ │ ├── VALIDATE_ROOM_HANDLER │
51
+ │ │ └── CLIENT_CONNECTED_HANDLER │
52
+ │ │ │
53
+ │ └── RuntimeModules.detect() │
54
+ │ ├── BUN → registerBunHook() │
55
+ │ └── NODE → registerNodeHook() │
56
+ │ │
57
+ │ (Post-start hooks execute after server) │
58
+ │ ├── Creates SocketIOServerHelper │
59
+ │ ├── await socketIOHelper.configure() │
60
+ │ ├── Binds to SOCKET_IO_INSTANCE │
61
+ │ └── Wires into server (runtime-specific)│
62
+ └──────────────────────────────────────────┘
63
+ ```
39
64
 
40
- ### Tech Stack
65
+ ### Lifecycle Integration
41
66
 
42
- - **Socket.IO**
43
- - **`@socket.io/redis-adapter`**
44
- - **`@socket.io/redis-emitter`**
45
- - **`ioredis`** (if using Redis)
67
+ The component uses the **post-start hook** system to solve a fundamental timing problem: Socket.IO needs a running server instance, but components are initialized *before* the server starts.
46
68
 
47
- ### Configuration
69
+ ```
70
+ Application Lifecycle
71
+ ═════════════════════
72
+
73
+ ┌─────────────────┐
74
+ │ preConfigure() │ ← Register SocketIOComponent here
75
+ └────────┬────────┘
76
+
77
+ ┌────────▼────────┐
78
+ │ initialize() │ ← Component.binding() runs here
79
+ │ │ Resolves bindings, registers post-start hook
80
+ └────────┬────────┘
81
+
82
+ ┌────────▼────────┐
83
+ │ setupMiddlewares │
84
+ └────────┬────────┘
85
+
86
+ ┌────────▼──────────────┐
87
+ │ startBunModule() OR │ ← Server starts, instance created
88
+ │ startNodeModule() │
89
+ └────────┬──────────────┘
90
+
91
+ ┌────────▼──────────────────┐
92
+ │ executePostStartHooks() │ ← SocketIOServerHelper created HERE
93
+ │ └── socket-io-initialize│ Server instance is now available
94
+ └───────────────────────────┘
95
+ ```
48
96
 
49
- To use the Socket.IO component, you need to provide a few things in your application's DI container:
97
+ ### Runtime-Specific Behavior
50
98
 
51
- - **`SocketIOBindingKeys.SERVER_OPTIONS`**: (Optional) Custom options for the Socket.IO server.
52
- - **`SocketIOBindingKeys.REDIS_CONNECTION`**: An instance of `DefaultRedisHelper` (or a compatible class) for the Redis adapter.
53
- - **`SocketIOBindingKeys.AUTHENTICATE_HANDLER`**: A function to handle the authentication of new socket connections.
54
- - **`SocketIOBindingKeys.CLIENT_CONNECTED_HANDLER`**: (Optional) A function to be called when a client is successfully connected and authenticated.
99
+ | Aspect | Node.js | Bun |
100
+ |--------|---------|-----|
101
+ | **Server Type** | `node:http.Server` | `Bun.Server` |
102
+ | **IO Server Init** | `new IOServer(httpServer, opts)` | `new IOServer()` + `io.bind(engine)` |
103
+ | **Engine** | Built-in (`socket.io`) | `@socket.io/bun-engine` (optional peer dep) |
104
+ | **Request Routing** | Socket.IO attaches to HTTP server automatically | `server.reload({ fetch, websocket })` wires engine into Bun's request loop |
105
+ | **WebSocket Upgrade** | Handled by `node:http.Server` upgrade event | Handled by Bun's `websocket` handler |
106
+ | **Dynamic Import** | None needed | `await import('@socket.io/bun-engine')` at runtime |
55
107
 
56
- ### Code Samples
108
+ ---
57
109
 
58
- #### 1. Setting up the Socket.IO Component
110
+ ## Setup Guide
111
+
112
+ ### Step 1: Install Dependencies
113
+
114
+ ```bash
115
+ # Core dependency (already included via @venizia/ignis)
116
+ # ioredis is required for the Redis adapter
117
+
118
+ # For Bun runtime only — optional peer dependency
119
+ bun add @socket.io/bun-engine
120
+ ```
59
121
 
60
- In your `src/application.ts`:
122
+ ### Step 2: Bind Required Services
123
+
124
+ In your application's `preConfigure()` method, bind the required services and register the component:
61
125
 
62
126
  ```typescript
63
127
  import {
128
+ BaseApplication,
64
129
  SocketIOComponent,
65
130
  SocketIOBindingKeys,
66
- RedisHelper, // Your Redis helper
67
- BaseApplication,
131
+ RedisHelper,
132
+ TSocketIOAuthenticateFn,
133
+ TSocketIOValidateRoomFn,
134
+ TSocketIOClientConnectedFn,
68
135
  ValueOrPromise,
69
- IHandshake,
70
136
  } from '@venizia/ignis';
71
137
 
72
- // ...
73
-
74
138
  export class Application extends BaseApplication {
75
- // ...
139
+ private redisHelper: RedisHelper;
76
140
 
77
141
  preConfigure(): ValueOrPromise<void> {
78
- // ...
79
-
80
- // 1. Bind Redis connection
81
- const redis = new RedisHelper({
82
- name: 'redis',
83
- host: 'localhost',
84
- port: 6379,
85
- password: 'password',
142
+ this.setupSocketIO();
143
+ // ... other setup
144
+ }
145
+
146
+ setupSocketIO() {
147
+ // 1. Redis connection (required for adapter + emitter)
148
+ this.redisHelper = new RedisHelper({
149
+ name: 'socket-io-redis',
150
+ host: process.env.REDIS_HOST ?? 'localhost',
151
+ port: +(process.env.REDIS_PORT ?? 6379),
152
+ password: process.env.REDIS_PASSWORD,
153
+ autoConnect: false,
86
154
  });
87
- this.bind({ key: SocketIOBindingKeys.REDIS_CONNECTION }).toValue(redis);
88
155
 
89
- // 2. Bind authentication handler
90
- const authenticateFn = async (handshake: IHandshake) => {
91
- const { token } = handshake.auth;
92
- // Your custom authentication logic here
93
- // e.g., verify a JWT
156
+ this.bind<RedisHelper>({
157
+ key: SocketIOBindingKeys.REDIS_CONNECTION,
158
+ }).toValue(this.redisHelper);
159
+
160
+ // 2. Authentication handler (required)
161
+ const authenticateFn: TSocketIOAuthenticateFn = handshake => {
162
+ const token = handshake.headers.authorization;
163
+ // Implement your auth logic — JWT verification, session check, etc.
94
164
  return !!token;
95
165
  };
96
- this.bind({ key: SocketIOBindingKeys.AUTHENTICATE_HANDLER }).toValue(authenticateFn);
97
166
 
98
- // 3. Register the component
167
+ this.bind<TSocketIOAuthenticateFn>({
168
+ key: SocketIOBindingKeys.AUTHENTICATE_HANDLER,
169
+ }).toValue(authenticateFn);
170
+
171
+ // 3. Room validation handler (optional — joins rejected without this)
172
+ const validateRoomFn: TSocketIOValidateRoomFn = ({ socket, rooms }) => {
173
+ // Return the rooms that the client is allowed to join
174
+ const allowedRooms = rooms.filter(room => room.startsWith('public-'));
175
+ return allowedRooms;
176
+ };
177
+
178
+ this.bind<TSocketIOValidateRoomFn>({
179
+ key: SocketIOBindingKeys.VALIDATE_ROOM_HANDLER,
180
+ }).toValue(validateRoomFn);
181
+
182
+ // 4. Client connected handler (optional)
183
+ const clientConnectedFn: TSocketIOClientConnectedFn = ({ socket }) => {
184
+ console.log('Client connected:', socket.id);
185
+ // Register custom event handlers on the socket
186
+ };
187
+
188
+ this.bind<TSocketIOClientConnectedFn>({
189
+ key: SocketIOBindingKeys.CLIENT_CONNECTED_HANDLER,
190
+ }).toValue(clientConnectedFn);
191
+
192
+ // 5. Register the component — that's it!
99
193
  this.component(SocketIOComponent);
100
194
  }
101
-
102
- // ...
103
195
  }
104
196
  ```
105
197
 
106
- #### 2. Emitting Events
198
+ ### Step 3: Use in Services/Controllers
107
199
 
108
- You can get the `SocketIOServerHelper` instance from the container and use it to emit events.
200
+ Inject `SocketIOServerHelper` to interact with Socket.IO:
109
201
 
110
202
  ```typescript
111
- import { SocketIOServerHelper, SocketIOBindingKeys, inject } from '@venizia/ignis';
203
+ import {
204
+ BaseService,
205
+ inject,
206
+ SocketIOBindingKeys,
207
+ SocketIOServerHelper,
208
+ CoreBindings,
209
+ BaseApplication,
210
+ } from '@venizia/ignis';
211
+
212
+ export class NotificationService extends BaseService {
213
+ // Lazy getter pattern — helper is bound AFTER server starts
214
+ private _io: SocketIOServerHelper | null = null;
215
+
216
+ constructor(
217
+ @inject({ key: CoreBindings.APPLICATION_INSTANCE })
218
+ private application: BaseApplication,
219
+ ) {
220
+ super({ scope: NotificationService.name });
221
+ }
222
+
223
+ private get io(): SocketIOServerHelper {
224
+ if (!this._io) {
225
+ this._io = this.application.get<SocketIOServerHelper>({
226
+ key: SocketIOBindingKeys.SOCKET_IO_INSTANCE,
227
+ isOptional: true,
228
+ }) ?? null;
229
+ }
112
230
 
113
- // ... in a service or controller
231
+ if (!this._io) {
232
+ throw new Error('SocketIO not initialized');
233
+ }
114
234
 
115
- @inject({ key: SocketIOBindingKeys.SOCKET_IO_INSTANCE })
116
- private io: SocketIOServerHelper;
235
+ return this._io;
236
+ }
117
237
 
118
- sendNotification(userId: string, message: string) {
238
+ // Send to a specific client
239
+ notifyUser(opts: { userId: string; message: string }) {
119
240
  this.io.send({
120
- destination: userId, // Room or socket ID
241
+ destination: opts.userId,
121
242
  payload: {
122
243
  topic: 'notification',
123
- data: { message },
244
+ data: { message: opts.message, time: new Date().toISOString() },
124
245
  },
125
246
  });
126
247
  }
248
+
249
+ // Send to a room
250
+ notifyRoom(opts: { room: string; message: string }) {
251
+ this.io.send({
252
+ destination: opts.room,
253
+ payload: {
254
+ topic: 'room:update',
255
+ data: { message: opts.message },
256
+ },
257
+ });
258
+ }
259
+
260
+ // Broadcast to all clients
261
+ broadcastAnnouncement(opts: { message: string }) {
262
+ this.io.send({
263
+ payload: {
264
+ topic: 'system:announcement',
265
+ data: { message: opts.message },
266
+ },
267
+ });
268
+ }
269
+ }
127
270
  ```
128
271
 
272
+ > [!IMPORTANT]
273
+ > **Lazy getter pattern**: Since `SocketIOServerHelper` is bound via a post-start hook, it's not available during DI construction. Use a lazy getter that resolves from the application container on first access.
274
+
275
+ ---
276
+
277
+ ## Default Server Options
278
+
279
+ The component applies these defaults if `SocketIOBindingKeys.SERVER_OPTIONS` is not bound or partially overridden:
280
+
281
+ ```typescript
282
+ const DEFAULT_SERVER_OPTIONS = {
283
+ identifier: 'SOCKET_IO_SERVER',
284
+ path: '/io',
285
+ cors: {
286
+ origin: '*',
287
+ methods: ['GET', 'POST'],
288
+ preflightContinue: false,
289
+ optionsSuccessStatus: 204,
290
+ credentials: true,
291
+ },
292
+ perMessageDeflate: {
293
+ threshold: 4096,
294
+ zlibDeflateOptions: { chunkSize: 10 * 1024 },
295
+ zlibInflateOptions: { windowBits: 12, memLevel: 8 },
296
+ clientNoContextTakeover: true,
297
+ serverNoContextTakeover: true,
298
+ serverMaxWindowBits: 10,
299
+ concurrencyLimit: 20,
300
+ },
301
+ };
302
+ ```
303
+
304
+ | Option | Default | Description |
305
+ |--------|---------|-------------|
306
+ | `identifier` | `'SOCKET_IO_SERVER'` | Unique identifier for the helper instance |
307
+ | `path` | `'/io'` | URL path for Socket.IO handshake/polling |
308
+ | `cors.origin` | `'*'` | Allowed origins (restrict in production!) |
309
+ | `cors.credentials` | `true` | Allow cookies/auth headers |
310
+ | `perMessageDeflate` | Enabled | WebSocket compression settings |
311
+
312
+ ### Custom Server Options
313
+
314
+ Override defaults by binding custom options:
315
+
316
+ ```typescript
317
+ this.bind<Partial<IServerOptions>>({
318
+ key: SocketIOBindingKeys.SERVER_OPTIONS,
319
+ }).toValue({
320
+ identifier: 'my-app-socket',
321
+ path: '/socket.io',
322
+ cors: {
323
+ origin: ['https://myapp.com', 'https://admin.myapp.com'],
324
+ methods: ['GET', 'POST'],
325
+ credentials: true,
326
+ },
327
+ pingTimeout: 60000,
328
+ pingInterval: 25000,
329
+ maxHttpBufferSize: 1e6, // 1MB
330
+ });
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Component Internals
336
+
337
+ ### `resolveBindings()`
338
+
339
+ Reads all binding keys from the DI container and validates required ones:
340
+
341
+ | Binding | Validation | Error on Failure |
342
+ |---------|-----------|------------------|
343
+ | `SERVER_OPTIONS` | Optional, merged with defaults | — |
344
+ | `REDIS_CONNECTION` | Must be `instanceof DefaultRedisHelper` | `"Invalid instance of redisConnection"` |
345
+ | `AUTHENTICATE_HANDLER` | Must be a function (non-null) | `"Invalid authenticateFn"` |
346
+ | `VALIDATE_ROOM_HANDLER` | Optional, checked via `isBound()` | — |
347
+ | `CLIENT_CONNECTED_HANDLER` | Optional, checked via `isBound()` | — |
348
+
349
+ ### `registerBunHook()`
350
+
351
+ Registers a post-start hook that:
352
+
353
+ 1. Dynamically imports `@socket.io/bun-engine`
354
+ 2. Creates a `BunEngine` instance with CORS config bridging
355
+ 3. Creates `SocketIOServerHelper` with `runtime: RuntimeModules.BUN`
356
+ 4. Awaits `socketIOHelper.configure()` which waits for all Redis connections to be ready before initializing the adapter and emitter
357
+ 5. Binds the helper to `SOCKET_IO_INSTANCE`
358
+ 6. Calls `serverInstance.reload()` to wire the engine's `fetch` and `websocket` handlers into the running Bun server
359
+
360
+ **CORS type bridging**: Socket.IO and `@socket.io/bun-engine` have slightly different CORS type definitions. The component extracts individual fields explicitly to avoid type mismatches without using `as any`.
361
+
362
+ ### `registerNodeHook()`
363
+
364
+ Registers a post-start hook that:
365
+
366
+ 1. Gets the HTTP server instance via `getServerInstance()`
367
+ 2. Creates `SocketIOServerHelper` with `runtime: RuntimeModules.NODE`
368
+ 3. Awaits `socketIOHelper.configure()` which waits for all Redis connections to be ready before initializing the adapter and emitter
369
+ 4. Binds the helper to `SOCKET_IO_INSTANCE`
370
+
371
+ Node mode is simpler because Socket.IO natively attaches to `node:http.Server`.
372
+
373
+ ---
374
+
375
+ ## Post-Start Hook System
376
+
377
+ The component relies on `AbstractApplication`'s post-start hook system:
378
+
379
+ ### API
380
+
381
+ ```typescript
382
+ // Register a hook (during binding phase)
383
+ application.registerPostStartHook({
384
+ identifier: string, // Unique name for logging
385
+ hook: () => ValueOrPromise<void>, // Async function to execute
386
+ });
387
+
388
+ // Get the server instance (available after start)
389
+ application.getServerInstance<T>(): T | undefined;
390
+ ```
391
+
392
+ ### How Hooks Execute
393
+
394
+ ```
395
+ executePostStartHooks()
396
+ ├── Hook 1: "socket-io-initialize"
397
+ │ ├── performance.now() → start
398
+ │ ├── await hook()
399
+ │ └── log: "Executed hook | identifier: socket-io-initialize | took: 12.5 (ms)"
400
+ ├── Hook 2: "another-hook"
401
+ │ └── ...
402
+ └── (hooks run sequentially in registration order)
403
+ ```
404
+
405
+ - Hooks run **sequentially** (not parallel) to guarantee ordering
406
+ - Each hook is timed with `performance.now()` for diagnostics
407
+ - If a hook throws, it propagates to `start()` and the server fails to start
408
+
409
+ ---
410
+
411
+ ## Graceful Shutdown
412
+
413
+ Always shut down the Socket.IO server before stopping the application:
414
+
415
+ ```typescript
416
+ override async stop(): Promise<void> {
417
+ // 1. Shut down Socket.IO (disconnects all clients, closes IO server, quits Redis)
418
+ const socketIOHelper = this.get<SocketIOServerHelper>({
419
+ key: SocketIOBindingKeys.SOCKET_IO_INSTANCE,
420
+ isOptional: true,
421
+ });
422
+
423
+ if (socketIOHelper) {
424
+ await socketIOHelper.shutdown();
425
+ }
426
+
427
+ // 2. Disconnect Redis helper
428
+ if (this.redisHelper) {
429
+ await this.redisHelper.disconnect();
430
+ }
431
+
432
+ // 3. Stop the HTTP/Bun server
433
+ await super.stop();
434
+ }
435
+ ```
436
+
437
+ ### Shutdown Sequence
438
+
439
+ ```
440
+ socketIOHelper.shutdown()
441
+ ├── Disconnect all tracked clients
442
+ │ ├── clearInterval(ping)
443
+ │ ├── clearTimeout(authenticateTimeout)
444
+ │ └── socket.disconnect()
445
+ ├── clients.clear()
446
+ ├── io.close() — closes the Socket.IO server
447
+ └── Redis cleanup
448
+ ├── redisPub.quit()
449
+ ├── redisSub.quit()
450
+ └── redisEmitter.quit()
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Complete Example
456
+
457
+ A full working example is available at `examples/socket-io-test/`. It demonstrates:
458
+
459
+ | Feature | Implementation |
460
+ |---------|---------------|
461
+ | Application setup | `src/application.ts` — bindings, component registration, graceful shutdown |
462
+ | REST endpoints | `src/controllers/socket-test.controller.ts` — 9 endpoints for Socket.IO management |
463
+ | Event handling | `src/services/socket-event.service.ts` — chat, echo, room management |
464
+ | Automated test client | `client.ts` — 15+ test cases covering all features |
465
+
466
+ ### REST API Endpoints
467
+
468
+ | Method | Path | Description |
469
+ |--------|------|-------------|
470
+ | `GET` | `/socket/info` | Server status + connected client count |
471
+ | `GET` | `/socket/clients` | List all connected client IDs |
472
+ | `GET` | `/socket/health` | Health check (is SocketIO ready?) |
473
+ | `POST` | `/socket/broadcast` | Broadcast `{ topic, data }` to all clients |
474
+ | `POST` | `/socket/room/{roomId}/send` | Send `{ topic, data }` to a room |
475
+ | `POST` | `/socket/client/{clientId}/send` | Send `{ topic, data }` to a specific client |
476
+ | `POST` | `/socket/client/{clientId}/join` | Join client to `{ rooms: string[] }` |
477
+ | `POST` | `/socket/client/{clientId}/leave` | Remove client from `{ rooms: string[] }` |
478
+ | `GET` | `/socket/client/{clientId}/rooms` | List rooms a client belongs to |
479
+
480
+ ### Running the Example
481
+
482
+ ```bash
483
+ # Start the server
484
+ cd examples/socket-io-test
485
+ bun run server:dev
486
+
487
+ # In another terminal — run automated tests
488
+ bun client.ts
489
+ ```
490
+
491
+ ---
492
+
493
+ ## Troubleshooting
494
+
495
+ ### "SocketIO not initialized"
496
+
497
+ **Cause**: You're trying to use `SocketIOServerHelper` before the server has started (e.g., during DI construction).
498
+
499
+ **Fix**: Use the lazy getter pattern shown in [Step 3](#step-3-use-in-servicescontrollers). Never `@inject` `SOCKET_IO_INSTANCE` directly in a constructor — it doesn't exist yet at construction time.
500
+
501
+ ### "Invalid instance of redisConnection"
502
+
503
+ **Cause**: The value bound to `REDIS_CONNECTION` is not an instance of `DefaultRedisHelper` (or its subclass `RedisHelper`).
504
+
505
+ **Fix**: Use `RedisHelper` (recommended) or `DefaultRedisHelper`:
506
+
507
+ ```typescript
508
+ // Correct
509
+ this.bind({ key: SocketIOBindingKeys.REDIS_CONNECTION })
510
+ .toValue(new RedisHelper({ name: 'socket-io', host, port, password }));
511
+
512
+ // Wrong — raw ioredis client
513
+ this.bind({ key: SocketIOBindingKeys.REDIS_CONNECTION })
514
+ .toValue(new Redis(6379)); // This is NOT a DefaultRedisHelper!
515
+ ```
516
+
517
+ ### "Cannot find module '@socket.io/bun-engine'"
518
+
519
+ **Cause**: Running on Bun runtime without the optional peer dependency installed.
520
+
521
+ **Fix**: `bun add @socket.io/bun-engine`
522
+
523
+ ### Socket.IO connects but events aren't received
524
+
525
+ **Cause**: Clients must emit `authenticate` after connecting. Unauthenticated clients are disconnected after the timeout (default: 10 seconds).
526
+
527
+ **Fix**: Ensure your client emits the authenticate event:
528
+
529
+ ```typescript
530
+ socket.on('connect', () => {
531
+ socket.emit('authenticate');
532
+ });
533
+
534
+ socket.on('authenticated', (data) => {
535
+ // Now ready to send/receive events
536
+ });
537
+ ```
538
+
539
+ ---
540
+
129
541
  ## See Also
130
542
 
131
543
  - **Related Concepts:**
132
- - [Components Overview](/guides/core-concepts/components) - Component system basics
133
- - [Application](/guides/core-concepts/application/) - Registering components
544
+ - [Components Overview](/guides/core-concepts/components) Component system basics
545
+ - [Application](/guides/core-concepts/application/) Registering components
134
546
 
135
547
  - **Other Components:**
136
- - [Components Index](./index) - All built-in components
548
+ - [Components Index](./index) All built-in components
137
549
 
138
550
  - **References:**
139
- - [Socket.IO Helper](/references/helpers/socket-io) - Socket.IO utilities
551
+ - [Socket.IO Helper](/references/helpers/socket-io) Full `SocketIOServerHelper` + `SocketIOClientHelper` API reference
140
552
 
141
553
  - **External Resources:**
142
- - [Socket.IO Documentation](https://socket.io/docs/) - WebSocket library
554
+ - [Socket.IO Documentation](https://socket.io/docs/) Official docs
555
+ - [Socket.IO Redis Adapter](https://socket.io/docs/v4/redis-adapter/) — Horizontal scaling guide
556
+ - [@socket.io/bun-engine](https://github.com/socketio/bun-engine) — Bun runtime support
143
557
 
144
558
  - **Tutorials:**
145
- - [Real-Time Chat](/guides/tutorials/realtime-chat) - Building a chat app with Socket.IO
559
+ - [Real-Time Chat](/guides/tutorials/realtime-chat) Building a chat app with Socket.IO
560
+
561
+ - **Changelog:**
562
+ - [2026-02-06: Socket.IO Integration Fix](/changelogs/2026-02-06-socket-io-integration-fix) — Lifecycle timing fix + Bun runtime support