rabbitmq-with-retry-and-dlq 1.0.24 → 1.0.25
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 +235 -139
- package/dist/consumerMq.d.ts +89 -3
- package/dist/consumerMq.d.ts.map +1 -1
- package/dist/consumerMq.js +274 -59
- package/dist/consumerMq.js.map +1 -1
- package/dist/publisherMq.d.ts +2 -70
- package/dist/publisherMq.d.ts.map +1 -1
- package/dist/publisherMq.js +76 -235
- package/dist/publisherMq.js.map +1 -1
- package/dist/types.d.ts +13 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,6 +20,48 @@ npm install rabbitmq-with-retry-and-dlq
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
## Quick Start (Best Practice)
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
initializeRabbitMQ,
|
|
28
|
+
publisher,
|
|
29
|
+
consumer,
|
|
30
|
+
} from 'rabbitmq-with-retry-and-dlq';
|
|
31
|
+
|
|
32
|
+
// 1. Initialize connection
|
|
33
|
+
await initializeRabbitMQ('amqp://localhost:5672');
|
|
34
|
+
|
|
35
|
+
// 2. Publisher: Assert exchange
|
|
36
|
+
await publisher.assertExchange('orders', { exchangeType: 'topic' });
|
|
37
|
+
|
|
38
|
+
// 3. Consumer: Setup queue with exchange and routing keys
|
|
39
|
+
await consumer.setupQueue({
|
|
40
|
+
queueName: 'orders-processor',
|
|
41
|
+
exchangeName: 'orders',
|
|
42
|
+
exchangeType: 'topic',
|
|
43
|
+
routingKeys: ['order.created', 'order.updated'],
|
|
44
|
+
retryConfig: { maxRetries: 5, retryDelayMs: 2000 },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// 4. Consumer: Start consuming
|
|
48
|
+
await consumer.consumeQueue({
|
|
49
|
+
queueName: 'orders-processor',
|
|
50
|
+
onMessage: async (message) => {
|
|
51
|
+
await processOrder(message);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 5. Publisher: Publish messages
|
|
56
|
+
await publisher.publishToExchange({
|
|
57
|
+
exchangeName: 'orders',
|
|
58
|
+
routingKey: 'order.created',
|
|
59
|
+
message: { orderId: 123 },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
23
65
|
## Usage
|
|
24
66
|
|
|
25
67
|
### 1. Initialize (Required - Call Once with Await)
|
|
@@ -27,7 +69,11 @@ npm install rabbitmq-with-retry-and-dlq
|
|
|
27
69
|
**You must call `await initializeRabbitMQ()` once** in your application setup or helper file:
|
|
28
70
|
|
|
29
71
|
```typescript
|
|
30
|
-
import {
|
|
72
|
+
import {
|
|
73
|
+
initializeRabbitMQ,
|
|
74
|
+
isPublisherConnected,
|
|
75
|
+
isConsumerConnected,
|
|
76
|
+
} from 'rabbitmq-with-retry-and-dlq';
|
|
31
77
|
|
|
32
78
|
try {
|
|
33
79
|
// Call once at application startup (in your helper/setup file)
|
|
@@ -36,7 +82,7 @@ try {
|
|
|
36
82
|
|
|
37
83
|
// Check connection status (both should be true after await)
|
|
38
84
|
console.log('Publisher connected:', isPublisherConnected()); // true
|
|
39
|
-
console.log('Consumer connected:', isConsumerConnected());
|
|
85
|
+
console.log('Consumer connected:', isConsumerConnected()); // true
|
|
40
86
|
} catch (error) {
|
|
41
87
|
// Connection failed (wrong URL, RabbitMQ not running, timeout, etc.)
|
|
42
88
|
console.error('Failed to connect to RabbitMQ:', error);
|
|
@@ -45,6 +91,7 @@ try {
|
|
|
45
91
|
```
|
|
46
92
|
|
|
47
93
|
**Error Handling:**
|
|
94
|
+
|
|
48
95
|
- Throws error if URL is wrong
|
|
49
96
|
- Throws error if RabbitMQ is not running
|
|
50
97
|
- Throws error if connection timeout (default: 30 seconds)
|
|
@@ -61,7 +108,7 @@ import { publisher, consumer } from 'rabbitmq-with-retry-and-dlq';
|
|
|
61
108
|
// First call automatically establishes connection
|
|
62
109
|
await publisher.publishToQueue({
|
|
63
110
|
queueName: 'orders',
|
|
64
|
-
message: { orderId: 123 }
|
|
111
|
+
message: { orderId: 123 },
|
|
65
112
|
});
|
|
66
113
|
```
|
|
67
114
|
|
|
@@ -75,6 +122,7 @@ This library follows the RabbitMQ best practice pattern:
|
|
|
75
122
|
- **Consumer** → Owns **Queues** and **Bindings** (decides what to subscribe to)
|
|
76
123
|
|
|
77
124
|
This is cleaner because:
|
|
125
|
+
|
|
78
126
|
1. Publishers don't need to know about queues
|
|
79
127
|
2. Consumers can create their own queues and choose their bindings
|
|
80
128
|
3. Multiple consumers can bind differently to the same exchange
|
|
@@ -95,8 +143,8 @@ await publisher.assertExchange(['orders', 'payments', 'notifications']);
|
|
|
95
143
|
|
|
96
144
|
// With options
|
|
97
145
|
await publisher.assertExchange('orders', {
|
|
98
|
-
exchangeType: 'direct',
|
|
99
|
-
durable: true,
|
|
146
|
+
exchangeType: 'direct', // 'direct' | 'topic' | 'fanout' | 'headers' (default: 'direct')
|
|
147
|
+
durable: true, // Survives broker restart (default: true)
|
|
100
148
|
});
|
|
101
149
|
```
|
|
102
150
|
|
|
@@ -111,12 +159,52 @@ await consumer.assertExchange('orders', { exchangeType: 'topic' });
|
|
|
111
159
|
await consumer.assertExchange('payments', { exchangeType: 'direct' });
|
|
112
160
|
|
|
113
161
|
// Or assert multiple exchanges at once
|
|
114
|
-
await consumer.assertExchange(['orders', 'payments'], {
|
|
162
|
+
await consumer.assertExchange(['orders', 'payments'], {
|
|
163
|
+
exchangeType: 'direct',
|
|
164
|
+
});
|
|
115
165
|
```
|
|
116
166
|
|
|
117
167
|
> **Why?** Publisher and consumer use separate connections. Even if publisher creates the exchange, the consumer's connection might not see it immediately. Asserting exchanges on the consumer's connection ensures they exist before binding.
|
|
118
168
|
|
|
119
|
-
### Consumer:
|
|
169
|
+
### Consumer: Setup Queue (RECOMMENDED - Best Practice)
|
|
170
|
+
|
|
171
|
+
**Use `setupQueue()` for complete setup in one atomic operation** - prevents race conditions and supports multiple routing keys:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// ✅ RECOMMENDED: Complete setup in one call
|
|
175
|
+
await consumer.setupQueue({
|
|
176
|
+
queueName: 'orders-processor',
|
|
177
|
+
exchangeName: 'orders',
|
|
178
|
+
exchangeType: 'topic',
|
|
179
|
+
routingKeys: ['order.created', 'order.updated', 'order.cancelled'], // Multiple routing keys!
|
|
180
|
+
retryConfig: {
|
|
181
|
+
maxRetries: 5,
|
|
182
|
+
retryDelayMs: 2000,
|
|
183
|
+
backoffStrategy: 'exponential',
|
|
184
|
+
jitterMs: 1000,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Then consume (queue already set up)
|
|
189
|
+
await consumer.consumeQueue({
|
|
190
|
+
queueName: 'orders-processor',
|
|
191
|
+
onMessage: async (message) => {
|
|
192
|
+
console.log('Processing order:', message);
|
|
193
|
+
await processOrder(message);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**What `setupQueue()` does:**
|
|
199
|
+
|
|
200
|
+
1. ✅ Asserts exchange
|
|
201
|
+
2. ✅ Creates queue with retry/DLQ infrastructure
|
|
202
|
+
3. ✅ Binds queue to exchange with all routing keys
|
|
203
|
+
4. ✅ All in ONE atomic operation (no race conditions)
|
|
204
|
+
|
|
205
|
+
### Consumer: Assert Queues (Alternative - More Control)
|
|
206
|
+
|
|
207
|
+
Use `assertQueues()` when you need more control or want to add bindings dynamically:
|
|
120
208
|
|
|
121
209
|
```typescript
|
|
122
210
|
// Assert a single queue
|
|
@@ -125,9 +213,11 @@ await consumer.assertQueues('orders-worker');
|
|
|
125
213
|
// Assert multiple queues
|
|
126
214
|
await consumer.assertQueues(['orders-worker', 'payments-worker']);
|
|
127
215
|
|
|
128
|
-
// Assert queue with retry/DLQ configuration
|
|
216
|
+
// Assert queue with exchange binding and retry/DLQ configuration
|
|
129
217
|
await consumer.assertQueues('orders-worker', {
|
|
130
|
-
|
|
218
|
+
exchangeName: 'orders',
|
|
219
|
+
exchangeType: 'direct',
|
|
220
|
+
routingKey: 'order.created', // Single routing key
|
|
131
221
|
retryConfig: {
|
|
132
222
|
maxRetries: 5,
|
|
133
223
|
retryDelayMs: 2000,
|
|
@@ -136,28 +226,34 @@ await consumer.assertQueues('orders-worker', {
|
|
|
136
226
|
});
|
|
137
227
|
```
|
|
138
228
|
|
|
139
|
-
### Consumer: Bind Queue to Exchange
|
|
229
|
+
### Consumer: Bind Queue to Exchange (For Additional Bindings)
|
|
230
|
+
|
|
231
|
+
Use `bindQueue()` to add more routing key bindings after initial setup:
|
|
140
232
|
|
|
141
233
|
```typescript
|
|
142
234
|
// IMPORTANT: Call consumer.assertExchange() FIRST to prevent race conditions!
|
|
143
235
|
|
|
144
236
|
// Bind queue to exchange with routing key
|
|
145
|
-
await consumer.bindQueue('orders-worker', 'orders', 'order.created'
|
|
237
|
+
await consumer.bindQueue('orders-worker', 'orders', 'order.created', {
|
|
238
|
+
exchangeType: 'topic', // Ensures exchange exists (recommended)
|
|
239
|
+
});
|
|
146
240
|
|
|
147
241
|
// Bind with pattern (for topic exchanges)
|
|
148
|
-
await consumer.bindQueue('all-orders', 'events', 'order.*'
|
|
242
|
+
await consumer.bindQueue('all-orders', 'events', 'order.*', {
|
|
243
|
+
exchangeType: 'topic',
|
|
244
|
+
});
|
|
149
245
|
|
|
150
|
-
//
|
|
246
|
+
// Add multiple bindings to same queue
|
|
151
247
|
await consumer.bindQueue('orders-worker', 'orders', 'order.created');
|
|
152
248
|
await consumer.bindQueue('orders-worker', 'orders', 'order.updated');
|
|
249
|
+
await consumer.bindQueue('orders-worker', 'orders', 'order.cancelled');
|
|
153
250
|
```
|
|
154
251
|
|
|
155
252
|
### Complete Best Practice Example (RECOMMENDED)
|
|
156
253
|
|
|
157
|
-
Use `setupQueue()` for atomic setup - it does everything in ONE operation, preventing all race conditions:
|
|
158
|
-
|
|
159
254
|
```typescript
|
|
160
255
|
// === PUBLISHER SIDE ===
|
|
256
|
+
// Publisher only knows about exchanges
|
|
161
257
|
await publisher.assertExchange('orders', { exchangeType: 'topic' });
|
|
162
258
|
|
|
163
259
|
await publisher.publishToExchange({
|
|
@@ -167,81 +263,66 @@ await publisher.publishToExchange({
|
|
|
167
263
|
});
|
|
168
264
|
|
|
169
265
|
// === CONSUMER SIDE ===
|
|
170
|
-
//
|
|
266
|
+
// Step 1: Setup queue with exchange and multiple routing keys (RECOMMENDED)
|
|
171
267
|
await consumer.setupQueue({
|
|
172
268
|
queueName: 'orders-processor',
|
|
173
269
|
exchangeName: 'orders',
|
|
174
270
|
exchangeType: 'topic',
|
|
175
|
-
routingKeys: ['order.created', 'order.updated'],
|
|
176
|
-
retryConfig: { maxRetries: 3, retryDelayMs: 5000 }
|
|
271
|
+
routingKeys: ['order.created', 'order.updated'], // Multiple keys!
|
|
272
|
+
retryConfig: { maxRetries: 3, retryDelayMs: 5000 },
|
|
177
273
|
});
|
|
178
274
|
|
|
179
|
-
// Start consuming
|
|
275
|
+
// Step 2: Start consuming (queue already set up)
|
|
180
276
|
await consumer.consumeQueue({
|
|
181
277
|
queueName: 'orders-processor',
|
|
182
278
|
onMessage: async (message) => {
|
|
183
279
|
console.log('Processing order:', message);
|
|
280
|
+
await processOrder(message);
|
|
184
281
|
},
|
|
185
282
|
});
|
|
186
283
|
```
|
|
187
284
|
|
|
188
285
|
### Alternative: Step-by-Step Setup
|
|
189
286
|
|
|
190
|
-
If you need more control,
|
|
287
|
+
If you need more granular control, use separate methods:
|
|
191
288
|
|
|
192
289
|
```typescript
|
|
193
|
-
// Step 1: Assert exchanges on consumer's connection
|
|
290
|
+
// Step 1: Assert exchanges on consumer's connection (defensive)
|
|
194
291
|
await consumer.assertExchange('orders', { exchangeType: 'topic' });
|
|
195
292
|
|
|
196
293
|
// Step 2: Create queues with retry/DLQ config
|
|
197
294
|
await consumer.assertQueues('orders-processor', {
|
|
198
|
-
retryConfig: { maxRetries: 3, retryDelayMs: 5000 }
|
|
295
|
+
retryConfig: { maxRetries: 3, retryDelayMs: 5000 },
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Step 3: Bind queues to exchanges (can add multiple routing keys)
|
|
299
|
+
await consumer.bindQueue('orders-processor', 'orders', 'order.created', {
|
|
300
|
+
exchangeType: 'topic',
|
|
301
|
+
});
|
|
302
|
+
await consumer.bindQueue('orders-processor', 'orders', 'order.updated', {
|
|
303
|
+
exchangeType: 'topic',
|
|
199
304
|
});
|
|
200
305
|
|
|
201
|
-
// Step
|
|
202
|
-
await consumer.
|
|
203
|
-
|
|
306
|
+
// Step 4: Start consuming
|
|
307
|
+
await consumer.consumeQueue({
|
|
308
|
+
queueName: 'orders-processor',
|
|
309
|
+
onMessage: async (message) => {
|
|
310
|
+
await processOrder(message);
|
|
311
|
+
},
|
|
312
|
+
});
|
|
204
313
|
```
|
|
205
314
|
|
|
206
315
|
---
|
|
207
316
|
|
|
208
|
-
##
|
|
209
|
-
|
|
210
|
-
For simpler use cases, the publisher can also manage queues (backward compatible):
|
|
317
|
+
## Direct Queue Publishing (Without Exchange)
|
|
211
318
|
|
|
212
|
-
|
|
319
|
+
For simple point-to-point messaging without exchanges:
|
|
213
320
|
|
|
214
|
-
|
|
321
|
+
### Publish to Queue
|
|
215
322
|
|
|
216
323
|
```typescript
|
|
217
|
-
|
|
218
|
-
durable: true, // Queue survives restart (default: true)
|
|
219
|
-
retryConfig: {
|
|
220
|
-
maxRetries: 5, // Required
|
|
221
|
-
retryDelayMs: 2000, // Base delay (default: 5000ms)
|
|
222
|
-
backoffStrategy: 'exponential', // 'exponential' or 'linear' (default: exponential)
|
|
223
|
-
maxDelayMs: 300000, // Max delay cap (default: 300000 = 5min)
|
|
224
|
-
jitterMs: 1000, // Random jitter 0-1000ms (default: 0)
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Assert multiple queues
|
|
229
|
-
await publisher.assertQueues(['orders', 'payments', 'notifications'], {
|
|
230
|
-
retryConfig: { maxRetries: 3 }
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// With exchange
|
|
234
|
-
await publisher.assertQueues('order_processing', {
|
|
235
|
-
exchangeName: 'orders_exchange',
|
|
236
|
-
exchangeType: 'direct', // 'direct' | 'topic' | 'fanout' | 'headers'
|
|
237
|
-
routingKey: 'order.created',
|
|
238
|
-
retryConfig: { maxRetries: 3 },
|
|
239
|
-
});
|
|
240
|
-
```
|
|
324
|
+
// Note: Queue should be set up by consumer first using assertQueues() or setupQueue()
|
|
241
325
|
|
|
242
|
-
### 4. Publish to Queue
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
326
|
await publisher.publishToQueue({
|
|
246
327
|
queueName: 'orders',
|
|
247
328
|
message: {
|
|
@@ -249,14 +330,19 @@ await publisher.publishToQueue({
|
|
|
249
330
|
amount: 99.99,
|
|
250
331
|
},
|
|
251
332
|
options: {
|
|
252
|
-
persistent: true,
|
|
253
|
-
priority: 5,
|
|
254
|
-
expiration: '5000' // TTL in ms (optional)
|
|
333
|
+
persistent: true, // Survives broker restart (default: true)
|
|
334
|
+
priority: 5, // 0-10 priority (optional)
|
|
335
|
+
expiration: '5000', // TTL in ms (optional)
|
|
336
|
+
},
|
|
337
|
+
// Optional: retryConfig if queue wasn't set up with retry
|
|
338
|
+
retryConfig: {
|
|
339
|
+
maxRetries: 5,
|
|
340
|
+
retryDelayMs: 2000,
|
|
255
341
|
},
|
|
256
342
|
});
|
|
257
343
|
```
|
|
258
344
|
|
|
259
|
-
###
|
|
345
|
+
### Publish to Exchange
|
|
260
346
|
|
|
261
347
|
```typescript
|
|
262
348
|
// Publisher only needs exchange and routing key (not queue!)
|
|
@@ -272,10 +358,10 @@ await publisher.publishToExchange({
|
|
|
272
358
|
});
|
|
273
359
|
```
|
|
274
360
|
|
|
275
|
-
###
|
|
361
|
+
### Consumer (Best Practice)
|
|
276
362
|
|
|
277
363
|
```typescript
|
|
278
|
-
// Set up error handler
|
|
364
|
+
// Set up error handler FIRST
|
|
279
365
|
consumer.on('error', (errorEvent) => {
|
|
280
366
|
console.error('RabbitMQ Error:', errorEvent);
|
|
281
367
|
if (errorEvent.type === 'DLQ_FAILED') {
|
|
@@ -283,31 +369,34 @@ consumer.on('error', (errorEvent) => {
|
|
|
283
369
|
}
|
|
284
370
|
});
|
|
285
371
|
|
|
286
|
-
//
|
|
372
|
+
// Step 1: Setup queue with exchange and routing keys
|
|
373
|
+
await consumer.setupQueue({
|
|
374
|
+
queueName: 'orders-processor',
|
|
375
|
+
exchangeName: 'orders_exchange',
|
|
376
|
+
exchangeType: 'direct',
|
|
377
|
+
routingKeys: ['order.created', 'order.updated'],
|
|
378
|
+
retryConfig: {
|
|
379
|
+
maxRetries: 5,
|
|
380
|
+
retryDelayMs: 2000,
|
|
381
|
+
backoffStrategy: 'exponential',
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Step 2: Start consuming (queue already set up)
|
|
287
386
|
await consumer.consumeQueue({
|
|
288
|
-
queueName: 'orders',
|
|
387
|
+
queueName: 'orders-processor',
|
|
289
388
|
onMessage: async (message, messageInfo) => {
|
|
290
389
|
console.log('Processing:', message);
|
|
291
|
-
|
|
390
|
+
|
|
292
391
|
// Your business logic
|
|
293
392
|
await processOrder(message);
|
|
294
|
-
|
|
393
|
+
|
|
295
394
|
// Success = auto-ack
|
|
296
|
-
// Throw error = retry with backoff
|
|
395
|
+
// Throw error = retry with backoff → DLQ
|
|
297
396
|
},
|
|
298
397
|
options: {
|
|
299
|
-
prefetch: 5,
|
|
300
|
-
noAck: false,
|
|
301
|
-
},
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
// Consume from exchange
|
|
305
|
-
await consumer.consumeQueue({
|
|
306
|
-
queueName: 'order_processing',
|
|
307
|
-
exchangeName: 'orders_exchange',
|
|
308
|
-
routingKey: 'order.created',
|
|
309
|
-
onMessage: async (message) => {
|
|
310
|
-
await processOrder(message);
|
|
398
|
+
prefetch: 5, // Max unacked messages (default: 5)
|
|
399
|
+
noAck: false, // Auto-ack (default: false)
|
|
311
400
|
},
|
|
312
401
|
});
|
|
313
402
|
```
|
|
@@ -335,11 +424,11 @@ process.on('SIGINT', shutdown);
|
|
|
335
424
|
|
|
336
425
|
```typescript
|
|
337
426
|
interface RetryConfig {
|
|
338
|
-
maxRetries: number;
|
|
339
|
-
retryDelayMs?: number;
|
|
340
|
-
backoffStrategy?: string;
|
|
341
|
-
maxDelayMs?: number;
|
|
342
|
-
jitterMs?: number;
|
|
427
|
+
maxRetries: number; // Required: max retry attempts
|
|
428
|
+
retryDelayMs?: number; // Optional: base delay in ms (default: 5000)
|
|
429
|
+
backoffStrategy?: string; // Optional: 'exponential' | 'linear' (default: 'exponential')
|
|
430
|
+
maxDelayMs?: number; // Optional: max delay cap (default: 300000)
|
|
431
|
+
jitterMs?: number; // Optional: random jitter range (default: 0)
|
|
343
432
|
}
|
|
344
433
|
```
|
|
345
434
|
|
|
@@ -403,9 +492,9 @@ async function startApp() {
|
|
|
403
492
|
// 1. Initialize RabbitMQ connection (required - use await)
|
|
404
493
|
console.log('Connecting to RabbitMQ...');
|
|
405
494
|
await initializeRabbitMQ('amqp://user:pass@localhost:5672');
|
|
406
|
-
|
|
495
|
+
|
|
407
496
|
console.log('✓ Publisher connected:', isPublisherConnected()); // true
|
|
408
|
-
console.log('✓ Consumer connected:', isConsumerConnected());
|
|
497
|
+
console.log('✓ Consumer connected:', isConsumerConnected()); // true
|
|
409
498
|
|
|
410
499
|
// 2. Set up error handler
|
|
411
500
|
consumer.on('error', (errorEvent: RabbitMQErrorEvent) => {
|
|
@@ -416,12 +505,12 @@ async function startApp() {
|
|
|
416
505
|
await publisher.assertExchange('orders', { exchangeType: 'topic' });
|
|
417
506
|
console.log('✓ Publisher exchange asserted');
|
|
418
507
|
|
|
419
|
-
// 4. Consumer:
|
|
420
|
-
await consumer.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
508
|
+
// 4. Consumer: Setup queue with exchange and routing keys (RECOMMENDED)
|
|
509
|
+
await consumer.setupQueue({
|
|
510
|
+
queueName: 'orders-processor',
|
|
511
|
+
exchangeName: 'orders',
|
|
512
|
+
exchangeType: 'topic',
|
|
513
|
+
routingKeys: ['order.created', 'order.updated'],
|
|
425
514
|
retryConfig: {
|
|
426
515
|
maxRetries: 5,
|
|
427
516
|
retryDelayMs: 2000,
|
|
@@ -429,14 +518,9 @@ async function startApp() {
|
|
|
429
518
|
jitterMs: 1000,
|
|
430
519
|
},
|
|
431
520
|
});
|
|
432
|
-
console.log('✓
|
|
433
|
-
|
|
434
|
-
// 6. Consumer: Bind queue to exchange (consumer decides what to receive)
|
|
435
|
-
await consumer.bindQueue('orders-processor', 'orders', 'order.created');
|
|
436
|
-
await consumer.bindQueue('orders-processor', 'orders', 'order.updated');
|
|
437
|
-
console.log('✓ Queue bindings created');
|
|
521
|
+
console.log('✓ Queue setup complete');
|
|
438
522
|
|
|
439
|
-
//
|
|
523
|
+
// 5. Start consumer (queue already set up)
|
|
440
524
|
await consumer.consumeQueue({
|
|
441
525
|
queueName: 'orders-processor',
|
|
442
526
|
onMessage: async (message) => {
|
|
@@ -498,29 +582,28 @@ startApp();
|
|
|
498
582
|
|
|
499
583
|
### Publisher Methods
|
|
500
584
|
|
|
501
|
-
| Method
|
|
502
|
-
|
|
503
|
-
| `assertExchange(names, options?)` | Create exchange(s) - Publisher owns exchanges
|
|
504
|
-
| `
|
|
505
|
-
| `
|
|
506
|
-
| `
|
|
507
|
-
| `
|
|
508
|
-
| `deleteQueues(names, options?)` | Delete queue(s) and related retry/DLQ queues |
|
|
509
|
-
| `close()` | Close publisher connection |
|
|
585
|
+
| Method | Description |
|
|
586
|
+
| --------------------------------- | -------------------------------------------------------------- |
|
|
587
|
+
| `assertExchange(names, options?)` | Create exchange(s) - Publisher owns exchanges |
|
|
588
|
+
| `publishToExchange(config)` | Publish to exchange with routing key |
|
|
589
|
+
| `publishToQueue(config)` | Publish directly to queue (queue should be set up by consumer) |
|
|
590
|
+
| `publishBatch(configs)` | Publish multiple messages |
|
|
591
|
+
| `close()` | Close publisher connection |
|
|
510
592
|
|
|
511
593
|
### Consumer Methods
|
|
512
594
|
|
|
513
|
-
| Method
|
|
514
|
-
|
|
515
|
-
| `setupQueue(config)`
|
|
516
|
-
| `assertExchange(names, options?)`
|
|
517
|
-
| `assertQueues(names, options?)`
|
|
518
|
-
| `bindQueue(queue, exchange, routingKey, options?)` | Bind queue to exchange |
|
|
519
|
-
| `
|
|
520
|
-
| `
|
|
521
|
-
| `
|
|
522
|
-
| `
|
|
523
|
-
| `
|
|
595
|
+
| Method | Description |
|
|
596
|
+
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
597
|
+
| `setupQueue(config)` | **⭐ RECOMMENDED** - Complete setup: exchange + queue + bindings + retry/DLQ in one atomic operation |
|
|
598
|
+
| `assertExchange(names, options?)` | Assert exchange(s) on consumer connection (defensive) |
|
|
599
|
+
| `assertQueues(names, options?)` | Create queue(s) with optional exchange binding and retry/DLQ config |
|
|
600
|
+
| `bindQueue(queue, exchange, routingKey, options?)` | Bind queue to exchange (for additional routing keys) |
|
|
601
|
+
| `deleteQueues(names, options?)` | Delete queue(s) and related retry/DLQ queues |
|
|
602
|
+
| `consumeQueue(config)` | Start consuming from queue (queue must be set up first) |
|
|
603
|
+
| `consumeMultipleQueues(config)` | Consume from multiple queues |
|
|
604
|
+
| `consumeWithPattern(exchange, pattern, handler)` | Consume with pattern matching (topic) |
|
|
605
|
+
| `getConsumerStats()` | Get consumer statistics |
|
|
606
|
+
| `close()` | Close consumer connection |
|
|
524
607
|
|
|
525
608
|
---
|
|
526
609
|
|
|
@@ -550,12 +633,13 @@ startApp();
|
|
|
550
633
|
|
|
551
634
|
```typescript
|
|
552
635
|
await consumer.bindQueue(
|
|
553
|
-
'queue-name',
|
|
554
|
-
'exchange-name',
|
|
555
|
-
'routing.key',
|
|
556
|
-
{
|
|
557
|
-
|
|
558
|
-
|
|
636
|
+
'queue-name', // Queue to bind
|
|
637
|
+
'exchange-name', // Exchange to bind to
|
|
638
|
+
'routing.key', // Routing key or pattern (for topic exchanges)
|
|
639
|
+
{
|
|
640
|
+
// Options (optional)
|
|
641
|
+
exchangeType: 'topic', // If provided, asserts exchange before binding (RECOMMENDED)
|
|
642
|
+
durable: true, // Exchange durability (default: true)
|
|
559
643
|
}
|
|
560
644
|
);
|
|
561
645
|
|
|
@@ -602,13 +686,13 @@ await consumer.bindQueue(
|
|
|
602
686
|
|
|
603
687
|
```typescript
|
|
604
688
|
// Delete queue (includes retry and DLQ by default)
|
|
605
|
-
await
|
|
689
|
+
await consumer.deleteQueues('orders');
|
|
606
690
|
|
|
607
691
|
// Delete multiple queues
|
|
608
|
-
await
|
|
692
|
+
await consumer.deleteQueues(['orders', 'payments']);
|
|
609
693
|
|
|
610
694
|
// Delete only main queue
|
|
611
|
-
await
|
|
695
|
+
await consumer.deleteQueues('orders', {
|
|
612
696
|
includeRetry: false,
|
|
613
697
|
includeDLQ: false,
|
|
614
698
|
});
|
|
@@ -619,11 +703,13 @@ await publisher.deleteQueues('orders', {
|
|
|
619
703
|
## Important Notes
|
|
620
704
|
|
|
621
705
|
### Required (Must Implement)
|
|
706
|
+
|
|
622
707
|
- ⚠️ **Must call `await initializeRabbitMQ(url)` with try/catch** - throws error on connection failure
|
|
623
708
|
- ⚠️ **Must implement graceful shutdown handlers** (`SIGTERM`, `SIGINT`)
|
|
624
709
|
- ⚠️ **Must monitor DLQ failures** (indicates message loss)
|
|
625
710
|
|
|
626
711
|
### Error Handling
|
|
712
|
+
|
|
627
713
|
- `initializeRabbitMQ()` throws error if:
|
|
628
714
|
- RabbitMQ URL is wrong
|
|
629
715
|
- RabbitMQ is not running
|
|
@@ -631,6 +717,7 @@ await publisher.deleteQueues('orders', {
|
|
|
631
717
|
- Always wrap in try/catch for fail-fast behavior
|
|
632
718
|
|
|
633
719
|
### Automatic (No Action Needed)
|
|
720
|
+
|
|
634
721
|
- ✅ Auto-reconnection on connection loss
|
|
635
722
|
- ✅ Lazy loading - connects on first use (after initialization)
|
|
636
723
|
- ✅ Message durability with `persistent: true`
|
|
@@ -638,12 +725,16 @@ await publisher.deleteQueues('orders', {
|
|
|
638
725
|
- ✅ Thread-safe initialization
|
|
639
726
|
|
|
640
727
|
### Best Practices
|
|
728
|
+
|
|
641
729
|
- Call `initializeRabbitMQ(url)` once in your setup/helper file
|
|
642
730
|
- **Use separation of concerns pattern:**
|
|
643
|
-
- Publisher → `assertExchange()` (owns exchanges)
|
|
644
|
-
- Consumer → `assertQueues()` + `bindQueue()` (owns queues and bindings)
|
|
645
|
-
-
|
|
646
|
-
|
|
731
|
+
- Publisher → `assertExchange()` + `publishToExchange()` (owns exchanges)
|
|
732
|
+
- Consumer → `setupQueue()` or `assertQueues()` + `bindQueue()` (owns queues and bindings)
|
|
733
|
+
- **Recommended workflow:**
|
|
734
|
+
1. Setup infrastructure at startup: `consumer.setupQueue()` or `consumer.assertQueues()`
|
|
735
|
+
2. Start consuming: `consumer.consumeQueue()`
|
|
736
|
+
- Set `retryConfig` in `setupQueue()` or `assertQueues()` - auto-used when processing
|
|
737
|
+
- `assertExchange()`, `assertQueues()`, `setupQueue()`, and `bindQueue()` are idempotent - safe to call multiple times
|
|
647
738
|
- Use exponential backoff with jitter to prevent thundering herd
|
|
648
739
|
- Always set up error event handlers before consuming
|
|
649
740
|
- Use graceful shutdown in production
|
|
@@ -655,9 +746,10 @@ await publisher.deleteQueues('orders', {
|
|
|
655
746
|
### Error: PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange'
|
|
656
747
|
|
|
657
748
|
**Problem:**
|
|
749
|
+
|
|
658
750
|
```
|
|
659
|
-
Error: Operation failed: QueueDeclare; 406 (PRECONDITION_FAILED) with message
|
|
660
|
-
"PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue
|
|
751
|
+
Error: Operation failed: QueueDeclare; 406 (PRECONDITION_FAILED) with message
|
|
752
|
+
"PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue
|
|
661
753
|
'my_queue' in vhost '/': received 'my_exchange' but current is ''"
|
|
662
754
|
```
|
|
663
755
|
|
|
@@ -668,32 +760,36 @@ The queue already exists in RabbitMQ with different configuration arguments than
|
|
|
668
760
|
Delete the existing queue and let your code recreate it with the correct configuration.
|
|
669
761
|
|
|
670
762
|
**Option 1 - Using the utility script:**
|
|
763
|
+
|
|
671
764
|
```bash
|
|
672
765
|
npx ts-node scripts/delete-queue.ts my_queue
|
|
673
766
|
```
|
|
674
767
|
|
|
675
768
|
**Option 2 - Using RabbitMQ Management UI:**
|
|
769
|
+
|
|
676
770
|
1. Navigate to `http://localhost:15672` (default credentials: guest/guest)
|
|
677
771
|
2. Go to the "Queues" tab
|
|
678
772
|
3. Find and delete the problematic queue
|
|
679
773
|
|
|
680
774
|
**Option 3 - Using RabbitMQ CLI:**
|
|
775
|
+
|
|
681
776
|
```bash
|
|
682
777
|
rabbitmqadmin delete queue name=my_queue
|
|
683
778
|
```
|
|
684
779
|
|
|
685
780
|
**Option 4 - Using this library:**
|
|
781
|
+
|
|
686
782
|
```typescript
|
|
687
|
-
import {
|
|
783
|
+
import { consumer } from 'rabbitmq-with-retry-and-dlq';
|
|
688
784
|
|
|
689
|
-
await
|
|
785
|
+
await consumer.deleteQueues('my_queue', {
|
|
690
786
|
includeRetry: true,
|
|
691
|
-
includeDLQ: true
|
|
787
|
+
includeDLQ: true,
|
|
692
788
|
});
|
|
693
789
|
```
|
|
694
790
|
|
|
695
791
|
**Prevention:**
|
|
696
|
-
Always use `
|
|
792
|
+
Always use `consumer.setupQueue()` or `consumer.assertQueues()` to create queues with the proper retry configuration before consuming.
|
|
697
793
|
|
|
698
794
|
---
|
|
699
795
|
|