UNPKG

@andrew_eragon/mcp-server-salesforce

Version:

A SaaS-ready Salesforce connector MCP Server with connection pooling and multi-user support.

142 lines (141 loc) 4.38 kB
/** * Utilities for parallel execution and rate limiting */ /** * Execute promises with concurrency limit */ export async function parallelLimit(items, concurrency, fn) { const results = []; const executing = []; for (const [index, item] of items.entries()) { const promise = fn(item).then(result => { results[index] = result; }); executing.push(promise); if (executing.length >= concurrency) { await Promise.race(executing); executing.splice(executing.findIndex(p => { // Remove completed promises return p === promise || p.settled; }), 1); } } await Promise.all(executing); return results; } /** * Batch items and process in parallel */ export async function batchProcess(items, batchSize, fn) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await fn(batch); results.push(...batchResults); } return results; } /** * Rate limiter */ export class RateLimiter { /** * @param maxRequests Maximum number of requests * @param timeWindow Time window in milliseconds */ constructor(maxRequests, timeWindow) { this.queue = []; this.currentRequests = 0; this.requestTimestamps = []; this.maxRequests = maxRequests; this.timeWindow = timeWindow; } /** * Execute function with rate limiting */ async execute(fn) { await this.waitForSlot(); try { return await fn(); } finally { this.releaseSlot(); } } async waitForSlot() { const now = Date.now(); // Remove old timestamps this.requestTimestamps = this.requestTimestamps.filter(ts => now - ts < this.timeWindow); // If we have capacity, proceed immediately if (this.requestTimestamps.length < this.maxRequests) { this.requestTimestamps.push(now); this.currentRequests++; return; } // Wait for the oldest request to expire const oldestTimestamp = this.requestTimestamps[0]; const waitTime = this.timeWindow - (now - oldestTimestamp); if (waitTime > 0) { await new Promise(resolve => setTimeout(resolve, waitTime + 10)); } // Try again return this.waitForSlot(); } releaseSlot() { this.currentRequests--; } /** * Get current rate limit status */ getStatus() { const now = Date.now(); this.requestTimestamps = this.requestTimestamps.filter(ts => now - ts < this.timeWindow); return { current: this.requestTimestamps.length, max: this.maxRequests, available: this.maxRequests - this.requestTimestamps.length }; } } /** * Retry function with exponential backoff */ export async function retry(fn, options = {}) { const { maxAttempts = 3, initialDelay = 1000, maxDelay = 10000, backoffMultiplier = 2, onRetry } = options; let lastError; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt === maxAttempts) { throw lastError; } const delay = Math.min(initialDelay * Math.pow(backoffMultiplier, attempt - 1), maxDelay); if (onRetry) { onRetry(lastError, attempt); } console.error(`⚠️ Retry attempt ${attempt}/${maxAttempts} after ${delay}ms: ${lastError.message}`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } /** * Timeout wrapper for promises */ export async function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') { let timeoutHandle; const timeoutPromise = new Promise((_, reject) => { timeoutHandle = setTimeout(() => { reject(new Error(errorMessage)); }, timeoutMs); }); try { return await Promise.race([promise, timeoutPromise]); } finally { clearTimeout(timeoutHandle); } }