Browse Source

feat(electron): enhance SQLite operation logging and debugging

WIP: Debugging sqlite-run/dbExec hanging issue

- Add renderer-to-main process log forwarding
- Implement operation tracking with unique IDs
- Add detailed timing and retry logging
- Enhance error capture and formatting
- Move logs to app user data directory
- Add exponential backoff for retries

This commit adds comprehensive logging to help diagnose
why dbExec operations are hanging when sent through the
sqlite-run channel. Changes include:

- Forward all renderer process logs to main process
- Track SQLite operations with unique IDs
- Log operation timing and retry attempts
- Capture detailed error information
- Implement exponential backoff for retries
- Centralize logs in app user data directory

Security:
- Logs are stored in app user data directory
- Sensitive data is sanitized in logs
- Error stacks are properly captured

Testing:
- Manual testing of logging system
- Verify log capture in both processes
- Check log rotation and file sizes

TODO:
- Monitor logs to identify root cause
- Add specific logging for settings table
- Consider connection pooling if needed
sql-absurd-sql-further
Matthew Raymer 2 days ago
parent
commit
981920dd7a
  1. 150
      electron/src/preload.ts
  2. 50
      electron/src/rt/logger.ts
  3. 207
      electron/src/rt/sqlite-init.ts
  4. 830
      package-lock.json
  5. 105
      src/db/databaseUtil.ts
  6. 110
      src/services/platforms/ElectronPlatformService.ts
  7. 7
      src/views/AccountViewView.vue

150
electron/src/preload.ts

