import { serve } from '@hono/node-server'; import { createAuth } from './auth/options'; import { createDb } from './db/index'; import { pendingMigrations } from './db/migrator'; import { createApp } from './http/app'; import type { AppDeps } from './http/context'; import { loadEnv } from './lib/env'; import { createOtpSender } from './modules/notifications/mailer'; const DRAIN_TIMEOUT_MS = 25_000; const env = loadEnv(); const handle = createDb(env.DATABASE_URL); const auth = createAuth({ db: handle.db, dialect: handle.dialect, env, sendOtp: createOtpSender(env), }); const deps: AppDeps = { env, handle, auth }; const { app, startDraining, inFlight } = createApp(deps); const pending = await pendingMigrations(handle).catch(() => ['']); if (pending.length > 0) { console.warn(`[boot] pending migrations: ${pending.join(', ')}. Run pnpm db:migrate.`); } if (env.ROLE === 'worker') { // The worker shares this image and this bootstrap. It serves only the health // endpoints; the job poller is wired in with the jobs module. console.info('[boot] role=worker'); } const server = serve({ fetch: app.fetch, port: env.PORT, hostname: '0.0.0.0' }, (info) => { console.info(`[boot] role=${env.ROLE} dialect=${handle.dialect} listening on :${info.port}`); }); let shuttingDown = false; async function shutdown(signal: string): Promise { if (shuttingDown) return; shuttingDown = true; console.info(`[shutdown] ${signal}: draining`); // Fail readiness first so the load balancer stops routing here, then stop accepting. startDraining(); server.close(); const deadline = Date.now() + DRAIN_TIMEOUT_MS; while (inFlight() > 0 && Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 100)); } if (inFlight() > 0) { console.warn(`[shutdown] ${inFlight()} requests still in flight after drain timeout`); if ('closeAllConnections' in server) server.closeAllConnections(); } await handle.close(); console.info('[shutdown] done'); process.exit(0); } process.on('SIGTERM', () => void shutdown('SIGTERM')); process.on('SIGINT', () => void shutdown('SIGINT'));