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)

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
unknown→AppError - Preserves existing
AppErrorinstances - 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
runfunction withsetLoading,onError, andonFinallyhooks - 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(normalizesunknown→Error)
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
unknownto 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
Erroracross layers - ❌ Handling
unknownoutsideAppError.from - ❌ Using UI error boundaries for control flow
- ❌ Encoding business rules in
ErrorMap - ❌ Treating
AsyncHandleras 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.