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.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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
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/
|
|
582
|
-
var
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
return true;
|
|
636
|
+
constructor(message, meta) {
|
|
637
|
+
this.message = message;
|
|
638
|
+
this.meta = meta;
|
|
592
639
|
}
|
|
593
640
|
};
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
this.
|
|
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
|
-
|
|
630
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
|
1220
|
-
|
|
1377
|
+
new RunMQSchemaFailureProcessor(
|
|
1378
|
+
new RunMQBaseProcessor(
|
|
1379
|
+
consumerConfiguration.processor,
|
|
1380
|
+
consumerConfiguration.processorConfig,
|
|
1381
|
+
new DefaultDeserializer()
|
|
1382
|
+
),
|
|
1221
1383
|
consumerConfiguration.processorConfig,
|
|
1222
|
-
|
|
1384
|
+
DLQPublisher,
|
|
1385
|
+
this.logger
|
|
1223
1386
|
),
|
|
1224
|
-
this.logger
|
|
1387
|
+
this.logger,
|
|
1388
|
+
this.logFullMessagePayload
|
|
1225
1389
|
),
|
|
1226
1390
|
consumerConfiguration.processorConfig,
|
|
1227
|
-
|
|
1228
|
-
this.
|
|
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
|
-
|
|
1303
|
-
|
|
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.
|
|
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
|
-
|
|
1561
|
+
const messageId = RunMQUtils.generateUUID();
|
|
1562
|
+
await this.publisher.publish(
|
|
1370
1563
|
topic,
|
|
1371
1564
|
RabbitMQMessage.from(
|
|
1372
1565
|
message,
|
|
1373
|
-
this.
|
|
1374
|
-
new RabbitMQMessageProperties(
|
|
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
|
-
|
|
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
|
};
|