1import * as Alchemy from "alchemy";
2import * as Cloudflare from "alchemy/Cloudflare";
3import * as Drizzle from "alchemy/Drizzle";
4import * as Neon from "alchemy/Neon";
5import * as Effect from "effect/Effect";
6
7/**
8 * A Drizzle schema + Neon project + feature branch. The branch's
9 * `migrations` prop is wired to the schema resource, so the
10 * provider order becomes:
11 *
12 * 1. `Drizzle.Schema` regenerates pending migration SQL files.
13 * 2. `Neon.Branch` scans the directory and applies any new migrations
14 * transactionally.
15 */
16export const NeonDatabase = Effect.gen(function* () {
17 const { stage } = yield* Alchemy.Stack;
18
19 const schema = yield* Drizzle.Schema("Schema", {
20 schema: "./src/backend/schema.ts",
21 out: "./migrations",
22 });
23
24 const project = stage.startsWith("pr-")
25 ? yield* Neon.Project.ref("Database", { stage: `staging-${stage}` })
26 : yield* Neon.Project("Database", {
27 region: "aws-us-east-1",
28 });
29
30 const branch = yield* Neon.Branch("Branch", {
31 project,
32 migrations: schema,
33 });
34
35 return { project, branch, schema };
36});
37
38/**
39 * A Hyperdrive pool in front of the Neon branch. The backend worker binds to
40 * this so Drizzle talks to Postgres through Cloudflare's connection pooler.
41 */
42export const Hyperdrive = Effect.gen(function* () {
43 const { branch } = yield* NeonDatabase;
44
45 return yield* Cloudflare.Hyperdrive.Connection("Hyperdrive", {
46 origin: branch.origin,
47
48 // By default, Hyperdrive caches responses for 60 seconds.
49 // In this case, we wanted database writes to be reflected immediately,
50 // so we disabled caching while retaining Hyperdrive for connection pooling.
51 // You should configure this based on your requirements.
52 // Another common approach is to use two Hyperdrive connections: one with caching enabled
53 // for use cases that can accept eventual consistency in exchange for performance, and one
54 // without for when you need immediate consistency.
55 caching: {
56 disabled: true,
57 },
58 });
59});