Skip to content

Improve error handling and resilience #15

Description

@bburda

Summary

Improve error handling to match SOVD GenericError format and add resilience features for unreliable connections.


Proposed solution

1. SOVD GenericError Handling

Gateway returns errors in SOVD-compliant format:

{
  "error_code": "ERR_RESOURCE_NOT_FOUND",
  "message": "Component not found",
  "parameters": {
    "entity_id": "unknown_component",
    "entity_type": "component"
  }
}

Create typed error handling:

interface SovdError {
  error_code: string;
  message: string;
  parameters?: Record<string, unknown>;
}

class ApiError extends Error {
  constructor(
    public readonly status: number,
    public readonly sovdError: SovdError
  ) {
    super(sovdError.message);
  }
}

// Error code mapping for user-friendly messages
const ERROR_MESSAGES: Record<string, string> = {
  'ERR_RESOURCE_NOT_FOUND': 'The requested resource was not found',
  'ERR_INVALID_REQUEST': 'Invalid request parameters',
  'ERR_SERVICE_UNAVAILABLE': 'Service temporarily unavailable',
  'ERR_UNAUTHORIZED': 'Authentication required',
  'ERR_FORBIDDEN': 'Access denied',
  // ...
};

2. Retry Logic with Exponential Backoff

async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
    retryOn?: (error: Error) => boolean;
  } = {}
): Promise<T> {
  const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) throw error;

      const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
      await new Promise(r => setTimeout(r, delay));
    }
  }
  throw new Error('Unreachable');
}

3. Connection State Management

interface ConnectionState {
  status: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
  lastConnected: Date | null;
  reconnectAttempts: number;
  error: string | null;
}

// Auto-reconnect on connection loss
// Exponential backoff for reconnection attempts
// Visual indicator in header showing connection state

4. Offline Support (Optional PWA)

  • Cache entity tree structure for offline viewing
  • Queue write operations when offline
  • Sync when back online
  • Service worker for caching static assets

5. Error Boundaries

// App-level error boundary
function AppErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundary
      fallback={<AppCrashScreen />}
      onError={(error) => {
        // Log to monitoring service
        console.error('App crashed:', error);
      }}
    >
      {children}
    </ErrorBoundary>
  );
}

// Panel-level error boundary with retry
function PanelErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundary
      fallback={({ resetErrorBoundary }) => (
        <ErrorPanel onRetry={resetErrorBoundary} />
      )}
    >
      {children}
    </ErrorBoundary>
  );
}

Additional context

Error handling priorities:

  1. User sees clear, actionable error messages
  2. Transient errors auto-retry without user action
  3. Connection issues show reconnection status
  4. App never fully crashes - graceful degradation

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions