Skip to main content
Version: 1.4.0

Migrations

JSONVault ships with a simple file-based migration runner. Each migration is a JavaScript module exporting up (required) and down (optional) functions. Applied migration IDs live in meta.json, so the runtime always knows which scripts ran.

Anatomy of a migration

migrations/20240508-add-status.js
module.exports = {
async up(db) {
const orders = db.collection("orders");
await orders.updateMany(
{ status: { $exists: false } },
{ $set: { status: "pending" } },
);
},

async down(db) {
const orders = db.collection("orders");
await orders.updateMany({}, { $unset: { status: true } });
},
};

CLI workflow

# Scaffold a new file (timestamped ID)
npx jsonvault migrate ./data create add-status --dir=./migrations

# Apply the next pending migrations (up)
npx jsonvault migrate ./data up --dir=./migrations

# Roll back the most recent migration
npx jsonvault migrate ./data down --step=1 --dir=./migrations

# Inspect status
npx jsonvault migrate ./data status --dir=./migrations
  • Migrations run inside a transaction by default; throw to abort and roll back.
  • The change log records each migration-induced mutation, so downstream consumers stay in sync.
  • Use the --dryRun flag to preview what would run without touching data.

Programmatic control

const { migrateUp, migrateDown, migrationStatus } = require("jsonvault");

await migrateUp(db, { directory: "./migrations" });
const status = await migrationStatus(db, { directory: "./migrations" });
console.log(status.pending);

await migrateDown(db, { directory: "./migrations", step: 1 });

Choose a naming convention (timestamp or semantic) and stick to it. The runner sorts migrations lexicographically, so 20240508-add-status.js will always run before 20240509-index-email.js.