@ -4,7 +4,7 @@
This document outlines the migration process from Dexie.js to absurd-sql for the TimeSafari app's storage implementation. The migration aims to provide a consistent SQLite-based storage solution across all platforms while maintaining data integrity and ensuring a smooth transition for users.
This document outlines the migration process from Dexie.js to absurd-sql for the TimeSafari app's storage implementation. The migration aims to provide a consistent SQLite-based storage solution across all platforms while maintaining data integrity and ensuring a smooth transition for users.
**Current Status**: The migration is in **Phase 2** with a well-defined migration fence in place. Core settings and account data have been migrated, with contact migration in progress.
**Current Status**: The migration is in **Phase 2** with a well-defined migration fence in place. Core settings and account data have been migrated, with contact migration in progress. **ActiveDid migration has been implemented** to ensure user identity continuity.
## Migration Goals
## Migration Goals
@ -12,403 +12,215 @@ This document outlines the migration process from Dexie.js to absurd-sql for the
- Preserve all existing data
- Preserve all existing data
- Maintain data relationships
- Maintain data relationships
- Ensure data consistency
- Ensure data consistency
- **Preserve user's active identity**
2. **Performance**
2. **Performance**
- Improve query performance
- Improve query performance
- Reduce storage overhead
- Reduce storage overhead
- Optimize for platform-specific features
- Optimize for platform-specific capabilities
3. **Security**
- Maintain or improve encryption
- Preserve access controls
- Enhance data protection
4. **User Experience**
- Zero data loss
- Minimal downtime
- Automatic migration where possible
## Migration Fence
The migration is controlled by a **migration fence** that separates legacy Dexie code from the new SQLite implementation. See [Migration Fence Definition ](./migration-fence-definition.md ) for complete details.
### Key Fence Components
1. **Configuration Control** : `USE_DEXIE_DB = false` (default)
2. **Service Layer** : All database operations go through `PlatformService`
3. **Migration Tools** : Exclusive access to both databases during migration
4. **Code Boundaries** : Clear separation between legacy and new code
## Prerequisites
1. **Backup Requirements**
```typescript
interface MigrationBackup {
timestamp: number;
accounts: Account[];
settings: Setting[];
contacts: Contact[];
metadata: {
version: string;
platform: string;
dexieVersion: string;
};
}
```
2. **Dependencies**
```json
{
"@jlongster/sql.js": "^1.8.0",
"absurd-sql": "^1.8.0"
}
```
3. **Storage Requirements**
- Sufficient IndexedDB quota
- Available disk space for SQLite
- Backup storage space
4. **Platform Support**
- Web: Modern browser with IndexedDB support
- iOS: iOS 13+ with SQLite support
- Android: Android 5+ with SQLite support
- Electron: Latest version with SQLite support
## Current Migration Status
### ✅ Completed
- **SQLite Database Service** : Fully implemented with absurd-sql
- **Platform Service Layer** : Unified database interface
- **Migration Tools** : Data comparison and transfer utilities
- **Settings Migration** : Core user settings transferred
- **Account Migration** : Identity and key management
- **Schema Migration** : Complete table structure migration
### 🔄 In Progress
- **Contact Migration** : User contact data (via import interface)
- **Data Verification** : Comprehensive integrity checks
- **Performance Optimization** : Query optimization and indexing
### 📋 Planned
- **Code Cleanup** : Remove unused Dexie imports
- **Documentation Updates** : Complete migration guides
- **Testing** : Comprehensive migration testing
## Migration Process
3. **User Experience**
- Seamless transition with no data loss
- Maintain user's active identity and preferences
- Preserve application state
### 1. Preparation
## Migration Architecture
```typescript
### Migration Fence
// src/services/storage/migration/MigrationService.ts
The migration fence is defined by the `USE_DEXIE_DB` constant in `src/constants/app.ts` :
import initSqlJs from '@jlongster/sql.js';
- `USE_DEXIE_DB = false` (default): Uses SQLite database
import { SQLiteFS } from 'absurd-sql';
- `USE_DEXIE_DB = true` : Uses Dexie database (for migration purposes)
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';
class MigrationService {
private async checkPrerequisites(): Promise< void > {
// Check IndexedDB availability
if (!window.indexedDB) {
throw new StorageError(
'IndexedDB not available',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
// Check storage quota
const quota = await navigator.storage.estimate();
if (quota.quota & & quota.usage & & quota.usage > quota.quota * 0.9) {
throw new StorageError(
'Insufficient storage space',
StorageErrorCodes.STORAGE_FULL
);
}
// Check platform support
const capabilities = await PlatformDetection.getCapabilities();
if (!capabilities.hasFileSystem) {
throw new StorageError(
'Platform does not support required features',
StorageErrorCodes.INITIALIZATION_FAILED
);
}
}
private async createBackup(): Promise< MigrationBackup > {
const dexieDB = new Dexie('TimeSafariDB');
return {
timestamp: Date.now(),
accounts: await dexieDB.accounts.toArray(),
settings: await dexieDB.settings.toArray(),
contacts: await dexieDB.contacts.toArray(),
metadata: {
version: '1.0.0',
platform: await PlatformDetection.getPlatform(),
dexieVersion: Dexie.version
}
};
}
}
```
### 2. Data Migration
### Migration Order
The migration follows a specific order to maintain data integrity:
```typescript
1. **Accounts** (foundational - contains DIDs)
// src/services/storage/migration/DataMigration.ts
2. **Settings** (references accountDid, activeDid)
class DataMigration {
3. **ActiveDid** (depends on accounts and settings) ⭐ **NEW**
async migrateAccounts(): Promise< MigrationResult > {
4. **Contacts** (independent, but migrated after accounts for consistency)
const result: MigrationResult = {
success: true,
## ActiveDid Migration ⭐ **NEW FEATURE**
accountsMigrated: 0,
errors: [],
warnings: []
};
try {
const dexieAccounts = await this.getDexieAccounts();
for (const account of dexieAccounts) {
try {
await this.migrateAccount(account);
result.accountsMigrated++;
} catch (error) {
result.errors.push(`Failed to migrate account ${account.did}: ${error}`);
result.success = false;
}
}
} catch (error) {
result.errors.push(`Account migration failed: ${error}`);
result.success = false;
}
return result;
}
async migrateSettings(): Promise< MigrationResult > {
const result: MigrationResult = {
success: true,
settingsMigrated: 0,
errors: [],
warnings: []
};
try {
const dexieSettings = await this.getDexieSettings();
for (const setting of dexieSettings) {
try {
await this.migrateSetting(setting);
result.settingsMigrated++;
} catch (error) {
result.errors.push(`Failed to migrate setting ${setting.id}: ${error}`);
result.success = false;
}
}
} catch (error) {
result.errors.push(`Settings migration failed: ${error}`);
result.success = false;
}
return result;
}
async migrateContacts(): Promise< MigrationResult > {
// Contact migration is handled through the contact import interface
// This provides better user control and validation
const result: MigrationResult = {
success: true,
contactsMigrated: 0,
errors: [],
warnings: []
};
try {
const dexieContacts = await this.getDexieContacts();
// Redirect to contact import view with pre-populated data
await this.redirectToContactImport(dexieContacts);
result.contactsMigrated = dexieContacts.length;
} catch (error) {
result.errors.push(`Contact migration failed: ${error}`);
result.success = false;
}
return result;
}
}
```
### 3. Verification
### Problem Solved
Previously, the `activeDid` setting was not migrated from Dexie to SQLite, causing users to lose their active identity after migration.
### Solution Implemented
The migration now includes a dedicated step for migrating the `activeDid` :
1. **Detection** : Identifies the `activeDid` from Dexie master settings
2. **Validation** : Verifies the `activeDid` exists in SQLite accounts
3. **Migration** : Updates SQLite master settings with the `activeDid`
4. **Error Handling** : Graceful handling of missing accounts
### Implementation Details
#### New Function: `migrateActiveDid()`
```typescript
```typescript
class MigrationVerification {
export async function migrateActiveDid(): Promise< MigrationResult > {
async verifyMigration(dexieData: MigrationData): Promise< boolean > {
// 1. Get Dexie settings to find the activeDid
// Verify account count
const dexieSettings = await getDexieSettings();
const accountResult = await this.sqliteDB.exec('SELECT COUNT(*) as count FROM accounts');
const masterSettings = dexieSettings.find(setting => !setting.accountDid);
const accountCount = accountResult[0].values[0][0];
if (accountCount !== dexieData.accounts.length) {
// 2. Verify the activeDid exists in SQLite accounts
return false;
const accountExists = await platformService.dbQuery(
}
"SELECT did FROM accounts WHERE did = ?",
[dexieActiveDid],
// Verify settings count
);
const settingsResult = await this.sqliteDB.exec('SELECT COUNT(*) as count FROM settings');
const settingsCount = settingsResult[0].values[0][0];
// 3. Update SQLite master settings
if (settingsCount !== dexieData.settings.length) {
await updateDefaultSettings({ activeDid: dexieActiveDid });
return false;
}
// Verify data integrity
for (const account of dexieData.accounts) {
const result = await this.sqliteDB.exec(
'SELECT * FROM accounts WHERE did = ?',
[account.did]
);
const migratedAccount = result[0]?.values[0];
if (!migratedAccount ||
migratedAccount[1] !== account.publicKeyHex) {
return false;
}
}
return true;
}
}
}
```
```
## Using the Migration Interface
#### Enhanced `migrateSettings()` Function
The settings migration now includes activeDid handling:
- Extracts `activeDid` from Dexie master settings
- Validates account existence in SQLite
- Updates SQLite master settings with the `activeDid`
### Accessing Migration Tools
#### Updated `migrateAll()` Function
The complete migration now includes a dedicated step for activeDid:
```typescript
// Step 3: Migrate ActiveDid (depends on accounts and settings)
logger.info("[MigrationService] Step 3: Migrating activeDid...");
const activeDidResult = await migrateActiveDid();
```
1. Navigate to the **Account** page in the TimeSafari app
### Benefits
2. Scroll down to find the **Database Migration** link
- ✅ **User Identity Preservation** : Users maintain their active identity
3. Click the link to open the migration interface
- ✅ **Seamless Experience** : No need to manually select identity after migration
- ✅ **Data Consistency** : Ensures all identity-related settings are preserved
- ✅ **Error Resilience** : Graceful handling of edge cases
### Migration Steps
## Migration Process
1. **Compare Databases**
### Phase 1: Preparation ✅
- Click "Compare Databases" to see difference s
- [x] Enable Dexie database access
- Review the comparison result s
- [x] Implement data comparison tools
- Identify data that needs migration
- [x] Create migration service structure
2. **Migrate Settings**
### Phase 2: Core Migration ✅
- Click "Migrate Settings" to transfer user settings
- [x] Account migration with `importFromMnemonic`
- Verify settings are correctly transferred
- [x] Settings migration (excluding activeDid)
- Check application functionality
- [x] **ActiveDid migration** ⭐ **COMPLETED**
- [x] Contact migration framework
3. **Migrate Contacts**
### Phase 3: Validation and Cleanup 🔄
- Click "Migrate Contacts" to open contact import
- [ ] Comprehensive data validation
- Review and confirm contact data
- [ ] Performance testing
- Complete the import process
- [ ] User acceptance testing
- [ ] Dexie removal
4. **Verify Migration**
## Usage
- Run comparison again to verify completion
- Test application functionality
- Export backup data if needed
## Error Handling
### Manual Migration
```typescript
import { migrateAll, migrateActiveDid } from '../services/indexedDBMigrationService';
### Common Issues
// Complete migration
const result = await migrateAll();
1. **Dexie Database Not Enabled**
// Or migrate just the activeDid
- **Error** : "Dexie database is not enabled"
const activeDidResult = await migrateActiveDid();
- **Solution** : Set `USE_DEXIE_DB = true ` in `constants/app.ts` temporarily
```
2. **Database Connection Issues**
### Migration Verification
- **Error** : "Failed to retrieve data"
```typescript
- **Solution** : Check database initialization and permissions
import { compareDatabases } from '../services/indexedDBMigrationService';
3. **Migration Failures**
const comparison = await compareDatabases();
- **Error** : "Migration failed: [specific error]"
console.log('Migration differences:', comparison.differences);
- **Solution** : Review error details and check data integrity
```
### Error Recovery
## Error Handling
1. **Review** error messages carefully
### ActiveDid Migration Errors
2. **Check** browser console for additional details
- **Missing Account** : If the `activeDid` from Dexie doesn't exist in SQLite accounts
3. **Verify** database connectivity and permissions
- **Database Errors** : Connection or query failures
4. **Retry** the operation if appropriate
- **Settings Update Failures** : Issues updating SQLite master settings
5. **Export** comparison data for manual review if needed
## Best Practices
### Recovery Strategies
1. **Automatic Recovery** : Migration continues even if activeDid migration fails
2. **Manual Recovery** : Users can manually select their identity after migration
3. **Fallback** : System creates new identity if none exists
### Before Migration
## Security Considerations
1. **Backup** your data if possible
### Data Protection
2. **Test** the migration on a small dataset first
- All sensitive data (mnemonics, private keys) are encrypted
3. **Verify** that both databases are accessible
- Migration preserves encryption standards
4. **Review** the comparison results before migrating
- No plaintext data exposure during migration
### During Migration
### Identity Verification
- ActiveDid migration validates account existence
- Prevents setting non-existent identities as active
- Maintains cryptographic integrity
1. **Don't** interrupt the migration process
## Testing
2. **Monitor** the progress and error messages
3. **Note** any warnings or skipped records
4. **Export** comparison data for reference
### After Migration
### Migration Testing
```bash
# Enable Dexie for testing
# Set USE_DEXIE_DB = true in constants/app.ts
1. **Verify** that data was migrated correctly
# Run migration
2. **Test** the application functionality
npm run migrate
3. **Disable** Dexie database (`USE_DEXIE_DB = false`)
4. **Clean up** any temporary files or exports
## Performance Considerations
# Verify results
npm run test:migration
```
### 1. Migration Performance
### ActiveDid Testing
- Use transactions for bulk data transfer
```typescript
- Implement progress indicators
// Test activeDid migration specifically
- Process data in background when possible
const result = await migrateActiveDid();
expect(result.success).toBe(true);
expect(result.warnings).toContain('Successfully migrated activeDid');
```
### 2. Application Performance
## Troubleshooting
- Optimize SQLite queries
- Maintain proper database indexes
- Use efficient memory management
## Security Consideration s
### Common Issues
### 1. Data Protection
1. **ActiveDid Not Found**
- Maintain encryption standards across migration
- Ensure accounts were migrated before activeDid migration
- Preserve user privacy during migration
- Check that the Dexie activeDid exists in SQLite accounts
- Log all migration operations
### 2. Error Handling
2. **Migration Failures**
- Handle migration failures gracefully
- Verify Dexie database is accessible
- Provide clear user messaging
- Check SQLite database permissions
- Maintain rollback capabilitie s
- Review migration logs for specific error s
## Testing Strategy
3. **Data Inconsistencies**
- Use `compareDatabases()` to identify differences
- Re-run migration if necessary
- Check for duplicate or conflicting records
### 1. Migration Testing
### Debugg ing
```typescript
```typescript
describe('Database Migration', () => {
// Enable detailed logging
it('should migrate data without loss', async () => {
logger.setLevel('debug');
// 1. Enable Dexie
// 2. Create test data
// 3. Run migration
// 4. Verify data integrity
// 5. Disable Dexie
});
});
```
### 2. Application Testing
// Check migration status
```typescript
const comparison = await compareDatabases();
describe('Feature with Database', () => {
console.log('Settings differences:', comparison.differences.settings);
it('should work with SQLite only', async () => {
// Test with USE_DEXIE_DB = false
// Verify all operations use PlatformService
});
});
```
```
## Future Enhancements
### Planned Improvements
1. **Batch Processing** : Optimize for large datasets
2. **Incremental Migration** : Support partial migrations
3. **Rollback Capability** : Ability to revert migration
4. **Progress Tracking** : Real-time migration progress
### Performance Optimizations
1. **Parallel Processing** : Migrate independent data concurrently
2. **Memory Management** : Optimize for large datasets
3. **Transaction Batching** : Reduce database round trips
## Conclusion
## Conclusion
The migration from Dexie to absurd-sql provides:
The Dexie to SQLite migration provides a robust, secure, and user-friendly transition path. The addition of activeDid migration ensures that users maintain their identity continuity throughout the migration process, significantly improving the user experience.
- **Better Performance** : Improved query performance and storage efficiency
- **Cross-Platform Consistency** : Unified database interface across platforms
- **Enhanced Security** : Better encryption and access controls
- **Future-Proof Architecture** : Modern SQLite-based storage system
The migration fence ensures a controlled and safe transition while maintaining data integrity and application stability.
The migration fence architecture allows for controlled, reversible migration while maintaining application stability and data integrity.