@iota-big3/sdk-gateway
Version:
Universal API Gateway with protocol translation, intelligent routing, rate limiting, health checking, and caching
659 lines • 22.8 kB
JavaScript
;
/**
* @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