Skip to content

ocomsoft/morphic

Repository files navigation

morphic

A Go-first database migration tool with a Django-style workflow. Define your schema in YAML, generate type-safe Go migration files, and run them in-process β€” no Go toolchain required at runtime, no compiled binary to ship.

How migrations run. Generated migration files are real .go source β€” your IDE, gopls, and go vet treat them as ordinary Go. At runtime morphic migrate does not invoke go build. Instead it loads each .go file with yaegi, an embedded Go interpreter, and runs the migrations in the morphic process. The language inside migration files is "Go-like" β€” yaegi implements the Go spec but is not the official gc compiler, so a few features (cgo, deep reflection, some generics edge cases) work differently. For migrations generated by morphic generate this is invisible; for hand-edited migrations that import third-party packages, see Extending the yaegi Symbol Map.

✨ Why Go Migrations?

  • πŸ”’ Type-safe at edit time: Migrations are real .go files interpreted by yaegi β€” your IDE, gopls, and go vet catch errors before they ever run
  • ⚑ No build step at runtime: yaegi interprets the .go files directly; no go build, no temporary binary, no GOWORK juggling
  • πŸ—„οΈ Database-agnostic schema: Write YAML once, deploy to PostgreSQL, MySQL, SQLite, or SQL Server β€” scripting migrations in Go (rather than raw SQL) means the same migration code works across all supported databases
  • πŸ”€ DAG-based ordering: Migrations form a dependency graph so parallel branches merge cleanly
  • πŸ”„ Auto change detection: Diff YAML schemas, generate only what changed
  • ⚠️ Safe destructive ops: Field removals, table drops, and renames require explicit review
  • πŸ›  Optional standalone binary: The generated migrations/ directory is still a buildable Go module, so you can go build it for IDE type-checking or as an escape hatch

πŸš€ Quick Start

1. Install

go install github.com/ocomsoft/morphic@latest

2. Initialise your project

cd your-project
morphic init

This creates:

your-project/
└── migrations/
    β”œβ”€β”€ main.go     ← optional fallback entry point (`go build` still works)
    └── go.mod      ← dedicated migrations module (used by your IDE / gopls)

3. Define your schema

schema/schema.yaml:

defaults:
  postgresql:
    new_uuid: gen_random_uuid()
  mysql:
    new_uuid: uuid()
  sqlite:
    new_uuid: (lower(hex(randomblob(16))))

tables:
  - name: users
    fields:
      - name: id
        type: uuid
        primary_key: true
        default: new_uuid
      - name: email
        type: varchar
        length: 255
        nullable: false
      - name: created_at
        type: timestamp
        auto_create: true

  - name: posts
    fields:
      - name: id
        type: uuid
        primary_key: true
        default: new_uuid
      - name: title
        type: varchar
        length: 200
        nullable: false
      - name: user_id
        type: foreign_key
        foreign_key:
          table: users
          on_delete: CASCADE

The defaults section maps symbolic names (like new_uuid) to database-specific SQL expressions. Fields reference them by name β€” morphic resolves the correct expression for each target database at migration time.

4. Generate your first migration

morphic generate --name "initial"
# Creates: migrations/0001_initial.go

5. Apply to your database

export DATABASE_URL="postgresql://user:pass@localhost/mydb"
morphic migrate up

morphic migrate interprets the migration files in-process using yaegi and runs the embedded migration App. No go build, no temporary binary.


πŸ”„ Day-to-Day Workflow

# 1. Edit your YAML schema
vim schema/schema.yaml

# 2. Preview what will be generated
morphic generate --dry-run

# 3. Generate the migration
morphic generate --name "add user preferences"
# Creates: migrations/0004_add_user_preferences.go

# 4. Review the SQL before applying
morphic migrate showsql

# 5. Apply
morphic migrate up

# 6. Verify
morphic migrate status

πŸ“‹ migrate Subcommands

morphic migrate interprets the migration files in-process via yaegi and runs the embedded App. All arguments are forwarded:

morphic migrate up                       # apply all pending
morphic migrate up --to 0003_add_index   # apply up to a specific migration
morphic migrate down                     # roll back one
morphic migrate down --steps 3           # roll back multiple
morphic migrate status                   # show applied / pending
morphic migrate showsql                  # print SQL without running it
morphic migrate fake 0001_initial        # mark applied without running SQL
morphic migrate dag                      # show migration dependency graph

πŸ—οΈ Project Structure

your-project/
β”œβ”€β”€ schema/
β”‚   └── schema.yaml              ← your YAML schema definition
β”œβ”€β”€ migrations/
β”‚   β”œβ”€β”€ main.go                  ← binary entry point (generated by init)
β”‚   β”œβ”€β”€ go.mod                   ← dedicated module (generated by init)
β”‚   β”œβ”€β”€ 0001_initial.go          ← migration files (generated by morphic)
β”‚   β”œβ”€β”€ 0002_add_posts.go
β”‚   └── 0003_add_index.go
β”œβ”€β”€ go.mod
└── main.go

