# Error Handling Reference

This guide covers error handling patterns and recovery strategies for WS-Dottie applications.

> **📚 Documentation Navigation**: [Documentation Index](../../INDEX.md) • [Getting Started](../../getting-started.md) • [API Guide](../api-guide.md)

## 🚨 Error Types

### ApiError

WS-Dottie uses a single error type for all API-related issues. All errors (network errors, validation errors, API errors) are wrapped in an `ApiError` object:

```typescript
interface ApiError {
  /** Error name for type identification */
  readonly name: "ApiError";
  /** Human-readable error message (preserves original error) */
  readonly message: string;
  /** HTTP status code if available */
  readonly status?: number;
  /** Additional error context */
  readonly context: {
    /** API endpoint that was called */
    endpoint: string;
    /** Full request URL */
    url: string;
    /** Timestamp when the error occurred */
    timestamp: Date;
    /** Request identifier for tracking (optional) */
    requestId?: string;
  };
}
```

**Important**: All errors in WS-Dottie are wrapped as `ApiError`, including:
- Network errors (connection timeouts, DNS failures, etc.)
- Validation errors (Zod schema validation failures)
- API errors (HTTP status errors, authentication failures, etc.)

The original error message is preserved in the `message` field, and the `context` object provides additional information about where and when the error occurred.

## 🔄 Error Handling Patterns

### Try-Catch Pattern

```javascript
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';
import { isApiError } from 'ws-dottie';

async function fetchWithErrorHandling() {
  try {
    const data = await fetchVesselLocations({
      fetchMode: 'native',
      validate: true
    });
    
    return data;
  } catch (error) {
    if (isApiError(error)) {
      console.error('API Error:', error.message);
      console.error('Status:', error.status);
      console.error('Context:', error.context);
    } else if (error instanceof Error) {
      console.error('Unexpected Error:', error.message);
    } else {
      console.error('Unknown error', error);
    }
    
    throw error;
  }
}
```

### Error Boundaries (React)

```javascript
import { Component } from 'react';
import { isApiError } from 'ws-dottie';

class TransportationErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    console.error('Transportation Error:', error, errorInfo);
    
    if (isApiError(error)) {
      // Log API-specific context
      logErrorToService(error, errorInfo, {
        endpoint: error.context?.endpoint,
        status: error.status,
      });
    } else {
      logErrorToService(error, errorInfo);
    }
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>We're having trouble loading transportation data.</p>
          <details>
            <summary>Error Details</summary>
            <pre>{this.state.error?.message}</pre>
          </details>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try Again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <TransportationErrorBoundary>
      <TransportationDashboard />
    </TransportationErrorBoundary>
  );
}
```

### Async/Await Pattern

```javascript
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';
import { isApiError } from 'ws-dottie';

async function fetchWithAsyncAwait() {
  let vessels;
  
  try {
    vessels = await fetchVesselLocations({
      fetchMode: 'native',
      validate: true
    });
  } catch (error) {
    if (isApiError(error)) {
      console.error('Failed to fetch vessels:', error.message);
      // Handle specific API error
      if (error.status === 401) {
        // Handle authentication error
        showAuthErrorDialog();
      } else if (error.status >= 500) {
        // Handle server error
        showServerErrorDialog();
      }
    } else {
      // Handle other errors
      showGenericErrorDialog();
    }
    
    // Return empty array as fallback
    return [];
  }
  
  return vessels;
}
```

## 🛠️ Recovery Strategies

### Retry Logic

