DEPRECATION WARNING: We've decided to deprecate this module in favour of blockai/cloud-cache which works mostly the same except it doesn't try to automatically guess key names (those heuristics were a bit confusing) and clearly separates Promise vs Stream based APIs.
Memoize / cache arbitrary functions to the local file system, Amazon S3, Google Drive, Google Cloud, PostgreSQL, Bittorrent, etc.
Designed to work across different processes or restarts. Functions must
be uniquely identified by the user through a name
argument. Also uses
smart heuristics to further identify like using the package.json
's
name
and version
properties.
Supports optional cache expiry through a maxAge
argument.
Supports promise and stream returning functions.
Any abstract-blob-store compatible store is supported.
npm install --save persistent-memoize
Returns a memoize() function.
blobStore should be an abstract-blob-store.
opts (optional) configuration object
opts.name (defaults to the npm_package_name
environment variable
which is automatically set when running script through npm)
Used when generating the key used by the blob store. See Key Generation.
opts.version (defaults to the npm_package_version
environment
variable which is automatically set when running script through npm)
Used when generating the key used by the blob store. We suggest
setting the version to 'latest'
or null
if you chose to version
functions individually. This prevents needlessly invalidating
the cache when the app version is bumped but the individual function
versions haven't changed. See Key Generation.
opts.maxAge (defaults to Infinity
)
Specifies after how long (in milliseconds since it was last updated) a
cached value is considered stale/expired. Can be overriden through
memoize()
options.
opts.disable (defaults to false
)
Can be used to completely disable memoization. This can be useful for debugging.
fn
The function to memoize.
In order for memoization to work as expected, the return value of fn
must exclusively depend on its arguments and nothing else. It also must
not produce any visible side effects.
A function can only be memoized if it is referentially transparent; that is, only if calling the function has exactly the same effect as replacing that function call with its return value.
In addition, all fn
arguments and its return value must be compatible
with JSON.stringify
.
json-stable-stringify is used to deterministically generate a string from arguments which is then hashed with sha1 to compute a unique key.
name
Since we want the memoization to work across different processes or restarts, we must name the function so that it can uniquely be identified. See Key Generation.
opts optional configuration object
opts.version
A version can optionally be specified if you wish to version functions individually instead of using a global version number. See Key Generation.
opts.maxAge
Overrides the default maxAge
value.
persistent-memoize
is designed to memoize functions across processes
and restarts. In order to do that reliably, functions must be named and
versioned explicitly.
The algorithm to generate keys for specific function calls is as follows:
globalName/globalVersion/name/version/argumentHash
Where globalName
and globalVersion
are respectively the opts.name
and opts.version
values passed to initMemoize(store, opts)
. name
and
version
are respectively the name
argument and opts.version
value
passed to memoize(fn, name, opts)
.
argumentHash
is, in pseudo code:
argumentHash = sha1(stringifyToJson(arguments))
Where arguments
is an array of arguments passed to the memoized
function and stringifyToJson
is a call to
json-stable-stringify
which is similar to JSON.stringify
but more deterministic (e.g. order
of object keys doesn't matter).
import initMemoize from 'persistent-memoize'
import initBlobStore from 'fs-blob-store'
const memoize = initMemoize(initBlobStore())
const someSlowFunction = (i) => Promise.resolve(`your number is ${i}`)
const getValue = memoize(someSlowFunction, 'someSlowFunction')
getValue(2)
// caches result in blob store and returns it
.then((str) => console.log(str))
// now that the result is cached, it will return it from cache
.then((str) => console.log(str))
Boilerplate:
import persistentMemoize from 'persistent-memoize'
import fetch from 'node-fetch'
import http from 'http'
import initBlobStore from 's3-blob-store'
import aws from 'aws-sdk'
const store = initBlobStore({
client: new aws.S3({
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY
}),
bucket: 'mybucket',
})
const memoize = persistentMemoize(store)
// Memoize fetch calls (fetch returns a promise)
// Cache will be invalidated when version in `package.json`
// is bumped OR when cache is expired (see maxAge option)
const memoizedFetch = memoize((...args) => (
fetch(...args)
.then((response) => {
// If an error is thrown, the funciton call won't be memoized
if (!response.ok) throw new Error('oops, problem with request')
return response.text()
})
), 'memoizedFetch')
// Functions can be versioned individually
const memoizeNoVersion = persistentMemoize(store, { version: null })
// Sync function. Be careful when memoizing a sync function as it
// doesn't create a drop in replacement because the memoized will
// version be async and returns a promise.
const expensiveComputation = memoizeNoVersion((i) => {
// some expensive computation :)
return i + 1
}, 'expensive-computation', { version: 'v1' })
See ./test for more examples.