const fs = require('fs'); const path = require('path'); const { Pool } = require('pg'); const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); pool.on('error', (err) => { console.error('Unexpected Postgres pool error:', err); }); // Apply schema.sql on startup (idempotent — uses CREATE TABLE IF NOT EXISTS). async function init() { const schema = fs.readFileSync(path.join(__dirname, '..', 'db', 'schema.sql'), 'utf8'); await pool.query(schema); } function query(text, params) { return pool.query(text, params); } // Run a function inside a transaction, passing it a dedicated client. // Postgres breaks a deadlock by killing one of the transactions. The victim // rolled back and changed nothing, so running it again is safe — and usually // works, because the transaction it collided with has since finished. const RETRY_CODES = new Set(['40P01', '40001']); // deadlock_detected, serialization_failure async function transaction(fn, attempt = 0) { const client = await pool.connect(); try { await client.query('BEGIN'); const result = await fn(client); await client.query('COMMIT'); return result; } catch (err) { await client.query('ROLLBACK').catch(() => {}); // the connection may be gone if (!RETRY_CODES.has(err.code) || attempt >= 3) throw err; console.warn(`Database ${err.code}, retrying the transaction (attempt ${attempt + 2}).`); } finally { client.release(); // before the pause below, so a retry doesn't hold a connection } await new Promise((r) => setTimeout(r, 50 * (attempt + 1) ** 2)); return transaction(fn, attempt + 1); } module.exports = { pool, query, transaction, init };