@boopkit/cap-background-jobs 0.2.6 → 0.3.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.
Files changed (2) hide show
  1. package/README.md +186 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # @boopkit/cap-background-jobs
2
+
3
+ **Part of [Boop](https://www.npmjs.com/package/@boopkit/cli)** — a bootstrap platform for AI-first builders. `boop init` scaffolds a running Node/TypeScript web app from composable **capabilities**, each one bundling the code, dependencies, middleware wiring, and agent skills needed for a feature. You pick the capabilities; `boop add` handles the rest.
4
+
5
+ `cap-background-jobs` adds async job processing via RabbitMQ (CloudAMQP). You get a publish/listen API, a dedicated worker process, a scheduler CLI for cron-triggered jobs, and automatic retry/dead-letter topology per queue. Use it for anything that shouldn't block an HTTP response: image processing, outbound email, nightly reconciles, third-party webhooks.
6
+
7
+ ## What You Get
8
+
9
+ ### Scaffold files
10
+
11
+ Copied into your project on install:
12
+
13
+ - `src/worker.ts` — worker process entry point. Boots services, registers consumers, handles graceful shutdown
14
+ - `src/scheduler.ts` — CLI for publishing scheduled triggers, filtered by `--interval` and `--job`
15
+ - `src/controllers/example-job.controller.ts` — copyable template for message consumers (auto-ACK pattern)
16
+ - `tests/controllers/example-job.controller.test.ts` — example consumer tests
17
+ - `reference/background-jobs.ref.md` — infrastructure topology, ACK modes, Heroku setup
18
+
19
+ ### Scripts added to `package.json`
20
+
21
+ | Script | Command | Purpose |
22
+ |--------|---------|---------|
23
+ | `worker` | `tsx src/worker.ts` | Start the background job worker process |
24
+ | `scheduler` | `tsx src/scheduler.ts` | Publish trigger messages to job queues (`run-jobs`, `publish`, `list` subcommands) |
25
+
26
+ ### Skills installed
27
+
28
+ - `design.background-jobs.skill.md` — teaches agents how to add a consumer, choose between auto- and manual-ACK, add scheduled jobs, and handle retries.
29
+
30
+ ### Post-install
31
+
32
+ 1. Provision CloudAMQP (or any RabbitMQ instance) and add `CLOUDAMQP_URL` to `.env`
33
+ 2. Set `QUEUE_PREFIX=dev/<yourname>` to isolate your queues from other developers sharing the instance
34
+ 3. Add `worker: npm run worker` to your `Procfile`
35
+ 4. Configure your external scheduler (e.g. Heroku Scheduler) to run `npm run scheduler -- run-jobs --interval <tag>` on the desired cadence
36
+
37
+ `boop status` reminds you of these steps after install.
38
+
39
+ ## Requirements
40
+
41
+ **Other capabilities:**
42
+
43
+ - `cap-services` — the message queue is registered as `services.message-queue` via the service map
44
+
45
+ **npm dependencies added to your project:**
46
+
47
+ - `amqplib` — RabbitMQ client
48
+ - `minimist` — scheduler CLI argument parsing
49
+
50
+ **External services:**
51
+
52
+ - **RabbitMQ** — CloudAMQP is the easiest path on Heroku (`heroku addons:create cloudamqp`, which sets `CLOUDAMQP_URL` automatically). Any RabbitMQ broker works — local Docker, self-hosted, or AWS MQ — point `CLOUDAMQP_URL` at it.
53
+
54
+ **Environment variables:**
55
+
56
+ | Variable | Required | Description |
57
+ |----------|----------|-------------|
58
+ | `CLOUDAMQP_URL` | Yes (at boot) | RabbitMQ connection URL (`amqp://...` or `amqps://...`) |
59
+ | `QUEUE_PREFIX` | No | Queue name prefix for environment isolation (e.g. `dev/mike`). Leave empty in production |
60
+ | `MAX_MESSAGE_RETRIES` | No | Max retry attempts before dead-lettering (default: `5`) |
61
+
62
+ ## Usage
63
+
64
+ Three moving parts — web publishes, scheduler publishes on a cron, worker consumes. All share one `MessageQueueService` instance registered in the service map.
65
+
66
+ ### Publish a job
67
+
68
+ From a route handler, service, or anywhere in the web process:
69
+
70
+ ```ts
71
+ import { services } from '@services';
72
+
73
+ app.post('/upload', async (req, res) => {
74
+ const upload = await services.uploads.create(req.body);
75
+
76
+ // Enqueue async processing — returns immediately
77
+ await services.messageQueue.publish('process-upload', {
78
+ task: 'process-upload',
79
+ data: { uploadId: upload.id, userId: req.user.id },
80
+ });
81
+
82
+ res.status(202).json({ id: upload.id });
83
+ });
84
+ ```
85
+
86
+ The publish connection is lazy — it only opens when you actually publish. Your web dynos stay cheap.
87
+
88
+ ### Consume a job
89
+
90
+ **1.** Copy `src/controllers/example-job.controller.ts` to `src/controllers/process-upload.controller.ts` and replace the handler body:
91
+
92
+ ```ts
93
+ export const QUEUE_NAME = 'process-upload';
94
+
95
+ export async function onMessage(msg: unknown): Promise<boolean> {
96
+ const payload = validatePayload(msg);
97
+
98
+ try {
99
+ await services.uploads.process(payload.data.uploadId);
100
+ return true; // ACK — success
101
+ } catch (err) {
102
+ console.error('[process-upload] failed:', err);
103
+ return false; // retry with exponential backoff
104
+ }
105
+ }
106
+ ```
107
+
108
+ **2.** Register the consumer in `src/worker.ts`:
109
+
110
+ ```ts
111
+ import * as processUploadController from './controllers/process-upload.controller';
112
+
113
+ const consumers = [
114
+ { queue: exampleJobController.QUEUE_NAME, handler: exampleJobController.onMessage },
115
+ { queue: processUploadController.QUEUE_NAME, handler: processUploadController.onMessage },
116
+ ];
117
+ ```
118
+
119
+ **3.** Run the worker locally:
120
+
121
+ ```bash
122
+ npm run worker
123
+ ```
124
+
125
+ On first listen, the service asserts the queue topology: main queue, `.retry` queue (TTL-backed redelivery), and `.failed` dead-letter queue. Failed handlers retry with exponential backoff (4s → 60s, capped) up to `MAX_MESSAGE_RETRIES`, then land in the DLQ for inspection.
126
+
127
+ ### Error classification
128
+
129
+ Return value from the handler decides routing:
130
+
131
+ | Return | Result |
132
+ |--------|--------|
133
+ | `return true` | ACK — success, or permanent failure that retrying won't fix |
134
+ | `return false` | Retry with backoff |
135
+ | `throw` | Retry, then DLQ after max retries |
136
+
137
+ ### Scheduled jobs
138
+
139
+ Add an entry to the `jobs` array in `src/scheduler.ts`:
140
+
141
+ ```ts
142
+ const jobs: JobConfig[] = [
143
+ { name: 'nightly-sync', queue: 'sync-accounts', interval: '60-minutes',
144
+ payload: { task: 'nightly-sync' } },
145
+ ];
146
+ ```
147
+
148
+ Then configure your external scheduler:
149
+
150
+ ```bash
151
+ # Run all jobs tagged 60-minutes
152
+ npm run scheduler -- run-jobs --interval 60-minutes
153
+
154
+ # Run a specific job on demand
155
+ npm run scheduler -- run-jobs --job nightly-sync
156
+
157
+ # Publish an ad-hoc message
158
+ npm run scheduler -- publish --queue sync-accounts --task manual-run
159
+ ```
160
+
161
+ ### Manual ACK
162
+
163
+ For batch commits or transaction coordination, opt into manual ACK:
164
+
165
+ ```ts
166
+ await services.messageQueue.listen('batch-job', async (msg, ack, nack) => {
167
+ try {
168
+ await processBatch(msg);
169
+ ack();
170
+ } catch {
171
+ nack();
172
+ }
173
+ }, { manualAck: true });
174
+ ```
175
+
176
+ First call to `ack()` or `nack()` wins — subsequent calls are silent no-ops.
177
+
178
+ See `reference/background-jobs.ref.md` in your scaffolded project for the full topology diagram, connection lifecycle, and Heroku deployment guide.
179
+
180
+ ## Added by
181
+
182
+ Not included by default. Add to an existing project with:
183
+
184
+ ```bash
185
+ boop add background-jobs
186
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cap-background-jobs",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "Background job processing with RabbitMQ for boopkit-scaffolded projects",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",