GitHub - MemberJunction/skyway: TypeScript implementation of database migration tool - compatible with Flyway

Skyway

A TypeScript-native database migration tool inspired by Flyway, built specifically for SQL Server.

Skyway eliminates the Java dependency required by Flyway while providing the same migration workflow — versioned migrations, baseline support, repeatable migrations, checksum validation, and schema history tracking. It targets SQL Server using the mssql (tedious) driver and wraps migrations in transactions for safe, atomic execution.

Why Skyway?

Flyway is the gold standard for database migrations, but it requires a Java runtime. For TypeScript/JavaScript projects targeting SQL Server, this means maintaining a Java toolchain alongside Node.js just for migrations. Skyway solves this by reimplementing Flyway's core migration engine natively in TypeScript:

  • No Java required — runs anywhere Node.js runs
  • Flyway-compatible — uses the same file naming conventions, history table schema, and checksum algorithm
  • Transaction safety — wraps migrations in SQL Server transactions (DDL is transactional in SQL Server)
  • Dual interface — use as a library in your application or as a standalone CLI tool
  • Drop-in replacement — point Skyway at your existing Flyway migration files and it just works

Usage

As a Library

import { Skyway } from '@skyway/core';

const skyway = new Skyway({
    Database: {
        Server: 'localhost',
        Database: 'my_app',
        User: 'sa',
        Password: 'secret',
    },
    Migrations: {
        Locations: ['./migrations'],
        DefaultSchema: 'dbo',
        BaselineOnMigrate: true,
    },
    Placeholders: {
        'flyway:defaultSchema': 'dbo',
    },
    TransactionMode: 'per-run',
});

// Apply pending migrations
const result = await skyway.Migrate();
console.log(`Applied ${result.MigrationsApplied} migrations`);

// Check migration status
const info = await skyway.Info();
info.forEach(m => console.log(`${m.Version} [${m.State}] ${m.Description}`));

// Validate checksums
const validation = await skyway.Validate();
if (!validation.Valid) {
    console.error('Checksum mismatch detected:', validation.Errors);
}

// Baseline the database at a version
await skyway.Baseline('202601122300');

// Repair history table (remove failed entries, realign checksums)
await skyway.Repair();

// Clean all objects from the schema
await skyway.Clean();

// Create or drop the database
await skyway.CreateDatabase();
await skyway.DropDatabase();

await skyway.Close();

As a CLI

# Apply pending migrations
skyway migrate --server localhost --database my_app --user sa --password secret --locations ./migrations

# Show migration status
skyway info

# Validate applied migrations against local files
skyway validate

# Clean database (drop all objects in the schema)
skyway clean

# Baseline an existing database at a version
skyway baseline --baseline-version 202601122300

# Repair history table
skyway repair

# Create or drop the database
skyway create-db
skyway drop-db

# Dry-run mode (show what would be applied without executing)
skyway migrate --dry-run

# Quiet mode (suppress per-migration output)
skyway migrate --quiet

Configuration File

Create a skyway.json in your project root. Both camelCase and PascalCase keys are supported:

{
    "Database": {
        "Server": "localhost",
        "Port": 1433,
        "Database": "my_app",
        "User": "sa",
        "Password": "${SQL_PASSWORD}"
    },
    "Migrations": {
        "Locations": ["./migrations"],
        "DefaultSchema": "dbo",
        "HistoryTable": "flyway_schema_history",
        "BaselineOnMigrate": true,
        "BaselineVersion": "1"
    },
    "Placeholders": {
        "flyway:defaultSchema": "dbo"
    },
    "TransactionMode": "per-migration"
}

Migration File Types

Skyway supports the same three migration types as Flyway:

Versioned Migrations (V)

Run once, tracked by version. Applied in version order.

V202506130552__Add_Users_Table.sql
V202506140800__Add_Email_Column.sql

Baseline Migrations (B)

Applied only to empty databases (no prior migration history). Used to initialize a schema from scratch.

B202601122300__v3.0_Baseline.sql

Repeatable Migrations (R__)

Run after all versioned migrations whenever their checksum changes. Useful for views, stored procedures, or metadata refresh operations.

Naming Convention

V{version}__{description}.sql
B{version}__{description}.sql
R__{description}.sql
  • Version: Numeric timestamp (YYYYMMDDHHMM) or any sortable numeric string
  • Separator: Double underscore (__)
  • Description: Underscores converted to spaces for display

Flyway Compatibility

Skyway aims for compatibility with Flyway's behavior and artifacts:

Feature Status
flyway_schema_history table Compatible
CRC32 checksums Compatible
Versioned migrations (V) Supported
Baseline migrations (B) Supported
Repeatable migrations (R__) Supported
Placeholder substitution (${...}) Supported (smart — only known placeholders)
GO batch separator handling Supported
Transaction wrapping Supported (per-migration or per-run)
Out-of-order migrations Configurable
clean command Supported
baseline command Supported
repair command Supported
info command Supported
validate command Supported
Dry-run mode Supported (--dry-run)

CLI Commands

Command Description
skyway migrate Apply pending migrations
skyway info Show migration status
skyway validate Validate applied migration checksums
skyway clean Drop all objects in the configured schema
skyway baseline Baseline the database at a version
skyway repair Remove failed entries and realign checksums
skyway create-db Create the target database
skyway drop-db Drop the target database

