Fix worker-only database architecture and Vue Proxy serialization

- Implement worker-only database access to eliminate double migrations
- Add parameter serialization in usePlatformService to prevent Capacitor "object could not be cloned" errors
- Fix infinite logging loop with circuit breaker in databaseUtil
- Use dynamic imports in WebPlatformService to prevent worker thread errors
- Add higher-level database methods (getContacts, getSettings) to composable
- Eliminate Vue Proxy objects through JSON serialization and Object.freeze protection

Resolves Proxy(Array) serialization failures and worker context conflicts across Web/Capacitor/Electron platforms.
This commit is contained in:
Matthew Raymer
2025-07-02 07:24:51 +00:00
parent d3e0cd1c9f
commit 7b1f891c63
19 changed files with 1790 additions and 121 deletions

View File

@@ -169,6 +169,9 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
let lastCleanupDate: string | null = null;
export let memoryLogs: string[] = [];
// Flag to prevent infinite logging loops during database operations
let isLoggingToDatabase = false;
/**
* Logs a message to the database with proper handling of concurrent writes
* @param message - The message to log
@@ -179,36 +182,44 @@ export async function logToDb(
message: string,
level: string = "info",
): Promise<void> {
const platform = PlatformServiceFactory.getInstance();
const todayKey = new Date().toDateString();
// Prevent infinite logging loops - if we're already trying to log to database,
// just log to console instead to break circular dependency
if (isLoggingToDatabase) {
console.log(`[DB-PREVENTED-${level.toUpperCase()}] ${message}`);
return;
}
// Set flag to prevent circular logging
isLoggingToDatabase = true;
try {
memoryLogs.push(`${new Date().toISOString()} ${message}`);
// Insert using actual schema: date, message (no level column)
await platform.dbExec("INSERT INTO logs (date, message) VALUES (?, ?)", [
todayKey, // Use date string to match schema
`[${level.toUpperCase()}] ${message}`, // Include level in message
]);
const platform = PlatformServiceFactory.getInstance();
const todayKey = new Date().toDateString();
// Clean up old logs (keep only last 7 days) - do this less frequently
// Only clean up if the date is different from the last cleanup
if (!lastCleanupDate || lastCleanupDate !== todayKey) {
const sevenDaysAgo = new Date(
new Date().getTime() - 7 * 24 * 60 * 60 * 1000,
).toDateString(); // Use date string to match schema
memoryLogs = memoryLogs.filter((log) => log.split(" ")[0] > sevenDaysAgo);
await platform.dbExec("DELETE FROM logs WHERE date < ?", [sevenDaysAgo]);
lastCleanupDate = todayKey;
try {
memoryLogs.push(`${new Date().toISOString()} ${message}`);
// Insert using actual schema: date, message (no level column)
await platform.dbExec("INSERT INTO logs (date, message) VALUES (?, ?)", [
todayKey, // Use date string to match schema
`[${level.toUpperCase()}] ${message}`, // Include level in message
]);
// Clean up old logs (keep only last 7 days) - do this less frequently
// Only clean up if the date is different from the last cleanup
if (!lastCleanupDate || lastCleanupDate !== todayKey) {
const sevenDaysAgo = new Date(
new Date().getTime() - 7 * 24 * 60 * 60 * 1000,
).toDateString(); // Use date string to match schema
memoryLogs = memoryLogs.filter((log) => log.split(" ")[0] > sevenDaysAgo);
await platform.dbExec("DELETE FROM logs WHERE date < ?", [sevenDaysAgo]);
lastCleanupDate = todayKey;
}
} catch (error) {
console.error("Error logging to database:", error, " ... for original message:", message);
}
} catch (error) {
// Log to console as fallback
// eslint-disable-next-line no-console
console.error(
"Error logging to database:",
error,
" ... for original message:",
message,
);
} finally {
// Always reset the flag to prevent permanent blocking of database logging
isLoggingToDatabase = false;
}
}
@@ -217,13 +228,13 @@ export async function logConsoleAndDb(
message: string,
isError = false,
): Promise<void> {
const level = isError ? "error" : "info";
if (isError) {
logger.error(`${new Date().toISOString()}`, message);
console.error(message);
} else {
logger.log(`${new Date().toISOString()}`, message);
console.log(message);
}
await logToDb(message, level);
await logToDb(message, isError ? "error" : "info");
}
/**