UNPKG

@opra/nestjs

Version:

Opra NestJS module

152 lines (151 loc) 6.96 kB
import { __decorate, __metadata } from "tslib"; import { Controller, Injectable, } from '@nestjs/common'; import { createContextId, ModulesContainer, REQUEST } from '@nestjs/core'; import { ExternalContextCreator, } from '@nestjs/core/helpers/external-context-creator.js'; import { Injector } from '@nestjs/core/injector/injector.js'; import { InternalCoreModule } from '@nestjs/core/injector/internal-core-module/index.js'; import { REQUEST_CONTEXT_ID } from '@nestjs/core/router/request/request-constants.js'; import { PARAM_ARGS_METADATA } from '@nestjs/microservices/constants.js'; import { RPC_CONTROLLER_METADATA } from '@opra/common'; import { OpraNestUtils } from './opra-nest-utils.js'; import { RpcParamsFactory } from './rpc-params.factory.js'; /** * Factory class that wraps Opra controllers into NestJS controllers. */ let RpcControllerFactory = class RpcControllerFactory { modulesContainer; externalContextCreator; _metadataKey = RPC_CONTROLLER_METADATA; _coreModuleRef; paramsFactory = new RpcParamsFactory(); injector = new Injector(); constructor(modulesContainer, externalContextCreator) { this.modulesContainer = modulesContainer; this.externalContextCreator = externalContextCreator; } /** * Wraps and returns the controllers as NestJS compatible classes. * * @returns An array of NestJS compatible controller classes. */ wrapControllers() { const out = []; for (const { module, wrapper } of this.exploreControllers()) { const instance = wrapper.instance; const sourceClass = instance.constructor; const metadata = Reflect.getMetadata(this._metadataKey, sourceClass); const isRequestScoped = !wrapper.isDependencyTreeStatic(); /* Create a new controller class */ const newClass = { [sourceClass.name]: class extends sourceClass { }, }[sourceClass.name]; /* Copy metadata keys from source class to new one */ OpraNestUtils.copyDecoratorMetadata(newClass, sourceClass); Controller()(newClass); out.push(newClass); if (metadata.operations) { for (const operationName of Object.keys(metadata.operations)) { // const orgFn: Function = sourceClass.prototype[operationName]; newClass.prototype[operationName] = this._createContextCallback(instance, wrapper, module, operationName, isRequestScoped, 'rpc'); Reflect.defineMetadata(PARAM_ARGS_METADATA, [REQUEST], instance.constructor, operationName); } } } return out; } /** * Creates a context callback for a controller method. * * @param instance - The controller instance. * @param wrapper - The instance wrapper. * @param moduleRef - The module reference. * @param methodName - The name of the method to wrap. * @param isRequestScoped - Whether the controller is request-scoped. * @param contextType - The NestJS context type. * @param options - Optional external context options. * @returns A callback function that handles the execution context. */ _createContextCallback(instance, wrapper, moduleRef, methodName, isRequestScoped, contextType, options) { const paramsFactory = this.paramsFactory; if (isRequestScoped) { return async (opraContext) => { const contextId = createContextId(); Object.defineProperty(opraContext, REQUEST_CONTEXT_ID, { value: contextId, enumerable: false, configurable: false, writable: false, }); this.registerContextProvider(opraContext, contextId); const contextInstance = await this.injector.loadPerContext(instance, moduleRef, moduleRef.providers, contextId); const contextCallback = this.externalContextCreator.create(contextInstance, contextInstance[methodName], methodName, PARAM_ARGS_METADATA, paramsFactory, contextId, wrapper.id, options, opraContext.transport); return contextCallback(opraContext); }; } return this.externalContextCreator.create(instance, instance[methodName], methodName, PARAM_ARGS_METADATA, paramsFactory, undefined, undefined, options, contextType); } /** * Registers a context provider for the given request and context ID. * * @param request - The request object (Opra execution context). * @param contextId - The NestJS context ID. */ registerContextProvider(request, contextId) { if (!this._coreModuleRef) { const coreModuleArray = [...this.modulesContainer.entries()] .filter( // eslint-disable-next-line @typescript-eslint/no-unused-vars ([_, { metatype }]) => metatype && metatype.name === InternalCoreModule.name) // eslint-disable-next-line @typescript-eslint/no-unused-vars .map(([_, value]) => value); this._coreModuleRef = coreModuleArray[0]; } if (!this._coreModuleRef) { return; } const wrapper = this._coreModuleRef.getProviderByKey(REQUEST); wrapper.setInstanceByContextId(contextId, { instance: request, isResolved: true, }); } /** * Explores and returns all Opra controllers within the NestJS application. * * @returns An array of objects containing the module and instance wrapper for each controller. */ exploreControllers() { const scannedModules = new Set(); const controllers = new Set(); const scanModule = (module) => { if (scannedModules.has(module)) return; scannedModules.add(module); for (const mm of module.imports.values()) { scanModule(mm); } for (const wrapper of module.controllers.values()) { if (wrapper.instance && typeof wrapper.instance === 'object' && wrapper.instance.constructor && Reflect.getMetadata(this._metadataKey, wrapper.instance.constructor) && !controllers.has(wrapper)) { controllers.add({ module, wrapper }); } if (wrapper.host) scanModule(wrapper.host); } }; for (const module of this.modulesContainer.values()) { scanModule(module); } return Array.from(controllers); } }; RpcControllerFactory = __decorate([ Injectable(), __metadata("design:paramtypes", [ModulesContainer, ExternalContextCreator]) ], RpcControllerFactory); export { RpcControllerFactory };