From 73f5ecbf62c292d42bdd30370a0b4b25898af022 Mon Sep 17 00:00:00 2001 From: Kiko Beats Date: Sun, 24 Mar 2024 22:06:23 +0100 Subject: [PATCH] feat: automatically decorate etag --- src/index.js | 13 +++++--- test/etag.js | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/index.js | 25 --------------- 3 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 test/etag.js diff --git a/src/index.js b/src/index.js index 0123fba..9593b00 100644 --- a/src/index.js +++ b/src/index.js @@ -13,14 +13,14 @@ const cacheableResponse = ({ bypassQueryParameter = 'force', cache = new Keyv({ namespace: 'ssr' }), compress: enableCompression = false, - get, + get: rawGet, key: getKey = createKey(bypassQueryParameter), send, staleTtl: rawStaleTtl = 3600000, ttl: rawTtl = 86400000, ...compressOpts } = {}) => { - assert(get, '.get required') + assert(rawGet, '.get required') assert(send, '.send required') const staleTtl = isFunction(rawStaleTtl) @@ -34,6 +34,12 @@ const cacheableResponse = ({ ...compressOpts }) + const get = opts => Promise.resolve(rawGet(opts)).then(result => { + if (typeof result !== 'object') return result + result.etag = getEtag(serialize(result)) + return result + }) + const memoGet = memoize(get, cache, { key: getKey, objectMode: true, @@ -55,13 +61,12 @@ const cacheableResponse = ({ const { createdAt = Date.now(), data = null, - etag: cachedEtag, + etag = getEtag(serialize(result)), staleTtl = memoGet.staleTtl(result), ttl = memoGet.ttl(result), ...props } = result - const etag = cachedEtag || getEtag(serialize(result)) const ifNoneMatch = req.headers['if-none-match'] const isModified = etag !== ifNoneMatch diff --git a/test/etag.js b/test/etag.js new file mode 100644 index 0000000..2d0acea --- /dev/null +++ b/test/etag.js @@ -0,0 +1,89 @@ +'use strict' + +const Keyv = require('@keyvhq/core') +const test = require('ava') +const got = require('got') + +const cacheableResponse = require('..') +const { runServer } = require('./helpers') + +test('save etag in cache when is possible', async t => { + const cache = new Keyv() + const url = await runServer( + t, + cacheableResponse({ + cache, + staleTtl: false, + get: () => ({ + data: { foo: 'bar' }, + ttl: 30000, + createdAt: Date.now(), + foo: { bar: true } + }), + send: ({ data, headers, res, req, ...props }) => { + res.end('Hello World') + } + }) + ) + await got(`${url}/kikobeats`) + const { etag } = await cache.get('/') + t.truthy(etag) +}) + +test('etag is present', async t => { + const url = await runServer( + t, + cacheableResponse({ + staleTtl: false, + get: ({ req, res }) => { + return { + data: { foo: 'bar' }, + ttl: 30000, + createdAt: Date.now(), + foo: { bar: true } + } + }, + send: ({ data, headers, res, req, ...props }) => { + res.end('Hello World') + } + }) + ) + const { headers: headersOne } = await got(`${url}/kikobeats`) + t.is(headersOne['x-cache-status'], 'MISS') + const { headers: headersTwo } = await got(`${url}/kikobeats`) + t.is(headersTwo['x-cache-status'], 'HIT') + t.is(headersOne.etag, headersTwo.etag) +}) + +test('etag is different', async t => { + const url = await runServer( + t, + cacheableResponse({ + staleTtl: false, + key: ({ req }) => [req.url, req.url.includes('force')], + get: ({ req, res }) => { + return { + data: { foo: 'bar' }, + ttl: 30000, + createdAt: Date.now(), + foo: { bar: true } + } + }, + send: ({ data, headers, res, req }) => { + res.end('Hello World') + } + }) + ) + const { headers: headersOne } = await got(`${url}/kikobeats`) + const etagOne = headersOne.etag + + const { headers: headersTwo } = await got(`${url}/kikobeats`) + const etagTwo = headersTwo.etag + + t.is(etagOne, etagTwo) + + const { headers: headersThree } = await got(`${url}/kikobeats&force`) + const etagThree = headersThree.etag + + t.not(etagOne, etagThree) +}) diff --git a/test/index.js b/test/index.js index 0d3eddf..153fb83 100644 --- a/test/index.js +++ b/test/index.js @@ -6,31 +6,6 @@ const got = require('got') const cacheableResponse = require('..') const { runServer } = require('./helpers') -test('etag is present', async t => { - const url = await runServer( - t, - cacheableResponse({ - staleTtl: false, - get: ({ req, res }) => { - return { - data: { foo: 'bar' }, - ttl: 30000, - createdAt: Date.now(), - foo: { bar: true } - } - }, - send: ({ data, headers, res, req, ...props }) => { - res.end('Hello World') - } - }) - ) - const { headers: headersOne } = await got(`${url}/kikobeats`) - t.is(headersOne['x-cache-status'], 'MISS') - const { headers: headersTwo } = await got(`${url}/kikobeats`) - t.is(headersTwo['x-cache-status'], 'HIT') - t.is(headersOne.etag, headersTwo.etag) -}) - test('compress support', async t => { const url = await runServer( t,