1import * as Alchemy from "alchemy";
2import * as Cloudflare from "alchemy/Cloudflare";
3import * as Drizzle from "alchemy/Drizzle";
4import * as Planetscale from "alchemy/Planetscale";
5import * as Effect from "effect/Effect";
6
7/**
8 * A Drizzle schema + PlanetScale Postgres database + feature branch. The
9 * branch's `migrations` prop is wired to the schema resource, so
10 * the provider order becomes:
11 *
12 * 1. `Drizzle.Schema` regenerates pending migration SQL files.
13 * 2. `Planetscale.PostgresBranch` scans the directory and applies new
14 * migrations transactionally.
15 */
16export const PlanetscaleDb = Effect.gen(function* () {
17 const { stage } = yield* Alchemy.Stack;
18
19 const schema = yield* Drizzle.Schema("app-schema", {
20 schema: "./src/schema.ts",
21 out: "./migrations",
22 });
23
24 // Stages are organised in two tiers:
25 //
26 // - `staging-*` stages own the long-lived PlanetScale database.
27 // - `pr-*` stages reference the parallel `staging-pr-*` database
28 // and only own ephemeral compute (branch + role + Hyperdrive + Worker).
29 //
30 // Deriving `staging-${stage}` instead of a single global "staging"
31 // keeps each test / PR isolated. Locally (`dev_<user>`, etc.) we create
32 // a fresh database.
33 const database = stage.startsWith("pr-")
34 ? yield* Planetscale.PostgresDatabase.ref("app-db", {
35 stage: `staging-${stage}`,
36 })
37 : yield* Planetscale.PostgresDatabase("app-db", {
38 region: { slug: "us-east" },
39 clusterSize: "PS_10",
40 });
41
42 const branch = yield* Planetscale.PostgresBranch("app-branch", {
43 database,
44 migrations: schema,
45 });
46
47 const role = yield* Planetscale.PostgresRole("app-role", {
48 database,
49 branch,
50 inheritedRoles: ["postgres"],
51 });
52
53 return { database, branch, role, schema };
54});
55
56export const Hyperdrive: Effect.Effect<
57 Cloudflare.Hyperdrive.Connection,
58 never,
59 any
60> = Effect.gen(function* () {
61 const { role } = yield* PlanetscaleDb;
62 return yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", {
63 origin: role.origin,
64 caching: { disabled: true },
65 });
66});