@ -624,6 +624,12 @@ export async function initializeSQLite(): Promise<void> {
// Set up IPC handlers
export function setupSQLiteHandlers ( ) : void {
// Add IPC message logging
const logIPCMessage = ( channel : string , direction : 'in' | 'out' , data? : any ) = > {
const timestamp = new Date ( ) . toISOString ( ) ;
logger . debug ( ` [ ${ timestamp } ] IPC ${ direction . toUpperCase ( ) } ${ channel } : ` , data ) ;
} ;
// Remove any existing handlers to prevent duplicates
try {
ipcMain . removeHandler ( 'sqlite-is-available' ) ;
@ -639,38 +645,49 @@ export function setupSQLiteHandlers(): void {
// Register all handlers before any are called
ipcMain . handle ( 'sqlite-is-available' , async ( ) = > {
logIPCMessage ( 'sqlite-is-available' , 'in' ) ;
try {
// Check both plugin instance and initialization state
return sqlitePlugin !== null && sqliteInitialized ;
const result = sqlitePlugin !== null && sqliteInitialized ;
logIPCMessage ( 'sqlite-is-available' , 'out' , { result } ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-is-available:' , error ) ;
logIPCMessage ( 'sqlite-is-available' , 'out' , { error : error.message } ) ;
return false ;
}
} ) ;
// Add handler to get initialization error
ipcMain . handle ( 'sqlite-get-error' , async ( ) = > {
return initializationError ? {
logIPCMessage ( 'sqlite-get-error' , 'in' ) ;
const result = initializationError ? {
message : initializationError.message ,
stack : initializationError.stack ,
name : initializationError.name
} : null ;
logIPCMessage ( 'sqlite-get-error' , 'out' , { result } ) ;
return result ;
} ) ;
// Update other handlers to handle unavailable state gracefully
ipcMain . handle ( 'sqlite-echo' , async ( _event , value ) = > {
logIPCMessage ( 'sqlite-echo' , 'in' , { value } ) ;
try {
if ( ! sqlitePlugin || ! sqliteInitialized ) {
throw new Error ( 'SQLite plugin not available' ) ;
}
return await sqlitePlugin . echo ( { value } ) ;
const result = await sqlitePlugin . echo ( { value } ) ;
logIPCMessage ( 'sqlite-echo' , 'out' , { result } ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-echo:' , error ) ;
logIPCMessage ( 'sqlite-echo' , 'out' , { error : error.message } ) ;
throw error ;
}
} ) ;
ipcMain . handle ( 'sqlite-create-connection' , async ( _event , options ) = > {
logIPCMessage ( 'sqlite-create-connection' , 'in' , options ) ;
try {
if ( ! sqlitePlugin || ! sqliteInitialized ) {
throw new Error ( 'SQLite plugin not available' ) ;
@ -679,7 +696,18 @@ export function setupSQLiteHandlers(): void {
if ( ! dbPath || ! dbDir ) {
throw new Error ( 'Database path not initialized' ) ;
}
// First check if database exists
const dbExists = await sqlitePlugin . isDBExists ( {
database : 'timesafari'
} ) ;
debugLog ( 'Database existence check:' , {
exists : dbExists ,
database : 'timesafari' ,
path : path.join ( dbDir , 'timesafariSQLite.db' )
} ) ;
// Clean up connection options to be consistent
const connectionOptions = {
database : 'timesafari' , // Base name only
@ -697,9 +725,32 @@ export function setupSQLiteHandlers(): void {
expectedBehavior : 'Plugin will append SQLite suffix and handle path resolution' ,
actualPath : path.join ( dbDir , 'timesafariSQLite.db' )
} ) ;
// If database exists, try to close any existing connections first
if ( dbExists ? . result ) {
debugLog ( 'Database exists, checking for open connections' ) ;
try {
const isOpen = await sqlitePlugin . isDBOpen ( {
database : connectionOptions.database
} ) ;
if ( isOpen ? . result ) {
debugLog ( 'Database is open, attempting to close' ) ;
await sqlitePlugin . close ( {
database : connectionOptions.database
} ) ;
// Wait a moment for connection to fully close
await delay ( 500 ) ;
}
} catch ( closeError ) {
logger . warn ( 'Error checking/closing existing database:' , closeError ) ;
// Continue anyway, as the database might be in a bad state
}
}
// Create connection (returns undefined but registers internally)
const result = await sqlitePlugin . createConnection ( connectionOptions ) ;
debugLog ( 'Creating database connection' ) ;
await sqlitePlugin . createConnection ( connectionOptions ) ;
// Wait a moment for connection to be registered
await delay ( 500 ) ;
@ -715,56 +766,139 @@ export function setupSQLiteHandlers(): void {
actualPath : path.join ( dbDir , 'timesafariSQLite.db' )
} ) ;
if ( ! isRegistered ) {
if ( ! isRegistered ? . result ) {
throw new Error ( 'Database not registered after createConnection' ) ;
}
// Return success object with more details
return {
// Open the database with explicit mode
debugLog ( 'Opening database with explicit mode' ) ;
await sqlitePlugin . open ( {
database : connectionOptions.database ,
version : connectionOptions.version ,
readOnly : false ,
encryption : connectionOptions.encryption ,
useNative : connectionOptions.useNative ,
mode : 'rwc' // Force read-write-create mode
} ) ;
debugLog ( 'Database opened, verifying mode' ) ;
// Verify the connection is not read-only
const journalMode = await sqlitePlugin . query ( {
database : connectionOptions.database ,
statement : 'PRAGMA journal_mode;'
} ) ;
debugLog ( 'Journal mode check:' , journalMode ) ;
if ( ! journalMode ? . values ? . [ 0 ] ? . journal_mode ||
journalMode . values [ 0 ] . journal_mode === 'off' ) {
// Close the database before throwing
await sqlitePlugin . close ( {
database : connectionOptions.database
} ) ;
throw new Error ( 'Database opened in read-only mode despite options' ) ;
}
// Double check we can write
debugLog ( 'Verifying write access' ) ;
await sqlitePlugin . execute ( {
database : connectionOptions.database ,
statements : 'CREATE TABLE IF NOT EXISTS _write_test (id INTEGER PRIMARY KEY); DROP TABLE IF EXISTS _write_test;' ,
transaction : false
} ) ;
debugLog ( 'Write access verified' ) ;
// Log the result before returning
const result = {
success : true ,
database : connectionOptions.database ,
isRegistered : true ,
actualPath : path.join ( dbDir , 'timesafariSQLite.db' ) ,
options : connectionOptions
isOpen : true ,
readonly : false ,
actualPath : path.join ( dbDir , 'timesafariSQLite.db' )
} ;
logIPCMessage ( 'sqlite-create-connection' , 'out' , result ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-create-connection:' , error ) ;
logIPCMessage ( 'sqlite-create-connection' , 'out' , { error : error.message } ) ;
// Try to close the database if it was opened
try {
if ( sqlitePlugin ) {
await sqlitePlugin . close ( {
database : 'timesafari'
} ) ;
}
} catch ( closeError ) {
logger . warn ( 'Error closing database after connection error:' , closeError ) ;
}
throw error ;
}
} ) ;
ipcMain . handle ( 'sqlite-execute' , async ( _event , options ) = > {
logIPCMessage ( 'sqlite-execute' , 'in' , options ) ;
try {
if ( ! sqlitePlugin || ! sqliteInitialized ) {
throw new Error ( 'SQLite plugin not available' ) ;
}
return await sqlitePlugin . execute ( options ) ;
const result = await sqlitePlugin . execute ( options ) ;
logIPCMessage ( 'sqlite-execute' , 'out' , { result } ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-execute:' , error ) ;
logIPCMessage ( 'sqlite-execute' , 'out' , { error : error.message } ) ;
throw error ;
}
} ) ;
ipcMain . handle ( 'sqlite-query' , async ( _event , options ) = > {
logIPCMessage ( 'sqlite-query' , 'in' , options ) ;
try {
if ( ! sqlitePlugin || ! sqliteInitialized ) {
throw new Error ( 'SQLite plugin not available' ) ;
}
return await sqlitePlugin . query ( options ) ;
const result = await sqlitePlugin . query ( options ) ;
logIPCMessage ( 'sqlite-query' , 'out' , { result } ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-query:' , error ) ;
logIPCMessage ( 'sqlite-query' , 'out' , { error : error.message } ) ;
throw error ;
}
} ) ;
ipcMain . handle ( 'sqlite-run' , async ( _event , options ) = > {
logIPCMessage ( 'sqlite-run' , 'in' , options ) ;
try {
if ( ! sqlitePlugin || ! sqliteInitialized ) {
throw new Error ( 'SQLite plugin not available' ) ;
}
const result = await sqlitePlugin . run ( options ) ;
logIPCMessage ( 'sqlite-run' , 'out' , { result } ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-run:' , error ) ;
logIPCMessage ( 'sqlite-run' , 'out' , { error : error.message } ) ;
throw error ;
}
} ) ;
ipcMain . handle ( 'sqlite-close-connection' , async ( _event , options ) = > {
ipcMain . handle ( 'sqlite-close' , async ( _event , options ) = > {
logIPCMessage ( 'sqlite-close' , 'in' , options ) ;
try {
if ( ! sqlitePlugin || ! sqliteInitialized ) {
throw new Error ( 'SQLite plugin not available' ) ;
}
return await sqlitePlugin . closeConnection ( options ) ;
const result = await sqlitePlugin . close ( options ) ;
logIPCMessage ( 'sqlite-close' , 'out' , { result } ) ;
return result ;
} catch ( error ) {
logger . error ( 'Error in sqlite-close-connection:' , error ) ;
logger . error ( 'Error in sqlite-close:' , error ) ;
logIPCMessage ( 'sqlite-close' , 'out' , { error : error.message } ) ;
throw error ;
}
} ) ;