feat: US-002 — SQLite + Drizzle ORM schema and migrations

- Install better-sqlite3 + drizzle-orm as runtime deps; drizzle-kit + @types/better-sqlite3 as devDeps
- Define 5 tables in src/main/db/schema.ts: clients, projects, tasks, checkpoints, notes
- All IDs are TEXT (UUID); types inferred via InferSelectModel/InferInsertModel
- initDb() in src/main/db/index.ts opens adiuva.db at app.getPath('userData'), runs CREATE TABLE IF NOT EXISTS (non-destructive push), enables WAL mode
- Call initDb() in main process app ready handler
- Externalize better-sqlite3 in vite.main.config.mts; add AutoUnpackNativesPlugin to forge.config.ts
- Add drizzle.config.ts for drizzle-kit CLI support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto Musso
2026-02-19 16:37:24 +01:00
parent bc778f9a8f
commit 8a869f90ad
8 changed files with 1488 additions and 26 deletions

83
src/main/db/index.ts Normal file
View File

@@ -0,0 +1,83 @@
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { app } from 'electron';
import path from 'node:path';
import * as schema from './schema';
// SQL to create all tables if they don't exist (non-destructive push strategy)
const MIGRATION_SQL = `
CREATE TABLE IF NOT EXISTS clients (
id TEXT PRIMARY KEY,
parent_id TEXT,
name TEXT NOT NULL,
industry TEXT,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
client_id TEXT,
name TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active',
ai_summary TEXT,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
project_id TEXT,
title TEXT NOT NULL,
description TEXT,
status TEXT NOT NULL DEFAULT 'todo',
priority TEXT NOT NULL DEFAULT 'medium',
assignee TEXT,
due_date INTEGER,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS checkpoints (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL,
title TEXT NOT NULL,
date INTEGER NOT NULL,
is_ai_suggested INTEGER NOT NULL DEFAULT 0,
is_approved INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS notes (
id TEXT PRIMARY KEY,
project_id TEXT,
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
`;
type DbInstance = ReturnType<typeof drizzle<typeof schema>>;
let dbInstance: DbInstance | null = null;
export function initDb(): DbInstance {
const userDataPath = app.getPath('userData');
const dbPath = path.join(userDataPath, 'adiuva.db');
const sqlite = new Database(dbPath);
// Enable WAL mode for better concurrent read performance
sqlite.pragma('journal_mode = WAL');
// Run non-destructive migrations on every start
sqlite.exec(MIGRATION_SQL);
dbInstance = drizzle(sqlite, { schema });
return dbInstance;
}
export function getDb(): DbInstance {
if (!dbInstance) {
throw new Error('Database not initialized. Call initDb() first.');
}
return dbInstance;
}