CLI Flags

Flag Description
-s, --server <host> SQL Server hostname
-p, --port <port> SQL Server port
-d, --database <name> Database name
-u, --user <user> Database user
-P, --password <pass> Database password
-l, --locations <paths> Migration locations (comma-separated)
--schema <schema> Default schema name
--table <table> History table name
--baseline-version <ver> Baseline version
--baseline-on-migrate Auto-baseline on empty database
--transaction-mode <mode> per-run (default) or per-migration
--dry-run Show pending migrations without executing
-q, --quiet Suppress per-migration output
--config <path> Path to config file
--placeholder <key=value> Set a placeholder (repeatable)

Improvements Over Flyway

Skyway isn't just a clone — it fixes real pain points with Flyway:

Atomic Migration Runs (No More Partial Failures)

Flyway executes each SQL statement directly without transaction wrapping. If migration #5 fails halfway through, statements that already executed are committed and cannot be rolled back. Your database is left in a broken, partially-migrated state that requires manual intervention to fix.

Skyway takes advantage of SQL Server's transactional DDL support to wrap migrations in transactions. You choose the behavior:

Mode Behavior on failure Use case
per-run (default) All pending migrations roll back Maximum safety — your database is either fully migrated or completely untouched
per-migration Only the failed migration rolls back; previously completed migrations stay When you want partial progress preserved

Flyway (No Transaction Safety)

flowchart LR
    A[Migration 1] -->|committed| B[Migration 2]
    B -->|committed| C[Migration 3]
    C -->|committed| D[Migration 4]
    D -->|FAILS halfway| E[Partial State]
    style E fill:#ff6b6b,color:#fff
    style A fill:#51cf66,color:#fff
    style B fill:#51cf66,color:#fff
    style C fill:#51cf66,color:#fff
    style D fill:#ffd43b,color:#333
Loading

Migrations 1-3 are committed. Migration 4 is half-applied. Database is in a broken state requiring manual cleanup.

Skyway per-run Mode (Default)

flowchart LR
    subgraph TX [Single Transaction]
        A[Migration 1] --> B[Migration 2]
        B --> C[Migration 3]
        C --> D[Migration 4]
    end
    D -->|FAILS| E[ROLLBACK ALL]
    E --> F[Database Unchanged]
    style TX fill:#e7f5ff,stroke:#339af0
    style E fill:#ff6b6b,color:#fff
    style F fill:#51cf66,color:#fff
Loading

All migrations run inside one transaction. Any failure rolls back everything. Database remains exactly as it was.

Skyway per-migration Mode

flowchart LR
    subgraph TX1 [Transaction 1]
        A[Migration 1]
    end
    subgraph TX2 [Transaction 2]
        B[Migration 2]
    end
    subgraph TX3 [Transaction 3]
        C[Migration 3]
    end
    subgraph TX4 [Transaction 4]
        D[Migration 4]
    end
    TX1 -->|committed| TX2
    TX2 -->|committed| TX3
    TX3 -->|committed| TX4
    TX4 -->|FAILS| E[Migration 4 Rolled Back]
    style TX1 fill:#51cf66,color:#fff
    style TX2 fill:#51cf66,color:#fff
    style TX3 fill:#51cf66,color:#fff
    style TX4 fill:#ffd43b,color:#333
    style E fill:#ff6b6b,color:#fff
Loading

Migrations 1-3 stay committed. Only migration 4 rolls back cleanly — no partial state.

const skyway = new Skyway({
    // ...
    TransactionMode: 'per-run', // all-or-nothing (default)
});

Smart Placeholder Handling

Flyway treats every ${...} occurrence as a placeholder, which breaks migration files containing JavaScript template literals, embedded code, or other uses of the ${...} syntax. If your SQL migration inserts a stored procedure body containing ${myVar}, Flyway will either error out or silently corrupt the value.

Skyway only substitutes known placeholders:

  • ${flyway:defaultSchema}, ${flyway:timestamp}, and other ${flyway:*} built-in placeholders are always substituted
  • User-defined placeholders registered in the Placeholders config are substituted
  • All other ${...} patterns are left untouched

This means migrations containing JavaScript code, JSON templates, or any other ${...} syntax work correctly without escaping or workarounds.

Large String Support (No 4000-Character Truncation)

Flyway's JDBC-based execution can truncate or corrupt strings longer than 4000 characters — a known limitation when working with NVARCHAR(MAX) columns. This is problematic for migrations that insert large text values (code, HTML, JSON blobs, etc.).

Skyway uses the mssql (tedious) driver with explicit NVARCHAR(MAX) type declarations, ensuring strings of any length are transmitted to SQL Server intact. No truncation, no corruption, regardless of string size.

SQL Server Transaction Support

Unlike MySQL or PostgreSQL, SQL Server supports transactional DDL — CREATE TABLE, ALTER TABLE, and most schema changes can be rolled back within a transaction. Skyway takes full advantage of this with its per-run and per-migration transaction modes (see Atomic Migration Runs above).

Development

npm install
npm run build
npm test

License

MIT