@initbit/nestjs-jetstream
Version:
NestJS custom NATS JetStream transport
151 lines • 6.04 kB
JavaScript
"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