UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

401 lines β€’ 12.6 kB
/** * @module runtime/lifecycle-manager * @description Comprehensive lifecycle management for distributed Gati applications */ import { LifecyclePriority, RequestPhase } from './types/context.js'; /** * Distributed lifecycle manager */ export class LifecycleManager { startupHooks = []; shutdownHooks = []; preShutdownHooks = []; healthChecks = new Map(); configReloadHooks = new Map(); memoryPressureHooks = new Map(); circuitBreakerHooks = new Map(); isShuttingDownFlag = false; coordinator; constructor(coordinator) { this.coordinator = coordinator; } /** * Register startup hook */ onStartup(nameOrFn, fnOrPriority, maybePriority) { // Support both onStartup(name, fn, priority) and onStartup(fn, priority) signatures if (typeof nameOrFn === 'function') { // onStartup(fn, priority?) const fn = nameOrFn; const priority = (typeof fnOrPriority === 'number' ? fnOrPriority : LifecyclePriority.NORMAL); this.startupHooks.push({ name: `startup-${this.startupHooks.length}`, fn, priority }); } else { // onStartup(name, fn, priority?) const name = nameOrFn; const fn = fnOrPriority; const priority = maybePriority || LifecyclePriority.NORMAL; this.startupHooks.push({ name, fn, priority }); } this.startupHooks.sort((a, b) => b.priority - a.priority); } /** * Register health check */ onHealthCheck(name, fn) { this.healthChecks.set(name, fn); } /** * Register shutdown hook */ onShutdown(nameOrFn, fnOrPriority, maybePriority) { // Support both onShutdown(name, fn, priority) and onShutdown(fn, priority) signatures if (typeof nameOrFn === 'function') { // onShutdown(fn, priority?) const fn = nameOrFn; const priority = (typeof fnOrPriority === 'number' ? fnOrPriority : LifecyclePriority.NORMAL); this.shutdownHooks.push({ name: `shutdown-${this.shutdownHooks.length}`, fn, priority }); } else { // onShutdown(name, fn, priority?) const name = nameOrFn; const fn = fnOrPriority; const priority = maybePriority || LifecyclePriority.NORMAL; this.shutdownHooks.push({ name, fn, priority }); } this.shutdownHooks.sort((a, b) => b.priority - a.priority); } /** * Register pre-shutdown hook */ onPreShutdown(nameOrFn, maybeFn) { // Support both onPreShutdown(name, fn) and onPreShutdown(fn) signatures if (typeof nameOrFn === 'function') { // onPreShutdown(fn) this.preShutdownHooks.push({ name: `pre-shutdown-${this.preShutdownHooks.length}`, fn: nameOrFn, priority: LifecyclePriority.HIGH }); } else { // onPreShutdown(name, fn) this.preShutdownHooks.push({ name: nameOrFn, fn: maybeFn, priority: LifecyclePriority.HIGH }); } } /** * Register config reload hook */ onConfigReload(name, fn) { this.configReloadHooks.set(name, fn); } /** * Register memory pressure hook */ onMemoryPressure(name, fn) { this.memoryPressureHooks.set(name, fn); } /** * Register circuit breaker hook */ onCircuitBreakerChange(name, fn) { this.circuitBreakerHooks.set(name, fn); } /** * Execute startup hooks */ async executeStartup() { console.log('πŸš€ Executing startup hooks...'); // Register with service discovery if coordinator available if (this.coordinator) { await this.coordinator.register(); } for (const hook of this.startupHooks) { try { console.log(` ⚑ Starting: ${hook.name}`); await Promise.resolve(hook.fn()); console.log(` βœ… Started: ${hook.name}`); } catch (error) { console.error(` ❌ Failed to start: ${hook.name}`, error); throw error; } } console.log('βœ… All startup hooks completed'); } /** * Execute health checks */ async executeHealthChecks() { const checks = {}; let overallStatus = 'healthy'; for (const [name, checkFn] of this.healthChecks) { const start = Date.now(); try { const result = await Promise.race([ checkFn(), new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), 5000)) ]); checks[name] = { ...result, duration: Date.now() - start, }; if (result.status === 'fail') { overallStatus = 'unhealthy'; } else if (result.status === 'warn' && overallStatus === 'healthy') { overallStatus = 'degraded'; } } catch (error) { checks[name] = { status: 'fail', message: error instanceof Error ? error.message : 'Unknown error', duration: Date.now() - start, }; overallStatus = 'unhealthy'; } } const healthStatus = { status: overallStatus, checks, timestamp: Date.now(), }; // Report to coordinator if available if (this.coordinator) { try { await this.coordinator.reportHealth(healthStatus); } catch (error) { console.warn('Failed to report health to coordinator:', error); } } return healthStatus; } /** * Execute shutdown hooks */ async executeShutdown() { this.isShuttingDownFlag = true; console.log('πŸ›‘ Executing shutdown sequence...'); // Execute pre-shutdown hooks first console.log(' πŸ“‹ Pre-shutdown phase...'); for (const hook of this.preShutdownHooks) { try { console.log(` ⏸️ Pre-shutdown: ${hook.name}`); await Promise.resolve(hook.fn()); } catch (error) { console.error(` ❌ Pre-shutdown failed: ${hook.name}`, error); } } // Execute main shutdown hooks console.log(' πŸ”„ Main shutdown phase...'); for (const hook of this.shutdownHooks) { try { console.log(` πŸ›‘ Shutting down: ${hook.name}`); const timeout = hook.timeout || 10000; await Promise.race([ Promise.resolve(hook.fn()), new Promise((_, reject) => setTimeout(() => reject(new Error(`Shutdown timeout: ${hook.name}`)), timeout)) ]); console.log(` βœ… Shutdown complete: ${hook.name}`); } catch (error) { console.error(` ❌ Shutdown failed: ${hook.name}`, error); } } // Deregister from service discovery if (this.coordinator) { try { await this.coordinator.deregister(); } catch (error) { console.warn('Failed to deregister from coordinator:', error); } } console.log('βœ… Shutdown sequence completed'); } /** * Trigger config reload */ async reloadConfig(newConfig) { console.log('πŸ”„ Reloading configuration...'); for (const [name, hookFn] of this.configReloadHooks) { try { console.log(` πŸ”„ Reloading config for: ${name}`); await Promise.resolve(hookFn(newConfig)); console.log(` βœ… Config reloaded: ${name}`); } catch (error) { console.error(` ❌ Config reload failed: ${name}`, error); } } } /** * Trigger memory pressure handlers */ async handleMemoryPressure(level) { console.log(`⚠️ Memory pressure detected: ${level}`); for (const [name, hookFn] of this.memoryPressureHooks) { try { await Promise.resolve(hookFn(level)); } catch (error) { console.error(`Memory pressure handler failed: ${name}`, error); } } } /** * Trigger circuit breaker change handlers */ handleCircuitBreakerChange(service, state) { console.log(`πŸ”Œ Circuit breaker ${service}: ${state}`); for (const [name, hookFn] of this.circuitBreakerHooks) { try { hookFn(service, state); } catch (error) { console.error(`Circuit breaker handler failed: ${name}`, error); } } } /** * Check if shutting down */ isShuttingDown() { return this.isShuttingDownFlag; } } /** * Request lifecycle manager for individual requests */ export class RequestLifecycleManager { cleanupHooks = []; timeoutHandlers = []; errorHandlers = []; phaseChangeHandlers = []; currentPhase = RequestPhase.RECEIVED; isCleaningUpFlag = false; isTimedOutFlag = false; startTime; constructor(timeout) { this.startTime = Date.now(); if (timeout) { setTimeout(() => { this.isTimedOutFlag = true; this.executeTimeoutHandlers(); }, timeout); } } /** * Register cleanup hook */ onCleanup(nameOrFn, maybeFn) { // Support both onCleanup(name, fn) and onCleanup(fn) signatures if (typeof nameOrFn === 'function') { // onCleanup(fn) this.cleanupHooks.push({ name: `cleanup-${this.cleanupHooks.length}`, fn: nameOrFn }); } else { // onCleanup(name, fn) this.cleanupHooks.push({ name: nameOrFn, fn: maybeFn }); } } /** * Register timeout handler */ onTimeout(fn) { this.timeoutHandlers.push(fn); } /** * Register error handler */ onError(fn) { this.errorHandlers.push(fn); } /** * Register phase change handler */ onPhaseChange(fn) { this.phaseChangeHandlers.push(fn); } /** * Set current request phase */ setPhase(phase) { const previousPhase = this.currentPhase; this.currentPhase = phase; for (const handler of this.phaseChangeHandlers) { try { handler(phase, previousPhase); } catch (error) { console.error('Phase change handler error:', error); } } } /** * Execute cleanup hooks */ async executeCleanup() { this.isCleaningUpFlag = true; for (const hook of this.cleanupHooks) { try { await Promise.resolve(hook.fn()); } catch (error) { console.error(`Cleanup hook failed: ${hook.name}`, error); } } } /** * Execute timeout handlers */ async executeTimeoutHandlers() { for (const handler of this.timeoutHandlers) { try { await Promise.resolve(handler()); } catch (error) { console.error('Timeout handler error:', error); } } } /** * Execute error handlers */ async executeErrorHandlers(error) { for (const handler of this.errorHandlers) { try { await Promise.resolve(handler(error)); } catch (handlerError) { console.error('Error handler failed:', handlerError); } } } /** * Check if cleaning up */ isCleaningUp() { return this.isCleaningUpFlag; } /** * Check if timed out */ isTimedOut() { return this.isTimedOutFlag; } /** * Get request duration */ getDuration() { return Date.now() - this.startTime; } } //# sourceMappingURL=lifecycle-manager.js.map