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

umzeption

v0.5.1

Published

A recursive umzug extension

Readme

Umzeption

Umzeption is a recursive extension for Umzug. Every npm package in your dependency tree — including the host app itself — can ship its own DB migrations and an installSchema(); Umzeption discovers them, groups them per package (dependencies first, host app last), and runs them through Umzug. Fresh databases install each schema and mark migrations as applied; existing databases run only what's pending.

npm version npm downloads neostandard javascript style Module type: ESM Types in JS

Usage

createUmzeption sets up migrations once and returns a CLI runner, a programmatic Umzug instance, and an install-mode Umzug — all from a single call. The typical setup splits across three files:

umzeption-setup.js — one module owns the setup:

import { readFile } from 'node:fs/promises';
import pg from 'pg';
import {
  UmzeptionPgStorage,
  createUmzeption,
  createUmzeptionPgContext,
  installSchemaFromString,
} from 'umzeption';

export function buildUmzeption (pool) {
  return createUmzeption({
    umzeption: {
      dependencies: ['@yikesable/foo'],
      glob: ['migrations/*.js'],
      meta: import.meta,
      installSchema: async ({ context }) => {
        const sql = await readFile(new URL('schema.sql', import.meta.url), 'utf8');
        return installSchemaFromString(context, sql);
      },
    },
    context: createUmzeptionPgContext(pool ?? new pg.Pool({
      allowExitOnIdle: true,
      connectionString: process.env.DATABASE_URL,
    })),
    storage: new UmzeptionPgStorage(),
    logger: console,
  });
}

export const umzeption = buildUmzeption();

tools/umzug.js — CLI entry point, one line of logic:

import { umzeption } from '../umzeption-setup.js';

await umzeption.runAsCLI();

Then run:

node tools/umzug.js install    # fresh database: runs each package's installSchema()
node tools/umzug.js up         # applies any pending migrations
node tools/umzug.js pending    # lists pending migrations
node tools/umzug.js create --name add-users.js  # generates a new migration file

tools/test-helpers.js — pg-utils test bootstrap uses the install-mode Umzug directly:

import { pgTestSetupFor } from '@voxpelli/pg-utils';

import { buildUmzeption } from '../umzeption-setup.js';

export const pgTestSetupConfig = {
  connectionString: process.env.DATABASE_URL,
  schema: pool => buildUmzeption(pool).installUmzug,
};

export async function testHelpers (t) {
  return pgTestSetupFor(pgTestSetupConfig, t);
}

buildUmzeption(pool).installUmzug gives @voxpelli/pg-utils a ready Umzug instance; pg-utils calls .up() on it to install the schema before each test.

For programmatic use (running migrations at app startup), reach for the umzug property directly:

import { umzeption } from './umzeption-setup.js';
await umzeption.umzug.up();

If you need lower-level control — multiple Umzug instances, custom Umzug options, no helper at all — compose Umzug manually. The annotated example below also serves as a reference for every umzeption option:

import pg from 'pg';
import {
  UmzeptionPgStorage,
  createUmzeptionPgContext,
  umzeption,
} from 'umzeption';
import { Umzug } from 'umzug';

const umzug = new Umzug({
  migrations: umzeption({
    // Which dependencies we want to install migrations and schemas from
    dependencies: [
      '@yikesable/foo',
      '@yikesable/bar',
    ],
    // Optional: Which migrations do we have ourselves?
    glob: ['migrations/*.js'],
    // Optional: Sets up this package's schema on fresh installs (when install: true is set)
    async installSchema ({ context }) {},
    // Optional: Set to true if it should be a fresh install rather than a migration
    install: true,
    // Optional: Used to inform where to resolve "glob" from
    meta: import.meta,
    // Optional: Can be used instead of "meta" and if none are set, then process.cwd() is the default
    // cwd: process.cwd(),
    // Optional: Custom sort for migration file paths. Receives absolute paths
    // plus `{ pluginDir }` so the shared prefix can be stripped before
    // length-aware or numeric comparisons. Defaults to lexicographic order.
    // sortFiles: (files, { pluginDir }) => [...files].sort((a, b) => a.localeCompare(b)),
  }),
  context: createUmzeptionPgContext(new pg.Pool({
    allowExitOnIdle: true,
    connectionString: '...',
  })),
  storage: new UmzeptionPgStorage(),
  logger: console,
});

await umzug.up();

Concept

First install

On the first install in an environment you set install: true in umzeption(). This makes it so that the installSchema() methods will be what is run and all migrations will be marked as being run without actually running (as a fresh install should need no migrations).

Subsequent upgrades

On everything but the first install you set install: false in umzeption() (or leave it out). This makes it so that the installSchema() methods not be run, but all new migrations will be run as normal through Umzug.

How to make an Umzeption dependency

The dependency is expected to provide one of these two at its top level

Through umzeptionConfig property

Makes it easy to enforce types and keeps all Umzeption related stuff grouped together

