npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

knex-ptosc-plugin

v0.2.15

Published

Knex plugin to run migrations with ptosc

Readme

knex-ptosc-plugin

AI Disclosure

An LLM (GPT-5) was used heavily in the creation of this plugin.

WARNING

This code is a very early swing at extending Knex to use pt-online-schema-change for DB migrations. Use in production at your own risk.

Introduction

A Knex helper for running ALTER TABLE operations online using Percona Toolkit's pt-online-schema-change (pt-osc).

This plugin offers an alternative to the normal schema builder, routing the .alterTable() builder through pt-online-schema-change so changes can be executed with minimal locking and downtime. It also exposes alterTableWithPtoscRaw for executing raw ALTER TABLE statements. For operations that can be run INSTANT in the engine, pt-osc will not be invoked; instead knex will handle it.

Github Repo: https://github.com/gwinans/knex-ptosc-plugin

Test App: https://github.com/gwinans/kpp-test-app

Please, come contribute! Star the project!


Features

  • Safe execution: No shell concatenation, no command injection — arguments are passed directly to the pt-osc binary.
  • Password safety: The database password is passed via MYSQL_PWD environment variable (never on the command line or in logs).
  • Atomic migration lock: Uses Knex’s migrations lock row (knex_migrations_lock by default) to prevent concurrent schema changes. Table names can be customized with migrationsTable and migrationsLockTable. Both tables must exist before running migrations; otherwise an error is thrown.
  • Dry-run first: Always runs a pt-osc --dry-run before executing.
  • Full ALTER support: Works with Knex’s .alterTable() builder or raw ALTER TABLE strings via alterTableWithPtoscRaw.
  • Respects Knex bindings: Correctly interpolates values from .toSQL() output.
  • Instant alters when possible: Attempts native ALTER TABLE ... ALGORITHM=INSTANT and falls back to pt-osc when unsupported or when MySQL returns error 4092 ("Maximum row versions").
  • Native index operations: ADD INDEX and DROP INDEX statements run directly via Knex without pt-osc.

Servers running MySQL 5.6 or 5.7 skip the instant-alter attempt and always use pt-online-schema-change. This will not be changed - MySQL 5.6 and 5.7 are long-past End-of-Life.

Requirements

  • Node.js 16+
  • Knex configured for MySQL or MariaDB
  • Percona Toolkit installed and pt-online-schema-change available in $PATH
  • MySQL user with appropriate privileges, as required by Knex.

Installation

npm install knex-ptosc-plugin

Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | password | string | from Knex connection | Override DB password; passed via MYSQL_PWD env | | maxLoad | number | undefined | Passed to --max-load (e.g. Threads_connected=150) | | maxLoadMetric | string | 'Threads_running' | Metric name used in --max-load (e.g. Threads_connected) | | criticalLoad | number | undefined | Passed to --critical-load (e.g. Threads_running=50) | | criticalLoadMetric | string | 'Threads_running' | Metric name used in --critical-load (e.g. Threads_running) | | alterForeignKeysMethod | 'auto' \| 'rebuild_constraints' \| 'drop_swap' \| 'none' | 'auto' | Passed to --alter-foreign-keys-method | | ptoscPath | string | 'pt-online-schema-change' | Path to pt-osc binary | | forcePtosc | boolean | false | Skip the instant-alter attempt and always run pt-osc | | ptoscMinRows | number | 0 | Minimum row count required to use pt-osc; below this, ALTER runs natively | | analyzeBeforeSwap | boolean | true | --analyze-before-swap or --noanalyze-before-swap | | checkAlter | boolean | true | --check-alter or --nocheck-alter | | checkForeignKeys | boolean | true | --check-foreign-keys or --nocheck-foreign-keys | | checkInterval | number | undefined | Passed to --check-interval | | checkPlan | boolean | true | --check-plan or --nocheck-plan | | checkReplicationFilters | boolean | true | --check-replication-filters or --nocheck-replication-filters | | checkReplicaLag | boolean | false | Adds --check-replica-lag | | chunkIndex | string | undefined | Passed to --chunk-index | | chunkIndexColumns | number | undefined | Passed to --chunk-index-columns | | chunkSize | number | 1000 | Passed to --chunk-size | | chunkSizeLimit | number | 4.0 | Passed to --chunk-size-limit | | chunkTime | number | 0.5 | Passed to --chunk-time | | dropNewTable | boolean | true | --drop-new-table or --nodrop-new-table | | dropOldTable | boolean | true | --drop-old-table or --nodrop-old-table | | dropTriggers | boolean | true | --drop-triggers or --nodrop-triggers | | checkUniqueKeyChange | boolean | true | --check-unique-key-change or --nocheck-unique-key-change | | maxLag | number | 25 | Passed to --max-lag | | maxBuffer | number | 10485760 | child_process.execFile maxBuffer in bytes | | logger | { log: Function, error: Function } | console | Override default logging methods | | onProgress | (pct: number, eta?: string) => void | undefined | Callback for progress percentage and optional ETA parsed from output; logs include pt-osc ETA when available | | statistics | boolean | false | Adds --statistics; log and collect internal pt-osc counters | | onStatistics | (stats: Record<string, number>) => void | undefined | Invoked with parsed statistics object when statistics is true | | migrationsTable | string | 'knex_migrations' | Overrides migrations table name used for lock checks | | migrationsLockTable | string | 'knex_migrations_lock' | Overrides migrations lock table name used when acquiring lock | | timeoutMs | number | 30000 | Timeout in ms when acquiring migration lock | | intervalMs | number | 500 | Delay between lock retries in ms |

