import postgres from "postgres"; import { resolveStoreMode } from "@/src/platform/dependency-mode"; // Lazy, env-guarded Postgres client. Works with Vercel Postgres (POSTGRES_URL) // or any DATABASE_URL. Returns null when no DB is configured so the app still // builds/runs (with in-memory fallback) before storage is provisioned. let _sql: ReturnType | null = null; export function dbUrl(): string | undefined { return process.env.DATABASE_URL || process.env.POSTGRES_URL || process.env.POSTGRES_PRISMA_URL; } export function hasDb(): boolean { return Boolean(dbUrl()); } /** * P014 fix: `ssl` used to be hardcoded to `"require"` unconditionally. That * is the right default for every REAL deployment target this app has * (Vercel Postgres, managed Postgres providers), but it made `compose.dev.yaml`'s * local Postgres container -- which speaks plain, non-TLS Postgres -- and * ANY other throwaway/local Postgres (e.g. a restore-drill target) silently * unreachable via this function, contradicting that file's own doc comment * ("local-only Postgres for developers who want real persistence") and its * own "UNVERIFIED... not smoke-tested end to end" caveat -- this is * precisely why it was never smoke-tested: it could not have worked. * `DATABASE_SSL` is opt-in-to-disable ONLY; absence (the default, matching * every existing deployment's actual env) keeps the exact prior behavior. */ export function sslMode(): "require" | false { const raw = process.env.DATABASE_SSL?.trim().toLowerCase(); if (raw === "false" || raw === "disable" || raw === "off" || raw === "0") return false; return "require"; } export function getSql() { const url = dbUrl(); if (!url) return null; if (!_sql) { _sql = postgres(url, { prepare: false, idle_timeout: 20, max: 5, ssl: sslMode() }); } return _sql; } /** * Durable-store accessor for the legacy Next.js stores (`lib/orders-store.ts`, * `lib/subscriptions-store.ts`). * * Those stores predate `src/platform/dependency-mode.ts` and still call * `getSql()` directly, so when no database is configured they silently fall * back to module-global `Map`s. For orders, payments and subscriptions that is * not a degraded mode, it is invisible data loss: the API accepts an order, * returns 201, and the row evaporates on the next restart or on any other * container replica. Money-bearing state must never have a process-memory * "success" path in production. * * This routes those stores through the SAME decision every `src/repositories/*` * store already uses — `resolveStoreMode` — rather than re-implementing a * second production check. Reusing it matters for more than tidiness: * `isProductionRuntime` deliberately exempts `NEXT_PHASE=phase-production-build`, * because `next build` runs page-data collection under NODE_ENV=production * while serving nothing. A naive `process.env.NODE_ENV === "production"` test * here would fail the BUILD instead of protecting a request path. * * Returns a `sql` client when a database is configured, `null` outside * production when it is not (keeping the in-memory seam for tests/local dev), * and throws `SiaxError("DEPENDENCY_UNAVAILABLE")` in a production runtime. */ export function getSqlForDurableStore(storeName: string) { const mode = resolveStoreMode(storeName, dbUrl()); if (mode.mode === "memory") return null; return getSql(); }