Errors
FougereError represents a domain error with a typed code. Handlers throw it, and the
following layers retain its code, message, and details.
throw new FougereError({
code: ErrorCode.CONFLICT,
message: `Slug '${slug}' is already taken`,
entity: 'post',
operation: 'create',
// details?: [{ path, message }] — per-field, used by VALIDATION_FAILED
// cause?: unknown
});
The codes
| Code | Meaning | HTTP projection |
|---|---|---|
VALIDATION_FAILED | invalid input — details contains per-field { path, message } | 400 |
UNAUTHORIZED | no identity | 401 |
FORBIDDEN | identity present, right absent | 403 |
NOT_FOUND | designated thing does not exist | 404 |
CONFLICT | state refuses the transition (already published, slug taken) | 409 |
SERVICE_UNAVAILABLE | a remote Frond is unreachable | 503 |
INTERNAL_ERROR | anything unexpected | 500 |
ErrorCode carries twelve more, each projecting to the HTTP status of the same name:
BAD_REQUEST 400, METHOD_NOT_ALLOWED 405, REQUEST_TIMEOUT 408, GONE 410,
PRECONDITION_FAILED 412, PAYLOAD_TOO_LARGE 413, UNPROCESSABLE_ENTITY 422,
LOCKED 423, TOO_MANY_REQUESTS 429, NOT_IMPLEMENTED 501, BAD_GATEWAY 502,
GATEWAY_TIMEOUT 504. The seven above are the ones a domain reaches for; these exist so a
surface never has to invent a status the vocabulary could carry.
INTERNAL_ERROR is the one code whose message never leaves
The others were written for the caller and travel whole. An INTERNAL_ERROR was not:
it may quote a path, a query or a row, so it is replaced by a constant before leaving the
process.
It is logged in the same place — masking and recording live in one function, so the
sentence exists exactly once, on the server. An operator asking "why this 500" finds it in
the logs with its cause, its entity and its operation; an attacker never sees it. If your
message must reach the caller, then it is not an INTERNAL_ERROR: give it its code.
Error propagation
- Handler throws
FougereError— the same object whether the Frond is local or remote. - Local call — the error propagates in memory, untouched.
- Remote call — the transport frames it as a JSON-RPC error (
code: -32000,data: { code, message, entity, operation, details? }) and the client side rebuilds aFougereError, including itsdetails. - In a page —
useQuery/useCommandexpose it aserror(a realFougereError);useFormFormapsVALIDATION_FAILED.detailsonto per-field errors automatically. - On the REST surface —
toHttpErrorprojects the code to the real HTTP status (table above), the full typed value indata.
The browser receives the same code, message, and per-field errors whether the Frond is
local or remote. An unreachable host produces a typed SERVICE_UNAVAILABLE error.
Check order
The site's blog performs checks in this order:
requireUser(user) // UNAUTHORIZED
→ requireOwn(id, user) // NOT_FOUND, then FORBIDDEN
→ state checks // CONFLICT (already published, empty draft)
→ realize // storage.update(…)
Next: Seeds — initializing data through operations.