```javascript
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';
import { isApiError } from 'ws-dottie';

class RetryableFetch {
  constructor() {
    this.maxRetries = 3;
    this.baseDelay = 1000; // 1 second
    this.maxDelay = 10000; // 10 seconds
    this.backoffFactor = 2;
  }
  
  async fetchWithRetry(fetchFunction, params = {}) {
    let lastError;
    
    for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
      try {
        const data = await fetchFunction({
          ...params,
          fetchMode: 'native',
          validate: true
        });
        
        return data;
      } catch (error) {
        lastError = error;
        
        // Don't retry on authentication errors
        if (isApiError(error) && error.status === 401) {
          throw error;
        }
        
        // Don't retry on client errors (4xx)
        if (isApiError(error) && error.status >= 400 && error.status < 500) {
          throw error;
        }
        
        // If this is last attempt, throw error
        if (attempt === this.maxRetries) {
          throw error;
        }
        
        // Calculate delay for next attempt
        const delay = Math.min(
          this.baseDelay * Math.pow(this.backoffFactor, attempt - 1),
          this.maxDelay
        );
        
        console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
        
        // Wait before retrying
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    throw lastError;
  }
}

// Usage
const retryableFetch = new RetryableFetch();
const vessels = await retryableFetch.fetchWithRetry(fetchVesselLocations);
```

### Fallback Data

```javascript
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';

async function fetchWithFallback(fetchFunction, fallbackData = [], params = {}) {
  try {
    const data = await fetchFunction({
      ...params,
      fetchMode: 'native',
      validate: true
    });
    
    return data;
  } catch (error) {
    console.error('Failed to fetch data:', error.message);
    
    // Return fallback data
    return fallbackData;
  }
}

// Usage
const vessels = await fetchWithFallback(fetchVesselLocations, [
  { VesselID: 1, VesselName: 'Fallback Vessel 1' },
  { VesselID: 2, VesselName: 'Fallback Vessel 2' }
]);
```

### Graceful Degradation

```javascript
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';

class GracefulDegradation {
  constructor() {
    this.isHealthy = true;
    this.lastHealthCheck = Date.now();
  }
  
  async checkHealth() {
    try {
      // Simple health check
      await fetchVesselLocations({
        fetchMode: 'native',
        validate: true
      });
      
      this.isHealthy = true;
      this.lastHealthCheck = Date.now();
    } catch (error) {
      console.error('Health check failed:', error.message);
      this.isHealthy = false;
    }
  }
  
  async fetchWithGracefulDegradation(fetchFunction, params = {}) {
    // Check if service is healthy
    if (this.isHealthy || Date.now() - this.lastHealthCheck > 60000) { // 1 minute
      try {
        return await fetchFunction({
          ...params,
          fetchMode: 'native',
          validate: true
        });
      } catch (error) {
        console.error('API call failed:', error.message);
        throw error;
      }
    } else {
      // Service is unhealthy, return cached or fallback data
      console.warn('Service unhealthy, using fallback data');
      return this.getCachedOrFallbackData();
    }
  }
  
  getCachedOrFallbackData() {
    // Implementation depends on your caching strategy
    // This is a placeholder for actual implementation
    return [];
  }
}

// Usage
const degradation = new GracefulDegradation();

// Periodic health check
setInterval(() => degradation.checkHealth(), 30000); // Every 30 seconds

// Fetch with graceful degradation
const vessels = await degradation.fetchWithGracefulDegradation(fetchVesselLocations);
```

## 📊 Error Monitoring

### Error Logging

```javascript
import { fetchDottie, isApiError } from 'ws-dottie';

class ErrorLogger {
  constructor() {
    this.errors = [];
  }
  
  log(error, context = {}) {
    const errorEntry = {
      timestamp: new Date().toISOString(),
      message: error.message,
      stack: error.stack,
      context,
      ...(isApiError(error) && {
        status: error.status,
        endpoint: error.context?.endpoint,
        url: error.context?.url
      })
    };
    
    this.errors.push(errorEntry);
    
    // Log to console
    console.error('Transportation Error:', errorEntry);
    
    // In production, send to error tracking service
    if (process.env.NODE_ENV === 'production') {
      this.sendToErrorService(errorEntry);
    }
  }
  
  sendToErrorService(errorEntry) {
    // Implementation depends on your error tracking service
    // Example with a generic HTTP service
    fetch(process.env.ERROR_TRACKING_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(errorEntry)
    }).catch(err => {
      console.error('Failed to send error to tracking service:', err);
    });
  }
  
  getErrorSummary() {
    const errorCounts = this.errors.reduce((acc, error) => {
      const key = error.message || 'Unknown error';
      acc[key] = (acc[key] || 0) + 1;
      return acc;
    }, {});
    
    return {
      total: this.errors.length,
      byMessage: errorCounts,
      recent: this.errors.slice(-10) // Last 10 errors
    };
  }
}

// Usage with error handling wrapper
const errorLogger = new ErrorLogger();

async function withErrorHandling(fn, context = {}) {
  try {
    return await fn();
  } catch (error) {
    errorLogger.log(error, context);
    throw error;
  }
}

// Usage
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';

async function main() {
  await withErrorHandling(async () => {
    const vessels = await fetchVesselLocations({
      fetchMode: 'native',
      validate: true
    });
    
    console.log(`Found ${vessels.length} vessels`);
  }, { operation: 'fetchVessels' });
  
  // Print error summary
  console.log('Error Summary:', errorLogger.getErrorSummary());
}
```

