qbo-mcp-ts
Version:
TypeScript QuickBooks Online MCP Server with enhanced features and dual transport support
207 lines • 6.54 kB
JavaScript
;
/**
* 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