@boringnode/queue 0.5.0 → 0.5.2

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.
Files changed (37) hide show
  1. package/README.md +80 -6
  2. package/build/{chunk-WOUYSNK2.js → chunk-KI47AJ6U.js} +2 -2
  3. package/build/chunk-PZ5AY32C.js +10 -0
  4. package/build/chunk-PZ5AY32C.js.map +1 -0
  5. package/build/{chunk-ZZFSQY36.js → chunk-QEFYHCL7.js} +4 -6
  6. package/build/{chunk-ZZFSQY36.js.map → chunk-QEFYHCL7.js.map} +1 -1
  7. package/build/{chunk-OVYXMSSU.js → chunk-VRXHCWNK.js} +113 -31
  8. package/build/chunk-VRXHCWNK.js.map +1 -0
  9. package/build/chunk-WVLSICD4.js +20 -0
  10. package/build/chunk-WVLSICD4.js.map +1 -0
  11. package/build/index.d.ts +61 -10
  12. package/build/index.js +64 -51
  13. package/build/index.js.map +1 -1
  14. package/build/{index-B1XdqWpN.d.ts → job-Z5fBSzRX.d.ts} +160 -6
  15. package/build/src/contracts/adapter.d.ts +1 -1
  16. package/build/src/drivers/fake_adapter.d.ts +6 -1
  17. package/build/src/drivers/fake_adapter.js +4 -2
  18. package/build/src/drivers/knex_adapter.d.ts +1 -1
  19. package/build/src/drivers/knex_adapter.js +2 -1
  20. package/build/src/drivers/knex_adapter.js.map +1 -1
  21. package/build/src/drivers/redis_adapter.d.ts +1 -1
  22. package/build/src/drivers/redis_adapter.js +92 -93
  23. package/build/src/drivers/redis_adapter.js.map +1 -1
  24. package/build/src/drivers/sync_adapter.d.ts +1 -1
  25. package/build/src/drivers/sync_adapter.js +38 -18
  26. package/build/src/drivers/sync_adapter.js.map +1 -1
  27. package/build/src/otel.d.ts +63 -0
  28. package/build/src/otel.js +245 -0
  29. package/build/src/otel.js.map +1 -0
  30. package/build/src/types/index.d.ts +6 -1
  31. package/build/src/types/main.d.ts +1 -1
  32. package/build/src/types/tracing_channels.d.ts +34 -0
  33. package/build/src/types/tracing_channels.js +1 -0
  34. package/build/src/types/tracing_channels.js.map +1 -0
  35. package/package.json +36 -14
  36. package/build/chunk-OVYXMSSU.js.map +0 -1
  37. /package/build/{chunk-WOUYSNK2.js.map → chunk-KI47AJ6U.js.map} +0 -0
package/README.md CHANGED
@@ -270,20 +270,22 @@ await QueueManager.init({
270
270
  locations: ['./app/jobs/**/*.ts'],
271
271
  })
272
272
 
273
- const adapter = QueueManager.fake()
273
+ // The `using` keyword automatically restores the real adapters when
274
+ // the variable goes out of scope (at the end of the test function).
275
+ using fake = QueueManager.fake()
274
276
 
275
277
  await SendEmailJob.dispatch({ to: 'user@example.com' })
276
278
 
