@jitterbit/npdynamodb
Version:
A Node.js Simple Query Builder and ORM for AWS DynamoDB.
249 lines (209 loc) • 6.87 kB
JavaScript
;
var _ = require('lodash');
var Promise = require('bluebird');
var path = require('path');
var glob = require('glob');
var fs = require('fs');
var SchemaBuilder = require('../schema/builder');
var utils = require('../utils');
var npdynamodb = require('../npdynamodb');
var migration_suffixes = ['.js', '.cjs'];
function create(npd){
return function(){
return new Migrator(npd);
};
}
function Migrator(npd){
this.npd = npd;
}
Migrator.prototype.createTable = function(tableName, callback){
var query = this.npd();
var builder = new SchemaBuilder({
apiVer: query.apiVersion,
tableName: tableName
});
callback(builder);
var params = builder.buildCreateTable();
return query.table(tableName).createTable(params).then(function(){
return this.waitForTableExists(tableName);
}.bind(this));
};
Migrator.prototype.updateTable = function(tableName, callback) {
var query = this.npd();
var builder = new SchemaBuilder({
apiVer: query.apiVersion,
tableName: tableName,
IndexType: SchemaBuilder.Schema.IndexType.GSIU,
withoutDefaultTableInfo: true
});
callback(builder);
var params = builder.buildUpdateTable();
return query.table(tableName).rawClient().updateTable(params);
};
Migrator.prototype.deleteTable = function(tableName) {
return this.npd().table(tableName).deleteTable().then(function(){
return this.waitForTableNotExists(tableName);
}.bind(this));
};
Migrator.prototype.enableTTL = function(tableName, attributeName) {
return this.npd().rawClient().updateTimeToLive({
TableName: tableName,
TimeToLiveSpecification: {
AttributeName: attributeName,
Enabled: true,
},
});
};
Migrator.prototype.disableTTL = function(tableName, attributeName) {
return this.npd().rawClient().updateTimeToLive({
TableName: tableName,
TimeToLiveSpecification: {
AttributeName: attributeName,
Enabled: false,
},
});
};
Migrator.prototype.waitUntilTableActivate = function(tableName, timeoutms){
var self = this;
timeoutms = timeoutms || 10000;
return new Promise(function(resolve, reject){
var timer = setTimeout(function(){
reject(new Error("Operations is timed out."));
}, timeoutms);
function retry() {
self.npd().table(tableName).describe().then(function(result){
const deactiveCounts = (result.Table.GlobalSecondaryIndexes || []).filter(function(index){
return index.IndexStatus !== 'ACTIVE';
}).length;
if(result.Table.TableStatus === 'ACTIVE' && deactiveCounts === 0){
clearTimeout(timer);
timer = null;
resolve(result);
} else {
setTimeout(retry, 1000);
}
}).catch(function(error){
reject(error);
});
}
setTimeout(retry, 1000);
});
};
Migrator.prototype.waitForTableExists = function(tableName) {
return this.npd().rawClient().waitFor('tableExists', {
TableName: tableName
});
};
Migrator.prototype.waitForTableNotExists = function(tableName) {
return this.npd().rawClient().waitFor('tableNotExists', {
TableName: tableName
});
};
function MigrateRunner(config){
this.config = config;
var npd = npdynamodb(config.dynamoClient, config.options);
this.npd = npd;
this.migrator = create(npd);
}
MigrateRunner.prototype._createMigrateTableIfNotExists = function() {
var self = this;
var tableName = this.config.migrations.tableName;
return new Promise(function(resolve, reject) {
self.npd().table(tableName).describe().then(function(_) {
resolve();
}).catch(function (err) {
if (!err) {
reject();
} else if (err.code === 'ResourceInUseException') {
resolve();
} else if (err.code === 'ResourceNotFoundException') {
self.migrator().createTable(tableName, function(t) {
t.number('version').hashKey();
t.provisionedThroughput.apply(t, self.config.migrations.ProvisionedThroughput);
})
.then(function() {
self.npd().rawClient().waitFor('tableExists', {TableName: tableName}).then(resolve).catch(reject);
}).catch(reject);
} else {
reject();
}
});
});
};
MigrateRunner.prototype.run = function(){
var self = this;
var tableName = this.config.migrations.tableName;
return this._createMigrateTableIfNotExists().then(function(){
return self.npd().table(tableName).all().then(function(data){
var dirs = fs.readdirSync(self.config.cwd);
var versions = _.sortBy(_.map(data.Items, function(data){
return data.version;
}));
var incompletePaths = dirs.filter(function(dir){
var version = dir.split('_')[0];
if (version == parseInt(version)
&& _.some(migration_suffixes, function(suffix){
return (dir.indexOf(suffix, dir.length - suffix.length) !== -1);
})){
if(!_.includes(versions, parseInt(version))) {
return dir;
}
}
});
var tasks = incompletePaths.map(function(dir){
var version = dir.split('_')[0];
var migratorFile = require(self.config.cwd+'/'+dir);
return utils.lazyPromiseRunner(function(){
return migratorFile.up(self.migrator, self.config).then(function(){
return self.npd().table(tableName).create({version: parseInt(version)});
})
.then(function(){
return self.migrator().waitUntilTableActivate(tableName);
})
.then(function(){
return self.config.cwd+'/'+dir;
});
});
});
return utils.PromiseWaterfall(tasks);
});
}).catch(function (err) {
console.error(err);
});
};
MigrateRunner.prototype.rollback = function(){
var self = this;
var tableName = this.config.migrations.tableName;
var pglob = Promise.promisify(glob);
return this.npd().table(tableName).all().then(function(data){
var versions = _.sortBy(_.map(data.Items, function(data){
return data.version;
})).reverse();
var lastVersion = _.head(versions);
if(!lastVersion) {
return Promise.resolve(null);
}
return pglob(path.join(self.config.cwd, '/' + lastVersion + "_*.js")).then(function(maches){
return new Promise(function(resolve, reject){
return require(maches[0]).down(self.migrator, self.config).then(function(){
return self.npd()
.table(tableName)
.where('version', lastVersion)
.delete()
.then(function(){
return self.migrator().waitUntilTableActivate(tableName);
})
.then(function(){
resolve(maches[0]);
});
})
.catch(reject);
});
});
});
};
module.exports = {
create: create,
Runner: MigrateRunner,
Migrator: Migrator
};