Skip to main content

Adding Migrations

Because we persist state on a user's computer, we must migrate that state each time we release a version that changes the schema expected by the app. If any of the reducers in src/js/state are changed, we need to write a migration. When the app starts, it will automatically run all the pending migrations.

Creating a Migration

Let's say we've added a property called nickname to each lake connection in the app. We'll use the migration generator tool to create the necessary files. From the root directory, run bin/gen migration <name>. For example:

bin/gen migration addNicknameToConnection

This created a migration file and a test file, each with a timestamp.

Created: src/js/state/migrations/202007101024_addNicknameToConnection.js
Created: src/js/state/migrations/202007101024_addNicknameToConnection.test.js

The migration function takes the previous state as an parameter and returns the new desired state.

In this case:

export default function addNicknameToConnection(state) {
// Migrate state here
return state
}

As of this writing, the schema of the persisted state is called SessionState and is defined in: src/js/electron/tron/formatSessionState.js

Testing Your Migration

The migrations only ever run when we release a new version. To test your migration, you'll need a sample of the session state from the most recent release tag. Let's say the last release was v0.13.1. We can easily get a snapshot of the state at that version following these steps.

# Remove your "run" directory which clears the app state in development
rm -fr run
# Check out the last version
git checkout v0.13.1
# Start the app
yarn && yarn start

Now get the app into a state that you want to test against. In this example, we would add a few connections so we can ensure they have nicknames after the migration.

When ready, go to the App Menu and click Developer => Save Session for Testing Migrations.

Save Session Menu Item

This will save the session state and inform you where the file is.

Session Saved Popup

The name of the JSON file is just today's date, so it might be helpful to rename it to the version that you're targeting like v0.13.1.json

mv src/js/test/states/202007091803.json src/js/test/states/v0.13.1.json

Then in the migration test file, you can access this file using the helper function, getTestState(name).

// src/js/state/migrations/202007101024_addNicknameToConnection.test.js

import getTestState from "src/test/helpers/getTestState"
import migrate from "./202007101024_addNicknameToConnection"

test("202007101024_addNicknameToConnection", () => {
// The test data we just saved. Returns: {version: string, data: SessionState}
let {data} = getTestState("v0.13.1")

const next = migrate(data)

/* Let's pretend the test data had three connections and the migration added
"no nickname" as the default nickname for each of them. */
const {connections} = next.globalState

expect(connections.length).toBe(3)

connections.forEach((connection) => {
expect(connection.nickname).toBe("no nickname")
})
})

Once you've got that test passing, you're home free. The app will see the new migration and run it automatically the next time someone upgrades.