UNPKG

@ai-capabilities-suite/mcp-debugger-core

Version:

Core debugging engine for Node.js and TypeScript applications. Provides Inspector Protocol integration, breakpoint management, variable inspection, execution control, profiling, hang detection, and source map support.

268 lines 9.29 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RateLimiter = exports.RateLimitError = void 0; /** * Rate limit error */ class RateLimitError extends Error { constructor(message, operationType, retryAfter) { super(message); this.operationType = operationType; this.retryAfter = retryAfter; this.name = 'RateLimitError'; } } exports.RateLimitError = RateLimitError; /** * Manages rate limiting for debugging operations * Tracks requests per operation type and enforces limits */ class RateLimiter { /** * Create a new RateLimiter * @param defaultConfig Optional default rate limit configuration */ constructor(defaultConfig) { this.limits = new Map(); this.entries = new Map(); this.metrics = new Map(); this.defaultEntries = new Map(); this.defaultConfig = defaultConfig; } /** * Configure rate limit for an operation type * @param operationType The operation type (e.g., 'debugger_start', 'debugger_set_breakpoint') * @param config Rate limit configuration */ setLimit(operationType, config) { this.limits.set(operationType, config); if (!this.entries.has(operationType)) { this.entries.set(operationType, new Map()); } if (!this.metrics.has(operationType)) { this.metrics.set(operationType, { requestCount: 0, limitExceeded: 0 }); } } /** * Check if a request is allowed under rate limits * @param operationTypeOrIdentifier The operation type, or identifier if using default config * @param identifier Optional unique identifier for the requester (e.g., session ID, user ID) * @returns Object with allowed status and retryAfter time */ checkLimit(operationTypeOrIdentifier, identifier) { // If only one argument and default config exists, treat it as identifier if (identifier === undefined && this.defaultConfig) { return this.checkDefaultLimit(operationTypeOrIdentifier); } const operationType = operationTypeOrIdentifier; const id = identifier || 'default'; const config = this.limits.get(operationType); if (!config) { // No limit configured for this operation type return { allowed: true }; } const entries = this.entries.get(operationType); const now = new Date(); let entry = entries.get(id); // Initialize or reset entry if window has passed if (!entry || now >= entry.resetAt) { entry = { count: 0, resetAt: new Date(now.getTime() + config.windowMs), }; entries.set(id, entry); } // Increment request count entry.count++; // Track metrics const metrics = this.metrics.get(operationType); metrics.requestCount++; // Check if limit is exceeded if (entry.count > config.maxRequests) { metrics.limitExceeded++; const retryAfter = Math.ceil((entry.resetAt.getTime() - now.getTime()) / 1000); return { allowed: false, retryAfter }; } return { allowed: true }; } /** * Check limit using default configuration * @param identifier Unique identifier for the requester * @returns Object with allowed status and retryAfter time */ checkDefaultLimit(identifier) { if (!this.defaultConfig) { return { allowed: true }; } const now = new Date(); let entry = this.defaultEntries.get(identifier); // Initialize or reset entry if window has passed if (!entry || now >= entry.resetAt) { entry = { count: 0, resetAt: new Date(now.getTime() + this.defaultConfig.windowMs), }; this.defaultEntries.set(identifier, entry); } // Increment request count entry.count++; // Check if limit is exceeded if (entry.count > this.defaultConfig.maxRequests) { const retryAfter = Math.ceil((entry.resetAt.getTime() - now.getTime()) / 1000); return { allowed: false, retryAfter }; } return { allowed: true }; } /** * Check if a request is allowed and throw error if not (legacy method) * @param operationType The operation type * @param identifier Unique identifier for the requester * @throws RateLimitError if the rate limit is exceeded */ checkLimitOrThrow(operationType, identifier = 'default') { const result = this.checkLimit(operationType, identifier); if (!result.allowed) { throw new RateLimitError(`Rate limit exceeded for ${operationType}. Try again in ${result.retryAfter} seconds.`, operationType, result.retryAfter || 0); } } /** * Get the current rate limit status for an operation and identifier * @param operationType The operation type * @param identifier Unique identifier for the requester * @returns Current count and reset time, or null if no limit configured */ getStatus(operationType, identifier = 'default') { const config = this.limits.get(operationType); if (!config) { return null; } const entries = this.entries.get(operationType); if (!entries) { return null; } const entry = entries.get(identifier); if (!entry) { return { count: 0, limit: config.maxRequests, resetAt: new Date(Date.now() + config.windowMs), }; } return { count: entry.count, limit: config.maxRequests, resetAt: entry.resetAt, }; } /** * Get metrics for an operation type * @param operationType The operation type * @returns Metrics for the operation type */ getMetrics(operationType) { const config = this.limits.get(operationType); const metrics = this.metrics.get(operationType); const entries = this.entries.get(operationType); if (!config || !metrics) { return null; } // Get current window info (aggregate across all identifiers) let totalCount = 0; let earliestReset = new Date(Date.now() + config.windowMs); if (entries) { for (const entry of entries.values()) { totalCount += entry.count; if (entry.resetAt < earliestReset) { earliestReset = entry.resetAt; } } } return { operationType, requestCount: metrics.requestCount, limitExceeded: metrics.limitExceeded, currentWindow: { count: totalCount, resetAt: earliestReset, }, }; } /** * Get metrics for all operation types * @returns Array of metrics for all operation types */ getAllMetrics() { const allMetrics = []; for (const operationType of this.limits.keys()) { const metrics = this.getMetrics(operationType); if (metrics) { allMetrics.push(metrics); } } return allMetrics; } /** * Reset rate limit for a specific identifier * @param operationType The operation type * @param identifier Unique identifier for the requester * @returns True if the entry was found and reset */ reset(operationType, identifier = 'default') { const entries = this.entries.get(operationType); if (!entries) { return false; } return entries.delete(identifier); } /** * Reset all rate limits for an operation type * @param operationType The operation type * @returns True if the operation type was found */ resetAll(operationType) { const entries = this.entries.get(operationType); if (!entries) { return false; } entries.clear(); return true; } /** * Clean up expired entries */ cleanup() { const now = new Date(); for (const [operationType, entries] of this.entries.entries()) { for (const [identifier, entry] of entries.entries()) { if (now >= entry.resetAt) { entries.delete(identifier); } } } } /** * Clear all rate limits and metrics */ clear() { this.limits.clear(); this.entries.clear(); this.metrics.clear(); } /** * Get all configured operation types * @returns Array of operation types */ getOperationTypes() { return Array.from(this.limits.keys()); } /** * Check if an operation type has a rate limit configured * @param operationType The operation type * @returns True if a rate limit is configured */ hasLimit(operationType) { return this.limits.has(operationType); } } exports.RateLimiter = RateLimiter; //# sourceMappingURL=rate-limiter.js.map