UNPKG

han-prev-core

Version:

Core framework for Han - A powerful Node.js framework inspired by NestJS

473 lines 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HanFactory = void 0; exports.createHanApp = createHanApp; exports.createHanMicroservice = createHanMicroservice; require("reflect-metadata"); const express = require("express"); const container_1 = require("../container/container"); const app_factory_1 = require("./app.factory"); const utils_1 = require("../utils"); const error_middleware_1 = require("../middleware/error.middleware"); const cors = require("cors"); const helmet = require("helmet"); class HanFactory { constructor(moduleClass, options = {}) { this.controllers = []; this.server = null; this.isShuttingDown = false; this.shutdownCallbacks = new Set(); this.moduleClass = moduleClass; const defaultShutdownHooks = { enabled: true, signals: ["SIGINT", "SIGTERM"], gracefulTimeout: 10000, }; this.options = { cors: true, helmet: true, bodyParser: true, globalPrefix: "", microservice: false, logger: true, ...options, shutdownHooks: { ...defaultShutdownHooks, ...options.shutdownHooks, }, }; this.app = express(); } static async create(moduleClass, options = {}) { utils_1.EnvLoader.autoLoad(); const factory = new HanFactory(moduleClass, options); return await factory.bootstrap(); } static async createMicroservice(moduleClass, options = {}) { utils_1.EnvLoader.autoLoad(); const factory = new HanFactory(moduleClass, { ...options, microservice: true, cors: false, helmet: false, }); return await factory.bootstrap(); } async bootstrap() { await Promise.all([ this.setupCriticalMiddleware(), this.bootstrapModule(), ]); await container_1.container.resolveAsyncProviders(); this.setupOptionalMiddleware(); this.setupRoutes(); this.setupGlobalPrefix(); this.setupErrorHandling(); await container_1.container.callOnModuleInit(); const app = this.createApplicationInstance(); if (this.options.shutdownHooks?.enabled) { this.setupShutdownHooks(app); } return app; } async setupCriticalMiddleware() { if (this.options.bodyParser) { this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); } } setupOptionalMiddleware() { if (this.options.helmet) { if (typeof this.options.helmet === "boolean") { this.app.use(helmet()); } else { this.app.use(helmet(this.options.helmet)); } } if (this.options.cors) { if (typeof this.options.cors === "boolean") { this.app.use(cors()); } else { this.app.use(cors(this.options.cors)); } } } async bootstrapModule() { container_1.container.registerModule(this.moduleClass); this.controllers = this.extractControllersFromModule(this.moduleClass); this.configureModuleMiddleware(this.moduleClass); } configureModuleMiddleware(moduleClass) { const { MiddlewareConsumerImpl, } = require("../middleware/middleware-consumer"); const moduleInstance = this.createModuleInstance(moduleClass); if (moduleInstance && typeof moduleInstance.configure === "function") { const consumer = new MiddlewareConsumerImpl(); moduleInstance.configure(consumer); container_1.container.configureModuleMiddleware(moduleClass, consumer); } } createModuleInstance(moduleClass) { try { return new moduleClass(); } catch (error) { return null; } } setupRoutes() { app_factory_1.AppFactory.registerControllers(this.app, this.controllers); utils_1.RouteMapper.collectAllRoutes(this.controllers, this.options.globalPrefix); } setupGlobalPrefix() { if (this.options.globalPrefix) { const prefixedApp = express(); prefixedApp.use(this.options.globalPrefix, this.app); this.app = prefixedApp; } } setupErrorHandling() { this.app.use(error_middleware_1.errorHandler); } setupShutdownHooks(_app) { if (!this.options.shutdownHooks?.enabled) return; const signals = this.options.shutdownHooks.signals || ["SIGINT", "SIGTERM"]; const gracefulTimeout = this.options.shutdownHooks.gracefulTimeout || 10000; signals.forEach((signal) => { process.on(signal, async () => { if (this.isShuttingDown) { process.exit(1); } this.isShuttingDown = true; const shutdownTimer = setTimeout(() => { process.exit(1); }, gracefulTimeout); try { await container_1.container.callOnModuleDestroy(); const shutdownPromises = Array.from(this.shutdownCallbacks).map((callback) => Promise.resolve(callback())); await Promise.allSettled(shutdownPromises); if (this.server) { await new Promise((resolve) => { this.server.close((err) => { if (err && err.code !== "ERR_SERVER_NOT_RUNNING") { console.error("Error closing server:", err); } resolve(); }); }); } clearTimeout(shutdownTimer); process.exit(0); } catch (error) { clearTimeout(shutdownTimer); if (error.code === "ERR_SERVER_NOT_RUNNING") { process.exit(0); } else { console.error("Error during shutdown:", error); process.exit(1); } } }); }); console.log(`🛡️ Shutdown hooks automatically enabled for signals: ${signals.join(", ")}`); this.shutdownCallbacks.add(async () => { }); } extractControllersFromModule(moduleClass) { const controllers = []; const visited = new Set(); const extractControllers = (module) => { if (visited.has(module)) return; visited.add(module); const moduleMetadata = container_1.container.getModuleMetadata(module); if (moduleMetadata?.controllers) { controllers.push(...moduleMetadata.controllers); } if (moduleMetadata?.imports) { moduleMetadata.imports.forEach((importedModule) => { extractControllers(importedModule); }); } }; extractControllers(moduleClass); return controllers; } createApplicationInstance() { const factoryInstance = this; return { app: factoryInstance.app, async listen(port, hostnameOrCallback, callback) { const actualPort = typeof port === "string" ? parseInt(port, 10) : port; const envInfo = utils_1.EnvironmentDetector.detect(); let hostname; let actualCallback; if (typeof hostnameOrCallback === "string") { hostname = hostnameOrCallback; actualCallback = callback; } else { hostname = envInfo.defaultHost; actualCallback = hostnameOrCallback; } return new Promise((resolve) => { const server = factoryInstance.app.listen(actualPort, hostname, () => { factoryInstance.server = server; if (!factoryInstance.options.microservice) { const boundAddress = server.address(); let serverUrl = `http://localhost:${actualPort}`; if (boundAddress && typeof boundAddress !== "string") { let address = boundAddress.address; const port = boundAddress.port; if (boundAddress.family === "IPv6") { address = `[${address}]`; } if (address === "0.0.0.0" || address === "::") { address = "localhost"; } serverUrl = `http://${address}:${port}`; } const envInfo = utils_1.EnvironmentDetector.detect(); const environment = envInfo.isProduction ? "production" : "development"; utils_1.RouteMapper.displayRoutes(serverUrl, environment); } if (actualCallback) { actualCallback(); } resolve(server); }); }); }, enableCors() { factoryInstance.app.use(cors()); return this; }, useGlobalPrefix(_) { console.warn("useGlobalPrefix should be configured during factory creation for best results"); return this; }, async getUrl() { if (!factoryInstance.server) { throw new Error("Server is not listening. Call listen() first."); } const boundAddress = factoryInstance.server.address(); if (!boundAddress) { throw new Error("Server address is not available."); } const family = boundAddress.family; let address = boundAddress.address; const port = boundAddress.port; if (family === "IPv6") { address = `[${address}]`; } if (address === "0.0.0.0" || address === "::") { address = "localhost"; } const protocol = "http"; return `${protocol}://${address}:${port}`; }, getRoutes() { return utils_1.RouteMapper.collectAllRoutes(factoryInstance.controllers, factoryInstance.options.globalPrefix); }, async close() { if (factoryInstance.server) { return new Promise((resolve, reject) => { factoryInstance.server.close((err) => { if (err) reject(err); else resolve(); }); }); } }, get(token) { const tokenName = typeof token === "string" ? token : token.name; return container_1.container.resolve(tokenName); }, getHttpServer() { return factoryInstance.server; }, getHttpAdapter() { return factoryInstance.app; }, async startAllMicroservices() { return this; }, useGlobalFilters(...filters) { filters.forEach((filter) => { if (typeof filter === "function") { factoryInstance.app.use(filter); } else if (filter && typeof filter.catch === "function") { factoryInstance.app.use((err, req, res, next) => { filter.catch(err, { req, res, next }); }); } }); return this; }, useGlobalPipes(...pipes) { pipes.forEach((pipe) => { if (typeof pipe === "function") { factoryInstance.app.use(pipe); } else if (pipe && typeof pipe.transform === "function") { factoryInstance.app.use((req, res, next) => { try { pipe.transform(req.body, { req, res }); next(); } catch (error) { next(error); } }); } }); return this; }, useGlobalInterceptors(...interceptors) { interceptors.forEach((InterceptorOrClass) => { let interceptorInstance; if (typeof InterceptorOrClass === "function") { interceptorInstance = new InterceptorOrClass(); } else if (InterceptorOrClass && (InterceptorOrClass.beforeHandle || InterceptorOrClass.afterHandle || InterceptorOrClass.onError)) { interceptorInstance = InterceptorOrClass; } else { console.warn("Invalid interceptor provided. Must implement at least one HanInterceptor method."); return; } factoryInstance.app.use(async (req, res, next) => { const startTime = Date.now(); const traceId = req.headers["x-trace-id"] || `trace_${startTime}_${Math.random().toString(36).substring(2, 11)}`; const context = { req, res, method: req.method, path: req.path, startTime, traceId, }; try { if (interceptorInstance.beforeHandle) { await interceptorInstance.beforeHandle(context); } const originalSend = res.send; const originalJson = res.json; let responseData = null; let responseCalled = false; res.send = function (data) { if (!responseCalled) { responseCalled = true; responseData = data; handleResponse(); } return originalSend.call(this, data); }; res.json = function (data) { if (!responseCalled) { responseCalled = true; responseData = data; handleResponse(); } return originalJson.call(this, data); }; const handleResponse = async () => { try { if (interceptorInstance.afterHandle) { const response = { statusCode: res.statusCode, data: responseData, duration: Date.now() - startTime, }; await interceptorInstance.afterHandle(context, response); } } catch (error) { console.error("Error in interceptor afterHandle:", error); } }; next(); } catch (error) { if (interceptorInstance.onError) { try { await interceptorInstance.onError(context, error); } catch (interceptorError) { console.error("Error in interceptor onError handler:", interceptorError); } } next(error); } }); }); return this; }, useGlobalGuards(...guards) { guards.forEach((guard) => { if (typeof guard === "function") { factoryInstance.app.use(guard); } else if (guard && typeof guard.canActivate === "function") { factoryInstance.app.use((req, res, next) => { const canActivate = guard.canActivate({ req, res }); if (canActivate === true || (canActivate && canActivate.then)) { if (canActivate === true) { next(); } else { canActivate .then((result) => { if (result) { next(); } else { res.status(403).json({ message: "Forbidden" }); } }) .catch(next); } } else { res.status(403).json({ message: "Forbidden" }); } }); } }); return this; }, use(...args) { factoryInstance.app.use(...args); return this; }, async init() { console.log("🔧 Initializing Han Framework application..."); console.log("✅ Han Framework application initialized successfully"); }, onApplicationShutdown(callback) { factoryInstance.shutdownCallbacks.add(callback); }, }; } } exports.HanFactory = HanFactory; async function createHanApp(moduleClass, options) { return await HanFactory.create(moduleClass, options); } async function createHanMicroservice(moduleClass, options) { return await HanFactory.createMicroservice(moduleClass, options); } //# sourceMappingURL=han.factory.js.map