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.js CHANGED
@@ -124,9 +124,9 @@ var RabbitMQClientChannel = class {
124
124
  arguments: args
125
125
  });
126
126
  }
127
- publish(exchange, routingKey, content, options) {
127
+ async publish(exchange, routingKey, content, options) {
128
128
  var _a2;
129
- this.channel.basicPublish({
129
+ await this.channel.basicPublish({
130
130
  exchange,
131
131
  routingKey,
132
132
  correlationId: options == null ? void 0 : options.correlationId,
@@ -143,7 +143,9 @@ var RabbitMQClientChannel = class {
143
143
  userId: options == null ? void 0 : options.userId,
144
144
  appId: options == null ? void 0 : options.appId
145
145
  }, content);
146
- return true;
146
+ }
147
+ async confirmSelect() {
148
+ await this.channel.confirmSelect();
147
149
  }
148
150
  async consume(queue, onMessage, options) {
149
151
  const result = await this.channel.basicConsume({
@@ -246,12 +248,14 @@ var RabbitMQClientAdapter = class {
246
248
  this.logger = logger;
247
249
  this.isConnected = false;
248
250
  this.acquiredChannels = [];
251
+ this.isShuttingDown = false;
249
252
  }
250
253
  async connect() {
251
254
  try {
252
255
  if (this.connection && this.isConnected) {
253
256
  return this.connection;
254
257
  }
258
+ this.isShuttingDown = false;
255
259
  if (this.connection) {
256
260
  try {
257
261
  await this.connection.close();
@@ -300,21 +304,52 @@ var RabbitMQClientAdapter = class {
300
304
  );
301
305
  }
302
306
  }
303
- async getChannel() {
307
+ async getChannel(callbacks) {
304
308
  const connection = await this.connect();
305
309
  const rawChannel = await connection.acquire();
310
+ const channelId = rawChannel.id;
311
+ rawChannel.on("error", (err) => {
312
+ var _a2;
313
+ this.logger.error("RabbitMQ channel error:", { channelId, error: err });
314
+ try {
315
+ (_a2 = callbacks == null ? void 0 : callbacks.onError) == null ? void 0 : _a2.call(callbacks, err);
316
+ } catch (cbErr) {
317
+ this.logger.error("RabbitMQ channel onError callback threw:", { channelId, error: cbErr });
318
+ }
319
+ });
320
+ rawChannel.on("close", () => {
321
+ var _a2;
322
+ this.logger.warn("RabbitMQ channel closed", { channelId });
323
+ const idx = this.acquiredChannels.indexOf(rawChannel);
324
+ if (idx >= 0) {
325
+ this.acquiredChannels.splice(idx, 1);
326
+ }
327
+ if (this.isShuttingDown) {
328
+ return;
329
+ }
330
+ try {
331
+ (_a2 = callbacks == null ? void 0 : callbacks.onClose) == null ? void 0 : _a2.call(callbacks);
332
+ } catch (cbErr) {
333
+ this.logger.error("RabbitMQ channel onClose callback threw:", { channelId, error: cbErr });
334
+ }
335
+ });
306
336
  this.acquiredChannels.push(rawChannel);
307
337
  return new RabbitMQClientChannel(rawChannel);
308
338
  }
309
339
  async getDefaultChannel() {
310
340
  if (!this.defaultChannel) {
311
- this.defaultChannel = await this.getChannel();
341
+ this.defaultChannel = await this.getChannel({
342
+ onClose: () => {
343
+ this.defaultChannel = void 0;
344
+ }
345
+ });
312
346
  }
313
347
  return this.defaultChannel;
314
348
  }
315
349
  async disconnect() {
316
350
  const conn = this.connection;
317
351
  const channels = this.acquiredChannels;
352
+ this.isShuttingDown = true;
318
353
  this.connection = void 0;
319
354
  this.defaultChannel = void 0;
320
355
  this.isConnected = false;
@@ -387,20 +422,33 @@ var RabbitMQMessage = class _RabbitMQMessage {
387
422
  this.headers = headers;
388
423
  }
389
424
  /**
390
- * Acknowledges the message.
425
+ * Acknowledges the message. Returns true on success, false if the
426
+ * underlying channel rejected the call (e.g. closed mid-flight).
427
+ * Plumbing errors are intentionally swallowed: the broker will redeliver
428
+ * unacked messages on channel close, so escalating here only crashes
429
+ * the consumer for no recovery benefit.
391
430
  */
392
431
  ack() {
393
- if (this.amqpMessage) {
432
+ if (!this.amqpMessage) return false;
433
+ try {
394
434
  this.channel.ack(this.amqpMessage);
435
+ return true;
436
+ } catch (e) {
437
+ return false;
395
438
  }
396
439
  }
397
440
  /**
398
- * Negatively acknowledges the message.
441
+ * Negatively acknowledges the message. Returns true on success, false if
442
+ * the underlying channel rejected the call. See `ack()` for rationale.
399
443
  * @param requeue - Whether to requeue the message (default: false)
400
444
  */
401
445
  nack(requeue = false) {
402
- if (this.amqpMessage) {
446
+ if (!this.amqpMessage) return false;
447
+ try {
403
448
  this.channel.nack(this.amqpMessage, false, requeue);
449
+ return true;
450
+ } catch (e) {
451
+ return false;
404
452
  }
405
453
  }
406
454
  static from(messageData, channel, props, amqpMessage = null) {
@@ -417,13 +465,20 @@ var RabbitMQMessage = class _RabbitMQMessage {
417
465
 
418
466
  // src/core/consumer/processors/RunMQSucceededMessageAcknowledgerProcessor.ts
419
467
  var RunMQSucceededMessageAcknowledgerProcessor = class {
420
- constructor(consumer) {
468
+ constructor(consumer, logger) {
421
469
  this.consumer = consumer;
470
+ this.logger = logger;
422
471
  }
423
472
  async consume(message) {
473
+ var _a2;
424
474
  const result = await this.consumer.consume(message);
425
475
  if (result) {
426
- message.ack();
476
+ const acked = message.ack();
477
+ if (!acked) {
478
+ (_a2 = this.logger) == null ? void 0 : _a2.warn("Failed to ack message \u2014 channel likely closed. Broker will redeliver.", {
479
+ correlationId: message.correlationId
480
+ });
481
+ }
427
482
  }
428
483
  return result;
429
484
  }
@@ -431,40 +486,26 @@ var RunMQSucceededMessageAcknowledgerProcessor = class {
431
486
 
432
487
  // src/core/consumer/processors/RunMQFailedMessageRejecterProcessor.ts
433
488
  var RunMQFailedMessageRejecterProcessor = class {
434
- constructor(consumer) {
489
+ constructor(consumer, logger) {
435
490
  this.consumer = consumer;
491
+ this.logger = logger;
436
492
  }
437
493
  async consume(message) {
494
+ var _a2;
438
495
  try {
439
496
  return await this.consumer.consume(message);
440
497
  } catch (e) {
441
- message.nack(false);
498
+ const nacked = message.nack(false);
499
+ if (!nacked) {
500
+ (_a2 = this.logger) == null ? void 0 : _a2.warn("Failed to nack message \u2014 channel likely closed. Broker will redeliver.", {
501
+ correlationId: message.correlationId
502
+ });
503
+ }
442
504
  return false;
443
505
  }
444
506
  }
445
507
  };
446
508
 
447
- // src/core/message/RunMQMessage.ts
448
- var RunMQMessage = class {
449
- static isValid(obj) {
450
- if (typeof obj === "object" && obj !== null) {
451
- 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";
452
- }
453
- return false;
454
- }
455
- constructor(message, meta) {
456
- this.message = message;
457
- this.meta = meta;
458
- }
459
- };
460
- var RunMQMessageMeta = class {
461
- constructor(id, publishedAt, correlationId) {
462
- this.id = id;
463
- this.correlationId = correlationId;
464
- this.publishedAt = publishedAt;
465
- }
466
- };
467
-
468
509
  // src/core/consumer/ConsumerCreatorUtils.ts
469
510
  var ConsumerCreatorUtils = class {
470
511
  static getDLQTopicName(topic) {
@@ -481,11 +522,11 @@ var ConsumerCreatorUtils = class {
481
522
  // src/core/consumer/processors/RunMQRetriesCheckerProcessor.ts
482
523
  var _a;
483
524
  var RunMQRetriesCheckerProcessor = class {
484
- constructor(consumer, config, DLQPublisher, logger) {
525
+ constructor(consumer, config, logger, logFullMessagePayload = false) {
485
526
  this.consumer = consumer;
486
527
  this.config = config;
487
- this.DLQPublisher = DLQPublisher;
488
528
  this.logger = logger;
529
+ this.logFullMessagePayload = logFullMessagePayload;
489
530
  this.maxAttempts = (_a = this.config.attempts) != null ? _a : DEFAULTS.PROCESSING_ATTEMPTS;
490
531
  }
491
532
  async consume(message) {
@@ -494,7 +535,16 @@ var RunMQRetriesCheckerProcessor = class {
494
535
  } catch (e) {
495
536
  if (this.hasReachedMaxRetries(message)) {
496
537
  this.logMaxRetriesReached(message);
497
- this.moveToFinalDeadLetter(message);
538
+ try {
539
+ await this.moveToFinalDeadLetter(message);
540
+ } catch (publishError) {
541
+ this.logger.error("Failed to publish to DLQ \u2014 message will be redelivered", {
542
+ correlationId: message.correlationId,
543
+ cause: publishError instanceof Error ? publishError.message : String(publishError)
544
+ });
545
+ message.nack(false);
546
+ return false;
547
+ }
498
548
  this.acknowledgeMessage(message);
499
549
  return false;
500
550
  }
@@ -508,44 +558,38 @@ var RunMQRetriesCheckerProcessor = class {
508
558
  logMaxRetriesReached(message) {
509
559
  this.logger.error(
510
560
  `Message reached maximum attempts. Moving to dead-letter queue.`,
511
- {
512
- message: message.message,
561
+ __spreadValues({
562
+ correlationId: message.correlationId,
563
+ messageId: message.id,
513
564
  attempts: this.getRejectionCount(message),
514
565
  max: this.maxAttempts
515
- }
566
+ }, this.logFullMessagePayload ? { message: message.message } : {})
516
567
  );
517
568
  }
518
- moveToFinalDeadLetter(message) {
519
- const originalPayload = this.extractOriginalPayload(message);
520
- const dlqMessage = new RabbitMQMessage(
521
- originalPayload,
522
- message.id,
523
- message.correlationId,
524
- message.channel,
525
- message.amqpMessage,
526
- message.headers
527
- );
528
- this.DLQPublisher.publish(ConsumerCreatorUtils.getDLQTopicName(this.config.name), dlqMessage);
529
- }
530
- extractOriginalPayload(message) {
531
- if (typeof message.message === "string") {
532
- try {
533
- const parsed = JSON.parse(message.message);
534
- if (RunMQMessage.isValid(parsed)) {
535
- return parsed.message;
536
- }
537
- } catch (e) {
569
+ // Republish the original AMQP body verbatim so the envelope (including
570
+ // publishedAt) is preserved end-to-end for audit/replay. The consumer
571
+ // channel runs in confirm mode (see RunMQConsumerCreator), so awaiting
572
+ // this publish surfaces broker-side rejections instead of dropping them.
573
+ async moveToFinalDeadLetter(message) {
574
+ if (!message.amqpMessage) return;
575
+ await message.channel.publish(
576
+ Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME,
577
+ ConsumerCreatorUtils.getDLQTopicName(this.config.name),
578
+ message.amqpMessage.content,
579
+ {
580
+ correlationId: message.correlationId,
581
+ messageId: message.id,
582
+ headers: message.headers,
583
+ persistent: true
538
584
  }
539
- }
540
- return message.message;
585
+ );
541
586
  }
542
587
  acknowledgeMessage(message) {
543
- try {
544
- message.ack();
545
- } catch (e) {
546
- const error = new Error("A message acknowledge failed after publishing to final dead letter");
547
- this.logger.error(error.message, { cause: e instanceof Error ? e.message : String(e) });
548
- throw error;
588
+ const acked = message.ack();
589
+ if (!acked) {
590
+ this.logger.warn("Failed to ack message after publishing to final dead letter \u2014 channel likely closed. Broker will redeliver.", {
591
+ correlationId: message.correlationId
592
+ });
549
593
  }
550
594
  }
551
595
  getRejectionCount(message) {
@@ -559,9 +603,10 @@ var RunMQRetriesCheckerProcessor = class {
559
603
 
560
604
  // src/core/consumer/processors/RunMQFailureLoggerProcessor.ts
561
605
  var RunMQFailureLoggerProcessor = class {
562
- constructor(consumer, logger) {
606
+ constructor(consumer, logger, logFullMessagePayload = false) {
563
607
  this.consumer = consumer;
564
608
  this.logger = logger;
609
+ this.logFullMessagePayload = logFullMessagePayload;
565
610
  }
566
611
  async consume(message) {
567
612
  try {
@@ -569,9 +614,10 @@ var RunMQFailureLoggerProcessor = class {
569
614
  } catch (e) {
570
615
  this.logger.error(
571
616
  "Message processing failed",
572
- {
573
- message: message.message
574
- },
617
+ __spreadValues({
618
+ correlationId: message.correlationId,
619
+ messageId: message.id
620
+ }, this.logFullMessagePayload ? { message: message.message } : {}),
575
621
  e instanceof Error ? e.stack : void 0
576
622
  );
577
623
  throw e;
@@ -579,39 +625,24 @@ var RunMQFailureLoggerProcessor = class {
579
625
  }
580
626
  };
581
627
 
582
- // src/core/consumer/processors/RunMQBaseProcessor.ts
583
- var RunMQBaseProcessor = class {
584
- constructor(handler, processorConfig, serializer) {
585
- this.handler = handler;
586
- this.processorConfig = processorConfig;
587
- this.serializer = serializer;
628
+ // src/core/message/RunMQMessage.ts
629
+ var RunMQMessage = class {
630
+ static isValid(obj) {
631
+ if (typeof obj === "object" && obj !== null) {
632
+ 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";
633
+ }
634
+ return false;
588
635
  }
589
- async consume(message) {
590
- const rabbitMQMessage = this.serializer.deserialize(message.message, this.processorConfig);
591
- await this.handler(rabbitMQMessage);
592
- return true;
636
+ constructor(message, meta) {
637
+ this.message = message;
638
+ this.meta = meta;
593
639
  }
594
640
  };
595
-
596
- // src/core/consumer/processors/RunMQExceptionLoggerProcessor.ts
597
- var RunMQExceptionLoggerProcessor = class {
598
- constructor(consumer, logger) {
599
- this.consumer = consumer;
600
- this.logger = logger;
601
- }
602
- async consume(message) {
603
- try {
604
- return await this.consumer.consume(message);
605
- } catch (e) {
606
- if (e instanceof Error) {
607
- this.logger.error(e.message, e.stack);
608
- throw e;
609
- } else {
610
- const errorString = JSON.stringify(e);
611
- this.logger.error(errorString);
612
- throw new Error(errorString);
613
- }
614
- }
641
+ var RunMQMessageMeta = class {
642
+ constructor(id, publishedAt, correlationId) {
643
+ this.id = id;
644
+ this.correlationId = correlationId;
645
+ this.publishedAt = publishedAt;
615
646
  }
616
647
  };
617
648
 
@@ -620,6 +651,17 @@ import Ajv from "ajv";
620
651
  var AjvSchemaValidator = class {
621
652
  constructor() {
622
653
  this.lastValidator = null;
654
+ /**
655
+ * Cache of compiled validators, keyed by schema identity.
656
+ *
657
+ * `ajv.compile()` codegens an optimized JS function from the schema
658
+ * (typically 2-10ms for non-trivial schemas). Without this cache the
659
+ * compile would run on every message — at high throughput it dominates
660
+ * CPU usage.
661
+ *
662
+ * WeakMap so we don't pin schemas in memory if a processor is removed.
663
+ */
664
+ this.compiled = /* @__PURE__ */ new WeakMap();
623
665
  this.ajv = new Ajv({
624
666
  allErrors: true,
625
667
  verbose: true,
@@ -627,8 +669,14 @@ var AjvSchemaValidator = class {
627
669
  });
628
670
  }
629
671
  validate(schema, data) {
630
- this.lastValidator = this.ajv.compile(schema);
631
- return this.lastValidator(data);
672
+ const key = schema;
673
+ let validator = this.compiled.get(key);
674
+ if (!validator) {
675
+ validator = this.ajv.compile(schema);
676
+ this.compiled.set(key, validator);
677
+ }
678
+ this.lastValidator = validator;
679
+ return validator(data);
632
680
  }
633
681
  getError() {
634
682
  if (!this.lastValidator || !this.lastValidator.errors) {
@@ -714,18 +762,111 @@ var DefaultDeserializer = class {
714
762
  }
715
763
  };
716
764
 
765
+ // src/core/consumer/processors/RunMQSchemaFailureProcessor.ts
766
+ var RunMQSchemaFailureProcessor = class {
767
+ constructor(consumer, config, DLQPublisher, logger) {
768
+ this.consumer = consumer;
769
+ this.config = config;
770
+ this.DLQPublisher = DLQPublisher;
771
+ this.logger = logger;
772
+ }
773
+ async consume(message) {
774
+ try {
775
+ return await this.consumer.consume(message);
776
+ } catch (e) {
777
+ if (this.shouldRouteToDLQ(e)) {
778
+ this.logger.warn("Schema validation failed \u2014 routing message to DLQ.", {
779
+ correlationId: message.correlationId,
780
+ error: e instanceof RunMQSchemaValidationError ? e.error : void 0
781
+ });
782
+ this.routeToDLQ(message);
783
+ return true;
784
+ }
785
+ throw e;
786
+ }
787
+ }
788
+ shouldRouteToDLQ(e) {
789
+ var _a2;
790
+ if (!(e instanceof RunMQSchemaValidationError)) return false;
791
+ return ((_a2 = this.config.messageSchema) == null ? void 0 : _a2.failureStrategy) === "dlq";
792
+ }
793
+ routeToDLQ(message) {
794
+ const dlqMessage = new RabbitMQMessage(
795
+ this.extractOriginalPayload(message),
796
+ message.id,
797
+ message.correlationId,
798
+ message.channel,
799
+ message.amqpMessage,
800
+ message.headers
801
+ );
802
+ this.DLQPublisher.publish(
803
+ ConsumerCreatorUtils.getDLQTopicName(this.config.name),
804
+ dlqMessage
805
+ );
806
+ }
807
+ extractOriginalPayload(message) {
808
+ if (typeof message.message === "string") {
809
+ try {
810
+ const parsed = JSON.parse(message.message);
811
+ if (RunMQMessage.isValid(parsed)) {
812
+ return parsed.message;
813
+ }
814
+ } catch (e) {
815
+ }
816
+ }
817
+ return message.message;
818
+ }
819
+ };
820
+
821
+ // src/core/consumer/processors/RunMQBaseProcessor.ts
822
+ var RunMQBaseProcessor = class {
823
+ constructor(handler, processorConfig, serializer) {
824
+ this.handler = handler;
825
+ this.processorConfig = processorConfig;
826
+ this.serializer = serializer;
827
+ }
828
+ async consume(message) {
829
+ const rabbitMQMessage = this.serializer.deserialize(message.message, this.processorConfig);
830
+ await this.handler(rabbitMQMessage);
831
+ return true;
832
+ }
833
+ };
834
+
835
+ // src/core/consumer/processors/RunMQExceptionLoggerProcessor.ts
836
+ var RunMQExceptionLoggerProcessor = class {
837
+ constructor(consumer, logger) {
838
+ this.consumer = consumer;
839
+ this.logger = logger;
840
+ }
841
+ async consume(message) {
842
+ try {
843
+ return await this.consumer.consume(message);
844
+ } catch (e) {
845
+ if (e instanceof Error) {
846
+ this.logger.error(e.message, e.stack);
847
+ throw e;
848
+ } else {
849
+ const errorString = JSON.stringify(e);
850
+ this.logger.error(errorString);
851
+ throw new Error(errorString);
852
+ }
853
+ }
854
+ }
855
+ };
856
+
717
857
  // src/core/publisher/producers/RunMQFailureLoggerProducer.ts
718
858
  var RunMQFailureLoggerProducer = class {
719
859
  constructor(producer, logger) {
720
860
  this.producer = producer;
721
861
  this.logger = logger;
722
862
  }
723
- publish(topic, message) {
863
+ async publish(topic, message) {
724
864
  try {
725
- this.producer.publish(topic, message);
865
+ await this.producer.publish(topic, message);
726
866
  } catch (e) {
727
867
  this.logger.error("Message publishing failed", {
728
- message,
868
+ topic,
869
+ correlationId: message.correlationId,
729
870
  error: e instanceof Error ? e.message : JSON.stringify(e),
730
871
  stack: e instanceof Error ? e.stack : void 0
731
872
  });
@@ -740,7 +881,7 @@ var RunMQBaseProducer = class {
740
881
  this.serializer = serializer;
741
882
  this.exchange = exchange;
742
883
  }
743
- publish(topic, message) {
884
+ async publish(topic, message) {
744
885
  const runMQMessage = new RunMQMessage(
745
886
  message.message,
746
887
  new RunMQMessageMeta(
@@ -750,7 +891,7 @@ var RunMQBaseProducer = class {
750
891
  )
751
892
  );
752
893
  const serialized = this.serializer.serialize(runMQMessage);
753
- message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
894
+ await message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
754
895
  correlationId: message.correlationId,
755
896
  messageId: message.id,
756
897
  headers: message.headers,
@@ -1175,9 +1316,10 @@ var RunMQMetadataManager = class {
1175
1316
 
1176
1317
  // src/core/consumer/RunMQConsumerCreator.ts
1177
1318
  var RunMQConsumerCreator = class {
1178
- constructor(client, logger, managementConfig) {
1319
+ constructor(client, logger, managementConfig, logFullMessagePayload = false) {
1179
1320
  this.client = client;
1180
1321
  this.logger = logger;
1322
+ this.logFullMessagePayload = logFullMessagePayload;
1181
1323
  this.ttlPolicyManager = new RunMQTTLPolicyManager(logger, managementConfig);
1182
1324
  this.metadataManager = new RunMQMetadataManager(logger, managementConfig);
1183
1325
  }
@@ -1200,39 +1342,67 @@ var RunMQConsumerCreator = class {
1200
1342
  );
1201
1343
  }
1202
1344
  async runProcessor(consumerConfiguration) {
1203
- const consumerChannel = await this.getProcessorChannel();
1345
+ var _a2;
1346
+ const consumerChannel = await this.client.getChannel({
1347
+ onClose: () => {
1348
+ if (!this.client.isActive()) {
1349
+ return;
1350
+ }
1351
+ this.logger.warn("Consumer channel closed; attempting to re-subscribe", {
1352
+ processor: consumerConfiguration.processorConfig.name
1353
+ });
1354
+ this.resubscribeProcessor(consumerConfiguration);
1355
+ }
1356
+ });
1204
1357
  const DLQPublisher = new RunMQPublisherCreator(this.logger).createPublisher(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME);
1205
- await consumerChannel.prefetch(DEFAULTS.PREFETCH_COUNT);
1358
+ await consumerChannel.confirmSelect();
1359
+ const prefetchCount = (_a2 = consumerConfiguration.processorConfig.prefetch) != null ? _a2 : DEFAULTS.PREFETCH_COUNT;
1360
+ await consumerChannel.prefetch(prefetchCount);
1206
1361
  await consumerChannel.consume(consumerConfiguration.processorConfig.name, async (msg) => {
1207
- if (msg) {
1208
- const rabbitmqMessage = new RabbitMQMessage(
1209
- msg.content.toString(),
1210
- msg.properties.messageId,
1211
- msg.properties.correlationId,
1212
- consumerChannel,
1213
- msg,
1214
- msg.properties.headers
1215
- );
1216
- return new RunMQExceptionLoggerProcessor(
1362
+ if (!msg) return;
1363
+ const rabbitmqMessage = new RabbitMQMessage(
1364
+ msg.content.toString(),
1365
+ msg.properties.messageId,
1366
+ msg.properties.correlationId,
1367
+ consumerChannel,
1368
+ msg,
1369
+ msg.properties.headers
1370
+ );
1371
+ try {
1372
+ await new RunMQExceptionLoggerProcessor(
1217
1373
  new RunMQSucceededMessageAcknowledgerProcessor(
1218
1374
  new RunMQFailedMessageRejecterProcessor(
1219
1375
  new RunMQRetriesCheckerProcessor(
1220
1376
  new RunMQFailureLoggerProcessor(
1221
- new RunMQBaseProcessor(
1222
- consumerConfiguration.processor,
1377
+ new RunMQSchemaFailureProcessor(
1378
+ new RunMQBaseProcessor(
1379
+ consumerConfiguration.processor,
1380
+ consumerConfiguration.processorConfig,
1381
+ new DefaultDeserializer()
1382
+ ),
1223
1383
  consumerConfiguration.processorConfig,
1224
- new DefaultDeserializer()
1384
+ DLQPublisher,
1385
+ this.logger
1225
1386
  ),
1226
- this.logger
1387
+ this.logger,
1388
+ this.logFullMessagePayload
1227
1389
  ),
1228
1390
  consumerConfiguration.processorConfig,
1229
- DLQPublisher,
1230
- this.logger
1231
- )
1232
- )
1391
+ this.logger,
1392
+ this.logFullMessagePayload
1393
+ ),
1394
+ this.logger
1395
+ ),
1396
+ this.logger
1233
1397
  ),
1234
1398
  this.logger
1235
1399
  ).consume(rabbitmqMessage);
1400
+ } catch (e) {
1401
+ this.logger.error("Unhandled error in consumer chain", {
1402
+ correlationId: rabbitmqMessage.correlationId,
1403
+ cause: e instanceof Error ? e.message : String(e),
1404
+ stack: e instanceof Error ? e.stack : void 0
1405
+ });
1236
1406
  }
1237
1407
  });
1238
1408
  }
@@ -1301,8 +1471,20 @@ var RunMQConsumerCreator = class {
1301
1471
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name)
1302
1472
  );
1303
1473
  }
1304
- async getProcessorChannel() {
1305
- return await this.client.getChannel();
1474
+ resubscribeProcessor(consumerConfiguration) {
1475
+ const delay = DEFAULTS.RECONNECT_DELAY;
1476
+ setTimeout(() => {
1477
+ if (!this.client.isActive()) {
1478
+ return;
1479
+ }
1480
+ this.runProcessor(consumerConfiguration).catch((err) => {
1481
+ this.logger.error("Failed to re-subscribe consumer; will retry", {
1482
+ processor: consumerConfiguration.processorConfig.name,
1483
+ error: err instanceof Error ? err.message : err
1484
+ });
1485
+ this.resubscribeProcessor(consumerConfiguration);
1486
+ });
1487
+ }, delay);
1306
1488
  }
1307
1489
  };
1308
1490
 
@@ -1334,7 +1516,7 @@ var RunMQ = class _RunMQ {
1334
1516
  maxReconnectAttempts: (_b = config.maxReconnectAttempts) != null ? _b : DEFAULTS.MAX_RECONNECT_ATTEMPTS
1335
1517
  });
1336
1518
  this.client = new RabbitMQClientAdapter(this.config, this.logger);
1337
- this.consumer = new RunMQConsumerCreator(this.client, this.logger, this.config.management);
1519
+ this.consumer = new RunMQConsumerCreator(this.client, this.logger, this.config.management, this.config.logFullMessagePayload);
1338
1520
  }
1339
1521
  /**
1340
1522
  * Starts the RunMQ instance by establishing a connection to RabbitMQ and initializing necessary components.
@@ -1358,29 +1540,38 @@ var RunMQ = class _RunMQ {
1358
1540
  await this.consumer.createConsumer(new ConsumerConfiguration(topic, config, processor));
1359
1541
  }
1360
1542
  /**
1361
- * Publishes a message to the specified topic with an optional correlation ID
1543
+ * Publishes a message to the specified topic with an optional correlation ID.
1544
+ *
1545
+ * If publisher confirms are enabled (`usePublisherConfirms: true` in the
1546
+ * connection config), the returned promise resolves only after RabbitMQ
1547
+ * acknowledges the message; if the broker rejects, the promise rejects.
1548
+ * Otherwise it resolves once the message is flushed to the TCP socket
1549
+ * (fire-and-forget, no delivery guarantee — same behavior as before
1550
+ * publisher confirms were introduced).
1551
+ *
1362
1552
  * @param topic The name of the topic to publish the message to
1363
1553
  * @param message The message payload to be published
1364
1554
  * @param correlationId (Optional) A unique identifier for correlating messages; if not provided, a new UUID will be generated
1365
1555
  */
1366
- publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1367
- if (!this.publisher || !this.defaultChannel) {
1556
+ async publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1557
+ if (!this.publisher || !this.publishChannel) {
1368
1558
  throw new RunMQException(Exceptions.NOT_INITIALIZED, {});
1369
1559
  }
1370
1560
  RunMQUtils.assertRecord(message);
1371
- this.publisher.publish(
1561
+ const messageId = RunMQUtils.generateUUID();
1562
+ await this.publisher.publish(
1372
1563
  topic,
1373
1564
  RabbitMQMessage.from(
1374
1565
  message,
1375
- this.defaultChannel,
1376
- new RabbitMQMessageProperties(RunMQUtils.generateUUID(), correlationId)
1566
+ this.publishChannel,
1567
+ new RabbitMQMessageProperties(messageId, correlationId)
1377
1568
  )
1378
1569
  );
1379
- this.logger.info(`Published message`, {
1570
+ this.logger.info(`Published message`, __spreadValues({
1380
1571
  topic,
1381
1572
  correlationId,
1382
- message
1383
- });
1573
+ messageId
1574
+ }, this.config.logFullMessagePayload ? { message } : {}));
1384
1575
  }
1385
1576
  /**
1386
1577
  * Disconnects from RabbitMQ, handling any errors that may occur during the disconnection process.
@@ -1433,6 +1624,10 @@ var RunMQ = class _RunMQ {
1433
1624
  this.defaultChannel = await this.client.getDefaultChannel();
1434
1625
  await this.defaultChannel.assertExchange(Constants.ROUTER_EXCHANGE_NAME, "direct", { durable: true });
1435
1626
  await this.defaultChannel.assertExchange(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME, "direct", { durable: true });
1627
+ this.publishChannel = await this.client.getChannel();
1628
+ if (this.config.usePublisherConfirms !== false) {
1629
+ await this.publishChannel.confirmSelect();
1630
+ }
1436
1631
  this.publisher = new RunMQPublisherCreator(this.logger).createPublisher();
1437
1632
  }
1438
1633
  };