SPB Git

Repository Public

db.ts 1.9 KB 58 lines typescript

1/** pg Pool + a tiny typed query helper. Every statement is parameterised. */2import pg from 'pg';34// int8 (bigint counters, sizes) → number: all values here are far below 2^53.5pg.types.setTypeParser(20, (v) => Number(v));67export type SqlParam = string | number | boolean | null | Date | Buffer | string[] | object;8export interface Queryable {9  q<T = Record<string, unknown>>(sql: string, params?: readonly SqlParam[]): Promise<T[]>;10  one<T = Record<string, unknown>>(sql: string, params?: readonly SqlParam[]): Promise<T | null>;11}1213function wrap(runner: { query: (sql: string, params?: unknown[]) => Promise<{ rows: unknown[] }> }): Queryable {14  return {15    async q<T>(sql: string, params: readonly SqlParam[] = []): Promise<T[]> {16      const r = await runner.query(sql, params as unknown[]);17      return r.rows as T[];18    },19    async one<T>(sql: string, params: readonly SqlParam[] = []): Promise<T | null> {20      const r = await runner.query(sql, params as unknown[]);21      return (r.rows[0] as T | undefined) ?? null;22    },23  };24}2526export interface Db extends Queryable {27  pool: pg.Pool;28  withTx<T>(fn: (tx: Queryable) => Promise<T>): Promise<T>;29  end(): Promise<void>;30}3132export function createDb(databaseUrl: string, max = 10): Db {33  const pool = new pg.Pool({ connectionString: databaseUrl, max, idleTimeoutMillis: 30_000 });34  pool.on('error', () => {35    /* idle client errors are surfaced by the next query; never crash the process */36  });37  const base = wrap(pool);38  return {39    pool,40    ...base,41    async withTx<T>(fn: (tx: Queryable) => Promise<T>): Promise<T> {42      const client = await pool.connect();43      try {44        await client.query('BEGIN');45        const out = await fn(wrap(client));46        await client.query('COMMIT');47        return out;48      } catch (err) {49        await client.query('ROLLBACK').catch(() => undefined);50        throw err;51      } finally {52        client.release();53      }54    },55    end: () => pool.end(),56  };57}58
Files main
Menu

Theme