runmq 1.5.1 β 2.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/README.md +105 -106
- package/dist/index.cjs +337 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +337 -142
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -8,24 +8,29 @@
|
|
|
8
8
|
</a>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
+
<p align="center">
|
|
12
|
+
<strong>π Full documentation: <a href="https://runmq.github.io/docs">runmq.github.io/docs</a></strong>
|
|
13
|
+
</p>
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
**RunMQ** is a high-performance message queue library for **Node.js**, built on top of **RabbitMQ**'s rock-solid messaging guarantees.
|
|
13
16
|
|
|
14
|
-
It
|
|
17
|
+
It pairs RabbitMQ's proven reliability with the kind of developer experience you actually want to work with β clean APIs, fault tolerance baked in, and scaling that just works. No hand-rolled boilerplate, no leaky abstractions.
|
|
15
18
|
|
|
16
|
-
Whether you
|
|
19
|
+
Whether you're running **background jobs**, designing an **event-driven architecture**, or wiring up a **pub/sub event bus**, RunMQ has you covered β in a lightweight package, with a simple DX, and without the operational headaches you usually sign up for.
|
|
20
|
+
|
|
21
|
+
> Using NestJS? Check out [`nestjs-runmq`](https://github.com/runmq/nestjs) β the official module with decorators, an injectable publisher, and full lifecycle integration.
|
|
17
22
|
|
|
18
23
|
## Features
|
|
19
24
|
|
|
20
|
-
- **Reliable
|
|
21
|
-
- **Dead Letter
|
|
22
|
-
- **Pub/Sub with
|
|
23
|
-
- **Isolated
|
|
24
|
-
- **Schema
|
|
25
|
-
- **Concurrent
|
|
26
|
-
- **RabbitMQ
|
|
27
|
-
- **Custom
|
|
28
|
-
- **
|
|
25
|
+
- **Reliable retries** β failed messages are retried with configurable delays and limits, so transient errors don't take you down.
|
|
26
|
+
- **Dead Letter Queues (DLQ)** β once retries are exhausted, the message lands safely in a DLQ where you can inspect or replay it.
|
|
27
|
+
- **Pub/Sub with atomic delivery** β publish a message once, and every subscribed consumer gets it. No fan-out logic on your side.
|
|
28
|
+
- **Isolated queues per processor** β each processor has its own queue and its own DLQ, so one slow consumer can't drag the rest down.
|
|
29
|
+
- **Schema validation** β optional JSON Schema validation (powered by AJV) catches bad messages before they reach your business logic.
|
|
30
|
+
- **Concurrent consumers** β scale horizontally (more instances) or vertically (more consumers per queue, via RabbitMQ prefetch) β your choice.
|
|
31
|
+
- **RabbitMQ durability & acks** β built on RabbitMQ's persistent storage and acknowledgment model, so you get at-least-once delivery even across restarts.
|
|
32
|
+
- **Custom logging** β bring your own logger, or stick with the default. Either way, you stay in control of visibility.
|
|
33
|
+
- **Real-time dashboard** β pair RunMQ with [RunMQ Pulse](https://github.com/runmq/pulse) to monitor queues, DLQs, and message flow at a glance.
|
|
29
34
|
|
|
30
35
|
## Installation
|
|
31
36
|
|
|
@@ -35,8 +40,7 @@ npm install runmq
|
|
|
35
40
|
|
|
36
41
|
## Quick Start
|
|
37
42
|
|
|
38
|
-
###
|
|
39
|
-
The first step is to connect to RabbitMQ
|
|
43
|
+
### Connect to RabbitMQ
|
|
40
44
|
|
|
41
45
|
```typescript
|
|
42
46
|
const runMQ = await RunMQ.start({
|
|
@@ -47,107 +51,103 @@ const runMQ = await RunMQ.start({
|
|
|
47
51
|
url: "http://localhost:15673",
|
|
48
52
|
username: "guest",
|
|
49
53
|
password: "guest"
|
|
50
|
-
}
|
|
54
|
+
}
|
|
51
55
|
});
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
- `reconnectDelay`
|
|
56
|
-
- `maxReconnectAttempts`
|
|
57
|
-
-
|
|
58
|
+
A few quick notes:
|
|
59
|
+
- `reconnectDelay` is the wait time between reconnection attempts.
|
|
60
|
+
- `maxReconnectAttempts` caps how many times RunMQ will retry before giving up.
|
|
61
|
+
- `management` is optional, but **highly recommended** β it unlocks dynamic TTL via RabbitMQ policies. Without it, RunMQ falls back to queue-based TTL (which works fine, just less flexible).
|
|
58
62
|
|
|
59
|
-
###
|
|
63
|
+
### Set up your processors
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
A small but important detail: **start your processors before you publish.** Queues are created the first time a consumer subscribes, so a processor needs to be up for its queue to exist.
|
|
62
66
|
|
|
63
67
|
```typescript
|
|
64
68
|
import { RunMQ } from 'runmq';
|
|
65
69
|
|
|
66
70
|
// Processor 1: Email Service
|
|
67
71
|
await runMQ.process('user.created', {
|
|
68
|
-
name: 'emailService', // Unique
|
|
69
|
-
consumersCount: 2, //
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
name: 'emailService', // Unique name β isolated queue + DLQ
|
|
73
|
+
consumersCount: 2, // 2 channels; each holds its own prefetch window
|
|
74
|
+
prefetch: 20, // Per-channel prefetch (default 20). Total in-flight = consumersCount Γ prefetch
|
|
75
|
+
attempts: 3, // Retry up to 3 times before DLQ
|
|
76
|
+
attemptsDelay: 2000, // Wait 2s between retries
|
|
77
|
+
usePoliciesForDelay: true // Recommended (default: false)
|
|
73
78
|
}, async (message) => {
|
|
74
79
|
console.log('EmailService received:', message.message);
|
|
75
80
|
await sendEmail(message.message);
|
|
76
81
|
});
|
|
77
82
|
|
|
78
|
-
// Processor 2: SMS Service
|
|
83
|
+
// Processor 2: SMS Service β same topic, separate queue
|
|
79
84
|
await runMQ.process('user.created', {
|
|
80
|
-
name: 'smsService',
|
|
81
|
-
consumersCount: 1,
|
|
82
|
-
attempts: 5,
|
|
83
|
-
attemptsDelay: 1000,
|
|
84
|
-
usePoliciesForDelay: true
|
|
85
|
+
name: 'smsService',
|
|
86
|
+
consumersCount: 1,
|
|
87
|
+
attempts: 5,
|
|
88
|
+
attemptsDelay: 1000,
|
|
89
|
+
usePoliciesForDelay: true
|
|
85
90
|
}, async (message) => {
|
|
86
91
|
console.log('SMSService received:', message.message);
|
|
87
92
|
await sendSMS(message.message);
|
|
88
93
|
});
|
|
89
94
|
```
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
- `attempts` How many the message will be retried
|
|
97
|
-
- `attemptsDelay` The delay between attempts, and if management config is provided, it can be changed anytime!
|
|
98
|
-
- `consumersCount` The concurrency level, how many messages can be processed in the same time.
|
|
99
|
-
- `usePoliciesForDelay` Enable this to let RunMQ use policies for defining delay queue TTL. Highly recommended, as it allows you to adjust delay times dynamically without re-declaring queues.
|
|
96
|
+
What's happening here:
|
|
97
|
+
- **`name`** uniquely identifies the processor and gives it a dedicated queue + DLQ.
|
|
98
|
+
- **Pub/Sub is built in** β both processors subscribe to `user.created` and each receive every message. One sends an email, the other sends an SMS, and they don't interfere with each other.
|
|
99
|
+
- Every processor gets its **own retry policy, concurrency level, and delay configuration**. Tune them per workload.
|
|
100
|
+
- With `management` configured, you can change `attemptsDelay` later without re-declaring queues β RunMQ handles the rest.
|
|
100
101
|
|
|
101
|
-
###
|
|
102
|
+
### Publish a message
|
|
102
103
|
|
|
103
104
|
```typescript
|
|
104
|
-
runMQ.publish('user.created', {
|
|
105
|
+
await runMQ.publish('user.created', {
|
|
105
106
|
userId: '123',
|
|
106
107
|
email: 'user@example.com',
|
|
107
108
|
name: 'John Doe'
|
|
108
109
|
});
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
β
|
|
112
|
+
β
One publish, every subscribed processor receives the message β independently and atomically.
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
β
**Confirmed delivery by default.** `runMQ.publish()` returns a promise that resolves only after RabbitMQ has accepted the message; if the broker rejects it (alarm state, mandatory routing failure, etc.), the promise rejects so your code can handle it. Set `usePublisherConfirms: false` in the connection config to opt out and fall back to fire-and-forget publishing if per-publish round-trip latency matters more to you than detecting silent drops.
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
<br>
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
## Patterns RunMQ fits naturally
|
|
118
119
|
|
|
119
|
-
### 1. Event-Driven Architecture (Event Bus
|
|
120
|
+
### 1. Event-Driven Architecture (Event Bus)
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
Multiple services react to the same event independently. Each one owns its queue and its DLQ β full isolation, full autonomy.
|
|
122
123
|
|
|
123
124
|
```
|
|
124
125
|
Publisher β Topic (user.created)
|
|
125
|
-
ββ Queue: emailService
|
|
126
|
-
ββ Queue: analyticsService
|
|
126
|
+
ββ Queue: emailService β DLQ: emailService_dlq
|
|
127
|
+
ββ Queue: analyticsService β DLQ: analyticsService_dlq
|
|
127
128
|
ββ Queue: notificationService β DLQ: notificationService_dlq
|
|
128
129
|
```
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
- Each
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
- Schema validation ensures that only valid messages are processed; invalid messages can be routed to the DLQ automatically.
|
|
131
|
+
Why teams reach for this pattern:
|
|
132
|
+
- One publish reaches every interested service β no fan-out logic in your app.
|
|
133
|
+
- Each service tunes its own retries, concurrency, and delays.
|
|
134
|
+
- Adding a new service is just subscribing to an existing topic β no upstream changes.
|
|
135
|
+
- A failing consumer doesn't drag the others down; bad messages land in *its* DLQ.
|
|
136
|
+
- Schema validation can stop invalid payloads before they ever reach your handlers.
|
|
137
137
|
|
|
138
|
-
### 2. Background Processing
|
|
138
|
+
### 2. Background Processing
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
A worker drains jobs from a dedicated queue, with retries and a DLQ for the ones that fail.
|
|
141
141
|
|
|
142
142
|
```
|
|
143
143
|
Publisher β Topic (email.send) β Queue: emailWorker β DLQ: emailWorker_dlq
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
146
|
+
Why this works well:
|
|
147
|
+
- Run multiple workers in parallel for high throughput.
|
|
148
|
+
- Failures are captured in the DLQ where you can inspect or replay them.
|
|
149
|
+
- Schema validation keeps malformed jobs from breaking your worker.
|
|
150
|
+
- If your needs grow, this pattern transforms into an Event Bus by simply adding more processors to the same topic β no migration required.
|
|
151
151
|
|
|
152
152
|
<br>
|
|
153
153
|
|
|
@@ -155,9 +155,7 @@ Publisher β Topic (email.send) β Queue: emailWorker β DLQ: emailWorker_dlq
|
|
|
155
155
|
|
|
156
156
|
### Schema Validation
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
- Currently, AJV is supported for schema validation.
|
|
160
|
-
- Invalid messages are sent directly to the DLQ without being sent to the processor.
|
|
158
|
+
Validate messages before they hit your handler, so your business logic only ever sees well-formed data. Currently powered by [AJV](https://ajv.js.org/); invalid messages can be routed straight to the DLQ for later inspection.
|
|
161
159
|
|
|
162
160
|
```typescript
|
|
163
161
|
const orderSchema = {
|
|
@@ -193,48 +191,50 @@ await runMQ.process('order.placed', {
|
|
|
193
191
|
failureStrategy: 'dlq' // Invalid messages go straight to DLQ
|
|
194
192
|
}
|
|
195
193
|
}, async (message) => {
|
|
196
|
-
//
|
|
194
|
+
// message.message is guaranteed to match the schema
|
|
197
195
|
await processOrder(message.message);
|
|
198
196
|
});
|
|
199
197
|
```
|
|
200
|
-
**Key insights:**
|
|
201
|
-
- Schema validation enforces message correctness before processing, reducing runtime errors.
|
|
202
|
-
- Only messages matching the schema reach your business logic.
|
|
203
|
-
- DLQ ensures that invalid messages are captured and can be inspected later.
|
|
204
198
|
|
|
205
|
-
|
|
199
|
+
A few things to call out:
|
|
200
|
+
- Validation runs *before* your handler, so runtime errors from bad payloads are kept out of your code paths.
|
|
201
|
+
- Only schema-conformant messages reach your business logic β everything else is captured in the DLQ for inspection.
|
|
202
|
+
|
|
203
|
+
### Policy-based retry delays
|
|
206
204
|
|
|
207
|
-
RunMQ can
|
|
205
|
+
RunMQ can use RabbitMQ policies to manage the delay between attempts. It's off by default, but **highly recommended** to turn on.
|
|
208
206
|
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
-
|
|
207
|
+
- With `usePoliciesForDelay: true`, the delay TTL is set via a RabbitMQ policy instead of being hard-coded into the queue.
|
|
208
|
+
- Without it, changing the delay later means re-declaring (and sometimes deleting) queues β cumbersome and error-prone.
|
|
209
|
+
- With policies, you can update `attemptsDelay` on the fly. RunMQ takes care of applying it.
|
|
212
210
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
-
|
|
211
|
+
> π‘ **Pair it with [RunMQ Pulse](https://github.com/runmq/pulse).** Once policy-based delays are on, Pulse becomes your control panel: tweak retry delays **live from the dashboard**, no redeploys and no queue surgery. Production getting noisy? Bump the delay, watch the queues breathe, and dial it back when things settle β all from the UI.
|
|
212
|
+
|
|
213
|
+
**Why it matters:**
|
|
214
|
+
- Flexible, low-friction retry tuning β from code or from Pulse.
|
|
215
|
+
- Less operational overhead during incidents (change delays without touching infra).
|
|
216
|
+
- Fully compatible with the rest of RunMQ's retry and DLQ machinery.
|
|
217
217
|
|
|
218
218
|
### Queue Metadata Storage
|
|
219
219
|
|
|
220
|
-
RunMQ automatically stores queue metadata (
|
|
220
|
+
RunMQ automatically stores queue metadata (max retries, creation timestamp, etc.) using RabbitMQ's parameters API. External tools and dashboards can read this to understand what's running β without ever touching your application code.
|
|
221
221
|
|
|
222
|
-
When a processor is configured, RunMQ
|
|
223
|
-
- **Version
|
|
224
|
-
- **Max Retries
|
|
225
|
-
- **Created At
|
|
226
|
-
- **Updated At
|
|
222
|
+
When a processor is configured, RunMQ stores:
|
|
223
|
+
- **Version** β schema version for future-proof migrations.
|
|
224
|
+
- **Max Retries** β the retry limit configured for the queue.
|
|
225
|
+
- **Created At** β ISO 8601 timestamp from when the queue was first configured.
|
|
226
|
+
- **Updated At** β ISO 8601 timestamp from the most recent configuration change.
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
- **Dashboard
|
|
230
|
-
- **Self-
|
|
231
|
-
- **
|
|
228
|
+
**Why it matters:**
|
|
229
|
+
- **Dashboard-friendly** β tools can pull this from RabbitMQ's management API and surface topology info like "10 retries with 5s delay, then to DLQ" automatically.
|
|
230
|
+
- **Self-documenting queues** β your queue configuration is discoverable straight from RabbitMQ, no source code required.
|
|
231
|
+
- **Auto-updating** β config changes update the metadata, while preserving the original `createdAt` so you keep a clean timeline.
|
|
232
232
|
|
|
233
|
-
> **Note
|
|
233
|
+
> **Note:** This feature requires the RabbitMQ Management Plugin to be enabled β that's how the parameters get written and read.
|
|
234
234
|
|
|
235
235
|
### Custom Logger
|
|
236
236
|
|
|
237
|
-
RunMQ
|
|
237
|
+
RunMQ ships with a default console logger, but you can plug in your own by implementing the `RunMQLogger` interface:
|
|
238
238
|
|
|
239
239
|
```typescript
|
|
240
240
|
import { RunMQLogger } from 'runmq';
|
|
@@ -243,7 +243,7 @@ class CustomLogger implements RunMQLogger {
|
|
|
243
243
|
log(message: string): void {
|
|
244
244
|
// Custom info logging
|
|
245
245
|
}
|
|
246
|
-
|
|
246
|
+
|
|
247
247
|
error(message: string, error?: any): void {
|
|
248
248
|
// Custom error logging
|
|
249
249
|
}
|
|
@@ -253,9 +253,7 @@ class CustomLogger implements RunMQLogger {
|
|
|
253
253
|
const runMQ = await RunMQ.start(config, new CustomLogger());
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
- Custom loggers allow integration with centralized logging systems (e.g., Winston, Bunyan, Datadog).
|
|
258
|
-
- Both info and error methods can be customized to suit your monitoring strategy.
|
|
256
|
+
This is the hook you want when you're piping logs into Winston, Bunyan, Datadog, or any centralized logging stack β both `log` and `error` are yours to shape.
|
|
259
257
|
|
|
260
258
|
<br>
|
|
261
259
|
|
|
@@ -268,17 +266,18 @@ const runMQ = await RunMQ.start(config, new CustomLogger());
|
|
|
268
266
|
| `url` | `string` | β | The URL of the RabbitMQ server. |
|
|
269
267
|
| `reconnectDelay` | `number` | `5000` | Delay in milliseconds before attempting to reconnect after a disconnection. |
|
|
270
268
|
| `maxReconnectAttempts` | `number` | `5` | Maximum number of reconnection attempts. |
|
|
269
|
+
| `usePublisherConfirms` | `boolean` | `true` | Enable RabbitMQ publisher confirms on the user publish channel. When `true`, `publish()` resolves only after the broker acks the message and rejects on broker error. Set to `false` for fire-and-forget publishing. _Available in 2.x._ |
|
|
271
270
|
| `management` | `ManagementConfiguration` | β | RabbitMQ management API configuration. |
|
|
272
271
|
|
|
273
272
|
---
|
|
274
273
|
|
|
275
|
-
### Management
|
|
274
|
+
### Management Configuration
|
|
276
275
|
|
|
277
276
|
| Property | Type | Default | Description |
|
|
278
277
|
|----------|------|---------|-------------|
|
|
279
|
-
| `url` | `string` |
|
|
280
|
-
| `username` | `string` |
|
|
281
|
-
| `password` | `string` |
|
|
278
|
+
| `url` | `string` | β | The URL of the RabbitMQ management API. |
|
|
279
|
+
| `username` | `string` | β | Username for management API authentication. |
|
|
280
|
+
| `password` | `string` | β | Password for management API authentication. |
|
|
282
281
|
|
|
283
282
|
---
|
|
284
283
|
|
|
@@ -287,12 +286,12 @@ const runMQ = await RunMQ.start(config, new CustomLogger());
|
|
|
287
286
|
| Property | Type | Default | Description |
|
|
288
287
|
|----------|------|---------|-------------|
|
|
289
288
|
| `name` | `string` | β | Unique name of the processor, used to create isolated queues. |
|
|
290
|
-
| `consumersCount` | `number` | β | Number of concurrent consumers for this processor. |
|
|
289
|
+
| `consumersCount` | `number` | β | Number of concurrent consumers (independent AMQP channels) for this processor. Each consumer keeps its own `prefetch` window, so total in-flight = `consumersCount Γ prefetch`. |
|
|
290
|
+
| `prefetch` | `number` | `20` | Per-consumer prefetch count. This is **per channel**, not per processor β total unacked messages held by the processor is `consumersCount Γ prefetch`. Lower it if memory footprint or crash redelivery surface matters. |
|
|
291
291
|
| `attempts` | `number` | `1` | Maximum attempts to process a message. |
|
|
292
292
|
| `attemptsDelay` | `number` | `1000` | Delay in milliseconds between attempts. |
|
|
293
293
|
| `messageSchema` | `MessageSchema` | β | Optional schema configuration for message validation. |
|
|
294
|
-
| `usePoliciesForDelay` | `boolean` | false |
|
|
295
|
-
|
|
294
|
+
| `usePoliciesForDelay` | `boolean` | `false` | Use RabbitMQ policies for the retry delay. Highly recommended. |
|
|
296
295
|
|
|
297
296
|
---
|
|
298
297
|
|
|
@@ -300,7 +299,7 @@ const runMQ = await RunMQ.start(config, new CustomLogger());
|
|
|
300
299
|
|
|
301
300
|
| Property | Type | Description |
|
|
302
301
|
|----------|------|-------------|
|
|
303
|
-
| `type` | `'ajv'` | Type of schema used for validation (currently only AJV supported). |
|
|
302
|
+
| `type` | `'ajv'` | Type of schema used for validation (currently only AJV is supported). |
|
|
304
303
|
| `schema` | `any` | Schema definition for validating messages. |
|
|
305
304
|
| `failureStrategy` | `'dlq'` | Strategy applied when schema validation fails (e.g., move to DLQ). |
|
|
306
305
|
|