1import * as Cloudflare from "@/Cloudflare/index.ts";
2import * as Effect from "effect/Effect";
3import { HttpServerRequest } from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5import { TestBucket } from "./bucket.ts";
6import { writeRoutes } from "./write-routes.ts";
7
8/** Write-only access via the native Worker binding (`WriteBucketBinding`). */
9export default class R2WriteBindingWorker extends Cloudflare.Worker<R2WriteBindingWorker>()(
10 "R2WriteBindingWorker",
11 { main: import.meta.url },
12 Effect.gen(function* () {
13 const bucket = yield* TestBucket;
14 const r2 = yield* Cloudflare.R2.WriteBucket(bucket);
15 return {
16 fetch: Effect.gen(function* () {
17 const request = yield* HttpServerRequest;
18 const url = new URL(request.url, "http://x");
19 // `PUT /put-stream?key=&sha256=` — `put(key, stream, { contentLength,
20 // sha256 })`: the body reaches R2 as an Effect `Stream`, and R2 must
21 // verify it against the declared hash. Only the native binding can
22 // stream, so this route is not part of the shared write routes.
23 if (request.method === "PUT" && url.pathname === "/put-stream") {
24 const key = url.searchParams.get("key") ?? "";
25 const sha256 = url.searchParams.get("sha256") ?? "";
26 const contentLength = Number(request.headers["content-length"] ?? 0);
27 return yield* r2
28 .put(key, request.stream, { contentLength, sha256 })
29 .pipe(
30 Effect.flatMap((object) =>
31 HttpServerResponse.json({
32 stored: true,
33 size: object?.size ?? null,
34 }),
35 ),
36 Effect.catchTag("R2Error", (e) =>
37 HttpServerResponse.json(
38 { stored: false, error: e.message },
39 { status: 400 },
40 ),
41 ),
42 );
43 }
44 const handled = yield* writeRoutes(r2, request, url);
45 return handled ?? HttpServerResponse.text("Not Found", { status: 404 });
46 }),
47 };
48 }).pipe(Effect.provide(Cloudflare.R2.WriteBucketBinding)),
49) {}