Skip to content

Migrating between stores

Two paths when you outgrow your current store:

  • Start fresh — swap env vars and let history rebuild. Works for most teams because the detection model only cares about recent windows.
  • Copy history — dump from the old store, load into the new one. Needed if you want to preserve trend analysis or avoid a two-week “warm-up” gap after the switch.
  1. Install the new store package

    Terminal window
    npm i -D @flaky-tests/store-turso
  2. Update FLAKY_TESTS_STORE and credentials

    Terminal window
    FLAKY_TESTS_STORE=turso
    FLAKY_TESTS_CONNECTION_STRING=libsql://your-db.turso.io
    FLAKY_TESTS_AUTH_TOKEN=eyJhbGci...
  3. Run your tests

    The preload auto-calls migrate() on the first run, creating tables in the new store.

  4. (Optional) Remove the old store package

    Terminal window
    bun remove @flaky-tests/store-sqlite

Every store implements the same IStore interface, so copying data is a handful of reads against the old store and writes against the new one. There’s no CLI subcommand for this yet; a one-off script is the supported path.

migrate-stores.ts
import { SqliteStore } from '@flaky-tests/store-sqlite'
import { TursoStore } from '@flaky-tests/store-turso'
const source = new SqliteStore({ dbPath: './node_modules/.cache/flaky-tests/failures.db' })
const dest = new TursoStore({
url: process.env.FLAKY_TESTS_CONNECTION_STRING!,
authToken: process.env.FLAKY_TESTS_AUTH_TOKEN,
})
await dest.migrate()
// Pull the full history. `getRecentRuns` with a large limit is enough
// for most projects — if you've been capturing for years, raise it.
const runs = await source.getRecentRuns({ limit: 100_000 })
for (const run of runs) {
await dest.insertRun({
runId: run.runId,
startedAt: run.startedAt,
...(run.project !== null && { project: run.project }),
...(run.gitSha !== null && { gitSha: run.gitSha }),
...(run.gitDirty !== null && { gitDirty: run.gitDirty }),
})
await dest.updateRun(run.runId, {
...(run.endedAt !== null && { endedAt: run.endedAt }),
...(run.durationMs !== null && { durationMs: run.durationMs }),
...(run.status !== null && { status: run.status }),
...(run.totalTests !== null && { totalTests: run.totalTests }),
...(run.passedTests !== null && { passedTests: run.passedTests }),
...(run.failedTests !== null && { failedTests: run.failedTests }),
...(run.errorsBetweenTests !== null && {
errorsBetweenTests: run.errorsBetweenTests,
}),
})
}
await source.close()
await dest.close()
console.log(`Copied ${runs.length} runs`)

See Choosing a store for the full comparison. Summary:

ScenarioDestination
Still solo / local onlyStay on SQLite
Team wants shared historyTurso (free tier fits most teams)
Already on SupabaseSupabase
Already on Neon / managed PostgresPostgres

If FLAKY_TESTS_PROJECT differs between the two environments, rows written in one won’t show up when read in the other. Export and import with the same project name, or set FLAKY_TESTS_PROJECT="" to write rows with a NULL project during the copy.

insertRun on most adapters errors on duplicate run_id. If you run the migration script twice, wrap each call in a try/catch or add a pre-check that the run doesn’t already exist in the destination.

Turso / Supabase / Neon free tiers have write-per-minute caps. For large histories, add a small await new Promise(r => setTimeout(r, 50)) between rows, or batch with insertFailures where appropriate.

  1. CI env vars — update FLAKY_TESTS_STORE and credentials on every CI workflow.
  2. .env.example — tell teammates what to set locally.
  3. Uninstall the old store package — keeps node_modules lean and prevents accidental dual-writes.
  4. Rotate the old credentials if they’re no longer needed.