/** * Global sync progress tracker * Allows tracking progress of long-running sync operations * Can be polled from the UI to show real-time progress */ export interface SyncProgressState { syncId: string; entityType: string; status: 'idle' | 'running' | 'completed' | 'failed'; currentPage: number; totalRecords: number; estimatedTotal?: number; startTime: number; endTime?: number; error?: string; phase: 'fetching' | 'mapping' | 'upserting' | 'deleting' | 'completed'; } class SyncProgressTracker { private progressMap: Map = new Map(); /** * Start tracking a new sync operation */ startSync(syncId: string, entityType: string): void { this.progressMap.set(syncId, { syncId, entityType, status: 'running', currentPage: 0, totalRecords: 0, startTime: Date.now(), phase: 'fetching', }); } /** * Update progress for a sync operation */ updateProgress( syncId: string, updates: Partial> ): void { const current = this.progressMap.get(syncId); if (!current) return; this.progressMap.set(syncId, { ...current, ...updates, }); } /** * Mark sync as completed */ completeSync(syncId: string, totalRecords: number): void { const current = this.progressMap.get(syncId); if (!current) return; this.progressMap.set(syncId, { ...current, status: 'completed', totalRecords, endTime: Date.now(), phase: 'completed', }); } /** * Mark sync as failed */ failSync(syncId: string, error: string): void { const current = this.progressMap.get(syncId); if (!current) return; this.progressMap.set(syncId, { ...current, status: 'failed', endTime: Date.now(), error, }); } /** * Get progress for a specific sync */ getProgress(syncId: string): SyncProgressState | null { return this.progressMap.get(syncId) || null; } /** * Get all active syncs */ getActiveSyncs(): SyncProgressState[] { return Array.from(this.progressMap.values()).filter( (p) => p.status === 'running' ); } /** * Get the most recent sync for an entity type */ getLatestSync(entityType: string): SyncProgressState | null { const syncs = Array.from(this.progressMap.values()) .filter((p) => p.entityType === entityType) .sort((a, b) => b.startTime - a.startTime); return syncs[0] || null; } /** * Clean up old completed/failed syncs (keep last 10 per entity) */ cleanup(): void { const byEntity = new Map(); // Group by entity type for (const progress of this.progressMap.values()) { if (!byEntity.has(progress.entityType)) { byEntity.set(progress.entityType, []); } byEntity.get(progress.entityType)!.push(progress); } // Keep only the 10 most recent per entity for (const [entityType, syncs] of byEntity.entries()) { const sorted = syncs.sort((a, b) => b.startTime - a.startTime); const toKeep = sorted.slice(0, 10); const toRemove = sorted.slice(10); for (const sync of toRemove) { this.progressMap.delete(sync.syncId); } } } } // Global singleton instance export const syncProgressTracker = new SyncProgressTracker();