spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import pg from "pg";2import { config } from "../config.js";3import { logger } from "../logger.js";45const { Pool, types } = pg;6// timestamptz stays a Date (pg default → ISO in JSON); numeric/int8 as numbers where safe.7types.setTypeParser(20, (v) => Number(v));8types.setTypeParser(1700, (v) => Number(v));910export const pool = new Pool({11 connectionString: config.databaseUrl,12 max: config.role === "api" ? 8 : 16,13 idleTimeoutMillis: 30_000,14 statement_timeout: 60_000,15 options: "-c lock_timeout=15000", // never let a lock wait starve the pool (health checks must keep answering)16 application_name: `market-atlas-${config.role}`,17});1819pool.on("error", (err) => logger.error({ err }, "pg pool error"));2021export type Queryable = pg.Pool | pg.PoolClient;2223export async function query<T extends pg.QueryResultRow = pg.QueryResultRow>(text: string, params: unknown[] = [], client: Queryable = pool) {24 return client.query<T>(text, params as never[]);25}2627export async function withTransaction<T>(fn: (client: pg.PoolClient) => Promise<T>): Promise<T> {28 const client = await pool.connect();29 try {30 await client.query("begin");31 const out = await fn(client);32 await client.query("commit");33 return out;34 } catch (err) {35 await client.query("rollback").catch(() => {});36 throw err;37 } finally {38 client.release();39 }40}4142export const tsToMs = (v: unknown): number | null => {43 if (v == null) return null;44 if (v instanceof Date) return v.getTime();45 const ms = Date.parse(String(v));46 return Number.isFinite(ms) ? ms : null;47};4849export const msToTs = (ms: number | null | undefined): string | null => (ms == null ? null : new Date(ms).toISOString());50