diff --git a/README.md b/README.md index 95359d4..9517d8b 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,19 @@ export default { if (!success) { return new Response(`429 Failure – rate limit exceeded for ${pathname}`, { status: 429, - headers: await rateLimit.writeHttpMetadata({ key: pathname }), + headers: await rateLimit.writeHttpMetadata({ + key: pathname, + resource: "resource identifier", // Optional + }), }); } return new Response(`Success!`, { status: 200, - headers: await rateLimit.writeHttpMetadata({ key: pathname }), + headers: await rateLimit.writeHttpMetadata({ + key: pathname, + resource: "resource identifier", // Optional + }), }); }, } satisfies ExportedHandler; @@ -50,7 +56,10 @@ The `writeHttpMetadata` function returns an object with the necessary headers to If you want to apply extra headers, you can either keep the result Headers object and append headers there. ```ts -let headers = await rateLimit.writeHttpMetadata({ key: pathname }); +let headers = await rateLimit.writeHttpMetadata({ + key: pathname, + resource: "resource identifier", // Optional +}); headers.set("X-Extra-Header", "Extra Value"); ``` @@ -60,9 +69,26 @@ Or you can pass a Headers object to the `writeHttpMetadata` function and it will let headers = new Headers(); headers.set("X-Extra-Header", "Extra Value"); -await rateLimit.writeHttpMetadata({ key: pathname, headers }); +await rateLimit.writeHttpMetadata( + { + key: pathname, + resource: "resource identifier", // Optional + }, + headers +); ``` +The `writeHttpMetadata` will set the following headers: + +- `X-RateLimit-Limit`: The maximum number of requests allowed in the current window. +- `X-RateLimit-Remaining`: The number of requests remaining in the current window. +- `X-RateLimit-Used`: The number of requests used in the current window. +- `X-RateLimit-Reset`: The time in seconds when the rate limit window resets. +- `X-RateLimit-Resource`: The resource being rate limited (if provided). +- `Retry-After`: The time in seconds when the rate limit window resets. + +The `X-RateLimit-Reset` and `Retry-After` headers are the same and represent the time in seconds when the rate limit window resets, the reason to duplicate them is to keep compatibility with the `Retry-After` header that is used by the HTTP standard and consistency with common `X-RateLimit-` headers. + ## License See [LICENSE](./LICENSE) diff --git a/package.json b/package.json index 5665afa..8491446 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,7 @@ "version": "0.0.1", "description": "A Rate Limit based on Cloudflare's Worker KV.", "license": "MIT", - "funding": [ - "https://github.com/sponsors/sergiodxa" - ], + "funding": ["https://github.com/sponsors/sergiodxa"], "author": { "name": "Sergio Xalambrí", "email": "hello+oss@sergiodxa.com", @@ -31,11 +29,7 @@ "engines": { "node": ">=20.0.0" }, - "files": [ - "build", - "package.json", - "README.md" - ], + "files": ["build", "package.json", "README.md"], "exports": { ".": "./build/index.js", "./package.json": "./package.json" diff --git a/src/lib/worker-kv-rate-limit.test.ts b/src/lib/worker-kv-rate-limit.test.ts index 95887d8..6ec02fb 100644 --- a/src/lib/worker-kv-rate-limit.test.ts +++ b/src/lib/worker-kv-rate-limit.test.ts @@ -43,13 +43,13 @@ describe(WorkerKVRateLimit.name, () => { let key = "key:4"; let headers = new Headers(); - await rateLimit.writeHttpMetadata({ key }, headers); + await rateLimit.writeHttpMetadata({ key, resource: "test" }, headers); expect(headers.get("X-RateLimit-Limit")).toBe("1"); expect(headers.get("X-RateLimit-Remaining")).toBe("1"); expect(headers.get("X-RateLimit-Used")).toBe("0"); expect(new Date(Number(headers.get("X-RateLimit-Reset")))).toBeValidDate(); - expect(headers.get("X-RateLimit-Resource")).toBe("key:4"); + expect(headers.get("X-RateLimit-Resource")).toBe("test"); expect(new Date(Number(headers.get("Retry-After")))).toBeValidDate(); }); @@ -57,13 +57,13 @@ describe(WorkerKVRateLimit.name, () => { let rateLimit = new WorkerKVRateLimit(kv, { limit: 1, period: 60 }); let key = "key:5"; - let headers = await rateLimit.writeHttpMetadata({ key }); + let headers = await rateLimit.writeHttpMetadata({ key, resource: "test" }); expect(headers.get("X-RateLimit-Limit")).toBe("1"); expect(headers.get("X-RateLimit-Remaining")).toBe("1"); expect(headers.get("X-RateLimit-Used")).toBe("0"); expect(new Date(Number(headers.get("X-RateLimit-Reset")))).toBeValidDate(); - expect(headers.get("X-RateLimit-Resource")).toBe("key:5"); + expect(headers.get("X-RateLimit-Resource")).toBe("test"); expect(new Date(Number(headers.get("Retry-After")))).toBeValidDate(); }); }); diff --git a/src/lib/worker-kv-rate-limit.ts b/src/lib/worker-kv-rate-limit.ts index 28736c1..184f66b 100644 --- a/src/lib/worker-kv-rate-limit.ts +++ b/src/lib/worker-kv-rate-limit.ts @@ -94,7 +94,7 @@ export class WorkerKVRateLimit implements RateLimit { } async writeHttpMetadata( - options: RateLimitOptions, + options: RateLimitOptions & { resource?: string }, headers = new Headers(), ): Promise { let limit = this.#limit; @@ -115,7 +115,12 @@ export class WorkerKVRateLimit implements RateLimit { (this.#limit - result.remaining).toString(), ); headers.append("X-RateLimit-Reset", result.reset.toString()); - headers.append("X-RateLimit-Resource", options.key); + + if (options.resource) { + headers.append("X-RateLimit-Resource", options.resource); + } + + // Standard HTTP header for rate limiting headers.append("Retry-After", result.reset.toString()); return headers;