@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.
- package/README.md +80 -6
- package/build/{chunk-WOUYSNK2.js → chunk-KI47AJ6U.js} +2 -2
- package/build/chunk-PZ5AY32C.js +10 -0
- package/build/chunk-PZ5AY32C.js.map +1 -0
- package/build/{chunk-ZZFSQY36.js → chunk-QEFYHCL7.js} +4 -6
- package/build/{chunk-ZZFSQY36.js.map → chunk-QEFYHCL7.js.map} +1 -1
- package/build/{chunk-OVYXMSSU.js → chunk-VRXHCWNK.js} +113 -31
- package/build/chunk-VRXHCWNK.js.map +1 -0
- package/build/chunk-WVLSICD4.js +20 -0
- package/build/chunk-WVLSICD4.js.map +1 -0
- package/build/index.d.ts +61 -10
- package/build/index.js +64 -51
- package/build/index.js.map +1 -1
- package/build/{index-B1XdqWpN.d.ts → job-Z5fBSzRX.d.ts} +160 -6
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/fake_adapter.d.ts +6 -1
- package/build/src/drivers/fake_adapter.js +4 -2
- package/build/src/drivers/knex_adapter.d.ts +1 -1
- package/build/src/drivers/knex_adapter.js +2 -1
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/build/src/drivers/redis_adapter.d.ts +1 -1
- package/build/src/drivers/redis_adapter.js +92 -93
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/build/src/drivers/sync_adapter.d.ts +1 -1
- package/build/src/drivers/sync_adapter.js +38 -18
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/otel.d.ts +63 -0
- package/build/src/otel.js +245 -0
- package/build/src/otel.js.map +1 -0
- package/build/src/types/index.d.ts +6 -1
- package/build/src/types/main.d.ts +1 -1
- package/build/src/types/tracing_channels.d.ts +34 -0
- package/build/src/types/tracing_channels.js +1 -0
- package/build/src/types/tracing_channels.js.map +1 -0
- package/package.json +36 -14
- package/build/chunk-OVYXMSSU.js.map +0 -1
- /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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
+
fake.assertPushed(SendEmailJob)
|
|
280
|
+
fake.assertPushed(SendEmailJob, {
|
|
279
281
|
queue: 'default',
|
|
280
282
|
payload: (payload) => payload.to === 'user@example.com',
|
|
281
283
|
})
|
|
282
|
-
|
|
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-
|
|
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-
|
|
128
|
+
//# sourceMappingURL=chunk-KI47AJ6U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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-
|
|
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":"
|
|
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-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
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
|
-
*
|
|
442
|
+
* using fake = QueueManager.fake()
|
|
408
443
|
*
|
|
409
444
|
* await SendEmailJob.dispatch({ to: 'user@example.com' })
|
|
410
445
|
*
|
|
411
446
|
* fake.assertPushed(SendEmailJob)
|
|
412
|
-
*
|
|
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
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
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-
|
|
1724
|
+
//# sourceMappingURL=chunk-VRXHCWNK.js.map
|