Skip to content

Migrations

Connect to an existing database and use models immediately — opt into migration runtime only when you need schema lifecycle management.

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 }),
],
});

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',
});
import { migrate, migrateDown } from 'jsorm';
await migrate(ormSource); // apply all pending
await migrateDown(ormSource); // roll back last batch
OperationDescription
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

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))",
),
],
});

When jsorm.config.ts has migrationSources + defaults.migrationSource configured, CLI commands need no arguments:

Terminal window
jsorm migrate:status # check pending
jsorm migrate # apply all pending
jsorm migrate:up # apply next one
jsorm migrate:down # roll back last batch (guarded in production)

Pass compiled schema + export name explicitly when not using config-first:

Terminal window
jsorm migrate ./dist/schema.js ormSource
jsorm migrate:up ./dist/schema.js ormSource
jsorm migrate:down ./dist/schema.js ormSource
jsorm migrate:status ./dist/schema.js ormSource

jsorm tracks applied migrations in jsorm_migrations (configurable via migrationTable):

-- auto-managed, do not modify manually
id SERIAL PRIMARY KEY
name TEXT NOT NULL UNIQUE
batch INTEGER NOT NULL
applied_at TIMESTAMP NOT NULL DEFAULT NOW()

Generate reviewed migration files by diffing current models against a stored snapshot:

// jsorm.config.ts
import { defineMigrationGenerateSource } from 'jsorm';
export const generateSource = defineMigrationGenerateSource({
models: [Role, User],
snapshotPath: '.migrations/schema.json',
});

Then run:

Terminal window
jsorm migrate:generate
# or legacy mode:
jsorm migrate:generate ./dist/schema.js generateSource

The generator classifies each change as safe, review_required, or dangerous, emits a timestamped migration file, and logs detected many-to-many pivot tables.

  1. Keep migrations small — one concern per migration.
  2. Always write down when a step is not automatically reversible.
  3. Never modify an already-applied migration.
  4. Run migrate:status as part of deployment to confirm all migrations applied.
  5. Treat migrate:down, db:fresh, and db:rollback as environment-sensitive — keep them out of production automation.