From 4245808ee7e7d33156d92a16bd10918e6003185d Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Nov 2025 14:28:15 +0700 Subject: [PATCH 1/2] feat(ratelimit): Cloudflare Worker Ratelimit Adapter --- apps/content/docs/helpers/ratelimit.md | 18 +++++++++++++++++- packages/ratelimit/package.json | 8 +++++++- .../src/adapters/cloudflare-ratelimit.test.ts | 17 +++++++++++++++++ .../src/adapters/cloudflare-ratelimit.ts | 13 +++++++++++++ 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 packages/ratelimit/src/adapters/cloudflare-ratelimit.test.ts create mode 100644 packages/ratelimit/src/adapters/cloudflare-ratelimit.ts diff --git a/apps/content/docs/helpers/ratelimit.md b/apps/content/docs/helpers/ratelimit.md index 0257b0675..40deb9d88 100644 --- a/apps/content/docs/helpers/ratelimit.md +++ b/apps/content/docs/helpers/ratelimit.md @@ -72,7 +72,7 @@ const limiter = new RedisRatelimiter({ You can use any Redis client that supports Lua script evaluation by providing an `eval` function. ::: -### Upstash Adapter +### Upstash Ratelimit Adapter Adapter for [@upstash/ratelimit](https://www.npmjs.com/package/@upstash/ratelimit), optimized for serverless environments like Vercel Edge and Cloudflare Workers. @@ -103,6 +103,22 @@ const limiter = new UpstashRatelimiter(ratelimit, { ::: +### Cloudflare Ratelimit Adapter + +Adapter for [Cloudflare Workers Ratelimit](https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/). + +```ts +import { CloudflareRatelimiter } from '@orpc/experimental-ratelimit/cloudflare-ratelimit' + +export default { + async fetch(request, env) { + const limiter = new CloudflareRatelimiter(env.MY_RATE_LIMITER) + + return new Response(`Hello World!`) + } +} +``` + ## Blocking Mode Some adapters support blocking mode, which waits for the rate limit to reset instead of immediately rejecting requests. diff --git a/packages/ratelimit/package.json b/packages/ratelimit/package.json index e53ecfe03..63d478fa2 100644 --- a/packages/ratelimit/package.json +++ b/packages/ratelimit/package.json @@ -34,6 +34,11 @@ "types": "./dist/adapters/upstash-ratelimit.d.mts", "import": "./dist/adapters/upstash-ratelimit.mjs", "default": "./dist/adapters/upstash-ratelimit.mjs" + }, + "./cloudflare-ratelimit": { + "types": "./dist/adapters/cloudflare-ratelimit.d.mts", + "import": "./dist/adapters/cloudflare-ratelimit.mjs", + "default": "./dist/adapters/cloudflare-ratelimit.mjs" } } }, @@ -41,7 +46,8 @@ ".": "./src/index.ts", "./memory": "./src/adapters/memory.ts", "./redis": "./src/adapters/redis.ts", - "./upstash-ratelimit": "./src/adapters/upstash-ratelimit.ts" + "./upstash-ratelimit": "./src/adapters/upstash-ratelimit.ts", + "./cloudflare-ratelimit": "./src/adapters/cloudflare-ratelimit.ts" }, "files": [ "dist" diff --git a/packages/ratelimit/src/adapters/cloudflare-ratelimit.test.ts b/packages/ratelimit/src/adapters/cloudflare-ratelimit.test.ts new file mode 100644 index 000000000..3d99781dd --- /dev/null +++ b/packages/ratelimit/src/adapters/cloudflare-ratelimit.test.ts @@ -0,0 +1,17 @@ +import { CloudflareRatelimiter } from './cloudflare-ratelimit' + +it('cloudflareRatelimiter', async () => { + const ratelimit = { + limit: vi.fn(() => Promise.resolve({ success: true })), + } + + const ratelimiter = new CloudflareRatelimiter(ratelimit) + + const result = ratelimiter.limit('test-key') + + expect(ratelimit.limit).toHaveBeenCalledTimes(1) + expect(ratelimit.limit).toHaveBeenCalledWith({ key: 'test-key' }) + expect(result).toBe(ratelimit.limit.mock.results[0]!.value) + + expect(await result).toEqual({ success: true }) +}) diff --git a/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts b/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts new file mode 100644 index 000000000..061878b5e --- /dev/null +++ b/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts @@ -0,0 +1,13 @@ +import type { Ratelimiter, RatelimiterLimitResult } from '@orpc/experimental-ratelimit' + +export class CloudflareRatelimiter implements Ratelimiter { + constructor( + private readonly ratelimit: { + limit: (options: { key: string }) => Promise + }, + ) {} + + limit(key: string): Promise { + return this.ratelimit.limit({ key }) + } +} From 24987e9f0ca24005534cbce7f0fb671d628793b1 Mon Sep 17 00:00:00 2001 From: unnoq Date: Thu, 13 Nov 2025 14:46:33 +0700 Subject: [PATCH 2/2] Update packages/ratelimit/src/adapters/cloudflare-ratelimit.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/ratelimit/src/adapters/cloudflare-ratelimit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts b/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts index 061878b5e..a94fa368f 100644 --- a/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts +++ b/packages/ratelimit/src/adapters/cloudflare-ratelimit.ts @@ -1,4 +1,4 @@ -import type { Ratelimiter, RatelimiterLimitResult } from '@orpc/experimental-ratelimit' +import type { Ratelimiter, RatelimiterLimitResult } from '../types' export class CloudflareRatelimiter implements Ratelimiter { constructor(