@savioruz/owi
v0.0.5
Published
Simple database migration tool for Swift and Vapor
Maintainers
Readme
Owi - Simple Database Migrations for Swift
A clean and simple database migration tool for Swift, designed to work seamlessly with Vapor and other Swift web frameworks. See Changelogs
Table of Contents
Installation
As a Library (for Vapor apps)
Add to your Package.swift:
.package(url: "https://github.com/savioruz/owi.git", from: "0.0.1")Then add to your target:
.product(name: "Owi", package: "owi")As a CLI Tool
Quick Install
Using the install script:
curl -fsSL https://raw.githubusercontent.com/savioruz/owi/main/install.sh | shOr download and run:
curl -fsSL -o install.sh https://raw.githubusercontent.com/savioruz/owi/main/install.sh
chmod +x install.sh
./install.shHomebrew
brew tap savioruz/homebrew-tap
brew install owiManual Installation
Download the latest release from GitHub Releases:
Migration File Format
Migrations are written in SQL files with a specific format:
-- migrate:up
CREATE TABLE users (id SERIAL PRIMARY KEY);
-- migrate:down
DROP TABLE users;
-- migrate:up
ALTER TABLE users ADD COLUMN email VARCHAR(255);
-- migrate:down
ALTER TABLE users DROP COLUMN email;Rules
- Each file can contain single or multiple up/down pairs
- Sections are marked with
-- migrate:upand-- migrate:down - Down sections are applied in reverse order during rollback
- File names should follow the pattern:
{number}_{description}.sql- Example:
001_create_users_table.sql
- Example:
CLI Usage
Create a New Migration
owi new create_users_table
# With custom directory
owi new create_users_table -m ./db/migrationsThis creates a new migration file with an auto-incremented number:
migrations/
001_create_users_table.sqlRun Migrations
# SQLite
owi migrate -u ./db.sqlite -m ./migrations
# PostgreSQL
owi migrate -u "postgres://user:pass@localhost:5432/mydb" --type postgres -m ./migrations
# MySQL
owi migrate -u "mysql://user:pass@localhost:3306/mydb" --type mysql -m ./migrationsRollback Migrations
# Rollback last migration
owi rollback -u ./db.sqlite
# Rollback last 3 migrations
owi rollback -u ./db.sqlite --count 3Check Status
owi status -u ./db.sqlite -m ./migrationsOutput:
Migration Status:
─────────────────────────────────────
✓ 001_create_users_table
✓ 002_add_email_to_users
✗ 003_create_posts_table
─────────────────────────────────────
Applied: 2, Pending: 1CLI Options
All commands support these options:
-u, --database-url <url>- Database URL or path-m, --migrations-dir <dir>- Migrations directory (default:./migrations)--type <type>- Database type:sqlite,postgres, ormysql(default:sqlite)
Library Usage (Vapor Integration)
Using Vapor's Database Connection (Recommended)
When integrating with Vapor, pass your existing database connection to avoid connection pool issues:
import Vapor
import Owi
func configureMigrations(_ app: Application) async throws {
// Use Vapor's existing database connection - no pool management needed!
let driver = PostgresDriver(database: app.db(.psql))
// Create runner
let runner = Runner(
driver: driver,
migrationDir: "./Migrations"
)
// Run migrations
try await runner.migrate()
// No need to call close() - Vapor manages the connection!
}For MySQL:
let driver = MySQLDriver(database: app.db(.mysql))For SQLite:
let driver = SQLiteDriver(database: app.db(.sqlite))In configure.swift
public func configure(_ app: Application) async throws {
// ... your database configuration
// Run migrations on startup (optional)
try await configureMigrations(app)
// ... rest of configuration
}Standalone Usage (Without Vapor)
If you're building a CLI tool or not using Vapor, create your own connection:
import Owi
import PostgresKit
// Create configuration using native PostgresKit types
let config = PostgresConfiguration(
hostname: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "myapp",
tls: .disable
)
// Create driver (manages its own connection pool)
let driver = try await PostgresDriver(configuration: config)
// Create runner
let runner = Runner(driver: driver, migrationDir: "./Migrations")
// Run migrations
try await runner.migrate()
// Check status
try await runner.status()
// Rollback
try await runner.rollback(count: 1)
// IMPORTANT: Close the connection when done!
try await runner.close()Key Difference:
- With Vapor database:
PostgresDriver(database: app.db(.psql))- NOclose()needed - With configuration:
PostgresDriver(configuration: config)- MUST callclose()
API Reference
Runner
Main migration runner.
public struct Runner {
public init(
driver: any DatabaseDriver,
migrationDir: String,
schemaTableName: String = "owi_schema"
)
public func migrate() async throws
public func rollback(count: Int = 1) async throws
public func status() async throws
public func close() async throws
}Parser
Parses migration files.
public struct Parser {
public func parse(fileURL: URL) throws -> Migration
public func loadMigrations(from directoryURL: URL) throws -> [Migration]
}Migration
Represents a single migration.
public struct Migration {
public let id: String
public let upSQL: String
public let downSQL: String
public let filePath: String
public var version: Int? // Extracted from ID (e.g., "001" -> 1)
}SchemaVersion
Represents the current schema version state in the database.
public struct SchemaVersion {
public let id: Int // Always 1
public let version: Int // Current migration version
public let dirty: Bool // Is a migration in progress?
public let modifiedAt: Date // Last modification time
}Database Schema
The schema tracking table (owi_schema by default, customizable) stores a single row:
| Column | Type | Description |
|--------|------|-------------|
| id | INTEGER | Always 1 (enforced by CHECK constraint) |
| version | INTEGER | Current migration version (e.g., 0, 1, 2, 3) |
| dirty | BOOLEAN/INTEGER | Migration in progress flag |
| modified_at | TIMESTAMP/TEXT | Last modification timestamp |
Example table content:
id | version | dirty | modified_at
1 | 3 | 0 | 2025-10-26 20:08:53Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - feel free to use in your projects!
Inspiration
Inspired by tools like dbmate, but designed specifically for Swift and Vapor.
