feat: Implement activeDid migration from Dexie to SQLite

- Add migrateActiveDid() function for dedicated activeDid migration
- Enhance migrateSettings() to handle activeDid extraction and validation
- Update migrateAll() to include activeDid migration step
- Add comprehensive error handling and validation
- Update migration documentation with activeDid migration details
- Ensure user identity continuity during migration process

Files changed:
- src/services/indexedDBMigrationService.ts (153 lines added)
- doc/migration-to-wa-sqlite.md (documentation updated)

Migration order: Accounts -> Settings -> ActiveDid -> Contacts
This commit is contained in:
Matthew Raymer
2025-06-20 04:48:51 +00:00
parent b89f3310ef
commit 7978133e7f
2 changed files with 315 additions and 356 deletions

View File

@@ -39,6 +39,7 @@ import {
generateUpdateStatement,
generateInsertStatement,
} from "../db/databaseUtil";
import { updateDefaultSettings } from "../db/databaseUtil";
import { importFromMnemonic } from "../libs/util";
/**
@@ -1080,6 +1081,17 @@ export async function migrateSettings(): Promise<MigrationResult> {
});
const platformService = PlatformServiceFactory.getInstance();
// Find the master settings (accountDid is null) which contains the activeDid
const masterSettings = dexieSettings.find(setting => !setting.accountDid);
let dexieActiveDid: string | undefined;
if (masterSettings?.activeDid) {
dexieActiveDid = masterSettings.activeDid;
logger.info("[MigrationService] Found activeDid in Dexie master settings", {
activeDid: dexieActiveDid,
});
}
// Create an array of promises for all settings migrations
const migrationPromises = dexieSettings.map(async (setting) => {
logger.info("[MigrationService] Starting to migrate settings", setting);
@@ -1139,6 +1151,38 @@ export async function migrateSettings(): Promise<MigrationResult> {
// Wait for all migrations to complete
const updatedSettings = await Promise.all(migrationPromises);
// Step 2: Migrate the activeDid if it exists in Dexie
if (dexieActiveDid) {
try {
// Verify that the activeDid exists in SQLite accounts
const accountExists = await platformService.dbQuery(
"SELECT did FROM accounts WHERE did = ?",
[dexieActiveDid],
);
if (accountExists?.values?.length) {
// Update the master settings with the activeDid
await updateDefaultSettings({ activeDid: dexieActiveDid });
logger.info("[MigrationService] Successfully migrated activeDid", {
activeDid: dexieActiveDid,
});
result.warnings.push(`Migrated activeDid: ${dexieActiveDid}`);
} else {
logger.warn("[MigrationService] activeDid from Dexie not found in SQLite accounts", {
activeDid: dexieActiveDid,
});
result.warnings.push(
`activeDid from Dexie (${dexieActiveDid}) not found in SQLite accounts - skipping activeDid migration`,
);
}
} catch (error) {
logger.error("[MigrationService] Failed to migrate activeDid:", error);
result.errors.push(`Failed to migrate activeDid: ${error}`);
}
} else {
logger.info("[MigrationService] No activeDid found in Dexie settings");
}
logger.info(
"[MigrationService] Finished migrating settings",
updatedSettings,
@@ -1279,6 +1323,96 @@ export async function migrateAccounts(): Promise<MigrationResult> {
}
}
/**
* Migrates the activeDid from Dexie to SQLite
*
* This function specifically handles the migration of the activeDid setting
* from the Dexie database to the SQLite database. It ensures that the
* activeDid exists in the SQLite accounts table before setting it as active.
*
* The function is designed to be called after accounts have been migrated
* to ensure the target DID exists in the SQLite database.
*
* @async
* @function migrateActiveDid
* @returns {Promise<MigrationResult>} Result of the activeDid migration
* @throws {Error} If the migration process fails
* @example
* ```typescript
* try {
* const result = await migrateActiveDid();
* if (result.success) {
* console.log('ActiveDid migration successful');
* } else {
* console.error('ActiveDid migration failed:', result.errors);
* }
* } catch (error) {
* console.error('ActiveDid migration process failed:', error);
* }
* ```
*/
export async function migrateActiveDid(): Promise<MigrationResult> {
logger.info("[MigrationService] Starting activeDid migration");
const result: MigrationResult = {
success: true,
contactsMigrated: 0,
settingsMigrated: 0,
accountsMigrated: 0,
errors: [],
warnings: [],
};
try {
// Get Dexie settings to find the activeDid
const dexieSettings = await getDexieSettings();
const masterSettings = dexieSettings.find(setting => !setting.accountDid);
if (!masterSettings?.activeDid) {
logger.info("[MigrationService] No activeDid found in Dexie master settings");
result.warnings.push("No activeDid found in Dexie settings");
return result;
}
const dexieActiveDid = masterSettings.activeDid;
logger.info("[MigrationService] Found activeDid in Dexie", {
activeDid: dexieActiveDid,
});
const platformService = PlatformServiceFactory.getInstance();
// Verify that the activeDid exists in SQLite accounts
const accountExists = await platformService.dbQuery(
"SELECT did FROM accounts WHERE did = ?",
[dexieActiveDid],
);
if (!accountExists?.values?.length) {
const errorMessage = `activeDid from Dexie (${dexieActiveDid}) not found in SQLite accounts`;
logger.error("[MigrationService]", errorMessage);
result.errors.push(errorMessage);
result.success = false;
return result;
}
// Update the master settings with the activeDid
await updateDefaultSettings({ activeDid: dexieActiveDid });
logger.info("[MigrationService] Successfully migrated activeDid", {
activeDid: dexieActiveDid,
});
result.warnings.push(`Successfully migrated activeDid: ${dexieActiveDid}`);
return result;
} catch (error) {
const errorMessage = `ActiveDid migration failed: ${error}`;
logger.error("[MigrationService]", errorMessage, error);
result.errors.push(errorMessage);
result.success = false;
return result;
}
}
/**
* Migrates all data from Dexie to SQLite in the proper order
*
@@ -1286,7 +1420,8 @@ export async function migrateAccounts(): Promise<MigrationResult> {
* in the correct order to avoid foreign key constraint issues:
* 1. Accounts (foundational - contains DIDs)
* 2. Settings (references accountDid, activeDid)
* 3. Contacts (independent, but migrated after accounts for consistency)
* 3. ActiveDid (depends on accounts and settings)
* 4. Contacts (independent, but migrated after accounts for consistency)
*
* The migration runs within a transaction to ensure atomicity. If any step fails,
* the entire migration is rolled back.
@@ -1332,9 +1467,21 @@ export async function migrateAll(): Promise<MigrationResult> {
result.settingsMigrated = settingsResult.settingsMigrated;
result.warnings.push(...settingsResult.warnings);
// Step 3: Migrate Contacts (independent, but after accounts for consistency)
// Step 3: Migrate ActiveDid (depends on accounts and settings)
logger.info("[MigrationService] Step 3: Migrating activeDid...");
const activeDidResult = await migrateActiveDid();
if (!activeDidResult.success) {
result.errors.push(
`ActiveDid migration failed: ${activeDidResult.errors.join(", ")}`,
);
// Don't fail the entire migration for activeDid issues
logger.warn("[MigrationService] ActiveDid migration failed, but continuing with migration");
}
result.warnings.push(...activeDidResult.warnings);
// Step 4: Migrate Contacts (independent, but after accounts for consistency)
// ... but which is better done through the contact import view
// logger.info("[MigrationService] Step 3: Migrating contacts...");
// logger.info("[MigrationService] Step 4: Migrating contacts...");
// const contactsResult = await migrateContacts();
// if (!contactsResult.success) {
// result.errors.push(