import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import * as schema from './schema'; // officer_db, like every other plugin. // // This originally connected to a separate `atelier` database via `ATELIER_POSTGRES_URL`, carried over // from the standalone service. Reversed on 2026-08-20 by the owner: the tables are `atelier_`-prefixed // in officer_db, the plugin starts empty, and the standalone service's rows are migrated afterwards // with someone watching. // // The reason is not tidiness. A second database meant a second connection string to set, a second thing // to back up, and a plugin whose failure modes nobody else's plugin shares. `OFFICERDEV_POSTGRES_URL ?? // POSTGRES_URL` is the house convention (src/servers/env-prefix.ts), and the standalone repo's whole // "a development process cannot reach production" enforcement stops applying once this is one process // under one PM2 entry. const POSTGRES_URL = process.env.OFFICERDEV_POSTGRES_URL ?? process.env.POSTGRES_URL; if (!POSTGRES_URL) { throw new Error('OFFICERDEV_POSTGRES_URL (or POSTGRES_URL) is required — the platform sets it in .env.'); } /** * One client per PROCESS, not one per module evaluation — see the standalone repo's original comment * (kept because the reasoning still holds under PM2's own occasional module reloads): a fresh pool per * reload abandons the previous one's open sockets, and a query in flight when the swap happens never * resolves and never releases its connection. */ const globalForDb = globalThis as unknown as { __atelierPgClient?: ReturnType }; const client = (globalForDb.__atelierPgClient ??= postgres(POSTGRES_URL, { max: 10, idle_timeout: 30, connect_timeout: 10, connection: { statement_timeout: 15_000, }, })); export const db = drizzle(client, { schema }); /** * Block until Postgres answers, or give up after `timeoutMs`. * * `postgres()` is lazy — nothing fails at import when the database is not up yet — so startup work * that queries fires once, fails, and is swallowed by whatever `.catch()` it was written with. The * restart sweep that fails interrupted rows (`failInterrupted`) is exactly that kind of work. */ export async function waitForDatabase(timeoutMs = 30_000): Promise { const started = Date.now(); let announced = false; for (;;) { try { await client`select 1`; if (announced) console.log('[atelier:db] Postgres is up'); return true; } catch (error) { if (Date.now() - started >= timeoutMs) { console.error( `[atelier:db] Postgres did not answer within ${Math.round(timeoutMs / 1000)}s:`, error instanceof Error ? error.message : error, ); return false; } if (!announced) { console.log('[atelier:db] waiting for Postgres…'); announced = true; } await new Promise((resolve) => setTimeout(resolve, 1_000)); } } }