@boringnode/queue 0.0.1-alpha.2 → 0.0.1-alpha.4
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 +145 -34
- package/build/chunk-5PDDRF5O.js +26 -0
- package/build/chunk-5PDDRF5O.js.map +1 -0
- package/build/chunk-CD45GT6E.js +357 -0
- package/build/chunk-CD45GT6E.js.map +1 -0
- package/build/chunk-HMGNQSSG.js +103 -0
- package/build/chunk-HMGNQSSG.js.map +1 -0
- package/build/index-C3_tlebh.d.ts +776 -0
- package/build/index.d.ts +352 -4
- package/build/index.js +526 -137
- package/build/index.js.map +1 -1
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/knex_adapter.d.ts +2 -1
- package/build/src/drivers/knex_adapter.js +76 -17
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/build/src/drivers/redis_adapter.d.ts +2 -2
- package/build/src/drivers/redis_adapter.js +74 -27
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/build/src/drivers/sync_adapter.d.ts +7 -3
- package/build/src/drivers/sync_adapter.js +27 -7
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/types/index.d.ts +1 -0
- package/build/src/types/index.js +1 -0
- package/build/src/types/index.js.map +1 -0
- package/build/src/types/main.d.ts +1 -1
- package/package.json +20 -15
- package/build/job-Bd_c2lFK.d.ts +0 -149
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Create a job by extending the `Job` class:
|
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
30
|
import { Job } from '@boringnode/queue'
|
|
31
|
-
import type { JobOptions } from '@boringnode/queue/types
|
|
31
|
+
import type { JobContext, JobOptions } from '@boringnode/queue/types'
|
|
32
32
|
|
|
33
33
|
interface SendEmailPayload {
|
|
34
34
|
to: string
|
|
@@ -41,8 +41,12 @@ export default class SendEmailJob extends Job<SendEmailPayload> {
|
|
|
41
41
|
queue: 'email',
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
constructor(payload: SendEmailPayload, context: JobContext) {
|
|
45
|
+
super(payload, context)
|
|
46
|
+
}
|
|
47
|
+
|
|
44
48
|
async execute(): Promise<void> {
|
|
45
|
-
console.log(`Sending email to: ${this.payload.to}`)
|
|
49
|
+
console.log(`[Attempt ${this.context.attempt}] Sending email to: ${this.payload.to}`)
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
```
|
|
@@ -72,7 +76,7 @@ const config = {
|
|
|
72
76
|
|
|
73
77
|
worker: {
|
|
74
78
|
concurrency: 5,
|
|
75
|
-
|
|
79
|
+
idleDelay: '2s',
|
|
76
80
|
},
|
|
77
81
|
|
|
78
82
|
locations: ['./app/jobs/**/*.ts'],
|
|
@@ -227,10 +231,10 @@ Schedule jobs to run in the future:
|
|
|
227
231
|
|
|
228
232
|
```typescript
|
|
229
233
|
// Various time formats
|
|
230
|
-
await SendEmailJob.dispatch(payload).in('30s')
|
|
231
|
-
await SendEmailJob.dispatch(payload).in('5m')
|
|
232
|
-
await SendEmailJob.dispatch(payload).in('2h')
|
|
233
|
-
await SendEmailJob.dispatch(payload).in('1d')
|
|
234
|
+
await SendEmailJob.dispatch(payload).in('30s') // 30 seconds
|
|
235
|
+
await SendEmailJob.dispatch(payload).in('5m') // 5 minutes
|
|
236
|
+
await SendEmailJob.dispatch(payload).in('2h') // 2 hours
|
|
237
|
+
await SendEmailJob.dispatch(payload).in('1d') // 1 day
|
|
234
238
|
```
|
|
235
239
|
|
|
236
240
|
## Priority
|
|
@@ -242,7 +246,7 @@ export default class UrgentJob extends Job<Payload> {
|
|
|
242
246
|
static readonly jobName = 'UrgentJob'
|
|
243
247
|
|
|
244
248
|
static options: JobOptions = {
|
|
245
|
-
priority: 1,
|
|
249
|
+
priority: 1, // Processed before default priority (5)
|
|
246
250
|
}
|
|
247
251
|
|
|
248
252
|
async execute(): Promise<void> {
|
|
@@ -264,12 +268,13 @@ export default class ReliableJob extends Job<Payload> {
|
|
|
264
268
|
static options: JobOptions = {
|
|
265
269
|
maxRetries: 5,
|
|
266
270
|
retry: {
|
|
267
|
-
backoff: () =>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
backoff: () =>
|
|
272
|
+
exponentialBackoff({
|
|
273
|
+
baseDelay: '1s',
|
|
274
|
+
maxDelay: '1m',
|
|
275
|
+
multiplier: 2,
|
|
276
|
+
jitter: true,
|
|
277
|
+
}),
|
|
273
278
|
},
|
|
274
279
|
}
|
|
275
280
|
|
|
@@ -294,7 +299,7 @@ export default class LimitedJob extends Job<Payload> {
|
|
|
294
299
|
static readonly jobName = 'LimitedJob'
|
|
295
300
|
|
|
296
301
|
static options: JobOptions = {
|
|
297
|
-
timeout: '30s',
|
|
302
|
+
timeout: '30s', // Maximum execution time
|
|
298
303
|
failOnTimeout: false, // Retry on timeout (default)
|
|
299
304
|
}
|
|
300
305
|
|
|
@@ -309,48 +314,154 @@ You can also set a global timeout in the worker configuration:
|
|
|
309
314
|
```typescript
|
|
310
315
|
const config = {
|
|
311
316
|
worker: {
|
|
312
|
-
timeout: '1m',
|
|
317
|
+
timeout: '1m', // Default timeout for all jobs
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Job Context
|
|
323
|
+
|
|
324
|
+
Every job has access to execution context via `this.context`. This provides metadata about the current job execution:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { Job } from '@boringnode/queue'
|
|
328
|
+
import type { JobContext } from '@boringnode/queue'
|
|
329
|
+
|
|
330
|
+
export default class MyJob extends Job<Payload> {
|
|
331
|
+
constructor(payload: Payload, context: JobContext) {
|
|
332
|
+
super(payload, context)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async execute(): Promise<void> {
|
|
336
|
+
console.log(`Job ID: ${this.context.jobId}`)
|
|
337
|
+
console.log(`Attempt: ${this.context.attempt}`) // 1, 2, 3...
|
|
338
|
+
console.log(`Queue: ${this.context.queue}`)
|
|
339
|
+
console.log(`Priority: ${this.context.priority}`)
|
|
340
|
+
console.log(`Acquired at: ${this.context.acquiredAt}`)
|
|
341
|
+
|
|
342
|
+
if (this.context.attempt > 1) {
|
|
343
|
+
console.log('This is a retry!')
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Context Properties
|
|
350
|
+
|
|
351
|
+
| Property | Type | Description |
|
|
352
|
+
| -------------- | ------ | ----------------------------------------------- |
|
|
353
|
+
| `jobId` | string | Unique identifier for this job |
|
|
354
|
+
| `name` | string | Job class name |
|
|
355
|
+
| `attempt` | number | Current attempt number (1-based) |
|
|
356
|
+
| `queue` | string | Queue name this job is being processed from |
|
|
357
|
+
| `priority` | number | Job priority (lower = higher priority) |
|
|
358
|
+
| `acquiredAt` | Date | When this job was acquired by the worker |
|
|
359
|
+
| `stalledCount` | number | Times this job was recovered from stalled state |
|
|
360
|
+
|
|
361
|
+
## Dependency Injection
|
|
362
|
+
|
|
363
|
+
Use the `jobFactory` option to integrate with IoC containers for dependency injection. This allows your jobs to receive injected services in their constructor.
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import { QueueManager } from '@boringnode/queue'
|
|
367
|
+
|
|
368
|
+
await QueueManager.init({
|
|
369
|
+
default: 'redis',
|
|
370
|
+
adapters: { redis: redis(connection) },
|
|
371
|
+
jobFactory: async (JobClass, payload, context) => {
|
|
372
|
+
// Use your IoC container to instantiate jobs
|
|
373
|
+
return app.container.make(JobClass, [payload, context])
|
|
313
374
|
},
|
|
375
|
+
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Example with injected dependencies:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { Job } from '@boringnode/queue'
|
|
382
|
+
import type { JobContext } from '@boringnode/queue'
|
|
383
|
+
|
|
384
|
+
interface SendEmailPayload {
|
|
385
|
+
to: string
|
|
386
|
+
subject: string
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export default class SendEmailJob extends Job<SendEmailPayload> {
|
|
390
|
+
static readonly jobName = 'SendEmailJob'
|
|
391
|
+
|
|
392
|
+
constructor(
|
|
393
|
+
payload: SendEmailPayload,
|
|
394
|
+
context: JobContext,
|
|
395
|
+
private mailer: MailerService, // Injected by IoC container
|
|
396
|
+
private logger: Logger // Injected by IoC container
|
|
397
|
+
) {
|
|
398
|
+
super(payload, context)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async execute(): Promise<void> {
|
|
402
|
+
this.logger.info(`[Attempt ${this.context.attempt}] Sending email to ${this.payload.to}`)
|
|
403
|
+
await this.mailer.send(this.payload)
|
|
404
|
+
}
|
|
314
405
|
}
|
|
315
406
|
```
|
|
316
407
|
|
|
408
|
+
Without a `jobFactory`, jobs are instantiated with `new JobClass(payload, context)`.
|
|
409
|
+
|
|
317
410
|
## Job Discovery
|
|
318
411
|
|
|
319
412
|
The queue manager automatically discovers and registers jobs from the specified locations:
|
|
320
413
|
|
|
321
414
|
```typescript
|
|
322
415
|
const config = {
|
|
323
|
-
locations: [
|
|
324
|
-
'./app/jobs/**/*.ts',
|
|
325
|
-
'./modules/**/jobs/**/*.ts',
|
|
326
|
-
],
|
|
416
|
+
locations: ['./app/jobs/**/*.ts', './modules/**/jobs/**/*.ts'],
|
|
327
417
|
}
|
|
328
418
|
```
|
|
329
419
|
|
|
330
420
|
Jobs must:
|
|
421
|
+
|
|
331
422
|
- Extend the `Job` class
|
|
332
423
|
- Have a static `jobName` property
|
|
333
424
|
- Implement the `execute` method
|
|
334
425
|
- Be exported as default
|
|
335
426
|
|
|
427
|
+
## Logging
|
|
428
|
+
|
|
429
|
+
You can pass a logger to the queue manager for debugging or monitoring. The logger must be compatible with the [pino](https://github.com/pinojs/pino) interface.
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import { pino } from 'pino'
|
|
433
|
+
|
|
434
|
+
const config = {
|
|
435
|
+
default: 'redis',
|
|
436
|
+
adapters: {
|
|
437
|
+
/* ... */
|
|
438
|
+
},
|
|
439
|
+
logger: pino(),
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
await QueueManager.init(config)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
By default, a simple console logger is used that only outputs warnings and errors.
|
|
446
|
+
|
|
336
447
|
## Benchmarks
|
|
337
448
|
|
|
338
449
|
Performance comparison with BullMQ using realistic jobs (5ms simulated work per job):
|
|
339
450
|
|
|
340
|
-
| Jobs | Concurrency | @boringnode/queue | BullMQ | Diff
|
|
341
|
-
|
|
342
|
-
| 100 | 1 | 562ms | 596ms | 5.7% faster
|
|
343
|
-
| 100 | 5 | 116ms | 117ms | ~same
|
|
344
|
-
| 100 | 10 | 62ms | 62ms | ~same
|
|
345
|
-
| 500 | 1 | 2728ms | 2798ms | 2.5% faster
|
|
346
|
-
| 500 | 5 | 565ms | 565ms | ~same
|
|
347
|
-
| 500 | 10 | 287ms | 288ms | ~same
|
|
348
|
-
| 1000 | 1 | 5450ms | 5547ms | 1.7% faster
|
|
349
|
-
| 1000 | 5 | 1096ms | 1116ms | 1.8% faster
|
|
350
|
-
| 1000 | 10 | 565ms | 579ms | 2.4% faster
|
|
351
|
-
| 100K | 5 | 110.5s | 112.3s | 1.5% faster
|
|
352
|
-
| 100K | 10 | 56.2s | 57.5s | 2.1% faster
|
|
353
|
-
| 100K | 20 | 29.1s | 29.6s | 1.7% faster
|
|
451
|
+
| Jobs | Concurrency | @boringnode/queue | BullMQ | Diff |
|
|
452
|
+
| ---- | ----------- | ----------------- | ------ | ----------- |
|
|
453
|
+
| 100 | 1 | 562ms | 596ms | 5.7% faster |
|
|
454
|
+
| 100 | 5 | 116ms | 117ms | ~same |
|
|
455
|
+
| 100 | 10 | 62ms | 62ms | ~same |
|
|
456
|
+
| 500 | 1 | 2728ms | 2798ms | 2.5% faster |
|
|
457
|
+
| 500 | 5 | 565ms | 565ms | ~same |
|
|
458
|
+
| 500 | 10 | 287ms | 288ms | ~same |
|
|
459
|
+
| 1000 | 1 | 5450ms | 5547ms | 1.7% faster |
|
|
460
|
+
| 1000 | 5 | 1096ms | 1116ms | 1.8% faster |
|
|
461
|
+
| 1000 | 10 | 565ms | 579ms | 2.4% faster |
|
|
462
|
+
| 100K | 5 | 110.5s | 112.3s | 1.5% faster |
|
|
463
|
+
| 100K | 10 | 56.2s | 57.5s | 2.1% faster |
|
|
464
|
+
| 100K | 20 | 29.1s | 29.6s | 1.7% faster |
|
|
354
465
|
|
|
355
466
|
Run benchmarks yourself:
|
|
356
467
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E_INVALID_DURATION_EXPRESSION,
|
|
3
|
+
PRIORITY_SCORE_MULTIPLIER
|
|
4
|
+
} from "./chunk-HMGNQSSG.js";
|
|
5
|
+
|
|
6
|
+
// src/utils.ts
|
|
7
|
+
import { parse as parseDuration } from "@lukeed/ms";
|
|
8
|
+
function parse(duration) {
|
|
9
|
+
if (typeof duration === "number") {
|
|
10
|
+
return duration;
|
|
11
|
+
}
|
|
12
|
+
const milliseconds = parseDuration(duration);
|
|
13
|
+
if (typeof milliseconds === "undefined") {
|
|
14
|
+
throw new E_INVALID_DURATION_EXPRESSION([duration]);
|
|
15
|
+
}
|
|
16
|
+
return milliseconds;
|
|
17
|
+
}
|
|
18
|
+
function calculateScore(priority, timestamp) {
|
|
19
|
+
return priority * PRIORITY_SCORE_MULTIPLIER + timestamp;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
parse,
|
|
24
|
+
calculateScore
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=chunk-5PDDRF5O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import { parse as parseDuration } from '@lukeed/ms'\nimport type { Duration } from './types/main.js'\nimport * as errors from './exceptions.js'\nimport { PRIORITY_SCORE_MULTIPLIER } from './constants.js'\n\nexport function parse(duration: Duration): number {\n if (typeof duration === 'number') {\n return duration\n }\n\n const milliseconds = parseDuration(duration)\n\n if (typeof milliseconds === 'undefined') {\n throw new errors.E_INVALID_DURATION_EXPRESSION([duration])\n }\n\n return milliseconds\n}\n\n/**\n * Calculate the score for job ordering in the queue.\n * Lower scores are processed first.\n *\n * @param priority - Job priority (1-10, lower = higher priority)\n * @param timestamp - Timestamp in milliseconds\n * @returns Score for queue ordering\n */\nexport function calculateScore(priority: number, timestamp: number): number {\n return priority * PRIORITY_SCORE_MULTIPLIER + timestamp\n}\n"],"mappings":";;;;;;AAAA,SAAS,SAAS,qBAAqB;AAKhC,SAAS,MAAM,UAA4B;AAChD,MAAI,OAAO,aAAa,UAAU;AAChC,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,cAAc,QAAQ;AAE3C,MAAI,OAAO,iBAAiB,aAAa;AACvC,UAAM,IAAW,8BAA8B,CAAC,QAAQ,CAAC;AAAA,EAC3D;AAEA,SAAO;AACT;AAUO,SAAS,eAAe,UAAkB,WAA2B;AAC1E,SAAO,WAAW,4BAA4B;AAChD;","names":[]}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E_ADAPTER_INIT_ERROR,
|
|
3
|
+
E_CONFIGURATION_ERROR,
|
|
4
|
+
E_JOB_NOT_FOUND,
|
|
5
|
+
E_QUEUE_NOT_INITIALIZED
|
|
6
|
+
} from "./chunk-HMGNQSSG.js";
|
|
7
|
+
|
|
8
|
+
// src/debug.ts
|
|
9
|
+
import { debuglog } from "util";
|
|
10
|
+
var debug_default = debuglog("boringnode:queue");
|
|
11
|
+
|
|
12
|
+
// src/locator.ts
|
|
13
|
+
import { glob } from "fs/promises";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
var LocatorSingleton = class {
|
|
16
|
+
#registry = /* @__PURE__ */ new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Register a job class with a given name.
|
|
19
|
+
*
|
|
20
|
+
* @param name - The job name (usually the class name)
|
|
21
|
+
* @param JobClass - The job class constructor
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* Locator.register('SendEmailJob', SendEmailJob)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
register(name, JobClass) {
|
|
29
|
+
debug_default("registering job: %s", name);
|
|
30
|
+
this.#registry.set(name, JobClass);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Auto-register job classes from files matching glob patterns.
|
|
34
|
+
*
|
|
35
|
+
* Each file should have a default export that is a Job class.
|
|
36
|
+
* The class name is used as the registration name.
|
|
37
|
+
*
|
|
38
|
+
* @param patterns - Glob patterns to match job files
|
|
39
|
+
* @returns Number of jobs successfully registered
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const count = await Locator.registerFromGlob([
|
|
44
|
+
* './jobs/**\/*.js',
|
|
45
|
+
* './app/jobs/**\/*.ts'
|
|
46
|
+
* ])
|
|
47
|
+
* console.log(`Registered ${count} jobs`)
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
async registerFromGlob(patterns) {
|
|
51
|
+
let registered = 0;
|
|
52
|
+
for (const pattern of patterns) {
|
|
53
|
+
debug_default("registering jobs from glob pattern: %s", pattern);
|
|
54
|
+
for await (const file of glob(pattern)) {
|
|
55
|
+
debug_default("found job file: %s", file);
|
|
56
|
+
try {
|
|
57
|
+
const absolutePath = resolve(file);
|
|
58
|
+
const module = await import(`file://${absolutePath}`);
|
|
59
|
+
const JobClass = module.default;
|
|
60
|
+
if (JobClass && typeof JobClass === "function" && JobClass.name) {
|
|
61
|
+
this.register(JobClass.name, JobClass);
|
|
62
|
+
registered++;
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn(`Failed to load job from ${file}:`, error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return registered;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get a job class by name.
|
|
73
|
+
*
|
|
74
|
+
* @param name - The job name to look up
|
|
75
|
+
* @returns The job class, or undefined if not found
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const JobClass = Locator.get('SendEmailJob')
|
|
80
|
+
* if (JobClass) {
|
|
81
|
+
* const instance = new JobClass(payload)
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
get(name) {
|
|
86
|
+
return this.#registry.get(name);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get a job class by name, throwing if not found.
|
|
90
|
+
*
|
|
91
|
+
* @param name - The job name to look up
|
|
92
|
+
* @returns The job class
|
|
93
|
+
* @throws {E_JOB_NOT_FOUND} If the job is not registered
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* const JobClass = Locator.getOrThrow('SendEmailJob')
|
|
98
|
+
* const instance = new JobClass(payload)
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
getOrThrow(name) {
|
|
102
|
+
const JobClass = this.get(name);
|
|
103
|
+
if (!JobClass) {
|
|
104
|
+
throw new E_JOB_NOT_FOUND([name]);
|
|
105
|
+
}
|
|
106
|
+
return JobClass;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Remove all registered jobs.
|
|
110
|
+
*
|
|
111
|
+
* Primarily useful for testing.
|
|
112
|
+
*/
|
|
113
|
+
clear() {
|
|
114
|
+
this.#registry.clear();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var Locator = new LocatorSingleton();
|
|
118
|
+
|
|
119
|
+
// src/logger.ts
|
|
120
|
+
var ConsoleLogger = class _ConsoleLogger {
|
|
121
|
+
#prefix;
|
|
122
|
+
constructor(prefix = "queue") {
|
|
123
|
+
this.#prefix = prefix;
|
|
124
|
+
}
|
|
125
|
+
#format(level, msgOrObj, msg) {
|
|
126
|
+
const prefix = `[${this.#prefix}] ${level}:`;
|
|
127
|
+
if (typeof msgOrObj === "object") {
|
|
128
|
+
return [`${prefix} ${msg}`, msgOrObj];
|
|
129
|
+
}
|
|
130
|
+
return [`${prefix} ${msgOrObj}`];
|
|
131
|
+
}
|
|
132
|
+
trace(msgOrObj, msg) {
|
|
133
|
+
const [message, obj] = this.#format("TRACE", msgOrObj, msg);
|
|
134
|
+
if (obj) {
|
|
135
|
+
return console.log(message, obj);
|
|
136
|
+
}
|
|
137
|
+
console.log(message);
|
|
138
|
+
}
|
|
139
|
+
debug(msgOrObj, msg) {
|
|
140
|
+
const [message, obj] = this.#format("DEBUG", msgOrObj, msg);
|
|
141
|
+
if (obj) {
|
|
142
|
+
return console.log(message, obj);
|
|
143
|
+
}
|
|
144
|
+
console.log(message);
|
|
145
|
+
}
|
|
146
|
+
info(msgOrObj, msg) {
|
|
147
|
+
const [message, obj] = this.#format("INFO", msgOrObj, msg);
|
|
148
|
+
if (obj) {
|
|
149
|
+
return console.log(message, obj);
|
|
150
|
+
}
|
|
151
|
+
console.log(message);
|
|
152
|
+
}
|
|
153
|
+
warn(msgOrObj, msg) {
|
|
154
|
+
const [message, obj] = this.#format("WARN", msgOrObj, msg);
|
|
155
|
+
if (obj) {
|
|
156
|
+
return console.warn(message, obj);
|
|
157
|
+
}
|
|
158
|
+
console.warn(message);
|
|
159
|
+
}
|
|
160
|
+
error(msgOrObj, msg) {
|
|
161
|
+
const [message, obj] = this.#format("ERROR", msgOrObj, msg);
|
|
162
|
+
if (obj) {
|
|
163
|
+
return console.error(message, obj);
|
|
164
|
+
}
|
|
165
|
+
console.error(message);
|
|
166
|
+
}
|
|
167
|
+
child(obj) {
|
|
168
|
+
const childPrefix = obj.pkg ? String(obj.pkg) : this.#prefix;
|
|
169
|
+
return new _ConsoleLogger(childPrefix);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
var consoleLogger = new ConsoleLogger();
|
|
173
|
+
|
|
174
|
+
// src/queue_manager.ts
|
|
175
|
+
var QueueManagerSingleton = class {
|
|
176
|
+
#initialized = false;
|
|
177
|
+
#defaultAdapter;
|
|
178
|
+
#adapters = {};
|
|
179
|
+
#adapterInstances = /* @__PURE__ */ new Map();
|
|
180
|
+
#globalRetryConfig;
|
|
181
|
+
#queueConfigs = /* @__PURE__ */ new Map();
|
|
182
|
+
#logger = consoleLogger;
|
|
183
|
+
#jobFactory;
|
|
184
|
+
/**
|
|
185
|
+
* Initialize the queue system with the given configuration.
|
|
186
|
+
*
|
|
187
|
+
* This must be called before using the queue system. It:
|
|
188
|
+
* - Validates the configuration
|
|
189
|
+
* - Registers adapters
|
|
190
|
+
* - Auto-discovers and registers job classes from `locations`
|
|
191
|
+
*
|
|
192
|
+
* @param config - The queue configuration
|
|
193
|
+
* @returns This instance for chaining
|
|
194
|
+
* @throws {E_CONFIGURATION_ERROR} If the configuration is invalid
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* await QueueManager.init({
|
|
199
|
+
* default: 'redis',
|
|
200
|
+
* adapters: {
|
|
201
|
+
* redis: redis(),
|
|
202
|
+
* postgres: knex(pgConfig),
|
|
203
|
+
* },
|
|
204
|
+
* locations: ['./jobs/**\/*.js'],
|
|
205
|
+
* })
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
async init(config) {
|
|
209
|
+
debug_default("initializing queue manager with config: %O", config);
|
|
210
|
+
this.#validateConfig(config);
|
|
211
|
+
this.#adapterInstances.clear();
|
|
212
|
+
this.#defaultAdapter = config.default;
|
|
213
|
+
this.#adapters = config.adapters;
|
|
214
|
+
this.#globalRetryConfig = config.retry;
|
|
215
|
+
this.#logger = config.logger ?? consoleLogger;
|
|
216
|
+
this.#jobFactory = config.jobFactory;
|
|
217
|
+
if (config.queues) {
|
|
218
|
+
for (const [queue, queueConfig] of Object.entries(config.queues)) {
|
|
219
|
+
this.#queueConfigs.set(queue, queueConfig);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (config.locations && config.locations.length > 0) {
|
|
223
|
+
const registered = await Locator.registerFromGlob(config.locations);
|
|
224
|
+
if (registered === 0) {
|
|
225
|
+
this.#logger.warn(
|
|
226
|
+
`No jobs found for locations: ${config.locations.join(", ")}. Verify your glob patterns match your job files.`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
this.#initialized = true;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get an adapter instance by name.
|
|
235
|
+
*
|
|
236
|
+
* Adapter instances are cached and reused. If no name is provided,
|
|
237
|
+
* the default adapter is returned.
|
|
238
|
+
*
|
|
239
|
+
* @param adapter - Adapter name (optional, defaults to the default adapter)
|
|
240
|
+
* @returns The adapter instance
|
|
241
|
+
* @throws {E_QUEUE_NOT_INITIALIZED} If `init()` hasn't been called
|
|
242
|
+
* @throws {E_CONFIGURATION_ERROR} If the adapter is not registered
|
|
243
|
+
* @throws {E_ADAPTER_INIT_ERROR} If the adapter factory throws
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* // Get default adapter
|
|
248
|
+
* const adapter = QueueManager.use()
|
|
249
|
+
*
|
|
250
|
+
* // Get specific adapter
|
|
251
|
+
* const redisAdapter = QueueManager.use('redis')
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
use(adapter) {
|
|
255
|
+
if (!this.#initialized) {
|
|
256
|
+
throw new E_QUEUE_NOT_INITIALIZED();
|
|
257
|
+
}
|
|
258
|
+
if (!adapter) {
|
|
259
|
+
adapter = this.#defaultAdapter;
|
|
260
|
+
}
|
|
261
|
+
const cached = this.#adapterInstances.get(adapter);
|
|
262
|
+
if (cached) {
|
|
263
|
+
return cached;
|
|
264
|
+
}
|
|
265
|
+
const adapterFactory = this.#adapters[adapter];
|
|
266
|
+
if (!adapterFactory) {
|
|
267
|
+
throw new E_CONFIGURATION_ERROR([`Adapter "${adapter}" is not registered`]);
|
|
268
|
+
}
|
|
269
|
+
debug_default('using adapter "%s"', adapter);
|
|
270
|
+
try {
|
|
271
|
+
const instance = adapterFactory();
|
|
272
|
+
this.#adapterInstances.set(adapter, instance);
|
|
273
|
+
return instance;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
276
|
+
throw new E_ADAPTER_INIT_ERROR([adapter, message]);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the merged retry configuration for a job.
|
|
281
|
+
*
|
|
282
|
+
* Configuration is merged with priority: job > queue > global.
|
|
283
|
+
* This allows specific jobs or queues to override global defaults.
|
|
284
|
+
*
|
|
285
|
+
* @param queue - The queue name
|
|
286
|
+
* @param jobRetryConfig - Optional job-level retry config
|
|
287
|
+
* @returns The merged retry configuration
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* // Global: maxRetries=3, Queue: maxRetries=5, Job: maxRetries=1
|
|
292
|
+
* // Result: maxRetries=1 (job wins)
|
|
293
|
+
* const config = QueueManager.getMergedRetryConfig('emails', { maxRetries: 1 })
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
getMergedRetryConfig(queue, jobRetryConfig) {
|
|
297
|
+
const queueConfig = this.#queueConfigs.get(queue);
|
|
298
|
+
const queueRetryConfig = queueConfig?.retry || {};
|
|
299
|
+
let maxRetries = jobRetryConfig?.maxRetries || queueRetryConfig.maxRetries || this.#globalRetryConfig?.maxRetries || 0;
|
|
300
|
+
let backoff = jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff;
|
|
301
|
+
return { maxRetries, backoff };
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Get the configured job factory for custom instantiation.
|
|
305
|
+
*
|
|
306
|
+
* @returns The job factory function, or undefined if not configured
|
|
307
|
+
*/
|
|
308
|
+
getJobFactory() {
|
|
309
|
+
return this.#jobFactory;
|
|
310
|
+
}
|
|
311
|
+
#validateConfig(config) {
|
|
312
|
+
if (!config.adapters || Object.keys(config.adapters).length === 0) {
|
|
313
|
+
throw new E_CONFIGURATION_ERROR(["At least one adapter must be configured"]);
|
|
314
|
+
}
|
|
315
|
+
if (!config.default) {
|
|
316
|
+
throw new E_CONFIGURATION_ERROR(["Default adapter must be specified"]);
|
|
317
|
+
}
|
|
318
|
+
if (!config.adapters[config.default]) {
|
|
319
|
+
throw new E_CONFIGURATION_ERROR([
|
|
320
|
+
`Default adapter "${config.default}" not found in adapters configuration`
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
323
|
+
for (const [name, factory] of Object.entries(config.adapters)) {
|
|
324
|
+
if (typeof factory !== "function") {
|
|
325
|
+
throw new E_CONFIGURATION_ERROR([`Adapter "${name}" must be a factory function`]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Clean up all adapter instances and reset state.
|
|
331
|
+
*
|
|
332
|
+
* Call this when shutting down the application or when
|
|
333
|
+
* you need to reinitialize with a new configuration.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```typescript
|
|
337
|
+
* // On application shutdown
|
|
338
|
+
* await QueueManager.destroy()
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
async destroy() {
|
|
342
|
+
for (const [name, adapter] of this.#adapterInstances) {
|
|
343
|
+
debug_default('destroying adapter "%s"', name);
|
|
344
|
+
await adapter.destroy();
|
|
345
|
+
}
|
|
346
|
+
this.#adapterInstances.clear();
|
|
347
|
+
this.#initialized = false;
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var QueueManager = new QueueManagerSingleton();
|
|
351
|
+
|
|
352
|
+
export {
|
|
353
|
+
debug_default,
|
|
354
|
+
Locator,
|
|
355
|
+
QueueManager
|
|
356
|
+
};
|
|
357
|
+
//# sourceMappingURL=chunk-CD45GT6E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/debug.ts","../src/locator.ts","../src/logger.ts","../src/queue_manager.ts"],"sourcesContent":["import { debuglog } from 'node:util'\n\nexport default debuglog('boringnode:queue')\n","import { Job } from './job.js'\nimport * as errors from './exceptions.js'\nimport type { JobClass } from './types/main.js'\nimport debug from './debug.js'\nimport { glob } from 'node:fs/promises'\nimport { resolve } from 'node:path'\n\n/**\n * Job class registry.\n *\n * The Locator maintains a mapping of job names to their classes,\n * allowing the Worker to instantiate jobs by name when processing.\n *\n * Jobs are typically registered automatically via `QueueManager.init()`\n * using the `locations` config option, but can also be registered manually.\n *\n * @example\n * ```typescript\n * import { Locator } from '@boringnode/queue'\n * import SendEmailJob from './jobs/send_email_job.js'\n *\n * // Manual registration\n * Locator.register('SendEmailJob', SendEmailJob)\n *\n * // Auto-registration via glob (used by QueueManager.init)\n * await Locator.registerFromGlob(['./jobs/**\\/*.js'])\n *\n * // Retrieve a job class\n * const JobClass = Locator.getOrThrow('SendEmailJob')\n * ```\n */\nclass LocatorSingleton {\n #registry = new Map<string, JobClass>()\n\n /**\n * Register a job class with a given name.\n *\n * @param name - The job name (usually the class name)\n * @param JobClass - The job class constructor\n *\n * @example\n * ```typescript\n * Locator.register('SendEmailJob', SendEmailJob)\n * ```\n */\n register<T extends Job>(name: string, JobClass: JobClass<T>) {\n debug('registering job: %s', name)\n\n this.#registry.set(name, JobClass)\n }\n\n /**\n * Auto-register job classes from files matching glob patterns.\n *\n * Each file should have a default export that is a Job class.\n * The class name is used as the registration name.\n *\n * @param patterns - Glob patterns to match job files\n * @returns Number of jobs successfully registered\n *\n * @example\n * ```typescript\n * const count = await Locator.registerFromGlob([\n * './jobs/**\\/*.js',\n * './app/jobs/**\\/*.ts'\n * ])\n * console.log(`Registered ${count} jobs`)\n * ```\n */\n async registerFromGlob(patterns: string[]): Promise<number> {\n let registered = 0\n\n for (const pattern of patterns) {\n debug('registering jobs from glob pattern: %s', pattern)\n for await (const file of glob(pattern)) {\n debug('found job file: %s', file)\n\n try {\n const absolutePath = resolve(file)\n const module = await import(`file://${absolutePath}`)\n const JobClass = module.default as JobClass\n\n if (JobClass && typeof JobClass === 'function' && JobClass.name) {\n this.register(JobClass.name, JobClass)\n registered++\n }\n } catch (error) {\n console.warn(`Failed to load job from ${file}:`, error)\n }\n }\n }\n\n return registered\n }\n\n /**\n * Get a job class by name.\n *\n * @param name - The job name to look up\n * @returns The job class, or undefined if not found\n *\n * @example\n * ```typescript\n * const JobClass = Locator.get('SendEmailJob')\n * if (JobClass) {\n * const instance = new JobClass(payload)\n * }\n * ```\n */\n get<T extends Job = Job>(name: string): JobClass<T> | undefined {\n return this.#registry.get(name) as JobClass<T> | undefined\n }\n\n /**\n * Get a job class by name, throwing if not found.\n *\n * @param name - The job name to look up\n * @returns The job class\n * @throws {E_JOB_NOT_FOUND} If the job is not registered\n *\n * @example\n * ```typescript\n * const JobClass = Locator.getOrThrow('SendEmailJob')\n * const instance = new JobClass(payload)\n * ```\n */\n getOrThrow<T extends Job = Job>(name: string): JobClass<T> {\n const JobClass = this.get<T>(name)\n\n if (!JobClass) {\n throw new errors.E_JOB_NOT_FOUND([name])\n }\n\n return JobClass\n }\n\n /**\n * Remove all registered jobs.\n *\n * Primarily useful for testing.\n */\n clear(): void {\n this.#registry.clear()\n }\n}\n\n/** Global job class registry singleton */\nexport const Locator = new LocatorSingleton()\n","export interface LogObject {\n [key: string]: unknown\n}\n\nexport interface ErrorObject extends LogObject {\n err?: Error\n}\n\nexport interface Logger {\n trace(msg: string): void\n trace(obj: LogObject, msg: string): void\n\n debug(msg: string): void\n debug(obj: LogObject, msg: string): void\n\n info(msg: string): void\n info(obj: LogObject, msg: string): void\n\n warn(msg: string): void\n warn(obj: LogObject, msg: string): void\n\n error(msg: string): void\n error(obj: ErrorObject, msg: string): void\n\n child(obj: LogObject): Logger\n}\n\n/**\n * A simple logger that writes to console.\n */\nclass ConsoleLogger implements Logger {\n #prefix: string\n\n constructor(prefix: string = 'queue') {\n this.#prefix = prefix\n }\n\n #format(level: string, msgOrObj: string | LogObject, msg?: string): [string, LogObject?] {\n const prefix = `[${this.#prefix}] ${level}:`\n\n if (typeof msgOrObj === 'object') {\n return [`${prefix} ${msg}`, msgOrObj]\n }\n\n return [`${prefix} ${msgOrObj}`]\n }\n\n trace(msg: string): void\n trace(obj: LogObject, msg: string): void\n trace(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('TRACE', msgOrObj, msg)\n\n if (obj) {\n return console.log(message, obj)\n }\n\n console.log(message)\n }\n\n debug(msg: string): void\n debug(obj: LogObject, msg: string): void\n debug(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('DEBUG', msgOrObj, msg)\n\n if (obj) {\n return console.log(message, obj)\n }\n\n console.log(message)\n }\n\n info(msg: string): void\n info(obj: LogObject, msg: string): void\n info(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('INFO', msgOrObj, msg)\n\n if (obj) {\n return console.log(message, obj)\n }\n\n console.log(message)\n }\n\n warn(msg: string): void\n warn(obj: LogObject, msg: string): void\n warn(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('WARN', msgOrObj, msg)\n\n if (obj) {\n return console.warn(message, obj)\n }\n\n console.warn(message)\n }\n\n error(msg: string): void\n error(obj: ErrorObject, msg: string): void\n error(msgOrObj: string | ErrorObject, msg?: string): void {\n const [message, obj] = this.#format('ERROR', msgOrObj, msg)\n\n if (obj) {\n return console.error(message, obj)\n }\n\n console.error(message)\n }\n\n child(obj: LogObject): Logger {\n const childPrefix = obj.pkg ? String(obj.pkg) : this.#prefix\n return new ConsoleLogger(childPrefix)\n }\n}\n\nexport const consoleLogger = new ConsoleLogger()\n","import * as errors from './exceptions.js'\nimport debug from './debug.js'\nimport { Locator } from './locator.js'\nimport { consoleLogger, type Logger } from './logger.js'\nimport type { Adapter } from './contracts/adapter.js'\nimport type {\n AdapterFactory,\n JobFactory,\n QueueConfig,\n QueueManagerConfig,\n RetryConfig,\n} from './types/main.js'\n\n/**\n * Central configuration and adapter management for the queue system.\n *\n * The QueueManager is responsible for:\n * - Initializing adapters and job registration\n * - Providing adapter instances to workers and dispatchers\n * - Managing retry configuration across global, queue, and job levels\n *\n * @example\n * ```typescript\n * import { QueueManager, redis } from '@boringnode/queue'\n *\n * await QueueManager.init({\n * default: 'redis',\n * adapters: {\n * redis: redis({ host: 'localhost' }),\n * },\n * locations: ['./jobs/**\\/*.js'],\n * retry: {\n * maxRetries: 3,\n * backoff: exponentialBackoff(),\n * },\n * })\n *\n * // Get the default adapter\n * const adapter = QueueManager.use()\n *\n * // Clean up when done\n * await QueueManager.destroy()\n * ```\n */\nclass QueueManagerSingleton {\n #initialized = false\n #defaultAdapter!: string\n #adapters: Record<string, AdapterFactory> = {}\n #adapterInstances: Map<string, Adapter> = new Map()\n #globalRetryConfig?: RetryConfig\n #queueConfigs: Map<string, QueueConfig> = new Map()\n #logger: Logger = consoleLogger\n #jobFactory?: JobFactory\n\n /**\n * Initialize the queue system with the given configuration.\n *\n * This must be called before using the queue system. It:\n * - Validates the configuration\n * - Registers adapters\n * - Auto-discovers and registers job classes from `locations`\n *\n * @param config - The queue configuration\n * @returns This instance for chaining\n * @throws {E_CONFIGURATION_ERROR} If the configuration is invalid\n *\n * @example\n * ```typescript\n * await QueueManager.init({\n * default: 'redis',\n * adapters: {\n * redis: redis(),\n * postgres: knex(pgConfig),\n * },\n * locations: ['./jobs/**\\/*.js'],\n * })\n * ```\n */\n async init(config: QueueManagerConfig) {\n debug('initializing queue manager with config: %O', config)\n\n this.#validateConfig(config)\n\n this.#adapterInstances.clear()\n\n this.#defaultAdapter = config.default\n this.#adapters = config.adapters\n this.#globalRetryConfig = config.retry\n this.#logger = config.logger ?? consoleLogger\n this.#jobFactory = config.jobFactory\n\n if (config.queues) {\n for (const [queue, queueConfig] of Object.entries(config.queues)) {\n this.#queueConfigs.set(queue, queueConfig as QueueConfig)\n }\n }\n\n if (config.locations && config.locations.length > 0) {\n const registered = await Locator.registerFromGlob(config.locations)\n\n if (registered === 0) {\n this.#logger.warn(\n `No jobs found for locations: ${config.locations.join(', ')}. ` +\n 'Verify your glob patterns match your job files.'\n )\n }\n }\n\n this.#initialized = true\n\n return this\n }\n\n /**\n * Get an adapter instance by name.\n *\n * Adapter instances are cached and reused. If no name is provided,\n * the default adapter is returned.\n *\n * @param adapter - Adapter name (optional, defaults to the default adapter)\n * @returns The adapter instance\n * @throws {E_QUEUE_NOT_INITIALIZED} If `init()` hasn't been called\n * @throws {E_CONFIGURATION_ERROR} If the adapter is not registered\n * @throws {E_ADAPTER_INIT_ERROR} If the adapter factory throws\n *\n * @example\n * ```typescript\n * // Get default adapter\n * const adapter = QueueManager.use()\n *\n * // Get specific adapter\n * const redisAdapter = QueueManager.use('redis')\n * ```\n */\n use(adapter?: string): Adapter {\n if (!this.#initialized) {\n throw new errors.E_QUEUE_NOT_INITIALIZED()\n }\n\n if (!adapter) {\n adapter = this.#defaultAdapter\n }\n\n // Return cached instance if exists\n const cached = this.#adapterInstances.get(adapter)\n if (cached) {\n return cached\n }\n\n const adapterFactory = this.#adapters[adapter]\n\n if (!adapterFactory) {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${adapter}\" is not registered`])\n }\n\n debug('using adapter \"%s\"', adapter)\n\n try {\n const instance = adapterFactory()\n this.#adapterInstances.set(adapter, instance)\n return instance\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new errors.E_ADAPTER_INIT_ERROR([adapter, message])\n }\n }\n\n /**\n * Get the merged retry configuration for a job.\n *\n * Configuration is merged with priority: job > queue > global.\n * This allows specific jobs or queues to override global defaults.\n *\n * @param queue - The queue name\n * @param jobRetryConfig - Optional job-level retry config\n * @returns The merged retry configuration\n *\n * @example\n * ```typescript\n * // Global: maxRetries=3, Queue: maxRetries=5, Job: maxRetries=1\n * // Result: maxRetries=1 (job wins)\n * const config = QueueManager.getMergedRetryConfig('emails', { maxRetries: 1 })\n * ```\n */\n getMergedRetryConfig(queue: string, jobRetryConfig?: RetryConfig): RetryConfig {\n const queueConfig = this.#queueConfigs.get(queue)\n const queueRetryConfig = queueConfig?.retry || {}\n\n let maxRetries =\n jobRetryConfig?.maxRetries ||\n queueRetryConfig.maxRetries ||\n this.#globalRetryConfig?.maxRetries ||\n 0\n\n let backoff =\n jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff\n\n return { maxRetries, backoff }\n }\n\n /**\n * Get the configured job factory for custom instantiation.\n *\n * @returns The job factory function, or undefined if not configured\n */\n getJobFactory(): JobFactory | undefined {\n return this.#jobFactory\n }\n\n #validateConfig(config: QueueManagerConfig): void {\n if (!config.adapters || Object.keys(config.adapters).length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['At least one adapter must be configured'])\n }\n\n if (!config.default) {\n throw new errors.E_CONFIGURATION_ERROR(['Default adapter must be specified'])\n }\n\n if (!config.adapters[config.default]) {\n throw new errors.E_CONFIGURATION_ERROR([\n `Default adapter \"${config.default}\" not found in adapters configuration`,\n ])\n }\n\n for (const [name, factory] of Object.entries(config.adapters)) {\n if (typeof factory !== 'function') {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${name}\" must be a factory function`])\n }\n }\n }\n\n /**\n * Clean up all adapter instances and reset state.\n *\n * Call this when shutting down the application or when\n * you need to reinitialize with a new configuration.\n *\n * @example\n * ```typescript\n * // On application shutdown\n * await QueueManager.destroy()\n * ```\n */\n async destroy() {\n for (const [name, adapter] of this.#adapterInstances) {\n debug('destroying adapter \"%s\"', name)\n await adapter.destroy()\n }\n this.#adapterInstances.clear()\n this.#initialized = false\n }\n}\n\n/** Global queue manager singleton */\nexport const QueueManager = new QueueManagerSingleton()\n"],"mappings":"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","names":[]}
|