1import type * as PgClient from "@effect/sql-pg/PgClient";
2import * as Redacted from "effect/Redacted";
3
4type PgSsl = PgClient.PgPoolConfig["ssl"];
5
6/**
7 * `sslmode` values `@effect/sql-pg` (≥ 4.0.0-rc.113, still as of rc.115)
8 * refuses unless `ssl` is set explicitly: `sslmode "prefer" is not
9 * supported: set ssl explicitly to true or false`. node-postgres treated both
10 * as "TLS on" (aliases of `verify-full`), and URLs carry them by default in
11 * places — Hyperdrive's local `dev` origin passthrough hands the worker
12 * `?sslmode=prefer`, and `prefer` is libpq's own default.
13 */
14const OPPORTUNISTIC_SSL_MODES: ReadonlySet<string> = new Set([
15 "prefer",
16 "allow",
17]);
18
19/**
20 * Resolve the `ssl` option to hand `@effect/sql-pg` for a connection URL.
21 *
22 * When the caller left `ssl` implicit and the URL's `sslmode` is `prefer` or
23 * `allow`, returns `ssl: true` so the connection is TLS-on exactly as it was
24 * under node-postgres. Railway's `no-verify` mode defaults to TLS without
25 * certificate verification. Explicit caller settings take precedence, and
26 * other URL modes keep driving the decision inside `@effect/sql-pg`.
27 *
28 * (`@effect/sql-pg` < rc.115 also sent no TLS SNI; that was fixed upstream in
29 * Effect-TS/effect#8174, so this helper no longer sets `servername`.)
30 */
31export const resolveSsl = (
32 url: Redacted.Redacted<string>,
33 ssl: PgSsl,
34): PgSsl => {
35 if (ssl !== undefined) return ssl;
36 let parsed: URL;
37 try {
38 parsed = new URL(Redacted.value(url));
39 } catch {
40 // Let `@effect/sql-pg` report the malformed URL.
41 return ssl;
42 }
43 const sslmode = parsed.searchParams.get("sslmode");
44 if (sslmode === "no-verify") return { rejectUnauthorized: false };
45 return sslmode !== null && OPPORTUNISTIC_SSL_MODES.has(sslmode) ? true : ssl;
46};
47
48/**
49 * Normalize the URL and TLS settings together for Effect's Postgres driver.
50 * Translate node-postgres's `sslmode=no-verify` to `require` with explicit
51 * TLS options so it also works with stricter URL parsers. Retain its verification
52 * choice in the TLS options. Other URL parameters and explicit TLS settings
53 * remain intact; credentials stay redacted.
54 */
55export const resolveConnectionOptions = (
56 url: Redacted.Redacted<string>,
57 ssl?: PgSsl,
58): { url: Redacted.Redacted<string>; ssl: PgSsl } => {
59 const resolvedSsl = resolveSsl(url, ssl);
60 try {
61 const parsed = new URL(Redacted.value(url));
62 if (parsed.searchParams.get("sslmode") === "no-verify") {
63 parsed.searchParams.set("sslmode", "require");
64 return { url: Redacted.make(parsed.toString()), ssl: resolvedSsl };
65 }
66 } catch {
67 // The driver owns malformed URL diagnostics.
68 }
69 return { url, ssl: resolvedSsl };
70};