import consola from 'consola'; import { connect, JetStreamClient, KV, KvOptions, NatsConnection } from 'nats'; import { config } from '../config'; /** * Singleton NATS client. * Call connect() once during app startup (after the DB loader). * All other modules obtain the connection via getKV(). */ class NatsClient { private connection: NatsConnection | null = null; private js: JetStreamClient | null = null; private kvBuckets: Map = new Map(); /** * Establishes the NATS connection and JetStream context. * Must be called once before any KV operations. */ public async connect(): Promise { this.connection = await connect({ servers: config.nats_url }); this.js = this.connection.jetstream(); consola.success(`NATS connected to ${config.nats_url}`); } /** * Returns a KV bucket by name, creating it if it doesn't exist yet. * Results are cached — repeated calls with the same name return the same instance. */ public async getKV(bucketName: string, options?: Partial): Promise { if (this.kvBuckets.has(bucketName)) { return this.kvBuckets.get(bucketName); } if (!this.js) { throw new Error('NATS not connected. Call NatsClient.connect() first.'); } const kv = await this.js.views.kv(bucketName, options); this.kvBuckets.set(bucketName, kv); return kv; } /** * Gracefully closes the NATS connection. * Call during app shutdown if needed. */ public async disconnect(): Promise { if (this.connection) { await this.connection.drain(); this.connection = null; this.js = null; this.kvBuckets.clear(); consola.info('NATS disconnected.'); } } public isConnected(): boolean { return this.connection !== null; } } export default new NatsClient();