@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,417 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Decorators
|
|
3
|
+
*
|
|
4
|
+
* Decorators for creating WebSocket gateways with event handlers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
8
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
WebSocketGatewayOptions,
|
|
12
|
+
GatewayMetadata,
|
|
13
|
+
WsHandlerMetadata,
|
|
14
|
+
WsParamMetadata,
|
|
15
|
+
WsGuard,
|
|
16
|
+
} from './ws.types';
|
|
17
|
+
|
|
18
|
+
import { Reflect } from './metadata';
|
|
19
|
+
import { WsHandlerType, WsParamType } from './ws.types';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Metadata Keys
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const WS_GATEWAY_METADATA = 'onebun:ws:gateway';
|
|
26
|
+
const WS_PARAMS_METADATA = 'onebun:ws:params';
|
|
27
|
+
const WS_GUARDS_METADATA = 'onebun:ws:guards';
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Metadata Storage
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
const META_GATEWAYS = new Map<Function, GatewayMetadata>();
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Class Decorators
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decorator to mark a class as a WebSocket Gateway
|
|
41
|
+
*
|
|
42
|
+
* @param options - Gateway options (path, namespace)
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* @WebSocketGateway({ path: '/ws', namespace: 'chat' })
|
|
47
|
+
* export class ChatGateway extends BaseWebSocketGateway {
|
|
48
|
+
* // ...
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function WebSocketGateway(options?: WebSocketGatewayOptions) {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
return <T extends new (...args: any[]) => any>(target: T): T => {
|
|
55
|
+
const metadata: GatewayMetadata = {
|
|
56
|
+
path: options?.path || '/',
|
|
57
|
+
namespace: options?.namespace,
|
|
58
|
+
handlers: [],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Get existing handlers metadata
|
|
62
|
+
const existingMetadata = META_GATEWAYS.get(target);
|
|
63
|
+
if (existingMetadata) {
|
|
64
|
+
metadata.handlers = existingMetadata.handlers;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
META_GATEWAYS.set(target, metadata);
|
|
68
|
+
Reflect.defineMetadata(WS_GATEWAY_METADATA, metadata, target);
|
|
69
|
+
|
|
70
|
+
return target;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get gateway metadata for a class
|
|
76
|
+
*/
|
|
77
|
+
export function getGatewayMetadata(target: Function): GatewayMetadata | undefined {
|
|
78
|
+
return META_GATEWAYS.get(target) || Reflect.getMetadata(WS_GATEWAY_METADATA, target);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a class is a WebSocket Gateway
|
|
83
|
+
*/
|
|
84
|
+
export function isWebSocketGateway(target: Function): boolean {
|
|
85
|
+
return META_GATEWAYS.has(target) || Reflect.getMetadata(WS_GATEWAY_METADATA, target) !== undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Method Decorators (Event Handlers)
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create a WebSocket handler decorator
|
|
94
|
+
*/
|
|
95
|
+
function createWsHandlerDecorator(type: WsHandlerType, pattern?: string) {
|
|
96
|
+
return (target: object, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
97
|
+
const gatewayClass = target.constructor as Function;
|
|
98
|
+
|
|
99
|
+
// Get existing metadata or create new
|
|
100
|
+
let metadata = META_GATEWAYS.get(gatewayClass);
|
|
101
|
+
if (!metadata) {
|
|
102
|
+
metadata = { path: '/', handlers: [] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get parameter metadata
|
|
106
|
+
const params: WsParamMetadata[] =
|
|
107
|
+
Reflect.getMetadata(WS_PARAMS_METADATA, target, propertyKey) || [];
|
|
108
|
+
|
|
109
|
+
// Get guards metadata
|
|
110
|
+
const guards: Function[] =
|
|
111
|
+
Reflect.getMetadata(WS_GUARDS_METADATA, target, propertyKey) || [];
|
|
112
|
+
|
|
113
|
+
// Create handler metadata
|
|
114
|
+
const handlerMetadata: WsHandlerMetadata = {
|
|
115
|
+
type,
|
|
116
|
+
pattern,
|
|
117
|
+
handler: propertyKey,
|
|
118
|
+
params,
|
|
119
|
+
guards,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
metadata.handlers.push(handlerMetadata);
|
|
123
|
+
META_GATEWAYS.set(gatewayClass, metadata);
|
|
124
|
+
|
|
125
|
+
return descriptor;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Decorator for handling client connection events
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* @OnConnect()
|
|
135
|
+
* handleConnect(@Client() client: WsClientData) {
|
|
136
|
+
* console.log('Client connected:', client.id);
|
|
137
|
+
* return { event: 'welcome', data: { message: 'Welcome!' } };
|
|
138
|
+
* }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function OnConnect() {
|
|
142
|
+
return createWsHandlerDecorator(WsHandlerType.CONNECT);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Decorator for handling client disconnection events
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```typescript
|
|
150
|
+
* @OnDisconnect()
|
|
151
|
+
* handleDisconnect(@Client() client: WsClientData) {
|
|
152
|
+
* console.log('Client disconnected:', client.id);
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function OnDisconnect() {
|
|
157
|
+
return createWsHandlerDecorator(WsHandlerType.DISCONNECT);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Decorator for handling room join events
|
|
162
|
+
*
|
|
163
|
+
* @param pattern - Optional pattern for room name matching
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* @OnJoinRoom('room:{roomId}')
|
|
168
|
+
* handleJoinRoom(@Client() client: WsClientData, @RoomName() room: string) {
|
|
169
|
+
* console.log(`Client ${client.id} joined room ${room}`);
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export function OnJoinRoom(pattern?: string) {
|
|
174
|
+
return createWsHandlerDecorator(WsHandlerType.JOIN_ROOM, pattern);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Decorator for handling room leave events
|
|
179
|
+
*
|
|
180
|
+
* @param pattern - Optional pattern for room name matching
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* @OnLeaveRoom('room:*')
|
|
185
|
+
* handleLeaveRoom(@Client() client: WsClientData, @RoomName() room: string) {
|
|
186
|
+
* console.log(`Client ${client.id} left room ${room}`);
|
|
187
|
+
* }
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export function OnLeaveRoom(pattern?: string) {
|
|
191
|
+
return createWsHandlerDecorator(WsHandlerType.LEAVE_ROOM, pattern);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Decorator for handling incoming messages
|
|
196
|
+
*
|
|
197
|
+
* @param pattern - Event pattern to match (supports wildcards and parameters)
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* @OnMessage('chat:message')
|
|
202
|
+
* handleMessage(@Client() client: WsClientData, @MessageData() data: any) {
|
|
203
|
+
* this.broadcast('chat:message', { userId: client.id, ...data });
|
|
204
|
+
* }
|
|
205
|
+
*
|
|
206
|
+
* @OnMessage('chat:{roomId}:message')
|
|
207
|
+
* handleRoomMessage(
|
|
208
|
+
* @Client() client: WsClientData,
|
|
209
|
+
* @MessageData() data: any,
|
|
210
|
+
* @PatternParams() params: { roomId: string }
|
|
211
|
+
* ) {
|
|
212
|
+
* this.emitToRoom(`room:${params.roomId}`, 'chat:message', data);
|
|
213
|
+
* }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export function OnMessage(pattern: string) {
|
|
217
|
+
return createWsHandlerDecorator(WsHandlerType.MESSAGE, pattern);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================================================
|
|
221
|
+
// Parameter Decorators
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Create a WebSocket parameter decorator
|
|
226
|
+
*/
|
|
227
|
+
function createWsParamDecorator(type: WsParamType, property?: string) {
|
|
228
|
+
return (target: object, propertyKey: string, parameterIndex: number) => {
|
|
229
|
+
const params: WsParamMetadata[] =
|
|
230
|
+
Reflect.getMetadata(WS_PARAMS_METADATA, target, propertyKey) || [];
|
|
231
|
+
|
|
232
|
+
params.push({
|
|
233
|
+
type,
|
|
234
|
+
property,
|
|
235
|
+
index: parameterIndex,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
Reflect.defineMetadata(WS_PARAMS_METADATA, params, target, propertyKey);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Decorator to inject client data into handler
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* @OnMessage('chat:message')
|
|
248
|
+
* handleMessage(@Client() client: WsClientData) {
|
|
249
|
+
* console.log('Message from:', client.id);
|
|
250
|
+
* }
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
export function Client() {
|
|
254
|
+
return createWsParamDecorator(WsParamType.CLIENT);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Decorator to inject raw WebSocket into handler
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* @OnMessage('ping')
|
|
263
|
+
* handlePing(@Socket() socket: ServerWebSocket) {
|
|
264
|
+
* socket.send(JSON.stringify({ event: 'pong', data: {} }));
|
|
265
|
+
* }
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export function Socket() {
|
|
269
|
+
return createWsParamDecorator(WsParamType.SOCKET);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Decorator to inject message data into handler
|
|
274
|
+
*
|
|
275
|
+
* @param property - Optional property path to extract from message data
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```typescript
|
|
279
|
+
* @OnMessage('chat:message')
|
|
280
|
+
* handleMessage(@MessageData() data: { text: string }) {
|
|
281
|
+
* console.log('Message:', data.text);
|
|
282
|
+
* }
|
|
283
|
+
*
|
|
284
|
+
* @OnMessage('chat:message')
|
|
285
|
+
* handleMessage(@MessageData('text') text: string) {
|
|
286
|
+
* console.log('Message text:', text);
|
|
287
|
+
* }
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export function MessageData(property?: string) {
|
|
291
|
+
return createWsParamDecorator(WsParamType.MESSAGE_DATA, property);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Decorator to inject room name into handler
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```typescript
|
|
299
|
+
* @OnJoinRoom('room:{roomId}')
|
|
300
|
+
* handleJoinRoom(@RoomName() room: string) {
|
|
301
|
+
* console.log('Joining room:', room);
|
|
302
|
+
* }
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
export function RoomName() {
|
|
306
|
+
return createWsParamDecorator(WsParamType.ROOM_NAME);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Decorator to inject pattern parameters into handler
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```typescript
|
|
314
|
+
* @OnMessage('chat:{roomId}:message')
|
|
315
|
+
* handleMessage(@PatternParams() params: { roomId: string }) {
|
|
316
|
+
* console.log('Room ID:', params.roomId);
|
|
317
|
+
* }
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
export function PatternParams() {
|
|
321
|
+
return createWsParamDecorator(WsParamType.PATTERN_PARAMS);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Decorator to inject WebSocket server into handler or property
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* @WebSocketGateway()
|
|
330
|
+
* export class ChatGateway extends BaseWebSocketGateway {
|
|
331
|
+
* @WsServer()
|
|
332
|
+
* server: WsServer;
|
|
333
|
+
*
|
|
334
|
+
* // Or as parameter
|
|
335
|
+
* @OnMessage('broadcast')
|
|
336
|
+
* handleBroadcast(@WsServer() server: WsServer) {
|
|
337
|
+
* server.publish('all', 'Hello everyone!');
|
|
338
|
+
* }
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
export function WsServer() {
|
|
343
|
+
return createWsParamDecorator(WsParamType.SERVER);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// Guards Decorator
|
|
348
|
+
// ============================================================================
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Decorator to apply guards to a WebSocket handler
|
|
352
|
+
*
|
|
353
|
+
* @param guards - Guard classes or instances to apply
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```typescript
|
|
357
|
+
* @UseWsGuards(WsAuthGuard)
|
|
358
|
+
* @OnMessage('admin:*')
|
|
359
|
+
* handleAdminMessage(@Client() client: WsClientData) {
|
|
360
|
+
* // Only authenticated clients can reach here
|
|
361
|
+
* }
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
export function UseWsGuards(...guards: (Function | WsGuard)[]): MethodDecorator {
|
|
365
|
+
return (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
|
|
366
|
+
const existingGuards: (Function | WsGuard)[] =
|
|
367
|
+
Reflect.getMetadata(WS_GUARDS_METADATA, target, propertyKey as string) || [];
|
|
368
|
+
|
|
369
|
+
Reflect.defineMetadata(
|
|
370
|
+
WS_GUARDS_METADATA,
|
|
371
|
+
[...existingGuards, ...guards],
|
|
372
|
+
target,
|
|
373
|
+
propertyKey as string,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
return descriptor;
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// Helper Functions
|
|
382
|
+
// ============================================================================
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get handler metadata for a gateway method
|
|
386
|
+
*/
|
|
387
|
+
export function getWsHandlerMetadata(
|
|
388
|
+
target: Function,
|
|
389
|
+
methodName: string,
|
|
390
|
+
): WsHandlerMetadata | undefined {
|
|
391
|
+
const metadata = getGatewayMetadata(target);
|
|
392
|
+
|
|
393
|
+
return metadata?.handlers.find((h) => h.handler === methodName);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Get all handlers from a gateway
|
|
398
|
+
*/
|
|
399
|
+
export function getWsHandlers(target: Function): WsHandlerMetadata[] {
|
|
400
|
+
const metadata = getGatewayMetadata(target);
|
|
401
|
+
|
|
402
|
+
return metadata?.handlers || [];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get parameter metadata for a handler
|
|
407
|
+
*/
|
|
408
|
+
export function getWsParamMetadata(target: object, methodName: string): WsParamMetadata[] {
|
|
409
|
+
return Reflect.getMetadata(WS_PARAMS_METADATA, target, methodName) || [];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get guards for a handler
|
|
414
|
+
*/
|
|
415
|
+
export function getWsGuards(target: object, methodName: string): Function[] {
|
|
416
|
+
return Reflect.getMetadata(WS_GUARDS_METADATA, target, methodName) || [];
|
|
417
|
+
}
|