diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 5d810d4..a0b3f5b 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,8 +1,30 @@ // src/hooks.server.ts import type { Handle } from '@sveltejs/kit'; import { generateUserId } from '$lib/generateUserId.js'; +import { ensureDatabaseConnection } from '$lib/database/healthCheck'; + +// Global flag to track if database health check has been performed +let dbHealthCheckPerformed = false; export const handle: Handle = async ({ event, resolve }) => { + // Perform database health check only once during application startup + if (!dbHealthCheckPerformed) { + try { + await ensureDatabaseConnection({ + maxRetries: 3, + baseDelay: 1000, + maxDelay: 10000, + timeout: 5000 + }); + dbHealthCheckPerformed = true; + } catch (error) { + console.error('Database health check failed:', error); + // The ensureDatabaseConnection function will exit the process + // if the database is unavailable, so this catch is just for safety + process.exit(1); + } + } + const cactoideUserId = event.cookies.get('cactoideUserId'); const userId = generateUserId(); diff --git a/src/lib/database/healthCheck.ts b/src/lib/database/healthCheck.ts new file mode 100644 index 0000000..de74c78 --- /dev/null +++ b/src/lib/database/healthCheck.ts @@ -0,0 +1,104 @@ +import postgres from 'postgres'; +import { env } from '$env/dynamic/private'; + +interface HealthCheckOptions { + maxRetries?: number; + baseDelay?: number; + maxDelay?: number; + timeout?: number; +} + +interface HealthCheckResult { + success: boolean; + error?: string; + attempts: number; + duration?: number; +} + +/** + * Performs a database health check with retry logic and exponential backoff + */ +export async function checkDatabaseHealth( + options: HealthCheckOptions = {} +): Promise { + const { + maxRetries = 3, + baseDelay = 1000, // 1 second + maxDelay = 10000, // 10 seconds + timeout = 5000 // 5 seconds + } = options; + + if (!env.DATABASE_URL) { + console.error('DATABASE_URL environment variable is not set'); + return { + success: false, + error: 'DATABASE_URL environment variable is not set', + attempts: 0 + }; + } + + let lastError: Error | null = null; + + console.log(`Starting database health check (max retries: ${maxRetries})`); + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + console.log(`Attempt ${attempt}/${maxRetries} - Testing database connection`); + + try { + // Create a new connection for the health check + const client = postgres(env.DATABASE_URL, { + max: 1, + idle_timeout: 20, + connect_timeout: timeout / 1000, // Convert to seconds + onnotice: () => {} // Suppress notices + }); + + // Test the connection with a simple query + await client`SELECT 1 as health_check`; + await client.end(); + + console.log(`Database connection successful on attempt ${attempt}.`); + + return { + success: true, + attempts: attempt + }; + } catch (error) { + lastError = error as Error; + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + console.error(`Connection failed on attempt ${attempt}: ${errorMessage}`); + + // Don't wait after the last attempt + if (attempt < maxRetries) { + const delay = Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay); + console.log(`Waiting ${delay}ms before retry...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + } + + const finalError = lastError?.message || 'Unknown database connection error'; + + console.error(`All ${maxRetries} attempts failed. Final error: ${finalError}`); + + return { + success: false, + error: finalError, + attempts: maxRetries + }; +} + +/** + * Runs database health check and exits the process if it fails + */ +export async function ensureDatabaseConnection(options?: HealthCheckOptions): Promise { + const result = await checkDatabaseHealth(options); + + if (!result.success) { + console.error('Database connection failed after all retry attempts'); + console.error(`Error: ${result.error}`); + console.error(`Attempts made: ${result.attempts}`); + console.error('Exiting application...'); + process.exit(1); + } +}