souvenir
v1.1.1
Published
Unobtrusive caching for async functions
Downloads
13
Readme
souvenir 
Unobtrusive caching for asynchronous functions.
Installation
$ npm install souvenirExample
Souvenir is a library for caching the results of asynchronous functions. Instead of cluttering your function's logic with cache implementation, the souvenir cache layer is above the code:
Before souvenir
user.js
var database = require("...");
// options: object with
// user_id
function get_user_details(options, callback) {
get_cache(options, function(error, result) {
if (result)
return callback(null, result);
database.query("SELECT * FROM user WHERE ...", function(error2, result2) {
if (error2)
return callback(error2);
save_cache(options, result2, function() {
callback(null, result2);
});
});
});
}
exports.get_user_details = get_user_details;elsewhere.js
var user = require("./user.js");
user.get_user_details({ "user_id": 123 }, ...);After souvenir
user.js
var database = require("...");
var souvenir = require("souvenir");
var cache = new souvenir.cache(new souvenir.cache_providers.memory());
// options: object with
// user_id
function get_user_details(options, callback) {
database.query("SELECT * FROM user WHERE ...", callback);
}
exports.get_user_details = cache.wrap(get_user_details);elsewhere.js
var user = require("./user.js");
user.get_user_details({ "user_id": 123 }, ...);Notice that
- there is no change for the external consumer of
get_user_details. - the bare function now includes only the pertinent logic.
Usage
A souvenir cache is created by
var souvenir = require("souvenir");
var cache_provider = new souvenir.cache_providers.memory();
var cache = new souvenir.cache(cache_provider);Here we cache a slow function my_slow_computation, keeping the cached result for an hour:
var my_cached_computation = cache.wrap(my_slow_computation, { "namespace": "my_slow_computation_namespace", "ttl": 60 * 60 });
my_cached_computation({ "a": 1, "b": 7 }, function() {
// do something with the result
});The first time it's called, the cached computation will be as slow as usual, but any subsequent calls in the next hour (with the same options, { "a": 1, "b": 7 }) will be very quick.
Now some information has come in which makes the cached result outdated. Invalidate the cache:
cache.invalidate("my_slow_computation_namespace", { "a": 1, "b": 7 });The next call to my_cached_computation will have to do the slow re-compute.
Cache providers
A cache provider tells souvenir how to store and retrieve cached data. This is freeform -- you can supply your own. Souvenir comes with two cache providers out-of-the-box (with souvenir = require("souvenir")):
souvenir.cache_providers.memoryfor storing cache in memory. This is useful in development but less useful when the cache should be shared across systems (because invalidation is tricky).souvenir.cache_providers.redisfor storing cache in redis. This can be used to share cache across systems.
A cache provider is a javascript object exposing methods get, set and invalidate. Here is a valid (but useless) cache provider:
var my_cool_cache_provider = {
"get": function(key, callback) { callback(null, "fake data"); },
"set": function(key, value, ttl, callback) { callback(); },
"invalidate": function(key, callback) { callback(); }
};
var souvenir = require("souvenir");
var cache = new souvenir.cache(my_cool_cache_provider);Any function wrapped by this cache will always return the string fake data and the underlying function will never be invoked.
Cache-wrapped function
Souvenir currently only supports wrapping functions with signature function(options, callback) where callback has the conventional signature function(error, result).
API
With souvenir = require("souvenir"):
new souvenir.cache(cache_provider[, cache_options])
Creates a souvenir cache, which is the point of contact between your code and the cached data.
Arguments
cache_providera cache provider (see above)cache_optionsoptional object withdefault_ttl: optional integer (default300, which is 5 minutes), the default time-to-live (in seconds) for cache entries in this cache. This can be overridden on a per-method basis.
Example
var souvenir = require("souvenir");
var cache = new souvenir.cache(new souvenir.cache_providers.memory());With cache = new souvenir.cache(...):
cache.wrap(bare_method[, wrap_options])
Wraps bare_method in a souvenir caching layer, using the options given in wrap_options. Returns the cache-wrapped function, which is a drop-in replacement for bare_method elsewhere in the code.
Arguments
bare_methodfunction(options, callback), the function to be cached.wrap_optionsoptional object withnamespaceoptional string for namespacing cache entries to prevent collision. Cache entries are created based on a hash of theoptionsbeing passed tobare_method. It's easy to imagine two methods (e.g.get_usernameandget_user_email) which take identical options (e.g.{ "user_id": 123 }) which may collide in cache. Namespacing the wrapped methods resolves this issue.ttloptional integer (defaults to the cache'sdefault_ttl), the time-to-live (in seconds) for cache entries for this function. This is the number of seconds before the cache entry should expire under normal circumstances (i.e. aside from explicitly invalidating the cache entry).
Example
function foo(options, callback) { ... }
// Cache results of `foo` for 30 seconds.
exports.foo = cache.wrap(foo, { "namespace": "foo", "ttl": 30 });cache.invalidate(namespace, options[, callback])
Explicitly remove an item from cache.
Arguments
namespacestring, the namespace of the cache-wrapped function whose entry needs to be invalidated.optionscallbackoptionalfunction()invoked after the cache entry has been invalidated.
Example
function get_username(options, callback) { ... }
var cached_get_username = cache.wrap(get_username, { "namespace": "get_username" });
function update_username(options, callback) {
...
cache.invalidate("get_username", { "user_id": options.user_id }, callback);
}Custom cache provider
A cache provider is an interface to a key/value store, exposing the following API:
get(key, callback)
Retrieve an item from the cache.
Arguments
keystring, identifier for the cache entry to retrieve.callbackfunction(error, result).- should be invoked when the data has been retrieved from the cache.
- if
keyis not found in the cache, noerrororresultshould be passed. - if
keyis found in the cache, the cached value should be passed asresult.
set(key, value, ttl, callback)
Write an item to the cache.
Arguments
keystring, identifier for the cache entry to write.valuearbitrary, the data to be written to the cache.ttlinteger, the number of seconds before the cache entry should expire.callbackfunction(error)to call when the data has been written.
Note that it is the cache provider's job to expire cache entries based on TTL.
invalidate(key, callback)
Remove an item from the cache.
Arguments
keystring, identifier for the cache entry to remove.callbackfunction(error)to call when the item has been removed.
redis cache provider
The redis cache provider, souvenir.cache_providers.redis takes one argument, a node_redis client. You are responsible for initializing the client (providing credentials, optionally selecting a database, etc.) with require("redis").createClient().
Example
var redis = require("redis");
var souvenir = require("souvenir");
var redis_client = redis.createClient();
var cache = new souvenir.cache(new souvenir.cache_providers.redis(redis_client));Development
The test suite can be run with npm test. Tests run on every push in CircleCI: https://circleci.com/gh/MakerStudios/souvenir
The tests for the redis cache provider expect a redis server to be running at localhost:6379. You can specify a REDIS_HOST environment variable to instruct the tests to look elsewhere, e.g. REDIS_HOST=a.b.c.d npm test.
