import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import * as schema from './schema'; // The `atelier` Postgres database — separate from officer_db, on purpose. See PLUGIN.md. // // `ATELIER_POSTGRES_URL` rather than officer's own `OFFICERDEV_POSTGRES_URL`/`POSTGRES_URL`: this // plugin talks to a different database on (most likely) the same Postgres server, and the platform's // own connection string must never be reused here by accident — the standalone service's // `atelier_db/src/env.ts` had a whole enforcement mechanism for exactly this mistake ("a development // process cannot reach the production database"), which no longer applies once this runs as one // process under one PM2 entry, but the principle — don't let this plugin's queries land on the // platform's own tables — still does. const POSTGRES_URL = process.env.ATELIER_POSTGRES_URL; if (!POSTGRES_URL) { throw new Error( 'ATELIER_POSTGRES_URL environment variable is required — set it in the platform .env to the ' + '`atelier` Postgres database this plugin should use. See plugins/atelier/PLUGIN.md.', ); } /** * 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)); } } }