There is a lot of context in Backup migrations scoping.
This diff creates a system on how to implement and run JS migrations compatible with backup. I think this is the best solution because it has no impact on the existing migration process, and gives the same flexibility in migrations as before. The only thing to consider now is that whenever writing a migration, the DB ops code has to be factored out to sharedMigrations and just call it in the redux-persist migration. That way, we're able to run it again on the database downloaded from the backup.
As mentioned in the docs, there are 4 types of migrations:
- T1 - Schema migrations - executed after downloading the database, it was implemented in this stack in prior diffs, the final diff is D14752
- T2 - SQLite data migrations using redux-persist - this is primarily solved by this diff, I created D14782 that shows how to do it
- T3 - Redux-only migrations - those shouldn't be executed when restoring backup (already having the newest, probably default state), but we still need to run it on app start on existing clients, for this migration type approach remains unchanged, see example in D14783
- T4 - SQLite and Redux migrations - this is the complex because it could affect both DB and state, and when running backup migration, we shouldn't modify the existing state. But thanks to this approach, we still can achieve it by separating DB ops code (sharedMigrations) with state migrations (put in the same place as before). In D14782, there is an example of migration that is executed on the backup, but existing clients can modify the state, too. Because of that, we can cancel ENG-10597. There is also part about moving things from redux-persist to SQLite, I don't think we need to support it for backup because if something has to be in backup, we definitely need to have it there before launch, and if not, when restoring, if we have this data in store then it was migrated to SQLite using local client migration on app start.
There is one alternative to this solution, which is running the same exact migration but supplying it with the default state and ignoring the state produced on the output. There are two problems with this solution that I don't like:
- Default state types have to be frozen in time; we can't just modify them, but whenever changing, introduce a new one and don't modify existing ones. The same issues as described in ENG-4946.
- Migration logic might need to be aware that it is running on the actual or on the default state, which might add some complexity.
We can consider one more automation here, modify createAsyncMigrate to run sharedMigrations automatically without explicit call (see D14782), but this removes the flexibility we have now with still being able to modify the state, so I strongly prefer this one.
Depends on D14793