fix(db): narrow the status column instead of asserting the row (#308)
`.$castTo<AdminItemRow>()` / `.$castTo<PublicItemRow>()` at the two list-query call sites replaced the entire result type with an assertion rather than narrowing the one column that actually disagreed, which meant a projection that silently lost a column would still compile — exactly the failure mode this task exists to close, and the opposite of what the commit body claims. Fixed at the source instead: `itemSelect.ts` now defines `ItemsWithStatus`/`ItemDB`, narrowing `items.status` from the schema mirror's `Generated<string>` (a CHECK-constrained text column, so `kysely-codegen` has no literal union to give it) to `Generated<ItemStatus>`, and builds `ItemContext`, `adminItemQuery()` and `publicItemQuery()` from an `itemDb` typed with `ItemDB` instead of `db`/`DB`. Both `$castTo` calls and their comments are gone; the `AdminItemRow[]` / `PublicItemRow[]` annotations at the two call sites now check for real. Verified by temporarily dropping a column from `adminItemQuery`'s projection: the `AdminItemRow[]` assignment failed to compile as expected, confirming the guarantee actually holds. Also corrected two now-false statements left over from the conversion: `db-kysely/CONVENTIONS.md`'s worked-example section said `buildItemFilterSql` was "still raw `pg`" and that converting it "would put a second copy of a live function in `src/` that nothing calls" — both untrue since #308 shipped it as `itemFilterExpressions`. And two doc comments in `itemSelect.ts` still named the deleted `PUBLIC_ITEM_SELECT`/`ADMIN_ITEM_SELECT` constants instead of the functions that replaced them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -137,15 +137,9 @@ router.get('/items', asyncRoute(async (req: Request, res: Response) => {
|
||||
// sixteen lines in itemFilters.ts explained why that was safe. The clauses
|
||||
// are Kysely expressions now: a value cannot reach the SQL text, because the
|
||||
// types do not let it.
|
||||
// `.$castTo` narrows `status` from the schema mirror's generic `string` (the
|
||||
// column is a CHECK-constrained text column, not a native Postgres enum, so
|
||||
// kysely-codegen has no literal union to give it) to the app-level
|
||||
// `ItemStatus` the CHECK constraint actually enforces. Every other field on
|
||||
// AdminItemRow already matches the projection without a cast.
|
||||
const rows: AdminItemRow[] = await adminItemQuery()
|
||||
.where((eb) => eb.and(itemFilterExpressions(eb, filters, null)))
|
||||
.orderBy('i.created_at', 'desc')
|
||||
.$castTo<AdminItemRow>()
|
||||
.execute();
|
||||
|
||||
res.json(rows);
|
||||
|
||||
Reference in New Issue
Block a user