npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

souvenir

v1.1.1

Published

Unobtrusive caching for async functions

Downloads

16

Readme

souvenir Circle CI

Unobtrusive caching for asynchronous functions.

Installation

$ npm install souvenir

Example

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

  1. there is no change for the external consumer of get_user_details.
  2. 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.memory for 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.redis for 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_provider a cache provider (see above)
  • cache_options optional object with
    • default_ttl: optional integer (default 300, 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_method function(options, callback), the function to be cached.
  • wrap_options optional object with
    • namespace optional string for namespacing cache entries to prevent collision. Cache entries are created based on a hash of the options being passed to bare_method. It's easy to imagine two methods (e.g. get_username and get_user_email) which take identical options (e.g. { "user_id": 123 }) which may collide in cache. Namespacing the wrapped methods resolves this issue.
    • ttl optional integer (defaults to the cache's default_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
  • namespace string, the namespace of the cache-wrapped function whose entry needs to be invalidated.
  • options
  • callback optional function() 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
  • key string, identifier for the cache entry to retrieve.
  • callback function(error, result).
    • should be invoked when the data has been retrieved from the cache.
    • if key is not found in the cache, no error or result should be passed.
    • if key is found in the cache, the cached value should be passed as result.

set(key, value, ttl, callback)

Write an item to the cache.

Arguments
  • key string, identifier for the cache entry to write.
  • value arbitrary, the data to be written to the cache.
  • ttl integer, the number of seconds before the cache entry should expire.
  • callback function(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
  • key string, identifier for the cache entry to remove.
  • callback function(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.