import { prisma } from '../database/prisma.js'; import { neo4jAdapter } from '../adapters/Neo4jAdapter.js'; // BootstrapGate ensures critical infra is reachable before we start listening export class BootstrapGate { constructor( private readonly options = { timeoutMs: 10000, // Increased timeout for slow container startup redisUrl: process.env.REDIS_URL || 'redis://localhost:6379', } ) {} async init(): Promise { const results = await Promise.all([this.checkPostgres(), this.checkNeo4j(), this.checkRedis()]); const failing = results.filter(r => !r.ok); if (failing.length > 0) { failing.forEach(f => console.error(`❌ Bootstrap check failed: ${f.service} -> ${f.error}`)); throw new Error('BootstrapGate failed'); } console.log('✅ BootstrapGate: all critical services are reachable'); } private async checkPostgres(): Promise<{ service: string; ok: boolean; error?: string }> { const op = async () => { // prisma uses configured connection (expected on port 5433 via env) await prisma.$queryRaw`SELECT 1`; }; return this.runWithTimeout('postgres', op); } private async checkNeo4j(): Promise<{ service: string; ok: boolean; error?: string }> { const op = async () => { const health = await neo4jAdapter.healthCheck(); if (!health.connected) throw new Error('neo4j not connected'); }; return this.runWithTimeout('neo4j', op); } private async checkRedis(): Promise<{ service: string; ok: boolean; error?: string }> { const op = async () => { // Dynamic import to avoid hard failure if dependency missing during tests const Redis = (await import('ioredis')).default; const client = new Redis(this.options.redisUrl, { maxRetriesPerRequest: 2 }); try { const res = await client.ping(); if (res !== 'PONG') throw new Error(`unexpected ping reply: ${res}`); } finally { client.disconnect(); } }; return this.runWithTimeout('redis', op); } private async runWithTimeout( service: string, fn: () => Promise ): Promise<{ service: string; ok: boolean; error?: string }> { const timeout = new Promise((_, reject) => { setTimeout(() => reject(new Error('timeout')), this.options.timeoutMs); }); try { await Promise.race([fn(), timeout]); return { service, ok: true }; } catch (error: any) { return { service, ok: false, error: error?.message || 'unknown error' }; } } } export const bootstrapGate = new BootstrapGate();