@onebun/core 0.2.12 → 0.2.13
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/application.test.ts +0 -7
- package/src/decorators/metadata.test.ts +86 -0
- package/src/decorators/metadata.ts +199 -0
- package/src/module/module.ts +36 -0
- package/src/queue/adapters/memory.adapter.test.ts +0 -4
- package/src/queue/adapters/memory.adapter.ts +0 -46
- package/src/queue/adapters/redis.adapter.test.ts +0 -64
- package/src/queue/adapters/redis.adapter.ts +0 -41
- package/src/queue/docs-examples.test.ts +130 -9
- package/src/queue/index.ts +8 -1
- package/src/queue/queue-service-proxy.test.ts +12 -3
- package/src/queue/queue-service-proxy.ts +37 -7
- package/src/queue/queue.service.test.ts +138 -16
- package/src/queue/queue.service.ts +48 -11
- package/src/queue/scheduler.test.ts +280 -0
- package/src/queue/scheduler.ts +143 -1
- package/src/queue/types.ts +75 -27
|
@@ -239,6 +239,286 @@ describe('QueueScheduler', () => {
|
|
|
239
239
|
});
|
|
240
240
|
});
|
|
241
241
|
|
|
242
|
+
describe('addJob', () => {
|
|
243
|
+
it('should add a cron job visible via getJob', () => {
|
|
244
|
+
scheduler.addJob({
|
|
245
|
+
type: 'cron',
|
|
246
|
+
name: 'my-cron',
|
|
247
|
+
expression: '0 0 * * * *',
|
|
248
|
+
pattern: 'test.cron',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const job = scheduler.getJob('my-cron');
|
|
252
|
+
expect(job).toBeDefined();
|
|
253
|
+
expect(job!.type).toBe('cron');
|
|
254
|
+
expect(job!.paused).toBe(false);
|
|
255
|
+
expect(job!.declarative).toBe(false);
|
|
256
|
+
expect(job!.schedule.cron).toBe('0 0 * * * *');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should add an interval job visible via getJob', () => {
|
|
260
|
+
scheduler.addJob({
|
|
261
|
+
type: 'interval',
|
|
262
|
+
name: 'my-interval',
|
|
263
|
+
intervalMs: 5000,
|
|
264
|
+
pattern: 'test.interval',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const job = scheduler.getJob('my-interval');
|
|
268
|
+
expect(job).toBeDefined();
|
|
269
|
+
expect(job!.type).toBe('interval');
|
|
270
|
+
expect(job!.paused).toBe(false);
|
|
271
|
+
expect(job!.declarative).toBe(false);
|
|
272
|
+
expect(job!.schedule.every).toBe(5000);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should add a timeout job visible via getJob', () => {
|
|
276
|
+
scheduler.addJob({
|
|
277
|
+
type: 'timeout',
|
|
278
|
+
name: 'my-timeout',
|
|
279
|
+
timeoutMs: 3000,
|
|
280
|
+
pattern: 'test.timeout',
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const job = scheduler.getJob('my-timeout');
|
|
284
|
+
expect(job).toBeDefined();
|
|
285
|
+
expect(job!.type).toBe('timeout');
|
|
286
|
+
expect(job!.paused).toBe(false);
|
|
287
|
+
expect(job!.declarative).toBe(false);
|
|
288
|
+
expect(job!.schedule.timeout).toBe(3000);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('getJobs returns type, paused, and timeout', () => {
|
|
293
|
+
it('should return correct type, paused, and schedule for all job types', () => {
|
|
294
|
+
scheduler.addJob({
|
|
295
|
+
type: 'cron', name: 'c1', expression: '* * * * *', pattern: 'p',
|
|
296
|
+
});
|
|
297
|
+
scheduler.addJob({
|
|
298
|
+
type: 'interval', name: 'i1', intervalMs: 1000, pattern: 'p',
|
|
299
|
+
});
|
|
300
|
+
scheduler.addJob({
|
|
301
|
+
type: 'timeout', name: 't1', timeoutMs: 2000, pattern: 'p',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const jobs = scheduler.getJobs();
|
|
305
|
+
expect(jobs.length).toBe(3);
|
|
306
|
+
|
|
307
|
+
const cronJob = jobs.find((j) => j.name === 'c1');
|
|
308
|
+
expect(cronJob!.type).toBe('cron');
|
|
309
|
+
expect(cronJob!.paused).toBe(false);
|
|
310
|
+
|
|
311
|
+
const intervalJob = jobs.find((j) => j.name === 'i1');
|
|
312
|
+
expect(intervalJob!.type).toBe('interval');
|
|
313
|
+
expect(intervalJob!.paused).toBe(false);
|
|
314
|
+
expect(intervalJob!.schedule.every).toBe(1000);
|
|
315
|
+
|
|
316
|
+
const timeoutJob = jobs.find((j) => j.name === 't1');
|
|
317
|
+
expect(timeoutJob!.type).toBe('timeout');
|
|
318
|
+
expect(timeoutJob!.paused).toBe(false);
|
|
319
|
+
expect(timeoutJob!.schedule.timeout).toBe(2000);
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
describe('declarative field', () => {
|
|
324
|
+
it('should mark jobs added via addCronJob with declarative option as declarative', () => {
|
|
325
|
+
scheduler.addCronJob('dec-cron', '* * * * *', 'p', undefined, { declarative: true });
|
|
326
|
+
|
|
327
|
+
const job = scheduler.getJob('dec-cron');
|
|
328
|
+
expect(job).toBeDefined();
|
|
329
|
+
expect(job!.declarative).toBe(true);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should mark jobs added via addIntervalJob with declarative option as declarative', () => {
|
|
333
|
+
scheduler.addIntervalJob('dec-interval', 1000, 'p', undefined, { declarative: true });
|
|
334
|
+
|
|
335
|
+
const job = scheduler.getJob('dec-interval');
|
|
336
|
+
expect(job).toBeDefined();
|
|
337
|
+
expect(job!.declarative).toBe(true);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should mark jobs added via addTimeoutJob with declarative option as declarative', () => {
|
|
341
|
+
scheduler.addTimeoutJob('dec-timeout', 1000, 'p', undefined, { declarative: true });
|
|
342
|
+
|
|
343
|
+
const job = scheduler.getJob('dec-timeout');
|
|
344
|
+
expect(job).toBeDefined();
|
|
345
|
+
expect(job!.declarative).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should mark jobs added via addJob as not declarative', () => {
|
|
349
|
+
scheduler.addJob({
|
|
350
|
+
type: 'cron', name: 'dyn-cron', expression: '* * * * *', pattern: 'p',
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const job = scheduler.getJob('dyn-cron');
|
|
354
|
+
expect(job).toBeDefined();
|
|
355
|
+
expect(job!.declarative).toBe(false);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe('pauseJob', () => {
|
|
360
|
+
it('should pause an existing job and return true', () => {
|
|
361
|
+
scheduler.addJob({
|
|
362
|
+
type: 'interval', name: 'j1', intervalMs: 1000, pattern: 'p',
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const result = scheduler.pauseJob('j1');
|
|
366
|
+
expect(result).toBe(true);
|
|
367
|
+
|
|
368
|
+
const job = scheduler.getJob('j1');
|
|
369
|
+
expect(job!.paused).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should return false for nonexistent job', () => {
|
|
373
|
+
const result = scheduler.pauseJob('nonexistent');
|
|
374
|
+
expect(result).toBe(false);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('resumeJob', () => {
|
|
379
|
+
it('should resume a paused job and return true', () => {
|
|
380
|
+
scheduler.addJob({
|
|
381
|
+
type: 'interval', name: 'j1', intervalMs: 1000, pattern: 'p',
|
|
382
|
+
});
|
|
383
|
+
scheduler.pauseJob('j1');
|
|
384
|
+
|
|
385
|
+
const result = scheduler.resumeJob('j1');
|
|
386
|
+
expect(result).toBe(true);
|
|
387
|
+
|
|
388
|
+
const job = scheduler.getJob('j1');
|
|
389
|
+
expect(job!.paused).toBe(false);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should return false for nonexistent job', () => {
|
|
393
|
+
const result = scheduler.resumeJob('nonexistent');
|
|
394
|
+
expect(result).toBe(false);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should return false for non-paused job', () => {
|
|
398
|
+
scheduler.addJob({
|
|
399
|
+
type: 'interval', name: 'j1', intervalMs: 1000, pattern: 'p',
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const result = scheduler.resumeJob('j1');
|
|
403
|
+
expect(result).toBe(false);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe('updateJob', () => {
|
|
408
|
+
it('should update cron expression', () => {
|
|
409
|
+
scheduler.addJob({
|
|
410
|
+
type: 'cron',
|
|
411
|
+
name: 'c1',
|
|
412
|
+
expression: '0 0 * * * *',
|
|
413
|
+
pattern: 'p',
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const result = scheduler.updateJob({ type: 'cron', name: 'c1', expression: '*/5 * * * *' });
|
|
417
|
+
expect(result).toBe(true);
|
|
418
|
+
|
|
419
|
+
const job = scheduler.getJob('c1');
|
|
420
|
+
expect(job!.schedule.cron).toBe('*/5 * * * *');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should update interval', () => {
|
|
424
|
+
scheduler.addJob({
|
|
425
|
+
type: 'interval', name: 'i1', intervalMs: 1000, pattern: 'p',
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const result = scheduler.updateJob({ type: 'interval', name: 'i1', intervalMs: 5000 });
|
|
429
|
+
expect(result).toBe(true);
|
|
430
|
+
|
|
431
|
+
const job = scheduler.getJob('i1');
|
|
432
|
+
expect(job!.schedule.every).toBe(5000);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should update timeout', () => {
|
|
436
|
+
scheduler.addJob({
|
|
437
|
+
type: 'timeout', name: 't1', timeoutMs: 1000, pattern: 'p',
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const result = scheduler.updateJob({ type: 'timeout', name: 't1', timeoutMs: 9000 });
|
|
441
|
+
expect(result).toBe(true);
|
|
442
|
+
|
|
443
|
+
const job = scheduler.getJob('t1');
|
|
444
|
+
expect(job!.schedule.timeout).toBe(9000);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should return false when type does not match', () => {
|
|
448
|
+
scheduler.addJob({
|
|
449
|
+
type: 'interval', name: 'i1', intervalMs: 1000, pattern: 'p',
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const result = scheduler.updateJob({ type: 'cron', name: 'i1', expression: '* * * * *' });
|
|
453
|
+
expect(result).toBe(false);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should return false for nonexistent job', () => {
|
|
457
|
+
const result = scheduler.updateJob({ type: 'cron', name: 'nope', expression: '* * * * *' });
|
|
458
|
+
expect(result).toBe(false);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
describe('paused jobs do not fire', () => {
|
|
463
|
+
it('should not fire paused cron job', async () => {
|
|
464
|
+
const received: Message[] = [];
|
|
465
|
+
await adapter.subscribe('test.cron', async (message) => {
|
|
466
|
+
received.push(message);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Every second cron
|
|
470
|
+
scheduler.addJob({
|
|
471
|
+
type: 'cron',
|
|
472
|
+
name: 'cron-paused',
|
|
473
|
+
expression: '* * * * * *',
|
|
474
|
+
pattern: 'test.cron',
|
|
475
|
+
});
|
|
476
|
+
scheduler.pauseJob('cron-paused');
|
|
477
|
+
scheduler.start();
|
|
478
|
+
|
|
479
|
+
// Advance time well past when cron would fire
|
|
480
|
+
advanceTime(5000);
|
|
481
|
+
await Promise.resolve();
|
|
482
|
+
|
|
483
|
+
expect(received.length).toBe(0);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should not fire paused interval job, and resume restarts it', async () => {
|
|
487
|
+
const received: Message[] = [];
|
|
488
|
+
await adapter.subscribe('test.interval', async (message) => {
|
|
489
|
+
received.push(message);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
scheduler.addJob({
|
|
493
|
+
type: 'interval',
|
|
494
|
+
name: 'int-paused',
|
|
495
|
+
intervalMs: 100,
|
|
496
|
+
pattern: 'test.interval',
|
|
497
|
+
});
|
|
498
|
+
scheduler.start();
|
|
499
|
+
|
|
500
|
+
// Should fire immediately on start
|
|
501
|
+
advanceTime(10);
|
|
502
|
+
await Promise.resolve();
|
|
503
|
+
expect(received.length).toBe(1);
|
|
504
|
+
|
|
505
|
+
// Pause the job
|
|
506
|
+
scheduler.pauseJob('int-paused');
|
|
507
|
+
|
|
508
|
+
// Advance time — no new messages
|
|
509
|
+
advanceTime(500);
|
|
510
|
+
await Promise.resolve();
|
|
511
|
+
expect(received.length).toBe(1);
|
|
512
|
+
|
|
513
|
+
// Resume — should restart interval and fire immediately
|
|
514
|
+
scheduler.resumeJob('int-paused');
|
|
515
|
+
|
|
516
|
+
advanceTime(10);
|
|
517
|
+
await Promise.resolve();
|
|
518
|
+
expect(received.length).toBe(2);
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
242
522
|
describe('createQueueScheduler', () => {
|
|
243
523
|
it('should create scheduler instance', () => {
|
|
244
524
|
const created = createQueueScheduler(adapter);
|
package/src/queue/scheduler.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
|
+
AddJobOptions,
|
|
10
|
+
UpdateJobOptions,
|
|
9
11
|
QueueAdapter,
|
|
10
12
|
ScheduledJobInfo,
|
|
11
13
|
OverlapStrategy,
|
|
@@ -42,6 +44,12 @@ interface ScheduledJob {
|
|
|
42
44
|
// Timeout-specific
|
|
43
45
|
timeoutMs?: number;
|
|
44
46
|
|
|
47
|
+
// Pause state
|
|
48
|
+
paused?: boolean;
|
|
49
|
+
|
|
50
|
+
// Whether created via decorator
|
|
51
|
+
declarative?: boolean;
|
|
52
|
+
|
|
45
53
|
// Runtime state
|
|
46
54
|
timer?: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>;
|
|
47
55
|
isRunning?: boolean;
|
|
@@ -90,8 +98,12 @@ export class QueueScheduler {
|
|
|
90
98
|
this.checkCronJobs();
|
|
91
99
|
}, this.cronCheckIntervalMs);
|
|
92
100
|
|
|
93
|
-
// Start all interval and timeout jobs
|
|
101
|
+
// Start all interval and timeout jobs (skip paused)
|
|
94
102
|
for (const job of this.jobs.values()) {
|
|
103
|
+
if (job.paused) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
if (job.type === 'interval' && job.intervalMs) {
|
|
96
108
|
this.startIntervalJob(job);
|
|
97
109
|
} else if (job.type === 'timeout' && job.timeoutMs) {
|
|
@@ -137,6 +149,7 @@ export class QueueScheduler {
|
|
|
137
149
|
options?: {
|
|
138
150
|
metadata?: Partial<MessageMetadata>;
|
|
139
151
|
overlapStrategy?: OverlapStrategy;
|
|
152
|
+
declarative?: boolean;
|
|
140
153
|
},
|
|
141
154
|
): void {
|
|
142
155
|
const schedule = parseCronExpression(expression);
|
|
@@ -152,6 +165,7 @@ export class QueueScheduler {
|
|
|
152
165
|
getDataFn,
|
|
153
166
|
metadata: options?.metadata,
|
|
154
167
|
overlapStrategy: options?.overlapStrategy ?? 'skip',
|
|
168
|
+
declarative: options?.declarative,
|
|
155
169
|
};
|
|
156
170
|
|
|
157
171
|
this.jobs.set(name, job);
|
|
@@ -167,6 +181,7 @@ export class QueueScheduler {
|
|
|
167
181
|
getDataFn?: () => unknown | Promise<unknown>,
|
|
168
182
|
options?: {
|
|
169
183
|
metadata?: Partial<MessageMetadata>;
|
|
184
|
+
declarative?: boolean;
|
|
170
185
|
},
|
|
171
186
|
): void {
|
|
172
187
|
const job: ScheduledJob = {
|
|
@@ -176,6 +191,7 @@ export class QueueScheduler {
|
|
|
176
191
|
intervalMs,
|
|
177
192
|
getDataFn,
|
|
178
193
|
metadata: options?.metadata,
|
|
194
|
+
declarative: options?.declarative,
|
|
179
195
|
};
|
|
180
196
|
|
|
181
197
|
this.jobs.set(name, job);
|
|
@@ -196,6 +212,7 @@ export class QueueScheduler {
|
|
|
196
212
|
getDataFn?: () => unknown | Promise<unknown>,
|
|
197
213
|
options?: {
|
|
198
214
|
metadata?: Partial<MessageMetadata>;
|
|
215
|
+
declarative?: boolean;
|
|
199
216
|
},
|
|
200
217
|
): void {
|
|
201
218
|
const job: ScheduledJob = {
|
|
@@ -205,6 +222,7 @@ export class QueueScheduler {
|
|
|
205
222
|
timeoutMs,
|
|
206
223
|
getDataFn,
|
|
207
224
|
metadata: options?.metadata,
|
|
225
|
+
declarative: options?.declarative,
|
|
208
226
|
};
|
|
209
227
|
|
|
210
228
|
this.jobs.set(name, job);
|
|
@@ -215,6 +233,118 @@ export class QueueScheduler {
|
|
|
215
233
|
}
|
|
216
234
|
}
|
|
217
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Add a job using the unified API
|
|
238
|
+
*/
|
|
239
|
+
addJob(options: AddJobOptions): void {
|
|
240
|
+
switch (options.type) {
|
|
241
|
+
case 'cron':
|
|
242
|
+
this.addCronJob(options.name, options.expression, options.pattern, options.getDataFn, {
|
|
243
|
+
metadata: options.metadata,
|
|
244
|
+
overlapStrategy: options.overlapStrategy,
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
case 'interval':
|
|
248
|
+
this.addIntervalJob(options.name, options.intervalMs, options.pattern, options.getDataFn, {
|
|
249
|
+
metadata: options.metadata,
|
|
250
|
+
});
|
|
251
|
+
break;
|
|
252
|
+
case 'timeout':
|
|
253
|
+
this.addTimeoutJob(options.name, options.timeoutMs, options.pattern, options.getDataFn, {
|
|
254
|
+
metadata: options.metadata,
|
|
255
|
+
});
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Pause a scheduled job
|
|
262
|
+
*/
|
|
263
|
+
pauseJob(name: string): boolean {
|
|
264
|
+
const job = this.jobs.get(name);
|
|
265
|
+
if (!job) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
job.paused = true;
|
|
270
|
+
|
|
271
|
+
if (job.timer) {
|
|
272
|
+
clearTimeout(job.timer);
|
|
273
|
+
clearInterval(job.timer);
|
|
274
|
+
job.timer = undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resume a paused job
|
|
282
|
+
*/
|
|
283
|
+
resumeJob(name: string): boolean {
|
|
284
|
+
const job = this.jobs.get(name);
|
|
285
|
+
if (!job || !job.paused) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
job.paused = false;
|
|
290
|
+
|
|
291
|
+
if (this.running) {
|
|
292
|
+
if (job.type === 'interval' && job.intervalMs) {
|
|
293
|
+
this.startIntervalJob(job);
|
|
294
|
+
} else if (job.type === 'timeout' && job.timeoutMs) {
|
|
295
|
+
this.startTimeoutJob(job);
|
|
296
|
+
} else if (job.type === 'cron' && job.cronSchedule) {
|
|
297
|
+
job.nextRun = getNextRun(job.cronSchedule) ?? undefined;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Update a scheduled job's timing configuration
|
|
306
|
+
*/
|
|
307
|
+
updateJob(options: UpdateJobOptions): boolean {
|
|
308
|
+
const job = this.jobs.get(options.name);
|
|
309
|
+
if (!job || job.type !== options.type) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
switch (options.type) {
|
|
314
|
+
case 'cron': {
|
|
315
|
+
const schedule = parseCronExpression(options.expression);
|
|
316
|
+
job.cronExpression = options.expression;
|
|
317
|
+
job.cronSchedule = schedule;
|
|
318
|
+
job.nextRun = getNextRun(schedule) ?? undefined;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case 'interval': {
|
|
322
|
+
if (job.timer) {
|
|
323
|
+
clearInterval(job.timer);
|
|
324
|
+
job.timer = undefined;
|
|
325
|
+
}
|
|
326
|
+
job.intervalMs = options.intervalMs;
|
|
327
|
+
if (this.running && !job.paused) {
|
|
328
|
+
this.startIntervalJob(job);
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case 'timeout': {
|
|
333
|
+
if (job.timer) {
|
|
334
|
+
clearTimeout(job.timer);
|
|
335
|
+
job.timer = undefined;
|
|
336
|
+
}
|
|
337
|
+
job.timeoutMs = options.timeoutMs;
|
|
338
|
+
if (this.running && !job.paused) {
|
|
339
|
+
this.startTimeoutJob(job);
|
|
340
|
+
}
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
218
348
|
/**
|
|
219
349
|
* Remove a job
|
|
220
350
|
*/
|
|
@@ -244,10 +374,14 @@ export class QueueScheduler {
|
|
|
244
374
|
for (const job of this.jobs.values()) {
|
|
245
375
|
result.push({
|
|
246
376
|
name: job.name,
|
|
377
|
+
type: job.type,
|
|
378
|
+
paused: job.paused ?? false,
|
|
379
|
+
declarative: job.declarative ?? false,
|
|
247
380
|
pattern: job.pattern,
|
|
248
381
|
schedule: {
|
|
249
382
|
cron: job.cronExpression,
|
|
250
383
|
every: job.intervalMs,
|
|
384
|
+
timeout: job.timeoutMs,
|
|
251
385
|
},
|
|
252
386
|
nextRun: job.nextRun,
|
|
253
387
|
lastRun: job.lastRun,
|
|
@@ -269,10 +403,14 @@ export class QueueScheduler {
|
|
|
269
403
|
|
|
270
404
|
return {
|
|
271
405
|
name: job.name,
|
|
406
|
+
type: job.type,
|
|
407
|
+
paused: job.paused ?? false,
|
|
408
|
+
declarative: job.declarative ?? false,
|
|
272
409
|
pattern: job.pattern,
|
|
273
410
|
schedule: {
|
|
274
411
|
cron: job.cronExpression,
|
|
275
412
|
every: job.intervalMs,
|
|
413
|
+
timeout: job.timeoutMs,
|
|
276
414
|
},
|
|
277
415
|
nextRun: job.nextRun,
|
|
278
416
|
lastRun: job.lastRun,
|
|
@@ -302,6 +440,10 @@ export class QueueScheduler {
|
|
|
302
440
|
continue;
|
|
303
441
|
}
|
|
304
442
|
|
|
443
|
+
if (job.paused) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
|
|
305
447
|
// Check if it's time to run
|
|
306
448
|
if (job.nextRun && now >= job.nextRun) {
|
|
307
449
|
// Handle overlap strategy
|
package/src/queue/types.ts
CHANGED
|
@@ -224,30 +224,79 @@ export interface Subscription {
|
|
|
224
224
|
export type OverlapStrategy = 'skip' | 'queue';
|
|
225
225
|
|
|
226
226
|
/**
|
|
227
|
-
*
|
|
227
|
+
* Add a cron job
|
|
228
228
|
*/
|
|
229
|
-
export interface
|
|
230
|
-
|
|
229
|
+
export interface AddCronJob {
|
|
230
|
+
type: 'cron';
|
|
231
|
+
name: string;
|
|
232
|
+
expression: string;
|
|
231
233
|
pattern: string;
|
|
234
|
+
getDataFn?: () => unknown | Promise<unknown>;
|
|
235
|
+
metadata?: Partial<MessageMetadata>;
|
|
236
|
+
overlapStrategy?: OverlapStrategy;
|
|
237
|
+
}
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
239
|
+
/**
|
|
240
|
+
* Add an interval job
|
|
241
|
+
*/
|
|
242
|
+
export interface AddIntervalJob {
|
|
243
|
+
type: 'interval';
|
|
244
|
+
name: string;
|
|
245
|
+
intervalMs: number;
|
|
246
|
+
pattern: string;
|
|
247
|
+
getDataFn?: () => unknown | Promise<unknown>;
|
|
248
|
+
metadata?: Partial<MessageMetadata>;
|
|
249
|
+
}
|
|
243
250
|
|
|
244
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Add a timeout job
|
|
253
|
+
*/
|
|
254
|
+
export interface AddTimeoutJob {
|
|
255
|
+
type: 'timeout';
|
|
256
|
+
name: string;
|
|
257
|
+
timeoutMs: number;
|
|
258
|
+
pattern: string;
|
|
259
|
+
getDataFn?: () => unknown | Promise<unknown>;
|
|
245
260
|
metadata?: Partial<MessageMetadata>;
|
|
261
|
+
}
|
|
246
262
|
|
|
247
|
-
|
|
248
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Discriminated union for adding any scheduled job type
|
|
265
|
+
*/
|
|
266
|
+
export type AddJobOptions = AddCronJob | AddIntervalJob | AddTimeoutJob;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Update a cron job's expression
|
|
270
|
+
*/
|
|
271
|
+
export interface UpdateCronJob {
|
|
272
|
+
type: 'cron';
|
|
273
|
+
name: string;
|
|
274
|
+
expression: string;
|
|
249
275
|
}
|
|
250
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Update an interval job's interval
|
|
279
|
+
*/
|
|
280
|
+
export interface UpdateIntervalJob {
|
|
281
|
+
type: 'interval';
|
|
282
|
+
name: string;
|
|
283
|
+
intervalMs: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Update a timeout job's delay
|
|
288
|
+
*/
|
|
289
|
+
export interface UpdateTimeoutJob {
|
|
290
|
+
type: 'timeout';
|
|
291
|
+
name: string;
|
|
292
|
+
timeoutMs: number;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Discriminated union for updating any scheduled job type
|
|
297
|
+
*/
|
|
298
|
+
export type UpdateJobOptions = UpdateCronJob | UpdateIntervalJob | UpdateTimeoutJob;
|
|
299
|
+
|
|
251
300
|
/**
|
|
252
301
|
* Information about a scheduled job
|
|
253
302
|
*/
|
|
@@ -255,13 +304,23 @@ export interface ScheduledJobInfo {
|
|
|
255
304
|
/** Job name */
|
|
256
305
|
name: string;
|
|
257
306
|
|
|
307
|
+
/** Job type */
|
|
308
|
+
type: 'cron' | 'interval' | 'timeout';
|
|
309
|
+
|
|
258
310
|
/** Pattern to publish to */
|
|
259
311
|
pattern: string;
|
|
260
312
|
|
|
313
|
+
/** Whether the job is paused */
|
|
314
|
+
paused: boolean;
|
|
315
|
+
|
|
316
|
+
/** Whether the job was created via decorator (@Cron, @Interval, @Timeout) */
|
|
317
|
+
declarative: boolean;
|
|
318
|
+
|
|
261
319
|
/** Schedule configuration */
|
|
262
320
|
schedule: {
|
|
263
321
|
cron?: string;
|
|
264
322
|
every?: number;
|
|
323
|
+
timeout?: number;
|
|
265
324
|
};
|
|
266
325
|
|
|
267
326
|
/** Next scheduled run time */
|
|
@@ -286,7 +345,6 @@ export type QueueFeature =
|
|
|
286
345
|
| 'priority'
|
|
287
346
|
| 'dead-letter-queue'
|
|
288
347
|
| 'retry'
|
|
289
|
-
| 'scheduled-jobs'
|
|
290
348
|
| 'consumer-groups'
|
|
291
349
|
| 'pattern-subscriptions';
|
|
292
350
|
|
|
@@ -353,16 +411,6 @@ export interface QueueAdapter {
|
|
|
353
411
|
options?: SubscribeOptions
|
|
354
412
|
): Promise<Subscription>;
|
|
355
413
|
|
|
356
|
-
// Scheduled Jobs
|
|
357
|
-
/** Add a scheduled job */
|
|
358
|
-
addScheduledJob(name: string, options: ScheduledJobOptions): Promise<void>;
|
|
359
|
-
|
|
360
|
-
/** Remove a scheduled job */
|
|
361
|
-
removeScheduledJob(name: string): Promise<boolean>;
|
|
362
|
-
|
|
363
|
-
/** Get all scheduled jobs */
|
|
364
|
-
getScheduledJobs(): Promise<ScheduledJobInfo[]>;
|
|
365
|
-
|
|
366
414
|
// Feature Support
|
|
367
415
|
/** Check if a feature is supported by this adapter */
|
|
368
416
|
supports(feature: QueueFeature): boolean;
|