UNPKG

@nestjs/microservices

Version:

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

172 lines (171 loc) 8.54 kB
import { forkJoin, from as fromPromise, isObservable, mergeMap, of, } from 'rxjs'; import { RequestContextHost } from './context/request-context-host.js'; import { DEFAULT_CALLBACK_METADATA, DEFAULT_GRPC_CALLBACK_METADATA, } from './context/rpc-metadata-constants.js'; import { Transport } from './enums/index.js'; import { ListenerMetadataExplorer, } from './listener-metadata-explorer.js'; import { ServerGrpc } from './server/index.js'; import { isUndefined } from '@nestjs/common/internal'; import { ContextIdFactory, MetadataScanner, } from '@nestjs/core'; import { ExecutionContextHost, STATIC_CONTEXT, REQUEST_CONTEXT_ID, } from '@nestjs/core/internal'; export class ListenersController { clientsContainer; contextCreator; container; injector; clientFactory; exceptionFiltersContext; graphInspector; metadataExplorer = new ListenerMetadataExplorer(new MetadataScanner()); exceptionFiltersCache = new WeakMap(); constructor(clientsContainer, contextCreator, container, injector, clientFactory, exceptionFiltersContext, graphInspector) { this.clientsContainer = clientsContainer; this.contextCreator = contextCreator; this.container = container; this.injector = injector; this.clientFactory = clientFactory; this.exceptionFiltersContext = exceptionFiltersContext; this.graphInspector = graphInspector; } registerPatternHandlers(instanceWrapper, serverInstance, moduleKey) { const { instance } = instanceWrapper; const isStatic = instanceWrapper.isDependencyTreeStatic(); const patternHandlers = this.metadataExplorer.explore(instance); const moduleRef = this.container.getModuleByKey(moduleKey); const defaultCallMetadata = serverInstance instanceof ServerGrpc ? DEFAULT_GRPC_CALLBACK_METADATA : DEFAULT_CALLBACK_METADATA; patternHandlers .filter(({ transport }) => isUndefined(transport) || isUndefined(serverInstance.transportId) || transport === serverInstance.transportId) .flatMap(handler => handler.patterns.map(pattern => ({ ...handler, patterns: [pattern], }))) .forEach((definition) => { const { patterns: [pattern], targetCallback, methodKey, extras, isEventHandler, } = definition; this.insertEntrypointDefinition(instanceWrapper, definition, serverInstance.transportId); if (isStatic) { const proxy = this.contextCreator.create(instance, targetCallback, moduleKey, methodKey, STATIC_CONTEXT, undefined, defaultCallMetadata); if (isEventHandler) { const eventHandler = async (...args) => { const originalArgs = args; const [dataOrContextHost] = originalArgs; if (dataOrContextHost instanceof RequestContextHost) { args = args.slice(1, args.length); } const returnValue = proxy(...args); return this.forkJoinHandlersIfAttached(returnValue, originalArgs, eventHandler); }; return serverInstance.addHandler(pattern, eventHandler, isEventHandler, extras); } else { return serverInstance.addHandler(pattern, proxy, isEventHandler, extras); } } const asyncHandler = this.createRequestScopedHandler(instanceWrapper, pattern, moduleRef, moduleKey, methodKey, defaultCallMetadata, isEventHandler); serverInstance.addHandler(pattern, asyncHandler, isEventHandler, extras); }); } insertEntrypointDefinition(instanceWrapper, definition, transportId) { this.graphInspector.insertEntrypointDefinition({ type: 'microservice', methodName: definition.methodKey, className: instanceWrapper.metatype?.name, classNodeId: instanceWrapper.id, metadata: { key: definition.patterns.toString(), transportId: typeof transportId === 'number' ? Transport[transportId] : transportId, patterns: definition.patterns, isEventHandler: definition.isEventHandler, extras: definition.extras, }, }, instanceWrapper.id); } forkJoinHandlersIfAttached(currentReturnValue, originalArgs, handlerRef) { if (handlerRef.next) { const returnedValueWrapper = handlerRef.next(...originalArgs); return forkJoin({ current: this.transformToObservable(currentReturnValue), next: this.transformToObservable(returnedValueWrapper), }); } return currentReturnValue; } assignClientsToProperties(instance) { for (const { property, metadata, } of this.metadataExplorer.scanForClientHooks(instance)) { const client = this.clientFactory.create(metadata); this.clientsContainer.addClient(client); this.assignClientToInstance(instance, property, client); } } assignClientToInstance(instance, property, client) { Reflect.set(instance, property, client); } createRequestScopedHandler(wrapper, pattern, moduleRef, moduleKey, methodKey, defaultCallMetadata = DEFAULT_CALLBACK_METADATA, isEventHandler = false) { const collection = moduleRef.controllers; const { instance } = wrapper; const isTreeDurable = wrapper.isDependencyTreeDurable(); const requestScopedHandler = async (...args) => { try { let contextId; let [dataOrContextHost] = args; if (dataOrContextHost instanceof RequestContextHost) { contextId = this.getContextId(dataOrContextHost, isTreeDurable); args.shift(); } else { const [data, reqCtx] = args; const request = RequestContextHost.create(pattern, data, reqCtx); contextId = this.getContextId(request, isTreeDurable); dataOrContextHost = request; } const contextInstance = await this.injector.loadPerContext(instance, moduleRef, collection, contextId); const proxy = this.contextCreator.create(contextInstance, contextInstance[methodKey], moduleKey, methodKey, contextId, wrapper.id, defaultCallMetadata); const returnValue = proxy(...args); if (isEventHandler) { return this.forkJoinHandlersIfAttached(returnValue, [dataOrContextHost, ...args], requestScopedHandler); } return returnValue; } catch (err) { let exceptionFilter = this.exceptionFiltersCache.get(instance[methodKey]); if (!exceptionFilter) { exceptionFilter = this.exceptionFiltersContext.create(instance, instance[methodKey], moduleKey); this.exceptionFiltersCache.set(instance[methodKey], exceptionFilter); } const host = new ExecutionContextHost(args); host.setType('rpc'); return exceptionFilter.handle(err, host); } }; return requestScopedHandler; } getContextId(request, isTreeDurable) { const contextId = ContextIdFactory.getByRequest(request); if (!request[REQUEST_CONTEXT_ID]) { Object.defineProperty(request, REQUEST_CONTEXT_ID, { value: contextId, enumerable: false, writable: false, configurable: false, }); const requestProviderValue = isTreeDurable ? contextId.payload : Object.assign(request, contextId.payload); this.container.registerRequestProvider(requestProviderValue, contextId); } return contextId; } 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); } }