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.
@@ -76,8 +76,11 @@ declare class RabbitMQConsumer extends EventEmitter {
76
76
  * await consumer.assertQueues(['orders-worker', 'payments-worker']);
77
77
  *
78
78
  * @example
79
- * // Assert queue with DLQ setup
79
+ * // Assert queue with exchange binding and retry config
80
80
  * await consumer.assertQueues('orders-worker', {
81
+ * exchangeName: 'orders',
82
+ * exchangeType: 'direct',
83
+ * routingKey: 'order.created',
81
84
  * retryConfig: { maxRetries: 3, retryDelayMs: 5000 }
82
85
  * });
83
86
  */
@@ -85,12 +88,44 @@ declare class RabbitMQConsumer extends EventEmitter {
85
88
  durable?: boolean;
86
89
  exclusive?: boolean;
87
90
  autoDelete?: boolean;
91
+ exchangeName?: string;
92
+ exchangeType?: 'direct' | 'topic' | 'fanout' | 'headers';
93
+ routingKey?: string;
88
94
  retryConfig?: RetryConfig;
89
95
  }): Promise<void>;
90
96
  /**
91
- * Setup retry queue and DLQ for a main queue
97
+ * Delete queues (Best Practice for cleanup/testing)
98
+ * Can be called with a single queue name or multiple queue names
99
+ * Also deletes associated retry and DLQ queues if they exist
100
+ *
101
+ * @param queueNames - Single queue name (string) or array of queue names
102
+ * @param options - Delete options (includeRetry, includeDLQ)
103
+ *
104
+ * @example
105
+ * // Delete a single queue
106
+ * await consumer.deleteQueues('orders');
107
+ *
108
+ * @example
109
+ * // Delete multiple queues
110
+ * await consumer.deleteQueues(['orders', 'payments', 'notifications']);
111
+ *
112
+ * @example
113
+ * // Delete queue without retry/DLQ queues
114
+ * await consumer.deleteQueues('orders', {
115
+ * includeRetry: false,
116
+ * includeDLQ: false
117
+ * });
118
+ */
119
+ deleteQueues(queueNames: string | string[], options?: {
120
+ /** Whether to delete associated retry queue (default: true) */
121
+ includeRetry?: boolean;
122
+ /** Whether to delete associated DLQ (default: true) */
123
+ includeDLQ?: boolean;
124
+ }): Promise<void>;
125
+ /**
126
+ * Setup Dead Letter Queue (DLQ) infrastructure for a queue with retry mechanism
92
127
  */
93
- private setupRetryAndDLQQueues;
128
+ private setupDLQInfrastructure;
94
129
  /**
95
130
  * Bind a queue to an exchange with a routing key (Best Practice for Consumers)
96
131
  * Consumers decide which exchanges/routing keys they want to subscribe to
@@ -176,10 +211,61 @@ declare class RabbitMQConsumer extends EventEmitter {
176
211
  private calculateRetryDelay;
177
212
  /**
178
213
  * Consume messages from a queue with automatic retry and DLQ handling
214
+ *
215
+ * Note: Queue should be set up separately before consuming:
216
+ * - Use assertQueues() to create queue with exchange binding
217
+ * - Use setupQueue() for complete setup with multiple routing keys
218
+ * - Use bindQueue() to add additional routing key bindings
219
+ *
220
+ * @example
221
+ * // Step 1: Setup queue with exchange and routing keys
222
+ * await consumer.setupQueue({
223
+ * queueName: 'orders',
224
+ * exchangeName: 'orders',
225
+ * exchangeType: 'topic',
226
+ * routingKeys: ['order.created', 'order.updated'],
227
+ * retryConfig: { maxRetries: 5 }
228
+ * });
229
+ *
230
+ * // Step 2: Consume (queue already set up)
231
+ * await consumer.consumeQueue({
232
+ * queueName: 'orders',
233
+ * onMessage: async (message) => {
234
+ * await processOrder(message);
235
+ * }
236
+ * });
237
+ *
238
+ * @example
239
+ * // Direct queue consumption (no exchange)
240
+ * await consumer.assertQueues('simple-queue');
241
+ * await consumer.consumeQueue({
242
+ * queueName: 'simple-queue',
243
+ * onMessage: async (message) => { ... }
244
+ * });
179
245
  */
