Skip to content

Commit 7bc5fc6

Browse files
committed
initial commit
1 parent ea7fd0c commit 7bc5fc6

File tree

4 files changed

+2573
-0
lines changed

4 files changed

+2573
-0
lines changed

get-keys.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const matcher = require('matcher')
2+
3+
const getKeys = (cache, pattern) => new Promise((resolve) => {
4+
if (pattern.indexOf('*') > -1) {
5+
const args = [pattern, (_, res) => resolve(matcher(res, [pattern]))]
6+
if (cache.store.name !== 'redis') {
7+
args.shift()
8+
}
9+
10+
cache.keys.apply(cache, args)
11+
} else resolve([pattern])
12+
})
13+
14+
module.exports = getKeys

index.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const CacheManager = require('cache-manager')
2+
const iu = require('middleware-if-unless')
3+
const ms = require('ms')
4+
const onEnd = require('on-http-end')
5+
const getKeys = require('./get-keys')
6+
7+
const X_CACHE_EXPIRE = 'x-cache-expire'
8+
const X_CACHE_TIMEOUT = 'x-cache-timeout'
9+
const X_CACHE_HIT = 'x-cache-hit'
10+
11+
const middleware = (opts) => async (req, res, next) => {
12+
opts = Object.assign({
13+
stores: [CacheManager.caching({ store: 'memory', max: 1000, ttl: 30 })]
14+
}, opts)
15+
16+
// creating multi-cache instance
17+
const mcache = CacheManager.multiCaching(opts.stores)
18+
19+
if (req.cacheDisabled) return
20+
21+
let { url, cacheAppendKey = req => '' } = req
22+
cacheAppendKey = await cacheAppendKey(req)
23+
24+
const key = req.method + url + cacheAppendKey
25+
// ref cache key on req object
26+
req.cacheKey = key
27+
28+
// try to retrieve cached response
29+
const cached = await get(mcache, key)
30+
31+
if (cached) {
32+
// respond from cache if there is a hit
33+
let { status, headers, data } = JSON.parse(cached)
34+
if (typeof data === 'object' && data.type === 'Buffer') {
35+
data = Buffer.from(data.data)
36+
}
37+
headers[X_CACHE_HIT] = '1'
38+
39+
// set cached response headers
40+
Object.keys(headers).forEach(header => res.setHeader(header, headers[header]))
41+
42+
// send cached payload
43+
req.cacheHit = true
44+
res.statusCode = status
45+
res.end(data)
46+
47+
return
48+
}
49+
50+
onEnd(res, (payload) => {
51+
// avoid double caching
52+
if (req.cacheHit) return
53+
54+
if (payload.headers[X_CACHE_EXPIRE]) {
55+
// support service level expiration
56+
const keysPattern = payload.headers[X_CACHE_EXPIRE]
57+
// delete keys on all cache tiers
58+
opts.stores.forEach(cache => getKeys(cache, keysPattern).then(keys => mcache.del(keys)))
59+
} else if (payload.headers[X_CACHE_TIMEOUT]) {
60+
// we need to cache response
61+
mcache.set(req.cacheKey, JSON.stringify(payload), {
62+
// @NOTE: cache-manager uses seconds as TTL unit
63+
// restrict to min value "1 second"
64+
ttl: Math.max(ms(payload.headers[X_CACHE_TIMEOUT]), 1000) / 1000
65+
})
66+
}
67+
})
68+
69+
return next()
70+
}
71+
72+
const get = (cache, key) => new Promise((resolve) => {
73+
cache.getAndPassUp(key, (_, res) => {
74+
resolve(res)
75+
})
76+
})
77+
78+
module.exports = iu(middleware)

0 commit comments

Comments
 (0)