mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Share one configured PostgreSQL pool across worker repositories and make API and worker connection budgets explicit. Add privacy-safe aggregate request, query, pool, sync, and outbox telemetry, a loopback-only representative load fixture, bounded worker shutdown, and focused regression and integration coverage. Document the database-wide budget formula and defer pg_stat_statements activation to a reviewed migration and planned restart.
53 lines
2.1 KiB
TypeScript
53 lines
2.1 KiB
TypeScript
import { Pool } from 'pg';
|
|
import { instrumentPool, type TelemetryProcess } from '../telemetry.js';
|
|
|
|
/**
|
|
* A repository is constructed either from a connection string, in which case it
|
|
* owns the pool it creates and must end it, or from a pool the caller owns. The
|
|
* API process takes the second path so every domain shares one bounded pool
|
|
* instead of opening a default-sized pool per repository; tests keep taking the
|
|
* first so each suite stays independently closable.
|
|
*/
|
|
export type DatabaseConnection = string | Pool;
|
|
|
|
export interface DatabasePoolOptions {
|
|
/** Server-wide ceiling on concurrent PostgreSQL connections. */
|
|
max?: number;
|
|
idleTimeoutMillis?: number;
|
|
connectionTimeoutMillis?: number;
|
|
}
|
|
|
|
/**
|
|
* `pg` defaults to ten connections per pool, which multiplied by one pool per
|
|
* repository. One shared pool of the same size keeps the same headroom for a
|
|
* single API process while bounding what a self-hosted PostgreSQL must accept.
|
|
*/
|
|
export const DEFAULT_POOL_MAX = 10;
|
|
|
|
export function createDatabasePool(databaseUrl: string, options: DatabasePoolOptions = {}, telemetryProcess?: TelemetryProcess): Pool {
|
|
const pool = new Pool({
|
|
connectionString: databaseUrl,
|
|
max: options.max ?? DEFAULT_POOL_MAX,
|
|
...(options.idleTimeoutMillis === undefined ? {} : { idleTimeoutMillis: options.idleTimeoutMillis }),
|
|
...(options.connectionTimeoutMillis === undefined ? {} : { connectionTimeoutMillis: options.connectionTimeoutMillis }),
|
|
});
|
|
return telemetryProcess ? instrumentPool(telemetryProcess, pool) : pool;
|
|
}
|
|
|
|
export interface ResolvedPool {
|
|
pool: Pool;
|
|
/** True when this repository created the pool and is responsible for ending it. */
|
|
owned: boolean;
|
|
}
|
|
|
|
export function resolvePool(connection: DatabaseConnection): ResolvedPool {
|
|
return typeof connection === 'string'
|
|
? { pool: new Pool({ connectionString: connection }), owned: true }
|
|
: { pool: connection, owned: false };
|
|
}
|
|
|
|
/** Ends the pool only when this repository created it, so shared pools survive. */
|
|
export async function closeResolvedPool(pool: Pool, owned: boolean): Promise<void> {
|
|
if (owned) await pool.end();
|
|
}
|