UNPKG

hikma-engine

Version:

Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents

296 lines (295 loc) 11.3 kB
"use strict"; /** * @file Express.js API server for hikma-engine semantic search. * Provides RESTful endpoints for semantic, structural, git, and hybrid searches. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.APIServer = void 0; exports.createAPIServer = createAPIServer; const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const helmet_1 = __importDefault(require("helmet")); const morgan_1 = __importDefault(require("morgan")); const compression_1 = __importDefault(require("compression")); const logger_1 = require("../utils/logger"); const health_1 = require("./routes/health"); const middleware_1 = require("./middleware"); /** * API Server class that manages the Express.js application lifecycle. */ class APIServer { constructor(config) { this.config = config; this.logger = (0, logger_1.getLogger)('APIServer'); this.app = (0, express_1.default)(); this.setupMiddleware(); this.setupRoutes(); this.setupErrorHandling(); } /** * Sets up middleware stack for the Express application. */ setupMiddleware() { // Security middleware this.app.use((0, helmet_1.default)({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:", "https:"], }, }, })); // CORS configuration this.app.use((0, cors_1.default)({ origin: this.config.cors.origin, credentials: this.config.cors.credentials, methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], })); // Rate limiting (before other middleware for efficiency) this.app.use((0, middleware_1.getEnvironmentRateLimit)()); // Compression middleware this.app.use((0, compression_1.default)()); // Body parsing middleware this.app.use(express_1.default.json({ limit: '10mb' })); this.app.use(express_1.default.urlencoded({ extended: true, limit: '10mb' })); // Correlation and request tracking middleware this.app.use(middleware_1.correlationMiddleware); this.app.use(middleware_1.timingMiddleware); this.app.use(middleware_1.requestLoggingMiddleware); this.app.use(middleware_1.performanceMonitoringMiddleware); // Rate limit headers middleware this.app.use(middleware_1.addRateLimitHeaders); // Request logging middleware (using morgan for HTTP logs) this.app.use((0, morgan_1.default)('combined', { stream: { write: (message) => { this.logger.info(message.trim(), { source: 'http' }); }, }, })); // Request timeout middleware this.app.use((req, res, next) => { res.setTimeout(this.config.timeout, () => { this.logger.warn('Request timeout', { method: req.method, url: req.url, timeout: this.config.timeout, requestId: req.context?.requestId, }); if (!res.headersSent) { res.status(408).json({ success: false, error: { code: 'REQUEST_TIMEOUT', message: 'Request timeout', }, meta: { timestamp: new Date().toISOString(), requestId: req.context?.requestId || 'unknown', }, }); } }); next(); }); } /** * Sets up API routes. */ setupRoutes() { // Health check routes this.app.use('/health', (0, health_1.createHealthRouter)()); // API version prefix const apiV1 = express_1.default.Router(); this.app.use('/api/v1', apiV1); // Search routes const { createSearchRouter } = require('./routes/search'); const { createMonitoringRouter } = require('./routes/monitoring'); const { getConfig } = require('../config'); apiV1.use('/search', createSearchRouter(getConfig())); apiV1.use('/monitoring', createMonitoringRouter()); // Root endpoint this.app.get('/', (req, res) => { res.json({ success: true, data: { name: 'hikma-engine API', version: '1.0.0', description: 'Semantic search API for code knowledge graphs', endpoints: { health: '/health', api: '/api/v1', search: { semantic: '/api/v1/search/semantic', structural: '/api/v1/search/structure', git: '/api/v1/search/git', hybrid: '/api/v1/search/hybrid', comprehensive: '/api/v1/search/comprehensive', stats: '/api/v1/search/stats', }, monitoring: { health: '/api/v1/monitoring/health', errors: '/api/v1/monitoring/errors', performance: '/api/v1/monitoring/performance', trends: '/api/v1/monitoring/trends', system: '/api/v1/monitoring/system', cleanup: '/api/v1/monitoring/cleanup', }, }, }, meta: { timestamp: new Date().toISOString(), requestId: req.context?.requestId || 'unknown', }, }); }); } /** * Sets up global error handling middleware. */ setupErrorHandling() { // 404 handler for undefined routes this.app.use((req, res, next) => { res.status(404).json({ success: false, error: { code: 'RESOURCE_NOT_FOUND', message: `Route ${req.method} ${req.url} not found`, }, meta: { timestamp: new Date().toISOString(), requestId: req.context?.requestId || 'unknown', }, }); }); // Global error handler (must be last) this.app.use(middleware_1.globalErrorHandler); } /** * Starts the server and begins listening for requests. */ async start() { return new Promise((resolve, reject) => { try { this.server = this.app.listen(this.config.port, this.config.host, () => { this.logger.info('API server started', { host: this.config.host, port: this.config.port, environment: process.env.NODE_ENV || 'development', }); resolve(); }); this.server.on('error', (error) => { this.logger.error('Server error', { error: error.message }); reject(error); }); // Setup graceful shutdown handlers this.setupGracefulShutdown(); } catch (error) { this.logger.error('Failed to start server', { error: error.message }); reject(error); } }); } /** * Stops the server gracefully. */ async stop() { return new Promise((resolve) => { if (this.server) { this.logger.info('Shutting down API server...'); this.server.close(() => { this.logger.info('API server shut down successfully'); resolve(); }); // Force close after timeout setTimeout(() => { this.logger.warn('Force closing server after timeout'); this.server.destroy(); resolve(); }, 10000); } else { resolve(); } }); } /** * Sets up graceful shutdown handling for process signals. */ setupGracefulShutdown() { const gracefulShutdown = async (signal) => { this.logger.info(`Received ${signal}, starting graceful shutdown...`); try { await this.stop(); process.exit(0); } catch (error) { this.logger.error('Error during graceful shutdown', { error: error.message }); process.exit(1); } }; // Handle various shutdown signals process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2')); // nodemon restart // Handle uncaught exceptions and rejections process.on('uncaughtException', (error) => { this.logger.error('Uncaught exception', { error: error.message, stack: error.stack }); gracefulShutdown('uncaughtException'); }); process.on('unhandledRejection', (reason) => { this.logger.error('Unhandled rejection', { reason: reason?.toString() }); gracefulShutdown('unhandledRejection'); }); } /** * Gets the Express application instance. */ getApp() { return this.app; } /** * Gets the HTTP server instance. */ getServer() { return this.server; } } exports.APIServer = APIServer; /** * Creates and configures the API server with default settings. */ function createAPIServer(overrides = {}) { // Initialize configuration if not already initialized const { initializeConfig, getConfig } = require('../config'); try { getConfig(); } catch (error) { // Configuration not initialized, initialize it initializeConfig(); } const defaultConfig = { port: parseInt(process.env.PORT || '3000', 10), host: process.env.HOST || '0.0.0.0', cors: { origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : true, credentials: true, }, rateLimit: { windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs }, timeout: parseInt(process.env.REQUEST_TIMEOUT || '30000', 10), // 30 seconds }; const config = { ...defaultConfig, ...overrides }; return new APIServer(config); }