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";
6import * as Layer from "effect/Layer";
7import Backend from "./src/backend/api.ts";
8import { Hyperdrive, NeonDatabase } from "./src/backend/database.ts";
9
10/**
11 * The TanStack Start frontend. Deployed as a Cloudflare Worker + static assets
12 * by `Cloudflare.Website.Vite`. The `Backend` RPC worker is injected as a private
13 * `BACKEND` service binding; the `/rpc` server route proxies the browser's
14 * `AtomRpc` traffic to it.
15 */
16export class Website extends Cloudflare.Website.Vite<Website>()("Website", {
17 compatibility: {
18 // Node.js compatibility is supplied by Alchemy's 2026-08-31 default.
19 flags: ["enable_request_signal"],
20 },
21 env: {
22 BACKEND: Backend,
23 },
24}) {}
25
26export type WebsiteEnv = Cloudflare.InferEnv<typeof Website>;
27
28export default Alchemy.Stack(
29 "CloudflareTanstackRpcDrizzleExample",
30 {
31 providers: Layer.mergeAll(
32 Cloudflare.providers(),
33 Drizzle.providers(),
34 Neon.providers(),
35 ),
36 state: Alchemy.localState(),
37 },
38 Effect.gen(function* () {
39 const { branch } = yield* NeonDatabase;
40 const hd = yield* Hyperdrive;
41 const website = yield* Website;
42
43 // Backend does not need to be yielded here.
44 // The service binding adds it to the resource graph automatically, and there's no direct URL.
45
46 return {
47 websiteUrl: website.url.as<string>(),
48 branchId: branch.branchId,
49 hyperdriveId: hd.hyperdriveId,
50 };
51 }),
52);