Last active
April 30, 2021 00:00
-
-
Save bitflower/aa06462e6d70a9192949e6f601a38948 to your computer and use it in GitHub Desktop.
MongoDB migrations with umzug in a FeathersJS application
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Service } from '@feathersjs/feathers'; | |
import { App } from '../src/app.interface'; | |
import { Transformation } from '../src/services/transformations/transformations.interface'; | |
export default { | |
up: async (app: App): Promise<boolean> => { | |
// Get service | |
const s: Service<Transformation> = app.service('transformations'); | |
// Read all content atoms that have properties | |
const newTransformations: Transformation[] = [ | |
{ | |
category: 'conditionals', | |
functionCode: 'compareSwitch', | |
name: 'compareSwitch', | |
returnType: 'Mixed', | |
args: [ | |
{ | |
name: 'value1', | |
propertyType: 'Mixed', | |
required: true | |
}, | |
{ | |
name: 'value2', | |
propertyType: 'Mixed', | |
required: true | |
}, | |
{ | |
name: 'eqValue', | |
propertyType: 'Mixed', | |
required: true | |
}, | |
{ | |
name: 'ltValue', | |
propertyType: 'Mixed', | |
required: true | |
}, | |
{ | |
name: 'gtValue', | |
propertyType: 'Mixed', | |
required: true | |
} | |
] | |
} as any // remove for type checking while developing | |
]; | |
for (const transformation of newTransformations) { | |
console.log(`Creating transformation ${transformation.name}`); | |
await s.create(transformation); | |
} | |
return true; | |
}, | |
down: (app: App) => { | |
// a | |
} | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Bluebird = require('bluebird'); | |
Bluebird.config({ longStackTraces: true, warnings: true }); | |
global.Promise = Bluebird; | |
import App from '../index'; // '../app'; | |
import program from 'commander'; | |
import Umzug, { Migration } from 'umzug'; | |
import { Mongoose, Connection } from 'mongoose'; | |
import path from 'path'; | |
import { Application } from '@feathersjs/express'; | |
import { inspect } from 'util'; | |
// import leakedHandles from "leaked-handles"; | |
// leakedHandles.set({ | |
// fullStack: true, // use full stack traces | |
// timeout: 30000, // run every 30 seconds instead of 5. | |
// debugSockets: true // pretty print tcp thrown exceptions. | |
// }); | |
function getMigrationName(migration: Migration) { | |
return path.basename(migration.path, '.ts'); | |
} | |
async function getStatus(umzug: Umzug.Umzug) { | |
const executed = (await umzug.executed()).map(getMigrationName); | |
const pending = (await umzug.pending()).map(getMigrationName); | |
const current = executed.length > 0 ? executed[0] : '<NO_MIGRATIONS>'; | |
return { current, executed, pending }; | |
} | |
async function init() { | |
const app = App(); | |
const mongoose = app.get('mongooseClient') as Mongoose; | |
await waitConnected(mongoose.connection); | |
const umzug = createUmzug(app, mongoose); | |
return { app, mongoose, umzug }; | |
} | |
function createUmzug(app: Application<any>, mongoose: Mongoose) { | |
const umzug = new Umzug({ | |
migrations: { | |
params: [app], | |
pattern: /\.ts$/ | |
}, | |
storage: 'mongodb', | |
storageOptions: { | |
connection: mongoose.connection, | |
collectionName: '_migrations' | |
}, | |
logging(...args: any[]) { | |
console.log(...args); | |
} | |
}); | |
function logUmzugEvent(eventName: string) { | |
return function(name: string, migration: Umzug.Migration) { | |
console.log(`${name} ${eventName}`); | |
}; | |
} | |
umzug.on('migrating', logUmzugEvent('migrating')); | |
umzug.on('migrated', logUmzugEvent('migrated')); | |
umzug.on('reverting', logUmzugEvent('reverting')); | |
umzug.on('reverted', logUmzugEvent('reverted')); | |
return umzug; | |
} | |
function handleError(reason: any, p: any) { | |
// tslint:disable-next-line:no-console | |
console.error( | |
'Unhandled Rejection at:', | |
p, | |
'reason:', | |
inspect(reason, { showHidden: false, depth: 6 }) | |
); | |
process.exit(1); | |
} | |
function waitConnected(connection: Connection) { | |
return new Promise((resolve, reject) => { | |
setTimeout( | |
() => | |
reject( | |
new Error('Mongo connection took too long, please check for errors.') | |
), | |
5000 | |
); | |
connection.on('connected', () => resolve()); | |
}); | |
} | |
const errorEvents: any[] = [ | |
'unhandledRejection', | |
'uncaughtException', | |
'SIGINT' | |
]; | |
errorEvents.map(eventName => process.on(eventName, handleError)); | |
program | |
.command('up') | |
.description('Migrate the database to the last version.') | |
.action(async () => { | |
const { mongoose, umzug } = await init(); | |
const appliedMigrations = await umzug.up(); | |
if (appliedMigrations.length === 0) { | |
console.log('No migrations were applied.'); | |
} | |
await mongoose.disconnect(); | |
}); | |
try { | |
program | |
.command('reset') | |
.description('Reverts the database to the initial state') | |
.action(async () => { | |
const { umzug, mongoose } = await init(); | |
await umzug.down({ to: 0 }); | |
await mongoose.disconnect(); | |
}); | |
} catch (e) { | |
console.error(e); | |
process.exit(1); | |
} | |
program | |
.command('status') | |
.description('Shows which migrations were applied and which not') | |
.action(async () => { | |
const { umzug, mongoose } = await init(); | |
const status = await getStatus(umzug); | |
console.log(JSON.stringify(status, null, 2)); | |
await mongoose.disconnect(); | |
}); | |
program | |
.command('prev') | |
.description('Goes back to the previous version of the database') | |
.action(async () => { | |
const { umzug, mongoose } = await init(); | |
const executed = await umzug.executed(); | |
if (executed.length === 0) { | |
throw new Error('Already at initial state'); | |
} | |
const prev = getMigrationName(executed[executed.length - 1]); | |
console.log('prev >>>', prev); | |
await umzug.down({ to: prev }); | |
await mongoose.disconnect(); | |
}); | |
// error on unknown commands | |
program.on('command:*', function() { | |
console.error( | |
'Invalid command: %s\nSee --help for a list of available commands.', | |
program.args.join(' ') | |
); | |
process.exit(1); | |
}); | |
program.parse(process.argv); | |
if (program.args.length === 0) { | |
program.help(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you so much for your example. I am working on this very problem. I do have a small question ...
Your example refers to a service:
app.service('transformations')
However, your code does not include the code for this service. Can you provide the code for this? Or do you have more information that might aid me in following after your approach. Thanks!