153 lines
4.1 KiB
TypeScript
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);
|
|
});
|
|
}
|