πŸ—„οΈ Database Support

Database Status Notes
PostgreSQL βœ… Full UUID, JSONB, arrays, advanced types
MySQL βœ… Supported JSON, AUTO_INCREMENT, InnoDB
SQLite βœ… Supported Simplified types, basic constraints
SQL Server βœ… Supported UNIQUEIDENTIFIER, NVARCHAR, BIT
Amazon Redshift βœ… Provider ready SUPER JSON, IDENTITY sequences
ClickHouse βœ… Provider ready MergeTree engine, Nullable types
TiDB βœ… Provider ready MySQL-compatible, distributed
Vertica βœ… Provider ready Columnar analytics
YDB (Yandex) βœ… Provider ready Optional, native JSON
Turso βœ… Provider ready Edge SQLite
StarRocks βœ… Provider ready MPP analytics, OLAP
Aurora DSQL βœ… Provider ready AWS serverless, PostgreSQL-compatible

PostgreSQL has been tested against real database instances. All other providers have comprehensive unit tests but may need additional validation for production.


⚠️ Destructive Operations

When a field removal, table drop, or rename is detected, morphic shows an interactive prompt before generating:

⚠  Destructive: field_removed on "users" (field: "legacy_col")
SQL: ALTER TABLE "users" DROP COLUMN "legacy_col"

> Generate       β€” include operation in migration
  Review         β€” include with // REVIEW comment
  Omit           β€” skip SQL; schema state still advances (SchemaOnly)
  IgnoreErrors   β€” include with IgnoreErrors: true
  Exit           β€” cancel migration generation

  Scope: [This only]  All remaining   All of this type

↑/↓ select β€’ tab scope β€’ enter confirm β€’ esc cancel

Use Tab to cycle the scope β€” This only, All remaining (apply your choice to every subsequent destructive op), or All of this type (e.g. apply to all field_removed ops but still prompt for table_removed).


πŸ”€ Branch & Merge

When two developers generate migrations concurrently the DAG develops branches:

0001_initial
β”œβ”€β”€ 0002_add_messaging   (developer A)
└── 0003_add_payments    (developer B)

Resolve with a merge migration:

morphic generate --merge
# Creates: migrations/0004_merge_0002_add_messaging_and_0003_add_payments.go

βš™οΈ Configuration

Database connection

morphic migrate reads connection details from DATABASE_URL and DB_TYPE:

export DATABASE_URL="postgresql://user:pass@localhost/mydb"
export DB_TYPE=postgresql   # optional, defaults to postgresql

If you prefer the optional fallback path of compiling migrations/ into a standalone binary, edit migrations/main.go to read additional vars (DB_HOST, DB_PORT, DB_USER, …) β€” see the Manual Build Guide.

Configuration file

migrations/morphic.config.yaml:

database:
  type: postgresql

migration:
  directory: migrations
  include_down_sql: true

output:
  verbose: false

See the Configuration Guide for complete options.


πŸ“– Documentation

Guides

Command Reference

Schema & Generation

Command Description
init Initialize migrations directory and config
generate Generate migration files from YAML schema changes
generate empty Create a blank migration for custom operations
generate dump-data Generate a data-seeding migration from live DB

Migration Runtime

Command Description
migrate Run migrations in-process via yaegi

Inspection & Debugging

Command Description
schema-diff Show drift between YAML schema and migration state
db-diff Compare live DB schema against migration state
current-state Show reconstructed schema state as YAML
schema-to-sql Convert merged YAML schema to SQL
schema-to-diagram Generate Markdown docs with diagrams

Conversion Tools

Command Description
db-to-schema Reverse-engineer YAML schema from existing DB
struct-to-schema Convert Go structs to YAML schema
find-includes Discover schema includes from Go modules

πŸ€– Claude Code Skill

morphic includes a Claude Code skill that auto-triggers whenever Claude detects database schema work in your Go project. It enforces the correct workflow: edit schema/schema.yaml β†’ generate migrations β†’ avoid raw SQL.

Installing the Skill

Option 1: Personal skill (recommended β€” available in all Go projects):

cp -r skills/ ~/.claude/skills/go-morphic

Option 2: Plugin (if the repo is registered as a Claude Code plugin):

The .claude-plugin/ directory is detected automatically when installed.

What the Skill Does

  • Auto-triggers when you ask Claude to add tables, fields, indexes, foreign keys, or modify columns
  • Guides Claude through init β†’ schema edit β†’ migration generation β†’ SQL preview β†’ verification
  • Provides quick reference for field types, properties, indexes, defaults, and type mappings
  • Enforces schema-first workflow β€” RunSQL only as a last resort
  • Covers both new project bootstrapping and ongoing schema changes

See skills/SKILL.md for the full skill content and docs/claude-code-skill.md for detailed usage.


🀝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Add tests for new functionality
  4. Ensure all tests pass: go test ./...
  5. Submit a pull request

πŸ“„ License

MIT License β€” see LICENSE file for details.


Ready to get started? Run morphic init in your project directory.

About

Automate creating migrations into go code

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages