/** * Módulo de conexão com PostgreSQL * Gerencia pool de conexões e fornece helpers para queries */ import { Pool, type QueryResult, type QueryResultRow } from "pg"; let pool: Pool | null = null; /** * Valida variáveis de ambiente necessárias */ function validateEnvVars() { if (!process.env.PG_DB) { throw new Error( "PG_DB não está definido. Configure a variável de ambiente PG_DB com a connection string do PostgreSQL.", ); } } /** * Obtém ou cria o pool de conexões PostgreSQL */ export function getPool(): Pool { if (!pool) { validateEnvVars(); const connectionString = process.env.PG_DB; pool = new Pool({ connectionString, min: 2, // Mínimo de conexões no pool max: 10, // Máximo de conexões no pool idleTimeoutMillis: 30000, // Fecha conexões idle após 30s connectionTimeoutMillis: 5000, // Timeout ao tentar conectar }); // Event listeners para debug pool.on("error", (err) => { console.error("❌ Erro inesperado no pool de conexões PostgreSQL:", err); }); pool.on("connect", () => { console.log("✅ Nova conexão estabelecida com PostgreSQL"); }); } return pool; } /** * Executa uma query SQL com parâmetros * @param text - SQL query com placeholders ($1, $2, etc) * @param params - Array de parâmetros para a query * @returns Resultado da query */ export async function query( text: string, params?: any[], ): Promise> { const poolInstance = getPool(); const start = Date.now(); try { const result = await poolInstance.query(text, params); const duration = Date.now() - start; // Log apenas em desenvolvimento ou queries lentas (>100ms) if (process.env.NODE_ENV !== "production" || duration > 100) { console.log("📊 Query executada", { duration: `${duration}ms`, query: text.substring(0, 100), }); } return result; } catch (error: any) { const errorMessage = error instanceof Error ? error.message : String(error); const errorCode = error?.code; const errorStack = error instanceof Error ? error.stack : undefined; // Ignorar silenciosamente erros relacionados à tabela "items" não existir // Isso é esperado durante a transição e já está sendo tratado nas funções const isItemsTableMissing = errorCode === "42P01" && (errorMessage.includes('relation "items" does not exist') || text.includes("FROM items") || text.includes('FROM "items"')); // Não logar erros esperados sobre tabela items não existir if (!isItemsTableMissing) { console.error("❌ Erro ao executar query:", { query: text.substring(0, 200), error: errorMessage, stack: errorStack, }); // Se o erro for sobre coluna não existir, sugerir executar migração if ( errorMessage.includes("column") && errorMessage.includes("does not exist") ) { console.error( "\n💡 DICA: Parece que a migração do banco de dados não foi executada.", ); console.error(" Execute: pnpm run db:migrate\n"); } } throw error; } } /** * Health check do banco de dados * @returns true se o banco está acessível, false caso contrário */ export async function healthCheck(): Promise { try { const poolInstance = getPool(); const result = await poolInstance.query("SELECT NOW()"); return result.rows.length > 0; } catch (error) { console.error("❌ Health check falhou:", error); return false; } } /** * Fecha o pool de conexões * Útil para cleanup em testes ou shutdown graceful */ export async function closePool(): Promise { if (pool) { await pool.end(); pool = null; console.log("🔌 Pool de conexões PostgreSQL fechado"); } } // Cleanup ao encerrar o processo if (typeof process !== "undefined") { process.on("SIGINT", async () => { await closePool(); process.exit(0); }); process.on("SIGTERM", async () => { await closePool(); process.exit(0); }); }