Skip to content

Commit 7d638cb

Browse files
authored
feat: configure explicit Postgres connection pool and timeouts (#114)
* feat: configure explicit Postgres connection pool and timeouts * test: cover intFromEnv fallback branches
1 parent 36b8dad commit 7d638cb

3 files changed

Lines changed: 58 additions & 1 deletion

File tree

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ PUBLIC_SITE_URL=http://localhost:5173
33

44
# Database
55
DATABASE_URL="postgres://user:password@localhost:5432/postguard_business"
6+
# Optional connection-pool tuning (postgres.js). Defaults are used if unset.
7+
# DB_POOL_MAX=10 # max pooled connections
8+
# DB_IDLE_TIMEOUT=20 # seconds before an idle connection is closed
9+
# DB_CONNECT_TIMEOUT=10 # seconds to wait when opening a connection
10+
# DB_MAX_LIFETIME=1800 # seconds before a connection is recycled
611

712
# Yivi (YIVI_SERVER_URL and YIVI_SERVER_TOKEN set in docker-compose / k8s)
813
YIVI_DEMO_ATTRIBUTES=true

src/lib/server/db/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ import { env } from '$env/dynamic/private';
55

66
if (!env.DATABASE_URL) throw new Error('DATABASE_URL is not set');
77

8-
const client = postgres(env.DATABASE_URL);
8+
// Parse a positive-integer env var, falling back to a default when unset/invalid.
9+
export function intFromEnv(value: string | undefined, fallback: number): number {
10+
const parsed = Number(value);
11+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
12+
}
13+
14+
// Explicit pool sizing + timeouts. postgres.js otherwise leaves connect/idle
15+
// timeouts effectively unbounded, and `max` should be sized against Postgres
16+
// `max_connections` across replicas — so make it tunable via env.
17+
const client = postgres(env.DATABASE_URL, {
18+
max: intFromEnv(env.DB_POOL_MAX, 10),
19+
idle_timeout: intFromEnv(env.DB_IDLE_TIMEOUT, 20), // seconds
20+
connect_timeout: intFromEnv(env.DB_CONNECT_TIMEOUT, 10), // seconds
21+
max_lifetime: intFromEnv(env.DB_MAX_LIFETIME, 60 * 30) // seconds
22+
});
923

1024
export const db = drizzle(client, { schema });

tests/unit/db-pool.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
3+
// Stub the module's side-effectful imports so importing the db module doesn't
4+
// try to open a real connection at import time.
5+
vi.mock('$env/dynamic/private', () => ({
6+
env: { DATABASE_URL: 'postgres://user:pass@localhost:5432/db' }
7+
}));
8+
vi.mock('postgres', () => ({ default: vi.fn(() => ({})) }));
9+
vi.mock('drizzle-orm/postgres-js', () => ({ drizzle: vi.fn(() => ({})) }));
10+
11+
import { intFromEnv } from '$lib/server/db';
12+
13+
describe('intFromEnv', () => {
14+
it('parses a valid positive integer', () => {
15+
expect(intFromEnv('25', 10)).toBe(25);
16+
});
17+
18+
it('falls back when unset', () => {
19+
expect(intFromEnv(undefined, 10)).toBe(10);
20+
});
21+
22+
it('falls back on an empty string', () => {
23+
expect(intFromEnv('', 10)).toBe(10);
24+
});
25+
26+
it('falls back on a non-numeric value', () => {
27+
expect(intFromEnv('abc', 10)).toBe(10);
28+
});
29+
30+
it('falls back on zero and negative values', () => {
31+
expect(intFromEnv('0', 10)).toBe(10);
32+
expect(intFromEnv('-5', 10)).toBe(10);
33+
});
34+
35+
it('falls back on a non-integer (float)', () => {
36+
expect(intFromEnv('1.5', 10)).toBe(10);
37+
});
38+
});

0 commit comments

Comments
 (0)