/** * Error handling and state management for version control system */ class VersionControlError extends Error { constructor(message, code, details = {}) { super(message); this.name = 'VersionControlError'; this.code = code; this.details = details; this.timestamp = new Date(); } } // Error boundary for version control operations class VersionControlErrorBoundary { constructor(options = {}) { this.onError = options.onError || this.defaultErrorHandler; this.errors = new Map(); this.retryAttempts = new Map(); this.maxRetries = options.maxRetries || 3; } defaultErrorHandler(error) { console.error(`[Version Control Error]: ${error.message}`, error); this.showErrorNotification(error); } showErrorNotification(error) { const notification = document.createElement('div'); notification.className = 'version-control-error notification'; notification.innerHTML = `
⚠️ ${error.message}
${error.details.retry ? '' : ''} `; document.body.appendChild(notification); // Auto-hide after 5 seconds unless it's a critical error if (!error.details.critical) { setTimeout(() => { notification.remove(); }, 5000); } // Handle retry const retryBtn = notification.querySelector('.retry-btn'); if (retryBtn && error.details.retryCallback) { retryBtn.addEventListener('click', () => { notification.remove(); error.details.retryCallback(); }); } // Handle close const closeBtn = notification.querySelector('.close-btn'); closeBtn.addEventListener('click', () => notification.remove()); } async wrapOperation(operationKey, operation) { try { // Check if operation is already in progress if (this.errors.has(operationKey)) { throw new VersionControlError( 'Operation already in progress', 'DUPLICATE_OPERATION' ); } // Show loading state this.showLoading(operationKey); const result = await operation(); // Clear any existing errors for this operation this.errors.delete(operationKey); this.retryAttempts.delete(operationKey); return result; } catch (error) { const retryCount = this.retryAttempts.get(operationKey) || 0; // Handle specific error types if (error.name === 'VersionControlError') { this.handleVersionControlError(error, operationKey, retryCount); } else { // Convert unknown errors to VersionControlError const vcError = new VersionControlError( 'An unexpected error occurred', 'UNKNOWN_ERROR', { originalError: error } ); this.handleVersionControlError(vcError, operationKey, retryCount); } throw error; } finally { this.hideLoading(operationKey); } } handleVersionControlError(error, operationKey, retryCount) { this.errors.set(operationKey, error); // Determine if operation can be retried const canRetry = retryCount < this.maxRetries; error.details.retry = canRetry; error.details.retryCallback = canRetry ? () => this.retryOperation(operationKey) : undefined; this.onError(error); } async retryOperation(operationKey) { const retryCount = (this.retryAttempts.get(operationKey) || 0) + 1; this.retryAttempts.set(operationKey, retryCount); // Exponential backoff for retries const backoffDelay = Math.min(1000 * Math.pow(2, retryCount - 1), 10000); await new Promise(resolve => setTimeout(resolve, backoffDelay)); // Get the original operation and retry const operation = this.pendingOperations.get(operationKey); if (operation) { return this.wrapOperation(operationKey, operation); } } showLoading(operationKey) { const loadingElement = document.createElement('div'); loadingElement.className = `loading-indicator loading-${operationKey}`; loadingElement.innerHTML = `
Processing... `; document.body.appendChild(loadingElement); } hideLoading(operationKey) { const loadingElement = document.querySelector(`.loading-${operationKey}`); if (loadingElement) { loadingElement.remove(); } } } // Create singleton instance const errorBoundary = new VersionControlErrorBoundary({ onError: (error) => { // Log to monitoring system if (window.monitoring) { window.monitoring.logError('version_control', error); } } }); // Export error handling utilities export const versionControl = { /** * Wrap version control operations with error handling */ async performOperation(key, operation) { return errorBoundary.wrapOperation(key, operation); }, /** * Create a new error instance */ createError(message, code, details) { return new VersionControlError(message, code, details); }, /** * Show loading state manually */ showLoading(key) { errorBoundary.showLoading(key); }, /** * Hide loading state manually */ hideLoading(key) { errorBoundary.hideLoading(key); }, /** * Show error notification manually */ showError(error) { errorBoundary.showErrorNotification(error); } }; // Add global error handler for uncaught version control errors window.addEventListener('unhandledrejection', event => { if (event.reason instanceof VersionControlError) { event.preventDefault(); errorBoundary.defaultErrorHandler(event.reason); } });