Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rxdi/xmigrate

Migration tool working with Mongodb Native driver and Mongoose with Rollback support and Typescript compatability
https://github.com/rxdi/xmigrate

migrations-runner mongo-migration mongo-migrator mongodb mongoose rxdi rxdi-core typescript xmigrate

Last synced: 2 days ago
JSON representation

Migration tool working with Mongodb Native driver and Mongoose with Rollback support and Typescript compatability

Awesome Lists containing this project

README

        

# @rxdi/xmigrate

[![Build Status](https://travis-ci.org/rxdi/xmigrate.svg?branch=master)](https://travis-ci.org/rxdi/xmigrate)
[![Coverage Status](https://coveralls.io/repos/github/rxdi/xmigrate/badge.svg?branch=master)](https://coveralls.io/github/rxdi/xmigrate?branch=master)

Migration library for `Mongodb` and `Mongoose` written in `TypeScript`

## Features

- Simple UI/UX
- Rollback support
- Templates for migrations
- TypeScript/JavaScript compatible
- `async`/`await` configuration loader
- Mongoose and Mongodb compatibility
- ACID transactions provided by MongoDB
- `error` and `success` logs for `up`/`down` migrations
- Infinite error log with `append` NodeJS streaming technique
- 100% TypeScript support with JIT compilation provided by [esbuild](https://esbuild.github.io/)

## Installation

Using `binary`

```bash
wget https://github.com/rxdi/xmigrate/raw/master/dist/xmigrate-linux
```

Give it permission to execute

```bash
chmod +x xmigrate-linux
```

```bash
./xmigrate up|down|create|status
```

Using `NodeJS`

```bash
npm i -g @rxdi/xmigrate
```

## Configuration

Automatic configuration

```bash
xmigrate init
```

Manual configuration

You can create a `xmigrate.js` file where you execute the `xmigrate` command:

```typescript
import { MongoClient } from 'mongodb';
import { connect } from 'mongoose';

export default async () => {
return {
changelogCollectionName: 'migrations',
migrationsDir: './migrations',
defaultTemplate: 'es6',
typescript: true,
outDir: './.xmigrate',
/* Custom datetime formatting can be applied like so */
// dateTimeFormat: () => new Date().toISOString(),
/* If you do need some better bundling of your migrations when there are tsconfig paths namespaces @shared/my-namespace
You should consider using `bundler.build()` configuration.
*/
// bundler: {
// build(entryPoints: string[], outdir: string) {
// return esbuild.build({
// entryPoints,
// bundle: true,
// sourcemap: false,
// minify: false,
// platform: 'node',
// format: 'cjs',
// outdir,
// logLevel: 'info',
// plugins: [pluginTsc()],
// })
// },
// },
logger: {
folder: './migrations-log',
up: {
success: 'up.success.log',
error: 'up.error.log',
},
down: {
success: 'down.success.log',
error: 'down.error.log',
},
},
database: {
async connect() {
const url = 'mongodb://localhost:27017';

await connect(url);
const client = await MongoClient.connect(url);
return client;
},
},
};
};
```

## Commands

#### First time run

```bash
xmigrate init
```

#### Creating migration

```bash
xmigrate create "my-migration"
```

#### Creating migration with template.

Templates to choose: `es5`, `es6`, `native`, `typescript`. By default `xmigrate create "my-migration"` executes with `es6` template

```bash
xmigrate create "my-migration" --template (es5|es6|native|typescript)
```

```bash
xmigrate create "my-migration" --template typescript
```

#### Up migrations

Will execute all migrations which have the status `PENDING`

```bash
xmigrate up
```

#### Up migrations with rollback down to current errored migration

```bash
xmigrate up --rollback
```

#### Down migrations

Will execute migrations one by one starting from the last created by timestamp

```bash
xmigrate down
```

#### Status of migrations

```bash
xmigrate status
```

Will print inside the console a table.
When there is a `PENDING` flag these migrations were not running against the current database.

```bash
๐Ÿ–ฅ๏ธ Database: test

๐Ÿ’ฟ DBCollection: migrations

๐Ÿ—„๏ธ LoggerDir: ./migrations-log

๐Ÿ“ MigrationsDir: migrations

๐Ÿ‘ท Script: xmigrate status

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ (index) โ”‚ fileName โ”‚ appliedAt โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 0 โ”‚ '20190725160010-pesho.js' โ”‚ '2019-07-25T16:07:27.012Z' โ”‚
โ”‚ 1 โ”‚ '20190725160011-pesho.js' โ”‚ 'PENDING' โ”‚
โ”‚ 2 โ”‚ '20190725160012-pesho.js' โ”‚ 'PENDING' โ”‚
โ”‚ 3 โ”‚ '20190725160013-pesho.js' โ”‚ 'PENDING' โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”ฅ There are 3 migrations with status 'PENDING', run 'xmigrate up' command!
```

## Migration templates

Native mongo driver template

```typescript
module.exports = {

async prepare(client) {
return [client]
}

async up([client]) {
await client
.db()
.collection('albums')
.updateOne({ artist: 'The Beatles' }, { $set: { blacklisted: true } });
await client
.collection('albums')
.updateOne({ artist: 'The Doors' }, { $set: { stars: 5 } });
},

async down([client]) {
await client
.db()
.collection('albums')
.updateOne({ artist: 'The Doors' }, { $set: { stars: 0 } });
await client
.collection('albums')
.updateOne({ artist: 'The Beatles' }, { $set: { blacklisted: false } });
},
};
```

`ES5` template

```typescript
module.exports = {

async prepare(client) {
return [client]
}

async up([client]) {
return ['UP'];
},

async down([client]) {
return ['DOWN'];
},
};
```

`ES6` template

```typescript
export async function prepare(client) {
return [client];
}
export async function up([client]) {
return ['Up'];
}
export async function down([client]) {
return ['Down'];
}
```

`Typescript` template

(Optional) type definitions for `mongodb` and `mongoose`

```bash
npm install @types/mongodb @types/mongoose -D
```

```typescript
import { MongoClient } from 'mongodb';

export async function prepare(client: mongoClient) {
return [client];
}

export async function up([client]: [MongoClient]) {
await client
.db()
.collection('albums')
.updateOne({ artist: 'The Beatles' }, { $set: { blacklisted: true } });

await client
.db()
.collection('albums')
.updateOne({ artist: 'The Doors' }, { $set: { stars: 5 } });
}

export async function down([client]: [MongoClient]) {
await client
.db()
.collection('albums')
.updateOne({ artist: 'The Doors' }, { $set: { stars: 0 } });

await client
.db()
.collection('albums')
.updateOne({ artist: 'The Beatles' }, { $set: { blacklisted: false } });
}
```

## TypeScript migrations (Deprecated use option `builder: 'ESBUILD' in your xmigrate config file`)

To be able to run migrations with TypeScript you need to set `typescript: true` inside `xmigrate.js` and install `@gapi/cli` globally

Install `@gapi/cli` for runtime build using `glob`

```bash
npm i -g @gapi/cli
```

Command that will be run internally

```bash
npx gapi build --glob ./1-migration.ts,./2-migration.ts
```

After success transpiled migration will be started from `./.xmigrate/1-migration.js`
Before exit script will remove `artifacts` left from transpilation located inside `./.xmigrate` folder

## Rollback

When executing command `xmigrate up --rollback` this will trigger immediate rollback to DOWN migration on the current crashed migration
The log will look something like this:

```
๐Ÿ–ฅ๏ธ Database: test

๐Ÿ’ฟ DBCollection: migrations

๐Ÿ—„๏ธ LoggerDir: ./migrations-log

๐Ÿ“ MigrationsDir: migrations

๐Ÿ‘ท Script: xmigrate up

๐Ÿ”ฅ Status: Operation executed with error
๐Ÿงจ Error: {"fileName":"20190725160011-pesho.js","migrated":[]}
๐Ÿ“จ Message: AAA

๐Ÿ™ Status: Executing rollback operation xmigrate down
๐Ÿ“ Migration: /migrations/20190725160011-pesho.js

๐Ÿš€ Rollback operation success, nothing changed if written correctly!
```

## Logs

By default logs will append streaming content for every interaction made by migration

Down migration success Log

```json
๐Ÿš€ ********* Thu Jul 25 2019 11:23:06 GMT+0300 (Eastern European Summer Time) *********

{
"fileName": "20190723165157-example.js",
"appliedAt": "2019-07-25T08:23:06.668Z",
"result": [
{
"result": "DOWN Executed"
}
]
}
```

Down migration error log

```json
๐Ÿ”ฅ ********* Thu Jul 25 2019 03:28:48 GMT+0300 (Eastern European Summer Time) *********

{
"downgraded": [],
"errorMessage": "AAA",
"fileName": "20190724235527-pesho.js"
}
```

Up migration success log

```json
๐Ÿš€ ********* Thu Jul 25 2019 11:23:24 GMT+0300 (Eastern European Summer Time) *********

{
"fileName": "20190723165157-example.js",
"appliedAt": "2019-07-25T08:23:24.642Z",
"result": [
{
"result": "UP Executed"
}
]
}
```

Up migration error log

```json
๐Ÿ”ฅ ********* Thu Jul 25 2019 03:39:00 GMT+0300 (Eastern European Summer Time) *********

{
"migrated": [],
"errorMessage": "AAA",
"fileName": "20190724235545-pesho.js"
}
```

# TypeScript configuration

When you change your configuration file to `xmigrate.ts` it will automatically transpile to `ES5` and will be loaded

```typescript
import { Config } from '@rxdi/xmigrate';
import { MongoClient } from 'mongodb';
import { connect } from 'mongoose';

export default async (): Promise => {
return {
changelogCollectionName: 'migrations',
migrationsDir: './migrations',
defaultTemplate: 'typescript',
typescript: true,
outDir: './.xmigrate',
// bundler: {
// build(entryPoints: string[], outdir: string) {
// return esbuild.build({
// entryPoints,
// bundle: true,
// sourcemap: false,
// minify: false,
// platform: 'node',
// format: 'cjs',
// outdir,
// logLevel: 'info',
// plugins: [pluginTsc()],
// });
// },
// },
logger: {
folder: './migrations-log',
up: {
success: 'up.success.log',
error: 'up.error.log',
},
down: {
success: 'down.success.log',
error: 'down.error.log',
},
},
database: {
async connect() {
const url =
process.env.MONGODB_CONNECTION_STRING ?? 'mongodb://localhost:27017';

await connect(url);
const client = await MongoClient.connect(url);
return client;
},
},
};
};
```

Everytime `xmigrate.ts` is loaded `timestamp` is checked whether or not this file is changed

This information is saved inside `.xmigrate/config.temp` like a regular `Date` object

This will ensure that we don't need to transpile configuration if it is not changed inside `xmigrate.ts` file

Basically this is caching to improve performance and user experience with TypeScript configuration

# API Usage

```typescript
import { Container, setup } from '@rxdi/core';

import {
MigrationService,
GenericRunner,
LogFactory,
ConfigService,
LoggerConfig,
Config,
} from '@rxdi/xmigrate';
import { MongoClient } from 'mongodb';
import { connect } from 'mongoose';

const config = {
changelogCollectionName: 'migrations',
migrationsDir: './migrations',
defaultTemplate: 'typescript',
typescript: true,
outDir: './.xmigrate',
logger: {
folder: './migrations-log',
up: {
success: 'up.success.log',
error: 'up.error.log',
},
down: {
success: 'down.success.log',
error: 'down.error.log',
},
},
database: {
async connect() {
const url =
process.env.MONGODB_CONNECTION_STRING ?? 'mongodb://localhost:27017';

await connect(url);
const client = await MongoClient.connect(url);
return client;
},
},
};

setup({
providers: [
GenericRunner,
LogFactory,
ConfigService,
{
provide: Config,
useValue: config,
},
{
provide: LoggerConfig,
useValue: config.logger,
},
],
}).subscribe(async () => {
const template = `
import { MongoClient } from 'mongodb';

export async function prepare(client: MongoClient) {
return [client]
}

export async function up([client]: [MongoClient]) {
return true
}

export async function down([client]: [MongoClient]) {
return true
}
`;

const migrationService = Container.get(MigrationService);

// Create migration with template
const filePath = await migrationService.createWithTemplate(
template as 'typescript',
'pesho1234',
{ raw: true, typescript: true },
);
console.log(filePath);

// Up migration
await migrationService.up();
process.exit(0);

// Down migration
await migrationService.down();
process.exit(0);

// Status
await migrationService.status();

process.exit(0);
}, console.error.bind(console));
```