Changelog - 2026-07-16
Error Handling
Bug Fix Enhancement New APIIn one line. Every error now carries a normalized message ready for translation, you can declare reusable error definitions in a catalog, and cause finally reaches Error.cause.
What changed
- Every error now carries a
normalizedmessage. One object ({ text, code, args }) is all a client needs to render and translate any error, instead of pairingmessageCodeandextra.messageArgsby hand. All five handler branches emit it, including validation. causenow actually reachesError.cause. It used to be swept intoextra.causewhile the native field stayedundefined- and the error handler reads the native field, so the stack of the failure you wrapped never surfaced. Five framework call sites were losing it, including boot failures.- New: declare a domain error once, reuse it everywhere. A catalog entry fixes an error's code, HTTP status, and default message so every call site that raises it stays in sync. See Details below.
- New:
transformbuildsnormalizedyourself, sonormalized.textcan be a rendered, translated string whilemessagekeeps the raw text the throw site wrote. - The error layer moved to
@venizia/ignis-inversion. Backend imports are unchanged - keep importing from@venizia/ignis-helpers. This lets browser applications (which already depend on inversion for dependency injection) raise and read the same errors the server does. It also deletes a second, divergentApplicationErrorthat lived in inversion and never resolved itsmessageCode.
Who is affected
- Anyone reading
error.messageCodeorerror.extra.messageArgson the client. Both still work but are deprecated. Migrate toerror.normalized.code/error.normalized.args. - Browser or frontend applications. Can now import
ApplicationError,getError, and related types from@venizia/ignis-inversiondirectly. - Anyone spreading an error definition into
getError. Not a regression - it was already broken - but worth fixing now. See Details. - Everyone else. No action needed.
getErroraccepts every shape it accepted before, unknown keys still land inextra, and the wire only gainsnormalized.
No breaking changes
getError's input is unchanged. Any key it does not model still rides into extra:
typescript
throw getError({ message: 'Active transaction exists', transaction: { id, uid, status } });
// -> error.extra.transaction, exactly as beforeThe wire shape only gains normalized. Nothing is removed, nothing moves.
Details
messageCodeandextra.messageArgsare deprecated in favor ofnormalized.code/normalized.args, and stay only until every client has migrated.- Never spread a definition into
getError.throw getError({ ...SomeErrors.SOME_ERROR })reads naturally and is wrong: a definition carrieskey, notmessageCode, so the key lands inextra.keyand the error degrades tocore.system_error- unlocalizable, since clients branch on the code. The status and message still arrive, so it survives review. Pass it aserrorinstead. Nothing catches this for you: the index signature that carries your context acceptskeytoo. - The index signature is a deliberate trade. It is what lets a throw site attach context the framework knows nothing about - and it is also why
getError({ message, statuscode: 503 })compiles, leavesstatusCodeat400, and parks503inextra.statuscode. The framework cannot tell your context from your typo. - The catalog entry's
descriptionfield is not a code comment - it is the only context a translator gets, since they work from a spreadsheet, not the source. - Declare a catalog entry's
keyas a string literal, notMessageCode.build(...)-build()returnsstring, which widens the key registry toRecord<string, true>and silently breaks autocomplete. - Free-form
getError({ message })is unchanged and still the right choice for errors nobody translates (invariants, misconfiguration, seed guards). - Inversion ships both a CJS and an ESM build, so
instanceof ApplicationErrorcan still fail across a package boundary even after this change - keep usingisApplicationError(). categoryon a catalog entry is metadata for grouping and translator exports; it does not reach the response.
See Also
- Error Helper - the full reference
- Error Handling - when to catalog and when to raise free-form