Skip to content

Commit bfc769c

Browse files
committed
Rate limit handling
1 parent 5f84120 commit bfc769c

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,29 @@ const loops = new LoopsClient(process.env.LOOPS_API_KEY);
3434
const resp = await loops.createContact("[email protected]");
3535
```
3636

37+
## Handling rate limits
38+
39+
If you import `RateLimitExceededError` you can check for rate limit issues with your requests.
40+
41+
You can access details about the rate limits from the `limit` and `remaining` attributes.
42+
43+
```javascript
44+
import { LoopsClient, RateLimitExceededError } from "loops";
45+
46+
const loops = new LoopsClient(process.env.LOOPS_API_KEY);
47+
48+
try {
49+
const resp = await loops.createContact("[email protected]");
50+
} catch (error) {
51+
if (error instanceof RateLimitExceededError) {
52+
console.log(`Rate limit exceeded (${error.limit} per second)`);
53+
// Code here to re-try this request
54+
} else {
55+
// Handle other errors
56+
}
57+
}
58+
```
59+
3760
## Default contact properties
3861

3962
Each contact in Loops has a set of default properties. These will always be returned in API results.
@@ -531,6 +554,7 @@ If your account has no custom fields, an empty list will be returned.
531554

532555
## Version history
533556

557+
- `v3.4.0` (Oct 29, 2024) - Added rate limit handling with [`RateLimitExceededError`](#handling-rate-limits).
534558
- `v3.3.0` (Sep 9, 2024) - Added [`testApiKey()`](#testapikey) method.
535559
- `v3.2.0` (Aug 23, 2024) - Added support for a new `mailingLists` attribute in [`findContact()`](#findcontact).
536560
- `v3.1.1` (Aug 16, 2024) - Support for a new `isPublic` attribute in [`getMailingLists()`](#getmailinglists).

src/index.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,17 @@ interface MailingList {
144144
isPublic: boolean;
145145
}
146146

147+
class RateLimitExceededError extends Error {
148+
limit: number;
149+
remaining: number;
150+
constructor(limit: number, remaining: number) {
151+
super(`Rate limit of ${limit} requests per second exceeded.`);
152+
this.name = "RateLimitExceededError";
153+
this.limit = limit;
154+
this.remaining = remaining;
155+
}
156+
}
157+
147158
class LoopsClient {
148159
apiKey: string;
149160
apiRoot = "https://app.loops.so/api/";
@@ -183,6 +194,28 @@ class LoopsClient {
183194
headers,
184195
body: payload ? JSON.stringify(payload) : undefined,
185196
});
197+
198+
// Handle rate limiting
199+
if (response.status === 429) {
200+
const limit = parseInt(
201+
response.headers.get("x-ratelimit-limit") || "10",
202+
10
203+
);
204+
const remaining = parseInt(
205+
response.headers.get("x-ratelimit-remaining") || "10",
206+
10
207+
);
208+
throw new RateLimitExceededError(limit, remaining);
209+
}
210+
211+
if (!response.ok) {
212+
const errorData = await response.json();
213+
throw new Error(
214+
`API request failed: ${response.status} ${response.statusText}. ${
215+
errorData.message || ""
216+
}`
217+
);
218+
}
186219
return await response.json();
187220
} catch (error) {
188221
throw error;
@@ -430,4 +463,4 @@ class LoopsClient {
430463
}
431464
}
432465

433-
export { LoopsClient };
466+
export { LoopsClient, RateLimitExceededError };

0 commit comments

Comments
 (0)