Migrations
The use of migrations is optional
Section titled “The use of migrations is optional”Connect to an existing database and use models immediately — opt into migration runtime only when you need schema lifecycle management.
Define migrations
Section titled “Define migrations”Each migration is an object with name, up, and optionally down. The up/down callbacks receive t — the migration builder:
import { defineModel, defineMigration, t } from 'jsorm';
const Role = defineModel('roles', { id: t.number().primary(), name: t.string().unique(),});
const User = defineModel('users', { id: t.number().primary(), name: t.string(), email: t.string().optional(), active: t.boolean().default(true).index(), role: t.belongsTo(Role),});
const initRolesAndUsers = defineMigration({ name: 'init-roles-and-users', up: (t) => [ t.createTable(Role), t.createTable(User), ], down: (t) => [ t.dropTable('users'), t.dropTable('roles'), ],});
const addUserTimezone = defineMigration({ name: 'add-user-timezone', up: (t) => [ t.addColumn('users', 'timezone', t.string().optional()), ], down: (t) => [ t.dropColumn('users', 'timezone', { ifExists: true }), ],});Define a migration source
Section titled “Define a migration source”A migration source groups an adapter with an ordered list of migrations:
import { defineMigrationSource } from 'jsorm';import { pgAdapter } from 'jsorm-pg';
const ormSource = defineMigrationSource({ adapter: pgAdapter({ name: 'main', connectionString: process.env.DATABASE_URL!, }), migrations: [initRolesAndUsers, addUserTimezone], migrationTable: 'jsorm_migrations',});Run migrations programmatically
Section titled “Run migrations programmatically”import { migrate, migrateDown } from 'jsorm';
await migrate(ormSource); // apply all pendingawait migrateDown(ormSource); // roll back last batchMigration operations (via t)
Section titled “Migration operations (via t)”| Operation | Description |
|---|---|
t.createTable(Model) | Create table from model definition |
t.dropTable(tableName) | Drop a table |
t.addColumn(table, name, field) | Add a column |
t.dropColumn(table, name, opts?) | Drop a column |
t.renameColumn(table, from, to) | Rename a column |
t.createIndex(table, columns) | Add an index |
t.dropIndex(table, name) | Remove an index |
Raw SQL migrations
Section titled “Raw SQL migrations”For complex DDL that the builder doesn’t cover, use rawSql():
import { defineMigration, rawSql } from 'jsorm';
const addFullTextIndex = defineMigration({ name: 'add-fulltext-index', up: [ rawSql( "CREATE INDEX users_name_fts ON users USING GIN (to_tsvector('english', name))", 'DROP INDEX IF EXISTS users_name_fts', ), ], down: [ rawSql( 'DROP INDEX IF EXISTS users_name_fts', "CREATE INDEX users_name_fts ON users USING GIN (to_tsvector('english', name))", ), ],});Config-first CLI migrations
Section titled “Config-first CLI migrations”When jsorm.config.ts has migrationSources + defaults.migrationSource configured, CLI commands need no arguments:
jsorm migrate:status # check pendingjsorm migrate # apply all pendingjsorm migrate:up # apply next onejsorm migrate:down # roll back last batch (guarded in production)Legacy module-export mode
Section titled “Legacy module-export mode”Pass compiled schema + export name explicitly when not using config-first:
jsorm migrate ./dist/schema.js ormSourcejsorm migrate:up ./dist/schema.js ormSourcejsorm migrate:down ./dist/schema.js ormSourcejsorm migrate:status ./dist/schema.js ormSourceMigration tracking table
Section titled “Migration tracking table”jsorm tracks applied migrations in jsorm_migrations (configurable via migrationTable):
-- auto-managed, do not modify manuallyid SERIAL PRIMARY KEYname TEXT NOT NULL UNIQUEbatch INTEGER NOT NULLapplied_at TIMESTAMP NOT NULL DEFAULT NOW()Automatic migration generation
Section titled “Automatic migration generation”Generate reviewed migration files by diffing current models against a stored snapshot:
// jsorm.config.tsimport { defineMigrationGenerateSource } from 'jsorm';
export const generateSource = defineMigrationGenerateSource({ models: [Role, User], snapshotPath: '.migrations/schema.json',});Then run:
jsorm migrate:generate# or legacy mode:jsorm migrate:generate ./dist/schema.js generateSourceThe generator classifies each change as safe, review_required, or dangerous, emits a timestamped migration file, and logs detected many-to-many pivot tables.
Best practices
Section titled “Best practices”- Keep migrations small — one concern per migration.
- Always write
downwhen a step is not automatically reversible. - Never modify an already-applied migration.
- Run
migrate:statusas part of deployment to confirm all migrations applied. - Treat
migrate:down,db:fresh, anddb:rollbackas environment-sensitive — keep them out of production automation.