Open History to see the last commit
1import * as Cloudflare from "@alchemy.run/cloudflare";
2import * as Prisma from "@alchemy.run/prisma";
3import * as Alchemy from "alchemy";
4import * as Command from "alchemy/Command";
5import * as Output from "alchemy/Output";
6import * as Config from "effect/Config";
7import * as Effect from "effect/Effect";
8import * as Layer from "effect/Layer";
9import * as Redacted from "effect/Redacted";
10
11const tanstackMessageConfig = (fallback: string) =>
12 Config.String("TANSTACK_MESSAGE").pipe(Effect.orElseSucceed(() => fallback));
13
14export default Alchemy.Stack(
15 "PrismaTanstackStart",
16 {
17 providers: Layer.mergeAll(Prisma.providers(), Command.providers()),
18 state: Cloudflare.state(),
19 },
20 Effect.gen(function* () {
21 const stage = yield* Alchemy.Stage;
22 const appName = yield* Config.String("PRISMA_TANSTACK_APP").pipe(
23 Effect.orElseSucceed(() => `alchemy-prisma-tanstack-start-${stage}`),
24 );
25 const customDomainHostname = yield* Config.String("PRISMA_TANSTACK_DOMAIN").pipe(
26 Effect.orElseSucceed(() => undefined),
27 );
28 const devPort = yield* Config.Number("PRISMA_TANSTACK_DEV_PORT").pipe(
29 Effect.orElseSucceed(() => 3000),
30 );
31
32 // Project is the Prisma Console container for everything else in this
33 // stack: its default branch, databases, connections, env vars, and Apps.
34 // `createDatabase: false` keeps the example explicit so the database below
35 // is visible as its own Alchemy resource.
36 const project = yield* Prisma.Project("Project", {
37 name: yield* Config.String("PRISMA_PROJECT").pipe(Effect.orElseSucceed(() => undefined)),
38 createDatabase: false,
39 region: "eu-west-3",
40 });
41
42 // Postgres is the product-shaped alias for Prisma.Database. In deploy it
43 // creates a real Prisma Postgres database and attaches it to the default
44 // `main` branch created transactionally with the project. In `alchemy dev`,
45 // the `dev` block starts a local Prisma Postgres via `@prisma/dev` and
46 // returns the same connection-string attributes.
47 const postgres = yield* Prisma.Postgres("Postgres", {
48 project,
49 region: "eu-west-3",
50 branchGitName: "main",
51 dev: {
52 provider: "@prisma/dev",
53 // Local dev should boot into a useful app, not an empty shell. This
54 // command runs after @prisma/dev is ready, with DATABASE_URL pointed at
55 // the direct local Postgres URL.
56 migrate: "bun run db:setup",
57 },
58 });
59
60 // Connection creates the app-facing database credentials. Its returned
61 // strings are redacted in Alchemy state. In dev mode it mirrors the local
62 // `@prisma/dev` URLs from Postgres, so the app keeps the same env names.
63 const connection = yield* Prisma.Connection("Connection", {
64 database: postgres,
65 });
66
67 // Apply checked-in, transactional Prisma migrations before building a
68 // deployment. Apply is idempotent and intentionally runs every deploy so a
69 // restored or drifted database is verified instead of trusting local state.
70 // Production data is never seeded here.
71 const databaseSchema = yield* Command.Exec("DatabaseSchema", {
72 command: "bun run db:migrate",
73 cwd: ".",
74 env: {
75 DATABASE_URL: connection.directConnectionString.as<Redacted.Redacted<string>>(),
76 // Bound server-side lock waits and statement execution independently
77 // from the release-process deadline below.
78 PGOPTIONS: "-c lock_timeout=30s -c statement_timeout=5min",
79 // The release subprocess needs only the database credential. Shadow
80 // the IaC process's Management API credentials so schema tooling and
81 // any of its plugins cannot read the workspace-scoped service token.
82 PRISMA_SERVICE_TOKEN: "",
83 PRISMA_API_TOKEN: "",
84 },
85 memo: false,
86 timeout: "6 minutes",
87 });
88
89 // Compute builds the TanStack Start app, uploads the `.output` artifact
90 // to Prisma Compute, and promotes a Deployment. The env block is the runtime
91 // contract for deploy; the dev block runs Vite locally while reusing the
92 // same database/project IDs generated by the resources above.
93 const app = yield* Prisma.Compute("App", {
94 project,
95 appName,
96 regionId: "eu-west-3",
97 branchGitName: "main",
98 path: ".",
99 build: {
100 command: "bun run build",
101 outdir: ".output",
102 entrypoint: "server/index.mjs",
103 env: {
104 // Creates an explicit dependency on successful schema convergence
105 // without exposing the migration credential to the build.
106 ALCHEMY_SCHEMA_HASH: databaseSchema.hash.pipe(
107 Output.map((hash) => hash.input ?? "unmemoized"),
108 ),
109 },
110 },
111 port: 3000,
112 healthCheck: { path: "/api/health" },
113 env: {
114 // The deployed application receives only the pooled credential it
115 // uses. The direct credential is scoped to the schema command above.
116 DATABASE_URL: connection.databaseUrl,
117 TANSTACK_MESSAGE: yield* tanstackMessageConfig(
118 "hello from TanStack Start on Prisma Compute",
119 ),
120 // Keep values consumed by this deployment in Compute.env so they are
121 // reconciled before the deployment snapshots its branch environment.
122 TANSTACK_SHARED_FLAG: Redacted.make("project-level"),
123 },
124 dev: {
125 command: "bun run dev:start",
126 port: devPort,
127 env: {
128 TANSTACK_MESSAGE: yield* tanstackMessageConfig("hello from local alchemy dev"),
129 TANSTACK_SHARED_FLAG: "local-project-level",
130 PRISMA_TANSTACK_PG_POOL_MAX: "1",
131 },
132 },
133 destroyOldDeployment: true,
134 });
135
136 const customDomain = customDomainHostname
137 ? yield* Prisma.CustomDomain("CustomDomain", {
138 app: app.appId,
139 hostname: customDomainHostname,
140 })
141 : undefined;
142
143 return {
144 projectId: project.projectId,
145 branchId: postgres.branchId,
146 databaseId: postgres.databaseId,
147 connectionId: connection.connectionId,
148 appId: app.appId,
149 deploymentId: app.deploymentId,
150 url: app.url,
151 customDomainId: customDomain?.customDomainId,
152 customDomainHostname: customDomain?.hostname,
153 customDomainStatus: customDomain?.status,
154 customDomainDnsRecords: customDomain?.dnsRecords,
155 };
156 }),
157);