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/dist/index.cjs CHANGED
@@ -157,9 +157,9 @@ var RabbitMQClientChannel = class {
157
157
  arguments: args
158
158
  });
159
159
  }
160
- publish(exchange, routingKey, content, options) {
160
+ async publish(exchange, routingKey, content, options) {
161
161
  var _a2;
162
- this.channel.basicPublish({
162
+ await this.channel.basicPublish({
163
163
  exchange,
164
164
  routingKey,
165
165
  correlationId: options == null ? void 0 : options.correlationId,
@@ -176,7 +176,9 @@ var RabbitMQClientChannel = class {
176
176
  userId: options == null ? void 0 : options.userId,
177
177
  appId: options == null ? void 0 : options.appId
178
178
  }, content);
179
- return true;
179
+ }
180
+ async confirmSelect() {
181
+ await this.channel.confirmSelect();
180
182
  }
181
183
  async consume(queue, onMessage, options) {
182
184
  const result = await this.channel.basicConsume({
@@ -279,12 +281,14 @@ var RabbitMQClientAdapter = class {
279
281
  this.logger = logger;
280
282
  this.isConnected = false;
281
283
  this.acquiredChannels = [];
284
+ this.isShuttingDown = false;
282
285
  }
283
286
  async connect() {
284
287
  try {
285
288
  if (this.connection && this.isConnected) {
286
289
  return this.connection;
287
290
  }
291
+ this.isShuttingDown = false;
288
292
  if (this.connection) {
289
293
  try {
290
294
  await this.connection.close();
@@ -297,7 +301,8 @@ var RabbitMQClientAdapter = class {
297
301
  // Disable automatic retries - we handle retries at RunMQ level
298
302
  retryLow: 100,
299
303
  retryHigh: 200,
300
- connectionTimeout: 5e3
304
+ connectionTimeout: 5e3,
305
+ noDelay: true
301
306
  });
302
307
  this.connection.on("error", (err) => {
303
308
  this.logger.error("RabbitMQ connection error:", { error: err });
@@ -332,21 +337,52 @@ var RabbitMQClientAdapter = class {
332
337
  );
333
338
  }
334
339
  }
335
- async getChannel() {
340
+ async getChannel(callbacks) {
336
341
  const connection = await this.connect();
337
342
  const rawChannel = await connection.acquire();
343
+ const channelId = rawChannel.id;
344
+ rawChannel.on("error", (err) => {
345
+ var _a2;
346
+ this.logger.error("RabbitMQ channel error:", { channelId, error: err });
347
+ try {
348
+ (_a2 = callbacks == null ? void 0 : callbacks.onError) == null ? void 0 : _a2.call(callbacks, err);
349
+ } catch (cbErr) {
350
+ this.logger.error("RabbitMQ channel onError callback threw:", { channelId, error: cbErr });
351
+ }
352
+ });
353
+ rawChannel.on("close", () => {
354
+ var _a2;
355
+ this.logger.warn("RabbitMQ channel closed", { channelId });
356
+ const idx = this.acquiredChannels.indexOf(rawChannel);
357
+ if (idx >= 0) {
358
+ this.acquiredChannels.splice(idx, 1);
359
+ }
360
+ if (this.isShuttingDown) {
361
+ return;
362
+ }
363
+ try {
364
+ (_a2 = callbacks == null ? void 0 : callbacks.onClose) == null ? void 0 : _a2.call(callbacks);
365
+ } catch (cbErr) {
366
+ this.logger.error("RabbitMQ channel onClose callback threw:", { channelId, error: cbErr });
367
+ }
368
+ });
338
369
  this.acquiredChannels.push(rawChannel);
339
370
  return new RabbitMQClientChannel(rawChannel);
340
371
  }
341
372
  async getDefaultChannel() {
342
373
  if (!this.defaultChannel) {
343
- this.defaultChannel = await this.getChannel();
374
+ this.defaultChannel = await this.getChannel({
375
+ onClose: () => {
376
+ this.defaultChannel = void 0;
377
+ }
378
+ });
344
379
  }
345
380
  return this.defaultChannel;
346
381
  }
347
382
  async disconnect() {
348
383
  const conn = this.connection;
349
384
  const channels = this.acquiredChannels;
385
+ this.isShuttingDown = true;
350
386
  this.connection = void 0;
351
387
  this.defaultChannel = void 0;
352
388
  this.isConnected = false;
@@ -419,20 +455,33 @@ var RabbitMQMessage = class _RabbitMQMessage {
419
455
  this.headers = headers;
420
456
  }
421
457
  /**
422
- * Acknowledges the message.
458
+ * Acknowledges the message. Returns true on success, false if the
459
+ * underlying channel rejected the call (e.g. closed mid-flight).
460
+ * Plumbing errors are intentionally swallowed: the broker will redeliver
461
+ * unacked messages on channel close, so escalating here only crashes
462
+ * the consumer for no recovery benefit.
423
463
  */
424
464
  ack() {
425
- if (this.amqpMessage) {
465
+ if (!this.amqpMessage) return false;
466
+ try {
426
467
  this.channel.ack(this.amqpMessage);
468
+ return true;
469
+ } catch (e) {
470
+ return false;
427
471
  }
428
472
  }
429
473
  /**
430
- * Negatively acknowledges the message.
474
+ * Negatively acknowledges the message. Returns true on success, false if
475
+ * the underlying channel rejected the call. See `ack()` for rationale.
431
476
  * @param requeue - Whether to requeue the message (default: false)
432
477
  */
433
478
  nack(requeue = false) {
434
- if (this.amqpMessage) {
479
+ if (!this.amqpMessage) return false;
480
+ try {
435
481
  this.channel.nack(this.amqpMessage, false, requeue);
482
+ return true;
483
+ } catch (e) {
484
+ return false;
436
485
  }
437
486
  }
438
487
  static from(messageData, channel, props, amqpMessage = null) {
@@ -449,13 +498,20 @@ var RabbitMQMessage = class _RabbitMQMessage {
449
498
 
450
499
  // src/core/consumer/processors/RunMQSucceededMessageAcknowledgerProcessor.ts
451
500
  var RunMQSucceededMessageAcknowledgerProcessor = class {
452
- constructor(consumer) {
501
+ constructor(consumer, logger) {
453
502
  this.consumer = consumer;
503
+ this.logger = logger;
454
504
  }
455
505
  async consume(message) {
506
+ var _a2;
456
507
  const result = await this.consumer.consume(message);
457
508
  if (result) {
458
- message.ack();
509
+ const acked = message.ack();
510
+ if (!acked) {
511
+ (_a2 = this.logger) == null ? void 0 : _a2.warn("Failed to ack message \u2014 channel likely closed. Broker will redeliver.", {
512
+ correlationId: message.correlationId
513
+ });
514
+ }
459
515
  }
460
516
  return result;
461
517
  }
@@ -463,40 +519,26 @@ var RunMQSucceededMessageAcknowledgerProcessor = class {
463
519
 
464
520
  // src/core/consumer/processors/RunMQFailedMessageRejecterProcessor.ts
465
521
  var RunMQFailedMessageRejecterProcessor = class {
466
- constructor(consumer) {
522
+ constructor(consumer, logger) {
467
523
  this.consumer = consumer;
524
+ this.logger = logger;
468
525
  }
469
526
  async consume(message) {
527
+ var _a2;
470
528
  try {
471
529
  return await this.consumer.consume(message);
472
530
  } catch (e) {
473
- message.nack(false);
531
+ const nacked = message.nack(false);
532
+ if (!nacked) {
533
+ (_a2 = this.logger) == null ? void 0 : _a2.warn("Failed to nack message \u2014 channel likely closed. Broker will redeliver.", {
534
+ correlationId: message.correlationId
535
+ });
536
+ }
474
537
  return false;
475
538
  }
476
539
  }
477
540
  };
478
541
 
479
- // src/core/message/RunMQMessage.ts
480
- var RunMQMessage = class {
481
- static isValid(obj) {
482
- if (typeof obj === "object" && obj !== null) {
483
- return "message" in obj && "meta" in obj && typeof obj.message === "object" && obj.message !== null && Array.isArray(obj.message) === false && typeof obj.meta === "object" && obj.meta !== null && "id" in obj.meta && "correlationId" in obj.meta && "publishedAt" in obj.meta && typeof obj.meta.id === "string" && typeof obj.meta.correlationId === "string" && typeof obj.meta.publishedAt === "number";
484
- }
485
- return false;
486
- }
487
- constructor(message, meta) {
488
- this.message = message;
489
- this.meta = meta;
490
- }
491
- };
492
- var RunMQMessageMeta = class {
493
- constructor(id, publishedAt, correlationId) {
494
- this.id = id;
495
- this.correlationId = correlationId;
496
- this.publishedAt = publishedAt;
497
- }
498
- };
499
-
500
542
  // src/core/consumer/ConsumerCreatorUtils.ts
501
543
  var ConsumerCreatorUtils = class {
502
544
  static getDLQTopicName(topic) {
@@ -513,11 +555,11 @@ var ConsumerCreatorUtils = class {
513
555
  // src/core/consumer/processors/RunMQRetriesCheckerProcessor.ts
514
556
  var _a;
515
557
  var RunMQRetriesCheckerProcessor = class {
516
- constructor(consumer, config, DLQPublisher, logger) {
558
+ constructor(consumer, config, logger, logFullMessagePayload = false) {
517
559
  this.consumer = consumer;
518
560
  this.config = config;
519
- this.DLQPublisher = DLQPublisher;
520
561
  this.logger = logger;
562
+ this.logFullMessagePayload = logFullMessagePayload;
521
563
  this.maxAttempts = (_a = this.config.attempts) != null ? _a : DEFAULTS.PROCESSING_ATTEMPTS;
522
564
  }
523
565
  async consume(message) {
@@ -526,7 +568,16 @@ var RunMQRetriesCheckerProcessor = class {
526
568
  } catch (e) {
527
569
  if (this.hasReachedMaxRetries(message)) {
528
570
  this.logMaxRetriesReached(message);
529
- this.moveToFinalDeadLetter(message);
571
+ try {
572
+ await this.moveToFinalDeadLetter(message);
573
+ } catch (publishError) {
574
+ this.logger.error("Failed to publish to DLQ \u2014 message will be redelivered", {
575
+ correlationId: message.correlationId,
576
+ cause: publishError instanceof Error ? publishError.message : String(publishError)
577
+ });
578
+ message.nack(false);
579
+ return false;
580
+ }
530
581
  this.acknowledgeMessage(message);
531
582
  return false;
532
583
  }
@@ -540,44 +591,38 @@ var RunMQRetriesCheckerProcessor = class {
540
591
  logMaxRetriesReached(message) {
541
592
  this.logger.error(
542
593
  `Message reached maximum attempts. Moving to dead-letter queue.`,
543
- {
544
- message: message.message,
594
+ __spreadValues({
595
+ correlationId: message.correlationId,
596
+ messageId: message.id,
545
597
  attempts: this.getRejectionCount(message),
546
598
  max: this.maxAttempts
547
- }
599
+ }, this.logFullMessagePayload ? { message: message.message } : {})
548
600
  );
549
601
  }
550
- moveToFinalDeadLetter(message) {
551
- const originalPayload = this.extractOriginalPayload(message);
552
- const dlqMessage = new RabbitMQMessage(
553
- originalPayload,
554
- message.id,
555
- message.correlationId,
556
- message.channel,
557
- message.amqpMessage,
558
- message.headers
559
- );
560
- this.DLQPublisher.publish(ConsumerCreatorUtils.getDLQTopicName(this.config.name), dlqMessage);
561
- }
562
- extractOriginalPayload(message) {
563
- if (typeof message.message === "string") {
564
- try {
565
- const parsed = JSON.parse(message.message);
566
- if (RunMQMessage.isValid(parsed)) {
567
- return parsed.message;
568
- }
569
- } catch (e) {
602
+ // Republish the original AMQP body verbatim so the envelope (including
603
+ // publishedAt) is preserved end-to-end for audit/replay. The consumer
604
+ // channel runs in confirm mode (see RunMQConsumerCreator), so awaiting
605
+ // this publish surfaces broker-side rejections instead of dropping them.
606
+ async moveToFinalDeadLetter(message) {
607
+ if (!message.amqpMessage) return;
608
+ await message.channel.publish(
609
+ Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
610
+ ConsumerCreatorUtils.getDLQTopicName(this.config.name),
611
+ message.amqpMessage.content,
612
+ {
613
+ correlationId: message.correlationId,
614
+ messageId: message.id,
615
+ headers: message.headers,
616
+ persistent: true
570
617
  }
571
- }
572
- return message.message;
618
+ );
573
619
  }
574
620
  acknowledgeMessage(message) {
575
- try {
576
- message.ack();
577
- } catch (e) {
578
- const error = new Error("A message acknowledge failed after publishing to final dead letter");
579
- this.logger.error(error.message, { cause: e instanceof Error ? e.message : String(e) });
580
- throw error;
621
+ const acked = message.ack();
622
+ if (!acked) {
623
+ this.logger.warn("Failed to ack message after publishing to final dead letter \u2014 channel likely closed. Broker will redeliver.", {
624
+ correlationId: message.correlationId
625
+ });
581
626
  }
582
627
  }
583
628
  getRejectionCount(message) {
@@ -591,9 +636,10 @@ var RunMQRetriesCheckerProcessor = class {
591
636
 
592
637
  // src/core/consumer/processors/RunMQFailureLoggerProcessor.ts
593
638
  var RunMQFailureLoggerProcessor = class {
594
- constructor(consumer, logger) {
639
+ constructor(consumer, logger, logFullMessagePayload = false) {
595
640
  this.consumer = consumer;
596
641
  this.logger = logger;
642
+ this.logFullMessagePayload = logFullMessagePayload;
597
643
  }
598
644
  async consume(message) {
599
645
  try {
@@ -601,9 +647,10 @@ var RunMQFailureLoggerProcessor = class {
601
647
  } catch (e) {
602
648
  this.logger.error(
603
649
  "Message processing failed",
604
- {
605
- message: message.message
606
- },
650
+ __spreadValues({
651
+ correlationId: message.correlationId,
652
+ messageId: message.id
653
+ }, this.logFullMessagePayload ? { message: message.message } : {}),
607
654
  e instanceof Error ? e.stack : void 0
608
655
  );
609
656
  throw e;
@@ -611,39 +658,24 @@ var RunMQFailureLoggerProcessor = class {
611
658
  }
612
659
  };
613
660
 
614
- // src/core/consumer/processors/RunMQBaseProcessor.ts
615
- var RunMQBaseProcessor = class {
616
- constructor(handler, processorConfig, serializer) {
617
- this.handler = handler;
618
- this.processorConfig = processorConfig;
619
- this.serializer = serializer;
661
+ // src/core/message/RunMQMessage.ts
662
+ var RunMQMessage = class {
663
+ static isValid(obj) {
664
+ if (typeof obj === "object" && obj !== null) {
665
+ return "message" in obj && "meta" in obj && typeof obj.message === "object" && obj.message !== null && Array.isArray(obj.message) === false && typeof obj.meta === "object" && obj.meta !== null && "id" in obj.meta && "correlationId" in obj.meta && "publishedAt" in obj.meta && typeof obj.meta.id === "string" && typeof obj.meta.correlationId === "string" && typeof obj.meta.publishedAt === "number";
666
+ }
667
+ return false;
620
668
  }
621
- async consume(message) {
622
- const rabbitMQMessage = this.serializer.deserialize(message.message, this.processorConfig);
623
- await this.handler(rabbitMQMessage);
624
- return true;
669
+ constructor(message, meta) {
670
+ this.message = message;
671
+ this.meta = meta;
625
672
  }
626
673
  };
627
-
628
- // src/core/consumer/processors/RunMQExceptionLoggerProcessor.ts
629
- var RunMQExceptionLoggerProcessor = class {
630
- constructor(consumer, logger) {
631
- this.consumer = consumer;
632
- this.logger = logger;
633
- }
634
- async consume(message) {
635
- try {
636
- return await this.consumer.consume(message);
637
- } catch (e) {
638
- if (e instanceof Error) {
639
- this.logger.error(e.message, e.stack);
640
- throw e;
641
- } else {
642
- const errorString = JSON.stringify(e);
643
- this.logger.error(errorString);
644
- throw new Error(errorString);
645
- }
646
- }
674
+ var RunMQMessageMeta = class {
675
+ constructor(id, publishedAt, correlationId) {
676
+ this.id = id;
677
+ this.correlationId = correlationId;
678
+ this.publishedAt = publishedAt;
647
679
  }
648
680
  };
649
681
 
@@ -652,6 +684,17 @@ var import_ajv = __toESM(require("ajv"), 1);
652
684
  var AjvSchemaValidator = class {
653
685
  constructor() {
654
686
  this.lastValidator = null;
687
+ /**
688
+ * Cache of compiled validators, keyed by schema identity.
689
+ *
690
+ * `ajv.compile()` codegens an optimized JS function from the schema
691
+ * (typically 2-10ms for non-trivial schemas). Without this cache the
692
+ * compile would run on every message — at high throughput it dominates
693
+ * CPU usage.
694
+ *
695
+ * WeakMap so we don't pin schemas in memory if a processor is removed.
696
+ */
697
+ this.compiled = /* @__PURE__ */ new WeakMap();
655
698
  this.ajv = new import_ajv.default({
656
699
  allErrors: true,
657
700
  verbose: true,
@@ -659,8 +702,14 @@ var AjvSchemaValidator = class {
659
702
  });
660
703
  }
661
704
  validate(schema, data) {
662
- this.lastValidator = this.ajv.compile(schema);
663
- return this.lastValidator(data);
705
+ const key = schema;
706
+ let validator = this.compiled.get(key);
707
+ if (!validator) {
708
+ validator = this.ajv.compile(schema);
709
+ this.compiled.set(key, validator);
710
+ }
711
+ this.lastValidator = validator;
712
+ return validator(data);
664
713
  }
665
714
  getError() {
666
715
  if (!this.lastValidator || !this.lastValidator.errors) {
@@ -746,18 +795,111 @@ var DefaultDeserializer = class {
746
795
  }
747
796
  };
748
797
 
798
+ // src/core/consumer/processors/RunMQSchemaFailureProcessor.ts
799
+ var RunMQSchemaFailureProcessor = class {
800
+ constructor(consumer, config, DLQPublisher, logger) {
801
+ this.consumer = consumer;
802
+ this.config = config;
803
+ this.DLQPublisher = DLQPublisher;
804
+ this.logger = logger;
805
+ }
806
+ async consume(message) {
807
+ try {
808
+ return await this.consumer.consume(message);
809
+ } catch (e) {
810
+ if (this.shouldRouteToDLQ(e)) {
811
+ this.logger.warn("Schema validation failed \u2014 routing message to DLQ.", {
812
+ correlationId: message.correlationId,
813
+ error: e instanceof RunMQSchemaValidationError ? e.error : void 0
814
+ });
815
+ this.routeToDLQ(message);
816
+ return true;
817
+ }
818
+ throw e;
819
+ }
820
+ }
821
+ shouldRouteToDLQ(e) {
822
+ var _a2;
823
+ if (!(e instanceof RunMQSchemaValidationError)) return false;
824
+ return ((_a2 = this.config.messageSchema) == null ? void 0 : _a2.failureStrategy) === "dlq";
825
+ }
826
+ routeToDLQ(message) {
827
+ const dlqMessage = new RabbitMQMessage(
828
+ this.extractOriginalPayload(message),
829
+ message.id,
830
+ message.correlationId,
831
+ message.channel,
832
+ message.amqpMessage,
833
+ message.headers
834
+ );
835
+ this.DLQPublisher.publish(
836
+ ConsumerCreatorUtils.getDLQTopicName(this.config.name),
837
+ dlqMessage
838
+ );
839
+ }
840
+ extractOriginalPayload(message) {
841
+ if (typeof message.message === "string") {
842
+ try {
843
+ const parsed = JSON.parse(message.message);
844
+ if (RunMQMessage.isValid(parsed)) {
845
+ return parsed.message;
846
+ }
847
+ } catch (e) {
848
+ }
849
+ }
850
+ return message.message;
851
+ }
852
+ };
853
+
854
+ // src/core/consumer/processors/RunMQBaseProcessor.ts
855
+ var RunMQBaseProcessor = class {
856
+ constructor(handler, processorConfig, serializer) {
857
+ this.handler = handler;
858
+ this.processorConfig = processorConfig;
859
+ this.serializer = serializer;
860
+ }
861
+ async consume(message) {
862
+ const rabbitMQMessage = this.serializer.deserialize(message.message, this.processorConfig);
863
+ await this.handler(rabbitMQMessage);
864
+ return true;
865
+ }
866
+ };
867
+
868
+ // src/core/consumer/processors/RunMQExceptionLoggerProcessor.ts
869
+ var RunMQExceptionLoggerProcessor = class {
870
+ constructor(consumer, logger) {
871
+ this.consumer = consumer;
872
+ this.logger = logger;
873
+ }
874
+ async consume(message) {
875
+ try {
876
+ return await this.consumer.consume(message);
877
+ } catch (e) {
878
+ if (e instanceof Error) {
879
+ this.logger.error(e.message, e.stack);
880
+ throw e;
881
+ } else {
882
+ const errorString = JSON.stringify(e);
883
+ this.logger.error(errorString);
884
+ throw new Error(errorString);
885
+ }
886
+ }
887
+ }
888
+ };
889
+
749
890
  // src/core/publisher/producers/RunMQFailureLoggerProducer.ts
750
891
  var RunMQFailureLoggerProducer = class {
751
892
  constructor(producer, logger) {
752
893
  this.producer = producer;
753
894
  this.logger = logger;
754
895
  }
755
- publish(topic, message) {
896
+ async publish(topic, message) {
756
897
  try {
757
- this.producer.publish(topic, message);
898
+ await this.producer.publish(topic, message);
758
899
  } catch (e) {
759
900
  this.logger.error("Message publishing failed", {
760
- message,
901
+ topic,
902
+ correlationId: message.correlationId,
761
903
  error: e instanceof Error ? e.message : JSON.stringify(e),
762
904
  stack: e instanceof Error ? e.stack : void 0
763
905
  });
@@ -772,7 +914,7 @@ var RunMQBaseProducer = class {
772
914
  this.serializer = serializer;
773
915
  this.exchange = exchange;
774
916
  }
775
- publish(topic, message) {
917
+ async publish(topic, message) {
776
918
  const runMQMessage = new RunMQMessage(
777
919
  message.message,
778
920
  new RunMQMessageMeta(
@@ -782,10 +924,11 @@ var RunMQBaseProducer = class {
782
924
  )
783
925
  );
784
926
  const serialized = this.serializer.serialize(runMQMessage);
785
- message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
927
+ await message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
786
928
  correlationId: message.correlationId,
787
929
  messageId: message.id,
788
- headers: message.headers
930
+ headers: message.headers,
931
+ persistent: true
789
932
  });
790
933
  }
791
934
  };
@@ -1206,9 +1349,10 @@ var RunMQMetadataManager = class {
1206
1349
 
1207
1350
  // src/core/consumer/RunMQConsumerCreator.ts
1208
1351
  var RunMQConsumerCreator = class {
1209
- constructor(client, logger, managementConfig) {
1352
+ constructor(client, logger, managementConfig, logFullMessagePayload = false) {
1210
1353
  this.client = client;
1211
1354
  this.logger = logger;
1355
+ this.logFullMessagePayload = logFullMessagePayload;
1212
1356
  this.ttlPolicyManager = new RunMQTTLPolicyManager(logger, managementConfig);
1213
1357
  this.metadataManager = new RunMQMetadataManager(logger, managementConfig);
1214
1358
  }
@@ -1231,39 +1375,67 @@ var RunMQConsumerCreator = class {
1231
1375
  );
1232
1376
  }
1233
1377
  async runProcessor(consumerConfiguration) {
1234
- const consumerChannel = await this.getProcessorChannel();
1378
+ var _a2;
1379
+ const consumerChannel = await this.client.getChannel({
1380
+ onClose: () => {
1381
+ if (!this.client.isActive()) {
1382
+ return;
1383
+ }
1384
+ this.logger.warn("Consumer channel closed; attempting to re-subscribe", {
1385
+ processor: consumerConfiguration.processorConfig.name
1386
+ });
1387
+ this.resubscribeProcessor(consumerConfiguration);
1388
+ }
1389
+ });
1235
1390
  const DLQPublisher = new RunMQPublisherCreator(this.logger).createPublisher(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME);
1236
- await consumerChannel.prefetch(DEFAULTS.PREFETCH_COUNT);
1391
+ await consumerChannel.confirmSelect();
1392
+ const prefetchCount = (_a2 = consumerConfiguration.processorConfig.prefetch) != null ? _a2 : DEFAULTS.PREFETCH_COUNT;
1393
+ await consumerChannel.prefetch(prefetchCount);
1237
1394
  await consumerChannel.consume(consumerConfiguration.processorConfig.name, async (msg) => {
1238
- if (msg) {
1239
- const rabbitmqMessage = new RabbitMQMessage(
1240
- msg.content.toString(),
1241
- msg.properties.messageId,
1242
- msg.properties.correlationId,
1243
- consumerChannel,
1244
- msg,
1245
- msg.properties.headers
1246
- );
1247
- return new RunMQExceptionLoggerProcessor(
1395
+ if (!msg) return;
1396
+ const rabbitmqMessage = new RabbitMQMessage(
1397
+ msg.content.toString(),
1398
+ msg.properties.messageId,
1399
+ msg.properties.correlationId,
1400
+ consumerChannel,
1401
+ msg,
1402
+ msg.properties.headers
1403
+ );
1404
+ try {
1405
+ await new RunMQExceptionLoggerProcessor(
1248
1406
  new RunMQSucceededMessageAcknowledgerProcessor(
1249
1407
  new RunMQFailedMessageRejecterProcessor(
1250
1408
  new RunMQRetriesCheckerProcessor(
1251
1409
  new RunMQFailureLoggerProcessor(
1252
- new RunMQBaseProcessor(
1253
- consumerConfiguration.processor,
1410
+ new RunMQSchemaFailureProcessor(
1411
+ new RunMQBaseProcessor(
1412
+ consumerConfiguration.processor,
1413
+ consumerConfiguration.processorConfig,
1414
+ new DefaultDeserializer()
1415
+ ),
1254
1416
  consumerConfiguration.processorConfig,
1255
- new DefaultDeserializer()
1417
+ DLQPublisher,
1418
+ this.logger
1256
1419
  ),
1257
- this.logger
1420
+ this.logger,
1421
+ this.logFullMessagePayload
1258
1422
  ),
1259
1423
  consumerConfiguration.processorConfig,
1260
- DLQPublisher,
1261
- this.logger
1262
- )
1263
- )
1424
+ this.logger,
1425
+ this.logFullMessagePayload
1426
+ ),
1427
+ this.logger
1428
+ ),
1429
+ this.logger
1264
1430
  ),
1265
1431
  this.logger
1266
1432
  ).consume(rabbitmqMessage);
1433
+ } catch (e) {
1434
+ this.logger.error("Unhandled error in consumer chain", {
1435
+ correlationId: rabbitmqMessage.correlationId,
1436
+ cause: e instanceof Error ? e.message : String(e),
1437
+ stack: e instanceof Error ? e.stack : void 0
1438
+ });
1267
1439
  }
1268
1440
  });
1269
1441
  }
@@ -1332,8 +1504,20 @@ var RunMQConsumerCreator = class {
1332
1504
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name)
1333
1505
  );
1334
1506
  }
1335
- async getProcessorChannel() {
1336
- return await this.client.getChannel();
1507
+ resubscribeProcessor(consumerConfiguration) {
1508
+ const delay = DEFAULTS.RECONNECT_DELAY;
1509
+ setTimeout(() => {
1510
+ if (!this.client.isActive()) {
1511
+ return;
1512
+ }
1513
+ this.runProcessor(consumerConfiguration).catch((err) => {
1514
+ this.logger.error("Failed to re-subscribe consumer; will retry", {
1515
+ processor: consumerConfiguration.processorConfig.name,
1516
+ error: err instanceof Error ? err.message : err
1517
+ });
1518
+ this.resubscribeProcessor(consumerConfiguration);
1519
+ });
1520
+ }, delay);
1337
1521
  }
1338
1522
  };
1339
1523
 
@@ -1365,7 +1549,7 @@ var RunMQ = class _RunMQ {
1365
1549
  maxReconnectAttempts: (_b = config.maxReconnectAttempts) != null ? _b : DEFAULTS.MAX_RECONNECT_ATTEMPTS
1366
1550
  });
1367
1551
  this.client = new RabbitMQClientAdapter(this.config, this.logger);
1368
- this.consumer = new RunMQConsumerCreator(this.client, this.logger, this.config.management);
1552
+ this.consumer = new RunMQConsumerCreator(this.client, this.logger, this.config.management, this.config.logFullMessagePayload);
1369
1553
  }
1370
1554
  /**
1371
1555
  * Starts the RunMQ instance by establishing a connection to RabbitMQ and initializing necessary components.
@@ -1389,29 +1573,38 @@ var RunMQ = class _RunMQ {
1389
1573
  await this.consumer.createConsumer(new ConsumerConfiguration(topic, config, processor));
1390
1574
  }
1391
1575
  /**
1392
- * Publishes a message to the specified topic with an optional correlation ID
1576
+ * Publishes a message to the specified topic with an optional correlation ID.
1577
+ *
1578
+ * If publisher confirms are enabled (`usePublisherConfirms: true` in the
1579
+ * connection config), the returned promise resolves only after RabbitMQ
1580
+ * acknowledges the message; if the broker rejects, the promise rejects.
1581
+ * Otherwise it resolves once the message is flushed to the TCP socket
1582
+ * (fire-and-forget, no delivery guarantee — same behavior as before
1583
+ * publisher confirms were introduced).
1584
+ *
1393
1585
  * @param topic The name of the topic to publish the message to
1394
1586
  * @param message The message payload to be published
1395
1587
  * @param correlationId (Optional) A unique identifier for correlating messages; if not provided, a new UUID will be generated
1396
1588
  */
1397
- publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1398
- if (!this.publisher || !this.defaultChannel) {
1589
+ async publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1590
+ if (!this.publisher || !this.publishChannel) {
1399
1591
  throw new RunMQException(Exceptions.NOT_INITIALIZED, {});
1400
1592
  }
1401
1593
  RunMQUtils.assertRecord(message);
1402
- this.publisher.publish(
1594
+ const messageId = RunMQUtils.generateUUID();
1595
+ await this.publisher.publish(
1403
1596
  topic,
1404
1597
  RabbitMQMessage.from(
1405
1598
  message,
1406
- this.defaultChannel,
1407
- new RabbitMQMessageProperties(RunMQUtils.generateUUID(), correlationId)
1599
+ this.publishChannel,
1600
+ new RabbitMQMessageProperties(messageId, correlationId)
1408
1601
  )
1409
1602
  );
1410
- this.logger.info(`Published message`, {
1603
+ this.logger.info(`Published message`, __spreadValues({
1411
1604
  topic,
1412
1605
  correlationId,
1413
- message
1414
- });
1606
+ messageId
1607
+ }, this.config.logFullMessagePayload ? { message } : {}));
1415
1608
  }
1416
1609
  /**
1417
1610
  * Disconnects from RabbitMQ, handling any errors that may occur during the disconnection process.
@@ -1464,6 +1657,10 @@ var RunMQ = class _RunMQ {
1464
1657
  this.defaultChannel = await this.client.getDefaultChannel();
1465
1658
  await this.defaultChannel.assertExchange(Constants.ROUTER_EXCHANGE_NAME, "direct", { durable: true });
1466
1659
  await this.defaultChannel.assertExchange(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME, "direct", { durable: true });
1660
+ this.publishChannel = await this.client.getChannel();
1661
+ if (this.config.usePublisherConfirms !== false) {
1662
+ await this.publishChannel.confirmSelect();
1663
+ }
1467
1664
  this.publisher = new RunMQPublisherCreator(this.logger).createPublisher();
1468
1665
  }
1469
1666
  };