├── 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 |
--------------------------------------------------------------------------------