@warlock.js/queue 5.19.1 → 5.20.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 +6 -0
- package/llms-full.txt +213 -213
- package/llms.txt +4 -4
- package/package.json +3 -3
- package/skills/configure-queue/SKILL.md +59 -59
- package/skills/define-jobs/SKILL.md +51 -51
- package/skills/index.md +21 -0
- package/skills/manage-failed-jobs/SKILL.md +68 -68
- package/skills/queue-notifications/SKILL.md +35 -35
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `@warlock.js/queue` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.20.0 - 2026-09-24
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Added a package-level skill index and clearer discovery descriptions for grouped agent guidance.
|
|
12
|
+
|
|
7
13
|
## 5.19.0 - 2026-09-23
|
|
8
14
|
|
|
9
15
|
### Changed
|
package/llms-full.txt
CHANGED
|
@@ -6,192 +6,192 @@
|
|
|
6
6
|
|
|
7
7
|
## configure-queue `@warlock.js/queue/configure-queue/SKILL.md`
|
|
8
8
|
|
|
9
|
-
---
|
|
10
|
-
name: configure-queue
|
|
11
|
-
description:
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Configure the queue
|
|
15
|
-
|
|
16
|
-
Redis is required. The config is declarative; the connector applies it.
|
|
17
|
-
|
|
18
|
-
```ts title="src/config/queue.ts"
|
|
19
|
-
import type { QueueConfig } from "@warlock.js/queue";
|
|
20
|
-
|
|
21
|
-
const queueConfig: QueueConfig = {
|
|
22
|
-
connection: { host: "127.0.0.1", port: 6379 }, // any BullMQ connection option, or { url }
|
|
23
|
-
prefix: "my-app", // Redis key prefix, default "warlock"
|
|
24
|
-
defaultJobOptions: { attempts: 3, backoff: { type: "exponential", delay: 1000 } },
|
|
25
|
-
workers: {
|
|
26
|
-
enabled: true, // default true
|
|
27
|
-
concurrency: 5, // jobs in parallel per queue, default 1
|
|
28
|
-
shutdownTimeout: 30_000, // ms to wait for active jobs, default 30000
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export default queueConfig;
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
```ts title="warlock.config.ts"
|
|
36
|
-
import { defineConfig } from "@warlock.js/core";
|
|
37
|
-
import { queueConnector } from "@warlock.js/queue";
|
|
38
|
-
|
|
39
|
-
export default defineConfig({
|
|
40
|
-
connectors: [queueConnector()],
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## What the connector does
|
|
45
|
-
|
|
46
|
-
- Starts in the **late** phase — after app code is imported — so every `defineJob` has registered. One worker per queue that has a job.
|
|
47
|
-
- No `queue` config → logs a warning and does nothing.
|
|
48
|
-
- On SIGINT/SIGTERM: workers stop taking jobs, active jobs get up to `shutdownTimeout` ms, then Redis connections close. A job still running after that is retried by another worker once its lock expires.
|
|
49
|
-
|
|
50
|
-
## Dispatch-only process
|
|
51
|
-
|
|
52
|
-
Set `workers.enabled: false`. `dispatch()` still works; nothing is processed in that process. Run the workers elsewhere with the same job definitions:
|
|
53
|
-
|
|
54
|
-
```ts
|
|
55
|
-
import { setQueueConfig, startWorkers, closeQueue } from "@warlock.js/queue";
|
|
56
|
-
import "./jobs"; // modules that call defineJob
|
|
57
|
-
|
|
58
|
-
setQueueConfig({ connection: { host: "127.0.0.1", port: 6379 } });
|
|
59
|
-
await startWorkers();
|
|
60
|
-
process.on("SIGTERM", () => closeQueue());
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Every process must use the same `prefix`, or they will not see each other's jobs.
|
|
64
|
-
|
|
65
|
-
## Dashboard
|
|
66
|
-
|
|
67
|
-
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see [`manage-failed-jobs`](../manage-failed-jobs/SKILL.md#dashboard-optional) for the `warlock add bull-board` setup and the production guard (`QueueDashboardUnguardedError`).
|
|
9
|
+
---
|
|
10
|
+
name: configure-queue
|
|
11
|
+
description: '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 — `@warlock.js/queue/define-jobs/SKILL.md`.'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Configure the queue
|
|
15
|
+
|
|
16
|
+
Redis is required. The config is declarative; the connector applies it.
|
|
17
|
+
|
|
18
|
+
```ts title="src/config/queue.ts"
|
|
19
|
+
import type { QueueConfig } from "@warlock.js/queue";
|
|
20
|
+
|
|
21
|
+
const queueConfig: QueueConfig = {
|
|
22
|
+
connection: { host: "127.0.0.1", port: 6379 }, // any BullMQ connection option, or { url }
|
|
23
|
+
prefix: "my-app", // Redis key prefix, default "warlock"
|
|
24
|
+
defaultJobOptions: { attempts: 3, backoff: { type: "exponential", delay: 1000 } },
|
|
25
|
+
workers: {
|
|
26
|
+
enabled: true, // default true
|
|
27
|
+
concurrency: 5, // jobs in parallel per queue, default 1
|
|
28
|
+
shutdownTimeout: 30_000, // ms to wait for active jobs, default 30000
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default queueConfig;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```ts title="warlock.config.ts"
|
|
36
|
+
import { defineConfig } from "@warlock.js/core";
|
|
37
|
+
import { queueConnector } from "@warlock.js/queue";
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
connectors: [queueConnector()],
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## What the connector does
|
|
45
|
+
|
|
46
|
+
- Starts in the **late** phase — after app code is imported — so every `defineJob` has registered. One worker per queue that has a job.
|
|
47
|
+
- No `queue` config → logs a warning and does nothing.
|
|
48
|
+
- On SIGINT/SIGTERM: workers stop taking jobs, active jobs get up to `shutdownTimeout` ms, then Redis connections close. A job still running after that is retried by another worker once its lock expires.
|
|
49
|
+
|
|
50
|
+
## Dispatch-only process
|
|
51
|
+
|
|
52
|
+
Set `workers.enabled: false`. `dispatch()` still works; nothing is processed in that process. Run the workers elsewhere with the same job definitions:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { setQueueConfig, startWorkers, closeQueue } from "@warlock.js/queue";
|
|
56
|
+
import "./jobs"; // modules that call defineJob
|
|
57
|
+
|
|
58
|
+
setQueueConfig({ connection: { host: "127.0.0.1", port: 6379 } });
|
|
59
|
+
await startWorkers();
|
|
60
|
+
process.on("SIGTERM", () => closeQueue());
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Every process must use the same `prefix`, or they will not see each other's jobs.
|
|
64
|
+
|
|
65
|
+
## Dashboard
|
|
66
|
+
|
|
67
|
+
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see [`manage-failed-jobs`](../manage-failed-jobs/SKILL.md#dashboard-optional) for the `warlock add bull-board` setup and the production guard (`QueueDashboardUnguardedError`).
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
## define-jobs `@warlock.js/queue/define-jobs/SKILL.md`
|
|
71
71
|
|
|
72
|
-
---
|
|
73
|
-
name: define-jobs
|
|
74
|
-
description:
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
# Define and dispatch jobs
|
|
78
|
-
|
|
79
|
-
```ts title="src/app/invoices/jobs/send-invoice.job.ts"
|
|
80
|
-
import { defineJob } from "@warlock.js/queue";
|
|
81
|
-
|
|
82
|
-
export const sendInvoice = defineJob({
|
|
83
|
-
name: "invoices.send", // unique across the app
|
|
84
|
-
attempts: 5, // total tries, default 1
|
|
85
|
-
backoff: { type: "exponential", delay: 2000 }, // or a number = fixed ms
|
|
86
|
-
async handle(payload: { invoiceId: string }, ctx) {
|
|
87
|
-
await ctx.log(`attempt ${ctx.attempt} of ${ctx.maxAttempts}`);
|
|
88
|
-
await ctx.progress(50);
|
|
89
|
-
// throw to fail this attempt; it is retried until attempts run out
|
|
90
|
-
return { sent: true }; // stored as the job result
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The job module must be imported by the process that runs workers — in a Warlock app, anything under `src/app` that is loaded at boot.
|
|
96
|
-
|
|
97
|
-
## Dispatch
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
|
|
101
|
-
|
|
102
|
-
await sendInvoice.dispatch({ invoiceId: "43" }, {
|
|
103
|
-
delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
|
|
104
|
-
priority: 1, // 1 runs first; larger numbers later
|
|
105
|
-
jobId: "invoice:43", // a second dispatch with a live id is ignored
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
110
|
-
|
|
111
|
-
## Read a job
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
const job = await sendInvoice.find(id);
|
|
115
|
-
// { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Rules
|
|
119
|
-
|
|
120
|
-
- A job name with no handler in the worker process fails at once, without retries.
|
|
121
|
-
- Payloads are stored as JSON: pass ids, not model instances.
|
|
122
|
-
- Defining the same name again replaces the handler (this is what keeps dev reloads working), so keep names unique.
|
|
72
|
+
---
|
|
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 — `@warlock.js/queue/configure-queue/SKILL.md`; failed jobs — `@warlock.js/queue/manage-failed-jobs/SKILL.md`.'
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# Define and dispatch jobs
|
|
78
|
+
|
|
79
|
+
```ts title="src/app/invoices/jobs/send-invoice.job.ts"
|
|
80
|
+
import { defineJob } from "@warlock.js/queue";
|
|
81
|
+
|
|
82
|
+
export const sendInvoice = defineJob({
|
|
83
|
+
name: "invoices.send", // unique across the app
|
|
84
|
+
attempts: 5, // total tries, default 1
|
|
85
|
+
backoff: { type: "exponential", delay: 2000 }, // or a number = fixed ms
|
|
86
|
+
async handle(payload: { invoiceId: string }, ctx) {
|
|
87
|
+
await ctx.log(`attempt ${ctx.attempt} of ${ctx.maxAttempts}`);
|
|
88
|
+
await ctx.progress(50);
|
|
89
|
+
// throw to fail this attempt; it is retried until attempts run out
|
|
90
|
+
return { sent: true }; // stored as the job result
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The job module must be imported by the process that runs workers — in a Warlock app, anything under `src/app` that is loaded at boot.
|
|
96
|
+
|
|
97
|
+
## Dispatch
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
|
|
101
|
+
|
|
102
|
+
await sendInvoice.dispatch({ invoiceId: "43" }, {
|
|
103
|
+
delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
|
|
104
|
+
priority: 1, // 1 runs first; larger numbers later
|
|
105
|
+
jobId: "invoice:43", // a second dispatch with a live id is ignored
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
110
|
+
|
|
111
|
+
## Read a job
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const job = await sendInvoice.find(id);
|
|
115
|
+
// { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Rules
|
|
119
|
+
|
|
120
|
+
- A job name with no handler in the worker process fails at once, without retries.
|
|
121
|
+
- Payloads are stored as JSON: pass ids, not model instances.
|
|
122
|
+
- Defining the same name again replaces the handler (this is what keeps dev reloads working), so keep names unique.
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
## manage-failed-jobs `@warlock.js/queue/manage-failed-jobs/SKILL.md`
|
|
126
126
|
|
|
127
|
-
---
|
|
128
|
-
name: manage-failed-jobs
|
|
129
|
-
description:
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
# Failed jobs
|
|
133
|
-
|
|
134
|
-
A job is failed once it has used every attempt, or failed in a way that cannot be retried (for example, no handler for its name). Failed jobs are kept unless you set `removeOnFail`.
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
import { failedJobs, retryFailedJob } from "@warlock.js/queue";
|
|
138
|
-
|
|
139
|
-
const failed = await failedJobs({ queue: "default", start: 0, end: 49 }); // newest first
|
|
140
|
-
|
|
141
|
-
for (const job of failed) {
|
|
142
|
-
console.log(job.name, job.failedReason, job.attemptsMade);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
await failed[0]?.retry(); // back to waiting
|
|
146
|
-
await retryFailedJob("invoice:43"); // by id; throws FailedJobNotFoundError if not failed
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Dashboard (optional)
|
|
150
|
-
|
|
151
|
-
```sh
|
|
152
|
-
warlock add bull-board
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
This installs `@bull-board/api` + `@bull-board/fastify` and writes a `dashboard` block to `src/config/queue.ts`. It `requires: ["queue"]`, so it adds the `queue` feature first automatically when it isn't installed yet — no need to run `warlock add queue` yourself first:
|
|
156
|
-
|
|
157
|
-
```ts title="src/config/queue.ts"
|
|
158
|
-
import type { QueueConfig } from "@warlock.js/queue";
|
|
159
|
-
import { middleware } from "@warlock.js/core";
|
|
160
|
-
import { authMiddleware } from "@warlock.js/auth";
|
|
161
|
-
|
|
162
|
-
const queueConfig: QueueConfig = {
|
|
163
|
-
connection: { host: "127.0.0.1", port: 6379 },
|
|
164
|
-
dashboard: {
|
|
165
|
-
enabled: true,
|
|
166
|
-
path: "/admin/queues", // default
|
|
167
|
-
middleware: [authMiddleware("admin")], // guards every dashboard route
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export default queueConfig;
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
- The dashboard can retry and delete jobs, so it must be guarded. In production, an empty `middleware` list throws `QueueDashboardUnguardedError` at boot instead of mounting exposed. Outside production it logs a warning and mounts anyway.
|
|
175
|
-
- `queueConnector()` mounts the dashboard at boot from this config — no extra wiring needed.
|
|
176
|
-
|
|
177
|
-
### Advanced: manual `queueDashboard()`
|
|
178
|
-
|
|
179
|
-
For mounting outside the config-driven path (a custom server, a non-standard boot sequence), call `queueDashboard` yourself:
|
|
180
|
-
|
|
181
|
-
```ts
|
|
182
|
-
import { getHttpServer } from "@warlock.js/core";
|
|
183
|
-
import { queueDashboard } from "@warlock.js/queue";
|
|
184
|
-
import { authMiddleware } from "@warlock.js/auth";
|
|
185
|
-
|
|
186
|
-
await queueDashboard(getHttpServer(), {
|
|
187
|
-
basePath: "/admin/queues",
|
|
188
|
-
middleware: [authMiddleware("admin")],
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
- Call it before the HTTP server starts listening.
|
|
193
|
-
- Shows every queue that has a job, plus the default queue, unless you pass `queues`.
|
|
194
|
-
- It has no authentication of its own — pass `middleware` yourself; this path does not enforce the production guard that the `dashboard` config does.
|
|
127
|
+
---
|
|
128
|
+
name: manage-failed-jobs
|
|
129
|
+
description: '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 — `@warlock.js/queue/define-jobs/SKILL.md`.'
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Failed jobs
|
|
133
|
+
|
|
134
|
+
A job is failed once it has used every attempt, or failed in a way that cannot be retried (for example, no handler for its name). Failed jobs are kept unless you set `removeOnFail`.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { failedJobs, retryFailedJob } from "@warlock.js/queue";
|
|
138
|
+
|
|
139
|
+
const failed = await failedJobs({ queue: "default", start: 0, end: 49 }); // newest first
|
|
140
|
+
|
|
141
|
+
for (const job of failed) {
|
|
142
|
+
console.log(job.name, job.failedReason, job.attemptsMade);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await failed[0]?.retry(); // back to waiting
|
|
146
|
+
await retryFailedJob("invoice:43"); // by id; throws FailedJobNotFoundError if not failed
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Dashboard (optional)
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
warlock add bull-board
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This installs `@bull-board/api` + `@bull-board/fastify` and writes a `dashboard` block to `src/config/queue.ts`. It `requires: ["queue"]`, so it adds the `queue` feature first automatically when it isn't installed yet — no need to run `warlock add queue` yourself first:
|
|
156
|
+
|
|
157
|
+
```ts title="src/config/queue.ts"
|
|
158
|
+
import type { QueueConfig } from "@warlock.js/queue";
|
|
159
|
+
import { middleware } from "@warlock.js/core";
|
|
160
|
+
import { authMiddleware } from "@warlock.js/auth";
|
|
161
|
+
|
|
162
|
+
const queueConfig: QueueConfig = {
|
|
163
|
+
connection: { host: "127.0.0.1", port: 6379 },
|
|
164
|
+
dashboard: {
|
|
165
|
+
enabled: true,
|
|
166
|
+
path: "/admin/queues", // default
|
|
167
|
+
middleware: [authMiddleware("admin")], // guards every dashboard route
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export default queueConfig;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- The dashboard can retry and delete jobs, so it must be guarded. In production, an empty `middleware` list throws `QueueDashboardUnguardedError` at boot instead of mounting exposed. Outside production it logs a warning and mounts anyway.
|
|
175
|
+
- `queueConnector()` mounts the dashboard at boot from this config — no extra wiring needed.
|
|
176
|
+
|
|
177
|
+
### Advanced: manual `queueDashboard()`
|
|
178
|
+
|
|
179
|
+
For mounting outside the config-driven path (a custom server, a non-standard boot sequence), call `queueDashboard` yourself:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { getHttpServer } from "@warlock.js/core";
|
|
183
|
+
import { queueDashboard } from "@warlock.js/queue";
|
|
184
|
+
import { authMiddleware } from "@warlock.js/auth";
|
|
185
|
+
|
|
186
|
+
await queueDashboard(getHttpServer(), {
|
|
187
|
+
basePath: "/admin/queues",
|
|
188
|
+
middleware: [authMiddleware("admin")],
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- Call it before the HTTP server starts listening.
|
|
193
|
+
- Shows every queue that has a job, plus the default queue, unless you pass `queues`.
|
|
194
|
+
- It has no authentication of its own — pass `middleware` yourself; this path does not enforce the production guard that the `dashboard` config does.
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
## overview `@warlock.js/queue/overview/SKILL.md`
|
|
@@ -226,40 +226,40 @@ Durable background jobs. Jobs are stored in **Redis** (required), retried on fai
|
|
|
226
226
|
|
|
227
227
|
## queue-notifications `@warlock.js/queue/queue-notifications/SKILL.md`
|
|
228
228
|
|
|
229
|
-
---
|
|
230
|
-
name: queue-notifications
|
|
231
|
-
description:
|
|
232
|
-
---
|
|
233
|
-
|
|
234
|
-
# Queue notifications with BullMQ
|
|
235
|
-
|
|
236
|
-
```ts title="src/config/notifications.ts"
|
|
237
|
-
import { type NotificationConfig, bullmqQueue, mailChannel } from "@warlock.js/notifications";
|
|
238
|
-
|
|
239
|
-
const config: NotificationConfig = {
|
|
240
|
-
channels: { mail: mailChannel() },
|
|
241
|
-
queue: bullmqQueue({ attempts: 3, backoff: { type: "exponential", delay: 5000 } }),
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
export default config;
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
`bullmqQueue` ships inside `@warlock.js/notifications` itself and lazy-loads `@warlock.js/queue` (an OPTIONAL peer) the first time `.queue()` runs — install it with `warlock add queue` if it isn't there yet, otherwise the first `.queue()` throws `QueuePackageNotInstalledError`.
|
|
248
|
-
|
|
249
|
-
Also configure the queue itself (`configure-queue`). No separate notifications worker is needed: the delivery is an ordinary job, run by the queue workers.
|
|
250
|
-
|
|
251
|
-
```ts
|
|
252
|
-
await orderShipped.queue(user, { order }); // enqueued, delivered by a worker
|
|
253
|
-
await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10 minutes
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## Behaviour
|
|
257
|
-
|
|
258
|
-
- The job carries the rendered payload and resolved route; the worker looks the channel up by name in its own notifications config and calls `channel.send`.
|
|
259
|
-
- `delay`: a number is seconds (notifications' convention); a string is a duration.
|
|
260
|
-
- `channel.send` throws → retried per `attempts` / `backoff`, then listed by `failedJobs()`.
|
|
261
|
-
- Channel not configured in the worker → fails at once, no retries.
|
|
262
|
-
- Deliveries run under the job name `warlock.notifications.deliver`.
|
|
263
|
-
|
|
229
|
+
---
|
|
230
|
+
name: queue-notifications
|
|
231
|
+
description: '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 — `@warlock.js/notifications/queue-notifications/SKILL.md`.'
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
# Queue notifications with BullMQ
|
|
235
|
+
|
|
236
|
+
```ts title="src/config/notifications.ts"
|
|
237
|
+
import { type NotificationConfig, bullmqQueue, mailChannel } from "@warlock.js/notifications";
|
|
238
|
+
|
|
239
|
+
const config: NotificationConfig = {
|
|
240
|
+
channels: { mail: mailChannel() },
|
|
241
|
+
queue: bullmqQueue({ attempts: 3, backoff: { type: "exponential", delay: 5000 } }),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export default config;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`bullmqQueue` ships inside `@warlock.js/notifications` itself and lazy-loads `@warlock.js/queue` (an OPTIONAL peer) the first time `.queue()` runs — install it with `warlock add queue` if it isn't there yet, otherwise the first `.queue()` throws `QueuePackageNotInstalledError`.
|
|
248
|
+
|
|
249
|
+
Also configure the queue itself (`configure-queue`). No separate notifications worker is needed: the delivery is an ordinary job, run by the queue workers.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
await orderShipped.queue(user, { order }); // enqueued, delivered by a worker
|
|
253
|
+
await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10 minutes
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Behaviour
|
|
257
|
+
|
|
258
|
+
- The job carries the rendered payload and resolved route; the worker looks the channel up by name in its own notifications config and calls `channel.send`.
|
|
259
|
+
- `delay`: a number is seconds (notifications' convention); a string is a duration.
|
|
260
|
+
- `channel.send` throws → retried per `attempts` / `backoff`, then listed by `failedJobs()`.
|
|
261
|
+
- Channel not configured in the worker → fails at once, no retries.
|
|
262
|
+
- Deliveries run under the job name `warlock.notifications.deliver`.
|
|
263
|
+
|
|
264
264
|
|
|
265
265
|
|
package/llms.txt
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [configure-queue](@warlock.js/queue/configure-queue/SKILL.md):
|
|
10
|
-
- [define-jobs](@warlock.js/queue/define-jobs/SKILL.md):
|
|
11
|
-
- [manage-failed-jobs](@warlock.js/queue/manage-failed-jobs/SKILL.md):
|
|
9
|
+
- [configure-queue](@warlock.js/queue/configure-queue/SKILL.md): (no description)
|
|
10
|
+
- [define-jobs](@warlock.js/queue/define-jobs/SKILL.md): (no description)
|
|
11
|
+
- [manage-failed-jobs](@warlock.js/queue/manage-failed-jobs/SKILL.md): (no description)
|
|
12
12
|
- [overview](@warlock.js/queue/overview/SKILL.md): @warlock.js/queue overview for orientation and choosing the focused skill for the task.
|
|
13
|
-
- [queue-notifications](@warlock.js/queue/queue-notifications/SKILL.md):
|
|
13
|
+
- [queue-notifications](@warlock.js/queue/queue-notifications/SKILL.md): (no description)
|
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.20.0",
|
|
15
|
+
"@warlock.js/logger": "5.20.0"
|
|
16
16
|
},
|
|
17
17
|
"peerDependenciesMeta": {
|
|
18
18
|
"@bull-board/api": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"author": "hassanzohdy",
|
|
39
39
|
"license": "MIT",
|
|
40
|
-
"version": "5.
|
|
40
|
+
"version": "5.20.0",
|
|
41
41
|
"main": "./cjs/index.cjs",
|
|
42
42
|
"module": "./esm/index.mjs",
|
|
43
43
|
"types": "./esm/index.d.mts",
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: configure-queue
|
|
3
|
-
description:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Configure the queue
|
|
7
|
-
|
|
8
|
-
Redis is required. The config is declarative; the connector applies it.
|
|
9
|
-
|
|
10
|
-
```ts title="src/config/queue.ts"
|
|
11
|
-
import type { QueueConfig } from "@warlock.js/queue";
|
|
12
|
-
|
|
13
|
-
const queueConfig: QueueConfig = {
|
|
14
|
-
connection: { host: "127.0.0.1", port: 6379 }, // any BullMQ connection option, or { url }
|
|
15
|
-
prefix: "my-app", // Redis key prefix, default "warlock"
|
|
16
|
-
defaultJobOptions: { attempts: 3, backoff: { type: "exponential", delay: 1000 } },
|
|
17
|
-
workers: {
|
|
18
|
-
enabled: true, // default true
|
|
19
|
-
concurrency: 5, // jobs in parallel per queue, default 1
|
|
20
|
-
shutdownTimeout: 30_000, // ms to wait for active jobs, default 30000
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export default queueConfig;
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
```ts title="warlock.config.ts"
|
|
28
|
-
import { defineConfig } from "@warlock.js/core";
|
|
29
|
-
import { queueConnector } from "@warlock.js/queue";
|
|
30
|
-
|
|
31
|
-
export default defineConfig({
|
|
32
|
-
connectors: [queueConnector()],
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## What the connector does
|
|
37
|
-
|
|
38
|
-
- Starts in the **late** phase — after app code is imported — so every `defineJob` has registered. One worker per queue that has a job.
|
|
39
|
-
- No `queue` config → logs a warning and does nothing.
|
|
40
|
-
- On SIGINT/SIGTERM: workers stop taking jobs, active jobs get up to `shutdownTimeout` ms, then Redis connections close. A job still running after that is retried by another worker once its lock expires.
|
|
41
|
-
|
|
42
|
-
## Dispatch-only process
|
|
43
|
-
|
|
44
|
-
Set `workers.enabled: false`. `dispatch()` still works; nothing is processed in that process. Run the workers elsewhere with the same job definitions:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { setQueueConfig, startWorkers, closeQueue } from "@warlock.js/queue";
|
|
48
|
-
import "./jobs"; // modules that call defineJob
|
|
49
|
-
|
|
50
|
-
setQueueConfig({ connection: { host: "127.0.0.1", port: 6379 } });
|
|
51
|
-
await startWorkers();
|
|
52
|
-
process.on("SIGTERM", () => closeQueue());
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Every process must use the same `prefix`, or they will not see each other's jobs.
|
|
56
|
-
|
|
57
|
-
## Dashboard
|
|
58
|
-
|
|
59
|
-
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see [`manage-failed-jobs`](../manage-failed-jobs/SKILL.md#dashboard-optional) for the `warlock add bull-board` setup and the production guard (`QueueDashboardUnguardedError`).
|
|
1
|
+
---
|
|
2
|
+
name: configure-queue
|
|
3
|
+
description: '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 — `@warlock.js/queue/define-jobs/SKILL.md`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Configure the queue
|
|
7
|
+
|
|
8
|
+
Redis is required. The config is declarative; the connector applies it.
|
|
9
|
+
|
|
10
|
+
```ts title="src/config/queue.ts"
|
|
11
|
+
import type { QueueConfig } from "@warlock.js/queue";
|
|
12
|
+
|
|
13
|
+
const queueConfig: QueueConfig = {
|
|
14
|
+
connection: { host: "127.0.0.1", port: 6379 }, // any BullMQ connection option, or { url }
|
|
15
|
+
prefix: "my-app", // Redis key prefix, default "warlock"
|
|
16
|
+
defaultJobOptions: { attempts: 3, backoff: { type: "exponential", delay: 1000 } },
|
|
17
|
+
workers: {
|
|
18
|
+
enabled: true, // default true
|
|
19
|
+
concurrency: 5, // jobs in parallel per queue, default 1
|
|
20
|
+
shutdownTimeout: 30_000, // ms to wait for active jobs, default 30000
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default queueConfig;
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts title="warlock.config.ts"
|
|
28
|
+
import { defineConfig } from "@warlock.js/core";
|
|
29
|
+
import { queueConnector } from "@warlock.js/queue";
|
|
30
|
+
|
|
31
|
+
export default defineConfig({
|
|
32
|
+
connectors: [queueConnector()],
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What the connector does
|
|
37
|
+
|
|
38
|
+
- Starts in the **late** phase — after app code is imported — so every `defineJob` has registered. One worker per queue that has a job.
|
|
39
|
+
- No `queue` config → logs a warning and does nothing.
|
|
40
|
+
- On SIGINT/SIGTERM: workers stop taking jobs, active jobs get up to `shutdownTimeout` ms, then Redis connections close. A job still running after that is retried by another worker once its lock expires.
|
|
41
|
+
|
|
42
|
+
## Dispatch-only process
|
|
43
|
+
|
|
44
|
+
Set `workers.enabled: false`. `dispatch()` still works; nothing is processed in that process. Run the workers elsewhere with the same job definitions:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { setQueueConfig, startWorkers, closeQueue } from "@warlock.js/queue";
|
|
48
|
+
import "./jobs"; // modules that call defineJob
|
|
49
|
+
|
|
50
|
+
setQueueConfig({ connection: { host: "127.0.0.1", port: 6379 } });
|
|
51
|
+
await startWorkers();
|
|
52
|
+
process.on("SIGTERM", () => closeQueue());
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Every process must use the same `prefix`, or they will not see each other's jobs.
|
|
56
|
+
|
|
57
|
+
## Dashboard
|
|
58
|
+
|
|
59
|
+
`QueueConfig` also takes a `dashboard: { enabled, path, middleware }` block — see [`manage-failed-jobs`](../manage-failed-jobs/SKILL.md#dashboard-optional) for the `warlock add bull-board` setup and the production guard (`QueueDashboardUnguardedError`).
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: define-jobs
|
|
3
|
-
description:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Define and dispatch jobs
|
|
7
|
-
|
|
8
|
-
```ts title="src/app/invoices/jobs/send-invoice.job.ts"
|
|
9
|
-
import { defineJob } from "@warlock.js/queue";
|
|
10
|
-
|
|
11
|
-
export const sendInvoice = defineJob({
|
|
12
|
-
name: "invoices.send", // unique across the app
|
|
13
|
-
attempts: 5, // total tries, default 1
|
|
14
|
-
backoff: { type: "exponential", delay: 2000 }, // or a number = fixed ms
|
|
15
|
-
async handle(payload: { invoiceId: string }, ctx) {
|
|
16
|
-
await ctx.log(`attempt ${ctx.attempt} of ${ctx.maxAttempts}`);
|
|
17
|
-
await ctx.progress(50);
|
|
18
|
-
// throw to fail this attempt; it is retried until attempts run out
|
|
19
|
-
return { sent: true }; // stored as the job result
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
The job module must be imported by the process that runs workers — in a Warlock app, anything under `src/app` that is loaded at boot.
|
|
25
|
-
|
|
26
|
-
## Dispatch
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
|
|
30
|
-
|
|
31
|
-
await sendInvoice.dispatch({ invoiceId: "43" }, {
|
|
32
|
-
delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
|
|
33
|
-
priority: 1, // 1 runs first; larger numbers later
|
|
34
|
-
jobId: "invoice:43", // a second dispatch with a live id is ignored
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
39
|
-
|
|
40
|
-
## Read a job
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
const job = await sendInvoice.find(id);
|
|
44
|
-
// { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
## Rules
|
|
48
|
-
|
|
49
|
-
- A job name with no handler in the worker process fails at once, without retries.
|
|
50
|
-
- Payloads are stored as JSON: pass ids, not model instances.
|
|
51
|
-
- Defining the same name again replaces the handler (this is what keeps dev reloads working), so keep names unique.
|
|
1
|
+
---
|
|
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 — `@warlock.js/queue/configure-queue/SKILL.md`; failed jobs — `@warlock.js/queue/manage-failed-jobs/SKILL.md`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Define and dispatch jobs
|
|
7
|
+
|
|
8
|
+
```ts title="src/app/invoices/jobs/send-invoice.job.ts"
|
|
9
|
+
import { defineJob } from "@warlock.js/queue";
|
|
10
|
+
|
|
11
|
+
export const sendInvoice = defineJob({
|
|
12
|
+
name: "invoices.send", // unique across the app
|
|
13
|
+
attempts: 5, // total tries, default 1
|
|
14
|
+
backoff: { type: "exponential", delay: 2000 }, // or a number = fixed ms
|
|
15
|
+
async handle(payload: { invoiceId: string }, ctx) {
|
|
16
|
+
await ctx.log(`attempt ${ctx.attempt} of ${ctx.maxAttempts}`);
|
|
17
|
+
await ctx.progress(50);
|
|
18
|
+
// throw to fail this attempt; it is retried until attempts run out
|
|
19
|
+
return { sent: true }; // stored as the job result
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The job module must be imported by the process that runs workers — in a Warlock app, anything under `src/app` that is loaded at boot.
|
|
25
|
+
|
|
26
|
+
## Dispatch
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const { id } = await sendInvoice.dispatch({ invoiceId: "42" });
|
|
30
|
+
|
|
31
|
+
await sendInvoice.dispatch({ invoiceId: "43" }, {
|
|
32
|
+
delay: "10m", // ms number or "500ms" | "30s" | "10m" | "2h" | "1d"
|
|
33
|
+
priority: 1, // 1 runs first; larger numbers later
|
|
34
|
+
jobId: "invoice:43", // a second dispatch with a live id is ignored
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Option precedence: dispatch options > `defineJob` > `queue.defaultJobOptions`.
|
|
39
|
+
|
|
40
|
+
## Read a job
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
const job = await sendInvoice.find(id);
|
|
44
|
+
// { id, name, queue, state, payload, progress, attemptsMade, result, failedReason, createdAt, finishedAt }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Rules
|
|
48
|
+
|
|
49
|
+
- A job name with no handler in the worker process fails at once, without retries.
|
|
50
|
+
- Payloads are stored as JSON: pass ids, not model instances.
|
|
51
|
+
- Defining the same name again replaces the handler (this is what keeps dev reloads working), so keep names unique.
|
package/skills/index.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
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
|
+
---
|
|
4
|
+
# @warlock.js/queue
|
|
5
|
+
|
|
6
|
+
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
|
+
## The 80% path
|
|
9
|
+
1. Orient with `overview.md`.
|
|
10
|
+
2. Configure `src/config/queue.ts` and register `queueConnector()`: `configure-queue.md`.
|
|
11
|
+
3. Define and dispatch jobs (attempts, backoff, delay, priority, progress): `define-jobs.md`.
|
|
12
|
+
4. Inspect and recover failures, optionally the bull-board dashboard: `manage-failed-jobs.md`.
|
|
13
|
+
5. Back `@warlock.js/notifications` `.queue()` with BullMQ: `queue-notifications.md`.
|
|
14
|
+
|
|
15
|
+
## Conventions and pitfalls
|
|
16
|
+
- Job names are the routing key; a job with no matching `defineJob` in the worker process cannot run.
|
|
17
|
+
- Handlers must be idempotent: retries re-run them.
|
|
18
|
+
- Shutdown lets active jobs finish up to `workers.shutdownTimeout`, then closes connections.
|
|
19
|
+
- Guard the dashboard with middleware; an unguarded one is refused on purpose.
|
|
20
|
+
- Core `Queue` is an in-memory batcher, lost on exit; use this package when work must survive.
|
|
21
|
+
- Do not value-import from client code.
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: manage-failed-jobs
|
|
3
|
-
description:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Failed jobs
|
|
7
|
-
|
|
8
|
-
A job is failed once it has used every attempt, or failed in a way that cannot be retried (for example, no handler for its name). Failed jobs are kept unless you set `removeOnFail`.
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
import { failedJobs, retryFailedJob } from "@warlock.js/queue";
|
|
12
|
-
|
|
13
|
-
const failed = await failedJobs({ queue: "default", start: 0, end: 49 }); // newest first
|
|
14
|
-
|
|
15
|
-
for (const job of failed) {
|
|
16
|
-
console.log(job.name, job.failedReason, job.attemptsMade);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
await failed[0]?.retry(); // back to waiting
|
|
20
|
-
await retryFailedJob("invoice:43"); // by id; throws FailedJobNotFoundError if not failed
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Dashboard (optional)
|
|
24
|
-
|
|
25
|
-
```sh
|
|
26
|
-
warlock add bull-board
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
This installs `@bull-board/api` + `@bull-board/fastify` and writes a `dashboard` block to `src/config/queue.ts`. It `requires: ["queue"]`, so it adds the `queue` feature first automatically when it isn't installed yet — no need to run `warlock add queue` yourself first:
|
|
30
|
-
|
|
31
|
-
```ts title="src/config/queue.ts"
|
|
32
|
-
import type { QueueConfig } from "@warlock.js/queue";
|
|
33
|
-
import { middleware } from "@warlock.js/core";
|
|
34
|
-
import { authMiddleware } from "@warlock.js/auth";
|
|
35
|
-
|
|
36
|
-
const queueConfig: QueueConfig = {
|
|
37
|
-
connection: { host: "127.0.0.1", port: 6379 },
|
|
38
|
-
dashboard: {
|
|
39
|
-
enabled: true,
|
|
40
|
-
path: "/admin/queues", // default
|
|
41
|
-
middleware: [authMiddleware("admin")], // guards every dashboard route
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export default queueConfig;
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
- The dashboard can retry and delete jobs, so it must be guarded. In production, an empty `middleware` list throws `QueueDashboardUnguardedError` at boot instead of mounting exposed. Outside production it logs a warning and mounts anyway.
|
|
49
|
-
- `queueConnector()` mounts the dashboard at boot from this config — no extra wiring needed.
|
|
50
|
-
|
|
51
|
-
### Advanced: manual `queueDashboard()`
|
|
52
|
-
|
|
53
|
-
For mounting outside the config-driven path (a custom server, a non-standard boot sequence), call `queueDashboard` yourself:
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
import { getHttpServer } from "@warlock.js/core";
|
|
57
|
-
import { queueDashboard } from "@warlock.js/queue";
|
|
58
|
-
import { authMiddleware } from "@warlock.js/auth";
|
|
59
|
-
|
|
60
|
-
await queueDashboard(getHttpServer(), {
|
|
61
|
-
basePath: "/admin/queues",
|
|
62
|
-
middleware: [authMiddleware("admin")],
|
|
63
|
-
});
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
- Call it before the HTTP server starts listening.
|
|
67
|
-
- Shows every queue that has a job, plus the default queue, unless you pass `queues`.
|
|
68
|
-
- It has no authentication of its own — pass `middleware` yourself; this path does not enforce the production guard that the `dashboard` config does.
|
|
1
|
+
---
|
|
2
|
+
name: manage-failed-jobs
|
|
3
|
+
description: '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 — `@warlock.js/queue/define-jobs/SKILL.md`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Failed jobs
|
|
7
|
+
|
|
8
|
+
A job is failed once it has used every attempt, or failed in a way that cannot be retried (for example, no handler for its name). Failed jobs are kept unless you set `removeOnFail`.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { failedJobs, retryFailedJob } from "@warlock.js/queue";
|
|
12
|
+
|
|
13
|
+
const failed = await failedJobs({ queue: "default", start: 0, end: 49 }); // newest first
|
|
14
|
+
|
|
15
|
+
for (const job of failed) {
|
|
16
|
+
console.log(job.name, job.failedReason, job.attemptsMade);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await failed[0]?.retry(); // back to waiting
|
|
20
|
+
await retryFailedJob("invoice:43"); // by id; throws FailedJobNotFoundError if not failed
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Dashboard (optional)
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
warlock add bull-board
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This installs `@bull-board/api` + `@bull-board/fastify` and writes a `dashboard` block to `src/config/queue.ts`. It `requires: ["queue"]`, so it adds the `queue` feature first automatically when it isn't installed yet — no need to run `warlock add queue` yourself first:
|
|
30
|
+
|
|
31
|
+
```ts title="src/config/queue.ts"
|
|
32
|
+
import type { QueueConfig } from "@warlock.js/queue";
|
|
33
|
+
import { middleware } from "@warlock.js/core";
|
|
34
|
+
import { authMiddleware } from "@warlock.js/auth";
|
|
35
|
+
|
|
36
|
+
const queueConfig: QueueConfig = {
|
|
37
|
+
connection: { host: "127.0.0.1", port: 6379 },
|
|
38
|
+
dashboard: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
path: "/admin/queues", // default
|
|
41
|
+
middleware: [authMiddleware("admin")], // guards every dashboard route
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default queueConfig;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- The dashboard can retry and delete jobs, so it must be guarded. In production, an empty `middleware` list throws `QueueDashboardUnguardedError` at boot instead of mounting exposed. Outside production it logs a warning and mounts anyway.
|
|
49
|
+
- `queueConnector()` mounts the dashboard at boot from this config — no extra wiring needed.
|
|
50
|
+
|
|
51
|
+
### Advanced: manual `queueDashboard()`
|
|
52
|
+
|
|
53
|
+
For mounting outside the config-driven path (a custom server, a non-standard boot sequence), call `queueDashboard` yourself:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { getHttpServer } from "@warlock.js/core";
|
|
57
|
+
import { queueDashboard } from "@warlock.js/queue";
|
|
58
|
+
import { authMiddleware } from "@warlock.js/auth";
|
|
59
|
+
|
|
60
|
+
await queueDashboard(getHttpServer(), {
|
|
61
|
+
basePath: "/admin/queues",
|
|
62
|
+
middleware: [authMiddleware("admin")],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Call it before the HTTP server starts listening.
|
|
67
|
+
- Shows every queue that has a job, plus the default queue, unless you pass `queues`.
|
|
68
|
+
- It has no authentication of its own — pass `middleware` yourself; this path does not enforce the production guard that the `dashboard` config does.
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: queue-notifications
|
|
3
|
-
description:
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Queue notifications with BullMQ
|
|
7
|
-
|
|
8
|
-
```ts title="src/config/notifications.ts"
|
|
9
|
-
import { type NotificationConfig, bullmqQueue, mailChannel } from "@warlock.js/notifications";
|
|
10
|
-
|
|
11
|
-
const config: NotificationConfig = {
|
|
12
|
-
channels: { mail: mailChannel() },
|
|
13
|
-
queue: bullmqQueue({ attempts: 3, backoff: { type: "exponential", delay: 5000 } }),
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export default config;
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
`bullmqQueue` ships inside `@warlock.js/notifications` itself and lazy-loads `@warlock.js/queue` (an OPTIONAL peer) the first time `.queue()` runs — install it with `warlock add queue` if it isn't there yet, otherwise the first `.queue()` throws `QueuePackageNotInstalledError`.
|
|
20
|
-
|
|
21
|
-
Also configure the queue itself (`configure-queue`). No separate notifications worker is needed: the delivery is an ordinary job, run by the queue workers.
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
await orderShipped.queue(user, { order }); // enqueued, delivered by a worker
|
|
25
|
-
await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10 minutes
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Behaviour
|
|
29
|
-
|
|
30
|
-
- The job carries the rendered payload and resolved route; the worker looks the channel up by name in its own notifications config and calls `channel.send`.
|
|
31
|
-
- `delay`: a number is seconds (notifications' convention); a string is a duration.
|
|
32
|
-
- `channel.send` throws → retried per `attempts` / `backoff`, then listed by `failedJobs()`.
|
|
33
|
-
- Channel not configured in the worker → fails at once, no retries.
|
|
34
|
-
- Deliveries run under the job name `warlock.notifications.deliver`.
|
|
35
|
-
|
|
1
|
+
---
|
|
2
|
+
name: queue-notifications
|
|
3
|
+
description: '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 — `@warlock.js/notifications/queue-notifications/SKILL.md`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Queue notifications with BullMQ
|
|
7
|
+
|
|
8
|
+
```ts title="src/config/notifications.ts"
|
|
9
|
+
import { type NotificationConfig, bullmqQueue, mailChannel } from "@warlock.js/notifications";
|
|
10
|
+
|
|
11
|
+
const config: NotificationConfig = {
|
|
12
|
+
channels: { mail: mailChannel() },
|
|
13
|
+
queue: bullmqQueue({ attempts: 3, backoff: { type: "exponential", delay: 5000 } }),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default config;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`bullmqQueue` ships inside `@warlock.js/notifications` itself and lazy-loads `@warlock.js/queue` (an OPTIONAL peer) the first time `.queue()` runs — install it with `warlock add queue` if it isn't there yet, otherwise the first `.queue()` throws `QueuePackageNotInstalledError`.
|
|
20
|
+
|
|
21
|
+
Also configure the queue itself (`configure-queue`). No separate notifications worker is needed: the delivery is an ordinary job, run by the queue workers.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
await orderShipped.queue(user, { order }); // enqueued, delivered by a worker
|
|
25
|
+
await orderShipped.queue(user, { order }, { delay: "10m" }); // delivered in 10 minutes
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Behaviour
|
|
29
|
+
|
|
30
|
+
- The job carries the rendered payload and resolved route; the worker looks the channel up by name in its own notifications config and calls `channel.send`.
|
|
31
|
+
- `delay`: a number is seconds (notifications' convention); a string is a duration.
|
|
32
|
+
- `channel.send` throws → retried per `attempts` / `backoff`, then listed by `failedJobs()`.
|
|
33
|
+
- Channel not configured in the worker → fails at once, no retries.
|
|
34
|
+
- Deliveries run under the job name `warlock.notifications.deliver`.
|
|
35
|
+
|