error-codes
Error codes (SALO-444)
A DB-backed reference catalog of error codes: public.error_codes (migration
0135_error_codes.sql). One row per distinct error condition — code,
category, http_status, message, is_dynamic, severity.
This is a reference catalog only — there is no occurrence log (no per-request logging table) in v1. The table just answers "what does code X mean, what category is it, what status/message go with it."
Status: what's actually done vs. not
- Done: the table + an initial seed of 231 codes, generated by scanning
every distinct
return { error: "..." },throw new Error("..."), andNextResponse.json({ error: "..." })string literal acrossweb/src/lib/**andweb/src/app/api/**. - Not done: none of the ~530 call sites that produce those messages have been changed to use a code yet. They still throw/return the raw string they always did. The catalog exists, but nothing in the app references it yet — that's deliberately separate follow-up work (see below), because it means touching ~150 files across every business domain (billing, staff, bookings, inventory, ...), which deserves its own reviewed, staged effort rather than a single sweep.
Categories
| Category | HTTP status | Meaning |
|---|---|---|
AUTH |
401 | Not signed in / bad session |
PERMISSION |
403 | Signed in, but not allowed to do this |
VALIDATION |
422 | Bad input — missing/invalid field, out-of-range value |
NOT_FOUND |
404 | Referenced record doesn't exist |
CONFLICT |
409 | Duplicate / already exists / already booked |
RATE_LIMIT |
429 | Too many attempts |
BUSINESS_RULE |
422 | Domain-specific rule violation (insufficient balance, invoice already paid, etc.) — the catch-all for messages that don't fit the categories above |
SYSTEM |
500 | Internal failure — misconfiguration, external API failure |
Code naming
<CATEGORY>_<SLUG_OF_MESSAGE>, e.g. NOT_FOUND_GIFT_CARD_NOT_FOUND. Slugs
are derived from the message text and truncated to the first few
significant words. Where two different exact-string messages collapse to
the same slug (e.g. differing only in trailing punctuation or case), a
numeric suffix (_2, _3, ...) disambiguates.
Messages that are template literals with interpolated values (e.g.
`Insufficient balance: ₹${available} available, ₹${requested} requested`)
have their ${...} expressions collapsed to a {value} placeholder and are
flagged is_dynamic = true. The message column holds the template, not a
live instance.
Known limitations of the v1 seed
This was generated by regex extraction, not a full read of every file, so:
- Categorization is heuristic, not hand-verified per row. Expect some
misclassified rows, especially in
BUSINESS_RULE(the catch-all) — correcting them is a directUPDATEon the table, no code change needed. - Only string-literal messages were captured. A minority of error sites build messages through variables/helpers rather than an inline string/template literal and were not captured by the sweep.
- Case/punctuation variants produce separate rows (e.g.
"Brand not found"vs"brand not found") rather than being merged. - Table is editable directly — this is meant to be curated over time by whoever owns each domain, not treated as generated/read-only output.
Regenerating the seed
The extraction script is not checked into the repo (it was a one-off audit
tool). To redo the sweep: regex-match return \{ error: (['"])(.?)\1, throw new Error((['"])(.*?)\3\), and
NextResponse\.json\(\{ error: (['"])(.?)\5acrossweb/src/lib//*.tsandweb/src/app/api//.ts(excluding.test.ts), dedupe by message text (after stripping trailing periods), collapse ${...}to{value}, categorize by keyword heuristics (see table above), and emit INSERT ... ON CONFLICT (code) DO NOTHING` so re-running is idempotent
against manually-curated rows.
Follow-up work (not part of SALO-444's initial pass)
- Thread codes through call sites. Replace the ~530 raw
return { error: "..." }/throw new Error(...)sites with a reference to the matchingerror_codes.code, likely via a smallAppErrorhelper inweb/src/lib/errors/(not yet built). Do this staged, by domain — not as one PR. - Wire codes to UI states. Once server actions/API routes return a
codeinstead of (or alongside) a raw message, the frontend can mapAUTH_*→SessionExpiredState,PERMISSION_*→PermissionDeniedState,VALIDATION_*→FieldError, etc. (the components from SALO-442/443). - Decide whether an occurrence log is actually needed later — deliberately out of scope for v1 per the "reference catalog only" decision.