1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Drizzle from "alchemy/Drizzle/MySQL";
3import { eq } from "drizzle-orm";
4import { Layer } from "effect";
5import * as Effect from "effect/Effect";
6import * as HttpServerRequest from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import { Hyperdrive } from "./Db.ts";
9import { relations, Users } from "./schema.ts";
10
11export default class Api extends Cloudflare.Worker<Api>()(
12 "Api",
13 {
14 main: import.meta.url,
15 compatibility: {
16 date: "2026-08-31",
17 },
18 },
19 Effect.gen(function* () {
20 const conn = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive);
21 const db = yield* Drizzle.MySQL(conn.connectionString, {
22 relations,
23 });
24
25 return {
26 fetch: Effect.gen(function* () {
27 const request = yield* HttpServerRequest.HttpServerRequest;
28 switch (request.method) {
29 case "GET": {
30 if (request.url === "/") {
31 const users = yield* db.select().from(Users);
32 return yield* HttpServerResponse.json({ users });
33 }
34 const id = Number(request.url.split("/").pop());
35 if (Number.isNaN(id)) {
36 return yield* HttpServerResponse.json(
37 { error: "Invalid user ID" },
38 { status: 400 },
39 );
40 }
41 const user = yield* db.query.Users.findFirst({
42 where: { id },
43 with: { posts: true },
44 });
45 return yield* HttpServerResponse.json({ user });
46 }
47 case "POST": {
48 // MySQL has no RETURNING — $returningId reports the generated
49 // primary key, and the row is read back with a SELECT.
50 const [created] = yield* db
51 .insert(Users)
52 .values({
53 name: crypto.randomUUID(),
54 email: crypto.randomUUID(),
55 })
56 .$returningId();
57 const user = yield* db
58 .select()
59 .from(Users)
60 .where(eq(Users.id, created!.id));
61 return yield* HttpServerResponse.json({ user });
62 }
63 case "DELETE": {
64 const id = Number(request.url.split("/").pop());
65 if (Number.isNaN(id)) {
66 return yield* HttpServerResponse.json(
67 { error: "Invalid user ID" },
68 { status: 400 },
69 );
70 }
71 const [user] = yield* db
72 .select()
73 .from(Users)
74 .where(eq(Users.id, id));
75 yield* db.delete(Users).where(eq(Users.id, id));
76 return yield* HttpServerResponse.json({ user });
77 }
78 default: {
79 return yield* HttpServerResponse.json(
80 { error: "Method not allowed" },
81 { status: 405 },
82 );
83 }
84 }
85 }).pipe(
86 Effect.catch((cause: any) => {
87 const peel = (e: any): any => (e?.cause ? peel(e.cause) : e);
88 const root = peel(cause);
89 return HttpServerResponse.json(
90 {
91 ok: false,
92 error: String(cause),
93 rootError: root?.message ?? String(root),
94 rootCode: root?.code,
95 },
96 { status: 500 },
97 );
98 }),
99 ),
100 };
101 }).pipe(Effect.provide(Layer.mergeAll(Cloudflare.Hyperdrive.ConnectBinding))),
102) {}