@onebun/core 0.1.1 → 0.1.2
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 +233 -0
- package/package.json +1 -1
- package/src/application.test.ts +119 -0
- package/src/application.ts +112 -5
- package/src/docs-examples.test.ts +753 -0
- package/src/index.ts +96 -0
- package/src/module.ts +10 -4
- package/src/redis-client.ts +502 -0
- package/src/shared-redis.ts +231 -0
- package/src/types.ts +50 -0
- package/src/ws-base-gateway.test.ts +479 -0
- package/src/ws-base-gateway.ts +514 -0
- package/src/ws-client.test.ts +511 -0
- package/src/ws-client.ts +628 -0
- package/src/ws-client.types.ts +129 -0
- package/src/ws-decorators.test.ts +331 -0
- package/src/ws-decorators.ts +417 -0
- package/src/ws-guards.test.ts +334 -0
- package/src/ws-guards.ts +298 -0
- package/src/ws-handler.ts +658 -0
- package/src/ws-integration.test.ts +517 -0
- package/src/ws-pattern-matcher.test.ts +152 -0
- package/src/ws-pattern-matcher.ts +240 -0
- package/src/ws-service-definition.ts +223 -0
- package/src/ws-socketio-protocol.test.ts +344 -0
- package/src/ws-socketio-protocol.ts +567 -0
- package/src/ws-storage-memory.test.ts +246 -0
- package/src/ws-storage-memory.ts +222 -0
- package/src/ws-storage-redis.ts +302 -0
- package/src/ws-storage.ts +210 -0
- package/src/ws.types.ts +342 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis WebSocket Storage
|
|
3
|
+
*
|
|
4
|
+
* Redis-based implementation of WsStorageAdapter with pub/sub support
|
|
5
|
+
* for multi-instance deployments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { RedisClient } from './redis-client';
|
|
9
|
+
import type { WsPubSubStorageAdapter, WsStorageEventPayload } from './ws-storage';
|
|
10
|
+
import type { WsClientData, WsRoom } from './ws.types';
|
|
11
|
+
|
|
12
|
+
import { isPatternMatch } from './ws-pattern-matcher';
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Redis key prefixes for WebSocket data
|
|
17
|
+
*/
|
|
18
|
+
const KEYS = {
|
|
19
|
+
CLIENTS: 'ws:clients:',
|
|
20
|
+
ROOMS: 'ws:rooms:',
|
|
21
|
+
ROOM_MEMBERS: 'ws:room:members:',
|
|
22
|
+
CLIENT_ROOMS: 'ws:client:rooms:',
|
|
23
|
+
PUBSUB_CHANNEL: 'ws:events',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Redis-based storage for WebSocket clients and rooms
|
|
28
|
+
* with pub/sub support for multi-instance deployments
|
|
29
|
+
*/
|
|
30
|
+
export class RedisWsStorage implements WsPubSubStorageAdapter {
|
|
31
|
+
private eventHandlers: Array<(payload: WsStorageEventPayload) => void> = [];
|
|
32
|
+
private subscribed = false;
|
|
33
|
+
|
|
34
|
+
constructor(private redisClient: RedisClient) {}
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Client Operations
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
async addClient(client: WsClientData): Promise<void> {
|
|
41
|
+
const key = KEYS.CLIENTS + client.id;
|
|
42
|
+
await this.redisClient.set(key, JSON.stringify(client));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async removeClient(clientId: string): Promise<void> {
|
|
46
|
+
// Remove from all rooms first
|
|
47
|
+
await this.removeClientFromAllRooms(clientId);
|
|
48
|
+
|
|
49
|
+
// Remove client data
|
|
50
|
+
await this.redisClient.del(KEYS.CLIENTS + clientId);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getClient(clientId: string): Promise<WsClientData | null> {
|
|
54
|
+
const data = await this.redisClient.get(KEYS.CLIENTS + clientId);
|
|
55
|
+
if (!data) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(data) as WsClientData;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getAllClients(): Promise<WsClientData[]> {
|
|
67
|
+
const keys = await this.redisClient.keys(KEYS.CLIENTS + '*');
|
|
68
|
+
const clients: WsClientData[] = [];
|
|
69
|
+
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
const clientId = key.replace(KEYS.CLIENTS, '');
|
|
72
|
+
const client = await this.getClient(clientId);
|
|
73
|
+
if (client) {
|
|
74
|
+
clients.push(client);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return clients;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async updateClient(clientId: string, data: Partial<WsClientData>): Promise<void> {
|
|
82
|
+
const client = await this.getClient(clientId);
|
|
83
|
+
if (client) {
|
|
84
|
+
const updated = { ...client, ...data };
|
|
85
|
+
await this.redisClient.set(KEYS.CLIENTS + clientId, JSON.stringify(updated));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async getClientCount(): Promise<number> {
|
|
90
|
+
const keys = await this.redisClient.keys(KEYS.CLIENTS + '*');
|
|
91
|
+
|
|
92
|
+
return keys.length;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Room Operations
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
async createRoom(room: WsRoom): Promise<void> {
|
|
100
|
+
const key = KEYS.ROOMS + room.name;
|
|
101
|
+
await this.redisClient.set(key, JSON.stringify(room));
|
|
102
|
+
|
|
103
|
+
// Also store members in a set for efficient queries
|
|
104
|
+
if (room.clientIds.length > 0) {
|
|
105
|
+
await this.redisClient.sadd(KEYS.ROOM_MEMBERS + room.name, ...room.clientIds);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async deleteRoom(name: string): Promise<void> {
|
|
110
|
+
const room = await this.getRoom(name);
|
|
111
|
+
if (!room) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Remove room from all clients' room lists
|
|
116
|
+
for (const clientId of room.clientIds) {
|
|
117
|
+
await this.redisClient.srem(KEYS.CLIENT_ROOMS + clientId, name);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Delete room data and members set
|
|
121
|
+
await this.redisClient.del(KEYS.ROOMS + name);
|
|
122
|
+
await this.redisClient.del(KEYS.ROOM_MEMBERS + name);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getRoom(name: string): Promise<WsRoom | null> {
|
|
126
|
+
const data = await this.redisClient.get(KEYS.ROOMS + name);
|
|
127
|
+
if (!data) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const room = JSON.parse(data) as WsRoom;
|
|
133
|
+
// Get current members from set
|
|
134
|
+
room.clientIds = await this.redisClient.smembers(KEYS.ROOM_MEMBERS + name);
|
|
135
|
+
|
|
136
|
+
return room;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async getAllRooms(): Promise<WsRoom[]> {
|
|
143
|
+
const keys = await this.redisClient.keys(KEYS.ROOMS + '*');
|
|
144
|
+
const rooms: WsRoom[] = [];
|
|
145
|
+
|
|
146
|
+
for (const key of keys) {
|
|
147
|
+
const roomName = key.replace(KEYS.ROOMS, '');
|
|
148
|
+
const room = await this.getRoom(roomName);
|
|
149
|
+
if (room) {
|
|
150
|
+
rooms.push(room);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return rooms;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getRoomsByPattern(pattern: string): Promise<WsRoom[]> {
|
|
158
|
+
const allRooms = await this.getAllRooms();
|
|
159
|
+
|
|
160
|
+
return allRooms.filter((room) => isPatternMatch(pattern, room.name));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async updateRoomMetadata(name: string, metadata: Record<string, unknown>): Promise<void> {
|
|
164
|
+
const room = await this.getRoom(name);
|
|
165
|
+
if (room) {
|
|
166
|
+
room.metadata = { ...room.metadata, ...metadata };
|
|
167
|
+
await this.redisClient.set(KEYS.ROOMS + name, JSON.stringify(room));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// Room Membership Operations
|
|
173
|
+
// ============================================================================
|
|
174
|
+
|
|
175
|
+
async addClientToRoom(clientId: string, roomName: string): Promise<void> {
|
|
176
|
+
// Ensure room exists
|
|
177
|
+
let room = await this.getRoom(roomName);
|
|
178
|
+
if (!room) {
|
|
179
|
+
room = { name: roomName, clientIds: [] };
|
|
180
|
+
await this.redisClient.set(KEYS.ROOMS + roomName, JSON.stringify(room));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Add to room members set
|
|
184
|
+
await this.redisClient.sadd(KEYS.ROOM_MEMBERS + roomName, clientId);
|
|
185
|
+
|
|
186
|
+
// Add to client's rooms set
|
|
187
|
+
await this.redisClient.sadd(KEYS.CLIENT_ROOMS + clientId, roomName);
|
|
188
|
+
|
|
189
|
+
// Update client's room list in client data
|
|
190
|
+
const client = await this.getClient(clientId);
|
|
191
|
+
if (client && !client.rooms.includes(roomName)) {
|
|
192
|
+
client.rooms.push(roomName);
|
|
193
|
+
await this.updateClient(clientId, { rooms: client.rooms });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async removeClientFromRoom(clientId: string, roomName: string): Promise<void> {
|
|
198
|
+
// Remove from room members set
|
|
199
|
+
await this.redisClient.srem(KEYS.ROOM_MEMBERS + roomName, clientId);
|
|
200
|
+
|
|
201
|
+
// Remove from client's rooms set
|
|
202
|
+
await this.redisClient.srem(KEYS.CLIENT_ROOMS + clientId, roomName);
|
|
203
|
+
|
|
204
|
+
// Update client's room list in client data
|
|
205
|
+
const client = await this.getClient(clientId);
|
|
206
|
+
if (client) {
|
|
207
|
+
client.rooms = client.rooms.filter((r) => r !== roomName);
|
|
208
|
+
await this.updateClient(clientId, { rooms: client.rooms });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Delete room if empty
|
|
212
|
+
const memberCount = await this.redisClient.scard(KEYS.ROOM_MEMBERS + roomName);
|
|
213
|
+
if (memberCount === 0) {
|
|
214
|
+
await this.redisClient.del(KEYS.ROOMS + roomName);
|
|
215
|
+
await this.redisClient.del(KEYS.ROOM_MEMBERS + roomName);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async getClientsInRoom(roomName: string): Promise<string[]> {
|
|
220
|
+
return await this.redisClient.smembers(KEYS.ROOM_MEMBERS + roomName);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async getRoomsForClient(clientId: string): Promise<string[]> {
|
|
224
|
+
return await this.redisClient.smembers(KEYS.CLIENT_ROOMS + clientId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async removeClientFromAllRooms(clientId: string): Promise<void> {
|
|
228
|
+
const rooms = await this.getRoomsForClient(clientId);
|
|
229
|
+
|
|
230
|
+
for (const roomName of rooms) {
|
|
231
|
+
await this.removeClientFromRoom(clientId, roomName);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// Pub/Sub Operations
|
|
237
|
+
// ============================================================================
|
|
238
|
+
|
|
239
|
+
async subscribe(handler: (payload: WsStorageEventPayload) => void): Promise<void> {
|
|
240
|
+
this.eventHandlers.push(handler);
|
|
241
|
+
|
|
242
|
+
if (!this.subscribed) {
|
|
243
|
+
await this.redisClient.subscribe(KEYS.PUBSUB_CHANNEL, (message: string) => {
|
|
244
|
+
try {
|
|
245
|
+
const payload = JSON.parse(message) as WsStorageEventPayload;
|
|
246
|
+
for (const h of this.eventHandlers) {
|
|
247
|
+
try {
|
|
248
|
+
h(payload);
|
|
249
|
+
} catch {
|
|
250
|
+
// Ignore handler errors
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
// Ignore invalid messages
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
this.subscribed = true;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async publish(payload: WsStorageEventPayload): Promise<void> {
|
|
262
|
+
await this.redisClient.publish(KEYS.PUBSUB_CHANNEL, JSON.stringify(payload));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async unsubscribe(): Promise<void> {
|
|
266
|
+
this.eventHandlers = [];
|
|
267
|
+
if (this.subscribed) {
|
|
268
|
+
await this.redisClient.unsubscribe(KEYS.PUBSUB_CHANNEL);
|
|
269
|
+
this.subscribed = false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// Lifecycle Operations
|
|
275
|
+
// ============================================================================
|
|
276
|
+
|
|
277
|
+
async clear(): Promise<void> {
|
|
278
|
+
// Get all keys
|
|
279
|
+
const clientKeys = await this.redisClient.keys(KEYS.CLIENTS + '*');
|
|
280
|
+
const roomKeys = await this.redisClient.keys(KEYS.ROOMS + '*');
|
|
281
|
+
const memberKeys = await this.redisClient.keys(KEYS.ROOM_MEMBERS + '*');
|
|
282
|
+
const clientRoomKeys = await this.redisClient.keys(KEYS.CLIENT_ROOMS + '*');
|
|
283
|
+
|
|
284
|
+
// Delete all keys
|
|
285
|
+
for (const key of [...clientKeys, ...roomKeys, ...memberKeys, ...clientRoomKeys]) {
|
|
286
|
+
await this.redisClient.del(key);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async close(): Promise<void> {
|
|
291
|
+
await this.unsubscribe();
|
|
292
|
+
// Note: We don't disconnect the Redis client here
|
|
293
|
+
// because it might be shared with other consumers
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create Redis WebSocket storage with a Redis client
|
|
299
|
+
*/
|
|
300
|
+
export function createRedisWsStorage(redisClient: RedisClient): WsPubSubStorageAdapter {
|
|
301
|
+
return new RedisWsStorage(redisClient);
|
|
302
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Storage Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for storing WebSocket client and room data.
|
|
5
|
+
* Implementations include in-memory storage and Redis storage.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { WsClientData, WsRoom } from './ws.types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Storage adapter interface for WebSocket state management
|
|
12
|
+
*/
|
|
13
|
+
export interface WsStorageAdapter {
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Client Operations
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Add a new client to storage
|
|
20
|
+
* @param client - Client data to store
|
|
21
|
+
*/
|
|
22
|
+
addClient(client: WsClientData): Promise<void>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Remove a client from storage
|
|
26
|
+
* @param clientId - ID of client to remove
|
|
27
|
+
*/
|
|
28
|
+
removeClient(clientId: string): Promise<void>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get a client by ID
|
|
32
|
+
* @param clientId - ID of client to retrieve
|
|
33
|
+
* @returns Client data or null if not found
|
|
34
|
+
*/
|
|
35
|
+
getClient(clientId: string): Promise<WsClientData | null>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get all connected clients
|
|
39
|
+
* @returns Array of all client data
|
|
40
|
+
*/
|
|
41
|
+
getAllClients(): Promise<WsClientData[]>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Update client data
|
|
45
|
+
* @param clientId - ID of client to update
|
|
46
|
+
* @param data - Partial client data to merge
|
|
47
|
+
*/
|
|
48
|
+
updateClient(clientId: string, data: Partial<WsClientData>): Promise<void>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get number of connected clients
|
|
52
|
+
* @returns Client count
|
|
53
|
+
*/
|
|
54
|
+
getClientCount(): Promise<number>;
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Room Operations
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a new room
|
|
62
|
+
* @param room - Room data to create
|
|
63
|
+
*/
|
|
64
|
+
createRoom(room: WsRoom): Promise<void>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Delete a room
|
|
68
|
+
* @param name - Name of room to delete
|
|
69
|
+
*/
|
|
70
|
+
deleteRoom(name: string): Promise<void>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get a room by name
|
|
74
|
+
* @param name - Name of room to retrieve
|
|
75
|
+
* @returns Room data or null if not found
|
|
76
|
+
*/
|
|
77
|
+
getRoom(name: string): Promise<WsRoom | null>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get all rooms
|
|
81
|
+
* @returns Array of all room data
|
|
82
|
+
*/
|
|
83
|
+
getAllRooms(): Promise<WsRoom[]>;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get rooms matching a pattern
|
|
87
|
+
* @param pattern - Pattern to match room names
|
|
88
|
+
* @returns Array of matching rooms
|
|
89
|
+
*/
|
|
90
|
+
getRoomsByPattern(pattern: string): Promise<WsRoom[]>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Update room metadata
|
|
94
|
+
* @param name - Name of room to update
|
|
95
|
+
* @param metadata - Metadata to merge
|
|
96
|
+
*/
|
|
97
|
+
updateRoomMetadata(name: string, metadata: Record<string, unknown>): Promise<void>;
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Room Membership Operations
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Add a client to a room
|
|
105
|
+
* @param clientId - ID of client to add
|
|
106
|
+
* @param roomName - Name of room to join
|
|
107
|
+
*/
|
|
108
|
+
addClientToRoom(clientId: string, roomName: string): Promise<void>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Remove a client from a room
|
|
112
|
+
* @param clientId - ID of client to remove
|
|
113
|
+
* @param roomName - Name of room to leave
|
|
114
|
+
*/
|
|
115
|
+
removeClientFromRoom(clientId: string, roomName: string): Promise<void>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get all client IDs in a room
|
|
119
|
+
* @param roomName - Name of room
|
|
120
|
+
* @returns Array of client IDs
|
|
121
|
+
*/
|
|
122
|
+
getClientsInRoom(roomName: string): Promise<string[]>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get all rooms a client has joined
|
|
126
|
+
* @param clientId - ID of client
|
|
127
|
+
* @returns Array of room names
|
|
128
|
+
*/
|
|
129
|
+
getRoomsForClient(clientId: string): Promise<string[]>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Remove a client from all rooms
|
|
133
|
+
* @param clientId - ID of client to remove from all rooms
|
|
134
|
+
*/
|
|
135
|
+
removeClientFromAllRooms(clientId: string): Promise<void>;
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Lifecycle Operations
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clear all data (clients and rooms)
|
|
143
|
+
*/
|
|
144
|
+
clear(): Promise<void>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Close storage connections (if any)
|
|
148
|
+
*/
|
|
149
|
+
close(): Promise<void>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Event types for storage pub/sub (used with Redis)
|
|
154
|
+
*/
|
|
155
|
+
export enum WsStorageEvent {
|
|
156
|
+
CLIENT_CONNECTED = 'ws:client:connected',
|
|
157
|
+
CLIENT_DISCONNECTED = 'ws:client:disconnected',
|
|
158
|
+
CLIENT_JOINED_ROOM = 'ws:client:joined',
|
|
159
|
+
CLIENT_LEFT_ROOM = 'ws:client:left',
|
|
160
|
+
BROADCAST = 'ws:broadcast',
|
|
161
|
+
ROOM_BROADCAST = 'ws:room:broadcast',
|
|
162
|
+
CLIENT_MESSAGE = 'ws:client:message',
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Payload for storage events
|
|
167
|
+
*/
|
|
168
|
+
export interface WsStorageEventPayload {
|
|
169
|
+
/** Event type */
|
|
170
|
+
type: WsStorageEvent;
|
|
171
|
+
/** Source instance ID (for multi-instance setups) */
|
|
172
|
+
sourceInstanceId: string;
|
|
173
|
+
/** Event-specific data */
|
|
174
|
+
data: {
|
|
175
|
+
clientId?: string;
|
|
176
|
+
roomName?: string;
|
|
177
|
+
event?: string;
|
|
178
|
+
message?: unknown;
|
|
179
|
+
excludeClientIds?: string[];
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extended storage adapter with pub/sub support (for Redis)
|
|
185
|
+
*/
|
|
186
|
+
export interface WsPubSubStorageAdapter extends WsStorageAdapter {
|
|
187
|
+
/**
|
|
188
|
+
* Subscribe to storage events
|
|
189
|
+
* @param handler - Event handler function
|
|
190
|
+
*/
|
|
191
|
+
subscribe(handler: (payload: WsStorageEventPayload) => void): Promise<void>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Publish an event to all instances
|
|
195
|
+
* @param payload - Event payload
|
|
196
|
+
*/
|
|
197
|
+
publish(payload: WsStorageEventPayload): Promise<void>;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Unsubscribe from storage events
|
|
201
|
+
*/
|
|
202
|
+
unsubscribe(): Promise<void>;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if storage adapter supports pub/sub
|
|
207
|
+
*/
|
|
208
|
+
export function isPubSubAdapter(adapter: WsStorageAdapter): adapter is WsPubSubStorageAdapter {
|
|
209
|
+
return 'subscribe' in adapter && 'publish' in adapter && 'unsubscribe' in adapter;
|
|
210
|
+
}
|