Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions utils/rateLimit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* rateLimit - Restricts how many times a function can be called within an interval
*
* @param {Function} fn - The function to rate limit
* @param {Object} config - Configuration object
* @param {number} config.limit - Max number of calls allowed per interval
* @param {number} config.interval - Time window in milliseconds
* @returns {Function} - A rate-limited version of the input function
*
* @example
* const limitedFn = rateLimit(apiCall, { limit: 3, interval: 1000 });
* limitedFn(); // allowed
* limitedFn(); // allowed
* limitedFn(); // allowed
* limitedFn(); // throws Error('Rate limit exceeded')
*/
export function rateLimit(fn, config) {
if (typeof fn !== 'function') {
throw new TypeError('rateLimit: first argument must be a function');
}

if (!config || typeof config !== 'object') {
throw new TypeError('rateLimit: second argument must be a config object');
}

const { limit, interval } = config;

if (typeof limit !== 'number' || limit <= 0) {
throw new RangeError('rateLimit: limit must be a number greater than 0');
}

if (typeof interval !== 'number' || interval <= 0) {
throw new RangeError('rateLimit: interval must be a number greater than 0');
}

let callCount = 0;
let windowStart = Date.now();

return function rateLimited(...args) {
const now = Date.now();

// Reset counter if the interval window has passed
if (now - windowStart >= interval) {
callCount = 0;
windowStart = now;
}

if (callCount >= limit) {
throw new Error('Rate limit exceeded');
}

callCount++;
return fn.apply(this, args);
};
}