← back to Grant

lib/db.ts

50 lines

import { Pool, PoolClient, QueryResult, QueryResultRow } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 5000,
});

// Log pool errors so they don't crash the process
pool.on('error', (err: Error) => {
  console.error('[db] Unexpected pool error:', err.message);
});

/**
 * Execute a parameterized query against the pool.
 * Returns the full QueryResult so callers can read .rows, .rowCount, etc.
 */
export async function query<T extends QueryResultRow = QueryResultRow>(
  text: string,
  params?: unknown[],
): Promise<QueryResult<T>> {
  const start = Date.now();
  try {
    const result = await pool.query<T>(text, params);
    const duration = Date.now() - start;
    if (duration > 2000) {
      // 2026-05-30 (audit P2-5): only echo SQL text outside production so query
      // structure doesn't sit in prod log rotation.
      const detail = process.env.NODE_ENV === 'production' ? '' : ` ${text.slice(0, 120)}`;
      console.warn(`[db] Slow query (${duration}ms):${detail}`);
    }
    return result;
  } catch (err) {
    const detail = process.env.NODE_ENV === 'production' ? '' : `\n  SQL: ${text.slice(0, 200)}`;
    console.error('[db] Query error:', (err as Error).message, detail);
    throw err;
  }
}

/**
 * Acquire a dedicated client from the pool -- use for transactions.
 * IMPORTANT: Always call client.release() in a finally block.
 */
export async function getClient(): Promise<PoolClient> {
  return pool.connect();
}

export default pool;