offshore
Version:
An ORM for Node.js
124 lines (108 loc) • 3.77 kB
JavaScript
var _ = require('lodash');
var async = require('async');
module.exports = function(models, cb) {
var transactionCnx = {};
var queries = {};
models = _.isArray(models) ? models : [models];
var errs = [];
models.forEach(function(model) {
if (!_.isObject(model) || !_.isObject(model.adapter) || !model.adapter.connection) {
return errs.push('Invalid model : ' + model);
}
var cnx = model.adapter.connection;
queries[model.identity] = model._query || {};
// check default connection
if (cnx === 'default' && queries[model.identity].defaultConnection) {
cnx = queries[model.identity].defaultConnection;
}
if (!model.offshore.connections[cnx]._adapter.registerTransaction) {
return errs.push(new Error('Adapter ' + model.offshore.connections[cnx]._adapter.identity + ' has no transactable interface'));
}
if (!transactionCnx[cnx]) {
transactionCnx[cnx] = {collections: [], adapter: model.offshore.connections[cnx]._adapter, context: model.offshore};
}
if (transactionCnx[cnx].collections.indexOf(model.identity) < 0) {
transactionCnx[cnx].collections.push(model.identity);
}
});
if (errs.length) {
return cb(errs[0]);
}
var transactionDeferred = new TransactionDeferred(transactionCnx);
var transactionDictionary = {};
async.eachSeries(_.keys(transactionCnx), function(cnx, cb) {
transactionCnx[cnx].adapter.registerTransaction(cnx, transactionCnx[cnx].collections, function(err, id) {
if (err) {
return cb(err);
}
transactionDictionary[cnx] = id;
transactionCnx[cnx].id = id;
cb();
});
}, function(err) {
if (err) {
return transactionDeferred.rollback(err);
}
var transaction = {};
_.keys(transactionCnx).forEach(function(cnx) {
var connection = transactionCnx[cnx];
connection.collections.forEach(function(col) {
transaction[col] = connection.context.collections[col]._loadQuery(queries[col])._loadQuery({transaction: transactionDictionary});
});
});
cb(transaction, function(err, result) {
if (err) {
return transactionDeferred.rollback(err);
}
transactionDeferred.commit(result);
});
});
return transactionDeferred;
};
var TransactionDeferred = function(connections) {
this._connections = connections;
this._commit = null;
this._rollback = null;
this._execCallback = null;
};
TransactionDeferred.prototype.commit = function(result) {
var self = this;
if (self._rollback) {
throw new Error('Cannot commit when transaction has been rollbacked');
}
if (self._commit) {
throw new Error('Transaction already commited');
}
this._commit = result;
async.eachSeries(_.keys(self._connections), function(cnx, next) {
self._connections[cnx].adapter.commit(self._connections[cnx].id, _.keys(self._connections[cnx].collections), next);
}, function() {
if (self._execCallback) {
setImmediate(function() {
self._execCallback(null, self._commit);
});
}
});
};
TransactionDeferred.prototype.rollback = function(err) {
var self = this;
if (self._commit) {
throw new Error('Cannot rollback when transaction has been commited');
}
if (self._rollback) {
throw new Error('Transaction already rollbacked');
}
this._rollback = err;
async.eachSeries(_.keys(self._connections), function(cnx, next) {
self._connections[cnx].adapter.rollback(self._connections[cnx].id, _.keys(self._connections[cnx].collections), next);
}, function() {
if (self._execCallback) {
setImmediate(function() {
self._execCallback(self._rollback);
});
}
});
};
TransactionDeferred.prototype.exec = function(cb) {
this._execCallback = cb;
};