Skip to content

Commit eeb9f7f

Browse files
committed
initial implementation
1 parent 9d37c4b commit eeb9f7f

11 files changed

+909
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_module

index.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
var cache = {
2+
caching: require('./lib/caching'),
3+
multi_caching: require('./lib/multi_caching')
4+
};
5+
6+
module.exports = cache;

lib/caching.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
var caching = function(args) {
2+
args = args || {};
3+
var self = {};
4+
self.store_name = args.store || 'redis';
5+
self.store = require('./stores/' + self.store_name).create(args);
6+
7+
/**
8+
* Wraps a function in cache. I.e., the first time the function is run,
9+
* its results are stored in cache so subsequent calls retrieve from cache
10+
* instead of calling the function.
11+
*
12+
* @example
13+
*
14+
* var key = 'user_' + user_id;
15+
* cache.run(key, function(cb) {
16+
* user_adapter.get(user_id, cb);
17+
* }, function(err, user) {
18+
* console.log(user);
19+
* });
20+
*/
21+
self.run = function(key, work, cb) {
22+
self.store.get(key, function(err, result) {
23+
if (err) { return cb(err); }
24+
if (result) {
25+
return cb(null, result);
26+
}
27+
28+
work(function() {
29+
var work_args = Array.prototype.slice.call(arguments, 0);
30+
self.store.set(key, work_args[1], function(err) {
31+
if (err) { return cb(err); }
32+
cb.apply(null, work_args);
33+
});
34+
});
35+
});
36+
};
37+
38+
self.get = self.store.get;
39+
40+
self.set = self.store.set;
41+
42+
self.del = self.store.del;
43+
44+
return self;
45+
};
46+
47+
module.exports = caching;

lib/multi_caching.js

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
var async = require('async');
2+
3+
/**
4+
* Module that lets you specify a hiearchy of caches.
5+
*/
6+
var multi_caching = function(caches) {
7+
var self = {};
8+
if (!Array.isArray(caches)) { throw new Error('multi_caching requires an array of caches'); }
9+
10+
function get_from_highest_priority_cache(key, cb) {
11+
var i = 0;
12+
async.forEachSeries(caches, function(cache, async_cb) {
13+
cache.store.get(key, function(err, result) {
14+
if (err) { return cb(err); }
15+
if (result) {
16+
// break out of async loop.
17+
return cb(err, result, i);
18+
}
19+
20+
i += 1;
21+
async_cb(err);
22+
});
23+
}, cb);
24+
}
25+
26+
function set_in_multiple_caches(caches, key, value, cb) {
27+
async.forEach(caches, function(cache, async_cb) {
28+
cache.store.set(key, value, async_cb);
29+
}, cb);
30+
};
31+
32+
/**
33+
* Wraps a function in one or more caches.
34+
* Has same API as regular caching module.
35+
*
36+
* If a key doesn't exist in any cache, it gets set in all caches.
37+
* If a key exists in a high-priority (e.g., first) cache, it gets returned immediately
38+
* without getting set in other lower-priority caches.
39+
* If a key doesn't exist in a higher-priority cache but exists in a lower-priority
40+
* cache, it gets set in all higher-priority caches.
41+
*/
42+
self.run = function(key, work, cb) {
43+
get_from_highest_priority_cache(key, function(err, result, index) {
44+
if (err) { return cb(err); }
45+
if (result) {
46+
var caches_to_set = caches.slice(0, index);
47+
set_in_multiple_caches(caches_to_set, key, result, function(err) {
48+
return cb(err, result);
49+
});
50+
} else {
51+
work(function() {
52+
var work_args = Array.prototype.slice.call(arguments, 0);
53+
54+
set_in_multiple_caches(caches, key, work_args[1], function(err) {
55+
cb.apply(null, work_args);
56+
});
57+
});
58+
}
59+
});
60+
};
61+
62+
self.set = function(key, value, cb) {
63+
set_in_multiple_caches(caches, key, value, cb);
64+
};
65+
66+
self.get = function(key, cb) {
67+
get_from_highest_priority_cache(key, cb);
68+
};
69+
70+
self.del = function(key, cb) {
71+
async.forEach(caches, function(cache, async_cb) {
72+
cache.store.del(key, async_cb);
73+
}, cb);
74+
};
75+
76+
return self;
77+
};
78+
79+
module.exports = multi_caching;

