Overview

Prev Next

Overview

This document describes the error handling architecture used across ONECMS.

Error handling is intentionally layered, explicit, and boring.
Each layer has a single responsibility and a clear boundary.

Design Goal

The error system exists to enforce one invariant:

Errors must be predictable, normalized, safe to expose, and handled at the correct layer.

No layer attempts to do more than its job.

The Error Flow (High Level)

Image

Each step exists for a reason.
Skipping or collapsing layers leads to leakage and ambiguity.

Layer Responsibilities

1. Execution Boundaries — AsyncHandler

Responsibility:
Normalize anything throwable into a deterministic error shape.

  • Explicitly wraps execution
  • Converts unknownAppError
  • Preserves existing AppError instances
  • Framework-agnostic

AsyncHandler is:

  • opt-in
  • not middleware
  • not global

It establishes a failure boundary, nothing more.

2. UI Async Boundaries — GenericTryCatch

Responsibility:
Provide structured try/catch/finally for UI-initiated async operations.

  • Wraps a run function with setLoading, onError, and onFinally hooks
  • Returns a fallback value on error when provided
  • Re-throws normalized errors when no fallback is given
  • Complements AsyncHandler — used at the UI layer rather than service boundaries

GenericTryCatch is:

  • opt-in
  • stateless (single static method)
  • independent of AppError (normalizes unknownError)

3. Error Primitive — AppError

Responsibility:
Represent errors in a stable, externally safe form.

An AppError always contains:

  • a stable error code
  • a safe message
  • transport semantics (status)
  • optional structured metadata
  • an internal cause (never exposed)

AppError is:

  • portable
  • serializable
  • framework-independent

4. Error Policy — ErrorMap

Responsibility:
Define the authoritative registry of error semantics.

ErrorMap:

  • maps error codes → status + message
  • contains no logic
  • contains no helpers
  • defines policy, not behavior

Error codes are contracts.
They are stable and never reused.

5. Transport Error Adapters

Responsibility:
Adapt AppError to protocol-specific responses.

Examples:

  • HTTP API error responses
  • GraphQL error mapping
  • background job failure reporting

Transport adapters:

  • read AppError.status
  • serialize via toJSON()
  • must not infer or mutate error meaning

This is the only place where transport semantics apply.

6. UI Error Boundary

Responsibility:
Contain unhandled failures and protect the user experience.

The UI Error Boundary:

  • runs only in the client
  • presents a safe fallback UI
  • offers recovery actions
  • avoids branching on internal error codes

It does not:

  • fix errors
  • retry logic
  • perform domain decisions

It contains failure — nothing else.

What Error Handling Is Not

Error handling in ONECMS does not:

  • rely on global interception
  • mix business logic with presentation
  • allow unknown to leak
  • branch UI on internal codes by default
  • embed retry or orchestration logic in errors

If you see any of the above, the boundary is wrong.

Common Anti-Patterns to Avoid

  • ❌ Throwing raw Error across layers
  • ❌ Handling unknown outside AppError.from
  • ❌ Using UI error boundaries for control flow
  • ❌ Encoding business rules in ErrorMap
  • ❌ Treating AsyncHandler as middleware

Each of these breaks a layer contract.

Why This Architecture Works

Because it enforces:

Explicit boundaries
No hidden interception or magic.

Deterministic behavior
Same error → same shape → same handling.

Safe exposure
Internal causes never leak.

Composable layers
Execution, transport, and UI remain independent.

Most importantly:

Failures become boring.

Mental Model Summary

Layer Responsibility
AsyncHandler Normalize execution failures
GenericTryCatch Structured UI async boundaries
AppError Define error shape
ErrorMap Define error semantics
Transport Adapters Protocol mapping
UI Error Boundary User-facing containment

If each layer stays in its lane, error handling stays predictable.

Final Note

Error handling is not about preventing failure.
It is about containing failure correctly.

When errors feel boring, understandable, and consistent —
the system is working as designed.