@andmarruda/react-native-sqlite-migrations
v0.1.0
Published
Laravel-style SQLite migrations for React Native with pre-destructive data migration hooks.
Downloads
74
Maintainers
Readme
react-native-sqlite-migrations
Laravel-style SQLite migrations for React Native, with support for safe data migration before destructive schema changes.
This package is designed to keep your app focused on business rules instead of migration orchestration.
Why This Package
React Native apps that work offline usually keep important user data locally. That makes schema changes more sensitive than in a typical web app.
This package helps you:
- keep SQL files outside the library, inside your app
- run migrations in order using timestamp-based names
- track applied migrations with Laravel-like
batchsemantics - move or transform data before dropping tables or replacing structures
- keep the SQLite driver adapter small and explicit
- verify migration integrity with stored SQL checksums
- inspect health issues before running risky migrations or rollbacks
Why It Is Easy to Adopt
The package is intentionally small to integrate:
- keep your SQL files in a folder such as
src/database/migrations - provide a tiny SQLite adapter with
execute,query, andwithTransaction - point the library to your SQL directory through
readSqlFile - run
await runner.migrate()during database boot
That means you do not need to move your SQL into the library or let the library own your database driver.
Suggested App Structure
src/
database/
migrations/
202603210001_create_users.up.sql
202603210001_create_users.down.sql
202603210002_split_full_name.up.sql
202603210002_split_full_name.down.sqlQuick Example
import { defineMigrations, MigrationRunner } from "react-native-sqlite-migrations";
const catalog = defineMigrations({
directory: "src/database/migrations",
migrations: [
{
name: "202603210001_create_users",
sql: {
up: "202603210001_create_users.up.sql",
down: "202603210001_create_users.down.sql",
},
},
{
name: "202603210002_split_full_name",
sql: {
up: "202603210002_split_full_name.up.sql",
down: "202603210002_split_full_name.down.sql",
},
beforeDestructive: async ({ db }) => {
await db.execute({
sql: "ALTER TABLE users ADD COLUMN first_name TEXT",
});
await db.execute({
sql: "ALTER TABLE users ADD COLUMN last_name TEXT",
});
const rows = await db.query<{ id: number; full_name: string | null }>({
sql: "SELECT id, full_name FROM users",
});
for (const row of rows) {
const [firstName, ...rest] = (row.full_name ?? "").trim().split(/\s+/);
await db.execute({
sql: "UPDATE users SET first_name = ?, last_name = ? WHERE id = ?",
params: [firstName ?? null, rest.join(" ") || null, row.id],
});
}
},
},
],
});
const runner = new MigrationRunner({
db: sqliteExecutor,
catalog,
readSqlFile: async ({ directory, path }) => {
return loadSqlFromBundle(`${directory}/${path}`);
},
});
await runner.migrate();SQLite Adapter Contract
The package does not force expo-sqlite, react-native-quick-sqlite, or any other specific driver.
Your app only needs to provide this adapter shape:
type SqliteExecutor = {
execute(statement: { sql: string; params?: Array<string | number | null> }): Promise<void>;
query<T>(statement: { sql: string; params?: Array<string | number | null> }): Promise<T[]>;
withTransaction<T>(callback: () => Promise<T>): Promise<T>;
};Official adapter helpers are now available for:
createExpoSqliteExecutorcreateQuickSqliteExecutor
These helpers are covered by automated tests, but you should still validate the exact SQLite driver version used by your app before rollout.
Migration Flow
When you call migrate(), the library:
- creates the migration repository table if needed
- discovers pending migrations
- opens one transaction per migration
- runs
beforeDestructive - runs
beforeUp - loads the
.up.sqlfile - executes SQL statements sequentially
- records the migration with a
batch
By default, that local repository table is named __rn_sqlite_migrations.
It is how the package keeps track of what already ran on the device.
When you call rollbackLastBatch(), the library:
- finds the latest
batch - runs migrations in reverse order
- runs
beforeDown,down.sql, andafterDown - removes migration records from the repository table
When to Use beforeDestructive
Use beforeDestructive when data must be preserved before a destructive structural change.
Examples:
- move data from an old table into a new one
- split a legacy column into multiple new columns
- normalize old values before a table rebuild
- create temporary storage before recreating a table
After that, the .up.sql file can focus on the final schema change itself.
Loading SQL Files
React Native often cannot access arbitrary local files dynamically at runtime. That is why the package receives readSqlFile instead of using fs directly.
This makes it easy to use:
require- bundled assets
- a generated manifest
- any custom local asset loader
Helper loaders are also exported:
createStaticSqlLoadercreateAssetSqlLoader
Conceptual example:
const sqlFiles = {
"src/database/migrations/202603210001_create_users.up.sql": require("./src/database/migrations/202603210001_create_users.up.sql"),
};
async function loadSqlFromBundle(path: string) {
return sqlFiles[path];
}CLI
The package includes a CLI for common migration maintenance tasks:
rn-sqlite-migrations help
rn-sqlite-migrations create add_users --dir src/database/migrations
rn-sqlite-migrations validate --dir src/database/migrations
rn-sqlite-migrations manifest --dir src/database/migrations --out src/database/migrations/manifest.generated.jsonCan This Work With npx?
Yes.
Because the package exposes a bin command, it can be used with npx.
If the package is already installed in your project:
npx rn-sqlite-migrations create add_users --dir src/database/migrationsIf the package is published and you want to run it without installing first:
npx --package react-native-sqlite-migrations rn-sqlite-migrations create add_users --dir src/database/migrationsThat means it is absolutely possible to generate migrations automatically with an npx command.
Useful npx Commands
Create a migration:
npx rn-sqlite-migrations create create_users --dir src/database/migrationsIf you pass --timestamp, use the format yyyymmddHHMMSS.
Validate your migration folder:
npx rn-sqlite-migrations validate --dir src/database/migrationsdown.sql files are optional. If you prefer forward-only migrations, validation still passes as long as the up.sql file is present and correctly named.
Generate a static manifest for bundled SQL loading:
npx rn-sqlite-migrations manifest --dir src/database/migrations --out src/database/migrations/manifest.generated.jsonExamples
Consumer examples live in:
examples/basic-usage.tsexamples/expo-adapter.tsexamples/logger.tsexamples/quick-sqlite-adapter.ts
Integrity and Health Checks
The runner now supports integrity verification with stored SQL checksums.
You can enable stricter enforcement with:
const runner = new MigrationRunner({
db: sqliteExecutor,
catalog,
readSqlFile,
integrityMode: "strict",
});And you can inspect health issues before running migrations:
const report = await runner.healthCheck();Release Readiness
The repository now includes:
- CI for typecheck, tests with coverage, and publish-artifact smoke checks
- npm package metadata for repository, issues, Node engine, and exports
- a changelog for release notes
Local release smoke check:
npm run pack:checkUseful when you want to detect:
- checksum drift in already-applied migrations
- applied migrations missing from the current catalog
- rollback-unavailable situations
Internal Docs
Additional English documentation lives in:
docs/architecture.mddocs/coverage-roadmap.en.mddocs/coverage.mddocs/excellence.en.mddocs/improvements.mddocs/implementation-tasks.mddocs/quickstart.md
Documentacao adicional em portugues vive em:
docs/coverage-roadmap.pt-BR.mddocs/excellence.pt-BR.md
Local Testing
Run the isolated package checks with:
npm testThis script:
- builds the package into
dist/ - imports the built output
- runs local tests against the package in isolation
To collect native Node coverage for the built package, run:
npm run test:coverageThis writes raw V8 coverage files into coverage/v8/ and prints the coverage summary in the terminal.
Support
If you want to support the project:
- Buy Me a Coffee:
buymeacoffee.com/andmarruda
