• Stars
    star
    254
  • Rank 154,471 (Top 4 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 4 years ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

(WIP) Driver-agnostic database migrations
ley
Driver agnostic database migrations

TODO

WIP: What's here is the end of Night #1

Features

  • Agnostic
    Supports postgres, pg, better-sqlite3, sqlite, mysql, mysql2, and custom drivers!

  • Lightweight
    Does not include any driver dependencies.

  • Transactional
    Runs all migration files within a transaction for rollback safety.

  • Familiar
    Does not invent new syntax or abstractions.
    You're always working directly with your driver of choice.

  • Flexible
    Find the CLI to restrictive? You may require ley for your own scripting!

Install

$ npm install --save-dev ley

Usage

Both Programmatic and CLI usages are supported.

Setup

You must have a migrations directory created, preferably in your project's root.

Note: You may configure the target directory and location.

Your filenames within this directory determine the order of their execution.
Because of this, it's often recommended to prefix migrations with a timestamp or numerical sequence.

Numerical Sequence

/migrations
  |-- 000-users.js
  |-- 001-teams.js
  |-- 002-seats.js

Note: You may create the next file via ley new todos --length 3 where todos is a meaningful name.
The above command will create the migrations/003-todos.js filepath.

Timestamped

/migrations
  |-- 1581323445-users.js
  |-- 1581323453-teams.js
  |-- 1581323458-seats.js

Note: You may create the next file via ley new todos --timestamp where todos is a meaningful name.
The above command will create the migrations/1584389617-todos.js filepath...or similar.

The order of your migrations is critically important!
Migrations must be treated as an append-only immutable task chain. Without this, there's no way to reliably rollback or recreate your database.

Example: (Above) You cannot apply/create 001-teams.js after 002-seats.js has already been applied.
Doing so would force your teammates or database replicas to recreate "the world" in the wrong sequence.
This may not always pose a problem (eg, unrelated tasks) but it often does and so ley enforces this practice.

Lastly, each migration file must have an up and a down task.
These must be exported functions β€” async okay! β€” and will receive your pre-installed client driver as its only argument:

exports.up = async function (DB) {
  // with `pg` :: DB === pg.Client
  await DB.query(`select * from users`);

  // with `postgres` :: DB === sql``
  await DB`select * from users`;
}

exports.down = async function (DB) {
  // My pre-configured "undo" function
}

CLI

  1. Add ley as one of your package.json scripts; "migrate", for example:

    // package.json
    {
      "scripts": {
        "migrate": "ley"
      }
    }
  2. Invoke ley up to apply new migrations, or ley down to rollback previous migrations.

    $ npm run migrate up
    $ yarn migrate up

    ley up screenshot #1
    ley up screenshot #2
    ley up screenshot #3

Programmatic

Note: See API for documentation

With programmatic/scripting usage, you will not inherit any of ley's CLI tooling, which includes all colors and error formatting. Instead, you must manually catch & handle all thrown Errors.

const ley = require('ley');

const successes = await ley.up({ ... });

Config

TL;DR: The contents of a ley.config.js file (default file name) is irrelevant to ley itself!

A config file is entirely optional since ley assumes that you're providing the correct environment variable(s) for your client driver. However, that may not always be possible. In those instances, a ley.config.js file (default file name) can be used to adjust your driver's connect method – the file contents are passed directly to this function.

For example, if your hosting provider sets non-standard environment variables for the client driver (like Heroku does), you could extract the information and set the standard environment variables:

// ley.config.js
if (process.env.DATABASE_URL) {
  const { parse } = require('pg-connection-string');

  // Extract the connection information from the Heroku environment variable
  const { host, database, user, password } = parse(process.env.DATABASE_URL);

  // Set standard environment variables
  process.env.PGHOST = host;
  process.env.PGDATABASE = database;
  process.env.PGUSERNAME = user;
  process.env.PGPASSWORD = password;
}

Or, if your database provider requires certain SSL connection options to be set in production, you could do that:

// ley.config.js
const options = {};

if (process.env.NODE_ENV === 'production') {
  options.ssl = true;
}

module.exports = options;

When the config filename uses the .js extension, then ley will attempt to auto-load a .mjs or a .cjs variant of the file if/when the original .js file was not found. This means that, by default, these files are searched (in order):

  • ley.config.js
  • ley.config.mjs
  • ley.config.cjs

ES Modules

As of [email protected] and Node.js 12+, you may choose to use ECMAScript modules (ESM). There are a few ways to take advantage of this:

Note: These are separate options. You do not need to perform both items

  1. Define "type": "module" in your root package.json file.
    This signals the Node.js runtime that all *.js files in the project should be treated as ES modules. With this setting, you may only use CommonJS format within .cjs files.

    // package.json
    {
      "type": "module",
      "scripts": {
        "migrate": "ley"
      }
    }
  2. Author ES modules only in .mjs files.
    Regardless of the value of the "type" field (above), .mjs files are always treated as ES modules and .cjs files are always treated as CommonJS.

In terms of ley usage, this means that your config file may use ESM syntax. Similarly, by default, both ley.config.mjs and ley.config.cjs will be auto-loaded, if found and ley.config.js is missing.

// ley.config.mjs
// or w/ "type": "module" ~> ley.config.js
export default {
	host: 'localhost',
	port: 5432,
	// ...
}

Finally, migration files may also be written using ESM syntax:

// migrations/000-example.mjs
// or w/ "type": "module" ~> migrations/000-example.js
export async function up(DB) {
  // with `pg` :: DB === pg.Client
  await DB.query(`select * from users`);

  // with `postgres` :: DB === sql``
  await DB`select * from users`;
}

export async function down(DB) {
  // My pre-configured "undo" function
}

You may generate new migration files in ESM syntax by passing the --esm flag to the ley new command:

$ ley new todos --esm
#=> migrations/003-todos.mjs

$ cat migrations/003-todos.mjs
#=> export async function up(client) {
#=> }
#=> 
#=> export async function down(client) {
#=> }

Drivers

Out of the box, ley includes drivers for the following npm packages:

When no driver is specified, ley will attempt to autodetect usage of these libraries in the above order.

However, should you need a driver that's not listed – or should you need to override a supplied driver – you may easily do so via a number of avenues:

  1. CLI users can add --driver <filename> to any command; or
  2. Programmatic users can pass opts.driver to any command; or
  3. A ley.config.js file can export a special driver config key.

With any of these, if driver is a string then it will be passed through require() automatically. Otherwise, with the latter two, the driver is assumed to be a Driver class and is validated as such.

Important: All drivers must adhere to the Driver interface!

Typed Migrations

For extra confidence while writing your migration file(s), there are two options:

TypeScript

  1. Ensure tsm is installed

  2. Run ley with the require option so that tsm can process file(s)

    $ ley -r tsm <cmd>
    # or
    $ ley --require tsm <cmd>

JSDoc

You may also use JSDoc annotations throughout your file to achieve (most) of the benefits of TypeScript, but without installing and configuring TypeScript.

/** @param {import('pg').Client} DB */
exports.up = async function (DB) {
  await DB.query(...)
}

API

Important: See Options for common options shared all commands.
In this API section, you will only find command-specific options listed.

ley.up(opts?)

Returns: Promise<string[]>

Returns a list of the relative filenames (eg, 000-users.js) that were successfully applied.

opts.single

Type: boolean
Default: false

Enable to apply only one migration file's up task.
By default, all migration files will be queue for application.

ley.down(opts?)

Returns: Promise<string[]>

Returns a list of the relative filenames (eg, 000-users.js) that were successfully applied.

opts.all

Type: boolean
Default: false

Enable to apply all migration files' down task.
By default, only the most recently-applied migration file is invoked.

ley.status(opts?)

Returns: Promise<string[]>

Returns a list of the relative filenames (eg, 000-users.js) that have not yet been applied.

ley.new(opts?)

Returns: Promise<string>

Returns the newly created relative filename (eg, 000-users.js).

opts.filename

Type: string

Required. The name of the file to be created.

Note: A prefix will be prepended based on opts.timestamp and opts.length values.
If your input does not already end with an extension, then .js or .mjs will be appended.

opts.esm

Type: boolean
Default: false

Create a migration file with ESM syntax.

Note: When true, the opts.filename will contain the .mjs file extension unless your input already has an extension.

opts.timestamp

Type: boolean
Default: false

Should the migration file have a timestamped prefix?
If so, will use Date.now() floored to the nearest second.

opts.length

Type: number
Default: 5

When not using a timestamped prefix, this value controls the prefix total length.
For example, 00000-users.js will be followed by 00001-teams.js.

Options

Note: These are available to all ley commands.
See API for programmatic command documentation.

opts.cwd

Type: string
Default: .

A target location to treat as the current working directory.

Note: This value is path.resolve()d from the current process.cwd() location.

opts.dir

Type: string
Default: migrations

The directory (relative to opts.cwd) to find migration files.

opts.driver

Type: string or Driver
Default: undefined

When defined and a string, this can be (a) the name of an internal driver, (b) the name of a third-party driver module, or (c) a filepath to a local driver implementation. It will pass through require() as written.

When defined an not a string, it's expected to match the Driver interface and will be validated immediately.

When undefined, ley searches for all supported client drivers in this order:

['postgres', 'pg', 'mysql', 'mysql2', 'better-sqlite3']

opts.config

Type: object
Default: undefined

A configuration object for your client driver to establish a connection.
When unspecified, ley assumes that your client driver is able to connect through process.env variables.

Note: The ley CLI will search for a ley.config.js config file (configurable).
If found, this file may contain an object or a function that resolves to anything of your chosing.
Please see Config for more information.

opts.require

Type: string or string[]
Default: undefined

A module name (or list of names) to be required by ley at startup.

For example, you may want to use dotenv to load existing .env file(s) in your project:

const ley = require('ley');

const files = await ley.status({
  require: ['dotenv/config']
});

Through CLI usage, this is equivalent to:

$ ley -r dotenv/config status
# or
$ ley --require dotenv/config status

License

MIT Β© Luke Edwards

More Repositories

1

clsx

A tiny (239B) utility for constructing `className` strings conditionally.
JavaScript
7,354
star
2

polka

A micro web server so fast, it'll make you dance! πŸ‘―
JavaScript
5,266
star
3

pwa

(WIP) Universal PWA Builder
JavaScript
3,123
star
4

uvu

uvu is an extremely fast and lightweight test runner for Node.js and the browser
JavaScript
2,938
star
5

taskr

A fast, concurrency-focused task automation tool.
JavaScript
2,528
star
6

sockette

The cutest little WebSocket wrapper! 🧦
JavaScript
2,398
star
7

worktop

The next generation web framework for Cloudflare Workers
TypeScript
1,635
star
8

kleur

The fastest Node.js library for formatting terminal text with ANSI colors~!
JavaScript
1,593
star
9

klona

A tiny (240B to 501B) and fast utility to "deep clone" Objects, Arrays, Dates, RegExps, and more!
JavaScript
1,537
star
10

dequal

A tiny (304B to 489B) utility to check for deep equality
JavaScript
1,271
star
11

tsm

TypeScript Module Loader
TypeScript
1,164
star
12

tinydate

A tiny (349B) reusable date formatter. Extremely fast!
JavaScript
1,060
star
13

sirv

An optimized middleware & CLI application for serving static files~!
JavaScript
1,023
star
14

sade

Smooth (CLI) Operator 🎢
JavaScript
1,009
star
15

rosetta

A general purpose internationalization library in 292 bytes
JavaScript
766
star
16

navaid

A navigation aid (aka, router) for the browser in 850 bytes~!
JavaScript
755
star
17

dset

A tiny (194B) utility for safely writing deep Object values~!
JavaScript
739
star
18

uid

A tiny (130B to 205B) and fast utility to generate random IDs of fixed length
JavaScript
634
star
19

httpie

A Node.js HTTP client as easy as pie! πŸ₯§
JavaScript
575
star
20

ganalytics

A tiny (312B) client-side module for tracking with Google Analytics
JavaScript
575
star
21

trouter

🐟 A fast, small-but-mighty, familiar fish...errr, router*
JavaScript
563
star
22

regexparam

A tiny (394B) utility that converts route patterns into RegExp. Limited alternative to `path-to-regexp` πŸ™‡β€β™‚οΈ
JavaScript
555
star
23

dimport

Run ES Module syntax (`import`, `import()`, and `export`) in any browser – even IE!
JavaScript
547
star
24

mri

Quickly scan for CLI flags and arguments
JavaScript
533
star
25

tempura

A light, crispy, and delicious template engine 🍀
JavaScript
514
star
26

calendarize

A tiny (202B) utility to generate calendar views.
JavaScript
471
star
27

formee

A tiny (532B) library for handling <form> elements.
JavaScript
443
star
28

qss

A tiny (294b) browser utility for encoding & decoding a querystring.
JavaScript
408
star
29

preact-starter

Webpack3 boilerplate for building SPA / PWA / offline front-end apps with Preact
JavaScript
387
star
30

uuid

A tiny (~230B)and fast UUID (V4) generator for Node and the browser
JavaScript
386
star
31

vegemite

A Pub/Sub state manager you'll love... or hate
JavaScript
373
star
32

resolve.exports

A tiny (952b), correct, general-purpose, and configurable `"exports"` and `"imports"` resolver without file-system reliance
TypeScript
351
star
33

polkadot

The tiny HTTP server that gets out of your way! ・
JavaScript
322
star
34

matchit

Quickly parse & match URLs
JavaScript
321
star
35

fetch-event-stream

A tiny (736b) utility for Server Sent Event (SSE) streaming via `fetch` and Web Streams API
TypeScript
321
star
36

flru

A tiny (215B) and fast Least Recently Used (LRU) cache
JavaScript
310
star
37

mrmime

A tiny (2.8kB) and fast utility for getting a MIME type from an extension or filename
TypeScript
300
star
38

watchlist

Recursively watch a list of directories & run a command on any file system changes
JavaScript
261
star
39

arr

A collection of tiny, highly performant Array.prototype alternatives
JavaScript
255
star
40

flattie

A tiny (203B) and fast utility to flatten an object with customizable glue
JavaScript
254
star
41

webpack-messages

Beautifully format Webpack messages throughout your bundle lifecycle(s)!
JavaScript
246
star
42

obj-str

A tiny (96B) library for serializing Object values to Strings.
JavaScript
225
star
43

templite

Lightweight templating in 150 bytes
JavaScript
221
star
44

ms

A tiny (414B) and fast utility to convert milliseconds to and from strings.
JavaScript
200
star
45

nestie

A tiny (215B) and fast utility to expand a flattened object
JavaScript
199
star
46

throttles

A tiny (139B to 204B) utility to regulate the execution rate of your functions
JavaScript
195
star
47

hexoid

A tiny (190B) and extremely fast utility to generate random IDs of fixed length
JavaScript
185
star
48

fromnow

A tiny (339B) utility for human-readable time differences between now and past or future dates.
JavaScript
178
star
49

astray

Walk an AST without being led astray
JavaScript
177
star
50

tmp-cache

A least-recently-used cache in 35 lines of code~!
JavaScript
174
star
51

bundt

A simple bundler for your delicious modules
JavaScript
167
star
52

wrr

A tiny (148B) weighted round robin utility
JavaScript
164
star
53

freshie

(WIP) A fresh take on building universal applications with support for pluggable frontends and backends.
TypeScript
153
star
54

svelte-ssr-worker

A quick demo for rendering Svelte server-side (SSR), but within a Cloudflare Worker!
JavaScript
152
star
55

totalist

A tiny (195B to 224B) utility to recursively list all (total) files in a directory
JavaScript
148
star
56

typescript-module

Template repository for authoring npm modules via TypeScript
TypeScript
141
star
57

sublet

Reactive leases for data subscriptions
JavaScript
139
star
58

escalade

A tiny (183B to 210B) and fast utility to ascend parent directories
JavaScript
138
star
59

webpack-route-manifest

Generate an asset manifest file, keyed by route patterns!
JavaScript
127
star
60

url-shim

A 1.5kB browser polyfill for the Node.js `URL` and `URLSearchParams` classes.
JavaScript
123
star
61

semiver

A tiny (153B) utility to compare semver strings.
JavaScript
119
star
62

cfw

(WIP) A build and deploy utility for Cloudflare Workers.
TypeScript
113
star
63

svelte-demo

Multi-page demo built Svelte 3.x and Rollup with code-splitting
Svelte
113
star
64

webpack-format-messages

Beautiful formatting for Webpack messages; ported from Create React App!
JavaScript
111
star
65

gittar

🎸 Download and/or Extract git repositories (GitHub, GitLab, BitBucket). Cross-platform and Offline-first!
JavaScript
111
star
66

saturated

A tiny (203B) utility to enqueue items for batch processing and/or satisfying rate limits.
JavaScript
110
star
67

webpack-critical

Extracts & inlines Critical CSS with Wepack
JavaScript
109
star
68

pages-fullstack

Demo SvelteKit application running on Cloudflare Pages
Svelte
102
star
69

sort-isostring

A tiny (110B) and fast utility to sort ISO 8601 Date strings
JavaScript
98
star
70

colors-app

🎨 A PWA for copying values from popular color palettes. Supports HEX, RGB, and HSL formats.
JavaScript
95
star
71

salteen

A snappy and lightweight (259B) utility to encrypt and decrypt values with salt.
JavaScript
95
star
72

is-offline

A tiny (174B) library to detect `offline` status & respond to changes in the browser.
JavaScript
91
star
73

seolint

(WIP) A robust and configurable SEO linter
TypeScript
86
star
74

rafps

A tiny (178B) helper for playing, pausing, and setting `requestAnimationFrame` frame rates
JavaScript
82
star
75

preact-cli-ssr

A quick demo for adding SSR to a Preact CLI app
JavaScript
79
star
76

webpack-modules

Handle `.mjs` files correctly within webpack
JavaScript
71
star
77

csprng

A tiny (~90B) isomorphic wrapper for `crypto.randomBytes` in Node.js and browsers.
JavaScript
66
star
78

classico

A tiny (255B) shim when Element.classList cannot be used~!
JavaScript
62
star
79

premove

A tiny (201B to 247B) utility to remove items recursively
JavaScript
62
star
80

mk-dirs

A tiny (381B to 419B) utility to make a directory and its parents, recursively
JavaScript
54
star
81

primeval

A tiny (128B) utility to check if a value is a prime number
JavaScript
51
star
82

loadr

Quickly attach multiple ESM Loaders and/or Require Hooks together but without the repetitive `--experimental-loader` and/or `--require` Node flags
JavaScript
49
star
83

preact-progress

Simple and lightweight (~590 bytes gzip) progress bar component for Preact
JavaScript
49
star
84

route-manifest

A tiny (412B) runtime to retrieve the correct entry from a Route Manifest file.
JavaScript
46
star
85

svelte-preprocess-esbuild

A Svelte Preprocessor to compile TypeScript via esbuild!
TypeScript
44
star
86

preact-scroll-header

A (800b gzip) header that will show/hide while scrolling for Preact
JavaScript
41
star
87

inferno-starter

Webpack2 boilerplate for building SPA / PWA / offline front-end apps with Inferno.js
JavaScript
41
star
88

rollup-route-manifest

A Rollup plugin to generate an asset manifest, keyed by route patterns ("route manifest")
JavaScript
40
star
89

onloaded

A tiny (350B) library to detect when images have loaded.
JavaScript
38
star
90

webpack-plugin-replace

Replace content while bundling.
JavaScript
36
star
91

route-sort

A tiny (200B) utility to sort route patterns by specificity
JavaScript
35
star
92

scorta

A tiny (330B to 357B) and fast utility to find a package's hidden supply / cache directory.
JavaScript
33
star
93

local-hostname

A tiny (171B) utility to check if a hostname is local
JavaScript
32
star
94

taskr-outdated

A generator & coroutine-based task runner. Fasten your seatbelt. πŸš€
JavaScript
32
star
95

rewrite-imports

Rewrite `import` statements as `require()`s; via RegExp
JavaScript
31
star
96

preact-offline

A (300b gzip) component to render alerts/messages when offline.
JavaScript
29
star
97

fly-kit-preact

A starter kit for building offline / SPA / PWA apps with Preact
JavaScript
28
star
98

fannypack

The tool belt for front-end developers
JavaScript
28
star
99

is-ready

A tiny (309B) library to detect when `window` globals are defined and ready to use~!
JavaScript
28
star
100

ava-http

Async HTTP request wrapper πŸš€
JavaScript
25
star