onemig
v0.3.9
Published
CLI application for migrating MariaDB databases.
Downloads
260
Maintainers
Readme
OneMig
The last migration tool you will ever need! 😂
OneMig is a CLI application for describing MariaDB table schemas in YAML, pulling schema resources from a live database, and generating migration SQL from those resources.
It is designed around a review-first workflow: generate SQL, inspect it, and then either apply it manually or rerun the command with --apply.
Installation
bun add onemigYou can also run the CLI through bunx:
bunx onemig --helpAgent Skill
OneMig includes an Agent Skill for Codex and other skill-aware agents. The skill teaches the agent when to use the onemig CLI, how to pull schema resources, how to generate pre/post migration SQL, and when --apply is safe to use.
From a project that has onemig installed or linked, install the skill from node_modules:
npx skills add ./node_modules/onemig/skills/onemig -a codexOr you can install it from GitHub:
npx skills add https://github.com/mnpenner/npm-packages/tree/main/packages/onemig/skills/onemig -a codexAdd -g to install the skill globally instead of only for the current project. Choose a symlink when prompted if you want local edits to the skill to be picked up during development.
Commands
onemig [global options] pull [-w | --dir=dir | --file=file]
onemig [global options] migrate [input] (--stage=pre|post | --pre | --post) [-w | --file=file] [--apply]
onemig [global options] kysley [--camel-case] [--temporal] [--file=file] [mysql2 type options]
onemig --help
onemig --versionpull, migrate, and kysley are implemented.
Connection Options
Commands that talk to the database accept global connection options before the command name:
onemig -h localhost -u root -D my_app -p pull-h, --host=name Connect to host.
-u, --user=name User for login.
-P, --port=# Port number to use for connection.
-D, --database=name Database to use.
-p, --password Prompt for password.Global options override environment variables. OneMig resolves connection settings in this order:
ONEMIG_URLorONEMIG_DATABASE_URLONEMIG_DB_HOST,ONEMIG_DB_PORT,ONEMIG_DB_USER,ONEMIG_DB_PASSWORD,ONEMIG_DB_PASSWORD_FILE,ONEMIG_DB_NAME,ONEMIG_DB_SOCKETDATABASE_URLDB_URLDB_HOST,DB_PORT,DB_USER,DB_USERNAME,DB_PASSWORD,DB_DATABASE,DB_NAME,DB_SOCKETMYSQL_HOST,MYSQL_TCP_PORT,MYSQL_PWD,MYSQL_UNIX_PORT
ONEMIG_DATABASE_URL=mariadb://user:password@localhost:3306/my_app
# or
ONEMIG_DB_HOST=localhost
ONEMIG_DB_PORT=3306
ONEMIG_DB_USER=root
ONEMIG_DB_PASSWORD=secret
ONEMIG_DB_NAME=my_appOneMig reads environment variables that are already present in the process; it does not load .env files itself. Bun users who want Bun's automatic .env and .env.local loading can run the CLI with Bun:
bunx --bun onemig migrate --stage=preNode users can preload env files with a tool such as dotenv-cli:
npx dotenv-cli -e .env -e .env.local -o -- onemig migrate --stage=prePulling Schema
Use pull to introspect the selected database and emit OneMig table resources.
onemig pullBy default, pull writes a YAML document stream to stdout. Use -w to write table files under onemig/tables/:
onemig pull -wWrite options print each file path after it is written.
You can also choose an output directory or single output file:
onemig pull --dir=schema
onemig pull --file=schema.yaml--write, --dir, and --file are mutually exclusive.
Writing Schema
OneMig reads YAML files containing one or more resource documents. The migration generator currently consumes Table resources. The schema also defines Database, SeedData, and User resources for future command support.
For editor validation, point YAML language tooling at the package schema:
# yaml-language-server: $schema=./node_modules/onemig/schema.jsonEach table resource describes columns, indexes, foreign keys, check constraints, triggers, and table options:
kind: Table
name: users
options:
engine: InnoDB
collation: utf8mb4_unicode_ci
columns:
- name: id
type: int
unsigned: true
autoIncrement: true
- name: email
type: varchar
length: 255
- name: created_at
type: datetime
fracDigits: 6
default: CURRENT_TIMESTAMP(6)
- name: metadata
type: json
nullable: true
indexes:
- name: PRIMARY
type: PRIMARY
columns:
- id
- name: uix_users_email
type: UNIQUE
columns:
- email
foreignKeys: []
checks:
- name: users_metadata_json
expr: JSON_VALID(`metadata`)
triggers: []Use oldNames to model table or column renames:
kind: Table
name: accounts
oldNames:
- users
columns:
- name: display_name
oldNames:
- name
type: varchar
length: 180
indexes: []
foreignKeys: []
checks: []
triggers: []Generating Migrations
Use migrate to compare desired YAML resources with the selected live database.
onemig migrate --stage=pre
onemig migrate --stage=post
onemig migrate --pre
onemig migrate --postThe optional positional input can be a YAML file or a directory containing .yaml and .yml files. It defaults to onemig.
onemig migrate schema.yaml --stage=pre
onemig migrate schema/ --stage=post
onemig migrate schema.yaml --pre
onemig migrate schema/ --postWithout --file, --write, or --apply, generated SQL is written to stdout:
onemig migrate --stage=pre > pre.sqlUse --file to choose an output file:
onemig migrate --stage=pre --file=onemig/migrations/pre.sqlUse -w or --write to write to onemig/migrations/YYYY-MM-DD-HHMM.sql:
onemig migrate --stage=pre --writeWrite options print the migration file path after it is written.
Use --apply to execute the generated statements against the selected database. It can be combined with --file or --write to keep a copy of the SQL that was applied.
onemig migrate --stage=pre --write --applyMigration Stages
--stage=pre or --pre is intended to run before deploying application code. It generates additive or widening changes such as:
- creating missing tables
- adding missing columns
- adding missing indexes and foreign keys when no column rename is pending
- widening
char,varchar,binary, andvarbinarylengths - increasing temporal fractional precision
- increasing decimal capacity without reducing scale
- promoting integer, text, or blob types to larger compatible types
--stage=post or --post is intended to run after deploying application code. It also generates cleanup and replacement changes such as:
- renaming tables and columns matched by
oldNames - dropping changed or extra indexes, foreign keys, check constraints, and triggers
- modifying changed columns and table options
- recreating changed indexes, foreign keys, check constraints, and triggers
- dropping extra columns
--drop-missing-tables is only valid with --stage=post and drops live tables that are not represented by the input resources.
onemig migrate --stage=post --drop-missing-tablesGenerating Kysely Types
Use kysley to generate Kysely schema types from the selected live database.
onemig kysleyWithout --file, writes TypeScript to stdout. Use --file to write the generated TypeScript to a file:
onemig kysley --file=src/db/schema.tsOptions
--camel-case Generate camelCase table and column keys for Kysely's CamelCasePlugin.
--connection-helper=file
Generate a configured mysql2/Kysely pool helper. Requires --file.
--date-strings Match mysql2 dateStrings: true; DATE, DATETIME, and TIMESTAMP select as string.
--decimal-numbers Match mysql2 decimalNumbers: true; DECIMAL selects as number.
--support-big-numbers Match mysql2 supportBigNumbers: true; unsafe BIGINT values can select as string.
--big-number-strings Match mysql2 bigNumberStrings: true. Requires --support-big-numbers.
--temporal Generate Temporal types and matching mysql2 conversions.
--file=file Write generated TypeScript to file.Temporal values and a connection helper
Use --temporal to map MySQL temporal columns to Temporal types:
| MySQL type | Generated select type |
| ----------- | ------------------------ |
| DATE | Temporal.PlainDate |
| DATETIME | Temporal.PlainDateTime |
| TIME | Temporal.PlainTime |
| TIMESTAMP | Temporal.Instant |
| YEAR | number |
MySQL does not have native equivalents for Temporal.PlainYearMonth or
Temporal.PlainMonthDay. --temporal cannot be combined with --date-strings.
The generated types need matching mysql2 runtime configuration. Generate an application-owned connection helper alongside the schema to apply it automatically:
onemig kysley \
--temporal \
--camel-case \
--file=src/db/schema.ts \
--connection-helper=src/db/database.tsThe helper exports createKyselyPool. Pass connection and pool settings as its first argument and
additional Kysely configuration, including logging and plugins, as its second argument:
import { createKyselyPool } from './database.ts'
await using db = createKyselyPool(
{
database: process.env.DB_DATABASE,
host: process.env.DB_HOST,
password: process.env.DB_PASSWORD,
port: Number(process.env.DB_PORT) || 3306,
user: process.env.DB_USER,
},
{
log: ['error'],
},
)The helper owns mysql2 options that affect the generated value types. In Temporal mode it also pins
the mysql2 client and every MySQL session to UTC, ensuring that TIMESTAMP values can safely become
Temporal.Instant values. If --camel-case is present, CamelCasePlugin is installed before any
plugins supplied by the caller.
Generated helpers target Kysely 0.29.5 or newer and mysql2 3.23.0 or newer. Temporal types use the
global Temporal object and TypeScript's ESNext library declarations, so the application runtime
must provide Temporal.
Temporal mode intentionally applies strict value validation:
- MySQL
TIMEis treated as a wall-clock time. Negative values and values of 24 hours or more throw, even though MySQL also permitsTIMEto represent longer durations. - Zero and incomplete MySQL dates throw instead of producing invalid Temporal values.
- Direct
Temporal.Instantwrites through mysql2 have millisecond precision. To retain MySQL microseconds, write a correctly formatted string instead.
Development
From this repository, run package checks with:
bun run check onemigBuild the package with:
bun run --cwd packages/onemig build