@syntropylog/adapters
Version:
External adapters for SyntropyLog framework
114 lines • 4.78 kB
JavaScript
import { connect, JSONCodec, headers as NatsHeaders } from 'nats';
import { PayloadSerializer } from '../utils/PayloadSerializer';
export class NatsAdapter {
constructor(natsServers = ['nats://localhost:4222']) {
this.natsConnection = null;
this.codec = JSONCodec();
this.subscriptions = new Map();
this.natsServers = natsServers;
}
async connect() {
this.natsConnection = await connect({
servers: this.natsServers,
});
}
async disconnect() {
if (this.natsConnection) {
// Unsubscribe from all topics first
if (this.subscriptions.size > 0) {
for (const [topic, subscription] of this.subscriptions) {
try {
subscription.unsubscribe();
console.log(`✅ Cancelled NATS subscription for topic: ${topic}`);
}
catch (error) {
console.warn(`⚠️ Error cancelling subscription for topic ${topic}:`, error);
}
}
this.subscriptions.clear();
}
await this.natsConnection.drain();
this.natsConnection.close();
this.natsConnection = null;
}
}
async publish(topic, message) {
if (!this.natsConnection) {
throw new Error('NATS connection is not available. Please connect first.');
}
const serializedPayload = PayloadSerializer.serializeForBroker(message);
const natsHeaders = this.recordToNatsHeaders(message.headers);
await this.natsConnection.publish(topic, this.codec.encode(JSON.parse(serializedPayload)), { headers: natsHeaders });
}
async subscribe(topic, handler) {
if (!this.natsConnection) {
throw new Error('NATS connection is not available. Please connect first.');
}
const subscription = this.natsConnection.subscribe(topic);
(async () => {
for await (const msg of subscription) {
try {
// Decode the JSON payload from NATS
const decodedPayload = this.codec.decode(msg.data);
const headers = this.natsHeadersToRecord(msg.headers);
const brokerMessage = PayloadSerializer.createBrokerMessage(Buffer.from(JSON.stringify(decodedPayload)), headers);
const controls = {
ack: async () => {
// NATS doesn't require explicit ack for most use cases
// but we can implement it if needed
},
nack: async () => {
// NATS doesn't have a built-in nack mechanism
// but we can implement custom logic if needed
console.log(`NACK received for message on topic ${topic}.`);
},
};
await handler(brokerMessage, controls);
}
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);
}
}
})().catch(console.error);
this.subscriptions.set(topic, subscription);
}
async unsubscribe(topic) {
if (!this.natsConnection) {
throw new Error('NATS connection is not available.');
}
const subscription = this.subscriptions.get(topic);
if (subscription) {
subscription.unsubscribe();
this.subscriptions.delete(topic);
console.log(`✅ Unsubscribed from NATS topic: ${topic}`);
}
else {
console.warn(`No active subscription found for topic: ${topic}`);
}
}
natsHeadersToRecord(natsHeaders) {
if (!natsHeaders) {
return undefined;
}
const record = {};
// NATS headers are iterable but don't have .entries() method
for (const [key, value] of natsHeaders) {
record[key] = value;
}
return record;
}
recordToNatsHeaders(record) {
if (!record) {
return undefined;
}
const natsHeaders = NatsHeaders();
for (const [key, value] of Object.entries(record)) {
natsHeaders.set(key, String(value));
}
return natsHeaders;
}
}
//# sourceMappingURL=NatsAdapter.js.map