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
JavaScript
"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);
}