@nestjs/microservices
Version:
Nest - modern, fast, powerful node.js web framework (@microservices)
175 lines (174 loc) • 6.21 kB
JavaScript
import { connectable, EMPTY, from as fromPromise, isObservable, of, ReplaySubject, Subject, } from 'rxjs';
import { catchError, distinctUntilChanged, finalize, mergeMap, } from 'rxjs/operators';
import { NO_EVENT_HANDLER } from '../constants.js';
import { IncomingRequestDeserializer } from '../deserializers/incoming-request.deserializer.js';
import { IdentitySerializer } from '../serializers/identity.serializer.js';
import { transformPatternToRoute } from '../utils/index.js';
import { Logger } from '@nestjs/common';
import { loadPackage, loadPackageSync } from '@nestjs/common/internal';
/**
* @publicApi
*/
export class Server {
/**
* Unique transport identifier.
*/
transportId;
messageHandlers = new Map();
logger = new Logger(Server.name);
serializer;
deserializer;
onProcessingStartHook = (transportId, context, done) => done();
onProcessingEndHook;
_status$ = new ReplaySubject(1);
/**
* Returns an observable that emits status changes.
*/
get status() {
return this._status$.asObservable().pipe(distinctUntilChanged());
}
/**
* Sets the transport identifier.
* @param transportId Unique transport identifier.
*/
setTransportId(transportId) {
this.transportId = transportId;
}
/**
* Sets a hook that will be called when processing starts.
*/
setOnProcessingStartHook(hook) {
this.onProcessingStartHook = hook;
}
/**
* Sets a hook that will be called when processing ends.
*/
setOnProcessingEndHook(hook) {
this.onProcessingEndHook = hook;
}
addHandler(pattern, callback, isEventHandler = false, extras = {}) {
const normalizedPattern = this.normalizePattern(pattern);
callback.isEventHandler = isEventHandler;
callback.extras = extras;
if (this.messageHandlers.has(normalizedPattern) && isEventHandler) {
const headRef = this.messageHandlers.get(normalizedPattern);
const getTail = (handler) => handler?.next ? getTail(handler.next) : handler;
const tailRef = getTail(headRef);
tailRef.next = callback;
}
else {
this.messageHandlers.set(normalizedPattern, callback);
}
}
getHandlers() {
return this.messageHandlers;
}
getHandlerByPattern(pattern) {
const route = this.getRouteFromPattern(pattern);
return this.messageHandlers.has(route)
? this.messageHandlers.get(route)
: null;
}
send(stream$, respond) {
const dataQueue = [];
let isProcessing = false;
const scheduleOnNextTick = (data) => {
if (data.isDisposed && dataQueue.length > 0) {
dataQueue[dataQueue.length - 1].isDisposed = true;
}
else {
dataQueue.push(data);
}
if (!isProcessing) {
isProcessing = true;
process.nextTick(async () => {
while (dataQueue.length > 0) {
const packet = dataQueue.shift();
if (packet) {
await respond(packet);
}
}
isProcessing = false;
});
}
};
return stream$
.pipe(catchError((err) => {
scheduleOnNextTick({ err });
return EMPTY;
}), finalize(() => scheduleOnNextTick({ isDisposed: true })))
.subscribe((response) => scheduleOnNextTick({ response }));
}
async handleEvent(pattern, packet, context) {
const handler = this.getHandlerByPattern(pattern);
if (!handler) {
return this.logger.error(NO_EVENT_HANDLER `${pattern}`);
}
return this.onProcessingStartHook(this.transportId, context, async () => {
const resultOrStream = await handler(packet.data, context);
if (isObservable(resultOrStream)) {
const connectableSource = connectable(resultOrStream.pipe(finalize(() => this.onProcessingEndHook?.(this.transportId, context))), {
connector: () => new Subject(),
resetOnDisconnect: false,
});
connectableSource.connect();
}
else {
this.onProcessingEndHook?.(this.transportId, context);
}
});
}
transformToObservable(resultOrDeferred) {
if (resultOrDeferred instanceof Promise) {
return fromPromise(resultOrDeferred).pipe(mergeMap(val => (isObservable(val) ? val : of(val))));
}
if (isObservable(resultOrDeferred)) {
return resultOrDeferred;
}
return of(resultOrDeferred);
}
getOptionsProp(obj, prop, defaultValue = undefined) {
return obj && prop in obj ? obj[prop] : defaultValue;
}
handleError(error) {
this.logger.error(error);
}
loadPackage(name, ctx, loader) {
return loadPackage(name, ctx, loader);
}
loadPackageSynchronously(name, ctx, loader) {
return loadPackageSync(name, ctx, loader);
}
initializeSerializer(options) {
this.serializer =
(options &&
options.serializer) ||
new IdentitySerializer();
}
initializeDeserializer(options) {
this.deserializer =
(options &&
options.deserializer) ||
new IncomingRequestDeserializer();
}
/**
* Transforms the server Pattern to valid type and returns a route for him.
*
* @param {string} pattern - server pattern
* @returns string
*/
getRouteFromPattern(pattern) {
let validPattern;
try {
validPattern = JSON.parse(pattern);
}
catch (error) {
// Uses a fundamental object (`pattern` variable without any conversion)
validPattern = pattern;
}
return this.normalizePattern(validPattern);
}
normalizePattern(pattern) {
return transformPatternToRoute(pattern);
}
}