Skip to content

Commit f1aaef9

Browse files
authored
feat: store etag (#115)
1 parent 6659425 commit f1aaef9

3 files changed

Lines changed: 98 additions & 29 deletions

File tree

src/index.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ const cacheableResponse = ({
1313
bypassQueryParameter = 'force',
1414
cache = new Keyv({ namespace: 'ssr' }),
1515
compress: enableCompression = false,
16-
get,
16+
get: rawGet,
1717
key: getKey = createKey(bypassQueryParameter),
1818
send,
1919
staleTtl: rawStaleTtl = 3600000,
2020
ttl: rawTtl = 86400000,
2121
...compressOpts
2222
} = {}) => {
23-
assert(get, '.get required')
23+
assert(rawGet, '.get required')
2424
assert(send, '.send required')
2525

2626
const staleTtl = isFunction(rawStaleTtl)
@@ -34,6 +34,12 @@ const cacheableResponse = ({
3434
...compressOpts
3535
})
3636

37+
const get = opts => Promise.resolve(rawGet(opts)).then(result => {
38+
if (typeof result !== 'object') return result
39+
result.etag = getEtag(serialize(result))
40+
return result
41+
})
42+
3743
const memoGet = memoize(get, cache, {
3844
key: getKey,
3945
objectMode: true,
@@ -55,13 +61,12 @@ const cacheableResponse = ({
5561
const {
5662
createdAt = Date.now(),
5763
data = null,
58-
etag: cachedEtag,
64+
etag = getEtag(serialize(result)),
5965
staleTtl = memoGet.staleTtl(result),
6066
ttl = memoGet.ttl(result),
6167
...props
6268
} = result
6369

64-
const etag = cachedEtag || getEtag(serialize(result))
6570
const ifNoneMatch = req.headers['if-none-match']
6671
const isModified = etag !== ifNoneMatch
6772

test/etag.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict'
2+
3+
const Keyv = require('@keyvhq/core')
4+
const test = require('ava')
5+
const got = require('got')
6+
7+
const cacheableResponse = require('..')
8+
const { runServer } = require('./helpers')
9+
10+
test('save etag in cache when is possible', async t => {
11+
const cache = new Keyv()
12+
const url = await runServer(
13+
t,
14+
cacheableResponse({
15+
cache,
16+
staleTtl: false,
17+
get: () => ({
18+
data: { foo: 'bar' },
19+
ttl: 30000,
20+
createdAt: Date.now(),
21+
foo: { bar: true }
22+
}),
23+
send: ({ data, headers, res, req, ...props }) => {
24+
res.end('Hello World')
25+
}
26+
})
27+
)
28+
await got(`${url}/kikobeats`)
29+
const { etag } = await cache.get('/')
30+
t.truthy(etag)
31+
})
32+
33+
test('etag is present', async t => {
34+
const url = await runServer(
35+
t,
36+
cacheableResponse({
37+
staleTtl: false,
38+
get: ({ req, res }) => {
39+
return {
40+
data: { foo: 'bar' },
41+
ttl: 30000,
42+
createdAt: Date.now(),
43+
foo: { bar: true }
44+
}
45+
},
46+
send: ({ data, headers, res, req, ...props }) => {
47+
res.end('Hello World')
48+
}
49+
})
50+
)
51+
const { headers: headersOne } = await got(`${url}/kikobeats`)
52+
t.is(headersOne['x-cache-status'], 'MISS')
53+
const { headers: headersTwo } = await got(`${url}/kikobeats`)
54+
t.is(headersTwo['x-cache-status'], 'HIT')
55+
t.is(headersOne.etag, headersTwo.etag)
56+
})
57+
58+
test('etag is different', async t => {
59+
const url = await runServer(
60+
t,
61+
cacheableResponse({
62+
staleTtl: false,
63+
key: ({ req }) => [req.url, req.url.includes('force')],
64+
get: ({ req, res }) => {
65+
return {
66+
data: { foo: 'bar' },
67+
ttl: 30000,
68+
createdAt: Date.now(),
69+
foo: { bar: true }
70+
}
71+
},
72+
send: ({ data, headers, res, req }) => {
73+
res.end('Hello World')
74+
}
75+
})
76+
)
77+
const { headers: headersOne } = await got(`${url}/kikobeats`)
78+
const etagOne = headersOne.etag
79+
80+
const { headers: headersTwo } = await got(`${url}/kikobeats`)
81+
const etagTwo = headersTwo.etag
82+
83+
t.is(etagOne, etagTwo)
84+
85+
const { headers: headersThree } = await got(`${url}/kikobeats&force`)
86+
const etagThree = headersThree.etag
87+
88+
t.not(etagOne, etagThree)
89+
})

test/index.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,6 @@ const got = require('got')
66
const cacheableResponse = require('..')
77
const { runServer } = require('./helpers')
88

9-
test('etag is present', async t => {
10-
const url = await runServer(
11-
t,
12-
cacheableResponse({
13-
staleTtl: false,
14-
get: ({ req, res }) => {
15-
return {
16-
data: { foo: 'bar' },
17-
ttl: 30000,
18-
createdAt: Date.now(),
19-
foo: { bar: true }
20-
}
21-
},
22-
send: ({ data, headers, res, req, ...props }) => {
23-
res.end('Hello World')
24-
}
25-
})
26-
)
27-
const { headers: headersOne } = await got(`${url}/kikobeats`)
28-
t.is(headersOne['x-cache-status'], 'MISS')
29-
const { headers: headersTwo } = await got(`${url}/kikobeats`)
30-
t.is(headersTwo['x-cache-status'], 'HIT')
31-
t.is(headersOne.etag, headersTwo.etag)
32-
})
33-
349
test('compress support', async t => {
3510
const url = await runServer(
3611
t,

0 commit comments

Comments
 (0)