@boringnode/queue 0.4.1 → 0.5.1
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 +90 -0
- package/build/chunk-KI47AJ6U.js +128 -0
- package/build/chunk-KI47AJ6U.js.map +1 -0
- 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-6EBS7CW4.js → chunk-VHN3XZDC.js} +180 -65
- package/build/chunk-VHN3XZDC.js.map +1 -0
- package/build/chunk-WVLSICD4.js +20 -0
- package/build/chunk-WVLSICD4.js.map +1 -0
- package/build/index.d.ts +81 -25
- package/build/index.js +79 -113
- package/build/index.js.map +1 -1
- package/build/{index-BAMFA6FI.d.ts → job-DImdhRFO.d.ts} +19 -4
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/fake_adapter.d.ts +1 -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 +2 -1
- 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 +74 -19
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/otel.d.ts +63 -0
- package/build/src/otel.js +242 -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 +35 -12
- package/build/chunk-6EBS7CW4.js.map +0 -1
package/README.md
CHANGED
|
@@ -292,6 +292,17 @@ import { sync } from '@boringnode/queue/drivers/sync_adapter'
|
|
|
292
292
|
const adapter = sync() // Jobs execute immediately
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
+
Use the `sync` adapter for tests and lightweight local development only.
|
|
296
|
+
|
|
297
|
+
- `await MyJob.dispatch(payload).run()` waits for the job to fully finish.
|
|
298
|
+
- Retries are executed inline, not by a background worker.
|
|
299
|
+
- If you configure backoff, the adapter will `sleep` between attempts.
|
|
300
|
+
- This means the caller can stay blocked for the full retry duration.
|
|
301
|
+
|
|
302
|
+
Example: with `maxRetries: 3` and an exponential backoff of `1s`, `2s`, `4s`,
|
|
303
|
+
the request or command that dispatched the job can stay busy for about 7 seconds
|
|
304
|
+
before the job exhausts its retries and runs `failed()`.
|
|
305
|
+
|
|
295
306
|
## Job Options
|
|
296
307
|
|
|
297
308
|
```typescript
|
|
@@ -338,6 +349,14 @@ export default class ReliableJob extends Job<Payload> {
|
|
|
338
349
|
}
|
|
339
350
|
```
|
|
340
351
|
|
|
352
|
+
`maxRetries` can be defined directly on the job options, and `retry.backoff`
|
|
353
|
+
controls the delay between attempts.
|
|
354
|
+
|
|
355
|
+
> With the `sync` adapter, these delays happen inline in the caller via
|
|
356
|
+
> `sleep`. If a job fails repeatedly, `dispatch().run()` will take as long as
|
|
357
|
+
> the total backoff duration. Use a worker-backed adapter when you do not want
|
|
358
|
+
> retries to slow down the request/command that dispatched the job.
|
|
359
|
+
|
|
341
360
|
<details>
|
|
342
361
|
<summary><strong>Available strategies</strong></summary>
|
|
343
362
|
|
|
@@ -500,6 +519,77 @@ await QueueManager.init({
|
|
|
500
519
|
})
|
|
501
520
|
```
|
|
502
521
|
|
|
522
|
+
## OpenTelemetry Instrumentation (experimental)
|
|
523
|
+
|
|
524
|
+
> [!WARNING]
|
|
525
|
+
> The OpenTelemetry instrumentation is experimental and its API may change in future releases.
|
|
526
|
+
|
|
527
|
+
`@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/).
|
|
528
|
+
|
|
529
|
+
### Quick Setup
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import { QueueInstrumentation } from '@boringnode/queue/otel'
|
|
533
|
+
import * as boringqueue from '@boringnode/queue'
|
|
534
|
+
|
|
535
|
+
const instrumentation = new QueueInstrumentation({
|
|
536
|
+
messagingSystem: 'boringqueue', // default
|
|
537
|
+
executionSpanLinkMode: 'link', // or 'parent'
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
instrumentation.enable()
|
|
541
|
+
instrumentation.manuallyRegister(boringqueue)
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
The instrumentation patches `QueueManager.init()` to automatically inject its wrappers — no config changes needed in your queue setup.
|
|
545
|
+
|
|
546
|
+
### Span Attributes
|
|
547
|
+
|
|
548
|
+
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.
|
|
549
|
+
|
|
550
|
+
| Attribute | Kind | Description |
|
|
551
|
+
| ------------------------------- | ------- | ------------------------------------------ |
|
|
552
|
+
| `messaging.system` | Semconv | `'boringqueue'` (configurable) |
|
|
553
|
+
| `messaging.operation.name` | Semconv | `'publish'` or `'process'` |
|
|
554
|
+
| `messaging.destination.name` | Semconv | Queue name |
|
|
555
|
+
| `messaging.message.id` | Semconv | Job ID for single-message spans |
|
|
556
|
+
| `messaging.batch.message_count` | Semconv | Number of jobs in a batch dispatch |
|
|
557
|
+
| `messaging.message.retry.count` | Custom | Retry count (0-based) for a job attempt |
|
|
558
|
+
| `messaging.job.name` | Custom | Job class name (e.g. `SendEmailJob`) |
|
|
559
|
+
| `messaging.job.status` | Custom | `'completed'`, `'failed'`, or `'retrying'` |
|
|
560
|
+
| `messaging.job.group_id` | Custom | Queue-specific group identifier |
|
|
561
|
+
| `messaging.job.priority` | Custom | Queue-specific job priority |
|
|
562
|
+
| `messaging.job.delay_ms` | Custom | Delay before the job becomes available |
|
|
563
|
+
|
|
564
|
+
### Trace Context Propagation
|
|
565
|
+
|
|
566
|
+
The instrumentation automatically propagates trace context from dispatch to execution:
|
|
567
|
+
|
|
568
|
+
- **Link mode** (default): Each job execution is an independent trace, linked to the dispatch span
|
|
569
|
+
- **Parent mode**: Job execution is a child of the dispatch span (same trace)
|
|
570
|
+
|
|
571
|
+
Child spans created inside `execute()` (DB queries, HTTP calls, etc.) are automatically parented to the job consumer span.
|
|
572
|
+
|
|
573
|
+
### diagnostics_channel
|
|
574
|
+
|
|
575
|
+
Raw telemetry events are available via `diagnostics_channel` for custom subscribers:
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
import { tracingChannels } from '@boringnode/queue'
|
|
579
|
+
|
|
580
|
+
const { executeChannel } = tracingChannels
|
|
581
|
+
|
|
582
|
+
executeChannel.subscribe({
|
|
583
|
+
start() {},
|
|
584
|
+
end() {},
|
|
585
|
+
asyncStart() {},
|
|
586
|
+
asyncEnd(message) {
|
|
587
|
+
console.log(`Job ${message.job.name} ${message.status} in ${message.duration}ms`)
|
|
588
|
+
},
|
|
589
|
+
error() {},
|
|
590
|
+
})
|
|
591
|
+
```
|
|
592
|
+
|
|
503
593
|
## Benchmarks
|
|
504
594
|
|
|
505
595
|
Performance comparison with BullMQ (5ms simulated work per job):
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E_JOB_MAX_ATTEMPTS_REACHED,
|
|
3
|
+
E_JOB_TIMEOUT,
|
|
4
|
+
parse
|
|
5
|
+
} from "./chunk-QEFYHCL7.js";
|
|
6
|
+
|
|
7
|
+
// src/job_runtime.ts
|
|
8
|
+
var JobExecutionRuntime = class _JobExecutionRuntime {
|
|
9
|
+
#jobName;
|
|
10
|
+
#options;
|
|
11
|
+
#retryConfig;
|
|
12
|
+
#timeout;
|
|
13
|
+
/**
|
|
14
|
+
* Build a runtime from already-resolved queue/job execution config.
|
|
15
|
+
*/
|
|
16
|
+
static from({
|
|
17
|
+
jobName,
|
|
18
|
+
options,
|
|
19
|
+
retryConfig,
|
|
20
|
+
defaultTimeout
|
|
21
|
+
}) {
|
|
22
|
+
return new _JobExecutionRuntime({
|
|
23
|
+
jobName,
|
|
24
|
+
options,
|
|
25
|
+
retryConfig,
|
|
26
|
+
defaultTimeout
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
get maxRetries() {
|
|
30
|
+
return this.#retryConfig.maxRetries;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a runtime with fully resolved retry and timeout settings.
|
|
34
|
+
*/
|
|
35
|
+
constructor({ jobName, options, retryConfig, defaultTimeout }) {
|
|
36
|
+
this.#jobName = jobName;
|
|
37
|
+
this.#options = options || {};
|
|
38
|
+
this.#retryConfig = retryConfig;
|
|
39
|
+
const timeout = this.#options.timeout ?? defaultTimeout;
|
|
40
|
+
this.#timeout = timeout === void 0 ? void 0 : parse(timeout);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Execute a hydrated job instance and enforce the configured timeout.
|
|
44
|
+
*/
|
|
45
|
+
async execute(instance, payload, context) {
|
|
46
|
+
if (this.#timeout === void 0) {
|
|
47
|
+
instance.$hydrate(payload, context);
|
|
48
|
+
return instance.execute();
|
|
49
|
+
}
|
|
50
|
+
const signal = AbortSignal.timeout(this.#timeout);
|
|
51
|
+
instance.$hydrate(payload, context, signal);
|
|
52
|
+
const { abortPromise, cleanupAbortListener } = this.#createTimeoutAbortRace(
|
|
53
|
+
signal,
|
|
54
|
+
instance.constructor.name
|
|
55
|
+
);
|
|
56
|
+
try {
|
|
57
|
+
await Promise.race([instance.execute(), abortPromise]);
|
|
58
|
+
} finally {
|
|
59
|
+
cleanupAbortListener();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert an execution error into a retry or permanent-failure outcome.
|
|
64
|
+
*/
|
|
65
|
+
resolveFailure(error, attempts) {
|
|
66
|
+
if (error instanceof E_JOB_TIMEOUT && this.#options.failOnTimeout) {
|
|
67
|
+
return {
|
|
68
|
+
type: "failed",
|
|
69
|
+
reason: "timeout",
|
|
70
|
+
storageError: error,
|
|
71
|
+
hookError: error
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (typeof this.#retryConfig.maxRetries === "undefined" || this.#retryConfig.maxRetries <= 0) {
|
|
75
|
+
return {
|
|
76
|
+
type: "failed",
|
|
77
|
+
reason: "no-retries",
|
|
78
|
+
storageError: error,
|
|
79
|
+
hookError: error
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (attempts >= this.#retryConfig.maxRetries) {
|
|
83
|
+
return {
|
|
84
|
+
type: "failed",
|
|
85
|
+
reason: "max-attempts",
|
|
86
|
+
storageError: error,
|
|
87
|
+
hookError: new E_JOB_MAX_ATTEMPTS_REACHED([this.#jobName], { cause: error })
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (this.#retryConfig.backoff) {
|
|
91
|
+
return {
|
|
92
|
+
type: "retry",
|
|
93
|
+
retryAt: this.#retryConfig.backoff().getNextRetryAt(attempts + 1)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { type: "retry" };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create the timeout race used to abort a job execution cleanly.
|
|
100
|
+
*/
|
|
101
|
+
#createTimeoutAbortRace(signal, runtimeJobName) {
|
|
102
|
+
const timeout = this.#timeout;
|
|
103
|
+
let abortHandler;
|
|
104
|
+
const abortPromise = new Promise((_, reject) => {
|
|
105
|
+
abortHandler = () => {
|
|
106
|
+
reject(new E_JOB_TIMEOUT([runtimeJobName, timeout]));
|
|
107
|
+
};
|
|
108
|
+
if (signal.aborted) {
|
|
109
|
+
abortHandler();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
abortPromise,
|
|
116
|
+
cleanupAbortListener: () => {
|
|
117
|
+
if (abortHandler) {
|
|
118
|
+
signal.removeEventListener("abort", abortHandler);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
JobExecutionRuntime
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chunk-KI47AJ6U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/job_runtime.ts"],"sourcesContent":["import * as errors from './exceptions.js'\nimport type { Job } from './job.js'\nimport type { Duration, JobContext, JobOptions, RetryConfig } from './types/main.js'\nimport { parse } from './utils.js'\n\nexport type JobExecutionOutcome =\n | { type: 'completed' }\n | { type: 'retry'; retryAt?: Date }\n | {\n type: 'failed'\n reason: 'timeout' | 'no-retries' | 'max-attempts'\n storageError: Error\n hookError: Error\n }\n\ntype JobExecutionRuntimeConfig = {\n jobName: string\n options?: JobOptions\n retryConfig: RetryConfig\n defaultTimeout?: Duration\n}\n\ntype JobExecutionRuntimeFactoryOptions = {\n jobName: string\n options?: JobOptions\n retryConfig: RetryConfig\n defaultTimeout?: Duration\n}\n\n/**\n * Shared execution policy for a single job runtime.\n *\n * It encapsulates timeout resolution and retry/failure decisions so the\n * worker and the sync adapter follow the same execution rules.\n */\nexport class JobExecutionRuntime {\n readonly #jobName: string\n readonly #options: JobOptions\n readonly #retryConfig: RetryConfig\n readonly #timeout?: number\n\n /**\n * Build a runtime from already-resolved queue/job execution config.\n */\n static from({\n jobName,\n options,\n retryConfig,\n defaultTimeout,\n }: JobExecutionRuntimeFactoryOptions): JobExecutionRuntime {\n return new JobExecutionRuntime({\n jobName,\n options,\n retryConfig,\n defaultTimeout,\n })\n }\n\n get maxRetries(): number | undefined {\n return this.#retryConfig.maxRetries\n }\n\n /**\n * Create a runtime with fully resolved retry and timeout settings.\n */\n constructor({ jobName, options, retryConfig, defaultTimeout }: JobExecutionRuntimeConfig) {\n this.#jobName = jobName\n this.#options = options || {}\n this.#retryConfig = retryConfig\n\n const timeout = this.#options.timeout ?? defaultTimeout\n this.#timeout = timeout === undefined ? undefined : parse(timeout)\n }\n\n /**\n * Execute a hydrated job instance and enforce the configured timeout.\n */\n async execute(instance: Job, payload: unknown, context: JobContext): Promise<void> {\n if (this.#timeout === undefined) {\n instance.$hydrate(payload, context)\n return instance.execute()\n }\n\n const signal = AbortSignal.timeout(this.#timeout)\n instance.$hydrate(payload, context, signal)\n\n const { abortPromise, cleanupAbortListener } = this.#createTimeoutAbortRace(\n signal,\n instance.constructor.name\n )\n\n try {\n await Promise.race([instance.execute(), abortPromise])\n } finally {\n cleanupAbortListener()\n }\n }\n\n /**\n * Convert an execution error into a retry or permanent-failure outcome.\n */\n resolveFailure(error: Error, attempts: number): JobExecutionOutcome {\n if (error instanceof errors.E_JOB_TIMEOUT && this.#options.failOnTimeout) {\n return {\n type: 'failed',\n reason: 'timeout',\n storageError: error,\n hookError: error,\n }\n }\n\n if (typeof this.#retryConfig.maxRetries === 'undefined' || this.#retryConfig.maxRetries <= 0) {\n return {\n type: 'failed',\n reason: 'no-retries',\n storageError: error,\n hookError: error,\n }\n }\n\n if (attempts >= this.#retryConfig.maxRetries) {\n return {\n type: 'failed',\n reason: 'max-attempts',\n storageError: error,\n hookError: new errors.E_JOB_MAX_ATTEMPTS_REACHED([this.#jobName], { cause: error }),\n }\n }\n\n if (this.#retryConfig.backoff) {\n return {\n type: 'retry',\n retryAt: this.#retryConfig.backoff().getNextRetryAt(attempts + 1),\n }\n }\n\n return { type: 'retry' }\n }\n\n /**\n * Create the timeout race used to abort a job execution cleanly.\n */\n #createTimeoutAbortRace(signal: AbortSignal, runtimeJobName: string) {\n const timeout = this.#timeout!\n let abortHandler: (() => void) | undefined\n\n const abortPromise = new Promise<never>((_, reject) => {\n abortHandler = () => {\n reject(new errors.E_JOB_TIMEOUT([runtimeJobName, timeout]))\n }\n\n if (signal.aborted) {\n abortHandler()\n return\n }\n\n signal.addEventListener('abort', abortHandler, { once: true })\n })\n\n return {\n abortPromise,\n cleanupAbortListener: () => {\n if (abortHandler) {\n signal.removeEventListener('abort', abortHandler)\n }\n },\n }\n }\n}\n"],"mappings":";;;;;;;AAmCO,IAAM,sBAAN,MAAM,qBAAoB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAKT,OAAO,KAAK;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAA2D;AACzD,WAAO,IAAI,qBAAoB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,aAAiC;AACnC,WAAO,KAAK,aAAa;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,EAAE,SAAS,SAAS,aAAa,eAAe,GAA8B;AACxF,SAAK,WAAW;AAChB,SAAK,WAAW,WAAW,CAAC;AAC5B,SAAK,eAAe;AAEpB,UAAM,UAAU,KAAK,SAAS,WAAW;AACzC,SAAK,WAAW,YAAY,SAAY,SAAY,MAAM,OAAO;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAQ,UAAe,SAAkB,SAAoC;AACjF,QAAI,KAAK,aAAa,QAAW;AAC/B,eAAS,SAAS,SAAS,OAAO;AAClC,aAAO,SAAS,QAAQ;AAAA,IAC1B;AAEA,UAAM,SAAS,YAAY,QAAQ,KAAK,QAAQ;AAChD,aAAS,SAAS,SAAS,SAAS,MAAM;AAE1C,UAAM,EAAE,cAAc,qBAAqB,IAAI,KAAK;AAAA,MAClD;AAAA,MACA,SAAS,YAAY;AAAA,IACvB;AAEA,QAAI;AACF,YAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,GAAG,YAAY,CAAC;AAAA,IACvD,UAAE;AACA,2BAAqB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,OAAc,UAAuC;AAClE,QAAI,iBAAwB,iBAAiB,KAAK,SAAS,eAAe;AACxE,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,WAAW;AAAA,MACb;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,aAAa,eAAe,eAAe,KAAK,aAAa,cAAc,GAAG;AAC5F,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,WAAW;AAAA,MACb;AAAA,IACF;AAEA,QAAI,YAAY,KAAK,aAAa,YAAY;AAC5C,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,WAAW,IAAW,2BAA2B,CAAC,KAAK,QAAQ,GAAG,EAAE,OAAO,MAAM,CAAC;AAAA,MACpF;AAAA,IACF;AAEA,QAAI,KAAK,aAAa,SAAS;AAC7B,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,KAAK,aAAa,QAAQ,EAAE,eAAe,WAAW,CAAC;AAAA,MAClE;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,QAAQ;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAwB,QAAqB,gBAAwB;AACnE,UAAM,UAAU,KAAK;AACrB,QAAI;AAEJ,UAAM,eAAe,IAAI,QAAe,CAAC,GAAG,WAAW;AACrD,qBAAe,MAAM;AACnB,eAAO,IAAW,cAAc,CAAC,gBAAgB,OAAO,CAAC,CAAC;AAAA,MAC5D;AAEA,UAAI,OAAO,SAAS;AAClB,qBAAa;AACb;AAAA,MACF;AAEA,aAAO,iBAAiB,SAAS,cAAc,EAAE,MAAM,KAAK,CAAC;AAAA,IAC/D,CAAC;AAED,WAAO;AAAA,MACL;AAAA,MACA,sBAAsB,MAAM;AAC1B,YAAI,cAAc;AAChB,iBAAO,oBAAoB,SAAS,YAAY;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -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";
|
|
@@ -185,17 +188,93 @@ var ConsoleLogger = class _ConsoleLogger {
|
|
|
185
188
|
};
|
|
186
189
|
var consoleLogger = new ConsoleLogger();
|
|
187
190
|
|
|
191
|
+
// src/queue_config_resolver.ts
|
|
192
|
+
var QueueConfigResolver = class _QueueConfigResolver {
|
|
193
|
+
#globalRetryConfig;
|
|
194
|
+
#globalJobOptions;
|
|
195
|
+
#queueConfigs;
|
|
196
|
+
#workerTimeout;
|
|
197
|
+
/**
|
|
198
|
+
* Create a resolver from the queue manager config.
|
|
199
|
+
*/
|
|
200
|
+
static from(config) {
|
|
201
|
+
return new _QueueConfigResolver({
|
|
202
|
+
globalRetryConfig: config.retry,
|
|
203
|
+
globalJobOptions: config.defaultJobOptions,
|
|
204
|
+
queueConfigs: new Map(Object.entries(config.queues || {})),
|
|
205
|
+
workerTimeout: config.worker?.timeout
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Create a resolver from already-materialized config fragments.
|
|
210
|
+
*/
|
|
211
|
+
constructor({
|
|
212
|
+
globalRetryConfig,
|
|
213
|
+
globalJobOptions,
|
|
214
|
+
queueConfigs,
|
|
215
|
+
workerTimeout
|
|
216
|
+
}) {
|
|
217
|
+
this.#globalRetryConfig = globalRetryConfig;
|
|
218
|
+
this.#globalJobOptions = globalJobOptions;
|
|
219
|
+
this.#queueConfigs = queueConfigs ?? /* @__PURE__ */ new Map();
|
|
220
|
+
this.#workerTimeout = workerTimeout;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Resolve the retry policy for a job using priority: job > queue > global.
|
|
224
|
+
*/
|
|
225
|
+
resolveRetryConfig(queue, jobOptions) {
|
|
226
|
+
const queueConfig = this.#queueConfigs.get(queue);
|
|
227
|
+
const queueRetryConfig = queueConfig?.retry || {};
|
|
228
|
+
const jobRetryConfig = this.#normalizeJobRetryConfig(jobOptions);
|
|
229
|
+
const maxRetries = jobRetryConfig?.maxRetries ?? queueRetryConfig.maxRetries ?? this.#globalRetryConfig?.maxRetries ?? 0;
|
|
230
|
+
const backoff = jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff;
|
|
231
|
+
return { maxRetries, backoff };
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Resolve effective retention options using priority: job > queue > global.
|
|
235
|
+
*/
|
|
236
|
+
resolveJobOptions(queue, jobOptions) {
|
|
237
|
+
const queueConfig = this.#queueConfigs.get(queue);
|
|
238
|
+
const queueJobOptions = queueConfig?.defaultJobOptions;
|
|
239
|
+
return {
|
|
240
|
+
removeOnComplete: jobOptions?.removeOnComplete ?? queueJobOptions?.removeOnComplete ?? this.#globalJobOptions?.removeOnComplete,
|
|
241
|
+
removeOnFail: jobOptions?.removeOnFail ?? queueJobOptions?.removeOnFail ?? this.#globalJobOptions?.removeOnFail
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Return the configured default worker timeout.
|
|
246
|
+
*/
|
|
247
|
+
getWorkerTimeout() {
|
|
248
|
+
return this.#workerTimeout;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Normalize job retry settings so top-level `maxRetries` participates in the
|
|
252
|
+
* merge like `retry.maxRetries`.
|
|
253
|
+
*/
|
|
254
|
+
#normalizeJobRetryConfig(jobOptions) {
|
|
255
|
+
if (!jobOptions || jobOptions.retry === void 0 && jobOptions.maxRetries === void 0) {
|
|
256
|
+
return void 0;
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
...jobOptions.retry,
|
|
260
|
+
maxRetries: jobOptions.retry?.maxRetries ?? jobOptions.maxRetries
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
188
265
|
// src/queue_manager.ts
|
|
266
|
+
var noopInternalOperationWrapper = async (fn) => fn();
|
|
267
|
+
var noopExecutionWrapper = async (fn) => fn();
|
|
189
268
|
var QueueManagerSingleton = class {
|
|
190
269
|
#initialized = false;
|
|
191
270
|
#defaultAdapter;
|
|
192
271
|
#adapters = {};
|
|
193
272
|
#adapterInstances = /* @__PURE__ */ new Map();
|
|
194
|
-
#globalRetryConfig;
|
|
195
|
-
#globalJobOptions;
|
|
196
|
-
#queueConfigs = /* @__PURE__ */ new Map();
|
|
197
273
|
#logger = consoleLogger;
|
|
198
274
|
#jobFactory;
|
|
275
|
+
#internalOperationWrapper;
|
|
276
|
+
#executionWrapper;
|
|
277
|
+
#configResolver = new QueueConfigResolver({});
|
|
199
278
|
#fakeState;
|
|
200
279
|
/**
|
|
201
280
|
* Initialize the queue system with the given configuration.
|
|
@@ -224,18 +303,14 @@ var QueueManagerSingleton = class {
|
|
|
224
303
|
async init(config) {
|
|
225
304
|
debug_default("initializing queue manager with config: %O", config);
|
|
226
305
|
this.#validateConfig(config);
|
|
227
|
-
this.#
|
|
306
|
+
await this.#cleanupBeforeReinitialization();
|
|
228
307
|
this.#defaultAdapter = config.default;
|
|
229
308
|
this.#adapters = config.adapters;
|
|
230
|
-
this.#globalRetryConfig = config.retry;
|
|
231
|
-
this.#globalJobOptions = config.defaultJobOptions;
|
|
232
309
|
this.#logger = config.logger ?? consoleLogger;
|
|
233
310
|
this.#jobFactory = config.jobFactory;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
}
|
|
311
|
+
this.#internalOperationWrapper = config.internalOperationWrapper;
|
|
312
|
+
this.#executionWrapper = config.executionWrapper;
|
|
313
|
+
this.#configResolver = QueueConfigResolver.from(config);
|
|
239
314
|
if (config.locations && config.locations.length > 0) {
|
|
240
315
|
const registered = await Locator.registerFromGlob(config.locations);
|
|
241
316
|
if (registered === 0) {
|
|
@@ -247,6 +322,40 @@ var QueueManagerSingleton = class {
|
|
|
247
322
|
this.#initialized = true;
|
|
248
323
|
return this;
|
|
249
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Destroy any materialized adapters from the current configuration before
|
|
327
|
+
* replacing it with a new one.
|
|
328
|
+
*/
|
|
329
|
+
async #cleanupBeforeReinitialization() {
|
|
330
|
+
const destroyedAdapters = /* @__PURE__ */ new Set();
|
|
331
|
+
await this.#destroyAdapters(this.#adapterInstances, destroyedAdapters);
|
|
332
|
+
if (this.#fakeState) {
|
|
333
|
+
await this.#destroyAdapter("fake", this.#fakeState.fakeAdapter, destroyedAdapters);
|
|
334
|
+
await this.#destroyAdapters(this.#fakeState.adapterInstances, destroyedAdapters);
|
|
335
|
+
this.#fakeState = void 0;
|
|
336
|
+
}
|
|
337
|
+
this.#adapterInstances.clear();
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Destroy a collection of adapters while avoiding double-destroying the same
|
|
341
|
+
* instance through multiple references.
|
|
342
|
+
*/
|
|
343
|
+
async #destroyAdapters(adapters, destroyedAdapters) {
|
|
344
|
+
for (const [name, adapter] of adapters) {
|
|
345
|
+
await this.#destroyAdapter(name, adapter, destroyedAdapters);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Destroy a single adapter once for the current cleanup pass.
|
|
350
|
+
*/
|
|
351
|
+
async #destroyAdapter(name, adapter, destroyedAdapters) {
|
|
352
|
+
if (destroyedAdapters.has(adapter)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
destroyedAdapters.add(adapter);
|
|
356
|
+
debug_default('destroying adapter "%s" before reinitialization', name);
|
|
357
|
+
await adapter.destroy();
|
|
358
|
+
}
|
|
250
359
|
/**
|
|
251
360
|
* Get an adapter instance by name.
|
|
252
361
|
*
|
|
@@ -324,11 +433,11 @@ var QueueManagerSingleton = class {
|
|
|
324
433
|
defaultAdapter: this.#defaultAdapter,
|
|
325
434
|
adapters: this.#adapters,
|
|
326
435
|
adapterInstances: this.#adapterInstances,
|
|
327
|
-
globalRetryConfig: this.#globalRetryConfig,
|
|
328
|
-
globalJobOptions: this.#globalJobOptions,
|
|
329
|
-
queueConfigs: this.#queueConfigs,
|
|
330
436
|
logger: this.#logger,
|
|
331
437
|
jobFactory: this.#jobFactory,
|
|
438
|
+
internalOperationWrapper: this.#internalOperationWrapper,
|
|
439
|
+
executionWrapper: this.#executionWrapper,
|
|
440
|
+
configResolver: this.#configResolver,
|
|
332
441
|
fakeAdapter
|
|
333
442
|
};
|
|
334
443
|
const fakeFactory = () => fakeAdapter;
|
|
@@ -356,35 +465,11 @@ var QueueManagerSingleton = class {
|
|
|
356
465
|
this.#defaultAdapter = state.defaultAdapter;
|
|
357
466
|
this.#adapters = state.adapters;
|
|
358
467
|
this.#adapterInstances = state.adapterInstances;
|
|
359
|
-
this.#globalRetryConfig = state.globalRetryConfig;
|
|
360
|
-
this.#globalJobOptions = state.globalJobOptions;
|
|
361
|
-
this.#queueConfigs = state.queueConfigs;
|
|
362
468
|
this.#logger = state.logger;
|
|
363
469
|
this.#jobFactory = state.jobFactory;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
*
|
|
368
|
-
* Configuration is merged with priority: job > queue > global.
|
|
369
|
-
* This allows specific jobs or queues to override global defaults.
|
|
370
|
-
*
|
|
371
|
-
* @param queue - The queue name
|
|
372
|
-
* @param jobRetryConfig - Optional job-level retry config
|
|
373
|
-
* @returns The merged retry configuration
|
|
374
|
-
*
|
|
375
|
-
* @example
|
|
376
|
-
* ```typescript
|
|
377
|
-
* // Global: maxRetries=3, Queue: maxRetries=5, Job: maxRetries=1
|
|
378
|
-
* // Result: maxRetries=1 (job wins)
|
|
379
|
-
* const config = QueueManager.getMergedRetryConfig('emails', { maxRetries: 1 })
|
|
380
|
-
* ```
|
|
381
|
-
*/
|
|
382
|
-
getMergedRetryConfig(queue, jobRetryConfig) {
|
|
383
|
-
const queueConfig = this.#queueConfigs.get(queue);
|
|
384
|
-
const queueRetryConfig = queueConfig?.retry || {};
|
|
385
|
-
let maxRetries = jobRetryConfig?.maxRetries ?? queueRetryConfig.maxRetries ?? this.#globalRetryConfig?.maxRetries ?? 0;
|
|
386
|
-
let backoff = jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff;
|
|
387
|
-
return { maxRetries, backoff };
|
|
470
|
+
this.#internalOperationWrapper = state.internalOperationWrapper;
|
|
471
|
+
this.#executionWrapper = state.executionWrapper;
|
|
472
|
+
this.#configResolver = state.configResolver;
|
|
388
473
|
}
|
|
389
474
|
/**
|
|
390
475
|
* Get the configured job factory for custom instantiation.
|
|
@@ -395,15 +480,37 @@ var QueueManagerSingleton = class {
|
|
|
395
480
|
return this.#jobFactory;
|
|
396
481
|
}
|
|
397
482
|
/**
|
|
398
|
-
*
|
|
483
|
+
* Whether the queue manager has been initialized.
|
|
399
484
|
*/
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
485
|
+
isInitialized() {
|
|
486
|
+
return this.#initialized;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Get the configured logger used by the queue runtime.
|
|
490
|
+
*/
|
|
491
|
+
getLogger() {
|
|
492
|
+
return this.#logger;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Get the configured internal operation wrapper.
|
|
496
|
+
*/
|
|
497
|
+
getInternalOperationWrapper() {
|
|
498
|
+
return this.#internalOperationWrapper ?? noopInternalOperationWrapper;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Get the configured execution wrapper.
|
|
502
|
+
*/
|
|
503
|
+
getExecutionWrapper() {
|
|
504
|
+
return this.#executionWrapper ?? noopExecutionWrapper;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Get the resolver responsible for effective queue/job runtime config.
|
|
508
|
+
*/
|
|
509
|
+
getConfigResolver() {
|
|
510
|
+
if (!this.#initialized) {
|
|
511
|
+
throw new E_QUEUE_NOT_INITIALIZED();
|
|
512
|
+
}
|
|
513
|
+
return this.#configResolver;
|
|
407
514
|
}
|
|
408
515
|
#validateConfig(config) {
|
|
409
516
|
if (!config.adapters || Object.keys(config.adapters).length === 0) {
|
|
@@ -449,6 +556,9 @@ var QueueManagerSingleton = class {
|
|
|
449
556
|
}
|
|
450
557
|
this.#adapterInstances.clear();
|
|
451
558
|
this.#initialized = false;
|
|
559
|
+
this.#internalOperationWrapper = void 0;
|
|
560
|
+
this.#executionWrapper = void 0;
|
|
561
|
+
this.#configResolver = new QueueConfigResolver({});
|
|
452
562
|
this.#fakeState = void 0;
|
|
453
563
|
}
|
|
454
564
|
};
|
|
@@ -588,7 +698,9 @@ var JobDispatcher = class {
|
|
|
588
698
|
const id = randomUUID();
|
|
589
699
|
debug_default("dispatching job %s with id %s using payload %s", this.#name, id, this.#payload);
|
|
590
700
|
const adapter = this.#getAdapterInstance();
|
|
591
|
-
const
|
|
701
|
+
const wrapInternal = QueueManager.getInternalOperationWrapper();
|
|
702
|
+
const parsedDelay = this.#delay ? parse(this.#delay) : void 0;
|
|
703
|
+
const jobData = {
|
|
592
704
|
id,
|
|
593
705
|
name: this.#name,
|
|
594
706
|
payload: this.#payload,
|
|
@@ -596,15 +708,15 @@ var JobDispatcher = class {
|
|
|
596
708
|
priority: this.#priority,
|
|
597
709
|
groupId: this.#groupId
|
|
598
710
|
};
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
};
|
|
711
|
+
const message = { jobs: [jobData], queue: this.#queue, delay: parsedDelay };
|
|
712
|
+
await dispatchChannel.tracePromise(async () => {
|
|
713
|
+
if (parsedDelay !== void 0) {
|
|
714
|
+
await wrapInternal(() => adapter.pushLaterOn(this.#queue, jobData, parsedDelay));
|
|
715
|
+
} else {
|
|
716
|
+
await wrapInternal(() => adapter.pushOn(this.#queue, jobData));
|
|
717
|
+
}
|
|
718
|
+
}, message);
|
|
719
|
+
return { jobId: id };
|
|
608
720
|
}
|
|
609
721
|
/**
|
|
610
722
|
* Thenable implementation for auto-dispatch when awaited.
|
|
@@ -729,8 +841,10 @@ var JobBatchDispatcher = class {
|
|
|
729
841
|
* ```
|
|
730
842
|
*/
|
|
731
843
|
async run() {
|
|
844
|
+
if (this.#payloads.length === 0) return { jobIds: [] };
|
|
732
845
|
debug_default("dispatching %d jobs of type %s", this.#payloads.length, this.#name);
|
|
733
846
|
const adapter = this.#getAdapterInstance();
|
|
847
|
+
const wrapInternal = QueueManager.getInternalOperationWrapper();
|
|
734
848
|
const jobs = this.#payloads.map((payload) => ({
|
|
735
849
|
id: randomUUID2(),
|
|
736
850
|
name: this.#name,
|
|
@@ -739,10 +853,11 @@ var JobBatchDispatcher = class {
|
|
|
739
853
|
priority: this.#priority,
|
|
740
854
|
groupId: this.#groupId
|
|
741
855
|
}));
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
};
|
|
856
|
+
const message = { jobs, queue: this.#queue };
|
|
857
|
+
await dispatchChannel.tracePromise(async () => {
|
|
858
|
+
await wrapInternal(() => adapter.pushManyOn(this.#queue, jobs));
|
|
859
|
+
}, message);
|
|
860
|
+
return { jobIds: jobs.map((job) => job.id) };
|
|
746
861
|
}
|
|
747
862
|
/**
|
|
748
863
|
* Thenable implementation for auto-dispatch when awaited.
|
|
@@ -1562,4 +1677,4 @@ export {
|
|
|
1562
1677
|
ScheduleBuilder,
|
|
1563
1678
|
Job
|
|
1564
1679
|
};
|
|
1565
|
-
//# sourceMappingURL=chunk-
|
|
1680
|
+
//# sourceMappingURL=chunk-VHN3XZDC.js.map
|