UNPKG

@smartsamurai/krapi-sdk

Version:

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

441 lines (416 loc) 13.5 kB
/** * Health Adapter * * Unifies HealthHttpClient and HealthService behind a common interface. * Implements all methods required by KrapiSocketInterface. */ import { DatabaseHealthManager } from "../../database-health"; import { HealthService , SystemHealth, DatabaseHealth } from "../../health-service"; import { HealthHttpClient } from "../../http-clients/health-http-client"; import { createAdapterInitError } from "./error-handler"; type Mode = "client" | "server"; export class HealthAdapter { private mode: Mode; private httpClient: HealthHttpClient | undefined; private service: HealthService | undefined; private databaseHealthManager: DatabaseHealthManager | undefined; constructor( mode: Mode, httpClient?: HealthHttpClient, service?: HealthService, databaseHealthManager?: DatabaseHealthManager ) { this.mode = mode; this.httpClient = httpClient; this.service = service; this.databaseHealthManager = databaseHealthManager; } async check(): Promise<{ healthy: boolean; message: string; details?: Record<string, unknown>; version: string; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.check(); const data = response.data as SystemHealth; const result: { healthy: boolean; message: string; details?: Record<string, unknown>; version: string; } = { healthy: data?.healthy || false, message: data?.message || "Health check completed", version: data?.version || "unknown", }; if (data?.details) { result.details = data.details; } return result; } else { if (!this.service) { throw createAdapterInitError("Health service", this.mode); } // Server mode: use runDiagnostics to get health status const diagnostics = await this.service.runDiagnostics(); return { healthy: diagnostics.success, message: diagnostics.message, details: diagnostics.details as Record<string, unknown>, version: "1.0.0", // Server version - could be from package.json }; } } async checkDatabase(): Promise<{ healthy: boolean; message: string; details?: Record<string, unknown>; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.checkDatabase(); const data = response.data as DatabaseHealth; const result: { healthy: boolean; message: string; details?: Record<string, unknown>; } = { healthy: data?.healthy || false, message: data?.message || "Database check completed", }; if (data?.details) { result.details = data.details; } return result; } else { if (!this.service) { throw createAdapterInitError("Health service", this.mode); } // Server mode: use runDiagnostics to get database health const diagnostics = await this.service.runDiagnostics(); const dbHealth = diagnostics.details.database; return { healthy: dbHealth.status === "healthy", message: dbHealth.message, details: { connection: dbHealth.connection, tables: dbHealth.tables, performance: dbHealth.performance, }, }; } } async runDiagnostics(): Promise<{ tests: Array<{ name: string; passed: boolean; message: string; duration: number; }>; summary: { total: number; passed: number; failed: number; duration: number; }; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.runDiagnostics(); const data = response.data; if (!data) { throw createAdapterInitError("Diagnostics data", this.mode, "No diagnostics data returned"); } return { tests: data.tests || [], summary: data.summary || { total: 0, passed: 0, failed: 0, duration: 0, }, }; } else { if (!this.service) { throw createAdapterInitError("Health service", this.mode); } const diagnostics = await this.service.runDiagnostics(); // Transform HealthDiagnostics to expected format const tests: Array<{ name: string; passed: boolean; message: string; duration: number; }> = []; // Database test tests.push({ name: "Database", passed: diagnostics.details.database.status === "healthy", message: diagnostics.details.database.message, duration: 0, }); // System test tests.push({ name: "System", passed: diagnostics.details.system.status === "healthy", message: `Memory: ${diagnostics.details.system.memory.percentage}%, CPU: ${diagnostics.details.system.cpu.usage}%`, duration: 0, }); // Service tests diagnostics.details.services.forEach((service) => { tests.push({ name: service.name, passed: service.status === "healthy", message: service.message, duration: 0, }); }); const passed = tests.filter((t) => t.passed).length; const failed = tests.filter((t) => !t.passed).length; return { tests, summary: { total: tests.length, passed, failed, duration: 0, }, }; } } async validateSchema(): Promise<{ valid: boolean; issues: Array<{ type: string; table?: string; field?: string; message: string; severity: "error" | "warning" | "info"; }>; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.validateSchema(); const data = response.data; if (!data) { throw createAdapterInitError("Schema validation data", this.mode, "No schema validation data returned"); } return { valid: data.valid, issues: data.issues || [], }; } else { if (!this.databaseHealthManager) { throw createAdapterInitError("Database health manager", this.mode); } const result = await this.databaseHealthManager.validateSchema(); // Transform the result to match the expected interface const issues: Array<{ type: string; table?: string; field?: string; message: string; severity: "error" | "warning" | "info"; }> = []; // Add missing tables as issues for (const table of result.missing_tables || []) { issues.push({ type: "missing_table", table, message: `Table '${table}' is missing`, severity: "error", }); } // Add extra tables as issues for (const table of result.extra_tables || []) { issues.push({ type: "extra_table", table, message: `Table '${table}' exists but is not expected`, severity: "warning", }); } // Add field mismatches as issues for (const mismatch of result.field_mismatches || []) { issues.push({ type: "field_mismatch", table: mismatch.table, field: mismatch.field, message: `Field '${mismatch.field}' in table '${mismatch.table}': expected ${mismatch.expected_type}, got ${mismatch.actual_type}`, severity: "error", }); } return { valid: result.valid, issues, }; } } async autoFix(): Promise<{ success: boolean; fixes_applied: number; details: string[]; remaining_issues: number; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.autoFix(); const data = response.data; if (!data) { throw createAdapterInitError("Auto-fix data", this.mode, "No auto-fix data returned"); } return { success: data.success, fixes_applied: data.fixed_issues?.length || 0, details: data.fixed_issues || [], remaining_issues: data.issues_remaining?.length || 0, }; } else { if (!this.databaseHealthManager) { throw createAdapterInitError("Database health manager", this.mode); } const result = await this.databaseHealthManager.autoFix(); return { success: result.success, fixes_applied: result.fixesApplied || 0, details: result.appliedFixes || [], remaining_issues: 0, }; } } async migrate(): Promise<{ success: boolean; migrations_applied: number; details: string[]; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.runMigrations(); const data = response.data; if (!data) { throw createAdapterInitError("Migration data", this.mode, "No migration data returned"); } return { success: data.success, migrations_applied: data.applied_migrations?.length || 0, details: data.applied_migrations || [], }; } else { // Migrations are typically run through external tools (e.g., knex, prisma) // In server mode, use autoFix which applies schema fixes if (!this.databaseHealthManager) { throw createAdapterInitError("Database health manager", this.mode); } const result = await this.databaseHealthManager.autoFix(); return { success: result.success, migrations_applied: result.fixesApplied || 0, details: result.appliedFixes || [], }; } } async getStats(): Promise<{ database: { size_bytes: number; tables_count: number; connections: number; uptime: number; }; system: { memory_usage: number; cpu_usage: number; disk_usage: number; }; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } // Use getPerformanceMetrics or getSystemInfo to get stats const [systemInfo, performanceMetrics] = await Promise.all([ this.httpClient.getSystemInfo().catch(() => null), this.httpClient.getPerformanceMetrics().catch(() => null), ]); const systemData = systemInfo?.data; const perfData = performanceMetrics?.data; return { database: { size_bytes: 0, // Not available in performance metrics tables_count: 0, // Not available in performance metrics connections: perfData?.database?.connection_count || 0, uptime: systemData?.uptime || 0, }, system: { memory_usage: systemData?.memory?.percentage || 0, cpu_usage: systemData?.cpu?.usage || 0, disk_usage: systemData?.disk?.percentage || 0, }, }; } else { if (!this.service) { throw createAdapterInitError("Health service", this.mode); } const diagnostics = await this.service.runDiagnostics(); return { database: { size_bytes: 0, // Not available in HealthService tables_count: diagnostics.details.database.tables.length, connections: diagnostics.details.database.performance.connectionPool.total, uptime: diagnostics.details.system.uptime, }, system: { memory_usage: diagnostics.details.system.memory.percentage, cpu_usage: diagnostics.details.system.cpu.usage, disk_usage: diagnostics.details.system.disk.percentage, }, }; } } async repairDatabase(): Promise<{ success: boolean; actions: string[]; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.repairDatabase(); const data = response.data; if (!data) { throw createAdapterInitError("Repair data", this.mode, "No repair data returned"); } const actions: string[] = []; if (data.repaired_tables) actions.push(...data.repaired_tables.map((t) => `Repaired table: ${t}`)); if (data.repaired_indexes) actions.push(...data.repaired_indexes.map((i) => `Repaired index: ${i}`)); if (data.repaired_constraints) actions.push(...data.repaired_constraints.map((c) => `Repaired constraint: ${c}`)); return { success: data.success, actions, }; } else { if (!this.databaseHealthManager) { throw createAdapterInitError("Database health manager", this.mode); } // Use autoFix as the repair mechanism const result = await this.databaseHealthManager.autoFix(); return { success: result.success, actions: result.appliedFixes || [], }; } } }