lib/stores/memory.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
var Lru = require("lru-cache")
2+
3+
var memory_store = function(args) {
4+
args = args || {};
5+
var db = args.db || 'cache';
6+
var self = {};
7+
var ttl = args.ttl;
8+
var lru_opts = {
9+
max: args.max || 500,
10+
maxAge: ttl ? ttl * 1000 : null
11+
};
12+
13+
var lru_cache = new Lru(lru_opts);
14+
15+
self.set = function(key, value, cb) {
16+
lru_cache.set(key, value);
17+
cb(null);
18+
};
19+
20+
self.get = function(key, cb) {
21+
cb(null, lru_cache.get(key));
22+
};
23+
24+
self.del = function(key, cb) {
25+
lru_cache.del(key);
26+
cb(null);
27+
};
28+
29+
return self;
30+
};
31+
32+
var methods = {
33+
create: function(args) {
34+
return memory_store(args);
35+
}
36+
};
37+
38+
module.exports = methods;

lib/stores/redis.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
var redis_store = function(args) {
3+
args = args || {};
4+
var db = args.db || 'cache';
5+
var self = {};
6+
var ttl = args.ttl;
7+
var client = djs.backends.redis.client({db: djs.settings.redis.dbs[db]});
8+
9+
self.set = function(key, value, cb) {
10+
var val = JSON.stringify(value);
11+
if (ttl) {
12+
client.command('setex', {key: key, ttl: ttl, value: val}, cb);
13+
} else {
14+
client.command('set', {key: key, value: val}, cb);
15+
}
16+
};
17+
18+
self.get = function(key, cb) {
19+
client.command('get', {key: key}, function(err, result) {
20+
if (err) { return cb(err); }
21+
if (result === undefined) { return cb(null, null); }
22+
return cb(null, JSON.parse(result));
23+
});
24+
};
25+
26+
self.del = function(key, cb) {
27+
client.command('del', {key: key}, cb);
28+
};
29+
30+
return self;
31+
};
32+
*/
33+
34+
function redis_store(args) {
35+
args = args || {};
36+
var self = {};
37+
var ttl = args.ttl;
38+
self.client = require('redis').createClient(args.port, args.host, args);
39+
40+
self.get = function(key, cb) {
41+
self.client.get(key, function(err, result) {
42+
cb(err, JSON.parse(result));
43+
});
44+
};
45+
46+
self.set = function(key, value, cb) {
47+
if (ttl) {
48+
self.client.setex(key, ttl, JSON.stringify(value), cb);
49+
} else {
50+
self.client.set(key, JSON.stringify(value), cb);
51+
}
52+
};
53+
54+
self.del = function(key, cb) {
55+
self.client.del(key, cb);
56+
};
57+
58+
return self;
59+
}
60+
61+
62+
var methods = {
63+
create: function(args) {
64+
return redis_store(args);
65+
}
66+
};
67+
68+
module.exports = methods;

package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "cashew",
3+
"version": "0.0.1",
4+
"description": "Cache module for Node.JS",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "mocha"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/BryanDonovan/node-cashew.git"
12+
},
13+
"keywords": [
14+
"cache",
15+
"redis",
16+
"lru-cache"
17+
],
18+
"author": "Bryan Donovan",
19+
"license": "BSD",
20+
"dependencies": {
21+
"async": "0.1.22",
22+
"hiredis": "0.1.14",
23+
"lru-cache": "2.3.0",
24+
"redis": "0.6.7"
25+
},
26+
"devDependencies": {
27+
"Faker" : "0.5.6",
28+
"istanbul": "0.1.29",
29+
"jshint": "1.0.0",
30+
"mocha": "1.8.1",
31+
"sinon": "1.5.2"
32+
}
33+
}

0 commit comments

Comments
 (0)