1// Async (non-Effect) Worker that exercises the native R2 binding against
2// the local workerd simulator: put / get / head / list / delete.
3interface R2ObjectLike {
4 key: string;
5 etag: string;
6 httpEtag: string;
7 size: number;
8 text(): Promise<string>;
9}
10
11interface Env {
12 BUCKET: {
13 put(
14 key: string,
15 value: string,
16 options?: { httpMetadata?: { contentType?: string } },
17 ): Promise<R2ObjectLike>;
18 get(key: string): Promise<R2ObjectLike | null>;
19 head(key: string): Promise<R2ObjectLike | null>;
20 list(): Promise<{ objects: Array<{ key: string; size: number }> }>;
21 delete(key: string): Promise<void>;
22 };
23}
24
25export default {
26 fetch: async (request: Request, env: Env) => {
27 const url = new URL(request.url);
28 if (url.pathname === "/seed") {
29 // Writes without deleting, so the test can verify out-of-band (via
30 // the cloud API for an `Alchemy.remote()` bucket) that the object
31 // actually landed in the bound bucket.
32 await env.BUCKET.put("seed.txt", "seeded by worker", {
33 httpMetadata: { contentType: "text/plain" },
34 });
35 const head = await env.BUCKET.head("seed.txt");
36 return Response.json({ etag: head?.etag ?? null });
37 }
38 if (url.pathname === "/roundtrip") {
39 await env.BUCKET.put("greeting.txt", "hello r2", {
40 httpMetadata: { contentType: "text/plain" },
41 });
42 const obj = await env.BUCKET.get("greeting.txt");
43 const text = obj === null ? null : await obj.text();
44 const head = await env.BUCKET.head("greeting.txt");
45 const list = await env.BUCKET.list();
46 await env.BUCKET.delete("greeting.txt");
47 const afterDelete = await env.BUCKET.get("greeting.txt");
48 return Response.json({
49 text,
50 etag: head?.etag ?? null,
51 size: head?.size ?? null,
52 keys: list.objects.map((o) => o.key),
53 afterDelete: afterDelete === null,
54 });
55 }
56 if (url.pathname === "/get") {
57 const obj = await env.BUCKET.get(url.searchParams.get("key") ?? "");
58 return Response.json({ text: obj === null ? null : await obj.text() });
59 }
60 return new Response("not found", { status: 404 });
61 },
62};