@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
401 lines β’ 12.6 kB
JavaScript
/**
* @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