UNPKG

qbo-mcp-ts

Version:

TypeScript QuickBooks Online MCP Server with enhanced features and dual transport support

207 lines 6.54 kB
"use strict"; /** * Queue service for managing API rate limits and concurrent requests */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.queueService = exports.QueueService = void 0; const p_queue_1 = __importDefault(require("p-queue")); const config_1 = require("../utils/config"); const logger_1 = require("../utils/logger"); /** * Queue service implementation using p-queue */ class QueueService { queue; rateLimitPerMinute; constructor() { const apiConfig = config_1.config.getAPIConfig(); this.rateLimitPerMinute = apiConfig.rateLimitPerMinute; // Create queue with rate limiting this.queue = new p_queue_1.default({ concurrency: 5, // Max concurrent requests interval: 60000, // 1 minute intervalCap: this.rateLimitPerMinute, // Requests per minute timeout: apiConfig.timeout, throwOnTimeout: true, }); // Set up event listeners this.setupEventListeners(); } /** * Set up queue event listeners for monitoring */ setupEventListeners() { this.queue.on('active', () => { logger_1.logger.debug(`Queue: Task started. Size: ${this.queue.size}, Pending: ${this.queue.pending}`); }); this.queue.on('idle', () => { logger_1.logger.debug('Queue: All tasks completed'); }); this.queue.on('add', () => { logger_1.logger.debug(`Queue: Task added. Size: ${this.queue.size}`); }); this.queue.on('next', () => { logger_1.logger.debug(`Queue: Processing next task. Remaining: ${this.queue.size}`); }); } /** * Add a task to the queue */ async add(task, priority = 0) { return this.queue.add(task, { priority }); } /** * Pause the queue */ pause() { this.queue.pause(); logger_1.logger.info('Queue paused'); } /** * Resume the queue */ resume() { this.queue.start(); logger_1.logger.info('Queue resumed'); } /** * Clear all pending tasks */ clear() { this.queue.clear(); logger_1.logger.info('Queue cleared'); } /** * Get queue size (waiting tasks) */ size() { return this.queue.size; } /** * Get number of pending tasks (running + waiting) */ pending() { return this.queue.pending; } /** * Check if queue is paused */ isPaused() { return this.queue.isPaused; } /** * Check if queue is idle */ isIdle() { return this.queue.size === 0 && this.queue.pending === 0; } /** * Wait for all tasks to complete */ async onIdle() { await this.queue.onIdle(); } /** * Wait for queue to be empty (no waiting tasks) */ async onEmpty() { await this.queue.onEmpty(); } /** * Get queue statistics */ getStats() { return { size: this.queue.size, pending: this.queue.pending, isPaused: this.queue.isPaused, concurrency: this.queue.concurrency, rateLimitPerMinute: this.rateLimitPerMinute, }; } /** * Add multiple tasks as a batch */ async addBatch(tasks, priority = 0) { const promises = tasks.map((task) => this.add(task, priority)); return Promise.all(promises); } /** * Execute a task with retry logic */ async addWithRetry(task, options = {}) { const { retries = 3, retryDelay = 1000, priority = 0, onRetry } = options; return this.add(async () => { let lastError = null; for (let attempt = 1; attempt <= retries; attempt++) { try { return await task(); } catch (error) { lastError = error; if (attempt < retries) { if (onRetry) { onRetry(attempt, lastError); } logger_1.logger.warn(`Task retry attempt ${attempt}/${retries}`, { error: lastError.message, }); // Exponential backoff const delay = retryDelay * Math.pow(2, attempt - 1); await new Promise((resolve) => setTimeout(resolve, delay)); } } } throw lastError; }, priority); } /** * Execute tasks with rate limiting per time window */ createRateLimitedExecutor(windowMs, maxRequests) { const requestTimes = []; return async (task) => { return this.add(async () => { const now = Date.now(); // Remove old requests outside the window while (requestTimes.length > 0 && requestTimes[0] < now - windowMs) { requestTimes.shift(); } // Check if we've exceeded the limit if (requestTimes.length >= maxRequests) { const oldestRequest = requestTimes[0]; const waitTime = windowMs - (now - oldestRequest); if (waitTime > 0) { logger_1.logger.debug(`Rate limit: waiting ${waitTime}ms`); await new Promise((resolve) => setTimeout(resolve, waitTime)); } } // Record this request requestTimes.push(Date.now()); // Execute the task return task(); }); }; } /** * Shutdown the queue gracefully */ async shutdown() { logger_1.logger.info('Shutting down queue service'); // Stop accepting new tasks this.pause(); // Wait for current tasks to complete if (this.pending() > 0) { logger_1.logger.info(`Waiting for ${this.pending()} tasks to complete`); await this.onIdle(); } logger_1.logger.info('Queue service shutdown complete'); } } exports.QueueService = QueueService; // Export singleton instance exports.queueService = new QueueService(); //# sourceMappingURL=queue.js.map