1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Effect from "effect/Effect";
3import { HttpServerRequest } from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5
6export const Bucket = Cloudflare.R2.Bucket("Bucket", { forceDestroy: true });
7
8export default class Backend extends Cloudflare.Worker<Backend>()(
9 "Backend",
10 {
11 main: import.meta.url,
12 },
13 Effect.gen(function* () {
14 const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
15
16 return {
17 // RPC method — read an object from R2 by key, returning the body as
18 // text or `null` if the key is missing. Other workers can call this
19 // directly via the service binding (option 3 in `api.hello.ts`).
20 hello: Effect.fn("Backend.hello")(function* (key: string) {
21 const object = yield* bucket.get(key);
22 if (object === null) return null;
23 return yield* object.text();
24 }),
25
26 // HTTP handler — supports `GET` and `PUT` against R2 by `?key=...`.
27 // Other workers can call this via `env.BACKEND.fetch(...)` (option 2
28 // in `api.hello.ts`).
29 fetch: Effect.gen(function* () {
30 const request = yield* HttpServerRequest;
31 const key = new URL(request.url, "http://backend").searchParams.get(
32 "key",
33 );
34 if (!key) {
35 return HttpServerResponse.text("Missing 'key' query parameter", {
36 status: 400,
37 });
38 }
39
40 if (request.method === "GET") {
41 const object = yield* bucket.get(key);
42 if (object === null) {
43 return HttpServerResponse.text("Not found", { status: 404 });
44 }
45 return HttpServerResponse.stream(object.body);
46 }
47
48 if (request.method === "PUT") {
49 yield* bucket.put(key, request.stream, {
50 contentLength: Number(request.headers["content-length"] ?? 0),
51 });
52 return HttpServerResponse.empty({ status: 204 });
53 }
54
55 return HttpServerResponse.text("Method not allowed", { status: 405 });
56 }).pipe(
57 Effect.catchTag("R2Error", (error) =>
58 Effect.succeed(
59 HttpServerResponse.text(error.message, { status: 500 }),
60 ),
61 ),
62 ),
63 };
64 }).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)),
65) {}