refactor(db): consolidate the two Postgres pools
db/client.ts was creating its own pg Pool with its own lazy initialization, while src/lib/db.ts already had an identical pool and a withTx helper. Two pools per process means double the Postgres connections, double the env-var parsing, and double the BEGIN/COMMIT plumbing to maintain. After this change: - db/client.ts imports getPool/withTx from src/lib/db.ts - withBrand and withPlatformAdmin wrap withTx (single transaction helper) - One pg.Pool per Node process No behavior change for callers — withTx is byte-identical to the deleted runInTransaction. The shared getPool uses a 30s connection timeout (was 10s in the deleted copy); 30s is what src/lib/db.ts has always shipped to production via its callers, so this aligns the Drizzle layer with the rest of the app.
This commit is contained in:
+12
-49
@@ -23,38 +23,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "server-only";
|
import "server-only";
|
||||||
import { Pool, type PoolClient } from "pg";
|
|
||||||
import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres";
|
import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||||
import * as schema from "./schema";
|
import * as schema from "./schema";
|
||||||
|
import { getPool, withTx } from "@/lib/db";
|
||||||
|
|
||||||
type Schema = typeof schema;
|
type Schema = typeof schema;
|
||||||
export type Db = NodePgDatabase<Schema>;
|
export type Db = NodePgDatabase<Schema>;
|
||||||
|
|
||||||
let _pool: Pool | null = null;
|
/**
|
||||||
|
* The Drizzle layer reuses the shared `pg` `Pool` from `@/lib/db` so the
|
||||||
function getPool(): Pool {
|
* app connects to Postgres through a single pool. Connection settings
|
||||||
if (_pool) return _pool;
|
* (`PG_POOL_MAX`, `PG_POOL_IDLE_MS`, `PG_POOL_CONN_TIMEOUT_MS`,
|
||||||
const connectionString = process.env.DATABASE_URL;
|
* `DATABASE_URL`) live in one place. See `src/lib/db.ts` for the config.
|
||||||
if (!connectionString) {
|
*
|
||||||
throw new Error(
|
* The `withBrand` and `withPlatformAdmin` helpers reuse `withTx` from
|
||||||
"DATABASE_URL is not set. Add it to .env.local (see .env.example).",
|
* `@/lib/db` so the BEGIN/COMMIT/ROLLBACK handling has a single home.
|
||||||
);
|
*/
|
||||||
}
|
|
||||||
_pool = new Pool({
|
|
||||||
connectionString,
|
|
||||||
max: parseInt(process.env.PG_POOL_MAX ?? "10", 10),
|
|
||||||
idleTimeoutMillis: parseInt(process.env.PG_POOL_IDLE_MS ?? "30000", 10),
|
|
||||||
connectionTimeoutMillis: parseInt(
|
|
||||||
process.env.PG_POOL_CONN_TIMEOUT_MS ?? "10000",
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
allowExitOnIdle: false,
|
|
||||||
});
|
|
||||||
_pool.on("error", (err) => {
|
|
||||||
console.error("[db] idle client error", err);
|
|
||||||
});
|
|
||||||
return _pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run `fn` with a Drizzle client. No brand context is set — the caller
|
* Run `fn` with a Drizzle client. No brand context is set — the caller
|
||||||
@@ -83,7 +67,7 @@ export async function withBrand<T>(
|
|||||||
brandId: string,
|
brandId: string,
|
||||||
fn: (db: Db) => Promise<T>,
|
fn: (db: Db) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return runInTransaction(async (client) => {
|
return withTx(async (client) => {
|
||||||
// set_config(setting, value, is_local) — is_local=true makes it
|
// set_config(setting, value, is_local) — is_local=true makes it
|
||||||
// transaction-local so it auto-resets at COMMIT/ROLLBACK and never
|
// transaction-local so it auto-resets at COMMIT/ROLLBACK and never
|
||||||
// leaks across pooled connections.
|
// leaks across pooled connections.
|
||||||
@@ -103,7 +87,7 @@ export async function withBrand<T>(
|
|||||||
export async function withPlatformAdmin<T>(
|
export async function withPlatformAdmin<T>(
|
||||||
fn: (db: Db) => Promise<T>,
|
fn: (db: Db) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return runInTransaction(async (client) => {
|
return withTx(async (client) => {
|
||||||
await client.query("SELECT set_config('app.current_brand_id', '', true)");
|
await client.query("SELECT set_config('app.current_brand_id', '', true)");
|
||||||
await client.query("SELECT set_config('app.platform_admin', 'true', true)");
|
await client.query("SELECT set_config('app.platform_admin', 'true', true)");
|
||||||
const db = drizzle(client, { schema });
|
const db = drizzle(client, { schema });
|
||||||
@@ -111,25 +95,4 @@ export async function withPlatformAdmin<T>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runInTransaction<T>(
|
|
||||||
fn: (client: PoolClient) => Promise<T>,
|
|
||||||
): Promise<T> {
|
|
||||||
const client = await getPool().connect();
|
|
||||||
try {
|
|
||||||
await client.query("BEGIN");
|
|
||||||
const result = await fn(client);
|
|
||||||
await client.query("COMMIT");
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
try {
|
|
||||||
await client.query("ROLLBACK");
|
|
||||||
} catch {
|
|
||||||
// ignore secondary rollback failure
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
client.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { schema };
|
export { schema };
|
||||||
|
|||||||
Reference in New Issue
Block a user