@nestjs/microservices
Version:
Nest - modern, fast, powerful node.js web framework (@microservices)
208 lines (207 loc) • 8.32 kB
JavaScript
import { Logger } from '@nestjs/common';
import { isObject, loadPackageSync } from '@nestjs/common/internal';
import { EventEmitter } from 'events';
import { createRequire } from 'module';
import { NATS_DEFAULT_URL } from '../constants.js';
import { NatsResponseJSONDeserializer } from '../deserializers/nats-response-json.deserializer.js';
import { EmptyResponseException } from '../errors/empty-response.exception.js';
import { NatsRecordSerializer } from '../serializers/nats-record.serializer.js';
import { ClientProxy } from './client-proxy.js';
let natsPackage = {};
/**
* @publicApi
*/
export class ClientNats extends ClientProxy {
options;
logger = new Logger(ClientNats.name);
natsClient = null;
connectionPromise = null;
statusEventEmitter = new EventEmitter();
constructor(options) {
super();
this.options = options;
natsPackage = loadPackageSync('@nats-io/transport-node', ClientNats.name, () => createRequire(import.meta.url)('@nats-io/transport-node'));
this.initializeSerializer(options);
this.initializeDeserializer(options);
}
async close() {
await this.natsClient?.close();
this.statusEventEmitter.removeAllListeners();
this.natsClient = null;
this.connectionPromise = null;
}
async connect() {
if (this.connectionPromise) {
return this.connectionPromise;
}
this.connectionPromise = this.createClient();
this.natsClient = await this.connectionPromise.catch(err => {
this.connectionPromise = null;
throw err;
});
this._status$.next("connected" /* NatsStatus.CONNECTED */);
void this.handleStatusUpdates(this.natsClient);
return this.natsClient;
}
async createClient() {
// Eagerly initialize serializer/deserializer so they can be used synchronously
if (this.serializer &&
typeof this.serializer.init === 'function') {
await this.serializer.init();
}
if (this.deserializer &&
typeof this.deserializer.init === 'function') {
await this.deserializer.init();
}
const options = this.options || {};
return natsPackage.connect({
servers: NATS_DEFAULT_URL,
...options,
});
}
async handleStatusUpdates(client) {
for await (const status of client.status()) {
switch (status.type) {
case 'error':
this.logger.error(`NatsError: type: "${status.type}", error: "${status.error}".`);
break;
case 'disconnect':
this.connectionPromise = Promise.reject('Error: Connection lost. Trying to reconnect...');
// Prevent unhandled promise rejection
this.connectionPromise.catch(() => { });
this.logger.error(`NatsError: type: "${status.type}".`);
this._status$.next("disconnected" /* NatsStatus.DISCONNECTED */);
this.statusEventEmitter.emit("disconnect" /* NatsEventsMap.DISCONNECT */, status.server);
break;
case 'reconnecting':
this._status$.next("reconnecting" /* NatsStatus.RECONNECTING */);
break;
case 'reconnect':
this.connectionPromise = Promise.resolve(client);
this.logger.log(`NatsStatus: type: "${status.type}".`);
this._status$.next("connected" /* NatsStatus.CONNECTED */);
this.statusEventEmitter.emit("reconnect" /* NatsEventsMap.RECONNECT */, status.server);
break;
case 'ping':
if (this.options.debug) {
this.logger.debug(`NatsStatus: type: "${status.type}", pending pings: "${status.pendingPings}".`);
}
break;
case 'update':
this.logger.log(`NatsStatus: type: "${status.type}", added: "${status.added}", deleted: "${status.deleted}".`);
this.statusEventEmitter.emit("update" /* NatsEventsMap.UPDATE */, {
added: status.added,
deleted: status.deleted,
});
break;
default: {
const data = 'data' in status && isObject(status.data)
? JSON.stringify(status.data)
: 'data' in status
? status.data
: '';
this.logger.log(`NatsStatus: type: "${status.type}", data: "${data}".`);
break;
}
}
}
}
on(event, callback) {
this.statusEventEmitter.on(event, callback);
}
unwrap() {
if (!this.natsClient) {
throw new Error('Not initialized. Please call the "connect" method first.');
}
return this.natsClient;
}
createSubscriptionHandler(packet, callback) {
return async (error, natsMsg) => {
if (error) {
return callback({
err: error,
});
}
const rawPacket = natsMsg.data;
if (rawPacket?.length === 0) {
return callback({
err: new EmptyResponseException(this.normalizePattern(packet.pattern)),
isDisposed: true,
});
}
const message = await this.deserializer.deserialize(natsMsg);
if (message.id && message.id !== packet.id) {
return undefined;
}
const { err, response, isDisposed } = message;
if (isDisposed || err) {
return callback({
err,
response,
isDisposed: true,
});
}
callback({
err,
response,
});
};
}
publish(partialPacket, callback) {
try {
const packet = this.assignPacketId(partialPacket);
const channel = this.normalizePattern(partialPacket.pattern);
const serializedPacket = this.serializer.serialize(packet);
const inbox = natsPackage.createInbox(this.options.inboxPrefix);
const subscriptionHandler = this.createSubscriptionHandler(packet, callback);
const subscription = this.natsClient.subscribe(inbox, {
callback: subscriptionHandler,
});
const headers = this.mergeHeaders(serializedPacket.headers);
this.natsClient.publish(channel, serializedPacket.data, {
reply: inbox,
headers,
});
return () => subscription.unsubscribe();
}
catch (err) {
callback({ err });
return () => { };
}
}
async dispatchEvent(packet) {
const pattern = this.normalizePattern(packet.pattern);
const serializedPacket = await this.serializer.serialize(packet);
const headers = this.mergeHeaders(serializedPacket.headers);
return new Promise((resolve, reject) => {
try {
this.natsClient.publish(pattern, serializedPacket.data, {
headers,
});
resolve();
}
catch (err) {
reject(err);
}
});
}
initializeSerializer(options) {
this.serializer = options?.serializer ?? new NatsRecordSerializer();
}
initializeDeserializer(options) {
this.deserializer =
options?.deserializer ?? new NatsResponseJSONDeserializer();
}
mergeHeaders(requestHeaders) {
if (!requestHeaders && !this.options?.headers) {
return undefined;
}
const headers = requestHeaders ?? natsPackage.headers();
for (const [key, value] of Object.entries(this.options?.headers || {})) {
if (!headers.has(key)) {
headers.set(key, value);
}
}
return headers;
}
}