BillTracker/db/kysely.cts

76 lines
2.7 KiB
TypeScript

import type { Db } from '../types/db';
// Kysely as a TYPE-SAFE QUERY BUILDER over the existing synchronous better-sqlite3
// connection. The whole app is sync, and Kysely's `.execute()` is async — so we do
// NOT execute through Kysely. Instead we build the query (fully type-checked against
// the schema below), `.compile()` it to `{ sql, parameters }`, and run it
// synchronously via the shared connection. This gives compile-time safety
// (columns, user_id / deleted_at scoping, types) with zero change to the sync model.
//
// Grow the `Database` schema as queries are migrated. See docs/CODE_STANDARDS.md.
const { Kysely, SqliteDialect } = require('kysely') as typeof import('kysely');
const BetterSqlite3 = require('better-sqlite3');
const { getDb } = require('./database.cts');
// ── Schema types (subset; extend as adopted) ─────────────────────────────────
export interface CategoriesTable {
id: number;
user_id: number;
name: string;
sort_order: number | null;
spending_enabled: number;
group_id: number | null;
deleted_at: string | null;
is_seeded: number;
created_at: string;
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
// factory is never invoked because we never `.execute()` — only `.compile()`.
let _kysely: import('kysely').Kysely<Database> | null = null;
function kysely(): import('kysely').Kysely<Database> {
if (!_kysely) {
_kysely = new Kysely<Database>({
dialect: new SqliteDialect({ database: () => new BetterSqlite3(':memory:') }),
});
}
return _kysely;
}
type Compilable = { compile(): { sql: string; parameters: readonly unknown[] } };
/** Compile a Kysely query and run it synchronously (returns all rows). */
function all<R = any>(query: Compilable): R[] {
const c = query.compile();
return (getDb() as Db).prepare(c.sql).all(...(c.parameters as any[])) as R[];
}
/** Compile a Kysely query and run it synchronously (returns the first row). */
function get<R = any>(query: Compilable): R | undefined {
const c = query.compile();
return (getDb() as Db).prepare(c.sql).get(...(c.parameters as any[])) as R | undefined;
}
/** Compile a Kysely mutation and run it synchronously (INSERT/UPDATE/DELETE). */
function run(query: Compilable) {
const c = query.compile();
return (getDb() as Db).prepare(c.sql).run(...(c.parameters as any[]));
}
module.exports = { kysely, all, get, run };