feat(db): bundle migration SQL via Vite glob + auto-create data dirs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 15:41:20 +02:00
parent 0f9aabe76b
commit ea9a79226a
2 changed files with 21 additions and 13 deletions

View File

@@ -1,10 +1,15 @@
import Database from 'better-sqlite3'; import Database from 'better-sqlite3';
import { mkdirSync } from 'node:fs';
import { dirname } from 'node:path';
import { runMigrations } from './migrate'; import { runMigrations } from './migrate';
let instance: Database.Database | null = null; let instance: Database.Database | null = null;
export function getDb(path = process.env.DATABASE_PATH ?? './data/kochwas.db'): Database.Database { export function getDb(path = process.env.DATABASE_PATH ?? './data/kochwas.db'): Database.Database {
if (instance) return instance; if (instance) return instance;
mkdirSync(dirname(path), { recursive: true });
const imageDir = process.env.IMAGE_DIR ?? './data/images';
mkdirSync(imageDir, { recursive: true });
instance = new Database(path); instance = new Database(path);
instance.pragma('journal_mode = WAL'); instance.pragma('journal_mode = WAL');
instance.pragma('foreign_keys = ON'); instance.pragma('foreign_keys = ON');

View File

@@ -1,7 +1,17 @@
import type Database from 'better-sqlite3'; import type Database from 'better-sqlite3';
import { readdirSync, readFileSync } from 'node:fs';
import { join, dirname } from 'node:path'; // Vite bundles these SQL files as strings at build time. In dev this is also live.
import { fileURLToPath } from 'node:url'; const migrations = import.meta.glob('./migrations/*.sql', {
eager: true,
query: '?raw',
import: 'default'
}) as Record<string, string>;
function orderedMigrations(): { name: string; sql: string }[] {
return Object.entries(migrations)
.map(([path, sql]) => ({ name: path.split('/').pop()!, sql }))
.sort((a, b) => a.name.localeCompare(b.name));
}
export function runMigrations(db: Database.Database): void { export function runMigrations(db: Database.Database): void {
db.exec(`CREATE TABLE IF NOT EXISTS schema_migration ( db.exec(`CREATE TABLE IF NOT EXISTS schema_migration (
@@ -9,22 +19,15 @@ export function runMigrations(db: Database.Database): void {
applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);`); );`);
const here = dirname(fileURLToPath(import.meta.url));
const dir = join(here, 'migrations');
const files = readdirSync(dir)
.filter((f) => f.endsWith('.sql'))
.sort();
const applied = new Set( const applied = new Set(
(db.prepare('SELECT name FROM schema_migration').all() as { name: string }[]).map((r) => r.name) (db.prepare('SELECT name FROM schema_migration').all() as { name: string }[]).map((r) => r.name)
); );
for (const file of files) { for (const { name, sql } of orderedMigrations()) {
if (applied.has(file)) continue; if (applied.has(name)) continue;
const sql = readFileSync(join(dir, file), 'utf8');
const tx = db.transaction(() => { const tx = db.transaction(() => {
db.exec(sql); db.exec(sql);
db.prepare('INSERT INTO schema_migration(name) VALUES (?)').run(file); db.prepare('INSERT INTO schema_migration(name) VALUES (?)').run(name);
}); });
tx(); tx();
} }