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.
73 lines
2.9 KiB
TypeScript
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));
|
|
}
|
|
}
|
|
}
|