@nicnocquee/dataqueue 1.31.0 → 1.33.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/src/processor.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Worker } from 'worker_threads';
2
- import { Pool } from 'pg';
3
2
  import {
4
3
  JobRecord,
5
4
  ProcessorOptions,
@@ -15,69 +14,8 @@ import {
15
14
  WaitTokenResult,
16
15
  } from './types.js';
17
16
  import { QueueBackend } from './backend.js';
18
- import { PostgresBackend } from './backends/postgres.js';
19
- import {
20
- waitJob,
21
- updateStepData,
22
- createWaitpoint,
23
- getWaitpoint,
24
- } from './queue.js';
25
17
  import { log, setLogContext } from './log-context.js';
26
18
 
27
- /**
28
- * Try to extract the underlying pg Pool from a QueueBackend.
29
- * Returns null for non-PostgreSQL backends.
30
- */
31
- function tryExtractPool(backend: QueueBackend): Pool | null {
32
- if (backend instanceof PostgresBackend) {
33
- return backend.getPool();
34
- }
35
- return null;
36
- }
37
-
38
- /**
39
- * Build a JobContext without wait support (for non-PostgreSQL backends).
40
- * prolong/onTimeout work normally; wait-related methods throw helpful errors.
41
- */
42
- function buildBasicContext(
43
- backend: QueueBackend,
44
- jobId: number,
45
- baseCtx: {
46
- prolong: JobContext['prolong'];
47
- onTimeout: JobContext['onTimeout'];
48
- },
49
- ): JobContext {
50
- const waitError = () =>
51
- new Error(
52
- 'Wait features (waitFor, waitUntil, createToken, waitForToken, ctx.run) are currently only supported with the PostgreSQL backend.',
53
- );
54
- return {
55
- prolong: baseCtx.prolong,
56
- onTimeout: baseCtx.onTimeout,
57
- run: async <T>(_stepName: string, fn: () => Promise<T>): Promise<T> => {
58
- // Without PostgreSQL, just execute the function directly (no persistence)
59
- return fn();
60
- },
61
- waitFor: async () => {
62
- throw waitError();
63
- },
64
- waitUntil: async () => {
65
- throw waitError();
66
- },
67
- createToken: async () => {
68
- throw waitError();
69
- },
70
- waitForToken: async () => {
71
- throw waitError();
72
- },
73
- setProgress: async (percent: number) => {
74
- if (percent < 0 || percent > 100)
75
- throw new Error('Progress must be between 0 and 100');
76
- await backend.updateProgress(jobId, Math.round(percent));
77
- },
78
- };
79
- }
80
-
81
19
  /**
82
20
  * Validates that a handler can be serialized for worker thread execution.
83
21
  * Throws an error with helpful message if serialization fails.
@@ -388,7 +326,7 @@ function createNoOpContext(
388
326
  * Marks pending waits as completed and fetches token outputs.
389
327
  */
390
328
  async function resolveCompletedWaits(
391
- pool: Pool,
329
+ backend: QueueBackend,
392
330
  stepData: Record<string, any>,
393
331
  ): Promise<void> {
394
332
  for (const key of Object.keys(stepData)) {
@@ -401,7 +339,7 @@ async function resolveCompletedWaits(
401
339
  stepData[key] = { ...entry, completed: true };
402
340
  } else if (entry.type === 'token' && entry.tokenId) {
403
341
  // Token-based wait -- fetch the waitpoint result
404
- const wp = await getWaitpoint(pool, entry.tokenId);
342
+ const wp = await backend.getWaitpoint(entry.tokenId);
405
343
  if (wp && wp.status === 'completed') {
406
344
  stepData[key] = {
407
345
  ...entry,
@@ -422,10 +360,10 @@ async function resolveCompletedWaits(
422
360
 
423
361
  /**
424
362
  * Build the extended JobContext with step tracking and wait support.
363
+ * Works with any QueueBackend (Postgres or Redis).
425
364
  */
426
365
  function buildWaitContext(
427
366
  backend: QueueBackend,
428
- pool: Pool,
429
367
  jobId: number,
430
368
  stepData: Record<string, any>,
431
369
  baseCtx: {
@@ -455,7 +393,7 @@ function buildWaitContext(
455
393
 
456
394
  // Persist step result
457
395
  stepData[stepName] = { __completed: true, result };
458
- await updateStepData(pool, jobId, stepData);
396
+ await backend.updateStepData(jobId, stepData);
459
397
 
460
398
  return result;
461
399
  },
@@ -498,7 +436,7 @@ function buildWaitContext(
498
436
  },
499
437
 
500
438
  createToken: async (options?) => {
501
- const token = await createWaitpoint(pool, jobId, options);
439
+ const token = await backend.createWaitpoint(jobId, options);
502
440
  return token;
503
441
  },
504
442
 
@@ -517,7 +455,7 @@ function buildWaitContext(
517
455
  }
518
456
 
519
457
  // Check if the token is already completed (e.g., completed while job was still processing)
520
- const wp = await getWaitpoint(pool, tokenId);
458
+ const wp = await backend.getWaitpoint(tokenId);
521
459
  if (wp && wp.status === 'completed') {
522
460
  const result: WaitTokenResult<T> = {
523
461
  ok: true,
@@ -529,7 +467,7 @@ function buildWaitContext(
529
467
  completed: true,
530
468
  result,
531
469
  };
532
- await updateStepData(pool, jobId, stepData);
470
+ await backend.updateStepData(jobId, stepData);
533
471
  return result;
534
472
  }
535
473
  if (wp && wp.status === 'timed_out') {
@@ -543,7 +481,7 @@ function buildWaitContext(
543
481
  completed: true,
544
482
  result,
545
483
  };
546
- await updateStepData(pool, jobId, stepData);
484
+ await backend.updateStepData(jobId, stepData);
547
485
  return result;
548
486
  }
549
487
 
@@ -591,17 +529,14 @@ export async function processJobWithHandlers<
591
529
  // Load step data (may contain completed steps from previous invocations)
592
530
  const stepData: Record<string, any> = { ...(job.stepData || {}) };
593
531
 
594
- // Try to get pool for wait features (PostgreSQL-only)
595
- const pool = tryExtractPool(backend);
596
-
597
532
  // If resuming from a wait, resolve any pending wait entries
598
533
  const hasStepHistory = Object.keys(stepData).some((k) =>
599
534
  k.startsWith('__wait_'),
600
535
  );
601
- if (hasStepHistory && pool) {
602
- await resolveCompletedWaits(pool, stepData);
536
+ if (hasStepHistory) {
537
+ await resolveCompletedWaits(backend, stepData);
603
538
  // Persist the resolved step data
604
- await updateStepData(pool, job.id, stepData);
539
+ await backend.updateStepData(job.id, stepData);
605
540
  }
606
541
 
607
542
  // Per-job timeout logic
@@ -685,10 +620,8 @@ export async function processJobWithHandlers<
685
620
  },
686
621
  };
687
622
 
688
- // Build context: full wait support for PostgreSQL, basic for others
689
- const ctx = pool
690
- ? buildWaitContext(backend, pool, job.id, stepData, baseCtx)
691
- : buildBasicContext(backend, job.id, baseCtx);
623
+ // Build context: full wait support for all backends
624
+ const ctx = buildWaitContext(backend, job.id, stepData, baseCtx);
692
625
 
693
626
  // If forceKillOnTimeout was set but timeoutMs was missing, warn
694
627
  if (forceKillOnTimeout && !hasTimeout) {
@@ -720,22 +653,10 @@ export async function processJobWithHandlers<
720
653
 
721
654
  // Check if this is a WaitSignal (not a real error)
722
655
  if (error instanceof WaitSignal) {
723
- if (!pool) {
724
- // Wait signals should never happen with non-PostgreSQL backends
725
- // since the context methods throw, but guard just in case
726
- await backend.failJob(
727
- job.id,
728
- new Error(
729
- 'WaitSignal received but wait features require the PostgreSQL backend.',
730
- ),
731
- FailureReason.HandlerError,
732
- );
733
- return;
734
- }
735
656
  log(
736
657
  `Job ${job.id} entering wait: type=${error.type}, waitUntil=${error.waitUntil?.toISOString() ?? 'none'}, tokenId=${error.tokenId ?? 'none'}`,
737
658
  );
738
- await waitJob(pool, job.id, {
659
+ await backend.waitJob(job.id, {
739
660
  waitUntil: error.waitUntil,
740
661
  waitTokenId: error.tokenId,
741
662
  stepData: error.stepData,
package/src/queue.test.ts CHANGED
@@ -141,6 +141,35 @@ describe('queue integration', () => {
141
141
  expect(job).toBeNull();
142
142
  });
143
143
 
144
+ it('should cleanup old completed jobs in batches', async () => {
145
+ // Add and complete 5 jobs
146
+ const ids: number[] = [];
147
+ for (let i = 0; i < 5; i++) {
148
+ const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(
149
+ pool,
150
+ {
151
+ jobType: 'email',
152
+ payload: { to: `batch-${i}@example.com` },
153
+ },
154
+ );
155
+ await queue.getNextBatch(pool, 'worker-batch-cleanup', 1);
156
+ await queue.completeJob(pool, jobId);
157
+ ids.push(jobId);
158
+ }
159
+ // Manually backdate all 5
160
+ await pool.query(
161
+ `UPDATE job_queue SET updated_at = NOW() - INTERVAL '31 days' WHERE id = ANY($1::int[])`,
162
+ [ids],
163
+ );
164
+ // Cleanup with batchSize=2 so it takes multiple iterations
165
+ const deleted = await queue.cleanupOldJobs(pool, 30, 2);
166
+ expect(deleted).toBe(5);
167
+ for (const id of ids) {
168
+ const job = await queue.getJob(pool, id);
169
+ expect(job).toBeNull();
170
+ }
171
+ });
172
+
144
173
  it('should cancel a scheduled job', async () => {
145
174
  const jobId = await queue.addJob<{ email: { to: string } }, 'email'>(pool, {
146
175
  jobType: 'email',
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
- ): Promise<number> => new PostgresBackend(pool).cleanupOldJobs(daysToKeep);
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 (PostgreSQL-only) ─────────────────────────────────
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
- function parseTimeoutString(timeout: string): number {
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
- const client = await pool.connect();
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
- log(`Created waitpoint ${id} for job ${jobId}`);
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
- const client = await pool.connect();
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
- await client.query('COMMIT');
481
- const count = result.rowCount || 0;
482
- if (count > 0) {
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
  }
@@ -690,12 +707,21 @@ export interface JobQueue<PayloadMap> {
690
707
  retryJob: (jobId: number) => Promise<void>;
691
708
  /**
692
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).
693
713
  */
694
- cleanupOldJobs: (daysToKeep?: number) => Promise<number>;
714
+ cleanupOldJobs: (daysToKeep?: number, batchSize?: number) => Promise<number>;
695
715
  /**
696
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).
697
720
  */
698
- cleanupOldJobEvents: (daysToKeep?: number) => Promise<number>;
721
+ cleanupOldJobEvents: (
722
+ daysToKeep?: number,
723
+ batchSize?: number,
724
+ ) => Promise<number>;
699
725
  /**
700
726
  * Cancel a job given its ID.
701
727
  * - This will set the job status to 'cancelled' and clear the locked_at and locked_by.
@@ -779,8 +805,6 @@ export interface JobQueue<PayloadMap> {
779
805
  * Tokens can be completed externally to resume a waiting job.
780
806
  * Can be called outside of handlers (e.g., from an API route).
781
807
  *
782
- * **PostgreSQL backend only.** Throws if the backend is Redis.
783
- *
784
808
  * @param options - Optional token configuration (timeout, tags).
785
809
  * @returns A token object with `id`.
786
810
  */
@@ -790,8 +814,6 @@ export interface JobQueue<PayloadMap> {
790
814
  * Complete a waitpoint token, resuming the associated waiting job.
791
815
  * Can be called from anywhere (API routes, external services, etc.).
792
816
  *
793
- * **PostgreSQL backend only.** Throws if the backend is Redis.
794
- *
795
817
  * @param tokenId - The ID of the token to complete.
796
818
  * @param data - Optional data to pass to the waiting handler.
797
819
  */
@@ -800,8 +822,6 @@ export interface JobQueue<PayloadMap> {
800
822
  /**
801
823
  * Retrieve a waitpoint token by its ID.
802
824
  *
803
- * **PostgreSQL backend only.** Throws if the backend is Redis.
804
- *
805
825
  * @param tokenId - The ID of the token to retrieve.
806
826
  * @returns The token record, or null if not found.
807
827
  */
@@ -811,8 +831,6 @@ export interface JobQueue<PayloadMap> {
811
831
  * Expire timed-out waitpoint tokens and resume their associated jobs.
812
832
  * Call this periodically (e.g., alongside `reclaimStuckJobs`).
813
833
  *
814
- * **PostgreSQL backend only.** Throws if the backend is Redis.
815
- *
816
834
  * @returns The number of tokens that were expired.
817
835
  */
818
836
  expireTimedOutTokens: () => Promise<number>;