Files
2025-11-11 03:08:36 -03:00

153 lines
4.1 KiB
TypeScript

/**
* 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<T extends QueryResultRow = any>(
text: string,
params?: any[],
): Promise<QueryResult<T>> {
const poolInstance = getPool();
const start = Date.now();
try {
const result = await poolInstance.query<T>(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<boolean> {
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<void> {
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);
});
}