1import type * as Presign from "@distilled.cloud/aws/Presign";
2import type * as secretsmanager from "@distilled.cloud/aws/secrets-manager";
3import type * as Effect from "effect/Effect";
4import type * as Redacted from "effect/Redacted";
5import * as Binding from "../../Binding.ts";
6import type { Input } from "../../Input.ts";
7import type { RuntimeContext } from "../../RuntimeContext.ts";
8import type { SecurityGroupId } from "../EC2/SecurityGroup.ts";
9import type { SubnetId } from "../EC2/Subnet.ts";
10import type { Secret } from "../SecretsManager/Secret.ts";
11import type { DBCluster } from "./DBCluster.ts";
12import type { DBProxy } from "./DBProxy.ts";
13import type { DBProxyEndpoint } from "./DBProxyEndpoint.ts";
14
15export type ConnectResource = DBCluster | DBProxy | DBProxyEndpoint;
16
17export interface ConnectionInfo {
18 host: string;
19 port: number;
20 database?: string;
21 username?: string;
22 password?: string;
23 ssl: boolean;
24 /**
25 * RFC-3986 connection URL — feeds `Drizzle.Postgres` / a Hyperdrive
26 * origin directly. `Redacted` because it embeds the password.
27 */
28 url: Redacted.Redacted<string>;
29 /**
30 * IAM auth mode only: re-mints a fresh short-lived (15 minute) IAM
31 * database authentication token.
32 *
33 * Per-execution pools (Lambda invoke, Worker event) never need it — the
34 * `Connect` runtime effect re-runs on each execution and mints a fresh
35 * token as `password`/`url`. Long-lived serverful pools (ECS Task /
36 * ServerHost) should wire this into the driver's lazy-password hook
37 * (`postgres.js` `pass: () => ...`, `pg` `password: () => ...`) so every
38 * new physical connection authenticates with a fresh token instead of a
39 * token minted at pool construction.
40 */
41 refreshPassword?: Effect.Effect<
42 Redacted.Redacted<string>,
43 Presign.PresignError
44 >;
45}
46
47interface ConnectOptionsBase {
48 database?: string;
49 port?: number;
50 ssl?: boolean;
51 /**
52 * Subnets the host Function should be attached to so it can open a
53 * socket to the cluster/proxy. Wired through the host's `vpc` binding
54 * channel — equivalent to setting the Function's `vpc` prop manually.
55 */
56 subnetIds?: Input<SubnetId[]>;
57 /**
58 * Security groups for the host Function's VPC attachment (see
59 * {@link ConnectOptionsBase.subnetIds}). The referenced groups must be
60 * allowed ingress on the database port by the cluster/proxy's security
61 * group.
62 */
63 securityGroupIds?: Input<SecurityGroupId[]>;
64}
65
66/**
67 * Default credential strategy: read `{ username, password }` from a
68 * Secrets Manager secret. The deploy half grants
69 * `secretsmanager:GetSecretValue` on the secret; the runtime half fetches
70 * and parses it.
71 */
72export interface SecretConnectOptions extends ConnectOptionsBase {
73 /** @default "secret" */
74 auth?: "secret";
75 secret: Secret;
76}
77
78/**
79 * IAM database authentication: the deploy half grants `rds-db:connect` on
80 * `arn:aws:rds-db:{region}:{account}:dbuser:{resourceId}/{username}`; the
81 * runtime half presigns a short-lived auth token as the password. Requires
82 * `enableIAMDatabaseAuthentication: true` on the cluster and a database
83 * user granted the `rds_iam` role (Postgres) / `AWSAuthenticationPlugin`
84 * (MySQL). TLS is mandatory — `ssl` is forced to `true`.
85 */
86export interface IamConnectOptions extends ConnectOptionsBase {
87 auth: "iam";
88 /** Database user the IAM token authenticates as. */
89 username: string;
90}
91
92export type ConnectOptions = SecretConnectOptions | IamConnectOptions;
93
94/**
95 * Runtime binding that resolves connection settings for an Aurora cluster,
96 * proxy, or proxy endpoint using a Secrets Manager secret or IAM database
97 * authentication.
98 *
99 * Binding it yields an Effect (not a callable) that resolves a
100 * `ConnectionInfo` — host, port, credentials, and a ready-to-use `url` —
101 * fresh on every execution. No socket is opened; feed the result into your
102 * database driver. Provide the implementation with
103 * `Effect.provide(AWS.RDS.ConnectHttp)`.
104 * ### Connecting to a Database
105 * **Example:** Resolve Credentials from a Secret
106 * ```typescript
107 * export default MyFunction.make(
108 * { main: import.meta.url, functionUrl: true },
109 * Effect.gen(function* () {
110 * const db = yield* AWS.RDS.Aurora("AppDb", {
111 * subnetIds: [subnetA.subnetId, subnetB.subnetId],
112 * securityGroupIds: [dbSecurityGroup.groupId],
113 * });
114 * // init — bind the cluster + admin secret; grants
115 * // secretsmanager:GetSecretValue and (optionally) attaches the
116 * // function to the given subnets/security groups
117 * const connect = yield* AWS.RDS.Connect(db.cluster, {
118 * secret: db.secret,
119 * database: "app",
120 * subnetIds: [subnetA.subnetId, subnetB.subnetId],
121 * securityGroupIds: [appSecurityGroup.groupId],
122 * });
123 *
124 * return {
125 * fetch: Effect.gen(function* () {
126 * // runtime — resolve host/port/credentials, hand `info.url`
127 * // (Redacted) to postgres.js / pg / drizzle
128 * const info = yield* connect;
129 * return yield* HttpServerResponse.json({
130 * host: info.host,
131 * port: info.port,
132 * database: info.database,
133 * });
134 * }).pipe(Effect.orDie),
135 * };
136 * }).pipe(Effect.provide(AWS.RDS.ConnectHttp)),
137 * );
138 * ```
139 *
140 * **Example:** IAM Database Authentication
141 * ```typescript
142 * // init — grants rds-db:connect for the `app_iam` user; the runtime half
143 * // presigns a short-lived (15 minute) auth token as the password
144 * const connect = yield* AWS.RDS.Connect(db.cluster, {
145 * auth: "iam",
146 * username: "app_iam",
147 * database: "app",
148 * });
149 *
150 * // runtime — long-lived pools should wire info.refreshPassword into the
151 * // driver's lazy-password hook so each new connection gets a fresh token
152 * const info = yield* connect;
153 * ```
154 *
155 * @binding
156 */
157export interface Connect extends Binding.Service<
158 Connect,
159 "AWS.RDS.Connect",
160 (
161 resource: ConnectResource,
162 options: ConnectOptions,
163 ) => Effect.Effect<
164 Effect.Effect<
165 ConnectionInfo,
166 secretsmanager.GetSecretValueError | Presign.PresignError,
167 RuntimeContext
168 >
169 >
170> {}
171export const Connect = Binding.Service<Connect>("AWS.RDS.Connect");