UNPKG

monc

Version:

The ultimate MongoDB cache module.

243 lines (202 loc) 6.99 kB
exports.install = module.exports.install = function(mongoose, options, Aggregate) { var _this = mongoose; //TODO USE Memcached and Redis var _cacheModule = require('lru-cache'); _this.Query.prototype.cacheDefaultOpts = _this.cacheDefaultOpts = { max: 500, maxAge: 1000 * 60 * 60 }; _this.Query.prototype.__cached = _this.__cached = false; _this.Query.prototype.__cleanCache = _this.__cleanCache = false; _this.shouldAttach = options.setFlag ? true : false; _this.canLog = options.debug || false; _this.__log = _this.canLog ? console.log : function() {}; _this.__attachModelValues = options.modelValues || true; _this.cacheMod = _cacheModule(options || _this.cacheDefaultOpts); _this.modelBinKey = ""; //store default declarations var originalFuns = { execFind: mongoose.Query.prototype.execFind, exec: mongoose.Query.prototype.exec }; //helper functions _this.setKey = function(key,thisRef){ _this.modelBinKey = thisRef.model.modelName; if(key){ if(key.constructor == String) return key; else return JSON.stringify(key); }else{ return _this.generateBin(thisRef); } } //cache flag setter _this.cache = function(binKey){ this.__cached = true; this.__binKey = _this.setKey(binKey,this); return this; }; //clean a specific query _this.cleanCache = function(binKey){ this.__cleanCache = true; this.__binKey = _this.setKey(binKey,this); _this.cacheMod.del(this.__binKey); _this.__log('\n Deleting cache ' + this.__binKey ); return this; }; //clean all the sored data! _this.cleanAll = function(){ if(_this.canLog){ _this.cacheMod.forEach(function(value,key,cache){ _this.__log('\n Deleting cache in loop key -> ' + key ); }); } _this.cacheMod.reset(); return this; } _this.cleanMultiple = function(arr){ if(arr.construct == Array){ arr.forEach(function(value,key){ _this.__log('\n Deleting cache in key -> ' + key ); _this.cacheMod.del(key); }); } return this; } _this.generateBin = function(refQuery){ if (refQuery._pipeline) { return _this.generateBinAggregate(refQuery); } var newBinKey = JSON.stringify({ model: refQuery.model.modelName, query: refQuery._conditions, fields: refQuery._fields || {}, options: refQuery.options }); _this.__log('\n Generating cache bin key for ' + newBinKey ); return newBinKey; }; _this.generateBinAggregate = function(refQueryAggr) { var newBinKey = JSON.stringify({ model: refQueryAggr._model.modelName, pipeline: refQueryAggr._pipeline, options: refQueryAggr.options }); _this.__log('\n Generating cache bin key for ' + newBinKey ); return newBinKey; }; //execution alteration _this.execAlter = function(caller, args) { if(!this.__cached){ return originalFuns[caller].apply(this, args); } var cachedObj = _this.cacheMod.get(this.__binKey); if(cachedObj){ _this.__log('\n Cache hit -> ', this.__binKey); for (var i = 0; i < args.length; i++) { if (typeof args[i] === 'function') { args[i](null, cachedObj); break; } } return this; } var setCache = function(err, obj) { if (!err && obj!= null) { _this.__log('\n Save to cache with binkey ', key); //array or object if(obj instanceof Array && _this.shouldAttach){ obj.map(function(v,i){ v.comingFromCache = true; }) }else if(_this.shouldAttach){ obj.comingFromCache = true; } _this.cacheMod.set(key, obj); //each time cache is set //an associated array is saved binded to Model's names ;) //if(var modelCache = _this.cacheMod.get(key)) if(_this.__attachModelValues) _this.attachModelValues(key); } this.apply(this, arguments); }; var key = this.__binKey; //no cache generate and store for (var i = 0; i < args.length; i++) { if (typeof args[i] !== 'function') continue; args[i] = (setCache).bind(args[i]); } return originalFuns[caller].apply(this, args); }; _this.attachModelValues = function(key){ var alreadyThereCached = _this.cacheMod.get(_this.modelBinKey) || []; //remove staled data! alreadyThereCached = _this.removeStaled(alreadyThereCached); //attach alreadyThereCached.push(key); _this.__log('\n Updating Model\'s cache'); _this.cacheMod.set(_this.modelBinKey, alreadyThereCached); } _this.removeStaled = function(checkIfStaled){ _this.__log('\n Removing staled data from model\'s cache ' ); return checkIfStaled.filter(function(v,i){ return _this.cacheMod.get(v); }); } _this.assoc = function(){ _this.__log('\n Retrieving Model\'s associated keys' ); return _this.cacheMod.get(_this.modelBinKey) || []; } _this.cleanAssoc = function(){ _this.__log('\n Reseting model\'s associated keys' ); var keys = _this.cacheMod.get(_this.modelBinKey) || []; keys.map(function(v,i) { _this.cacheMod.del(v); }) return this; } _this.assocObj = function(){ _this.__log('\n Retrieving Model\'s associated objects' ); var keys = _this.cacheMod.get(_this.modelBinKey) || []; return _this.cacheMod.get(keys); } _this.allValues = function(){ _this.__log('\n Retrieving all cache values' ); return _this.cacheMod.values(); } _this.dump = function(){ _this.__log('\n Dumping whole cache' ); return _this.cacheMod.dump(); } _this.setDir = function(key,obj){ _this.__log('\n Directly setting cache for' + key ); return _this.cacheMod.set(key, obj); } _this.getDir = function(key){ _this.__log('\n Cache hit -> ' + key ); return _this.cacheMod.get(key); } _this.cleanFirst = function(binKey){ this.__binKey = _this.setKey(binKey,this); return this; } _this.getModelBinKey = function(){ _this.__log('\n Return ' + this.modelName ); return this.modelName; }; //register helpers and alterations _this.Query.prototype.cache = _this.cache; _this.Query.prototype.cleanCache = _this.cleanCache; //map the utility functions to the Model and the query prototype! _this.Query.prototype.clean = _this.cleanFirst; _this.Model.cleanAll = _this.Query.prototype.cleanAll = _this.cleanAll; _this.Model.cleanMultiple = _this.Query.prototype.cleanMultiple = _this.cleanMultiple; _this.Model.allValues = _this.allValues; _this.Model.dump = _this.dump; _this.Model.setDir = _this.setDir; _this.Model.getDir = _this.getDir; _this.Model.getModelBinKey = _this.getModelBinKey; _this.Model.assoc = _this.assoc; _this.Model.assocObj = _this.assocObj; _this.Model.cleanAssoc = _this.cleanAssoc; //extend default proto _this.Query.prototype.exec = function(arg1, arg2) { return _this.execAlter.call(this, 'exec', arguments); }; return this; };