├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── spec ├── TransactionSpec.js ├── helpers └── UserSchema.js └── support └── jasmine.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | - "0.11" 5 | - "0.10" 6 | - "iojs" 7 | - "iojs-v1.0.4" 8 | services: mongodb 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## mongoose-transaction 2 | A node module for transaction-like db writes 3 | 4 | [![Build Status](https://travis-ci.org/anand-seeenivasagam/mongoose-transaction.svg?branch=master)](https://travis-ci.org/anand-seeenivasagam/mongoose-transaction) 5 | [![NPM Version](http://img.shields.io/npm/v/mongoose-transaction.svg?style=flat)](https://www.npmjs.org/package/mongoose-transaction) 6 | [![NPM Downloads](https://img.shields.io/npm/dm/mongoose-transaction.svg?style=flat)](https://www.npmjs.org/package/mongoose-transaction) 7 | 8 | mongoose-transaction handles insert, update and remove. 9 | 10 | If any operation that you provide to mongoose-transaction fails, 11 | all the documents that involved in the process will return back to its old state. 12 | 13 | Example: 14 | 15 | ```javascript 16 | var mongoose = require('mongoose'); 17 | var Transaction = require('mongoose-transaction')(mongoose); 18 | 19 | var transaction = new Transaction(); 20 | transaction.insert('User', {userId:'someuser1' , emailId:'test email1'}); 21 | transaction.update('User', id, {userId:'someuser2' , emailId:'test email2'}); 22 | transaction.remove('User', id2); 23 | transaction.run(function(err, docs){ 24 | // your code here 25 | }); 26 | ``` 27 | 28 | To run the tests: 29 | ```javascript 30 | npm test 31 | ``` 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery-deferred'); 2 | 3 | 4 | function Transaction (mongoose) { 5 | 6 | var transacts = []; 7 | var updateOrRemoveObjects = []; 8 | 9 | this.insert = function(collectionName, data){ 10 | var Model = mongoose.model(collectionName); 11 | if(!Model) 12 | throw new Error('Collection not found'); 13 | transacts.push(constructInsertTask({ Model: Model, data:data, type: 'insert' })); 14 | }; 15 | 16 | this.update = function(collectionName, objectId, data){ 17 | 18 | var Model = mongoose.model(collectionName); 19 | if(!Model) 20 | throw new Error('Collection not found'); 21 | updateOrRemoveObjects.push({objectId: objectId, data:data, Model:Model, type:'update'}); 22 | // var doc = Model(data); 23 | // var transact = { type: 'update', doc: doc }; 24 | // storeOldDoc(objectId, transact); 25 | }; 26 | 27 | this.remove = function(collectionName, objectId){ 28 | var Model = mongoose.model(collectionName); 29 | if(!Model) 30 | throw new Error('Collection not found'); 31 | updateOrRemoveObjects.push({objectId: objectId, Model:Model, type:'remove'}); 32 | // var transact = {type: 'remove'}; 33 | // storeOldDoc(objectId, transact); 34 | }; 35 | 36 | this.run = function(callback){ 37 | var updateOrRemoveDeferredArray = []; 38 | updateOrRemoveObjects.forEach(function(docData){ 39 | updateOrRemoveDeferredArray.push(getTask(docData)); 40 | }); 41 | 42 | $.when.apply($, updateOrRemoveDeferredArray).done(function(){ 43 | var tasks = Array.prototype.slice.call(arguments); 44 | if(tasks && tasks.length > 0) 45 | transacts = transacts.concat(tasks); 46 | 47 | var transactsDeffered = []; 48 | transacts.forEach(function(transact){ 49 | transactsDeffered.push(transact.call()); 50 | }); 51 | $.when.apply($, transactsDeffered).done(function(){ 52 | results = Array.prototype.slice.call(arguments); 53 | var errs = [], successDocData = [], docs = []; 54 | results.forEach(function(result){ 55 | if (!result) 56 | return; 57 | if (result[0]) errs.push(result[0]); 58 | if (result[1]) successDocData.push(result[1]); 59 | if (result[2]) docs.push(result[2]); 60 | }); 61 | if(errs.length !== 0){ 62 | var rollbacksDeffered = []; 63 | if (successDocData.length !== 0){ 64 | successDocData.forEach(function(docData){ 65 | rollbacksDeffered.push(rollback(docData)); 66 | }); 67 | $.when.apply($, rollbacksDeffered).done(function(docs){ 68 | callback(docs); 69 | }); 70 | } else { 71 | callback(errs, null); 72 | } 73 | } else { 74 | callback(null, docs); 75 | } 76 | }); 77 | }).fail(function(err){ 78 | callback(err); 79 | }); 80 | }; 81 | 82 | function getTask (docData) { 83 | var deferred = $.Deferred(); 84 | docData.Model.findById(docData.objectId, function(err, oldDoc){ 85 | if(err) 86 | deferred.reject(err); 87 | else{ 88 | var task; 89 | docData.oldDoc = oldDoc; 90 | if (docData.type === 'update') { 91 | task = constructUpdateTask(docData); 92 | } else if (docData.type === 'remove') { 93 | task = constructRemoveTask(docData); 94 | } 95 | deferred.resolve(task); 96 | } 97 | }); 98 | return deferred.promise(); 99 | } 100 | 101 | function rollback (docData) { 102 | var deferred = $.Deferred(); 103 | if (!docData || !docData.doc && !docData.oldDoc) { deferred.resolve(); } 104 | else { 105 | if(docData.type === 'insert') 106 | docData.doc.remove(function (err, doc) { 107 | if(err) 108 | deferred.reject(err); 109 | else 110 | deferred.resolve(); 111 | }); 112 | else if (docData.type === 'update') { 113 | for (var key in docData.oldDoc) { 114 | docData.doc[key] = docData.oldDoc[key]; 115 | } 116 | docData.doc.save(function (err, doc){ 117 | if(err) 118 | deferred.reject(err); 119 | else 120 | deferred.resolve(); 121 | }); 122 | } 123 | else if (docData.type === 'remove'){ 124 | var oldDocData = JSON.parse(JSON.stringify(docData.oldDoc)); 125 | var oldDoc = new docData.Model(oldDocData); 126 | oldDoc.save(function (err, doc){ 127 | if(err) 128 | deferred.reject(err); 129 | else 130 | deferred.resolve(); 131 | }); 132 | } 133 | } 134 | return deferred.promise(); 135 | } 136 | 137 | function constructUpdateTask (docData) { 138 | return function () { 139 | var deferred = $.Deferred(); 140 | var oldDocData = JSON.parse(JSON.stringify(docData.oldDoc)); 141 | docData.doc = docData.oldDoc; 142 | for (var key in docData.data) { 143 | docData.doc[key] = docData.data[key]; 144 | } 145 | docData.doc.save(function(err, doc){ 146 | if(err) 147 | deferred.resolve(err, null, null); 148 | else { 149 | docData.oldDoc = oldDocData; 150 | deferred.resolve(null, docData, doc); 151 | } 152 | }); 153 | return deferred.promise(); 154 | }; 155 | } 156 | 157 | function constructRemoveTask (docData) { 158 | return function () { 159 | var deferred = $.Deferred(); 160 | docData.oldDoc.remove(function(err, doc){ 161 | if(err){ 162 | deferred.resolve(err, null, null); 163 | } 164 | else { 165 | deferred.resolve(null, docData, doc); 166 | } 167 | }); 168 | return deferred.promise(); 169 | }; 170 | } 171 | 172 | function constructInsertTask (docData) { 173 | return function () { 174 | var deferred = $.Deferred(); 175 | var model = new docData.Model(docData.data); 176 | model.save(function(err, doc){ 177 | if(err){ 178 | deferred.resolve(err, null, null); 179 | } 180 | else { 181 | docData.doc = doc; 182 | deferred.resolve(null, docData, doc); 183 | } 184 | }); 185 | return deferred.promise(); 186 | }; 187 | } 188 | } 189 | 190 | module.exports = function(mongoose) { 191 | return function (){ 192 | return new Transaction(mongoose); 193 | }; 194 | }; 195 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-transaction", 3 | "version": "0.0.3", 4 | "description": "A small library to provide an transaction-like process for mongoose", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/jasmine" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/anand-seeenivasagam/mongoose-transaction.git" 12 | }, 13 | "keywords": [ 14 | "mongoose", 15 | "transaction", 16 | "mongodb" 17 | ], 18 | "author": "Anand S ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/anand-seeenivasagam/mongoose-transaction/issues" 22 | }, 23 | "dependencies": { 24 | "jquery-deferred": "^0.3.0" 25 | }, 26 | "devDependencies": { 27 | "jasmine": "^2.3.1", 28 | "mongoose": "^4.0.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spec/TransactionSpec.js: -------------------------------------------------------------------------------- 1 | describe("Transaction", function() { 2 | var mongoose = require('mongoose'); 3 | mongoose.connect("mongodb://localhost/test"); 4 | var userSchema = require("./helpers/UserSchema"); 5 | mongoose.model('User', userSchema); 6 | var Transaction = require('../index')(mongoose); 7 | 8 | 9 | beforeEach(function(done) { 10 | mongoose.model('User').remove({}, function(){ 11 | done(); 12 | }); 13 | }); 14 | 15 | it("should properly insert User data into Db", function(done) { 16 | var transaction = new Transaction(); 17 | transaction.insert('User', {userId:'someuser1' , emailId:'test email1'}); 18 | transaction.run(function(err, docs){ 19 | mongoose.model('User').findOne({userId:'someuser1'}, function(err, docs){ 20 | expect(docs.emailId).toEqual('test email1'); 21 | done(); 22 | }); 23 | }); 24 | }); 25 | 26 | it("should rollback one insert when other insert fails", function(done) { 27 | var transaction = new Transaction(); 28 | transaction.insert('User', {userId:'someuser1' , emailId:'test email1'}); 29 | transaction.insert('User', {}); 30 | transaction.run(function(err, docs){ 31 | mongoose.model('User').findOne({userId:'someuser1'}, function(err, docs){ 32 | expect(docs).toEqual(null); 33 | done(); 34 | }); 35 | }); 36 | }); 37 | 38 | it("should update when there is no fails", function(done) { 39 | mongoose.model('User')({userId:'someuser1' , emailId:'test email1'}).save(function(err, doc){ 40 | expect(doc).not.toBe(null); 41 | expect(doc.userId).toEqual("someuser1"); 42 | var transaction = new Transaction(); 43 | transaction.update('User', doc._id, {userId:'someuser2' , emailId:'test email2'}); 44 | transaction.run(function(err, docs){ 45 | mongoose.model('User').findOne({_id:doc._id}, function(err, docs){ 46 | expect(docs.userId).toEqual("someuser2"); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | it("should rollback update when one insert fails", function(done) { 54 | mongoose.model('User')({userId:'someuser1' , emailId:'test email1'}).save(function(err, doc){ 55 | expect(doc).not.toBe(null); 56 | expect(doc.userId).toEqual("someuser1"); 57 | var transaction = new Transaction(); 58 | transaction.update('User', doc._id, {userId:'someuser2' , emailId:'test email2'}); 59 | transaction.insert('User', {}); 60 | transaction.run(function(err, docs){ 61 | mongoose.model('User').findOne({userId:'someuser1'}, function(err, docs){ 62 | expect(docs.userId).toEqual("someuser1"); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | }); 68 | 69 | it("should rollback remove when one insert fails", function(done) { 70 | mongoose.model('User')({userId:'someuser1' , emailId:'test email1'}).save(function(err, doc){ 71 | expect(doc).not.toBe(null); 72 | expect(doc.userId).toEqual("someuser1"); 73 | var transaction = new Transaction(); 74 | transaction.remove('User', doc._id); 75 | transaction.insert('User', {}); 76 | transaction.run(function(err, docs){ 77 | mongoose.model('User').findOne({_id:doc._id}, function(err, docs){ 78 | expect(docs.userId).toEqual("someuser1"); 79 | done(); 80 | }); 81 | }); 82 | }); 83 | }); 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /spec/helpers/UserSchema.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | 5 | var UserSchema = new Schema({ 6 | userId: { 7 | type: String, 8 | index: true, 9 | unique: true, 10 | required: true, 11 | }, 12 | emailId: { 13 | type: String, 14 | index: true, 15 | unique: true, 16 | required: true, 17 | }, 18 | created: { 19 | type: Number, 20 | default: new Date().getTime() 21 | } 22 | }); 23 | module.exports = UserSchema; -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "helpers/**/*.js" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------