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.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();
@@ -264,7 +268,8 @@ var RabbitMQClientAdapter = class {
264
268
  // Disable automatic retries - we handle retries at RunMQ level
265
269
  retryLow: 100,
266
270
  retryHigh: 200,
267
- connectionTimeout: 5e3
271
+ connectionTimeout: 5e3,
272
+ noDelay: true
268
273
  });
269
274
  this.connection.on("error", (err) => {
270
275
  this.logger.error("RabbitMQ connection error:", { error: err });
@@ -299,21 +304,52 @@ var RabbitMQClientAdapter = class {
299
304
  );
300
305
  }
301
306
  }
302
- async getChannel() {
307
+ async getChannel(callbacks) {
303
308
  const connection = await this.connect();
304
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
+ });
305
336
  this.acquiredChannels.push(rawChannel);
306
337
  return new RabbitMQClientChannel(rawChannel);
307
338
  }
308
339
  async getDefaultChannel() {
309
340
  if (!this.defaultChannel) {
310
- this.defaultChannel = await this.getChannel();
341
+ this.defaultChannel = await this.getChannel({
342
+ onClose: () => {
343
+ this.defaultChannel = void 0;
344
+ }
345
+ });
311
346
  }
312
347
  return this.defaultChannel;
313
348
  }
314
349
  async disconnect() {
315
350
  const conn = this.connection;
316
351
  const channels = this.acquiredChannels;
352
+ this.isShuttingDown = true;
317
353
  this.connection = void 0;
318
354
  this.defaultChannel = void 0;
319
355
  this.isConnected = false;
@@ -386,20 +422,33 @@ var RabbitMQMessage = class _RabbitMQMessage {
386
422
  this.headers = headers;
387
423
  }
388
424
  /**
389
- * 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.
390
430
  */
391
431
  ack() {
392
- if (this.amqpMessage) {
432
+ if (!this.amqpMessage) return false;
433
+ try {
393
434
  this.channel.ack(this.amqpMessage);
435
+ return true;
436
+ } catch (e) {
437
+ return false;
394
438
  }
395
439
  }
396
440
  /**
397
- * 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.
398
443
  * @param requeue - Whether to requeue the message (default: false)
399
444
  */
400
445
  nack(requeue = false) {
401
- if (this.amqpMessage) {
446
+ if (!this.amqpMessage) return false;
447
+ try {
402
448
  this.channel.nack(this.amqpMessage, false, requeue);
449
+ return true;
450
+ } catch (e) {
451
+ return false;
403
452
  }
404
453
  }
405
454
  static from(messageData, channel, props, amqpMessage = null) {
@@ -416,13 +465,20 @@ var RabbitMQMessage = class _RabbitMQMessage {
416
465
 
417
466
  // src/core/consumer/processors/RunMQSucceededMessageAcknowledgerProcessor.ts
418
467
  var RunMQSucceededMessageAcknowledgerProcessor = class {
419
- constructor(consumer) {
468
+ constructor(consumer, logger) {
420
469
  this.consumer = consumer;
470
+ this.logger = logger;
421
471
  }
422
472
  async consume(message) {
473
+ var _a2;
423
474
  const result = await this.consumer.consume(message);
424
475
  if (result) {
425
- 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
+ }
426
482
  }
427
483
  return result;
428
484
  }
@@ -430,40 +486,26 @@ var RunMQSucceededMessageAcknowledgerProcessor = class {
430
486
 
431
487
  // src/core/consumer/processors/RunMQFailedMessageRejecterProcessor.ts
432
488
  var RunMQFailedMessageRejecterProcessor = class {
433
- constructor(consumer) {
489
+ constructor(consumer, logger) {
434
490
  this.consumer = consumer;
491
+ this.logger = logger;
435
492
  }
436
493
  async consume(message) {
494
+ var _a2;
437
495
  try {
438
496
  return await this.consumer.consume(message);
439
497
  } catch (e) {
440
- 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
+ }
441
504
  return false;
442
505
  }
443
506
  }
444
507
  };
445
508
 
446
- // src/core/message/RunMQMessage.ts
447
- var RunMQMessage = class {
448
- static isValid(obj) {
449
- if (typeof obj === "object" && obj !== null) {
450
- 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";
451
- }
452
- return false;
453
- }
454
- constructor(message, meta) {
455
- this.message = message;
456
- this.meta = meta;
457
- }
458
- };
459
- var RunMQMessageMeta = class {
460
- constructor(id, publishedAt, correlationId) {
461
- this.id = id;
462
- this.correlationId = correlationId;
463
- this.publishedAt = publishedAt;
464
- }
465
- };
466
-
467
509
  // src/core/consumer/ConsumerCreatorUtils.ts
468
510
  var ConsumerCreatorUtils = class {
469
511
  static getDLQTopicName(topic) {
@@ -480,11 +522,11 @@ var ConsumerCreatorUtils = class {
480
522
  // src/core/consumer/processors/RunMQRetriesCheckerProcessor.ts
481
523
  var _a;
482
524
  var RunMQRetriesCheckerProcessor = class {
483
- constructor(consumer, config, DLQPublisher, logger) {
525
+ constructor(consumer, config, logger, logFullMessagePayload = false) {
484
526
  this.consumer = consumer;
485
527
  this.config = config;
486
- this.DLQPublisher = DLQPublisher;
487
528
  this.logger = logger;
529
+ this.logFullMessagePayload = logFullMessagePayload;
488
530
  this.maxAttempts = (_a = this.config.attempts) != null ? _a : DEFAULTS.PROCESSING_ATTEMPTS;
489
531
  }
490
532
  async consume(message) {
@@ -493,7 +535,16 @@ var RunMQRetriesCheckerProcessor = class {
493
535
  } catch (e) {
494
536
  if (this.hasReachedMaxRetries(message)) {
495
537
  this.logMaxRetriesReached(message);
496
- 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
+ }
497
548
  this.acknowledgeMessage(message);
498
549
  return false;
499
550
  }
@@ -507,44 +558,38 @@ var RunMQRetriesCheckerProcessor = class {
507
558
  logMaxRetriesReached(message) {
508
559
  this.logger.error(
509
560
  `Message reached maximum attempts. Moving to dead-letter queue.`,
510
- {
511
- message: message.message,
561
+ __spreadValues({
562
+ correlationId: message.correlationId,
563
+ messageId: message.id,
512
564
  attempts: this.getRejectionCount(message),
513
565
  max: this.maxAttempts
514
- }
566
+ }, this.logFullMessagePayload ? { message: message.message } : {})
515
567
  );
516
568
  }
517
- moveToFinalDeadLetter(message) {
518
- const originalPayload = this.extractOriginalPayload(message);
519
- const dlqMessage = new RabbitMQMessage(
520
- originalPayload,
521
- message.id,
522
- message.correlationId,
523
- message.channel,
524
- message.amqpMessage,
525
- message.headers
526
- );
527
- this.DLQPublisher.publish(ConsumerCreatorUtils.getDLQTopicName(this.config.name), dlqMessage);
528
- }
529
- extractOriginalPayload(message) {
530
- if (typeof message.message === "string") {
531
- try {
532
- const parsed = JSON.parse(message.message);
533
- if (RunMQMessage.isValid(parsed)) {
534
- return parsed.message;
535
- }
536
- } 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
537
584
  }
538
- }
539
- return message.message;
585
+ );
540
586
  }
541
587
  acknowledgeMessage(message) {
542
- try {
543
- message.ack();
544
- } catch (e) {
545
- const error = new Error("A message acknowledge failed after publishing to final dead letter");
546
- this.logger.error(error.message, { cause: e instanceof Error ? e.message : String(e) });
547
- 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
+ });
548
593
  }
549
594
  }
550
595
  getRejectionCount(message) {
@@ -558,9 +603,10 @@ var RunMQRetriesCheckerProcessor = class {
558
603
 
559
604
  // src/core/consumer/processors/RunMQFailureLoggerProcessor.ts
560
605
  var RunMQFailureLoggerProcessor = class {
561
- constructor(consumer, logger) {
606
+ constructor(consumer, logger, logFullMessagePayload = false) {
562
607
  this.consumer = consumer;
563
608
  this.logger = logger;
609
+ this.logFullMessagePayload = logFullMessagePayload;
564
610
  }
565
611
  async consume(message) {
566
612
  try {
@@ -568,9 +614,10 @@ var RunMQFailureLoggerProcessor = class {
568
614
  } catch (e) {
569
615
  this.logger.error(
570
616
  "Message processing failed",
571
- {
572
- message: message.message
573
- },
617
+ __spreadValues({
618
+ correlationId: message.correlationId,
619
+ messageId: message.id
620
+ }, this.logFullMessagePayload ? { message: message.message } : {}),
574
621
  e instanceof Error ? e.stack : void 0
575
622
  );
576
623
  throw e;
@@ -578,39 +625,24 @@ var RunMQFailureLoggerProcessor = class {
578
625
  }
579
626
  };
580
627
 
581
- // src/core/consumer/processors/RunMQBaseProcessor.ts
582
- var RunMQBaseProcessor = class {
583
- constructor(handler, processorConfig, serializer) {
584
- this.handler = handler;
585
- this.processorConfig = processorConfig;
586
- 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;
587
635
  }
588
- async consume(message) {
589
- const rabbitMQMessage = this.serializer.deserialize(message.message, this.processorConfig);
590
- await this.handler(rabbitMQMessage);
591
- return true;
636
+ constructor(message, meta) {
637
+ this.message = message;
638
+ this.meta = meta;
592
639
  }
593
640
  };
594
-
595
- // src/core/consumer/processors/RunMQExceptionLoggerProcessor.ts
596
- var RunMQExceptionLoggerProcessor = class {
597
- constructor(consumer, logger) {
598
- this.consumer = consumer;
599
- this.logger = logger;
600
- }
601
- async consume(message) {
602
- try {
603
- return await this.consumer.consume(message);
604
- } catch (e) {
605
- if (e instanceof Error) {
606
- this.logger.error(e.message, e.stack);
607
- throw e;
608
- } else {
609
- const errorString = JSON.stringify(e);
610
- this.logger.error(errorString);
611
- throw new Error(errorString);
612
- }
613
- }
641
+ var RunMQMessageMeta = class {
642
+ constructor(id, publishedAt, correlationId) {
643
+ this.id = id;
644
+ this.correlationId = correlationId;
645
+ this.publishedAt = publishedAt;
614
646
  }
615
647
  };
616
648
 
@@ -619,6 +651,17 @@ import Ajv from "ajv";
619
651
  var AjvSchemaValidator = class {
620
652
  constructor() {
621
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();
622
665
  this.ajv = new Ajv({
623
666
  allErrors: true,
624
667
  verbose: true,
@@ -626,8 +669,14 @@ var AjvSchemaValidator = class {
626
669
  });
627
670
  }
628
671
  validate(schema, data) {
629
- this.lastValidator = this.ajv.compile(schema);
630
- 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);
631
680
  }
632
681
  getError() {
633
682
  if (!this.lastValidator || !this.lastValidator.errors) {
@@ -713,18 +762,111 @@ var DefaultDeserializer = class {
713
762
  }
714
763
  };
715
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
+
716
857
  // src/core/publisher/producers/RunMQFailureLoggerProducer.ts
717
858
  var RunMQFailureLoggerProducer = class {
718
859
  constructor(producer, logger) {
719
860
  this.producer = producer;
720
861
  this.logger = logger;
721
862
  }
722
- publish(topic, message) {
863
+ async publish(topic, message) {
723
864
  try {
724
- this.producer.publish(topic, message);
865
+ await this.producer.publish(topic, message);
725
866
  } catch (e) {
726
867
  this.logger.error("Message publishing failed", {
727
- message,
868
+ topic,
869
+ correlationId: message.correlationId,
728
870
  error: e instanceof Error ? e.message : JSON.stringify(e),
729
871
  stack: e instanceof Error ? e.stack : void 0
730
872
  });
@@ -739,7 +881,7 @@ var RunMQBaseProducer = class {
739
881
  this.serializer = serializer;
740
882
  this.exchange = exchange;
741
883
  }
742
- publish(topic, message) {
884
+ async publish(topic, message) {
743
885
  const runMQMessage = new RunMQMessage(
744
886
  message.message,
745
887
  new RunMQMessageMeta(
@@ -749,10 +891,11 @@ var RunMQBaseProducer = class {
749
891
  )
750
892
  );
751
893
  const serialized = this.serializer.serialize(runMQMessage);
752
- message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
894
+ await message.channel.publish(this.exchange, topic, Buffer.from(serialized), {
753
895
  correlationId: message.correlationId,
754
896
  messageId: message.id,
755
- headers: message.headers
897
+ headers: message.headers,
898
+ persistent: true
756
899
  });
757
900
  }
758
901
  };
@@ -1173,9 +1316,10 @@ var RunMQMetadataManager = class {
1173
1316
 
1174
1317
  // src/core/consumer/RunMQConsumerCreator.ts
1175
1318
  var RunMQConsumerCreator = class {
1176
- constructor(client, logger, managementConfig) {
1319
+ constructor(client, logger, managementConfig, logFullMessagePayload = false) {
1177
1320
  this.client = client;
1178
1321
  this.logger = logger;
1322
+ this.logFullMessagePayload = logFullMessagePayload;
1179
1323
  this.ttlPolicyManager = new RunMQTTLPolicyManager(logger, managementConfig);
1180
1324
  this.metadataManager = new RunMQMetadataManager(logger, managementConfig);
1181
1325
  }
@@ -1198,39 +1342,67 @@ var RunMQConsumerCreator = class {
1198
1342
  );
1199
1343
  }
1200
1344
  async runProcessor(consumerConfiguration) {
1201
- 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
+ });
1202
1357
  const DLQPublisher = new RunMQPublisherCreator(this.logger).createPublisher(Constants.DEAD_LETTER_ROUTER_EXCHANGE_NAME);
1203
- 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);
1204
1361
  await consumerChannel.consume(consumerConfiguration.processorConfig.name, async (msg) => {
1205
- if (msg) {
1206
- const rabbitmqMessage = new RabbitMQMessage(
1207
- msg.content.toString(),
1208
- msg.properties.messageId,
1209
- msg.properties.correlationId,
1210
- consumerChannel,
1211
- msg,
1212
- msg.properties.headers
1213
- );
1214
- 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(
1215
1373
  new RunMQSucceededMessageAcknowledgerProcessor(
1216
1374
  new RunMQFailedMessageRejecterProcessor(
1217
1375
  new RunMQRetriesCheckerProcessor(
1218
1376
  new RunMQFailureLoggerProcessor(
1219
- new RunMQBaseProcessor(
1220
- consumerConfiguration.processor,
1377
+ new RunMQSchemaFailureProcessor(
1378
+ new RunMQBaseProcessor(
1379
+ consumerConfiguration.processor,
1380
+ consumerConfiguration.processorConfig,
1381
+ new DefaultDeserializer()
1382
+ ),
1221
1383
  consumerConfiguration.processorConfig,
1222
- new DefaultDeserializer()
1384
+ DLQPublisher,
1385
+ this.logger
1223
1386
  ),
1224
- this.logger
1387
+ this.logger,
1388
+ this.logFullMessagePayload
1225
1389
  ),
1226
1390
  consumerConfiguration.processorConfig,
1227
- DLQPublisher,
1228
- this.logger
1229
- )
1230
- )
1391
+ this.logger,
1392
+ this.logFullMessagePayload
1393
+ ),
1394
+ this.logger
1395
+ ),
1396
+ this.logger
1231
1397
  ),
1232
1398
  this.logger
1233
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
+ });
1234
1406
  }
1235
1407
  });
1236
1408
  }
@@ -1299,8 +1471,20 @@ var RunMQConsumerCreator = class {
1299
1471
  ConsumerCreatorUtils.getDLQTopicName(consumerConfiguration.processorConfig.name)
1300
1472
  );
1301
1473
  }
1302
- async getProcessorChannel() {
1303
- 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);
1304
1488
  }
1305
1489
  };
1306
1490
 
@@ -1332,7 +1516,7 @@ var RunMQ = class _RunMQ {
1332
1516
  maxReconnectAttempts: (_b = config.maxReconnectAttempts) != null ? _b : DEFAULTS.MAX_RECONNECT_ATTEMPTS
1333
1517
  });
1334
1518
  this.client = new RabbitMQClientAdapter(this.config, this.logger);
1335
- 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);
1336
1520
  }
1337
1521
  /**
1338
1522
  * Starts the RunMQ instance by establishing a connection to RabbitMQ and initializing necessary components.
@@ -1356,29 +1540,38 @@ var RunMQ = class _RunMQ {
1356
1540
  await this.consumer.createConsumer(new ConsumerConfiguration(topic, config, processor));
1357
1541
  }
1358
1542
  /**
1359
- * 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
+ *
1360
1552
  * @param topic The name of the topic to publish the message to
1361
1553
  * @param message The message payload to be published
1362
1554
  * @param correlationId (Optional) A unique identifier for correlating messages; if not provided, a new UUID will be generated
1363
1555
  */
1364
- publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1365
- if (!this.publisher || !this.defaultChannel) {
1556
+ async publish(topic, message, correlationId = RunMQUtils.generateUUID()) {
1557
+ if (!this.publisher || !this.publishChannel) {
1366
1558
  throw new RunMQException(Exceptions.NOT_INITIALIZED, {});
1367
1559
  }
1368
1560
  RunMQUtils.assertRecord(message);
1369
- this.publisher.publish(
1561
+ const messageId = RunMQUtils.generateUUID();
1562
+ await this.publisher.publish(
1370
1563
  topic,
1371
1564
  RabbitMQMessage.from(
1372
1565
  message,
1373
- this.defaultChannel,
1374
- new RabbitMQMessageProperties(RunMQUtils.generateUUID(), correlationId)
1566
+ this.publishChannel,
1567
+ new RabbitMQMessageProperties(messageId, correlationId)
1375
1568
  )
1376
1569
  );
1377
- this.logger.info(`Published message`, {
1570
+ this.logger.info(`Published message`, __spreadValues({
1378
1571
  topic,
1379
1572
  correlationId,
1380
- message
1381
- });
1573
+ messageId
1574
+ }, this.config.logFullMessagePayload ? { message } : {}));
1382
1575
  }
1383
1576
  /**
1384
1577
  * Disconnects from RabbitMQ, handling any errors that may occur during the disconnection process.
@@ -1431,6 +1624,10 @@ var RunMQ = class _RunMQ {
1431
1624
  this.defaultChannel = await this.client.getDefaultChannel();
1432
1625
  await this.defaultChannel.assertExchange(Constants.ROUTER_EXCHANGE_NAME, "direct", { durable: true });
1433
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
+ }
1434
1631
  this.publisher = new RunMQPublisherCreator(this.logger).createPublisher();
1435
1632
  }
1436
1633
  };