ded9671a82
- Replaced hardcoded migration port with dynamic port retrieval using `getWorkerPort()` in worker-cli.ts. - Updated context generator to clarify error handling comments. - Introduced timeout constants in ProcessManager for better maintainability. - Configured SQLite settings using constants for mmap size and cache size in DatabaseManager. - Added timeout constants for Git and NPM commands in BranchManager. - Enhanced error logging in FormattingService and SearchManager to provide more context on failures. - Removed deprecated silentDebug function and replaced its usage with logger.debug. - Updated tests to use dynamic worker port retrieval instead of hardcoded values.
175 lines
4.5 KiB
TypeScript
175 lines
4.5 KiB
TypeScript
import { Database } from 'bun:sqlite';
|
|
import { DATA_DIR, DB_PATH, ensureDir } from '../../shared/paths.js';
|
|
|
|
// SQLite configuration constants
|
|
const SQLITE_MMAP_SIZE_BYTES = 256 * 1024 * 1024; // 256MB
|
|
const SQLITE_CACHE_SIZE_PAGES = 10_000;
|
|
|
|
export interface Migration {
|
|
version: number;
|
|
up: (db: Database) => void;
|
|
down?: (db: Database) => void;
|
|
}
|
|
|
|
let dbInstance: Database | null = null;
|
|
|
|
/**
|
|
* SQLite Database singleton with migration support and optimized settings
|
|
*/
|
|
export class DatabaseManager {
|
|
private static instance: DatabaseManager;
|
|
private db: Database | null = null;
|
|
private migrations: Migration[] = [];
|
|
|
|
static getInstance(): DatabaseManager {
|
|
if (!DatabaseManager.instance) {
|
|
DatabaseManager.instance = new DatabaseManager();
|
|
}
|
|
return DatabaseManager.instance;
|
|
}
|
|
|
|
/**
|
|
* Register a migration to be run during initialization
|
|
*/
|
|
registerMigration(migration: Migration): void {
|
|
this.migrations.push(migration);
|
|
// Keep migrations sorted by version
|
|
this.migrations.sort((a, b) => a.version - b.version);
|
|
}
|
|
|
|
/**
|
|
* Initialize database connection with optimized settings
|
|
*/
|
|
async initialize(): Promise<Database> {
|
|
if (this.db) {
|
|
return this.db;
|
|
}
|
|
|
|
// Ensure the data directory exists
|
|
ensureDir(DATA_DIR);
|
|
|
|
this.db = new Database(DB_PATH, { create: true, readwrite: true });
|
|
|
|
// Apply optimized SQLite settings
|
|
this.db.run('PRAGMA journal_mode = WAL');
|
|
this.db.run('PRAGMA synchronous = NORMAL');
|
|
this.db.run('PRAGMA foreign_keys = ON');
|
|
this.db.run('PRAGMA temp_store = memory');
|
|
this.db.run(`PRAGMA mmap_size = ${SQLITE_MMAP_SIZE_BYTES}`);
|
|
this.db.run(`PRAGMA cache_size = ${SQLITE_CACHE_SIZE_PAGES}`);
|
|
|
|
// Initialize schema_versions table
|
|
this.initializeSchemaVersions();
|
|
|
|
// Run migrations
|
|
await this.runMigrations();
|
|
|
|
dbInstance = this.db;
|
|
return this.db;
|
|
}
|
|
|
|
/**
|
|
* Get the current database connection
|
|
*/
|
|
getConnection(): Database {
|
|
if (!this.db) {
|
|
throw new Error('Database not initialized. Call initialize() first.');
|
|
}
|
|
return this.db;
|
|
}
|
|
|
|
/**
|
|
* Execute a function within a transaction
|
|
*/
|
|
withTransaction<T>(fn: (db: Database) => T): T {
|
|
const db = this.getConnection();
|
|
const transaction = db.transaction(fn);
|
|
return transaction(db);
|
|
}
|
|
|
|
/**
|
|
* Close the database connection
|
|
*/
|
|
close(): void {
|
|
if (this.db) {
|
|
this.db.close();
|
|
this.db = null;
|
|
dbInstance = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the schema_versions table
|
|
*/
|
|
private initializeSchemaVersions(): void {
|
|
if (!this.db) return;
|
|
|
|
this.db.run(`
|
|
CREATE TABLE IF NOT EXISTS schema_versions (
|
|
id INTEGER PRIMARY KEY,
|
|
version INTEGER UNIQUE NOT NULL,
|
|
applied_at TEXT NOT NULL
|
|
)
|
|
`);
|
|
}
|
|
|
|
/**
|
|
* Run all pending migrations
|
|
*/
|
|
private async runMigrations(): Promise<void> {
|
|
if (!this.db) return;
|
|
|
|
const query = this.db.query('SELECT version FROM schema_versions ORDER BY version');
|
|
const appliedVersions = query.all().map((row: any) => row.version);
|
|
|
|
const maxApplied = appliedVersions.length > 0 ? Math.max(...appliedVersions) : 0;
|
|
|
|
for (const migration of this.migrations) {
|
|
if (migration.version > maxApplied) {
|
|
console.log(`Applying migration ${migration.version}...`);
|
|
|
|
const transaction = this.db.transaction(() => {
|
|
migration.up(this.db!);
|
|
|
|
const insertQuery = this.db!.query('INSERT INTO schema_versions (version, applied_at) VALUES (?, ?)');
|
|
insertQuery.run(migration.version, new Date().toISOString());
|
|
});
|
|
|
|
transaction();
|
|
console.log(`Migration ${migration.version} applied successfully`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current schema version
|
|
*/
|
|
getCurrentVersion(): number {
|
|
if (!this.db) return 0;
|
|
|
|
const query = this.db.query('SELECT MAX(version) as version FROM schema_versions');
|
|
const result = query.get() as { version: number } | undefined;
|
|
|
|
return result?.version || 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the global database instance (for compatibility)
|
|
*/
|
|
export function getDatabase(): Database {
|
|
if (!dbInstance) {
|
|
throw new Error('Database not initialized. Call DatabaseManager.getInstance().initialize() first.');
|
|
}
|
|
return dbInstance;
|
|
}
|
|
|
|
/**
|
|
* Initialize and get database manager
|
|
*/
|
|
export async function initializeDatabase(): Promise<Database> {
|
|
const manager = DatabaseManager.getInstance();
|
|
return await manager.initialize();
|
|
}
|
|
|
|
export { Database }; |