1/**
2 * Hyperdrive in local dev: the binding is a passthrough to the Connection's
3 * `dev` origin (there is no SQL simulator), so it needs a REAL reachable
4 * Postgres. The stack only includes this worker when `HYPERDRIVE_DEV_URL`
5 * is set, e.g.:
6 *
7 * ```sh
8 * docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:16
9 * HYPERDRIVE_DEV_URL=postgres://postgres:postgres@localhost:5432/postgres bun test
10 * ```
11 */
12import * as Cloudflare from "alchemy/Cloudflare";
13import * as SQL from "alchemy/SQL/Postgres";
14import * as Effect from "effect/Effect";
15import * as Redacted from "effect/Redacted";
16import { HttpServerRequest } from "effect/http/HttpServerRequest";
17import * as HttpServerResponse from "effect/http/HttpServerResponse";
18
19const parseOrigin = (raw: string | undefined) => {
20 // The fallback keeps module evaluation safe inside workerd (where the env
21 // var doesn't exist); the worker only deploys when the var is set.
22 const url = new URL(raw ?? "postgres://user:pass@localhost:5432/postgres");
23 return {
24 scheme: "postgres" as const,
25 host: url.hostname,
26 port: url.port ? Number(url.port) : 5432,
27 database: url.pathname.slice(1) || "postgres",
28 user: decodeURIComponent(url.username),
29 password: Redacted.make(decodeURIComponent(url.password)),
30 };
31};
32
33/**
34 * The `dev` origin is what the local passthrough binding connects to;
35 * `origin` is what a live deploy would use. This example points both at the
36 * same database. `sslmode: "disable"` suits a local Docker Postgres — adapt
37 * if your dev database requires TLS.
38 */
39export const DevPostgres = Effect.gen(function* () {
40 const origin = parseOrigin(process.env.HYPERDRIVE_DEV_URL);
41 return yield* Cloudflare.Hyperdrive.Connection("DevPostgres", {
42 origin,
43 caching: { disabled: true },
44 dev: { ...origin, sslmode: "disable" },
45 });
46});
47
48export default class HyperdriveWorker extends Cloudflare.Worker<HyperdriveWorker>()(
49 "HyperdriveWorker",
50 {
51 main: import.meta.url,
52 },
53 Effect.gen(function* () {
54 const hyperdrive = yield* Cloudflare.Hyperdrive.Connect(DevPostgres);
55 const sql = yield* SQL.Postgres({ url: hyperdrive.connectionString });
56
57 return {
58 fetch: Effect.gen(function* () {
59 const request = yield* HttpServerRequest;
60 const url = new URL(request.url, "http://internal");
61 if (url.pathname === "/query") {
62 const rows = (yield* sql`
63 SELECT 1 + 1 AS sum, current_database() AS db
64 `) as ReadonlyArray<{ sum: number; db: string }>;
65 return yield* HttpServerResponse.json({ row: rows[0] });
66 }
67 return HttpServerResponse.text("Not Found", { status: 404 });
68 }).pipe(
69 Effect.catchCause((cause) =>
70 HttpServerResponse.json({ error: String(cause) }, { status: 500 }),
71 ),
72 ),
73 };
74 }).pipe(Effect.provide(Cloudflare.Hyperdrive.ConnectBinding)),
75) {}