-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrate-limiter.js
More file actions
112 lines (96 loc) · 3.85 KB
/
Copy pathrate-limiter.js
File metadata and controls
112 lines (96 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// Rate limiter for API calls
class RateLimiter {
constructor() {
this.queues = new Map(); // Separate queue for each API
this.limits = {
'tmdb': {
maxRequests: 30, // 30 requests
perSeconds: 10, // per 10 seconds
minDelay: 350 // minimum 350ms between requests
},
'thetvdb': {
maxRequests: 100, // 100 requests
perSeconds: 60, // per minute
minDelay: 650 // minimum 650ms between requests
},
'openai': {
maxRequests: 60, // 60 requests
perSeconds: 60, // per minute
minDelay: 1000 // minimum 1 second between requests
},
'default': {
maxRequests: 10, // Conservative default
perSeconds: 10,
minDelay: 1000
}
};
this.requests = new Map(); // Track requests per API
}
async throttle(apiName, fn) {
const limit = this.limits[apiName] || this.limits.default;
// Initialize tracking for this API if needed
if (!this.requests.has(apiName)) {
this.requests.set(apiName, []);
}
const now = Date.now();
const requests = this.requests.get(apiName);
// Remove old requests outside the time window
const windowStart = now - (limit.perSeconds * 1000);
const recentRequests = requests.filter(time => time > windowStart);
// Check if we've hit the rate limit
if (recentRequests.length >= limit.maxRequests) {
// Calculate how long to wait
const oldestRequest = recentRequests[0];
const waitTime = (oldestRequest + (limit.perSeconds * 1000)) - now + 100; // Add 100ms buffer
console.log(`Rate limit reached for ${apiName}, waiting ${waitTime}ms`);
await new Promise(resolve => setTimeout(resolve, waitTime));
// Recursive call after waiting
return this.throttle(apiName, fn);
}
// Check minimum delay between requests
if (recentRequests.length > 0) {
const lastRequest = recentRequests[recentRequests.length - 1];
const timeSinceLastRequest = now - lastRequest;
if (timeSinceLastRequest < limit.minDelay) {
const waitTime = limit.minDelay - timeSinceLastRequest + 10; // Add 10ms buffer
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
// Track this request
recentRequests.push(Date.now());
this.requests.set(apiName, recentRequests);
// Execute the function
try {
return await fn();
} catch (error) {
// On error, wait extra time before next request
await new Promise(resolve => setTimeout(resolve, limit.minDelay * 2));
throw error;
}
}
getStats() {
const stats = {};
for (const [api, requests] of this.requests.entries()) {
const now = Date.now();
const limit = this.limits[api] || this.limits.default;
const windowStart = now - (limit.perSeconds * 1000);
const recentRequests = requests.filter(time => time > windowStart);
stats[api] = {
recent: recentRequests.length,
limit: limit.maxRequests,
window: `${limit.perSeconds}s`
};
}
return stats;
}
reset(apiName) {
if (apiName) {
this.requests.delete(apiName);
} else {
this.requests.clear();
}
}
}
// Singleton instance
const rateLimiter = new RateLimiter();
module.exports = rateLimiter;