Declarative database schema management. Define your schemas in JSON, and Vespertide automatically generates migration plans and SQL from model diffs.
- Declarative Schema: Define your desired database state in JSON files
- Automatic Diffing: Vespertide compares your models against applied migrations to compute changes
- Migration Planning: Generates typed migration actions (not raw SQL) for safety and portability
- Multi-Database Support: PostgreSQL, MySQL, SQLite
- Enum Types: Native string enums and integer enums (no migration needed for new values)
- Zero-Runtime Migrations: Compile-time macro generates database-specific SQL
- JSON Schema Validation: Ships with JSON Schemas for IDE autocompletion and validation
- ORM Export: Export schemas to SeaORM, SQLAlchemy, SQLModel
cargo install vespertide-cli# Initialize a new project
vespertide init
# Create a model template
vespertide new user
# Edit models/user.json, then check changes
vespertide diff
# Preview the SQL
vespertide sql
# Generate a migration file
vespertide revision -m "create user table"| Command | Description |
|---|---|
vespertide init |
Create vespertide.json configuration file |
vespertide new <name> |
Create a new model template with JSON Schema reference |
vespertide diff |
Show pending changes between migrations and current models |
vespertide sql |
Print SQL statements for the next migration |
vespertide sql --backend mysql |
SQL for specific backend (postgres/mysql/sqlite) |
vespertide revision -m "<msg>" |
Persist pending changes as a migration file |
vespertide status |
Show configuration and sync status overview |
vespertide log |
List applied migrations with generated SQL |
vespertide export --orm seaorm |
Export models to ORM code |
Models are JSON files in the models/ directory. Always include $schema for IDE validation:
{
"$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/model.schema.json",
"name": "user",
"columns": [
{ "name": "id", "type": "integer", "nullable": false, "primary_key": true },
{ "name": "email", "type": "text", "nullable": false, "unique": true, "index": true },
{ "name": "name", "type": { "kind": "varchar", "length": 100 }, "nullable": false },
{
"name": "status",
"type": { "kind": "enum", "name": "user_status", "values": ["active", "inactive", "banned"] },
"nullable": false,
"default": "'active'"
},
{ "name": "created_at", "type": "timestamptz", "nullable": false, "default": "NOW()" }
]
}Simple Types:
| Type | SQL Type | Type | SQL Type |
|---|---|---|---|
"integer" |
INTEGER | "text" |
TEXT |
"big_int" |
BIGINT | "boolean" |
BOOLEAN |
"small_int" |
SMALLINT | "uuid" |
UUID |
"real" |
REAL | "json" |
JSON |
"double_precision" |
DOUBLE PRECISION | "jsonb" |
JSONB |
"date" |
DATE | "bytea" |
BYTEA |
"time" |
TIME | "inet" |
INET |
"timestamp" |
TIMESTAMP | "cidr" |
CIDR |
"timestamptz" |
TIMESTAMPTZ | "macaddr" |
MACADDR |
"interval" |
INTERVAL | "xml" |
XML |
Complex Types:
{ "kind": "varchar", "length": 255 }
{ "kind": "char", "length": 2 }
{ "kind": "numeric", "precision": 10, "scale": 2 }
{ "kind": "enum", "name": "status", "values": ["active", "inactive"] }
{ "kind": "custom", "custom_type": "TSVECTOR" }Use enums instead of text columns for status fields and categories:
String Enum (PostgreSQL native enum):
{
"name": "status",
"type": { "kind": "enum", "name": "order_status", "values": ["pending", "shipped", "delivered"] },
"nullable": false,
"default": "'pending'"
}Integer Enum (stored as INTEGER, no DB migration needed for new values):
{
"name": "priority",
"type": {
"kind": "enum",
"name": "priority_level",
"values": [
{ "name": "low", "value": 0 },
{ "name": "medium", "value": 10 },
{ "name": "high", "value": 20 }
]
},
"nullable": false,
"default": 10
}Define constraints directly on columns instead of using table-level constraints:
{
"name": "author_id",
"type": "integer",
"nullable": false,
"foreign_key": {
"ref_table": "user",
"ref_columns": ["id"],
"on_delete": "cascade"
},
"index": true
}Reference Actions (snake_case): "cascade", "restrict", "set_null", "set_default", "no_action"
Composite Primary Key (inline):
{ "name": "user_id", "type": "integer", "nullable": false, "primary_key": true },
{ "name": "role_id", "type": "integer", "nullable": false, "primary_key": true }Table-level constraints are only needed for CHECK expressions:
"constraints": [
{ "type": "check", "name": "check_positive", "expr": "amount > 0" }
]See SKILL.md for complete documentation.
Important: Migration files are auto-generated. Never create or edit them manually.
# Always use the CLI to create migrations
vespertide revision -m "add status column"The only exception is adding fill_with values when prompted (for NOT NULL columns without defaults).
| Database | Identifier Quoting | Notes |
|---|---|---|
| PostgreSQL | "identifier" |
Full feature support |
| MySQL | `identifier` |
Full feature support |
| SQLite | "identifier" |
Full feature support |
vespertide export --orm seaorm # Rust - SeaORM entities
vespertide export --orm sqlalchemy # Python - SQLAlchemy models
vespertide export --orm sqlmodel # Python - SQLModel (FastAPI)Use the vespertide_migration! macro to run migrations at application startup:
[dependencies]
vespertide = "0.1"
sea-orm = { version = "1.0", features = ["runtime-tokio-native-tls", "sqlx-postgres"] }use sea_orm::Database;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let db = Database::connect("postgres://user:pass@localhost/mydb").await?;
vespertide::vespertide_migration!(db).await?;
Ok(())
}The macro generates database-specific SQL at compile time for zero-runtime overhead.
vespertide/
├── vespertide-core # Data structures (TableDef, ColumnDef, MigrationAction)
├── vespertide-planner # Schema diffing and migration planning
├── vespertide-query # SQL generation (PostgreSQL, MySQL, SQLite)
├── vespertide-cli # Command-line interface
├── vespertide-exporter # ORM code generation
├── vespertide-macro # Compile-time migration macro
└── vespertide-config # Configuration management
- Define Models: Write table definitions in JSON files with
$schemafor validation - Replay Migrations: Applied migrations are replayed to reconstruct the baseline schema
- Diff Schemas: Current models are compared against the baseline
- Generate Plan: Changes are converted into typed
MigrationActionenums - Emit SQL: Migration actions are translated to database-specific SQL
vespertide.json:
{
"modelsDir": "models",
"migrationsDir": "migrations",
"tableNamingCase": "snake",
"columnNamingCase": "snake",
"modelFormat": "json"
}cargo build # Build
cargo test # Test
cargo clippy --all-targets --all-features # Lint
cargo fmt # Format
cargo run -p vespertide-schema-gen -- --out schemas # Regenerate JSON SchemasApache-2.0