Skip to content

feat(db): add semantic error code enum (DbErrorCode)#164

Merged
vertz-dev-core[bot] merged 1 commit intofeat/db-v1from
feat/db-error-codes
Feb 11, 2026
Merged

feat(db): add semantic error code enum (DbErrorCode)#164
vertz-dev-core[bot] merged 1 commit intofeat/db-v1from
feat/db-error-codes

Conversation

@vertz-dev-core
Copy link
Copy Markdown
Contributor

Summary

  • Add DbErrorCode const object mapping semantic names to PG numeric codes (e.g., UNIQUE_VIOLATION -> '23505')
  • Add .code property on error classes with semantic name (e.g., 'UNIQUE_VIOLATION' instead of '23505')
  • Add .pgCode property on error classes for raw PG SQLSTATE access
  • Add PgCodeToName reverse lookup map and resolveErrorCode() helper
  • Export DbErrorCode, DbErrorCodeName, DbErrorCodeValue, PgCodeToName, resolveErrorCode from @vertz/db
  • Update all tests (unit, integration, E2E, diagnostic) to assert semantic codes
  • Fix http-adapter test to use ESM import instead of CJS require

Addresses follow-up #38 (PG numeric error codes -> semantic names).

Test plan

  • Error classes have .code with semantic name (UNIQUE_VIOLATION, FOREIGN_KEY_VIOLATION, etc.)
  • Error classes have .pgCode with raw PG SQLSTATE code (23505, 23503, etc.)
  • DbErrorCode enum is exported and usable in switch statements
  • toJSON() serializes semantic code (not numeric)
  • PgCodeToName reverse map resolves PG codes to semantic names
  • resolveErrorCode() helper works for known and unknown codes
  • All 18 new error-codes tests pass
  • All existing error tests updated to use semantic codes
  • E2E test assertions updated
  • Diagnostic test assertions updated
  • Full suite: 780 tests pass, 0 type errors
  • Biome lint clean
  • TypeScript typecheck clean

🤖 Generated with Claude Code

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>
@github-actions
Copy link
Copy Markdown
Contributor

Coverage Report for DB (packages/db)

