Adapter Migration
Migrate data between adapters using the export/import pipeline without downtime.
Why migrate?
As your application grows, you may need to move from one adapter to another. Common migrations include SQLite to Postgres (scaling beyond a single machine), local SQLite to a remote Postgres sync server, or consolidating multiple SQLite files into a single Postgres database.
Export/import pipeline
Store exposes migration planning on the client and portable archive helpers for backup and restore workflows. Use a dry-run plan before changing routes or moving state.
import { createStore } from "@cuitty/store/client";
const store = await createStore({
module: "app",
profile: {
id: "migration",
name: "Migration",
local: { adapter: "local.sqlite", path: "./data/app.db" },
remotes: [{ id: "postgres", adapter: "postgres.record", dsnRef: "secret://pg" }],
routes: [{ namespace: "users", class: "record", localAdapter: "local.sqlite", remoteAdapters: ["postgres"] }],
encryption: { atRest: "preferred", remote: "required", p2p: "disabled" },
sync: { mode: "manual" },
},
schema: { users: { class: "record" } },
});
const plan = await store.migrate.dryRun({
from: "local.sqlite",
to: "postgres.record",
namespace: "users",
});
console.log(plan.steps.map((step) => step.label));
CLI migration
The CLI provides a simpler interface for common migrations:
# Export to a portable format
cui store export my-app --format jsonl --output ./backup.jsonl
# Import into a new store
cui store import my-app --from ./backup.jsonl \
--adapter postgres \
--connection "postgresql://user:pass@host:5432/store"
The JSONL format stores one record per line, making it easy to inspect, filter, or transform with standard Unix tools.
Zero-downtime migration
For production workloads, use a route-based dual-write pattern:
- Set up the destination store with the new adapter.
- Add the destination remote to the profile route while local writes still commit first.
- Run the backfill to copy existing data from source to destination.
- Verify that both stores have identical data.
- Switch reads to the destination.
- Disable dual-write and decommission the source.
const route = {
namespace: "users",
class: "record",
localAdapter: "local.sqlite",
remoteAdapters: ["postgres"],
};
await store.sync.flush();
Data integrity
The migration pipeline checksums each batch and verifies that the destination matches the source. If a batch fails, the pipeline retries with exponential backoff. After migration, run cui store verify to compare record counts and checksums between source and destination.
cui store verify my-app \
--source sqlite:./data/app.db \
--target postgres:postgresql://user:pass@host/store