UNPKG

@iota-big3/sdk-gateway

Version:

Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching

659 lines 22.8 kB
"use strict"; /** * @iota-big3/sdk-gateway * Clean Gateway Manager - Phase 2d Rebuild */ Object.defineProperty(exports, "__esModule", { value: true }); exports.UniversalAPIGateway = void 0; exports.createGateway = createGateway; const events_1 = require("events"); const response_aggregator_1 = require("../aggregation/response-aggregator"); const service_registry_1 = require("../discovery/service-registry"); const health_checker_1 = require("../monitoring/health-checker"); const circuit_breaker_1 = require("../resilience/circuit-breaker"); const rate_limiter_1 = require("../resilience/rate-limiter"); const routing_engine_1 = require("../routing/routing-engine"); class UniversalAPIGateway extends events_1.EventEmitter { constructor(config) { super(); this.adapters = new Map(); this.isRunning = false; this.isEnabled = true; this.metrics = { requests: { total: 0, byProtocol: {}, byService: {}, byStatus: {} }, latency: { p50: 0, p95: 0, p99: 0, avg: 0 }, errors: { total: 0, byType: {}, rate: 0 } }; this.config = config; // Initialize SDK integrations this.database = config.database; this.logger = config.logger; this.authManager = config.authManager; this.eventBus = config.eventBus; // Initialize routing engine this.routingEngine = new routing_engine_1.RoutingEngine(); // Initialize circuit breaker this.circuitBreaker = new circuit_breaker_1.CircuitBreaker({ failureThreshold: 5, recoveryTimeout: 30000, monitoringPeriod: 60000 }); // Initialize service discovery this.serviceDiscovery = new service_registry_1.ServiceDiscovery({ provider: 'static', config: {} }); // Initialize response aggregator this.responseAggregator = new response_aggregator_1.ResponseAggregator(); // Initialize rate limiter if configured if (this.config.rateLimit) { this.rateLimiter = new rate_limiter_1.RateLimiter({ windowMs: this.config.rateLimit.windowMs, max: this.config.rateLimit.max }); } // Initialize health checker if configured if (this.config.healthCheck) { this.healthChecker = new health_checker_1.HealthChecker({ interval: this.config.healthCheck.interval, timeout: this.config.healthCheck.timeout, retries: this.config.healthCheck.retries }); // Add health checks for all configured endpoints this.config.routes.forEach(route => { route.endpoints.forEach(endpoint => { if (endpoint.healthCheck !== false) { this.healthChecker.addEndpointCheck({ id: endpoint.id, url: endpoint.url, healthPath: endpoint.healthCheck || '/health' }); } }); }); } // Initialize routes from config this.config.routes.forEach(route => { this.routingEngine.addRoute(route); }); this.emit('gateway:created', { config: this.config }); } /** * Initialize the gateway and all integrations */ async initializeAsync() { try { // Initialize database if provided if (this.database) { try { await this.database.initialize(); this.log('info', 'Database initialized'); // Load routes from database if enabled if (this.config.loadRoutesFromDb) { await this.loadRoutesFromDatabase(); } } catch (error) { this.log('error', 'Failed to initialize database', { error }); // Continue without database } } // Initialize auth manager if provided if (this.authManager) { try { await this.authManager.initialize(); this.log('info', 'Auth manager initialized'); } catch (error) { this.log('error', 'Failed to initialize auth manager', { error }); // Continue without auth } } // Setup event listeners if event bus provided if (this.eventBus) { this.setupEventListeners(); } // Log initialization complete this.log('info', 'Gateway initialized', { name: this.config.name || 'API Gateway', routes: this.routingEngine.getAllRoutes().length, integrations: { database: !!this.database, logger: !!this.logger, auth: !!this.authManager, events: !!this.eventBus } }); // Emit initialization event this.emitEvent('gateway:initialized', { name: this.config.name || 'API Gateway', timestamp: new Date() }); } catch (error) { this.log('error', 'Failed to initialize gateway', { error }); throw error; } } /** * Validate a request (for auth integration) */ async validateRequestAsync(request) { if (!this.config.requireAuth || !this.authManager) { return true; } try { // Check for API key const apiKey = request.headers['x-api-key']; if (apiKey) { const result = await this.authManager.validateApiKey(apiKey); return result.valid; } // Check for JWT token const authHeader = request.headers['authorization']; if (authHeader && authHeader.startsWith('Bearer ')) { const token = authHeader.substring(7); const result = await this.authManager.validateToken(token); return result.valid; } // No auth provided return false; } catch (error) { this.log('error', 'Auth validation failed', { error }); return false; } } /** * Reload routes from database */ async reloadRoutesAsync() { if (!this.database || !this.config.loadRoutesFromDb) { return { success: true, data: undefined }; } // Use cache if enabled and not expired if (this.config.cacheDbQueries) { // TODO: Implement caching logic return { success: true, data: undefined }; } return await this.loadRoutesFromDatabase(); } /** * Load routes from database */ async loadRoutesFromDatabase() { if (!this.database) { return { success: false, error: new Error('Database not configured') }; } try { const routes = await this.database.query('routes', {}); routes.forEach(route => { if (this.isValidRoute(route)) { this.routingEngine.addRoute(route); } }); this.log('info', `Loaded ${routes.length} routes from database`); return { success: true, data: undefined }; } catch (error) { this.log('error', 'Failed to load routes from database', { error }); return { success: false, error: error instanceof Error ? error : new Error(String(error)) }; } } /** * Setup event listeners for integration */ setupEventListeners() { if (!this.eventBus) return; // Listen to service discovery events this.eventBus.on('discovery:service.discovered', (data) => { const event = data; if (event.service) { // Auto-register route for discovered service const route = { id: `auto-${event.service.id}`, path: `/services/${event.service.name}/*`, endpoints: [{ id: `${event.service.id}-endpoint`, url: event.service.url, protocol: 'http' }] }; this.addRoute(route); this.log('info', 'Auto-registered route for discovered service', { service: event.service.name }); } }); } /** * Helper to log messages */ log(level, message, context) { if (this.logger) { this.logger[level](message, context); } } /** * Helper to emit events */ emitEvent(event, data) { // Emit on internal EventEmitter this.emit(event, data); // Also emit on external event bus if provided if (this.eventBus) { this.eventBus.emit(event, data); } } /** * Check if object is a valid route */ isValidRoute(obj) { if (!obj || typeof obj !== 'object') return false; const route = obj; return (typeof route.id === 'string' && typeof route.path === 'string' && Array.isArray(route.endpoints)); } /** * Start the gateway */ async startAsync() { if (this.isRunning) { throw new Error('Gateway is already running'); } // Starting ${this.config.name || 'API Gateway'}... // Start health checker if configured if (this.healthChecker) { await this.healthChecker.startAsync(); } // Start service discovery if configured if (this.config.monitoring?.enabled) { // Would start monitoring here } this.isRunning = true; this.emit('gateway:started'); // Gateway started on port ${this.config.port || 8080} } /** * Stop the gateway */ async stopAsync() { if (!this.isRunning) { return; } // Stopping gateway... if (this.serviceDiscovery) { await this.serviceDiscovery.stopAsync(); } // Stop health checker if (this.healthChecker) { await this.healthChecker.stop(); } // Clean up rate limiter if (this.rateLimiter) { this.rateLimiter.destroy(); } // Clean up cache if (this.cacheManager) { await this.cacheManager.clearAsync(); this.cacheManager.destroy(); } this.isRunning = false; this.emit('gateway:stopped'); // Gateway stopped } /** * Add a new route */ addRoute(route) { this.routingEngine.addRoute(route); // Log the operation this.log('info', 'Route added', { routeId: route.id, path: route.path, endpoints: route.endpoints.length }); // Persist to database if available if (this.database) { this.database.insert('routes', { ...route }).catch(error => { this.log('error', 'Failed to persist route to database', { error, routeId: route.id }); }); } // Emit events this.emit('route:added', { route }); this.emitEvent('gateway:route.added', { route, timestamp: new Date() }); } /** * Remove a route */ removeRoute(routeId) { const route = this.routingEngine.getAllRoutes().find(r => r.id === routeId); if (!route) { return false; } const removed = this.routingEngine.removeRouteById(routeId); if (removed) { // Log the operation this.log('info', 'Route removed', { routeId }); // Remove from database if available if (this.database) { this.database.query('routes', { id: routeId }).then(routes => { if (routes.length > 0) { // TODO: Implement delete method in database adapter this.log('warn', 'Route removed from gateway but not from database', { routeId }); } }).catch(error => { this.log('error', 'Failed to remove route from database', { error, routeId }); }); } // Emit events this.emit('route:removed', { routeId }); this.emitEvent('gateway:route.removed', { routeId, timestamp: new Date() }); } return removed; } /** * Get all routes */ getRoutes() { return this.routingEngine.getRoutes(); } /** * Update gateway configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; // Update components that depend on config if (config.rateLimit && this.rateLimiter) { // Would update rate limiter config } if (config.healthCheck && this.healthChecker) { // Would update health checker config } if (config.caching && this.cacheManager) { // Would update cache manager config } this.emit('config:updated', this.config); } /** * Enable the gateway */ enable() { this.isEnabled = true; this.emit('gateway:enabled'); } /** * Disable the gateway */ disable() { this.isEnabled = false; this.emit('gateway:disabled'); } /** * Check if gateway is enabled */ isGatewayEnabled() { return this.isEnabled; } /** * Check if gateway is running */ isGatewayRunning() { return this.isRunning; } /** * Handle incoming request */ async handleRequestAsync(request) { if (!this.isEnabled) { return { statusCode: 503, headers: { 'content-type': 'application/json' }, body: { error: 'Gateway is disabled' } }; } const startTime = Date.now(); try { // Check rate limit if enabled if (this.rateLimiter) { const rateLimitResult = await this.rateLimiter.checkLimitAsync(request); if (!rateLimitResult.allowed) { return { statusCode: 429, headers: { 'content-type': 'application/json', 'retry-after': String(Math.ceil((rateLimitResult.retryAfter || 0) / 1000)) }, body: { error: 'Too many requests', retryAfter: rateLimitResult.retryAfter } }; } } // Check cache if enabled if (this.cacheManager && this.cacheManager.shouldCache(request)) { const cacheKey = this.cacheManager.generateKey(request); const cachedResponse = await this.cacheManager.get(cacheKey); if (cachedResponse) { // Add cache headers cachedResponse.headers = { ...cachedResponse.headers, 'x-cache': 'HIT', 'x-cache-key': cacheKey }; return cachedResponse; } } // Route the request const routingDecision = this.routingEngine.findRoute(request); if (!routingDecision) { return { statusCode: 404, headers: { 'content-type': 'application/json' }, body: { error: 'No route found' } }; } // Check endpoint health if health checker is enabled if (this.healthChecker) { const healthState = this.healthChecker.getHealthStates()[routingDecision.endpoint.id]; if (healthState?.status === 'unhealthy') { // Try to find alternative healthy endpoint const alternativeEndpoint = await this.findHealthyEndpointAsync(routingDecision.route); if (alternativeEndpoint) { routingDecision.endpoint = alternativeEndpoint; } else { return { statusCode: 503, headers: { 'content-type': 'application/json' }, body: { error: 'Service unavailable - all endpoints unhealthy' } }; } } } // Execute with circuit breaker const response = await this.circuitBreaker.execute(async () => this.makeRequestAsync(request, routingDecision)); // Cache successful responses if (this.cacheManager && this.cacheManager.shouldCache(request) && this.cacheManager.shouldCacheResponse(response)) { const cacheKey = this.cacheManager.generateKey(request); const routeTags = [routingDecision.route.id, routingDecision.endpoint.id]; await this.cacheManager.set(cacheKey, response, undefined, routeTags); // Add cache headers response.headers = { ...response.headers, 'x-cache': 'MISS', 'x-cache-key': cacheKey }; } // Update metrics this.updateMetrics(request, response, Date.now() - startTime); return response; } catch (error) { this.metrics.errors.total++; return { statusCode: 500, headers: { 'content-type': 'application/json' }, body: { error: error instanceof Error ? error.message : 'Internal server error' } }; } } /** * Handle multi-service request */ async handleMultiServiceRequestAsync(request, response, decision) { // Multi-service request handling would be implemented here // For now, just return the response return response; } /** * Find a healthy endpoint from the route */ async findHealthyEndpointAsync(route) { if (!this.healthChecker) { return route.endpoints[0] || null; } const healthStates = this.healthChecker.getHealthStates(); for (const endpoint of route.endpoints) { const state = healthStates[endpoint.id]; if (!state || state.status === 'healthy') { return endpoint; } } return null; } /** * Make request to backend service */ async makeRequestAsync(request, decision) { // This would use the appropriate protocol adapter // For now, returning a mock response return { statusCode: 200, headers: { 'content-type': 'application/json' }, body: { message: 'Response from service', service: decision.endpoint.id, route: decision.route.id } }; } /** * Update gateway metrics */ updateMetrics(request, response, latency) { this.metrics.requests.total++; // Update protocol metrics this.metrics.requests.byProtocol[request.protocol] = (this.metrics.requests.byProtocol[request.protocol] || 0) + 1; // Update status metrics this.metrics.requests.byStatus[response.statusCode] = (this.metrics.requests.byStatus[response.statusCode] || 0) + 1; // Update latency (simplified) this.metrics.latency.avg = (this.metrics.latency.avg * (this.metrics.requests.total - 1) + latency) / this.metrics.requests.total; } /** * Get current metrics */ getMetrics() { const metrics = { ...this.metrics }; if (this.cacheManager) { metrics.cache = this.cacheManager.getStats(); } return metrics; } /** * Get health status */ async getHealthStatusAsync() { if (!this.healthChecker) { return { status: 'healthy', message: 'Health checking not enabled' }; } return this.healthChecker.getOverallHealthAsync(); } /** * Add a route with Result pattern */ addRouteSafe(route) { try { this.addRoute(route); return { success: true, data: undefined }; } catch (error) { return { success: false, error: error instanceof Error ? error : new Error('Failed to add route') }; } } /** * Remove a route with Result pattern */ removeRouteSafe(routeId) { try { const result = this.removeRoute(routeId); return { success: true, data: result }; } catch (error) { return { success: false, error: error instanceof Error ? error : new Error('Failed to remove route') }; } } /** * Handle a request with Result pattern */ async handleRequestSafeAsync(request) { try { const response = await this.handleRequestAsync(request); return { success: true, data: response }; } catch (error) { return { success: false, error: error instanceof Error ? error : new Error('Request handling failed') }; } } } exports.UniversalAPIGateway = UniversalAPIGateway; /** * Factory function to create gateway */ function createGateway(config) { return new UniversalAPIGateway(config); } //# sourceMappingURL=gateway-manager.js.map