diff --git a/src/server/package.json b/src/server/package.json index deb2710..70a7d79 100644 --- a/src/server/package.json +++ b/src/server/package.json @@ -10,8 +10,8 @@ "test": "bun test" }, "dependencies": { - "@lilith/quinn-db-pg": "1.0.1-dev.1776900251", "hono": "^4.6.0", + "pg": "^8.13.0", "zod": "^3.23.0" }, "devDependencies": { diff --git a/src/server/src/shared/db/index.ts b/src/server/src/shared/db/index.ts index 87695ab..7c68f97 100644 --- a/src/server/src/shared/db/index.ts +++ b/src/server/src/shared/db/index.ts @@ -1,5 +1,5 @@ import { Pool, type QueryResultRow } from 'pg'; -import { createPool } from '@lilith/quinn-db-pg'; +import { createPool } from './pool'; export type Sql = Pool; diff --git a/src/server/src/shared/db/pool.ts b/src/server/src/shared/db/pool.ts new file mode 100644 index 0000000..6ed44bb --- /dev/null +++ b/src/server/src/shared/db/pool.ts @@ -0,0 +1,38 @@ +import { Pool } from 'pg'; +import { logger } from '@/shared/logger'; + +/** + * Service-keyed Postgres pool factory. + * + * Each service reads its own connection string from `QUINN__DB_URL` + * (e.g. service `macsync` → `QUINN_MACSYNC_DB_URL`). The URL points at the local + * pgBouncer (`127.0.0.1:6432`), which terminates TLS to the DO Managed PG + * upstream — so the app→bouncer hop is plaintext on the loopback and needs no + * `ssl` block here. + * + * Vendored in-repo (was `@lilith/quinn-db-pg`) — that registry lived on the + * retired `black` host and is gone; this is the faithful, self-contained pool. + */ +export function createPool(serviceName: string): Pool { + const envKey = `QUINN_${serviceName.toUpperCase().replace(/-/g, '_')}_DB_URL`; + const connectionString = process.env[envKey]; + if (!connectionString) { + throw new Error(`${envKey} is required (no default — set the connection string in env)`); + } + + const pool = new Pool({ + connectionString, + max: Number(process.env['DB_POOL_MAX'] ?? 10), + idleTimeoutMillis: 30_000, + connectionTimeoutMillis: 10_000, + }); + + // An idle backend dropping the connection emits 'error' on the pool; without a + // handler node-postgres rethrows and crashes the process. Log and let the pool + // evict/replace the client on next checkout. + pool.on('error', (err) => { + logger.error(`db: idle client error`, { service: serviceName, err: err.message }); + }); + + return pool; +}