Skip to content

Repository files navigation

litevolve

Versioned SQLite migrations for Bun, Node, and Deno — usable as a library or a CLI.

CI version License: MIT Bun Node Deno


what_it_does

litevolve reads a directory of numbered SQL files ({version}_name.sql, {version}_name.down.sql, optional {version}_name.seed.sql) and applies them up or down against a SQLite database to reach a target schema version. Each step runs in a single BEGIN IMMEDIATE transaction so a failed seed rolls back its schema change too. The current schema version is tracked in SQLite's native PRAGMA user_version; a sticky init_seeds flag is recorded in an internal _db_meta table so seed behavior stays consistent across subsequent upgrades.

coming_soon

litevolve is still in embrional phase.

It will be available as:

  • npm package for use on Bun, Node, and Deno
  • executable for several targets (Darwin, Linux), architectures (x64, arm64), and platforms (glibc, musl)
  • executable for Bun, Node, and Deno ecosystems
  • docker image

The following instructions might not work since the integration is missing.

install

# Bun
bun add litevolve-bun

# Node: npm / pnpm / yarn
npm install litevolve-node
pnpm add litevolve-node
yarn add litevolve-node

# Deno (JSR) Not available yet
deno add jsr:@litevolve-deno/litevolve-deno

# Homebrew (CLI only) Not available yet
brew install litevolve

Node caveat: litevolve-node uses the built-in node:sqlite (DatabaseSync), added in Node 22.5.0 and still marked experimental (Stability 1.2 — Release Candidate). It emits an ExperimentalWarning and its API may shift in a minor/patch release. Requires node >= 22.5.

library_usage

// Import from the runtime-specific package: litevolve-bun or litevolve-node.
import { migrate_db } from "litevolve-bun"

// Apply migrations up (or down) to reach version 2.
// Returns the open Bun Database handle.
const db = migrate_db(
  2,                    // apply_version: target schema version
  "./migrations",       // migrations_path: directory holding the .sql files
  "./data/birds.db",    // db_path: SQLite file (or ":memory:")
  true,                 // init_seeds: only honored on a fresh DB at v0
)

The signature lives at src/migrate.ts:172 and the error type at src/migration_error.ts:1.

CLI_usage

The CLI takes the same four inputs as named flags:

litevolve \
  --apply_version=2 \
  --db_path=./data/birds.db \
  --migrations_path=./migrations \
  --init_seeds

Runtime-equivalent invocations:

bunx litevolve --apply_version=2 --db_path=./data/birds.db --migrations_path=./migrations
npx  litevolve --apply_version=2 --db_path=./data/birds.db --migrations_path=./migrations
deno run --allow-read --allow-write npm:litevolve \
  --apply_version=2 --db_path=./data/birds.db --migrations_path=./migrations

docker_usage

To run migrations during a Docker build without installing litevolve's runtime in your image, copy the binary from the official image in a multi-stage build:

FROM litevolve:latest AS migrator

FROM debian:bookworm-slim
COPY --from=migrator /usr/local/bin/litevolve /usr/local/bin/litevolve
COPY ./migrations /migrations
RUN litevolve --apply_version=3 --db_path=./data/app.db --migrations_path=/migrations

For Alpine-based images, use the musl-linked variant:

FROM litevolve:musl AS migrator

This pattern is suited for baking a pre-seeded read-only SQLite file into an image. For runtime migrations against a writable volume, run litevolve at container startup instead.

migration_file_conventions

Files in the migrations directory are validated by a strict regex:

0*[1-9][0-9]*_([a-z]|[A-Z]|_)+\.(sql|seed\.sql|down\.sql)

Breakdown:

  • 0* — optional leading zeros for zero-padding (padding is not required).
  • [1-9][0-9]* — the numeric version: a non-zero leading digit followed by any digits.
  • _ — separator.
  • ([a-z]|[A-Z]|_)+ — a [a-zA-Z_]+ description.
  • \.(sql|seed\.sql|down\.sql) — one of three extensions.
Extension Direction When applied
.sql up when current_version < N ≤ target
.down.sql down when target < N ≤ current_version
.seed.sql up + init_seeds optional, same transaction as .sql

