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:
@@ -1,10 +1,15 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { dirname } from 'node:path';
|
||||
import { runMigrations } from './migrate';
|
||||
|
||||
let instance: Database.Database | null = null;
|
||||
|
||||
export function getDb(path = process.env.DATABASE_PATH ?? './data/kochwas.db'): Database.Database {
|
||||
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.pragma('journal_mode = WAL');
|
||||
instance.pragma('foreign_keys = ON');
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import type Database from 'better-sqlite3';
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
// Vite bundles these SQL files as strings at build time. In dev this is also live.
|
||||
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 {
|
||||
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
|
||||
);`);
|
||||
|
||||
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(
|
||||
(db.prepare('SELECT name FROM schema_migration').all() as { name: string }[]).map((r) => r.name)
|
||||
);
|
||||
|
||||
for (const file of files) {
|
||||
if (applied.has(file)) continue;
|
||||
const sql = readFileSync(join(dir, file), 'utf8');
|
||||
for (const { name, sql } of orderedMigrations()) {
|
||||
if (applied.has(name)) continue;
|
||||
const tx = db.transaction(() => {
|
||||
db.exec(sql);
|
||||
db.prepare('INSERT INTO schema_migration(name) VALUES (?)').run(file);
|
||||
db.prepare('INSERT INTO schema_migration(name) VALUES (?)').run(name);
|
||||
});
|
||||
tx();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user