700 lines
15 KiB
Markdown

# Errors Store
The errors store provides comprehensive error handling and management for the entire application. It centralizes error tracking, automatic retry logic, and integration with the notification system to provide users with consistent error feedback.
## Overview
- **Location**: `src/stores/errors.js`
- **Type**: Pinia Store
- **Purpose**: Centralized error state management and handling
- **Integration**: Works with notification store for user feedback
## Installation & Setup
```javascript
// Import in your component
import { useErrorStore } from "@/stores/errors";
// Use in component
const errorStore = useErrorStore();
```
## State Structure
### Core State Properties
```javascript
state: {
hasError: false, // Global error flag
lastError: null, // Most recent global error
apiErrors: new Map(), // API-specific errors by key
componentErrors: { // Component-specific errors
dataTable: null,
form: null,
clients: null,
jobs: null,
timesheets: null,
warranties: null,
routes: null
},
errorHistory: [], // Historical error log
maxHistorySize: 50, // Maximum history entries
autoNotifyErrors: true // Auto-show notifications for errors
}
```
### Error Object Structure
```javascript
{
message: 'Error description', // Human-readable error message
type: 'api_error', // Error classification
timestamp: '2025-11-12T10:30:00Z', // When error occurred
name: 'ValidationError', // Error name (if available)
stack: 'Error stack trace...', // Stack trace (if available)
status: 404, // HTTP status (for API errors)
statusText: 'Not Found', // HTTP status text
data: {...} // Additional error data
}
```
## Getters
### `hasAnyError`
Check if there are any errors in the application.
```javascript
const hasErrors = errorStore.hasAnyError;
```
### `getComponentError(componentName)`
Get error for a specific component.
```javascript
const formError = errorStore.getComponentError("form");
```
### `getApiError(apiKey)`
Get error for a specific API operation.
```javascript
const loginError = errorStore.getApiError("user-login");
```
### `getRecentErrors(limit)`
Get recent errors from history.
```javascript
const recentErrors = errorStore.getRecentErrors(10);
```
## Actions
### Global Error Management
#### `setGlobalError(error, showNotification)`
Set a global application error.
```javascript
errorStore.setGlobalError(
new Error("Critical system failure"),
true, // Show notification
);
// With custom error object
errorStore.setGlobalError({
message: "Database connection lost",
type: "connection_error",
recoverable: true,
});
```
#### `clearGlobalError()`
Clear the global error state.
```javascript
errorStore.clearGlobalError();
```
### Component-Specific Error Management
#### `setComponentError(componentName, error, showNotification)`
Set an error for a specific component.
```javascript
// Set error for form component
errorStore.setComponentError("form", new Error("Validation failed"), true);
// Clear error (pass null)
errorStore.setComponentError("form", null);
```
#### `clearComponentError(componentName)`
Clear error for a specific component.
```javascript
errorStore.clearComponentError("form");
```
### API Error Management
#### `setApiError(apiKey, error, showNotification)`
Set an error for a specific API operation.
```javascript
// Set API error
errorStore.setApiError("user-login", apiError, true);
// Clear API error (pass null)
errorStore.setApiError("user-login", null);
```
#### `clearApiError(apiKey)`
Clear error for a specific API operation.
```javascript
errorStore.clearApiError("user-login");
```
### Bulk Operations
#### `clearAllErrors()`
Clear all errors from the store.
```javascript
errorStore.clearAllErrors();
```
### Advanced Error Handling
#### `handleApiCall(apiKey, apiFunction, options)`
Handle an API call with automatic error management and retry logic.
```javascript
const result = await errorStore.handleApiCall(
"fetch-users",
async () => {
return await api.getUsers();
},
{
showNotification: true,
retryCount: 2,
retryDelay: 1000,
onSuccess: (result) => console.log("Success:", result),
onError: (error) => console.log("Failed:", error),
},
);
```
#### `withErrorHandling(operationKey, asyncOperation, options)`
Wrap an async operation with comprehensive error handling.
```javascript
const result = await errorStore.withErrorHandling(
"save-data",
async () => {
return await saveUserData();
},
{
componentName: "userForm",
showNotification: true,
rethrow: false, // Don't re-throw errors
},
);
```
## Error Types
The store automatically categorizes errors into different types:
### `string_error`
Simple string errors.
```javascript
errorStore.setGlobalError("Something went wrong");
```
### `javascript_error`
Standard JavaScript Error objects.
```javascript
errorStore.setGlobalError(new Error("Validation failed"));
```
### `api_error`
HTTP/API response errors with status codes.
```javascript
// Automatically detected from axios-style error objects
{
message: 'Not Found',
status: 404,
statusText: 'Not Found',
type: 'api_error',
data: {...}
}
```
### `network_error`
Network connectivity errors.
```javascript
{
message: 'Network error - please check your connection',
type: 'network_error'
}
```
### `unknown_error`
Unrecognized error formats.
```javascript
{
message: 'An unknown error occurred',
type: 'unknown_error',
originalError: {...}
}
```
## Configuration Methods
### `setAutoNotifyErrors(enabled)`
Enable/disable automatic error notifications.
```javascript
errorStore.setAutoNotifyErrors(false); // Disable auto-notifications
```
### `setMaxHistorySize(size)`
Set maximum number of errors to keep in history.
```javascript
errorStore.setMaxHistorySize(100);
```
## Usage Patterns
### Basic Error Handling
```javascript
const errorStore = useErrorStore();
// Simple error setting
try {
await riskyOperation();
} catch (error) {
errorStore.setGlobalError(error);
}
// Component-specific error
try {
await validateForm();
} catch (validationError) {
errorStore.setComponentError("form", validationError);
}
```
### API Error Handling with Retry
```javascript
// Automatic retry logic
const userData = await errorStore.handleApiCall(
"fetch-user-data",
async () => {
const response = await fetch("/api/users");
if (!response.ok) throw new Error("Failed to fetch users");
return response.json();
},
{
retryCount: 3,
retryDelay: 1000,
showNotification: true,
},
);
```
### Comprehensive Operation Wrapping
```javascript
// Wrap complex operations
const result = await errorStore.withErrorHandling(
"complex-workflow",
async () => {
// Step 1: Validate data
await validateInputData();
// Step 2: Save to database
const saveResult = await saveToDatabase();
// Step 3: Send notification email
await sendNotificationEmail();
return saveResult;
},
{
componentName: "workflow",
showNotification: true,
rethrow: false,
},
);
if (result) {
// Success - result contains the return value
console.log("Workflow completed:", result);
} else {
// Error occurred - check component error for details
const workflowError = errorStore.getComponentError("workflow");
console.log("Workflow failed:", workflowError?.message);
}
```
### Integration with Vue Components
```javascript
// In a Vue component
import { useErrorStore } from "@/stores/errors";
import { useNotificationStore } from "@/stores/notifications";
export default {
setup() {
const errorStore = useErrorStore();
const notificationStore = useNotificationStore();
const submitForm = async (formData) => {
// Clear any previous errors
errorStore.clearComponentError("form");
try {
await errorStore.withErrorHandling(
"form-submit",
async () => {
const result = await api.submitForm(formData);
notificationStore.addSuccess("Form submitted successfully!");
return result;
},
{
componentName: "form",
showNotification: true,
},
);
// Reset form on success
resetForm();
} catch (error) {
// Error handling is automatic, but you can add custom logic here
console.log("Form submission failed");
}
};
return {
submitForm,
formError: computed(() => errorStore.getComponentError("form")),
};
},
};
```
## Integration with Enhanced API
The errors store works seamlessly with the enhanced API wrapper:
```javascript
import { ApiWithErrorHandling } from "@/api-enhanced";
// The enhanced API automatically uses the error store
try {
const clients = await ApiWithErrorHandling.getPaginatedClientDetails(
pagination,
filters,
[],
{
componentName: "clients",
retryCount: 2,
showErrorNotifications: true,
},
);
} catch (error) {
// Error is automatically handled by the error store
// Check component error for details
const clientError = errorStore.getComponentError("clients");
}
```
## Error Recovery Patterns
### Graceful Degradation
```javascript
const loadCriticalData = async () => {
let primaryData = null;
let fallbackData = null;
// Try primary data source
try {
primaryData = await errorStore.withErrorHandling(
"primary-data",
() => api.getPrimaryData(),
{ showNotification: false, rethrow: true },
);
} catch (error) {
console.log("Primary data failed, trying fallback...");
// Try fallback data source
try {
fallbackData = await errorStore.withErrorHandling(
"fallback-data",
() => api.getFallbackData(),
{ showNotification: false, rethrow: true },
);
notificationStore.addWarning(
"Using cached data due to connectivity issues",
);
} catch (fallbackError) {
errorStore.setGlobalError("Unable to load data from any source");
return null;
}
}
return primaryData || fallbackData;
};
```
### User-Driven Error Recovery
```javascript
const handleApiError = async (operation) => {
try {
return await operation();
} catch (error) {
// Show error with recovery options
notificationStore.addNotification({
type: "error",
title: "Operation Failed",
message: "Would you like to try again or continue with cached data?",
persistent: true,
actions: [
{
label: "Retry",
variant: "primary",
handler: () => handleApiError(operation), // Recursive retry
},
{
label: "Use Cached Data",
variant: "secondary",
handler: () => loadCachedData(),
},
{
label: "Cancel",
variant: "secondary",
},
],
});
throw error; // Let caller handle as needed
}
};
```
## Debugging & Monitoring
### Error History Access
```javascript
// Get all error history for debugging
const allErrors = errorStore.errorHistory;
// Get recent errors with details
const recent = errorStore.getRecentErrors(5);
recent.forEach((error) => {
console.log(`[${error.timestamp}] ${error.source}: ${error.message}`);
});
// Filter errors by type
const apiErrors = errorStore.errorHistory.filter((e) => e.type === "api_error");
```
### Error Reporting
```javascript
// Send error reports to monitoring service
const reportErrors = () => {
const recentErrors = errorStore.getRecentErrors(10);
recentErrors.forEach((error) => {
if (error.type === "api_error" && error.status >= 500) {
// Report server errors
analyticsService.reportError({
message: error.message,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: error.timestamp,
stack: error.stack,
});
}
});
};
```
## Testing
### Unit Testing
```javascript
import { setActivePinia, createPinia } from "pinia";
import { useErrorStore } from "@/stores/errors";
describe("Errors Store", () => {
beforeEach(() => {
setActivePinia(createPinia());
});
it("sets and clears global errors", () => {
const store = useErrorStore();
store.setGlobalError("Test error");
expect(store.hasError).toBe(true);
expect(store.lastError.message).toBe("Test error");
store.clearGlobalError();
expect(store.hasError).toBe(false);
});
it("handles component errors", () => {
const store = useErrorStore();
store.setComponentError("form", new Error("Validation failed"));
expect(store.getComponentError("form").message).toBe("Validation failed");
store.clearComponentError("form");
expect(store.getComponentError("form")).toBeNull();
});
it("tracks error history", () => {
const store = useErrorStore();
store.setGlobalError("Error 1");
store.setGlobalError("Error 2");
expect(store.errorHistory).toHaveLength(2);
expect(store.getRecentErrors(1)[0].message).toBe("Error 2");
});
});
```
### Integration Testing
```javascript
// Test error handling with API calls
it("handles API errors correctly", async () => {
const store = useErrorStore();
const mockApi = jest.fn().mockRejectedValue(new Error("API Error"));
const result = await store.withErrorHandling("test-api", mockApi, {
componentName: "test",
showNotification: false,
rethrow: false,
});
expect(result).toBeNull();
expect(store.getComponentError("test").message).toBe("API Error");
});
```
## Best Practices
### Do's
- ✅ Use component-specific errors for UI validation
- ✅ Use API-specific errors for network operations
- ✅ Enable auto-notifications for user-facing errors
- ✅ Use retry logic for transient failures
- ✅ Clear errors when operations succeed
- ✅ Keep error messages user-friendly and actionable
### Don'ts
- ❌ Don't set global errors for minor validation issues
- ❌ Don't ignore error context (component/API source)
- ❌ Don't let error history grow indefinitely
- ❌ Don't show technical stack traces to end users
- ❌ Don't retry operations that will consistently fail
### Performance Considerations
- Error history is automatically trimmed to prevent memory leaks
- Use component-specific errors to isolate issues
- Clear errors promptly when no longer relevant
- Consider disabling auto-notifications for high-frequency operations
## Common Patterns
### Form Validation Errors
```javascript
const validateAndSubmit = async (formData) => {
errorStore.clearComponentError("form");
try {
await errorStore.withErrorHandling(
"form-validation",
async () => {
validateFormData(formData);
await submitForm(formData);
},
{
componentName: "form",
showNotification: true,
},
);
} catch (error) {
// Validation errors are now stored in component error
// and automatically displayed to user
}
};
```
### Background Task Monitoring
```javascript
const monitorBackgroundTask = async (taskId) => {
await errorStore.handleApiCall(
`task-${taskId}`,
async () => {
const status = await api.getTaskStatus(taskId);
if (status.failed) {
throw new Error(`Task failed: ${status.error}`);
}
return status;
},
{
retryCount: 5,
retryDelay: 2000,
showNotification: status.failed, // Only notify on final failure
},
);
};
```