Files that do not match the regex are silently skipped — keep auxiliary files (notes, fixtures, sub-directories) out of the migrations directory or they won't be picked up.

Sort order is numeric after stripping leading zeros, not lexicographic. 0999_x.sql sorts before 01000_y.sql because parseInt("0999") is 999 and parseInt("01000") is 1000. Padding is optional and its width can vary across migrations without breaking the order: 1_…, 0042_…, 0999_…, 01000_… all sort correctly together, and an unpadded 42_… sorts identically to 0042_….

Valid examples: 0001_create_initial_schema.sql, 1234_create_users_table.sql, 0042_add_users_language_column.down.sql, 01000_split_audit_log.seed.sql. Invalid: 0000_foo.sql (no non-zero digit), 0_foo.sql (version 0), 0001-foo.sql (hyphen not allowed), 0001_foo.txt (wrong extension).

Notes about the parser (see src/migrate.ts:48):

  • Line comments -- … are stripped
  • Down migrations never apply seeds. Each .down.sql is responsible for its own data cleanup before dropping columns or tables

init_seeds_semantics

init_seeds is sticky: it is only honored when the database is at version 0 (fresh or fully rolled back). The chosen value is recorded in _db_meta and reused for every subsequent up-migration on the same database. Passing --init_seeds to a partially-migrated DB is silently ignored — this guarantees that a database either consistently has its seed rows or consistently does not. See the behavior contract in src/migrate.test.ts (the init_seeds_* tests).

example_ornithology_database

The migrations/working/ directory in this repository ships a runnable three-version example modelling a bird-observation system (the sibling migrations/broken/ holds an intentionally-invalid migration used only by the test suite):

  • v1 (0001_create_initial_schema.sql) — minimal core, four tables:

    • observation_sites (id, name)
    • birders (id, name, joined_at)
    • time_slots (id, site_id, starts_at, ends_at, reserved)
    • sightings (id, birder_id, site_id, species_common_name, observed_at, status)

    Optional seed populates 3 sites, 8 birders (Alice Johnson, Bob Smith, …), 32 two-hour observation windows, and 3 sightings (pending / verified / rejected).

  • v2 (0002_expand_schema.sql) — adds richer metadata via ALTER TABLE ADD COLUMN and creates two intake tables:

    • observation_sites gains latitude, longitude, habitat_type, timezone.
    • birders gains email, skill_level, favorite_species, timezone.
    • time_slots gains weather.
    • sightings gains species_scientific_name, individual_count.
    • New tables incoming_reports (id, source, raw_payload, received_at) and incoming_reports_archive (…, archived_at).

    Optional seed back-fills coordinates, skill levels, scientific names, weather notes, and sets timezone = 'America/New_York' for Central Park, Alice, and Bob.

  • v3 (0003_add_birder_mentors.sql) — adds mentor_birder_id TEXT REFERENCES birders(id) to birders (a self-referential FK). Optional seed marks Alice as the mentor of Carol/Dan/Eve and Bob as the mentor of Frank/Grace.

    The down migration demonstrates the NULL-before-drop pattern for foreign-key removal:

    -- 0003_add_birder_mentors.down.sql
    UPDATE birders SET mentor_birder_id = NULL;
    ALTER TABLE birders DROP COLUMN mentor_birder_id;

    Clearing the FK values first is the right habit even when DROP COLUMN would also strip the inline REFERENCES constraint — it's the pattern you must use when removing a FK constraint while keeping the column, since SQLite has no ALTER TABLE DROP CONSTRAINT and the alternative is a CREATE TABLE … / INSERT SELECT / DROP / RENAME table-rebuild that would otherwise copy stale references into the new table.

Drive it from the Makefile:

make migrate_seeds DB_PATH=./birds.db VERSION=2
sqlite3 ./birds.db "SELECT name, timezone FROM observation_sites;"

contributing_guidelines

Refer to Makefile for a comprehensive list of available helping commands.

  • OSX is recommended for development
    • if you have any experience contributing to this library under Linux please share your setup
  • litevolve basic ecosystem is Bun
  • Makefile approach is opinionated (sorry)
  • Use any editor but don't push any related configuration of it, keep it in your machine

license

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages