runmq 1.5.0 β†’ 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 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
- <b>RunMQ</b> is a high-performance message queue library for <b>Node.js</b>, built on top of <b>RabbitMQ</b>’s rock-solid messaging guarantees.
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 combines RabbitMQ’s proven reliability with a modern developer experience β€” offering simple APIs, built-in fault tolerance, and seamless scaling for distributed systems.
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’re running <b>background jobs</b>, designing an <b>event-driven architecture</b>, or managing a <b>pub/sub event bus</b>, RunMQ provides everything you need β€” all in a <b>lightweight package</b> with a <b>simple DX</b>, <b>without the hassle of managing everything on your own</b>.
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 Message Processing with Retries**: Automatically retries failed messages with configurable delays and retry limits.
21
- - **Dead Letter Queue (DLQ) Support**: Failed messages are seamlessly routed to a DLQ after all retry attempts are exhausted.
22
- - **Pub/Sub with Atomic Delivery**: Publish a message once, and all subscribed consumers receive it atomically, without the need to publish multiple times.
23
- - **Isolated Queues per Processor**: Each processor gets its own dedicated queue and DLQ, ensuring full isolation and predictable behavior across services.
24
- - **Schema Validation**: Optional JSON Schema validation powered by AJV for safer message handling and data integrity.
25
- - **Concurrent Consumers**: Scale either horizontally (multiple instances) or vertically (multiple consumers per queue, leveraging RabbitMQ prefetch) to maximize throughput and efficiency.
26
- - **RabbitMQ Durability & Acknowledgements**: Leverages RabbitMQ's persistent storage and acknowledgment model to guarantee at-least-once delivery, even across restarts and failures.
27
- - **Custom Logging**: Plug in your own logger or use the default console logger for full control over message visibility.
28
- - **Management Dashboard**: A web-based dashboard for real-time monitoring and management of queues, DLQs, and message processing. [Check it out!](https://github.com/runmq/pulse)
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
- ### Initialize RunMQ
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
- #### Notes:
55
- - `reconnectDelay` defines the wait time between reconnection attempts.
56
- - `maxReconnectAttempts` limits the number of retries when RabbitMQ is unavailable.
57
- - Management configuration is optional but **highly recommended** to enables dynamic TTL via RabbitMQ policies; otherwise, RunMQ uses queue-based TTL.
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
- ### Processing side
63
+ ### Set up your processors
60
64
 
61
- It’s important that processors run before publishing messages, because queues are created internally when a consumer starts for the first time.
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 processor name (creates an isolated queue)
69
- consumersCount: 2, // Process up to 2 messages concurrently
70
- attempts: 3, // Retry failed messages up to 3 times
71
- attemptsDelay: 2000, // Wait 2 seconds between retries
72
- usePoliciesForDelay: true // highly recommended, default is false
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', // Unique processor name (separate queue)
81
- consumersCount: 1, // Process 1 message at a time
82
- attempts: 5, // Retry failed messages up to 5 times
83
- attemptsDelay: 1000, // Wait 1 second between retries,
84
- usePoliciesForDelay: true // highly recommended, default is false
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
- #### Notes:
92
- - `name` is the unique identifier for each processor.
93
- - RunMQ supports <b>Pub/Sub</b> out-of-the-box: multiple processors can consume the same message independently.
94
- - Example: When a user is created, one processor can send an email verification while another sends an SMS.
95
- - Each processor can have its own configuration for:
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
- ### Publishing side
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
- βœ… Each processor receives the message independently without needing multiple publishes.
112
+ βœ… One publish, every subscribed processor receives the message β€” independently and atomically.
112
113
 
113
- <br>
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
- ## Patterns in details
116
+ <br>
116
117
 
117
- RunMQ can be used to implement various messaging patterns. Two common architectures are:
118
+ ## Patterns RunMQ fits naturally
118
119
 
119
- ### 1. Event-Driven Architecture (Event Bus Pattern)
120
+ ### 1. Event-Driven Architecture (Event Bus)
120
121
 
121
- The Event Bus pattern allows multiple services (or processors) to react independently to the same events. Each service has its own queue and DLQ, ensuring full isolation and autonomy.
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 β†’ DLQ: emailService_dlq
126
- β”œβ†’ Queue: analyticsService β†’ DLQ: analyticsService_dlq
126
+ β”œβ†’ Queue: emailService β†’ DLQ: emailService_dlq
127
+ β”œβ†’ Queue: analyticsService β†’ DLQ: analyticsService_dlq
127
128
  β””β†’ Queue: notificationService β†’ DLQ: notificationService_dlq
128
129
  ```
129
130
 
130
- **Key insights:**
131
- - Publishing a single message delivers it to all processors subscribed to the topic.
132
- - Each processor can have its own retry policy, consumer count, and delay configuration.
133
- - Easily add new services by subscribing to existing topics.
134
- - Dead Letter Queues allow failed messages to be captured without affecting other services.
135
- - This architecture ensures microservices autonomy, reliability, and scalability.
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 Pattern
138
+ ### 2. Background Processing
139
139
 
140
- RunMQ can also act as a job queue for background tasks. A worker service processes jobs from a dedicated queue with retries and DLQ support.
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
- **Key insights:**
147
- - Dead Letter Queues allow failed messages to be captured without affecting other services.
148
- - Schema validation ensures that only valid messages are processed; invalid messages can be routed to the DLQ automatically.
149
- - Multiple concurrent workers can process jobs in parallel for high throughput.
150
- - at anytime could be transformed into Event-Driven Architecture by adding more processors to the same topic.
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
- RunMQ supports JSON Schema validation to ensure message integrity, so only valid messages are passed to your processors.
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
- // Message is guaranteed to be valid
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
- ### Policies for attempts delay
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 leverage RabbitMQ policies to manage the delay between attempts, it's not used by default, however it's <b>highly recommended</b> to enable it.
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
- - When `usePoliciesForDelay` is enabled in consumer config, RunMQ creates delay queues with TTL configured via RabbitMQ policies rather than hard-coding TTL in the queue itself.
210
- - Hard-coding the TTL requires manual queue re-declaration to change delays, which can involve deleting queues - making it cumbersome and error-prone.
211
- - Policies allow dynamic updates to the TTL without recreating queues β€” you can change attempts delay anytime, and RunMQ will take care of the rest.
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
- #### Benefits
214
- - Flexible and easy management of retry delays
215
- - Reduces operational overhead
216
- - Fully compatible with RunMQ's retry and DLQ mechanisms
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 (such as max retries and creation timestamp) using RabbitMQ's parameters API. This enables external tools and dashboards to discover RunMQ-managed queues and understand their configuration without direct access to the application code.
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 creates a metadata parameter that stores:
223
- - **Version**: Schema version for future-proof migrations.
224
- - **Max Retries**: The configured retry limit for the queue.
225
- - **Created At**: ISO 8601 timestamp when the queue was first configured.
226
- - **Updated At**: ISO 8601 timestamp when the configuration was last changed (if applicable).
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
- #### Benefits
229
- - **Dashboard Integration**: External monitoring tools and dashboards can query RabbitMQ's management API to retrieve queue metadata and display topology information (e.g., "10 retries with 5s delay, then to DLQ").
230
- - **Self-Documenting Queues**: Queue configurations are discoverable directly from RabbitMQ, without needing access to application source code.
231
- - **Automatic Updates**: When processor configuration changes, metadata is automatically updated while preserving the original creation timestamp.
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**: This feature requires RabbitMQ Management Plugin to be enabled for external tools to query the metadata parameters and for the parameters to be set.
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 uses a default console logger, but you can provide a custom logger by implementing the RunMQLogger interface:
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
- **Key insights:**
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 configuration
274
+ ### Management Configuration
276
275
 
277
276
  | Property | Type | Default | Description |
278
277
  |----------|------|---------|-------------|
279
- | `url` | `string` | - | The URL of the RabbitMQ management API. |
280
- | `username` | `string` | - | Username for management API authentication. |
281
- | `password` | `string` | - | Password for management API authentication. |
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 | Optional configuration to use Policies for attempts delay, highly recommended. |
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