@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.
@@ -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
+ }