# Migration Guide: Dexie to absurd-sql

## Overview

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.

## Migration Goals

1. **Data Integrity**
   - Preserve all existing data
   - Maintain data relationships
   - Ensure data consistency

2. **Performance**
   - Improve query performance
   - Reduce storage overhead
   - Optimize for platform-specific features

3. **Security**
   - Maintain or improve encryption
   - Preserve access controls
   - Enhance data protection

4. **User Experience**
   - Zero data loss
   - Minimal downtime
   - Automatic migration where possible

## 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

## Migration Process

### 1. Preparation

```typescript
// src/services/storage/migration/MigrationService.ts
import initSqlJs from '@jlongster/sql.js';
import { SQLiteFS } from 'absurd-sql';
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';

export class MigrationService {
  private static instance: MigrationService;
  private backup: MigrationBackup | null = null;
  private sql: any = null;
  private db: any = null;

  async prepare(): Promise<void> {
    try {
      // 1. Check prerequisites
      await this.checkPrerequisites();
      
      // 2. Create backup
      this.backup = await this.createBackup();
      
      // 3. Verify backup integrity
      await this.verifyBackup();
      
      // 4. Initialize absurd-sql
      await this.initializeAbsurdSql();
    } catch (error) {
      throw new StorageError(
        'Migration preparation failed',
        StorageErrorCodes.MIGRATION_FAILED,
        error
      );
    }
  }

  private async initializeAbsurdSql(): Promise<void> {
    // Initialize SQL.js
    this.sql = await initSqlJs({
      locateFile: (file: string) => {
        return new URL(`/node_modules/@jlongster/sql.js/dist/${file}`, import.meta.url).href;
      }
    });

    // Setup SQLiteFS with IndexedDB backend
    const sqlFS = new SQLiteFS(this.sql.FS, new IndexedDBBackend());
    this.sql.register_for_idb(sqlFS);

    // Create and mount filesystem
    this.sql.FS.mkdir('/sql');
    this.sql.FS.mount(sqlFS, {}, '/sql');

    // Open database
    const path = '/sql/db.sqlite';
    if (typeof SharedArrayBuffer === 'undefined') {
      let stream = this.sql.FS.open(path, 'a+');
      await stream.node.contents.readIfFallback();
      this.sql.FS.close(stream);
    }

    this.db = new this.sql.Database(path, { filename: true });
    if (!this.db) {
      throw new StorageError(
        'Database initialization failed',
        StorageErrorCodes.INITIALIZATION_FAILED
      );
    }

    // Configure database
    await this.db.exec(`PRAGMA journal_mode=MEMORY;`);
  }

  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

```typescript
// src/services/storage/migration/DataMigration.ts
export class DataMigration {
  async migrate(backup: MigrationBackup): Promise<void> {
    try {
      // 1. Create new database schema
      await this.createSchema();
      
      // 2. Migrate accounts
      await this.migrateAccounts(backup.accounts);
      
      // 3. Migrate settings
      await this.migrateSettings(backup.settings);
      
      // 4. Migrate contacts
      await this.migrateContacts(backup.contacts);
      
      // 5. Verify migration
      await this.verifyMigration(backup);
    } catch (error) {
      // 6. Handle failure
      await this.handleMigrationFailure(error, backup);
    }
  }

  private async migrateAccounts(accounts: Account[]): Promise<void> {
    // Use transaction for atomicity
    await this.db.exec('BEGIN TRANSACTION;');
    try {
      for (const account of accounts) {
        await this.db.run(`
          INSERT INTO accounts (did, public_key_hex, created_at, updated_at)
          VALUES (?, ?, ?, ?)
        `, [
          account.did,
          account.publicKeyHex,
          account.createdAt,
          account.updatedAt
        ]);
      }
      await this.db.exec('COMMIT;');
    } catch (error) {
      await this.db.exec('ROLLBACK;');
      throw error;
    }
  }

