All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m6s
36 lines
1 KiB
TypeScript
36 lines
1 KiB
TypeScript
/**
|
|
* In-memory sliding window rate limiter.
|
|
* Single-instance, single-user container — no need for Redis.
|
|
*/
|
|
|
|
interface RateLimitEntry {
|
|
timestamps: number[];
|
|
}
|
|
|
|
const store = new Map<string, RateLimitEntry>();
|
|
|
|
const WINDOW_MS = 60 * 1000; // 1 minute
|
|
const MAX_REQUESTS = 10;
|
|
|
|
export function checkRateLimit(
|
|
key: string,
|
|
options: { windowMs?: number; maxRequests?: number } = {}
|
|
): { allowed: boolean; retryAfterMs: number } {
|
|
const now = Date.now();
|
|
const windowMs = options.windowMs ?? WINDOW_MS;
|
|
const maxRequests = options.maxRequests ?? MAX_REQUESTS;
|
|
const entry = store.get(key) ?? { timestamps: [] };
|
|
|
|
// Remove timestamps outside the window
|
|
entry.timestamps = entry.timestamps.filter((t) => now - t < windowMs);
|
|
|
|
if (entry.timestamps.length >= maxRequests) {
|
|
const oldestInWindow = entry.timestamps[0];
|
|
const retryAfterMs = windowMs - (now - oldestInWindow);
|
|
return { allowed: false, retryAfterMs };
|
|
}
|
|
|
|
entry.timestamps.push(now);
|
|
store.set(key, entry);
|
|
return { allowed: true, retryAfterMs: 0 };
|
|
}
|