@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,379 @@
1
+ /**
2
+ * Queue Scheduler
3
+ *
4
+ * Handles cron, interval, and timeout scheduling.
5
+ * Creates messages to be published via the queue adapter.
6
+ */
7
+
8
+ import type {
9
+ QueueAdapter,
10
+ ScheduledJobInfo,
11
+ OverlapStrategy,
12
+ MessageMetadata,
13
+ } from './types';
14
+
15
+ import {
16
+ parseCronExpression,
17
+ getNextRun,
18
+ type CronSchedule,
19
+ } from './cron-parser';
20
+
21
+ // ============================================================================
22
+ // Types
23
+ // ============================================================================
24
+
25
+ /**
26
+ * Job configuration
27
+ */
28
+ interface ScheduledJob {
29
+ name: string;
30
+ type: 'cron' | 'interval' | 'timeout';
31
+ pattern: string;
32
+ metadata?: Partial<MessageMetadata>;
33
+ overlapStrategy?: OverlapStrategy;
34
+
35
+ // Cron-specific
36
+ cronExpression?: string;
37
+ cronSchedule?: CronSchedule;
38
+
39
+ // Interval-specific
40
+ intervalMs?: number;
41
+
42
+ // Timeout-specific
43
+ timeoutMs?: number;
44
+
45
+ // Runtime state
46
+ timer?: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>;
47
+ isRunning?: boolean;
48
+ lastRun?: Date;
49
+ nextRun?: Date;
50
+
51
+ // Data provider function
52
+ getDataFn?: () => unknown | Promise<unknown>;
53
+ }
54
+
55
+ // ============================================================================
56
+ // Scheduler Implementation
57
+ // ============================================================================
58
+
59
+ /**
60
+ * Scheduler for managing cron, interval, and timeout jobs
61
+ */
62
+ export class QueueScheduler {
63
+ private jobs = new Map<string, ScheduledJob>();
64
+ private running = false;
65
+ private cronCheckInterval?: ReturnType<typeof setInterval>;
66
+ private readonly cronCheckIntervalMs = 1000; // Check cron jobs every second
67
+
68
+ constructor(private readonly adapter: QueueAdapter) {}
69
+
70
+ /**
71
+ * Start the scheduler
72
+ */
73
+ start(): void {
74
+ if (this.running) {
75
+ return;
76
+ }
77
+
78
+ this.running = true;
79
+
80
+ // Start cron check interval
81
+ this.cronCheckInterval = setInterval(() => {
82
+ this.checkCronJobs();
83
+ }, this.cronCheckIntervalMs);
84
+
85
+ // Start all interval and timeout jobs
86
+ for (const job of this.jobs.values()) {
87
+ if (job.type === 'interval' && job.intervalMs) {
88
+ this.startIntervalJob(job);
89
+ } else if (job.type === 'timeout' && job.timeoutMs) {
90
+ this.startTimeoutJob(job);
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Stop the scheduler
97
+ */
98
+ stop(): void {
99
+ if (!this.running) {
100
+ return;
101
+ }
102
+
103
+ this.running = false;
104
+
105
+ // Clear cron check interval
106
+ if (this.cronCheckInterval) {
107
+ clearInterval(this.cronCheckInterval);
108
+ this.cronCheckInterval = undefined;
109
+ }
110
+
111
+ // Clear all job timers
112
+ for (const job of this.jobs.values()) {
113
+ if (job.timer) {
114
+ clearTimeout(job.timer);
115
+ clearInterval(job.timer);
116
+ job.timer = undefined;
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Add a cron job
123
+ */
124
+ addCronJob(
125
+ name: string,
126
+ expression: string,
127
+ pattern: string,
128
+ getDataFn?: () => unknown | Promise<unknown>,
129
+ options?: {
130
+ metadata?: Partial<MessageMetadata>;
131
+ overlapStrategy?: OverlapStrategy;
132
+ },
133
+ ): void {
134
+ const schedule = parseCronExpression(expression);
135
+ const nextRun = getNextRun(schedule) ?? undefined;
136
+
137
+ const job: ScheduledJob = {
138
+ name,
139
+ type: 'cron',
140
+ pattern,
141
+ cronExpression: expression,
142
+ cronSchedule: schedule,
143
+ nextRun,
144
+ getDataFn,
145
+ metadata: options?.metadata,
146
+ overlapStrategy: options?.overlapStrategy ?? 'skip',
147
+ };
148
+
149
+ this.jobs.set(name, job);
150
+ }
151
+
152
+ /**
153
+ * Add an interval job
154
+ */
155
+ addIntervalJob(
156
+ name: string,
157
+ intervalMs: number,
158
+ pattern: string,
159
+ getDataFn?: () => unknown | Promise<unknown>,
160
+ options?: {
161
+ metadata?: Partial<MessageMetadata>;
162
+ },
163
+ ): void {
164
+ const job: ScheduledJob = {
165
+ name,
166
+ type: 'interval',
167
+ pattern,
168
+ intervalMs,
169
+ getDataFn,
170
+ metadata: options?.metadata,
171
+ };
172
+
173
+ this.jobs.set(name, job);
174
+
175
+ // Start immediately if scheduler is running
176
+ if (this.running) {
177
+ this.startIntervalJob(job);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Add a timeout job (one-time delayed job)
183
+ */
184
+ addTimeoutJob(
185
+ name: string,
186
+ timeoutMs: number,
187
+ pattern: string,
188
+ getDataFn?: () => unknown | Promise<unknown>,
189
+ options?: {
190
+ metadata?: Partial<MessageMetadata>;
191
+ },
192
+ ): void {
193
+ const job: ScheduledJob = {
194
+ name,
195
+ type: 'timeout',
196
+ pattern,
197
+ timeoutMs,
198
+ getDataFn,
199
+ metadata: options?.metadata,
200
+ };
201
+
202
+ this.jobs.set(name, job);
203
+
204
+ // Start immediately if scheduler is running
205
+ if (this.running) {
206
+ this.startTimeoutJob(job);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Remove a job
212
+ */
213
+ removeJob(name: string): boolean {
214
+ const job = this.jobs.get(name);
215
+ if (!job) {
216
+ return false;
217
+ }
218
+
219
+ // Clear timer
220
+ if (job.timer) {
221
+ clearTimeout(job.timer);
222
+ clearInterval(job.timer);
223
+ }
224
+
225
+ this.jobs.delete(name);
226
+
227
+ return true;
228
+ }
229
+
230
+ /**
231
+ * Get all scheduled jobs
232
+ */
233
+ getJobs(): ScheduledJobInfo[] {
234
+ const result: ScheduledJobInfo[] = [];
235
+
236
+ for (const job of this.jobs.values()) {
237
+ result.push({
238
+ name: job.name,
239
+ pattern: job.pattern,
240
+ schedule: {
241
+ cron: job.cronExpression,
242
+ every: job.intervalMs,
243
+ },
244
+ nextRun: job.nextRun,
245
+ lastRun: job.lastRun,
246
+ isRunning: job.isRunning,
247
+ });
248
+ }
249
+
250
+ return result;
251
+ }
252
+
253
+ /**
254
+ * Get a specific job
255
+ */
256
+ getJob(name: string): ScheduledJobInfo | undefined {
257
+ const job = this.jobs.get(name);
258
+ if (!job) {
259
+ return undefined;
260
+ }
261
+
262
+ return {
263
+ name: job.name,
264
+ pattern: job.pattern,
265
+ schedule: {
266
+ cron: job.cronExpression,
267
+ every: job.intervalMs,
268
+ },
269
+ nextRun: job.nextRun,
270
+ lastRun: job.lastRun,
271
+ isRunning: job.isRunning,
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Check if a job exists
277
+ */
278
+ hasJob(name: string): boolean {
279
+ return this.jobs.has(name);
280
+ }
281
+
282
+ // ============================================================================
283
+ // Private Methods
284
+ // ============================================================================
285
+
286
+ /**
287
+ * Check and execute cron jobs
288
+ */
289
+ private checkCronJobs(): void {
290
+ const now = new Date();
291
+
292
+ for (const job of this.jobs.values()) {
293
+ if (job.type !== 'cron' || !job.cronSchedule) {
294
+ continue;
295
+ }
296
+
297
+ // Check if it's time to run
298
+ if (job.nextRun && now >= job.nextRun) {
299
+ // Handle overlap strategy
300
+ if (job.isRunning && job.overlapStrategy === 'skip') {
301
+ // Skip this run, but update next run time
302
+ job.nextRun = getNextRun(job.cronSchedule, now) ?? undefined;
303
+ continue;
304
+ }
305
+
306
+ // Execute the job
307
+ this.executeJob(job);
308
+
309
+ // Update next run time
310
+ job.nextRun = getNextRun(job.cronSchedule, now) ?? undefined;
311
+ }
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Start an interval job
317
+ */
318
+ private startIntervalJob(job: ScheduledJob): void {
319
+ if (job.timer || !job.intervalMs) {
320
+ return;
321
+ }
322
+
323
+ job.timer = setInterval(() => {
324
+ this.executeJob(job);
325
+ }, job.intervalMs);
326
+
327
+ // Also execute immediately
328
+ this.executeJob(job);
329
+ }
330
+
331
+ /**
332
+ * Start a timeout job
333
+ */
334
+ private startTimeoutJob(job: ScheduledJob): void {
335
+ if (job.timer || !job.timeoutMs) {
336
+ return;
337
+ }
338
+
339
+ job.timer = setTimeout(() => {
340
+ this.executeJob(job);
341
+ // Remove the job after execution (it's one-time)
342
+ this.jobs.delete(job.name);
343
+ }, job.timeoutMs);
344
+ }
345
+
346
+ /**
347
+ * Execute a scheduled job
348
+ */
349
+ private async executeJob(job: ScheduledJob): Promise<void> {
350
+ try {
351
+ job.isRunning = true;
352
+ job.lastRun = new Date();
353
+
354
+ // Get data from the data provider function
355
+ let data: unknown;
356
+ if (job.getDataFn) {
357
+ data = await job.getDataFn();
358
+ } else {
359
+ data = { timestamp: Date.now() };
360
+ }
361
+
362
+ // Publish the message
363
+ await this.adapter.publish(job.pattern, data, {
364
+ metadata: job.metadata,
365
+ });
366
+ } catch {
367
+ // Error executing job - silently continue (error handling should be done via events)
368
+ } finally {
369
+ job.isRunning = false;
370
+ }
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Create a queue scheduler
376
+ */
377
+ export function createQueueScheduler(adapter: QueueAdapter): QueueScheduler {
378
+ return new QueueScheduler(adapter);
379
+ }