diff --git a/README.md b/README.md index c001a22..bb8f79e 100644 --- a/README.md +++ b/README.md @@ -418,9 +418,9 @@ If provided, cacache will memoize the given cache insertion in memory, bypassing any filesystem checks for that key or digest in future cache fetches. Nothing will be written to the in-memory cache unless this option is explicitly truthy. -There is no facility for limiting memory usage short of -[`cacache.clearMemoized()`](#clear-memoized), so be mindful of the sort of data -you ask to get memoized! +If `opts.memoize` is an object or a `Map`-like (that is, an object with `get` +and `set` methods), it will be written to instead of the global memoization +cache. Reading from existing memoized data can be forced by explicitly passing `memoize: false` to the reader functions, but their default will be to read from diff --git a/get.js b/get.js index 19ed4d1..58adb8e 100644 --- a/get.js +++ b/get.js @@ -19,8 +19,8 @@ function getData (byDigest, cache, key, opts) { opts = opts || {} const memoized = ( byDigest - ? memo.get.byDigest(cache, key) - : memo.get(cache, key) + ? memo.get.byDigest(cache, key, opts) + : memo.get(cache, key, opts) ) if (memoized && opts.memoize !== false) { return BB.resolve(byDigest ? memoized : { @@ -46,9 +46,9 @@ function getData (byDigest, cache, key, opts) { integrity: entry.integrity }).then(res => { if (opts.memoize && byDigest) { - memo.put.byDigest(cache, key, res) + memo.put.byDigest(cache, key, res, opts) } else if (opts.memoize) { - memo.put(cache, entry, res.data) + memo.put(cache, entry, res.data, opts) } return res }) @@ -59,7 +59,7 @@ module.exports.stream = getStream function getStream (cache, key, opts) { opts = opts || {} let stream = through() - const memoized = memo.get(cache, key) + const memoized = memo.get(cache, key, opts) if (memoized && opts.memoize !== false) { stream.on('newListener', function (ev, cb) { ev === 'metadata' && cb(memoized.entry.metadata) @@ -84,7 +84,7 @@ function getStream (cache, key, opts) { memoLength += c.length cb(null, c, en) }, cb => { - memoData && memo.put(cache, entry, Buffer.concat(memoData, memoLength)) + memoData && memo.put(cache, entry, Buffer.concat(memoData, memoLength), opts) cb() }) } else { @@ -111,7 +111,7 @@ function getStream (cache, key, opts) { module.exports.stream.byDigest = getStreamDigest function getStreamDigest (cache, integrity, opts) { opts = opts || {} - const memoized = memo.get.byDigest(cache, integrity) + const memoized = memo.get.byDigest(cache, integrity, opts) if (memoized && opts.memoize !== false) { const stream = through() stream.write(memoized, () => stream.end()) @@ -129,7 +129,8 @@ function getStreamDigest (cache, integrity, opts) { memoData && memo.put.byDigest( cache, integrity, - Buffer.concat(memoData, memoLength) + Buffer.concat(memoData, memoLength), + opts ) cb() }) @@ -142,7 +143,7 @@ function getStreamDigest (cache, integrity, opts) { module.exports.info = info function info (cache, key, opts) { opts = opts || {} - const memoized = memo.get(cache, key) + const memoized = memo.get(cache, key, opts) if (memoized && opts.memoize !== false) { return BB.resolve(memoized.entry) } else { diff --git a/lib/memoization.js b/lib/memoization.js index abbab9b..92179c7 100644 --- a/lib/memoization.js +++ b/lib/memoization.js @@ -28,22 +28,42 @@ function clearMemoized () { } module.exports.put = put -function put (cache, entry, data) { - MEMOIZED.set(`key:${cache}:${entry.key}`, { entry, data }) - putDigest(cache, entry.integrity, data) +function put (cache, entry, data, opts) { + pickMem(opts).set(`key:${cache}:${entry.key}`, { entry, data }) + putDigest(cache, entry.integrity, data, opts) } module.exports.put.byDigest = putDigest -function putDigest (cache, integrity, data) { - MEMOIZED.set(`digest:${cache}:${integrity}`, data) +function putDigest (cache, integrity, data, opts) { + pickMem(opts).set(`digest:${cache}:${integrity}`, data) } module.exports.get = get -function get (cache, key) { - return MEMOIZED.get(`key:${cache}:${key}`) +function get (cache, key, opts) { + return pickMem(opts).get(`key:${cache}:${key}`) } module.exports.get.byDigest = getDigest -function getDigest (cache, integrity) { - return MEMOIZED.get(`digest:${cache}:${integrity}`) +function getDigest (cache, integrity, opts) { + return pickMem(opts).get(`digest:${cache}:${integrity}`) +} + +class ObjProxy { + constructor (obj) { + this.obj = obj + } + get (key) { return this.obj[key] } + set (key, val) { this.obj[key] = val } +} + +function pickMem (opts) { + if (!opts || !opts.memoize) { + return MEMOIZED + } else if (opts.memoize.get && opts.memoize.set) { + return opts.memoize + } else if (typeof opts.memoize === 'object') { + return new ObjProxy(opts.memoize) + } else { + return MEMOIZED + } } diff --git a/put.js b/put.js index 5f525d0..fe1293e 100644 --- a/put.js +++ b/put.js @@ -13,7 +13,7 @@ function putData (cache, key, data, opts) { opts.size = res.size return index.insert(cache, key, res.integrity, opts).then(entry => { if (opts.memoize) { - memo.put(cache, entry, data) + memo.put(cache, entry, data, opts) } return res.integrity }) @@ -49,7 +49,7 @@ function putStream (cache, key, opts) { opts.size = size index.insert(cache, key, integrity, opts).then(entry => { if (opts.memoize) { - memo.put(cache, entry, Buffer.concat(memoData, memoTotal)) + memo.put(cache, entry, Buffer.concat(memoData, memoTotal), opts) } stream.emit('integrity', integrity) cb() diff --git a/test/memoization.js b/test/memoization.js index e6ef1f8..1a6c8f6 100644 --- a/test/memoization.js +++ b/test/memoization.js @@ -66,3 +66,96 @@ test('can clear out the memoization cache', t => { ) t.done() }) + +test('accepts optional injected cache', t => { + memo.clearMemoized() + const MEMO = new Map() + memo.put(CACHE, ENTRY, DATA, {memoize: MEMO}) + t.deepEqual( + memo.get(CACHE, ENTRY.key), + null, + 'entry not in global memo cache' + ) + t.deepEqual( + memo.get(CACHE, ENTRY.key, {memoize: MEMO}), + {entry: ENTRY, data: DATA}, + 'entry fetched from injected memoizer' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: MEMO}), + DATA, + 'content entry fetched from injected memoizer' + ) + t.deepEqual( + MEMO.get(`key:${CACHE}:${ENTRY.key}`), + {entry: ENTRY, data: DATA}, + 'entry is in the injected memoizer' + ) + t.deepEqual( + MEMO.get(`digest:${CACHE}:${ENTRY.integrity}`), + DATA, + 'content entry is in the injected memoizer' + ) + MEMO.clear() + t.deepEqual( + memo.get(CACHE, ENTRY.key, {memoize: MEMO}), + null, + 'tried to read from cleared memoizer' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: MEMO}), + null, + 'tried to read by digest from cleared memoizer' + ) + memo.put.byDigest(CACHE, ENTRY.integrity, DATA, {memoize: MEMO}) + t.deepEqual( + MEMO.get(`digest:${CACHE}:${ENTRY.integrity}`), + DATA, + 'content entry is in the injected memoizer' + ) + const obj = {} + memo.put(CACHE, ENTRY, DATA, {memoize: obj}) + t.deepEqual( + memo.get(CACHE, ENTRY.key, {memoize: obj}), + {entry: ENTRY, data: DATA}, + 'entry fetched from injected object memoizer' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: MEMO}), + DATA, + 'content entry fetched from injected object memoizer' + ) + memo.clearMemoized() + memo.put(CACHE, ENTRY, DATA, {memoize: 'foo'}) + t.deepEqual( + memo.get(CACHE, ENTRY.key, {memoize: 'foo'}), + {entry: ENTRY, data: DATA}, + 'entry fetched from global memoization obj on non-obj option' + ) + t.deepEqual( + memo.get(CACHE, ENTRY.key, {memoize: 'foo'}), + {entry: ENTRY, data: DATA}, + 'entry fetched from global memoization obj on non-obj option' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: 'foo'}), + DATA, + 'content entry fetched global memoizer obj on non-obj option' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: 'foo'}), + DATA, + 'content entry fetched global memoizer obj on non-obj option' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: false}), + DATA, + 'content entry fetched global memoizer obj on non-obj option' + ) + t.deepEqual( + memo.get.byDigest(CACHE, ENTRY.integrity, {memoize: false}), + DATA, + 'content entry fetched global memoizer obj on non-obj option' + ) + t.done() +})