UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

201 lines 6.49 kB
/** * Token Bucket Rate Limiter for URL Downloads * * Implements a token bucket algorithm to limit concurrent URL downloads. * This prevents DoS attacks from rapid URL download requests. * * Default configuration: 10 downloads per second */ import { logger } from "./logger.js"; import { ErrorFactory } from "./errorHandling.js"; /** * Default configuration: 10 downloads per second */ const DEFAULT_CONFIG = { maxTokens: 10, refillIntervalMs: 1000, tokensPerRefill: 10, maxQueueSize: 100, queueTimeoutMs: 30000, }; /** * Token Bucket Rate Limiter * * Uses a token bucket algorithm where: * - Tokens are consumed when a download is requested * - Tokens are refilled at a fixed rate * - Requests that exceed the limit are queued */ export class TokenBucketRateLimiter { tokens; config; queue = []; refillTimer = null; lastRefillTime; constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; this.tokens = this.config.maxTokens; this.lastRefillTime = Date.now(); this.startRefillTimer(); } /** * Start the token refill timer */ startRefillTimer() { if (this.refillTimer) { return; } this.refillTimer = setInterval(() => { this.refillTokens(); this.processQueue(); }, this.config.refillIntervalMs); // Unref to prevent keeping the process alive this.refillTimer.unref(); } /** * Refill tokens based on elapsed time */ refillTokens() { const now = Date.now(); const elapsed = now - this.lastRefillTime; const intervalsElapsed = Math.floor(elapsed / this.config.refillIntervalMs); if (intervalsElapsed > 0) { const tokensToAdd = intervalsElapsed * this.config.tokensPerRefill; this.tokens = Math.min(this.config.maxTokens, this.tokens + tokensToAdd); this.lastRefillTime = now; } } /** * Process queued requests when tokens become available */ processQueue() { const now = Date.now(); // Remove timed out requests while (this.queue.length > 0) { const request = this.queue[0]; if (now - request.timestamp > this.config.queueTimeoutMs) { this.queue.shift(); if (request.timeoutTimer) { clearTimeout(request.timeoutTimer); } request.reject(ErrorFactory.rateLimiterQueueTimeout(this.config.queueTimeoutMs)); } else { break; } } // Process requests while we have tokens while (this.tokens > 0 && this.queue.length > 0) { const request = this.queue.shift(); if (request) { if (request.timeoutTimer) { clearTimeout(request.timeoutTimer); } this.tokens--; request.resolve(); } } } /** * Acquire a token for a download * Returns immediately if token is available, otherwise queues the request * * @throws NeuroLinkError if queue is full or request times out */ async acquire() { // Refill tokens based on elapsed time this.refillTokens(); // If token available, consume it immediately if (this.tokens > 0) { this.tokens--; return; } // Check queue size limit if (this.queue.length >= this.config.maxQueueSize) { logger.warn(`Rate limiter queue full (${this.config.maxQueueSize} requests pending)`); throw ErrorFactory.rateLimiterQueueFull(this.config.maxQueueSize); } // Queue the request with per-request timeout handling return new Promise((resolve, reject) => { // Create per-request timeout timer for robust timeout handling const timeoutTimer = setTimeout(() => { // Remove from queue if still present const index = this.queue.findIndex((req) => req.timeoutTimer === timeoutTimer); if (index !== -1) { this.queue.splice(index, 1); reject(ErrorFactory.rateLimiterQueueTimeout(this.config.queueTimeoutMs)); } }, this.config.queueTimeoutMs); this.queue.push({ resolve, reject, timestamp: Date.now(), timeoutTimer, }); }); } /** * Get current rate limiter statistics */ getStats() { return { availableTokens: this.tokens, queueLength: this.queue.length, maxTokens: this.config.maxTokens, maxQueueSize: this.config.maxQueueSize, }; } /** * Reset the rate limiter to initial state */ reset() { this.tokens = this.config.maxTokens; this.lastRefillTime = Date.now(); // Reject all queued requests and clear timeout timers while (this.queue.length > 0) { const request = this.queue.shift(); if (request) { if (request.timeoutTimer) { clearTimeout(request.timeoutTimer); } request.reject(ErrorFactory.rateLimiterReset()); } } } /** * Stop the rate limiter and clean up resources */ stop() { if (this.refillTimer) { clearInterval(this.refillTimer); this.refillTimer = null; } this.reset(); } } /** * Global rate limiter instance for URL downloads * Default: 10 downloads per second */ export const urlDownloadRateLimiter = new TokenBucketRateLimiter({ maxTokens: 10, refillIntervalMs: 1000, tokensPerRefill: 10, maxQueueSize: 100, queueTimeoutMs: 30000, }); /** * Rate-limited wrapper for async functions * Ensures the function is rate-limited using the provided rate limiter * * @param fn - The async function to wrap * @param rateLimiter - The rate limiter to use (defaults to urlDownloadRateLimiter) * @returns A rate-limited version of the function */ export function withRateLimit(fn, rateLimiter = urlDownloadRateLimiter) { return async (...args) => { await rateLimiter.acquire(); return fn(...args); }; } //# sourceMappingURL=rateLimiter.js.map