@spaik/mcp-server-roi
Version:
MCP server for AI ROI prediction and tracking with Monte Carlo simulations
207 lines • 6.28 kB
JavaScript
import { createLogger } from './logger.js';
import { DatabaseError } from './errors.js';
const logger = createLogger({ component: 'BatchProcessor' });
/**
* Generic batch processor for database operations
*/
export class BatchProcessor {
queue = [];
processing = false;
flushTimer;
config;
processFn;
constructor(processFn, config = {}) {
this.processFn = processFn;
this.config = {
maxBatchSize: config.maxBatchSize || 100,
flushIntervalMs: config.flushIntervalMs || 1000,
maxRetries: config.maxRetries || 3,
concurrency: config.concurrency || 5
};
logger.info('Batch processor initialized', this.config);
}
/**
* Add item to batch queue
*/
async add(data) {
return new Promise((resolve, reject) => {
const item = {
id: this.generateId(),
data,
retries: 0
};
// Store resolve/reject callbacks
this.callbacks.set(item.id, { resolve, reject });
// Add to queue
this.queue.push(item);
logger.debug('Item added to batch', {
id: item.id,
queueSize: this.queue.length
});
// Check if we should flush
if (this.queue.length >= this.config.maxBatchSize) {
this.flush();
}
else {
this.scheduleFlush();
}
});
}
/**
* Add multiple items to batch queue
*/
async addMany(items) {
const promises = items.map(item => this.add(item));
return Promise.all(promises);
}
/**
* Process all pending items immediately
*/
async flush() {
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = undefined;
}
if (this.processing || this.queue.length === 0) {
return;
}
this.processing = true;
try {
// Process in chunks based on concurrency
const chunks = this.createChunks(this.queue, this.config.maxBatchSize);
for (const chunk of chunks) {
await this.processChunk(chunk);
}
}
finally {
this.processing = false;
}
}
/**
* Process a chunk of items
*/
async processChunk(chunk) {
logger.debug('Processing batch chunk', { size: chunk.length });
try {
const data = chunk.map(item => item.data);
const results = await this.processFn(data);
// Match results to items
chunk.forEach((item, index) => {
const callback = this.callbacks.get(item.id);
if (callback && results[index] !== undefined) {
callback.resolve(results[index]);
this.callbacks.delete(item.id);
}
});
// Remove processed items from queue
this.queue = this.queue.filter(item => !chunk.some(c => c.id === item.id));
}
catch (error) {
logger.error('Batch processing failed', error);
// Handle retries
for (const item of chunk) {
item.retries++;
if (item.retries >= this.config.maxRetries) {
const callback = this.callbacks.get(item.id);
if (callback) {
callback.reject(new DatabaseError('Batch operation failed after max retries', { retries: item.retries, error: error.message }));
this.callbacks.delete(item.id);
}
// Remove from queue
this.queue = this.queue.filter(q => q.id !== item.id);
}
}
}
}
/**
* Schedule a flush operation
*/
scheduleFlush() {
if (this.flushTimer) {
return;
}
this.flushTimer = setTimeout(() => {
this.flushTimer = undefined;
this.flush();
}, this.config.flushIntervalMs);
}
/**
* Create chunks from array
*/
createChunks(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
/**
* Generate unique ID
*/
generateId() {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Callbacks storage
*/
callbacks = new Map();
/**
* Get queue statistics
*/
getStats() {
return {
queueSize: this.queue.length,
processing: this.processing,
pendingCallbacks: this.callbacks.size
};
}
/**
* Clear the queue
*/
clear() {
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = undefined;
}
// Reject all pending callbacks
this.callbacks.forEach(callback => {
callback.reject(new Error('Batch processor cleared'));
});
this.queue = [];
this.callbacks.clear();
this.processing = false;
logger.info('Batch processor cleared');
}
}
/**
* Create batch processors for common database operations
*/
export const batchProcessors = {
/**
* Batch insert processor
*/
createInsertProcessor(tableName, insertFn) {
return new BatchProcessor(async (items) => {
logger.debug(`Batch inserting ${items.length} items into ${tableName}`);
return insertFn(items);
}, {
maxBatchSize: 100,
flushIntervalMs: 500,
maxRetries: 3
});
},
/**
* Batch update processor
*/
createUpdateProcessor(tableName, updateFn) {
return new BatchProcessor(async (items) => {
logger.debug(`Batch updating ${items.length} items in ${tableName}`);
return updateFn(items);
}, {
maxBatchSize: 50,
flushIntervalMs: 1000,
maxRetries: 3
});
}
};
//# sourceMappingURL=batch-processor.js.map