File size: 5,780 Bytes
d613519
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/**
 * 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
};