/** * SQLite Database Access Module * Provides cross-platform database operations for Antigravity state. * * Uses better-sqlite3 for: * - Windows compatibility (no CLI dependency) * - Native performance * - Synchronous API (simple error handling) * * Includes auto-rebuild capability for handling Node.js version updates * that cause native module incompatibility. */ import { createRequire } from 'module'; import { ANTIGRAVITY_DB_PATH } from '../constants.js'; import { isModuleVersionError, attemptAutoRebuild, clearRequireCache } from '../utils/native-module-helper.js'; import { logger } from '../utils/logger.js'; import { NativeModuleError } from '../errors.js'; const require = createRequire(import.meta.url); // Lazy-loaded Database constructor let Database = null; let moduleLoadError = null; /** * Load the better-sqlite3 module with auto-rebuild on version mismatch * Uses synchronous require to maintain API compatibility * @returns {Function} The Database constructor * @throws {Error} If module cannot be loaded even after rebuild */ function loadDatabaseModule() { // Return cached module if already loaded if (Database) return Database; // Re-throw cached error if previous load failed permanently if (moduleLoadError) throw moduleLoadError; try { Database = require('better-sqlite3'); return Database; } catch (error) { if (isModuleVersionError(error)) { logger.warn('[Database] Native module version mismatch detected'); if (attemptAutoRebuild(error)) { // Clear require cache and retry try { const resolvedPath = require.resolve('better-sqlite3'); // Clear the module and all its dependencies from cache clearRequireCache(resolvedPath, require.cache); Database = require('better-sqlite3'); logger.success('[Database] Module reloaded successfully after rebuild'); return Database; } catch (retryError) { // Rebuild succeeded but reload failed - user needs to restart moduleLoadError = new NativeModuleError( 'Native module rebuild completed. Please restart the server to apply the fix.', true, // rebuildSucceeded true // restartRequired ); logger.info('[Database] Rebuild succeeded - server restart required'); throw moduleLoadError; } } else { moduleLoadError = new NativeModuleError( 'Failed to auto-rebuild native module. Please run manually:\n' + ' npm rebuild better-sqlite3\n' + 'Or if using npx, find the package location in the error and run:\n' + ' cd /path/to/better-sqlite3 && npm rebuild', false, // rebuildSucceeded false // restartRequired ); throw moduleLoadError; } } // Non-version-mismatch error, just throw it throw error; } } /** * Query Antigravity database for authentication status * @param {string} [dbPath] - Optional custom database path * @returns {Object} Parsed auth data with apiKey, email, name, etc. * @throws {Error} If database doesn't exist, query fails, or no auth status found */ export function getAuthStatus(dbPath = ANTIGRAVITY_DB_PATH) { const Db = loadDatabaseModule(); let db; try { // Open database in read-only mode db = new Db(dbPath, { readonly: true, fileMustExist: true }); // Prepare and execute query const stmt = db.prepare( "SELECT value FROM ItemTable WHERE key = 'antigravityAuthStatus'" ); const row = stmt.get(); if (!row || !row.value) { throw new Error('No auth status found in database'); } // Parse JSON value const authData = JSON.parse(row.value); if (!authData.apiKey) { throw new Error('Auth data missing apiKey field'); } return authData; } catch (error) { // Enhance error messages for common issues if (error.code === 'SQLITE_CANTOPEN') { throw new Error( `Database not found at ${dbPath}. ` + 'Make sure Antigravity is installed and you are logged in.' ); } // Re-throw with context if not already our error if (error.message.includes('No auth status') || error.message.includes('missing apiKey')) { throw error; } // Re-throw native module errors from loadDatabaseModule without wrapping if (error instanceof NativeModuleError) { throw error; } throw new Error(`Failed to read Antigravity database: ${error.message}`); } finally { // Always close database connection if (db) { db.close(); } } } /** * Check if database exists and is accessible * @param {string} [dbPath] - Optional custom database path * @returns {boolean} True if database exists and can be opened */ export function isDatabaseAccessible(dbPath = ANTIGRAVITY_DB_PATH) { let db; try { const Db = loadDatabaseModule(); db = new Db(dbPath, { readonly: true, fileMustExist: true }); return true; } catch { return false; } finally { if (db) { db.close(); } } } export default { getAuthStatus, isDatabaseAccessible };