refactor(backend): type the admin item queries, and fix the stale status union (#159)
Linting / lint (pull_request) Successful in 2m11s
SonarQube Analysis / sonarqube (pull_request) Failing after 5m2s

admin.ts has no untyped reads left. Typed sites go from 43 to 49.

New ItemRecord in itemSelect.ts for the bare `items` row that `RETURNING *` gives back. Deliberately not AdminItemRow: that describes a select which joins the category and adds images and tags as subqueries, so typing a RETURNING * as it would promise three fields the result does not contain. Three shapes for one table, because three different queries return three different things.

The typing found a real defect on its first run, which is the case for doing this at all.

`ItemStatus` in types.ts was `'available' | 'reserved' | 'sold'`. The database has four values and defaults to 'pending' — items have arrived pending since #90. itemFilters.ts declared its own copy that had all four and was correct. Two declarations of one union with nothing connecting them: one went stale and nothing said so.

It was invisible while query rows were `any`. Typing them turned `if (status === 'pending')` in admin.ts into TS2367, "this comparison appears to be unintentional because the types 'ItemStatus' and '\"pending\"' have no overlap" — a compiler telling us the unpublish route's guard could never be true, against a type that was simply wrong.

Confirmed against the database rather than by picking the more plausible of the two declarations: `SELECT DISTINCT status FROM items` returns pending, available, reserved and sold.

Fixed by removing the duplication rather than by patching both copies. types.ts now holds the only declaration and itemFilters.ts imports it, re-exporting so its existing importers are unaffected. Patching both would have left the next drift free to happen the same way.

Verified: tsc clean, unit 254/254, integration 238/238, and backend lint unchanged — the four warnings it reports are identical to those on main with these changes stashed, so none of them are new.

Refs #159
This commit is contained in:
2026-08-24 14:15:27 -05:00
parent 36c9fe9227
commit d43e2d5871
4 changed files with 55 additions and 10 deletions
+21
View File
@@ -87,3 +87,24 @@ export interface AdminItemRow extends ItemRowBase {
sold_at: Date | null;
paypal_order_id: string | null;
}
/**
* A bare `items` row, as `RETURNING *` gives it back.
*
* Distinct from the two select rows above and not interchangeable with them:
* this is the table, so it has no category_name, no images and no tags. Those
* come from the joins and subqueries the selects add, and typing a RETURNING *
* as AdminItemRow would promise three fields that are not in the result.
*/
export interface ItemRecord {
id: number;
name: string;
description: string | null;
price_cents: number;
status: ItemStatus;
reserved_until: Date | null;
sold_at: Date | null;
paypal_order_id: string | null;
created_at: Date;
category_id: number | null;
}