### Error Metrics

```javascript
import { fetchVesselLocations } from 'ws-dottie/wsf-vessels/core';
import { isApiError } from 'ws-dottie';

class ErrorMetrics {
  constructor() {
    this.metrics = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      errorsByStatus: {},
      errorsByEndpoint: {},
      averageResponseTime: 0,
      lastReset: Date.now()
    };
  }
  
  recordRequest(endpointName, startTime, endTime, success, error = null) {
    this.metrics.totalRequests++;
    
    if (success) {
      this.metrics.successfulRequests++;
    } else {
      this.metrics.failedRequests++;
      
      if (isApiError(error) && error.status) {
        const status = error.status.toString();
        this.metrics.errorsByStatus[status] = (this.metrics.errorsByStatus[status] || 0) + 1;
      }
      
      if (isApiError(error) && error.context?.endpoint) {
        const endpointName = error.context.endpoint;
        this.metrics.errorsByEndpoint[endpointName] = (this.metrics.errorsByEndpoint[endpointName] || 0) + 1;
      }
    }
    
    const responseTime = endTime - startTime;
    this.metrics.averageResponseTime = (
      (this.metrics.averageResponseTime * (this.metrics.totalRequests - 1) + responseTime) / 
      this.metrics.totalRequests
    );
  }
  
  getMetrics() {
    const successRate = this.metrics.totalRequests > 0 
      ? (this.metrics.successfulRequests / this.metrics.totalRequests * 100).toFixed(2) + '%'
      : '0%';
    
    return {
      ...this.metrics,
      successRate,
      errorRate: (100 - parseFloat(successRate)).toFixed(2) + '%'
    };
  }
  
  reset() {
    this.metrics = {
      totalRequests: 0,
      successfulRequests: 0,
      failedRequests: 0,
      errorsByStatus: {},
      errorsByEndpoint: {},
      averageResponseTime: 0,
      lastReset: Date.now()
    };
  }
}

// Usage
const errorMetrics = new ErrorMetrics();

// Wrap fetch function to record metrics
async function fetchWithMetrics(fetchFunction, params = {}) {
  const startTime = Date.now();
  const endpointName = 'fetchVesselLocations'; // or extract from function name
  
  try {
    const data = await fetchFunction({
      ...params,
      fetchMode: 'native',
      validate: true
    });
    
    errorMetrics.recordRequest(endpointName, startTime, Date.now(), true);
    return data;
  } catch (error) {
    errorMetrics.recordRequest(endpointName, startTime, Date.now(), false, error);
    throw error;
  }
}

// Usage
const vessels = await fetchWithMetrics(fetchVesselLocations);
console.log('Current metrics:', errorMetrics.getMetrics());
```

## 📚 Next Steps

- **[TanStack Query Guide](./tanstack-query.md)** - TanStack Query integration and caching
- **[Fetching Data Guide](./fetching-data.md)** - Basic fetch-dottie usage patterns
- **[CLI Usage Guide](./cli-usage.md)** - Command-line interface and debugging
