@onebun/core 0.1.2 → 0.1.3

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 (79) hide show
  1. package/package.json +1 -1
  2. package/src/{application.test.ts → application/application.test.ts} +6 -5
  3. package/src/{application.ts → application/application.ts} +131 -12
  4. package/src/application/index.ts +9 -0
  5. package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
  6. package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
  7. package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
  8. package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
  9. package/src/{decorators.ts → decorators/decorators.ts} +3 -2
  10. package/src/decorators/index.ts +15 -0
  11. package/src/index.ts +47 -134
  12. package/src/module/index.ts +12 -0
  13. package/src/{module.test.ts → module/module.test.ts} +3 -2
  14. package/src/{module.ts → module/module.ts} +6 -5
  15. package/src/queue/adapters/index.ts +8 -0
  16. package/src/queue/adapters/memory.adapter.test.ts +405 -0
  17. package/src/queue/adapters/memory.adapter.ts +509 -0
  18. package/src/queue/adapters/redis.adapter.ts +673 -0
  19. package/src/queue/cron-expression.test.ts +145 -0
  20. package/src/queue/cron-expression.ts +115 -0
  21. package/src/queue/cron-parser.test.ts +185 -0
  22. package/src/queue/cron-parser.ts +287 -0
  23. package/src/queue/decorators.test.ts +292 -0
  24. package/src/queue/decorators.ts +493 -0
  25. package/src/queue/docs-examples.test.ts +449 -0
  26. package/src/queue/guards.test.ts +309 -0
  27. package/src/queue/guards.ts +307 -0
  28. package/src/queue/index.ts +118 -0
  29. package/src/queue/pattern-matcher.test.ts +191 -0
  30. package/src/queue/pattern-matcher.ts +252 -0
  31. package/src/queue/queue.service.ts +421 -0
  32. package/src/queue/scheduler.test.ts +235 -0
  33. package/src/queue/scheduler.ts +379 -0
  34. package/src/queue/types.ts +502 -0
  35. package/src/redis/index.ts +8 -0
  36. package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
  37. package/src/service-client/index.ts +10 -0
  38. package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
  39. package/src/{service-client.ts → service-client/service-client.ts} +1 -1
  40. package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
  41. package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
  42. package/src/testing/index.ts +7 -0
  43. package/src/types.ts +34 -5
  44. package/src/websocket/index.ts +50 -0
  45. package/src/{ws-decorators.ts → websocket/ws-decorators.ts} +2 -1
  46. package/src/{ws-integration.test.ts → websocket/ws-integration.test.ts} +3 -2
  47. package/src/{ws-service-definition.ts → websocket/ws-service-definition.ts} +2 -1
  48. package/src/{ws-storage-redis.ts → websocket/ws-storage-redis.ts} +1 -1
  49. /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
  50. /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
  51. /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
  52. /package/src/{config.service.ts → module/config.service.ts} +0 -0
  53. /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
  54. /package/src/{controller.ts → module/controller.ts} +0 -0
  55. /package/src/{service.test.ts → module/service.test.ts} +0 -0
  56. /package/src/{service.ts → module/service.ts} +0 -0
  57. /package/src/{redis-client.ts → redis/redis-client.ts} +0 -0
  58. /package/src/{shared-redis.ts → redis/shared-redis.ts} +0 -0
  59. /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
  60. /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
  61. /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
  62. /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
  63. /package/src/{ws-base-gateway.test.ts → websocket/ws-base-gateway.test.ts} +0 -0
  64. /package/src/{ws-base-gateway.ts → websocket/ws-base-gateway.ts} +0 -0
  65. /package/src/{ws-client.test.ts → websocket/ws-client.test.ts} +0 -0
  66. /package/src/{ws-client.ts → websocket/ws-client.ts} +0 -0
  67. /package/src/{ws-client.types.ts → websocket/ws-client.types.ts} +0 -0
  68. /package/src/{ws-decorators.test.ts → websocket/ws-decorators.test.ts} +0 -0
  69. /package/src/{ws-guards.test.ts → websocket/ws-guards.test.ts} +0 -0
  70. /package/src/{ws-guards.ts → websocket/ws-guards.ts} +0 -0
  71. /package/src/{ws-handler.ts → websocket/ws-handler.ts} +0 -0
  72. /package/src/{ws-pattern-matcher.test.ts → websocket/ws-pattern-matcher.test.ts} +0 -0
  73. /package/src/{ws-pattern-matcher.ts → websocket/ws-pattern-matcher.ts} +0 -0
  74. /package/src/{ws-socketio-protocol.test.ts → websocket/ws-socketio-protocol.test.ts} +0 -0
  75. /package/src/{ws-socketio-protocol.ts → websocket/ws-socketio-protocol.ts} +0 -0
  76. /package/src/{ws-storage-memory.test.ts → websocket/ws-storage-memory.test.ts} +0 -0
  77. /package/src/{ws-storage-memory.ts → websocket/ws-storage-memory.ts} +0 -0
  78. /package/src/{ws-storage.ts → websocket/ws-storage.ts} +0 -0
  79. /package/src/{ws.types.ts → websocket/ws.types.ts} +0 -0
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Queue Decorators
3
+ *
4
+ * Unified decorators for queue message handling and scheduling.
5
+ * These decorators work with any queue adapter (memory, redis, nats, jetstream).
6
+ */
7
+
8
+ import type {
9
+ SubscribeOptions,
10
+ CronDecoratorOptions,
11
+ IntervalDecoratorOptions,
12
+ TimeoutDecoratorOptions,
13
+ MessageGuard,
14
+ MessageGuardConstructor,
15
+ } from './types';
16
+
17
+ import { defineMetadata, getMetadata } from '../decorators/metadata';
18
+
19
+ // ============================================================================
20
+ // Metadata Keys
21
+ // ============================================================================
22
+
23
+ export const QUEUE_METADATA = {
24
+ SUBSCRIBE: 'queue:subscribe',
25
+ SUBSCRIBE_OPTIONS: 'queue:subscribe:options',
26
+ CRON: 'queue:cron',
27
+ INTERVAL: 'queue:interval',
28
+ TIMEOUT: 'queue:timeout',
29
+ GUARDS: 'queue:guards',
30
+ ON_READY: 'queue:on_ready',
31
+ ON_ERROR: 'queue:on_error',
32
+ ON_MESSAGE_FAILED: 'queue:on_message_failed',
33
+ ON_MESSAGE_RECEIVED: 'queue:on_message_received',
34
+ ON_MESSAGE_PROCESSED: 'queue:on_message_processed',
35
+ } as const;
36
+
37
+ // ============================================================================
38
+ // Subscribe Decorator
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Subscribe handler metadata
43
+ */
44
+ export interface SubscribeMetadata {
45
+ pattern: string;
46
+ options?: SubscribeOptions;
47
+ propertyKey: string | symbol;
48
+ }
49
+
50
+ /**
51
+ * Decorator for subscribing to queue messages
52
+ *
53
+ * @param pattern - Message pattern to subscribe to (supports wildcards)
54
+ * @param options - Subscription options
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Simple subscription with auto-ack
59
+ * \@Subscribe('orders.created')
60
+ * async handleOrderCreated(message: Message<OrderData>) {
61
+ * await this.processOrder(message.data);
62
+ * }
63
+ *
64
+ * // Subscription with manual ack
65
+ * \@Subscribe('orders.*', { ackMode: 'manual', group: 'order-processors' })
66
+ * async handleOrder(message: Message<OrderData>) {
67
+ * try {
68
+ * await this.processOrder(message.data);
69
+ * await message.ack();
70
+ * } catch (error) {
71
+ * await message.nack(true); // requeue
72
+ * }
73
+ * }
74
+ *
75
+ * // Multi-level wildcard
76
+ * \@Subscribe('events.#')
77
+ * async handleAllEvents(message: Message<EventData>) {
78
+ * console.log('Event:', message.pattern, message.data);
79
+ * }
80
+ * ```
81
+ */
82
+ // eslint-disable-next-line @typescript-eslint/naming-convention
83
+ export function Subscribe(pattern: string, options?: SubscribeOptions): MethodDecorator {
84
+ return (target, propertyKey, descriptor) => {
85
+ // Get existing subscriptions or create new array
86
+ const subscriptions: SubscribeMetadata[] =
87
+ getMetadata(QUEUE_METADATA.SUBSCRIBE, target.constructor) || [];
88
+
89
+ // Add this subscription
90
+ subscriptions.push({
91
+ pattern,
92
+ options,
93
+ propertyKey,
94
+ });
95
+
96
+ // Store metadata
97
+ defineMetadata(QUEUE_METADATA.SUBSCRIBE, subscriptions, target.constructor);
98
+
99
+ return descriptor;
100
+ };
101
+ }
102
+
103
+ // ============================================================================
104
+ // Scheduling Decorators
105
+ // ============================================================================
106
+
107
+ /**
108
+ * Cron job metadata
109
+ */
110
+ export interface CronMetadata {
111
+ expression: string;
112
+ options: CronDecoratorOptions;
113
+ propertyKey: string | symbol;
114
+ }
115
+
116
+ /**
117
+ * Decorator for cron-based scheduled jobs
118
+ *
119
+ * The decorated method should return the data to be published.
120
+ *
121
+ * @param expression - Cron expression (5 or 6 fields)
122
+ * @param options - Cron options including the pattern to publish to
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * import { CronExpression } from '\@onebun/core';
127
+ *
128
+ * // Daily report at 9 AM
129
+ * \@Cron('0 0 9 * * *', { pattern: 'reports.daily' })
130
+ * getDailyReportData(): ReportData {
131
+ * return { type: 'daily', date: new Date() };
132
+ * }
133
+ *
134
+ * // Using CronExpression enum
135
+ * \@Cron(CronExpression.EVERY_HOUR, { pattern: 'health.check' })
136
+ * getHealthData(): HealthData {
137
+ * return { status: 'ok', timestamp: Date.now() };
138
+ * }
139
+ * ```
140
+ */
141
+ // eslint-disable-next-line @typescript-eslint/naming-convention
142
+ export function Cron(expression: string, options: CronDecoratorOptions): MethodDecorator {
143
+ return (target, propertyKey, descriptor) => {
144
+ const cronJobs: CronMetadata[] = getMetadata(QUEUE_METADATA.CRON, target.constructor) || [];
145
+
146
+ cronJobs.push({
147
+ expression,
148
+ options: {
149
+ ...options,
150
+ name: options.name ?? String(propertyKey),
151
+ },
152
+ propertyKey,
153
+ });
154
+
155
+ defineMetadata(QUEUE_METADATA.CRON, cronJobs, target.constructor);
156
+
157
+ return descriptor;
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Interval job metadata
163
+ */
164
+ export interface IntervalMetadata {
165
+ milliseconds: number;
166
+ options: IntervalDecoratorOptions;
167
+ propertyKey: string | symbol;
168
+ }
169
+
170
+ /**
171
+ * Decorator for interval-based scheduled jobs
172
+ *
173
+ * The decorated method should return the data to be published.
174
+ *
175
+ * @param milliseconds - Interval in milliseconds
176
+ * @param options - Interval options including the pattern to publish to
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * // Every minute
181
+ * \@Interval(60000, { pattern: 'health.check' })
182
+ * getHealthData(): HealthData {
183
+ * return { timestamp: Date.now() };
184
+ * }
185
+ *
186
+ * // Every 5 seconds
187
+ * \@Interval(5000, { pattern: 'metrics.collect' })
188
+ * getMetrics(): MetricsData {
189
+ * return { cpu: process.cpuUsage(), memory: process.memoryUsage() };
190
+ * }
191
+ * ```
192
+ */
193
+ // eslint-disable-next-line @typescript-eslint/naming-convention
194
+ export function Interval(milliseconds: number, options: IntervalDecoratorOptions): MethodDecorator {
195
+ return (target, propertyKey, descriptor) => {
196
+ const intervals: IntervalMetadata[] =
197
+ getMetadata(QUEUE_METADATA.INTERVAL, target.constructor) || [];
198
+
199
+ intervals.push({
200
+ milliseconds,
201
+ options: {
202
+ ...options,
203
+ name: options.name ?? String(propertyKey),
204
+ },
205
+ propertyKey,
206
+ });
207
+
208
+ defineMetadata(QUEUE_METADATA.INTERVAL, intervals, target.constructor);
209
+
210
+ return descriptor;
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Timeout job metadata
216
+ */
217
+ export interface TimeoutMetadata {
218
+ milliseconds: number;
219
+ options: TimeoutDecoratorOptions;
220
+ propertyKey: string | symbol;
221
+ }
222
+
223
+ /**
224
+ * Decorator for one-time delayed jobs
225
+ *
226
+ * The decorated method should return the data to be published.
227
+ *
228
+ * @param milliseconds - Delay in milliseconds after application start
229
+ * @param options - Timeout options including the pattern to publish to
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * // Publish init complete after 5 seconds
234
+ * \@Timeout(5000, { pattern: 'init.complete' })
235
+ * getInitData(): InitData {
236
+ * return { startedAt: this.startTime };
237
+ * }
238
+ * ```
239
+ */
240
+ // eslint-disable-next-line @typescript-eslint/naming-convention
241
+ export function Timeout(milliseconds: number, options: TimeoutDecoratorOptions): MethodDecorator {
242
+ return (target, propertyKey, descriptor) => {
243
+ const timeouts: TimeoutMetadata[] =
244
+ getMetadata(QUEUE_METADATA.TIMEOUT, target.constructor) || [];
245
+
246
+ timeouts.push({
247
+ milliseconds,
248
+ options: {
249
+ ...options,
250
+ name: options.name ?? String(propertyKey),
251
+ },
252
+ propertyKey,
253
+ });
254
+
255
+ defineMetadata(QUEUE_METADATA.TIMEOUT, timeouts, target.constructor);
256
+
257
+ return descriptor;
258
+ };
259
+ }
260
+
261
+ // ============================================================================
262
+ // Guard Decorator
263
+ // ============================================================================
264
+
265
+ /**
266
+ * Decorator for applying guards to message handlers
267
+ *
268
+ * @param guards - Guards to apply (can be guard instances or constructors)
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * @UseMessageGuards(MessageAuthGuard)
273
+ * @Subscribe('orders.*')
274
+ * async handleOrder(message: Message<OrderData>) {
275
+ * // Only messages with authorization token will be processed
276
+ * }
277
+ *
278
+ * @UseMessageGuards(MessageAuthGuard, new MessageServiceGuard(['payment-service']))
279
+ * @Subscribe('payments.*')
280
+ * async handlePayment(message: Message<PaymentData>) {
281
+ * // Requires both auth and service check
282
+ * }
283
+ * ```
284
+ */
285
+ export function UseMessageGuards(
286
+ ...guards: Array<MessageGuard | MessageGuardConstructor>
287
+ ): MethodDecorator {
288
+ return (target, propertyKey, descriptor) => {
289
+ defineMetadata(QUEUE_METADATA.GUARDS, guards, target.constructor, propertyKey);
290
+
291
+ return descriptor;
292
+ };
293
+ }
294
+
295
+ // ============================================================================
296
+ // Lifecycle Decorators
297
+ // ============================================================================
298
+
299
+ /**
300
+ * Lifecycle handler metadata
301
+ */
302
+ export interface LifecycleMetadata {
303
+ propertyKey: string | symbol;
304
+ }
305
+
306
+ /**
307
+ * Decorator for handling queue ready events
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * \@OnQueueReady()
312
+ * handleReady() {
313
+ * console.log('Queue connected and ready');
314
+ * }
315
+ * ```
316
+ */
317
+ // eslint-disable-next-line @typescript-eslint/naming-convention
318
+ export function OnQueueReady(): MethodDecorator {
319
+ return (target, propertyKey, descriptor) => {
320
+ const handlers: LifecycleMetadata[] =
321
+ getMetadata(QUEUE_METADATA.ON_READY, target.constructor) || [];
322
+
323
+ handlers.push({ propertyKey });
324
+
325
+ defineMetadata(QUEUE_METADATA.ON_READY, handlers, target.constructor);
326
+
327
+ return descriptor;
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Decorator for handling queue error events
333
+ *
334
+ * @example
335
+ * ```typescript
336
+ * \@OnQueueError()
337
+ * handleError(error: Error) {
338
+ * console.error('Queue error:', error);
339
+ * }
340
+ * ```
341
+ */
342
+ // eslint-disable-next-line @typescript-eslint/naming-convention
343
+ export function OnQueueError(): MethodDecorator {
344
+ return (target, propertyKey, descriptor) => {
345
+ const handlers: LifecycleMetadata[] =
346
+ getMetadata(QUEUE_METADATA.ON_ERROR, target.constructor) || [];
347
+
348
+ handlers.push({ propertyKey });
349
+
350
+ defineMetadata(QUEUE_METADATA.ON_ERROR, handlers, target.constructor);
351
+
352
+ return descriptor;
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Decorator for handling message failure events
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * \@OnMessageFailed()
362
+ * handleFailed(message: Message, error: Error) {
363
+ * console.error(`Message ${message.id} failed:`, error);
364
+ * }
365
+ * ```
366
+ */
367
+ // eslint-disable-next-line @typescript-eslint/naming-convention
368
+ export function OnMessageFailed(): MethodDecorator {
369
+ return (target, propertyKey, descriptor) => {
370
+ const handlers: LifecycleMetadata[] =
371
+ getMetadata(QUEUE_METADATA.ON_MESSAGE_FAILED, target.constructor) || [];
372
+
373
+ handlers.push({ propertyKey });
374
+
375
+ defineMetadata(QUEUE_METADATA.ON_MESSAGE_FAILED, handlers, target.constructor);
376
+
377
+ return descriptor;
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Decorator for handling message received events
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * \@OnMessageReceived()
387
+ * handleReceived(message: Message) {
388
+ * console.log(`Message received: ${message.id}`);
389
+ * }
390
+ * ```
391
+ */
392
+ // eslint-disable-next-line @typescript-eslint/naming-convention
393
+ export function OnMessageReceived(): MethodDecorator {
394
+ return (target, propertyKey, descriptor) => {
395
+ const handlers: LifecycleMetadata[] =
396
+ getMetadata(QUEUE_METADATA.ON_MESSAGE_RECEIVED, target.constructor) || [];
397
+
398
+ handlers.push({ propertyKey });
399
+
400
+ defineMetadata(QUEUE_METADATA.ON_MESSAGE_RECEIVED, handlers, target.constructor);
401
+
402
+ return descriptor;
403
+ };
404
+ }
405
+
406
+ /**
407
+ * Decorator for handling message processed events
408
+ *
409
+ * @example
410
+ * ```typescript
411
+ * \@OnMessageProcessed()
412
+ * handleProcessed(message: Message) {
413
+ * console.log(`Message processed: ${message.id}`);
414
+ * }
415
+ * ```
416
+ */
417
+ // eslint-disable-next-line @typescript-eslint/naming-convention
418
+ export function OnMessageProcessed(): MethodDecorator {
419
+ return (target, propertyKey, descriptor) => {
420
+ const handlers: LifecycleMetadata[] =
421
+ getMetadata(QUEUE_METADATA.ON_MESSAGE_PROCESSED, target.constructor) || [];
422
+
423
+ handlers.push({ propertyKey });
424
+
425
+ defineMetadata(QUEUE_METADATA.ON_MESSAGE_PROCESSED, handlers, target.constructor);
426
+
427
+ return descriptor;
428
+ };
429
+ }
430
+
431
+ // ============================================================================
432
+ // Metadata Helpers
433
+ // ============================================================================
434
+
435
+ /**
436
+ * Get all subscribe metadata for a class
437
+ */
438
+ export function getSubscribeMetadata(target: object): SubscribeMetadata[] {
439
+ return getMetadata(QUEUE_METADATA.SUBSCRIBE, target) || [];
440
+ }
441
+
442
+ /**
443
+ * Get all cron metadata for a class
444
+ */
445
+ export function getCronMetadata(target: object): CronMetadata[] {
446
+ return getMetadata(QUEUE_METADATA.CRON, target) || [];
447
+ }
448
+
449
+ /**
450
+ * Get all interval metadata for a class
451
+ */
452
+ export function getIntervalMetadata(target: object): IntervalMetadata[] {
453
+ return getMetadata(QUEUE_METADATA.INTERVAL, target) || [];
454
+ }
455
+
456
+ /**
457
+ * Get all timeout metadata for a class
458
+ */
459
+ export function getTimeoutMetadata(target: object): TimeoutMetadata[] {
460
+ return getMetadata(QUEUE_METADATA.TIMEOUT, target) || [];
461
+ }
462
+
463
+ /**
464
+ * Get guards for a method
465
+ */
466
+ export function getMessageGuards(
467
+ target: object,
468
+ propertyKey: string | symbol,
469
+ ): Array<MessageGuard | MessageGuardConstructor> {
470
+ return getMetadata(QUEUE_METADATA.GUARDS, target, propertyKey) || [];
471
+ }
472
+
473
+ /**
474
+ * Get lifecycle handlers for a class
475
+ */
476
+ export function getLifecycleHandlers(
477
+ target: object,
478
+ event: keyof typeof QUEUE_METADATA,
479
+ ): LifecycleMetadata[] {
480
+ return getMetadata(QUEUE_METADATA[event], target) || [];
481
+ }
482
+
483
+ /**
484
+ * Check if a class has any queue decorators
485
+ */
486
+ export function hasQueueDecorators(target: object): boolean {
487
+ return (
488
+ getSubscribeMetadata(target).length > 0 ||
489
+ getCronMetadata(target).length > 0 ||
490
+ getIntervalMetadata(target).length > 0 ||
491
+ getTimeoutMetadata(target).length > 0
492
+ );
493
+ }