AppError
AppError is the canonical error type used throughout ONECMS.
It represents a normalized, externally safe error contract that is independent of framework, transport, or UI concerns.
Purpose
AppError exists to enforce one invariant:
All errors crossing architectural boundaries must have a stable code, safe message, and clear transport semantics.
It provides a single, predictable shape for errors across:
- services
- domain logic
- transports
- UI boundaries
What AppError Is
- A domain-level error primitive
- Stable and serializable
- Explicitly coded (via
ErrorCode) - Safe to expose externally
What AppError Is Not
AppError is not:
- an exception hierarchy
- a business-rule engine
- a logging mechanism
- a retry signal
- a framework-specific error type
It does not contain logic beyond normalization and representation.
Structure
An AppError instance always contains:
code— stable error identifierstatus— HTTP / transport semanticsmessage— externally safe, human-readable messagemeta(optional) — structured diagnostic contextcause(optional) — original error (never exposed externally)
Creation Rules
AppError.create
AppError.create(code: ErrorCode, options?: AppErrorOptions): AppError
- Creates a new
AppErrorfrom a known error code - Resolves:
- HTTP status
- default message
- Allows optional overrides:
messagemetacause
Use this when:
- you are intentionally raising a known failure
- the error condition is understood and explicit
AppError.from
AppError.from(err: unknown, fallbackCode?: ErrorCode): AppError
This is the only allowed place where unknown is handled.
Behavior:
- If
erris already anAppError, it is returned as-is - If
erris anError, it is wrapped using the fallback code - If
erris anything else, it is normalized into anAppError
This guarantees:
- no
unknownescapes - error shape is always deterministic
Serialization
toJSON()
{
code: string;
message: string;
status: number;
meta?: Record<string, unknown>;
}
- This is the only safe external representation
causeis never exposed- Transport adapters must rely on this shape
ErrorMap
ErrorMap defines the authoritative registry of error codes and their semantics.
It is policy, not logic.
ErrorMap Invariants
The following rules are strict and non-negotiable:
- Error codes are stable and never reused
- Messages are safe to expose externally
statusrepresents transport semantics only- No business logic is allowed
- No helpers, factories, or functions are allowed
ErrorMap must remain a plain, declarative object.
Error Codes
Each entry in ErrorMap defines:
{
status: number;
message: string;
}
Categories are namespaced for clarity, e.g.:
system.*validation.*auth.*resource.*domain.*db.*external.*config.*
Namespaces are organizational only; semantics come from the code itself.
Relationship to Other Layers
AsyncHandler
AsyncHandlerguarantees that onlyAppErrorescapes execution boundariesAppErrordefines the normalized shape used downstream
Transport Error Adapters
- Transports map
AppError.status→ protocol status toJSON()is used as the response body- No transport-specific logic belongs inside
AppError
UI Error Boundaries
- UI receives a sanitized error
- UI must not branch on internal error codes by default
- Recovery and presentation are UI concerns, not error concerns
Design Principles
Stability Over Convenience
Error codes are contracts.
Changing or reusing codes breaks consumers.
Explicitness Over Inference
If an error matters, it must have:
- a code
- a place in
ErrorMap
No implicit or ad-hoc errors.
Separation of Concerns
- AppError → shape and normalization
- ErrorMap → policy and semantics
- Transports → protocol adaptation
- UI → presentation and recovery
Each layer stays boring and focused.
Summary
AppError and ErrorMap together form the spine of error handling in ONECMS.
They ensure that:
- errors are predictable
- boundaries are respected
- failures are safe to expose
- systems fail loudly and consistently
If an error bypasses AppError, the system is broken.