1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Effect from "effect/Effect";
3import * as HttpServerRequest from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5import { Uploads } from "./Uploads.ts";
6import { Visits } from "./Visits.ts";
7
8const GREETING = "Hello from Alchemy";
9
10const page = (message: string) => `<!doctype html>
11<html>
12 <head>
13 <meta charset="utf-8" />
14 <title>${message}</title>
15 <style>
16 body { margin: 0; min-height: 100vh; display: grid; place-items: center;
17 background: #1e1e2e; color: #cdd6f4;
18 font: 18px/1.5 ui-sans-serif, system-ui, sans-serif; }
19 main { text-align: center; }
20 h1 { font-size: 2.75rem; margin: 0 0 .5rem; }
21 p { margin: 0; color: #a6adc8; }
22 </style>
23 </head>
24 <body><main><h1>${message}</h1><p>Cloudflare Worker · KV · R2</p></main></body>
25</html>`;
26
27export default class Api extends Cloudflare.Worker<Api>()(
28 "Api",
29 {
30 main: import.meta.url,
31 // Fixed, uncommon port so the demo never silently lands on another
32 // process's dev server (1337 is the default and is often taken).
33 dev: { port: 3111, strictPort: true },
34 },
35 Effect.gen(function* () {
36 const visits = yield* Cloudflare.KV.ReadWriteNamespace(Visits);
37 const uploads = yield* Cloudflare.R2.ReadWriteBucket(Uploads);
38
39 return {
40 fetch: Effect.gen(function* () {
41 const request = yield* HttpServerRequest.HttpServerRequest;
42 const url = new URL(request.url, "http://internal");
43
44 if (url.pathname === "/favicon.ico") {
45 return HttpServerResponse.empty({ status: 404 });
46 }
47
48 if (url.pathname === "/upload") {
49 const body = yield* request.text;
50 yield* uploads.put("latest.txt", body).pipe(Effect.orDie);
51 return yield* HttpServerResponse.json({ stored: body.length });
52 }
53
54 const previous = yield* visits.get("count").pipe(Effect.orDie);
55 const count = Number(previous ?? "0") + 1;
56 yield* visits.put("count", String(count)).pipe(Effect.orDie);
57
58 // Browsers get a page, curl gets JSON.
59 if (request.headers.accept?.includes("text/html")) {
60 return HttpServerResponse.html(page(GREETING));
61 }
62 return yield* HttpServerResponse.json({
63 message: GREETING,
64 visits: count,
65 });
66 }),
67 };
68 }).pipe(
69 Effect.provide([
70 Cloudflare.KV.ReadWriteNamespaceBinding,
71 Cloudflare.R2.ReadWriteBucketBinding,
72 ]),
73 ),
74) {}