mirror of
https://github.com/rajnandan1/kener.git
synced 2026-06-23 04:10:22 +00:00
2.9 KiB
2.9 KiB
Database & Migrations
Overview
Kener uses Knex.js as a database abstraction layer supporting three engines: SQLite (default, via better-sqlite3), PostgreSQL (pg), and MySQL (mysql2). The engine is selected at runtime from the DATABASE_URL environment variable prefix (sqlite://, postgresql://, mysql://).
Architecture
Connection & Config
| File | Responsibility |
|---|---|
knexfile.ts |
Parses DATABASE_URL, selects client, exports Knex config |
src/lib/server/db/db.ts |
Singleton Knex instance — all app code imports from here |
src/lib/server/db/dbimpl.js |
High-level DB methods (wraps repositories) |
src/lib/server/db/repositories/ |
Per-domain query classes extending BaseRepository |
Repository Pattern
Each repository lives in src/lib/server/db/repositories/<domain>.ts, extends BaseRepository (which receives the Knex instance), and exposes typed async methods. Queries use Knex query builder — never raw SQL (except index creation wrapped in try/catch).
Migrations
Migrations live in migrations/ as TypeScript files with naming convention YYYYMMDDHHMMSS_<description>.ts.
Key patterns observed across all existing migrations:
- Idempotency guards:
knex.schema.hasTable/knex.schema.hasColumnbeforecreateTable/alterTable. - Column types: Knex abstractions only (
.string(),.integer(),.text(),.float()). No raw DDL. - Defaults:
.defaultTo()+.notNullable()for YES/NO string flags (e.g.,is_hidden,is_owner). - Data seeding in migrations: Standard Knex query builder (
.orderBy().first(),.update()) — works on all three engines. - Index creation: Wrapped in
try/catchbecauseCREATE INDEX IF NOT EXISTSisn't portable. - PostgreSQL insert returning: Some repos branch on
GetDbType() === "postgresql"to use.returning("*"), with fallback to re-read by inserted ID for SQLite/MySQL.
Edge Cases and Gotchas
- SQLite requires
useNullAsDefault: truein Knex config. - PostgreSQL
INSERT ... RETURNING *is not supported by SQLite/MySQL — branch onGetDbType(). knex.fn.now()is the portable way to set timestamps; never useNOW()ordatetime('now').- String-based YES/NO flags (not booleans) are the project convention for flag columns.
Design Decisions
- YES/NO strings over booleans: Consistent with existing
is_hidden,include_degraded_in_downtime, etc. Avoids SQLite boolean quirks. - hasColumn guard in migrations: Allows re-running migrations safely without failure on already-applied columns.
- Owner flag (
is_owner): Set during migration on the first user byid ASC. Only one user should be owner; enforced at application level, not DB constraint.