@boringnode/queue 0.3.3 → 0.4.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/README.md +79 -38
- package/build/index.d.ts +25 -1
- package/build/index.js +77 -2
- package/build/index.js.map +1 -1
- package/build/src/drivers/knex_adapter.js +0 -77
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/package.json +9 -10
package/README.md
CHANGED
|
@@ -123,12 +123,10 @@ Organize related jobs together for monitoring and filtering:
|
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
125
|
// Group newsletter jobs
|
|
126
|
-
await SendEmailJob.dispatch({ to: 'user@example.com' })
|
|
127
|
-
.group('newsletter-jan-2025')
|
|
126
|
+
await SendEmailJob.dispatch({ to: 'user@example.com' }).group('newsletter-jan-2025')
|
|
128
127
|
|
|
129
128
|
// Group with bulk dispatch
|
|
130
|
-
await SendEmailJob.dispatchMany(recipients)
|
|
131
|
-
.group('newsletter-jan-2025')
|
|
129
|
+
await SendEmailJob.dispatchMany(recipients).group('newsletter-jan-2025')
|
|
132
130
|
```
|
|
133
131
|
|
|
134
132
|
The `groupId` is stored with job data and accessible via `job.data.groupId`.
|
|
@@ -153,7 +151,7 @@ export default class ImportantJob extends Job<Payload> {
|
|
|
153
151
|
<summary><strong>Retention options</strong></summary>
|
|
154
152
|
|
|
155
153
|
| Value | Behavior |
|
|
156
|
-
|
|
154
|
+
| --------------------------- | ------------------ |
|
|
157
155
|
| `true` (default) | Remove immediately |
|
|
158
156
|
| `false` | Keep forever |
|
|
159
157
|
| `{ count: n }` | Keep last n jobs |
|
|
@@ -164,9 +162,9 @@ Query job history:
|
|
|
164
162
|
|
|
165
163
|
```typescript
|
|
166
164
|
const job = await adapter.getJob('job-id', 'queue-name')
|
|
167
|
-
console.log(job.status)
|
|
168
|
-
console.log(job.finishedAt)
|
|
169
|
-
console.log(job.error)
|
|
165
|
+
console.log(job.status) // 'completed' | 'failed'
|
|
166
|
+
console.log(job.finishedAt) // timestamp
|
|
167
|
+
console.log(job.error) // error message (if failed)
|
|
170
168
|
```
|
|
171
169
|
|
|
172
170
|
</details>
|
|
@@ -211,7 +209,50 @@ const adapter = knex(connection)
|
|
|
211
209
|
const adapter = knex(config, 'custom_jobs_table')
|
|
212
210
|
```
|
|
213
211
|
|
|
214
|
-
|
|
212
|
+
</details>
|
|
213
|
+
|
|
214
|
+
<details>
|
|
215
|
+
<summary><strong>Database setup with QueueSchemaService</strong></summary>
|
|
216
|
+
|
|
217
|
+
The Knex adapter requires tables to be created before use. Use `QueueSchemaService` to create them:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { QueueSchemaService } from '@boringnode/queue'
|
|
221
|
+
import Knex from 'knex'
|
|
222
|
+
|
|
223
|
+
const connection = Knex({ client: 'pg', connection: '...' })
|
|
224
|
+
const schemaService = new QueueSchemaService(connection)
|
|
225
|
+
|
|
226
|
+
// Create tables with default names
|
|
227
|
+
await schemaService.createJobsTable()
|
|
228
|
+
await schemaService.createSchedulesTable()
|
|
229
|
+
|
|
230
|
+
// Or extend with custom columns
|
|
231
|
+
await schemaService.createJobsTable('queue_jobs', (table) => {
|
|
232
|
+
table.string('tenant_id', 255).nullable()
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**AdonisJS migration example:**
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { BaseSchema } from '@adonisjs/lucid/schema'
|
|
240
|
+
import { QueueSchemaService } from '@boringnode/queue'
|
|
241
|
+
|
|
242
|
+
export default class extends BaseSchema {
|
|
243
|
+
async up() {
|
|
244
|
+
const schemaService = new QueueSchemaService(this.db.connection().getWriteClient())
|
|
245
|
+
await schemaService.createJobsTable()
|
|
246
|
+
await schemaService.createSchedulesTable()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async down() {
|
|
250
|
+
const schemaService = new QueueSchemaService(this.db.connection().getWriteClient())
|
|
251
|
+
await schemaService.dropSchedulesTable()
|
|
252
|
+
await schemaService.dropJobsTable()
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
215
256
|
|
|
216
257
|
</details>
|
|
217
258
|
|
|
@@ -256,13 +297,13 @@ const adapter = sync() // Jobs execute immediately
|
|
|
256
297
|
```typescript
|
|
257
298
|
export default class MyJob extends Job<Payload> {
|
|
258
299
|
static options: JobOptions = {
|
|
259
|
-
queue: 'email',
|
|
260
|
-
priority: 1,
|
|
261
|
-
maxRetries: 3,
|
|
262
|
-
timeout: '30s',
|
|
263
|
-
failOnTimeout: true,
|
|
264
|
-
removeOnComplete: { count: 100 },
|
|
265
|
-
removeOnFail: { age: '7d' },
|
|
300
|
+
queue: 'email', // Queue name (default: 'default')
|
|
301
|
+
priority: 1, // Lower = higher priority (default: 5)
|
|
302
|
+
maxRetries: 3, // Retry attempts before failing
|
|
303
|
+
timeout: '30s', // Max execution time
|
|
304
|
+
failOnTimeout: true, // Fail permanently on timeout (default: retry)
|
|
305
|
+
removeOnComplete: { count: 100 }, // Keep last 100 completed
|
|
306
|
+
removeOnFail: { age: '7d' }, // Keep failed for 7 days
|
|
266
307
|
}
|
|
267
308
|
}
|
|
268
309
|
```
|
|
@@ -270,10 +311,10 @@ export default class MyJob extends Job<Payload> {
|
|
|
270
311
|
## Delayed Jobs
|
|
271
312
|
|
|
272
313
|
```typescript
|
|
273
|
-
await SendEmailJob.dispatch(payload).in('30s')
|
|
274
|
-
await SendEmailJob.dispatch(payload).in('5m')
|
|
275
|
-
await SendEmailJob.dispatch(payload).in('2h')
|
|
276
|
-
await SendEmailJob.dispatch(payload).in('1d')
|
|
314
|
+
await SendEmailJob.dispatch(payload).in('30s') // 30 seconds
|
|
315
|
+
await SendEmailJob.dispatch(payload).in('5m') // 5 minutes
|
|
316
|
+
await SendEmailJob.dispatch(payload).in('2h') // 2 hours
|
|
317
|
+
await SendEmailJob.dispatch(payload).in('1d') // 1 day
|
|
277
318
|
```
|
|
278
319
|
|
|
279
320
|
## Retry & Backoff
|
|
@@ -285,12 +326,13 @@ export default class ReliableJob extends Job<Payload> {
|
|
|
285
326
|
static options: JobOptions = {
|
|
286
327
|
maxRetries: 5,
|
|
287
328
|
retry: {
|
|
288
|
-
backoff: () =>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
329
|
+
backoff: () =>
|
|
330
|
+
exponentialBackoff({
|
|
331
|
+
baseDelay: '1s',
|
|
332
|
+
maxDelay: '1m',
|
|
333
|
+
multiplier: 2,
|
|
334
|
+
jitter: true,
|
|
335
|
+
}),
|
|
294
336
|
},
|
|
295
337
|
}
|
|
296
338
|
}
|
|
@@ -356,13 +398,12 @@ Run jobs on a recurring basis:
|
|
|
356
398
|
|
|
357
399
|
```typescript
|
|
358
400
|
// Every 10 seconds
|
|
359
|
-
await MetricsJob.schedule({ endpoint: '/health' })
|
|
360
|
-
.every('10s')
|
|
401
|
+
await MetricsJob.schedule({ endpoint: '/health' }).every('10s')
|
|
361
402
|
|
|
362
403
|
// Cron schedule
|
|
363
404
|
await CleanupJob.schedule({ days: 30 })
|
|
364
405
|
.id('daily-cleanup')
|
|
365
|
-
.cron('0 0 * * *')
|
|
406
|
+
.cron('0 0 * * *') // Midnight daily
|
|
366
407
|
.timezone('Europe/Paris')
|
|
367
408
|
```
|
|
368
409
|
|
|
@@ -376,7 +417,7 @@ import { Schedule } from '@boringnode/queue'
|
|
|
376
417
|
const schedule = await Schedule.find('daily-cleanup')
|
|
377
418
|
await schedule.pause()
|
|
378
419
|
await schedule.resume()
|
|
379
|
-
await schedule.trigger()
|
|
420
|
+
await schedule.trigger() // Run now
|
|
380
421
|
await schedule.delete()
|
|
381
422
|
|
|
382
423
|
// List schedules
|
|
@@ -387,7 +428,7 @@ const active = await Schedule.list({ status: 'active' })
|
|
|
387
428
|
**Schedule options:**
|
|
388
429
|
|
|
389
430
|
| Method | Description |
|
|
390
|
-
|
|
431
|
+
| ------------------- | --------------------------------- |
|
|
391
432
|
| `.id(string)` | Unique identifier |
|
|
392
433
|
| `.every(duration)` | Fixed interval ('5s', '1m', '1h') |
|
|
393
434
|
| `.cron(expression)` | Cron schedule |
|
|
@@ -437,13 +478,13 @@ export default class SendEmailJob extends Job<SendEmailPayload> {
|
|
|
437
478
|
```typescript
|
|
438
479
|
const config = {
|
|
439
480
|
worker: {
|
|
440
|
-
concurrency: 5,
|
|
441
|
-
idleDelay: '2s',
|
|
442
|
-
timeout: '1m',
|
|
481
|
+
concurrency: 5, // Parallel jobs
|
|
482
|
+
idleDelay: '2s', // Poll interval when idle
|
|
483
|
+
timeout: '1m', // Default job timeout
|
|
443
484
|
stalledThreshold: '30s', // When to consider job stalled
|
|
444
|
-
stalledInterval: '30s',
|
|
445
|
-
maxStalledCount: 1,
|
|
446
|
-
gracefulShutdown: true,
|
|
485
|
+
stalledInterval: '30s', // How often to check
|
|
486
|
+
maxStalledCount: 1, // Max recoveries before failing
|
|
487
|
+
gracefulShutdown: true, // Wait for jobs on SIGTERM
|
|
447
488
|
},
|
|
448
489
|
}
|
|
449
490
|
```
|
|
@@ -464,7 +505,7 @@ await QueueManager.init({
|
|
|
464
505
|
Performance comparison with BullMQ (5ms simulated work per job):
|
|
465
506
|
|
|
466
507
|
| Jobs | Concurrency | @boringnode/queue | BullMQ | Diff |
|
|
467
|
-
|
|
508
|
+
| ---- | ----------- | ----------------- | ------ | ----------- |
|
|
468
509
|
| 1000 | 5 | 1096ms | 1116ms | 1.8% faster |
|
|
469
510
|
| 1000 | 10 | 565ms | 579ms | 2.4% faster |
|
|
470
511
|
| 100K | 10 | 56.2s | 57.5s | 2.1% faster |
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Q as QueueManagerConfig, W as WorkerCycle, A as Adapter, R as RetryConfig, g as JobFactory, h as JobOptions, i as Job, a as JobClass, e as ScheduleData, j as ScheduleStatus, f as ScheduleListOptions } from './index-CoubP-c4.js';
|
|
2
2
|
export { l as JobBatchDispatcher, k as ScheduleBuilder, m as customBackoff, o as exponentialBackoff, p as fixedBackoff, n as linearBackoff } from './index-CoubP-c4.js';
|
|
3
3
|
import { FakeAdapter } from './src/drivers/fake_adapter.js';
|
|
4
|
+
import { Knex } from 'knex';
|
|
4
5
|
import * as _poppinss_utils from '@poppinss/utils';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -447,6 +448,29 @@ declare class Schedule {
|
|
|
447
448
|
trigger(): Promise<void>;
|
|
448
449
|
}
|
|
449
450
|
|
|
451
|
+
declare class QueueSchemaService {
|
|
452
|
+
#private;
|
|
453
|
+
constructor(connection: Knex);
|
|
454
|
+
/**
|
|
455
|
+
* Creates the jobs table with the default schema.
|
|
456
|
+
* The optional callback allows adding custom columns.
|
|
457
|
+
*/
|
|
458
|
+
createJobsTable(tableName?: string, extend?: (table: Knex.CreateTableBuilder) => void): Promise<void>;
|
|
459
|
+
/**
|
|
460
|
+
* Creates the schedules table with the default schema.
|
|
461
|
+
* The optional callback allows adding custom columns.
|
|
462
|
+
*/
|
|
463
|
+
createSchedulesTable(tableName?: string, extend?: (table: Knex.CreateTableBuilder) => void): Promise<void>;
|
|
464
|
+
/**
|
|
465
|
+
* Drops the jobs table if it exists.
|
|
466
|
+
*/
|
|
467
|
+
dropJobsTable(tableName?: string): Promise<void>;
|
|
468
|
+
/**
|
|
469
|
+
* Drops the schedules table if it exists.
|
|
470
|
+
*/
|
|
471
|
+
dropSchedulesTable(tableName?: string): Promise<void>;
|
|
472
|
+
}
|
|
473
|
+
|
|
450
474
|
declare const E_INVALID_DURATION_EXPRESSION: new (args?: any, options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
451
475
|
declare const E_INVALID_BASE_DELAY: new (args: [reason: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
452
476
|
declare const E_INVALID_MAX_DELAY: new (args: [reason: string], options?: ErrorOptions) => _poppinss_utils.Exception;
|
|
@@ -478,4 +502,4 @@ declare namespace exceptions {
|
|
|
478
502
|
export { exceptions_E_ADAPTER_INIT_ERROR as E_ADAPTER_INIT_ERROR, exceptions_E_CONFIGURATION_ERROR as E_CONFIGURATION_ERROR, exceptions_E_INVALID_BASE_DELAY as E_INVALID_BASE_DELAY, exceptions_E_INVALID_CRON_EXPRESSION as E_INVALID_CRON_EXPRESSION, exceptions_E_INVALID_DURATION_EXPRESSION as E_INVALID_DURATION_EXPRESSION, exceptions_E_INVALID_MAX_DELAY as E_INVALID_MAX_DELAY, exceptions_E_INVALID_MULTIPLIER as E_INVALID_MULTIPLIER, exceptions_E_INVALID_SCHEDULE_CONFIG as E_INVALID_SCHEDULE_CONFIG, exceptions_E_JOB_MAX_ATTEMPTS_REACHED as E_JOB_MAX_ATTEMPTS_REACHED, exceptions_E_JOB_NOT_FOUND as E_JOB_NOT_FOUND, exceptions_E_JOB_TIMEOUT as E_JOB_TIMEOUT, exceptions_E_NO_JOBS_FOUND as E_NO_JOBS_FOUND, exceptions_E_QUEUE_NOT_INITIALIZED as E_QUEUE_NOT_INITIALIZED };
|
|
479
503
|
}
|
|
480
504
|
|
|
481
|
-
export { Job, Locator, QueueManager, Schedule, Worker, exceptions as errors };
|
|
505
|
+
export { Job, Locator, QueueManager, QueueSchemaService, Schedule, Worker, exceptions as errors };
|
package/build/index.js
CHANGED
|
@@ -288,8 +288,15 @@ var Worker = class {
|
|
|
288
288
|
yield { type: "idle", suggestedDelay: this.#idleDelay };
|
|
289
289
|
continue;
|
|
290
290
|
}
|
|
291
|
-
const
|
|
292
|
-
|
|
291
|
+
const hasCapacity = this.#pool.hasCapacity(this.#concurrency);
|
|
292
|
+
const result = await Promise.race([
|
|
293
|
+
this.#pool.waitForNextCompletion().then((completed) => ({ kind: "completed", completed })),
|
|
294
|
+
...hasCapacity ? [setTimeout(this.#idleDelay).then(() => ({ kind: "tick" }))] : []
|
|
295
|
+
]);
|
|
296
|
+
if (result.kind === "tick") {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
yield { type: "completed", queue: result.completed.queue, job: result.completed.job };
|
|
293
300
|
} catch (error) {
|
|
294
301
|
yield {
|
|
295
302
|
type: "error",
|
|
@@ -607,6 +614,73 @@ var Schedule = class _Schedule {
|
|
|
607
614
|
}
|
|
608
615
|
};
|
|
609
616
|
|
|
617
|
+
// src/services/queue_schema.ts
|
|
618
|
+
var QueueSchemaService = class {
|
|
619
|
+
#connection;
|
|
620
|
+
constructor(connection) {
|
|
621
|
+
this.#connection = connection;
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Creates the jobs table with the default schema.
|
|
625
|
+
* The optional callback allows adding custom columns.
|
|
626
|
+
*/
|
|
627
|
+
async createJobsTable(tableName = "queue_jobs", extend) {
|
|
628
|
+
await this.#connection.schema.createTable(tableName, (table) => {
|
|
629
|
+
table.string("id", 255).notNullable();
|
|
630
|
+
table.string("queue", 255).notNullable();
|
|
631
|
+
table.enu("status", ["pending", "active", "delayed", "completed", "failed"]).notNullable();
|
|
632
|
+
table.text("data").notNullable();
|
|
633
|
+
table.bigint("score").unsigned().nullable();
|
|
634
|
+
table.string("worker_id", 255).nullable();
|
|
635
|
+
table.bigint("acquired_at").unsigned().nullable();
|
|
636
|
+
table.bigint("execute_at").unsigned().nullable();
|
|
637
|
+
table.bigint("finished_at").unsigned().nullable();
|
|
638
|
+
table.text("error").nullable();
|
|
639
|
+
table.primary(["id", "queue"]);
|
|
640
|
+
table.index(["queue", "status", "score"]);
|
|
641
|
+
table.index(["queue", "status", "execute_at"]);
|
|
642
|
+
table.index(["queue", "status", "finished_at"]);
|
|
643
|
+
extend?.(table);
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Creates the schedules table with the default schema.
|
|
648
|
+
* The optional callback allows adding custom columns.
|
|
649
|
+
*/
|
|
650
|
+
async createSchedulesTable(tableName = "queue_schedules", extend) {
|
|
651
|
+
await this.#connection.schema.createTable(tableName, (table) => {
|
|
652
|
+
table.string("id", 255).primary();
|
|
653
|
+
table.string("status", 50).notNullable().defaultTo("active");
|
|
654
|
+
table.string("name", 255).notNullable();
|
|
655
|
+
table.text("payload").notNullable();
|
|
656
|
+
table.string("cron_expression", 255).nullable();
|
|
657
|
+
table.bigint("every_ms").unsigned().nullable();
|
|
658
|
+
table.string("timezone", 100).notNullable().defaultTo("UTC");
|
|
659
|
+
table.timestamp("from_date").nullable();
|
|
660
|
+
table.timestamp("to_date").nullable();
|
|
661
|
+
table.integer("run_limit").unsigned().nullable();
|
|
662
|
+
table.integer("run_count").unsigned().notNullable().defaultTo(0);
|
|
663
|
+
table.timestamp("next_run_at").nullable();
|
|
664
|
+
table.timestamp("last_run_at").nullable();
|
|
665
|
+
table.timestamp("created_at").notNullable().defaultTo(this.#connection.fn.now());
|
|
666
|
+
table.index(["status", "next_run_at"]);
|
|
667
|
+
extend?.(table);
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Drops the jobs table if it exists.
|
|
672
|
+
*/
|
|
673
|
+
async dropJobsTable(tableName = "queue_jobs") {
|
|
674
|
+
await this.#connection.schema.dropTableIfExists(tableName);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Drops the schedules table if it exists.
|
|
678
|
+
*/
|
|
679
|
+
async dropSchedulesTable(tableName = "queue_schedules") {
|
|
680
|
+
await this.#connection.schema.dropTableIfExists(tableName);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
610
684
|
// src/strategies/backoff_strategy.ts
|
|
611
685
|
import { RuntimeException } from "@poppinss/utils";
|
|
612
686
|
import { assertUnreachable } from "@poppinss/utils/assert";
|
|
@@ -757,6 +831,7 @@ export {
|
|
|
757
831
|
JobBatchDispatcher,
|
|
758
832
|
Locator,
|
|
759
833
|
QueueManager,
|
|
834
|
+
QueueSchemaService,
|
|
760
835
|
Schedule,
|
|
761
836
|
ScheduleBuilder,
|
|
762
837
|
Worker,
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/worker.ts","../src/job_pool.ts","../src/schedule.ts","../src/strategies/backoff_strategy.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport debug from './debug.js'\nimport { parse } from './utils.js'\nimport * as errors from './exceptions.js'\nimport { QueueManager } from './queue_manager.js'\nimport { JobPool } from './job_pool.js'\nimport type { Adapter, AcquiredJob } from './contracts/adapter.js'\nimport type { JobContext, JobOptions, QueueManagerConfig, WorkerCycle } from './types/main.js'\nimport { Locator } from './locator.js'\nimport { DEFAULT_PRIORITY } from './constants.js'\nimport type { Job } from './job.js'\nimport {\n DEFAULT_IDLE_DELAY,\n DEFAULT_STALLED_INTERVAL,\n DEFAULT_STALLED_THRESHOLD,\n DEFAULT_ERROR_RETRY_DELAY,\n} from './constants.js'\n\n/**\n * Job processing worker.\n *\n * The Worker continuously polls queues for jobs and executes them\n * with configurable concurrency. It handles:\n * - Concurrent job execution via JobPool\n * - Automatic retries with backoff strategies\n * - Stalled job detection and recovery\n * - Graceful shutdown on SIGINT/SIGTERM\n *\n * @example\n * ```typescript\n * import { Worker, redis } from '@boringnode/queue'\n *\n * const worker = new Worker({\n * default: 'redis',\n * adapters: { redis: redis() },\n * locations: ['./jobs/**\\/*.js'],\n * worker: {\n * concurrency: 5,\n * idleDelay: '1s',\n * },\n * })\n *\n * // Start processing jobs\n * await worker.start(['default', 'emails'])\n *\n * // Or for testing, process one cycle at a time\n * const cycle = await worker.processCycle(['default'])\n * ```\n */\nexport class Worker {\n readonly #id: string\n readonly #config: QueueManagerConfig\n readonly #idleDelay: number\n readonly #stalledInterval: number\n readonly #stalledThreshold: number\n readonly #maxStalledCount: number\n readonly #concurrency: number\n readonly #gracefulShutdown: boolean\n readonly #onShutdownSignal?: () => void | Promise<void>\n\n #adapter!: Adapter\n #running = false\n #initialized = false\n #generator?: AsyncGenerator<WorkerCycle, void, unknown>\n #pool?: JobPool\n #lastStalledCheck = 0\n #shutdownHandler?: () => Promise<void>\n\n /** Unique identifier for this worker instance */\n get id() {\n return this.#id\n }\n\n /**\n * Create a new worker instance.\n *\n * @param config - Queue configuration including adapter and worker settings\n */\n constructor(config: QueueManagerConfig) {\n this.#config = config\n this.#id = randomUUID()\n\n // Parse worker config once at construction\n this.#idleDelay = parse(config.worker?.idleDelay ?? DEFAULT_IDLE_DELAY)\n this.#stalledInterval = parse(config.worker?.stalledInterval ?? DEFAULT_STALLED_INTERVAL)\n this.#stalledThreshold = parse(config.worker?.stalledThreshold ?? DEFAULT_STALLED_THRESHOLD)\n this.#maxStalledCount = config.worker?.maxStalledCount ?? 1\n this.#concurrency = config.worker?.concurrency ?? 1\n this.#gracefulShutdown = config.worker?.gracefulShutdown ?? true\n this.#onShutdownSignal = config.worker?.onShutdownSignal\n\n debug('created worker with id %s and config %O', this.#id, config)\n }\n\n /**\n * Initialize the worker (called automatically by `start()`).\n *\n * Sets up the QueueManager and adapter connection.\n */\n async init() {\n if (this.#initialized) {\n return\n }\n\n debug('initializing worker %s', this.#id)\n\n await QueueManager.init(this.#config)\n\n this.#adapter = QueueManager.use()\n this.#adapter.setWorkerId(this.#id)\n\n this.#initialized = true\n\n debug('worker %s initialized', this.#id)\n }\n\n /**\n * Start processing jobs from the specified queues.\n *\n * This method blocks until the worker is stopped (via `stop()` or signal).\n * Jobs are processed concurrently up to the configured concurrency limit.\n *\n * @param queues - Queue names to process (default: ['default'])\n *\n * @example\n * ```typescript\n * // Process single queue\n * await worker.start()\n *\n * // Process multiple queues (priority order)\n * await worker.start(['high-priority', 'default', 'low-priority'])\n * ```\n */\n async start(queues: string[] = ['default']): Promise<void> {\n await this.init()\n\n if (this.#running) {\n debug('worker %s is already running', this.#id)\n return\n }\n\n this.#running = true\n\n debug('starting worker %s on queues: %O', this.#id, queues)\n\n this.#setupGracefulShutdown()\n\n for await (const cycle of this.process(queues)) {\n if (['started', 'completed'].includes(cycle.type)) {\n continue\n }\n\n if (['idle', 'error'].includes(cycle.type)) {\n // @ts-expect-error - we know suggestedDelay exists for these types\n const delay = parse(cycle.suggestedDelay)\n\n if (cycle.type === 'error') {\n debug('worker %s encountered an error: %O', this.#id, cycle.error)\n } else {\n debug('worker %s is idle, waiting for %dms', this.#id, delay)\n }\n\n await setTimeout(delay)\n }\n }\n }\n\n /**\n * Stop the worker gracefully.\n *\n * Waits for all running jobs to complete before shutting down.\n * Called automatically on SIGINT/SIGTERM if gracefulShutdown is enabled.\n */\n async stop() {\n debug('stopping worker %s', this.#id)\n\n this.#running = false\n\n if (this.#pool) {\n debug('worker %s: waiting for %d running jobs to complete', this.#id, this.#pool.size)\n await this.#pool.drain()\n }\n\n if (this.#adapter) {\n await this.#adapter.destroy()\n }\n\n this.#removeShutdownHandlers()\n }\n\n /**\n * Process a single cycle and return the result.\n *\n * Useful for testing or when you need fine-grained control.\n * Each cycle may start new jobs, complete a job, or return idle.\n *\n * @param queues - Queue names to process\n * @returns The cycle result, or null if the worker was stopped\n *\n * @example\n * ```typescript\n * const worker = new Worker(config)\n *\n * // Process cycles manually\n * let cycle = await worker.processCycle(['default'])\n * while (cycle) {\n * console.log('Cycle:', cycle.type)\n * cycle = await worker.processCycle(['default'])\n * }\n * ```\n */\n async processCycle(queues: string[]): Promise<WorkerCycle | null> {\n await this.init()\n\n this.#running = true\n\n if (!this.#generator) {\n this.#generator = this.process(queues)\n }\n\n const result = await this.#generator.next()\n\n if (result.done) {\n this.#generator = undefined\n return null\n }\n\n return result.value\n }\n\n /**\n * Generator that yields worker cycle events.\n *\n * Low-level API for processing jobs. Yields events for:\n * - `started`: A new job began execution\n * - `completed`: A job finished (success or failure)\n * - `idle`: No jobs available, suggest waiting\n * - `error`: An error occurred during processing\n *\n * @param queues - Queue names to process\n * @yields WorkerCycle events\n *\n * @example\n * ```typescript\n * for await (const cycle of worker.process(['default'])) {\n * switch (cycle.type) {\n * case 'started':\n * console.log(`Started job ${cycle.job.id}`)\n * break\n * case 'completed':\n * console.log(`Completed job ${cycle.job.id}`)\n * break\n * case 'idle':\n * await sleep(cycle.suggestedDelay)\n * break\n * }\n * }\n * ```\n */\n async *process(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n this.#pool = new JobPool()\n\n while (this.#running) {\n try {\n // Check for stalled jobs periodically\n await this.#checkStalledJobs(queues)\n\n // Dispatch any due scheduled jobs\n await this.#dispatchDueSchedules()\n\n yield* this.#fillPool(queues)\n\n if (this.#pool.isEmpty()) {\n yield { type: 'idle', suggestedDelay: this.#idleDelay }\n continue\n }\n\n const completed = await this.#pool.waitForNextCompletion()\n yield { type: 'completed', queue: completed.queue, job: completed.job }\n } catch (error) {\n yield {\n type: 'error',\n error: error as Error,\n suggestedDelay: parse(DEFAULT_ERROR_RETRY_DELAY),\n }\n }\n }\n }\n\n async *#fillPool(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const slotsAvailable = this.#concurrency - this.#pool!.size\n\n if (slotsAvailable <= 0) return\n\n const popPromises = Array.from({ length: slotsAvailable }, () => this.#acquireNextJob(queues))\n\n const results = await Promise.all(popPromises)\n\n for (const result of results) {\n if (!result) continue\n\n const { job, queue } = result\n const promise = this.#execute(job, queue)\n this.#pool!.add(job, queue, promise)\n\n yield { type: 'started', queue, job }\n }\n }\n\n async #execute(job: AcquiredJob, queue: string): Promise<void> {\n const startTime = performance.now()\n\n debug('worker %s: executing job %s (%s)', this.#id, job.id, job.name)\n\n const { instance, options, timeout, context, payload } = await this.#initJob(job, queue)\n const retention = QueueManager.getMergedJobOptions(queue, options)\n\n try {\n await this.#executeWithTimeout(instance, payload, context, timeout)\n await this.#adapter.completeJob(job.id, queue, retention.removeOnComplete)\n\n const duration = (performance.now() - startTime).toFixed(2)\n debug('worker %s: successfully executed job %s in %dms', this.#id, job.id, duration)\n } catch (e) {\n const isTimeout = e instanceof errors.E_JOB_TIMEOUT\n\n if (isTimeout && options.failOnTimeout) {\n debug('worker %s: job %s timed out and failOnTimeout is set', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error, retention.removeOnFail)\n await instance.failed?.(e as Error)\n return\n }\n\n const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry)\n\n if (typeof mergedConfig.maxRetries === 'undefined' || mergedConfig.maxRetries <= 0) {\n debug('worker %s: job %s has no retries configured, marking as failed', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error, retention.removeOnFail)\n await instance.failed?.(e as Error)\n return\n }\n\n if (job.attempts >= mergedConfig.maxRetries!) {\n debug(\n 'worker %s: job %s has exceeded max retries (%d), marking as failed',\n this.#id,\n job.id,\n mergedConfig.maxRetries\n )\n await this.#adapter.failJob(job.id, queue, e as Error, retention.removeOnFail)\n const exception = new errors.E_JOB_MAX_ATTEMPTS_REACHED([job.name], { cause: e })\n await instance.failed?.(exception)\n\n return\n }\n\n if (mergedConfig.backoff) {\n const strategy = mergedConfig.backoff()\n const nextRetryAt = strategy.getNextRetryAt(job.attempts + 1)\n\n debug('worker %s: job %s will retry at %s', this.#id, job.id, nextRetryAt.toISOString())\n\n await this.#adapter.retryJob(job.id, queue, nextRetryAt)\n return\n }\n\n await this.#adapter.retryJob(job.id, queue)\n }\n }\n\n async #initJob(\n job: AcquiredJob,\n queue: string\n ): Promise<{\n instance: Job\n options: JobOptions\n timeout: number | undefined\n context: JobContext\n payload: any\n }> {\n try {\n const JobClass = Locator.getOrThrow(job.name)\n\n const context: JobContext = {\n jobId: job.id,\n name: job.name,\n attempt: job.attempts + 1,\n queue,\n priority: job.priority ?? DEFAULT_PRIORITY,\n acquiredAt: new Date(job.acquiredAt),\n stalledCount: job.stalledCount ?? 0,\n }\n\n const jobFactory = QueueManager.getJobFactory()\n const instance = jobFactory ? await jobFactory(JobClass) : new JobClass()\n const options = JobClass.options || {}\n const timeout = this.#getJobTimeout(options)\n\n return { instance, options, timeout, context, payload: job.payload }\n } catch (error) {\n debug('worker %s: failed to initialize job %s (%s)', this.#id, job.id, job.name)\n const retention = QueueManager.getMergedJobOptions(queue)\n await this.#adapter.failJob(job.id, queue, error as Error, retention.removeOnFail)\n throw error\n }\n }\n\n #getJobTimeout(options: JobOptions): number | undefined {\n if (options.timeout !== undefined) {\n return parse(options.timeout)\n }\n\n if (this.#config.worker?.timeout !== undefined) {\n return parse(this.#config.worker.timeout)\n }\n\n return undefined\n }\n\n async #executeWithTimeout(\n instance: Job,\n payload: any,\n context: JobContext,\n timeout?: number\n ): Promise<void> {\n if (!timeout) {\n instance.$hydrate(payload, context)\n return instance.execute()\n }\n\n const signal = AbortSignal.timeout(timeout)\n instance.$hydrate(payload, context, signal)\n\n const abortPromise = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => {\n reject(new errors.E_JOB_TIMEOUT([instance.constructor.name, timeout]))\n })\n })\n\n await Promise.race([instance.execute(), abortPromise])\n }\n\n async #acquireNextJob(queues: string[]): Promise<{ job: AcquiredJob; queue: string } | null> {\n for (const queue of queues) {\n const job = await this.#adapter.popFrom(queue)\n\n if (!job) {\n continue\n }\n\n debug('worker %s: acquired job %s', this.#id, job.id)\n return { job, queue }\n }\n\n return null\n }\n\n async #checkStalledJobs(queues: string[]): Promise<void> {\n const now = Date.now()\n\n // Only check if enough time has passed since last check\n if (now - this.#lastStalledCheck < this.#stalledInterval) {\n return\n }\n\n this.#lastStalledCheck = now\n\n for (const queue of queues) {\n const recovered = await this.#adapter.recoverStalledJobs(\n queue,\n this.#stalledThreshold,\n this.#maxStalledCount\n )\n\n if (recovered > 0) {\n debug('worker %s: recovered %d stalled jobs from queue %s', this.#id, recovered, queue)\n }\n }\n }\n\n #setupGracefulShutdown() {\n if (!this.#gracefulShutdown) {\n return\n }\n\n this.#shutdownHandler = async () => {\n debug('received shutdown signal, stopping worker...')\n\n if (this.#onShutdownSignal) {\n await this.#onShutdownSignal()\n }\n\n await this.stop()\n }\n\n process.on('SIGINT', this.#shutdownHandler)\n process.on('SIGTERM', this.#shutdownHandler)\n }\n\n #removeShutdownHandlers() {\n if (this.#shutdownHandler) {\n process.off('SIGINT', this.#shutdownHandler)\n process.off('SIGTERM', this.#shutdownHandler)\n this.#shutdownHandler = undefined\n }\n }\n\n /**\n * Dispatch any due scheduled jobs.\n *\n * Claims due schedules from the adapter and dispatches the corresponding\n * jobs to their configured queues.\n */\n async #dispatchDueSchedules(): Promise<void> {\n // Keep claiming due schedules until there are none left\n while (true) {\n const schedule = await this.#adapter.claimDueSchedule()\n\n if (!schedule) {\n break\n }\n\n debug(\n 'worker %s: dispatching scheduled job %s (schedule: %s, runCount: %d)',\n this.#id,\n schedule.name,\n schedule.id,\n schedule.runCount + 1\n )\n\n // Get the job class to determine the target queue\n const JobClass = Locator.get(schedule.name)\n const queue = JobClass?.options?.queue ?? 'default'\n\n // Dispatch the job to the queue\n await this.#adapter.pushOn(queue, {\n id: randomUUID(),\n name: schedule.name,\n payload: schedule.payload,\n attempts: 0,\n priority: JobClass?.options?.priority,\n })\n }\n }\n}\n","import type { AcquiredJob } from './contracts/adapter.js'\n\n/**\n * Entry representing an active job in the pool.\n */\ninterface PoolEntry {\n /** Promise that resolves when the job completes */\n promise: Promise<void>\n /** The acquired job data */\n job: AcquiredJob\n /** The queue this job came from */\n queue: string\n}\n\n/**\n * Manages concurrent job execution with a fixed pool size.\n *\n * The pool tracks running jobs and returns the first one to complete,\n * allowing maximum throughput regardless of individual job duration:\n *\n * ```\n * Job A: ████████████████████░░░░░░░░░░ (slow - 10s)\n * Job B: ████ done (fast - 100ms) ← returns first\n * Job C: ████████████░░░░░░░░░░░░░░░░░░ (medium - 2s)\n * ↑\n * Slot freed, new job can start immediately\n * ```\n *\n * Key insight: slow jobs don't block the pool. As soon as any job\n * completes, its slot becomes available for new work.\n */\nexport class JobPool {\n #activeJobs = new Map<string, PoolEntry>()\n\n /** Number of currently running jobs */\n get size() {\n return this.#activeJobs.size\n }\n\n /**\n * Check if the pool has no running jobs.\n *\n * @returns True if no jobs are running\n */\n isEmpty() {\n return this.#activeJobs.size === 0\n }\n\n /**\n * Check if the pool can accept more jobs.\n *\n * @param concurrency - Maximum number of concurrent jobs\n * @returns True if there's room for more jobs\n */\n hasCapacity(concurrency: number) {\n return this.#activeJobs.size < concurrency\n }\n\n /**\n * Add a job to the pool.\n *\n * @param job - The acquired job data\n * @param queue - The queue the job came from\n * @param promise - Promise that resolves when the job completes\n */\n add(job: AcquiredJob, queue: string, promise: Promise<void>) {\n this.#activeJobs.set(job.id, { promise, job, queue })\n }\n\n /**\n * Wait for the next job to complete and return it.\n *\n * Uses `Promise.race()` internally, so the fastest job wins.\n * The completed job is removed from the pool.\n *\n * @returns The first job to complete (success or failure)\n */\n async waitForNextCompletion(): Promise<PoolEntry> {\n const completedJobId = await Promise.race(\n [...this.#activeJobs.entries()].map(async ([id, { promise }]) => {\n try {\n await promise\n } catch {\n // Errors are handled in Worker#execute\n }\n return id\n })\n )\n\n const completed = this.#activeJobs.get(completedJobId)!\n this.#activeJobs.delete(completedJobId)\n\n return completed\n }\n\n /**\n * Wait for all running jobs to complete.\n *\n * Used during graceful shutdown to ensure no jobs are abandoned.\n * Clears the pool after all jobs finish.\n */\n async drain(): Promise<void> {\n const promises = [...this.#activeJobs.values()].map(async ({ promise }) => {\n try {\n await promise\n } catch {\n // Errors are handled in Worker#execute\n }\n })\n\n await Promise.all(promises)\n this.#activeJobs.clear()\n }\n}\n","import { QueueManager } from './queue_manager.js'\nimport { JobDispatcher } from './job_dispatcher.js'\nimport type { ScheduleData, ScheduleListOptions, ScheduleStatus } from './types/main.js'\n\n/**\n * Represents a persisted job schedule.\n *\n * Use `Schedule.find()` or `Schedule.list()` to retrieve schedules,\n * then use instance methods to manage them.\n *\n * @example\n * ```typescript\n * const schedule = await Schedule.find('cleanup-daily')\n * if (schedule) {\n * await schedule.pause()\n * // Later...\n * await schedule.resume()\n * }\n *\n * // List all active schedules\n * const activeSchedules = await Schedule.list({ status: 'active' })\n * ```\n */\nexport class Schedule {\n readonly #data: ScheduleData\n\n constructor(data: ScheduleData) {\n this.#data = data\n }\n\n get id(): string {\n return this.#data.id\n }\n\n get name(): string {\n return this.#data.name\n }\n\n get payload(): any {\n return this.#data.payload\n }\n\n get cronExpression(): string | null {\n return this.#data.cronExpression\n }\n\n get everyMs(): number | null {\n return this.#data.everyMs\n }\n\n get timezone(): string {\n return this.#data.timezone\n }\n\n get from(): Date | null {\n return this.#data.from\n }\n\n get to(): Date | null {\n return this.#data.to\n }\n\n get limit(): number | null {\n return this.#data.limit\n }\n\n get runCount(): number {\n return this.#data.runCount\n }\n\n get nextRunAt(): Date | null {\n return this.#data.nextRunAt\n }\n\n get lastRunAt(): Date | null {\n return this.#data.lastRunAt\n }\n\n get status(): ScheduleStatus {\n return this.#data.status\n }\n\n get createdAt(): Date {\n return this.#data.createdAt\n }\n\n /**\n * Find a schedule by ID.\n *\n * @param id - The schedule ID\n * @returns The schedule instance, or null if not found\n */\n static async find(id: string): Promise<Schedule | null> {\n const adapter = QueueManager.use()\n const data = await adapter.getSchedule(id)\n\n if (!data) return null\n\n return new Schedule(data)\n }\n\n /**\n * List all schedules matching the given options.\n *\n * @param options - Optional filters for listing\n * @returns Array of schedule instances\n */\n static async list(options?: ScheduleListOptions): Promise<Schedule[]> {\n const adapter = QueueManager.use()\n const schedules = await adapter.listSchedules(options)\n\n return schedules.map((data) => new Schedule(data))\n }\n\n /**\n * Pause this schedule.\n * No jobs will be dispatched while paused.\n */\n async pause(): Promise<void> {\n const adapter = QueueManager.use()\n await adapter.updateSchedule(this.#data.id, { status: 'paused' })\n this.#data.status = 'paused'\n }\n\n /**\n * Resume this schedule.\n * Jobs will be dispatched according to the schedule.\n */\n async resume(): Promise<void> {\n const adapter = QueueManager.use()\n await adapter.updateSchedule(this.#data.id, { status: 'active' })\n this.#data.status = 'active'\n }\n\n /**\n * Delete this schedule permanently.\n */\n async delete(): Promise<void> {\n const adapter = QueueManager.use()\n await adapter.deleteSchedule(this.#data.id)\n }\n\n /**\n * Trigger immediate execution of this schedule's job.\n * Also updates runCount and lastRunAt.\n *\n * If the schedule has reached its limit, the job will not be dispatched.\n */\n async trigger(): Promise<void> {\n // Check if limit is reached\n if (this.#data.limit !== null && this.#data.runCount >= this.#data.limit) {\n return\n }\n\n const adapter = QueueManager.use()\n\n // Dispatch the job\n const dispatcher = new JobDispatcher(this.#data.name, this.#data.payload)\n await dispatcher.run()\n\n // Update run metadata\n const now = new Date()\n const newRunCount = this.#data.runCount + 1\n\n await adapter.updateSchedule(this.#data.id, {\n runCount: newRunCount,\n lastRunAt: now,\n })\n\n this.#data.runCount = newRunCount\n this.#data.lastRunAt = now\n }\n}\n","import type { BackoffConfig, Duration } from '../types/main.js'\nimport * as errors from '../exceptions.js'\nimport { parse } from '../utils.js'\nimport { RuntimeException } from '@poppinss/utils'\nimport { assertUnreachable } from '@poppinss/utils/assert'\n\n/**\n * Calculates retry delays using configurable strategies.\n *\n * Supports three built-in strategies:\n * - `exponential`: Delay doubles each attempt (1s, 2s, 4s, 8s, ...)\n * - `linear`: Delay increases linearly (5s, 10s, 15s, 20s, ...)\n * - `fixed`: Same delay every time (10s, 10s, 10s, ...)\n *\n * All strategies support:\n * - `maxDelay`: Cap the maximum delay\n * - `jitter`: Add randomness to prevent thundering herd\n *\n * @example\n * ```typescript\n * const strategy = new BackoffStrategy({\n * strategy: 'exponential',\n * baseDelay: '1s',\n * maxDelay: '5m',\n * multiplier: 2,\n * jitter: true,\n * })\n *\n * strategy.calculateDelay(1) // ~1000ms\n * strategy.calculateDelay(2) // ~2000ms\n * strategy.calculateDelay(3) // ~4000ms\n * ```\n */\nexport class BackoffStrategy {\n readonly #config: BackoffConfig\n\n /**\n * Create a new backoff strategy.\n *\n * @param config - Backoff configuration\n * @throws {E_INVALID_BASE_DELAY} If baseDelay is not positive\n * @throws {E_INVALID_MAX_DELAY} If maxDelay is invalid\n * @throws {E_INVALID_MULTIPLIER} If multiplier is invalid\n */\n constructor(config: BackoffConfig) {\n this.#config = config\n this.#validateConfig()\n }\n\n /**\n * Calculate the delay for a given attempt number.\n *\n * @param attempt - The attempt number (1-based)\n * @returns Delay in milliseconds\n * @throws {RuntimeException} If attempt is less than 1\n *\n * @example\n * ```typescript\n * // Exponential: 1s, 2s, 4s, 8s, 16s, ...\n * strategy.calculateDelay(1) // 1000\n * strategy.calculateDelay(2) // 2000\n * strategy.calculateDelay(3) // 4000\n * ```\n */\n calculateDelay(attempt: number): number {\n if (attempt < 1) {\n throw new RuntimeException('Attempt number must be >= 1')\n }\n\n const baseDelayMs = parse(this.#config.baseDelay)\n const maxDelayMs = this.#config.maxDelay ? parse(this.#config.maxDelay) : Infinity\n const multiplier = this.#config.multiplier ?? 2\n\n let delay: number\n\n switch (this.#config.strategy) {\n case 'exponential':\n delay = baseDelayMs * Math.pow(multiplier, attempt - 1)\n break\n case 'linear':\n delay = baseDelayMs * attempt\n break\n case 'fixed':\n delay = baseDelayMs\n break\n default:\n assertUnreachable(this.#config.strategy)\n }\n\n // Apply max delay limit\n delay = Math.min(delay, maxDelayMs)\n\n if (this.#config.jitter) {\n delay = this.#applyJitter(delay)\n }\n\n return Math.floor(delay)\n }\n\n /**\n * Get the Date when the next retry should occur.\n *\n * @param attempt - The attempt number (1-based)\n * @returns Date for the next retry\n *\n * @example\n * ```typescript\n * const nextRetry = strategy.getNextRetryAt(3)\n * console.log(`Retry at: ${nextRetry.toISOString()}`)\n * ```\n */\n getNextRetryAt(attempt: number): Date {\n const delay = this.calculateDelay(attempt)\n return new Date(Date.now() + delay)\n }\n\n /**\n * Get a frozen copy of the configuration.\n *\n * @returns Readonly configuration object\n */\n getConfig(): Readonly<BackoffConfig> {\n return Object.freeze({ ...this.#config })\n }\n\n #validateConfig() {\n const baseDelayMs = parse(this.#config.baseDelay)\n\n if (baseDelayMs <= 0) {\n throw new errors.E_INVALID_BASE_DELAY([\n 'Base delay must be a positive integer greater than zero',\n ])\n }\n\n if (this.#config.maxDelay) {\n const maxDelayMs = parse(this.#config.maxDelay)\n\n if (maxDelayMs <= 0) {\n throw new errors.E_INVALID_MAX_DELAY([\n 'Max delay must be a positive integer greater than zero',\n ])\n }\n\n if (maxDelayMs <= baseDelayMs) {\n throw new errors.E_INVALID_MAX_DELAY(['Max delay should be greater than base delay'])\n }\n }\n\n if (this.#config.multiplier !== undefined) {\n if (this.#config.multiplier <= 0) {\n throw new errors.E_INVALID_MULTIPLIER([\n 'Multiplier must be a positive number greater than zero',\n ])\n }\n\n if (this.#config.strategy === 'exponential' && this.#config.multiplier < 1) {\n throw new errors.E_INVALID_MULTIPLIER(['Exponential strategy multiplier should be >= 1'])\n }\n }\n }\n\n #applyJitter(delay: number): number {\n const jitterRange = delay * 0.25\n const jitter = (Math.random() - 0.5) * 2 * jitterRange\n\n return Math.max(0, delay + jitter)\n }\n}\n\n/**\n * Create an exponential backoff strategy factory.\n *\n * Delay doubles with each attempt: 1s → 2s → 4s → 8s → ...\n *\n * Default config:\n * - baseDelay: 1s\n * - maxDelay: 5m\n * - multiplier: 2\n * - jitter: true\n *\n * @param config - Optional overrides for default config\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 5,\n * backoff: exponentialBackoff({ baseDelay: '500ms', maxDelay: '1m' }),\n * },\n * }\n * ```\n */\nexport function exponentialBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'exponential',\n baseDelay: '1s',\n maxDelay: '5m',\n multiplier: 2,\n jitter: true,\n ...config,\n })\n}\n\n/**\n * Create a linear backoff strategy factory.\n *\n * Delay increases linearly: 5s → 10s → 15s → 20s → ...\n *\n * Default config:\n * - baseDelay: 5s\n * - maxDelay: 2m\n *\n * @param config - Optional overrides for default config\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 3,\n * backoff: linearBackoff({ baseDelay: '10s' }),\n * },\n * }\n * ```\n */\nexport function linearBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'linear',\n baseDelay: '5s',\n maxDelay: '2m',\n ...config,\n })\n}\n\n/**\n * Create a fixed delay backoff strategy factory.\n *\n * Same delay every time: 10s → 10s → 10s → ...\n *\n * @param delay - The fixed delay (default: '10s')\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 3,\n * backoff: fixedBackoff('30s'),\n * },\n * }\n * ```\n */\nexport function fixedBackoff(delay: Duration = '10s') {\n return () =>\n new BackoffStrategy({\n strategy: 'fixed',\n baseDelay: delay,\n })\n}\n\n/**\n * Create a custom backoff strategy factory.\n *\n * Use this when you need full control over the configuration.\n *\n * @param config - Complete backoff configuration\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 5,\n * backoff: customBackoff({\n * strategy: 'exponential',\n * baseDelay: '100ms',\n * maxDelay: '30s',\n * multiplier: 3,\n * jitter: false,\n * }),\n * },\n * }\n * ```\n */\nexport function customBackoff(config: BackoffConfig) {\n return () => new BackoffStrategy(config)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;;;AC8BpB,IAAM,UAAN,MAAc;AAAA,EACnB,cAAc,oBAAI,IAAuB;AAAA;AAAA,EAGzC,IAAI,OAAO;AACT,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU;AACR,WAAO,KAAK,YAAY,SAAS;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,YAAY,aAAqB;AAC/B,WAAO,KAAK,YAAY,OAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,KAAkB,OAAe,SAAwB;AAC3D,SAAK,YAAY,IAAI,IAAI,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,wBAA4C;AAChD,UAAM,iBAAiB,MAAM,QAAQ;AAAA,MACnC,CAAC,GAAG,KAAK,YAAY,QAAQ,CAAC,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM;AAC/D,YAAI;AACF,gBAAM;AAAA,QACR,QAAQ;AAAA,QAER;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,UAAM,YAAY,KAAK,YAAY,IAAI,cAAc;AACrD,SAAK,YAAY,OAAO,cAAc;AAEtC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAuB;AAC3B,UAAM,WAAW,CAAC,GAAG,KAAK,YAAY,OAAO,CAAC,EAAE,IAAI,OAAO,EAAE,QAAQ,MAAM;AACzE,UAAI;AACF,cAAM;AAAA,MACR,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAED,UAAM,QAAQ,IAAI,QAAQ;AAC1B,SAAK,YAAY,MAAM;AAAA,EACzB;AACF;;;AD/DO,IAAM,SAAN,MAAa;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET;AAAA,EACA,WAAW;AAAA,EACX,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,KAAK;AACP,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY,QAA4B;AACtC,SAAK,UAAU;AACf,SAAK,MAAM,WAAW;AAGtB,SAAK,aAAa,MAAM,OAAO,QAAQ,aAAa,kBAAkB;AACtE,SAAK,mBAAmB,MAAM,OAAO,QAAQ,mBAAmB,wBAAwB;AACxF,SAAK,oBAAoB,MAAM,OAAO,QAAQ,oBAAoB,yBAAyB;AAC3F,SAAK,mBAAmB,OAAO,QAAQ,mBAAmB;AAC1D,SAAK,eAAe,OAAO,QAAQ,eAAe;AAClD,SAAK,oBAAoB,OAAO,QAAQ,oBAAoB;AAC5D,SAAK,oBAAoB,OAAO,QAAQ;AAExC,kBAAM,2CAA2C,KAAK,KAAK,MAAM;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAO;AACX,QAAI,KAAK,cAAc;AACrB;AAAA,IACF;AAEA,kBAAM,0BAA0B,KAAK,GAAG;AAExC,UAAM,aAAa,KAAK,KAAK,OAAO;AAEpC,SAAK,WAAW,aAAa,IAAI;AACjC,SAAK,SAAS,YAAY,KAAK,GAAG;AAElC,SAAK,eAAe;AAEpB,kBAAM,yBAAyB,KAAK,GAAG;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,MAAM,MAAM,SAAmB,CAAC,SAAS,GAAkB;AACzD,UAAM,KAAK,KAAK;AAEhB,QAAI,KAAK,UAAU;AACjB,oBAAM,gCAAgC,KAAK,GAAG;AAC9C;AAAA,IACF;AAEA,SAAK,WAAW;AAEhB,kBAAM,oCAAoC,KAAK,KAAK,MAAM;AAE1D,SAAK,uBAAuB;AAE5B,qBAAiB,SAAS,KAAK,QAAQ,MAAM,GAAG;AAC9C,UAAI,CAAC,WAAW,WAAW,EAAE,SAAS,MAAM,IAAI,GAAG;AACjD;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ,OAAO,EAAE,SAAS,MAAM,IAAI,GAAG;AAE1C,cAAM,QAAQ,MAAM,MAAM,cAAc;AAExC,YAAI,MAAM,SAAS,SAAS;AAC1B,wBAAM,sCAAsC,KAAK,KAAK,MAAM,KAAK;AAAA,QACnE,OAAO;AACL,wBAAM,uCAAuC,KAAK,KAAK,KAAK;AAAA,QAC9D;AAEA,cAAM,WAAW,KAAK;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAO;AACX,kBAAM,sBAAsB,KAAK,GAAG;AAEpC,SAAK,WAAW;AAEhB,QAAI,KAAK,OAAO;AACd,oBAAM,sDAAsD,KAAK,KAAK,KAAK,MAAM,IAAI;AACrF,YAAM,KAAK,MAAM,MAAM;AAAA,IACzB;AAEA,QAAI,KAAK,UAAU;AACjB,YAAM,KAAK,SAAS,QAAQ;AAAA,IAC9B;AAEA,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAM,aAAa,QAA+C;AAChE,UAAM,KAAK,KAAK;AAEhB,SAAK,WAAW;AAEhB,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,KAAK,QAAQ,MAAM;AAAA,IACvC;AAEA,UAAM,SAAS,MAAM,KAAK,WAAW,KAAK;AAE1C,QAAI,OAAO,MAAM;AACf,WAAK,aAAa;AAClB,aAAO;AAAA,IACT;AAEA,WAAO,OAAO;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,OAAO,QAAQ,QAA8D;AAC3E,SAAK,QAAQ,IAAI,QAAQ;AAEzB,WAAO,KAAK,UAAU;AACpB,UAAI;AAEF,cAAM,KAAK,kBAAkB,MAAM;AAGnC,cAAM,KAAK,sBAAsB;AAEjC,eAAO,KAAK,UAAU,MAAM;AAE5B,YAAI,KAAK,MAAM,QAAQ,GAAG;AACxB,gBAAM,EAAE,MAAM,QAAQ,gBAAgB,KAAK,WAAW;AACtD;AAAA,QACF;AAEA,cAAM,YAAY,MAAM,KAAK,MAAM,sBAAsB;AACzD,cAAM,EAAE,MAAM,aAAa,OAAO,UAAU,OAAO,KAAK,UAAU,IAAI;AAAA,MACxE,SAAS,OAAO;AACd,cAAM;AAAA,UACJ,MAAM;AAAA,UACN;AAAA,UACA,gBAAgB,MAAM,yBAAyB;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,UAAU,QAA8D;AAC7E,UAAM,iBAAiB,KAAK,eAAe,KAAK,MAAO;AAEvD,QAAI,kBAAkB,EAAG;AAEzB,UAAM,cAAc,MAAM,KAAK,EAAE,QAAQ,eAAe,GAAG,MAAM,KAAK,gBAAgB,MAAM,CAAC;AAE7F,UAAM,UAAU,MAAM,QAAQ,IAAI,WAAW;AAE7C,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,OAAQ;AAEb,YAAM,EAAE,KAAK,MAAM,IAAI;AACvB,YAAM,UAAU,KAAK,SAAS,KAAK,KAAK;AACxC,WAAK,MAAO,IAAI,KAAK,OAAO,OAAO;AAEnC,YAAM,EAAE,MAAM,WAAW,OAAO,IAAI;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,KAAkB,OAA8B;AAC7D,UAAM,YAAY,YAAY,IAAI;AAElC,kBAAM,oCAAoC,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI;AAEpE,UAAM,EAAE,UAAU,SAAS,SAAS,SAAS,QAAQ,IAAI,MAAM,KAAK,SAAS,KAAK,KAAK;AACvF,UAAM,YAAY,aAAa,oBAAoB,OAAO,OAAO;AAEjE,QAAI;AACF,YAAM,KAAK,oBAAoB,UAAU,SAAS,SAAS,OAAO;AAClE,YAAM,KAAK,SAAS,YAAY,IAAI,IAAI,OAAO,UAAU,gBAAgB;AAEzE,YAAM,YAAY,YAAY,IAAI,IAAI,WAAW,QAAQ,CAAC;AAC1D,oBAAM,mDAAmD,KAAK,KAAK,IAAI,IAAI,QAAQ;AAAA,IACrF,SAAS,GAAG;AACV,YAAM,YAAY,aAAoB;AAEtC,UAAI,aAAa,QAAQ,eAAe;AACtC,sBAAM,wDAAwD,KAAK,KAAK,IAAI,EAAE;AAC9E,cAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,GAAY,UAAU,YAAY;AAC7E,cAAM,SAAS,SAAS,CAAU;AAClC;AAAA,MACF;AAEA,YAAM,eAAe,aAAa,qBAAqB,OAAO,QAAQ,KAAK;AAE3E,UAAI,OAAO,aAAa,eAAe,eAAe,aAAa,cAAc,GAAG;AAClF,sBAAM,kEAAkE,KAAK,KAAK,IAAI,EAAE;AACxF,cAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,GAAY,UAAU,YAAY;AAC7E,cAAM,SAAS,SAAS,CAAU;AAClC;AAAA,MACF;AAEA,UAAI,IAAI,YAAY,aAAa,YAAa;AAC5C;AAAA,UACE;AAAA,UACA,KAAK;AAAA,UACL,IAAI;AAAA,UACJ,aAAa;AAAA,QACf;AACA,cAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,GAAY,UAAU,YAAY;AAC7E,cAAM,YAAY,IAAW,2BAA2B,CAAC,IAAI,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC;AAChF,cAAM,SAAS,SAAS,SAAS;AAEjC;AAAA,MACF;AAEA,UAAI,aAAa,SAAS;AACxB,cAAM,WAAW,aAAa,QAAQ;AACtC,cAAM,cAAc,SAAS,eAAe,IAAI,WAAW,CAAC;AAE5D,sBAAM,sCAAsC,KAAK,KAAK,IAAI,IAAI,YAAY,YAAY,CAAC;AAEvF,cAAM,KAAK,SAAS,SAAS,IAAI,IAAI,OAAO,WAAW;AACvD;AAAA,MACF;AAEA,YAAM,KAAK,SAAS,SAAS,IAAI,IAAI,KAAK;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,MAAM,SACJ,KACA,OAOC;AACD,QAAI;AACF,YAAM,WAAW,QAAQ,WAAW,IAAI,IAAI;AAE5C,YAAM,UAAsB;AAAA,QAC1B,OAAO,IAAI;AAAA,QACX,MAAM,IAAI;AAAA,QACV,SAAS,IAAI,WAAW;AAAA,QACxB;AAAA,QACA,UAAU,IAAI,YAAY;AAAA,QAC1B,YAAY,IAAI,KAAK,IAAI,UAAU;AAAA,QACnC,cAAc,IAAI,gBAAgB;AAAA,MACpC;AAEA,YAAM,aAAa,aAAa,cAAc;AAC9C,YAAM,WAAW,aAAa,MAAM,WAAW,QAAQ,IAAI,IAAI,SAAS;AACxE,YAAM,UAAU,SAAS,WAAW,CAAC;AACrC,YAAM,UAAU,KAAK,eAAe,OAAO;AAE3C,aAAO,EAAE,UAAU,SAAS,SAAS,SAAS,SAAS,IAAI,QAAQ;AAAA,IACrE,SAAS,OAAO;AACd,oBAAM,+CAA+C,KAAK,KAAK,IAAI,IAAI,IAAI,IAAI;AAC/E,YAAM,YAAY,aAAa,oBAAoB,KAAK;AACxD,YAAM,KAAK,SAAS,QAAQ,IAAI,IAAI,OAAO,OAAgB,UAAU,YAAY;AACjF,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,eAAe,SAAyC;AACtD,QAAI,QAAQ,YAAY,QAAW;AACjC,aAAO,MAAM,QAAQ,OAAO;AAAA,IAC9B;AAEA,QAAI,KAAK,QAAQ,QAAQ,YAAY,QAAW;AAC9C,aAAO,MAAM,KAAK,QAAQ,OAAO,OAAO;AAAA,IAC1C;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,oBACJ,UACA,SACA,SACA,SACe;AACf,QAAI,CAAC,SAAS;AACZ,eAAS,SAAS,SAAS,OAAO;AAClC,aAAO,SAAS,QAAQ;AAAA,IAC1B;AAEA,UAAM,SAAS,YAAY,QAAQ,OAAO;AAC1C,aAAS,SAAS,SAAS,SAAS,MAAM;AAE1C,UAAM,eAAe,IAAI,QAAe,CAAC,GAAG,WAAW;AACrD,aAAO,iBAAiB,SAAS,MAAM;AACrC,eAAO,IAAW,cAAc,CAAC,SAAS,YAAY,MAAM,OAAO,CAAC,CAAC;AAAA,MACvE,CAAC;AAAA,IACH,CAAC;AAED,UAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,GAAG,YAAY,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,gBAAgB,QAAuE;AAC3F,eAAW,SAAS,QAAQ;AAC1B,YAAM,MAAM,MAAM,KAAK,SAAS,QAAQ,KAAK;AAE7C,UAAI,CAAC,KAAK;AACR;AAAA,MACF;AAEA,oBAAM,8BAA8B,KAAK,KAAK,IAAI,EAAE;AACpD,aAAO,EAAE,KAAK,MAAM;AAAA,IACtB;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,kBAAkB,QAAiC;AACvD,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,MAAM,KAAK,oBAAoB,KAAK,kBAAkB;AACxD;AAAA,IACF;AAEA,SAAK,oBAAoB;AAEzB,eAAW,SAAS,QAAQ;AAC1B,YAAM,YAAY,MAAM,KAAK,SAAS;AAAA,QACpC;AAAA,QACA,KAAK;AAAA,QACL,KAAK;AAAA,MACP;AAEA,UAAI,YAAY,GAAG;AACjB,sBAAM,sDAAsD,KAAK,KAAK,WAAW,KAAK;AAAA,MACxF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,yBAAyB;AACvB,QAAI,CAAC,KAAK,mBAAmB;AAC3B;AAAA,IACF;AAEA,SAAK,mBAAmB,YAAY;AAClC,oBAAM,8CAA8C;AAEpD,UAAI,KAAK,mBAAmB;AAC1B,cAAM,KAAK,kBAAkB;AAAA,MAC/B;AAEA,YAAM,KAAK,KAAK;AAAA,IAClB;AAEA,YAAQ,GAAG,UAAU,KAAK,gBAAgB;AAC1C,YAAQ,GAAG,WAAW,KAAK,gBAAgB;AAAA,EAC7C;AAAA,EAEA,0BAA0B;AACxB,QAAI,KAAK,kBAAkB;AACzB,cAAQ,IAAI,UAAU,KAAK,gBAAgB;AAC3C,cAAQ,IAAI,WAAW,KAAK,gBAAgB;AAC5C,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,wBAAuC;AAE3C,WAAO,MAAM;AACX,YAAM,WAAW,MAAM,KAAK,SAAS,iBAAiB;AAEtD,UAAI,CAAC,UAAU;AACb;AAAA,MACF;AAEA;AAAA,QACE;AAAA,QACA,KAAK;AAAA,QACL,SAAS;AAAA,QACT,SAAS;AAAA,QACT,SAAS,WAAW;AAAA,MACtB;AAGA,YAAM,WAAW,QAAQ,IAAI,SAAS,IAAI;AAC1C,YAAM,QAAQ,UAAU,SAAS,SAAS;AAG1C,YAAM,KAAK,SAAS,OAAO,OAAO;AAAA,QAChC,IAAI,WAAW;AAAA,QACf,MAAM,SAAS;AAAA,QACf,SAAS,SAAS;AAAA,QAClB,UAAU;AAAA,QACV,UAAU,UAAU,SAAS;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;AE1gBO,IAAM,WAAN,MAAM,UAAS;AAAA,EACX;AAAA,EAET,YAAY,MAAoB;AAC9B,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,IAAI,KAAa;AACf,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,UAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,iBAAgC;AAClC,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,UAAyB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,OAAoB;AACtB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,KAAkB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,QAAuB;AACzB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,WAAmB;AACrB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAyB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAyB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,SAAyB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,IAAI,YAAkB;AACpB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,KAAK,IAAsC;AACtD,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,OAAO,MAAM,QAAQ,YAAY,EAAE;AAEzC,QAAI,CAAC,KAAM,QAAO;AAElB,WAAO,IAAI,UAAS,IAAI;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAa,KAAK,SAAoD;AACpE,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,YAAY,MAAM,QAAQ,cAAc,OAAO;AAErD,WAAO,UAAU,IAAI,CAAC,SAAS,IAAI,UAAS,IAAI,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAuB;AAC3B,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,QAAQ,eAAe,KAAK,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AAChE,SAAK,MAAM,SAAS;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,SAAwB;AAC5B,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,QAAQ,eAAe,KAAK,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AAChE,SAAK,MAAM,SAAS;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,SAAwB;AAC5B,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,QAAQ,eAAe,KAAK,MAAM,EAAE;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAyB;AAE7B,QAAI,KAAK,MAAM,UAAU,QAAQ,KAAK,MAAM,YAAY,KAAK,MAAM,OAAO;AACxE;AAAA,IACF;AAEA,UAAM,UAAU,aAAa,IAAI;AAGjC,UAAM,aAAa,IAAI,cAAc,KAAK,MAAM,MAAM,KAAK,MAAM,OAAO;AACxE,UAAM,WAAW,IAAI;AAGrB,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,cAAc,KAAK,MAAM,WAAW;AAE1C,UAAM,QAAQ,eAAe,KAAK,MAAM,IAAI;AAAA,MAC1C,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AAED,SAAK,MAAM,WAAW;AACtB,SAAK,MAAM,YAAY;AAAA,EACzB;AACF;;;ACzKA,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AA6B3B,IAAM,kBAAN,MAAsB;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUT,YAAY,QAAuB;AACjC,SAAK,UAAU;AACf,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,eAAe,SAAyB;AACtC,QAAI,UAAU,GAAG;AACf,YAAM,IAAI,iBAAiB,6BAA6B;AAAA,IAC1D;AAEA,UAAM,cAAc,MAAM,KAAK,QAAQ,SAAS;AAChD,UAAM,aAAa,KAAK,QAAQ,WAAW,MAAM,KAAK,QAAQ,QAAQ,IAAI;AAC1E,UAAM,aAAa,KAAK,QAAQ,cAAc;AAE9C,QAAI;AAEJ,YAAQ,KAAK,QAAQ,UAAU;AAAA,MAC7B,KAAK;AACH,gBAAQ,cAAc,KAAK,IAAI,YAAY,UAAU,CAAC;AACtD;AAAA,MACF,KAAK;AACH,gBAAQ,cAAc;AACtB;AAAA,MACF,KAAK;AACH,gBAAQ;AACR;AAAA,MACF;AACE,0BAAkB,KAAK,QAAQ,QAAQ;AAAA,IAC3C;AAGA,YAAQ,KAAK,IAAI,OAAO,UAAU;AAElC,QAAI,KAAK,QAAQ,QAAQ;AACvB,cAAQ,KAAK,aAAa,KAAK;AAAA,IACjC;AAEA,WAAO,KAAK,MAAM,KAAK;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,eAAe,SAAuB;AACpC,UAAM,QAAQ,KAAK,eAAe,OAAO;AACzC,WAAO,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAqC;AACnC,WAAO,OAAO,OAAO,EAAE,GAAG,KAAK,QAAQ,CAAC;AAAA,EAC1C;AAAA,EAEA,kBAAkB;AAChB,UAAM,cAAc,MAAM,KAAK,QAAQ,SAAS;AAEhD,QAAI,eAAe,GAAG;AACpB,YAAM,IAAW,qBAAqB;AAAA,QACpC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,QAAQ,UAAU;AACzB,YAAM,aAAa,MAAM,KAAK,QAAQ,QAAQ;AAE9C,UAAI,cAAc,GAAG;AACnB,cAAM,IAAW,oBAAoB;AAAA,UACnC;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,cAAc,aAAa;AAC7B,cAAM,IAAW,oBAAoB,CAAC,6CAA6C,CAAC;AAAA,MACtF;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ,eAAe,QAAW;AACzC,UAAI,KAAK,QAAQ,cAAc,GAAG;AAChC,cAAM,IAAW,qBAAqB;AAAA,UACpC;AAAA,QACF,CAAC;AAAA,MACH;AAEA,UAAI,KAAK,QAAQ,aAAa,iBAAiB,KAAK,QAAQ,aAAa,GAAG;AAC1E,cAAM,IAAW,qBAAqB,CAAC,gDAAgD,CAAC;AAAA,MAC1F;AAAA,IACF;AAAA,EACF;AAAA,EAEA,aAAa,OAAuB;AAClC,UAAM,cAAc,QAAQ;AAC5B,UAAM,UAAU,KAAK,OAAO,IAAI,OAAO,IAAI;AAE3C,WAAO,KAAK,IAAI,GAAG,QAAQ,MAAM;AAAA,EACnC;AACF;AA0BO,SAAS,mBAAmB,QAAmD;AACpF,SAAO,MACL,IAAI,gBAAgB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,GAAG;AAAA,EACL,CAAC;AACL;AAwBO,SAAS,cAAc,QAAmD;AAC/E,SAAO,MACL,IAAI,gBAAgB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,GAAG;AAAA,EACL,CAAC;AACL;AAoBO,SAAS,aAAa,QAAkB,OAAO;AACpD,SAAO,MACL,IAAI,gBAAgB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,EACb,CAAC;AACL;AA0BO,SAAS,cAAc,QAAuB;AACnD,SAAO,MAAM,IAAI,gBAAgB,MAAM;AACzC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/worker.ts","../src/job_pool.ts","../src/schedule.ts","../src/services/queue_schema.ts","../src/strategies/backoff_strategy.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { setTimeout } from 'node:timers/promises'\nimport debug from './debug.js'\nimport { parse } from './utils.js'\nimport * as errors from './exceptions.js'\nimport { QueueManager } from './queue_manager.js'\nimport { JobPool } from './job_pool.js'\nimport type { Adapter, AcquiredJob } from './contracts/adapter.js'\nimport type { JobContext, JobOptions, QueueManagerConfig, WorkerCycle } from './types/main.js'\nimport { Locator } from './locator.js'\nimport { DEFAULT_PRIORITY } from './constants.js'\nimport type { Job } from './job.js'\nimport {\n DEFAULT_IDLE_DELAY,\n DEFAULT_STALLED_INTERVAL,\n DEFAULT_STALLED_THRESHOLD,\n DEFAULT_ERROR_RETRY_DELAY,\n} from './constants.js'\n\n/**\n * Job processing worker.\n *\n * The Worker continuously polls queues for jobs and executes them\n * with configurable concurrency. It handles:\n * - Concurrent job execution via JobPool\n * - Automatic retries with backoff strategies\n * - Stalled job detection and recovery\n * - Graceful shutdown on SIGINT/SIGTERM\n *\n * @example\n * ```typescript\n * import { Worker, redis } from '@boringnode/queue'\n *\n * const worker = new Worker({\n * default: 'redis',\n * adapters: { redis: redis() },\n * locations: ['./jobs/**\\/*.js'],\n * worker: {\n * concurrency: 5,\n * idleDelay: '1s',\n * },\n * })\n *\n * // Start processing jobs\n * await worker.start(['default', 'emails'])\n *\n * // Or for testing, process one cycle at a time\n * const cycle = await worker.processCycle(['default'])\n * ```\n */\nexport class Worker {\n readonly #id: string\n readonly #config: QueueManagerConfig\n readonly #idleDelay: number\n readonly #stalledInterval: number\n readonly #stalledThreshold: number\n readonly #maxStalledCount: number\n readonly #concurrency: number\n readonly #gracefulShutdown: boolean\n readonly #onShutdownSignal?: () => void | Promise<void>\n\n #adapter!: Adapter\n #running = false\n #initialized = false\n #generator?: AsyncGenerator<WorkerCycle, void, unknown>\n #pool?: JobPool\n #lastStalledCheck = 0\n #shutdownHandler?: () => Promise<void>\n\n /** Unique identifier for this worker instance */\n get id() {\n return this.#id\n }\n\n /**\n * Create a new worker instance.\n *\n * @param config - Queue configuration including adapter and worker settings\n */\n constructor(config: QueueManagerConfig) {\n this.#config = config\n this.#id = randomUUID()\n\n // Parse worker config once at construction\n this.#idleDelay = parse(config.worker?.idleDelay ?? DEFAULT_IDLE_DELAY)\n this.#stalledInterval = parse(config.worker?.stalledInterval ?? DEFAULT_STALLED_INTERVAL)\n this.#stalledThreshold = parse(config.worker?.stalledThreshold ?? DEFAULT_STALLED_THRESHOLD)\n this.#maxStalledCount = config.worker?.maxStalledCount ?? 1\n this.#concurrency = config.worker?.concurrency ?? 1\n this.#gracefulShutdown = config.worker?.gracefulShutdown ?? true\n this.#onShutdownSignal = config.worker?.onShutdownSignal\n\n debug('created worker with id %s and config %O', this.#id, config)\n }\n\n /**\n * Initialize the worker (called automatically by `start()`).\n *\n * Sets up the QueueManager and adapter connection.\n */\n async init() {\n if (this.#initialized) {\n return\n }\n\n debug('initializing worker %s', this.#id)\n\n await QueueManager.init(this.#config)\n\n this.#adapter = QueueManager.use()\n this.#adapter.setWorkerId(this.#id)\n\n this.#initialized = true\n\n debug('worker %s initialized', this.#id)\n }\n\n /**\n * Start processing jobs from the specified queues.\n *\n * This method blocks until the worker is stopped (via `stop()` or signal).\n * Jobs are processed concurrently up to the configured concurrency limit.\n *\n * @param queues - Queue names to process (default: ['default'])\n *\n * @example\n * ```typescript\n * // Process single queue\n * await worker.start()\n *\n * // Process multiple queues (priority order)\n * await worker.start(['high-priority', 'default', 'low-priority'])\n * ```\n */\n async start(queues: string[] = ['default']): Promise<void> {\n await this.init()\n\n if (this.#running) {\n debug('worker %s is already running', this.#id)\n return\n }\n\n this.#running = true\n\n debug('starting worker %s on queues: %O', this.#id, queues)\n\n this.#setupGracefulShutdown()\n\n for await (const cycle of this.process(queues)) {\n if (['started', 'completed'].includes(cycle.type)) {\n continue\n }\n\n if (['idle', 'error'].includes(cycle.type)) {\n // @ts-expect-error - we know suggestedDelay exists for these types\n const delay = parse(cycle.suggestedDelay)\n\n if (cycle.type === 'error') {\n debug('worker %s encountered an error: %O', this.#id, cycle.error)\n } else {\n debug('worker %s is idle, waiting for %dms', this.#id, delay)\n }\n\n await setTimeout(delay)\n }\n }\n }\n\n /**\n * Stop the worker gracefully.\n *\n * Waits for all running jobs to complete before shutting down.\n * Called automatically on SIGINT/SIGTERM if gracefulShutdown is enabled.\n */\n async stop() {\n debug('stopping worker %s', this.#id)\n\n this.#running = false\n\n if (this.#pool) {\n debug('worker %s: waiting for %d running jobs to complete', this.#id, this.#pool.size)\n await this.#pool.drain()\n }\n\n if (this.#adapter) {\n await this.#adapter.destroy()\n }\n\n this.#removeShutdownHandlers()\n }\n\n /**\n * Process a single cycle and return the result.\n *\n * Useful for testing or when you need fine-grained control.\n * Each cycle may start new jobs, complete a job, or return idle.\n *\n * @param queues - Queue names to process\n * @returns The cycle result, or null if the worker was stopped\n *\n * @example\n * ```typescript\n * const worker = new Worker(config)\n *\n * // Process cycles manually\n * let cycle = await worker.processCycle(['default'])\n * while (cycle) {\n * console.log('Cycle:', cycle.type)\n * cycle = await worker.processCycle(['default'])\n * }\n * ```\n */\n async processCycle(queues: string[]): Promise<WorkerCycle | null> {\n await this.init()\n\n this.#running = true\n\n if (!this.#generator) {\n this.#generator = this.process(queues)\n }\n\n const result = await this.#generator.next()\n\n if (result.done) {\n this.#generator = undefined\n return null\n }\n\n return result.value\n }\n\n /**\n * Generator that yields worker cycle events.\n *\n * Low-level API for processing jobs. Yields events for:\n * - `started`: A new job began execution\n * - `completed`: A job finished (success or failure)\n * - `idle`: No jobs available, suggest waiting\n * - `error`: An error occurred during processing\n *\n * @param queues - Queue names to process\n * @yields WorkerCycle events\n *\n * @example\n * ```typescript\n * for await (const cycle of worker.process(['default'])) {\n * switch (cycle.type) {\n * case 'started':\n * console.log(`Started job ${cycle.job.id}`)\n * break\n * case 'completed':\n * console.log(`Completed job ${cycle.job.id}`)\n * break\n * case 'idle':\n * await sleep(cycle.suggestedDelay)\n * break\n * }\n * }\n * ```\n */\n async *process(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n this.#pool = new JobPool()\n\n while (this.#running) {\n try {\n // Check for stalled jobs periodically\n await this.#checkStalledJobs(queues)\n\n // Dispatch any due scheduled jobs\n await this.#dispatchDueSchedules()\n\n yield* this.#fillPool(queues)\n\n if (this.#pool.isEmpty()) {\n yield { type: 'idle', suggestedDelay: this.#idleDelay }\n continue\n }\n\n const hasCapacity = this.#pool.hasCapacity(this.#concurrency)\n\n // If we have capacity, don't block indefinitely waiting for a completion;\n // wake up periodically to try to acquire newly enqueued jobs.\n const result = await Promise.race([\n this.#pool\n .waitForNextCompletion()\n .then((completed) => ({ kind: 'completed' as const, completed })),\n ...(hasCapacity\n ? [setTimeout(this.#idleDelay).then(() => ({ kind: 'tick' as const }))]\n : []),\n ])\n\n if (result.kind === 'tick') {\n // No completion yet, but we woke up to check the queue again\n continue\n }\n\n yield { type: 'completed', queue: result.completed.queue, job: result.completed.job }\n } catch (error) {\n yield {\n type: 'error',\n error: error as Error,\n suggestedDelay: parse(DEFAULT_ERROR_RETRY_DELAY),\n }\n }\n }\n }\n\n async *#fillPool(queues: string[]): AsyncGenerator<WorkerCycle, void, unknown> {\n const slotsAvailable = this.#concurrency - this.#pool!.size\n\n if (slotsAvailable <= 0) return\n\n const popPromises = Array.from({ length: slotsAvailable }, () => this.#acquireNextJob(queues))\n\n const results = await Promise.all(popPromises)\n\n for (const result of results) {\n if (!result) continue\n\n const { job, queue } = result\n const promise = this.#execute(job, queue)\n this.#pool!.add(job, queue, promise)\n\n yield { type: 'started', queue, job }\n }\n }\n\n async #execute(job: AcquiredJob, queue: string): Promise<void> {\n const startTime = performance.now()\n\n debug('worker %s: executing job %s (%s)', this.#id, job.id, job.name)\n\n const { instance, options, timeout, context, payload } = await this.#initJob(job, queue)\n const retention = QueueManager.getMergedJobOptions(queue, options)\n\n try {\n await this.#executeWithTimeout(instance, payload, context, timeout)\n await this.#adapter.completeJob(job.id, queue, retention.removeOnComplete)\n\n const duration = (performance.now() - startTime).toFixed(2)\n debug('worker %s: successfully executed job %s in %dms', this.#id, job.id, duration)\n } catch (e) {\n const isTimeout = e instanceof errors.E_JOB_TIMEOUT\n\n if (isTimeout && options.failOnTimeout) {\n debug('worker %s: job %s timed out and failOnTimeout is set', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error, retention.removeOnFail)\n await instance.failed?.(e as Error)\n return\n }\n\n const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry)\n\n if (typeof mergedConfig.maxRetries === 'undefined' || mergedConfig.maxRetries <= 0) {\n debug('worker %s: job %s has no retries configured, marking as failed', this.#id, job.id)\n await this.#adapter.failJob(job.id, queue, e as Error, retention.removeOnFail)\n await instance.failed?.(e as Error)\n return\n }\n\n if (job.attempts >= mergedConfig.maxRetries!) {\n debug(\n 'worker %s: job %s has exceeded max retries (%d), marking as failed',\n this.#id,\n job.id,\n mergedConfig.maxRetries\n )\n await this.#adapter.failJob(job.id, queue, e as Error, retention.removeOnFail)\n const exception = new errors.E_JOB_MAX_ATTEMPTS_REACHED([job.name], { cause: e })\n await instance.failed?.(exception)\n\n return\n }\n\n if (mergedConfig.backoff) {\n const strategy = mergedConfig.backoff()\n const nextRetryAt = strategy.getNextRetryAt(job.attempts + 1)\n\n debug('worker %s: job %s will retry at %s', this.#id, job.id, nextRetryAt.toISOString())\n\n await this.#adapter.retryJob(job.id, queue, nextRetryAt)\n return\n }\n\n await this.#adapter.retryJob(job.id, queue)\n }\n }\n\n async #initJob(\n job: AcquiredJob,\n queue: string\n ): Promise<{\n instance: Job\n options: JobOptions\n timeout: number | undefined\n context: JobContext\n payload: any\n }> {\n try {\n const JobClass = Locator.getOrThrow(job.name)\n\n const context: JobContext = {\n jobId: job.id,\n name: job.name,\n attempt: job.attempts + 1,\n queue,\n priority: job.priority ?? DEFAULT_PRIORITY,\n acquiredAt: new Date(job.acquiredAt),\n stalledCount: job.stalledCount ?? 0,\n }\n\n const jobFactory = QueueManager.getJobFactory()\n const instance = jobFactory ? await jobFactory(JobClass) : new JobClass()\n const options = JobClass.options || {}\n const timeout = this.#getJobTimeout(options)\n\n return { instance, options, timeout, context, payload: job.payload }\n } catch (error) {\n debug('worker %s: failed to initialize job %s (%s)', this.#id, job.id, job.name)\n const retention = QueueManager.getMergedJobOptions(queue)\n await this.#adapter.failJob(job.id, queue, error as Error, retention.removeOnFail)\n throw error\n }\n }\n\n #getJobTimeout(options: JobOptions): number | undefined {\n if (options.timeout !== undefined) {\n return parse(options.timeout)\n }\n\n if (this.#config.worker?.timeout !== undefined) {\n return parse(this.#config.worker.timeout)\n }\n\n return undefined\n }\n\n async #executeWithTimeout(\n instance: Job,\n payload: any,\n context: JobContext,\n timeout?: number\n ): Promise<void> {\n if (!timeout) {\n instance.$hydrate(payload, context)\n return instance.execute()\n }\n\n const signal = AbortSignal.timeout(timeout)\n instance.$hydrate(payload, context, signal)\n\n const abortPromise = new Promise<never>((_, reject) => {\n signal.addEventListener('abort', () => {\n reject(new errors.E_JOB_TIMEOUT([instance.constructor.name, timeout]))\n })\n })\n\n await Promise.race([instance.execute(), abortPromise])\n }\n\n async #acquireNextJob(queues: string[]): Promise<{ job: AcquiredJob; queue: string } | null> {\n for (const queue of queues) {\n const job = await this.#adapter.popFrom(queue)\n\n if (!job) {\n continue\n }\n\n debug('worker %s: acquired job %s', this.#id, job.id)\n return { job, queue }\n }\n\n return null\n }\n\n async #checkStalledJobs(queues: string[]): Promise<void> {\n const now = Date.now()\n\n // Only check if enough time has passed since last check\n if (now - this.#lastStalledCheck < this.#stalledInterval) {\n return\n }\n\n this.#lastStalledCheck = now\n\n for (const queue of queues) {\n const recovered = await this.#adapter.recoverStalledJobs(\n queue,\n this.#stalledThreshold,\n this.#maxStalledCount\n )\n\n if (recovered > 0) {\n debug('worker %s: recovered %d stalled jobs from queue %s', this.#id, recovered, queue)\n }\n }\n }\n\n #setupGracefulShutdown() {\n if (!this.#gracefulShutdown) {\n return\n }\n\n this.#shutdownHandler = async () => {\n debug('received shutdown signal, stopping worker...')\n\n if (this.#onShutdownSignal) {\n await this.#onShutdownSignal()\n }\n\n await this.stop()\n }\n\n process.on('SIGINT', this.#shutdownHandler)\n process.on('SIGTERM', this.#shutdownHandler)\n }\n\n #removeShutdownHandlers() {\n if (this.#shutdownHandler) {\n process.off('SIGINT', this.#shutdownHandler)\n process.off('SIGTERM', this.#shutdownHandler)\n this.#shutdownHandler = undefined\n }\n }\n\n /**\n * Dispatch any due scheduled jobs.\n *\n * Claims due schedules from the adapter and dispatches the corresponding\n * jobs to their configured queues.\n */\n async #dispatchDueSchedules(): Promise<void> {\n // Keep claiming due schedules until there are none left\n while (true) {\n const schedule = await this.#adapter.claimDueSchedule()\n\n if (!schedule) {\n break\n }\n\n debug(\n 'worker %s: dispatching scheduled job %s (schedule: %s, runCount: %d)',\n this.#id,\n schedule.name,\n schedule.id,\n schedule.runCount + 1\n )\n\n // Get the job class to determine the target queue\n const JobClass = Locator.get(schedule.name)\n const queue = JobClass?.options?.queue ?? 'default'\n\n // Dispatch the job to the queue\n await this.#adapter.pushOn(queue, {\n id: randomUUID(),\n name: schedule.name,\n payload: schedule.payload,\n attempts: 0,\n priority: JobClass?.options?.priority,\n })\n }\n }\n}\n","import type { AcquiredJob } from './contracts/adapter.js'\n\n/**\n * Entry representing an active job in the pool.\n */\ninterface PoolEntry {\n /** Promise that resolves when the job completes */\n promise: Promise<void>\n /** The acquired job data */\n job: AcquiredJob\n /** The queue this job came from */\n queue: string\n}\n\n/**\n * Manages concurrent job execution with a fixed pool size.\n *\n * The pool tracks running jobs and returns the first one to complete,\n * allowing maximum throughput regardless of individual job duration:\n *\n * ```\n * Job A: ████████████████████░░░░░░░░░░ (slow - 10s)\n * Job B: ████ done (fast - 100ms) ← returns first\n * Job C: ████████████░░░░░░░░░░░░░░░░░░ (medium - 2s)\n * ↑\n * Slot freed, new job can start immediately\n * ```\n *\n * Key insight: slow jobs don't block the pool. As soon as any job\n * completes, its slot becomes available for new work.\n */\nexport class JobPool {\n #activeJobs = new Map<string, PoolEntry>()\n\n /** Number of currently running jobs */\n get size() {\n return this.#activeJobs.size\n }\n\n /**\n * Check if the pool has no running jobs.\n *\n * @returns True if no jobs are running\n */\n isEmpty() {\n return this.#activeJobs.size === 0\n }\n\n /**\n * Check if the pool can accept more jobs.\n *\n * @param concurrency - Maximum number of concurrent jobs\n * @returns True if there's room for more jobs\n */\n hasCapacity(concurrency: number) {\n return this.#activeJobs.size < concurrency\n }\n\n /**\n * Add a job to the pool.\n *\n * @param job - The acquired job data\n * @param queue - The queue the job came from\n * @param promise - Promise that resolves when the job completes\n */\n add(job: AcquiredJob, queue: string, promise: Promise<void>) {\n this.#activeJobs.set(job.id, { promise, job, queue })\n }\n\n /**\n * Wait for the next job to complete and return it.\n *\n * Uses `Promise.race()` internally, so the fastest job wins.\n * The completed job is removed from the pool.\n *\n * @returns The first job to complete (success or failure)\n */\n async waitForNextCompletion(): Promise<PoolEntry> {\n const completedJobId = await Promise.race(\n [...this.#activeJobs.entries()].map(async ([id, { promise }]) => {\n try {\n await promise\n } catch {\n // Errors are handled in Worker#execute\n }\n return id\n })\n )\n\n const completed = this.#activeJobs.get(completedJobId)!\n this.#activeJobs.delete(completedJobId)\n\n return completed\n }\n\n /**\n * Wait for all running jobs to complete.\n *\n * Used during graceful shutdown to ensure no jobs are abandoned.\n * Clears the pool after all jobs finish.\n */\n async drain(): Promise<void> {\n const promises = [...this.#activeJobs.values()].map(async ({ promise }) => {\n try {\n await promise\n } catch {\n // Errors are handled in Worker#execute\n }\n })\n\n await Promise.all(promises)\n this.#activeJobs.clear()\n }\n}\n","import { QueueManager } from './queue_manager.js'\nimport { JobDispatcher } from './job_dispatcher.js'\nimport type { ScheduleData, ScheduleListOptions, ScheduleStatus } from './types/main.js'\n\n/**\n * Represents a persisted job schedule.\n *\n * Use `Schedule.find()` or `Schedule.list()` to retrieve schedules,\n * then use instance methods to manage them.\n *\n * @example\n * ```typescript\n * const schedule = await Schedule.find('cleanup-daily')\n * if (schedule) {\n * await schedule.pause()\n * // Later...\n * await schedule.resume()\n * }\n *\n * // List all active schedules\n * const activeSchedules = await Schedule.list({ status: 'active' })\n * ```\n */\nexport class Schedule {\n readonly #data: ScheduleData\n\n constructor(data: ScheduleData) {\n this.#data = data\n }\n\n get id(): string {\n return this.#data.id\n }\n\n get name(): string {\n return this.#data.name\n }\n\n get payload(): any {\n return this.#data.payload\n }\n\n get cronExpression(): string | null {\n return this.#data.cronExpression\n }\n\n get everyMs(): number | null {\n return this.#data.everyMs\n }\n\n get timezone(): string {\n return this.#data.timezone\n }\n\n get from(): Date | null {\n return this.#data.from\n }\n\n get to(): Date | null {\n return this.#data.to\n }\n\n get limit(): number | null {\n return this.#data.limit\n }\n\n get runCount(): number {\n return this.#data.runCount\n }\n\n get nextRunAt(): Date | null {\n return this.#data.nextRunAt\n }\n\n get lastRunAt(): Date | null {\n return this.#data.lastRunAt\n }\n\n get status(): ScheduleStatus {\n return this.#data.status\n }\n\n get createdAt(): Date {\n return this.#data.createdAt\n }\n\n /**\n * Find a schedule by ID.\n *\n * @param id - The schedule ID\n * @returns The schedule instance, or null if not found\n */\n static async find(id: string): Promise<Schedule | null> {\n const adapter = QueueManager.use()\n const data = await adapter.getSchedule(id)\n\n if (!data) return null\n\n return new Schedule(data)\n }\n\n /**\n * List all schedules matching the given options.\n *\n * @param options - Optional filters for listing\n * @returns Array of schedule instances\n */\n static async list(options?: ScheduleListOptions): Promise<Schedule[]> {\n const adapter = QueueManager.use()\n const schedules = await adapter.listSchedules(options)\n\n return schedules.map((data) => new Schedule(data))\n }\n\n /**\n * Pause this schedule.\n * No jobs will be dispatched while paused.\n */\n async pause(): Promise<void> {\n const adapter = QueueManager.use()\n await adapter.updateSchedule(this.#data.id, { status: 'paused' })\n this.#data.status = 'paused'\n }\n\n /**\n * Resume this schedule.\n * Jobs will be dispatched according to the schedule.\n */\n async resume(): Promise<void> {\n const adapter = QueueManager.use()\n await adapter.updateSchedule(this.#data.id, { status: 'active' })\n this.#data.status = 'active'\n }\n\n /**\n * Delete this schedule permanently.\n */\n async delete(): Promise<void> {\n const adapter = QueueManager.use()\n await adapter.deleteSchedule(this.#data.id)\n }\n\n /**\n * Trigger immediate execution of this schedule's job.\n * Also updates runCount and lastRunAt.\n *\n * If the schedule has reached its limit, the job will not be dispatched.\n */\n async trigger(): Promise<void> {\n // Check if limit is reached\n if (this.#data.limit !== null && this.#data.runCount >= this.#data.limit) {\n return\n }\n\n const adapter = QueueManager.use()\n\n // Dispatch the job\n const dispatcher = new JobDispatcher(this.#data.name, this.#data.payload)\n await dispatcher.run()\n\n // Update run metadata\n const now = new Date()\n const newRunCount = this.#data.runCount + 1\n\n await adapter.updateSchedule(this.#data.id, {\n runCount: newRunCount,\n lastRunAt: now,\n })\n\n this.#data.runCount = newRunCount\n this.#data.lastRunAt = now\n }\n}\n","import type { Knex } from 'knex'\n\nexport class QueueSchemaService {\n #connection: Knex\n\n constructor(connection: Knex) {\n this.#connection = connection\n }\n\n /**\n * Creates the jobs table with the default schema.\n * The optional callback allows adding custom columns.\n */\n async createJobsTable(\n tableName: string = 'queue_jobs',\n extend?: (table: Knex.CreateTableBuilder) => void\n ): Promise<void> {\n await this.#connection.schema.createTable(tableName, (table) => {\n table.string('id', 255).notNullable()\n table.string('queue', 255).notNullable()\n table.enu('status', ['pending', 'active', 'delayed', 'completed', 'failed']).notNullable()\n table.text('data').notNullable()\n table.bigint('score').unsigned().nullable()\n table.string('worker_id', 255).nullable()\n table.bigint('acquired_at').unsigned().nullable()\n table.bigint('execute_at').unsigned().nullable()\n table.bigint('finished_at').unsigned().nullable()\n table.text('error').nullable()\n table.primary(['id', 'queue'])\n table.index(['queue', 'status', 'score'])\n table.index(['queue', 'status', 'execute_at'])\n table.index(['queue', 'status', 'finished_at'])\n\n extend?.(table)\n })\n }\n\n /**\n * Creates the schedules table with the default schema.\n * The optional callback allows adding custom columns.\n */\n async createSchedulesTable(\n tableName: string = 'queue_schedules',\n extend?: (table: Knex.CreateTableBuilder) => void\n ): Promise<void> {\n await this.#connection.schema.createTable(tableName, (table) => {\n table.string('id', 255).primary()\n table.string('status', 50).notNullable().defaultTo('active')\n table.string('name', 255).notNullable()\n table.text('payload').notNullable()\n table.string('cron_expression', 255).nullable()\n table.bigint('every_ms').unsigned().nullable()\n table.string('timezone', 100).notNullable().defaultTo('UTC')\n table.timestamp('from_date').nullable()\n table.timestamp('to_date').nullable()\n table.integer('run_limit').unsigned().nullable()\n table.integer('run_count').unsigned().notNullable().defaultTo(0)\n table.timestamp('next_run_at').nullable()\n table.timestamp('last_run_at').nullable()\n table\n .timestamp('created_at')\n .notNullable()\n .defaultTo(this.#connection.fn.now())\n table.index(['status', 'next_run_at'])\n\n extend?.(table)\n })\n }\n\n /**\n * Drops the jobs table if it exists.\n */\n async dropJobsTable(tableName: string = 'queue_jobs'): Promise<void> {\n await this.#connection.schema.dropTableIfExists(tableName)\n }\n\n /**\n * Drops the schedules table if it exists.\n */\n async dropSchedulesTable(tableName: string = 'queue_schedules'): Promise<void> {\n await this.#connection.schema.dropTableIfExists(tableName)\n }\n}\n","import type { BackoffConfig, Duration } from '../types/main.js'\nimport * as errors from '../exceptions.js'\nimport { parse } from '../utils.js'\nimport { RuntimeException } from '@poppinss/utils'\nimport { assertUnreachable } from '@poppinss/utils/assert'\n\n/**\n * Calculates retry delays using configurable strategies.\n *\n * Supports three built-in strategies:\n * - `exponential`: Delay doubles each attempt (1s, 2s, 4s, 8s, ...)\n * - `linear`: Delay increases linearly (5s, 10s, 15s, 20s, ...)\n * - `fixed`: Same delay every time (10s, 10s, 10s, ...)\n *\n * All strategies support:\n * - `maxDelay`: Cap the maximum delay\n * - `jitter`: Add randomness to prevent thundering herd\n *\n * @example\n * ```typescript\n * const strategy = new BackoffStrategy({\n * strategy: 'exponential',\n * baseDelay: '1s',\n * maxDelay: '5m',\n * multiplier: 2,\n * jitter: true,\n * })\n *\n * strategy.calculateDelay(1) // ~1000ms\n * strategy.calculateDelay(2) // ~2000ms\n * strategy.calculateDelay(3) // ~4000ms\n * ```\n */\nexport class BackoffStrategy {\n readonly #config: BackoffConfig\n\n /**\n * Create a new backoff strategy.\n *\n * @param config - Backoff configuration\n * @throws {E_INVALID_BASE_DELAY} If baseDelay is not positive\n * @throws {E_INVALID_MAX_DELAY} If maxDelay is invalid\n * @throws {E_INVALID_MULTIPLIER} If multiplier is invalid\n */\n constructor(config: BackoffConfig) {\n this.#config = config\n this.#validateConfig()\n }\n\n /**\n * Calculate the delay for a given attempt number.\n *\n * @param attempt - The attempt number (1-based)\n * @returns Delay in milliseconds\n * @throws {RuntimeException} If attempt is less than 1\n *\n * @example\n * ```typescript\n * // Exponential: 1s, 2s, 4s, 8s, 16s, ...\n * strategy.calculateDelay(1) // 1000\n * strategy.calculateDelay(2) // 2000\n * strategy.calculateDelay(3) // 4000\n * ```\n */\n calculateDelay(attempt: number): number {\n if (attempt < 1) {\n throw new RuntimeException('Attempt number must be >= 1')\n }\n\n const baseDelayMs = parse(this.#config.baseDelay)\n const maxDelayMs = this.#config.maxDelay ? parse(this.#config.maxDelay) : Infinity\n const multiplier = this.#config.multiplier ?? 2\n\n let delay: number\n\n switch (this.#config.strategy) {\n case 'exponential':\n delay = baseDelayMs * Math.pow(multiplier, attempt - 1)\n break\n case 'linear':\n delay = baseDelayMs * attempt\n break\n case 'fixed':\n delay = baseDelayMs\n break\n default:\n assertUnreachable(this.#config.strategy)\n }\n\n // Apply max delay limit\n delay = Math.min(delay, maxDelayMs)\n\n if (this.#config.jitter) {\n delay = this.#applyJitter(delay)\n }\n\n return Math.floor(delay)\n }\n\n /**\n * Get the Date when the next retry should occur.\n *\n * @param attempt - The attempt number (1-based)\n * @returns Date for the next retry\n *\n * @example\n * ```typescript\n * const nextRetry = strategy.getNextRetryAt(3)\n * console.log(`Retry at: ${nextRetry.toISOString()}`)\n * ```\n */\n getNextRetryAt(attempt: number): Date {\n const delay = this.calculateDelay(attempt)\n return new Date(Date.now() + delay)\n }\n\n /**\n * Get a frozen copy of the configuration.\n *\n * @returns Readonly configuration object\n */\n getConfig(): Readonly<BackoffConfig> {\n return Object.freeze({ ...this.#config })\n }\n\n #validateConfig() {\n const baseDelayMs = parse(this.#config.baseDelay)\n\n if (baseDelayMs <= 0) {\n throw new errors.E_INVALID_BASE_DELAY([\n 'Base delay must be a positive integer greater than zero',\n ])\n }\n\n if (this.#config.maxDelay) {\n const maxDelayMs = parse(this.#config.maxDelay)\n\n if (maxDelayMs <= 0) {\n throw new errors.E_INVALID_MAX_DELAY([\n 'Max delay must be a positive integer greater than zero',\n ])\n }\n\n if (maxDelayMs <= baseDelayMs) {\n throw new errors.E_INVALID_MAX_DELAY(['Max delay should be greater than base delay'])\n }\n }\n\n if (this.#config.multiplier !== undefined) {\n if (this.#config.multiplier <= 0) {\n throw new errors.E_INVALID_MULTIPLIER([\n 'Multiplier must be a positive number greater than zero',\n ])\n }\n\n if (this.#config.strategy === 'exponential' && this.#config.multiplier < 1) {\n throw new errors.E_INVALID_MULTIPLIER(['Exponential strategy multiplier should be >= 1'])\n }\n }\n }\n\n #applyJitter(delay: number): number {\n const jitterRange = delay * 0.25\n const jitter = (Math.random() - 0.5) * 2 * jitterRange\n\n return Math.max(0, delay + jitter)\n }\n}\n\n/**\n * Create an exponential backoff strategy factory.\n *\n * Delay doubles with each attempt: 1s → 2s → 4s → 8s → ...\n *\n * Default config:\n * - baseDelay: 1s\n * - maxDelay: 5m\n * - multiplier: 2\n * - jitter: true\n *\n * @param config - Optional overrides for default config\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 5,\n * backoff: exponentialBackoff({ baseDelay: '500ms', maxDelay: '1m' }),\n * },\n * }\n * ```\n */\nexport function exponentialBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'exponential',\n baseDelay: '1s',\n maxDelay: '5m',\n multiplier: 2,\n jitter: true,\n ...config,\n })\n}\n\n/**\n * Create a linear backoff strategy factory.\n *\n * Delay increases linearly: 5s → 10s → 15s → 20s → ...\n *\n * Default config:\n * - baseDelay: 5s\n * - maxDelay: 2m\n *\n * @param config - Optional overrides for default config\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 3,\n * backoff: linearBackoff({ baseDelay: '10s' }),\n * },\n * }\n * ```\n */\nexport function linearBackoff(config?: Partial<Omit<BackoffConfig, 'strategy'>>) {\n return () =>\n new BackoffStrategy({\n strategy: 'linear',\n baseDelay: '5s',\n maxDelay: '2m',\n ...config,\n })\n}\n\n/**\n * Create a fixed delay backoff strategy factory.\n *\n * Same delay every time: 10s → 10s → 10s → ...\n *\n * @param delay - The fixed delay (default: '10s')\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 3,\n * backoff: fixedBackoff('30s'),\n * },\n * }\n * ```\n */\nexport function fixedBackoff(delay: Duration = '10s') {\n return () =>\n new BackoffStrategy({\n strategy: 'fixed',\n baseDelay: delay,\n })\n}\n\n/**\n * Create a custom backoff strategy factory.\n *\n * Use this when you need full control over the configuration.\n *\n * @param config - Complete backoff configuration\n * @returns Factory function for creating BackoffStrategy instances\n *\n * @example\n * ```typescript\n * const config = {\n * retry: {\n * maxRetries: 5,\n * backoff: customBackoff({\n * strategy: 'exponential',\n * baseDelay: '100ms',\n * maxDelay: '30s',\n * multiplier: 3,\n * jitter: false,\n * }),\n * },\n * }\n * ```\n */\nexport function customBackoff(config: BackoffConfig) {\n return () => new BackoffStrategy(config)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -20,7 +20,6 @@ var KnexAdapter = class {
|
|
|
20
20
|
#schedulesTable;
|
|
21
21
|
#ownsConnection;
|
|
22
22
|
#workerId = "";
|
|
23
|
-
#initialized = false;
|
|
24
23
|
constructor(config) {
|
|
25
24
|
this.#connection = config.connection;
|
|
26
25
|
this.#jobsTable = config.tableName ?? "queue_jobs";
|
|
@@ -30,66 +29,6 @@ var KnexAdapter = class {
|
|
|
30
29
|
setWorkerId(workerId) {
|
|
31
30
|
this.#workerId = workerId;
|
|
32
31
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Ensure all required tables exist.
|
|
35
|
-
* Creates them if not exists, handles race conditions.
|
|
36
|
-
*/
|
|
37
|
-
async #ensureTables() {
|
|
38
|
-
if (this.#initialized) return;
|
|
39
|
-
await Promise.all([this.#createJobsTable(), this.#createSchedulesTable()]);
|
|
40
|
-
this.#initialized = true;
|
|
41
|
-
}
|
|
42
|
-
async #createJobsTable() {
|
|
43
|
-
try {
|
|
44
|
-
await this.#connection.schema.createTable(this.#jobsTable, (table) => {
|
|
45
|
-
table.string("id", 255).notNullable();
|
|
46
|
-
table.string("queue", 255).notNullable();
|
|
47
|
-
table.enu("status", ["pending", "active", "delayed", "completed", "failed"]).notNullable();
|
|
48
|
-
table.text("data").notNullable();
|
|
49
|
-
table.bigint("score").unsigned().nullable();
|
|
50
|
-
table.string("worker_id", 255).nullable();
|
|
51
|
-
table.bigint("acquired_at").unsigned().nullable();
|
|
52
|
-
table.bigint("execute_at").unsigned().nullable();
|
|
53
|
-
table.bigint("finished_at").unsigned().nullable();
|
|
54
|
-
table.text("error").nullable();
|
|
55
|
-
table.primary(["id", "queue"]);
|
|
56
|
-
table.index(["queue", "status", "score"]);
|
|
57
|
-
table.index(["queue", "status", "execute_at"]);
|
|
58
|
-
table.index(["queue", "status", "finished_at"]);
|
|
59
|
-
});
|
|
60
|
-
} catch {
|
|
61
|
-
const hasTable = await this.#connection.schema.hasTable(this.#jobsTable);
|
|
62
|
-
if (!hasTable) {
|
|
63
|
-
throw new Error(`Failed to create table "${this.#jobsTable}"`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
async #createSchedulesTable() {
|
|
68
|
-
try {
|
|
69
|
-
await this.#connection.schema.createTable(this.#schedulesTable, (table) => {
|
|
70
|
-
table.string("id", 255).primary();
|
|
71
|
-
table.string("status", 50).notNullable().defaultTo("active");
|
|
72
|
-
table.string("name", 255).notNullable();
|
|
73
|
-
table.text("payload").notNullable();
|
|
74
|
-
table.string("cron_expression", 255).nullable();
|
|
75
|
-
table.bigint("every_ms").unsigned().nullable();
|
|
76
|
-
table.string("timezone", 100).notNullable().defaultTo("UTC");
|
|
77
|
-
table.timestamp("from_date").nullable();
|
|
78
|
-
table.timestamp("to_date").nullable();
|
|
79
|
-
table.integer("run_limit").unsigned().nullable();
|
|
80
|
-
table.integer("run_count").unsigned().notNullable().defaultTo(0);
|
|
81
|
-
table.timestamp("next_run_at").nullable();
|
|
82
|
-
table.timestamp("last_run_at").nullable();
|
|
83
|
-
table.timestamp("created_at").notNullable().defaultTo(this.#connection.fn.now());
|
|
84
|
-
table.index(["status", "next_run_at"]);
|
|
85
|
-
});
|
|
86
|
-
} catch {
|
|
87
|
-
const hasTable = await this.#connection.schema.hasTable(this.#schedulesTable);
|
|
88
|
-
if (!hasTable) {
|
|
89
|
-
throw new Error(`Failed to create table "${this.#schedulesTable}"`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
32
|
async destroy() {
|
|
94
33
|
if (this.#ownsConnection) {
|
|
95
34
|
await this.#connection.destroy();
|
|
@@ -99,7 +38,6 @@ var KnexAdapter = class {
|
|
|
99
38
|
return this.popFrom("default");
|
|
100
39
|
}
|
|
101
40
|
async popFrom(queue) {
|
|
102
|
-
await this.#ensureTables();
|
|
103
41
|
const now = Date.now();
|
|
104
42
|
await this.#processDelayedJobs(queue, now);
|
|
105
43
|
return this.#connection.transaction(async (trx) => {
|
|
@@ -160,7 +98,6 @@ var KnexAdapter = class {
|
|
|
160
98
|
});
|
|
161
99
|
}
|
|
162
100
|
async completeJob(jobId, queue, removeOnComplete) {
|
|
163
|
-
await this.#ensureTables();
|
|
164
101
|
const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete);
|
|
165
102
|
if (!keep) {
|
|
166
103
|
await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).where("status", "active").delete();
|
|
@@ -179,7 +116,6 @@ var KnexAdapter = class {
|
|
|
179
116
|
await this.#pruneHistory(queue, "completed", maxAge, maxCount, now);
|
|
180
117
|
}
|
|
181
118
|
async failJob(jobId, queue, error, removeOnFail) {
|
|
182
|
-
await this.#ensureTables();
|
|
183
119
|
const { keep, maxAge, maxCount } = resolveRetention(removeOnFail);
|
|
184
120
|
if (!keep) {
|
|
185
121
|
await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).where("status", "active").delete();
|
|
@@ -199,7 +135,6 @@ var KnexAdapter = class {
|
|
|
199
135
|
await this.#pruneHistory(queue, "failed", maxAge, maxCount, now);
|
|
200
136
|
}
|
|
201
137
|
async getJob(jobId, queue) {
|
|
202
|
-
await this.#ensureTables();
|
|
203
138
|
const row = await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).first();
|
|
204
139
|
if (!row) {
|
|
205
140
|
return null;
|
|
@@ -223,7 +158,6 @@ var KnexAdapter = class {
|
|
|
223
158
|
}
|
|
224
159
|
}
|
|
225
160
|
async retryJob(jobId, queue, retryAt) {
|
|
226
|
-
await this.#ensureTables();
|
|
227
161
|
const now = Date.now();
|
|
228
162
|
const activeJob = await this.#connection(this.#jobsTable).where("id", jobId).where("queue", queue).where("status", "active").first();
|
|
229
163
|
if (!activeJob) return;
|
|
@@ -256,7 +190,6 @@ var KnexAdapter = class {
|
|
|
256
190
|
return this.pushOn("default", jobData);
|
|
257
191
|
}
|
|
258
192
|
async pushOn(queue, jobData) {
|
|
259
|
-
await this.#ensureTables();
|
|
260
193
|
const priority = jobData.priority ?? DEFAULT_PRIORITY;
|
|
261
194
|
const timestamp = Date.now();
|
|
262
195
|
const score = calculateScore(priority, timestamp);
|
|
@@ -272,7 +205,6 @@ var KnexAdapter = class {
|
|
|
272
205
|
return this.pushLaterOn("default", jobData, delay);
|
|
273
206
|
}
|
|
274
207
|
async pushLaterOn(queue, jobData, delay) {
|
|
275
|
-
await this.#ensureTables();
|
|
276
208
|
const executeAt = Date.now() + delay;
|
|
277
209
|
await this.#connection(this.#jobsTable).insert({
|
|
278
210
|
id: jobData.id,
|
|
@@ -287,7 +219,6 @@ var KnexAdapter = class {
|
|
|
287
219
|
}
|
|
288
220
|
async pushManyOn(queue, jobs) {
|
|
289
221
|
if (jobs.length === 0) return;
|
|
290
|
-
await this.#ensureTables();
|
|
291
222
|
const now = Date.now();
|
|
292
223
|
const rows = jobs.map((job) => ({
|
|
293
224
|
id: job.id,
|
|
@@ -302,12 +233,10 @@ var KnexAdapter = class {
|
|
|
302
233
|
return this.sizeOf("default");
|
|
303
234
|
}
|
|
304
235
|
async sizeOf(queue) {
|
|
305
|
-
await this.#ensureTables();
|
|
306
236
|
const result = await this.#connection(this.#jobsTable).where("queue", queue).where("status", "pending").count("* as count").first();
|
|
307
237
|
return Number(result?.count ?? 0);
|
|
308
238
|
}
|
|
309
239
|
async recoverStalledJobs(queue, stalledThreshold, maxStalledCount) {
|
|
310
|
-
await this.#ensureTables();
|
|
311
240
|
const now = Date.now();
|
|
312
241
|
const stalledCutoff = now - stalledThreshold;
|
|
313
242
|
return this.#connection.transaction(async (trx) => {
|
|
@@ -340,7 +269,6 @@ var KnexAdapter = class {
|
|
|
340
269
|
});
|
|
341
270
|
}
|
|
342
271
|
async createSchedule(config) {
|
|
343
|
-
await this.#ensureTables();
|
|
344
272
|
const id = config.id ?? randomUUID();
|
|
345
273
|
const data = {
|
|
346
274
|
id,
|
|
@@ -372,13 +300,11 @@ var KnexAdapter = class {
|
|
|
372
300
|
return id;
|
|
373
301
|
}
|
|
374
302
|
async getSchedule(id) {
|
|
375
|
-
await this.#ensureTables();
|
|
376
303
|
const row = await this.#connection(this.#schedulesTable).where("id", id).first();
|
|
377
304
|
if (!row) return null;
|
|
378
305
|
return this.#rowToScheduleData(row);
|
|
379
306
|
}
|
|
380
307
|
async listSchedules(options) {
|
|
381
|
-
await this.#ensureTables();
|
|
382
308
|
let query = this.#connection(this.#schedulesTable).whereNot("status", "cancelled");
|
|
383
309
|
if (options?.status) {
|
|
384
310
|
query = query.where("status", options.status);
|
|
@@ -387,7 +313,6 @@ var KnexAdapter = class {
|
|
|
387
313
|
return rows.map((row) => this.#rowToScheduleData(row));
|
|
388
314
|
}
|
|
389
315
|
async updateSchedule(id, updates) {
|
|
390
|
-
await this.#ensureTables();
|
|
391
316
|
const data = {};
|
|
392
317
|
if (updates.status !== void 0) data.status = updates.status;
|
|
393
318
|
if (updates.nextRunAt !== void 0) data.next_run_at = updates.nextRunAt;
|
|
@@ -398,11 +323,9 @@ var KnexAdapter = class {
|
|
|
398
323
|
}
|
|
399
324
|
}
|
|
400
325
|
async deleteSchedule(id) {
|
|
401
|
-
await this.#ensureTables();
|
|
402
326
|
await this.#connection(this.#schedulesTable).where("id", id).delete();
|
|
403
327
|
}
|
|
404
328
|
async claimDueSchedule() {
|
|
405
|
-
await this.#ensureTables();
|
|
406
329
|
const now = /* @__PURE__ */ new Date();
|
|
407
330
|
return this.#connection.transaction(async (trx) => {
|
|
408
331
|
let query = trx(this.#schedulesTable).where("status", "active").whereNotNull("next_run_at").where("next_run_at", "<=", now).where((builder) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobData,\n JobRecord,\n JobRetention,\n JobStatus,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore, resolveRetention } from '../utils.js'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n schedulesTableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #jobsTable: string\n readonly #schedulesTable: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n #initialized: boolean = false\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#jobsTable = config.tableName ?? 'queue_jobs'\n this.#schedulesTable = config.schedulesTableName ?? 'queue_schedules'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n /**\n * Ensure all required tables exist.\n * Creates them if not exists, handles race conditions.\n */\n async #ensureTables(): Promise<void> {\n if (this.#initialized) return\n\n await Promise.all([this.#createJobsTable(), this.#createSchedulesTable()])\n\n this.#initialized = true\n }\n\n async #createJobsTable(): Promise<void> {\n try {\n await this.#connection.schema.createTable(this.#jobsTable, (table) => {\n table.string('id', 255).notNullable()\n table.string('queue', 255).notNullable()\n table.enu('status', ['pending', 'active', 'delayed', 'completed', 'failed']).notNullable()\n table.text('data').notNullable()\n table.bigint('score').unsigned().nullable()\n table.string('worker_id', 255).nullable()\n table.bigint('acquired_at').unsigned().nullable()\n table.bigint('execute_at').unsigned().nullable()\n table.bigint('finished_at').unsigned().nullable()\n table.text('error').nullable()\n table.primary(['id', 'queue'])\n table.index(['queue', 'status', 'score'])\n table.index(['queue', 'status', 'execute_at'])\n table.index(['queue', 'status', 'finished_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#jobsTable)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#jobsTable}\"`)\n }\n }\n }\n\n async #createSchedulesTable(): Promise<void> {\n try {\n await this.#connection.schema.createTable(this.#schedulesTable, (table) => {\n table.string('id', 255).primary()\n table.string('status', 50).notNullable().defaultTo('active')\n table.string('name', 255).notNullable()\n table.text('payload').notNullable()\n table.string('cron_expression', 255).nullable()\n table.bigint('every_ms').unsigned().nullable()\n table.string('timezone', 100).notNullable().defaultTo('UTC')\n table.timestamp('from_date').nullable()\n table.timestamp('to_date').nullable()\n table.integer('run_limit').unsigned().nullable()\n table.integer('run_count').unsigned().notNullable().defaultTo(0)\n table.timestamp('next_run_at').nullable()\n table.timestamp('last_run_at').nullable()\n table.timestamp('created_at').notNullable().defaultTo(this.#connection.fn.now())\n // Indexes\n table.index(['status', 'next_run_at'])\n })\n } catch {\n /**\n * If table creation fails, verify the table actually exists.\n * This handles race conditions where multiple instances try to create\n * the table simultaneously.\n */\n const hasTable = await this.#connection.schema.hasTable(this.#schedulesTable)\n if (!hasTable) {\n throw new Error(`Failed to create table \"${this.#schedulesTable}\"`)\n }\n }\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n await this.#ensureTables()\n\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Build the query for highest priority job (lowest score)\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const job = await query.first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n // For SQLite (no SKIP LOCKED), add status='pending' guard to prevent double-claim\n const updateQuery = trx(this.#jobsTable).where('id', job.id).where('queue', queue)\n\n if (!this.#supportsSkipLocked()) {\n updateQuery.where('status', 'pending')\n }\n\n const updated = await updateQuery.update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n // Another worker already claimed this job\n if (updated === 0) {\n return null\n }\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n /**\n * Check if the database supports FOR UPDATE SKIP LOCKED.\n * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.\n * SQLite does not, but it's single-writer so it doesn't need it.\n */\n #supportsSkipLocked(): boolean {\n const client = this.#connection.client.config.client\n return client === 'pg' || client === 'mysql' || client === 'mysql2' || client === 'mariadb'\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Use a transaction with row locking to prevent race conditions\n await this.#connection.transaction(async (trx) => {\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const delayedJobs = await query\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable).where('id', job.id).where('queue', queue).update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n })\n }\n\n async completeJob(jobId: string, queue: string, removeOnComplete?: JobRetention): Promise<void> {\n await this.#ensureTables()\n\n const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'completed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'completed', maxAge, maxCount, now)\n }\n\n async failJob(\n jobId: string,\n queue: string,\n error?: Error,\n removeOnFail?: JobRetention\n ): Promise<void> {\n await this.#ensureTables()\n\n const { keep, maxAge, maxCount } = resolveRetention(removeOnFail)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'failed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n error: error?.message || null,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'failed', maxAge, maxCount, now)\n }\n\n async getJob(jobId: string, queue: string): Promise<JobRecord | null> {\n await this.#ensureTables()\n\n const row = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .first()\n\n if (!row) {\n return null\n }\n\n const jobData: JobData = JSON.parse(row.data)\n\n return {\n status: row.status as JobStatus,\n data: jobData,\n finishedAt: row.finished_at ? Number(row.finished_at) : undefined,\n error: row.error || undefined,\n }\n }\n\n async #pruneHistory(\n queue: string,\n status: 'completed' | 'failed',\n maxAge: number,\n maxCount: number,\n now: number\n ): Promise<void> {\n if (maxAge > 0) {\n const cutoff = now - maxAge\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .where('finished_at', '<', cutoff)\n .delete()\n }\n\n if (maxCount > 0) {\n const toKeep = this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .orderBy('finished_at', 'desc')\n .limit(maxCount)\n .select('id')\n\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .whereNotIn('id', toKeep)\n .delete()\n }\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n await this.#ensureTables()\n\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#jobsTable).where('id', jobId).where('queue', queue).update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await this.#connection(this.#jobsTable).where('id', jobId).where('queue', queue).update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n await this.#ensureTables()\n\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n await this.#ensureTables()\n\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n if (jobs.length === 0) return\n\n await this.#ensureTables()\n\n const now = Date.now()\n const rows = jobs.map((job) => ({\n id: job.id,\n queue,\n status: 'pending' as const,\n data: JSON.stringify(job),\n score: calculateScore(job.priority ?? DEFAULT_PRIORITY, now),\n }))\n\n await this.#connection(this.#jobsTable).insert(rows)\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n await this.#ensureTables()\n\n const result = await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n await this.#ensureTables()\n\n const now = Date.now()\n const stalledCutoff = now - stalledThreshold\n\n // Use a transaction with row locking to prevent race conditions\n return this.#connection.transaction(async (trx) => {\n let recovered = 0\n\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'active')\n .where('acquired_at', '<', stalledCutoff)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const stalledJobs = await query\n\n for (const row of stalledJobs) {\n const jobData: JobData = JSON.parse(row.data)\n const currentStalledCount = jobData.stalledCount ?? 0\n\n if (currentStalledCount >= maxStalledCount) {\n // Fail permanently - remove the job\n await trx(this.#jobsTable).where('id', row.id).where('queue', queue).delete()\n } else {\n // Recover: increment stalledCount and put back in pending\n jobData.stalledCount = currentStalledCount + 1\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: JSON.stringify(jobData),\n worker_id: null,\n acquired_at: null,\n score,\n })\n\n recovered++\n }\n }\n\n return recovered\n })\n }\n\n async createSchedule(config: ScheduleConfig): Promise<string> {\n await this.#ensureTables()\n\n const id = config.id ?? randomUUID()\n\n const data = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n cron_expression: config.cronExpression ?? null,\n every_ms: config.everyMs ?? null,\n timezone: config.timezone,\n from_date: config.from ?? null,\n to_date: config.to ?? null,\n run_limit: config.limit ?? null,\n status: 'active',\n }\n\n // Atomic upsert\n await this.#connection(this.#schedulesTable)\n .insert({\n ...data,\n run_count: 0,\n created_at: this.#connection.fn.now(),\n })\n .onConflict('id')\n .merge({\n name: data.name,\n payload: data.payload,\n cron_expression: data.cron_expression,\n every_ms: data.every_ms,\n timezone: data.timezone,\n from_date: data.from_date,\n to_date: data.to_date,\n run_limit: data.run_limit,\n status: 'active',\n })\n\n return id\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n await this.#ensureTables()\n\n const row = await this.#connection(this.#schedulesTable).where('id', id).first()\n if (!row) return null\n\n return this.#rowToScheduleData(row)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n await this.#ensureTables()\n\n let query = this.#connection(this.#schedulesTable).whereNot('status', 'cancelled')\n\n if (options?.status) {\n query = query.where('status', options.status)\n }\n\n const rows = await query\n return rows.map((row: any) => this.#rowToScheduleData(row))\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n await this.#ensureTables()\n\n const data: Record<string, any> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) data.next_run_at = updates.nextRunAt\n if (updates.lastRunAt !== undefined) data.last_run_at = updates.lastRunAt\n if (updates.runCount !== undefined) data.run_count = updates.runCount\n\n if (Object.keys(data).length > 0) {\n await this.#connection(this.#schedulesTable).where('id', id).update(data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n await this.#ensureTables()\n\n await this.#connection(this.#schedulesTable).where('id', id).delete()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n await this.#ensureTables()\n\n const now = new Date()\n\n return this.#connection.transaction(async (trx) => {\n // Find one due schedule with row locking\n let query = trx(this.#schedulesTable)\n .where('status', 'active')\n .whereNotNull('next_run_at')\n .where('next_run_at', '<=', now)\n .where((builder) => {\n builder.whereNull('run_limit').orWhereRaw('run_count < run_limit')\n })\n .where((builder) => {\n builder.whereNull('to_date').orWhere('to_date', '>=', now)\n })\n .orderBy('next_run_at', 'asc')\n .limit(1)\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const row = await query.first()\n if (!row) return null\n\n // Calculate next run time\n let nextRunAt: Date | null = null\n const newRunCount = (row.run_count ?? 0) + 1\n\n if (row.every_ms) {\n nextRunAt = new Date(now.getTime() + Number(row.every_ms))\n } else if (row.cron_expression) {\n // Import cron-parser dynamically to calculate next run\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(row.cron_expression, {\n currentDate: now,\n tz: row.timezone || 'UTC',\n })\n nextRunAt = cron.next().toDate()\n }\n\n // Check if limit will be reached\n if (row.run_limit !== null && newRunCount >= row.run_limit) {\n nextRunAt = null\n }\n\n // Check if past end date\n if (nextRunAt && row.to_date && nextRunAt > new Date(row.to_date)) {\n nextRunAt = null\n }\n\n // Update atomically\n await trx(this.#schedulesTable).where('id', row.id).update({\n next_run_at: nextRunAt,\n last_run_at: now,\n run_count: newRunCount,\n })\n\n // Return schedule data (before update state for payload)\n return this.#rowToScheduleData(row)\n })\n }\n\n #rowToScheduleData(row: any): ScheduleData {\n return {\n id: row.id,\n name: row.name,\n payload: typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n cronExpression: row.cron_expression ?? null,\n everyMs: row.every_ms ? Number(row.every_ms) : null,\n timezone: row.timezone ?? 'UTC',\n from: row.from_date ? new Date(row.from_date) : null,\n to: row.to_date ? new Date(row.to_date) : null,\n limit: row.run_limit ? Number(row.run_limit) : null,\n runCount: Number(row.run_count ?? 0),\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,\n status: row.status === 'cancelled' ? 'paused' : row.status,\n createdAt: row.created_at ? new Date(row.created_at) : new Date(),\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobData,\n JobRecord,\n JobRetention,\n JobStatus,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore, resolveRetention } from '../utils.js'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n schedulesTableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #jobsTable: string\n readonly #schedulesTable: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#jobsTable = config.tableName ?? 'queue_jobs'\n this.#schedulesTable = config.schedulesTableName ?? 'queue_schedules'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Build the query for highest priority job (lowest score)\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const job = await query.first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n // For SQLite (no SKIP LOCKED), add status='pending' guard to prevent double-claim\n const updateQuery = trx(this.#jobsTable)\n .where('id', job.id)\n .where('queue', queue)\n\n if (!this.#supportsSkipLocked()) {\n updateQuery.where('status', 'pending')\n }\n\n const updated = await updateQuery.update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n // Another worker already claimed this job\n if (updated === 0) {\n return null\n }\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n /**\n * Check if the database supports FOR UPDATE SKIP LOCKED.\n * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.\n * SQLite does not, but it's single-writer so it doesn't need it.\n */\n #supportsSkipLocked(): boolean {\n const client = this.#connection.client.config.client\n return client === 'pg' || client === 'mysql' || client === 'mysql2' || client === 'mariadb'\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Use a transaction with row locking to prevent race conditions\n await this.#connection.transaction(async (trx) => {\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const delayedJobs = await query\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n })\n }\n\n async completeJob(jobId: string, queue: string, removeOnComplete?: JobRetention): Promise<void> {\n const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'completed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'completed', maxAge, maxCount, now)\n }\n\n async failJob(\n jobId: string,\n queue: string,\n error?: Error,\n removeOnFail?: JobRetention\n ): Promise<void> {\n const { keep, maxAge, maxCount } = resolveRetention(removeOnFail)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'failed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n error: error?.message || null,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'failed', maxAge, maxCount, now)\n }\n\n async getJob(jobId: string, queue: string): Promise<JobRecord | null> {\n const row = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .first()\n\n if (!row) {\n return null\n }\n\n const jobData: JobData = JSON.parse(row.data)\n\n return {\n status: row.status as JobStatus,\n data: jobData,\n finishedAt: row.finished_at ? Number(row.finished_at) : undefined,\n error: row.error || undefined,\n }\n }\n\n async #pruneHistory(\n queue: string,\n status: 'completed' | 'failed',\n maxAge: number,\n maxCount: number,\n now: number\n ): Promise<void> {\n if (maxAge > 0) {\n const cutoff = now - maxAge\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .where('finished_at', '<', cutoff)\n .delete()\n }\n\n if (maxCount > 0) {\n const toKeep = this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .orderBy('finished_at', 'desc')\n .limit(maxCount)\n .select('id')\n\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .whereNotIn('id', toKeep)\n .delete()\n }\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n if (jobs.length === 0) return\n\n const now = Date.now()\n const rows = jobs.map((job) => ({\n id: job.id,\n queue,\n status: 'pending' as const,\n data: JSON.stringify(job),\n score: calculateScore(job.priority ?? DEFAULT_PRIORITY, now),\n }))\n\n await this.#connection(this.#jobsTable).insert(rows)\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n const result = await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const now = Date.now()\n const stalledCutoff = now - stalledThreshold\n\n // Use a transaction with row locking to prevent race conditions\n return this.#connection.transaction(async (trx) => {\n let recovered = 0\n\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'active')\n .where('acquired_at', '<', stalledCutoff)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const stalledJobs = await query\n\n for (const row of stalledJobs) {\n const jobData: JobData = JSON.parse(row.data)\n const currentStalledCount = jobData.stalledCount ?? 0\n\n if (currentStalledCount >= maxStalledCount) {\n // Fail permanently - remove the job\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .delete()\n } else {\n // Recover: increment stalledCount and put back in pending\n jobData.stalledCount = currentStalledCount + 1\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: JSON.stringify(jobData),\n worker_id: null,\n acquired_at: null,\n score,\n })\n\n recovered++\n }\n }\n\n return recovered\n })\n }\n\n async createSchedule(config: ScheduleConfig): Promise<string> {\n const id = config.id ?? randomUUID()\n\n const data = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n cron_expression: config.cronExpression ?? null,\n every_ms: config.everyMs ?? null,\n timezone: config.timezone,\n from_date: config.from ?? null,\n to_date: config.to ?? null,\n run_limit: config.limit ?? null,\n status: 'active',\n }\n\n // Atomic upsert\n await this.#connection(this.#schedulesTable)\n .insert({\n ...data,\n run_count: 0,\n created_at: this.#connection.fn.now(),\n })\n .onConflict('id')\n .merge({\n name: data.name,\n payload: data.payload,\n cron_expression: data.cron_expression,\n every_ms: data.every_ms,\n timezone: data.timezone,\n from_date: data.from_date,\n to_date: data.to_date,\n run_limit: data.run_limit,\n status: 'active',\n })\n\n return id\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n const row = await this.#connection(this.#schedulesTable)\n .where('id', id)\n .first()\n if (!row) return null\n\n return this.#rowToScheduleData(row)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n let query = this.#connection(this.#schedulesTable).whereNot('status', 'cancelled')\n\n if (options?.status) {\n query = query.where('status', options.status)\n }\n\n const rows = await query\n return rows.map((row: any) => this.#rowToScheduleData(row))\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n const data: Record<string, any> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) data.next_run_at = updates.nextRunAt\n if (updates.lastRunAt !== undefined) data.last_run_at = updates.lastRunAt\n if (updates.runCount !== undefined) data.run_count = updates.runCount\n\n if (Object.keys(data).length > 0) {\n await this.#connection(this.#schedulesTable)\n .where('id', id)\n .update(data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n await this.#connection(this.#schedulesTable)\n .where('id', id)\n .delete()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n const now = new Date()\n\n return this.#connection.transaction(async (trx) => {\n // Find one due schedule with row locking\n let query = trx(this.#schedulesTable)\n .where('status', 'active')\n .whereNotNull('next_run_at')\n .where('next_run_at', '<=', now)\n .where((builder) => {\n builder.whereNull('run_limit').orWhereRaw('run_count < run_limit')\n })\n .where((builder) => {\n builder.whereNull('to_date').orWhere('to_date', '>=', now)\n })\n .orderBy('next_run_at', 'asc')\n .limit(1)\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const row = await query.first()\n if (!row) return null\n\n // Calculate next run time\n let nextRunAt: Date | null = null\n const newRunCount = (row.run_count ?? 0) + 1\n\n if (row.every_ms) {\n nextRunAt = new Date(now.getTime() + Number(row.every_ms))\n } else if (row.cron_expression) {\n // Import cron-parser dynamically to calculate next run\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(row.cron_expression, {\n currentDate: now,\n tz: row.timezone || 'UTC',\n })\n nextRunAt = cron.next().toDate()\n }\n\n // Check if limit will be reached\n if (row.run_limit !== null && newRunCount >= row.run_limit) {\n nextRunAt = null\n }\n\n // Check if past end date\n if (nextRunAt && row.to_date && nextRunAt > new Date(row.to_date)) {\n nextRunAt = null\n }\n\n // Update atomically\n await trx(this.#schedulesTable)\n .where('id', row.id)\n .update({\n next_run_at: nextRunAt,\n last_run_at: now,\n run_count: newRunCount,\n })\n\n // Return schedule data (before update state for payload)\n return this.#rowToScheduleData(row)\n })\n }\n\n #rowToScheduleData(row: any): ScheduleData {\n return {\n id: row.id,\n name: row.name,\n payload: typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n cronExpression: row.cron_expression ?? null,\n everyMs: row.every_ms ? Number(row.every_ms) : null,\n timezone: row.timezone ?? 'UTC',\n from: row.from_date ? new Date(row.from_date) : null,\n to: row.to_date ? new Date(row.to_date) : null,\n limit: row.run_limit ? Number(row.run_limit) : null,\n runCount: Number(row.run_count ?? 0),\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,\n status: row.status === 'cancelled' ? 'paused' : row.status,\n createdAt: row.created_at ? new Date(row.created_at) : new Date(),\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boringnode/queue",
|
|
3
3
|
"description": "A simple and efficient queue system for Node.js applications",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
"benchmark:full": "node --import=@poppinss/ts-exec benchmark/run.ts --full",
|
|
20
20
|
"build": "yarn clean && tsup-node",
|
|
21
21
|
"clean": "del-cli build",
|
|
22
|
-
"
|
|
23
|
-
"lint": "
|
|
22
|
+
"lint": "oxlint",
|
|
23
|
+
"lint:fix": "oxlint --fix",
|
|
24
|
+
"format": "oxfmt --check .",
|
|
25
|
+
"format:fix": "oxfmt --write .",
|
|
24
26
|
"prepublishOnly": "yarn build",
|
|
25
27
|
"release": "yarn dlx release-it",
|
|
26
28
|
"test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts --reporters=dot",
|
|
@@ -33,8 +35,6 @@
|
|
|
33
35
|
"cron-parser": "^5.4.0"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
|
-
"@adonisjs/eslint-config": "^2.1.2",
|
|
37
|
-
"@adonisjs/prettier-config": "^1.4.5",
|
|
38
38
|
"@adonisjs/tsconfig": "^2.0.0-next.3",
|
|
39
39
|
"@japa/assert": "^4.1.1",
|
|
40
40
|
"@japa/expect-type": "^2.0.3",
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
"bullmq": "^5.65.1",
|
|
49
49
|
"c8": "^10.1.3",
|
|
50
50
|
"del-cli": "^7.0.0",
|
|
51
|
-
"eslint": "^9.35.0",
|
|
52
51
|
"ioredis": "^5.7.0",
|
|
53
52
|
"knex": "^3.1.0",
|
|
53
|
+
"oxfmt": "^0.26.0",
|
|
54
|
+
"oxlint": "^1.41.0",
|
|
55
|
+
"oxlint-tsgolint": "^0.11.1",
|
|
54
56
|
"pg": "^8.16.3",
|
|
55
|
-
"prettier": "^3.6.2",
|
|
56
57
|
"release-it": "^19.0.4",
|
|
57
|
-
"testcontainers": "^11.5.1",
|
|
58
58
|
"tsup": "^8.5.1",
|
|
59
59
|
"typescript": "^5.9.2"
|
|
60
60
|
},
|
|
@@ -79,7 +79,6 @@
|
|
|
79
79
|
"worker",
|
|
80
80
|
"redis"
|
|
81
81
|
],
|
|
82
|
-
"prettier": "@adonisjs/prettier-config",
|
|
83
82
|
"publishConfig": {
|
|
84
83
|
"access": "public",
|
|
85
84
|
"tag": "latest"
|
|
@@ -96,7 +95,7 @@
|
|
|
96
95
|
"web": true
|
|
97
96
|
}
|
|
98
97
|
},
|
|
99
|
-
"packageManager": "yarn@4.
|
|
98
|
+
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
|
|
100
99
|
"engines": {
|
|
101
100
|
"node": ">=24.0.0"
|
|
102
101
|
}
|