277
- adapter.assertPushed(SendEmailJob)
278
- adapter.assertPushed(SendEmailJob, {
279
+ fake.assertPushed(SendEmailJob)
280
+ fake.assertPushed(SendEmailJob, {
279
281
  queue: 'default',
280
282
  payload: (payload) => payload.to === 'user@example.com',
281
283
  })
282
- adapter.assertPushedCount(1)
283
-
284
- QueueManager.restore()
284
+ fake.assertPushedCount(1)
285
285
  ```
286
286
 
287
+ You can also call `QueueManager.restore()` manually if you need more control over when the real adapters are restored.
288
+
287
289
  ### Sync (for testing)
288
290
 
289
291
  ```typescript
@@ -519,6 +521,78 @@ await QueueManager.init({
519
521
  })
520
522
  ```
521
523
 
524
+ ## OpenTelemetry Instrumentation (experimental)
525
+
526
+ > [!WARNING]
527
+ > The OpenTelemetry instrumentation is experimental and its API may change in future releases.
528
+
529
+ `@boringnode/queue` ships with built-in OpenTelemetry instrumentation that creates **PRODUCER** spans for job dispatch and **CONSUMER** spans for job execution, following [OTel messaging semantic conventions](https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/).
530
+
531
+ ### Quick Setup
532
+
533
+ ```typescript
534
+ import { QueueInstrumentation } from '@boringnode/queue/otel'
535
+ import * as boringqueue from '@boringnode/queue'
536
+
537
+ const instrumentation = new QueueInstrumentation({
538
+ messagingSystem: 'boringqueue', // default
539
+ executionSpanLinkMode: 'link', // or 'parent'
540
+ })
541
+
542
+ instrumentation.enable()
543
+ instrumentation.manuallyRegister(boringqueue)
544
+ ```
545
+
546
+ The instrumentation patches `QueueManager.init()` to automatically inject its wrappers — no config changes needed in your queue setup.
547
+
548
+ ### Span Attributes
549
+
550
+ The instrumentation uses standard [OTel messaging semantic conventions](https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/) where they map cleanly, plus a few queue-specific custom attributes.
551
+
552
+ | Attribute | Kind | Description |
553
+ | ------------------------------- | ------- | ------------------------------------------ |
554
+ | `messaging.system` | Semconv | `'boringqueue'` (configurable) |
555
+ | `messaging.operation.name` | Semconv | `'publish'` or `'process'` |
556
+ | `messaging.destination.name` | Semconv | Queue name |
557
+ | `messaging.message.id` | Semconv | Job ID for single-message spans |
558
+ | `messaging.batch.message_count` | Semconv | Number of jobs in a batch dispatch |
559
+ | `messaging.message.retry.count` | Custom | Retry count (0-based) for a job attempt |
560
+ | `messaging.job.name` | Custom | Job class name (e.g. `SendEmailJob`) |
561
+ | `messaging.job.status` | Custom | `'completed'`, `'failed'`, or `'retrying'` |
562
+ | `messaging.job.group_id` | Custom | Queue-specific group identifier |
563
+ | `messaging.job.priority` | Custom | Queue-specific job priority |
564
+ | `messaging.job.delay_ms` | Custom | Delay before the job becomes available |
565
+ | `messaging.job.queue_time_ms` | Custom | Time spent waiting in queue before processing |
566
+
567
+ ### Trace Context Propagation
568
+
569
+ The instrumentation automatically propagates trace context from dispatch to execution:
570
+
571
+ - **Link mode** (default): Each job execution is an independent trace, linked to the dispatch span
572
+ - **Parent mode**: Job execution is a child of the dispatch span (same trace)
573
+
574
+ Child spans created inside `execute()` (DB queries, HTTP calls, etc.) are automatically parented to the job consumer span.
575
+
576
+ ### diagnostics_channel
577
+
578
+ Raw telemetry events are available via `diagnostics_channel` for custom subscribers:
579
+
580
+ ```typescript
581
+ import { tracingChannels } from '@boringnode/queue'
582
+
583
+ const { executeChannel } = tracingChannels
584
+
585
+ executeChannel.subscribe({
586
+ start() {},
587
+ end() {},
588
+ asyncStart() {},
589
+ asyncEnd(message) {
590
+ console.log(`Job ${message.job.name} ${message.status} in ${message.duration}ms`)
591
+ },
592
+ error() {},
593
+ })
594
+ ```
595
+
522
596
  ## Benchmarks
523
597
 
524
598
  Performance comparison with BullMQ (5ms simulated work per job):
@@ -2,7 +2,7 @@ import {
2
2
  E_JOB_MAX_ATTEMPTS_REACHED,
3
3
  E_JOB_TIMEOUT,
4
4
  parse
5
- } from "./chunk-ZZFSQY36.js";
5
+ } from "./chunk-QEFYHCL7.js";
6
6
 
7
7
  // src/job_runtime.ts
8
8
  var JobExecutionRuntime = class _JobExecutionRuntime {
@@ -125,4 +125,4 @@ var JobExecutionRuntime = class _JobExecutionRuntime {
125
125
  export {
126
126
  JobExecutionRuntime
127
127
  };
128
- //# sourceMappingURL=chunk-WOUYSNK2.js.map
128
+ //# sourceMappingURL=chunk-KI47AJ6U.js.map
@@ -0,0 +1,10 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export {
8
+ __export
9
+ };
10
+ //# sourceMappingURL=chunk-PZ5AY32C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,8 +1,6 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
1
+ import {
2
+ __export
3
+ } from "./chunk-PZ5AY32C.js";
6
4
 
7
5
  // src/exceptions.ts
8
6
  var exceptions_exports = {};
@@ -144,4 +142,4 @@ export {
144
142
  parse,
145
143
  calculateScore
146
144
  };
147
- //# sourceMappingURL=chunk-ZZFSQY36.js.map
145
+ //# sourceMappingURL=chunk-QEFYHCL7.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/exceptions.ts","../src/constants.ts","../src/utils.ts"],"sourcesContent":["import { createError } from '@poppinss/utils/exception'\n\nexport const E_INVALID_DURATION_EXPRESSION = createError(\n 'Invalid duration expression: \"%s\"',\n 'E_INVALID_DURATION_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_BASE_DELAY = createError<[reason: string]>(\n 'Invalid base delay. Reason: %s',\n 'E_INVALID_BASE_DELAY',\n 500\n)\n\nexport const E_INVALID_MAX_DELAY = createError<[reason: string]>(\n 'Invalid max delay. Reason: %s',\n 'E_INVALID_MAX_DELAY',\n 500\n)\n\nexport const E_INVALID_MULTIPLIER = createError<[reason: string]>(\n 'Invalid multiplier. Reason: %s',\n 'E_INVALID_MULTIPLIER',\n 500\n)\n\nexport const E_CONFIGURATION_ERROR = createError<[reason: string]>(\n 'Configuration error. Reason: %s',\n 'E_CONFIGURATION_ERROR',\n 500\n)\n\nexport const E_JOB_NOT_FOUND = createError<[jobName: string]>(\n 'Requested job \"%s\" is not registered',\n 'E_JOB_NOT_FOUND'\n)\n\nexport const E_JOB_MAX_ATTEMPTS_REACHED = createError<[jobName: string]>(\n 'The job \"%s\" has reached the maximum number of retry attempts',\n 'E_JOB_MAX_ATTEMPTS_REACHED'\n)\n\nexport const E_JOB_TIMEOUT = createError<[jobName: string, timeout: number]>(\n 'The job \"%s\" has exceeded the timeout of %dms',\n 'E_JOB_TIMEOUT'\n)\n\nexport const E_QUEUE_NOT_INITIALIZED = createError(\n 'QueueManager is not initialized. Call QueueManager.init() before using it.',\n 'E_QUEUE_NOT_INITIALIZED',\n 500\n)\n\nexport const E_ADAPTER_INIT_ERROR = createError<[adapterName: string, originalMessage: string]>(\n 'Failed to initialize adapter \"%s\". Reason: %s',\n 'E_ADAPTER_INIT_ERROR',\n 500\n)\n\nexport const E_NO_JOBS_FOUND = createError<[patterns: string]>(\n 'No jobs found for the specified locations: %s. Verify your glob patterns match your job files.',\n 'E_NO_JOBS_FOUND',\n 500\n)\n\nexport const E_INVALID_CRON_EXPRESSION = createError<[expression: string, reason: string]>(\n 'Invalid cron expression \"%s\": %s',\n 'E_INVALID_CRON_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_SCHEDULE_CONFIG = createError<[reason: string]>(\n 'Invalid schedule configuration: %s',\n 'E_INVALID_SCHEDULE_CONFIG',\n 500\n)\n","/**\n * Default job priority (1-10 scale, lower = higher priority)\n */\nexport const DEFAULT_PRIORITY = 5\n\n/**\n * Multiplier used in score calculation: priority * multiplier + timestamp\n *\n * This ensures higher priority jobs are processed first,\n * while preserving FIFO order within the same priority.\n * The value (1e13) leaves room for ~300 years of millisecond timestamps.\n */\nexport const PRIORITY_SCORE_MULTIPLIER = 1e13\n\n/**\n * Default delay when the worker is idle (no jobs in queue)\n */\nexport const DEFAULT_IDLE_DELAY = '2s'\n\n/**\n * Default interval between stalled job checks\n */\nexport const DEFAULT_STALLED_INTERVAL = '30s'\n\n/**\n * Default threshold after which a job is considered stalled\n */\nexport const DEFAULT_STALLED_THRESHOLD = '30s'\n\n/**\n * Default delay before retrying after an error\n */\nexport const DEFAULT_ERROR_RETRY_DELAY = '5s'\n","import { parse as parseDuration } from '@lukeed/ms'\nimport type { Duration, JobRetention } from './types/main.js'\nimport * as errors from './exceptions.js'\nimport { PRIORITY_SCORE_MULTIPLIER } from './constants.js'\n\nexport interface ResolvedRetention {\n keep: boolean\n maxAge: number\n maxCount: number\n}\n\nexport function resolveRetention(retention?: JobRetention): ResolvedRetention {\n if (retention === undefined || retention === true) {\n return { keep: false, maxAge: 0, maxCount: 0 }\n }\n\n if (retention === false) {\n return { keep: true, maxAge: 0, maxCount: 0 }\n }\n\n return {\n keep: true,\n maxAge: retention.age ? parse(retention.age) : 0,\n maxCount: retention.count ?? 0,\n }\n}\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;AAErB,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF;AAEO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AACF;AAEO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF;;;ACxEO,IAAM,mBAAmB;AASzB,IAAM,4BAA4B;AAKlC,IAAM,qBAAqB;AAK3B,IAAM,2BAA2B;AAKjC,IAAM,4BAA4B;AAKlC,IAAM,4BAA4B;;;AChCzC,SAAS,SAAS,qBAAqB;AAWhC,SAAS,iBAAiB,WAA6C;AAC5E,MAAI,cAAc,UAAa,cAAc,MAAM;AACjD,WAAO,EAAE,MAAM,OAAO,QAAQ,GAAG,UAAU,EAAE;AAAA,EAC/C;AAEA,MAAI,cAAc,OAAO;AACvB,WAAO,EAAE,MAAM,MAAM,QAAQ,GAAG,UAAU,EAAE;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,UAAU,MAAM,MAAM,UAAU,GAAG,IAAI;AAAA,IAC/C,UAAU,UAAU,SAAS;AAAA,EAC/B;AACF;AAEO,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":[]}
1
+ {"version":3,"sources":["../src/exceptions.ts","../src/constants.ts","../src/utils.ts"],"sourcesContent":["import { createError } from '@poppinss/utils/exception'\n\nexport const E_INVALID_DURATION_EXPRESSION = createError(\n 'Invalid duration expression: \"%s\"',\n 'E_INVALID_DURATION_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_BASE_DELAY = createError<[reason: string]>(\n 'Invalid base delay. Reason: %s',\n 'E_INVALID_BASE_DELAY',\n 500\n)\n\nexport const E_INVALID_MAX_DELAY = createError<[reason: string]>(\n 'Invalid max delay. Reason: %s',\n 'E_INVALID_MAX_DELAY',\n 500\n)\n\nexport const E_INVALID_MULTIPLIER = createError<[reason: string]>(\n 'Invalid multiplier. Reason: %s',\n 'E_INVALID_MULTIPLIER',\n 500\n)\n\nexport const E_CONFIGURATION_ERROR = createError<[reason: string]>(\n 'Configuration error. Reason: %s',\n 'E_CONFIGURATION_ERROR',\n 500\n)\n\nexport const E_JOB_NOT_FOUND = createError<[jobName: string]>(\n 'Requested job \"%s\" is not registered',\n 'E_JOB_NOT_FOUND'\n)\n\nexport const E_JOB_MAX_ATTEMPTS_REACHED = createError<[jobName: string]>(\n 'The job \"%s\" has reached the maximum number of retry attempts',\n 'E_JOB_MAX_ATTEMPTS_REACHED'\n)\n\nexport const E_JOB_TIMEOUT = createError<[jobName: string, timeout: number]>(\n 'The job \"%s\" has exceeded the timeout of %dms',\n 'E_JOB_TIMEOUT'\n)\n\nexport const E_QUEUE_NOT_INITIALIZED = createError(\n 'QueueManager is not initialized. Call QueueManager.init() before using it.',\n 'E_QUEUE_NOT_INITIALIZED',\n 500\n)\n\nexport const E_ADAPTER_INIT_ERROR = createError<[adapterName: string, originalMessage: string]>(\n 'Failed to initialize adapter \"%s\". Reason: %s',\n 'E_ADAPTER_INIT_ERROR',\n 500\n)\n\nexport const E_NO_JOBS_FOUND = createError<[patterns: string]>(\n 'No jobs found for the specified locations: %s. Verify your glob patterns match your job files.',\n 'E_NO_JOBS_FOUND',\n 500\n)\n\nexport const E_INVALID_CRON_EXPRESSION = createError<[expression: string, reason: string]>(\n 'Invalid cron expression \"%s\": %s',\n 'E_INVALID_CRON_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_SCHEDULE_CONFIG = createError<[reason: string]>(\n 'Invalid schedule configuration: %s',\n 'E_INVALID_SCHEDULE_CONFIG',\n 500\n)\n","/**\n * Default job priority (1-10 scale, lower = higher priority)\n */\nexport const DEFAULT_PRIORITY = 5\n\n/**\n * Multiplier used in score calculation: priority * multiplier + timestamp\n *\n * This ensures higher priority jobs are processed first,\n * while preserving FIFO order within the same priority.\n * The value (1e13) leaves room for ~300 years of millisecond timestamps.\n */\nexport const PRIORITY_SCORE_MULTIPLIER = 1e13\n\n/**\n * Default delay when the worker is idle (no jobs in queue)\n */\nexport const DEFAULT_IDLE_DELAY = '2s'\n\n/**\n * Default interval between stalled job checks\n */\nexport const DEFAULT_STALLED_INTERVAL = '30s'\n\n/**\n * Default threshold after which a job is considered stalled\n */\nexport const DEFAULT_STALLED_THRESHOLD = '30s'\n\n/**\n * Default delay before retrying after an error\n */\nexport const DEFAULT_ERROR_RETRY_DELAY = '5s'\n","import { parse as parseDuration } from '@lukeed/ms'\nimport type { Duration, JobRetention } from './types/main.js'\nimport * as errors from './exceptions.js'\nimport { PRIORITY_SCORE_MULTIPLIER } from './constants.js'\n\nexport interface ResolvedRetention {\n keep: boolean\n maxAge: number\n maxCount: number\n}\n\nexport function resolveRetention(retention?: JobRetention): ResolvedRetention {\n if (retention === undefined || retention === true) {\n return { keep: false, maxAge: 0, maxCount: 0 }\n }\n\n if (retention === false) {\n return { keep: true, maxAge: 0, maxCount: 0 }\n }\n\n return {\n keep: true,\n maxAge: retention.age ? parse(retention.age) : 0,\n maxCount: retention.count ?? 0,\n }\n}\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;AAErB,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF;AAEO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AACF;AAEO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AACF;;;ACxEO,IAAM,mBAAmB;AASzB,IAAM,4BAA4B;AAKlC,IAAM,qBAAqB;AAK3B,IAAM,2BAA2B;AAKjC,IAAM,4BAA4B;AAKlC,IAAM,4BAA4B;;;AChCzC,SAAS,SAAS,qBAAqB;AAWhC,SAAS,iBAAiB,WAA6C;AAC5E,MAAI,cAAc,UAAa,cAAc,MAAM;AACjD,WAAO,EAAE,MAAM,OAAO,QAAQ,GAAG,UAAU,EAAE;AAAA,EAC/C;AAEA,MAAI,cAAc,OAAO;AACvB,WAAO,EAAE,MAAM,MAAM,QAAQ,GAAG,UAAU,EAAE;AAAA,EAC9C;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,UAAU,MAAM,MAAM,UAAU,GAAG,IAAI;AAAA,IAC/C,UAAU,UAAU,SAAS;AAAA,EAC/B;AACF;AAEO,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":[]}
@@ -1,3 +1,6 @@
1
+ import {
2
+ dispatchChannel
3
+ } from "./chunk-WVLSICD4.js";
1
4
  import {
2
5
  DEFAULT_PRIORITY,
3
6
  E_ADAPTER_INIT_ERROR,
@@ -7,7 +10,7 @@ import {
7
10
  E_JOB_NOT_FOUND,
8
11
  E_QUEUE_NOT_INITIALIZED,
9
12
  parse
10
- } from "./chunk-ZZFSQY36.js";
13
+ } from "./chunk-QEFYHCL7.js";
11
14
 
12
15
  // src/drivers/fake_adapter.ts
13
16
  import assert from "assert/strict";
@@ -260,6 +263,8 @@ var QueueConfigResolver = class _QueueConfigResolver {
260
263
  };
261
264
 
262
265
  // src/queue_manager.ts
266
+ var noopInternalOperationWrapper = async (fn) => fn();
267
+ var noopExecutionWrapper = async (fn) => fn();
263
268
  var QueueManagerSingleton = class {
264
269
  #initialized = false;
265
270
  #defaultAdapter;
@@ -267,7 +272,10 @@ var QueueManagerSingleton = class {
267
272
  #adapterInstances = /* @__PURE__ */ new Map();
268
273
  #logger = consoleLogger;
269
274
  #jobFactory;
275
+ #internalOperationWrapper;
276
+ #executionWrapper;
270
277
  #configResolver = new QueueConfigResolver({});
278
+ #locations = [];
271
279
  #fakeState;
272
280
  /**
273
281
  * Initialize the queue system with the given configuration.
@@ -301,18 +309,46 @@ var QueueManagerSingleton = class {
301
309
  this.#adapters = config.adapters;
302
310
  this.#logger = config.logger ?? consoleLogger;
303
311
  this.#jobFactory = config.jobFactory;
312
+ this.#internalOperationWrapper = config.internalOperationWrapper;
313
+ this.#executionWrapper = config.executionWrapper;
304
314
  this.#configResolver = QueueConfigResolver.from(config);
305
- if (config.locations && config.locations.length > 0) {
306
- const registered = await Locator.registerFromGlob(config.locations);
307
- if (registered === 0) {
308
- this.#logger.warn(
309
- `No jobs found for locations: ${config.locations.join(", ")}. Verify your glob patterns match your job files.`
310
- );
311
- }
315
+ this.#locations = config.locations ?? [];
316
+ if (config.autoLoadJobs ?? true) {
317
+ await this.loadJobs();
312
318
  }
313
319
  this.#initialized = true;
314
320
  return this;
315
321
  }
322
+ /**
323
+ * Load and register job classes from configured or explicit locations.
324
+ *
325
+ * This low-level API is useful for framework integrations that need to
326
+ * register jobs at a precise moment in their command lifecycle.
327
+ *
328
+ * @param locations - Optional glob patterns. Defaults to the configured locations.
329
+ * @returns Number of jobs successfully registered.
330
+ *
331
+ * @example
332
+ * ```typescript
333
+ * await QueueManager.init(config)
334
+ * await QueueManager.loadJobs()
335
+ *
336
+ * // Or with explicit locations
337
+ * await QueueManager.loadJobs(['./app/jobs/**\/*.js'])
338
+ * ```
339
+ */
340
+ async loadJobs(locations = this.#locations) {
341
+ if (locations.length === 0) {
342
+ return 0;
343
+ }
344
+ const registered = await Locator.registerFromGlob(locations);
345
+ if (registered === 0) {
346
+ this.#logger.warn(
347
+ `No jobs found for locations: ${locations.join(", ")}. Verify your glob patterns match your job files.`
348
+ );
349
+ }
350
+ return registered;
351
+ }
316
352
  /**
317
353
  * Destroy any materialized adapters from the current configuration before
318
354
  * replacing it with a new one.
@@ -397,19 +433,18 @@ var QueueManagerSingleton = class {
397
433
  * Replace all adapters with a fake adapter for testing.
398
434
  *
399
435
  * The fake adapter records pushed jobs and exposes assertion helpers.
400
- * Call `restore()` to return to the previous configuration.
401
- *
402
- * @returns The fake adapter instance for assertions
403
- * @throws {E_QUEUE_NOT_INITIALIZED} If `init()` hasn't been called
436
+ * Use the `using` keyword to automatically restore the previous
437
+ * configuration when the variable goes out of scope, or call
438
+ * `restore()` manually.
404
439
  *
405
440
  * @example
406
441
  * ```typescript
407
- * const fake = QueueManager.fake()
442
+ * using fake = QueueManager.fake()
408
443
  *
409
444
  * await SendEmailJob.dispatch({ to: 'user@example.com' })
410
445
  *
411
446
  * fake.assertPushed(SendEmailJob)
412
- * QueueManager.restore()
447
+ * // Automatically restored at end of scope
413
448
  * ```
414
449
  */
415
450
  fake() {
@@ -420,13 +455,17 @@ var QueueManagerSingleton = class {
420
455
  return this.#fakeState.fakeAdapter;
421
456
  }
422
457
  const fakeAdapter = new FakeAdapter();
458
+ fakeAdapter.onDispose(() => this.restore());
423
459
  this.#fakeState = {
424
460
  defaultAdapter: this.#defaultAdapter,
425
461
  adapters: this.#adapters,
426
462
  adapterInstances: this.#adapterInstances,
427
463
  logger: this.#logger,
428
464
  jobFactory: this.#jobFactory,
465
+ internalOperationWrapper: this.#internalOperationWrapper,
466
+ executionWrapper: this.#executionWrapper,
429
467
  configResolver: this.#configResolver,
468
+ locations: this.#locations,
430
469
  fakeAdapter
431
470
  };
432
471
  const fakeFactory = () => fakeAdapter;
@@ -456,7 +495,10 @@ var QueueManagerSingleton = class {
456
495
  this.#adapterInstances = state.adapterInstances;
457
496
  this.#logger = state.logger;
458
497
  this.#jobFactory = state.jobFactory;
498
+ this.#internalOperationWrapper = state.internalOperationWrapper;
499
+ this.#executionWrapper = state.executionWrapper;
459
500
  this.#configResolver = state.configResolver;
501
+ this.#locations = state.locations;
460
502
  }
461
503
  /**
462
504
  * Get the configured job factory for custom instantiation.
@@ -466,12 +508,30 @@ var QueueManagerSingleton = class {
466
508
  getJobFactory() {
467
509
  return this.#jobFactory;
468
510
  }
511
+ /**
512
+ * Whether the queue manager has been initialized.
513
+ */
514
+ isInitialized() {
515
+ return this.#initialized;
516
+ }
469
517
  /**
470
518
  * Get the configured logger used by the queue runtime.
471
519
  */
472
520
  getLogger() {
473
521
  return this.#logger;
474
522
  }
523
+ /**
524
+ * Get the configured internal operation wrapper.
525
+ */
526
+ getInternalOperationWrapper() {
527
+ return this.#internalOperationWrapper ?? noopInternalOperationWrapper;
528
+ }
529
+ /**
530
+ * Get the configured execution wrapper.
531
+ */
532
+ getExecutionWrapper() {
533
+ return this.#executionWrapper ?? noopExecutionWrapper;
534
+ }
475
535
  /**
476
536
  * Get the resolver responsible for effective queue/job runtime config.
477
537
  */
@@ -525,7 +585,10 @@ var QueueManagerSingleton = class {
525
585
  }
526
586
  this.#adapterInstances.clear();
527
587
  this.#initialized = false;
588
+ this.#internalOperationWrapper = void 0;
589
+ this.#executionWrapper = void 0;
528
590
  this.#configResolver = new QueueConfigResolver({});
591
+ this.#locations = [];
529
592
  this.#fakeState = void 0;
530
593
  }
531
594
  };
@@ -665,23 +728,26 @@ var JobDispatcher = class {
665
728
  const id = randomUUID();
666
729
  debug_default("dispatching job %s with id %s using payload %s", this.#name, id, this.#payload);
667
730
  const adapter = this.#getAdapterInstance();
668
- const payload = {
731
+ const wrapInternal = QueueManager.getInternalOperationWrapper();
732
+ const parsedDelay = this.#delay ? parse(this.#delay) : void 0;
733
+ const jobData = {
669
734
  id,
670
735
  name: this.#name,
671
736
  payload: this.#payload,
672
737
  attempts: 0,
673
738
  priority: this.#priority,
674
- groupId: this.#groupId
675
- };
676
- if (this.#delay) {
677
- const parsedDelay = parse(this.#delay);
678
- await adapter.pushLaterOn(this.#queue, payload, parsedDelay);
679
- } else {
680
- await adapter.pushOn(this.#queue, payload);
681
- }
682
- return {
683
- jobId: id
739
+ groupId: this.#groupId,
740
+ createdAt: Date.now()
684
741
  };
742
+ const message = { jobs: [jobData], queue: this.#queue, delay: parsedDelay };
743
+ await dispatchChannel.tracePromise(async () => {
744
+ if (parsedDelay !== void 0) {
745
+ await wrapInternal(() => adapter.pushLaterOn(this.#queue, jobData, parsedDelay));
746
+ } else {
747
+ await wrapInternal(() => adapter.pushOn(this.#queue, jobData));
748
+ }
749
+ }, message);
750
+ return { jobId: id };
685
751
  }
686
752
  /**
687
753
  * Thenable implementation for auto-dispatch when awaited.
@@ -806,20 +872,25 @@ var JobBatchDispatcher = class {
806
872
  * ```
807
873
  */
808
874
  async run() {
875
+ if (this.#payloads.length === 0) return { jobIds: [] };
809
876
  debug_default("dispatching %d jobs of type %s", this.#payloads.length, this.#name);
810
877
  const adapter = this.#getAdapterInstance();
878
+ const wrapInternal = QueueManager.getInternalOperationWrapper();
879
+ const now = Date.now();
811
880
  const jobs = this.#payloads.map((payload) => ({
812
881
  id: randomUUID2(),
813
882
  name: this.#name,
814
883
  payload,
815
884
  attempts: 0,
816
885
  priority: this.#priority,
817
- groupId: this.#groupId
886
+ groupId: this.#groupId,
887
+ createdAt: now
818
888
  }));
819
- await adapter.pushManyOn(this.#queue, jobs);
820
- return {
821
- jobIds: jobs.map((job) => job.id)
822
- };
889
+ const message = { jobs, queue: this.#queue };
890
+ await dispatchChannel.tracePromise(async () => {
891
+ await wrapInternal(() => adapter.pushManyOn(this.#queue, jobs));
892
+ }, message);
893
+ return { jobIds: jobs.map((job) => job.id) };
823
894
  }
824
895
  /**
825
896
  * Thenable implementation for auto-dispatch when awaited.
@@ -1226,6 +1297,17 @@ var FakeAdapter = class {
1226
1297
  #pendingTimeouts = /* @__PURE__ */ new Set();
1227
1298
  #schedules = /* @__PURE__ */ new Map();
1228
1299
  #pushedJobs = [];
1300
+ #onDispose;
1301
+ /**
1302
+ * Set the function to call when the fake is disposed
1303
+ */
1304
+ onDispose(fn) {
1305
+ this.#onDispose = fn;
1306
+ return this;
1307
+ }
1308
+ [Symbol.dispose]() {
1309
+ this.#onDispose?.();
1310
+ }
1229
1311
  setWorkerId(_workerId) {
1230
1312
  }
1231
1313
  getPushedJobs() {
@@ -1639,4 +1721,4 @@ export {
1639
1721
  ScheduleBuilder,
1640
1722
  Job
1641
1723
  };
1642
- //# sourceMappingURL=chunk-OVYXMSSU.js.map
1724
+ //# sourceMappingURL=chunk-VRXHCWNK.js.map