feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007]#151
Conversation
- Abstract DbError base with code, name, query, table, toJSON() - UniqueConstraintError (23505), ForeignKeyError (23503), NotNullError (23502), CheckConstraintError (23514) - NotFoundError, ConnectionError, ConnectionPoolExhaustedError - PG error code parser with column/constraint/detail extraction - dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings - 42 new tests covering all error types, parser, and HTTP adapter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- createDb() factory accepts URL, pool config, table registry, casing, log - Returns typed DatabaseInstance<TTables> with $tenantGraph - Tenant graph computed at createDb() time from d.tenant() metadata - Traverses .references() chains for indirect tenant scoping - Classifies tables as root, directlyScoped, indirectlyScoped, shared - Logs notices for tables without tenant path and not .shared() - db.close() and db.isHealthy() stub implementations - 16 new tests for tenant graph and database factory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Adversarial Review — PR #151: Phase 2 error hierarchy + connection management (DB-006, DB-007)
Verdict: Approve
Solid work. Clean abstractions, good test coverage, no @ts-ignore or as any. A few findings below — one medium concern and several nits/suggestions. Nothing blocking.
DB-006 Acceptance Criteria Verification
| Criterion | Status |
|---|---|
| All DbError subclasses extend DbError abstract base | Pass — UniqueConstraintError, ForeignKeyError, NotNullError, CheckConstraintError, NotFoundError, ConnectionError, ConnectionPoolExhaustedError all extend DbError. Verified via tests with toBeInstanceOf(DbError). |
toJSON() produces { error, code, message, table?, column? } |
Pass — Base class toJSON() returns the shape. Subclasses override to include table and column where appropriate. |
PG error code 23505 maps to UniqueConstraintError with extracted column name |
Pass — parsePgError handles 23505, extracts column from detail via regex when column field not present. |
PG error code 23503 maps to ForeignKeyError |
Pass — with constraint name extracted. |
PG error code 23502 maps to NotNullError |
Pass — with column name extracted from PG error message when column not provided. |
| Error messages include table name and column name | Pass — all constraint error messages interpolate table and column/constraint. |
dbErrorToHttpError() maps all error types to correct HTTP status codes |
Pass — Unique→409, FK/NotNull/Check→422, NotFound→404, Connection→503, Unknown→500. |
DB-007 Acceptance Criteria Verification
| Criterion | Status |
|---|---|
createDb() returns a typed Database<TTables> instance |
Pass — returns DatabaseInstance<TTables> with generic preservation. |
| Tenant graph correctly identifies root, directly scoped, indirectly scoped, and shared tables | Pass — Multi-hop indirect resolution via iterative while (changed) loop works correctly. Tests cover 2-hop chains (tasks→projects→organizations). |
Startup notice logged for tables without tenant path and not .shared() |
Pass — audit_logs correctly triggers log notice. |
db.close() and db.isHealthy() exist with correct types |
Pass — Stubbed implementations return Promise<void> and Promise<boolean> respectively. |
Findings
[Medium] ConnectionPoolExhaustedError — fragile name reset (db-error.ts:217)
constructor(poolSize: number) {
super(`Connection pool exhausted (max: ${poolSize})`);
// Reset name since ConnectionError constructor sets it to ConnectionError
this.name = 'ConnectionPoolExhaustedError';
}The base class DbError uses new.target.name to set this.name, which should correctly resolve to 'ConnectionPoolExhaustedError' when constructed directly. The problem is that ConnectionError's constructor calls super(message) on DbError, and at that point new.target is ConnectionPoolExhaustedError, so DbError's constructor sets this.name = 'ConnectionPoolExhaustedError'. Then ConnectionError's constructor body runs — but it doesn't reassign this.name. So the manual reset in ConnectionPoolExhaustedError is actually redundant. It works correctly, but the comment is misleading — it claims ConnectionError's constructor sets name to ConnectionError, which it does not (it's DbError's constructor that sets name via new.target.name).
Not a bug, but the misleading comment should be cleaned up to avoid confusion in future maintenance.
[Low] Mutable cast in DbError.toJSON() (db-error.ts:30)
(json as { table: string }).table = this.table;The DbErrorJson interface has readonly fields, but toJSON() casts to a mutable type to set table. This works, but it's a pattern that could be cleaner — constructing the full object conditionally instead:
return {
error: this.name,
code: this.code,
message: this.message,
...(this.table !== undefined && { table: this.table }),
};Not blocking — the current approach is correct.
[Low] UnknownDbError not exported (pg-parser.ts:59)
UnknownDbError is a concrete subclass of DbError defined only in pg-parser.ts but not exported. This means consumers cannot instanceof check against it. The http-adapter test uses require() to get parsePgError and create an unknown error for the 500 status test — this is fine, but if consumers ever need to distinguish unknown PG errors from known ones, they would have no way to do so. Consider exporting it or providing a type guard.
[Low] extractKeyDetail regex does not handle composite keys (pg-parser.ts:30)
const match = detail.match(/^Key \(([^)]+)\)=\(([^)]*)\)/);PG composite unique constraints produce detail strings like Key (col_a, col_b)=(val_a, val_b) already exists. This regex would capture col_a, col_b as the column name and val_a, val_b as the value — both as a single string. Not necessarily wrong (it still extracts something useful), but worth a note or test for this edge case. The UniqueConstraintError column field would contain a comma-separated string rather than a clean column name.
[Nit] http-adapter test uses require() (http-adapter.test.ts:84)
const { parsePgError } = require('../pg-parser');This is the only require() in the test suite. Presumably done to avoid a circular type issue or to keep the import out of the top-level. Works fine, but it breaks ESM consistency. Consider using dynamic import() instead.
[Nit] Database vs DatabaseInstance naming
The schema/inference.ts already exports a Database<TTables> interface with readonly _tables: TTables. The new code introduces DatabaseInstance<TTables> which has _tables, $tenantGraph, close(), and isHealthy(). These are conceptually the same thing at different phases of implementation. It would be worth a TODO comment noting that DatabaseInstance should eventually replace or extend the Database type from inference.ts to avoid confusion.
Test Coverage Assessment
Tests are comprehensive. Good coverage of:
- All error class constructors, properties, and
toJSON()shapes - PG parser extraction from
detail,message, andconstraintfields - Fallback behavior for missing fields
- HTTP adapter mapping for all error types + unknown fallback
- Tenant graph: root, directly scoped, indirectly scoped, shared, multi-hop, null root
- createDb: table passthrough, graph computation, logging, pool/casing options
- close() and isHealthy() stub behavior
Missing (not blocking):
- No test for
Error.captureStackTraceor stack trace correctness - No test for
ConnectionError.toJSON()confirmingtablefield is absent (it inherits basetoJSON()which skipstablewhen undefined — implicitly covered) - PGlite integration tests mentioned in DB-007 are not present (expected for stub phase)
LGTM. Clean, well-tested, meets acceptance criteria. The stub approach for pool management is appropriate for this phase.
-- ava (vertz-dev-dx)
Coverage Report for Schema (packages/schema)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Compiler (packages/compiler)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Codegen (packages/codegen)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for CLI (packages/cli)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for CLI Runtime (packages/cli-runtime)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Fetch (packages/fetch)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage Report for Testing (packages/testing)
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||
…DB-007] (#151) * feat(db): add typed error hierarchy and PG error parser [DB-006] - Abstract DbError base with code, name, query, table, toJSON() - UniqueConstraintError (23505), ForeignKeyError (23503), NotNullError (23502), CheckConstraintError (23514) - NotFoundError, ConnectionError, ConnectionPoolExhaustedError - PG error code parser with column/constraint/detail extraction - dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings - 42 new tests covering all error types, parser, and HTTP adapter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add createDb(), tenant graph computation [DB-007] - createDb() factory accepts URL, pool config, table registry, casing, log - Returns typed DatabaseInstance<TTables> with $tenantGraph - Tenant graph computed at createDb() time from d.tenant() metadata - Traverses .references() chains for indirect tenant scoping - Classifies tables as root, directlyScoped, indirectlyScoped, shared - Logs notices for tables without tenant path and not .shared() - db.close() and db.isHealthy() stub implementations - 16 new tests for tenant graph and database factory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(db): add type inference engine (FindResult, filters, includes) [DB-005] Implement the type inference layer that powers all query type safety: - FilterType<TColumns>: typed where filters with eq/ne/gt/gte/lt/lte/in/notIn operators, string-specific contains/startsWith/endsWith, nullable isNull, and direct value shorthand - OrderByType<TColumns>: constrained to column names with 'asc' | 'desc' - SelectOption<TColumns>: mutually exclusive not:'sensitive'|'hidden' vs explicit field selection, enforced via never mapped keys - SelectNarrow<TColumns, TSelect>: narrows result based on select clause - IncludeResolve<TRelations, TInclude>: resolves relation includes with depth cap at 2, supports select sub-clauses for narrowing included relations - FindResult<TTable, TOptions, TRelations>: combines select + include - InsertInput/UpdateInput: standalone type utilities delegating to $insert/$update - Database<TTables>/TableEntry: registry types for typed query methods - Comprehensive type-level tests with @ts-expect-error negative cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007] (#151) * feat(db): add typed error hierarchy and PG error parser [DB-006] - Abstract DbError base with code, name, query, table, toJSON() - UniqueConstraintError (23505), ForeignKeyError (23503), NotNullError (23502), CheckConstraintError (23514) - NotFoundError, ConnectionError, ConnectionPoolExhaustedError - PG error code parser with column/constraint/detail extraction - dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings - 42 new tests covering all error types, parser, and HTTP adapter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add createDb(), tenant graph computation [DB-007] - createDb() factory accepts URL, pool config, table registry, casing, log - Returns typed DatabaseInstance<TTables> with $tenantGraph - Tenant graph computed at createDb() time from d.tenant() metadata - Traverses .references() chains for indirect tenant scoping - Classifies tables as root, directlyScoped, indirectlyScoped, shared - Logs notices for tables without tenant path and not .shared() - db.close() and db.isHealthy() stub implementations - 16 new tests for tenant graph and database factory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 3 — SQL Generator (db-008, db-009) (#152) * feat(db): add SQL statement builders (SELECT, INSERT, UPDATE, DELETE) [DB-008] Implements Phase 3 SQL generator builders: - SELECT builder: column selection with camelCase->snake_case aliasing, WHERE, ORDER BY, LIMIT/OFFSET, COUNT(*) OVER() for findManyAndCount - INSERT builder: single row, batch (multi-row VALUES), RETURNING, ON CONFLICT (upsert with DO NOTHING / DO UPDATE SET) - UPDATE builder: SET clause from data object, WHERE, RETURNING - DELETE builder: WHERE, RETURNING - WHERE builder: all 13+ filter operators (eq, ne, gt, gte, lt, lte, contains, startsWith, endsWith, in, notIn, isNull, isNotNull), nested OR/AND/NOT, JSONB operators (->/->>), array operators (@>, <@, &&) - Casing module: camelToSnake / snakeToCamel conversion - All values parameterized ($1, $2, ...) for SQL injection prevention - Handles 'now' sentinel for timestamp columns via NOW() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add sql tagged template and escape hatch [DB-009] Implements the sql tagged template literal for composable raw SQL: - sql`...${value}...` auto-parameterizes values as $1, $2, ... - sql.raw() inserts trusted SQL strings without parameterization - Nested sql`` fragments compose with automatic param renumbering - CTE (WITH ... AS) syntax works through tagged template composition - SqlFragment type with _tag discriminant for runtime identification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address 7 blocking review issues on Phase 3 SQL generator [DB-008, DB-009] 1. Empty IN/NOT IN arrays now produce FALSE/TRUE instead of invalid SQL 2. LIKE values escape %, _, and \ metacharacters before wrapping 3. Empty OR/AND arrays produce FALSE/TRUE (standard SQL logic) 4. JSONB path segments sanitize single quotes to prevent injection 5. LIMIT/OFFSET values are parameterized ($N) instead of inlined 6. Added PGlite integration test (DB-008 acceptance criterion) 7. Added db.query<T>() stub to DatabaseInstance (DB-009 acceptance criterion) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 4 — Query Builder + Relations + Aggregation (db-010/011/012) (#156) * feat(db): add CRUD query methods [DB-010] Implements typed find, create, update, upsert, and delete methods on the Database instance. All methods use the Phase 3 SQL builders, parameterized queries, and snake_case -> camelCase result mapping. - findOne, findOneOrThrow, findMany, findManyAndCount - create, createMany, createManyAndReturn - update, updateMany - upsert (INSERT ON CONFLICT with explicit update values) - deleteOne, deleteMany - Query executor with PG error mapping - Row mapper with snakeToCamel conversion - Helper utilities for column resolution Includes comprehensive PGlite integration tests covering all acceptance criteria: full CRUD cycle, pagination, select narrowing, UniqueConstraintError, ForeignKeyError, NotFoundError. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add relation loading with include option [DB-011] Implements the include option for find queries using a batched loading strategy (N+1 prevention via IN queries). - belongsTo (one) relations: loads single object by FK - hasMany (many) relations: loads array by parent PK - Batched loading: single IN query per relation - Select narrowing on included relations - Nested includes supported up to depth 2 - Integration with findOne, findMany, findManyAndCount PGlite integration tests cover belongsTo, hasMany, batched loading, include with select, and findManyAndCount with include. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add aggregation queries (count, aggregate, groupBy) [DB-012] Implements typed aggregation query methods: - count(table, { where? }): returns row count as number - aggregate(table, { _avg, _sum, _min, _max, _count, where? }): computes aggregation functions with typed results - groupBy(table, { by, _count?, _avg?, orderBy? }): groups rows with per-group aggregations All methods support where filters and generate parameterized SQL. Results use camelCase keys consistent with the rest of the query layer. PGlite integration tests cover count with filter, aggregate with all functions, groupBy with ordering, multi-column grouping, and combined aggregations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR #156 review — SQL injection, manyToMany, nested includes [db-010/011/012] B1: Validate orderBy columns in groupBy aggregation to prevent SQL injection. Underscore-prefixed columns are now validated against requested aggregation aliases; direction values are restricted to 'asc'/'desc' only. B2: Implement manyToMany relation loading via join tables (_through). Queries the join table first, collects target IDs, then batch-loads target rows and maps them back to parent rows. B3: Implement recursive nested includes (depth-2). Pass tablesRegistry through loadRelations so target table relations can be resolved for nested include: { posts: { include: { comments: true } } }. All fixes follow TDD: failing tests written first, then minimal implementation. --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 5 — Migration Differ + SQL Generator + Runner (db-013/014/015) (#157) * feat(db): add JSON snapshot format for schema state capture [db-013] * feat(db): add schema differ with rename detection and confidence scoring [db-013] * feat(db): add SQL migration generator with rollback support [db-014] * feat(db): add migration runner with history, drift detection, and ordering [db-015] * feat(db): add file management, module exports, and integration tests [db-015] --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> * feat(db): Phase 6 — CLI + Cache-Readiness Primitives (db-016, db-017) (#158) * feat(db): Phase 6 — CLI commands + cache-readiness primitives (db-016/db-017) - CLI: migrateDev, migrateDeploy, push, migrateStatus - Dry-run mode for migrateDev - Event bus for mutation events - Deterministic query fingerprinting - Plugin runner with beforeQuery/afterQuery hooks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR #158 review — status crash, snapshot return, exports - B1: migrateStatus now calls createHistoryTable before querying applied - B2: migrateDev returns snapshot in result for caller persistence - B3: cli and plugin modules re-exported from main barrel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 7 — E2E acceptance test + type error quality (db-018/db-019) (#159) - Full E2E test from design doc Section 7 - Type inference assertions with @ts-expect-error - Branded error types for readable compiler messages - @vertz/db/diagnostic export - Performance validation under 100k instantiation budget Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * docs(db): add v1.0 retrospective and changeset - Post-implementation review covering all 7 phases - Changeset for @vertz/db minor release Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): add @types/node to resolve node:crypto in CI typecheck The @vertz/db package imports from node:crypto (runner.ts, fingerprint.ts) but was missing @types/node in devDependencies. This worked locally because Bun hoists @types/node from sibling packages, but failed in CI where tsc runs in an isolated Dagger container. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): increase test/hook timeouts for PGlite in CI PGlite uses WebAssembly which is slower in Dagger's container environment. Tests were timing out at the default 5s/10s limits. Set testTimeout and hookTimeout to 30s to accommodate CI's constrained resources. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(db): update design doc to reflect implementation deviations Comprehensive update to align the design doc with the actual v1.0 implementation. Documents all breaking changes, missing features, and additions discovered during adversarial API diff review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): stabilize PGlite tests in CI Disable file parallelism to prevent concurrent WASM instances from crashing, add retry:2 as safety net, fix PGlite version constraint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): escape enum values and column defaults in DDL generation Enum values and column default values were interpolated directly into DDL strings without escaping single quotes. Malicious values like '); DROP TABLE users;-- would produce injectable SQL. Added escapeSqlString() helper that doubles internal single quotes, applied to enum_added, enum_altered, and column default expressions. Follow-up #22. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): guard deleteMany/updateMany against empty where clause Passing an empty where object ({}) to deleteMany or updateMany would generate SQL with no WHERE clause, silently affecting all rows. Added a runtime check that throws if the where object has no keys. Follow-up #19. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): preserve previous result when afterQuery plugin returns undefined If a plugin's afterQuery hook returned undefined (e.g. a logging-only plugin that observes but doesn't transform), the chain would pass undefined to subsequent plugins, causing runtime errors. Now defaults to the previous result when a plugin returns undefined: result = pluginResult !== undefined ? pluginResult : result; Follow-up #29. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): use dynamic PK column resolution in relation loader loadOneRelation, loadManyRelation, and loadManyToManyRelation all hard-coded 'id' as the primary key column name. Tables with non-standard PKs (e.g. 'code', 'slug') would silently fail to load relations. Now uses getPrimaryKeyColumns() to dynamically resolve the PK column name for both primary and target tables. Falls back to 'id' when no PK metadata is found for backward compatibility. Follow-up #14. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(db): split barrel export into tiered sub-paths Primary API at @vertz/db, SQL utilities at @vertz/db/sql, internal helpers at @vertz/db/internals, plugin system at @vertz/db/plugin. Reduces autocomplete noise and clarifies API surface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add semantic error code enum (DbErrorCode) 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> * fix(db): address review — types condition ordering, remove duplicate exports - Reorder package.json exports to put types before import - Remove camelToSnake/snakeToCamel from internals (already in sql) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add OR/NOT filter operators with proper parenthesization [#41] Fix OR and AND branch parenthesization so multi-condition branches are wrapped in parens, preventing operator precedence issues in generated SQL. Add comprehensive tests: - OR/AND/NOT with multi-condition branches - Nested composition (OR>AND>NOT, NOT>OR, etc.) - Operator-based conditions in logical branches - Parameter numbering across nested operators - PGlite integration tests for OR, NOT, nested queries - SQL injection prevention test with parameterized OR Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add createRegistry() and d.entry() for ergonomic relations [VER-40] Reduce boilerplate for defining table registries with type-safe relations. - d.entry(table) helper: reduces `{ table, relations: {} }` to a single call - d.entry(table, relations): wraps table with relations in one call - createRegistry(tables, callback): typed ref factory with per-table FK validation - ref.TABLE.one('target', 'fkColumn') validates both table name and column at compile time - ref.TABLE.many('target', 'fkColumn') validates FK against target table columns - Tables without explicit relations are auto-wrapped (no boilerplate needed) - Full test coverage: runtime tests, type-level tests (@ts-expect-error negatives) - E2E test updated to use createRegistry() instead of manual wrapping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add cursor-based pagination to findMany [#15] (#168) Implements cursor-based pagination as specified in design doc Section 1.7. - Add `cursor` and `take` options to SelectOptions, FindManyArgs, and TypedFindManyOptions interfaces - Single-column cursor generates `WHERE "col" > $N ORDER BY "col" ASC LIMIT $M` - Composite cursor uses row-value comparison: `(col1, col2) > ($1, $2)` - Cursor respects orderBy direction (> for asc, < for desc) - Cursor conditions AND with existing where filters - `take` aliases `limit` when paired with cursor - When no explicit orderBy, cursor columns are used for ORDER BY (default ASC) Tested with PGlite integration tests verifying multi-page traversal, cursor+where combinations, and desc ordering. Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add column-type-specific metadata to DefaultMeta extensions (#169) Downstream consumers (migration generators, query builders) need column-type-specific fields at the type level, not just at runtime. Previously DefaultMeta only carried boolean flags, while fields like length, precision, scale, enumName, enumValues, and format only existed in the runtime ColumnMetadata interface. Add four typed metadata extensions: - VarcharMeta<TLength> — carries length as a literal type - DecimalMeta<TPrecision, TScale> — carries precision and scale - EnumMeta<TName, TValues> — carries enumName and enumValues tuple - FormatMeta<TSqlType, TFormat> — carries format (e.g. 'email') Update d.varchar(), d.decimal(), d.enum(), and d.email() signatures to return these specific meta types instead of plain DefaultMeta. The metadata flows through modifier chains (nullable, unique, etc.) via the existing Omit & intersection pattern. Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add dry-run mode to migration runner (#167) * feat(db): add dry-run mode to migration runner and deploy CLI [VER-21] - Add ApplyOptions and ApplyResult types to runner.apply() - When dryRun: true, return SQL statements without executing - Add dryRun option to migrateDeploy() with per-migration details - Update migration index exports with new types - Tests: dry-run returns SQL without modifying DB, output matches what non-dry-run would execute, CLI respects the flag Closes VER-21 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): skip createHistoryTable during dry-run in migrateDeploy Guard createHistoryTable behind !dryRun so dry-run mode is truly side-effect-free. Wrap getApplied in try/catch during dry-run to gracefully handle missing history table (returns empty applied list). Updated tests to assert that no DDL is executed during dry-run and added coverage for dry-run with an existing history table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): wire generic type inference from schema to query results [DB-035] (#170) Thread TTables generic through DatabaseInstance method signatures so that findOne, findMany, create, update, upsert, delete, and other CRUD methods return properly typed results instead of Promise<unknown>. - Add TOptions generic to capture literal option shapes - Compute return types via FindResult<TTable, TOptions, TRelations> - Type data inputs using InsertInput/UpdateInput from table definition - Type where filters using FilterType<TColumns> - Add comprehensive .test-d.ts type flow verification tests - Remove 27 'as Record<string, unknown>' casts from E2E test Closes DB-035 Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: vertz-dev-core[bot] <260431274+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: vertz-tech-lead[bot] <2828099+vertz-tech-lead[bot]@users.noreply.github.com> Co-authored-by: vertz-devops[bot] <2832183+vertz-devops[bot]@users.noreply.github.com> Co-authored-by: vertz-devops[bot] <260554958+vertz-devops[bot]@users.noreply.github.com> Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com>
* feat(db): add type inference engine (FindResult, filters, includes) [DB-005] Implement the type inference layer that powers all query type safety: - FilterType<TColumns>: typed where filters with eq/ne/gt/gte/lt/lte/in/notIn operators, string-specific contains/startsWith/endsWith, nullable isNull, and direct value shorthand - OrderByType<TColumns>: constrained to column names with 'asc' | 'desc' - SelectOption<TColumns>: mutually exclusive not:'sensitive'|'hidden' vs explicit field selection, enforced via never mapped keys - SelectNarrow<TColumns, TSelect>: narrows result based on select clause - IncludeResolve<TRelations, TInclude>: resolves relation includes with depth cap at 2, supports select sub-clauses for narrowing included relations - FindResult<TTable, TOptions, TRelations>: combines select + include - InsertInput/UpdateInput: standalone type utilities delegating to $insert/$update - Database<TTables>/TableEntry: registry types for typed query methods - Comprehensive type-level tests with @ts-expect-error negative cases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 2 — error hierarchy + connection management [DB-006, DB-007] (#151) * feat(db): add typed error hierarchy and PG error parser [DB-006] - Abstract DbError base with code, name, query, table, toJSON() - UniqueConstraintError (23505), ForeignKeyError (23503), NotNullError (23502), CheckConstraintError (23514) - NotFoundError, ConnectionError, ConnectionPoolExhaustedError - PG error code parser with column/constraint/detail extraction - dbErrorToHttpError() adapter: 409, 404, 422, 503 mappings - 42 new tests covering all error types, parser, and HTTP adapter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add createDb(), tenant graph computation [DB-007] - createDb() factory accepts URL, pool config, table registry, casing, log - Returns typed DatabaseInstance<TTables> with $tenantGraph - Tenant graph computed at createDb() time from d.tenant() metadata - Traverses .references() chains for indirect tenant scoping - Classifies tables as root, directlyScoped, indirectlyScoped, shared - Logs notices for tables without tenant path and not .shared() - db.close() and db.isHealthy() stub implementations - 16 new tests for tenant graph and database factory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 3 — SQL Generator (db-008, db-009) (#152) * feat(db): add SQL statement builders (SELECT, INSERT, UPDATE, DELETE) [DB-008] Implements Phase 3 SQL generator builders: - SELECT builder: column selection with camelCase->snake_case aliasing, WHERE, ORDER BY, LIMIT/OFFSET, COUNT(*) OVER() for findManyAndCount - INSERT builder: single row, batch (multi-row VALUES), RETURNING, ON CONFLICT (upsert with DO NOTHING / DO UPDATE SET) - UPDATE builder: SET clause from data object, WHERE, RETURNING - DELETE builder: WHERE, RETURNING - WHERE builder: all 13+ filter operators (eq, ne, gt, gte, lt, lte, contains, startsWith, endsWith, in, notIn, isNull, isNotNull), nested OR/AND/NOT, JSONB operators (->/->>), array operators (@>, <@, &&) - Casing module: camelToSnake / snakeToCamel conversion - All values parameterized ($1, $2, ...) for SQL injection prevention - Handles 'now' sentinel for timestamp columns via NOW() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add sql tagged template and escape hatch [DB-009] Implements the sql tagged template literal for composable raw SQL: - sql`...${value}...` auto-parameterizes values as $1, $2, ... - sql.raw() inserts trusted SQL strings without parameterization - Nested sql`` fragments compose with automatic param renumbering - CTE (WITH ... AS) syntax works through tagged template composition - SqlFragment type with _tag discriminant for runtime identification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address 7 blocking review issues on Phase 3 SQL generator [DB-008, DB-009] 1. Empty IN/NOT IN arrays now produce FALSE/TRUE instead of invalid SQL 2. LIKE values escape %, _, and \ metacharacters before wrapping 3. Empty OR/AND arrays produce FALSE/TRUE (standard SQL logic) 4. JSONB path segments sanitize single quotes to prevent injection 5. LIMIT/OFFSET values are parameterized ($N) instead of inlined 6. Added PGlite integration test (DB-008 acceptance criterion) 7. Added db.query<T>() stub to DatabaseInstance (DB-009 acceptance criterion) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 4 — Query Builder + Relations + Aggregation (db-010/011/012) (#156) * feat(db): add CRUD query methods [DB-010] Implements typed find, create, update, upsert, and delete methods on the Database instance. All methods use the Phase 3 SQL builders, parameterized queries, and snake_case -> camelCase result mapping. - findOne, findOneOrThrow, findMany, findManyAndCount - create, createMany, createManyAndReturn - update, updateMany - upsert (INSERT ON CONFLICT with explicit update values) - deleteOne, deleteMany - Query executor with PG error mapping - Row mapper with snakeToCamel conversion - Helper utilities for column resolution Includes comprehensive PGlite integration tests covering all acceptance criteria: full CRUD cycle, pagination, select narrowing, UniqueConstraintError, ForeignKeyError, NotFoundError. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add relation loading with include option [DB-011] Implements the include option for find queries using a batched loading strategy (N+1 prevention via IN queries). - belongsTo (one) relations: loads single object by FK - hasMany (many) relations: loads array by parent PK - Batched loading: single IN query per relation - Select narrowing on included relations - Nested includes supported up to depth 2 - Integration with findOne, findMany, findManyAndCount PGlite integration tests cover belongsTo, hasMany, batched loading, include with select, and findManyAndCount with include. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add aggregation queries (count, aggregate, groupBy) [DB-012] Implements typed aggregation query methods: - count(table, { where? }): returns row count as number - aggregate(table, { _avg, _sum, _min, _max, _count, where? }): computes aggregation functions with typed results - groupBy(table, { by, _count?, _avg?, orderBy? }): groups rows with per-group aggregations All methods support where filters and generate parameterized SQL. Results use camelCase keys consistent with the rest of the query layer. PGlite integration tests cover count with filter, aggregate with all functions, groupBy with ordering, multi-column grouping, and combined aggregations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR #156 review — SQL injection, manyToMany, nested includes [db-010/011/012] B1: Validate orderBy columns in groupBy aggregation to prevent SQL injection. Underscore-prefixed columns are now validated against requested aggregation aliases; direction values are restricted to 'asc'/'desc' only. B2: Implement manyToMany relation loading via join tables (_through). Queries the join table first, collects target IDs, then batch-loads target rows and maps them back to parent rows. B3: Implement recursive nested includes (depth-2). Pass tablesRegistry through loadRelations so target table relations can be resolved for nested include: { posts: { include: { comments: true } } }. All fixes follow TDD: failing tests written first, then minimal implementation. --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 5 — Migration Differ + SQL Generator + Runner (db-013/014/015) (#157) * feat(db): add JSON snapshot format for schema state capture [db-013] * feat(db): add schema differ with rename detection and confidence scoring [db-013] * feat(db): add SQL migration generator with rollback support [db-014] * feat(db): add migration runner with history, drift detection, and ordering [db-015] * feat(db): add file management, module exports, and integration tests [db-015] --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> * feat(db): Phase 6 — CLI + Cache-Readiness Primitives (db-016, db-017) (#158) * feat(db): Phase 6 — CLI commands + cache-readiness primitives (db-016/db-017) - CLI: migrateDev, migrateDeploy, push, migrateStatus - Dry-run mode for migrateDev - Event bus for mutation events - Deterministic query fingerprinting - Plugin runner with beforeQuery/afterQuery hooks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): address PR #158 review — status crash, snapshot return, exports - B1: migrateStatus now calls createHistoryTable before querying applied - B2: migrateDev returns snapshot in result for caller persistence - B3: cli and plugin modules re-exported from main barrel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): Phase 7 — E2E acceptance test + type error quality (db-018/db-019) (#159) - Full E2E test from design doc Section 7 - Type inference assertions with @ts-expect-error - Branded error types for readable compiler messages - @vertz/db/diagnostic export - Performance validation under 100k instantiation budget Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * docs(db): add v1.0 retrospective and changeset - Post-implementation review covering all 7 phases - Changeset for @vertz/db minor release Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): add @types/node to resolve node:crypto in CI typecheck The @vertz/db package imports from node:crypto (runner.ts, fingerprint.ts) but was missing @types/node in devDependencies. This worked locally because Bun hoists @types/node from sibling packages, but failed in CI where tsc runs in an isolated Dagger container. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): increase test/hook timeouts for PGlite in CI PGlite uses WebAssembly which is slower in Dagger's container environment. Tests were timing out at the default 5s/10s limits. Set testTimeout and hookTimeout to 30s to accommodate CI's constrained resources. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(db): update design doc to reflect implementation deviations Comprehensive update to align the design doc with the actual v1.0 implementation. Documents all breaking changes, missing features, and additions discovered during adversarial API diff review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): stabilize PGlite tests in CI Disable file parallelism to prevent concurrent WASM instances from crashing, add retry:2 as safety net, fix PGlite version constraint. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): escape enum values and column defaults in DDL generation Enum values and column default values were interpolated directly into DDL strings without escaping single quotes. Malicious values like '); DROP TABLE users;-- would produce injectable SQL. Added escapeSqlString() helper that doubles internal single quotes, applied to enum_added, enum_altered, and column default expressions. Follow-up #22. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): guard deleteMany/updateMany against empty where clause Passing an empty where object ({}) to deleteMany or updateMany would generate SQL with no WHERE clause, silently affecting all rows. Added a runtime check that throws if the where object has no keys. Follow-up #19. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): preserve previous result when afterQuery plugin returns undefined If a plugin's afterQuery hook returned undefined (e.g. a logging-only plugin that observes but doesn't transform), the chain would pass undefined to subsequent plugins, causing runtime errors. Now defaults to the previous result when a plugin returns undefined: result = pluginResult !== undefined ? pluginResult : result; Follow-up #29. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): use dynamic PK column resolution in relation loader loadOneRelation, loadManyRelation, and loadManyToManyRelation all hard-coded 'id' as the primary key column name. Tables with non-standard PKs (e.g. 'code', 'slug') would silently fail to load relations. Now uses getPrimaryKeyColumns() to dynamically resolve the PK column name for both primary and target tables. Falls back to 'id' when no PK metadata is found for backward compatibility. Follow-up #14. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(db): split barrel export into tiered sub-paths Primary API at @vertz/db, SQL utilities at @vertz/db/sql, internal helpers at @vertz/db/internals, plugin system at @vertz/db/plugin. Reduces autocomplete noise and clarifies API surface. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add semantic error code enum (DbErrorCode) 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> * fix(db): address review — types condition ordering, remove duplicate exports - Reorder package.json exports to put types before import - Remove camelToSnake/snakeToCamel from internals (already in sql) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add OR/NOT filter operators with proper parenthesization [#41] Fix OR and AND branch parenthesization so multi-condition branches are wrapped in parens, preventing operator precedence issues in generated SQL. Add comprehensive tests: - OR/AND/NOT with multi-condition branches - Nested composition (OR>AND>NOT, NOT>OR, etc.) - Operator-based conditions in logical branches - Parameter numbering across nested operators - PGlite integration tests for OR, NOT, nested queries - SQL injection prevention test with parameterized OR Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add createRegistry() and d.entry() for ergonomic relations [VER-40] Reduce boilerplate for defining table registries with type-safe relations. - d.entry(table) helper: reduces `{ table, relations: {} }` to a single call - d.entry(table, relations): wraps table with relations in one call - createRegistry(tables, callback): typed ref factory with per-table FK validation - ref.TABLE.one('target', 'fkColumn') validates both table name and column at compile time - ref.TABLE.many('target', 'fkColumn') validates FK against target table columns - Tables without explicit relations are auto-wrapped (no boilerplate needed) - Full test coverage: runtime tests, type-level tests (@ts-expect-error negatives) - E2E test updated to use createRegistry() instead of manual wrapping Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add cursor-based pagination to findMany [#15] (#168) Implements cursor-based pagination as specified in design doc Section 1.7. - Add `cursor` and `take` options to SelectOptions, FindManyArgs, and TypedFindManyOptions interfaces - Single-column cursor generates `WHERE "col" > $N ORDER BY "col" ASC LIMIT $M` - Composite cursor uses row-value comparison: `(col1, col2) > ($1, $2)` - Cursor respects orderBy direction (> for asc, < for desc) - Cursor conditions AND with existing where filters - `take` aliases `limit` when paired with cursor - When no explicit orderBy, cursor columns are used for ORDER BY (default ASC) Tested with PGlite integration tests verifying multi-page traversal, cursor+where combinations, and desc ordering. Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add column-type-specific metadata to DefaultMeta extensions (#169) Downstream consumers (migration generators, query builders) need column-type-specific fields at the type level, not just at runtime. Previously DefaultMeta only carried boolean flags, while fields like length, precision, scale, enumName, enumValues, and format only existed in the runtime ColumnMetadata interface. Add four typed metadata extensions: - VarcharMeta<TLength> — carries length as a literal type - DecimalMeta<TPrecision, TScale> — carries precision and scale - EnumMeta<TName, TValues> — carries enumName and enumValues tuple - FormatMeta<TSqlType, TFormat> — carries format (e.g. 'email') Update d.varchar(), d.decimal(), d.enum(), and d.email() signatures to return these specific meta types instead of plain DefaultMeta. The metadata flows through modifier chains (nullable, unique, etc.) via the existing Omit & intersection pattern. Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): add dry-run mode to migration runner (#167) * feat(db): add dry-run mode to migration runner and deploy CLI [VER-21] - Add ApplyOptions and ApplyResult types to runner.apply() - When dryRun: true, return SQL statements without executing - Add dryRun option to migrateDeploy() with per-migration details - Update migration index exports with new types - Tests: dry-run returns SQL without modifying DB, output matches what non-dry-run would execute, CLI respects the flag Closes VER-21 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(db): skip createHistoryTable during dry-run in migrateDeploy Guard createHistoryTable behind !dryRun so dry-run mode is truly side-effect-free. Wrap getApplied in try/catch during dry-run to gracefully handle missing history table (returns empty applied list). Updated tests to assert that no DDL is executed during dry-run and added coverage for dry-run with an existing history table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat(db): wire generic type inference from schema to query results [DB-035] (#170) Thread TTables generic through DatabaseInstance method signatures so that findOne, findMany, create, update, upsert, delete, and other CRUD methods return properly typed results instead of Promise<unknown>. - Add TOptions generic to capture literal option shapes - Compute return types via FindResult<TTable, TOptions, TRelations> - Type data inputs using InsertInput/UpdateInput from table definition - Type where filters using FilterType<TColumns> - Add comprehensive .test-d.ts type flow verification tests - Remove 27 'as Record<string, unknown>' casts from E2E test Closes DB-035 Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com> --------- Co-authored-by: vertz-dev-core[bot] <2828081+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: vertz-dev-core[bot] <260431274+vertz-dev-core[bot]@users.noreply.github.com> Co-authored-by: vertz-tech-lead[bot] <2828099+vertz-tech-lead[bot]@users.noreply.github.com> Co-authored-by: vertz-devops[bot] <2832183+vertz-devops[bot]@users.noreply.github.com> Co-authored-by: vertz-devops[bot] <260554958+vertz-devops[bot]@users.noreply.github.com> Co-authored-by: vertz-dev-dx[bot] <2828112+vertz-dev-dx[bot]@users.noreply.github.com>
Summary
Phase 2 of @vertz/db: Error Hierarchy + Connection Management.
Tickets: DB-006, DB-007
DB-006: Typed Error Hierarchy + PG Error Parser
DbErrorbase + 7 subclasses (UniqueConstraint, ForeignKey, NotNull, CheckConstraint, NotFound, Connection, PoolExhausted)dbErrorToHttpError()adapter — maps DB errors to HTTP status codesDB-007: createDb() + Tenant Graph
createDb()factory returning typedDatabase<TTables>db.close()anddb.isHealthy()stubsQuality gates
Test plan