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 🙏

© 2024 – Pkg Stats / Ryan Hefner

sql-migrations-core

v0.0.3

Published

Database framework-agnostic solution for migrations with zero dependencies

Downloads

10

Readme

[WIP] SQL Migrations Core

npm node npm

Database framework-agnostic package for DIY migrations solutions with zero dependencies, written in TypeScript.

With sql-migrations-core you can write sql migrations as you want. Use ANY database framework and prepare CLI as you feel comfortable.

Table of Contents

Installation

Install with npm:

npm i sql-migrations-core

Or with yarn:

yarn add sql-migrations-core

...Or with pnpm:

pnpm add sql-migrations-core

Initialization

Minimal configuration looks like this:

const migrationsCore = MigrationsCore.create({
  path: path.join('..', 'path/to/migrations/dir'),
  sqlActions: {
    async createMigrationTable() {
      // TODO implement
    },
    async getMigrationsNames() {
      // TODO implement
      return [];
    },
    async migrateDown(migrations) {
      // TODO implement
    },
    async migrateUp(migrations) {
      // TODO implement
    },
  },
});

There is a trade-off - you can use any db framework, but you need to implement SQL queries by yourself. For example, with kysely:

import { db } from './database';

// Probably you'll want to move SQL logic to repository.
// In this example I'm just showing a minimal SQL implementation.

const migrationsCore = MigrationsCore.create({
  path: path.join('/migrations'),
  sqlActions: {
    async createMigrationTable() {
      await db.schema.createTable('__migrations')
        .ifNotExists()
        .addColumn('name', 'varchar', (cb) => cb.notNull().unique())
        .addColumn('migrated_at', 'timestamp', (cb) =>
          cb.notNull().defaultTo(sql`now()`)
        )
        .execute()
    },

    async getMigrationsNames() {
      const records = await db.selectFrom('__migrations')
        .select('name')
        .execute();
      return records.map(r => r.name);
    },

    async migrateDown(migrations) {
      await db.transaction().execute(async (trx) => {
        for (const migration of migrations) {
          await trx.deleteFrom('__migrations')
            .where({ name: migration.name })
            .execute();
          await sql`${migration.sql}`.execute(trx);
        }
      })
    },

    async migrateUp(migrations) {
      await db.transaction().execute(async (trx) => {
        for (const migration of migrations) {
          await trx.insertInto('__migrations')
            .values({ name: migration.name })
            .execute();
          await sql`${migration.sql}`.execute(trx);
        }
      })
    },
  },
});

See other examples here.

Usage

After initializing migrationsCore you can use methods:

await migrationsCore.createFiles('example'); // creates blank sql files

await migrationsCore.up(); // one migration up
await migrationsCore.down(); // one migration down
await migrationsCore.sync(); // all pending up migrations
await migrationsCore.drop(); // all down migrations of executed migrations
await migrationsCore.toLatest(); // all pending up migrations from last executed
await migrationsCore.to('123-example'); // all up/down migrations between last executed and provided

await migrationsCore.status(); // get statuses of migrations (name and is synced)

Also, you can pass chunkSize in some methods to run migrations by chunks in synchronous way:

await migrationsCore.sync(100); // runs migrations in chunks limit by 100 

See API for more info.

Migration files

Migrations core creates 2 migration files - up and down. It uses {timestamp}-${title}${postfix} format (name of migration is {timestamp}-{title}):

await migrationsCore.createFile('example');

// Will create something like this
// ./migrations/1718394484921-example.down.sql
// ./migrations/1718394484921-example.up.sql

CLI

There is no out-of-box CLI solution, so you need implement one by yourself. For example, create migration file:

import { migrationsCore } from './migrations-core';
import path from 'node:path';

const migrationsPath = path.join('./migrations');
migrationsCore.createFile(process.args[2]);

Usage:

node ./scripts/migrations-create.js example

See other CLI examples here.

API

There are several classes available to use:

  1. MigrationsCore - main class that gives access to all needed migration actions.
  2. LocalMigrations - class used by MigrationsCore. Hides all filesystem operations to access local migrations. For advanced usage.
  3. StoredMigrations - class used by MigrationsCore. Hides all database operations to run migrations and access metadata of executed migrations. For advanced usage.

Creating migrationsCore object

There are two ways to create instance of MigrationsCore:

  1. Using static MigrationsCore.create() method
  2. Using new MigrationsCore() constructor

Using create(config)

create() method receives config object and returns instance of MigrationsCore. Actually it's constructor wrapper, which creates LocalMigrations and StoredMigrations by itself. Config options are passed to these objects. Config options:

  • path - string, path to migrations directory. Used by LocalMigrations.
  • postfix - object, optional, custom postfix for migrations files. Used by LocalMigrations.
  • postfix.up - string, postfix for up migrations (default: .up.sql).
  • postfix.down - string, postfix for down migrations (default: .down.sql).
  • sqlActions - object, postfix for down migrations (default: .down.sql). Used by StoredMigrations.
  • sqlActions.createMigrationTable - function, used to create table before every StoredMigrations action. Recommended to use with IF NOT EXISTS statement.
  • sqlActions.migrateUp - function, used to run up migrations. Receives array of migration objects.
  • sqlActions.migrateDown - function, used to run down migrations. Receives array of migration objects.
  • sqlActions.getMigrationsNames - function, used to get executed migrations names. Should return array of migration names.
  • sqlActions.getLastMigrationName - function, optional, used to get last executed migrations name. Should return name or null. If function not provided - getMigrationsNames used instead.

Example:

const migrationsCore = MigrationsCore.create({
  path: path.join(__dirname, '../migrations'),
  postfix: {
    up: '.custom.up.sql',
    down: '.custom.down.sql',
  },
  sqlActions: {
    createMigrationTable() { /* ... */ },
    migrateUp(migrations) { /* ... */ },
    migrateDown(migrations) { /* ... */ },
    getMigrationsNames() { /* ... */ },
    getLastMigrationName() { /* ... */ },
  }
});

Using new MigrationsCore()

For options refer to Using create() method section.

Example:

const localMigrations = new LocalMigrations({
  dirPath: path.join(__dirname, '../migrations'),
  postfix: {
    up: '.up.sql',
    down: '.down.sql',
  },
});

const storedMigrations = new StoredMigrations({
  sqlActions: {
    createMigrationTable() { /* ... */ },
    migrateUp(migrations) { /* ... */ },
    migrateDown(migrations) { /* ... */ },
    getMigrationsNames() { /* ... */ },
    getLastMigrationName() { /* ... */ },
  }
});

const migrationsCore = new MigrationsCore(localMigrations, storedMigrations);

Methods of migrationsCore object

See migrations-core.interface.ts

Methods of localMigrations object

See local-migrations.interface.ts

Methods of storedMigrations object

See stored-migrations.interface.ts