@nicnocquee/dataqueue 1.30.0 → 1.32.0
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/dist/index.cjs +2531 -1283
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +367 -17
- package/dist/index.d.ts +367 -17
- package/dist/index.js +2530 -1284
- package/dist/index.js.map +1 -1
- package/migrations/1781200000004_create_cron_schedules_table.sql +33 -0
- package/package.json +3 -2
- package/src/backend.ts +139 -4
- package/src/backends/postgres.ts +676 -30
- package/src/backends/redis-scripts.ts +197 -22
- package/src/backends/redis.test.ts +971 -0
- package/src/backends/redis.ts +789 -22
- package/src/cron.test.ts +126 -0
- package/src/cron.ts +40 -0
- package/src/index.test.ts +361 -0
- package/src/index.ts +165 -29
- package/src/processor.ts +36 -97
- package/src/queue.test.ts +29 -0
- package/src/queue.ts +19 -251
- package/src/types.ts +177 -10
package/src/queue.ts
CHANGED
|
@@ -18,8 +18,6 @@ import {
|
|
|
18
18
|
WaitpointRecord,
|
|
19
19
|
} from './types.js';
|
|
20
20
|
import { PostgresBackend } from './backends/postgres.js';
|
|
21
|
-
import { randomUUID } from 'crypto';
|
|
22
|
-
import { log } from './log-context.js';
|
|
23
21
|
|
|
24
22
|
/* Thin wrappers — every function creates a lightweight backend wrapper
|
|
25
23
|
around the given pool and forwards the call. The class itself holds
|
|
@@ -94,7 +92,9 @@ export const retryJob = async (pool: Pool, jobId: number): Promise<void> =>
|
|
|
94
92
|
export const cleanupOldJobs = async (
|
|
95
93
|
pool: Pool,
|
|
96
94
|
daysToKeep = 30,
|
|
97
|
-
|
|
95
|
+
batchSize = 1000,
|
|
96
|
+
): Promise<number> =>
|
|
97
|
+
new PostgresBackend(pool).cleanupOldJobs(daysToKeep, batchSize);
|
|
98
98
|
|
|
99
99
|
export const cancelJob = async (pool: Pool, jobId: number): Promise<void> =>
|
|
100
100
|
new PostgresBackend(pool).cancelJob(jobId);
|
|
@@ -214,12 +214,9 @@ export const updateProgress = async (
|
|
|
214
214
|
progress: number,
|
|
215
215
|
): Promise<void> => new PostgresBackend(pool).updateProgress(jobId, progress);
|
|
216
216
|
|
|
217
|
-
// ── Wait support functions (
|
|
217
|
+
// ── Wait support functions (backward-compatible delegates) ────────────────────
|
|
218
218
|
|
|
219
|
-
/**
|
|
220
|
-
* Transition a job to 'waiting' status with wait_until and/or wait_token_id.
|
|
221
|
-
* Saves step_data so the handler can resume from where it left off.
|
|
222
|
-
*/
|
|
219
|
+
/** @deprecated Use backend.waitJob() directly. Delegates to PostgresBackend. */
|
|
223
220
|
export const waitJob = async (
|
|
224
221
|
pool: Pool,
|
|
225
222
|
jobId: number,
|
|
@@ -228,266 +225,37 @@ export const waitJob = async (
|
|
|
228
225
|
waitTokenId?: string;
|
|
229
226
|
stepData: Record<string, any>;
|
|
230
227
|
},
|
|
231
|
-
): Promise<void> =>
|
|
232
|
-
const client = await pool.connect();
|
|
233
|
-
try {
|
|
234
|
-
const result = await client.query(
|
|
235
|
-
`
|
|
236
|
-
UPDATE job_queue
|
|
237
|
-
SET status = 'waiting',
|
|
238
|
-
wait_until = $2,
|
|
239
|
-
wait_token_id = $3,
|
|
240
|
-
step_data = $4,
|
|
241
|
-
locked_at = NULL,
|
|
242
|
-
locked_by = NULL,
|
|
243
|
-
updated_at = NOW()
|
|
244
|
-
WHERE id = $1 AND status = 'processing'
|
|
245
|
-
`,
|
|
246
|
-
[
|
|
247
|
-
jobId,
|
|
248
|
-
options.waitUntil ?? null,
|
|
249
|
-
options.waitTokenId ?? null,
|
|
250
|
-
JSON.stringify(options.stepData),
|
|
251
|
-
],
|
|
252
|
-
);
|
|
253
|
-
if (result.rowCount === 0) {
|
|
254
|
-
log(
|
|
255
|
-
`Job ${jobId} could not be set to waiting (may have been reclaimed or is no longer processing)`,
|
|
256
|
-
);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
await recordJobEvent(pool, jobId, JobEventType.Waiting, {
|
|
260
|
-
waitUntil: options.waitUntil?.toISOString() ?? null,
|
|
261
|
-
waitTokenId: options.waitTokenId ?? null,
|
|
262
|
-
});
|
|
263
|
-
log(`Job ${jobId} set to waiting`);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
log(`Error setting job ${jobId} to waiting: ${error}`);
|
|
266
|
-
throw error;
|
|
267
|
-
} finally {
|
|
268
|
-
client.release();
|
|
269
|
-
}
|
|
270
|
-
};
|
|
228
|
+
): Promise<void> => new PostgresBackend(pool).waitJob(jobId, options);
|
|
271
229
|
|
|
272
|
-
/**
|
|
273
|
-
* Update step_data for a job. Called after each ctx.run() step completes
|
|
274
|
-
* to persist intermediate progress.
|
|
275
|
-
*/
|
|
230
|
+
/** @deprecated Use backend.updateStepData() directly. Delegates to PostgresBackend. */
|
|
276
231
|
export const updateStepData = async (
|
|
277
232
|
pool: Pool,
|
|
278
233
|
jobId: number,
|
|
279
234
|
stepData: Record<string, any>,
|
|
280
|
-
): Promise<void> =>
|
|
281
|
-
const client = await pool.connect();
|
|
282
|
-
try {
|
|
283
|
-
await client.query(
|
|
284
|
-
`UPDATE job_queue SET step_data = $2, updated_at = NOW() WHERE id = $1`,
|
|
285
|
-
[jobId, JSON.stringify(stepData)],
|
|
286
|
-
);
|
|
287
|
-
} catch (error) {
|
|
288
|
-
log(`Error updating step_data for job ${jobId}: ${error}`);
|
|
289
|
-
// Best-effort: do not throw to avoid killing the running handler
|
|
290
|
-
} finally {
|
|
291
|
-
client.release();
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Parse a timeout string like '10m', '1h', '24h', '7d' into milliseconds.
|
|
297
|
-
*/
|
|
298
|
-
/**
|
|
299
|
-
* Maximum allowed timeout in milliseconds (~365 days).
|
|
300
|
-
* Prevents overflow to Infinity when computing Date offsets.
|
|
301
|
-
*/
|
|
302
|
-
const MAX_TIMEOUT_MS = 365 * 24 * 60 * 60 * 1000;
|
|
235
|
+
): Promise<void> => new PostgresBackend(pool).updateStepData(jobId, stepData);
|
|
303
236
|
|
|
304
|
-
|
|
305
|
-
const match = timeout.match(/^(\d+)(s|m|h|d)$/);
|
|
306
|
-
if (!match) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
`Invalid timeout format: "${timeout}". Expected format like "10m", "1h", "24h", "7d".`,
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
const value = parseInt(match[1], 10);
|
|
312
|
-
const unit = match[2];
|
|
313
|
-
let ms: number;
|
|
314
|
-
switch (unit) {
|
|
315
|
-
case 's':
|
|
316
|
-
ms = value * 1000;
|
|
317
|
-
break;
|
|
318
|
-
case 'm':
|
|
319
|
-
ms = value * 60 * 1000;
|
|
320
|
-
break;
|
|
321
|
-
case 'h':
|
|
322
|
-
ms = value * 60 * 60 * 1000;
|
|
323
|
-
break;
|
|
324
|
-
case 'd':
|
|
325
|
-
ms = value * 24 * 60 * 60 * 1000;
|
|
326
|
-
break;
|
|
327
|
-
default:
|
|
328
|
-
throw new Error(`Unknown timeout unit: "${unit}"`);
|
|
329
|
-
}
|
|
330
|
-
if (!Number.isFinite(ms) || ms > MAX_TIMEOUT_MS) {
|
|
331
|
-
throw new Error(
|
|
332
|
-
`Timeout value "${timeout}" is too large. Maximum allowed is 365 days.`,
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
return ms;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Create a waitpoint token in the database.
|
|
340
|
-
* The token can be used to pause a job until an external signal completes it.
|
|
341
|
-
*
|
|
342
|
-
* @param pool - The database pool
|
|
343
|
-
* @param jobId - The job ID to associate with the token (null if created outside a handler)
|
|
344
|
-
* @param options - Optional timeout and tags
|
|
345
|
-
* @returns The created waitpoint token
|
|
346
|
-
*/
|
|
237
|
+
/** @deprecated Use backend.createWaitpoint() directly. Delegates to PostgresBackend. */
|
|
347
238
|
export const createWaitpoint = async (
|
|
348
239
|
pool: Pool,
|
|
349
240
|
jobId: number | null,
|
|
350
241
|
options?: { timeout?: string; tags?: string[] },
|
|
351
|
-
): Promise<{ id: string }> =>
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
const id = `wp_${randomUUID()}`;
|
|
355
|
-
let timeoutAt: Date | null = null;
|
|
356
|
-
|
|
357
|
-
if (options?.timeout) {
|
|
358
|
-
const ms = parseTimeoutString(options.timeout);
|
|
359
|
-
timeoutAt = new Date(Date.now() + ms);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
await client.query(
|
|
363
|
-
`INSERT INTO waitpoints (id, job_id, status, timeout_at, tags) VALUES ($1, $2, 'waiting', $3, $4)`,
|
|
364
|
-
[id, jobId, timeoutAt, options?.tags ?? null],
|
|
365
|
-
);
|
|
242
|
+
): Promise<{ id: string }> =>
|
|
243
|
+
new PostgresBackend(pool).createWaitpoint(jobId, options);
|
|
366
244
|
|
|
367
|
-
|
|
368
|
-
return { id };
|
|
369
|
-
} catch (error) {
|
|
370
|
-
log(`Error creating waitpoint: ${error}`);
|
|
371
|
-
throw error;
|
|
372
|
-
} finally {
|
|
373
|
-
client.release();
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Complete a waitpoint token, optionally providing output data.
|
|
379
|
-
* This also moves the associated job from 'waiting' back to 'pending' so
|
|
380
|
-
* it gets picked up by the polling loop.
|
|
381
|
-
*/
|
|
245
|
+
/** @deprecated Use backend.completeWaitpoint() directly. Delegates to PostgresBackend. */
|
|
382
246
|
export const completeWaitpoint = async (
|
|
383
247
|
pool: Pool,
|
|
384
248
|
tokenId: string,
|
|
385
249
|
data?: any,
|
|
386
|
-
): Promise<void> =>
|
|
387
|
-
const client = await pool.connect();
|
|
388
|
-
try {
|
|
389
|
-
await client.query('BEGIN');
|
|
390
|
-
|
|
391
|
-
// Update the waitpoint
|
|
392
|
-
const wpResult = await client.query(
|
|
393
|
-
`UPDATE waitpoints SET status = 'completed', output = $2, completed_at = NOW()
|
|
394
|
-
WHERE id = $1 AND status = 'waiting'
|
|
395
|
-
RETURNING job_id`,
|
|
396
|
-
[tokenId, data != null ? JSON.stringify(data) : null],
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
if (wpResult.rows.length === 0) {
|
|
400
|
-
await client.query('ROLLBACK');
|
|
401
|
-
log(`Waitpoint ${tokenId} not found or already completed`);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const jobId = wpResult.rows[0].job_id;
|
|
406
|
-
|
|
407
|
-
// Move the associated job back to 'pending' so it gets picked up
|
|
408
|
-
if (jobId != null) {
|
|
409
|
-
await client.query(
|
|
410
|
-
`UPDATE job_queue
|
|
411
|
-
SET status = 'pending', wait_token_id = NULL, wait_until = NULL, updated_at = NOW()
|
|
412
|
-
WHERE id = $1 AND status = 'waiting'`,
|
|
413
|
-
[jobId],
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
await client.query('COMMIT');
|
|
418
|
-
log(`Completed waitpoint ${tokenId} for job ${jobId}`);
|
|
419
|
-
} catch (error) {
|
|
420
|
-
await client.query('ROLLBACK');
|
|
421
|
-
log(`Error completing waitpoint ${tokenId}: ${error}`);
|
|
422
|
-
throw error;
|
|
423
|
-
} finally {
|
|
424
|
-
client.release();
|
|
425
|
-
}
|
|
426
|
-
};
|
|
250
|
+
): Promise<void> => new PostgresBackend(pool).completeWaitpoint(tokenId, data);
|
|
427
251
|
|
|
428
|
-
/**
|
|
429
|
-
* Retrieve a waitpoint token by its ID.
|
|
430
|
-
*/
|
|
252
|
+
/** @deprecated Use backend.getWaitpoint() directly. Delegates to PostgresBackend. */
|
|
431
253
|
export const getWaitpoint = async (
|
|
432
254
|
pool: Pool,
|
|
433
255
|
tokenId: string,
|
|
434
|
-
): Promise<WaitpointRecord | null> =>
|
|
435
|
-
|
|
436
|
-
try {
|
|
437
|
-
const result = await client.query(
|
|
438
|
-
`SELECT id, job_id AS "jobId", status, output, timeout_at AS "timeoutAt", created_at AS "createdAt", completed_at AS "completedAt", tags FROM waitpoints WHERE id = $1`,
|
|
439
|
-
[tokenId],
|
|
440
|
-
);
|
|
441
|
-
if (result.rows.length === 0) return null;
|
|
442
|
-
return result.rows[0] as WaitpointRecord;
|
|
443
|
-
} catch (error) {
|
|
444
|
-
log(`Error getting waitpoint ${tokenId}: ${error}`);
|
|
445
|
-
throw error;
|
|
446
|
-
} finally {
|
|
447
|
-
client.release();
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Expire timed-out waitpoint tokens and move their associated jobs back to 'pending'.
|
|
453
|
-
* Should be called periodically (e.g., alongside reclaimStuckJobs).
|
|
454
|
-
*/
|
|
455
|
-
export const expireTimedOutWaitpoints = async (pool: Pool): Promise<number> => {
|
|
456
|
-
const client = await pool.connect();
|
|
457
|
-
try {
|
|
458
|
-
await client.query('BEGIN');
|
|
459
|
-
|
|
460
|
-
// Find and expire timed-out waitpoints
|
|
461
|
-
const result = await client.query(
|
|
462
|
-
`UPDATE waitpoints
|
|
463
|
-
SET status = 'timed_out'
|
|
464
|
-
WHERE status = 'waiting' AND timeout_at IS NOT NULL AND timeout_at <= NOW()
|
|
465
|
-
RETURNING id, job_id`,
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
// Move associated jobs back to 'pending'
|
|
469
|
-
for (const row of result.rows) {
|
|
470
|
-
if (row.job_id != null) {
|
|
471
|
-
await client.query(
|
|
472
|
-
`UPDATE job_queue
|
|
473
|
-
SET status = 'pending', wait_token_id = NULL, wait_until = NULL, updated_at = NOW()
|
|
474
|
-
WHERE id = $1 AND status = 'waiting'`,
|
|
475
|
-
[row.job_id],
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
256
|
+
): Promise<WaitpointRecord | null> =>
|
|
257
|
+
new PostgresBackend(pool).getWaitpoint(tokenId);
|
|
479
258
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
log(`Expired ${count} timed-out waitpoints`);
|
|
484
|
-
}
|
|
485
|
-
return count;
|
|
486
|
-
} catch (error) {
|
|
487
|
-
await client.query('ROLLBACK');
|
|
488
|
-
log(`Error expiring timed-out waitpoints: ${error}`);
|
|
489
|
-
throw error;
|
|
490
|
-
} finally {
|
|
491
|
-
client.release();
|
|
492
|
-
}
|
|
493
|
-
};
|
|
259
|
+
/** @deprecated Use backend.expireTimedOutWaitpoints() directly. Delegates to PostgresBackend. */
|
|
260
|
+
export const expireTimedOutWaitpoints = async (pool: Pool): Promise<number> =>
|
|
261
|
+
new PostgresBackend(pool).expireTimedOutWaitpoints();
|
package/src/types.ts
CHANGED
|
@@ -485,6 +485,23 @@ export interface PostgresJobQueueConfig {
|
|
|
485
485
|
user?: string;
|
|
486
486
|
password?: string;
|
|
487
487
|
ssl?: DatabaseSSLConfig;
|
|
488
|
+
/**
|
|
489
|
+
* Maximum number of clients in the pool (default: 10).
|
|
490
|
+
* Increase when running multiple processors in the same process.
|
|
491
|
+
*/
|
|
492
|
+
max?: number;
|
|
493
|
+
/**
|
|
494
|
+
* Minimum number of idle clients in the pool (default: 0).
|
|
495
|
+
*/
|
|
496
|
+
min?: number;
|
|
497
|
+
/**
|
|
498
|
+
* Milliseconds a client must sit idle before being closed (default: 10000).
|
|
499
|
+
*/
|
|
500
|
+
idleTimeoutMillis?: number;
|
|
501
|
+
/**
|
|
502
|
+
* Milliseconds to wait for a connection before throwing (default: 0, no timeout).
|
|
503
|
+
*/
|
|
504
|
+
connectionTimeoutMillis?: number;
|
|
488
505
|
};
|
|
489
506
|
verbose?: boolean;
|
|
490
507
|
}
|
|
@@ -533,6 +550,90 @@ export type JobQueueConfigLegacy = PostgresJobQueueConfig;
|
|
|
533
550
|
|
|
534
551
|
export type TagQueryMode = 'exact' | 'all' | 'any' | 'none';
|
|
535
552
|
|
|
553
|
+
// ── Cron schedule types ──────────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Status of a cron schedule.
|
|
557
|
+
*/
|
|
558
|
+
export type CronScheduleStatus = 'active' | 'paused';
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Options for creating a recurring cron schedule.
|
|
562
|
+
* Each schedule defines a recurring job that is automatically enqueued
|
|
563
|
+
* when its cron expression matches.
|
|
564
|
+
*/
|
|
565
|
+
export interface CronScheduleOptions<
|
|
566
|
+
PayloadMap,
|
|
567
|
+
T extends JobType<PayloadMap>,
|
|
568
|
+
> {
|
|
569
|
+
/** Unique human-readable name for the schedule. */
|
|
570
|
+
scheduleName: string;
|
|
571
|
+
/** Standard cron expression (5 fields, e.g. "0 * * * *"). */
|
|
572
|
+
cronExpression: string;
|
|
573
|
+
/** Job type from the PayloadMap. */
|
|
574
|
+
jobType: T;
|
|
575
|
+
/** Payload for each job instance. */
|
|
576
|
+
payload: PayloadMap[T];
|
|
577
|
+
/** Maximum retry attempts for each job instance (default: 3). */
|
|
578
|
+
maxAttempts?: number;
|
|
579
|
+
/** Priority for each job instance (default: 0). */
|
|
580
|
+
priority?: number;
|
|
581
|
+
/** Timeout in milliseconds for each job instance. */
|
|
582
|
+
timeoutMs?: number;
|
|
583
|
+
/** Whether to force-kill the job on timeout (default: false). */
|
|
584
|
+
forceKillOnTimeout?: boolean;
|
|
585
|
+
/** Tags for each job instance. */
|
|
586
|
+
tags?: string[];
|
|
587
|
+
/** IANA timezone string for cron evaluation (default: "UTC"). */
|
|
588
|
+
timezone?: string;
|
|
589
|
+
/**
|
|
590
|
+
* Whether to allow overlapping job instances (default: false).
|
|
591
|
+
* When false, a new job will not be enqueued if the previous instance
|
|
592
|
+
* is still pending, processing, or waiting.
|
|
593
|
+
*/
|
|
594
|
+
allowOverlap?: boolean;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* A persisted cron schedule record.
|
|
599
|
+
*/
|
|
600
|
+
export interface CronScheduleRecord {
|
|
601
|
+
id: number;
|
|
602
|
+
scheduleName: string;
|
|
603
|
+
cronExpression: string;
|
|
604
|
+
jobType: string;
|
|
605
|
+
payload: any;
|
|
606
|
+
maxAttempts: number;
|
|
607
|
+
priority: number;
|
|
608
|
+
timeoutMs: number | null;
|
|
609
|
+
forceKillOnTimeout: boolean;
|
|
610
|
+
tags: string[] | undefined;
|
|
611
|
+
timezone: string;
|
|
612
|
+
allowOverlap: boolean;
|
|
613
|
+
status: CronScheduleStatus;
|
|
614
|
+
lastEnqueuedAt: Date | null;
|
|
615
|
+
lastJobId: number | null;
|
|
616
|
+
nextRunAt: Date | null;
|
|
617
|
+
createdAt: Date;
|
|
618
|
+
updatedAt: Date;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Options for editing an existing cron schedule.
|
|
623
|
+
* All fields are optional; only provided fields are updated.
|
|
624
|
+
*/
|
|
625
|
+
export interface EditCronScheduleOptions {
|
|
626
|
+
cronExpression?: string;
|
|
627
|
+
payload?: any;
|
|
628
|
+
maxAttempts?: number;
|
|
629
|
+
priority?: number;
|
|
630
|
+
timeoutMs?: number | null;
|
|
631
|
+
forceKillOnTimeout?: boolean;
|
|
632
|
+
tags?: string[] | null;
|
|
633
|
+
timezone?: string;
|
|
634
|
+
allowOverlap?: boolean;
|
|
635
|
+
}
|
|
636
|
+
|
|
536
637
|
export interface JobQueue<PayloadMap> {
|
|
537
638
|
/**
|
|
538
639
|
* Add a job to the job queue.
|
|
@@ -606,12 +707,21 @@ export interface JobQueue<PayloadMap> {
|
|
|
606
707
|
retryJob: (jobId: number) => Promise<void>;
|
|
607
708
|
/**
|
|
608
709
|
* Cleanup jobs that are older than the specified number of days.
|
|
710
|
+
* Deletes in batches for scale safety.
|
|
711
|
+
* @param daysToKeep - Number of days to retain completed jobs (default 30).
|
|
712
|
+
* @param batchSize - Number of rows to delete per batch (default 1000 for PostgreSQL, 200 for Redis).
|
|
609
713
|
*/
|
|
610
|
-
cleanupOldJobs: (daysToKeep?: number) => Promise<number>;
|
|
714
|
+
cleanupOldJobs: (daysToKeep?: number, batchSize?: number) => Promise<number>;
|
|
611
715
|
/**
|
|
612
716
|
* Cleanup job events that are older than the specified number of days.
|
|
717
|
+
* Deletes in batches for scale safety.
|
|
718
|
+
* @param daysToKeep - Number of days to retain events (default 30).
|
|
719
|
+
* @param batchSize - Number of rows to delete per batch (default 1000).
|
|
613
720
|
*/
|
|
614
|
-
cleanupOldJobEvents: (
|
|
721
|
+
cleanupOldJobEvents: (
|
|
722
|
+
daysToKeep?: number,
|
|
723
|
+
batchSize?: number,
|
|
724
|
+
) => Promise<number>;
|
|
615
725
|
/**
|
|
616
726
|
* Cancel a job given its ID.
|
|
617
727
|
* - This will set the job status to 'cancelled' and clear the locked_at and locked_by.
|
|
@@ -695,8 +805,6 @@ export interface JobQueue<PayloadMap> {
|
|
|
695
805
|
* Tokens can be completed externally to resume a waiting job.
|
|
696
806
|
* Can be called outside of handlers (e.g., from an API route).
|
|
697
807
|
*
|
|
698
|
-
* **PostgreSQL backend only.** Throws if the backend is Redis.
|
|
699
|
-
*
|
|
700
808
|
* @param options - Optional token configuration (timeout, tags).
|
|
701
809
|
* @returns A token object with `id`.
|
|
702
810
|
*/
|
|
@@ -706,8 +814,6 @@ export interface JobQueue<PayloadMap> {
|
|
|
706
814
|
* Complete a waitpoint token, resuming the associated waiting job.
|
|
707
815
|
* Can be called from anywhere (API routes, external services, etc.).
|
|
708
816
|
*
|
|
709
|
-
* **PostgreSQL backend only.** Throws if the backend is Redis.
|
|
710
|
-
*
|
|
711
817
|
* @param tokenId - The ID of the token to complete.
|
|
712
818
|
* @param data - Optional data to pass to the waiting handler.
|
|
713
819
|
*/
|
|
@@ -716,8 +822,6 @@ export interface JobQueue<PayloadMap> {
|
|
|
716
822
|
/**
|
|
717
823
|
* Retrieve a waitpoint token by its ID.
|
|
718
824
|
*
|
|
719
|
-
* **PostgreSQL backend only.** Throws if the backend is Redis.
|
|
720
|
-
*
|
|
721
825
|
* @param tokenId - The ID of the token to retrieve.
|
|
722
826
|
* @returns The token record, or null if not found.
|
|
723
827
|
*/
|
|
@@ -727,12 +831,75 @@ export interface JobQueue<PayloadMap> {
|
|
|
727
831
|
* Expire timed-out waitpoint tokens and resume their associated jobs.
|
|
728
832
|
* Call this periodically (e.g., alongside `reclaimStuckJobs`).
|
|
729
833
|
*
|
|
730
|
-
* **PostgreSQL backend only.** Throws if the backend is Redis.
|
|
731
|
-
*
|
|
732
834
|
* @returns The number of tokens that were expired.
|
|
733
835
|
*/
|
|
734
836
|
expireTimedOutTokens: () => Promise<number>;
|
|
735
837
|
|
|
838
|
+
// ── Cron schedule operations ────────────────────────────────────────
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Add a recurring cron schedule. The processor automatically enqueues
|
|
842
|
+
* due cron jobs before each batch, so no manual triggering is needed.
|
|
843
|
+
*
|
|
844
|
+
* @returns The ID of the created schedule.
|
|
845
|
+
* @throws If the cron expression is invalid or the schedule name is already taken.
|
|
846
|
+
*/
|
|
847
|
+
addCronJob: <T extends JobType<PayloadMap>>(
|
|
848
|
+
options: CronScheduleOptions<PayloadMap, T>,
|
|
849
|
+
) => Promise<number>;
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Get a cron schedule by its ID.
|
|
853
|
+
*/
|
|
854
|
+
getCronJob: (id: number) => Promise<CronScheduleRecord | null>;
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Get a cron schedule by its unique name.
|
|
858
|
+
*/
|
|
859
|
+
getCronJobByName: (name: string) => Promise<CronScheduleRecord | null>;
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* List all cron schedules, optionally filtered by status.
|
|
863
|
+
*/
|
|
864
|
+
listCronJobs: (status?: CronScheduleStatus) => Promise<CronScheduleRecord[]>;
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Remove a cron schedule by its ID. Does not cancel any already-enqueued jobs.
|
|
868
|
+
*/
|
|
869
|
+
removeCronJob: (id: number) => Promise<void>;
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Pause a cron schedule. Paused schedules are skipped by `enqueueDueCronJobs()`.
|
|
873
|
+
*/
|
|
874
|
+
pauseCronJob: (id: number) => Promise<void>;
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Resume a paused cron schedule.
|
|
878
|
+
*/
|
|
879
|
+
resumeCronJob: (id: number) => Promise<void>;
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Edit an existing cron schedule. Only provided fields are updated.
|
|
883
|
+
* If `cronExpression` or `timezone` changes, `nextRunAt` is recalculated.
|
|
884
|
+
*/
|
|
885
|
+
editCronJob: (id: number, updates: EditCronScheduleOptions) => Promise<void>;
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* Check all active cron schedules and enqueue jobs for any whose
|
|
889
|
+
* `nextRunAt` has passed. When `allowOverlap` is false (the default),
|
|
890
|
+
* a new job is not enqueued if the previous instance is still
|
|
891
|
+
* pending, processing, or waiting.
|
|
892
|
+
*
|
|
893
|
+
* **Note:** The processor calls this automatically before each batch,
|
|
894
|
+
* so you typically do not need to call it yourself. It is exposed for
|
|
895
|
+
* manual use in tests or one-off scripts.
|
|
896
|
+
*
|
|
897
|
+
* @returns The number of jobs that were enqueued.
|
|
898
|
+
*/
|
|
899
|
+
enqueueDueCronJobs: () => Promise<number>;
|
|
900
|
+
|
|
901
|
+
// ── Advanced access ───────────────────────────────────────────────────
|
|
902
|
+
|
|
736
903
|
/**
|
|
737
904
|
* Get the PostgreSQL database pool.
|
|
738
905
|
* Throws if the backend is not PostgreSQL.
|