han-prev-core
Version:
Core framework for Han - A powerful Node.js framework inspired by NestJS
473 lines • 20.1 kB
JavaScript
;
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