1import type * as redshift from "@distilled.cloud/aws/redshift";
2import type * as Duration from "effect/Duration";
3import type * as Effect from "effect/Effect";
4import * as Binding from "../../Binding.ts";
5import type { RuntimeContext } from "../../RuntimeContext.ts";
6import {
7 connectEnvPrefix as makeConnectEnvPrefix,
8 type SqlConnectionInfo,
9} from "../Connection/internal.ts";
10import type { Cluster } from "./Cluster.ts";
11
12/**
13 * Connection descriptor for a provisioned Redshift cluster. Extends the
14 * shared SQL shape with the temporary credential's expiration so callers
15 * can reason about token lifetime (credentials are minted fresh on every
16 * resolution of the runtime effect, so per-execution pools never outlive
17 * them).
18 */
19export interface ClusterConnectionInfo extends SqlConnectionInfo {
20 /**
21 * When the temporary database credentials expire (15-60 minutes from
22 * minting, per {@link ConnectOptions.duration}).
23 */
24 expiration: Date | undefined;
25}
26
27export interface ConnectOptions {
28 /**
29 * Database to connect to.
30 * @default the cluster's initial database (`dbName` attribute)
31 */
32 database?: string;
33 /**
34 * Database user to mint temporary credentials for via
35 * `redshift:GetClusterCredentials`. When omitted, credentials are minted
36 * with `redshift:GetClusterCredentialsWithIAM` instead — the database
37 * user is mapped 1:1 to the Function's IAM identity.
38 */
39 dbUser?: string;
40 /**
41 * Create the database user if it does not exist. Only used with
42 * {@link ConnectOptions.dbUser}; adds `redshift:CreateClusterUser` to the
43 * host's policy.
44 * @default false
45 */
46 autoCreate?: boolean;
47 /**
48 * Database groups the user joins for the session. Only used with
49 * {@link ConnectOptions.dbUser}; adds `redshift:JoinGroup` on each group
50 * to the host's policy.
51 */
52 dbGroups?: string[];
53 /**
54 * How long the temporary credentials remain valid, e.g. `"15 minutes"` or
55 * `Duration.minutes(30)` (a bare number is milliseconds). Rounded to whole
56 * seconds on the wire; must land between 900 and 3600 seconds.
57 * @default 900 seconds
58 */
59 duration?: Duration.Input;
60 /**
61 * Whether to require TLS on the connection.
62 * @default true
63 */
64 ssl?: boolean;
65}
66
67/**
68 * Errors the runtime credential mint can produce (`GetClusterCredentials`
69 * and `GetClusterCredentialsWithIAM` share the same union).
70 */
71export type ConnectError =
72 | redshift.GetClusterCredentialsError
73 | redshift.GetClusterCredentialsWithIAMError;
74
75/**
76 * Environment variable prefix under which {@link Connect} publishes the
77 * cluster endpoint on the host Function, derived from the cluster's logical
78 * ID. A cluster with logical ID `Analytics` yields `REDSHIFT_ANALYTICS` and
79 * the variables `REDSHIFT_ANALYTICS_HOST` / `REDSHIFT_ANALYTICS_PORT`.
80 */
81export const connectEnvPrefix = (logicalId: string): string =>
82 makeConnectEnvPrefix("REDSHIFT", logicalId);
83
84/**
85 * Runtime binding that resolves pgwire connection settings for a
86 * provisioned Redshift {@link Cluster} using IAM-minted temporary database
87 * credentials.
88 *
89 * At deploy time it attaches the `redshift:GetClusterCredentials[WithIAM]`
90 * IAM policy (scoped to the cluster's `dbname`/`dbuser` ARNs) and publishes
91 * the cluster endpoint as environment variables. At runtime it calls the
92 * corresponding SDK operation to mint short-lived credentials and returns a
93 * typed {@link ClusterConnectionInfo} whose `url` feeds `Drizzle.Postgres`
94 * directly.
95 *
96 * The Redshift Data API ({@link RedshiftData.Statements}) remains the
97 * recommended default — it needs no driver, no VPC reach, and no credential
98 * plumbing. Use `Connect` when you want a real pgwire connection (e.g.
99 * Drizzle). Redshift speaks the postgres wire protocol on port 5439 —
100 * configure Drizzle with `prepare: false` and avoid `RETURNING` (Redshift
101 * does not support either). The host Function must be able to reach the
102 * cluster endpoint (attach it to the cluster's VPC, or make the cluster
103 * `publiclyAccessible`). Provide the implementation with
104 * `Effect.provide(AWS.Redshift.ConnectHttp)`.
105 *
106 * ### Connecting to a Cluster
107 * **Example:** Resolve Connection Info inside a Function (IAM identity)
108 * ```typescript
109 * const connect = yield* Redshift.Connect(cluster);
110 * // inside a handler — mints fresh temporary credentials:
111 * const { host, port, username, password, url } = yield* connect;
112 * ```
113 *
114 * **Example:** Connect as a Named Database User
115 * ```typescript
116 * const connect = yield* Redshift.Connect(cluster, {
117 * dbUser: "etl",
118 * autoCreate: true,
119 * dbGroups: ["analysts"],
120 * database: "analytics",
121 * });
122 * ```
123 *
124 * **Example:** Drizzle over the Connection URL
125 * ```typescript
126 * const connect = yield* Redshift.Connect(cluster);
127 * const db = yield* Drizzle.Postgres(
128 * Effect.map(connect, (info) => info.url),
129 * { prepare: false },
130 * );
131 * ```
132 *
133 * @binding
134 */
135export interface Connect extends Binding.Service<
136 Connect,
137 "AWS.Redshift.Connect",
138 (
139 cluster: Cluster,
140 options?: ConnectOptions,
141 ) => Effect.Effect<
142 Effect.Effect<ClusterConnectionInfo, ConnectError, RuntimeContext>
143 >
144> {}
145export const Connect = Binding.Service<Connect>("AWS.Redshift.Connect");