1import * as Redacted from "effect/Redacted";
2
3/**
4 * Connection descriptor shared by every SQL-wire `Connect` binding
5 * (RDS/Aurora, DSQL, Redshift, ...). The `url` field is the RFC-3986
6 * connection URL — it feeds `Drizzle.Postgres` / a Hyperdrive origin
7 * directly, so consumers need zero per-engine glue.
8 */
9export interface SqlConnectionInfo {
10 /** Endpoint hostname. */
11 host: string;
12 /** Endpoint port. */
13 port: number;
14 /** Database name, when one was requested. */
15 database?: string;
16 /** Login user, when the credential strategy resolves one. */
17 username?: string;
18 /**
19 * Login password (or short-lived IAM auth token), when the credential
20 * strategy resolves one.
21 */
22 password?: Redacted.Redacted<string>;
23 /** Whether the connection requires TLS. */
24 ssl: boolean;
25 /**
26 * RFC-3986 connection URL — feeds `Drizzle.Postgres` / Hyperdrive
27 * origin directly.
28 */
29 url: Redacted.Redacted<string>;
30}
31
32/**
33 * Environment variable prefix under which a `Connect` binding publishes a
34 * resource's endpoint on the host Function, derived from the service name
35 * and the resource's logical ID. A MemoryDB cluster with logical ID
36 * `SessionStore` yields `MEMORYDB_SESSIONSTORE` and variables like
37 * `MEMORYDB_SESSIONSTORE_HOST` / `MEMORYDB_SESSIONSTORE_PORT`.
38 */
39export const connectEnvPrefix = (service: string, logicalId: string): string =>
40 `${sanitizeEnvSegment(service)}_${sanitizeEnvSegment(logicalId)}`;
41
42const sanitizeEnvSegment = (segment: string): string =>
43 segment.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
44
45export interface SqlConnectionUrlOptions {
46 /**
47 * URL scheme.
48 * @default "postgresql"
49 */
50 scheme?: string;
51 host: string;
52 port?: number;
53 database?: string;
54 username?: string;
55 password?: string | Redacted.Redacted<string>;
56 /**
57 * When `true`, appends `sslmode={sslMode}` to the URL query (the
58 * postgres-flavored TLS opt-in that both `postgres.js` and Hyperdrive
59 * origins understand).
60 */
61 ssl?: boolean;
62 /**
63 * `sslmode` value appended when {@link SqlConnectionUrlOptions.ssl} is
64 * `true`. `node-pg`'s URL parser treats `require` as full-verification
65 * TLS against Node's trust store — endpoints whose certificates chain to
66 * a private CA (RDS/Aurora) must use `no-verify` (TLS on, identity
67 * verification off — libpq `require` semantics) unless the CA bundle is
68 * provided out-of-band.
69 * @default "require"
70 */
71 sslMode?: "require" | "no-verify";
72 /** Extra query parameters to append verbatim. */
73 params?: Record<string, string>;
74}
75
76/**
77 * Format an RFC-3986 SQL connection URL
78 * (`scheme://user:pass@host:port/database?sslmode=require`). Username and
79 * password are percent-encoded; the result is `Redacted` because it embeds
80 * the password.
81 */
82export const formatSqlConnectionUrl = (
83 options: SqlConnectionUrlOptions,
84): Redacted.Redacted<string> => {
85 const scheme = options.scheme ?? "postgresql";
86 const password =
87 options.password === undefined
88 ? undefined
89 : typeof options.password === "string"
90 ? options.password
91 : Redacted.value(options.password);
92 const auth =
93 options.username !== undefined
94 ? password !== undefined
95 ? `${encodeURIComponent(options.username)}:${encodeURIComponent(password)}@`
96 : `${encodeURIComponent(options.username)}@`
97 : "";
98 const port = options.port !== undefined ? `:${options.port}` : "";
99 const database =
100 options.database !== undefined
101 ? `/${encodeURIComponent(options.database)}`
102 : "";
103 const query = new URLSearchParams(options.params);
104 if (options.ssl === true) {
105 query.set("sslmode", options.sslMode ?? "require");
106 }
107 const queryString = query.size > 0 ? `?${query.toString()}` : "";
108 return Redacted.make(
109 `${scheme}://${auth}${options.host}${port}${database}${queryString}`,
110 );
111};