180
246
  consumeQueue<T = any>(config: ConsumeConfig<T>): Promise<void>;
181
247
  /**
182
248
  * Consume from multiple queues with the same connection
249
+ * Note: Queues should be set up separately before consuming
250
+ *
251
+ * @example
252
+ * // Setup queues first
253
+ * await consumer.setupQueue({ queueName: 'orders', ... });
254
+ * await consumer.setupQueue({ queueName: 'payments', ... });
255
+ *
256
+ * // Then consume from all
257
+ * await consumer.consumeMultipleQueues({
258
+ * queues: [
259
+ * {
260
+ * queueName: 'orders',
261
+ * onMessage: async (msg) => { ... }
262
+ * },
263
+ * {
264
+ * queueName: 'payments',
265
+ * onMessage: async (msg) => { ... }
266
+ * }
267
+ * ]
268
+ * });
183
269
  */
184
270
  consumeMultipleQueues<T = any>(config: MultiQueueConsumeConfig<T>): Promise<void>;
185
271
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"consumerMq.d.ts","sourceRoot":"","sources":["../consumerMq.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"consumerMq.d.ts","sourceRoot":"","sources":["../consumerMq.ts"],"names":[],"mappings":"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"}
@@ -67,8 +67,10 @@ class RabbitMQConsumer extends events_1.EventEmitter {
67
67
  }
68
68
  else {
69
69
  this.rabbitUrl = options?.url || 'amqp://localhost';
70
- this.reconnectIntervalSeconds = options?.reconnectIntervalSeconds || types_1.RECONNECT_INTERVAL_SECONDS;
71
- this.connectionTimeoutMs = options?.connectionTimeoutMs || types_1.CONNECTION_TIMEOUT_MS;
70
+ this.reconnectIntervalSeconds =
71
+ options?.reconnectIntervalSeconds || types_1.RECONNECT_INTERVAL_SECONDS;
72
+ this.connectionTimeoutMs =
73
+ options?.connectionTimeoutMs || types_1.CONNECTION_TIMEOUT_MS;
72
74
  }
73
75
  }
74
76
  /**
@@ -197,8 +199,16 @@ class RabbitMQConsumer extends events_1.EventEmitter {
197
199
  // Auto-connect on first use (lazy loading)
198
200
  await this.ensureConnection();
199
201
  // Normalize to array
200
- const exchanges = Array.isArray(exchangeNames) ? exchangeNames : [exchangeNames];
202
+ const exchanges = Array.isArray(exchangeNames)
203
+ ? exchangeNames
204
+ : [exchangeNames];
201
205
  const { exchangeType = 'direct', durable = true } = options;
206
+ // Validate exchange names
207
+ for (const exchangeName of exchanges) {
208
+ if (!exchangeName || exchangeName.trim() === '') {
209
+ throw new Error('Exchange name cannot be empty');
210
+ }
211
+ }
202
212
  try {
203
213
  await this.channel.addSetup(async (ch) => {
204
214
  for (const exchangeName of exchanges) {
@@ -229,8 +239,11 @@ class RabbitMQConsumer extends events_1.EventEmitter {
229
239
  * await consumer.assertQueues(['orders-worker', 'payments-worker']);
230
240
  *
231
241
  * @example
232
- * // Assert queue with DLQ setup
242
+ * // Assert queue with exchange binding and retry config
233
243
  * await consumer.assertQueues('orders-worker', {
244
+ * exchangeName: 'orders',
245
+ * exchangeType: 'direct',
246
+ * routingKey: 'order.created',
234
247
  * retryConfig: { maxRetries: 3, retryDelayMs: 5000 }
235
248
  * });
236
249
  */
