@boopkit/cap-background-jobs 0.1.0 → 0.2.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/capability.json
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"type": "application",
|
|
5
5
|
"package": "@boopkit/cap-background-jobs",
|
|
6
6
|
"requires": ["services"],
|
|
7
|
-
"dependencies": ["amqplib@^0.10"],
|
|
8
|
-
"devDependencies": ["@types/amqplib@^0.10"],
|
|
7
|
+
"dependencies": ["amqplib@^0.10", "minimist@^1.2"],
|
|
8
|
+
"devDependencies": ["@types/amqplib@^0.10", "@types/minimist@^1.2"],
|
|
9
9
|
"env": [
|
|
10
10
|
{ "name": "CLOUDAMQP_URL", "description": "CloudAMQP connection URL (amqp://...)", "required": "boot" },
|
|
11
11
|
{ "name": "QUEUE_PREFIX", "description": "Queue name prefix for environment isolation (e.g. dev/mike)" },
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"worker": { "cmd": "ts-node src/worker.ts", "description": "Start the background job worker process" },
|
|
16
|
-
"scheduler": { "cmd": "ts-node src/scheduler.ts", "description": "Publish trigger messages to job queues
|
|
16
|
+
"scheduler": { "cmd": "ts-node src/scheduler.ts", "description": "Publish trigger messages to job queues — supports --interval and --job filtering" }
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"src/scheduler.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"Add CLOUDAMQP_URL to .env",
|
|
33
33
|
"Set QUEUE_PREFIX for local dev isolation (e.g. dev/yourname)",
|
|
34
34
|
"Add worker entry to Procfile: worker: npm run worker",
|
|
35
|
-
"Configure Heroku Scheduler to run npm run scheduler on desired interval"
|
|
35
|
+
"Configure Heroku Scheduler to run 'npm run scheduler -- run-jobs --interval <tag>' on desired interval (e.g. 10-minutes, 60-minutes)"
|
|
36
36
|
],
|
|
37
37
|
"verify": "npm run worker"
|
|
38
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boopkit/cap-background-jobs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -20,10 +20,12 @@
|
|
|
20
20
|
"build": "tsc -p tsconfig.json"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"amqplib": "^0.10.0"
|
|
23
|
+
"amqplib": "^0.10.0",
|
|
24
|
+
"minimist": "^1.2.8"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/amqplib": "^0.10.0",
|
|
28
|
+
"@types/minimist": "^1.2.5",
|
|
27
29
|
"typescript": "^5.9.3"
|
|
28
30
|
},
|
|
29
31
|
"license": "ISC",
|
|
@@ -33,7 +33,33 @@ Three separate OS processes share one MQ service:
|
|
|
33
33
|
|---------|------------|------|-----------|
|
|
34
34
|
| **Web** | `src/server.ts` | Publishes messages as a side effect of HTTP requests | Long-running |
|
|
35
35
|
| **Worker** | `src/worker.ts` | Consumes messages via registered controllers | Long-running |
|
|
36
|
-
| **Scheduler** | `src/scheduler.ts` | Publishes trigger messages, then exits | One-shot (cron) |
|
|
36
|
+
| **Scheduler** | `src/scheduler.ts` | Publishes trigger messages filtered by interval/job, then exits | One-shot (cron) |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Scheduler CLI
|
|
41
|
+
|
|
42
|
+
The scheduler supports subcommands and options for selecting which jobs to trigger:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Publish all jobs tagged with the '10-minutes' interval
|
|
46
|
+
npm run scheduler -- run-jobs --interval 10-minutes
|
|
47
|
+
|
|
48
|
+
# Publish a specific job regardless of interval
|
|
49
|
+
npm run scheduler -- run-jobs --job example-job
|
|
50
|
+
|
|
51
|
+
# Combine filters
|
|
52
|
+
npm run scheduler -- run-jobs --interval 60-minutes --job reconciler
|
|
53
|
+
|
|
54
|
+
# List all registered jobs with their queue and interval
|
|
55
|
+
npm run scheduler -- list
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each job in the `jobs` array has:
|
|
59
|
+
- `name` — human-readable identifier (for `--job` filtering)
|
|
60
|
+
- `queue` — target MQ queue name
|
|
61
|
+
- `interval` — tag for external scheduler filtering (e.g. `10-minutes`, `60-minutes`)
|
|
62
|
+
- `payload` — message body published to the queue
|
|
37
63
|
|
|
38
64
|
---
|
|
39
65
|
|
|
@@ -130,7 +156,9 @@ await MessageQueueService.listen('queue', handler, { prefetch: 10 });
|
|
|
130
156
|
|
|
131
157
|
1. **CloudAMQP addon**: `heroku addons:create cloudamqp` — sets `CLOUDAMQP_URL` automatically
|
|
132
158
|
2. **Procfile**: Add `worker: npm run worker`
|
|
133
|
-
3. **Heroku Scheduler**: Configure
|
|
159
|
+
3. **Heroku Scheduler**: Configure intervals, e.g.:
|
|
160
|
+
- Every 10 min: `npm run scheduler -- run-jobs --interval 10-minutes`
|
|
161
|
+
- Every hour: `npm run scheduler -- run-jobs --interval 60-minutes`
|
|
134
162
|
4. **Scale worker**: `heroku ps:scale worker=1`
|
|
135
163
|
|
|
136
164
|
---
|
|
@@ -153,7 +181,7 @@ If drain exceeds 30s, the worker force-exits with code 1.
|
|
|
153
181
|
| File | Purpose |
|
|
154
182
|
|------|---------|
|
|
155
183
|
| `src/worker.ts` | Worker entry point — boots services, registers consumers, handles shutdown |
|
|
156
|
-
| `src/scheduler.ts` | Scheduler entry point — publishes trigger messages, exits |
|
|
184
|
+
| `src/scheduler.ts` | Scheduler entry point — CLI with interval/job filtering, publishes trigger messages, exits |
|
|
157
185
|
| `src/services/message-queue.service.ts` | MQ service — publish, listen (auto/manual ACK), topology, retry/DLQ |
|
|
158
186
|
| `src/controllers/example-job.controller.ts` | Example consumer — copyable template for new job handlers |
|
|
159
187
|
| `tests/services/message-queue.service.test.ts` | MQ service unit tests |
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
/** @boopkit/cap-background-jobs — Scheduler entry point for triggering background jobs. */
|
|
2
2
|
|
|
3
3
|
import { loadServices } from '@services';
|
|
4
|
+
import minimist from 'minimist';
|
|
4
5
|
import MessageQueueService from './services/message-queue.service';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
interface JobConfig {
|
|
8
|
+
/** Human-readable job name (used for --job filtering). */
|
|
9
|
+
name: string;
|
|
10
|
+
/** Target queue name to publish to. */
|
|
11
|
+
queue: string;
|
|
12
|
+
/** Interval tag for external scheduler filtering (e.g. '10-minutes', '60-minutes'). */
|
|
13
|
+
interval: string;
|
|
14
|
+
/** Message payload to publish. */
|
|
15
|
+
payload: { task: string; data?: Record<string, unknown> };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Static job registration — add new entries here.
|
|
19
|
+
// The `interval` field tags each job so an external scheduler can run subsets:
|
|
20
|
+
// npm run scheduler -- run-jobs --interval 10-minutes
|
|
21
|
+
const jobs: JobConfig[] = [
|
|
8
22
|
{
|
|
23
|
+
name: 'example-job',
|
|
9
24
|
queue: 'example-job',
|
|
25
|
+
interval: '10-minutes',
|
|
10
26
|
payload: {
|
|
11
27
|
task: 'scheduled-run',
|
|
12
28
|
data: {
|
|
@@ -17,10 +33,28 @@ const jobs = [
|
|
|
17
33
|
},
|
|
18
34
|
];
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Publish trigger messages for jobs matching the given filters.
|
|
38
|
+
*/
|
|
39
|
+
async function runJobs(
|
|
40
|
+
opts: { interval?: string; job?: string; await?: boolean },
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
let selected = jobs;
|
|
43
|
+
|
|
44
|
+
if (opts.interval) {
|
|
45
|
+
selected = selected.filter((j) => j.interval === opts.interval);
|
|
46
|
+
}
|
|
47
|
+
if (opts.job) {
|
|
48
|
+
selected = selected.filter((j) => j.name === opts.job);
|
|
49
|
+
}
|
|
22
50
|
|
|
23
|
-
|
|
51
|
+
console.log(
|
|
52
|
+
`[scheduler] ${selected.length} job(s) selected` +
|
|
53
|
+
(opts.interval ? ` for interval "${opts.interval}"` : '') +
|
|
54
|
+
(opts.job ? ` matching job "${opts.job}"` : ''),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
for (const job of selected) {
|
|
24
58
|
const ok = await MessageQueueService.publish(job.queue, job.payload);
|
|
25
59
|
if (ok) {
|
|
26
60
|
console.log(`[scheduler] published to "${job.queue}": ${job.payload.task}`);
|
|
@@ -28,12 +62,60 @@ async function run() {
|
|
|
28
62
|
console.error(`[scheduler] failed to publish to "${job.queue}"`);
|
|
29
63
|
}
|
|
30
64
|
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function printUsage(): void {
|
|
68
|
+
console.log(`Usage: npm run scheduler -- <command> [options]
|
|
69
|
+
|
|
70
|
+
Commands:
|
|
71
|
+
run-jobs Publish trigger messages to matching job queues
|
|
72
|
+
list List all registered jobs
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--interval <tag> Filter jobs by interval tag (e.g. 10-minutes, 60-minutes)
|
|
76
|
+
--job <name> Filter jobs by name
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main(args: string[], opts: Record<string, unknown>): Promise<void> {
|
|
81
|
+
const command = (args[0] ?? '').toLowerCase();
|
|
82
|
+
|
|
83
|
+
if (!command) {
|
|
84
|
+
printUsage();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await loadServices();
|
|
89
|
+
|
|
90
|
+
switch (command) {
|
|
91
|
+
case 'run-jobs': {
|
|
92
|
+
await runJobs({
|
|
93
|
+
interval: opts.interval as string | undefined,
|
|
94
|
+
job: opts.job as string | undefined,
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'list': {
|
|
100
|
+
console.log('[scheduler] registered jobs:');
|
|
101
|
+
for (const job of jobs) {
|
|
102
|
+
console.log(` ${job.name} queue=${job.queue} interval=${job.interval}`);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
console.error(`[scheduler] unknown command: ${command}`);
|
|
109
|
+
printUsage();
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
31
112
|
|
|
32
113
|
await MessageQueueService.close();
|
|
33
114
|
process.exit(0);
|
|
34
115
|
}
|
|
35
116
|
|
|
36
|
-
|
|
117
|
+
const argv = minimist(process.argv.slice(2));
|
|
118
|
+
main(argv._, argv).catch((err) => {
|
|
37
119
|
console.error('[scheduler] failed:', err);
|
|
38
120
|
process.exit(1);
|
|
39
121
|
});
|
|
@@ -71,13 +71,27 @@ await MessageQueueService.publish('my-job', {
|
|
|
71
71
|
Add an entry to the `jobs` array in `src/scheduler.ts`:
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
const jobs = [
|
|
75
|
-
{ queue: 'example-job', payload: { task: 'scheduled-run', data: { ... } } },
|
|
76
|
-
{ queue: 'my-job',
|
|
74
|
+
const jobs: JobConfig[] = [
|
|
75
|
+
{ name: 'example-job', queue: 'example-job', interval: '10-minutes', payload: { task: 'scheduled-run', data: { ... } } },
|
|
76
|
+
{ name: 'nightly-sync', queue: 'my-job', interval: '60-minutes', payload: { task: 'nightly-sync', data: { ... } } },
|
|
77
77
|
];
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Each job has an `interval` tag. Configure Heroku Scheduler to run different intervals:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Every 10 minutes
|
|
84
|
+
npm run scheduler -- run-jobs --interval 10-minutes
|
|
85
|
+
|
|
86
|
+
# Every hour
|
|
87
|
+
npm run scheduler -- run-jobs --interval 60-minutes
|
|
88
|
+
|
|
89
|
+
# Specific job only
|
|
90
|
+
npm run scheduler -- run-jobs --job nightly-sync
|
|
91
|
+
|
|
92
|
+
# List all registered jobs
|
|
93
|
+
npm run scheduler -- list
|
|
94
|
+
```
|
|
81
95
|
|
|
82
96
|
## Auto-ACK vs manual-ACK
|
|
83
97
|
|