Skip to content

Commit 0a58db8

Browse files
committed
Add rate limiting support
1 parent c13ec06 commit 0a58db8

File tree

9 files changed

+487
-111
lines changed

9 files changed

+487
-111
lines changed

deno.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
"test": "deno test --allow-net"
1010
},
1111
"imports": {
12-
"@deno/dnt": "jsr:@deno/dnt@^0.41.3",
13-
"@std/assert": "jsr:@std/assert@^1.0.10",
14-
"@std/path": "jsr:@std/path@^1.0.8",
15-
"zod": "npm:zod@^3.24.1"
12+
"@deno/dnt": "jsr:@deno/dnt@^0.42.1",
13+
"@std/assert": "jsr:@std/assert@^1.0.13",
14+
"@std/path": "jsr:@std/path@^1.1.1",
15+
"zod": "npm:zod@^4.0.5"
1616
},
1717
"exclude": ["npm"]
1818
}

deno.lock

Lines changed: 55 additions & 104 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export {
1111
FetchClientProvider,
1212
} from "./src/FetchClientProvider.ts";
1313
export * from "./src/DefaultHelpers.ts";
14+
export { type RateLimitConfig, RateLimiter } from "./src/RateLimiter.ts";

readme.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ FetchClient is a library that makes it easier to use the fetch API for JSON APIs
77
* [Automatic model validation](#model-validator)
88
* [Caching](#caching)
99
* [Middleware](#middleware)
10+
* [Rate limiting](#rate-limiting)
1011
* [Problem Details](https://www.rfc-editor.org/rfc/rfc9457.html) support
1112
* Option to parse dates in responses
1213

@@ -130,6 +131,32 @@ const response = await client.getJSON<Products>(
130131
);
131132
```
132133

134+
### Rate Limiting
135+
136+
```ts
137+
import { FetchClientProvider } from '@exceptionless/fetchclient';
138+
139+
const provider = new FetchClientProvider();
140+
141+
// Enable rate limiting: max 100 requests per hour
142+
provider.enableRateLimit({
143+
maxRequests: 100,
144+
windowMs: 60 * 60 * 1000, // 1 hour
145+
});
146+
147+
const client = provider.getFetchClient();
148+
149+
// Requests exceeding the limit will receive HTTP 429 responses
150+
const response = await client.getJSON('https://api.example.com/data', {
151+
expectedStatusCodes: [200, 429] // Handle 429 without throwing
152+
});
153+
154+
if (response.status === 429) {
155+
const retryAfter = response.headers.get('Retry-After');
156+
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
157+
}
158+
```
159+
133160
Also, take a look at the tests:
134161

135162
[FetchClient Tests](src/FetchClient.test.ts)

src/DefaultHelpers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import type { FetchClientResponse } from "./FetchClientResponse.ts";
99
import type { ProblemDetails } from "./ProblemDetails.ts";
1010
import type { GetRequestOptions, RequestOptions } from "./RequestOptions.ts";
11+
import type { RateLimitConfig } from "./RateLimiter.ts";
1112

1213
let getCurrentProviderFunc: () => FetchClientProvider | null = () => null;
1314

@@ -164,3 +165,11 @@ export function useMiddleware(middleware: FetchClientMiddleware) {
164165
export function setRequestOptions(options: RequestOptions) {
165166
getCurrentProvider().applyOptions({ defaultRequestOptions: options });
166167
}
168+
169+
/**
170+
* Enables rate limiting for the current provider.
171+
* @param config - The rate limit configuration.
172+
*/
173+
export function enableRateLimit(config: RateLimitConfig) {
174+
getCurrentProvider().enableRateLimit(config);
175+
}

src/FetchClient.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import {
88
useFetchClient,
99
} from "../mod.ts";
1010
import { FetchClientProvider } from "./FetchClientProvider.ts";
11-
import { z, type ZodTypeAny } from "zod";
11+
import * as z from "zod/mini";
1212

1313
export const TodoSchema = z.object({
1414
userId: z.number(),
1515
id: z.number(),
1616
title: z.string(),
1717
completed: z.boolean(),
18-
completedTime: z.coerce.date().optional(),
18+
completedTime: z.optional(z.coerce.date()),
1919
});
2020

2121
type Todo = z.infer<typeof TodoSchema>;
@@ -614,7 +614,7 @@ Deno.test("can getJSON with zod schema", async () => {
614614
await next();
615615

616616
if (ctx.options.schema) {
617-
const schema = ctx.options.schema as ZodTypeAny;
617+
const schema = ctx.options.schema as z.ZodMiniType;
618618
const parsed = schema.safeParse(ctx.response!.data);
619619

620620
if (parsed.success) {

src/FetchClientProvider.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import type { ProblemDetails } from "./ProblemDetails.ts";
55
import { FetchClientCache } from "./FetchClientCache.ts";
66
import type { FetchClientOptions } from "./FetchClientOptions.ts";
77
import { type IObjectEvent, ObjectEvent } from "./ObjectEvent.ts";
8+
import {
9+
createRateLimitMiddleware,
10+
type RateLimitConfig,
11+
RateLimiter,
12+
} from "./RateLimiter.ts";
813

914
type Fetch = typeof globalThis.fetch;
1015

@@ -17,6 +22,7 @@ export class FetchClientProvider {
1722
#cache: FetchClientCache;
1823
#counter = new Counter();
1924
#onLoading = new ObjectEvent<boolean>();
25+
#rateLimiter?: RateLimiter;
2026

2127
/**
2228
* Creates a new instance of FetchClientProvider.
@@ -187,6 +193,22 @@ export class FetchClientProvider {
187193
],
188194
};
189195
}
196+
197+
/**
198+
* Enables rate limiting for all FetchClient instances created by this provider.
199+
* @param config - The rate limit configuration.
200+
*/
201+
public enableRateLimit(config: RateLimitConfig) {
202+
this.#rateLimiter = new RateLimiter(config);
203+
this.useMiddleware(createRateLimitMiddleware(this.#rateLimiter));
204+
}
205+
206+
/**
207+
* Gets the current rate limiter instance, if rate limiting is enabled.
208+
*/
209+
public get rateLimiter(): RateLimiter | undefined {
210+
return this.#rateLimiter;
211+
}
190212
}
191213

192214
const provider = new FetchClientProvider();

0 commit comments

Comments
 (0)