UNPKG

@initbit/nestjs-jetstream

Version:
151 lines 6.04 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NatsClient = void 0; const microservices_1 = require("@nestjs/microservices"); const common_1 = require("@nestjs/common"); const nats_1 = require("nats"); const rxjs_1 = require("rxjs"); class NatsClient extends microservices_1.ClientProxy { constructor(options = {}) { super(); this.options = options; this.codec = options.codec || (0, nats_1.JSONCodec)(); this.logger = options.logger || new common_1.Logger(this.constructor.name); } async connect() { if (this.connection) { return this.connection; } this.connection = await this.createNatsConnection(this.options.connection); this.jetstreamClient = this.createJetStreamClient(this.connection); this.logger.log(`Connected to ${this.connection.getServer()}`); this.handleStatusUpdates(this.connection); return this.connection; } async close() { if (this.connection) { await this.connection.drain(); this.connection = undefined; this.jetstreamClient = undefined; } } createJetStreamClient(connection) { return connection.jetstream(); } createNatsConnection(options = {}) { const servers = Array.isArray(options.servers) ? options.servers.join(', ') : options.servers || 'nats://localhost'; this.logger.log(`Connecting to NATS JetStream (${servers})...`); return (0, nats_1.connect)(options); } getConnection() { return this.connection; } getJetStreamClient() { return this.jetstreamClient; } async handleStatusUpdates(connection) { for await (const status of connection.status()) { const data = typeof status.data === 'object' ? JSON.stringify(status.data) : status.data; const message = `(${status.type}): ${data}`; switch (status.type) { case 'pingTimer': case 'reconnecting': case 'staleConnection': this.logger?.debug?.(message); break; case 'disconnect': case 'error': this.logger.error(message); break; case 'reconnect': this.logger.log(message); break; case 'ldm': this.logger.warn(message); break; case 'update': this.logger?.verbose?.(message); break; } } } // eslint-disable-next-line @typescript-eslint/no-explicit-any async dispatchEvent(packet) { // Ensure connection is established if (!this.connection) { this.logger.log('Connecting to NATS before dispatching event...'); await this.connect(); } if (!this.jetstreamClient) { this.logger.log('Initializing JetStream client...'); this.jetstreamClient = this.createJetStreamClient(this.connection); } // Throw error if a jetstream client is still undefined after an initialization attempt if (!this.jetstreamClient) { throw new Error('JetStream client is undefined'); } const payload = this.codec.encode(packet.data); const subject = this.normalizePattern(packet.pattern); // Add retry logic with exponential backoff const maxRetries = 3; let retryCount = 0; let lastError = null; while (retryCount < maxRetries) { try { await this.jetstreamClient.publish(subject, payload); this.logger.log(`Event successfully published to ${subject}`); return; } catch (error) { lastError = error; retryCount++; if (error.code === '503') { this.logger.warn(`JetStream service unavailable (503), retry ${retryCount}/${maxRetries}`); // Attempt to reconnect try { this.logger.log('Attempting to reconnect to JetStream...'); await this.close(); await this.connect(); this.jetstreamClient = this.createJetStreamClient(this.connection); } catch (reconnectError) { this.logger.error(`Failed to reconnect: ${reconnectError.message}`); } // Wait before retrying with exponential backoff const delay = Math.pow(2, retryCount) * 100; await new Promise(resolve => setTimeout(resolve, delay)); } else { this.logger.error(`Error publishing event: ${error.message}`); throw error; // For non-503 errors, don't retry } } } // If we've exhausted all retries if (lastError) { this.logger.error(`Failed to publish event after ${maxRetries} retries: ${lastError.message}`); throw lastError; } } publish(packet, callback) { if (!this.connection) { throw new Error('NATS not connected!'); } const payload = this.codec.encode(packet.data); const subject = this.normalizePattern(packet.pattern); this.connection .request(subject, payload) .then((encoded) => this.codec.decode(encoded.data)) .then((packet) => callback(packet)) .catch((err) => callback({ err })); // No teardown function needed as the subscription is handled for us, so return noop return rxjs_1.noop; } unwrap(value) { return value; } } exports.NatsClient = NatsClient; //# sourceMappingURL=nats.client.js.map