Spaces:
Paused
Paused
| 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<void> { | |
| 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<void> | |
| ): Promise<{ service: string; ok: boolean; error?: string }> { | |
| const timeout = new Promise<never>((_, 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(); | |