runmq 1.5.1 → 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();
@@ -333,21 +337,52 @@ var RabbitMQClientAdapter = class {
333
337
  );
334
338
  }
335
339
  }
336
- async getChannel() {
340
+ async getChannel(callbacks) {
337
341
  const connection = await this.connect();
338
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
+ });
339
369
  this.acquiredChannels.push(rawChannel);
340
370
  return new RabbitMQClientChannel(rawChannel);
341
371
  }
342
372
  async getDefaultChannel() {
343
373
  if (!this.defaultChannel) {
344
- this.defaultChannel = await this.getChannel();
374
+ this.defaultChannel = await this.getChannel({
375
+ onClose: () => {
376
+ this.defaultChannel = void 0;
377
+ }
378
+ });
345
379
  }
346
380
  return this.defaultChannel;
347
381
  }
348
382
  async disconnect() {
349
383
  const conn = this.connection;
350
384
  const channels = this.acquiredChannels;
385
+ this.isShuttingDown = true;
351
386
  this.connection = void 0;
352
387
  this.defaultChannel = void 0;
353
388
  this.isConnected = false;
@@ -420,20 +455,33 @@ var RabbitMQMessage = class _RabbitMQMessage {
420
455
  this.headers = headers;
421
456
  }
422
457
  /**
423
- * 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.
424
463
  */
425
464
  ack() {
426
- if (this.amqpMessage) {
465
+ if (!this.amqpMessage) return false;
466
+ try {
427
467
  this.channel.ack(this.amqpMessage);
468
+ return true;
469
+ } catch (e) {
470
+ return false;
428
471
  }
429
472
  }
430
473
  /**
431
- * 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.
432
476
  * @param requeue - Whether to requeue the message (default: false)
433
477
  */
434
478
  nack(requeue = false) {
435
- if (this.amqpMessage) {
479
+ if (!this.amqpMessage) return false;
480
+ try {
436
481
  this.channel.nack(this.amqpMessage, false, requeue);
482
+ return true;
483
+ } catch (e) {
484
+ return false;
437
485
  }
438
486
  }
439
487
  static from(messageData, channel, props, amqpMessage = null) {
@@ -450,13 +498,20 @@ var RabbitMQMessage = class _RabbitMQMessage {
450
498
 
451
499
  // src/core/consumer/processors/RunMQSucceededMessageAcknowledgerProcessor.ts
452
500
  var RunMQSucceededMessageAcknowledgerProcessor = class {
453
- constructor(consumer) {
501
+ constructor(consumer, logger) {
454
502
  this.consumer = consumer;
503
+ this.logger = logger;
455
504
  }
456
505
  async consume(message) {
506
+ var _a2;
457
507
  const result = await this.consumer.consume(message);
458
508
  if (result) {
459
- 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
+ }
460
515
  }
461
516
  return result;
462
517
  }
@@ -464,40 +519,26 @@ var RunMQSucceededMessageAcknowledgerProcessor = class {
464
519
 
465
520
  // src/core/consumer/processors/RunMQFailedMessageRejecterProcessor.ts
466
521
  var RunMQFailedMessageRejecterProcessor = class {
467
- constructor(consumer) {
522
+ constructor(consumer, logger) {
468
523
  this.consumer = consumer;
524
+ this.logger = logger;
469
525
  }
470
526
  async consume(message) {
527
+ var _a2;
471
528
  try {
472
529
  return await this.consumer.consume(message);
473
530
  } catch (e) {
474
- 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
+ }
475
537
  return false;
476
538
  }
477
539
  }
478
540
  };
479
541
 
480
- // src/core/message/RunMQMessage.ts
481
- var RunMQMessage = class {
482
- static isValid(obj) {
483
- if (typeof obj === "object" && obj !== null) {
484
- 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";
485
- }
486
- return false;
487
- }
488
- constructor(message, meta) {
489
- this.message = message;
490
- this.meta = meta;
491
- }
492
- };
493
- var RunMQMessageMeta = class {
494
- constructor(id, publishedAt, correlationId) {
495
- this.id = id;
496
- this.correlationId = correlationId;
497
- this.publishedAt = publishedAt;
498
- }
499
- };
500
-
501
542
  // src/core/consumer/ConsumerCreatorUtils.ts
502
543
  var ConsumerCreatorUtils = class {
503
544
  static getDLQTopicName(topic) {
@@ -514,11 +555,11 @@ var ConsumerCreatorUtils = class {
514
555
  // src/core/consumer/processors/RunMQRetriesCheckerProcessor.ts
515
556
  var _a;
516
557
  var RunMQRetriesCheckerProcessor = class {
517
- constructor(consumer, config, DLQPublisher, logger) {
558
+ constructor(consumer, config, logger, logFullMessagePayload = false) {
518
559
  this.consumer = consumer;
519
560
  this.config = config;
520
- this.DLQPublisher = DLQPublisher;
521
561
  this.logger = logger;
562
+ this.logFullMessagePayload = logFullMessagePayload;
522
563
  this.maxAttempts = (_a = this.config.attempts) != null ? _a : DEFAULTS.PROCESSING_ATTEMPTS;
523
564
  }
524
565
  async consume(message) {
@@ -527,7 +568,16 @@ var RunMQRetriesCheckerProcessor = class {
527
568
  } catch (e) {
528
569
  if (this.hasReachedMaxRetries(message)) {
529
570
  this.logMaxRetriesReached(message);
530
- 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
+ }
531
581
  this.acknowledgeMessage(message);
532
582
  return false;
533
583
  }
@@ -541,44 +591,38 @@ var RunMQRetriesCheckerProcessor = class {
541
591
  logMaxRetriesReached(message) {
542
592
  this.logger.error(
543
593
  `Message reached maximum attempts. Moving to dead-letter queue.`,
544
- {
545
- message: message.message,
594
+ __spreadValues({
595
+ correlationId: message.correlationId,
596
+ messageId: message.id,
546
597
  attempts: this.getRejectionCount(message),
547
598
  max: this.maxAttempts
548
- }
599
+ }, this.logFullMessagePayload ? { message: message.message } : {})
549
600
  );
550
601
  }
551
- moveToFinalDeadLetter(message) {
552
- const originalPayload = this.extractOriginalPayload(message);
553
- const dlqMessage = new RabbitMQMessage(
554
- originalPayload,
555
- message.id,
556
- message.correlationId,
557
- message.channel,
558
- message.amqpMessage,
559
- message.headers
560
- );
561
- this.DLQPublisher.publish(ConsumerCreatorUtils.getDLQTopicName(this.config.name), dlqMessage);
562
- }
563
- extractOriginalPayload(message) {
564
- if (typeof message.message === "string") {
565
- try {
566
- const parsed = JSON.parse(message.message);
567
- if (RunMQMessage.isValid(parsed)) {
568
- return parsed.message;
569
- }
570
- } 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
571
617
  }
572
- }
573
- return message.message;
618
+ );
574
619
  }
575
620
  acknowledgeMessage(message) {
576
- try {
577
- message.ack();
578
- } catch (e) {
579
- const error = new Error("A message acknowledge failed after publishing to final dead letter");
580
- this.logger.error(error.message, { cause: e instanceof Error ? e.message : String(e) });
581
- 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
+ });
582
626
  }
583
627
  }
584
628
  getRejectionCount(message) {
@@ -592,9 +636,10 @@ var RunMQRetriesCheckerProcessor = class {
592
636
 
593
637
  // src/core/consumer/processors/RunMQFailureLoggerProcessor.ts
594
638
  var RunMQFailureLoggerProcessor = class {
595
- constructor(consumer, logger) {
639
+ constructor(consumer, logger, logFullMessagePayload = false) {
596
640
  this.consumer = consumer;
597
641
  this.logger = logger;
642
+ this.logFullMessagePayload = logFullMessagePayload;
598
643
  }
599
644
  async consume(message) {
600
645
  try {
@@ -602,9 +647,10 @@ var RunMQFailureLoggerProcessor = class {
602
647
  } catch (e) {
603
648
  this.logger.error(
604
649
  "Message processing failed",
605
- {
606
- message: message.message
607
- },
650
+ __spreadValues({
651
+ correlationId: message.correlationId,
652
+ messageId: message.id
653
+ }, this.logFullMessagePayload ? { message: message.message } : {}),
608
654
  e instanceof Error ? e.stack : void 0
609
655
  );
610
656
  throw e;
@@ -612,39 +658,24 @@ var RunMQFailureLoggerProcessor = class {
612
658
  }
613
659
  };
614
660
 
615
- // src/core/consumer/processors/RunMQBaseProcessor.ts
616
- var RunMQBaseProcessor = class {
617
- constructor(handler, processorConfig, serializer) {
618
- this.handler = handler;
619
- this.processorConfig = processorConfig;
620
- 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;
621
668
  }
622
- async consume(message) {
623
- const rabbitMQMessage = this.serializer.deserialize(message.message, this.processorConfig);
624
- await this.handler(rabbitMQMessage);
625
- return true;
669
+ constructor(message, meta) {
670
+ this.message = message;
671
+ this.meta = meta;
626
672
  }
627
673
  };
628
-
629
- // src/core/consumer/processors/RunMQExceptionLoggerProcessor.ts
630
- var RunMQExceptionLoggerProcessor = class {
631
- constructor(consumer, logger) {
632
- this.consumer = consumer;
633
- this.logger = logger;
634
- }
635
- async consume(message) {
636
- try {
637
- return await this.consumer.consume(message);
638
- } catch (e) {
639
- if (e instanceof Error) {
640
- this.logger.error(e.message, e.stack);
641
- throw e;
642
- } else {
643
- const errorString = JSON.stringify(e);
644
- this.logger.error(errorString);
645
- throw new Error(errorString);
646
- }
647
- }
674
+ var RunMQMessageMeta = class {
675
+ constructor(id, publishedAt, correlationId) {
676
+ this.id = id;
677
+ this.correlationId = correlationId;
678
+ this.publishedAt = publishedAt;
648
679
  }
649
680
  };
650
681
 
@@ -653,6 +684,17 @@ var import_ajv = __toESM(require("ajv"), 1);
653
684
  var AjvSchemaValidator = class {
654
685
  constructor() {
655
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();
656
698
  this.ajv = new import_ajv.default({
657
699
  allErrors: true,
658
700
  verbose: true,
@@ -660,8 +702,14 @@ var AjvSchemaValidator = class {
660
702
  });
661
703
  }
662
704
  validate(schema, data) {
663
- this.lastValidator = this.ajv.compile(schema);
664
- 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);
665
713
  }
666
714
  getError() {
667
715
  if (!this.lastValidator || !this.lastValidator.errors) {
@@ -747,18 +795,111 @@ var DefaultDeserializer = class {
747
795
  }
748
796
  };
749
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
+
750
890
  // src/core/publisher/producers/RunMQFailureLoggerProducer.ts
751
891
  var RunMQFailureLoggerProducer = class {
752
892
  constructor(producer, logger) {
753
893
  this.producer = producer;
754
894
  this.logger = logger;
755
895
  }
756
- publish(topic, message) {
896
+ async publish(topic, message) {
757
897
  try {
758
- this.producer.publish(topic, message);
898
+ await this.producer.publish(topic, message);
759
899
  } catch (e) {
760
900
  this.logger.error("Message publishing failed", {
761
- message,
901
+ topic,
902
+ correlationId: message.correlationId,
762
903
  error: e instanceof Error ? e.message : JSON.stringify(e),
763
904
  stack: e instanceof Error ? e.stack : void 0
764
905
  });
@@ -773,7 +914,7 @@ var RunMQBaseProducer = class {
773
914
  this.serializer = serializer;
774
915
  this.exchange = exchange;
775
916
  }
776
- publish(topic, message) {
917
+ async publish(topic, message) {
777
918
  const runMQMessage = new RunMQMessage(
778
919
  message.message,
779
920
  new RunMQMessageMeta(
@@ -783,7 +924,7 @@ var RunMQBaseProducer = class {
783
924
  )
784
925
  );
785
926
  const serialized = this.serializer.serialize(runMQMessage);
786
- message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
927
+ await message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
787
928
  correlationId: message.correlationId,
788
929
  messageId: message.id,
789
930
  headers: message.headers,
@@ -1208,9 +1349,10 @@ var RunMQMetadataManager = class {
1208
1349
 
1209
1350
  // src/core/consumer/RunMQConsumerCreator.ts
1210
1351
  var RunMQConsumerCreator = class {
1211
- constructor(client, logger, managementConfig) {
1352
+ constructor(client, logger, managementConfig, logFullMessagePayload = false) {
1212
1353
  this.client = client;
1213
1354
  this.logger = logger;
1355
+ this.logFullMessagePayload = logFullMessagePayload;
1214
1356
  this.ttlPolicyManager = new RunMQTTLPolicyManager(logger, managementConfig);
1215
1357
  this.metadataManager = new RunMQMetadataManager(logger, managementConfig);
1216
1358
  }
@@ -1233,39 +1375,67 @@ var RunMQConsumerCreator = class {
1233
1375
  );
1234
1376
  }
1235
1377
  async runProcessor(consumerConfiguration) {
1236
- 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
+ });
1237
1390
  const DLQPublisher = new RunMQPublisherCreator(this.logger).createPublisher(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME);
1238
- 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);
1239
1394
  await consumerChannel.consume(consumerConfiguration.processorConfig.name, async (msg) => {
1240
- if (msg) {
1241
- const rabbitmqMessage = new RabbitMQMessage(
1242
- msg.content.toString(),
1243
- msg.properties.messageId,
1244
- msg.properties.correlationId,
1245
- consumerChannel,
1246
- msg,
1247
- msg.properties.headers
1248
- );
1249
- 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(
1250
1406
  new RunMQSucceededMessageAcknowledgerProcessor(
1251
1407
  new RunMQFailedMessageRejecterProcessor(
1252
1408
  new RunMQRetriesCheckerProcessor(
1253
1409
  new RunMQFailureLoggerProcessor(
1254
- new RunMQBaseProcessor(
1255
- consumerConfiguration.processor,
1410
+ new RunMQSchemaFailureProcessor(
1411
+ new RunMQBaseProcessor(
1412
+ consumerConfiguration.processor,
1413
+ consumerConfiguration.processorConfig,
1414
+ new DefaultDeserializer()
1415
+ ),
1256
1416
  consumerConfiguration.processorConfig,
1257
- new DefaultDeserializer()
1417
+ DLQPublisher,
1418
+ this.logger
1258
1419
  ),
1259
- this.logger
1420
+ this.logger,
1421
+ this.logFullMessagePayload
1260
1422
  ),
1261
1423
  consumerConfiguration.processorConfig,
1262
- DLQPublisher,
1263
- this.logger
1264
- )
1265
- )
1424
+ this.logger,
1425
+ this.logFullMessagePayload
1426
+ ),
1427
+ this.logger
1428
+ ),
1429
+ this.logger
1266
1430
  ),
1267
1431
  this.logger
1268
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
+ });
1269
1439
  }
1270
1440
  });
1271
1441
  }
@@ -1334,8 +1504,20 @@ var RunMQConsumerCreator = class {
1334
1504
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name)
1335
1505
  );
1336
1506
  }
1337
- async getProcessorChannel() {
1338
- 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);
1339
1521
  }
1340
1522
  };
1341
1523
 
@@ -1367,7 +1549,7 @@ var RunMQ = class _RunMQ {
1367
1549
  maxReconnectAttempts: (_b = config.maxReconnectAttempts) != null ? _b : DEFAULTS.MAX_RECONNECT_ATTEMPTS
1368
1550
  });
1369
1551
  this.client = new RabbitMQClientAdapter(this.config, this.logger);
1370
- 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);
1371
1553
  }
1372
1554
  /**
1373
1555
  * Starts the RunMQ instance by establishing a connection to RabbitMQ and initializing necessary components.
@@ -1391,29 +1573,38 @@ var RunMQ = class _RunMQ {
1391
1573
  await this.consumer.createConsumer(new ConsumerConfiguration(topic, config, processor));
1392
1574
  }
1393
1575
  /**
1394
- * 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
+ *
1395
1585
  * @param topic The name of the topic to publish the message to
1396
1586
  * @param message The message payload to be published
1397
1587
  * @param correlationId (Optional) A unique identifier for correlating messages; if not provided, a new UUID will be generated
1398
1588
  */
1399
- publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1400
- if (!this.publisher || !this.defaultChannel) {
1589
+ async publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1590
+ if (!this.publisher || !this.publishChannel) {
1401
1591
  throw new RunMQException(Exceptions.NOT_INITIALIZED, {});
1402
1592
  }
1403
1593
  RunMQUtils.assertRecord(message);
1404
- this.publisher.publish(
1594
+ const messageId = RunMQUtils.generateUUID();
1595
+ await this.publisher.publish(
1405
1596
  topic,
1406
1597
  RabbitMQMessage.from(
1407
1598
  message,
1408
- this.defaultChannel,
1409
- new RabbitMQMessageProperties(RunMQUtils.generateUUID(), correlationId)
1599
+ this.publishChannel,
1600
+ new RabbitMQMessageProperties(messageId, correlationId)
1410
1601
  )
1411
1602
  );
1412
- this.logger.info(`Published message`, {
1603
+ this.logger.info(`Published message`, __spreadValues({
1413
1604
  topic,
1414
1605
  correlationId,
1415
- message
1416
- });
1606
+ messageId
1607
+ }, this.config.logFullMessagePayload ? { message } : {}));
1417
1608
  }
1418
1609
  /**
1419
1610
  * Disconnects from RabbitMQ, handling any errors that may occur during the disconnection process.
@@ -1466,6 +1657,10 @@ var RunMQ = class _RunMQ {
1466
1657
  this.defaultChannel = await this.client.getDefaultChannel();
1467
1658
  await this.defaultChannel.assertExchange(Constants.ROUTER_EXCHANGE_NAME, "direct", { durable: true });
1468
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
+ }
1469
1664
  this.publisher = new RunMQPublisherCreator(this.logger).createPublisher();
1470
1665
  }
1471
1666
  };