@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.
- package/package.json +1 -1
- package/src/{application.test.ts → application/application.test.ts} +6 -5
- package/src/{application.ts → application/application.ts} +131 -12
- package/src/application/index.ts +9 -0
- package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
- package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
- package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
- package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
- package/src/{decorators.ts → decorators/decorators.ts} +3 -2
- package/src/decorators/index.ts +15 -0
- package/src/index.ts +47 -134
- package/src/module/index.ts +12 -0
- package/src/{module.test.ts → module/module.test.ts} +3 -2
- package/src/{module.ts → module/module.ts} +6 -5
- package/src/queue/adapters/index.ts +8 -0
- package/src/queue/adapters/memory.adapter.test.ts +405 -0
- package/src/queue/adapters/memory.adapter.ts +509 -0
- package/src/queue/adapters/redis.adapter.ts +673 -0
- package/src/queue/cron-expression.test.ts +145 -0
- package/src/queue/cron-expression.ts +115 -0
- package/src/queue/cron-parser.test.ts +185 -0
- package/src/queue/cron-parser.ts +287 -0
- package/src/queue/decorators.test.ts +292 -0
- package/src/queue/decorators.ts +493 -0
- package/src/queue/docs-examples.test.ts +449 -0
- package/src/queue/guards.test.ts +309 -0
- package/src/queue/guards.ts +307 -0
- package/src/queue/index.ts +118 -0
- package/src/queue/pattern-matcher.test.ts +191 -0
- package/src/queue/pattern-matcher.ts +252 -0
- package/src/queue/queue.service.ts +421 -0
- package/src/queue/scheduler.test.ts +235 -0
- package/src/queue/scheduler.ts +379 -0
- package/src/queue/types.ts +502 -0
- package/src/redis/index.ts +8 -0
- package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
- package/src/service-client/index.ts +10 -0
- package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
- package/src/{service-client.ts → service-client/service-client.ts} +1 -1
- package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
- package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
- package/src/testing/index.ts +7 -0
- package/src/types.ts +34 -5
- package/src/websocket/index.ts +50 -0
- package/src/{ws-decorators.ts → websocket/ws-decorators.ts} +2 -1
- package/src/{ws-integration.test.ts → websocket/ws-integration.test.ts} +3 -2
- package/src/{ws-service-definition.ts → websocket/ws-service-definition.ts} +2 -1
- package/src/{ws-storage-redis.ts → websocket/ws-storage-redis.ts} +1 -1
- /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
- /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
- /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
- /package/src/{config.service.ts → module/config.service.ts} +0 -0
- /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
- /package/src/{controller.ts → module/controller.ts} +0 -0
- /package/src/{service.test.ts → module/service.test.ts} +0 -0
- /package/src/{service.ts → module/service.ts} +0 -0
- /package/src/{redis-client.ts → redis/redis-client.ts} +0 -0
- /package/src/{shared-redis.ts → redis/shared-redis.ts} +0 -0
- /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
- /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
- /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
- /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
- /package/src/{ws-base-gateway.test.ts → websocket/ws-base-gateway.test.ts} +0 -0
- /package/src/{ws-base-gateway.ts → websocket/ws-base-gateway.ts} +0 -0
- /package/src/{ws-client.test.ts → websocket/ws-client.test.ts} +0 -0
- /package/src/{ws-client.ts → websocket/ws-client.ts} +0 -0
- /package/src/{ws-client.types.ts → websocket/ws-client.types.ts} +0 -0
- /package/src/{ws-decorators.test.ts → websocket/ws-decorators.test.ts} +0 -0
- /package/src/{ws-guards.test.ts → websocket/ws-guards.test.ts} +0 -0
- /package/src/{ws-guards.ts → websocket/ws-guards.ts} +0 -0
- /package/src/{ws-handler.ts → websocket/ws-handler.ts} +0 -0
- /package/src/{ws-pattern-matcher.test.ts → websocket/ws-pattern-matcher.test.ts} +0 -0
- /package/src/{ws-pattern-matcher.ts → websocket/ws-pattern-matcher.ts} +0 -0
- /package/src/{ws-socketio-protocol.test.ts → websocket/ws-socketio-protocol.test.ts} +0 -0
- /package/src/{ws-socketio-protocol.ts → websocket/ws-socketio-protocol.ts} +0 -0
- /package/src/{ws-storage-memory.test.ts → websocket/ws-storage-memory.test.ts} +0 -0
- /package/src/{ws-storage-memory.ts → websocket/ws-storage-memory.ts} +0 -0
- /package/src/{ws-storage.ts → websocket/ws-storage.ts} +0 -0
- /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
|
+
}
|