1import * as Cloudflare from "@/Cloudflare";
2import * as Effect from "effect/Effect";
3import { HttpServerRequest } from "effect/http/HttpServerRequest";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5
6/**
7 * Effect-native Worker fixture that exercises the real Cloudflare RateLimit
8 * binding via `RateLimitBinding`. Yielding `Cloudflare.RateLimit(...)`
9 * during the Init phase attaches the binding to this Worker and returns the
10 * runtime client in one step — no separate `.bind(...)` call. `/burst` then
11 * calls `throttle.limit({ key })` `n` times against a single isolate.
12 *
13 * Driving the limit inside one request makes throttling deterministic (no
14 * reliance on edge propagation between requests): with `limit: 2` the first
15 * two calls for a key succeed and the rest are denied, while a different key
16 * gets its own independent budget.
17 */
18export default class RateLimitEffectWorker extends Cloudflare.Worker<RateLimitEffectWorker>()(
19 "RateLimitEffectWorker",
20 {
21 main: import.meta.url,
22 },
23 Effect.gen(function* () {
24 const throttle = yield* Cloudflare.RateLimit("THROTTLE", {
25 namespaceId: 11_001,
26 simple: { limit: 2, period: 10 },
27 });
28
29 return {
30 fetch: Effect.gen(function* () {
31 const request = yield* HttpServerRequest;
32 const url = new URL(request.originalUrl);
33
34 if (url.pathname === "/burst") {
35 const key = url.searchParams.get("key") ?? "default";
36 const n = Number(url.searchParams.get("n") ?? "5");
37
38 const results: boolean[] = [];
39 for (let i = 0; i < n; i++) {
40 const { success } = yield* throttle
41 .limit({ key })
42 .pipe(Effect.orDie);
43 results.push(success);
44 }
45
46 return yield* HttpServerResponse.json({ key, results });
47 }
48
49 return HttpServerResponse.text("ok");
50 }),
51 };
52 }).pipe(Effect.provide(Cloudflare.Workers.RateLimitBinding)),
53) {}