1import * as Cloudflare from "@/Cloudflare/index.ts";
2import * as Drizzle from "@/Drizzle/Postgres.ts";
3import { eq } from "drizzle-orm";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import { HttpServerRequest } from "effect/http/HttpServerRequest";
7import * as HttpServerResponse from "effect/http/HttpServerResponse";
8import { Hyperdrive } from "./Stack.ts";
9import { relations, Widgets } from "./schema.ts";
10
11/**
12 * Worker fixture that binds a Cloudflare Hyperdrive (pointed at a
13 * Planetscale Postgres role) and exercises Drizzle's Effect-native
14 * client. Mirrors the cloudflare-planetscale-drizzle example but lives
15 * inside the package so the runtime path is covered by an integration
16 * test next to the resource it exercises.
17 */
18export default class HyperdriveWorker extends Cloudflare.Worker<HyperdriveWorker>()(
19 "PlanetscaleHyperdriveWorker",
20 {
21 main: import.meta.url,
22 },
23 Effect.gen(function* () {
24 const conn = yield* Cloudflare.Hyperdrive.Connect(Hyperdrive);
25 const db = yield* Drizzle.Postgres(conn.connectionString, { relations });
26
27 return {
28 fetch: Effect.gen(function* () {
29 const request = yield* HttpServerRequest;
30 const url = new URL(request.url, "http://x");
31
32 if (request.method === "GET" && url.pathname === "/hyperdrive") {
33 return yield* HttpServerResponse.json({
34 host: yield* conn.host,
35 port: yield* conn.port,
36 user: yield* conn.user,
37 database: yield* conn.database,
38 });
39 }
40
41 if (request.method === "GET" && url.pathname === "/widgets") {
42 const widgets = yield* db.select().from(Widgets);
43 return yield* HttpServerResponse.json({ widgets });
44 }
45
46 if (request.method === "POST" && url.pathname === "/widgets") {
47 const body = (yield* request.json) as { id: number; name: string };
48 const [inserted] = yield* db
49 .insert(Widgets)
50 .values({ id: body.id, name: body.name })
51 .onConflictDoUpdate({
52 target: Widgets.id,
53 set: { name: body.name },
54 })
55 .returning();
56 return yield* HttpServerResponse.json({ widget: inserted });
57 }
58
59 const idMatch = url.pathname.match(/^\/widgets\/(\d+)$/);
60 if (request.method === "DELETE" && idMatch) {
61 const id = Number(idMatch[1]);
62 const [deleted] = yield* db
63 .delete(Widgets)
64 .where(eq(Widgets.id, id))
65 .returning();
66 return yield* HttpServerResponse.json({ widget: deleted ?? null });
67 }
68
69 return HttpServerResponse.text("Not Found", { status: 404 });
70 }).pipe(
71 Effect.catch((cause: any) =>
72 HttpServerResponse.json(
73 { ok: false, error: String(cause) },
74 { status: 500 },
75 ),
76 ),
77 ),
78 };
79 }).pipe(Effect.provide(Layer.mergeAll(Cloudflare.Hyperdrive.ConnectBinding))),
80) {}