Marv
Marv is a programmatic database migration tool designed for Node.js environments, offering a flexible, driver-based approach to managing schema changes. Currently stable at version 6.1.0, it supports a wide range of relational databases including MySQL, PostgreSQL, SQLite, Microsoft SQL Server, and Oracle DB through dedicated pluggable drivers (e.g., `marv-pg-driver`). It distinguishes itself by providing both Promise and Callback-based APIs for integration into various application architectures. Marv's core functionality involves scanning a directory for SQL migration files, validating their sequence, and applying them to the target database. It enforces strict ordering, reporting errors for duplicate levels or out-of-sequence execution, which necessitates careful branching strategies when developing new migrations. Releases appear to follow a semantic versioning approach, with major versions introducing significant changes.
Common errors
-
Error: Duplicate migration level detected: [level] - [filename1] and [filename2]
cause Two or more migration files have the same numeric level in their filenames, which violates Marv's strict ordering requirement.fixRename one of the conflicting migration files to ensure all numeric levels are unique within the migrations directory. -
Error: Migration [level] has been run out of sequence.
cause A migration script was detected that should have been run earlier based on its numeric level but was not, indicating an inconsistent database state or a deployment issue.fixInvestigate the database history to understand which migration was skipped or run incorrectly. If necessary, manually revert the database to a consistent state, or adjust the schema history table (if safe) to reflect the actual applied migrations, then re-run. Ensure migration files are applied in strict ascending order of their levels. -
Error: Cannot find module 'marv-pg-driver' (or similar for other drivers)
cause The specific database driver package required by Marv for your chosen database (e.g., PostgreSQL, MySQL) has not been installed.fixInstall the correct Marv driver package for your database using npm, e.g., `npm install marv-pg-driver` or `npm install marv-mysql-driver`.
Warnings
- gotcha Marv deliberately does not run migration scripts within a transaction. This means that if a script fails mid-execution, partial changes may be applied, and manual cleanup might be required. It is strongly recommended to make migration scripts idempotent (e.g., using `CREATE TABLE IF NOT EXISTS`).
- breaking Marv enforces a strict sequential order for migrations. Attempting to run migrations with duplicate numeric levels or running migrations out of their detected sequence will result in errors. This has implications for branching strategies where multiple developers might create migrations concurrently.
- gotcha Marv requires a specific database driver package (e.g., `marv-pg-driver`, `marv-mysql-driver`) to be installed alongside it. Forgetting to install the correct driver for your target database will lead to runtime errors when attempting to connect or migrate.
- gotcha When using `marv-mysql-driver` with MySQL 8.0+, you may encounter authentication issues because MySQL 8.0 changed its default authentication plugin to `caching_sha2_password`. The underlying `mysql` library might not support this, requiring `mysql2` or a server configuration change.
Install
-
npm install marv -
yarn add marv -
pnpm add marv
Imports
- marv (Promise API)
import marv from 'marv/api/promise';
const marv = require('marv/api/promise'); - marv (Callback API)
import marv from 'marv/api/callback';
const marv = require('marv/api/callback'); - marv-pg-driver
import pgDriver from 'marv-pg-driver';
const pgDriver = require('marv-pg-driver');
Quickstart
const path = require('path');
const marv = require('marv/api/promise');
const pgDriver = require('marv-pg-driver');
const migrationsDirectory = path.resolve(__dirname, 'migrations');
// Create a dummy migrations directory and file for demonstration
const fs = require('fs');
if (!fs.existsSync(migrationsDirectory)) {
fs.mkdirSync(migrationsDirectory);
}
const migrationFile = path.join(migrationsDirectory, '001.create-test-table.sql');
if (!fs.existsSync(migrationFile)) {
fs.writeFileSync(migrationFile, 'CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(255));');
}
async function runMigrations() {
try {
// Placeholder for actual PostgreSQL connection details
const connection = {
host: process.env.DB_HOST ?? 'localhost',
port: parseInt(process.env.DB_PORT ?? '5432', 10),
user: process.env.DB_USER ?? 'postgres',
password: process.env.DB_PASSWORD ?? 'password',
database: process.env.DB_NAME ?? 'mydatabase',
// Optional: ssl: { rejectUnauthorized: false } for local testing if needed
};
const migrations = await marv.scan(migrationsDirectory);
await marv.migrate(migrations, pgDriver({ connection }));
console.log('Migrations applied successfully!');
} catch (err) {
console.error('Migration failed:', err.message);
process.exit(1);
}
}
runMigrations();