@warlock.js/queue 5.30.0 → 5.32.0
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/CHANGELOG.md +18 -0
- package/README.md +4 -2
- package/cjs/index.cjs +32 -2
- package/cjs/index.cjs.map +1 -1
- package/esm/config.d.mts +4 -6
- package/esm/config.d.mts.map +1 -1
- package/esm/dashboard-boot.d.mts +2 -4
- package/esm/dashboard-boot.d.mts.map +1 -1
- package/esm/dashboard.d.mts +8 -7
- package/esm/dashboard.d.mts.map +1 -1
- package/esm/dashboard.mjs +1 -1
- package/esm/dashboard.mjs.map +1 -1
- package/esm/define-job.d.mts +2 -2
- package/esm/define-job.d.mts.map +1 -1
- package/esm/define-job.mjs +15 -1
- package/esm/define-job.mjs.map +1 -1
- package/esm/dispatch-interceptor.d.mts +33 -0
- package/esm/dispatch-interceptor.d.mts.map +1 -0
- package/esm/dispatch-interceptor.mjs +14 -0
- package/esm/dispatch-interceptor.mjs.map +1 -0
- package/esm/duration.d.mts +1 -3
- package/esm/duration.d.mts.map +1 -1
- package/esm/errors.d.mts +6 -7
- package/esm/errors.d.mts.map +1 -1
- package/esm/failed-jobs.d.mts +8 -7
- package/esm/failed-jobs.d.mts.map +1 -1
- package/esm/failed-jobs.mjs +2 -1
- package/esm/failed-jobs.mjs.map +1 -1
- package/esm/index.d.mts +3 -1
- package/esm/index.mjs +3 -1
- package/esm/job-registry.d.mts +10 -0
- package/esm/job-registry.d.mts.map +1 -0
- package/esm/process-job.mjs.map +1 -1
- package/esm/queue-connector.d.mts +3 -5
- package/esm/queue-connector.d.mts.map +1 -1
- package/esm/queue-connector.mjs.map +1 -1
- package/esm/queue-context.d.mts +8 -7
- package/esm/queue-context.d.mts.map +1 -1
- package/esm/queue-dashboard-unguarded.error.d.mts +1 -2
- package/esm/queue-dashboard-unguarded.error.d.mts.map +1 -1
- package/esm/queue-dashboard-unguarded.error.mjs.map +1 -1
- package/esm/queue-manager.d.mts +5 -7
- package/esm/queue-manager.d.mts.map +1 -1
- package/esm/types.d.mts +76 -46
- package/esm/types.d.mts.map +1 -1
- package/llms-full.txt +36 -7
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/define-jobs/SKILL.md +36 -6
- package/skills/index.md +4 -1
- package/skills/queue-notifications/SKILL.md +0 -1
package/esm/types.d.mts
CHANGED
|
@@ -1,46 +1,52 @@
|
|
|
1
1
|
import { ConnectionOptions } from "bullmq";
|
|
2
2
|
import { Middleware } from "@warlock.js/core";
|
|
3
|
-
|
|
4
3
|
//#region ../../packages/queue/src/types.d.ts
|
|
5
4
|
/** Units accepted in a {@link Duration} string. */
|
|
6
|
-
type DurationUnit = "ms" | "s" | "m" | "h" | "d";
|
|
5
|
+
export type DurationUnit = "ms" | "s" | "m" | "h" | "d";
|
|
7
6
|
/**
|
|
8
7
|
* A duration: milliseconds as a number, or a string such as `"500ms"`,
|
|
9
8
|
* `"30s"`, `"10m"`, `"2h"`, `"1d"`.
|
|
10
9
|
*/
|
|
11
|
-
type Duration = number | `${number}${DurationUnit}`;
|
|
10
|
+
export type Duration = number | `${number}${DurationUnit}`;
|
|
12
11
|
/**
|
|
13
12
|
* How a failed attempt waits before the next one. A number is a fixed delay in
|
|
14
13
|
* milliseconds.
|
|
15
14
|
*/
|
|
16
|
-
type JobBackoff = number | {
|
|
17
|
-
/** `fixed` waits `delay` every time; `exponential` waits `delay * 2^(attempt - 1)`. */
|
|
15
|
+
export type JobBackoff = number | {
|
|
16
|
+
/** `fixed` waits `delay` every time; `exponential` waits `delay * 2^(attempt - 1)`. */
|
|
17
|
+
type: "fixed" | "exponential";
|
|
18
|
+
/** Base delay in milliseconds. */
|
|
18
19
|
delay: number;
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* How many finished jobs to keep: `true` removes them immediately, `false`
|
|
22
23
|
* keeps them all, a number keeps the newest N.
|
|
23
24
|
*/
|
|
24
|
-
type JobRetention = boolean | number;
|
|
25
|
+
export type JobRetention = boolean | number;
|
|
25
26
|
/**
|
|
26
27
|
* Options that shape how a job is retried and retained. Set app-wide in
|
|
27
28
|
* `queue.defaultJobOptions`, per job in `defineJob`.
|
|
28
29
|
*/
|
|
29
|
-
type JobOptions = {
|
|
30
|
-
/** Total attempts including the first. Default `1` (no retry). */
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
export type JobOptions = {
|
|
31
|
+
/** Total attempts including the first. Default `1` (no retry). */
|
|
32
|
+
attempts?: number;
|
|
33
|
+
/** Wait between attempts. */
|
|
34
|
+
backoff?: JobBackoff;
|
|
35
|
+
/** Retention for completed jobs. Default keeps them. */
|
|
36
|
+
removeOnComplete?: JobRetention;
|
|
37
|
+
/** Retention for failed jobs. Default keeps them, so `failedJobs()` can list them. */
|
|
33
38
|
removeOnFail?: JobRetention;
|
|
34
39
|
};
|
|
35
40
|
/**
|
|
36
41
|
* In-process worker settings.
|
|
37
42
|
*/
|
|
38
|
-
type QueueWorkersConfig = {
|
|
43
|
+
export type QueueWorkersConfig = {
|
|
39
44
|
/**
|
|
40
45
|
* Start workers in this process. Default `true`. Set `false` for a process
|
|
41
46
|
* that only dispatches jobs while another process consumes them.
|
|
42
47
|
*/
|
|
43
|
-
enabled?: boolean;
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
/** Jobs processed in parallel per queue. Default `1`. */
|
|
44
50
|
concurrency?: number;
|
|
45
51
|
/**
|
|
46
52
|
* How long shutdown waits for active jobs before force-closing the workers,
|
|
@@ -53,8 +59,10 @@ type QueueWorkersConfig = {
|
|
|
53
59
|
* `enabled` is `true`. Equivalent to calling `queueDashboard()` yourself at
|
|
54
60
|
* boot, driven by config instead — see `warlock add bull-board`.
|
|
55
61
|
*/
|
|
56
|
-
type QueueDashboardConfig = {
|
|
57
|
-
/** Mount the dashboard at boot. Default `false`. */
|
|
62
|
+
export type QueueDashboardConfig = {
|
|
63
|
+
/** Mount the dashboard at boot. Default `false`. */
|
|
64
|
+
enabled?: boolean;
|
|
65
|
+
/** URL path the dashboard is mounted on. Default `"/admin/queues"`. */
|
|
58
66
|
path?: string;
|
|
59
67
|
/**
|
|
60
68
|
* Run before every dashboard route — this is how the dashboard is guarded.
|
|
@@ -66,47 +74,65 @@ type QueueDashboardConfig = {
|
|
|
66
74
|
/**
|
|
67
75
|
* The `queue` configuration key — `src/config/queue.ts`.
|
|
68
76
|
*/
|
|
69
|
-
type QueueConfig = {
|
|
77
|
+
export type QueueConfig = {
|
|
70
78
|
/**
|
|
71
79
|
* Redis connection. Any BullMQ connection option: `{ host, port, password, db }`,
|
|
72
80
|
* `{ url }`, or an ioredis instance.
|
|
73
81
|
*/
|
|
74
|
-
connection: ConnectionOptions;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
82
|
+
connection: ConnectionOptions;
|
|
83
|
+
/** Redis key prefix for every queue. Default `"warlock"`. */
|
|
84
|
+
prefix?: string;
|
|
85
|
+
/** Queue name used when a job does not name one. Default `"default"`. */
|
|
86
|
+
defaultQueue?: string;
|
|
87
|
+
/** Defaults merged under every job's own options. */
|
|
88
|
+
defaultJobOptions?: JobOptions;
|
|
89
|
+
/** In-process workers. */
|
|
90
|
+
workers?: QueueWorkersConfig;
|
|
91
|
+
/** The bull-board job dashboard. */
|
|
79
92
|
dashboard?: QueueDashboardConfig;
|
|
80
93
|
};
|
|
81
94
|
/** A progress value: a number (e.g. a percentage) or a JSON object. */
|
|
82
|
-
type JobProgress = number | Record<string, unknown>;
|
|
95
|
+
export type JobProgress = number | Record<string, unknown>;
|
|
83
96
|
/**
|
|
84
97
|
* What a job handler receives beside its payload.
|
|
85
98
|
*/
|
|
86
|
-
type JobContext = {
|
|
87
|
-
/** The job id. */
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
export type JobContext = {
|
|
100
|
+
/** The job id. */
|
|
101
|
+
id: string;
|
|
102
|
+
/** The job name given to `defineJob`. */
|
|
103
|
+
name: string;
|
|
104
|
+
/** The queue the job runs on. */
|
|
105
|
+
queue: string;
|
|
106
|
+
/** The current attempt, starting at `1`. */
|
|
107
|
+
attempt: number;
|
|
108
|
+
/** Total attempts allowed. */
|
|
109
|
+
maxAttempts: number;
|
|
110
|
+
/** Record progress; readable through `job.find(id)`. */
|
|
111
|
+
progress(value: JobProgress): Promise<void>;
|
|
112
|
+
/** Append a line to the job's log. */
|
|
113
|
+
log(line: string): Promise<void>;
|
|
114
|
+
/** State captured at dispatch by the registered queue context, if any. */
|
|
94
115
|
context: unknown;
|
|
95
116
|
};
|
|
96
117
|
/**
|
|
97
118
|
* The definition passed to `defineJob`.
|
|
98
119
|
*/
|
|
99
|
-
type JobDefinition<TPayload, TResult> = JobOptions & {
|
|
100
|
-
/** Set `false` to skip capturing the queue context for this job. */
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
export type JobDefinition<TPayload, TResult> = JobOptions & {
|
|
121
|
+
/** Set `false` to skip capturing the queue context for this job. */
|
|
122
|
+
context?: false;
|
|
123
|
+
/** Unique job name, e.g. `"invoices.send"`. */
|
|
124
|
+
name: string;
|
|
125
|
+
/** Queue to run on. Default: `queue.defaultQueue` (`"default"`). */
|
|
126
|
+
queue?: string;
|
|
127
|
+
/** The work. Throw to fail the attempt; the return value is stored as the job result. */
|
|
103
128
|
handle(payload: TPayload, context: JobContext): Promise<TResult> | TResult;
|
|
104
129
|
};
|
|
105
130
|
/**
|
|
106
131
|
* Per-dispatch options.
|
|
107
132
|
*/
|
|
108
|
-
type DispatchOptions = {
|
|
109
|
-
/** Wait before the job becomes available. */
|
|
133
|
+
export type DispatchOptions = {
|
|
134
|
+
/** Wait before the job becomes available. */
|
|
135
|
+
delay?: Duration;
|
|
110
136
|
/**
|
|
111
137
|
* Priority: `1` is the highest; larger numbers run later. Omit for no
|
|
112
138
|
* priority — such jobs run ahead of every prioritized job.
|
|
@@ -116,20 +142,22 @@ type DispatchOptions = {
|
|
|
116
142
|
* Explicit job id. Dispatching again with an id that still exists is a
|
|
117
143
|
* no-op, which makes dispatch idempotent.
|
|
118
144
|
*/
|
|
119
|
-
jobId?: string;
|
|
120
|
-
|
|
145
|
+
jobId?: string;
|
|
146
|
+
/** Override the job's attempts for this dispatch. */
|
|
147
|
+
attempts?: number;
|
|
148
|
+
/** Override the job's backoff for this dispatch. */
|
|
121
149
|
backoff?: JobBackoff;
|
|
122
150
|
};
|
|
123
151
|
/** What `dispatch` resolves with. */
|
|
124
|
-
type DispatchedJob = {
|
|
152
|
+
export type DispatchedJob = {
|
|
125
153
|
id: string;
|
|
126
154
|
name: string;
|
|
127
155
|
queue: string;
|
|
128
156
|
};
|
|
129
157
|
/** A job's lifecycle state. */
|
|
130
|
-
type JobState = "waiting" | "delayed" | "prioritized" | "active" | "completed" | "failed" | "waiting-children" | "unknown";
|
|
158
|
+
export type JobState = "waiting" | "delayed" | "prioritized" | "active" | "completed" | "failed" | "waiting-children" | "unknown";
|
|
131
159
|
/** A point-in-time view of a job. */
|
|
132
|
-
type JobSnapshot<TPayload = unknown, TResult = unknown> = {
|
|
160
|
+
export type JobSnapshot<TPayload = unknown, TResult = unknown> = {
|
|
133
161
|
id: string;
|
|
134
162
|
name: string;
|
|
135
163
|
queue: string;
|
|
@@ -143,7 +171,7 @@ type JobSnapshot<TPayload = unknown, TResult = unknown> = {
|
|
|
143
171
|
finishedAt?: Date;
|
|
144
172
|
};
|
|
145
173
|
/** A failed job, with the means to retry it. */
|
|
146
|
-
type FailedJob<TPayload = unknown> = {
|
|
174
|
+
export type FailedJob<TPayload = unknown> = {
|
|
147
175
|
id: string;
|
|
148
176
|
name: string;
|
|
149
177
|
queue: string;
|
|
@@ -151,18 +179,20 @@ type FailedJob<TPayload = unknown> = {
|
|
|
151
179
|
attemptsMade: number;
|
|
152
180
|
failedReason: string;
|
|
153
181
|
stacktrace: string[];
|
|
154
|
-
failedAt?: Date;
|
|
182
|
+
failedAt?: Date;
|
|
183
|
+
/** Move the job back to waiting so a worker picks it up again. */
|
|
155
184
|
retry(): Promise<void>;
|
|
156
185
|
};
|
|
157
186
|
/**
|
|
158
187
|
* A job returned by `defineJob`.
|
|
159
188
|
*/
|
|
160
|
-
type QueueJob<TPayload, TResult = unknown> = {
|
|
189
|
+
export type QueueJob<TPayload, TResult = unknown> = {
|
|
161
190
|
readonly name: string;
|
|
162
|
-
readonly queue: string;
|
|
163
|
-
|
|
191
|
+
readonly queue: string;
|
|
192
|
+
/** Enqueue the job. */
|
|
193
|
+
dispatch(payload: TPayload, options?: DispatchOptions): Promise<DispatchedJob>;
|
|
194
|
+
/** Read a job of this type by id; `undefined` when it no longer exists. */
|
|
164
195
|
find(id: string): Promise<JobSnapshot<TPayload, TResult> | undefined>;
|
|
165
196
|
};
|
|
166
197
|
//#endregion
|
|
167
|
-
export { DispatchOptions, DispatchedJob, Duration, DurationUnit, FailedJob, JobBackoff, JobContext, JobDefinition, JobOptions, JobProgress, JobRetention, JobSnapshot, JobState, QueueConfig, QueueDashboardConfig, QueueJob, QueueWorkersConfig };
|
|
168
198
|
//# sourceMappingURL=types.d.mts.map
|
package/esm/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../packages/queue/src/types.ts"],"mappings":";;;;YAIY;;;;;YAMA,gCAAgC;;;;;YAMhC;;EAIN;;EAEA;;;;;;YAOM;;;;;YAMA;;EAEV;;EAEA,UAAU;;EAEV,mBAAmB;;EAEnB,eAAe;;;;;YAML;;;;;EAKV;;EAEA;;;;;EAKA;;;;;;;YAQU;;EAEV;;EAEA;;;;;;EAMA,aAAa;;;;;YAMH;;;;;EAKV,YAAY;;EAEZ;;EAEA;;EAEA,oBAAoB;;EAEpB,UAAU;;EAEV,YAAY;;;YAIF,uBAAuB;;;;YAKvB;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA,SAAS,OAAO,cAAc;;EAE9B,IAAI,eAAe;;EAEnB;;;;;YAMU,cAAc,UAAU,WAAW;;EAE7C;;EAEA;;EAEA;;EAEA,OAAO,SAAS,UAAU,SAAS,aAAa,QAAQ,WAAW;;;;;YAMzD;;EAEV,QAAQ;;;;;EAKR;;;;;EAKA;;EAEA;;EAEA,UAAU;;;YAIA;EACV;EACA;EACA;;;YAIU;;YAWA,YAAY,oBAAoB;EAC1C;EACA;EACA;EACA,OAAO;EACP,SAAS;EACT,UAAU;EACV;EACA,SAAS;EACT;EACA,WAAW;EACX,aAAa;;;YAIH,UAAU;EACpB;EACA;EACA;EACA,SAAS;EACT;EACA;EACA;EACA,WAAW;;EAEX,SAAS;;;;;YAMC,SAAS,UAAU;WACpB;WACA;;EAET,SAAS,SAAS,UAAU,UAAU,kBAAkB,QAAQ;;EAEhE,KAAK,aAAa,QAAQ,YAAY,UAAU"}
|
package/llms-full.txt
CHANGED
|
@@ -71,7 +71,7 @@ Every process must use the same `prefix`, or they will not see each other's jobs
|
|
|
71
71
|
|
|
72
72
|
---
|
|
73
73
|
name: define-jobs
|
|
74
|
-
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
74
|
+
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
75
75
|
---
|
|
76
76
|
|
|
77
77
|
# Define and dispatch jobs
|
|
@@ -99,11 +99,14 @@ The job module must be imported by the process that runs workers — in a Warloc
|
|
|
99
99
|
```ts
|
|
100
100
|
const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
|
|
101
101
|
|
|
102
|
-
await sendInvoice.dispatch(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
await sendInvoice.dispatch(
|
|
103
|
+
{ invoiceId: "43" },
|
|
104
|
+
{
|
|
105
|
+
delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
|
|
106
|
+
priority: 1, // 1 runs first; larger numbers later
|
|
107
|
+
jobId: "invoice:43", // a second dispatch with a live id is ignored
|
|
108
|
+
},
|
|
109
|
+
);
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
@@ -115,6 +118,16 @@ const job = await sendInvoice.find(id);
|
|
|
115
118
|
// { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
|
|
116
119
|
```
|
|
117
120
|
|
|
121
|
+
## List the defined jobs
|
|
122
|
+
|
|
123
|
+
`registeredJobs()` returns every definition `defineJob()` registered in this process, and `queueOf(definition)` the queue it runs on. Neither touches Redis; `warlock inspect --section jobs` prints the same list for a whole app.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { queueOf, registeredJobs } from "@warlock.js/queue";
|
|
127
|
+
|
|
128
|
+
const jobs = registeredJobs().map((job) => ({ name: job.name, queue: queueOf(job) }));
|
|
129
|
+
```
|
|
130
|
+
|
|
118
131
|
## Carry request context into jobs
|
|
119
132
|
|
|
120
133
|
```ts
|
|
@@ -131,6 +144,23 @@ setQueueContext(
|
|
|
131
144
|
- `defineJob({ context: false })` opts a job out.
|
|
132
145
|
- Throw `UnrecoverableJobError` from a handler or `restore` to fail the job without retries.
|
|
133
146
|
|
|
147
|
+
## Test dispatches without Redis
|
|
148
|
+
|
|
149
|
+
In the test environment `queueFake` from `@warlock.js/core/tests` is installed by default: dispatches are recorded, not pushed, and `find()` answers from the record.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// with: import { queueFake } from "@warlock.js/core/tests";
|
|
153
|
+
// plus your job (sendInvoice) and service (placeOrderService)
|
|
154
|
+
it("queues the invoice", async () => {
|
|
155
|
+
const order = await placeOrderService({ items: ["pen"] });
|
|
156
|
+
|
|
157
|
+
queueFake.assertPushed(sendInvoice, { invoiceId: order.invoiceId });
|
|
158
|
+
expect(queueFake.pushed(sendInvoice)).toHaveLength(1);
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Jobs are not run — call the handler to test it. Opt out with `tests.fakes: { queue: false }`. The seam is `setDispatchInterceptor({ dispatch, find? })`: while set, every job's `dispatch()` / `find()` goes to it instead of BullMQ; set it back to `undefined` to restore the broker.
|
|
163
|
+
|
|
134
164
|
## Rules
|
|
135
165
|
|
|
136
166
|
- A job name with no handler in the worker process fails at once, without retries.
|
|
@@ -278,4 +308,3 @@ await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10
|
|
|
278
308
|
- Deliveries run under the job name `warlock.notifications.deliver`.
|
|
279
309
|
|
|
280
310
|
|
|
281
|
-
|
package/llms.txt
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
9
|
- [configure-queue](@warlock.js/queue/configure-queue/SKILL.md): Configure `@warlock.js/queue`: the declarative `src/config/queue.ts` (`QueueConfig` — `connection`, `prefix`, `defaultQueue`, `defaultJobOptions`, `workers: { enabled, concurrency, shutdownTimeout }`), registering `queueConnector()` in `warlock.config.ts > connectors`, running a dispatch-only process, and graceful shutdown. Programmatic `setQueueConfig` / `startWorkers` / `closeQueue` for scripts and tests. Triggers: `QueueConfig`, `queueConnector`, `setQueueConfig`, `startWorkers`, `closeQueue`, `workers.enabled`, `shutdownTimeout`; "configure the queue", "connect BullMQ to Redis", "disable workers in the web process", "graceful shutdown of jobs". Skip: writing jobs — the `define-jobs` topic.
|
|
10
|
-
- [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
|
|
10
|
+
- [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.
|
|
11
11
|
- [manage-failed-jobs](@warlock.js/queue/manage-failed-jobs/SKILL.md): Inspect and retry failed `@warlock.js/queue` jobs: `failedJobs({ queue, start, end })` returns `FailedJob[]` (`id`, `name`, `payload`, `attemptsMade`, `failedReason`, `stacktrace`, `failedAt`, `retry()`), `retryFailedJob(id, { queue })` (throws `FailedJobNotFoundError`), and the bull-board UI — `warlock add bull-board` (requires: ["queue"], so it installs queue first when missing) writes a `dashboard: { enabled, path, middleware }` block to `src/config/queue.ts`; a guard middleware is required in production or boot throws `QueueDashboardUnguardedError`. Manual `queueDashboard(server, { basePath, queues, middleware })` also available — needs `@bull-board/api` + `@bull-board/fastify`, loaded only on call, missing ones throw `QueueDashboardDependencyError`. Triggers: `failedJobs`, `retryFailedJob`, `queueDashboard`, `bull-board`, `dashboard.enabled`, `QueueDashboardUnguardedError`; "list failed jobs", "retry a failed job", "queue dashboard", "job admin UI". Skip: defining retries — the `define-jobs` topic.
|
|
12
12
|
- [overview](@warlock.js/queue/overview/SKILL.md): @warlock.js/queue overview for orientation and choosing the focused skill for the task.
|
|
13
13
|
- [queue-notifications](@warlock.js/queue/queue-notifications/SKILL.md): Send `@warlock.js/notifications` `.queue()` deliveries through BullMQ with `bullmqQueue({ queue?, attempts?, backoff? })` from `@warlock.js/notifications` — put it in the `queue` slot of `src/config/notifications.ts`; the queue workers deliver. Honours `SendOptions.delay` (number = seconds, or "10m"), retries a failing `channel.send`, and fails at once for a channel missing from the worker config. Triggers: `bullmqQueue`, `NotificationConfig.queue`, `QueuePackageNotInstalledError`; "queue notifications with BullMQ", "retry notification delivery", "delayed notification". Skip: the herald backend — the `queue-notifications` topic of the `warlock-js-notifications` skill.
|
package/package.json
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"@bull-board/api": "^9.0.0",
|
|
13
13
|
"@bull-board/fastify": "^9.0.0",
|
|
14
|
-
"@warlock.js/core": "5.
|
|
15
|
-
"@warlock.js/logger": "5.
|
|
14
|
+
"@warlock.js/core": "5.32.0",
|
|
15
|
+
"@warlock.js/logger": "5.32.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"@bull-board/api": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"author": "hassanzohdy",
|
|
40
40
|
"license": "MIT",
|
|
41
|
-
"version": "5.
|
|
41
|
+
"version": "5.32.0",
|
|
42
42
|
"main": "./cjs/index.cjs",
|
|
43
43
|
"module": "./esm/index.mjs",
|
|
44
44
|
"types": "./esm/index.d.mts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: define-jobs
|
|
3
|
-
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
3
|
+
description: 'Define and dispatch background jobs with `@warlock.js/queue`: `defineJob({ name, queue?, attempts?, backoff?, removeOnComplete?, removeOnFail?, handle(payload, ctx) })` returns a typed job; `job.dispatch(payload, { delay, priority, jobId, attempts, backoff })`; the handler context (`id`, `attempt`, `maxAttempts`, `progress()`, `log()`); reading a job with `job.find(id)`. Triggers: `setDispatchInterceptor`, `queueFake`, `defineJob`, `.dispatch(`, `ctx.progress`, `JobContext`, `DispatchOptions`, `backoff`, `attempts`, `priority`, `jobId`; "run this in the background", "retry with backoff", "delay a job", "job progress", "idempotent dispatch". Skip: config and workers — the `configure-queue` topic; failed jobs — the `manage-failed-jobs` topic.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Define and dispatch jobs
|
|
@@ -28,11 +28,14 @@ The job module must be imported by the process that runs workers — in a Warloc
|
|
|
28
28
|
```ts
|
|
29
29
|
const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
|
|
30
30
|
|
|
31
|
-
await sendInvoice.dispatch(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
await sendInvoice.dispatch(
|
|
32
|
+
{ invoiceId: "43" },
|
|
33
|
+
{
|
|
34
|
+
delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
|
|
35
|
+
priority: 1, // 1 runs first; larger numbers later
|
|
36
|
+
jobId: "invoice:43", // a second dispatch with a live id is ignored
|
|
37
|
+
},
|
|
38
|
+
);
|
|
36
39
|
```
|
|
37
40
|
|
|
38
41
|
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
@@ -44,6 +47,16 @@ const job = await sendInvoice.find(id);
|
|
|
44
47
|
// { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
|
|
45
48
|
```
|
|
46
49
|
|
|
50
|
+
## List the defined jobs
|
|
51
|
+
|
|
52
|
+
`registeredJobs()` returns every definition `defineJob()` registered in this process, and `queueOf(definition)` the queue it runs on. Neither touches Redis; `warlock inspect --section jobs` prints the same list for a whole app.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { queueOf, registeredJobs } from "@warlock.js/queue";
|
|
56
|
+
|
|
57
|
+
const jobs = registeredJobs().map((job) => ({ name: job.name, queue: queueOf(job) }));
|
|
58
|
+
```
|
|
59
|
+
|
|
47
60
|
## Carry request context into jobs
|
|
48
61
|
|
|
49
62
|
```ts
|
|
@@ -60,6 +73,23 @@ setQueueContext(
|
|
|
60
73
|
- `defineJob({ context: false })` opts a job out.
|
|
61
74
|
- Throw `UnrecoverableJobError` from a handler or `restore` to fail the job without retries.
|
|
62
75
|
|
|
76
|
+
## Test dispatches without Redis
|
|
77
|
+
|
|
78
|
+
In the test environment `queueFake` from `@warlock.js/core/tests` is installed by default: dispatches are recorded, not pushed, and `find()` answers from the record.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// with: import { queueFake } from "@warlock.js/core/tests";
|
|
82
|
+
// plus your job (sendInvoice) and service (placeOrderService)
|
|
83
|
+
it("queues the invoice", async () => {
|
|
84
|
+
const order = await placeOrderService({ items: ["pen"] });
|
|
85
|
+
|
|
86
|
+
queueFake.assertPushed(sendInvoice, { invoiceId: order.invoiceId });
|
|
87
|
+
expect(queueFake.pushed(sendInvoice)).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Jobs are not run — call the handler to test it. Opt out with `tests.fakes: { queue: false }`. The seam is `setDispatchInterceptor({ dispatch, find? })`: while set, every job's `dispatch()` / `find()` goes to it instead of BullMQ; set it back to `undefined` to restore the broker.
|
|
92
|
+
|
|
63
93
|
## Rules
|
|
64
94
|
|
|
65
95
|
- A job name with no handler in the worker process fails at once, without retries.
|
package/skills/index.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: 'Durable background jobs on BullMQ + Redis: retries, delays, priorities, workers, failed-job recovery. Exports `defineJob`, `queueConnector`, `startWorkers`, `closeQueue`, `getQueue`, `failedJobs`, `retryFailedJob`, `mountQueueDashboard`, `QueueConfig`, `QueueJob`. Use for: "run this in the background", "retry a failed job", "delay a job", "survive restarts", "job dashboard". Not this package: cron or recurring schedules → @warlock.js/scheduler; broker pub/sub messaging → @warlock.js/herald; core `Queue` is an in-memory batcher.'
|
|
3
3
|
---
|
|
4
|
+
|
|
4
5
|
# @warlock.js/queue
|
|
5
6
|
|
|
6
7
|
Jobs are stored in Redis (required; not bundled), retried on failure, and run by workers, in the app process by default. `defineJob({ name, handle })` registers a handler and returns a typed job; `job.dispatch(payload, options)` enqueues it. Any process with the same definition and a worker can run it. Server-only.
|
|
7
8
|
|
|
8
9
|
## The 80% path
|
|
10
|
+
|
|
9
11
|
1. Orient with `overview.md`.
|
|
10
12
|
2. Configure `src/config/queue.ts` and register `queueConnector()`: `configure-queue.md`.
|
|
11
13
|
3. Define and dispatch jobs (attempts, backoff, delay, priority, progress): `define-jobs.md`.
|
|
@@ -13,6 +15,7 @@ Jobs are stored in Redis (required; not bundled), retried on failure, and run by
|
|
|
13
15
|
5. Back `@warlock.js/notifications` `.queue()` with BullMQ: `queue-notifications.md`.
|
|
14
16
|
|
|
15
17
|
## Conventions and pitfalls
|
|
18
|
+
|
|
16
19
|
- Job names are the routing key; a job with no matching `defineJob` in the worker process cannot run.
|
|
17
20
|
- Handlers must be idempotent: retries re-run them.
|
|
18
21
|
- Shutdown lets active jobs finish up to `workers.shutdownTimeout`, then closes connections.
|
|
@@ -32,4 +32,3 @@ await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10
|
|
|
32
32
|
- `channel.send` throws → retried per `attempts` / `backoff`, then listed by `failedJobs()`.
|
|
33
33
|
- Channel not configured in the worker → fails at once, no retries.
|
|
34
34
|
- Deliveries run under the job name `warlock.notifications.deliver`.
|
|
35
|
-
|