diff --git a/index.js b/index.js index 54b13d8..0cbe93d 100644 --- a/index.js +++ b/index.js @@ -191,6 +191,9 @@ module.exports = class Hypercore extends EventEmitter { s._passCapabilities(this) + // Pass on the cache unless explicitly disabled. + if (opts.cache !== false) s.cache = this.cache + ensureEncryption(s, opts) this.sessions.push(s) @@ -580,12 +583,10 @@ module.exports = class Hypercore extends EventEmitter { if (this.closing !== null) throw SESSION_CLOSED() if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE() - const c = this.cache && this.cache.get(index) - if (c) return c - const fork = this.core.tree.fork - const b = await this._get(index, opts) - if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b) - return b + const block = this.cache && this.cache.get(index) + if (block) return block + + return this._get(index, opts) } async _get (index, opts) { @@ -594,19 +595,45 @@ module.exports = class Hypercore extends EventEmitter { let block if (this.core.bitfield.get(index)) { - block = await this.core.blocks.get(index) + block = this._decryptAndDecode( + index, + this.core.tree.fork, + this.core.blocks.get(index), + encoding + ) + + if (this.cache) this.cache.set(index, block) } else { if (opts && opts.wait === false) return null if (opts && opts.onwait) opts.onwait(index) const activeRequests = (opts && opts.activeRequests) || this.activeRequests - const req = this.replicator.addBlock(activeRequests, index) - block = await req.promise + block = this._decryptAndDecode( + index, + this.core.tree.fork, + this.replicator + .addBlock(activeRequests, index) + .promise, + encoding + ) } + return block + } + + async _decryptAndDecode (index, fork, req, encoding) { + const block = await req + if (this.encryption) this.encryption.decrypt(index, block) - return this._decode(encoding, block) + + const value = this._decode(encoding, block) + + if (this.cache && fork === this.core.tree.fork) { + this.cache.set(index, Promise.resolve(value)) + } + + return value } createReadStream (opts) { @@ -758,7 +785,7 @@ module.exports = class Hypercore extends EventEmitter { } _decode (enc, block) { - block = block.subarray(this.padding) + if (this.padding) block = block.subarray(this.padding) if (enc) return c.decode(enc, block) return block } diff --git a/test/cache.js b/test/cache.js new file mode 100644 index 0000000..5967e9d --- /dev/null +++ b/test/cache.js @@ -0,0 +1,71 @@ +const test = require('brittle') +const { create, replicate } = require('./helpers') + +test('cache', async function (t) { + const a = await create({ cache: true }) + await a.append(['a', 'b', 'c']) + + const p = a.get(0) + const q = a.get(0) + + t.is(await p, await q, 'blocks are identical') +}) + +test('session cache', async function (t) { + const a = await create({ cache: true }) + await a.append(['a', 'b', 'c']) + + const s = a.session() + + const p = a.get(0) + const q = s.get(0) + + t.is(await p, await q, 'blocks are identical') +}) + +test('session cache opt-out', async function (t) { + const a = await create({ cache: true }) + await a.append(['a', 'b', 'c']) + + const s = a.session({ cache: false }) + + const p = a.get(0) + const q = s.get(0) + + t.not(await p, await q, 'blocks are not identical') +}) + +test('clear cache on truncate', async function (t) { + const a = await create({ cache: true }) + await a.append(['a', 'b', 'c']) + + const p = a.get(0) + + await a.truncate(0) + await a.append('d') + + const q = a.get(0) + + t.alike(await p, Buffer.from('a')) + t.alike(await q, Buffer.from('d')) +}) + +test('cache on replicate', async function (t) { + const a = await create() + await a.append(['a', 'b', 'c']) + + const b = await create(a.key, { cache: true }) + + replicate(a, b, t) + + // These will issue a replicator request + const p = b.get(0) + const q = b.get(0) + + t.is(await p, await q, 'blocks are identical') + + // This should use the cache + const r = b.get(0) + + t.is(await p, await r, 'blocks are identical') +})