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:
83
src/main/db/index.ts
Normal file
83
src/main/db/index.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user