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/README.md +105 -106
- package/dist/index.cjs +341 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -3
- package/dist/index.d.ts +46 -3
- package/dist/index.js +341 -144
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
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/
|
|
615
|
-
var
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
return true;
|
|
669
|
+
constructor(message, meta) {
|
|
670
|
+
this.message = message;
|
|
671
|
+
this.meta = meta;
|
|
625
672
|
}
|
|
626
673
|
};
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
this.
|
|
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
|
-
|
|
663
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
|
1253
|
-
|
|
1410
|
+
new RunMQSchemaFailureProcessor(
|
|
1411
|
+
new RunMQBaseProcessor(
|
|
1412
|
+
consumerConfiguration.processor,
|
|
1413
|
+
consumerConfiguration.processorConfig,
|
|
1414
|
+
new DefaultDeserializer()
|
|
1415
|
+
),
|
|
1254
1416
|
consumerConfiguration.processorConfig,
|
|
1255
|
-
|
|
1417
|
+
DLQPublisher,
|
|
1418
|
+
this.logger
|
|
1256
1419
|
),
|
|
1257
|
-
this.logger
|
|
1420
|
+
this.logger,
|
|
1421
|
+
this.logFullMessagePayload
|
|
1258
1422
|
),
|
|
1259
1423
|
consumerConfiguration.processorConfig,
|
|
1260
|
-
|
|
1261
|
-
this.
|
|
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
|
-
|
|
1336
|
-
|
|
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.
|
|
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
|
-
|
|
1594
|
+
const messageId = RunMQUtils.generateUUID();
|
|
1595
|
+
await this.publisher.publish(
|
|
1403
1596
|
topic,
|
|
1404
1597
|
RabbitMQMessage.from(
|
|
1405
1598
|
message,
|
|
1406
|
-
this.
|
|
1407
|
-
new RabbitMQMessageProperties(
|
|
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
|
-
|
|
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
|
};
|