  private async verifyMigration(backup: MigrationBackup): Promise<void> {
    // Verify account count
    const result = await this.db.exec('SELECT COUNT(*) as count FROM accounts');
    const accountCount = result[0].values[0][0];
    
    if (accountCount !== backup.accounts.length) {
      throw new StorageError(
        'Account count mismatch',
        StorageErrorCodes.VERIFICATION_FAILED
      );
    }

    // Verify data integrity
    await this.verifyDataIntegrity(backup);
  }
}
```

### 3. Rollback Strategy

```typescript
// src/services/storage/migration/RollbackService.ts
export class RollbackService {
  async rollback(backup: MigrationBackup): Promise<void> {
    try {
      // 1. Stop all database operations
      await this.stopDatabaseOperations();
      
      // 2. Restore from backup
      await this.restoreFromBackup(backup);
      
      // 3. Verify restoration
      await this.verifyRestoration(backup);
      
      // 4. Clean up absurd-sql
      await this.cleanupAbsurdSql();
    } catch (error) {
      throw new StorageError(
        'Rollback failed',
        StorageErrorCodes.ROLLBACK_FAILED,
        error
      );
    }
  }

  private async restoreFromBackup(backup: MigrationBackup): Promise<void> {
    const dexieDB = new Dexie('TimeSafariDB');
    
    // Restore accounts
    await dexieDB.accounts.bulkPut(backup.accounts);
    
    // Restore settings
    await dexieDB.settings.bulkPut(backup.settings);
    
    // Restore contacts
    await dexieDB.contacts.bulkPut(backup.contacts);
  }
}
```

## Migration UI

```vue
<!-- src/components/MigrationProgress.vue -->
<template>
  <div class="migration-progress">
    <h2>Database Migration</h2>
    
    <div class="progress-container">
      <div class="progress-bar" :style="{ width: `${progress}%` }" />
      <div class="progress-text">{{ progress }}%</div>
    </div>
    
    <div class="status-message">{{ statusMessage }}</div>
    
    <div v-if="error" class="error-message">
      {{ error }}
      <button @click="retryMigration">Retry</button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { MigrationService } from '@/services/storage/migration/MigrationService';

const progress = ref(0);
const statusMessage = ref('Preparing migration...');
const error = ref<string | null>(null);

const migrationService = MigrationService.getInstance();

async function startMigration() {
  try {
    // 1. Preparation
    statusMessage.value = 'Creating backup...';
    await migrationService.prepare();
    progress.value = 20;
    
    // 2. Data migration
    statusMessage.value = 'Migrating data...';
    await migrationService.migrate();
    progress.value = 80;
    
    // 3. Verification
    statusMessage.value = 'Verifying migration...';
    await migrationService.verify();
    progress.value = 100;
    
    statusMessage.value = 'Migration completed successfully!';
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Migration failed';
    statusMessage.value = 'Migration failed';
  }
}

async function retryMigration() {
  error.value = null;
  progress.value = 0;
  await startMigration();
}

onMounted(() => {
  startMigration();
});
</script>

<style scoped>
.migration-progress {
  padding: 2rem;
  max-width: 600px;
  margin: 0 auto;
}

.progress-container {
  position: relative;
  height: 20px;
  background: #eee;
  border-radius: 10px;
  overflow: hidden;
  margin: 1rem 0;
}

.progress-bar {
  position: absolute;
  height: 100%;
  background: #4CAF50;
  transition: width 0.3s ease;
}

.progress-text {
  position: absolute;
  width: 100%;
  text-align: center;
  line-height: 20px;
  color: #000;
}

.status-message {
  text-align: center;
  margin: 1rem 0;
}

.error-message {
  color: #f44336;
  text-align: center;
  margin: 1rem 0;
}

