/** * Database connection module with SQLite support * Uses better-sqlite3 for synchronous operations * Note: SQLCipher support requires special compilation and is enabled via configuration */ const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); let db = null; let dbPath = null; function escapeSqliteString(value) { return String(value || '').replace(/'/g, "''"); } /** * Initialize database connection * @param {string} databasePath - Path to the database file * @param {Object} options - Database options * @returns {Database} Database instance */ function initDatabase(databasePath, options = {}) { if (db) { return db; } dbPath = databasePath; // Ensure database directory exists const dbDir = path.dirname(databasePath); if (!fs.existsSync(dbDir)) { fs.mkdirSync(dbDir, { recursive: true }); } // Initialize database with options const dbOptions = { fileMustExist: false, timeout: options.timeout || 5000, }; // Add verbose if it's a function if (options.verbose && typeof options.verbose === 'function') { dbOptions.verbose = options.verbose; } db = new Database(databasePath, dbOptions); // SQLCipher support (optional) if (options.sqlcipherKey) { const escapedKey = escapeSqliteString(options.sqlcipherKey); db.pragma(`key = '${escapedKey}'`); if (options.cipherCompatibility) { db.pragma(`cipher_compatibility = ${Number(options.cipherCompatibility)}`); } if (options.kdfIter) { db.pragma(`kdf_iter = ${Number(options.kdfIter)}`); } } // Enable WAL mode for better concurrency if (options.walMode !== false) { db.pragma('journal_mode = WAL'); } // Set reasonable defaults db.pragma('synchronous = NORMAL'); db.pragma('cache_size = -64000'); // 64MB cache db.pragma('temp_store = MEMORY'); db.pragma('foreign_keys = ON'); console.log('✅ Database connected:', databasePath); return db; } /** * Get database instance * @returns {Database|null} Database instance or null if not initialized */ function getDatabase() { return db; } /** * Close database connection */ function closeDatabase() { if (db) { try { db.close(); console.log('✅ Database connection closed'); } catch (error) { console.error('Error closing database:', error); } finally { db = null; dbPath = null; } } } /** * Check if database is initialized * @returns {boolean} */ function isDatabaseInitialized() { return db !== null && db.open; } /** * Get database path * @returns {string|null} */ function getDatabasePath() { return dbPath; } /** * Create a backup of the database * @param {string} backupPath - Path to backup file * @returns {Promise} */ async function backupDatabase(backupPath) { if (!db) { throw new Error('Database not initialized'); } return new Promise((resolve, reject) => { try { const backup = db.backup(backupPath); backup.step(-1); // Copy all pages at once backup.finish(); console.log('✅ Database backup created:', backupPath); resolve(); } catch (error) { reject(error); } }); } /** * Execute a transaction * @param {Function} fn - Function to execute in transaction * @returns {*} Result of the function */ function transaction(fn) { if (!db) { throw new Error('Database not initialized'); } return db.transaction(fn)(); } module.exports = { initDatabase, getDatabase, closeDatabase, isDatabaseInitialized, getDatabasePath, backupDatabase, transaction };