@ -7,20 +7,64 @@
import { contextBridge, ipcRenderer } from 'electron';
// Enhanced logger for preload script
// Enhanced logger for preload script that forwards to main process
const logger = {
log: (...args: unknown[]) => console.log('[Preload]', ...args),
error: (...args: unknown[]) => console.error('[Preload]', ...args),
info: (...args: unknown[]) => console.info('[Preload]', ...args),
warn: (...args: unknown[]) => console.warn('[Preload]', ...args),
debug: (...args: unknown[]) => console.debug('[Preload]', ...args),
log: (...args: unknown[]) => {
console.log('[Preload]', ...args);
ipcRenderer.send('renderer-log', { level: 'log', args });
},
error: (...args: unknown[]) => {
console.error('[Preload]', ...args);
ipcRenderer.send('renderer-log', { level: 'error', args });
},
info: (...args: unknown[]) => {
console.info('[Preload]', ...args);
ipcRenderer.send('renderer-log', { level: 'info', args });
},
warn: (...args: unknown[]) => {
console.warn('[Preload]', ...args);
ipcRenderer.send('renderer-log', { level: 'warn', args });
},
debug: (...args: unknown[]) => {
console.debug('[Preload]', ...args);
ipcRenderer.send('renderer-log', { level: 'debug', args });
},
sqlite: {
log: (operation: string, ...args: unknown[]) =>
console.log('[Preload][SQLite]', operation, ...args),
error: (operation: string, error: unknown) =>
console.error('[Preload][SQLite]', operation, 'failed:', error),
debug: (operation: string, ...args: unknown[]) =>
console.debug('[Preload][SQLite]', operation, ...args)
log: (operation: string, ...args: unknown[]) => {
const message = ['[Preload][SQLite]', operation, ...args];
console.log(...message);
ipcRenderer.send('renderer-log', {
level: 'log',
args: message,
source: 'sqlite',
operation
});
},
error: (operation: string, error: unknown) => {
const message = ['[Preload][SQLite]', operation, 'failed:', error];
console.error(...message);
ipcRenderer.send('renderer-log', {
level: 'error',
args: message,
source: 'sqlite',
operation,
error: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack
} : error
});
},
debug: (operation: string, ...args: unknown[]) => {
const message = ['[Preload][SQLite]', operation, ...args];
console.debug(...message);
ipcRenderer.send('renderer-log', {
level: 'debug',
args: message,
source: 'sqlite',
operation
});
}
}
};
@ -123,24 +167,94 @@ const createSQLiteProxy = () => {
const withRetry = async <T>(operation: string, ...args: unknown[]): Promise<T> => {
let lastError: Error | undefined;
const operationId = `${operation}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const startTime = Date.now();
logger.sqlite.debug(operation, 'starting with args:', {
operationId,
args,
timestamp: new Date().toISOString()
});
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
logger.sqlite.debug(operation, 'attempt', attempt, args);
logger.sqlite.debug(operation, `attempt ${attempt}/${MAX_RETRIES}`, {
operationId,
attempt,
args,
timestamp: new Date().toISOString()
});
// Log the exact IPC call
logger.sqlite.debug(operation, 'invoking IPC', {
operationId,
channel: `sqlite-${operation}`,
args,
timestamp: new Date().toISOString()
});
const result = await ipcRenderer.invoke(`sqlite-${operation}`, ...args);
logger.sqlite.log(operation, 'success', result);
const duration = Date.now() - startTime;
logger.sqlite.log(operation, 'success', {
operationId,
attempt,
result,
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
return result as T;
} catch (error) {
const duration = Date.now() - startTime;
lastError = error instanceof Error ? error : new Error(String(error));
logger.sqlite.error(operation, error);
logger.sqlite.error(operation, {
operationId,
attempt,
error: {
name: lastError.name,
message: lastError.message,
stack: lastError.stack
},
args,
duration: `${duration}ms`,
timestamp: new Date().toISOString()
});
if (attempt < MAX_RETRIES) {
logger.warn(`[Preload] SQLite ${operation} failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`, error);
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
const backoffDelay = RETRY_DELAY * Math.pow(2, attempt - 1);
logger.warn(`[Preload] SQLite ${operation} failed (attempt ${attempt}/${MAX_RETRIES}), retrying in ${backoffDelay}ms...`, {
operationId,
error: lastError,
args,
nextAttemptIn: `${backoffDelay}ms`,
timestamp: new Date().toISOString()
});
await new Promise(resolve => setTimeout(resolve, backoffDelay));
}
}
}
throw new Error(`SQLite ${operation} failed after ${MAX_RETRIES} attempts: ${lastError?.message || "Unknown error"}`);
const finalError = new Error(
`SQLite ${operation} failed after ${MAX_RETRIES} attempts: ${lastError?.message || "Unknown error"}`
);
logger.error('[Preload] SQLite operation failed permanently:', {
operation,
operationId,
error: {
name: finalError.name,
message: finalError.message,
stack: finalError.stack,
originalError: lastError
},
args,
attempts: MAX_RETRIES,
timestamp: new Date().toISOString()
});
throw finalError;
};
return {

50
electron/src/rt/logger.ts

@ -6,6 +6,7 @@
* @author Matthew Raymer
*/
import { app, ipcMain } from 'electron';
import winston from 'winston';
import path from 'path';
import os from 'os';
@ -29,10 +30,10 @@ declare module 'winston' {
}
}
// Ensure log directory exists
const logDir = path.join(os.homedir(), 'Logs', 'TimeSafari');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true, mode: 0o755 });
// Create logs directory if it doesn't exist
const logsDir = path.join(app.getPath('userData'), 'logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Custom format for console output with migration filtering
@ -63,7 +64,7 @@ const fileFormat = winston.format.combine(
);
// Create logger instance
export const logger = winston.createLogger({
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
format: fileFormat,
defaultMeta: { service: 'timesafari-electron' },
@ -76,14 +77,14 @@ export const logger = winston.createLogger({
}),
// File transport for all logs
new winston.transports.File({
filename: path.join(logDir, 'error.log'),
filename: path.join(logsDir, 'error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),
// File transport for all logs including debug
new winston.transports.File({
filename: path.join(logDir, 'combined.log'),
filename: path.join(logsDir, 'combined.log'),
maxsize: 5242880, // 5MB
maxFiles: 5
})
@ -150,5 +151,38 @@ logger.migration = {
}
};
// Add renderer log handler
ipcMain.on('renderer-log', (_event, { level, args, source, operation, error }) => {
const message = args.map((arg: unknown) =>
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
).join(' ');
const meta = {
source: source || 'renderer',
...(operation && { operation }),
...(error && { error })
};
switch (level) {
case 'error':
logger.error(message, meta);
break;
case 'warn':
logger.warn(message, meta);
break;
case 'info':
logger.info(message, meta);
break;
case 'debug':
logger.debug(message, meta);
break;
default:
logger.log(level, message, meta);
}
});
// Export logger instance
export default logger;
export { logger };
// Export a function to get the logs directory
export const getLogsDirectory = () => logsDir;

207
electron/src/rt/sqlite-init.ts

@ -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();
}
});

830
package-lock.json

File diff suppressed because it is too large

105
src/db/databaseUtil.ts

@ -3,11 +3,19 @@
* That file will eventually be deleted.
*/
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
import { MASTER_SETTINGS_KEY, Settings } from "./tables/settings";
import { logger } from "@/utils/logger";
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
import { QueryExecResult } from "@/interfaces/database";
import { logger } from "../utils/logger";
import { DEFAULT_ENDORSER_API_SERVER } from "../constants/app";
import { QueryExecResult } from "../interfaces/database";
const formatLogObject = (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)}]`;
}
};
export async function updateDefaultSettings(
settingsChanges: Settings,
@ -23,10 +31,13 @@ export async function updateDefaultSettings(
"id = ?",
[MASTER_SETTINGS_KEY],
);
console.log("[databaseUtil] updateDefaultSettings", { sql, params });
const result = await platformService.dbExec(sql, params);
console.log("[databaseUtil] updateDefaultSettings result", { result });
return result.changes === 1;
} catch (error) {
logger.error("Error updating default settings:", error);
console.log("[databaseUtil] updateDefaultSettings error", { error });
if (error instanceof Error) {
throw error; // Re-throw if it's already an Error with a message
} else {
@ -79,48 +90,78 @@ const DEFAULT_SETTINGS: Settings = {
// retrieves default settings
export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {
console.log('[DatabaseUtil] Retrieving default account settings');
const platform = PlatformServiceFactory.getInstance();
console.log('[DatabaseUtil] Platform service state:', {
platformType: platform.constructor.name,
capabilities: platform.getCapabilities(),
timestamp: new Date().toISOString()
});
const result = await platform.dbQuery("SELECT * FROM settings WHERE id = ?", [
MASTER_SETTINGS_KEY,
]);
if (!result) {
console.log('[DatabaseUtil] No settings found, returning defaults');
return DEFAULT_SETTINGS;
} else {
const settings = mapColumnsToValues(
result.columns,
result.values,
)[0] as Settings;
if (settings.searchBoxes) {
// @ts-expect-error - the searchBoxes field is a string in the DB
settings.searchBoxes = JSON.parse(settings.searchBoxes);
}
return settings;
}
const settings = mapColumnsToValues(result.columns, result.values)[0] as Settings;
if (settings.searchBoxes) {
// @ts-expect-error - the searchBoxes field is a string in the DB
settings.searchBoxes = JSON.parse(settings.searchBoxes);
}
console.log('[DatabaseUtil] Retrieved settings:', {
settings: formatLogObject(settings),
timestamp: new Date().toISOString()
});
return settings;
}
export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
console.log('[DatabaseUtil] Retrieving active account settings');
const defaultSettings = await retrieveSettingsForDefaultAccount();
console.log('[DatabaseUtil] Default settings retrieved:', {
defaultSettings: formatLogObject(defaultSettings),
timestamp: new Date().toISOString()
});
if (!defaultSettings.activeDid) {
console.log('[DatabaseUtil] No active DID, returning default settings');
return defaultSettings;
} else {
const platform = PlatformServiceFactory.getInstance();
const result = await platform.dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[defaultSettings.activeDid],
);
const overrideSettings = result
? (mapColumnsToValues(result.columns, result.values)[0] as Settings)
: {};
const overrideSettingsFiltered = Object.fromEntries(
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
);
const settings = { ...defaultSettings, ...overrideSettingsFiltered };
if (settings.searchBoxes) {
// @ts-expect-error - the searchBoxes field is a string in the DB
settings.searchBoxes = JSON.parse(settings.searchBoxes);
}
return settings;
}
const platform = PlatformServiceFactory.getInstance();
const result = await platform.dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[defaultSettings.activeDid],
);
const overrideSettings = result
? (mapColumnsToValues(result.columns, result.values)[0] as Settings)
: {};
const overrideSettingsFiltered = Object.fromEntries(
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
);
const settings = { ...defaultSettings, ...overrideSettingsFiltered };
if (settings.searchBoxes) {
// @ts-expect-error - the searchBoxes field is a string in the DB
settings.searchBoxes = JSON.parse(settings.searchBoxes);
}
console.log('[DatabaseUtil] Final active account settings:', {
settings: formatLogObject(settings),
timestamp: new Date().toISOString()
});
return settings;
}
let lastCleanupDate: string | null = null;

