@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,459 +1,126 @@
1
- # WebSocket -- Setup & Configuration
1
+ ---
2
+ title: WebSocket Component
3
+ description: Wires WebSocketServerHelper into the running Bun server, with Redis Pub/Sub scaling and post-connection authentication
4
+ difficulty: intermediate
5
+ ---
2
6
 
3
- > Bun-native real-time, bidirectional communication using pure WebSocket -- with Redis Pub/Sub for horizontal scaling, application-level heartbeat, and post-connection authentication.
7
+ # WebSocket Component
4
8
 
5
- > [!IMPORTANT]
6
- > **Bun only.** The WebSocket component will throw an error if the runtime is Node.js. For Node.js support, use the [Socket.IO Component](../socket-io/) instead.
7
-
8
- ## Quick Reference
9
-
10
- | Item | Value |
11
- |------|-------|
12
- | **Package** | `@venizia/ignis` (core component) + `@venizia/ignis-helpers` (helper classes) |
13
- | **Component** | `WebSocketComponent` |
14
- | **Server Helper** | [`WebSocketServerHelper`](/extensions/helpers/websocket/) |
15
- | **Emitter Helper** | `WebSocketEmitter` (standalone Redis publisher) |
16
- | **Runtimes** | Bun only (throws on Node.js) |
17
- | **Scaling** | Redis Pub/Sub (ioredis -- single or Cluster) |
18
-
19
- #### Import Paths
9
+ `WebSocketComponent` registers a Bun-native WebSocket server on your application by creating and binding a [`WebSocketServerHelper`](/extensions/helpers/websocket/) once the HTTP server is listening.
20
10
 
21
11
  > [!IMPORTANT]
