Skip to content

Improve error handling and resilience #15

@bburda

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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions