This commit is contained in:
Colby McHenry
2026-01-18 16:25:00 -06:00
parent 08ccabb5a9
commit cc6e7a5c89
57 changed files with 23315 additions and 1 deletions
+154
View File
@@ -0,0 +1,154 @@
/**
* Database Layer
*
* Handles SQLite database initialization and connection management.
*/
import Database from 'better-sqlite3';
import * as fs from 'fs';
import * as path from 'path';
import { SchemaVersion } from '../types';
import { runMigrations, getCurrentVersion, CURRENT_SCHEMA_VERSION } from './migrations';
/**
* Database connection wrapper with lifecycle management
*/
export class DatabaseConnection {
private db: Database.Database;
private dbPath: string;
private constructor(db: Database.Database, dbPath: string) {
this.db = db;
this.dbPath = dbPath;
}
/**
* Initialize a new database at the given path
*/
static initialize(dbPath: string): DatabaseConnection {
// Ensure parent directory exists
const dir = path.dirname(dbPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create and configure database
const db = new Database(dbPath);
// Enable foreign keys and WAL mode for better performance
db.pragma('foreign_keys = ON');
db.pragma('journal_mode = WAL');
// Run schema initialization
const schemaPath = path.join(__dirname, 'schema.sql');
const schema = fs.readFileSync(schemaPath, 'utf-8');
db.exec(schema);
return new DatabaseConnection(db, dbPath);
}
/**
* Open an existing database
*/
static open(dbPath: string): DatabaseConnection {
if (!fs.existsSync(dbPath)) {
throw new Error(`Database not found: ${dbPath}`);
}
const db = new Database(dbPath);
// Enable foreign keys and WAL mode
db.pragma('foreign_keys = ON');
db.pragma('journal_mode = WAL');
// Check and run migrations if needed
const conn = new DatabaseConnection(db, dbPath);
const currentVersion = getCurrentVersion(db);
if (currentVersion < CURRENT_SCHEMA_VERSION) {
runMigrations(db, currentVersion);
}
return conn;
}
/**
* Get the underlying database instance
*/
getDb(): Database.Database {
return this.db;
}
/**
* Get database file path
*/
getPath(): string {
return this.dbPath;
}
/**
* Get current schema version
*/
getSchemaVersion(): SchemaVersion | null {
const row = this.db
.prepare('SELECT version, applied_at, description FROM schema_versions ORDER BY version DESC LIMIT 1')
.get() as { version: number; applied_at: number; description: string | null } | undefined;
if (!row) return null;
return {
version: row.version,
appliedAt: row.applied_at,
description: row.description ?? undefined,
};
}
/**
* Execute a function within a transaction
*/
transaction<T>(fn: () => T): T {
return this.db.transaction(fn)();
}
/**
* Get database file size in bytes
*/
getSize(): number {
const stats = fs.statSync(this.dbPath);
return stats.size;
}
/**
* Optimize database (vacuum and analyze)
*/
optimize(): void {
this.db.exec('VACUUM');
this.db.exec('ANALYZE');
}
/**
* Close the database connection
*/
close(): void {
this.db.close();
}
/**
* Check if the database connection is open
*/
isOpen(): boolean {
return this.db.open;
}
}
/**
* Default database filename
*/
export const DATABASE_FILENAME = 'codegraph.db';
/**
* Get the default database path for a project
*/
export function getDatabasePath(projectRoot: string): string {
return path.join(projectRoot, '.codegraph', DATABASE_FILENAME);
}