/** @satisfies {import('umzeption').UmzeptionDependency} */
export const umzeptionConfig = {
  dependencies: ['@yikesable/abc'],
  glob: ['migrations/*.js'],
  // Optional: a dependency can declare its own sort if its filenames need
  // special ordering (e.g. legacy non-timestamp names). Wins over the
  // top-level `sortFiles` for this dependency only.
  // sortFiles: (files, { pluginDir }) => [...files].sort(),
  async installSchema ({ context }) {
    if (context.type !== 'pg') {
      throw new Error(`Unsupported context type: ${context.type}`);
    }

    const tables = await getTables();

    await context.value.transact(async client => {
      for (const table of tables) {
        await client.query(table);
      }
    });
  },
};

Through top level exports

Alternative to umzeptionConfig when you prefer named exports over a config object. Each export must be typed individually (vs umzeptionConfig's single @satisfies UmzeptionDependency annotation), as the example below shows on its installSchema typedef.

export const dependencies = ['@yikesable/abc'];
export const glob = ['migrations/*.js'];
/** @type {import('umzeption').UmzeptionDependency["installSchema"]} */
export async function installSchema ({ context }) {
  if (context.type !== 'pg') {
    throw new Error(`Unsupported context type: ${context.type}`);
  }

  const tables = await getTables();

  await context.value.transact(async client => {
    for (const table of tables) {
      await client.query(table);
    }
  });
}

Using installSchemaFromString helper

Use this helper when your schema is a string of CREATE statements (e.g. loaded from a .sql file); non-CREATE DDL (ALTER TABLE, DO $$, etc.) must use context.value.transact directly.

import { readFile } from 'node:fs/promises';

import { installSchemaFromString } from 'umzeption';

/** @satisfies {import('umzeption').UmzeptionDependency} */
export const umzeptionConfig = {
  dependencies: ['@yikesable/abc'],
  glob: ['migrations/*.js'],
  installSchema: async ({ context }) => {
    const tables = await readFile(new URL('create-tables.sql', import.meta.url), 'utf8');
    return installSchemaFromString(context, tables);
  },
};

Using the CLI

Umzug ships a CLI with create, up, down, and pending subcommands. Umzeption layers an install subcommand on top — it runs each dependency's installSchema() and marks all existing migrations as already-executed (the "fresh database" mode). The create subcommand auto-generates timestamp-prefixed migration filenames and runs an allowConfusingOrdering safety check that errors if a new file would sort before existing migrations (see Notes on sortFiles ordering).

Use createUmzeption to wire it up in one step — see the Usage section above.

Then run:

node tools/umzug.js install                          # fresh database: runs installSchema() for every dependency
node tools/umzug.js create --name my-migration.js    # folder defaults to the location of tools/umzug.js
node tools/umzug.js pending                          # lists pending migrations
node tools/umzug.js up                               # applies all pending migrations

On a fresh project with no migrations yet, create auto-infers its destination folder from the location of your tools/umzug.js (via meta: import.meta); pass --folder path/to/migrations to override. Without this default, umzug would error with "Couldn't infer a directory to generate migration file in" on the very first create.

Note on install flags. The install subcommand is implemented by translating argv to ['up', ...rest] against the install-mode Umzug, so umzeption install --help prints up's help and up-only flags (--to, --step, --migrations) pass through unchanged — most are nonsensical for install mode. Run install with no further flags in the normal case.

When generating migrations manually, use filenames that match umzug's own create output format: YYYY.MM.DDTHH.MM.SS.name.js — dots throughout, e.g. 2026.05.19T14.23.45.my-migration.js. Umzeption sorts migration files lexicographically, so hand-authored names must sort consistently with anything produced by umzug create.

Notes on sortFiles ordering

Umzug's create CLI command runs an allowConfusingOrdering safety check that assumes lexicographic ordering of migration filenames — it errors if the new file would sort before existing ones. If you override sortFiles with a non-lexicographic comparator, that check's verdict may not match your actual execution order. Either keep sortFiles lexicographic (just permuting equal-priority groups) or run umzug create --allow-confusing-ordering and verify ordering yourself.

Upgrading from a pre-sort version (umzeption ≤ 0.4.x): earlier releases returned migration files in whatever order globby provided — typically close to lexicographic on Linux/macOS but never guaranteed. The new default lexicographic sort can therefore reorder pending migrations on upgrade (already-applied migrations are unaffected; Umzug skips them by name regardless of candidate-list order). The narrow failure shape is unpadded numeric filenames: 1.js, 2.js, 10.js now sort as 1, 10, 2, so migration 10 would run before 2. If you have pending migrations matching that pattern, either rename to zero-padded (01.js, 02.js, 10.js) or timestamp-prefixed names, or pass sortFiles: files => files to opt out and preserve the prior filesystem-order behavior.

See also

  • umzug – the base system this module is meant to be paired with
  • plugin-importer – the plugin loader that this module uses