1import type { ReadWriteNamespaceClient } from "@/Cloudflare/Artifacts/ReadWriteNamespace.ts";
2import * as Effect from "effect/Effect";
3import * as HttpServerResponse from "effect/http/HttpServerResponse";
4
5/**
6 * Shared routes that exercise every method of {@link ReadWriteNamespaceClient} over
7 * `fetch`. The same routes back both the Effect-native worker (which yields
8 * `Cloudflare.Artifacts.ReadWriteNamespace(...)`) and — by mirroring the logic in plain
9 * async/await — the async worker, so both invocation styles drive the exact
10 * same behavior against one shared namespace.
11 *
12 * Routes:
13 * - `POST /create?name=` — create a repo, returns `{ name, remote, hasToken }`
14 * - `GET /list` — list repos, returns `{ names: string[], total }`
15 * - `GET /get?name=` — fetch a repo handle, returns `{ found: boolean }`
16 * - `DELETE /delete?name=` — delete a repo, returns `{ deleted: boolean }`
17 *
18 * Returns `undefined` for unknown paths so the worker shell can 404.
19 */
20export const artifactsRoutes = (client: ReadWriteNamespaceClient, url: URL) =>
21 Effect.gen(function* () {
22 const name = url.searchParams.get("name") ?? "";
23
24 // Surface the typed ArtifactsError as a 500 with the message in the
25 // body (instead of orDie's empty 500) so a failing test's
26 // WorkerNotReady error says WHAT the binding rejected with.
27 const failWith = (error: { message: string }) =>
28 HttpServerResponse.json({ error: error.message }, { status: 500 });
29
30 if (url.pathname === "/create") {
31 return yield* client.create(name, { setDefaultBranch: "main" }).pipe(
32 Effect.flatMap((repo) =>
33 HttpServerResponse.json({
34 name: repo.name,
35 remote: repo.remote,
36 defaultBranch: repo.defaultBranch,
37 hasToken: typeof repo.token === "string" && repo.token.length > 0,
38 }),
39 ),
40 Effect.catchTag("ArtifactsError", failWith),
41 );
42 }
43
44 if (url.pathname === "/list") {
45 return yield* client.list().pipe(
46 Effect.flatMap((result) =>
47 HttpServerResponse.json({
48 names: result.repos.map((r) => r.name),
49 total: result.total,
50 }),
51 ),
52 Effect.catchTag("ArtifactsError", failWith),
53 );
54 }
55
56 if (url.pathname === "/get") {
57 const found = yield* client.get(name).pipe(
58 Effect.as(true),
59 Effect.catchTag("ArtifactsError", () => Effect.succeed(false)),
60 );
61 return yield* HttpServerResponse.json({ found });
62 }
63
64 if (url.pathname === "/delete") {
65 return yield* client.delete(name).pipe(
66 Effect.flatMap((deleted) => HttpServerResponse.json({ deleted })),
67 Effect.catchTag("ArtifactsError", failWith),
68 );
69 }
70
71 return undefined;
72 });