DatabaseManager.ts•4.5 kB
import Database, { Database as Db } from 'better-sqlite3';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url'; // Added for ES Module dirname
import { ConfigurationManager } from '../config/ConfigurationManager.js';
import { logger } from '../utils/logger.js'; // Assuming logger exists
export class DatabaseManager {
    private static instance: DatabaseManager;
    private db!: Db; // Added definite assignment assertion
    private dbPath: string;
    private constructor() {
        const configManager = ConfigurationManager.getInstance();
        // TODO: Get path from configManager once implemented
        // For now, use a default relative path
        this.dbPath = configManager.getDatabasePath(); // Assuming this method exists
        logger.info(`[DatabaseManager] Using database path: ${this.dbPath}`);
        this.initializeDatabase();
    }
    public static getInstance(): DatabaseManager {
        if (!DatabaseManager.instance) {
            DatabaseManager.instance = new DatabaseManager();
        }
        return DatabaseManager.instance;
    }
    private initializeDatabase(): void {
        try {
            const dbDir = path.dirname(this.dbPath);
            if (!fs.existsSync(dbDir)) {
                logger.info(`[DatabaseManager] Creating database directory: ${dbDir}`);
                fs.mkdirSync(dbDir, { recursive: true });
            }
            const dbExists = fs.existsSync(this.dbPath);
            logger.info(`[DatabaseManager] Database file ${this.dbPath} exists: ${dbExists}`);
            // Pass a wrapper function for verbose logging to match expected signature
            this.db = new Database(this.dbPath, {
                verbose: (message?: any, ...additionalArgs: any[]) => logger.debug({ sql: message, params: additionalArgs }, 'SQLite Query')
            });
            // Always enable foreign keys and WAL mode upon connection
            this.db.pragma('foreign_keys = ON');
            // Assert type for pragma result
            const journalMode = this.db.pragma('journal_mode = WAL') as [{ journal_mode: string }];
            logger.info(`[DatabaseManager] Journal mode set to: ${journalMode[0]?.journal_mode ?? 'unknown'}`);
            // Check if initialization is needed (simple check: does 'projects' table exist?)
            const tableCheck = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='projects';").get();
            if (!tableCheck) {
                logger.info('[DatabaseManager] Projects table not found. Initializing schema...');
                // Revert to looking for schema.sql relative to the compiled JS file's directory (__dirname)
                const __filename = fileURLToPath(import.meta.url);
                const __dirname = path.dirname(__filename); // This will be dist/db when running compiled code
                const schemaPath = path.join(__dirname, 'schema.sql');
                logger.info(`[DatabaseManager] Looking for schema file at: ${schemaPath}`);
                if (!fs.existsSync(schemaPath)) {
                    logger.error(`[DatabaseManager] Schema file not found at ${schemaPath}. Ensure build process copied it correctly.`);
                    throw new Error(`Schema file not found at ${schemaPath}. Build process might be incomplete.`);
                }
                const schemaSql = fs.readFileSync(schemaPath, 'utf8');
                this.db.exec(schemaSql);
                logger.info('[DatabaseManager] Database schema initialized successfully.');
            } else {
                logger.info('[DatabaseManager] Database schema already initialized.');
            }
        } catch (error) {
            logger.error('[DatabaseManager] Failed to initialize database:', error);
            // Propagate the error to prevent the server from starting with a broken DB connection
            throw error;
        }
    }
    public getDb(): Db {
        if (!this.db) {
            // This should ideally not happen if constructor succeeded
            logger.error('[DatabaseManager] Database connection not available.');
            throw new Error('Database connection not available.');
        }
        return this.db;
    }
    // Optional: Add a close method for graceful shutdown
    public closeDb(): void {
        if (this.db) {
            this.db.close();
            logger.info('[DatabaseManager] Database connection closed.');
        }
    }
}