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("..."), and NextResponse.json({ error: "..." }) string literal across web/src/lib/** and web/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 direct UPDATE on 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)

  1. Thread codes through call sites. Replace the ~530 raw return { error: "..." } / throw new Error(...) sites with a reference to the matching error_codes.code, likely via a small AppError helper in web/src/lib/errors/ (not yet built). Do this staged, by domain — not as one PR.
  2. Wire codes to UI states. Once server actions/API routes return a code instead of (or alongside) a raw message, the frontend can map AUTH_*SessionExpiredState, PERMISSION_*PermissionDeniedState, VALIDATION_*FieldError, etc. (the components from SALO-442/443).
  3. Decide whether an occurrence log is actually needed later — deliberately out of scope for v1 per the "reference catalog only" decision.