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 a82e00f4d9
commit e283fcf0ac
19 changed files with 1790 additions and 121 deletions

View File

@@ -16,7 +16,10 @@
import { Component, Vue, Prop } from "vue-facing-decorator";
import { AppString, NotificationIface } from "../constants/app";
import * as databaseUtil from "../db/databaseUtil";
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
import { DEFAULT_ENDORSER_API_SERVER } from "../constants/app";
import { usePlatformService } from "../utils/usePlatformService";
import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil";
@Component
export default class TopMessage extends Vue {
@@ -28,7 +31,7 @@ export default class TopMessage extends Vue {
async mounted() {
try {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const settings = await this.getActiveAccountSettings();
if (
settings.warnIfTestServer &&
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
@@ -54,5 +57,95 @@ export default class TopMessage extends Vue {
);
}
}
/**
* Get settings for the active account using the platform service composable.
* This replaces the direct call to databaseUtil.retrieveSettingsForActiveAccount()
* and demonstrates the new composable pattern.
*/
private async getActiveAccountSettings() {
const { dbQuery } = usePlatformService();
try {
// Get default settings first
const defaultSettings = await this.getDefaultSettings();
// If no active DID, return defaults
if (!defaultSettings.activeDid) {
return defaultSettings;
}
// Get account-specific settings using the composable
const result = await dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[defaultSettings.activeDid],
);
if (!result?.values?.length) {
return defaultSettings;
}
// Map and filter settings
const overrideSettings = mapColumnsToValues(
result.columns,
result.values,
)[0] as any;
const overrideSettingsFiltered = Object.fromEntries(
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
);
// Merge settings
const settings = { ...defaultSettings, ...overrideSettingsFiltered };
// Handle searchBoxes parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
}
return settings;
} catch (error) {
console.error(`Failed to retrieve account settings for ${defaultSettings.activeDid}:`, error);
return defaultSettings;
}
}
/**
* Get default settings using the platform service composable
*/
private async getDefaultSettings() {
const { dbQuery } = usePlatformService();
try {
const result = await dbQuery(
"SELECT * FROM settings WHERE id = ?",
[MASTER_SETTINGS_KEY],
);
if (!result?.values?.length) {
return {
id: MASTER_SETTINGS_KEY,
activeDid: undefined,
apiServer: DEFAULT_ENDORSER_API_SERVER,
};
}
const settings = mapColumnsToValues(result.columns, result.values)[0] as any;
// Handle searchBoxes parsing
if (settings.searchBoxes) {
settings.searchBoxes = parseJsonField(settings.searchBoxes, []);
}
return settings;
} catch (error) {
console.error("Failed to retrieve default settings:", error);
return {
id: MASTER_SETTINGS_KEY,
activeDid: undefined,
apiServer: DEFAULT_ENDORSER_API_SERVER,
};
}
}
}
</script>