1import * as Data from "effect/Data";
2import type * as Effect from "effect/Effect";
3import type * as Redacted from "effect/Redacted";
4import * as Binding from "../Binding.ts";
5import type { RuntimeContext } from "../RuntimeContext.ts";
6import type { Postgres } from "./Postgres.ts";
7
8/**
9 * Bind a {@link Postgres} cluster to a Fly {@link Service} and obtain
10 * the Effect-native connection strings for `Drizzle.Postgres` /
11 * `SQL.Postgres`. Alchemy transports the cluster's connection URIs
12 * automatically; callers do not configure connection-string env vars.
13 *
14 * `ConnectPostgres` is the Context tag, the type, and the callable —
15 * `yield* Fly.ConnectPostgres(Db)`. Provide {@link ConnectPostgresHttp}.
16 *
17 * **Example:** Bind Postgres in a Service
18 * ```typescript
19 * import * as Drizzle from "alchemy/Drizzle/Postgres";
20 *
21 * const conn = yield* Fly.ConnectPostgres(Db);
22 * const db = yield* Drizzle.Postgres(conn.connectionString);
23 *
24 * fetch: Effect.gen(function* () {
25 * const rows = yield* db.select().from(users);
26 * });
27 * ```
28 *
29 * @binding
30 * @product Postgres
31 */
32export interface ConnectPostgres extends Binding.Service<
33 ConnectPostgres,
34 "Fly.ConnectPostgres",
35 (postgres: Postgres) => Effect.Effect<ConnectPostgresClient>
36> {}
37
38export const ConnectPostgres = Binding.Service<ConnectPostgres>(
39 "Fly.ConnectPostgres",
40);
41
42export const connectEnvKeys = (postgres: Pick<Postgres, "LogicalId">) => {
43 const id = postgres.LogicalId.replaceAll(/[^a-zA-Z0-9]/g, "_").toUpperCase();
44 return {
45 pooled: `FLY_POSTGRES_${id}_POOLED`,
46 direct: `FLY_POSTGRES_${id}_DIRECT`,
47 };
48};
49
50export class PostgresUrlMissing extends Data.TaggedError(
51 "Fly.PostgresUrlMissing",
52)<{
53 name: string;
54}> {}
55
56export interface ConnectPostgresClient {
57 /**
58 * Pooled PgBouncer connection string. Pass this to
59 * {@link Drizzle.Postgres} or `SQL.Postgres`.
60 */
61 connectionString: Effect.Effect<
62 Redacted.Redacted<string>,
63 PostgresUrlMissing,
64 RuntimeContext
65 >;
66 /**
67 * Direct (non-PgBouncer) connection string. Use this for session-scoped
68 * features (advisory locks, `LISTEN/NOTIFY`) when you are not going
69 * through the pooled URL.
70 */
71 directConnectionString: Effect.Effect<
72 Redacted.Redacted<string>,
73 PostgresUrlMissing,
74 RuntimeContext
75 >;
76}