├── README.md ├── lodis.coffee ├── index.html └── lodis.js /README.md: -------------------------------------------------------------------------------- 1 | # Lodis 2 | 3 | ----------------------------------------------------------------------- 4 | |_____________________________________________________________________X 5 | | < | > | x |_____________________________________________________| 6 | ----------------------------------------------------------------------- 7 | | _._ | 8 | | _.-``__ ''-._ | 9 | | _.-`` `. `_. ''-._ | 10 | | .-`` .-```. ```\\/ _.,_ ''-._ | 11 | | ( ' , .-` | `, ) | 12 | | |`-._`-...-` __...-.``-._|'` _.-'| Lodis 0.1 | 13 | | | `-._ `._ / _.-' | | 14 | | `-._ `-._ `-./ _.-' _.-' like Redis | 15 | | |`-._`-._ `-.__.-' _.-'_.-'| but in a browser | 16 | | | `-._`-._ _.-'_.-' | | 17 | | `-._ `-._`-.__.-'_.-' _.-' | 18 | | |`-._`-._ `-.__.-' _.-'_.-'| | 19 | | | `-._`-._ _.-'_.-' | | 20 | | `-._ `-._`-.__.-'_.-' _.-' | 21 | | `-._ `-.__.-' _.-' | 22 | | `-._ _.-' github.com/elcuervo/lodis | 23 | | `-.__.-' | 24 | |---------------------------------------------------------------------| 25 | ----------------------------------------------------------------------- 26 | 27 | ## Compatibility 28 | Redis 2.4 29 | 30 | ## Supported commands 31 | * SET ✓ 32 | * GET ✓ 33 | * DEL ✓ 34 | * EXISTS ✓ 35 | * EXPIRE ✓ 36 | * DBSIZE ✓ 37 | * EXPIREAT ✓ 38 | * KEYS ✓ 39 | * APPEND key, value ✓ 40 | * AUTH password ✓ 41 | * BGREWRITEAOF ✓ 42 | * BGSAVE ✓ 43 | * BLPOP key [key ...] timeout 44 | * BRPOP key [key ...] timeout 45 | * BRPOPLPUSH source destination timeout 46 | * CONFIG GET parameter 47 | * CONFIG SET parameter value 48 | * CONFIG RESETSTAT 49 | * DEBUG OBJECT key 50 | * DEBUG SEGFAULT 51 | * DECR key ✓ 52 | * DECRBY key decrement ✓ 53 | * DISCARD 54 | * ECHO message ✓ 55 | * EXEC 56 | * FLUSHALL ✓ 57 | * FLUSHDB ✓ 58 | * GETBIT key offset 59 | * GETRANGE key start end ✓ 60 | * GETSET key value ✓ 61 | * HDEL key field [field ...] ✓ 62 | * HEXISTS key field ✓ 63 | * HGET key field ✓ 64 | * HGETALL key ✓ 65 | * HINCRBY key field increment ✓ 66 | * HKEYS key ✓ 67 | * HLEN key ✓ 68 | * HMGET key field [field ...] ✓ 69 | * HMSET key field value [field value ...] ✓ 70 | * HSET key field value ✓ 71 | * HSETNX key field value ✓ 72 | * HVALS key ✓ 73 | * INCR key ✓ 74 | * INCRBY key increment ✓ 75 | * INFO 76 | * LASTSAVE 77 | * LINDEX key index ✓ 78 | * LINSERT key BEFORE|AFTER pivot value ✓ 79 | * LLEN key ✓ 80 | * LPOP key ✓ 81 | * LPUSH key value [value ...] ✓ 82 | * LPUSHX key value ✓ 83 | * LRANGE key start stop ✓ 84 | * LREM key count value ✓ 85 | * LSET key index value ✓ 86 | * LTRIM key start stop ✓ 87 | * MGET key [key ...] ✓ 88 | * MONITOR 89 | * MOVE key db 90 | * MSET key value [key value ...] ✓ 91 | * MSETNX key value [key value ...] ✓ 92 | * MULTI 93 | * OBJECT subcommand [arguments [arguments ...]] 94 | * PERSIST key ✓ 95 | * PING ✓ 96 | * PSUBSCRIBE pattern [pattern ...] 97 | * PUBLISH channel message 98 | * PUNSUBSCRIBE [pattern [pattern ...]] 99 | * QUIT 100 | * RANDOMKEY ✓ 101 | * RENAME key newkey ✓ 102 | * RENAMENX key newkey ✓ 103 | * RPOP key ✓ 104 | * RPOPLPUSH source destination ✓ 105 | * RPUSH key value [value ...] ✓ 106 | * RPUSHX key value ✓ 107 | * SADD key member [member ...] ✓ 108 | * SAVE ✓ 109 | * SCARD key ✓ 110 | * SDIFF key [key ...] ✓ 111 | * SDIFFSTORE destination key [key ...] ✓ 112 | * SELECT index ✓ 113 | * SETBIT key offset value 114 | * SETEX key seconds value ✓ 115 | * SETNX key value ✓ 116 | * SETRANGE key offset value ✓ 117 | * SHUTDOWN ✓ 118 | * SINTER key [key ...] ✓ 119 | * SINTERSTORE destination key [key ...] ✓ 120 | * SISMEMBER key member ✓ 121 | * SLAVEOF host port 122 | * SLOWLOG subcommand [argument] 123 | * SMEMBERS key ✓ 124 | * SMOVE source destination member ✓ 125 | * SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] 126 | * SPOP key ✓ 127 | * SRANDMEMBER key ✓ 128 | * SREM key member [member ...] ✓ 129 | * STRLEN key ✓ 130 | * SUBSCRIBE channel [channel ...] 131 | * SUNION key [key ...] 132 | * SUNIONSTORE destination key [key ...] 133 | * SYNC 134 | * TYPE key ✓ 135 | * UNSUBSCRIBE [channel [channel ...]] 136 | * UNWATCH 137 | * WATCH key [key ...] 138 | * ZADD key score member 139 | * ZCARD key 140 | * ZCOUNT key min max 141 | * ZINCRBY key increment member 142 | * ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] 143 | * ZRANGE key start stop [WITHSCORES] 144 | * ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 145 | * ZRANK key member 146 | * ZREM key member 147 | * ZREMRANGEBYRANK key start stop 148 | * ZREMRANGEBYSCORE key min max 149 | * ZREVRANGE key start stop [WITHSCORES] 150 | * ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] 151 | * ZREVRANK key member 152 | * ZSCORE key member 153 | * ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] 154 | 155 | 156 | ## TODO 157 | * Store expirations within Lodis itself to avoid expiration dates on reload 158 | * Should handle types in a different way? custom types to match Redis? 159 | * Sync with Redis via HTTP? 160 | -------------------------------------------------------------------------------- /lodis.coffee: -------------------------------------------------------------------------------- 1 | class @Lodis 2 | constructor: -> 3 | this.storage = window.localStorage 4 | 5 | _pack: (value) -> JSON.stringify value 6 | 7 | _unpack: (value) -> JSON.parse value 8 | 9 | _expire_key: (key) => 10 | this.del(key) 11 | delete this._expiration_hash[key] 12 | 13 | _get_set_or_default: (key, default_value) -> 14 | this._unpack(this.get(key)) or default_value 15 | 16 | _get_set: (key) -> 17 | this._get_set_or_default(key, []) 18 | 19 | _extract_from_set: (source, members...) -> 20 | result = [] 21 | for member in members 22 | if member in this.smembers(source) 23 | result.push item for item in this._get_set(source) when item != member 24 | result 25 | 26 | _get_unpacked: (key) -> 27 | try 28 | this._unpack(this.get(key)) 29 | catch error 30 | this.get(key) 31 | 32 | _get_hash: (key) -> 33 | this._get_set_or_default(key, {}) 34 | 35 | _set_packed: (key, value) -> 36 | value = this._pack(value) 37 | this.set(key, value) 38 | 39 | _get_difference_or_intersection_for_sets: (action = 'DIFF', keys...) -> 40 | [head, tail...] = keys 41 | set = this._get_set(head) 42 | other_set = result = [] 43 | other_set = other_set.concat(this._get_set(key)) for key in tail 44 | 45 | for value in set 46 | condition = switch action.toUpperCase() 47 | when 'DIFF' then value not in other_set 48 | when 'INTER' then value in other_set 49 | result.push value if condition 50 | 51 | result.reverse() 52 | 53 | _get_from_hash: (key, options = with_keys: true, with_values: true, only: []) -> 54 | hash = this._get_hash(key) 55 | result = [] 56 | for key, value of hash 57 | result.push key if options["with_keys"] 58 | result.push value if options["with_values"] or ( options["only"] and key in options["only"]) 59 | result 60 | 61 | _alter_int_value: (key, quantity) -> 62 | if this.exists(key) 63 | value = parseInt this.get(key) 64 | if typeof value is "number" 65 | value = value + quantity 66 | this.set(key, value) 67 | value 68 | else 69 | throw new Error 70 | 71 | _expiration_hash: {} 72 | 73 | del: (keys...) -> 74 | this.storage.removeItem(key) for key in keys 75 | true 76 | 77 | set: (key, value) -> 78 | this.storage.setItem(key, value) 79 | true 80 | 81 | get: (key) -> 82 | this.storage.getItem(key) 83 | 84 | exists: (key) -> 85 | this.get(key)? 86 | 87 | dbsize: -> this.storage.length 88 | 89 | keys: (regexp) -> 90 | found_keys = [] 91 | for i in [0..this.dbsize() - 1] 92 | key = this.storage.key(i) 93 | found_keys.push key if key.match(regexp) 94 | found_keys 95 | 96 | expire: (key, seconds) -> 97 | miliseconds = seconds*1000 98 | timeout_id = setTimeout this._expire_key, miliseconds, key 99 | this._expiration_hash[key] = id: timeout_id, timeout: new Date().getTime() + miliseconds 100 | true 101 | 102 | expireat: (key, miliseconds) -> 103 | return false if (miliseconds < new Date().getTime()) or !this.exists(key) 104 | seconds = (miliseconds - new Date().getTime()) / 1000 105 | this.expire key, seconds 106 | true 107 | 108 | ttl: (key) -> 109 | if this.exists(key) 110 | if this._expiration_hash[key] 111 | Math.floor((this._expiration_hash[key].timeout - new Date().getTime()) / 1000) 112 | else 113 | -1 114 | 115 | append: (key, value) -> 116 | this.set(key, "#{this.get(key)}#{value}") if this.exists(key) 117 | 118 | auth: (password) -> 119 | true # this should do someting? 120 | 121 | bgrewriteaof: -> true 122 | 123 | bgsave: -> true 124 | 125 | blpop: -> 126 | 127 | lrange: (key, start, end) -> 128 | end += 1 129 | set = this._get_set(key) 130 | end = set.length + end if end < 1 131 | result = set.slice(start, end) 132 | result = [result] if result.constructor is String 133 | result 134 | 135 | lpush: (key, item) -> 136 | set = this._get_set(key) 137 | set.unshift(item) 138 | this._set_packed(key, set) 139 | 140 | rpush: (key, item) -> 141 | set = this._get_set(key) 142 | set.push(item) 143 | this._set_packed(key, set) 144 | 145 | rpushx: (key, item) -> 146 | this.rpush(key, item) if this.exists(key) 147 | 148 | decr: (key) -> 149 | this.decrby(key, 1) 150 | 151 | incr: (key) -> 152 | this.incrby(key, 1) 153 | 154 | decrby: (key, quantity = 1) -> 155 | this._alter_int_value(key, -quantity) 156 | 157 | incrby: (key, quantity = 1) -> 158 | this._alter_int_value(key, quantity) 159 | 160 | echo: (message) -> message 161 | 162 | flushall: -> 163 | this.storage.clear() 164 | 165 | flushdb: -> 166 | this.flushall() 167 | 168 | getrange: (key, start, end) -> 169 | if this.exists(key) 170 | string = this.get(key) 171 | start = string.length + start if start < 0 172 | end = string.length + end if end < 0 173 | string.substr start, end + 1 174 | 175 | getset: (key, value) -> 176 | if this.exists(key) 177 | old_value = this.get(key) 178 | this.set(key, value) 179 | old_value 180 | 181 | hset: (hash_key, key, value) -> 182 | hash = this._get_hash(hash_key) 183 | hash[key] = value 184 | this._set_packed(hash_key, hash) 185 | true 186 | 187 | hget: (hash_key, key) -> 188 | if this.exists(hash_key) 189 | hash = this._get_hash(hash_key) 190 | hash[key] 191 | 192 | hgetall: (hash_key) -> 193 | this._get_from_hash(hash_key) if this.exists(hash_key) 194 | 195 | hexists: (hash_key, key) -> 196 | this.hget(hash_key, key)? 197 | 198 | hkeys: (hash_key) -> 199 | this._get_from_hash(hash_key, with_keys: true, with_values: false) if this.exists(hash_key) 200 | 201 | hlen: (hash_key) -> 202 | this.hkeys(hash_key).length if this.exists(hash_key) 203 | 204 | hincrby: (hash_key, key, quantity) -> 205 | if this.hexists(hash_key, key) 206 | old_value = parseInt this.hget(hash_key, key) 207 | if typeof old_value is "number" 208 | new_value = old_value + quantity 209 | this.hset(hash_key, key, new_value) 210 | new_value 211 | else 212 | throw new Error("Invalid type") 213 | 214 | hmget: (hash_key, keys...) -> 215 | this._get_from_hash(hash_key, with_values: true, with_keys: false, only: keys) if this.exists(hash_key) 216 | 217 | hmset: (hash_key, keys_and_values...) -> 218 | result = {} 219 | result[keys_and_values[i-1]] = value for i, value of keys_and_values when i % 2 220 | this._set_packed(hash_key, result) 221 | 222 | hsetnx: (hash_key, key, value) -> 223 | if !this.exists(hash_key) 224 | this.hset(hash_key, key, value) 225 | true 226 | else 227 | false 228 | 229 | hvals: (hash_key) -> 230 | this._get_from_hash(hash_key, with_keys: false, with_values: true) if this.exists(hash_key) 231 | 232 | lindex: (key, index) -> 233 | if this.exists(key) 234 | hash = this._get_set(key) 235 | index = hash.length + index if index < 0 236 | hash[index] or false 237 | 238 | linsert: (key, direction, reference_value, value) -> 239 | if this.exists(key) 240 | direction = switch direction.toUpperCase() 241 | when "BEFORE" then -1 242 | when "AFTER" then 1 243 | 244 | set = this._get_set(key) 245 | reference_value = set.indexOf(reference_value) + direction 246 | [left_side, right_side] = [set[0...reference_value], set[reference_value..-1]] 247 | 248 | result = left_side.concat([value]) 249 | result = result.concat(right_side) 250 | this._set_packed(key, result) 251 | 252 | llen: (key) -> 253 | if this.exists(key) 254 | set = this._get_set(key) 255 | set.length 256 | 257 | lpop: (key) -> 258 | if this.exists(key) 259 | set = this._get_set(key) 260 | value = set[1..-1] 261 | this._set_packed(key, value) 262 | value 263 | 264 | lpushx: (key, value) -> 265 | if this.exists(key) 266 | this.lpush(key, value) 267 | else 268 | false 269 | 270 | lrem: (key, count, item) -> 271 | if this.exists(key) 272 | quantity = Math.abs(count) 273 | set = this._get_set(key) 274 | set = set.reverse() if count < 0 275 | result = [] 276 | 277 | for value in set 278 | if value == item and quantity > 0 279 | quantity -= 1 280 | else 281 | result.push value 282 | 283 | result = result.reverse() if count < 0 284 | this._set_packed(key, result) 285 | 286 | lset: (key, index, value) -> 287 | if this.exists(key) 288 | set = this._get_set(key) 289 | index = set.length + index if index < 0 290 | set[index] = value 291 | this._set_packed(key, set) 292 | 293 | ltrim: (key, start, end) -> 294 | if this.exists(key) 295 | set = this._get_set(key) 296 | end = set.length + end if end < 0 297 | result = set[start..end] 298 | this._set_packed(key, result) 299 | 300 | mget: (keys...) -> 301 | result = [] 302 | result.push this.get(key) for key in keys 303 | result 304 | 305 | mset: (keys_and_values...) -> 306 | this.set(keys_and_values[i-1], value) for i, value of keys_and_values when i % 2 307 | 308 | msetnx: (keys_and_values...) -> 309 | return for i, key_or_value of keys_and_values when (!(i % 2) and this.exists(key_or_value)) 310 | this.mset(keys_and_values...) 311 | 312 | persist: (key) -> 313 | if this.exists(key) 314 | if this._expiration_hash[key] 315 | clearTimeout this._expiration_hash[key].id 316 | delete this._expiration_hash[key] 317 | 318 | ping: -> "PONG" 319 | 320 | randomkey: -> 321 | keys = this.keys() 322 | keys[Math.floor(Math.random() * keys.length)] 323 | 324 | rename: (key, new_key) -> 325 | value = this.get(key) 326 | this.del(key) 327 | this.set(new_key, value) 328 | 329 | renamenx: (key, new_key) -> 330 | this.rename(key, new_key) if !this.exists(new_key) 331 | 332 | rpop: (key) -> 333 | if this.exists(key) 334 | set = this._get_set(key) 335 | value = set.pop() 336 | this._set_packed(key, set) 337 | value 338 | 339 | rpoplpush: (hash_key, other_hash_key) -> 340 | if this.exists(hash_key) 341 | value = this.rpop(hash_key) 342 | this.lpush(other_hash_key, value) 343 | value 344 | 345 | sadd: (key, members...) -> 346 | set = this._get_set(key) 347 | this.lpush(key, member) for member in members when member not in set 348 | 349 | smembers: (key) -> 350 | this._get_set(key) if this.exists(key) 351 | 352 | save: -> true # ? 353 | 354 | scard: (key) -> 355 | this._get_set(key).length 356 | 357 | sdiff: (keys...) -> 358 | this._get_difference_or_intersection_for_sets('DIFF', keys...) 359 | 360 | sdiffstore: (destination, keys...) -> 361 | this._set_packed(destination, this.sdiff(keys...)) 362 | 363 | select: (db) -> db is 0 364 | 365 | setex: (key, expire, value) -> 366 | this.set(key, value) 367 | this.expire(key, expire) 368 | 369 | setnx: (key, value) -> this.set(key, value) if !this.exists(key) 370 | 371 | setrange: (key, offset, value) -> 372 | old_value = if this.exists(key) 373 | this.get(key).substr(0, offset) 374 | else 375 | result = new String 376 | result += " " for i in [0...offset] 377 | result 378 | 379 | this.set(key, "#{old_value}#{value}") 380 | 381 | shutdown: -> true # ? 382 | 383 | sinter: (keys...) -> 384 | this._get_difference_or_intersection_for_sets('INTER', keys...) 385 | 386 | sinterstore: (destination, keys...) -> 387 | this._set_packed(destination, this.sinter(keys...)) 388 | 389 | sismember: (key, value) -> 390 | if this.exists(key) 391 | set = this._get_set(key) 392 | set.indexOf(value) > -1 393 | 394 | smove: (source, destination, member) -> 395 | if this.exists(source) 396 | if member in this.smembers(source) 397 | result = this._extract_from_set(source, member) 398 | this._set_packed(source, result) 399 | this.rpush(destination, member) 400 | true 401 | else 402 | false 403 | 404 | spop: (key) -> 405 | if this.exists(key) 406 | set = this._get_set(key) 407 | set.pop() 408 | this._set_packed(key, set) 409 | 410 | srandmember: (key) -> 411 | if this.exists(key) 412 | set = this._get_set(key) 413 | set[Math.floor(Math.random() * set.length)] 414 | 415 | srem: (key, members...) -> 416 | if this.exists(key) 417 | set = this._get_set(key) 418 | result = this._extract_from_set(key, members...) 419 | this._set_packed(key, result) 420 | 421 | strlen: (key) -> 422 | if this.exists(key) 423 | this.get(key).length 424 | else 425 | 0 426 | 427 | type: (key) -> 428 | this._get_unpacked(key).constructor.name.toLowerCase() if this.exists(key) 429 | 430 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