button {
  margin-top: 1rem;
  padding: 0.5rem 1rem;
  background: #2196F3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #1976D2;
}
</style>
```

## Testing Strategy

1. **Unit Tests**
   ```typescript
   // src/services/storage/migration/__tests__/MigrationService.spec.ts
   describe('MigrationService', () => {
     it('should initialize absurd-sql correctly', async () => {
       const service = MigrationService.getInstance();
       await service.initializeAbsurdSql();
       
       expect(service.isInitialized()).toBe(true);
       expect(service.getDatabase()).toBeDefined();
     });

     it('should create valid backup', async () => {
       const service = MigrationService.getInstance();
       const backup = await service.createBackup();
       
       expect(backup).toBeDefined();
       expect(backup.accounts).toBeInstanceOf(Array);
       expect(backup.settings).toBeInstanceOf(Array);
       expect(backup.contacts).toBeInstanceOf(Array);
     });

     it('should migrate data correctly', async () => {
       const service = MigrationService.getInstance();
       const backup = await service.createBackup();
       
       await service.migrate(backup);
       
       // Verify migration
       const accounts = await service.getMigratedAccounts();
       expect(accounts).toHaveLength(backup.accounts.length);
     });

     it('should handle rollback correctly', async () => {
       const service = MigrationService.getInstance();
       const backup = await service.createBackup();
       
       // Simulate failed migration
       await service.migrate(backup);
       await service.simulateFailure();
       
       // Perform rollback
       await service.rollback(backup);
       
       // Verify rollback
       const accounts = await service.getOriginalAccounts();
       expect(accounts).toHaveLength(backup.accounts.length);
     });
   });
   ```

2. **Integration Tests**
   ```typescript
   // src/services/storage/migration/__tests__/integration/Migration.spec.ts
   describe('Migration Integration', () => {
     it('should handle concurrent access during migration', async () => {
       const service = MigrationService.getInstance();
       
       // Start migration
       const migrationPromise = service.migrate();
       
       // Simulate concurrent access
       const accessPromises = Array(5).fill(null).map(() =>
         service.getAccount('did:test:123')
       );
       
       // Wait for all operations
       const [migrationResult, ...accessResults] = await Promise.allSettled([
         migrationPromise,
         ...accessPromises
       ]);
       
       // Verify results
       expect(migrationResult.status).toBe('fulfilled');
       expect(accessResults.some(r => r.status === 'rejected')).toBe(true);
     });

     it('should maintain data integrity during platform transition', async () => {
       const service = MigrationService.getInstance();
       
       // Simulate platform change
       await service.simulatePlatformChange();
       
       // Verify data
       const accounts = await service.getAllAccounts();
       const settings = await service.getAllSettings();
       const contacts = await service.getAllContacts();
       
       expect(accounts).toBeDefined();
       expect(settings).toBeDefined();
       expect(contacts).toBeDefined();
     });
   });
   ```

## Success Criteria

1. **Data Integrity**
   - [ ] All accounts migrated successfully
   - [ ] All settings preserved
   - [ ] All contacts transferred
   - [ ] No data corruption

2. **Performance**
   - [ ] Migration completes within acceptable time
   - [ ] No significant performance degradation
   - [ ] Efficient storage usage
   - [ ] Smooth user experience

3. **Security**
   - [ ] Encrypted data remains secure
   - [ ] Access controls maintained
   - [ ] No sensitive data exposure
   - [ ] Secure backup process

4. **User Experience**
   - [ ] Clear migration progress
   - [ ] Informative error messages
   - [ ] Automatic recovery from failures
   - [ ] No data loss

## Rollback Plan

1. **Automatic Rollback**
   - Triggered by migration failure
   - Restores from verified backup
   - Maintains data consistency
   - Logs rollback reason

2. **Manual Rollback**
   - Available through settings
   - Requires user confirmation
   - Preserves backup data
   - Provides rollback status

3. **Emergency Recovery**
   - Manual backup restoration
   - Database repair tools
   - Data recovery procedures
   - Support contact information

## Post-Migration

1. **Verification**
   - Data integrity checks
   - Performance monitoring
   - Error rate tracking
   - User feedback collection

2. **Cleanup**
   - Remove old database
   - Clear migration artifacts
   - Update application state
   - Archive backup data

3. **Monitoring**
   - Track migration success rate
   - Monitor performance metrics
   - Collect error reports
   - Gather user feedback

## Support

For assistance with migration:
1. Check the troubleshooting guide
2. Review error logs
3. Contact support team
4. Submit issue report

## Timeline

1. **Preparation Phase** (1 week)
   - Backup system implementation
   - Migration service development
   - Testing framework setup

2. **Testing Phase** (2 weeks)
   - Unit testing
   - Integration testing
   - Performance testing
   - Security testing

3. **Deployment Phase** (1 week)
   - Staged rollout
   - Monitoring
   - Support preparation
   - Documentation updates

4. **Post-Deployment** (2 weeks)
   - Monitoring
   - Bug fixes
   - Performance optimization
   - User feedback collection 