@simonecoelhosfo/optimizely-mcp-server
Version:
Optimizely MCP Server for AI assistants with integrated CLI tools
172 lines • 6.32 kB
JavaScript
/**
* 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