Skip to content

Commit

Permalink
feat: store etag (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kikobeats authored Mar 24, 2024
1 parent 6659425 commit f1aaef9
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 29 deletions.
13 changes: 9 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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

Expand Down
89 changes: 89 additions & 0 deletions test/etag.js
Original file line number Diff line number Diff line change
@@ -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)
})
25 changes: 0 additions & 25 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit f1aaef9

Please sign in to comment.