From d9545d6fd5909169924c7ef55de466913e24676d Mon Sep 17 00:00:00 2001 From: null Date: Mon, 6 Jul 2026 16:25:30 -0500 Subject: [PATCH] refactor(categories): adopt Kysely for the /groups query (real production use) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proves the POC in production: the category-groups list is now built with the typed Kysely builder (category_groups added to the schema; COLLATE NOCASE via sql``) and executed synchronously via all(). Byte-identical behavior — categoryGroups test 4/4, suite 248/248, typecheck:server clean. Establishes the migration recipe for rolling typed SQL out query-by-query. Co-Authored-By: Claude Opus 4.8 --- db/kysely.cts | 11 +++++++++++ routes/categories.cts | 23 +++++++++++------------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/db/kysely.cts b/db/kysely.cts index 2a548dc..3146b32 100644 --- a/db/kysely.cts +++ b/db/kysely.cts @@ -27,8 +27,19 @@ export interface CategoriesTable { updated_at: string; } +export interface CategoryGroupsTable { + id: number; + user_id: number; + name: string; + sort_order: number; + is_seeded: number; + created_at: string; + updated_at: string; +} + export interface Database { categories: CategoriesTable; + category_groups: CategoryGroupsTable; } // Build-only Kysely instance. The dialect supplies the SQLite compiler; its driver diff --git a/routes/categories.cts b/routes/categories.cts index b50fd21..7613ab9 100644 --- a/routes/categories.cts +++ b/routes/categories.cts @@ -5,6 +5,8 @@ const { ValidationError, NotFoundError, ConflictError } = require('../utils/apiE const router = express.Router(); const { getDb, ensureUserDefaultCategories } = require('../db/database.cts'); const { accountingActiveSql } = require('../services/paymentAccountingService.cts'); +const { kysely, all } = require('../db/kysely.cts'); +const { sql } = require('kysely'); // Maps a SQLite UNIQUE-constraint violation to a friendly 409; re-throws anything // else so the terminal handler returns a safe 500. @@ -198,19 +200,16 @@ router.put('/:id', (req: Req, res: Res) => { // ── Category groups ────────────────────────────────────────────────────────── -// GET /api/category-groups +// GET /api/category-groups — typed via Kysely (built, then run synchronously). router.get('/groups', (req: Req, res: Res) => { - const db = getDb(); - const groups = db - .prepare( - ` - SELECT id, name, sort_order, created_at, updated_at - FROM category_groups - WHERE user_id = ? - ORDER BY sort_order ASC, name COLLATE NOCASE ASC - `, - ) - .all(req.user.id); + const groups = all( + kysely() + .selectFrom('category_groups') + .select(['id', 'name', 'sort_order', 'created_at', 'updated_at']) + .where('user_id', '=', req.user.id) + .orderBy('sort_order', 'asc') + .orderBy(sql`name collate nocase`, 'asc'), + ); res.json(groups); });