/**
* Rate limiter for HN Algolia API
* Limit: 10,000 requests per hour per IP
*/
import { RateLimitError } from './errors.js';
import { logger } from './logger.js';
export class RateLimiter {
private requests = 0;
private readonly limit: number;
private resetTimer: NodeJS.Timeout | null = null;
private resetTime: Date;
constructor(limit = 10000) {
this.limit = limit;
this.resetTime = new Date(Date.now() + 60 * 60 * 1000);
this.startResetTimer();
}
private startResetTimer(): void {
if (this.resetTimer) {
clearInterval(this.resetTimer);
}
this.resetTimer = setInterval(
() => {
logger.info('Rate limit reset');
this.requests = 0;
this.resetTime = new Date(Date.now() + 60 * 60 * 1000);
},
60 * 60 * 1000
);
}
/** Check if request can proceed, throw if limit exceeded */
checkLimit(): void {
const percentage = (this.requests / this.limit) * 100;
if (percentage >= 95) {
throw new RateLimitError(this.requests, this.limit, this.resetTime);
}
if (percentage >= 90) {
logger.warn(
{
current: this.requests,
limit: this.limit,
percentage: percentage.toFixed(1),
resetTime: this.resetTime,
},
'Approaching rate limit'
);
}
this.requests++;
logger.debug({ current: this.requests, limit: this.limit }, 'Rate limit check passed');
}
/** Get current usage statistics */
getCurrentUsage(): { current: number; limit: number; percentage: number; resetTime: Date } {
return {
current: this.requests,
limit: this.limit,
percentage: (this.requests / this.limit) * 100,
resetTime: this.resetTime,
};
}
/** Clean up timer */
destroy(): void {
if (this.resetTimer) {
clearInterval(this.resetTimer);
this.resetTimer = null;
}
}
}
// Singleton instance
export const rateLimiter = new RateLimiter();