700 lines
15 KiB
Markdown
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
|
|
},
|
|
);
|
|
};
|
|
```
|