@@ -239,21 +252,51 @@ class RabbitMQConsumer extends events_1.EventEmitter {
239
252
  await this.ensureConnection();
240
253
  // Normalize to array
241
254
  const queues = Array.isArray(queueNames) ? queueNames : [queueNames];
242
- const { durable = true, exclusive = false, autoDelete = false, retryConfig, } = options;
255
+ const { durable = true, exclusive = false, autoDelete = false, exchangeName, exchangeType = 'direct', routingKey, retryConfig, } = options;
256
+ // Validate queue names
257
+ for (const queueName of queues) {
258
+ if (!queueName || queueName.trim() === '') {
259
+ throw new Error('Queue name cannot be empty');
260
+ }
261
+ }
262
+ // Validate exchange name if provided
263
+ if (exchangeName && exchangeName.trim() === '') {
264
+ throw new Error('Exchange name cannot be empty');
265
+ }
266
+ // Note: If exchangeName is provided without routingKey, queueName will be used as routingKey for each queue
243
267
  // Validate retry configuration if provided
244
268
  if (retryConfig) {
245
269
  (0, validation_1.validateRetryConfig)(retryConfig);
246
270
  }
247
271
  try {
272
+ const defaultExchange = '';
273
+ const dlxName = exchangeName
274
+ ? `${exchangeName}${types_1.DLX_SUFFIX}`
275
+ : `default${types_1.DLX_SUFFIX}`;
276
+ const effectiveExchange = exchangeName || defaultExchange;
277
+ const effectiveRoutingKey = routingKey || (queues.length === 1 ? queues[0] : '');
278
+ // Assert all queues
248
279
  await this.channel.addSetup(async (ch) => {
280
+ // Assert exchange if provided
281
+ if (exchangeName) {
282
+ await ch.assertExchange(exchangeName, exchangeType, { durable });
283
+ console.log(`✓ Exchange asserted: ${exchangeName} (${exchangeType})`);
284
+ }
249
285
  for (const queueName of queues) {
250
286
  const queueArgs = {};
287
+ const effectiveKey = routingKey || queueName;
251
288
  // Setup DLQ arguments if retry config is provided
252
289
  if (retryConfig && retryConfig.maxRetries > 0) {
253
- // Messages that fail will be sent to retry queue
254
- queueArgs['x-dead-letter-exchange'] = '';
255
- queueArgs['x-dead-letter-routing-key'] = `${queueName}.retry`;
290
+ // With retry: failed messages go to retry queue first
291
+ queueArgs['x-dead-letter-exchange'] = effectiveExchange;
292
+ queueArgs['x-dead-letter-routing-key'] = `${effectiveKey}${types_1.RETRY_QUEUE_SUFFIX}`;
293
+ }
294
+ else if (retryConfig) {
295
+ // No retry (maxRetries = 0): go directly to DLQ
296
+ queueArgs['x-dead-letter-exchange'] = dlxName;
297
+ queueArgs['x-dead-letter-routing-key'] = `${effectiveKey}${types_1.DLQ_SUFFIX}`;
256
298
  }
299
+ // Assert queue (idempotent - RabbitMQ won't recreate if exists with same params)
257
300
  await ch.assertQueue(queueName, {
258
301
  durable,
259
302
  exclusive,
@@ -261,12 +304,21 @@ class RabbitMQConsumer extends events_1.EventEmitter {
261
304
  arguments: Object.keys(queueArgs).length > 0 ? queueArgs : undefined,
262
305
  });
263
306
  console.log(`✓ Queue asserted: ${queueName}`);
264
- // Setup retry and DLQ queues if retry config is provided
265
- if (retryConfig) {
266
- await this.setupRetryAndDLQQueues(ch, queueName, retryConfig);
307
+ // Bind queue to exchange if exchangeName is provided (idempotent operation)
308
+ if (exchangeName) {
309
+ await ch.bindQueue(queueName, exchangeName, effectiveKey);
310
+ console.log(`✓ Queue bound to exchange: ${queueName} -> ${exchangeName} (${effectiveKey})`);
267
311
  }
268
312
  }
269
313
  });
314
+ // Setup DLQ infrastructure for all queues if retry config is provided
315
+ if (retryConfig) {
316
+ for (const queueName of queues) {
317
+ // Use provided exchange/routingKey or defaults
318
+ const effectiveKey = routingKey || queueName;
319
+ await this.setupDLQInfrastructure(queueName, effectiveExchange, effectiveKey, retryConfig);
320
+ }
321
+ }
270
322
  console.log(`✓ Successfully asserted ${queues.length} queue(s)`);
271
323
  }
272
324
  catch (error) {
@@ -275,30 +327,135 @@ class RabbitMQConsumer extends events_1.EventEmitter {
275
327
  }
276
328
  }
277
329
  /**
278
- * Setup retry queue and DLQ for a main queue
330
+ * Delete queues (Best Practice for cleanup/testing)
331
+ * Can be called with a single queue name or multiple queue names
332
+ * Also deletes associated retry and DLQ queues if they exist
333
+ *
334
+ * @param queueNames - Single queue name (string) or array of queue names
335
+ * @param options - Delete options (includeRetry, includeDLQ)
336
+ *
337
+ * @example
338
+ * // Delete a single queue
339
+ * await consumer.deleteQueues('orders');
340
+ *
341
+ * @example
342
+ * // Delete multiple queues
343
+ * await consumer.deleteQueues(['orders', 'payments', 'notifications']);
344
+ *
345
+ * @example
346
+ * // Delete queue without retry/DLQ queues
347
+ * await consumer.deleteQueues('orders', {
348
+ * includeRetry: false,
349
+ * includeDLQ: false
350
+ * });
279
351
  */
280
- async setupRetryAndDLQQueues(ch, queueName, retryConfig) {
281
- const retryQueueName = `${queueName}.retry`;
282
- const dlqName = `${queueName}.dlq`;
283
- // Create retry queue (messages come back to main queue after TTL)
284
- if (retryConfig.maxRetries > 0) {
285
- await ch.assertQueue(retryQueueName, {
352
+ async deleteQueues(queueNames, options = {}) {
353
+ // Auto-connect on first use (lazy loading)
354
+ await this.ensureConnection();
355
+ // Normalize to array
356
+ const queues = Array.isArray(queueNames) ? queueNames : [queueNames];
357
+ const { includeRetry = true, includeDLQ = true } = options;
358
+ try {
359
+ let deletedCount = 0;
360
+ // Delete all queues
361
+ await this.channel.addSetup(async (ch) => {
362
+ for (const queueName of queues) {
363
+ // Delete main queue
364
+ try {
365
+ await ch.deleteQueue(queueName, { ifEmpty: false });
366
+ console.log(`✓ Deleted queue: ${queueName}`);
367
+ deletedCount++;
368
+ }
369
+ catch (error) {
370
+ // Queue doesn't exist or already deleted
371
+ if (error.code !== 404) {
372
+ console.warn(`Warning: Could not delete queue ${queueName}:`, error.message);
373
+ }
374
+ }
375
+ // Delete retry queue if requested
376
+ if (includeRetry) {
377
+ try {
378
+ await ch.deleteQueue(`${queueName}${types_1.RETRY_QUEUE_SUFFIX}`, {
379
+ ifEmpty: false,
380
+ });
381
+ console.log(`✓ Deleted retry queue: ${queueName}${types_1.RETRY_QUEUE_SUFFIX}`);
382
+ deletedCount++;
383
+ }
384
+ catch (error) {
385
+ // Retry queue doesn't exist, skip
386
+ if (error.code !== 404) {
387
+ console.warn(`Warning: Could not delete retry queue ${queueName}${types_1.RETRY_QUEUE_SUFFIX}:`, error.message);
388
+ }
389
+ }
390
+ }
391
+ // Delete DLQ if requested
392
+ if (includeDLQ) {
393
+ try {
394
+ await ch.deleteQueue(`${queueName}${types_1.DLQ_SUFFIX}`, {
395
+ ifEmpty: false,
396
+ });
397
+ console.log(`✓ Deleted DLQ: ${queueName}${types_1.DLQ_SUFFIX}`);
398
+ deletedCount++;
399
+ }
400
+ catch (error) {
401
+ // DLQ doesn't exist, skip
402
+ if (error.code !== 404) {
403
+ console.warn(`Warning: Could not delete DLQ ${queueName}${types_1.DLQ_SUFFIX}:`, error.message);
404
+ }
405
+ }
406
+ }
407
+ }
408
+ });
409
+ console.log(`✓ Successfully deleted ${deletedCount} queue(s)`);
410
+ }
411
+ catch (error) {
412
+ console.error('Failed to delete queues:', error);
413
+ throw error;
414
+ }
415
+ }
416
+ /**
417
+ * Setup Dead Letter Queue (DLQ) infrastructure for a queue with retry mechanism
418
+ */
419
+ async setupDLQInfrastructure(queueName, exchangeName, routingKey, retryConfig) {
420
+ await this.channel.addSetup(async (ch) => {
421
+ const dlxName = `${exchangeName}${types_1.DLX_SUFFIX}`;
422
+ const dlqName = `${queueName}${types_1.DLQ_SUFFIX}`;
423
+ const retryQueueName = `${queueName}${types_1.RETRY_QUEUE_SUFFIX}`;
424
+ // 1. Create Dead Letter Exchange (DLX) - only if exchangeName is not empty
425
+ if (exchangeName) {
426
+ await ch.assertExchange(dlxName, 'direct', { durable: true });
427
+ }
428
+ // 2. Create Dead Letter Queue (DLQ) - Final resting place for failed messages
429
+ // assertQueue is idempotent - won't recreate if exists with same params
430
+ await ch.assertQueue(dlqName, {
286
431
  durable: true,
287
432
  arguments: {
288
- 'x-dead-letter-exchange': '',
289
- 'x-dead-letter-routing-key': queueName,
433
+ 'x-queue-mode': 'lazy', // Better for long-term storage, saves memory
290
434
  },
291
435
  });
292
- console.log(`✓ Retry queue asserted: ${retryQueueName}`);
293
- }
294
- // Create DLQ (final resting place for failed messages)
295
- await ch.assertQueue(dlqName, {
296
- durable: true,
297
- arguments: {
298
- 'x-queue-mode': 'lazy',
299
- },
436
+ // Bind DLQ to DLX if exchange is used
437
+ if (exchangeName) {
438
+ await ch.bindQueue(dlqName, dlxName, `${routingKey}${types_1.DLQ_SUFFIX}`);
439
+ }
440
+ console.log(`✓ DLQ asserted: ${dlqName}`);
441
+ // 3. Create Retry Queue (if retry is enabled)
442
+ if (retryConfig && retryConfig.maxRetries > 0) {
443
+ // assertQueue is idempotent - won't recreate if exists with same params
444
+ await ch.assertQueue(retryQueueName, {
445
+ durable: true,
446
+ arguments: {
447
+ // NO fixed TTL - we use per-message expiration for exponential backoff
448
+ 'x-dead-letter-exchange': exchangeName || '', // Send back to main exchange after TTL
449
+ 'x-dead-letter-routing-key': routingKey, // Use original routing key
450
+ },
451
+ });
452
+ console.log(`✓ Retry queue asserted: ${retryQueueName}`);
453
+ console.log(`DLQ infrastructure setup complete for queue: ${queueName} (max retries: ${retryConfig.maxRetries}, exponential backoff enabled)`);
454
+ }
455
+ else {
456
+ console.log(`DLQ infrastructure setup complete for queue: ${queueName} (no retries)`);
457
+ }
300
458
  });
301
- console.log(`✓ DLQ asserted: ${dlqName}`);
302
459
  }
303
460
  /**
304
461
  * Bind a queue to an exchange with a routing key (Best Practice for Consumers)
@@ -327,6 +484,16 @@ class RabbitMQConsumer extends events_1.EventEmitter {
327
484
  // Auto-connect on first use (lazy loading)
328
485
  await this.ensureConnection();
329
486
  const { exchangeType, durable = true } = options;
487
+ // Validate inputs
488
+ if (!queueName || queueName.trim() === '') {
489
+ throw new Error('Queue name cannot be empty');
490
+ }
491
+ if (!exchangeName || exchangeName.trim() === '') {
492
+ throw new Error('Exchange name cannot be empty');
493
+ }
494
+ if (!routingKey || routingKey.trim() === '') {
495
+ throw new Error('Routing key cannot be empty');
496
+ }
330
497
  try {
331
498
  await this.channel.addSetup(async (ch) => {
332
499
  // If exchangeType is provided, assert the exchange first (idempotent)
@@ -390,7 +557,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
390
557
  const queueArgs = {};
391
558
  if (retryConfig && retryConfig.maxRetries > 0) {
392
559
  queueArgs['x-dead-letter-exchange'] = '';
393
- queueArgs['x-dead-letter-routing-key'] = `${queueName}.retry`;
560
+ queueArgs['x-dead-letter-routing-key'] = `${queueName}${types_1.RETRY_QUEUE_SUFFIX}`;
394
561
  }
395
562
  // 3. Assert queue
396
563
  await ch.assertQueue(queueName, {
@@ -402,8 +569,8 @@ class RabbitMQConsumer extends events_1.EventEmitter {
402
569
  console.log(`✓ Queue asserted: ${queueName}`);
403
570
  // 4. Setup retry and DLQ queues if retry config is provided
404
571
  if (retryConfig) {
405
- const retryQueueName = `${queueName}.retry`;
406
- const dlqName = `${queueName}.dlq`;
572
+ const retryQueueName = `${queueName}${types_1.RETRY_QUEUE_SUFFIX}`;
573
+ const dlqName = `${queueName}${types_1.DLQ_SUFFIX}`;
407
574
  if (retryConfig.maxRetries > 0) {
408
575
  await ch.assertQueue(retryQueueName, {
409
576
  durable: true,
@@ -504,16 +671,50 @@ class RabbitMQConsumer extends events_1.EventEmitter {
504
671
  }
505
672
  /**
506
673
  * Consume messages from a queue with automatic retry and DLQ handling
674
+ *
675
+ * Note: Queue should be set up separately before consuming:
676
+ * - Use assertQueues() to create queue with exchange binding
677
+ * - Use setupQueue() for complete setup with multiple routing keys
678
+ * - Use bindQueue() to add additional routing key bindings
679
+ *
680
+ * @example
681
+ * // Step 1: Setup queue with exchange and routing keys
682
+ * await consumer.setupQueue({
683
+ * queueName: 'orders',
684
+ * exchangeName: 'orders',
685
+ * exchangeType: 'topic',
686
+ * routingKeys: ['order.created', 'order.updated'],
687
+ * retryConfig: { maxRetries: 5 }
688
+ * });
689
+ *
690
+ * // Step 2: Consume (queue already set up)
691
+ * await consumer.consumeQueue({
692
+ * queueName: 'orders',
693
+ * onMessage: async (message) => {
694
+ * await processOrder(message);
695
+ * }
696
+ * });
697
+ *
698
+ * @example
699
+ * // Direct queue consumption (no exchange)
700
+ * await consumer.assertQueues('simple-queue');
701
+ * await consumer.consumeQueue({
702
+ * queueName: 'simple-queue',
703
+ * onMessage: async (message) => { ... }
704
+ * });
507
705
  */
508
706
  async consumeQueue(config) {
509
707
  // Auto-connect on first use (lazy loading)
510
708
  await this.ensureConnection();
511
709
  try {
512
- console.log('Consuming queue:', config);
513
- const { queueName, exchangeName, exchangeType = 'direct', routingKey, onMessage, options = {} } = config;
514
- const { durable = true, prefetch = types_1.DEFAULT_PREFETCH_COUNT, noAck = false, exclusive = false, autoDelete = false } = options;
710
+ const { queueName, onMessage, options = {} } = config;
711
+ const { durable = true, prefetch = types_1.DEFAULT_PREFETCH_COUNT, noAck = false, exclusive = false, autoDelete = false, } = options;
712
+ // Validate inputs
713
+ if (!queueName || queueName.trim() === '') {
714
+ throw new Error('Queue name cannot be empty');
715
+ }
515
716
  await this.channel.addSetup(async (ch) => {
516
- // Set prefetch count
717
+ // Set prefetch count (critical for flow control)
517
718
  await ch.prefetch(prefetch);
518
719
  // Check if queue exists (passive check - doesn't modify queue properties)
519
720
  try {
@@ -521,16 +722,9 @@ class RabbitMQConsumer extends events_1.EventEmitter {
521
722
  }
522
723
  catch (error) {
523
724
  // Queue doesn't exist, create it without DLQ config (for simple cases)
524
- // If using with retry/DLQ, publisher should create the queue first
725
+ // For production, use assertQueues() or setupQueue() first
525
726
  await ch.assertQueue(queueName, { durable, exclusive, autoDelete });
526
- }
527
- // If exchange is specified, assert exchange and bind queue
528
- if (exchangeName && routingKey) {
529
- await ch.assertExchange(exchangeName, exchangeType, { durable });
530
- // Bind queue to exchange (idempotent operation)
531
- await ch.bindQueue(queueName, exchangeName, routingKey);
532
- // Also bind the retry routing key
533
- await ch.bindQueue(queueName, exchangeName, `${routingKey}.retry`);
727
+ console.warn(`Queue ${queueName} was auto-created. Consider using assertQueues() or setupQueue() for proper setup.`);
534
728
  }
535
729
  // Start consuming
536
730
  await ch.consume(queueName, async (msg) => {
@@ -547,10 +741,12 @@ class RabbitMQConsumer extends events_1.EventEmitter {
547
741
  fields: msg.fields,
548
742
  properties: msg.properties,
549
743
  content: msg.content,
550
- originalMessage: msg
744
+ originalMessage: msg,
551
745
  };
552
746
  let timestamp = new Date().toISOString();
553
- const retryInfo = maxRetries > 0 ? ` (attempt ${currentRetryCount + 1}/${maxRetries + 1})` : '';
747
+ const retryInfo = maxRetries > 0
748
+ ? ` (attempt ${currentRetryCount + 1}/${maxRetries + 1})`
749
+ : '';
554
750
  console.log(`[${timestamp}] Processing from ${queueName}${retryInfo}:`, content);
555
751
  // Try to process the message
556
752
  await onMessage(content, messageInfo);
@@ -583,7 +779,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
583
779
  const timestamp = new Date().toISOString();
584
780
  console.log(`[${timestamp}] 🔄 Retry ${nextRetryCount}/${maxRetries} in ${actualDelay}ms`);
585
781
  // Publish to retry queue with updated retry count
586
- const retryQueueName = `${queueName}.retry`;
782
+ const retryQueueName = `${queueName}${types_1.RETRY_QUEUE_SUFFIX}`;
587
783
  try {
588
784
  await ch.sendToQueue(retryQueueName, msg.content, {
589
785
  ...msg.properties,
@@ -593,7 +789,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
593
789
  'x-retry-count': nextRetryCount,
594
790
  'x-retry-timestamp': new Date().toISOString(),
595
791
  'x-base-retry-delay': baseRetryDelay, // Store the BASE delay (don't compound!)
596
- }
792
+ },
597
793
  });
598
794
  // Acknowledge original message after successful retry queue publish
599
795
  ch.ack(msg);
@@ -624,7 +820,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
624
820
  const timestamp = new Date().toISOString();
625
821
  console.log(`[${timestamp}] 💀 Max retries exceeded, moving to DLQ`);
626
822
  // Manually send to DLQ (can't rely on nack because main queue's DLX routes to retry)
627
- const dlqName = `${queueName}.dlq`;
823
+ const dlqName = `${queueName}${types_1.DLQ_SUFFIX}`;
628
824
  // Extract error code for categorization
629
825
  const errorCode = this.extractErrorCode(error);
630
826
  try {
@@ -636,8 +832,10 @@ class RabbitMQConsumer extends events_1.EventEmitter {
636
832
  'x-death-timestamp': new Date().toISOString(),
637
833
  'x-last-error': errorMessage,
638
834
  'x-last-error-code': errorCode,
639
- 'x-is-retryable': this.isRetryableError(errorCode) ? 'true' : 'false',
640
- }
835
+ 'x-is-retryable': this.isRetryableError(errorCode)
836
+ ? 'true'
837
+ : 'false',
838
+ },
641
839
  });
642
840
  // Acknowledge original message after successful DLQ send
643
841
  ch.ack(msg);
@@ -681,17 +879,34 @@ class RabbitMQConsumer extends events_1.EventEmitter {
681
879
  }
682
880
  /**
683
881
  * Consume from multiple queues with the same connection
882
+ * Note: Queues should be set up separately before consuming
883
+ *
884
+ * @example
885
+ * // Setup queues first
886
+ * await consumer.setupQueue({ queueName: 'orders', ... });
887
+ * await consumer.setupQueue({ queueName: 'payments', ... });
888
+ *
889
+ * // Then consume from all
890
+ * await consumer.consumeMultipleQueues({
891
+ * queues: [
892
+ * {
893
+ * queueName: 'orders',
894
+ * onMessage: async (msg) => { ... }
895
+ * },
896
+ * {
897
+ * queueName: 'payments',
898
+ * onMessage: async (msg) => { ... }
899
+ * }
900
+ * ]
901
+ * });
684
902
  */
685
903
  async consumeMultipleQueues(config) {
686
904
  const { queues, options = {} } = config;
687
905
  for (const queueConfig of queues) {
688
906
  await this.consumeQueue({
689
907
  queueName: queueConfig.queueName,
690
- exchangeName: queueConfig.exchangeName,
691
- exchangeType: queueConfig.exchangeType,
692
- routingKey: queueConfig.routingKey,
693
908
  onMessage: queueConfig.onMessage,
694
- options
909
+ options,
695
910
  });
696
911
  }
697
912
  }
@@ -723,7 +938,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
723
938
  fields: msg.fields,
724
939
  properties: msg.properties,
725
940
  content: msg.content,
726
- originalMessage: msg
941
+ originalMessage: msg,
727
942
  };
728
943
  await onMessage(content, routingKey, messageInfo);
729
944
  ch.ack(msg);
@@ -751,7 +966,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
751
966
  getConsumerStats() {
752
967
  return {
753
968
  rabbitUrl: this.rabbitUrl,
754
- activeQueues: [...this.activeQueues]
969
+ activeQueues: [...this.activeQueues],
755
970
  };
756
971
  }
757
972
  /**
@@ -763,7 +978,7 @@ class RabbitMQConsumer extends events_1.EventEmitter {
763
978
  if (this.channel) {
764
979
  // Give time for in-flight messages to complete processing
765
980
  // In production, you might want to implement a more sophisticated drain mechanism
766
- await new Promise(resolve => setTimeout(resolve, 1000));
981
+ await new Promise((resolve) => setTimeout(resolve, 1000));
767
982
  await this.channel.close();
768
983
  this.channel = undefined;
769
984
  }