@venizia/ignis-docs 0.2.0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/content/best-practices/api-usage-examples.md +39 -19
- package/content/best-practices/architectural-patterns.md +24 -13
- package/content/best-practices/architecture-decisions.md +29 -16
- package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
- package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
- package/content/best-practices/code-style-standards/control-flow.md +33 -1
- package/content/best-practices/code-style-standards/documentation.md +28 -8
- package/content/best-practices/code-style-standards/function-patterns.md +15 -4
- package/content/best-practices/code-style-standards/index.md +7 -2
- package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
- package/content/best-practices/code-style-standards/route-definitions.md +9 -5
- package/content/best-practices/code-style-standards/tooling.md +14 -1
- package/content/best-practices/code-style-standards/type-safety.md +39 -6
- package/content/best-practices/common-pitfalls.md +59 -35
- package/content/best-practices/contribution-workflow.md +8 -4
- package/content/best-practices/data-modeling.md +78 -62
- package/content/best-practices/deployment-strategies.md +56 -19
- package/content/best-practices/error-handling.md +247 -153
- package/content/best-practices/index.md +6 -0
- package/content/best-practices/performance-optimization.md +26 -13
- package/content/best-practices/security-guidelines.md +35 -9
- package/content/best-practices/testing-strategies.md +7 -2
- package/content/best-practices/troubleshooting-tips.md +27 -17
- package/content/extensions/components/api-reference.md +109 -323
- package/content/extensions/components/authentication/api.md +489 -602
- package/content/extensions/components/authentication/errors.md +135 -498
- package/content/extensions/components/authentication/index.md +89 -801
- package/content/extensions/components/authentication/usage.md +231 -955
- package/content/extensions/components/authorization/api.md +991 -644
- package/content/extensions/components/authorization/errors.md +204 -208
- package/content/extensions/components/authorization/getting-started.md +227 -0
- package/content/extensions/components/authorization/index.md +88 -795
- package/content/extensions/components/authorization/usage.md +219 -528
- package/content/extensions/components/health-check.md +77 -243
- package/content/extensions/components/index.md +24 -90
- package/content/extensions/components/mail/api.md +553 -285
- package/content/extensions/components/mail/errors.md +76 -62
- package/content/extensions/components/mail/index.md +111 -463
- package/content/extensions/components/mail/usage.md +139 -173
- package/content/extensions/components/request-tracker.md +70 -173
- package/content/extensions/components/socket-io/api.md +462 -785
- package/content/extensions/components/socket-io/errors.md +49 -51
- package/content/extensions/components/socket-io/index.md +69 -372
- package/content/extensions/components/socket-io/usage.md +212 -105
- package/content/extensions/components/static-asset/api.md +461 -141
- package/content/extensions/components/static-asset/errors.md +121 -53
- package/content/extensions/components/static-asset/index.md +84 -606
- package/content/extensions/components/static-asset/usage.md +184 -299
- package/content/extensions/components/template/index.md +3 -3
- package/content/extensions/components/websocket/api.md +311 -404
- package/content/extensions/components/websocket/errors.md +47 -56
- package/content/extensions/components/websocket/index.md +75 -407
- package/content/extensions/components/websocket/usage.md +120 -338
- package/content/extensions/helpers/cron/index.md +52 -160
- package/content/extensions/helpers/crypto/index.md +67 -480
- package/content/extensions/helpers/crypto/reference.md +528 -0
- package/content/extensions/helpers/env/index.md +64 -178
- package/content/extensions/helpers/error/index.md +286 -196
- package/content/extensions/helpers/index.md +61 -47
- package/content/extensions/helpers/inversion/index.md +76 -550
- package/content/extensions/helpers/inversion/reference.md +530 -0
- package/content/extensions/helpers/kafka/admin.md +23 -3
- package/content/extensions/helpers/kafka/compile-binary.md +83 -52
- package/content/extensions/helpers/kafka/consumer.md +65 -28
- package/content/extensions/helpers/kafka/examples.md +46 -253
- package/content/extensions/helpers/kafka/index.md +55 -617
- package/content/extensions/helpers/kafka/producer.md +156 -22
- package/content/extensions/helpers/kafka/schema-registry.md +59 -79
- package/content/extensions/helpers/logger/hf-logger.md +220 -0
- package/content/extensions/helpers/logger/index.md +78 -552
- package/content/extensions/helpers/logger/pino.md +105 -0
- package/content/extensions/helpers/logger/reference.md +937 -0
- package/content/extensions/helpers/network/api.md +276 -195
- package/content/extensions/helpers/network/index.md +87 -524
- package/content/extensions/helpers/queue/index.md +80 -897
- package/content/extensions/helpers/queue/reference.md +494 -0
- package/content/extensions/helpers/redis/index.md +86 -640
- package/content/extensions/helpers/redis/reference.md +784 -0
- package/content/extensions/helpers/secrets/index.md +136 -0
- package/content/extensions/helpers/socket-io/api.md +325 -204
- package/content/extensions/helpers/socket-io/index.md +79 -429
- package/content/extensions/helpers/storage/api.md +584 -464
- package/content/extensions/helpers/storage/index.md +80 -573
- package/content/extensions/helpers/types/index.md +75 -495
- package/content/extensions/helpers/types/reference.md +689 -0
- package/content/extensions/helpers/uid/index.md +176 -189
- package/content/extensions/helpers/websocket/api.md +366 -214
- package/content/extensions/helpers/websocket/index.md +68 -503
- package/content/extensions/helpers/worker-thread/index.md +62 -396
- package/content/extensions/helpers/worker-thread/reference.md +428 -0
- package/content/extensions/index.md +38 -39
- package/content/extensions/src-details/mcp-server.md +96 -548
- package/content/guides/core-concepts/persistent/datasources.md +2 -2
- package/content/guides/core-concepts/persistent/index.md +5 -1
- package/content/guides/core-concepts/persistent/models.md +1 -1
- package/content/guides/core-concepts/persistent/pglite.md +218 -0
- package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
- package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
- package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
- package/content/guides/core-concepts/persistent/sqlite.md +296 -0
- package/content/guides/core-concepts/persistent/transactions.md +12 -11
- package/content/guides/core-concepts/secrets-vault.md +177 -0
- package/content/guides/core-concepts/services.md +1 -1
- package/content/guides/get-started/5-minute-quickstart.md +59 -206
- package/content/guides/get-started/philosophy.md +135 -670
- package/content/guides/get-started/setup.md +53 -74
- package/content/guides/migrations/redis-helpers-migration.md +5 -4
- package/content/guides/migrations/scoped-rbac-migration.md +5 -5
- package/content/guides/migrations/unified-connectors-migration.md +8 -7
- package/content/guides/tutorials/ecommerce-api.md +3 -8
- package/content/guides/tutorials/realtime-chat.md +1 -1
- package/content/references/base/application.md +64 -22
- package/content/references/base/components.md +3 -3
- package/content/references/base/connectors.md +79 -136
- package/content/references/base/controllers.md +12 -12
- package/content/references/base/datasources-reference.md +600 -0
- package/content/references/base/datasources.md +84 -444
- package/content/references/base/dependency-injection.md +25 -46
- package/content/references/base/filter-system/application-usage.md +95 -123
- package/content/references/base/filter-system/array-operators.md +33 -43
- package/content/references/base/filter-system/comparison-operators.md +55 -63
- package/content/references/base/filter-system/default-filter.md +144 -350
- package/content/references/base/filter-system/fields-order-pagination.md +116 -148
- package/content/references/base/filter-system/index.md +114 -253
- package/content/references/base/filter-system/json-filtering.md +52 -181
- package/content/references/base/filter-system/list-operators.md +28 -44
- package/content/references/base/filter-system/logical-operators.md +69 -114
- package/content/references/base/filter-system/null-operators.md +41 -98
- package/content/references/base/filter-system/pattern-matching.md +48 -51
- package/content/references/base/filter-system/quick-reference.md +93 -196
- package/content/references/base/filter-system/range-operators.md +22 -38
- package/content/references/base/filter-system/tips.md +70 -133
- package/content/references/base/filter-system/use-cases.md +173 -232
- package/content/references/base/grpc-controllers.md +53 -17
- package/content/references/base/index.md +5 -3
- package/content/references/base/middlewares.md +43 -28
- package/content/references/base/models-reference.md +886 -0
- package/content/references/base/models.md +81 -1452
- package/content/references/base/providers.md +8 -8
- package/content/references/base/repositories/advanced.md +281 -419
- package/content/references/base/repositories/index.md +84 -644
- package/content/references/base/repositories/mixins.md +25 -21
- package/content/references/base/repositories/relations.md +194 -375
- package/content/references/base/repositories/soft-deletable.md +67 -55
- package/content/references/base/secrets.md +267 -0
- package/content/references/base/services.md +8 -6
- package/content/references/configuration/environment-variables.md +73 -21
- package/content/references/configuration/index.md +51 -31
- package/content/references/index.md +1 -1
- package/content/references/quick-reference.md +3 -16
- package/content/references/utilities/crypto.md +35 -76
- package/content/references/utilities/date.md +33 -73
- package/content/references/utilities/duration.md +85 -0
- package/content/references/utilities/index.md +6 -2
- package/content/references/utilities/jsx-reference.md +298 -0
- package/content/references/utilities/jsx.md +82 -525
- package/content/references/utilities/module.md +81 -61
- package/content/references/utilities/parse.md +34 -64
- package/content/references/utilities/performance.md +33 -58
- package/content/references/utilities/promise.md +28 -62
- package/content/references/utilities/request.md +58 -218
- package/content/references/utilities/retry.md +139 -0
- package/content/references/utilities/schema.md +43 -137
- package/content/references/utilities/statuses-reference.md +361 -0
- package/content/references/utilities/statuses.md +63 -667
- package/dist/mcp-server/index.js +0 -0
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
- package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
- package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
- package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
- package/package.json +24 -23
|
@@ -1,459 +1,127 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
7
|
+
# WebSocket Component
|
|
4
8
|
|
|
5
|
-
|
|
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
|
-
>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
## How it works
|
|
226
44
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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` and `AUTHENTICATE_HANDLER` are both required. `REDIS_CONNECTION` must be an `AbstractRedisHelper` instance. `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 new handler. It routes WebSocket upgrade requests - `GET <path>` with an `Upgrade: websocket` header - to Bun's native handler.
|
|
50
|
+
- Everything else goes to the existing Hono server, unchanged.
|
|
245
51
|
|
|
246
|
-
|
|
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.
|
|
52
|
+
## Common tasks
|
|
248
53
|
|
|
249
|
-
|
|
54
|
+
### Customize the server path, rooms, and heartbeat
|
|
250
55
|
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
import { WebSocketBindingKeys } from '@venizia/ignis/websocket';
|
|
56
|
+
Bind a partial options object to `SERVER_OPTIONS` before registering the component. Unset fields fall back to `WebSocketDefaults`.
|
|
254
57
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}).toValue({
|
|
258
|
-
identifier: 'my-app-websocket',
|
|
58
|
+
```typescript
|
|
59
|
+
this.bind({ key: WebSocketBindingKeys.SERVER_OPTIONS }).toValue({
|
|
259
60
|
path: '/realtime',
|
|
260
|
-
defaultRooms: ['general', 'announcements'],
|
|
261
|
-
heartbeatInterval:
|
|
262
|
-
heartbeatTimeout:
|
|
263
|
-
requireEncryption: true, // Require ECDH handshake
|
|
264
|
-
serverOptions: {
|
|
265
|
-
maxPayloadLength: 2097152, // 2 MB max payload
|
|
266
|
-
backpressureLimit: 2097152, // 2 MB backpressure limit
|
|
267
|
-
},
|
|
61
|
+
defaultRooms: ['general', 'announcements'],
|
|
62
|
+
heartbeatInterval: 20_000,
|
|
63
|
+
heartbeatTimeout: 60_000,
|
|
268
64
|
});
|
|
269
65
|
```
|
|
270
66
|
|
|
271
|
-
|
|
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
|
-
```
|
|
67
|
+
### Add optional lifecycle callbacks
|
|
310
68
|
|
|
311
|
-
|
|
69
|
+
`VALIDATE_ROOM_HANDLER`, `CLIENT_CONNECTED_HANDLER`, `CLIENT_DISCONNECTED_HANDLER`, and `MESSAGE_HANDLER` are all optional bindings.
|
|
312
70
|
|
|
313
|
-
#### Full `IServerOptions` Interface (Core Component)
|
|
314
71
|
```typescript
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
}
|
|
72
|
+
this.bind({ key: WebSocketBindingKeys.VALIDATE_ROOM_HANDLER }).toValue(
|
|
73
|
+
({ rooms }: { rooms: string[] }) => rooms.filter(room => room.startsWith('public-')),
|
|
74
|
+
);
|
|
324
75
|
```
|
|
325
76
|
|
|
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
77
|
> [!WARNING]
|
|
386
|
-
>
|
|
78
|
+
> Without `VALIDATE_ROOM_HANDLER` bound, **every** client `join` request is rejected.
|
|
387
79
|
|
|
388
|
-
|
|
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
|
-
```
|
|
80
|
+
### Inject the running instance in a service
|
|
398
81
|
|
|
399
|
-
|
|
82
|
+
`WEBSOCKET_INSTANCE` is bound after the server starts, so resolve it lazily rather than through the constructor.
|
|
400
83
|
|
|
401
|
-
#### `TWebSocketClientDisconnectedFn`
|
|
402
84
|
```typescript
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
})
|
|
85
|
+
private get ws(): WebSocketServerHelper {
|
|
86
|
+
return this.application.get<WebSocketServerHelper>({
|
|
87
|
+
key: WebSocketBindingKeys.WEBSOCKET_INSTANCE,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
407
90
|
```
|
|
408
91
|
|
|
409
|
-
|
|
92
|
+
### Send from a process with no WebSocket server
|
|
410
93
|
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
type TWebSocketMessageHandler = (opts: {
|
|
414
|
-
clientId: string;
|
|
415
|
-
userId?: string;
|
|
416
|
-
message: IWebSocketMessage;
|
|
417
|
-
}) => ValueOrPromise<void>;
|
|
418
|
-
```
|
|
94
|
+
Background workers, cron jobs, and other microservices use the standalone `WebSocketEmitter` - it uses the same Redis connection with no local server required.
|
|
419
95
|
|
|
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
96
|
```typescript
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
event: string;
|
|
430
|
-
data: DataType;
|
|
431
|
-
}) => ValueOrPromise<TNullable<{ event: string; data: DataType }>>;
|
|
97
|
+
import { WebSocketEmitter } from '@venizia/ignis-helpers';
|
|
98
|
+
|
|
99
|
+
const emitter = new WebSocketEmitter({ redisConnection: redisHelper });
|
|
100
|
+
await emitter.configure();
|
|
101
|
+
await emitter.toRoom({ room: 'dashboard-viewers', event: 'data:update', data: { cpu: 42 } });
|
|
432
102
|
```
|
|
433
103
|
|
|
434
|
-
|
|
104
|
+
### Require encrypted sessions
|
|
435
105
|
|
|
436
|
-
|
|
437
|
-
> The transformer is only called for clients where `client.encrypted === true`. Non-encrypted clients bypass this entirely (zero overhead).
|
|
106
|
+
Set `requireEncryption: true` and bind a `HANDSHAKE_HANDLER`. It becomes required the moment encryption is turned on.
|
|
438
107
|
|
|
439
|
-
#### `TWebSocketHandshakeFn`
|
|
440
108
|
```typescript
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
> = (opts: {
|
|
444
|
-
clientId: string;
|
|
445
|
-
userId?: string;
|
|
446
|
-
data: AuthDataType;
|
|
447
|
-
}) => ValueOrPromise<{ serverPublicKey: string; salt: string } | null | false>;
|
|
109
|
+
this.bind({ key: WebSocketBindingKeys.SERVER_OPTIONS }).toValue({ requireEncryption: true });
|
|
110
|
+
this.bind({ key: WebSocketBindingKeys.HANDSHAKE_HANDLER }).toValue(handshakeFn);
|
|
448
111
|
```
|
|
449
112
|
|
|
450
|
-
|
|
113
|
+
## See also
|
|
114
|
+
|
|
115
|
+
- [Usage & Examples](./usage) - injecting the helper, `WebSocketEmitter`, wire protocol, client tracking, delivery strategy
|
|
116
|
+
- [Full Reference](./api) - lifecycle diagram, binding keys, configuration options, `WebSocketEmitter` API, internals
|
|
117
|
+
- [Error Reference](./errors) - error conditions and troubleshooting
|
|
118
|
+
- [WebSocketServerHelper](/extensions/helpers/websocket/) - the helper this component wires in
|
|
119
|
+
- [Socket.IO Component](../socket-io/) - Node.js-compatible alternative
|
|
120
|
+
- [Bun WebSocket Documentation](https://bun.sh/docs/api/websockets) - official Bun WebSocket API reference
|
|
451
121
|
|
|
452
|
-
|
|
122
|
+
**Files:**
|
|
453
123
|
|
|
454
|
-
- [
|
|
455
|
-
- [
|
|
456
|
-
- [
|
|
457
|
-
- [
|
|
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
|
|
124
|
+
- [`packages/core-server/src/components/websocket/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/websocket/component.ts) - `WebSocketComponent`
|
|
125
|
+
- [`packages/core-server/src/components/websocket/common/keys.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/websocket/common/keys.ts) - `WebSocketBindingKeys`
|
|
126
|
+
- [`packages/core-server/src/components/websocket/common/types.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/websocket/common/types.ts) - `IServerOptions`, `DEFAULT_SERVER_OPTIONS`
|
|
127
|
+
- [`packages/core-server/src/components/websocket/handlers/bun.handler.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/websocket/handlers/bun.handler.ts) - `createBunFetchHandler`
|