@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 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/main'
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
- pollingInterval: '10ms',
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') // 30 seconds
231
- await SendEmailJob.dispatch(payload).in('5m') // 5 minutes
232
- await SendEmailJob.dispatch(payload).in('2h') // 2 hours
233
- await SendEmailJob.dispatch(payload).in('1d') // 1 day
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, // Processed before default priority (5)
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: () => exponentialBackoff({
268
- baseDelay: '1s',
269
- maxDelay: '1m',
270
- multiplier: 2,
271
- jitter: true,
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', // Maximum execution time
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', // Default timeout for all jobs
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":[]}