rabbitmq-with-retry-and-dlq 1.0.23 → 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 +247 -134
- package/dist/consumerMq.d.ts +126 -3
- package/dist/consumerMq.d.ts.map +1 -1
- package/dist/consumerMq.js +363 -56
- 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,27 +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
|
-
### Complete Best Practice Example
|
|
252
|
+
### Complete Best Practice Example (RECOMMENDED)
|
|
156
253
|
|
|
157
254
|
```typescript
|
|
158
255
|
// === PUBLISHER SIDE ===
|
|
159
|
-
// Publisher only
|
|
256
|
+
// Publisher only knows about exchanges
|
|
160
257
|
await publisher.assertExchange('orders', { exchangeType: 'topic' });
|
|
161
258
|
|
|
162
259
|
await publisher.publishToExchange({
|
|
@@ -166,66 +263,66 @@ await publisher.publishToExchange({
|
|
|
166
263
|
});
|
|
167
264
|
|
|
168
265
|
// === CONSUMER SIDE ===
|
|
169
|
-
// Step 1:
|
|
170
|
-
await consumer.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
266
|
+
// Step 1: Setup queue with exchange and multiple routing keys (RECOMMENDED)
|
|
267
|
+
await consumer.setupQueue({
|
|
268
|
+
queueName: 'orders-processor',
|
|
269
|
+
exchangeName: 'orders',
|
|
270
|
+
exchangeType: 'topic',
|
|
271
|
+
routingKeys: ['order.created', 'order.updated'], // Multiple keys!
|
|
272
|
+
retryConfig: { maxRetries: 3, retryDelayMs: 5000 },
|
|
175
273
|
});
|
|
176
274
|
|
|
177
|
-
// Step
|
|
178
|
-
await consumer.bindQueue('orders-processor', 'orders', 'order.created');
|
|
179
|
-
await consumer.bindQueue('orders-processor', 'orders', 'order.updated');
|
|
180
|
-
|
|
181
|
-
// Step 4: Start consuming
|
|
275
|
+
// Step 2: Start consuming (queue already set up)
|
|
182
276
|
await consumer.consumeQueue({
|
|
183
277
|
queueName: 'orders-processor',
|
|
184
278
|
onMessage: async (message) => {
|
|
185
279
|
console.log('Processing order:', message);
|
|
280
|
+
await processOrder(message);
|
|
186
281
|
},
|
|
187
282
|
});
|
|
188
283
|
```
|
|
189
284
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
## Alternative: Publisher-Managed Queues
|
|
193
|
-
|
|
194
|
-
For simpler use cases, the publisher can also manage queues (backward compatible):
|
|
195
|
-
|
|
196
|
-
### 3. Assert Queues (with Retry Config)
|
|
285
|
+
### Alternative: Step-by-Step Setup
|
|
197
286
|
|
|
198
|
-
|
|
287
|
+
If you need more granular control, use separate methods:
|
|
199
288
|
|
|
200
289
|
```typescript
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
maxDelayMs: 300000, // Max delay cap (default: 300000 = 5min)
|
|
208
|
-
jitterMs: 1000, // Random jitter 0-1000ms (default: 0)
|
|
209
|
-
},
|
|
290
|
+
// Step 1: Assert exchanges on consumer's connection (defensive)
|
|
291
|
+
await consumer.assertExchange('orders', { exchangeType: 'topic' });
|
|
292
|
+
|
|
293
|
+
// Step 2: Create queues with retry/DLQ config
|
|
294
|
+
await consumer.assertQueues('orders-processor', {
|
|
295
|
+
retryConfig: { maxRetries: 3, retryDelayMs: 5000 },
|
|
210
296
|
});
|
|
211
297
|
|
|
212
|
-
//
|
|
213
|
-
await
|
|
214
|
-
|
|
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',
|
|
215
304
|
});
|
|
216
305
|
|
|
217
|
-
//
|
|
218
|
-
await
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
306
|
+
// Step 4: Start consuming
|
|
307
|
+
await consumer.consumeQueue({
|
|
308
|
+
queueName: 'orders-processor',
|
|
309
|
+
onMessage: async (message) => {
|
|
310
|
+
await processOrder(message);
|
|
311
|
+
},
|
|
223
312
|
});
|
|
224
313
|
```
|
|
225
314
|
|
|
226
|
-
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Direct Queue Publishing (Without Exchange)
|
|
318
|
+
|
|
319
|
+
For simple point-to-point messaging without exchanges:
|
|
320
|
+
|
|
321
|
+
### Publish to Queue
|
|
227
322
|
|
|
228
323
|
```typescript
|
|
324
|
+
// Note: Queue should be set up by consumer first using assertQueues() or setupQueue()
|
|
325
|
+
|
|
229
326
|
await publisher.publishToQueue({
|
|
230
327
|
queueName: 'orders',
|
|
231
328
|
message: {
|
|
@@ -233,14 +330,19 @@ await publisher.publishToQueue({
|
|
|
233
330
|
amount: 99.99,
|
|
234
331
|
},
|
|
235
332
|
options: {
|
|
236
|
-
persistent: true,
|
|
237
|
-
priority: 5,
|
|
238
|
-
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,
|
|
239
341
|
},
|
|
240
342
|
});
|
|
241
343
|
```
|
|
242
344
|
|
|
243
|
-
###
|
|
345
|
+
### Publish to Exchange
|
|
244
346
|
|
|
245
347
|
```typescript
|
|
246
348
|
// Publisher only needs exchange and routing key (not queue!)
|
|
@@ -256,10 +358,10 @@ await publisher.publishToExchange({
|
|
|
256
358
|
});
|
|
257
359
|
```
|
|
258
360
|
|
|
259
|
-
###
|
|
361
|
+
### Consumer (Best Practice)
|
|
260
362
|
|
|
261
363
|
```typescript
|
|
262
|
-
// Set up error handler
|
|
364
|
+
// Set up error handler FIRST
|
|
263
365
|
consumer.on('error', (errorEvent) => {
|
|
264
366
|
console.error('RabbitMQ Error:', errorEvent);
|
|
265
367
|
if (errorEvent.type === 'DLQ_FAILED') {
|
|
@@ -267,31 +369,34 @@ consumer.on('error', (errorEvent) => {
|
|
|
267
369
|
}
|
|
268
370
|
});
|
|
269
371
|
|
|
270
|
-
//
|
|
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)
|
|
271
386
|
await consumer.consumeQueue({
|
|
272
|
-
queueName: 'orders',
|
|
387
|
+
queueName: 'orders-processor',
|
|
273
388
|
onMessage: async (message, messageInfo) => {
|
|
274
389
|
console.log('Processing:', message);
|
|
275
|
-
|
|
390
|
+
|
|
276
391
|
// Your business logic
|
|
277
392
|
await processOrder(message);
|
|
278
|
-
|
|
393
|
+
|
|
279
394
|
// Success = auto-ack
|
|
280
|
-
// Throw error = retry with backoff
|
|
395
|
+
// Throw error = retry with backoff → DLQ
|
|
281
396
|
},
|
|
282
397
|
options: {
|
|
283
|
-
prefetch: 5,
|
|
284
|
-
noAck: false,
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Consume from exchange
|
|
289
|
-
await consumer.consumeQueue({
|
|
290
|
-
queueName: 'order_processing',
|
|
291
|
-
exchangeName: 'orders_exchange',
|
|
292
|
-
routingKey: 'order.created',
|
|
293
|
-
onMessage: async (message) => {
|
|
294
|
-
await processOrder(message);
|
|
398
|
+
prefetch: 5, // Max unacked messages (default: 5)
|
|
399
|
+
noAck: false, // Auto-ack (default: false)
|
|
295
400
|
},
|
|
296
401
|
});
|
|
297
402
|
```
|
|
@@ -319,11 +424,11 @@ process.on('SIGINT', shutdown);
|
|
|
319
424
|
|
|
320
425
|
```typescript
|
|
321
426
|
interface RetryConfig {
|
|
322
|
-
maxRetries: number;
|
|
323
|
-
retryDelayMs?: number;
|
|
324
|
-
backoffStrategy?: string;
|
|
325
|
-
maxDelayMs?: number;
|
|
326
|
-
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)
|
|
327
432
|
}
|
|
328
433
|
```
|
|
329
434
|
|
|
@@ -387,9 +492,9 @@ async function startApp() {
|
|
|
387
492
|
// 1. Initialize RabbitMQ connection (required - use await)
|
|
388
493
|
console.log('Connecting to RabbitMQ...');
|
|
389
494
|
await initializeRabbitMQ('amqp://user:pass@localhost:5672');
|
|
390
|
-
|
|
495
|
+
|
|
391
496
|
console.log('✓ Publisher connected:', isPublisherConnected()); // true
|
|
392
|
-
console.log('✓ Consumer connected:', isConsumerConnected());
|
|
497
|
+
console.log('✓ Consumer connected:', isConsumerConnected()); // true
|
|
393
498
|
|
|
394
499
|
// 2. Set up error handler
|
|
395
500
|
consumer.on('error', (errorEvent: RabbitMQErrorEvent) => {
|
|
@@ -400,12 +505,12 @@ async function startApp() {
|
|
|
400
505
|
await publisher.assertExchange('orders', { exchangeType: 'topic' });
|
|
401
506
|
console.log('✓ Publisher exchange asserted');
|
|
402
507
|
|
|
403
|
-
// 4. Consumer:
|
|
404
|
-
await consumer.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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'],
|
|
409
514
|
retryConfig: {
|
|
410
515
|
maxRetries: 5,
|
|
411
516
|
retryDelayMs: 2000,
|
|
@@ -413,14 +518,9 @@ async function startApp() {
|
|
|
413
518
|
jitterMs: 1000,
|
|
414
519
|
},
|
|
415
520
|
});
|
|
416
|
-
console.log('✓
|
|
417
|
-
|
|
418
|
-
// 6. Consumer: Bind queue to exchange (consumer decides what to receive)
|
|
419
|
-
await consumer.bindQueue('orders-processor', 'orders', 'order.created');
|
|
420
|
-
await consumer.bindQueue('orders-processor', 'orders', 'order.updated');
|
|
421
|
-
console.log('✓ Queue bindings created');
|
|
521
|
+
console.log('✓ Queue setup complete');
|
|
422
522
|
|
|
423
|
-
//
|
|
523
|
+
// 5. Start consumer (queue already set up)
|
|
424
524
|
await consumer.consumeQueue({
|
|
425
525
|
queueName: 'orders-processor',
|
|
426
526
|
onMessage: async (message) => {
|
|
@@ -482,28 +582,28 @@ startApp();
|
|
|
482
582
|
|
|
483
583
|
### Publisher Methods
|
|
484
584
|
|
|
485
|
-
| Method
|
|
486
|
-
|
|
487
|
-
| `assertExchange(names, options?)` | Create exchange(s) - Publisher owns exchanges
|
|
488
|
-
| `
|
|
489
|
-
| `
|
|
490
|
-
| `
|
|
491
|
-
| `
|
|
492
|
-
| `deleteQueues(names, options?)` | Delete queue(s) and related retry/DLQ queues |
|
|
493
|
-
| `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 |
|
|
494
592
|
|
|
495
593
|
### Consumer Methods
|
|
496
594
|
|
|
497
|
-
| Method
|
|
498
|
-
|
|
499
|
-
| `
|
|
500
|
-
| `
|
|
501
|
-
| `
|
|
502
|
-
| `
|
|
503
|
-
| `
|
|
504
|
-
| `
|
|
505
|
-
| `
|
|
506
|
-
| `
|
|
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 |
|
|
507
607
|
|
|
508
608
|
---
|
|
509
609
|
|
|
@@ -533,12 +633,13 @@ startApp();
|
|
|
533
633
|
|
|
534
634
|
```typescript
|
|
535
635
|
await consumer.bindQueue(
|
|
536
|
-
'queue-name',
|
|
537
|
-
'exchange-name',
|
|
538
|
-
'routing.key',
|
|
539
|
-
{
|
|
540
|
-
|
|
541
|
-
|
|
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)
|
|
542
643
|
}
|
|
543
644
|
);
|
|
544
645
|
|
|
@@ -585,13 +686,13 @@ await consumer.bindQueue(
|
|
|
585
686
|
|
|
586
687
|
```typescript
|
|
587
688
|
// Delete queue (includes retry and DLQ by default)
|
|
588
|
-
await
|
|
689
|
+
await consumer.deleteQueues('orders');
|
|
589
690
|
|
|
590
691
|
// Delete multiple queues
|
|
591
|
-
await
|
|
692
|
+
await consumer.deleteQueues(['orders', 'payments']);
|
|
592
693
|
|
|
593
694
|
// Delete only main queue
|
|
594
|
-
await
|
|
695
|
+
await consumer.deleteQueues('orders', {
|
|
595
696
|
includeRetry: false,
|
|
596
697
|
includeDLQ: false,
|
|
597
698
|
});
|
|
@@ -602,11 +703,13 @@ await publisher.deleteQueues('orders', {
|
|
|
602
703
|
## Important Notes
|
|
603
704
|
|
|
604
705
|
### Required (Must Implement)
|
|
706
|
+
|
|
605
707
|
- ⚠️ **Must call `await initializeRabbitMQ(url)` with try/catch** - throws error on connection failure
|
|
606
708
|
- ⚠️ **Must implement graceful shutdown handlers** (`SIGTERM`, `SIGINT`)
|
|
607
709
|
- ⚠️ **Must monitor DLQ failures** (indicates message loss)
|
|
608
710
|
|
|
609
711
|
### Error Handling
|
|
712
|
+
|
|
610
713
|
- `initializeRabbitMQ()` throws error if:
|
|
611
714
|
- RabbitMQ URL is wrong
|
|
612
715
|
- RabbitMQ is not running
|
|
@@ -614,6 +717,7 @@ await publisher.deleteQueues('orders', {
|
|
|
614
717
|
- Always wrap in try/catch for fail-fast behavior
|
|
615
718
|
|
|
616
719
|
### Automatic (No Action Needed)
|
|
720
|
+
|
|
617
721
|
- ✅ Auto-reconnection on connection loss
|
|
618
722
|
- ✅ Lazy loading - connects on first use (after initialization)
|
|
619
723
|
- ✅ Message durability with `persistent: true`
|
|
@@ -621,12 +725,16 @@ await publisher.deleteQueues('orders', {
|
|
|
621
725
|
- ✅ Thread-safe initialization
|
|
622
726
|
|
|
623
727
|
### Best Practices
|
|
728
|
+
|
|
624
729
|
- Call `initializeRabbitMQ(url)` once in your setup/helper file
|
|
625
730
|
- **Use separation of concerns pattern:**
|
|
626
|
-
- Publisher → `assertExchange()` (owns exchanges)
|
|
627
|
-
- Consumer → `assertQueues()` + `bindQueue()` (owns queues and bindings)
|
|
628
|
-
-
|
|
629
|
-
|
|
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
|
|
630
738
|
- Use exponential backoff with jitter to prevent thundering herd
|
|
631
739
|
- Always set up error event handlers before consuming
|
|
632
740
|
- Use graceful shutdown in production
|
|
@@ -638,9 +746,10 @@ await publisher.deleteQueues('orders', {
|
|
|
638
746
|
### Error: PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange'
|
|
639
747
|
|
|
640
748
|
**Problem:**
|
|
749
|
+
|
|
641
750
|
```
|
|
642
|
-
Error: Operation failed: QueueDeclare; 406 (PRECONDITION_FAILED) with message
|
|
643
|
-
"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
|
|
644
753
|
'my_queue' in vhost '/': received 'my_exchange' but current is ''"
|
|
645
754
|
```
|
|
646
755
|
|
|
@@ -651,32 +760,36 @@ The queue already exists in RabbitMQ with different configuration arguments than
|
|
|
651
760
|
Delete the existing queue and let your code recreate it with the correct configuration.
|
|
652
761
|
|
|
653
762
|
**Option 1 - Using the utility script:**
|
|
763
|
+
|
|
654
764
|
```bash
|
|
655
765
|
npx ts-node scripts/delete-queue.ts my_queue
|
|
656
766
|
```
|
|
657
767
|
|
|
658
768
|
**Option 2 - Using RabbitMQ Management UI:**
|
|
769
|
+
|
|
659
770
|
1. Navigate to `http://localhost:15672` (default credentials: guest/guest)
|
|
660
771
|
2. Go to the "Queues" tab
|
|
661
772
|
3. Find and delete the problematic queue
|
|
662
773
|
|
|
663
774
|
**Option 3 - Using RabbitMQ CLI:**
|
|
775
|
+
|
|
664
776
|
```bash
|
|
665
777
|
rabbitmqadmin delete queue name=my_queue
|
|
666
778
|
```
|
|
667
779
|
|
|
668
780
|
**Option 4 - Using this library:**
|
|
781
|
+
|
|
669
782
|
```typescript
|
|
670
|
-
import {
|
|
783
|
+
import { consumer } from 'rabbitmq-with-retry-and-dlq';
|
|
671
784
|
|
|
672
|
-
await
|
|
785
|
+
await consumer.deleteQueues('my_queue', {
|
|
673
786
|
includeRetry: true,
|
|
674
|
-
includeDLQ: true
|
|
787
|
+
includeDLQ: true,
|
|
675
788
|
});
|
|
676
789
|
```
|
|
677
790
|
|
|
678
791
|
**Prevention:**
|
|
679
|
-
Always use `
|
|
792
|
+
Always use `consumer.setupQueue()` or `consumer.assertQueues()` to create queues with the proper retry configuration before consuming.
|
|
680
793
|
|
|
681
794
|
---
|
|
682
795
|
|