import { ipcMain } from 'electron' import { MySqlService } from '../services/database/mysql' import { SqlServerService } from '../services/database/sql-server' import { createLogger } from '../services/logger' import { ValidationError } from '../types/errors' import type { MySqlConfig, MySqlQueryResult, SqlServerConfig, SqlServerQueryResult } from '../types/ipc-api.types' import { IPC_CHANNELS } from '../../shared/ipc-channels' import { withErrorHandling, type IpcResult } from './index' const log = createLogger('DatabaseHandler') // Store MySQL service instances per window/connection const mysqlServices = new Map() // Store SQL Server service instances per window/connection const sqlServerServices = new Map() const cleanupBoundWindows = new Set() function bindWindowCleanup( windowId: string, sender: { once: (event: string, listener: () => void) => void } ): void { if (cleanupBoundWindows.has(windowId)) { return } sender.once('destroyed', () => { const mysql = getMySqlService(windowId) const sqlServer = getSqlServerService(windowId) if (mysql) { mysql .disconnect() .catch((error) => log.warn('MySQL disconnect on window destroy failed', { error })) deleteMySqlService(windowId) } if (sqlServer) { sqlServer .disconnect() .catch((error) => log.warn('SQL Server disconnect on window destroy failed', { error })) deleteSqlServerService(windowId) } cleanupBoundWindows.delete(windowId) }) cleanupBoundWindows.add(windowId) } /** * Get or create MySQL service for a connection ID */ function getMySqlService(connectionId: string): MySqlService | undefined { return mysqlServices.get(connectionId) } /** * Set MySQL service for a connection ID */ function setMySqlService(connectionId: string, service: MySqlService): void { mysqlServices.set(connectionId, service) } /** * Delete MySQL service for a connection ID */ function deleteMySqlService(connectionId: string): void { mysqlServices.delete(connectionId) } /** * Get or create SQL Server service for a connection ID */ function getSqlServerService(connectionId: string): SqlServerService | undefined { return sqlServerServices.get(connectionId) } /** * Set SQL Server service for a connection ID */ function setSqlServerService(connectionId: string, service: SqlServerService): void { sqlServerServices.set(connectionId, service) } /** * Delete SQL Server service for a connection ID */ function deleteSqlServerService(connectionId: string): void { sqlServerServices.delete(connectionId) } /** * Register IPC handlers for database operations */ export function registerDatabaseHandlers(): void { // Connect to MySQL ipcMain.handle( IPC_CHANNELS.DATABASE_MYSQL_CONNECT, async (event, config: MySqlConfig): Promise> => { return withErrorHandling(async () => { // Use window ID as connection identifier const windowId = (event.sender as { id: number }).id.toString() bindWindowCleanup( windowId, event.sender as { once: (event: string, listener: () => void) => void } ) log.info('Connecting to MySQL', { windowId }) const service = new MySqlService(config) await service.connect() setMySqlService(windowId, service) log.info('MySQL connected', { windowId }) }, 'database:mysql:connect') } ) // Disconnect from MySQL ipcMain.handle( IPC_CHANNELS.DATABASE_MYSQL_DISCONNECT, async (event): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() const service = getMySqlService(windowId) if (service) { await service.disconnect() deleteMySqlService(windowId) log.info('MySQL disconnected', { windowId }) } }, 'database:mysql:disconnect') } ) // Check if MySQL is connected ipcMain.handle( IPC_CHANNELS.DATABASE_MYSQL_IS_CONNECTED, async (event): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() const service = getMySqlService(windowId) return service ? service.isConnected() : false }, 'database:mysql:isConnected') } ) // Execute MySQL query ipcMain.handle( IPC_CHANNELS.DATABASE_MYSQL_QUERY, async (event, sql: string, params?: unknown[]): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() const service = getMySqlService(windowId) if (!service) { throw new ValidationError( 'Not connected to MySQL. Call connect() first.', 'VAL_INVALID_INPUT' ) } log.debug('Executing MySQL query', { windowId, sql: sql.substring(0, 100) }) return await service.query(sql, params) }, 'database:mysql:query') } ) // Connect to SQL Server ipcMain.handle( IPC_CHANNELS.DATABASE_SQLSERVER_CONNECT, async (event, config: SqlServerConfig): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() bindWindowCleanup( windowId, event.sender as { once: (event: string, listener: () => void) => void } ) log.info('Connecting to SQL Server', { windowId }) const service = new SqlServerService(config) await service.connect() setSqlServerService(windowId, service) log.info('SQL Server connected', { windowId }) }, 'database:sqlserver:connect') } ) // Disconnect from SQL Server ipcMain.handle( IPC_CHANNELS.DATABASE_SQLSERVER_DISCONNECT, async (event): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() const service = getSqlServerService(windowId) if (service) { await service.disconnect() deleteSqlServerService(windowId) log.info('SQL Server disconnected', { windowId }) } }, 'database:sqlserver:disconnect') } ) // Check if SQL Server is connected ipcMain.handle( IPC_CHANNELS.DATABASE_SQLSERVER_IS_CONNECTED, async (event): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() const service = getSqlServerService(windowId) return service ? service.isConnected() : false }, 'database:sqlserver:isConnected') } ) // Execute SQL Server query ipcMain.handle( IPC_CHANNELS.DATABASE_SQLSERVER_QUERY, async ( event, sqlString: string, params?: Record ): Promise> => { return withErrorHandling(async () => { const windowId = (event.sender as { id: number }).id.toString() const service = getSqlServerService(windowId) if (!service) { throw new ValidationError( 'Not connected to SQL Server. Call connect() first.', 'VAL_INVALID_INPUT' ) } log.debug('Executing SQL Server query', { windowId, sql: sqlString.substring(0, 100) }) // Use queryWithParams for named parameters, or query for no params if (params && Object.keys(params).length > 0) { // Convert to the format expected by queryWithParams const typedParams: Record = {} for (const [key, value] of Object.entries(params)) { typedParams[key] = { value } } return await service.queryWithParams(sqlString, typedParams) } else { return await service.query(sqlString) } }, 'database:sqlserver:query') } ) }