| 1 | import type { ReadWriteNamespaceClient } from "@/Cloudflare/Artifacts/ReadWriteNamespace.ts"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * 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 | */ |
| 20 | export 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 | }); |