@jgamaraalv/ts-dev-kit 1.0.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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bullmq
|
|
3
|
+
description: "BullMQ queue system reference for Redis-backed job queues, workers, flows, and schedulers. Use when: (1) creating queues and workers with BullMQ, (2) adding jobs (delayed, prioritized, repeatable, deduplicated), (3) setting up FlowProducer parent-child job hierarchies, (4) configuring retry strategies, rate limiting, or concurrency, (5) implementing job schedulers with cron/interval patterns, (6) preparing BullMQ for production (graceful shutdown, Redis config, monitoring), or (7) debugging stalled jobs or connection issues"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BullMQ
|
|
7
|
+
|
|
8
|
+
Redis-backed queue system for Node.js. Four core classes: `Queue`, `Worker`, `QueueEvents`, `FlowProducer`.
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Install](#install)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Connections](#connections)
|
|
15
|
+
- [Queue](#queue)
|
|
16
|
+
- [Worker](#worker)
|
|
17
|
+
- [TypeScript Generics](#typescript-generics)
|
|
18
|
+
- [Events](#events)
|
|
19
|
+
- [Job Lifecycle States](#job-lifecycle-states)
|
|
20
|
+
- [Advanced Topics](#advanced-topics)
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
`yarn add bullmq` — requires Redis 5.0+ with `maxmemory-policy=noeviction`.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Queue, Worker, QueueEvents } from "bullmq";
|
|
30
|
+
|
|
31
|
+
// --- Producer ---
|
|
32
|
+
const queue = new Queue("my-queue", {
|
|
33
|
+
connection: { host: "localhost", port: 6379 },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await queue.add("job-name", { foo: "bar" });
|
|
37
|
+
|
|
38
|
+
// --- Consumer ---
|
|
39
|
+
const worker = new Worker(
|
|
40
|
+
"my-queue",
|
|
41
|
+
async (job) => {
|
|
42
|
+
// process job
|
|
43
|
+
await job.updateProgress(50);
|
|
44
|
+
return { result: "done" };
|
|
45
|
+
},
|
|
46
|
+
{ connection: { host: "localhost", port: 6379 } },
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
worker.on("completed", (job, returnvalue) => {
|
|
50
|
+
console.log(`${job.id} completed with`, returnvalue);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
worker.on("failed", (job, err) => {
|
|
54
|
+
console.error(`${job.id} failed with`, err.message);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// IMPORTANT: always attach an error handler
|
|
58
|
+
worker.on("error", (err) => {
|
|
59
|
+
console.error(err);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// --- Global event listener (all workers) ---
|
|
63
|
+
const queueEvents = new QueueEvents("my-queue", {
|
|
64
|
+
connection: { host: "localhost", port: 6379 },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
queueEvents.on("completed", ({ jobId, returnvalue }) => {
|
|
68
|
+
console.log(`Job ${jobId} completed`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
queueEvents.on("failed", ({ jobId, failedReason }) => {
|
|
72
|
+
console.error(`Job ${jobId} failed: ${failedReason}`);
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Connections
|
|
77
|
+
|
|
78
|
+
BullMQ uses ioredis internally. Pass `connection` options or an existing ioredis instance.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { Queue, Worker } from "bullmq";
|
|
82
|
+
import { Redis } from "ioredis";
|
|
83
|
+
|
|
84
|
+
// Option 1: connection config (new connection per instance)
|
|
85
|
+
const queue = new Queue("q", {
|
|
86
|
+
connection: { host: "redis.example.com", port: 6379 },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Option 2: reuse ioredis instance (Queue and multiple Queues can share)
|
|
90
|
+
const connection = new Redis();
|
|
91
|
+
const q1 = new Queue("q1", { connection });
|
|
92
|
+
const q2 = new Queue("q2", { connection });
|
|
93
|
+
|
|
94
|
+
// Option 3: reuse for Workers (BullMQ internally duplicates for blocking)
|
|
95
|
+
const workerConn = new Redis({ maxRetriesPerRequest: null });
|
|
96
|
+
const w1 = new Worker("q1", async (job) => {}, { connection: workerConn });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Critical rules:**
|
|
100
|
+
|
|
101
|
+
- Workers REQUIRE `maxRetriesPerRequest: null` on the ioredis instance. BullMQ enforces this and will warn/throw if not set.
|
|
102
|
+
- Do NOT use ioredis `keyPrefix` option — use BullMQ's `prefix` option instead.
|
|
103
|
+
- `QueueEvents` cannot share connections (uses blocking Redis commands).
|
|
104
|
+
- Redis MUST have `maxmemory-policy=noeviction`.
|
|
105
|
+
|
|
106
|
+
## Queue
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const queue = new Queue("paint", { connection });
|
|
110
|
+
|
|
111
|
+
// Add a job
|
|
112
|
+
await queue.add("job-name", { color: "red" });
|
|
113
|
+
|
|
114
|
+
// Add with options
|
|
115
|
+
await queue.add(
|
|
116
|
+
"job-name",
|
|
117
|
+
{ color: "blue" },
|
|
118
|
+
{
|
|
119
|
+
delay: 5000, // wait 5s before processing
|
|
120
|
+
priority: 1, // lower = higher priority (0 is highest, max 2^21)
|
|
121
|
+
attempts: 3, // retry up to 3 times
|
|
122
|
+
backoff: { type: "exponential", delay: 1000 },
|
|
123
|
+
removeOnComplete: true, // or { count: 100 } to keep last 100
|
|
124
|
+
removeOnFail: 1000, // keep last 1000 failed jobs
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Add bulk
|
|
129
|
+
await queue.addBulk([
|
|
130
|
+
{ name: "job1", data: { x: 1 } },
|
|
131
|
+
{ name: "job2", data: { x: 2 }, opts: { priority: 1 } },
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
// Queue operations
|
|
135
|
+
await queue.pause();
|
|
136
|
+
await queue.resume();
|
|
137
|
+
await queue.obliterate({ force: true }); // remove all data
|
|
138
|
+
await queue.close();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Worker
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const worker = new Worker<MyData, MyReturn>(
|
|
145
|
+
"paint",
|
|
146
|
+
async (job) => {
|
|
147
|
+
await job.updateProgress(42);
|
|
148
|
+
return { cost: 100 };
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
connection,
|
|
152
|
+
concurrency: 5, // process 5 jobs concurrently
|
|
153
|
+
autorun: false, // don't start immediately
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
worker.run(); // start when ready
|
|
158
|
+
|
|
159
|
+
// Update concurrency at runtime
|
|
160
|
+
worker.concurrency = 10;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Processor receives 3 args:** `(job, token?, signal?)` — signal is an `AbortSignal` for cancellation support.
|
|
164
|
+
|
|
165
|
+
## TypeScript Generics
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
interface JobData {
|
|
169
|
+
color: string;
|
|
170
|
+
}
|
|
171
|
+
interface JobReturn {
|
|
172
|
+
cost: number;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const queue = new Queue<JobData, JobReturn>("paint");
|
|
176
|
+
const worker = new Worker<JobData, JobReturn>("paint", async (job) => {
|
|
177
|
+
// job.data is typed as JobData
|
|
178
|
+
return { cost: 100 }; // must match JobReturn
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Events
|
|
183
|
+
|
|
184
|
+
**Worker events** (local to that worker instance):
|
|
185
|
+
|
|
186
|
+
| Event | Callback signature |
|
|
187
|
+
| ----------- | ------------------------------------ |
|
|
188
|
+
| `completed` | `(job, returnvalue)` |
|
|
189
|
+
| `failed` | `(job \| undefined, error, prev)` |
|
|
190
|
+
| `progress` | `(job, progress: number \| object)` |
|
|
191
|
+
| `drained` | `()` — queue is empty |
|
|
192
|
+
| `error` | `(error)` — MUST attach this handler |
|
|
193
|
+
|
|
194
|
+
**QueueEvents** (global, all workers, uses Redis Streams):
|
|
195
|
+
|
|
196
|
+
| Event | Callback signature |
|
|
197
|
+
| -------------- | ------------------------------------------------- |
|
|
198
|
+
| `completed` | `({ jobId, returnvalue })` |
|
|
199
|
+
| `failed` | `({ jobId, failedReason })` |
|
|
200
|
+
| `progress` | `({ jobId, data })` |
|
|
201
|
+
| `waiting` | `({ jobId })` |
|
|
202
|
+
| `active` | `({ jobId, prev })` |
|
|
203
|
+
| `delayed` | `({ jobId, delay })` |
|
|
204
|
+
| `deduplicated` | `({ jobId, deduplicationId, deduplicatedJobId })` |
|
|
205
|
+
|
|
206
|
+
Event stream is auto-trimmed (~10,000 events). Configure via `streams.events.maxLen`.
|
|
207
|
+
|
|
208
|
+
## Job Lifecycle States
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
add() → wait / prioritized / delayed
|
|
212
|
+
↓
|
|
213
|
+
active → completed
|
|
214
|
+
↓
|
|
215
|
+
failed → (retry) → wait/delayed
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
With FlowProducer: jobs can also be in `waiting-children` state until all children complete.
|
|
219
|
+
|
|
220
|
+
## Advanced Topics
|
|
221
|
+
|
|
222
|
+
- **Job types and options** (delayed, prioritized, deduplication, repeatable): See [references/job-types-and-options.md](references/job-types-and-options.md)
|
|
223
|
+
- **Flows and schedulers** (FlowProducer, parent-child, job schedulers, cron): See [references/flows-and-schedulers.md](references/flows-and-schedulers.md)
|
|
224
|
+
- **Patterns** (step jobs, idempotent, throttle, manual rate-limit): See [references/patterns.md](references/patterns.md)
|
|
225
|
+
- **Production** (shutdown, Redis config, retries, backoff, monitoring): See [references/production.md](references/production.md)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Flows and Job Schedulers
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [FlowProducer — Parent-Child Relationships](#flowproducer--parent-child-relationships)
|
|
6
|
+
- [Job Schedulers (v5.16.0+)](#job-schedulers-v5160)
|
|
7
|
+
|
|
8
|
+
## FlowProducer — Parent-Child Relationships
|
|
9
|
+
|
|
10
|
+
A parent job waits in `waiting-children` state until all its children complete. Children can be in different queues.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { FlowProducer } from "bullmq";
|
|
14
|
+
|
|
15
|
+
const flowProducer = new FlowProducer({ connection });
|
|
16
|
+
|
|
17
|
+
const flow = await flowProducer.add({
|
|
18
|
+
name: "renovate-interior",
|
|
19
|
+
queueName: "renovate",
|
|
20
|
+
children: [
|
|
21
|
+
{ name: "paint", data: { place: "ceiling" }, queueName: "steps" },
|
|
22
|
+
{ name: "paint", data: { place: "walls" }, queueName: "steps" },
|
|
23
|
+
{ name: "fix", data: { place: "floor" }, queueName: "steps" },
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This atomically adds 4 jobs. The parent (`renovate`) processes only after all 3 children complete.
|
|
29
|
+
|
|
30
|
+
### Accessing Children Results
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
const renovateWorker = new Worker("renovate", async (job) => {
|
|
34
|
+
const childrenValues = await job.getChildrenValues();
|
|
35
|
+
// { "bull:steps:paint:1": 2500, "bull:steps:fix:3": 1750 }
|
|
36
|
+
const total = Object.values(childrenValues).reduce((a, b) => a + b, 0);
|
|
37
|
+
return total;
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Serial Chains (Deep Hierarchies)
|
|
42
|
+
|
|
43
|
+
Nest children to create sequential execution:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
await flowProducer.add({
|
|
47
|
+
name: "car",
|
|
48
|
+
data: { step: "engine" },
|
|
49
|
+
queueName: "assembly",
|
|
50
|
+
children: [
|
|
51
|
+
{
|
|
52
|
+
name: "car",
|
|
53
|
+
data: { step: "wheels" },
|
|
54
|
+
queueName: "assembly",
|
|
55
|
+
children: [{ name: "car", data: { step: "chassis" }, queueName: "assembly" }],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
// Processing order: chassis → wheels → engine
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Flow Getters
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// Get direct children
|
|
66
|
+
const deps = await job.getDependencies();
|
|
67
|
+
|
|
68
|
+
// Paginated by type
|
|
69
|
+
const { processed, nextProcessedCursor } = await job.getDependencies({
|
|
70
|
+
processed: { count: 5, cursor: 0 },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Count by type
|
|
74
|
+
const { failed, ignored, processed, unprocessed } = await job.getDependenciesCount();
|
|
75
|
+
|
|
76
|
+
// Check state
|
|
77
|
+
const state = await job.getState(); // "waiting-children"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Queue Options for Flows
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
await flowProducer.add(flowDefinition, {
|
|
84
|
+
queuesOptions: {
|
|
85
|
+
assembly: {
|
|
86
|
+
defaultJobOptions: { removeOnComplete: true },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Flow Job Removal
|
|
93
|
+
|
|
94
|
+
- Removing a **parent** removes all its children.
|
|
95
|
+
- Removing a **child** removes the parent's dependency on it. If it was the last child, the parent completes.
|
|
96
|
+
- If any job to be removed is **locked** (being processed), the removal throws an exception.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
await job.remove();
|
|
100
|
+
// or
|
|
101
|
+
await queue.remove(jobId);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Add Flows in Bulk
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
await flowProducer.addBulk([
|
|
108
|
+
{ name: "flow1", queueName: "q", children: [...] },
|
|
109
|
+
{ name: "flow2", queueName: "q", children: [...] },
|
|
110
|
+
]);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Job Schedulers (v5.16.0+)
|
|
114
|
+
|
|
115
|
+
Job Schedulers replace legacy "repeatable jobs". They act as factories producing jobs on a schedule.
|
|
116
|
+
|
|
117
|
+
### Fixed Interval
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const firstJob = await queue.upsertJobScheduler("my-scheduler", {
|
|
121
|
+
every: 1000, // every 1 second
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Cron Pattern
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
await queue.upsertJobScheduler(
|
|
129
|
+
"daily-report",
|
|
130
|
+
{ pattern: "0 15 3 * * *" }, // daily at 3:15 AM
|
|
131
|
+
{
|
|
132
|
+
name: "generate-report",
|
|
133
|
+
data: { type: "daily" },
|
|
134
|
+
opts: { attempts: 5, backoff: 3, removeOnFail: 1000 },
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Key Behaviors
|
|
140
|
+
|
|
141
|
+
- **Upsert semantics**: calling `upsertJobScheduler` with the same ID updates the existing scheduler (no duplicates).
|
|
142
|
+
- **Production rate**: a new job is only produced when the previous one starts processing. If the queue is busy, jobs may arrive less frequently than the interval.
|
|
143
|
+
- **Job ID**: jobs get auto-generated IDs — you cannot use custom job IDs. Use `name` to discriminate.
|
|
144
|
+
- There is always one associated job in "delayed" status.
|
|
145
|
+
|
|
146
|
+
### Manage Schedulers
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// List all schedulers
|
|
150
|
+
const schedulers = await queue.getJobSchedulers(0, 100);
|
|
151
|
+
|
|
152
|
+
// Remove a scheduler
|
|
153
|
+
await queue.removeJobScheduler("my-scheduler");
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Repeat Strategies
|
|
157
|
+
|
|
158
|
+
Built-in: `every` (fixed interval) and `pattern` (cron). You can define custom strategies:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const queue = new Queue("q", {
|
|
162
|
+
settings: {
|
|
163
|
+
repeatStrategy: (millis, opts) => {
|
|
164
|
+
// Return next execution timestamp
|
|
165
|
+
return Date.now() + customDelay;
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Repeat Options
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
await queue.upsertJobScheduler("scheduler-id", {
|
|
175
|
+
pattern: "0 * * * *", // cron expression
|
|
176
|
+
// or
|
|
177
|
+
every: 5000, // fixed ms interval
|
|
178
|
+
|
|
179
|
+
// Optional:
|
|
180
|
+
limit: 100, // max times to repeat
|
|
181
|
+
startDate: new Date("2025-01-01"),
|
|
182
|
+
endDate: new Date("2025-12-31"),
|
|
183
|
+
tz: "America/Sao_Paulo", // timezone for cron
|
|
184
|
+
immediately: true, // produce first job immediately
|
|
185
|
+
});
|
|
186
|
+
```
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Job Types and Options
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Delayed Jobs](#delayed-jobs)
|
|
6
|
+
- [Prioritized Jobs](#prioritized-jobs)
|
|
7
|
+
- [FIFO and LIFO](#fifo-and-lifo)
|
|
8
|
+
- [Custom Job IDs](#custom-job-ids)
|
|
9
|
+
- [Job Data](#job-data)
|
|
10
|
+
- [Deduplication](#deduplication)
|
|
11
|
+
- [Repeatable Jobs (Legacy)](#repeatable-jobs-legacy)
|
|
12
|
+
- [Auto-Removal Options](#auto-removal-options)
|
|
13
|
+
- [Returning Job Data](#returning-job-data)
|
|
14
|
+
|
|
15
|
+
## Delayed Jobs
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
await queue.add("email", { to: "user@example.com" }, { delay: 60000 }); // 1 min delay
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Jobs wait in the "delayed" set, then move to "wait" (or "prioritized") when the delay expires. No `QueueScheduler` needed since BullMQ 2.0.
|
|
22
|
+
|
|
23
|
+
## Prioritized Jobs
|
|
24
|
+
|
|
25
|
+
Priority range: `0` (highest) to `2^21` (lowest). Default is `0`. Follows Unix nice convention — higher number = lower priority.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
await queue.add("urgent", data, { priority: 1 });
|
|
29
|
+
await queue.add("normal", data, { priority: 100 });
|
|
30
|
+
await queue.add("low", data, { priority: 2097152 }); // 2^21
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Prioritized jobs go into a dedicated set and are picked before regular "wait" jobs.
|
|
34
|
+
|
|
35
|
+
## FIFO and LIFO
|
|
36
|
+
|
|
37
|
+
Default is FIFO. Use `{ lifo: true }` to place jobs at the head of the waiting list.
|
|
38
|
+
|
|
39
|
+
## Custom Job IDs
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
await queue.add("job", data, { jobId: "my-unique-id" });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If a job with the same ID exists and is not yet removed, the `add` call is ignored. Useful for idempotency. Do NOT use colons `:` in job IDs (used as internal separator).
|
|
46
|
+
|
|
47
|
+
## Job Data
|
|
48
|
+
|
|
49
|
+
Keep job data small — it's stored in Redis. For large payloads, store data externally and pass a reference.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
// Update data during processing
|
|
53
|
+
await job.updateData({ ...job.data, step: "next" });
|
|
54
|
+
|
|
55
|
+
// Get current data
|
|
56
|
+
const data = job.data;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Deduplication
|
|
60
|
+
|
|
61
|
+
Three modes, all use a `deduplication` option with an `id`:
|
|
62
|
+
|
|
63
|
+
### Simple Mode — deduplicate until job completes/fails
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
await queue.add("process", data, {
|
|
67
|
+
deduplication: { id: "unique-key" },
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Subsequent adds with same dedup ID are ignored while the job is active.
|
|
72
|
+
|
|
73
|
+
### Throttle Mode — deduplicate for a TTL period
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
await queue.add("process", data, {
|
|
77
|
+
deduplication: { id: "unique-key", ttl: 5000 },
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Ignores duplicate adds for 5 seconds regardless of job state.
|
|
82
|
+
|
|
83
|
+
### Debounce Mode — replace previous job, reset TTL
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
await queue.add("process", data, {
|
|
87
|
+
deduplication: { id: "unique-key", ttl: 5000, extend: true, replace: true },
|
|
88
|
+
delay: 5000,
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Replaces the previous pending job with new data. Only the last added job is processed.
|
|
93
|
+
|
|
94
|
+
### Deduplication API
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// Check which job holds the dedup key
|
|
98
|
+
const jobId = await queue.getDeduplicationJobId("unique-key");
|
|
99
|
+
|
|
100
|
+
// Remove dedup key manually
|
|
101
|
+
await queue.removeDeduplicationKey("unique-key");
|
|
102
|
+
// Or from the job itself
|
|
103
|
+
const removed = await job.removeDeduplicationKey();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Listen for dedup events:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
queueEvents.on("deduplicated", ({ jobId, deduplicationId, deduplicatedJobId }) => {
|
|
110
|
+
console.log(`Job ${deduplicatedJobId} was deduplicated`);
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Repeatable Jobs (Legacy)
|
|
115
|
+
|
|
116
|
+
Prefer Job Schedulers (`upsertJobScheduler`) for new code. Legacy `repeat` option still works:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
await queue.add("report", data, {
|
|
120
|
+
repeat: { every: 60000 }, // every 60s
|
|
121
|
+
// or
|
|
122
|
+
repeat: { pattern: "0 * * * *" }, // cron: every hour
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Auto-Removal Options
|
|
127
|
+
|
|
128
|
+
Control how many completed/failed jobs to keep:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
await queue.add("job", data, {
|
|
132
|
+
// removeOnComplete: true, // Option A: remove immediately
|
|
133
|
+
// removeOnComplete: { count: 100 }, // Option B: keep last 100
|
|
134
|
+
removeOnComplete: { age: 3600 }, // Option C: keep for 1 hour
|
|
135
|
+
removeOnFail: { count: 500 }, // keep last 500 failed
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Set defaults for all jobs:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const queue = new Queue("q", {
|
|
143
|
+
defaultJobOptions: {
|
|
144
|
+
removeOnComplete: { count: 200 },
|
|
145
|
+
removeOnFail: { count: 1000 },
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Returning Job Data
|
|
151
|
+
|
|
152
|
+
The value returned from the processor is stored as the job's `returnvalue`:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// In the worker
|
|
156
|
+
async (job) => {
|
|
157
|
+
return { processedAt: Date.now() };
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Retrieve later
|
|
161
|
+
const job = await Job.fromId(queue, jobId);
|
|
162
|
+
console.log(job.returnvalue); // { processedAt: ... }
|
|
163
|
+
```
|