Migrations and Schema Evolution

Local-first migrations must preserve real user data without a server rollback button.

Migration Rules

  1. Version every durable format.
  2. Keep old schemas or sample fixtures in version control.
  3. Test every supported old version to latest.
  4. Make destructive migration an explicit product decision, not a fallback.
  5. Include backup-before-migrate for high-value data.
  6. Make derived indexes rebuildable.

Cross-Platform Map

StoreVersion mechanismTest shape
Roomdatabase version, exported schemas, Migration classesold DB fixture -> migration -> schema validation -> data assertions
SQLitePRAGMA user_version or app metadata tablecopy old DB -> migration transaction -> integrity checks
IndexedDBdatabase version and onupgradeneeded/Dexie version()old browser DB fixture -> upgrade -> object assertions
Tauri SQLplugin migrationsinstall old app state -> launch new build -> verify DB
Filesmanifest formatVersionimport old fixture -> rewrite or lazy upgrade -> validate checksum

SQLite Migration Skeleton

BEGIN IMMEDIATE;

PRAGMA foreign_keys = OFF;

ALTER TABLE transactions ADD COLUMN category TEXT;

UPDATE app_meta SET value = '2' WHERE key = 'schema_version';

PRAGMA foreign_key_check;
PRAGMA foreign_keys = ON;

COMMIT;

Use a real migration runner in production. The important shape is one transaction, explicit version bump, and validation before success.

IndexedDB Sync Caveat

Synced browser data makes migrations harder because clients can run different app versions while sharing the same logical data. Prefer additive changes and backward-compatible readers. For large incompatible changes, consider export, offline migration, and importing into a new database.

Gotcha: A migration that passes on an empty database proves almost nothing.