1import type { ReadBucketClient } from "@/Prisma/ReadBucket.ts";
2import * as Effect from "effect/Effect";
3import * as HttpServerResponse from "effect/http/HttpServerResponse";
4
5/**
6 * Shared read-side routes so every method of {@link ReadBucketClient}
7 * (`get`, `head`, `list`, `presignGet`) is driven over `fetch`. Returns
8 * `undefined` when the path is not a read route so the caller can fall
9 * through (used by the read-write fixtures).
10 */
11export const readRoutes = (store: ReadBucketClient, url: URL) =>
12 Effect.gen(function* () {
13 if (url.pathname === "/get") {
14 const key = url.searchParams.get("key") ?? "";
15 const object = yield* store.get(key).pipe(Effect.orDie);
16 const value = object ? yield* object.text().pipe(Effect.orDie) : null;
17 return yield* HttpServerResponse.json({
18 value,
19 contentType: object?.contentType ?? null,
20 metadata: object?.metadata ?? null,
21 });
22 }
23 if (url.pathname === "/head") {
24 const key = url.searchParams.get("key") ?? "";
25 const object = yield* store.head(key).pipe(Effect.orDie);
26 return yield* HttpServerResponse.json({
27 exists: object !== null,
28 size: object?.size ?? null,
29 });
30 }
31 if (url.pathname === "/list") {
32 const limit = url.searchParams.get("limit");
33 const result = yield* store
34 .list({
35 prefix: url.searchParams.get("prefix") ?? undefined,
36 delimiter: url.searchParams.get("delimiter") ?? undefined,
37 cursor: url.searchParams.get("cursor") ?? undefined,
38 limit: limit ? Number(limit) : undefined,
39 })
40 .pipe(Effect.orDie);
41 return yield* HttpServerResponse.json({
42 keys: result.objects.map((o) => o.key),
43 delimitedPrefixes: result.delimitedPrefixes,
44 truncated: result.truncated,
45 cursor: result.truncated ? result.cursor : null,
46 });
47 }
48 // The app mints the URL from its bound credentials and hands it back; the
49 // test then fetches that URL directly, carrying no credentials of its own.
50 if (url.pathname === "/presign-get") {
51 const key = url.searchParams.get("key") ?? "";
52 const expiresIn = url.searchParams.get("expiresIn");
53 const contentType = url.searchParams.get("contentType");
54 const presigned = yield* store
55 .presignGet(key, {
56 expiresIn: expiresIn ? Number(expiresIn) : undefined,
57 contentType: contentType ?? undefined,
58 })
59 .pipe(Effect.orDie);
60 return yield* HttpServerResponse.json({ url: presigned });
61 }
62 return undefined;
63 });