rabbitmq-enterprise-toolkit
Version:
🚀 Enterprise-grade RabbitMQ wrapper for Node.js & TypeScript - High-throughput messaging with automatic retry, DLQ, batch processing & graceful shutdown. Production-ready AMQP client with zero message loss guarantee.
195 lines • 7.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Publisher = void 0;
const errors_1 = require("../utils/errors");
const helpers_1 = require("../utils/helpers");
class Publisher {
constructor(getChannel) {
this.getChannel = getChannel;
this.publishBuffer = [];
this.batchTimer = null;
this.BATCH_SIZE = 50; // Batch boyutunu düşür
this.BATCH_TIMEOUT = 100; // Timeout'u artır
this.assertedQueues = new Set(); // Cache for asserted queues
}
async publish(queueName, data, options = {}) {
// High-throughput için batch publishing
if (options.batch !== false) {
return this.batchPublish(queueName, data, options);
}
// Immediate publishing için
return this.immediatePublish(queueName, data, options);
}
async batchPublish(queueName, data, options) {
this.publishBuffer.push({ queueName, data, options });
// Batch size dolduğunda hemen gönder
if (this.publishBuffer.length >= this.BATCH_SIZE) {
await this.flushBatch();
return;
}
// Timer ile batch timeout
if (!this.batchTimer) {
this.batchTimer = setTimeout(async () => {
await this.flushBatch();
}, this.BATCH_TIMEOUT);
}
}
async flushBatch() {
if (this.publishBuffer.length === 0)
return;
const batch = [...this.publishBuffer];
this.publishBuffer = [];
if (this.batchTimer) {
clearTimeout(this.batchTimer);
this.batchTimer = null;
}
try {
// Batch'i sıralı olarak gönder
for (const item of batch) {
try {
await this.immediatePublish(item.queueName, item.data, item.options);
}
catch (error) {
console.error(`Failed to publish single message in batch:`, (0, helpers_1.formatError)(error));
// Tek mesaj hatası batch'i durdurmaz, devam et
}
}
}
catch (error) {
throw new errors_1.PublishError('Batch publish failed', {
batchSize: batch.length,
error: (0, helpers_1.formatError)(error)
});
}
}
async immediatePublish(queueName, data, options) {
const channel = this.getChannel();
const messageId = options.messageId || (0, helpers_1.generateMessageId)();
const timestamp = options.timestamp || Date.now();
const message = {
id: messageId,
content: data,
timestamp
};
const publishOptions = {
persistent: options.persistent !== false,
messageId,
timestamp,
deliveryMode: 2, // Persistent
expiration: options.expiration,
priority: options.priority
};
try {
const queueNameNormalized = (0, helpers_1.normalizeQueueName)(queueName);
const result = channel.sendToQueue(queueNameNormalized, Buffer.from(JSON.stringify(message)), publishOptions);
if (!result) {
// Queue dolu ise kısa bekle ve tekrar dene
await new Promise(resolve => setTimeout(resolve, 10));
const retryResult = channel.sendToQueue(queueNameNormalized, Buffer.from(JSON.stringify(message)), publishOptions);
if (!retryResult) {
throw new errors_1.PublishError('Message could not be queued - queue is full even after retry');
}
}
}
catch (error) {
throw new errors_1.PublishError('Failed to publish message', {
queueName,
messageId,
error: (0, helpers_1.formatError)(error)
});
}
}
// Safe queue assertion for publisher-only mode
async assertQueue(options) {
const channel = this.getChannel();
const queueNameNormalized = (0, helpers_1.normalizeQueueName)(options.queueName);
// Skip if already asserted and no force flag
if (this.assertedQueues.has(queueNameNormalized)) {
return;
}
try {
if (options.skipQueueAssert) {
// Just add to cache without asserting
this.assertedQueues.add(queueNameNormalized);
return;
}
if (options.createQueue !== false) {
// Minimal queue assertion for publisher
await channel.assertQueue(queueNameNormalized, {
durable: options.durable !== false // Default true
});
}
this.assertedQueues.add(queueNameNormalized);
console.log(`Queue asserted for publishing: ${queueNameNormalized}`);
}
catch (error) {
// If assertion fails, try passive declaration (check if exists)
try {
await channel.checkQueue(queueNameNormalized);
this.assertedQueues.add(queueNameNormalized);
console.log(`Queue exists and ready for publishing: ${queueNameNormalized}`);
}
catch (checkError) {
throw new errors_1.PublishError(`Failed to assert or check queue ${queueNameNormalized}`, {
originalError: (0, helpers_1.formatError)(error),
checkError: (0, helpers_1.formatError)(checkError)
});
}
}
}
// Check queue status without assertion
async checkQueue(queueName) {
const channel = this.getChannel();
const queueNameNormalized = (0, helpers_1.normalizeQueueName)(queueName);
try {
const result = await channel.checkQueue(queueNameNormalized);
return {
messageCount: result.messageCount,
consumerCount: result.consumerCount
};
}
catch (error) {
throw new errors_1.PublishError(`Failed to check queue ${queueNameNormalized}`, {
error: (0, helpers_1.formatError)(error)
});
}
}
// Publish to exchange with routing key
async publishToExchange(exchange, routingKey, data, options = {}) {
const channel = this.getChannel();
const messageId = options.messageId || (0, helpers_1.generateMessageId)();
const timestamp = options.timestamp || Date.now();
const message = {
id: messageId,
content: data,
timestamp
};
const publishOptions = {
persistent: options.persistent !== false,
messageId,
timestamp,
deliveryMode: 2,
expiration: options.expiration,
priority: options.priority
};
try {
channel.publish(exchange, routingKey, Buffer.from(JSON.stringify(message)), publishOptions);
}
catch (error) {
throw new errors_1.PublishError('Failed to publish to exchange', {
exchange,
routingKey,
messageId,
error: (0, helpers_1.formatError)(error)
});
}
}
// Graceful shutdown için batch'i temizle
async flush() {
if (this.publishBuffer.length > 0) {
await this.flushBatch();
}
}
}
exports.Publisher = Publisher;
//# sourceMappingURL=publisher.js.map