UNPKG

@nestjs/microservices

Version:

Nest - modern, fast, powerful node.js web framework (@microservices)

175 lines (174 loc) 6.21 kB
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); } }