Files
pastilhas c892f2e700 officer_db with atelier_ prefixes, like every other plugin
the extraction kept atelier's own postgres database, because the brief said to
when the tables already had rows. the owner reversed it, and the reason is not
tidiness: a plugin that behaves differently from every other plugin is a
plugin nobody can reason about.

the separate database meant a second connection string to set before install,
a second thing to back up, a second thing to get wrong on a new machine, and
failure modes no other plugin shares — none of it bought anything except
avoiding a migration.

four tables now: atelier_collections, atelier_collection_items,
atelier_providers, atelier_generations. index and check constraint names carry
the prefix too — those are global in postgres and are the one thing that would
otherwise collide with another plugin.

db/schema.ts re-exports the definitions rather than duplicating them: they stay
beside the code that queries them in sidecar/db/, and that file exists to be
found by gen-plugin-schemas.

ATELIER_POSTGRES_URL is gone. db.ts reads OFFICERDEV_POSTGRES_URL ?? POSTGRES_URL,
which the platform already sets.

the standalone service's rows are NOT here and nothing depends on them being
here — the old service is still running against the old database, untouched,
and migrating is a separate watched step after this is proven on alpha.
2026-08-20 17:30:53 +00:00

73 lines
2.9 KiB
TypeScript

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<typeof postgres> };
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<boolean> {
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));
}
}
}