1import type { WriteNamespaceClient } from "@/Cloudflare/KV/WriteNamespace.ts";
2import * as Effect from "effect/Effect";
3import type * as HttpServerRequest from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5
6/**
7 * Shared write-side routes exercised by both the binding and HTTP fixtures so
8 * every method of {@link WriteNamespaceClient} is driven over `fetch`:
9 *
10 * - `PUT /put?key=` — `put(key, body)`.
11 * - `PUT /put-meta?key=` — `put(key, body, { metadata, expirationTtl })`.
12 * - `DELETE /del?key=` — `delete(key)`.
13 *
14 * Returns `undefined` when the path is not a write route so the caller can
15 * fall through (used by the read-write fixtures).
16 */
17export const writeRoutes = (
18 kv: WriteNamespaceClient,
19 request: HttpServerRequest.HttpServerRequest,
20 url: URL,
21) =>
22 Effect.gen(function* () {
23 if (request.method === "PUT" && url.pathname === "/put") {
24 const key = url.searchParams.get("key") ?? "";
25 const body = yield* request.text;
26 yield* kv.put(key, body).pipe(Effect.orDie);
27 return yield* HttpServerResponse.json({ ok: true });
28 }
29 if (request.method === "PUT" && url.pathname === "/put-meta") {
30 const key = url.searchParams.get("key") ?? "";
31 const body = yield* request.text;
32 // `expirationTtl` must be ≥ 60s; metadata round-trips via getWithMetadata.
33 yield* kv
34 .put(key, body, { metadata: { tag: "meta" }, expirationTtl: 3600 })
35 .pipe(Effect.orDie);
36 return yield* HttpServerResponse.json({ ok: true });
37 }
38 if (request.method === "DELETE" && url.pathname === "/del") {
39 const key = url.searchParams.get("key") ?? "";
40 yield* kv.delete(key).pipe(Effect.orDie);
41 return yield* HttpServerResponse.json({ ok: true });
42 }
43 return undefined;
44 });