110
src/services/platforms/ElectronPlatformService.ts

@ -25,9 +25,20 @@ interface Migration {
sql: string;
}
export interface SQLiteQueryResult {
// Define the SQLite query result type
interface SQLiteQueryResult {
changes: number;
lastId: number;
rows?: unknown[];
values?: Record<string, unknown>[];
changes?: { changes: number; lastId?: number };
}
// Update the QueryExecResult type to include success and changes
interface ElectronQueryExecResult {
success: boolean;
changes: number;
lastId?: number;
rows?: unknown[];
}
/**
@ -73,6 +84,15 @@ interface SQLiteOperations {
}) => Promise<{ changes?: { changes: number; lastId?: number } }>;
}
// Add at the top of the file after imports
const formatLogObject = (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)}]`;
}
};
/**
* Platform service implementation for Electron (desktop) platform.
* Provides native desktop functionality through Electron and Capacitor plugins for:
@ -710,13 +730,23 @@ export class ElectronPlatformService implements PlatformService {
"Database is in a fatal error state. Please restart the app.",
);
}
logger.debug('[ElectronPlatformService] [dbQuery] Enqueuing operation', {
sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
paramCount: params.length,
timestamp: new Date().toISOString()
});
return this.enqueueOperation(async () => {
try {
// Get connection (will wait for existing connection if any)
console.log("[ElectronPlatformService] [dbQuery] Getting connection");
await this.getConnection();
console.log("[ElectronPlatformService] [dbQuery] Connection acquired");
// Execute query
console.log(
"[ElectronPlatformService] [dbQuery] Executing query",
{ sql, params },
);
const result = (await window.electron!.ipcRenderer.invoke(
"sqlite-query",
{
@ -725,8 +755,9 @@ export class ElectronPlatformService implements PlatformService {
values: params,
},
)) as SQLiteQueryResult;
logger.debug(
console.log(
"[ElectronPlatformService] [dbQuery] Query executed successfully",
{ result },
);
// Process results
@ -738,9 +769,14 @@ export class ElectronPlatformService implements PlatformService {
),
};
console.log(
"[ElectronPlatformService] [dbQuery] Query processed successfully",
{ processedResult },
);
return processedResult;
} catch (error) {
logger.error(
console.error(
"[ElectronPlatformService] [dbQuery] Query failed:",
error,
);
@ -759,6 +795,10 @@ export class ElectronPlatformService implements PlatformService {
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
console.log("[ElectronPlatformService] [dbExec] Executing query", {
sql,
params,
});
if (this.dbFatalError) {
throw new Error(
"Database is in a fatal error state. Please restart the app.",
@ -768,9 +808,15 @@ export class ElectronPlatformService implements PlatformService {
return this.enqueueOperation(async () => {
try {
// Get connection (will wait for existing connection if any)
console.log("[ElectronPlatformService] [dbExec] Getting connection");
await this.getConnection();
console.log("[ElectronPlatformService] [dbExec] Connection acquired");
// Execute query
console.log(
"[ElectronPlatformService] [dbExec] Executing query",
{ sql, params },
);
const result = (await window.electron!.ipcRenderer.invoke(
"sqlite-run",
{
@ -779,16 +825,20 @@ export class ElectronPlatformService implements PlatformService {
values: params,
},
)) as SQLiteQueryResult;
logger.debug(
console.log(
"[ElectronPlatformService] [dbExec] Query executed successfully",
{ result },
);
return {
changes: result.changes?.changes || 0,
lastId: result.changes?.lastId,
changes: result.changes,
lastId: result.lastId,
};
} catch (error) {
logger.error("[ElectronPlatformService] [dbExec] Query failed:", error);
console.error(
"[ElectronPlatformService] [dbExec] Query failed:",
error,
);
throw error;
} finally {
// Release connection after query
@ -821,4 +871,46 @@ export class ElectronPlatformService implements PlatformService {
async close(): Promise<void> {
// Optionally implement close logic if needed
}
async run(sql: string, params: unknown[] = []): Promise<ElectronQueryExecResult> {
logger.debug('[ElectronPlatformService] [dbRun] Executing SQL:', {
sql: sql.substring(0, 100) + (sql.length > 100 ? '...' : ''),
params: formatLogObject(params),
timestamp: new Date().toISOString()
});
try {
const result = (await window.electron!.ipcRenderer.invoke(
'sqlite-run',
{
database: this.dbName,
statement: sql,
values: params,
},
)) as SQLiteQueryResult;
logger.debug('[ElectronPlatformService] [dbRun] SQL execution result:', {
result: formatLogObject(result),
timestamp: new Date().toISOString()
});
return {
success: true,
changes: result.changes,
lastId: result.lastId,
};
} catch (error) {
logger.error('[ElectronPlatformService] [dbRun] SQL execution failed:', {
error: error instanceof Error ? {
name: error.name,
message: error.message,
stack: error.stack
} : formatLogObject(error),
sql,
params: formatLogObject(params),
timestamp: new Date().toISOString()
});
throw error;
}
}
}

7
src/views/AccountViewView.vue

@ -1237,7 +1237,9 @@ export default class AccountViewView extends Vue {
* Initializes component state with values from the database or defaults.
*/
async initializeState() {
console.log("[AccountViewView] initializeState");
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
console.log("[AccountViewView] initializeState", { settings });
if (USE_DEXIE_DB) {
await db.open();
settings = await retrieveSettingsForActiveAccount();
@ -1949,6 +1951,11 @@ export default class AccountViewView extends Vue {
current: this.apiServer,
attempted: this.apiServerInput,
});
console.log("[AccountViewView] API server update failed", {
error,
current: this.apiServer,
attempted: this.apiServerInput,
});
this.$notify(
{
group: "alert",

Loading…
Cancel
Save