10 KiB
ActiveDid Migration Plan - Separate Table Architecture
Author: Matthew Raymer Date: 2025-08-29T15:00Z Status: 🎯 IMPLEMENTATION READY - Streamlined for existing migration service
Objective
Move the activeDid field from the settings table to a dedicated
active_identity table to improve database architecture, prevent data corruption,
and separate identity selection from user preferences.
Result
This document serves as the focused implementation guide for the ActiveDid migration, integrated with the existing IndexedDB migration service.
Use/Run
Reference this document during implementation to ensure all migration
steps are followed correctly. Critical: This plan integrates with the
existing indexedDBMigrationService.ts and maintains backward compatibility.
Context & Scope
- In scope:
- Database schema modification for active_identity table with proper constraints
- Migration of existing activeDid data with validation
- Updates to PlatformServiceMixin API layer
- Integration with existing IndexedDB migration service
- Out of scope:
- Changes to user interface for identity selection
- Modifications to identity creation logic
- Changes to authentication flow
- Updates to individual components (handled by API layer)
Environment & Preconditions
- OS/Runtime: All platforms (Web, Electron, iOS, Android)
- Versions/Builds: Current development branch, SQLite database
- Services/Endpoints: Local database, PlatformServiceMixin, indexedDBMigrationService
- Auth mode: Existing authentication system unchanged
Architecture / Process Overview
The migration integrates with the existing IndexedDB migration service:
flowchart TD
A[Current State<br/>activeDid in settings] --> B[Phase 1: Schema Creation<br/>Add active_identity table with constraints]
B --> C[Phase 2: Data Migration<br/>Copy activeDid data with validation]
C --> D[Phase 3: API Updates<br/>Update PlatformServiceMixin methods]
D --> E[Final State<br/>Separate active_identity table + dual-write]
F[Existing IndexedDB Migration] --> G[Enhanced with active_identity support]
G --> H[Maintains backward compatibility]
H --> I[Preserves existing migration paths]
Current Codebase Assessment
✅ What's Already Implemented
- Database Schema:
activeDidfield exists insettingstable (src/db-sql/migration.ts:67) - Constants:
MASTER_SETTINGS_KEY = "1"is properly defined (src/db/tables/settings.ts:88) - Types: Settings type includes
activeDid?: string(src/db/tables/settings.ts:25) - Migration Infrastructure: SQLite migration system exists (
src/db-sql/migration.ts:31) - IndexedDB Migration Service: Complete service exists (
src/services/indexedDBMigrationService.ts) - PlatformServiceMixin: Basic structure exists with
$updateActiveDid()method
❌ What Needs Implementation
- Missing Table:
active_identitytable doesn't exist in current schema - Missing API Methods: Core PlatformServiceMixin methods need implementation
- Missing Types:
ActiveIdentityinterface needs creation
Required Changes
1. Database Schema via migration.ts
Add migration 003 to existing MIGRATIONS array:
// Add to MIGRATIONS array in src/db-sql/migration.ts
{
name: "003_active_did_separate_table",
sql: `
-- Create new active_identity table with proper constraints
CREATE TABLE IF NOT EXISTS active_identity (
id INTEGER PRIMARY KEY CHECK (id = 1),
activeDid TEXT NOT NULL,
lastUpdated TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE CASCADE
);
-- Add performance indexes
CREATE INDEX IF NOT EXISTS idx_active_identity_activeDid ON active_identity(activeDid);
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id);
-- Insert default record (will be updated during migration)
INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
`,
},
2. Type Definitions
Create ActiveIdentity interface in src/db/tables/settings.ts:
// Add to src/db/tables/settings.ts
export interface ActiveIdentity {
id: number;
activeDid: string;
lastUpdated: string;
}
3. PlatformServiceMixin Methods
Implement required methods in src/utils/PlatformServiceMixin.ts:
// Add to PlatformServiceMixin methods section
async $getActiveIdentity(): Promise<ActiveIdentity> {
try {
const result = await this.$dbQuery(
"SELECT id, activeDid, lastUpdated FROM active_identity WHERE id = 1"
);
if (result?.values?.length) {
const [id, activeDid, lastUpdated] = result.values[0];
return { id: id as number, activeDid: activeDid as string, lastUpdated: lastUpdated as string };
}
// Return default if no record exists
return { id: 1, activeDid: '', lastUpdated: new Date().toISOString() };
} catch (error) {
logger.error("[PlatformServiceMixin] Error getting active identity:", error);
throw error;
}
}
async $accountSettings(did?: string, defaults: Settings = {}): Promise<Settings> {
try {
// Get settings without activeDid
const settings = await this.$getSettings(MASTER_SETTINGS_KEY, defaults);
if (!settings) {
return defaults;
}
// Get activeDid from new table>
// Enhanced update activeDid method with dual-write pattern
async $updateActiveDid(newDid: string | null): Promise<boolean> {
try {
if (newDid === null) {
// Clear active identity in both tables
await this.$dbExec(
"UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1"
);
// Keep legacy field in sync (backward compatibility)
await this.$dbExec(
"UPDATE settings SET activeDid = '' WHERE id = ?",
[MASTER_SETTINGS_KEY]
);
} else {
// Validate DID exists before setting
const accountExists = await this.$dbQuery(
"SELECT did FROM accounts WHERE did = ?",
[newDid]
);
if (!accountExists?.values?.length) {
logger.error(`[PlatformServiceMixin] Cannot set activeDid to non-existent DID: ${newDid}`);
return false;
}
// Update active identity in new table
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[newDid]
);
// Keep legacy field in sync (backward compatibility)
await this.$dbExec(
"UPDATE settings SET activeDid = ? WHERE id = ?",
[newDid, MASTER_SETTINGS_KEY]
);
}
// Update internal tracking (existing functionality)
await this._updateInternalActiveDid(newDid);
return true;
} catch (error) {
logger.error("[PlatformServiceMixin] Error updating activeDid:", error);
return false;
}
}
4. Integration with Existing IndexedDB Migration Service
The existing indexedDBMigrationService.ts already handles activeDid migration
from Dexie to SQLite. This plan adds the separate table architecture while
maintaining compatibility.
No changes needed to the existing migration service - it will continue to work with the dual-write pattern.
What Doesn't Need to Change
- All Vue components - API layer handles migration transparently
- Platform services - Use PlatformServiceMixin, no direct access
- User interface - No changes to identity selection UI
- Authentication flow - Existing system unchanged
- Component logic - All activeDid handling through API methods
- Migration system - Use existing migration.ts approach
- IndexedDB migration service - Continues working unchanged
- Existing database operations - All current queries continue working
Implementation Steps
Step 1: Add Migration
- Add migration 003 to
MIGRATIONSarray insrc/db-sql/migration.ts - Deploy migration to create
active_identitytable
Step 2: Implement API Methods
- Create
ActiveIdentityinterface insrc/db/tables/settings.ts - Implement
$getActiveIdentity()method in PlatformServiceMixin - Implement
$accountSettings()method in PlatformServiceMixin - Enhance
$updateActiveDid()method with dual-write pattern
Step 3: Test Integration
- Test new methods with existing components
- Verify dual-write pattern works correctly
- Validate backward compatibility
Backward Compatibility
Critical Requirements
- IndexedDB Migration Service: Must continue working unchanged
- MASTER_SETTINGS_KEY = "1": Must be preserved for legacy support
- Dual-Write Pattern: Ensures both old and new systems stay in sync
- Existing Queries: All current database operations continue working
Migration Strategy
- Phase 1: Add new table alongside existing system
- Phase 2: Use dual-write pattern during transition
- Phase 3: Future cleanup (not in current scope)
Rollback Strategy
If migration fails, the existing activeDid field in settings table remains functional:
-- Rollback: Remove new table
DROP TABLE IF EXISTS active_identity;
No data loss risk - the legacy field continues working unchanged.
Success Criteria
active_identitytable created with proper constraints- All new PlatformServiceMixin methods implemented and tested
- Dual-write pattern working correctly
- Existing IndexedDB migration service continues working
- No breaking changes to existing functionality
- All platforms tested and verified
Risks & Mitigation
Low Risk: Migration Failure
- Mitigation: Rollback removes new table, legacy system continues working
- Impact: No data loss, no service interruption
Low Risk: API Changes
- Mitigation: Dual-write pattern maintains backward compatibility
- Impact: Existing components continue working unchanged
Low Risk: Performance Impact
- Mitigation: Proper indexing and minimal additional queries
- Impact: Negligible performance change
Summary
This migration plan:
- Adds new architecture without breaking existing functionality
- Integrates seamlessly with existing IndexedDB migration service
- Maintains full backward compatibility during transition
- Requires minimal changes to existing codebase
- Provides clear rollback path if issues arise
The plan focuses only on necessary changes while preserving all existing functionality and migration paths.