| 1 | import * as Alchemy from "alchemy"; |
| 2 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 3 | import * as Drizzle from "alchemy/Drizzle"; |
| 4 | import * as Planetscale from "alchemy/Planetscale"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | |
| 7 | /** |
| 8 | * A Drizzle schema + PlanetScale MySQL 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.MySQLBranch` scans the directory and applies new |
| 14 | * migrations transactionally. |
| 15 | */ |
| 16 | export 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 | dialect: "mysql", |
| 23 | }); |
| 24 | |
| 25 | // Stages are organised in two tiers: |
| 26 | // |
| 27 | // - `staging-*` stages own the long-lived PlanetScale database. |
| 28 | // - `pr-*` stages reference the parallel `staging-pr-*` database |
| 29 | // and only own ephemeral compute (branch + role + Hyperdrive + Worker). |
| 30 | // |
| 31 | // Deriving `staging-${stage}` instead of a single global "staging" |
| 32 | // keeps each test / PR isolated. Locally (`dev_<user>`, etc.) we create |
| 33 | // a fresh database. |
| 34 | const database = stage.startsWith("pr-") |
| 35 | ? yield* Planetscale.MySQLDatabase.ref("app-db", { |
| 36 | stage: `staging-${stage}`, |
| 37 | }) |
| 38 | : yield* Planetscale.MySQLDatabase("app-db", { |
| 39 | region: { slug: "us-east" }, |
| 40 | clusterSize: "PS_10", |
| 41 | }); |
| 42 | |
| 43 | const branch = yield* Planetscale.MySQLBranch("app-branch", { |
| 44 | database, |
| 45 | isProduction: false, |
| 46 | migrations: schema, |
| 47 | }); |
| 48 | |
| 49 | const password = yield* Planetscale.MySQLPassword("app-password", { |
| 50 | database, |
| 51 | branch, |
| 52 | role: "readwriter", |
| 53 | }); |
| 54 | |
| 55 | return { database, branch, password }; |
| 56 | }); |
| 57 | |
| 58 | export const Hyperdrive: Effect.Effect< |
| 59 | Cloudflare.Hyperdrive.Connection, |
| 60 | never, |
| 61 | any |
| 62 | > = Effect.gen(function* () { |
| 63 | const { password } = yield* PlanetscaleDb; |
| 64 | return yield* Cloudflare.Hyperdrive.Connection("app-hyperdrive", { |
| 65 | origin: password.origin, |
| 66 | caching: { disabled: true }, |
| 67 | }); |
| 68 | }); |