Skip to content

Commit b5f370a

Browse files
committed
npm rate limit
1 parent 8d24c3b commit b5f370a

File tree

2 files changed

+19
-0
lines changed

2 files changed

+19
-0
lines changed

src/npm.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import {utcDay, utcYear} from "d3-time";
22
import {format as formatIso} from "isoformat";
33
import {fetchCached as fetch} from "./fetch.js";
4+
import {RateLimiter} from "./rate.js";
45
import {today} from "./today.js";
56

7+
const rateLimit = RateLimiter(30, 60_000); // 30 requests per 60 seconds
8+
69
export async function fetchNpm(path) {
710
const url = new URL(path, "https://api.npmjs.org");
811
let response;
912
let headers;
1013
for (let attempt = 0, maxAttempts = 3; attempt < maxAttempts; ++attempt) {
14+
await rateLimit();
1115
response = await fetch(url, {headers: {"User-Agent": "observablehq/oss-analytics"}});
1216
headers = response.headers;
1317
if (response.ok) break;

src/rate.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function RateLimiter(n, duration) {
2+
let recents = [];
3+
return async () => {
4+
if (!((n = +n) >= 1)) throw new Error(`invalid n: ${n}`);
5+
while (true) {
6+
const time = Date.now() - duration;
7+
recents = recents.filter((r) => r >= time);
8+
if (recents.length < n) break;
9+
const delay = Math.max(recents[0] + duration - Date.now(), 100);
10+
console.warn(`rate limit reached; waiting ${delay}`);
11+
await new Promise((resolve) => setTimeout(resolve, delay));
12+
}
13+
recents.push(Date.now());
14+
};
15+
}

0 commit comments

Comments
 (0)