UNPKG

@syntropylog/adapters

Version:
114 lines 4.78 kB
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