Status Category Percentage Covered / Total
🔵 Lines 94.46% 2644 / 2799
🔵 Statements 94.46% 2644 / 2799
🔵 Functions 96.51% 194 / 201
🔵 Branches 86.41% 655 / 758
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/db/src/errors/db-error.ts 100% 100% 100% 100%
packages/db/src/errors/error-codes.ts 100% 100% 100% 100%
packages/db/src/errors/index.ts 0% 100% 100% 0% 7-26
Unchanged Files
packages/db/src/d.ts 100% 100% 100% 100%
packages/db/src/cli/index.ts 0% 100% 100% 0% 2-8
packages/db/src/cli/migrate-deploy.ts 100% 100% 100% 100%
packages/db/src/cli/migrate-dev.ts 90% 71.42% 100% 90% 54-57
packages/db/src/cli/push.ts 100% 100% 100% 100%
packages/db/src/cli/status.ts 100% 100% 100% 100%
packages/db/src/client/database.ts 92.16% 90.47% 100% 92.16% 271-272, 374-384
packages/db/src/client/index.ts 0% 100% 100% 0% 2-4
packages/db/src/client/tenant-graph.ts 100% 100% 100% 100%
packages/db/src/diagnostic/index.ts 100% 100% 100% 100%
packages/db/src/errors/http-adapter.ts 100% 100% 100% 100%
packages/db/src/errors/pg-parser.ts 100% 71.87% 100% 100%
packages/db/src/migration/differ.ts 97.97% 87.3% 100% 97.97% 183-185
packages/db/src/migration/files.ts 100% 100% 100% 100%
packages/db/src/migration/index.ts 100% 100% 100% 100%
packages/db/src/migration/runner.ts 100% 88.88% 100% 100%
packages/db/src/migration/snapshot.ts 100% 100% 100% 100%
packages/db/src/migration/sql-generator.ts 92.07% 60% 100% 92.07% 130-131, 140-141, 214, 218, 236, 238, 240, 242, 244-248, 250
packages/db/src/plugin/event-bus.ts 100% 100% 100% 100%
packages/db/src/plugin/fingerprint.ts 100% 100% 100% 100%
packages/db/src/plugin/index.ts 0% 100% 100% 0% 2-6
packages/db/src/plugin/plugin-runner.ts 100% 100% 100% 100%
packages/db/src/plugin/plugin-types.ts 100% 100% 100% 100%
packages/db/src/query/aggregate.ts 83.76% 75.75% 100% 83.76% 75-79, 123-129, 185-186, 195-199, 237-240, 273-274, 283-285, 289-291, 310-316
packages/db/src/query/crud.ts 99.13% 94.44% 100% 99.13% 230-231
packages/db/src/query/executor.ts 100% 100% 100% 100%
packages/db/src/query/helpers.ts 100% 81.48% 100% 100%
packages/db/src/query/index.ts 0% 100% 100% 0% 2-42
packages/db/src/query/relation-loader.ts 88.1% 65.21% 100% 88.1% 89-90, 102-103, 171-172, 203-207, 293-297, 305-306, 400-404, 453-454, 477-490
packages/db/src/query/row-mapper.ts 100% 100% 100% 100%
packages/db/src/schema/column.ts 100% 100% 100% 100%
packages/db/src/schema/filter.ts 100% 100% 100% 100%
packages/db/src/schema/inference.ts 100% 100% 100% 100%
packages/db/src/schema/relation.ts 100% 100% 100% 100%
packages/db/src/schema/table.ts 74.46% 100% 40% 74.46% 165-166, 168-169, 171-172, 174-175, 177-178, 180-181
packages/db/src/sql/casing.ts 100% 100% 100% 100%
packages/db/src/sql/delete.ts 100% 100% 100% 100%
packages/db/src/sql/index.ts 0% 0% 0% 0% 1-13
packages/db/src/sql/insert.ts 97.1% 96% 100% 97.1% 54-55
packages/db/src/sql/select.ts 100% 100% 100% 100%
packages/db/src/sql/tagged.ts 100% 92.3% 100% 100%
packages/db/src/sql/update.ts 100% 100% 100% 100%
packages/db/src/sql/where.ts 100% 94.91% 100% 100%
packages/db/src/types/branded-errors.ts 100% 100% 100% 100%
Generated in workflow #227 for commit 8ac5268 by the Vitest Coverage Report Action

Copy link
Copy Markdown
Contributor

@vertz-dev-dx vertz-dev-dx Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  1. Is DbErrorCode complete 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 like 42P01 (undefined table) or 42703 (undefined column) are schema/syntax errors that don't warrant semantic error classes yet. Easy to extend later without breaking changes.

  2. .code / .pgCode consistency. All 4 PG error subclasses (UniqueConstraintError, ForeignKeyError, NotNullError, CheckConstraintError) have both .code (semantic) and .pgCode (SQLSTATE) as as const literals. Application-level errors (NotFoundError, ConnectionError, ConnectionPoolExhaustedError) have .code only with no .pgCode, which is correct — they don't originate from PG. ConnectionError uses code: string = 'CONNECTION_ERROR' (not as const) which is slightly looser than the others, but this is pre-existing and not in scope for this PR.

  3. resolveErrorCode() for unknown codes. Returns undefined, which is correct. The test covers 42P01 as an unmapped code. PgCodeToName typed as Record<string, DbErrorCodeName | undefined> means index access always returns T | undefined — no unsafe assumptions.

  4. Switch exhaustiveness. The test demonstrates switch usage with string literal cases. Because error.code on each concrete class is as 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 like assertNever, but that's a consumer-side pattern, not something the library needs to enforce. The current design supports it without mandating it.

  5. PgCodeToName edge 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.

  6. UnknownDbError in 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 .code falls back to the raw value. The abstract base DbError declares code: string which allows this.

No issues found. Clean, well-tested, well-scoped addition.

@vertz-dev-core vertz-dev-core Bot merged commit 074a51e into feat/db-v1 Feb 11, 2026
3 checks passed
@viniciusdacal viniciusdacal deleted the feat/db-error-codes branch February 22, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants