feat(db): add semantic error code enum (DbErrorCode)#164
feat(db): add semantic error code enum (DbErrorCode)#164vertz-dev-core[bot] merged 1 commit intofeat/db-v1from
Conversation
Replace raw PG numeric error codes ('23505', '23503', etc.) with
developer-friendly semantic names on error classes. This makes error
handling more readable and enables future switch/case exhaustiveness
checking.
Changes:
- Add DbErrorCode const object mapping semantic names to PG SQLSTATE codes
- Add PgCodeToName reverse lookup and resolveErrorCode helper
- Change .code on constraint errors to semantic names (e.g., 'UNIQUE_VIOLATION')
- Add .pgCode property on error classes for raw PG code access
- Export DbErrorCode, DbErrorCodeName, DbErrorCodeValue, PgCodeToName,
resolveErrorCode from @vertz/db
- Update all tests to assert semantic codes instead of numeric strings
- Fix http-adapter test to use ESM import instead of CJS require
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Review: APPROVE
CI green (lint, typecheck, 780 tests, coverage all pass).
What's good
Clean semantic layer. The .code / .pgCode split is well-executed. Every concrete error subclass gets a string literal as const for .code (semantic name) and .pgCode (raw SQLSTATE), which means TypeScript narrows both in type guards and switch arms. The base class DbError declares abstract readonly code: string and optional readonly pgCode?: string | undefined, which is the right shape — subclasses constrain the union, callers get narrowing.
DbErrorCode const object is the right pattern. Using as const on a plain object (instead of an enum) gives you the string literal types, keyof typeof for the name union, and indexed access for the value union — all without TypeScript enum baggage. Good choice for a library.
PgCodeToName reverse map and resolveErrorCode() are correct. The Object.fromEntries(Object.entries(...).map(...)) flip is clean. Typing it as Record<string, DbErrorCodeName | undefined> is correct since arbitrary strings can be passed in. The resolveErrorCode wrapper is a thin convenience but earns its place.
Tests are thorough. 18 new error-codes tests cover every mapping, the reverse map, unknown codes, and switch usage. All existing tests across unit, integration, E2E, and diagnostic suites were updated from numeric to semantic assertions. No test was skipped or weakened.
CJS fix in http-adapter.test.ts is correct. Moving parsePgError from a require() inside the test body to an ESM import at the top is the right fix for an ESM-only package. No behavior change, just module system correctness.
toJSON() serialization is consistent. .code in JSON output is now the semantic name, matching what developers would use in API responses. pgCode is deliberately not serialized into JSON, which is correct — it's an implementation detail for internal logging, not for API consumers.
Adversarial checks
-
Is
DbErrorCodecomplete enough for v1? It covers the 4 integrity constraint violations the framework already handles (unique, FK, not-null, check), plus exclusion violation, serialization failure, deadlock, and 3 connection codes. For a v1, this is the right scope — these are the codes developers actually hit. Missing codes like42P01(undefined table) or42703(undefined column) are schema/syntax errors that don't warrant semantic error classes yet. Easy to extend later without breaking changes. -
.code/.pgCodeconsistency. All 4 PG error subclasses (UniqueConstraintError,ForeignKeyError,NotNullError,CheckConstraintError) have both.code(semantic) and.pgCode(SQLSTATE) asas constliterals. Application-level errors (NotFoundError,ConnectionError,ConnectionPoolExhaustedError) have.codeonly with no.pgCode, which is correct — they don't originate from PG.ConnectionErrorusescode: string = 'CONNECTION_ERROR'(notas const) which is slightly looser than the others, but this is pre-existing and not in scope for this PR. -
resolveErrorCode()for unknown codes. Returnsundefined, which is correct. The test covers42P01as an unmapped code.PgCodeToNametyped asRecord<string, DbErrorCodeName | undefined>means index access always returnsT | undefined— no unsafe assumptions. -
Switch exhaustiveness. The test demonstrates switch usage with string literal cases. Because
error.codeon each concrete class isas const, TypeScript can narrow it. Full exhaustiveness checking would require a discriminated union type (e.g.,DbErrorCode['UNIQUE_VIOLATION'] | DbErrorCode['FOREIGN_KEY_VIOLATION'] | ...) or a helper likeassertNever, but that's a consumer-side pattern, not something the library needs to enforce. The current design supports it without mandating it. -
PgCodeToNameedge case — application-level codes. The reverse map also contains entries like'NOT_FOUND' -> 'NOT_FOUND'and'CONNECTION_ERROR' -> 'CONNECTION_ERROR'because application-level codes map to themselves. This is harmless but worth noting —resolveErrorCode('NOT_FOUND')returns'NOT_FOUND', which is technically correct (it's a valid code name) but could surprise someone who expects only PG codes in the reverse map. Not a blocker — it's documented by the types and the behavior is consistent. -
UnknownDbErrorin pg-parser.ts. This class stores the raw PG code in.code(e.g.,'42P01'), not a semantic name, and has no.pgCode. This is correct — unknown codes don't have semantic names, so.codefalls back to the raw value. The abstract baseDbErrordeclarescode: stringwhich allows this.
No issues found. Clean, well-tested, well-scoped addition.
Summary
DbErrorCodeconst object mapping semantic names to PG numeric codes (e.g.,UNIQUE_VIOLATION->'23505').codeproperty on error classes with semantic name (e.g.,'UNIQUE_VIOLATION'instead of'23505').pgCodeproperty on error classes for raw PG SQLSTATE accessPgCodeToNamereverse lookup map andresolveErrorCode()helperDbErrorCode,DbErrorCodeName,DbErrorCodeValue,PgCodeToName,resolveErrorCodefrom@vertz/dbAddresses follow-up #38 (PG numeric error codes -> semantic names).
Test plan
.codewith semantic name (UNIQUE_VIOLATION,FOREIGN_KEY_VIOLATION, etc.).pgCodewith raw PG SQLSTATE code (23505,23503, etc.)DbErrorCodeenum is exported and usable in switch statementstoJSON()serializes semantic code (not numeric)PgCodeToNamereverse map resolves PG codes to semantic namesresolveErrorCode()helper works for known and unknown codes🤖 Generated with Claude Code