Files
antigravity-claude-proxy/src/auth/database.js
jgor20 e29cd5fa9d refactor(auth): use NativeModuleError for native module load failures
Replace generic Error instances with NativeModuleError in loadDatabaseModule
to provide more structured error information, including rebuild status and
restart requirements. Update getAuthStatus to re-throw NativeModuleError
instances without wrapping.
2026-01-05 01:20:35 +00:00

170 lines
5.6 KiB
JavaScript

/**
* 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
};