koa-cash 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,55 +10,70 @@
10
10
 
11
11
  > HTTP response caching for Koa. Supports Redis, in-memory store, and more!
12
12
 
13
-
14
- ## Table of Contents
15
-
16
- * [Features](#features)
17
- * [Install](#install)
18
- * [Usage](#usage)
19
- * [API](#api)
20
- * [app.use(koaCash(options))](#appusekoacashoptions)
21
- * [const cached = await ctx.cashed(\[maxAge\])](#const-cached--await-ctxcashedmaxage)
22
- * [Notes](#notes)
23
- * [Usage](#usage-1)
24
- * [Contributors](#contributors)
25
- * [License](#license)
26
-
13
+ Table of Contents
14
+
15
+ * [koa-cash](#koa-cash)
16
+ * [Features](#features)
17
+ * [Install](#install)
18
+ * [Usage](#usage)
19
+ * [API](#api)
20
+ * [app.use(koaCash(options))](#appusekoacashoptions)
21
+ * [`maxAge`](#maxage)
22
+ * [`threshold`](#threshold)
23
+ * [`compression`](#compression)
24
+ * [`setCachedHeader`](#setcachedheader)
25
+ * [`methods`](#methods)
26
+ * [`hash()`](#hash)
27
+ * [`get()`](#get)
28
+ * [`set()`](#set)
29
+ * [Example](#example)
30
+ * [Max age](#max-age)
31
+ * [Notes](#notes)
32
+ * [Usage](#usage-1)
33
+ * [Contributors](#contributors)
34
+ * [License](#license)
35
+ * [Links](#links)
27
36
 
28
37
  ## Features
29
38
 
30
39
  Caches the response based on any arbitrary store you'd like.
31
40
 
32
41
  * Handles JSON and stream bodies
33
- * Handles gzip compression negotiation
42
+ * Handles gzip compression negotiation (if `options.compression` is set to `true` as of v4.0.0)
34
43
  * Handles 304 responses
35
44
 
36
45
  :tada: **Pairs great with [@ladjs/koa-cache-responses](https://github.com/ladjs/koa-cache-responses)** :tada:
37
46
 
38
-
39
47
  ## Install
40
48
 
41
- [npm][]:
49
+ [NPM](https://www.npmjs.com/)
42
50
 
43
51
  ```sh
44
52
  npm install koa-cash
45
53
  ```
46
54
 
47
- [yarn][]:
55
+ [Yarn](https://yarnpkg.com/)
48
56
 
49
57
  ```sh
50
58
  yarn add koa-cash
51
59
  ```
52
60
 
53
-
54
61
  ## Usage
55
62
 
56
63
  ```js
57
- const koaCash = require('koa-cash');
64
+ import LRU from 'lru-cache';
65
+ import koaCash from 'koa-cash';
58
66
 
59
67
  // ...
60
-
61
- app.use(koaCash())
68
+ const cache = new LRU();
69
+ app.use(koaCash({
70
+ get: (key) => {
71
+ return cache.get(key);
72
+ },
73
+ set(key, value) {
74
+ return cache.set(key, value);
75
+ },
76
+ }))
62
77
 
63
78
  app.use(async ctx => {
64
79
  // this response is already cashed if `true` is returned,
@@ -71,7 +86,6 @@ app.use(async ctx => {
71
86
  });
72
87
  ```
73
88
 
74
-
75
89
  ## API
76
90
 
77
91
  ### app.use(koaCash(options))
@@ -86,6 +100,20 @@ Default max age (in milliseconds) for the cache if not set via `await ctx.cashed
86
100
 
87
101
  Minimum byte size to compress response bodies. Default `1kb`.
88
102
 
103
+ #### `compression`
104
+
105
+ If a truthy value is passed, then compression will be enabled. This value is `false` by default.
106
+
107
+ #### `setCachedHeader`
108
+
109
+ If a truthy value is passed, then `X-Cached-Response` header will be set as `HIT` when response is served from the cache. This value is `false` by default.
110
+
111
+ #### `methods`
112
+
113
+ If an object is passed, then add extra HTTP method caching. This value is empty by default. But `GET` and `HEAD` are enabled.
114
+
115
+ Eg: `{ POST: true }`
116
+
89
117
  #### `hash()`
90
118
 
91
119
  A hashing function. By default, it's:
@@ -146,7 +174,9 @@ app.use(koaCash({
146
174
 
147
175
  See [@ladjs/koa-cache-responses](https://github.com/ladjs/koa-cache-responses) test folder more examples (e.g. Redis with `ioredis`).
148
176
 
149
- ### const cached = await ctx.cashed(\[maxAge])
177
+ ### Max age
178
+
179
+ const cached = await ctx.cashed(\[maxAge])
150
180
 
151
181
  This is how you enable a route to be cached. If you don't call `await ctx.cashed()`, then this route will not be cached nor will it attempt to serve the request from the cache.
152
182
 
@@ -154,17 +184,14 @@ This is how you enable a route to be cached. If you don't call `await ctx.cashed
154
184
 
155
185
  If `cached` is `true`, then the current request has been served from cache and **you should early `return`**. Otherwise, continue setting `ctx.body=` and this will cache the response.
156
186
 
157
-
158
187
  ## Notes
159
188
 
160
189
  * Only `GET` and `HEAD` requests are cached.
161
190
  * Only `200` responses are cached. Don't set `304` status codes on these routes - this middleware will handle it for you
162
191
  * The underlying store should be able to handle `Date` objects as well as `Buffer` objects. Otherwise, you may have to serialize/deserialize yourself.
163
192
 
164
-
165
193
  ## Usage
166
194
 
167
-
168
195
  ## Contributors
169
196
 
170
197
  | Name | Website |
@@ -172,14 +199,11 @@ If `cached` is `true`, then the current request has been served from cache and *
172
199
  | **Jonathan Ong** | <http://jongleberry.com> |
173
200
  | **Nick Baugh** | <http://niftylettuce.com> |
174
201
 
175
-
176
202
  ## License
177
203
 
178
204
  [MIT](LICENSE) © [Jonathan Ong](http://jongleberry.com)
179
205
 
206
+ ## Links
180
207
 
181
- ##
182
-
183
- [npm]: https://www.npmjs.com/
184
-
185
- [yarn]: https://yarnpkg.com/
208
+ * [NPM](https://www.npmjs.com/)
209
+ * [Yarn](https://yarnpkg.com/)
package/index.js CHANGED
@@ -11,13 +11,15 @@ const safeStringify = require('fast-safe-stringify');
11
11
  const compress = promisify(gzip);
12
12
 
13
13
  // methods we cache
14
- const methods = {
14
+ const defaultMethods = {
15
15
  HEAD: true,
16
16
  GET: true
17
17
  };
18
18
 
19
19
  module.exports = function(options) {
20
- options = options || {};
20
+ options = options || { compression: false, setCachedHeader: false };
21
+
22
+ const methods = Object.assign(defaultMethods, options.methods);
21
23
 
22
24
  const hash =
23
25
  options.hash ||
@@ -53,12 +55,14 @@ module.exports = function(options) {
53
55
  this.response.type = obj.type;
54
56
  if (obj.lastModified) this.response.lastModified = obj.lastModified;
55
57
  if (obj.etag) this.response.etag = obj.etag;
58
+ if (options.setCachedHeader) this.response.set('X-Cached-Response', 'HIT');
56
59
  if (this.request.fresh) {
57
60
  this.response.status = 304;
58
61
  return true;
59
62
  }
60
63
 
61
64
  if (
65
+ options.compression &&
62
66
  obj.gzip &&
63
67
  this.request.acceptsEncodings('gzip', 'identity') === 'gzip'
64
68
  ) {
@@ -67,7 +71,9 @@ module.exports = function(options) {
67
71
  } else {
68
72
  this.response.body = obj.body;
69
73
  // tell any compress middleware to not bother compressing this
70
- this.response.set('Content-Encoding', 'identity');
74
+ if (options.compression) {
75
+ this.response.set('Content-Encoding', 'identity');
76
+ }
71
77
  }
72
78
 
73
79
  return true;
@@ -120,7 +126,11 @@ module.exports = function(options) {
120
126
  const { fresh } = ctx.request;
121
127
  if (fresh) ctx.response.status = 304;
122
128
 
123
- if (compressible(obj.type) && ctx.response.length >= threshold) {
129
+ if (
130
+ options.compression &&
131
+ compressible(obj.type) &&
132
+ ctx.response.length >= threshold
133
+ ) {
124
134
  obj.gzip = await compress(body);
125
135
  if (
126
136
  !fresh &&
@@ -131,7 +141,7 @@ module.exports = function(options) {
131
141
  }
132
142
  }
133
143
 
134
- if (!ctx.response.get('Content-Encoding'))
144
+ if (options.compression && !ctx.response.get('Content-Encoding'))
135
145
  ctx.response.set('Content-Encoding', 'identity');
136
146
 
137
147
  await set(ctx.cashKey, obj, ctx.cash.maxAge || options.maxAge || 0);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koa-cash",
3
3
  "description": "HTTP response caching for Koa. HTTP response caching for Koa. Supports Redis, in-memory store, and more!",
4
- "version": "4.0.1",
4
+ "version": "4.1.0",
5
5
  "author": "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)",
6
6
  "ava": {
7
7
  "verbose": true,
@@ -52,6 +52,9 @@
52
52
  "engines": {
53
53
  "node": ">=8.3"
54
54
  },
55
+ "files": [
56
+ "index.js"
57
+ ],
55
58
  "homepage": "https://github.com/koajs/cash",
56
59
  "husky": {
57
60
  "hooks": {
package/.editorconfig DELETED
@@ -1,9 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- indent_style = space
5
- indent_size = 2
6
- end_of_line = lf
7
- charset = utf-8
8
- trim_trailing_whitespace = true
9
- insert_final_newline = true
package/.lintstagedrc.js DELETED
@@ -1,7 +0,0 @@
1
- module.exports = {
2
- "*.md,!test/**/*.md": [
3
- filenames => filenames.map(filename => `remark ${filename} -qfo`)
4
- ],
5
- 'package.json': 'fixpack',
6
- '*.js': 'xo --fix'
7
- };
package/.remarkignore DELETED
@@ -1 +0,0 @@
1
- test/snapshots/**/*.md
package/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - '12'
4
- - 'lts/*'
5
- - 'node'
6
- script:
7
- npm run test-coverage
8
- after_success:
9
- npm run coverage
@@ -1,85 +0,0 @@
1
- const LRU = require('lru-cache');
2
- const Koa = require('koa');
3
- const request = require('supertest');
4
- const test = require('ava');
5
-
6
- const cash = require('..');
7
-
8
- const createApp = function(c, opts) {
9
- const app = new Koa();
10
- app.use(
11
- cash(
12
- opts || {
13
- get(key) {
14
- return c.get(key);
15
- },
16
- set(key, value) {
17
- return c.set(key, value);
18
- }
19
- }
20
- )
21
- );
22
- return app;
23
- };
24
-
25
- const c = new LRU();
26
- const date = Math.round(Date.now() / 1000);
27
-
28
- test.before.cb(t => {
29
- const app = createApp(c);
30
- app.use(async function(ctx) {
31
- if (await ctx.cashed()) return;
32
- ctx.body = 'lol';
33
- ctx.etag = 'lol';
34
- ctx.type = 'text/lol; charset=utf-8';
35
- ctx.lastModified = new Date(date * 1000);
36
- });
37
-
38
- request(app.listen())
39
- .get('/')
40
- .expect(200, t.end);
41
- });
42
-
43
- test.cb('when cached when the method is GET it should serve from cache', t => {
44
- const app = createApp(c);
45
- app.use(async function(ctx) {
46
- if (await ctx.cashed()) return;
47
- throw new Error('wtf');
48
- });
49
-
50
- request(app.listen())
51
- .get('/')
52
- .expect(200)
53
- .expect('Content-Type', 'text/lol; charset=utf-8')
54
- .expect('Content-Encoding', 'identity')
55
- .expect('ETag', '"lol"')
56
- .expect('lol', t.end);
57
- });
58
-
59
- test.cb(
60
- 'when cached when the method is POST it should not serve from cache',
61
- t => {
62
- const app = createApp(c);
63
- app.use(async function(ctx) {
64
- if (await ctx.cashed()) throw new Error('wtf');
65
- ctx.body = 'lol';
66
- });
67
-
68
- request(app.listen())
69
- .post('/')
70
- .expect(200, t.end);
71
- }
72
- );
73
-
74
- test.cb('when cached when the response is fresh it should 304', t => {
75
- const app = createApp(c);
76
- app.use(async function(ctx) {
77
- if (await ctx.cashed()) return;
78
- throw new Error('wtf');
79
- });
80
-
81
- request(app.listen())
82
- .get('/')
83
- .set('If-None-Match', '"lol"')
84
- .expect(304, t.end);
85
- });
@@ -1,373 +0,0 @@
1
- const Koa = require('koa');
2
- const LRU = require('lru-cache');
3
- const intoStream = require('into-stream');
4
- const request = require('supertest');
5
- const test = require('ava');
6
-
7
- const cash = require('..');
8
-
9
- const createApp = function(c, opts) {
10
- const app = new Koa();
11
- app.use(
12
- cash(
13
- opts || {
14
- get(key) {
15
- return c.get(key);
16
- },
17
- set(key, value) {
18
- return c.set(key, value);
19
- }
20
- }
21
- )
22
- );
23
- return app;
24
- };
25
-
26
- test.cb('should pass the maxAge through ctx.cash=', t => {
27
- let set = false;
28
-
29
- const c = new LRU();
30
- const app = createApp(c, {
31
- get(key) {
32
- return c.get(key);
33
- },
34
- set(key, value, maxAge) {
35
- set = true;
36
- t.is(maxAge, 300);
37
- return c.set(key, value);
38
- }
39
- });
40
-
41
- app.use(async function(ctx) {
42
- if (await ctx.cashed()) return;
43
- ctx.cash = {
44
- maxAge: 300
45
- };
46
- ctx.body = 'lol';
47
- });
48
-
49
- request(app.listen())
50
- .get('/')
51
- .expect(200)
52
- .expect('lol', err => {
53
- if (err) return t.end(err);
54
-
55
- t.truthy(set);
56
- t.is(c.get('/').body, 'lol');
57
- t.end();
58
- });
59
- });
60
-
61
- test.cb('when body is a string it should cache the response', t => {
62
- const c = new LRU();
63
- const app = createApp(c);
64
- app.use(async function(ctx) {
65
- if (await ctx.cashed()) return;
66
- ctx.body = 'lol';
67
- });
68
-
69
- request(app.listen())
70
- .get('/')
71
- .expect(200)
72
- .expect('lol', err => {
73
- if (err) return t.end(err);
74
-
75
- t.is(c.get('/').body, 'lol');
76
- t.end();
77
- });
78
- });
79
-
80
- test.cb('when the body is a buffer it should cache the response', t => {
81
- const c = new LRU();
82
- const app = createApp(c);
83
- app.use(async function(ctx) {
84
- if (await ctx.cashed()) return;
85
- ctx.body = Buffer.from('lol');
86
- });
87
-
88
- request(app.listen())
89
- .get('/')
90
- .expect(200)
91
- .expect('lol', err => {
92
- if (err) return t.end(err);
93
-
94
- t.is(c.get('/').body.toString('utf8'), 'lol');
95
- t.end();
96
- });
97
- });
98
-
99
- test.cb('when the body is JSON it should cache the response', t => {
100
- const c = new LRU();
101
- const app = createApp(c);
102
- app.use(async function(ctx) {
103
- if (await ctx.cashed()) return;
104
- ctx.body = {
105
- message: 'hi'
106
- };
107
- });
108
-
109
- request(app.listen())
110
- .get('/')
111
- .expect(200)
112
- .expect('{"message":"hi"}', err => {
113
- if (err) return t.end(err);
114
-
115
- t.is(c.get('/').body, '{"message":"hi"}');
116
- t.end();
117
- });
118
- });
119
-
120
- test.cb('when the body is a stream it should cache the response', t => {
121
- const c = new LRU();
122
- const app = createApp(c);
123
- app.use(async function(ctx) {
124
- if (await ctx.cashed()) return;
125
- ctx.body = intoStream('lol');
126
- });
127
-
128
- request(app.listen())
129
- .get('/')
130
- .expect(200)
131
- .expect('lol', err => {
132
- if (err) return t.end(err);
133
-
134
- t.is(c.get('/').body.toString('utf8'), 'lol');
135
- t.end();
136
- });
137
- });
138
-
139
- test.cb('when the type is compressible it should compress the body', t => {
140
- const c = new LRU();
141
- const app = createApp(c);
142
- app.use(async function(ctx) {
143
- if (await ctx.cashed()) return;
144
- ctx.response.type = 'text/plain';
145
- ctx.body = Buffer.alloc(2048);
146
- });
147
-
148
- request(app.listen())
149
- .get('/')
150
- .expect('Content-Encoding', 'gzip')
151
- .expect(200, err => {
152
- if (err) return t.end(err);
153
-
154
- t.truthy(c.get('/').body);
155
- t.truthy(c.get('/').gzip);
156
- t.is(c.get('/').type, 'text/plain; charset=utf-8');
157
- t.end();
158
- });
159
- });
160
-
161
- test.cb(
162
- 'when the type is compressible it should handle possible data serialisation and deserialisation',
163
- t => {
164
- const c = new LRU();
165
- const app = createApp(c, {
166
- get(key) {
167
- const value = c.get(key);
168
- return value && JSON.parse(value);
169
- },
170
- set(key, value) {
171
- return c.set(key, JSON.stringify(value));
172
- }
173
- });
174
- app.use(async function(ctx) {
175
- if (await ctx.cashed()) return;
176
- ctx.body = new Array(1024).join('42');
177
- });
178
-
179
- const server = app.listen();
180
- request(server)
181
- .get('/')
182
- .expect('Content-Encoding', 'gzip')
183
- .expect(200, (err, res1) => {
184
- if (err) return t.end(err);
185
-
186
- request(server)
187
- .get('/')
188
- .expect('Content-Encoding', 'gzip')
189
- .expect(200, (err, res2) => {
190
- if (err) return t.end(err);
191
-
192
- t.is(res1.text, res2.text);
193
- t.end();
194
- });
195
- });
196
- }
197
- );
198
-
199
- test.cb(
200
- 'when the type is not compressible it should not compress the body',
201
- t => {
202
- const c = new LRU();
203
- const app = createApp(c);
204
- app.use(async function(ctx) {
205
- if (await ctx.cashed()) return;
206
- ctx.response.type = 'image/png';
207
- ctx.body = Buffer.alloc(2048);
208
- });
209
-
210
- request(app.listen())
211
- .get('/')
212
- .expect('Content-Encoding', 'identity')
213
- .expect(200, err => {
214
- if (err) return t.end(err);
215
-
216
- t.truthy(c.get('/').body);
217
- t.true(!c.get('/').gzip);
218
- t.is(c.get('/').type, 'image/png');
219
- t.end();
220
- });
221
- }
222
- );
223
-
224
- test.cb(
225
- 'when the body is below the threshold it should not compress the body',
226
- t => {
227
- const c = new LRU();
228
- const app = createApp(c);
229
- app.use(async function(ctx) {
230
- if (await ctx.cashed()) return;
231
- ctx.body = 'lol';
232
- });
233
-
234
- request(app.listen())
235
- .get('/')
236
- .expect('Content-Encoding', 'identity')
237
- .expect('lol')
238
- .expect(200, err => {
239
- if (err) return t.end(err);
240
-
241
- t.truthy(c.get('/').body);
242
- t.true(!c.get('/').gzip);
243
- t.is(c.get('/').type, 'text/plain; charset=utf-8');
244
- t.end();
245
- });
246
- }
247
- );
248
-
249
- test.cb('when the method is HEAD it should cache the response', t => {
250
- const c = new LRU();
251
- const app = createApp(c);
252
- app.use(async function(ctx) {
253
- if (await ctx.cashed()) return;
254
- ctx.body = 'lol';
255
- });
256
-
257
- request(app.listen())
258
- .head('/')
259
- .expect('')
260
- .expect(200, err => {
261
- if (err) return t.end(err);
262
-
263
- t.is(c.get('/').body, 'lol');
264
- t.is(c.get('/').type, 'text/plain; charset=utf-8');
265
- t.end();
266
- });
267
- });
268
-
269
- test.cb('when the method is POST it should not cache the response', t => {
270
- const c = new LRU();
271
- const app = createApp(c);
272
- app.use(async function(ctx) {
273
- if (await ctx.cashed()) return;
274
- ctx.body = 'lol';
275
- });
276
-
277
- request(app.listen())
278
- .post('/')
279
- .expect('lol')
280
- .expect(200, err => {
281
- if (err) return t.end(err);
282
-
283
- t.true(!c.get('/'));
284
- t.end();
285
- });
286
- });
287
-
288
- test.cb(
289
- 'when the response code is not 200 it should not cache the response',
290
- t => {
291
- const c = new LRU();
292
- const app = createApp(c);
293
- app.use(async function(ctx) {
294
- if (await ctx.cashed()) return;
295
- ctx.body = 'lol';
296
- ctx.status = 201;
297
- });
298
-
299
- request(app.listen())
300
- .post('/')
301
- .expect('lol')
302
- .expect(201, err => {
303
- if (err) return t.end(err);
304
-
305
- t.true(!c.get('/'));
306
- t.end();
307
- });
308
- }
309
- );
310
-
311
- test.cb(
312
- 'when etag and last-modified headers are set it should cache those values',
313
- t => {
314
- const c = new LRU();
315
- const app = createApp(c);
316
- const date = Math.round(Date.now() / 1000);
317
- app.use(async function(ctx) {
318
- if (await ctx.cashed()) return;
319
- ctx.body = 'lol';
320
- ctx.etag = 'lol';
321
- ctx.type = 'text/lol; charset=utf-8';
322
- ctx.lastModified = new Date(date * 1000);
323
- });
324
-
325
- request(app.listen())
326
- .get('/')
327
- .expect('lol')
328
- .expect(200, err => {
329
- if (err) return t.end(err);
330
-
331
- const obj = c.get('/');
332
- t.truthy(obj);
333
- t.is(obj.body, 'lol');
334
- t.is(obj.etag, '"lol"');
335
- t.is(obj.type, 'text/lol; charset=utf-8');
336
- t.is(obj.lastModified.getTime(), new Date(date * 1000).getTime());
337
- t.end();
338
- });
339
- }
340
- );
341
-
342
- test.cb(
343
- 'when the response is fresh it should return a 304 and cache the response',
344
- t => {
345
- const c = new LRU();
346
- const app = createApp(c);
347
- const date = Math.round(Date.now() / 1000);
348
- app.use(async function(ctx) {
349
- if (await ctx.cashed()) return;
350
- ctx.body = 'lol';
351
- ctx.etag = 'lol';
352
- ctx.type = 'text/lol; charset=utf-8';
353
- ctx.lastModified = new Date(date * 1000);
354
- });
355
-
356
- const server = app.listen();
357
- request(server)
358
- .get('/')
359
- .set('If-None-Match', '"lol"')
360
- .expect('')
361
- .expect(304, err => {
362
- if (err) return t.end(err);
363
-
364
- const obj = c.get('/');
365
- t.truthy(obj);
366
- t.is(obj.body, 'lol');
367
- t.is(obj.etag, '"lol"');
368
- t.is(obj.type, 'text/lol; charset=utf-8');
369
- t.is(obj.lastModified.getTime(), new Date(date * 1000).getTime());
370
- t.end();
371
- });
372
- }
373
- );