1import * as Effect from "effect/Effect";
2import * as Redacted from "effect/Redacted";
3import type * as Scope from "effect/Scope";
4import type { Pool, PoolConfig } from "pg";
5
6/**
7 * Lazily load the raw `pg` driver (an optional peer dependency of
8 * alchemy) with a descriptive failure when it isn't installed. CJS/ESM
9 * interop is normalized — callers always get the module's named surface.
10 */
11export const importPg = (): Promise<typeof import("pg")> =>
12 import("pg")
13 .then((mod) =>
14 (mod as { default?: { Pool?: unknown } }).default?.Pool !== undefined
15 ? (mod as unknown as { default: typeof import("pg") }).default
16 : mod,
17 )
18 .catch((cause) => {
19 throw new Error(
20 "Failed to load the 'pg' driver. Install the optional peer dependency 'pg' to connect to Postgres.",
21 { cause },
22 );
23 });
24
25/**
26 * Open a raw `pg.Pool` on the current `Scope` — `pool.end()` runs when the
27 * scope closes. Pair with `makeExecutionMemo` for the one-pool-per-event
28 * shape workerd and Lambda require (see `SQL/Postgres.ts` for the
29 * effect-sql equivalent).
30 *
31 * `max` defaults to 1: per-execution pools never need more than one
32 * connection.
33 */
34export const openPostgresPool = (
35 url: Effect.Effect<Redacted.Redacted<string>>,
36 config?: Omit<PoolConfig, "connectionString">,
37): Effect.Effect<Pool, never, Scope.Scope> =>
38 Effect.gen(function* () {
39 const pg = yield* Effect.promise(importPg);
40 const connectionString = Redacted.value(yield* url);
41 return yield* Effect.acquireRelease(
42 Effect.sync(() => new pg.Pool({ connectionString, max: 1, ...config })),
43 (pool) => Effect.promise(() => pool.end()),
44 );
45 });