UNPKG

@simonecoelhosfo/optimizely-mcp-server

Version:

Optimizely MCP Server for AI assistants with integrated CLI tools

172 lines 6.32 kB
/** * Sync Scheduler for Optimizely MCP Server * @description Manages automatic background synchronization */ import { getLogger } from '../logging/Logger.js'; import { MCPErrorMapper } from '../errors/MCPErrorMapping.js'; /** * Manages automatic background synchronization */ export class SyncScheduler { cacheManager; config; intervals = new Map(); syncInProgress = false; lastSyncTime; consecutiveFailures = 0; isRunning = false; constructor(cacheManager, config) { this.cacheManager = cacheManager; this.config = config; } /** * Starts automatic synchronization */ async start() { if (this.isRunning) { getLogger().warn('SyncScheduler: Already running'); return; } this.isRunning = true; getLogger().info({ intervalMinutes: this.config.intervalMinutes, useIncremental: this.config.useIncremental }, 'SyncScheduler: Starting automatic sync'); // Perform initial sync await this.performScheduledSync(); // Schedule recurring syncs const intervalMs = this.config.intervalMinutes * 60 * 1000; const interval = setInterval(async () => { try { await this.performScheduledSync(); } catch (error) { getLogger().error({ error: error instanceof Error ? error.message : String(error) }, 'SyncScheduler: Scheduled sync failed'); } }, intervalMs); this.intervals.set('main', interval); getLogger().info({ nextSyncIn: `${this.config.intervalMinutes} minutes` }, 'SyncScheduler: Scheduled sync started'); } /** * Stops automatic synchronization */ stop() { if (!this.isRunning) { return; } // Clear all intervals for (const [key, interval] of this.intervals) { clearInterval(interval); this.intervals.delete(key); } this.isRunning = false; getLogger().info('SyncScheduler: Stopped automatic sync'); } /** * Performs a scheduled sync operation */ async performScheduledSync() { if (this.syncInProgress) { getLogger().warn('SyncScheduler: Sync already in progress, skipping'); return; } this.syncInProgress = true; const startTime = Date.now(); try { getLogger().info({ useIncremental: this.config.useIncremental, lastSyncTime: this.lastSyncTime?.toISOString() }, 'SyncScheduler: Starting scheduled sync'); let result; if (this.config.useIncremental) { result = await this.cacheManager.incrementalSync(); } else { result = await this.cacheManager.fullSync(); } const duration = Date.now() - startTime; this.lastSyncTime = new Date(); this.consecutiveFailures = 0; getLogger().info({ duration, projectsSynced: result.projectsSynced, changesProcessed: result.totalChanges || 0, success: result.success }, 'SyncScheduler: Scheduled sync completed'); // If incremental sync found no changes, log it as debug if (this.config.useIncremental && result.totalChanges === 0) { getLogger().debug('SyncScheduler: No changes detected since last sync'); } } catch (error) { this.consecutiveFailures++; const duration = Date.now() - startTime; getLogger().error({ error: error.message, duration, consecutiveFailures: this.consecutiveFailures }, 'SyncScheduler: Sync failed'); // If too many consecutive failures, stop the scheduler if (this.consecutiveFailures >= this.config.maxRetries) { getLogger().error({ consecutiveFailures: this.consecutiveFailures }, 'SyncScheduler: Too many consecutive failures, stopping scheduler'); this.stop(); throw MCPErrorMapper.toMCPError(error, 'Automatic sync stopped due to repeated failures'); } // Schedule retry with exponential backoff await this.scheduleRetry(); } finally { this.syncInProgress = false; } } /** * Schedules a retry with exponential backoff */ async scheduleRetry() { const baseDelay = 60 * 1000; // 1 minute base const delay = baseDelay * Math.pow(this.config.backoffMultiplier, this.consecutiveFailures - 1); const maxDelay = this.config.intervalMinutes * 60 * 1000; // Don't exceed normal interval const actualDelay = Math.min(delay, maxDelay); getLogger().info({ retryIn: `${actualDelay / 1000} seconds`, attempt: this.consecutiveFailures }, 'SyncScheduler: Scheduling retry'); await new Promise(resolve => setTimeout(resolve, actualDelay)); // Try again if still running if (this.isRunning) { await this.performScheduledSync(); } } /** * Gets the current scheduler status */ getStatus() { let nextSyncIn; if (this.isRunning && this.lastSyncTime) { const nextSyncTime = new Date(this.lastSyncTime.getTime() + this.config.intervalMinutes * 60 * 1000); const minutesUntilNext = Math.floor((nextSyncTime.getTime() - Date.now()) / (60 * 1000)); nextSyncIn = `${minutesUntilNext} minutes`; } return { isRunning: this.isRunning, syncInProgress: this.syncInProgress, lastSyncTime: this.lastSyncTime, consecutiveFailures: this.consecutiveFailures, nextSyncIn }; } /** * Forces an immediate sync (outside of schedule) */ async forceSync() { getLogger().info('SyncScheduler: Force sync requested'); await this.performScheduledSync(); } } //# sourceMappingURL=SyncScheduler.js.map