UNPKG

@nestjs/microservices

Version:

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

301 lines (300 loc) 10.8 kB
import { Transport } from './enums/transport.enum.js'; import { MicroservicesModule } from './microservices-module.js'; import { ServerFactory } from './server/server-factory.js'; import { Logger } from '@nestjs/common'; import { NestApplicationContext, } from '@nestjs/core'; import { MESSAGES, optionalRequire, Injector, makeSafeInstanceDecorator, } from '@nestjs/core/internal'; export class NestMicroservice extends NestApplicationContext { graphInspector; applicationConfig; logger = new Logger(NestMicroservice.name, { timestamp: true, }); microservicesModule = new MicroservicesModule(); socketModule = null; microserviceConfig; serverInstance; isTerminated = false; wasInitHookCalled = false; /** * Returns an observable that emits status changes. */ get status() { return this.serverInstance.status; } constructor(container, config = {}, graphInspector, applicationConfig) { super(container, config); this.graphInspector = graphInspector; this.applicationConfig = applicationConfig; this.injector = new Injector({ preview: config.preview, instanceDecorator: config.instrument?.instanceDecorator, }); this.microservicesModule.register(container, this.graphInspector, this.applicationConfig, this.appOptions); this.createServer(config); this.selectContextModule(); const modulesContainer = this.container.getModules(); modulesContainer.addRpcTarget(this.serverInstance); } createServer(config) { try { if ('useFactory' in config) { const resolvedConfig = this.resolveAsyncOptions(config); this.microserviceConfig = resolvedConfig; // Inject custom strategy if ('strategy' in resolvedConfig) { this.serverInstance = resolvedConfig.strategy; return; } } else { this.microserviceConfig = { transport: Transport.TCP, ...config, }; if ('strategy' in config) { this.serverInstance = config.strategy; return; } } this.serverInstance = ServerFactory.create(this.microserviceConfig); } catch (e) { this.logger.error(e); throw e; } } async registerModules() { await this.loadSocketModule(); this.socketModule && (await this.socketModule.register(this.container, this.applicationConfig, this.graphInspector, this.appOptions)); if (!this.appOptions.preview) { this.microservicesModule.setupClients(this.container); this.registerListeners(); } this.setIsInitialized(true); if (!this.wasInitHookCalled) { await this.callInitHook(); await this.callBootstrapHook(); } } registerListeners() { this.microservicesModule.setupListeners(this.container, this.serverInstance); } /** * Registers a web socket adapter that will be used for Gateways. * Use to override the default `socket.io` library. * * @param {WebSocketAdapter} adapter * @returns {this} */ useWebSocketAdapter(adapter) { if (this.isInitialized) { this.logger.warn('Cannot apply WebSocket adapter: registration must occur before initialization.'); } this.applicationConfig.setIoAdapter(adapter); return this; } /** * Registers global exception filters (will be used for every pattern handler). * * @param {...ExceptionFilter} filters */ useGlobalFilters(...filters) { if (this.isInitialized) { this.logger.warn('Cannot apply global exception filters: registration must occur before initialization.'); } filters = this.applyInstanceDecoratorIfRegistered(...filters); this.applicationConfig.useGlobalFilters(...filters); filters.forEach(item => this.graphInspector.insertOrphanedEnhancer({ subtype: 'filter', ref: item, })); return this; } /** * Registers global pipes (will be used for every pattern handler). * * @param {...PipeTransform} pipes */ useGlobalPipes(...pipes) { if (this.isInitialized) { this.logger.warn('Global pipes registered after initialization will not be applied.'); } pipes = this.applyInstanceDecoratorIfRegistered(...pipes); this.applicationConfig.useGlobalPipes(...pipes); pipes.forEach(item => this.graphInspector.insertOrphanedEnhancer({ subtype: 'pipe', ref: item, })); return this; } /** * Registers global interceptors (will be used for every pattern handler). * * @param {...NestInterceptor} interceptors */ useGlobalInterceptors(...interceptors) { if (this.isInitialized) { this.logger.warn('Cannot apply global interceptors: registration must occur before initialization.'); } interceptors = this.applyInstanceDecoratorIfRegistered(...interceptors); this.applicationConfig.useGlobalInterceptors(...interceptors); interceptors.forEach(item => this.graphInspector.insertOrphanedEnhancer({ subtype: 'interceptor', ref: item, })); return this; } useGlobalGuards(...guards) { if (this.isInitialized) { this.logger.warn('Cannot apply global guards: registration must occur before initialization.'); } guards = this.applyInstanceDecoratorIfRegistered(...guards); this.applicationConfig.useGlobalGuards(...guards); guards.forEach(item => this.graphInspector.insertOrphanedEnhancer({ subtype: 'guard', ref: item, })); return this; } /** * Registers a global preRequest hook (executed before all enhancers for every pattern handler). * * @param {...PreRequestHook} hooks */ registerPreRequestHook(...hooks) { if (this.isInitialized) { this.logger.warn('Cannot apply global preRequest hooks: registration must occur before initialization.'); } this.applicationConfig.registerPreRequestHook(...hooks); return this; } async init() { if (this.isInitialized) { return this; } // Lazy-load optional socket module (ESM-compatible) await this.loadSocketModule(); await super.init(); await this.registerModules(); return this; } /** * Starts the microservice. * * @returns {void} */ async listen() { this.assertNotInPreviewMode('listen'); !this.isInitialized && (await this.registerModules()); return new Promise((resolve, reject) => { this.serverInstance.listen((err, info) => { if (this.microserviceConfig?.autoFlushLogs ?? true) { this.flushLogs(); } if (err) { return reject(err); } this.logger.log(MESSAGES.MICROSERVICE_READY); resolve(info); }); }); } /** * Terminates the application. * * @returns {Promise<void>} */ async close() { await this.serverInstance.close(); if (this.isTerminated) { return; } this.setIsTerminated(true); await this.closeApplication(); } /** * Sets the flag indicating that the application is initialized. * @param isInitialized Value to set */ setIsInitialized(isInitialized) { this.isInitialized = isInitialized; } /** * Sets the flag indicating that the application is terminated. * @param isTerminated Value to set */ setIsTerminated(isTerminated) { this.isTerminated = isTerminated; } /** * Sets the flag indicating that the init hook was called. * @param isInitHookCalled Value to set */ setIsInitHookCalled(isInitHookCalled) { this.wasInitHookCalled = isInitHookCalled; } /** * Registers an event listener for the given event. * @param event Event name * @param callback Callback to be executed when the event is emitted */ on(event, callback) { if ('on' in this.serverInstance) { return this.serverInstance.on(event, callback); } throw new Error('"on" method not supported by the underlying server'); } /** * Returns an instance of the underlying server/broker instance, * or a group of servers if there are more than one. */ unwrap() { if ('unwrap' in this.serverInstance) { return this.serverInstance.unwrap(); } throw new Error('"unwrap" method not supported by the underlying server'); } /** * Returns the underlying transport server instance. * Use this to close only the transport (port/connection) without * triggering the full application shutdown lifecycle. */ getTransportServer() { return this.serverInstance; } async closeApplication() { this.socketModule && (await this.socketModule.close()); this.microservicesModule && (await this.microservicesModule.close()); await super.close(); this.setIsTerminated(true); } async dispose() { if (this.isTerminated) { return; } await this.serverInstance.close(); this.socketModule && (await this.socketModule.close()); this.microservicesModule && (await this.microservicesModule.close()); } resolveAsyncOptions(config) { const args = config.inject?.map(token => this.get(token, { strict: false })); return config.useFactory(...args); } applyInstanceDecoratorIfRegistered(...instances) { if (this.appOptions.instrument?.instanceDecorator) { const decorate = makeSafeInstanceDecorator(this.appOptions.instrument.instanceDecorator); return instances.map(instance => decorate(instance)); } return instances; } async loadSocketModule() { if (!this.socketModule) { const socketModule = await optionalRequire('@nestjs/websockets/socket-module', () => import('@nestjs/websockets/socket-module.js')); if (socketModule?.SocketModule) { this.socketModule = new socketModule.SocketModule(); } } } }