Facundo Olano
8 years ago
commit
98d196c00d
5 changed files with 218 additions and 0 deletions
@ -0,0 +1,14 @@ |
|||
{ |
|||
"env": { |
|||
"es6": true, |
|||
"mocha": true, |
|||
"node": true |
|||
}, |
|||
"extends": "semistandard", |
|||
"plugins": [ |
|||
"standard" |
|||
], |
|||
"rules": { |
|||
"no-unused-vars": [2, { "vars": "all", "args": "after-used" }] |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
# Logs |
|||
logs |
|||
*.log |
|||
|
|||
# Runtime data |
|||
pids |
|||
*.pid |
|||
*.seed |
|||
|
|||
# Directory for instrumented libs generated by jscoverage/JSCover |
|||
lib-cov |
|||
|
|||
# Coverage directory used by tools like istanbul |
|||
coverage |
|||
|
|||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) |
|||
.grunt |
|||
|
|||
# node-waf configuration |
|||
.lock-wscript |
|||
|
|||
# Compiled binary addons (http://nodejs.org/api/addons.html) |
|||
build/Release |
|||
|
|||
# Dependency directory |
|||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git |
|||
node_modules |
@ -0,0 +1,158 @@ |
|||
'use strict'; |
|||
|
|||
// TODO add maxAge (i.e. support expiration)
|
|||
|
|||
function buildCache (client, opts) { |
|||
if (typeof opts === 'number') { |
|||
opts = {max: opts}; |
|||
} |
|||
|
|||
opts = Object.assign({ |
|||
namespace: 'LRU-CACHE!' |
|||
}, opts); |
|||
|
|||
if (!opts.max) { |
|||
throw Error('max number of items in cache must be specified.'); |
|||
} |
|||
|
|||
const ZSET_KEY = `${opts.namespace}-i`; |
|||
|
|||
function namedKey (key) { |
|||
if (!typeof key === 'string') { |
|||
throw Error('key should be a string.'); |
|||
} |
|||
|
|||
return `${opts.namespace}-k-${key}`; |
|||
} |
|||
|
|||
/* |
|||
* Gets the value for the given key and updates its timestamp score, only if |
|||
* already present in the zset. The result is JSON.parsed before returned. |
|||
*/ |
|||
const get = (key) => new Promise((resolve, reject) => { |
|||
const score = -new Date().getTime(); |
|||
key = namedKey(key); |
|||
|
|||
client.multi() |
|||
.get(key) |
|||
.zadd(ZSET_KEY, 'XX', score, key) |
|||
.exec((err, results) => { |
|||
if (err) return reject(err); |
|||
|
|||
const value = results[0] && JSON.parse(results[0]); |
|||
resolve(value); |
|||
}); |
|||
}); |
|||
|
|||
/* |
|||
* Save (add/update) the new value for the given key, and update its timestamp |
|||
* score. The value is JSON.stringified before saving. |
|||
* |
|||
* If there are more than opts.max items in the cache after the operation |
|||
* then remove each exceeded key from the zset index and its value from the |
|||
* cache (in a single transaction). |
|||
*/ |
|||
const set = (key, value) => new Promise((resolve, reject) => { |
|||
const score = -new Date().getTime(); |
|||
key = namedKey(key); |
|||
|
|||
client.multi() |
|||
.set(key, JSON.stringify(value)) |
|||
.zadd(ZSET_KEY, score, key) |
|||
.zrange(ZSET_KEY, opts.max, -1) |
|||
.exec((err, results) => { |
|||
if (err) return reject(err); |
|||
|
|||
// we get zrange first then zrem each one instead of just zremrange,
|
|||
// that way we guarantee that zset is always in sync with available data in the cache
|
|||
|
|||
const exceededKeys = results[2]; |
|||
if (exceededKeys.length) { |
|||
return client.multi() |
|||
.zrem(ZSET_KEY, exceededKeys) |
|||
.del(exceededKeys) |
|||
.exec((err) => { |
|||
if (err) return reject(err); |
|||
resolve(); |
|||
}); |
|||
} |
|||
|
|||
return resolve(); |
|||
}); |
|||
}); |
|||
|
|||
/* |
|||
* Retrieve the value for key in the cache (if present), without updating the |
|||
* timestamp score. The result is JSON.parsed before returned. |
|||
*/ |
|||
const peek = (key) => new Promise((resolve, reject) => |
|||
client.get(namedKey(key), (err, result) => { |
|||
if (err) return reject(err); |
|||
|
|||
const value = result && JSON.parse(result); |
|||
resolve(value); |
|||
})); |
|||
|
|||
/* |
|||
* Remove the value of key from the cache (and the zset index). |
|||
*/ |
|||
function del (key) { |
|||
// del from key
|
|||
// zrem from zset
|
|||
} |
|||
|
|||
/* |
|||
* Remove all items from cache (and the zset index). |
|||
*/ |
|||
function reset () { |
|||
// get all from zet
|
|||
// del all keys from result
|
|||
// del zset
|
|||
} |
|||
|
|||
/* |
|||
* Return true if the given key is in the cache |
|||
*/ |
|||
function has (key) { |
|||
// !! zscore
|
|||
} |
|||
|
|||
/* |
|||
* Return an array of the keys currenlty in the cache, most reacently accessed |
|||
* first. |
|||
*/ |
|||
function keys () { |
|||
// get all from zset
|
|||
// remove prefix from all
|
|||
} |
|||
|
|||
/* |
|||
* Return an array of the values currenlty in the cache, most reacently accessed |
|||
* first. |
|||
*/ |
|||
function values () { |
|||
// get all from zset
|
|||
// get each value from result keys
|
|||
} |
|||
|
|||
/* |
|||
* Return the amount of items currenlty in the cache. |
|||
*/ |
|||
function count () { |
|||
|
|||
} |
|||
|
|||
return { |
|||
get: get, |
|||
set: set, |
|||
peek: peek, |
|||
del: del, |
|||
reset: reset, |
|||
has: has, |
|||
keys: keys, |
|||
values: values, |
|||
count: count |
|||
}; |
|||
} |
|||
|
|||
module.exports = buildCache; |
@ -0,0 +1,19 @@ |
|||
{ |
|||
"name": "redis-lru", |
|||
"version": "1.0.0", |
|||
"description": "redis-backed LRU cache", |
|||
"main": "index.js", |
|||
"scripts": { |
|||
"test": "echo \"Error: no test specified\" && exit 1" |
|||
}, |
|||
"devDependencies": { |
|||
"eslint": "^3.8.0", |
|||
"eslint-config-semistandard": "^6.0.1", |
|||
"eslint-config-standard": "^5.1.0", |
|||
"eslint-plugin-promise": "^1.1.0", |
|||
"eslint-plugin-standard": "^1.3.2", |
|||
"mocha": "^2.4.5" |
|||
}, |
|||
"author": "Facundo Olano", |
|||
"license": "MIT" |
|||
} |
Loading…
Reference in new issue