Statistics example

When statistics: true, pt-online-schema-change prints internal counters at the end of the run. These are parsed into an object, logged via the provided logger, and returned (or sent to onStatistics). Example output:

# Event          Count
# =====          =====
# chunk-size     1000
# copy_rows      12345

Debugging

The full pt-online-schema-change command is logged before execution regardless of DEBUG. Set DEBUG=knex-ptosc-plugin to enable verbose, line-by-line output.

DEBUG=knex-ptosc-plugin knex migrate:latest

Without DEBUG, only high-level progress percentages are logged. If pt-online-schema-change exits with an error, its full stdout and stderr are logged regardless of DEBUG, including any trailing lines that lack a newline.

Testing

Run npm test to execute linting and unit tests. Run npm run test:coverage to include coverage reporting. Continuous integration also clones the kpp-test-app and runs its migrations against a MySQL instance (root/test, database ptosc) to verify end-to-end behavior.


Safety & Security

  • No shell: Commands are executed via child_process.spawn with an args array, so backticks, semicolons, or other shell metacharacters in table names or SQL will not be interpreted by a shell.
  • Password is hidden: Never appears in process list, logs, or command history.
  • Atomic locks: Lock acquisition uses UPDATE ... WHERE is_locked=0 to avoid stealing locks from another process.
  • Dry-run first: Always verifies the migration before execution.

Example Migration

The alterTableWithPtosc helper accepts a table name and a schema builder callback, ensuring all changes use Knex's builder API.

CommonJS

const { alterTableWithPtosc } = require('knex-ptosc-plugin');

exports.up = function (knex) {
  return alterTableWithPtosc(
    knex,
    'widgets',
    (table) => {
      table.bigInteger('qty').alter();
    },
    { chunkSize: 500 }
  );
};

exports.down = function (knex) {
  return alterTableWithPtosc(
    knex,
    'widgets',
    (table) => {
      table.integer('qty').alter();
    },
    { chunkSize: 500 }
  );
};

ESM

import { alterTableWithPtosc } from 'knex-ptosc-plugin';

export function up(knex) {
  return alterTableWithPtosc(
    knex,
    'widgets',
    (table) => {
      table.bigInteger('qty').alter();
    },
    { chunkSize: 500 }
  );
}

export function down(knex) {
  return alterTableWithPtosc(
    knex,
    'widgets',
    (table) => {
      table.integer('qty').alter();
    },
    { chunkSize: 500 }
  );
}

Raw SQL

import { alterTableWithPtoscRaw } from 'knex-ptosc-plugin';

export function up(knex) {
  return alterTableWithPtoscRaw(
    knex,
    'ALTER TABLE widgets ALTER COLUMN qty TYPE BIGINT',
    { statistics: true }
  );
}

Advanced Usage

Handling Foreign Keys

pt-online-schema-change may refuse to run when tables have foreign key constraints. Use alterForeignKeysMethod: 'drop_swap' or 'rebuild_constraints' to control how pt-osc manages them:

return alterTableWithPtosc(
  knex,
  'widgets',
  (table) => {
    table.dropColumn('category_id');
  },
  { alterForeignKeysMethod: 'drop_swap' } // or 'rebuild_constraints'
);

Replica Lag Safeguard

Enable checkReplicaLag: true to have pt-osc abort if replicas fall behind, useful in production environments with read replicas:

return alterTableWithPtosc(
  knex,
  'widgets',
  (table) => {
    table.string('status').defaultTo('pending');
  },
  { checkReplicaLag: true }
);

Capture Statistics

Collect pt-osc statistics by setting statistics: true and handling them in the onStatistics callback:

return alterTableWithPtosc(
  knex,
  'widgets',
  (table) => {
    table.bigInteger('qty').alter();
  },
  {
    statistics: true,
    onStatistics: (stats) => {
      console.log('pt-osc stats', stats);
    },
  }
);

Troubleshooting

  • pt-online-schema-change: command not found
    The plugin runs which pt-online-schema-change during initialization and will throw if the binary cannot be found. Make sure Percona Toolkit is installed and in your PATH, or pass ptoscPath in options.

  • Permission errors
    Verify you followed the installation instructions for Knex.

  • Foreign key issues
    Use alterForeignKeysMethod: 'rebuild_constraints' or 'drop_swap' if pt-osc refuses to run due to FK constraints. Be careful with self-referencing FKs. Even ptosc can't handle them correctly.


License

This project is licensed under the MIT License.

© 2025 Geoff Winans