| 1 | import type { WriteQueueClient } from "@/Cloudflare/Queues/WriteQueue.ts"; |
| 2 | import * as Cause from "effect/Cause"; |
| 3 | import * as Effect from "effect/Effect"; |
| 4 | import type * as HttpServerRequest from "effect/http/HttpServerRequest"; |
| 5 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 6 | |
| 7 | /** Producer accepted the message(s). */ |
| 8 | const accepted = HttpServerResponse.json({ ok: true }, { status: 202 }); |
| 9 | const failed = (cause: Cause.Cause<unknown>) => |
| 10 | HttpServerResponse.json({ error: Cause.pretty(cause) }, { status: 500 }); |
| 11 | |
| 12 | /** |
| 13 | * Shared producer routes exercised by both the binding and HTTP fixtures so |
| 14 | * every method of {@link WriteQueueClient} is driven over `fetch`. The |
| 15 | * Cloudflare Queue binding is producer-only, so this is the full surface: |
| 16 | * |
| 17 | * - `POST /send` — `send(body)` with the default JSON content type. |
| 18 | * - `POST /send-text` — `send(body, { contentType: "text" })`. |
| 19 | * - `POST /sendBatch` — `sendBatch([...])` with JSON message bodies. |
| 20 | * - `POST /sendBatch-text` — `sendBatch([...])` with per-message `text` |
| 21 | * content type. |
| 22 | * |
| 23 | * (`raw` is the native-binding escape hatch and `Effect.die`s over the HTTP |
| 24 | * token, so it is intentionally not routed here.) |
| 25 | * |
| 26 | * Returns `undefined` when the path is not a producer route so the caller can |
| 27 | * fall through to a 404. |
| 28 | */ |
| 29 | export const producerRoutes = ( |
| 30 | q: WriteQueueClient, |
| 31 | request: HttpServerRequest.HttpServerRequest, |
| 32 | url: URL, |
| 33 | ) => |
| 34 | Effect.gen(function* () { |
| 35 | if (request.method !== "POST") return undefined; |
| 36 | switch (url.pathname) { |
| 37 | case "/send": { |
| 38 | const text = yield* request.text; |
| 39 | return yield* q.send({ text }).pipe( |
| 40 | Effect.matchCauseEffect({ |
| 41 | onSuccess: () => accepted, |
| 42 | onFailure: failed, |
| 43 | }), |
| 44 | ); |
| 45 | } |
| 46 | case "/send-text": { |
| 47 | const text = yield* request.text; |
| 48 | return yield* q.send(text, { contentType: "text" }).pipe( |
| 49 | Effect.matchCauseEffect({ |
| 50 | onSuccess: () => accepted, |
| 51 | onFailure: failed, |
| 52 | }), |
| 53 | ); |
| 54 | } |
| 55 | case "/sendBatch": |
| 56 | return yield* q |
| 57 | .sendBatch([{ body: { text: "a" } }, { body: { text: "b" } }]) |
| 58 | .pipe( |
| 59 | Effect.matchCauseEffect({ |
| 60 | onSuccess: () => accepted, |
| 61 | onFailure: failed, |
| 62 | }), |
| 63 | ); |
| 64 | case "/sendBatch-text": |
| 65 | return yield* q |
| 66 | .sendBatch([ |
| 67 | { body: "a", contentType: "text" }, |
| 68 | { body: "b", contentType: "text" }, |
| 69 | ]) |
| 70 | .pipe( |
| 71 | Effect.matchCauseEffect({ |
| 72 | onSuccess: () => accepted, |
| 73 | onFailure: failed, |
| 74 | }), |
| 75 | ); |
| 76 | default: |
| 77 | return undefined; |
| 78 | } |
| 79 | }); |