LOcal DIctonary Server

4 |

Tests

5 |

Scenarios

6 | 7 | 8 | 9 | 10 | 580 | 581 | 582 | -------------------------------------------------------------------------------- /lodis.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __slice = Array.prototype.slice, __indexOf = Array.prototype.indexOf || function(item) { 3 | for (var i = 0, l = this.length; i < l; i++) { 4 | if (this[i] === item) return i; 5 | } 6 | return -1; 7 | }; 8 | this.Lodis = (function() { 9 | function Lodis() { 10 | this._expire_key = __bind(this._expire_key, this); this.storage = window.localStorage; 11 | } 12 | Lodis.prototype._pack = function(value) { 13 | return JSON.stringify(value); 14 | }; 15 | Lodis.prototype._unpack = function(value) { 16 | return JSON.parse(value); 17 | }; 18 | Lodis.prototype._expire_key = function(key) { 19 | this.del(key); 20 | return delete this._expiration_hash[key]; 21 | }; 22 | Lodis.prototype._get_set_or_default = function(key, default_value) { 23 | return this._unpack(this.get(key)) || default_value; 24 | }; 25 | Lodis.prototype._get_set = function(key) { 26 | return this._get_set_or_default(key, []); 27 | }; 28 | Lodis.prototype._extract_from_set = function() { 29 | var item, member, members, result, source, _i, _j, _len, _len2, _ref; 30 | source = arguments[0], members = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 31 | result = []; 32 | for (_i = 0, _len = members.length; _i < _len; _i++) { 33 | member = members[_i]; 34 | if (__indexOf.call(this.smembers(source), member) >= 0) { 35 | _ref = this._get_set(source); 36 | for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) { 37 | item = _ref[_j]; 38 | if (item !== member) { 39 | result.push(item); 40 | } 41 | } 42 | } 43 | } 44 | return result; 45 | }; 46 | Lodis.prototype._get_unpacked = function(key) { 47 | try { 48 | return this._unpack(this.get(key)); 49 | } catch (error) { 50 | return this.get(key); 51 | } 52 | }; 53 | Lodis.prototype._get_hash = function(key) { 54 | return this._get_set_or_default(key, {}); 55 | }; 56 | Lodis.prototype._set_packed = function(key, value) { 57 | value = this._pack(value); 58 | return this.set(key, value); 59 | }; 60 | Lodis.prototype._get_difference_or_intersection_for_sets = function() { 61 | var action, condition, head, key, keys, other_set, result, set, tail, value, _i, _j, _len, _len2; 62 | action = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 63 | if (action == null) { 64 | action = 'DIFF'; 65 | } 66 | head = keys[0], tail = 2 <= keys.length ? __slice.call(keys, 1) : []; 67 | set = this._get_set(head); 68 | other_set = result = []; 69 | for (_i = 0, _len = tail.length; _i < _len; _i++) { 70 | key = tail[_i]; 71 | other_set = other_set.concat(this._get_set(key)); 72 | } 73 | for (_j = 0, _len2 = set.length; _j < _len2; _j++) { 74 | value = set[_j]; 75 | condition = (function() { 76 | switch (action.toUpperCase()) { 77 | case 'DIFF': 78 | return __indexOf.call(other_set, value) < 0; 79 | case 'INTER': 80 | return __indexOf.call(other_set, value) >= 0; 81 | } 82 | })(); 83 | if (condition) { 84 | result.push(value); 85 | } 86 | } 87 | return result.reverse(); 88 | }; 89 | Lodis.prototype._get_from_hash = function(key, options) { 90 | var hash, result, value; 91 | if (options == null) { 92 | options = { 93 | with_keys: true, 94 | with_values: true, 95 | only: [] 96 | }; 97 | } 98 | hash = this._get_hash(key); 99 | result = []; 100 | for (key in hash) { 101 | value = hash[key]; 102 | if (options["with_keys"]) { 103 | result.push(key); 104 | } 105 | if (options["with_values"] || (options["only"] && __indexOf.call(options["only"], key) >= 0)) { 106 | result.push(value); 107 | } 108 | } 109 | return result; 110 | }; 111 | Lodis.prototype._alter_int_value = function(key, quantity) { 112 | var value; 113 | if (this.exists(key)) { 114 | value = parseInt(this.get(key)); 115 | if (typeof value === "number") { 116 | value = value + quantity; 117 | this.set(key, value); 118 | return value; 119 | } else { 120 | throw new Error; 121 | } 122 | } 123 | }; 124 | Lodis.prototype._expiration_hash = {}; 125 | Lodis.prototype.del = function() { 126 | var key, keys, _i, _len; 127 | keys = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 128 | for (_i = 0, _len = keys.length; _i < _len; _i++) { 129 | key = keys[_i]; 130 | this.storage.removeItem(key); 131 | } 132 | return true; 133 | }; 134 | Lodis.prototype.set = function(key, value) { 135 | this.storage.setItem(key, value); 136 | return true; 137 | }; 138 | Lodis.prototype.get = function(key) { 139 | return this.storage.getItem(key); 140 | }; 141 | Lodis.prototype.exists = function(key) { 142 | return this.get(key) != null; 143 | }; 144 | Lodis.prototype.dbsize = function() { 145 | return this.storage.length; 146 | }; 147 | Lodis.prototype.keys = function(regexp) { 148 | var found_keys, i, key, _ref; 149 | found_keys = []; 150 | for (i = 0, _ref = this.dbsize() - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { 151 | key = this.storage.key(i); 152 | if (key.match(regexp)) { 153 | found_keys.push(key); 154 | } 155 | } 156 | return found_keys; 157 | }; 158 | Lodis.prototype.expire = function(key, seconds) { 159 | var miliseconds, timeout_id; 160 | miliseconds = seconds * 1000; 161 | timeout_id = setTimeout(this._expire_key, miliseconds, key); 162 | this._expiration_hash[key] = { 163 | id: timeout_id, 164 | timeout: new Date().getTime() + miliseconds 165 | }; 166 | return true; 167 | }; 168 | Lodis.prototype.expireat = function(key, miliseconds) { 169 | var seconds; 170 | if ((miliseconds < new Date().getTime()) || !this.exists(key)) { 171 | return false; 172 | } 173 | seconds = (miliseconds - new Date().getTime()) / 1000; 174 | this.expire(key, seconds); 175 | return true; 176 | }; 177 | Lodis.prototype.ttl = function(key) { 178 | if (this.exists(key)) { 179 | if (this._expiration_hash[key]) { 180 | return Math.floor((this._expiration_hash[key].timeout - new Date().getTime()) / 1000); 181 | } else { 182 | return -1; 183 | } 184 | } 185 | }; 186 | Lodis.prototype.append = function(key, value) { 187 | if (this.exists(key)) { 188 | return this.set(key, "" + (this.get(key)) + value); 189 | } 190 | }; 191 | Lodis.prototype.auth = function(password) { 192 | return true; 193 | }; 194 | Lodis.prototype.bgrewriteaof = function() { 195 | return true; 196 | }; 197 | Lodis.prototype.bgsave = function() { 198 | return true; 199 | }; 200 | Lodis.prototype.blpop = function() {}; 201 | Lodis.prototype.lrange = function(key, start, end) { 202 | var result, set; 203 | end += 1; 204 | set = this._get_set(key); 205 | if (end < 1) { 206 | end = set.length + end; 207 | } 208 | result = set.slice(start, end); 209 | if (result.constructor === String) { 210 | result = [result]; 211 | } 212 | return result; 213 | }; 214 | Lodis.prototype.lpush = function(key, item) { 215 | var set; 216 | set = this._get_set(key); 217 | set.unshift(item); 218 | return this._set_packed(key, set); 219 | }; 220 | Lodis.prototype.rpush = function(key, item) { 221 | var set; 222 | set = this._get_set(key); 223 | set.push(item); 224 | return this._set_packed(key, set); 225 | }; 226 | Lodis.prototype.rpushx = function(key, item) { 227 | if (this.exists(key)) { 228 | return this.rpush(key, item); 229 | } 230 | }; 231 | Lodis.prototype.decr = function(key) { 232 | return this.decrby(key, 1); 233 | }; 234 | Lodis.prototype.incr = function(key) { 235 | return this.incrby(key, 1); 236 | }; 237 | Lodis.prototype.decrby = function(key, quantity) { 238 | if (quantity == null) { 239 | quantity = 1; 240 | } 241 | return this._alter_int_value(key, -quantity); 242 | }; 243 | Lodis.prototype.incrby = function(key, quantity) { 244 | if (quantity == null) { 245 | quantity = 1; 246 | } 247 | return this._alter_int_value(key, quantity); 248 | }; 249 | Lodis.prototype.echo = function(message) { 250 | return message; 251 | }; 252 | Lodis.prototype.flushall = function() { 253 | return this.storage.clear(); 254 | }; 255 | Lodis.prototype.flushdb = function() { 256 | return this.flushall(); 257 | }; 258 | Lodis.prototype.getrange = function(key, start, end) { 259 | var string; 260 | if (this.exists(key)) { 261 | string = this.get(key); 262 | if (start < 0) { 263 | start = string.length + start; 264 | } 265 | if (end < 0) { 266 | end = string.length + end; 267 | } 268 | return string.substr(start, end + 1); 269 | } 270 | }; 271 | Lodis.prototype.getset = function(key, value) { 272 | var old_value; 273 | if (this.exists(key)) { 274 | old_value = this.get(key); 275 | this.set(key, value); 276 | return old_value; 277 | } 278 | }; 279 | Lodis.prototype.hset = function(hash_key, key, value) { 280 | var hash; 281 | hash = this._get_hash(hash_key); 282 | hash[key] = value; 283 | this._set_packed(hash_key, hash); 284 | return true; 285 | }; 286 | Lodis.prototype.hget = function(hash_key, key) { 287 | var hash; 288 | if (this.exists(hash_key)) { 289 | hash = this._get_hash(hash_key); 290 | return hash[key]; 291 | } 292 | }; 293 | Lodis.prototype.hgetall = function(hash_key) { 294 | if (this.exists(hash_key)) { 295 | return this._get_from_hash(hash_key); 296 | } 297 | }; 298 | Lodis.prototype.hexists = function(hash_key, key) { 299 | return this.hget(hash_key, key) != null; 300 | }; 301 | Lodis.prototype.hkeys = function(hash_key) { 302 | if (this.exists(hash_key)) { 303 | return this._get_from_hash(hash_key, { 304 | with_keys: true, 305 | with_values: false 306 | }); 307 | } 308 | }; 309 | Lodis.prototype.hlen = function(hash_key) { 310 | if (this.exists(hash_key)) { 311 | return this.hkeys(hash_key).length; 312 | } 313 | }; 314 | Lodis.prototype.hincrby = function(hash_key, key, quantity) { 315 | var new_value, old_value; 316 | if (this.hexists(hash_key, key)) { 317 | old_value = parseInt(this.hget(hash_key, key)); 318 | if (typeof old_value === "number") { 319 | new_value = old_value + quantity; 320 | this.hset(hash_key, key, new_value); 321 | return new_value; 322 | } else { 323 | throw new Error("Invalid type"); 324 | } 325 | } 326 | }; 327 | Lodis.prototype.hmget = function() { 328 | var hash_key, keys; 329 | hash_key = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 330 | if (this.exists(hash_key)) { 331 | return this._get_from_hash(hash_key, { 332 | with_values: true, 333 | with_keys: false, 334 | only: keys 335 | }); 336 | } 337 | }; 338 | Lodis.prototype.hmset = function() { 339 | var hash_key, i, keys_and_values, result, value; 340 | hash_key = arguments[0], keys_and_values = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 341 | result = {}; 342 | for (i in keys_and_values) { 343 | value = keys_and_values[i]; 344 | if (i % 2) { 345 | result[keys_and_values[i - 1]] = value; 346 | } 347 | } 348 | return this._set_packed(hash_key, result); 349 | }; 350 | Lodis.prototype.hsetnx = function(hash_key, key, value) { 351 | if (!this.exists(hash_key)) { 352 | this.hset(hash_key, key, value); 353 | return true; 354 | } else { 355 | return false; 356 | } 357 | }; 358 | Lodis.prototype.hvals = function(hash_key) { 359 | if (this.exists(hash_key)) { 360 | return this._get_from_hash(hash_key, { 361 | with_keys: false, 362 | with_values: true 363 | }); 364 | } 365 | }; 366 | Lodis.prototype.lindex = function(key, index) { 367 | var hash; 368 | if (this.exists(key)) { 369 | hash = this._get_set(key); 370 | if (index < 0) { 371 | index = hash.length + index; 372 | } 373 | return hash[index] || false; 374 | } 375 | }; 376 | Lodis.prototype.linsert = function(key, direction, reference_value, value) { 377 | var left_side, result, right_side, set, _ref; 378 | if (this.exists(key)) { 379 | direction = (function() { 380 | switch (direction.toUpperCase()) { 381 | case "BEFORE": 382 | return -1; 383 | case "AFTER": 384 | return 1; 385 | } 386 | })(); 387 | set = this._get_set(key); 388 | reference_value = set.indexOf(reference_value) + direction; 389 | _ref = [set.slice(0, reference_value), set.slice(reference_value)], left_side = _ref[0], right_side = _ref[1]; 390 | result = left_side.concat([value]); 391 | result = result.concat(right_side); 392 | return this._set_packed(key, result); 393 | } 394 | }; 395 | Lodis.prototype.llen = function(key) { 396 | var set; 397 | if (this.exists(key)) { 398 | set = this._get_set(key); 399 | return set.length; 400 | } 401 | }; 402 | Lodis.prototype.lpop = function(key) { 403 | var set, value; 404 | if (this.exists(key)) { 405 | set = this._get_set(key); 406 | value = set.slice(1); 407 | this._set_packed(key, value); 408 | return value; 409 | } 410 | }; 411 | Lodis.prototype.lpushx = function(key, value) { 412 | if (this.exists(key)) { 413 | return this.lpush(key, value); 414 | } else { 415 | return false; 416 | } 417 | }; 418 | Lodis.prototype.lrem = function(key, count, item) { 419 | var quantity, result, set, value, _i, _len; 420 | if (this.exists(key)) { 421 | quantity = Math.abs(count); 422 | set = this._get_set(key); 423 | if (count < 0) { 424 | set = set.reverse(); 425 | } 426 | result = []; 427 | for (_i = 0, _len = set.length; _i < _len; _i++) { 428 | value = set[_i]; 429 | if (value === item && quantity > 0) { 430 | quantity -= 1; 431 | } else { 432 | result.push(value); 433 | } 434 | } 435 | if (count < 0) { 436 | result = result.reverse(); 437 | } 438 | return this._set_packed(key, result); 439 | } 440 | }; 441 | Lodis.prototype.lset = function(key, index, value) { 442 | var set; 443 | if (this.exists(key)) { 444 | set = this._get_set(key); 445 | if (index < 0) { 446 | index = set.length + index; 447 | } 448 | set[index] = value; 449 | return this._set_packed(key, set); 450 | } 451 | }; 452 | Lodis.prototype.ltrim = function(key, start, end) { 453 | var result, set; 454 | if (this.exists(key)) { 455 | set = this._get_set(key); 456 | if (end < 0) { 457 | end = set.length + end; 458 | } 459 | result = set.slice(start, (end + 1) || 9e9); 460 | return this._set_packed(key, result); 461 | } 462 | }; 463 | Lodis.prototype.mget = function() { 464 | var key, keys, result, _i, _len; 465 | keys = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 466 | result = []; 467 | for (_i = 0, _len = keys.length; _i < _len; _i++) { 468 | key = keys[_i]; 469 | result.push(this.get(key)); 470 | } 471 | return result; 472 | }; 473 | Lodis.prototype.mset = function() { 474 | var i, keys_and_values, value, _results; 475 | keys_and_values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 476 | _results = []; 477 | for (i in keys_and_values) { 478 | value = keys_and_values[i]; 479 | if (i % 2) { 480 | _results.push(this.set(keys_and_values[i - 1], value)); 481 | } 482 | } 483 | return _results; 484 | }; 485 | Lodis.prototype.msetnx = function() { 486 | var i, key_or_value, keys_and_values; 487 | keys_and_values = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 488 | for (i in keys_and_values) { 489 | key_or_value = keys_and_values[i]; 490 | if (!(i % 2) && this.exists(key_or_value)) { 491 | return; 492 | } 493 | } 494 | return this.mset.apply(this, keys_and_values); 495 | }; 496 | Lodis.prototype.persist = function(key) { 497 | if (this.exists(key)) { 498 | if (this._expiration_hash[key]) { 499 | clearTimeout(this._expiration_hash[key].id); 500 | return delete this._expiration_hash[key]; 501 | } 502 | } 503 | }; 504 | Lodis.prototype.ping = function() { 505 | return "PONG"; 506 | }; 507 | Lodis.prototype.randomkey = function() { 508 | var keys; 509 | keys = this.keys(); 510 | return keys[Math.floor(Math.random() * keys.length)]; 511 | }; 512 | Lodis.prototype.rename = function(key, new_key) { 513 | var value; 514 | value = this.get(key); 515 | this.del(key); 516 | return this.set(new_key, value); 517 | }; 518 | Lodis.prototype.renamenx = function(key, new_key) { 519 | if (!this.exists(new_key)) { 520 | return this.rename(key, new_key); 521 | } 522 | }; 523 | Lodis.prototype.rpop = function(key) { 524 | var set, value; 525 | if (this.exists(key)) { 526 | set = this._get_set(key); 527 | value = set.pop(); 528 | this._set_packed(key, set); 529 | return value; 530 | } 531 | }; 532 | Lodis.prototype.rpoplpush = function(hash_key, other_hash_key) { 533 | var value; 534 | if (this.exists(hash_key)) { 535 | value = this.rpop(hash_key); 536 | this.lpush(other_hash_key, value); 537 | return value; 538 | } 539 | }; 540 | Lodis.prototype.sadd = function() { 541 | var key, member, members, set, _i, _len, _results; 542 | key = arguments[0], members = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 543 | set = this._get_set(key); 544 | _results = []; 545 | for (_i = 0, _len = members.length; _i < _len; _i++) { 546 | member = members[_i]; 547 | if (__indexOf.call(set, member) < 0) { 548 | _results.push(this.lpush(key, member)); 549 | } 550 | } 551 | return _results; 552 | }; 553 | Lodis.prototype.smembers = function(key) { 554 | if (this.exists(key)) { 555 | return this._get_set(key); 556 | } 557 | }; 558 | Lodis.prototype.save = function() { 559 | return true; 560 | }; 561 | Lodis.prototype.scard = function(key) { 562 | return this._get_set(key).length; 563 | }; 564 | Lodis.prototype.sdiff = function() { 565 | var keys; 566 | keys = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 567 | return this._get_difference_or_intersection_for_sets.apply(this, ['DIFF'].concat(__slice.call(keys))); 568 | }; 569 | Lodis.prototype.sdiffstore = function() { 570 | var destination, keys; 571 | destination = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 572 | return this._set_packed(destination, this.sdiff.apply(this, keys)); 573 | }; 574 | Lodis.prototype.select = function(db) { 575 | return db === 0; 576 | }; 577 | Lodis.prototype.setex = function(key, expire, value) { 578 | this.set(key, value); 579 | return this.expire(key, expire); 580 | }; 581 | Lodis.prototype.setnx = function(key, value) { 582 | if (!this.exists(key)) { 583 | return this.set(key, value); 584 | } 585 | }; 586 | Lodis.prototype.setrange = function(key, offset, value) { 587 | var i, old_value, result; 588 | old_value = (function() { 589 | if (this.exists(key)) { 590 | return this.get(key).substr(0, offset); 591 | } else { 592 | result = new String; 593 | for (i = 0; 0 <= offset ? i < offset : i > offset; 0 <= offset ? i++ : i--) { 594 | result += " "; 595 | } 596 | return result; 597 | } 598 | }).call(this); 599 | return this.set(key, "" + old_value + value); 600 | }; 601 | Lodis.prototype.shutdown = function() { 602 | return true; 603 | }; 604 | Lodis.prototype.sinter = function() { 605 | var keys; 606 | keys = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 607 | return this._get_difference_or_intersection_for_sets.apply(this, ['INTER'].concat(__slice.call(keys))); 608 | }; 609 | Lodis.prototype.sinterstore = function() { 610 | var destination, keys; 611 | destination = arguments[0], keys = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 612 | return this._set_packed(destination, this.sinter.apply(this, keys)); 613 | }; 614 | Lodis.prototype.sismember = function(key, value) { 615 | var set; 616 | if (this.exists(key)) { 617 | set = this._get_set(key); 618 | return set.indexOf(value) > -1; 619 | } 620 | }; 621 | Lodis.prototype.smove = function(source, destination, member) { 622 | var result; 623 | if (this.exists(source)) { 624 | if (__indexOf.call(this.smembers(source), member) >= 0) { 625 | result = this._extract_from_set(source, member); 626 | this._set_packed(source, result); 627 | this.rpush(destination, member); 628 | return true; 629 | } else { 630 | return false; 631 | } 632 | } 633 | }; 634 | Lodis.prototype.spop = function(key) { 635 | var set; 636 | if (this.exists(key)) { 637 | set = this._get_set(key); 638 | set.pop(); 639 | return this._set_packed(key, set); 640 | } 641 | }; 642 | Lodis.prototype.srandmember = function(key) { 643 | var set; 644 | if (this.exists(key)) { 645 | set = this._get_set(key); 646 | return set[Math.floor(Math.random() * set.length)]; 647 | } 648 | }; 649 | Lodis.prototype.srem = function() { 650 | var key, members, result, set; 651 | key = arguments[0], members = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 652 | if (this.exists(key)) { 653 | set = this._get_set(key); 654 | result = this._extract_from_set.apply(this, [key].concat(__slice.call(members))); 655 | return this._set_packed(key, result); 656 | } 657 | }; 658 | Lodis.prototype.strlen = function(key) { 659 | if (this.exists(key)) { 660 | return this.get(key).length; 661 | } else { 662 | return 0; 663 | } 664 | }; 665 | Lodis.prototype.type = function(key) { 666 | if (this.exists(key)) { 667 | return this._get_unpacked(key).constructor.name.toLowerCase(); 668 | } 669 | }; 670 | return Lodis; 671 | })(); 672 | }).call(this); 673 | --------------------------------------------------------------------------------