@syntropylog/adapters
Version:
External adapters for SyntropyLog framework
107 lines • 4.45 kB
JavaScript
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