feat: implement IPC handlers, database services and Extractor UI
- Add SqlServerService and MySqlService for database persistence - Implement IPC handlers for file, extractor, cleaner, and database operations - Define IPC API types and update preload script - Create ExtractorPage UI with OrderNumberInput component - Add unit and integration tests for MySQL and SQL Server - Update vitest config with path aliases
This commit is contained in:
@@ -2,6 +2,7 @@ import { app, shell, BrowserWindow, ipcMain } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import { registerIpcHandlers } from './ipc'
|
||||
|
||||
function createWindow(): void {
|
||||
// Create the browser window.
|
||||
@@ -49,6 +50,9 @@ app.whenReady().then(() => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
// Register IPC handlers
|
||||
registerIpcHandlers()
|
||||
|
||||
// IPC test
|
||||
ipcMain.on('ping', () => console.log('pong'))
|
||||
|
||||
|
||||
49
src/main/ipc/cleaner-handler.ts
Normal file
49
src/main/ipc/cleaner-handler.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { CleanerService } from '../services/erp/cleaner'
|
||||
import type { CleanerInput, CleanerResult } from '../types/cleaner.types'
|
||||
|
||||
/**
|
||||
* Register IPC handlers for cleaner service
|
||||
*/
|
||||
export function registerCleanerHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'cleaner:run',
|
||||
async (
|
||||
_event,
|
||||
input: CleanerInput
|
||||
): Promise<{ success: boolean; data?: CleanerResult; error?: string }> => {
|
||||
let authService: ErpAuthService | null = null
|
||||
|
||||
try {
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: process.env.ERP_URL || '',
|
||||
username: process.env.ERP_USERNAME || '',
|
||||
password: process.env.ERP_PASSWORD || '',
|
||||
headless: true
|
||||
})
|
||||
|
||||
await authService.login()
|
||||
|
||||
// Create cleaner service and run cleaning
|
||||
const cleaner = new CleanerService(authService)
|
||||
const result = await cleaner.clean(input)
|
||||
|
||||
return { success: true, data: result }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: message }
|
||||
} finally {
|
||||
// Clean up: close browser
|
||||
if (authService) {
|
||||
try {
|
||||
await authService.close()
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
181
src/main/ipc/database-handler.ts
Normal file
181
src/main/ipc/database-handler.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { MySqlService } from '../services/database/mysql'
|
||||
import { SqlServerService } from '../services/database/sql-server'
|
||||
import type {
|
||||
MySqlConfig,
|
||||
MySqlQueryResult,
|
||||
SqlServerConfig,
|
||||
SqlServerQueryResult
|
||||
} from '../types/ipc-api.types'
|
||||
|
||||
// Store MySQL service instances per window/connection
|
||||
const mysqlServices = new Map<string, MySqlService>()
|
||||
|
||||
// Store SQL Server service instances per window/connection
|
||||
const sqlServerServices = new Map<string, SqlServerService>()
|
||||
|
||||
/**
|
||||
* 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('database:mysql:connect', async (event, config: MySqlConfig): Promise<void> => {
|
||||
try {
|
||||
// Use window ID as connection identifier
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = new MySqlService(config)
|
||||
await service.connect()
|
||||
setMySqlService(windowId, service)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to connect to MySQL'
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
|
||||
// Disconnect from MySQL
|
||||
ipcMain.handle('database:mysql:disconnect', async (event): Promise<void> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = getMySqlService(windowId)
|
||||
if (service) {
|
||||
await service.disconnect()
|
||||
deleteMySqlService(windowId)
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to disconnect from MySQL'
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
|
||||
// Check if MySQL is connected
|
||||
ipcMain.handle('database:mysql:isConnected', async (event): Promise<boolean> => {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = getMySqlService(windowId)
|
||||
return service ? service.isConnected() : false
|
||||
})
|
||||
|
||||
// Execute MySQL query
|
||||
ipcMain.handle(
|
||||
'database:mysql:query',
|
||||
async (event, sql: string, params?: any[]): Promise<MySqlQueryResult> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = getMySqlService(windowId)
|
||||
|
||||
if (!service) {
|
||||
throw new Error('Not connected to MySQL. Call connect() first.')
|
||||
}
|
||||
|
||||
return await service.query(sql, params)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'MySQL query failed'
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Connect to SQL Server
|
||||
ipcMain.handle(
|
||||
'database:sqlserver:connect',
|
||||
async (event, config: SqlServerConfig): Promise<void> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = new SqlServerService(config)
|
||||
await service.connect()
|
||||
setSqlServerService(windowId, service)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to connect to SQL Server'
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Disconnect from SQL Server
|
||||
ipcMain.handle('database:sqlserver:disconnect', async (event): Promise<void> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = getSqlServerService(windowId)
|
||||
if (service) {
|
||||
await service.disconnect()
|
||||
deleteSqlServerService(windowId)
|
||||
}
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : 'Failed to disconnect from SQL Server'
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
|
||||
// Check if SQL Server is connected
|
||||
ipcMain.handle('database:sqlserver:isConnected', async (event): Promise<boolean> => {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = getSqlServerService(windowId)
|
||||
return service ? service.isConnected() : false
|
||||
})
|
||||
|
||||
// Execute SQL Server query
|
||||
ipcMain.handle(
|
||||
'database:sqlserver:query',
|
||||
async (
|
||||
event,
|
||||
sqlString: string,
|
||||
params?: Record<string, unknown>
|
||||
): Promise<SqlServerQueryResult> => {
|
||||
try {
|
||||
const windowId = (event.sender as any).id.toString()
|
||||
const service = getSqlServerService(windowId)
|
||||
|
||||
if (!service) {
|
||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||
}
|
||||
|
||||
return await service.query(sqlString, params)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'SQL Server query failed'
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
49
src/main/ipc/extractor-handler.ts
Normal file
49
src/main/ipc/extractor-handler.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import { ErpAuthService } from '../services/erp/erp-auth'
|
||||
import { ExtractorService } from '../services/erp/extractor'
|
||||
import type { ExtractorInput, ExtractorResult } from '../types/extractor.types'
|
||||
|
||||
/**
|
||||
* Register IPC handlers for extractor service
|
||||
*/
|
||||
export function registerExtractorHandlers(): void {
|
||||
ipcMain.handle(
|
||||
'extractor:run',
|
||||
async (
|
||||
_event,
|
||||
input: ExtractorInput
|
||||
): Promise<{ success: boolean; data?: ExtractorResult; error?: string }> => {
|
||||
let authService: ErpAuthService | null = null
|
||||
|
||||
try {
|
||||
// Create auth service and login
|
||||
authService = new ErpAuthService({
|
||||
url: process.env.ERP_URL || '',
|
||||
username: process.env.ERP_USERNAME || '',
|
||||
password: process.env.ERP_PASSWORD || '',
|
||||
headless: true
|
||||
})
|
||||
|
||||
await authService.login()
|
||||
|
||||
// Create extractor service and run extraction
|
||||
const extractor = new ExtractorService(authService)
|
||||
const result = await extractor.extract(input)
|
||||
|
||||
return { success: true, data: result }
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error'
|
||||
return { success: false, error: message }
|
||||
} finally {
|
||||
// Clean up: close browser
|
||||
if (authService) {
|
||||
try {
|
||||
await authService.close()
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
55
src/main/ipc/file-handler.ts
Normal file
55
src/main/ipc/file-handler.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ipcMain } from 'electron'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
|
||||
/**
|
||||
* Register IPC handlers for file operations
|
||||
*/
|
||||
export function registerFileHandlers(): void {
|
||||
// Read file content
|
||||
ipcMain.handle('file:read', async (_event, filePath: string): Promise<string> => {
|
||||
try {
|
||||
return await fs.readFile(filePath, 'utf-8')
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to read file'
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
|
||||
// Write content to file
|
||||
ipcMain.handle('file:write', async (_event, filePath: string, content: string): Promise<void> => {
|
||||
try {
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath)
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
await fs.writeFile(filePath, content, 'utf-8')
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to write file'
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
|
||||
// Check if file exists
|
||||
ipcMain.handle('file:exists', async (_event, filePath: string): Promise<boolean> => {
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// List files in directory
|
||||
ipcMain.handle('file:list', async (_event, dirPath: string): Promise<string[]> => {
|
||||
try {
|
||||
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
return entries
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name)
|
||||
.sort()
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to list directory'
|
||||
throw new Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
19
src/main/ipc/index.ts
Normal file
19
src/main/ipc/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* IPC Handler registration
|
||||
* Centralized registration for all IPC handlers
|
||||
*/
|
||||
|
||||
import { registerFileHandlers } from './file-handler'
|
||||
import { registerExtractorHandlers } from './extractor-handler'
|
||||
import { registerCleanerHandlers } from './cleaner-handler'
|
||||
import { registerDatabaseHandlers } from './database-handler'
|
||||
|
||||
/**
|
||||
* Register all IPC handlers
|
||||
*/
|
||||
export function registerIpcHandlers(): void {
|
||||
registerFileHandlers()
|
||||
registerExtractorHandlers()
|
||||
registerCleanerHandlers()
|
||||
registerDatabaseHandlers()
|
||||
}
|
||||
123
src/main/services/database/mysql.ts
Normal file
123
src/main/services/database/mysql.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import mysql from 'mysql2/promise'
|
||||
|
||||
export interface MySqlConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
}
|
||||
|
||||
export interface MySqlQueryResult {
|
||||
rows: Record<string, unknown>[]
|
||||
columns: string[]
|
||||
rowCount: number
|
||||
}
|
||||
|
||||
export class MySqlService {
|
||||
private connection: mysql.Connection | null = null
|
||||
private config: MySqlConfig
|
||||
|
||||
constructor(config: MySqlConfig) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to MySQL database
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.connection) {
|
||||
throw new Error('Already connected to MySQL')
|
||||
}
|
||||
|
||||
try {
|
||||
this.connection = await mysql.createConnection({
|
||||
host: this.config.host,
|
||||
port: this.config.port,
|
||||
user: this.config.user,
|
||||
password: this.config.password,
|
||||
database: this.config.database
|
||||
})
|
||||
|
||||
// Test connection
|
||||
await this.connection.ping()
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to connect to MySQL: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from MySQL database
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
if (!this.connection) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.connection.end()
|
||||
this.connection = null
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to disconnect from MySQL: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected to MySQL database
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return this.connection !== null
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query and return results
|
||||
*/
|
||||
async query(sql: string, params?: any[]): Promise<MySqlQueryResult> {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not connected to MySQL. Call connect() first.')
|
||||
}
|
||||
|
||||
try {
|
||||
const [rows, fields] = await this.connection.execute(sql, params)
|
||||
|
||||
// Convert rows to plain objects and extract column names
|
||||
const columns = fields.map((field) => field.name)
|
||||
const rowCount = Array.isArray(rows) ? rows.length : 0
|
||||
|
||||
// Type assertion for rows - mysql2 returns different types based on query
|
||||
const typedRows = Array.isArray(rows) ? (rows as Record<string, unknown>[]) : []
|
||||
|
||||
return {
|
||||
rows: typedRows,
|
||||
columns,
|
||||
rowCount
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`MySQL query failed: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple queries in a transaction
|
||||
*/
|
||||
async transaction(queries: { sql: string; params?: any[] }[]): Promise<void> {
|
||||
if (!this.connection) {
|
||||
throw new Error('Not connected to MySQL. Call connect() first.')
|
||||
}
|
||||
|
||||
try {
|
||||
await this.connection.beginTransaction()
|
||||
|
||||
for (const { sql, params } of queries) {
|
||||
await this.connection.execute(sql, params)
|
||||
}
|
||||
|
||||
await this.connection.commit()
|
||||
} catch (error) {
|
||||
if (this.connection) {
|
||||
await this.connection.rollback()
|
||||
}
|
||||
throw new Error(`MySQL transaction failed: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
184
src/main/services/database/sql-server.ts
Normal file
184
src/main/services/database/sql-server.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import sql from 'mssql'
|
||||
|
||||
export interface SqlServerConfig {
|
||||
server: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
options?: {
|
||||
encrypt?: boolean
|
||||
trustServerCertificate?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SqlServerQueryResult {
|
||||
rows: Record<string, unknown>[]
|
||||
columns: string[]
|
||||
rowCount: number
|
||||
}
|
||||
|
||||
export class SqlServerService {
|
||||
private pool: sql.ConnectionPool | null = null
|
||||
private config: SqlServerConfig
|
||||
|
||||
constructor(config: SqlServerConfig) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to SQL Server database
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
if (this.pool) {
|
||||
throw new Error('Already connected to SQL Server')
|
||||
}
|
||||
|
||||
try {
|
||||
const poolConfig: sql.config = {
|
||||
server: this.config.server,
|
||||
port: this.config.port,
|
||||
user: this.config.user,
|
||||
password: this.config.password,
|
||||
database: this.config.database,
|
||||
options: {
|
||||
encrypt: this.config.options?.encrypt ?? true,
|
||||
trustServerCertificate: this.config.options?.trustServerCertificate ?? false
|
||||
}
|
||||
}
|
||||
|
||||
this.pool = new sql.ConnectionPool(poolConfig)
|
||||
await this.pool.connect()
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to connect to SQL Server: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from SQL Server database
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
if (!this.pool) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await this.pool.close()
|
||||
this.pool = null
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to disconnect from SQL Server: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected to SQL Server database
|
||||
*/
|
||||
isConnected(): boolean {
|
||||
return this.pool !== null && this.pool.connected
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query and return results
|
||||
*/
|
||||
async query(sqlString: string, params?: Record<string, unknown>): Promise<SqlServerQueryResult> {
|
||||
if (!this.pool) {
|
||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||
}
|
||||
|
||||
try {
|
||||
const request = this.pool.request()
|
||||
|
||||
// Add parameters if provided
|
||||
if (params) {
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
request.input(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
const result = await request.query(sqlString)
|
||||
|
||||
// Convert recordset to array of objects
|
||||
const columns = result.recordset.columns?.map((col) => col.name) || []
|
||||
const rows = result.recordset as Record<string, unknown>[]
|
||||
|
||||
return {
|
||||
rows,
|
||||
columns,
|
||||
rowCount: result.rowsAffected?.[0] || rows.length
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`SQL Server query failed: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a prepared statement with parameters
|
||||
*/
|
||||
async queryWithParams(
|
||||
sqlString: string,
|
||||
params: Record<string, { value: unknown; type?: sql.ISqlType }>
|
||||
): Promise<SqlServerQueryResult> {
|
||||
if (!this.pool) {
|
||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||
}
|
||||
|
||||
try {
|
||||
const request = this.pool.request()
|
||||
|
||||
// Add parameters with explicit types
|
||||
for (const [key, { value, type }] of Object.entries(params)) {
|
||||
if (type) {
|
||||
request.input(key, type, value)
|
||||
} else {
|
||||
request.input(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
const result = await request.query(sqlString)
|
||||
|
||||
// Convert recordset to array of objects
|
||||
const columns = result.recordset.columns?.map((col) => col.name) || []
|
||||
const rows = result.recordset as Record<string, unknown>[]
|
||||
|
||||
return {
|
||||
rows,
|
||||
columns,
|
||||
rowCount: result.rowsAffected?.[0] || rows.length
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`SQL Server query failed: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple queries in a transaction
|
||||
*/
|
||||
async transaction(queries: { sql: string; params?: Record<string, unknown> }[]): Promise<void> {
|
||||
if (!this.pool) {
|
||||
throw new Error('Not connected to SQL Server. Call connect() first.')
|
||||
}
|
||||
|
||||
const transaction = new sql.Transaction(this.pool)
|
||||
|
||||
try {
|
||||
await transaction.begin()
|
||||
|
||||
for (const { sql: sqlString, params } of queries) {
|
||||
const request = new sql.Request(transaction)
|
||||
|
||||
if (params) {
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
request.input(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
await request.query(sqlString)
|
||||
}
|
||||
|
||||
await transaction.commit()
|
||||
} catch (error) {
|
||||
await transaction.rollback()
|
||||
throw new Error(`SQL Server transaction failed: ${(error as Error).message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
156
src/main/types/ipc-api.types.ts
Normal file
156
src/main/types/ipc-api.types.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* IPC API type definitions for renderer process
|
||||
* These types define the API exposed to the renderer process via contextBridge
|
||||
*/
|
||||
|
||||
import type { ExtractorInput, ExtractorResult } from './extractor.types'
|
||||
import type { CleanerInput, CleanerResult } from './cleaner.types'
|
||||
|
||||
/**
|
||||
* MySQL connection configuration
|
||||
*/
|
||||
export interface MySqlConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQL query result
|
||||
*/
|
||||
export interface MySqlQueryResult {
|
||||
rows: Record<string, unknown>[]
|
||||
columns: string[]
|
||||
rowCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server connection configuration
|
||||
*/
|
||||
export interface SqlServerConfig {
|
||||
server: string
|
||||
port: number
|
||||
user: string
|
||||
password: string
|
||||
database: string
|
||||
options?: {
|
||||
encrypt?: boolean
|
||||
trustServerCertificate?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL Server query result
|
||||
*/
|
||||
export interface SqlServerQueryResult {
|
||||
rows: Record<string, unknown>[]
|
||||
columns: string[]
|
||||
rowCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* File operation APIs
|
||||
*/
|
||||
export interface FileAPI {
|
||||
/**
|
||||
* Read file content as text
|
||||
* @param filePath - Path to the file
|
||||
*/
|
||||
readFile: (filePath: string) => Promise<string>
|
||||
|
||||
/**
|
||||
* Write content to file
|
||||
* @param filePath - Path to the file
|
||||
* @param content - Content to write
|
||||
*/
|
||||
writeFile: (filePath: string, content: string) => Promise<void>
|
||||
|
||||
/**
|
||||
* Check if file exists
|
||||
* @param filePath - Path to the file
|
||||
*/
|
||||
fileExists: (filePath: string) => Promise<boolean>
|
||||
|
||||
/**
|
||||
* Get list of files in directory
|
||||
* @param dirPath - Directory path
|
||||
*/
|
||||
listFiles: (dirPath: string) => Promise<string[]>
|
||||
}
|
||||
|
||||
/**
|
||||
* Extractor service APIs
|
||||
*/
|
||||
export interface ExtractorAPI {
|
||||
/**
|
||||
* Run ERP data extractor
|
||||
* @param input - Extractor input parameters
|
||||
*/
|
||||
runExtractor: (
|
||||
input: ExtractorInput
|
||||
) => Promise<{ success: boolean; data?: ExtractorResult; error?: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleaner service APIs
|
||||
*/
|
||||
export interface CleanerAPI {
|
||||
/**
|
||||
* Run ERP cleaner service
|
||||
* @param input - Cleaner input parameters
|
||||
*/
|
||||
runCleaner: (input: CleanerInput) => Promise<CleanerResult>
|
||||
}
|
||||
|
||||
/**
|
||||
* Database service APIs
|
||||
*/
|
||||
export interface DatabaseAPI {
|
||||
/**
|
||||
* Connect to MySQL database
|
||||
* @param config - MySQL connection config
|
||||
*/
|
||||
connectMySql: (config: MySqlConfig) => Promise<void>
|
||||
|
||||
/**
|
||||
* Disconnect from MySQL database
|
||||
*/
|
||||
disconnectMySql: () => Promise<void>
|
||||
|
||||
/**
|
||||
* Check if MySQL is connected
|
||||
*/
|
||||
isMySqlConnected: () => Promise<boolean>
|
||||
|
||||
/**
|
||||
* Execute MySQL query
|
||||
* @param sql - SQL query
|
||||
* @param params - Query parameters
|
||||
*/
|
||||
queryMySql: (sql: string, params?: unknown[]) => Promise<MySqlQueryResult>
|
||||
|
||||
/**
|
||||
* Connect to SQL Server database
|
||||
* @param config - SQL Server connection config
|
||||
*/
|
||||
connectSqlServer: (config: SqlServerConfig) => Promise<void>
|
||||
|
||||
/**
|
||||
* Disconnect from SQL Server database
|
||||
*/
|
||||
disconnectSqlServer: () => Promise<void>
|
||||
|
||||
/**
|
||||
* Check if SQL Server is connected
|
||||
*/
|
||||
isSqlServerConnected: () => Promise<boolean>
|
||||
|
||||
/**
|
||||
* Execute SQL Server query
|
||||
* @param sql - SQL query
|
||||
* @param params - Query parameters
|
||||
*/
|
||||
querySqlServer: (sql: string, params?: Record<string, unknown>) => Promise<SqlServerQueryResult>
|
||||
}
|
||||
Reference in New Issue
Block a user