@ -92,6 +92,7 @@ let transactionState: TransactionState = {
const MAX_RECOVERY_ATTEMPTS = 3 ;
const RECOVERY_DELAY_MS = 1000 ;
const VERIFICATION_TIMEOUT_MS = 5000 ;
const LOCK_TIMEOUT_MS = 10000 ; // Added for the new sqlite-run handler
// Type definitions for SQLite operations
interface SQLiteConnectionOptions {
@ -105,6 +106,7 @@ interface SQLiteConnectionOptions {
}
interface SQLiteQueryOptions {
database : string ;
statement : string ;
values? : unknown [ ] ;
}
@ -619,19 +621,102 @@ const cleanupSQLiteHandlers = (): void => {
registeredHandlers . clear ( ) ;
} ;
/ * *
* Registers an IPC handler with tracking
* @param channel The IPC channel to register
* @param handler The handler function
* /
// Add at the top of the file after imports
const stringifyObject = ( obj : unknown ) : string = > {
try {
return JSON . stringify ( obj , null , 2 ) ;
} catch ( error ) {
return ` [Object could not be stringified: ${ error instanceof Error ? error.message : String ( error ) } ] ` ;
}
} ;
// Update the registerHandler function
const registerHandler = ( channel : string , handler : ( . . . args : any [ ] ) = > Promise < any > ) : void = > {
logger . info ( ` [IPC: ${ channel } ] Registering handler ` , {
channel ,
timestamp : new Date ( ) . toISOString ( ) ,
existingHandler : registeredHandlers.has ( channel )
} ) ;
if ( registeredHandlers . has ( channel ) ) {
logger . debug ( ` Handler already registered for channel: ${ channel } , removing first ` ) ;
ipcMain . removeHandler ( channel ) ;
logger . warn ( ` [IPC: ${ channel } ] Handler already registered, removing first ` , {
channel ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
try {
ipcMain . removeHandler ( channel ) ;
logger . debug ( ` [IPC: ${ channel } ] Removed existing handler ` ) ;
} catch ( error ) {
logger . error ( ` [IPC: ${ channel } ] Failed to remove existing handler: ` , {
error : error instanceof Error ? {
name : error.name ,
message : error.message ,
stack : error.stack
} : String ( error ) ,
channel ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
}
}
// Wrap the handler with logging
const wrappedHandler = async ( . . . args : any [ ] ) = > {
const startTime = Date . now ( ) ;
logger . info ( ` [IPC: ${ channel } ] Handler called with args: ` , {
channel ,
args : args.map ( arg = > stringifyObject ( arg ) ) ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
try {
const result = await handler ( . . . args ) ;
const duration = Date . now ( ) - startTime ;
logger . info ( ` [IPC: ${ channel } ] Handler completed successfully: ` , {
channel ,
result : stringifyObject ( result ) ,
duration : ` ${ duration } ms ` ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
return result ;
} catch ( error ) {
const duration = Date . now ( ) - startTime ;
logger . error ( ` [IPC: ${ channel } ] Handler failed: ` , {
channel ,
error : error instanceof Error ? {
name : error.name ,
message : error.message ,
stack : error.stack
} : stringifyObject ( error ) ,
duration : ` ${ duration } ms ` ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
throw error ;
}
} ;
try {
ipcMain . handle ( channel , wrappedHandler ) ;
registeredHandlers . add ( channel ) ;
logger . info ( ` [IPC: ${ channel } ] Handler registered successfully ` , {
channel ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
} catch ( error ) {
logger . error ( ` [IPC: ${ channel } ] Failed to register handler: ` , {
error : error instanceof Error ? {
name : error.name ,
message : error.message ,
stack : error.stack
} : String ( error ) ,
channel ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
throw error ;
}
ipcMain . handle ( channel , handler ) ;
registeredHandlers . add ( channel ) ;
logger . debug ( ` Registered handler for channel: ${ channel } ` ) ;
} ;
/ * *
@ -743,19 +828,111 @@ export function setupSQLiteHandlers(): void {
// Handler for running SQL statements
registerHandler ( 'sqlite-run' , async ( _event , options : SQLiteQueryOptions ) = > {
logger . debug ( 'Running SQL statement:' , options ) ;
const startTime = Date . now ( ) ;
const operationId = ` run_ ${ Date . now ( ) } _ ${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } ` ;
logger . info ( '[sqlite-run] IPC handler called with options:' , {
operationId ,
options : stringifyObject ( options ) ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
try {
logger . debug ( '[sqlite-run] Starting database operation' , {
operationId ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
startDatabaseOperation ( ) ;
if ( ! pluginState . instance ) {
throw new SQLiteError ( 'Plugin not initialized' , 'sqlite-run' ) ;
const error = new SQLiteError ( 'Plugin not initialized' , 'sqlite-run' ) ;
logger . error ( '[sqlite-run] Plugin not initialized:' , {
operationId ,
error : stringifyObject ( error ) ,
pluginState : stringifyObject ( pluginState ) ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
throw error ;
}
// Verify database is open
const isOpen = await pluginState . instance . isDBOpen ( { database : options.database } ) ;
if ( ! isOpen ) {
const error = new SQLiteError ( 'Database not open' , 'sqlite-run' ) ;
logger . error ( '[sqlite-run] Database not open:' , {
operationId ,
database : options.database ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
throw error ;
}
const result = await pluginState . instance . run ( options ) ;
logger . debug ( 'SQL run result:' , result ) ;
logger . debug ( '[sqlite-run] Executing SQL with plugin:' , {
operationId ,
options : stringifyObject ( options ) ,
pluginState : stringifyObject ( pluginState ) ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
// Add timeout promise
const timeoutPromise = new Promise ( ( _ , reject ) = > {
setTimeout ( ( ) = > {
reject ( new Error ( ` SQLite run operation timed out after ${ LOCK_TIMEOUT_MS } ms ` ) ) ;
} , LOCK_TIMEOUT_MS ) ;
} ) ;
// Race between the operation and timeout
const result = await Promise . race ( [
pluginState . instance . run ( options ) ,
timeoutPromise
] ) ;
const duration = Date . now ( ) - startTime ;
logger . info ( '[sqlite-run] SQL execution successful:' , {
operationId ,
result : stringifyObject ( result ) ,
duration : ` ${ duration } ms ` ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
return result ;
} catch ( error ) {
logger . error ( 'SQL run failed:' , error ) ;
const duration = Date . now ( ) - startTime ;
const errorDetails = error instanceof Error ? {
name : error.name ,
message : error.message ,
stack : error.stack
} : stringifyObject ( error ) ;
logger . error ( '[sqlite-run] SQL execution failed:' , {
operationId ,
error : errorDetails ,
options : stringifyObject ( options ) ,
duration : ` ${ duration } ms ` ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
// Check for specific error types
if ( error instanceof Error ) {
const errorMsg = error . message . toLowerCase ( ) ;
if ( errorMsg . includes ( 'database is locked' ) ||
errorMsg . includes ( 'database is busy' ) ||
errorMsg . includes ( 'database is locked (5)' ) ) {
logger . warn ( '[sqlite-run] Database lock detected:' , {
operationId ,
error : errorDetails ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
}
}
throw error ;
} finally {
logger . debug ( '[sqlite-run] Ending database operation' , {
operationId ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
endDatabaseOperation ( ) ;
}
} ) ;