mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:51:09 -05:00
207 lines
6.3 KiB
JavaScript
207 lines
6.3 KiB
JavaScript
/**
|
||
* 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 = `
|
||
<div class="notification-content">
|
||
<span class="error-icon">⚠️</span>
|
||
<span class="error-message">${error.message}</span>
|
||
<button class="close-btn">×</button>
|
||
</div>
|
||
${error.details.retry ? '<button class="retry-btn">Retry</button>' : ''}
|
||
`;
|
||
|
||
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 = `
|
||
<div class="loading-spinner"></div>
|
||
<span class="loading-text">Processing...</span>
|
||
`;
|
||
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);
|
||
}
|
||
}); |