22
- > `WebSocketComponent` and `WebSocketBindingKeys` are **not** exported from the `@venizia/ignis` barrel. You must import from the `@venizia/ignis/websocket` subpath.
23
-
24
- ```typescript
25
- // From core -- subpath import (NOT from '@venizia/ignis')
26
- import {
27
- WebSocketComponent,
28
- WebSocketBindingKeys,
29
- } from '@venizia/ignis/websocket';
30
-
31
- // From helpers -- types, helpers, constants (exported from main entry)
32
- import {
33
- WebSocketServerHelper,
34
- WebSocketEmitter,
35
- WebSocketDefaults,
36
- WebSocketEvents,
37
- WebSocketChannels,
38
- WebSocketClientStates,
39
- WebSocketMessageTypes,
40
- } from '@venizia/ignis-helpers';
41
-
42
- import type {
43
- IWebSocketServerOptions,
44
- IWebSocketEmitterOptions,
45
- IWebSocketClient,
46
- IWebSocketMessage,
47
- IRedisSocketMessage,
48
- IBunWebSocketConfig,
49
- TWebSocketAuthenticateFn,
50
- TWebSocketValidateRoomFn,
51
- TWebSocketClientConnectedFn,
52
- TWebSocketClientDisconnectedFn,
53
- TWebSocketMessageHandler,
54
- TWebSocketOutboundTransformer,
55
- TWebSocketHandshakeFn,
56
- } from '@venizia/ignis-helpers';
57
- ```
58
-
59
- > [!NOTE]
60
- > `IServerOptions` (the core component's subset type) is **not** exported from `@venizia/ignis` or `@venizia/ignis/websocket`. Only `WebSocketBindingKeys` and `WebSocketComponent` are exported from the core subpath. All helper types, constants, and classes are imported from `@venizia/ignis-helpers`.
12
+ > **Bun only.** `binding()` throws if the runtime is Node.js. For Node.js support, use the [Socket.IO Component](../socket-io/) instead.
61
13
 
62
- ### Use Cases
14
+ ## In one example
63
15
 
64
- - Live notifications and alerts
65
- - Real-time chat and messaging
66
- - Collaborative editing (docs, whiteboards)
67
- - Live data streams (dashboards, monitoring)
68
- - Multiplayer game state synchronization
69
- - IoT device communication
70
- - Background job progress updates (via `WebSocketEmitter`)
71
- - Cross-service event broadcasting (via `WebSocketEmitter`)
72
-
73
- ## Setup
74
-
75
- ### Step 1: Install Dependencies
76
-
77
- ```bash
78
- # Core dependency (already included via @venizia/ignis)
79
- # ioredis is required for Redis Pub/Sub
80
- bun add ioredis
81
- ```
82
-
83
- ### Step 2: Bind Required Services
84
-
85
- In your application's `preConfigure()` method, bind the required services and register the component:
86
-
87
- #### Full Setup Example
88
16
  ```typescript
89
17
  import { BaseApplication } from '@venizia/ignis';
90
- import {
91
- WebSocketComponent,
92
- WebSocketBindingKeys,
93
- } from '@venizia/ignis/websocket';
94
- import {
95
- RedisSingleHelper,
96
- } from '@venizia/ignis-helpers';
97
- import type {
98
- TWebSocketAuthenticateFn,
99
- TWebSocketValidateRoomFn,
100
- TWebSocketClientConnectedFn,
101
- TWebSocketClientDisconnectedFn,
102
- TWebSocketMessageHandler,
103
- TWebSocketOutboundTransformer,
104
- TWebSocketHandshakeFn,
105
- IBunWebSocketConfig,
106
- ValueOrPromise,
107
- } from '@venizia/ignis-helpers';
18
+ import { WebSocketComponent, WebSocketBindingKeys } from '@venizia/ignis/websocket';
19
+ import { RedisSingleHelper, TWebSocketAuthenticateFn, ValueOrPromise } from '@venizia/ignis-helpers';
108
20
 
109
21
  export class Application extends BaseApplication {
110
- private redisHelper: RedisSingleHelper;
111
-
112
22
  preConfigure(): ValueOrPromise<void> {
113
- this.setupWebSocket();
114
- // ... other setup
115
- }
116
-
117
- setupWebSocket() {
118
- // 1. Redis connection (required for cross-instance messaging)
119
- this.redisHelper = new RedisSingleHelper({
120
- name: 'websocket-redis',
121
- host: process.env.REDIS_HOST ?? 'localhost',
122
- port: +(process.env.REDIS_PORT ?? 6379),
123
- password: process.env.REDIS_PASSWORD,
124
- autoConnect: false,
125
- });
126
-
127
- this.bind<RedisSingleHelper>({
128
- key: WebSocketBindingKeys.REDIS_CONNECTION,
129
- }).toValue(this.redisHelper);
130
-
131
- // 2. Authentication handler (required)
132
- const authenticateFn: TWebSocketAuthenticateFn = async (data) => {
133
- const token = data.token as string;
134
- if (!token) return null;
135
-
136
- const user = await verifyJWT(token);
137
- if (!user) return null;
138
-
139
- return { userId: user.id, metadata: { role: user.role } };
140
- };
141
-
142
- this.bind<TWebSocketAuthenticateFn>({
143
- key: WebSocketBindingKeys.AUTHENTICATE_HANDLER,
144
- }).toValue(authenticateFn);
145
-
146
- // 3. Room validation handler (optional -- joins rejected without this)
147
- const validateRoomFn: TWebSocketValidateRoomFn = ({ clientId, userId, rooms }) => {
148
- return rooms.filter(room => room.startsWith('public-'));
149
- };
150
-
151
- this.bind<TWebSocketValidateRoomFn>({
152
- key: WebSocketBindingKeys.VALIDATE_ROOM_HANDLER,
153
- }).toValue(validateRoomFn);
154
-
155
- // 4. Client connected handler (optional)
156
- const clientConnectedFn: TWebSocketClientConnectedFn = ({ clientId, userId }) => {
157
- console.log('Client connected:', clientId, userId);
158
- };
159
-
160
- this.bind<TWebSocketClientConnectedFn>({
161
- key: WebSocketBindingKeys.CLIENT_CONNECTED_HANDLER,
162
- }).toValue(clientConnectedFn);
163
-
164
- // 5. Client disconnected handler (optional)
165
- const clientDisconnectedFn: TWebSocketClientDisconnectedFn = ({ clientId, userId }) => {
166
- console.log('Client disconnected:', clientId, userId);
167
- };
168
-
169
- this.bind<TWebSocketClientDisconnectedFn>({
170
- key: WebSocketBindingKeys.CLIENT_DISCONNECTED_HANDLER,
171
- }).toValue(clientDisconnectedFn);
172
-
173
- // 6. Message handler (optional -- for custom events)
174
- const messageHandler: TWebSocketMessageHandler = ({ clientId, userId, message }) => {
175
- console.log('Custom event:', message.event, message.data);
176
- };
177
-
178
- this.bind<TWebSocketMessageHandler>({
179
- key: WebSocketBindingKeys.MESSAGE_HANDLER,
180
- }).toValue(messageHandler);
181
-
182
- // 7. Outbound transformer (optional -- for per-client encryption)
183
- const outboundTransformer: TWebSocketOutboundTransformer = async ({ client, event, data }) => {
184
- if (!client.encrypted) return null;
185
- // Encrypt using client's derived AES key (from ECDH handshake)
186
- const encrypted = await encryptForClient(client.id, JSON.stringify({ event, data }));
187
- return { event: 'encrypted', data: encrypted };
188
- };
189
-
190
- this.bind<TWebSocketOutboundTransformer>({
191
- key: WebSocketBindingKeys.OUTBOUND_TRANSFORMER,
192
- }).toValue(outboundTransformer);
193
-
194
- // 8. Handshake handler (optional -- required when requireEncryption is true)
195
- const handshakeFn: TWebSocketHandshakeFn = async ({ clientId, data }) => {
196
- const clientPubKey = data.publicKey as string;
197
- if (!clientPubKey) return null; // Reject -- no public key provided
198
- const salt = crypto.getRandomValues(new Uint8Array(32));
199
- const saltB64 = Buffer.from(salt).toString('base64');
200
- const aesKey = await deriveSharedSecret(clientPubKey, salt);
201
- storeClientKey(clientId, aesKey);
202
- return { serverPublicKey: serverPublicKeyB64, salt: saltB64 };
23
+ // 1. Redis connection (required, used for cross-instance Pub/Sub)
24
+ this.bind({ key: WebSocketBindingKeys.REDIS_CONNECTION }).toValue(
25
+ new RedisSingleHelper({ name: 'websocket-redis', host: 'localhost', port: 6379, autoConnect: false }),
26
+ );
27
+
28
+ // 2. Authenticate handler (required, decides accept/reject per client)
29
+ const authenticateFn: TWebSocketAuthenticateFn = async data => {
30
+ const user = await verifyJWT(data.token as string);
31
+ return user ? { userId: user.id } : null;
203
32
  };
33
+ this.bind({ key: WebSocketBindingKeys.AUTHENTICATE_HANDLER }).toValue(authenticateFn);
204
34
 
205
- this.bind<TWebSocketHandshakeFn>({
206
- key: WebSocketBindingKeys.HANDSHAKE_HANDLER,
207
- }).toValue(handshakeFn);
208
-
209
- // 9. Server options (optional -- customize defaults)
210
- this.bind({
211
- key: WebSocketBindingKeys.SERVER_OPTIONS,
212
- }).toValue({
213
- identifier: 'my-app-websocket',
214
- requireEncryption: true,
215
- });
216
-
217
- // 10. Register the component
35
+ // 3. Register (binding() validates the two bindings above and defers the rest)
218
36
  this.component(WebSocketComponent);
219
37
  }
220
38
  }
221
39
  ```
222
40
 
223
- ## Configuration
41
+ `WebSocketComponent` and `WebSocketBindingKeys` come from the `@venizia/ignis/websocket` subpath. They are **not** exported from the `@venizia/ignis` root barrel. Helper types (`TWebSocketAuthenticateFn`, `WebSocketServerHelper`, `WebSocketEmitter`, `WebSocketDefaults`, ...) come from `@venizia/ignis-helpers`.
224
42
 
225
- The core component's `IServerOptions` interface controls the WebSocket server setup. Default values come from `DEFAULT_SERVER_OPTIONS` and `WebSocketDefaults`:
43
+ ## How it works
226
44
 
227
- ```typescript
228
- {
229
- identifier: 'WEBSOCKET_SERVER',
230
- path: '/ws', // WebSocketDefaults.PATH
231
- defaultRooms: [ // Joined automatically after auth
232
- 'ws-default', // WebSocketDefaults.ROOM
233
- 'ws-notification', // WebSocketDefaults.NOTIFICATION_ROOM
234
- ],
235
- heartbeatInterval: 30000, // 30 seconds (WebSocketDefaults.HEARTBEAT_INTERVAL)
236
- heartbeatTimeout: 90000, // 90 seconds (WebSocketDefaults.HEARTBEAT_TIMEOUT)
237
- requireEncryption: false,
238
- serverOptions: { // Bun native WebSocket config (IBunWebSocketConfig)
239
- sendPings: true, // WebSocketDefaults.SEND_PINGS
240
- idleTimeout: 60, // WebSocketDefaults.IDLE_TIMEOUT (seconds)
241
- maxPayloadLength: 131072, // WebSocketDefaults.MAX_PAYLOAD_LENGTH (128 KB)
242
- },
243
- }
244
- ```
45
+ - **Two-phase startup solves a timing problem.** `binding()` runs during `initialize()`, before any Bun server exists. It validates bindings and registers a post-start hook; the actual `WebSocketServerHelper` is only constructed once `executePostStartHooks()` runs, after `Bun.serve()` has produced a live server instance.
46
+ - **Two bindings gate startup.** `REDIS_CONNECTION` (must be an `AbstractRedisHelper` instance) and `AUTHENTICATE_HANDLER` are required. `binding()` throws synchronously if either is missing or the wrong type - before the post-start hook is even registered.
47
+ - **Runtime is checked first, fast.** `RuntimeModules.detect()` runs at the top of `binding()`. On Node.js it throws immediately, so a misconfigured app fails at startup, not on first connection.
48
+ - **The instance appears only after start.** The post-start hook binds the configured helper to `WebSocketBindingKeys.WEBSOCKET_INSTANCE`. It does not exist during DI construction. Inject it lazily from a service or controller, never via `@inject` in a constructor.
49
+ - **A custom `fetch` handler splits traffic.** After the post-start hook runs, `server.reload()` swaps in a handler that routes WebSocket upgrade requests (`GET <path>` with an `Upgrade: websocket` header) to Bun's native handler, and everything else to the existing Hono server unchanged.
245
50
 
246
- > [!NOTE]
247
- > The `DEFAULT_SERVER_OPTIONS` in the core component only sets `identifier` and `path`. The remaining defaults (`defaultRooms`, `heartbeatInterval`, `heartbeatTimeout`, `serverOptions`) come from `WebSocketDefaults` applied by the helper constructor.
51
+ ## Common tasks
248
52
 
249
- To customize options, bind a partial options object before registering the component:
53
+ ### Customize the server path, rooms, and heartbeat
250
54
 
251
- #### Custom Server Options Example
252
- ```typescript
253
- import { WebSocketBindingKeys } from '@venizia/ignis/websocket';
55
+ Bind a partial options object to `SERVER_OPTIONS` before registering the component. Unset fields fall back to `WebSocketDefaults`.
254
56
 
255
- this.bind({
256
- key: WebSocketBindingKeys.SERVER_OPTIONS,
257
- }).toValue({
258
- identifier: 'my-app-websocket',
57
+ ```typescript
58
+ this.bind({ key: WebSocketBindingKeys.SERVER_OPTIONS }).toValue({
259
59
  path: '/realtime',
260
- defaultRooms: ['general', 'announcements'], // Override default rooms
261
- heartbeatInterval: 20000, // More frequent heartbeats
262
- heartbeatTimeout: 60000, // Shorter timeout
263
- requireEncryption: true, // Require ECDH handshake
264
- serverOptions: {
265
- maxPayloadLength: 2097152, // 2 MB max payload
266
- backpressureLimit: 2097152, // 2 MB backpressure limit
267
- },
60
+ defaultRooms: ['general', 'announcements'],
61
+ heartbeatInterval: 20_000,
62
+ heartbeatTimeout: 60_000,
268
63
  });
269
64
  ```
270
65
 
271
- > [!NOTE]
272
- > `authTimeout` and `encryptedBatchLimit` are properties of the helper's `IWebSocketServerOptions`, not the core component's `IServerOptions`. The component uses the helper defaults for those (`5000` ms and `10` respectively). If you need to customize them, you must set them on the helper directly (not via binding keys).
273
-
274
- ### `WebSocketDefaults` Constants
275
-
276
- All tunable defaults are defined in the `WebSocketDefaults` class. The helper falls back to these when no explicit value is provided.
277
-
278
- | Constant | Value | Description |
279
- |----------|-------|-------------|
280
- | `PATH` | `'/ws'` | Default WebSocket endpoint path |
281
- | `ROOM` | `'ws-default'` | Default room name |
282
- | `NOTIFICATION_ROOM` | `'ws-notification'` | Default notification room name |
283
- | `BROADCAST_TOPIC` | `'ws:internal:broadcast'` | Internal Bun pub/sub broadcast topic |
284
- | `MAX_PAYLOAD_LENGTH` | `131072` (128 KB) | Maximum message payload size |
285
- | `IDLE_TIMEOUT` | `60` | Bun idle timeout in seconds |
286
- | `BACKPRESSURE_LIMIT` | `1048576` (1 MB) | Bun backpressure limit |
287
- | `SEND_PINGS` | `true` | Enable WebSocket pings |
288
- | `PUBLISH_TO_SELF` | `false` | Whether server receives its own publishes |
289
- | `AUTH_TIMEOUT` | `5000` (5 s) | Time to authenticate before disconnect |
290
- | `HEARTBEAT_INTERVAL` | `30000` (30 s) | Interval between heartbeat sweeps |
291
- | `HEARTBEAT_TIMEOUT` | `90000` (90 s) | Disconnect after 3 missed heartbeats |
292
- | `ENCRYPTED_BATCH_LIMIT` | `10` | Max concurrent encryption operations |
293
-
294
- > [!TIP]
295
- > `MAX_PAYLOAD_LENGTH`, `IDLE_TIMEOUT`, `BACKPRESSURE_LIMIT`, `SEND_PINGS`, and `PUBLISH_TO_SELF` are Bun-native WebSocket settings passed via `serverOptions` inside `IServerOptions`. The rest are application-level settings on `IWebSocketServerOptions` (the helper constructor options).
296
-
297
- #### Full `IBunWebSocketConfig` Interface
298
- ```typescript
299
- /** Bun WebSocket native configuration options */
300
- interface IBunWebSocketConfig {
301
- perMessageDeflate?: boolean;
302
- maxPayloadLength?: number; // Default: 128 KB (131072)
303
- idleTimeout?: number; // Default: 60 s
304
- backpressureLimit?: number; // Default: 1 MB (1048576)
305
- closeOnBackpressureLimit?: boolean;
306
- sendPings?: boolean; // Default: true
307
- publishToSelf?: boolean; // Default: false
308
- }
309
- ```
66
+ ### Add optional lifecycle callbacks
310
67
 
311
- These options are passed directly to Bun's native WebSocket handler. Set them via `serverOptions` inside the options bound to `WebSocketBindingKeys.SERVER_OPTIONS`.
68
+ `VALIDATE_ROOM_HANDLER`, `CLIENT_CONNECTED_HANDLER`, `CLIENT_DISCONNECTED_HANDLER`, and `MESSAGE_HANDLER` are all optional bindings.
312
69
 
313
- #### Full `IServerOptions` Interface (Core Component)
314
70
  ```typescript
315
- interface IServerOptions {
316
- identifier: string; // Default: 'WEBSOCKET_SERVER'
317
- path?: string; // Default: '/ws' (from WebSocketDefaults.PATH)
318
- defaultRooms?: string[]; // Default: ['ws-default', 'ws-notification']
319
- serverOptions?: IBunWebSocketConfig; // Bun native WebSocket config
320
- heartbeatInterval?: number; // Default: 30000 (30 s)
321
- heartbeatTimeout?: number; // Default: 90000 (90 s)
322
- requireEncryption?: boolean; // Default: false
323
- }
71
+ this.bind({ key: WebSocketBindingKeys.VALIDATE_ROOM_HANDLER }).toValue(
72
+ ({ rooms }: { rooms: string[] }) => rooms.filter(room => room.startsWith('public-')),
73
+ );
324
74
  ```
325
75
 
326
- > [!NOTE]
327
- > `IServerOptions` is the **core component's** options type. It is a subset of the helper's `IWebSocketServerOptions`, which additionally includes `server`, `redisConnection`, callback functions, `authTimeout`, and `encryptedBatchLimit`. The component fills in those extra fields from the DI container before constructing the helper.
328
-
329
- ## Binding Keys
330
-
331
- | Binding Key | Constant | Type | Required | Default |
332
- |------------|----------|------|----------|---------|
333
- | `@app/websocket/server-options` | `WebSocketBindingKeys.SERVER_OPTIONS` | `Partial<IServerOptions>` | No | See [Configuration](#configuration) |
334
- | `@app/websocket/redis-connection` | `WebSocketBindingKeys.REDIS_CONNECTION` | `IRedisHelper` | **Yes** | `null` |
335
- | `@app/websocket/authenticate-handler` | `WebSocketBindingKeys.AUTHENTICATE_HANDLER` | `TWebSocketAuthenticateFn` | **Yes** | `null` |
336
- | `@app/websocket/validate-room-handler` | `WebSocketBindingKeys.VALIDATE_ROOM_HANDLER` | `TWebSocketValidateRoomFn` | No | `null` |
337
- | `@app/websocket/client-connected-handler` | `WebSocketBindingKeys.CLIENT_CONNECTED_HANDLER` | `TWebSocketClientConnectedFn` | No | `null` |
338
- | `@app/websocket/client-disconnected-handler` | `WebSocketBindingKeys.CLIENT_DISCONNECTED_HANDLER` | `TWebSocketClientDisconnectedFn` | No | `null` |
339
- | `@app/websocket/message-handler` | `WebSocketBindingKeys.MESSAGE_HANDLER` | `TWebSocketMessageHandler` | No | `null` |
340
- | `@app/websocket/outbound-transformer` | `WebSocketBindingKeys.OUTBOUND_TRANSFORMER` | `TWebSocketOutboundTransformer` | No | `null` |
341
- | `@app/websocket/handshake-handler` | `WebSocketBindingKeys.HANDSHAKE_HANDLER` | `TWebSocketHandshakeFn` | No* | `null` |
342
- | `@app/websocket/instance` | `WebSocketBindingKeys.WEBSOCKET_INSTANCE` | `WebSocketServerHelper` | -- | *Set by component* |
343
-
344
- > [!NOTE]
345
- > `HANDSHAKE_HANDLER` is required when `IServerOptions.requireEncryption` is `true`. It performs ECDH key exchange during authentication.
346
-
347
- > [!NOTE]
348
- > `WEBSOCKET_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 WebSocket.
349
-
350
- ### Callback Type Signatures
351
-
352
- | Binding Key | Callback Type | Required | Description |
353
- |-------------|--------------|----------|-------------|
354
- | `AUTHENTICATE_HANDLER` | `TWebSocketAuthenticateFn` | **Yes** | Returns <code v-pre>{ userId, metadata }</code> or `null`/`false` to reject |
355
- | `VALIDATE_ROOM_HANDLER` | `TWebSocketValidateRoomFn` | No | Filters requested rooms, returns allowed rooms |
356
- | `CLIENT_CONNECTED_HANDLER` | `TWebSocketClientConnectedFn` | No | Called after successful authentication |
357
- | `CLIENT_DISCONNECTED_HANDLER` | `TWebSocketClientDisconnectedFn` | No | Called on disconnect (after cleanup) |
358
- | `MESSAGE_HANDLER` | `TWebSocketMessageHandler` | No | Handles non-system messages from authenticated clients |
359
- | `OUTBOUND_TRANSFORMER` | `TWebSocketOutboundTransformer` | No | Transforms outbound messages (e.g., per-client encryption) |
360
- | `HANDSHAKE_HANDLER` | `TWebSocketHandshakeFn` | When `requireEncryption: true` | Returns <code v-pre>{ serverPublicKey, salt }</code> or `null`/`false` to reject |
361
-
362
- #### `TWebSocketAuthenticateFn`
363
- ```typescript
364
- type TWebSocketAuthenticateFn<
365
- AuthDataType extends Record<string, unknown> = Record<string, unknown>,
366
- MetadataType extends Record<string, unknown> = Record<string, unknown>,
367
- > = (
368
- opts: AuthDataType,
369
- ) => ValueOrPromise<{ userId?: string; metadata?: MetadataType } | null | false>;
370
- ```
371
-
372
- Receives the `data` field from the client's `authenticate` event. Return <code v-pre>{ userId, metadata }</code> on success, or `null`/`false` to reject (closes with code `4003`).
373
-
374
- #### `TWebSocketValidateRoomFn`
375
- ```typescript
376
- type TWebSocketValidateRoomFn = (opts: {
377
- clientId: string;
378
- userId?: string;
379
- rooms: string[];
380
- }) => ValueOrPromise<string[]>;
381
- ```
382
-
383
- Called when a client sends a `join` event. Receives the sanitized room list (internal `ws:` prefix rooms are already filtered out). Return the subset of rooms the client is allowed to join.
384
-
385
76
  > [!WARNING]
386
- > If no `validateRoomFn` is bound, **all join requests are rejected**. You must bind this handler if you want clients to join custom rooms.
77
+ > Without `VALIDATE_ROOM_HANDLER` bound, **every** client `join` request is rejected.
387
78
 
388
- #### `TWebSocketClientConnectedFn`
389
- ```typescript
390
- type TWebSocketClientConnectedFn<
391
- MetadataType extends Record<string, unknown> = Record<string, unknown>,
392
- > = (opts: {
393
- clientId: string;
394
- userId?: string;
395
- metadata?: MetadataType;
396
- }) => ValueOrPromise<void>;
397
- ```
79
+ ### Inject the running instance in a service
398
80
 
399
- Called after a client has been fully authenticated, joined default rooms, and received the `connected` event. Errors thrown here are caught and logged -- they do not disconnect the client.
81
+ `WEBSOCKET_INSTANCE` is bound after the server starts, so resolve it lazily rather than through the constructor.
400
82
 
401
- #### `TWebSocketClientDisconnectedFn`
402
83
  ```typescript
403
- type TWebSocketClientDisconnectedFn = (opts: {
404
- clientId: string;
405
- userId?: string;
406
- }) => ValueOrPromise<void>;
84
+ private get ws(): WebSocketServerHelper {
85
+ return this.application.get<WebSocketServerHelper>({
86
+ key: WebSocketBindingKeys.WEBSOCKET_INSTANCE,
87
+ });
88
+ }
407
89
  ```
408
90
 
409
- Called after internal cleanup (auth timer cleared, removed from user/room indexes, removed from clients map). Errors thrown here are caught and logged.
91
+ ### Send from a process with no WebSocket server
410
92
 
411
- #### `TWebSocketMessageHandler`
412
- ```typescript
413
- type TWebSocketMessageHandler = (opts: {
414
- clientId: string;
415
- userId?: string;
416
- message: IWebSocketMessage;
417
- }) => ValueOrPromise<void>;
418
- ```
93
+ Background workers, cron jobs, and other microservices use the standalone `WebSocketEmitter` - it uses the same Redis connection with no local server required.
419
94
 
420
- Called for any message from an authenticated client whose `event` is not a system event (`authenticate`, `connected`, `disconnect`, `join`, `leave`, `error`, `heartbeat`, `encrypted`). If no handler is bound, non-system messages are silently dropped.
421
-
422
- #### `TWebSocketOutboundTransformer`
423
95
  ```typescript
424
- type TWebSocketOutboundTransformer<
425
- DataType = unknown,
426
- MetadataType extends Record<string, unknown> = Record<string, unknown>,
427
- > = (opts: {
428
- client: IWebSocketClient<MetadataType>;
429
- event: string;
430
- data: DataType;
431
- }) => ValueOrPromise<TNullable<{ event: string; data: DataType }>>;
96
+ import { WebSocketEmitter } from '@venizia/ignis-helpers';
97
+
98
+ const emitter = new WebSocketEmitter({ redisConnection: redisHelper });
99
+ await emitter.configure();
100
+ await emitter.toRoom({ room: 'dashboard-viewers', event: 'data:update', data: { cpu: 42 } });
432
101
  ```
433
102
 
434
- Intercepts every outbound message **to encrypted clients only** before `socket.send()`. Return `null` to send the original <code v-pre>{ event, data }</code> unchanged, or return a transformed <code v-pre>{ event, data }</code> (e.g., <code v-pre>{ event: 'encrypted', data: ciphertext }</code>).
103
+ ### Require encrypted sessions
435
104
 
436
- > [!NOTE]
437
- > The transformer is only called for clients where `client.encrypted === true`. Non-encrypted clients bypass this entirely (zero overhead).
105
+ Set `requireEncryption: true` and bind a `HANDSHAKE_HANDLER`. It becomes required the moment encryption is turned on.
438
106
 
439
- #### `TWebSocketHandshakeFn`
440
107
  ```typescript
441
- type TWebSocketHandshakeFn<
442
- AuthDataType extends Record<string, unknown> = Record<string, unknown>,
443
- > = (opts: {
444
- clientId: string;
445
- userId?: string;
446
- data: AuthDataType;
447
- }) => ValueOrPromise<{ serverPublicKey: string; salt: string } | null | false>;
108
+ this.bind({ key: WebSocketBindingKeys.SERVER_OPTIONS }).toValue({ requireEncryption: true });
109
+ this.bind({ key: WebSocketBindingKeys.HANDSHAKE_HANDLER }).toValue(handshakeFn);
448
110
  ```
449
111
 
450
- Called during authentication when `requireEncryption` is `true`. Receives the same `data` payload as `authenticateFn`. Return <code v-pre>{ serverPublicKey, salt }</code> on success -- these are included in the `connected` event sent to the client. Return `null`/`false` to reject (closes with code `4004`).
112
+ ## See also
113
+
114
+ - [Usage & Examples](./usage) - injecting the helper, `WebSocketEmitter`, wire protocol, client tracking, delivery strategy
115
+ - [Full Reference](./api) - lifecycle diagram, binding keys, configuration options, `WebSocketEmitter` API, internals
116
+ - [Error Reference](./errors) - error conditions and troubleshooting
117
+ - [WebSocketServerHelper](/extensions/helpers/websocket/) - the helper this component wires in
118
+ - [Socket.IO Component](../socket-io/) - Node.js-compatible alternative
119
+ - [Bun WebSocket Documentation](https://bun.sh/docs/api/websockets) - official Bun WebSocket API reference
451
120
 
452
- ## See Also
121
+ **Files:**
453
122
 
454
- - [Usage & Examples](./usage) - Server-side usage, emitter, wire protocol, client tracking, and delivery strategy
455
- - [API Reference](./api) - Architecture, WebSocketEmitter API, and internals
456
- - [Error Reference](./errors) - Error conditions table and troubleshooting
457
- - [WebSocketServerHelper](/extensions/helpers/websocket/) - Helper API documentation
458
- - [Socket.IO Component](../socket-io/) - Node.js-compatible alternative with Socket.IO
459
- - [Bun WebSocket Documentation](https://bun.sh/docs/api/websockets) - Official Bun WebSocket API reference
123
+ - [`packages/core/src/components/websocket/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/websocket/component.ts) - `WebSocketComponent`
124
+ - [`packages/core/src/components/websocket/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/websocket/common/keys.ts) - `WebSocketBindingKeys`
125
+ - [`packages/core/src/components/websocket/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/websocket/common/types.ts) - `IServerOptions`, `DEFAULT_SERVER_OPTIONS`
126
+ - [`packages/core/src/components/websocket/handlers/bun.handler.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/websocket/handlers/bun.handler.ts) - `createBunFetchHandler`