@beignet/core
Version:
Core framework primitives for Beignet
59 lines • 2.22 kB
JavaScript
function assertPositiveInteger(name, value) {
if (!Number.isInteger(value) || value <= 0) {
throw new Error(`${name} must be a positive integer`);
}
}
function toRetryAfterSeconds(resetAt) {
return Math.max(0, Math.ceil((resetAt - Date.now()) / 1000));
}
/**
* Create an in-memory rate limiter for tests, examples, and single-process
* development.
*
* This adapter is not durable or distributed. Production apps should use a
* provider backed by a shared atomic store when multiple processes or regions
* can serve requests.
*
* @returns A rate-limit port backed by a local `Map`.
*/
export function createMemoryRateLimiter() {
const windows = new Map();
const sweepIntervalMs = 60_000;
let nextSweepAt = Date.now() + sweepIntervalMs;
return {
async hit({ key, limit, windowSec }) {
assertPositiveInteger("limit", limit);
assertPositiveInteger("windowSec", windowSec);
const now = Date.now();
// Lazy sweep: distinct keys would otherwise accumulate expired windows
// forever. Prune them at most once per sweep interval on the hit path.
if (now >= nextSweepAt) {
for (const [existingKey, existing] of windows) {
if (existing.resetAt <= now) {
windows.delete(existingKey);
}
}
nextSweepAt = now + sweepIntervalMs;
}
const current = windows.get(key);
const window = current && current.resetAt > now
? current
: {
count: 0,
resetAt: now + windowSec * 1000,
};
window.count += 1;
windows.set(key, window);
const allowed = window.count <= limit;
const remaining = Math.max(0, limit - window.count);
const resetAt = new Date(window.resetAt);
return {
allowed,
remaining,
resetAt,
retryAfterSeconds: allowed ? null : toRetryAfterSeconds(window.resetAt),
};
},
};
}
//# sourceMappingURL=rate-limit.js.map