UNPKG

@syntropylog/adapters

Version:
107 lines 4.45 kB
import * as amqplib from 'amqplib'; import { PayloadSerializer } from '../utils/PayloadSerializer'; export class RabbitMQAdapter { constructor(connectionString, exchangeName = 'topic_logs') { this.connection = null; this.channel = null; this.consumerTags = new Map(); this.connectionString = connectionString; this.exchangeName = exchangeName; } async connect() { this.connection = await amqplib.connect(this.connectionString); if (!this.connection) { throw new Error('Failed to connect to RabbitMQ'); } this.channel = await this.connection.createChannel(); if (!this.channel) { throw new Error('Failed to create RabbitMQ channel'); } await this.channel.assertExchange(this.exchangeName, 'topic', { durable: true }); } async disconnect() { try { // Cancel all active consumers first if (this.channel && this.consumerTags.size > 0) { for (const [topic, consumerTag] of this.consumerTags) { try { await this.channel.cancel(consumerTag); console.log(`✅ Cancelled consumer for topic: ${topic}`); } catch (error) { console.warn(`⚠️ Error cancelling consumer for topic ${topic}:`, error); } } this.consumerTags.clear(); } // Close channel if (this.channel) { await this.channel.close(); } // Close connection if (this.connection) { await this.connection.close(); } } catch (error) { console.error('Error during RabbitMQ disconnection:', error); } finally { this.channel = null; this.connection = null; } } async publish(topic, message) { if (!this.channel) { throw new Error('RabbitMQ channel is not available. Please connect first.'); } const routingKey = topic; const serializedPayload = PayloadSerializer.serializeForBroker(message); const content = Buffer.from(serializedPayload); const options = { headers: message.headers || {}, persistent: true, }; this.channel.publish(this.exchangeName, routingKey, content, options); } async subscribe(topic, handler) { if (!this.channel) { throw new Error('RabbitMQ channel is not available. Please connect first.'); } const q = await this.channel.assertQueue('', { exclusive: true }); await this.channel.bindQueue(q.queue, this.exchangeName, topic); const { consumerTag } = await this.channel.consume(q.queue, async (msg) => { if (msg && this.channel) { try { const brokerMessage = PayloadSerializer.createBrokerMessage(msg.content, msg.properties.headers); const ack = async () => this.channel.ack(msg); const nack = async (requeue = false) => this.channel.nack(msg, false, requeue); await handler(brokerMessage, { ack, nack }); } catch (err) { // If there's an error (e.g., JSON parsing), we can't process the message, // but we don't want to crash the whole service. We'll log it. // A more robust implementation might publish to a dead-letter queue. console.error(`Failed to process message from topic ${topic}`, err); // Nack the message to prevent infinite retries this.channel.nack(msg, false, false); } } }, { noAck: false }); this.consumerTags.set(topic, consumerTag); } async unsubscribe(topic) { if (!this.channel) { throw new Error('RabbitMQ channel is not available.'); } const consumerTag = this.consumerTags.get(topic); if (consumerTag) { await this.channel.cancel(consumerTag); this.consumerTags.delete(topic); } else { console.warn(`No active subscription found for topic: ${topic}`); } } } //# sourceMappingURL=RabbitMQAdapter.js.map