UNPKG

@smartsamurai/krapi-sdk

Version:

KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)

610 lines (549 loc) 16.6 kB
/** * Health Service for BackendSDK * * Provides comprehensive health diagnostics and system health monitoring. */ import fs from "fs"; import os from "os"; import path from "path"; import { DatabaseConnection, Logger } from "./core"; export interface HealthDiagnostics { success: boolean; message: string; details: { database: DatabaseHealthStatus; system: SystemHealthStatus; services: ServiceHealthStatus[]; }; recommendations: string[]; timestamp: string; } export interface DatabaseHealthStatus { status: "healthy" | "unhealthy" | "degraded"; connection: boolean; tables: TableHealthStatus[]; performance: PerformanceMetrics; message: string; } export interface TableHealthStatus { name: string; exists: boolean; rowCount: number; size: string; lastUpdated?: string; } export interface PerformanceMetrics { queryTime: number; connectionPool: { total: number; idle: number; active: number; }; slowQueries: number; } export interface SystemHealthStatus { status: "healthy" | "unhealthy" | "degraded"; memory: { used: number; total: number; percentage: number; }; cpu: { usage: number; load: number; }; disk: { used: number; total: number; percentage: number; }; uptime: number; } export interface ServiceHealthStatus { name: string; status: "healthy" | "unhealthy" | "degraded"; message: string; lastCheck: string; responseTime?: number; } export interface DatabaseHealth { healthy: boolean; message: string; details?: Record<string, unknown>; } export interface SystemHealth { healthy: boolean; message: string; details?: Record<string, unknown>; version: string; } export interface TestResult { name: string; passed: boolean; message: string; duration: number; details?: Record<string, unknown>; } export interface SchemaValidationResult { valid: boolean; issues: Array<{ type: string; table?: string; field?: string; message: string; severity: "error" | "warning" | "info"; }>; warnings: string[]; recommendations: string[]; } export interface AutoFixResult { success: boolean; fixed_issues: string[]; issues_remaining: string[]; message: string; } export interface MigrationResult { success: boolean; applied_migrations: string[]; rollbacks: string[]; message: string; } /** * Health Service for BackendSDK * * Provides comprehensive health diagnostics and system health monitoring. * * @class HealthService * @example * const healthService = new HealthService(dbConnection, logger); * const diagnostics = await healthService.runDiagnostics(); */ export class HealthService { private db: DatabaseConnection; private logger: Logger; /** * Create a new HealthService instance * * @param {DatabaseConnection} databaseConnection - Database connection * @param {Logger} logger - Logger instance */ constructor(databaseConnection: DatabaseConnection, logger: Logger) { this.db = databaseConnection; this.logger = logger; } /** * Run comprehensive health diagnostics * * Performs health checks on database, system resources, and services. * * @returns {Promise<HealthDiagnostics>} Comprehensive health diagnostics * @throws {Error} If diagnostics fail * * @example * const diagnostics = await healthService.runDiagnostics(); * if (diagnostics.success) { * console.log('System is healthy'); * } */ async runDiagnostics(): Promise<HealthDiagnostics> { try { this.logger.info("Starting comprehensive health diagnostics..."); // Add timeout to each health check to prevent hanging const timeout = 10000; // 10 seconds per check const timeoutPromise = (checkName: string) => new Promise<never>((_, reject) => setTimeout(() => reject(new Error(`${checkName} timeout after ${timeout}ms`)), timeout) ); const [databaseHealth, systemHealth, serviceHealth] = await Promise.all([ Promise.race([ this.checkDatabaseHealth(), timeoutPromise("Database health check"), ]).catch((error) => { this.logger.warn("Database health check failed or timed out:", error); return { status: "unhealthy" as const, connection: false, tables: [], performance: { queryTime: 0, connectionPool: { total: 0, idle: 0, active: 0 }, slowQueries: 0, }, message: `Database health check failed: ${error instanceof Error ? error.message : String(error)}`, }; }), Promise.race([ this.checkSystemHealth(), timeoutPromise("System health check"), ]).catch((error) => { this.logger.warn("System health check failed or timed out:", error); return { status: "unhealthy" as const, memory: { used: 0, total: 0, percentage: 0 }, cpu: { usage: 0, load: 0 }, disk: { used: 0, total: 0, percentage: 0 }, uptime: 0, }; }), Promise.race([ this.checkServiceHealth(), timeoutPromise("Service health check"), ]).catch((error) => { this.logger.warn("Service health check failed or timed out:", error); return []; }), ]); const recommendations = this.generateRecommendations( databaseHealth, systemHealth, serviceHealth ); const overallSuccess = this.determineOverallHealth( databaseHealth, systemHealth, serviceHealth ); const diagnostics: HealthDiagnostics = { success: overallSuccess, message: overallSuccess ? "System is healthy" : "System has health issues", details: { database: databaseHealth, system: systemHealth, services: serviceHealth, }, recommendations, timestamp: new Date().toISOString(), }; this.logger.info("Health diagnostics completed", { success: overallSuccess, }); return diagnostics; } catch (error) { this.logger.error("Health diagnostics failed:", error); return { success: false, message: "Health diagnostics failed", details: { database: { status: "unhealthy", connection: false, tables: [], performance: { queryTime: 0, connectionPool: { total: 0, idle: 0, active: 0 }, slowQueries: 0, }, message: "Unable to check database health", }, system: { status: "unhealthy", memory: { used: 0, total: 0, percentage: 0 }, cpu: { usage: 0, load: 0 }, disk: { used: 0, total: 0, percentage: 0 }, uptime: 0, }, services: [], }, recommendations: [ "Check system resources", "Verify database connection", ], timestamp: new Date().toISOString(), }; } } private async checkDatabaseHealth(): Promise<DatabaseHealthStatus> { try { const startTime = Date.now(); // Test connection await this.db.query("SELECT 1"); const queryTime = Date.now() - startTime; // Check critical tables (only main database tables) // Note: collections, documents, files, changelog, email_templates are project-specific // and are created in project databases, not the main database const criticalTables = [ "admin_users", "projects", "sessions", "api_keys", ]; const tableHealth: TableHealthStatus[] = []; for (const table of criticalTables) { try { // SQLite compatible table existence check // SQLite doesn't have information_schema, use PRAGMA table_list or try/catch let exists = false; let rowCount = 0; let size = "0 KB"; try { // Try to query the table - if it exists, this will succeed const countResult = await this.db.query( `SELECT COUNT(*) as count FROM ${table}` ); exists = true; rowCount = parseInt( String((countResult.rows[0] as { count: number | string }).count) ); size = `${rowCount} rows`; } catch { // Table doesn't exist or query failed exists = false; rowCount = 0; size = "0 KB"; } tableHealth.push({ name: table, exists, rowCount, size, }); } catch { tableHealth.push({ name: table, exists: false, rowCount: 0, size: "0 KB", }); } } // Check connection pool (if available) const connectionPool = { total: 0, idle: 0, active: 0, }; // Performance metrics const performance: PerformanceMetrics = { queryTime, connectionPool, slowQueries: 0, // Would need to track this over time }; const missingTables = tableHealth.filter((t) => !t.exists); const status = missingTables.length === 0 ? "healthy" : "degraded"; return { status, connection: true, tables: tableHealth, performance, message: missingTables.length === 0 ? "Database is healthy" : `Missing tables: ${missingTables.map((t) => t.name).join(", ")}`, }; } catch (error) { this.logger.error("Database health check failed:", error); return { status: "unhealthy", connection: false, tables: [], performance: { queryTime: 0, connectionPool: { total: 0, idle: 0, active: 0 }, slowQueries: 0, }, message: `Connection failed: ${error}`, }; } } private async checkSystemHealth(): Promise<SystemHealthStatus> { try { // Get system information const memory = await this.getMemoryInfo(); const cpu = await this.getCpuInfo(); const disk = await this.getDiskInfo(); const uptime = process.uptime(); // Determine overall system status const memoryHealthy = memory.percentage < 90; const cpuHealthy = cpu.usage < 90; const diskHealthy = disk.percentage < 90; const status = memoryHealthy && cpuHealthy && diskHealthy ? "healthy" : "degraded"; return { status, memory, cpu, disk, uptime, }; } catch (error) { this.logger.error("System health check failed:", error); return { status: "unhealthy", memory: { used: 0, total: 0, percentage: 0 }, cpu: { usage: 0, load: 0 }, disk: { used: 0, total: 0, percentage: 0 }, uptime: process.uptime(), }; } } private async checkServiceHealth(): Promise<ServiceHealthStatus[]> { const services: ServiceHealthStatus[] = []; const now = new Date().toISOString(); // Check database service try { const startTime = Date.now(); await this.db.query("SELECT 1"); const responseTime = Date.now() - startTime; services.push({ name: "Database", status: "healthy", message: "Database is responding", lastCheck: now, responseTime, }); } catch { services.push({ name: "Database", status: "unhealthy", message: "Database is not responding", lastCheck: now, }); } // Check file system access try { const tempDir = path.join(process.cwd(), "temp"); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } const testFile = path.join(tempDir, "health-test.txt"); fs.writeFileSync(testFile, "health test"); fs.unlinkSync(testFile); fs.rmdirSync(tempDir); services.push({ name: "File System", status: "healthy", message: "File system is accessible", lastCheck: now, }); } catch { services.push({ name: "File System", status: "unhealthy", message: "File system access failed", lastCheck: now, }); } // Check environment variables const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER"]; const missingEnvVars = requiredEnvVars.filter((env) => !process.env[env]); services.push({ name: "Environment", status: missingEnvVars.length === 0 ? "healthy" : "degraded", message: missingEnvVars.length === 0 ? "All required environment variables are set" : `Missing environment variables: ${missingEnvVars.join(", ")}`, lastCheck: now, }); return services; } private async getMemoryInfo(): Promise<{ used: number; total: number; percentage: number; }> { try { const total = os.totalmem(); const free = os.freemem(); const used = total - free; const percentage = (used / total) * 100; return { used: Math.round(used / 1024 / 1024), // MB total: Math.round(total / 1024 / 1024), // MB percentage: Math.round(percentage), }; } catch { return { used: 0, total: 0, percentage: 0 }; } } private async getCpuInfo(): Promise<{ usage: number; load: number }> { try { const loadAvg = os.loadavg(); const load = loadAvg[0] || 0; // 1 minute average // Simple CPU usage estimation based on load average const cpuCount = os.cpus().length; const usage = Math.min((load / cpuCount) * 100, 100); return { usage: Math.round(usage), load: Math.round(load * 100) / 100, }; } catch { return { usage: 0, load: 0 }; } } private async getDiskInfo(): Promise<{ used: number; total: number; percentage: number; }> { // This is a simplified approach - in production you'd want to use a proper disk usage library return { used: 0, // Would need proper disk usage library total: 0, // Would need proper disk usage library percentage: 0, // Would need proper disk usage library }; } private generateRecommendations( databaseHealth: DatabaseHealthStatus, systemHealth: SystemHealthStatus, serviceHealth: ServiceHealthStatus[] ): string[] { const recommendations: string[] = []; // Database recommendations if (databaseHealth.status !== "healthy") { if (!databaseHealth.connection) { recommendations.push("Check database connection and credentials"); } if (databaseHealth.tables.some((t) => !t.exists)) { recommendations.push( "Run database initialization to create missing tables" ); } } // System recommendations if (systemHealth.status !== "healthy") { if (systemHealth.memory.percentage > 90) { recommendations.push( "High memory usage detected - consider increasing memory or optimizing" ); } if (systemHealth.cpu.usage > 90) { recommendations.push( "High CPU usage detected - check for resource-intensive processes" ); } if (systemHealth.disk.percentage > 90) { recommendations.push( "High disk usage detected - clean up unnecessary files" ); } } // Service recommendations const unhealthyServices = serviceHealth.filter( (s) => s.status !== "healthy" ); if (unhealthyServices.length > 0) { recommendations.push( `Check the following services: ${unhealthyServices .map((s) => s.name) .join(", ")}` ); } if (recommendations.length === 0) { recommendations.push("System is healthy - no immediate action required"); } return recommendations; } private determineOverallHealth( databaseHealth: DatabaseHealthStatus, systemHealth: SystemHealthStatus, serviceHealth: ServiceHealthStatus[] ): boolean { const databaseHealthy = databaseHealth.status === "healthy"; const systemHealthy = systemHealth.status === "healthy"; const servicesHealthy = serviceHealth.every((s) => s.status === "healthy"); return databaseHealthy && systemHealthy && servicesHealthy; } }