1 var comb = require("comb"); 2 3 /** 4 * @class In memory store for Node JS. 5 * 6 * @name Hive 7 */ 8 exports = module.exports = comb.define(null, { 9 10 instance : { 11 /** 12 * @lends Hive.prototype 13 */ 14 15 __checkInterval : 1000, 16 17 constructor : function(options) { 18 options = options || {}; 19 //create a tree for times so we can quickly find values less 20 //than a specified time 21 this.__timeTree = new comb.collections.AVLTree({ 22 compare : function(a, b) { 23 var ret = 0; 24 if (a.expires < b.expires) { 25 ret = -1; 26 } else if (a.expires > b.expires) { 27 ret = 1; 28 } 29 return ret; 30 } 31 }); 32 //use a tree so we can have complex keys later 33 //otherwise we are limited to just strings 34 //also this performs great on look up! 35 //Also allows quick lookups on multiple values 36 var cmp = options.compare ? options.compare : function(a, b) { 37 var ret = 0; 38 if (a < b) { 39 ret = -1; 40 } else if (a > b) { 41 ret = 1; 42 } 43 return ret; 44 } 45 this.__values = new comb.collections.AVLTree({ 46 compare : function(a, b) { 47 return cmp(a.key, b.key); 48 } 49 }); 50 this.__cleanupInterval = setInterval(comb.hitch(this, this._checkValues), this.__checkInterval); 51 }, 52 53 /** 54 * Kills the clean up process for looking for expired keys. 55 */ 56 kill : function() { 57 clearInterval(this.__cleanupInterval); 58 }, 59 60 /** 61 * Casts a key 62 * @param {*} key the key to cast 63 */ 64 __castKey : function(key) { 65 //TODO make this overridable. 66 return "" + key; 67 }, 68 69 /** 70 * Checks for expired values. 71 */ 72 _checkValues : function() { 73 var timeTree = this.__timeTree, 74 valueTree = this.__values, 75 now = new Date().getTime(); 76 var vals = this.__timeTree.findLessThan(now); 77 vals.forEach(function(v) { 78 timeTree.remove(v); 79 valueTree.remove(v); 80 }); 81 }, 82 83 /** 84 * Covert seconds to milliseconds 85 * @param {Number} seconds number of seconds to convert. 86 */ 87 _convertSeconds : function(seconds) { 88 return seconds * 60000; 89 }, 90 91 /** 92 * Retrives all values with a key less than the provided key. 93 * 94 * @param {*} key the key to look up. 95 * 96 * @return {Array} an array of values. 97 */ 98 getKeyLt : function(key) { 99 key = this.__castKey(key); 100 return this.__values.findLessThan({key : key}, true).map(function(v) { 101 return v.value 102 }); 103 }, 104 105 /** 106 * Retrives all values with a key less or equal to a than the provided key. 107 * 108 * @param {*} key the key to look up. 109 * 110 * @return {Array} an array of values. 111 */ 112 getKeyLte : function(key) { 113 key = this.__castKey(key); 114 return this.__values.findLessThan({key : key}).map(function(v) { 115 return v.value 116 }); 117 }, 118 119 /** 120 * Retrives all values with a greater than the provided key. 121 * 122 * @param {*} key the key to look up. 123 * 124 * @return {Array} an array of values. 125 */ 126 getKeyGt : function(key) { 127 key = this.__castKey(key); 128 return this.__values.findGreaterThan({key : key}, true).map(function(v) { 129 return v.value 130 }); 131 }, 132 133 /** 134 * Retrives all values with a greater or equal to than the provided key. 135 * 136 * @param {*} key the key to look up. 137 * 138 * @return {Array} an array of values. 139 */ 140 getKeyGte : function(key) { 141 key = this.__castKey(key); 142 return this.__values.findGreaterThan({key : key}).map(function(v) { 143 return v.value 144 }); 145 }, 146 147 /** 148 * Retrive the value for a specified key 149 * 150 * @param {*} key the key to look up. 151 * 152 * * @return {*} the value or null if not found. 153 */ 154 get : function(key) { 155 key = this.__castKey(key); 156 var val = this.__values.find({key : key}); 157 return val ? val.value : null; 158 }, 159 160 /** 161 * Set a key value pair. 162 * @param {*} key the key to store 163 * @param {*} value the value to asociate with the key. 164 * @param {Number} [expires=Infinity] if provided sets a max life on a key, value pair. 165 * 166 * * @return {*} the value 167 */ 168 set : function(key, value, expires) { 169 expires = !expires ? Infinity : new Date().getTime() + this._convertSeconds(expires); 170 //we need to keep expires to ensure we can look up from tree also; 171 var node = {key : key, value : value, expires : expires}; 172 //store the pointer in both 173 this.__values.insert(node); 174 if (expires != Infinity) { 175 //dont worry about saving values that are set to infinity 176 //as it doesnt matter 177 this.__timeTree.insert(node); 178 } 179 return value; 180 }, 181 182 /** 183 * Replace a key value pair in this Store. 184 * 185 * @param {*} key the key to store 186 * @param {*} value the value to asociate with the key. 187 * @param {Number} [expires=Infinity] if provided sets a max life on a key, value pair. 188 * 189 * @return {*} the value 190 */ 191 replace : function(key, value, expires) { 192 var currValue = this.__values.find({key : key}); 193 if (currValue) { 194 currValue.value = value; 195 } else { 196 this.set(key, value, expires); 197 } 198 return value; 199 }, 200 201 /** 202 * Appends a value to a current value, if the current value is a string, and the appending 203 * value is a string then it will be appeneded to the value otherwise an array is created to 204 * store both values. 205 * 206 * @param {*} key the key to look up. 207 * @param {*} value the value to append 208 */ 209 append : function(key, value) { 210 var currNode = this.__values.find({key : key}); 211 if (currNode) { 212 var currValue = currNode.value; 213 //if they are both strings assume you can just appened it! 214 if (comb.isString(currValue) && comb.isString(value)) { 215 currNode.value += value; 216 } else { 217 //make it an array 218 currNode.value = [currValue, value]; 219 } 220 } 221 }, 222 223 /** 224 * Prepends a value to a current value, if the current value is a string, and the prepedning 225 * value is a string then it will be prepeneded to the value otherwise an array is created to 226 * store both values. 227 * 228 * @param {*} key the key to look up. 229 * @param {*} value the value to prepend 230 */ 231 prepend : function(key, value) { 232 var currNode = this.__values.find({key : key}); 233 if (currNode) { 234 var currValue = currNode.value; 235 //if they are both strings assume you can just appened it! 236 if (comb.isString(currValue) && comb.isString(value)) { 237 currNode.value = value + currValue; 238 } else { 239 //make it an array 240 currNode.value = [value, currValue]; 241 } 242 } 243 }, 244 245 /** 246 * Increments a value, if it is numeric. 247 * 248 * @param {*} key the key to look up the value to increment. 249 */ 250 incr : function(key) { 251 var num; 252 var currNode = this.__values.find({key : key}); 253 if (currNode) { 254 var currValue = currNode.value; 255 if (comb.isNumber(currValue)) { 256 //update the pointers value 257 return ++currNode.value; 258 } else { 259 num = Number(currValue); 260 if (!isNaN(num)) { 261 currNode.value = ++num; 262 } 263 } 264 } 265 return num; 266 }, 267 268 /** 269 * Decrements a value, if it is numeric. 270 * 271 * @param {*} key the key to look up the value to decrement. 272 */ 273 decr : function(key) { 274 var num; 275 var currNode = this.__values.find({key : key}); 276 if (currNode) { 277 var currValue = currNode.value; 278 if (comb.isNumber(currValue)) { 279 //update the pointers value 280 return --currNode.value; 281 } else { 282 num = Number(currValue); 283 if (!isNaN(num)) { 284 currNode.value = --num; 285 } 286 } 287 } 288 return num; 289 }, 290 291 /** 292 * Remove a value from this store. 293 * 294 * @param {*} key the key of the key value pair to remove. 295 */ 296 remove : function(key) { 297 var currValue = this.__values.find({key : key}); 298 if (currValue) { 299 this.__values.remove(currValue); 300 if (currValue.expires != Infinity) { 301 this.__timeTree.remove(currValue); 302 } 303 } 304 return null; 305 }, 306 307 /** 308 * Remove all values from the store. 309 */ 310 flushAll : function() { 311 this.__values.clear(); 312 this.__timeTree.clear(); 313 return true; 314 } 315 } 316 317 }); 318