Comparing version 3.0.1-0 to 3.0.1
148
index.js
@@ -1,10 +0,12 @@ | ||
'use strict' | ||
const { gzip } = require('zlib'); | ||
const { promisify } = require('util'); | ||
const compressible = require('compressible') | ||
const toArray = require('stream-to-array') | ||
const isJSON = require('koa-is-json') | ||
const Bluebird = require('bluebird') | ||
const bytes = require('bytes') | ||
const bytes = require('bytes'); | ||
const compressible = require('compressible'); | ||
const getStream = require('get-stream'); | ||
const isJSON = require('koa-is-json'); | ||
const isStream = require('is-stream'); | ||
const safeStringify = require('fast-safe-stringify'); | ||
const compress = Bluebird.promisify(require('zlib').gzip) | ||
const compress = promisify(gzip); | ||
@@ -15,63 +17,73 @@ // methods we cache | ||
GET: true | ||
} | ||
}; | ||
module.exports = function (options) { | ||
options = options || {} | ||
module.exports = function(options) { | ||
options = options || {}; | ||
const hash = options.hash || function (ctx) { return ctx.request.url } | ||
let threshold = options.threshold || '1kb' | ||
if (typeof threshold === 'string') threshold = bytes(threshold) | ||
const get = options.get | ||
const set = options.set | ||
if (!get) throw new Error('.get not defined') | ||
if (!set) throw new Error('.set not defined') | ||
const hash = | ||
options.hash || | ||
function(ctx) { | ||
return ctx.request.url; | ||
}; | ||
const stringify = options.stringify || safeStringify || JSON.stringify; | ||
let threshold = options.threshold || '1kb'; | ||
if (typeof threshold === 'string') threshold = bytes(threshold); | ||
const { get } = options; | ||
const { set } = options; | ||
if (!get) throw new Error('.get not defined'); | ||
if (!set) throw new Error('.set not defined'); | ||
// ctx.cashed(maxAge) => boolean | ||
const cashed = async function cashed (ctx, maxAge) { | ||
async function cashed(maxAge) { | ||
// uncacheable request method | ||
if (!methods[ctx.request.method]) return false | ||
if (!methods[this.request.method]) return false; | ||
const key = ctx.cashKey = hash(ctx) | ||
const obj = await get(key, maxAge || options.maxAge || 0) | ||
const body = obj && obj.body | ||
this.cashKey = hash(this); | ||
const key = this.cashKey; | ||
const obj = await get(key, maxAge || options.maxAge || 0); | ||
const body = obj && obj.body; | ||
if (!body) { | ||
// tell the upstream middleware to cache this response | ||
ctx.cash = { maxAge } | ||
return false | ||
this.cash = { maxAge }; | ||
return false; | ||
} | ||
// serve from cache | ||
ctx.response.type = obj.type | ||
if (obj.lastModified) ctx.response.lastModified = obj.lastModified | ||
if (obj.etag) ctx.response.etag = obj.etag | ||
if (ctx.request.fresh) { | ||
ctx.response.status = 304 | ||
return true | ||
this.response.type = obj.type; | ||
if (obj.lastModified) this.response.lastModified = obj.lastModified; | ||
if (obj.etag) this.response.etag = obj.etag; | ||
if (this.request.fresh) { | ||
this.response.status = 304; | ||
return true; | ||
} | ||
if (obj.gzip && ctx.request.acceptsEncodings('gzip', 'identity') === 'gzip') { | ||
ctx.response.body = new Buffer(obj.gzip) | ||
ctx.response.set('Content-Encoding', 'gzip') | ||
if ( | ||
obj.gzip && | ||
this.request.acceptsEncodings('gzip', 'identity') === 'gzip' | ||
) { | ||
this.response.body = Buffer.from(obj.gzip); | ||
this.response.set('Content-Encoding', 'gzip'); | ||
} else { | ||
ctx.response.body = obj.body | ||
this.response.body = obj.body; | ||
// tell any compress middleware to not bother compressing this | ||
ctx.response.set('Content-Encoding', 'identity') | ||
this.response.set('Content-Encoding', 'identity'); | ||
} | ||
return true | ||
return true; | ||
} | ||
// the actual middleware | ||
return async function cash (ctx, next) { | ||
ctx.vary('Accept-Encoding') | ||
ctx.cashed = function (maxAge) { | ||
return cashed(ctx, maxAge) | ||
} | ||
// eslint-disable-next-line complexity | ||
async function middleware(ctx, next) { | ||
ctx.vary('Accept-Encoding'); | ||
ctx.cashed = cashed.bind(ctx); | ||
await next() | ||
await next(); | ||
// check for HTTP caching just in case | ||
if (!ctx.cash) { | ||
if (ctx.request.fresh) ctx.response.status = 304 | ||
return | ||
if (ctx.request.fresh) ctx.response.status = 304; | ||
return; | ||
} | ||
@@ -82,13 +94,15 @@ | ||
// only cache GET/HEAD 200s | ||
if (ctx.response.status !== 200) return | ||
if (!methods[ctx.request.method]) return | ||
let body = ctx.response.body | ||
if (!body) return | ||
if (ctx.response.status !== 200) return; | ||
if (!methods[ctx.request.method]) return; | ||
let { body } = ctx.response; | ||
if (!body) return; | ||
// stringify JSON bodies | ||
if (isJSON(body)) body = ctx.response.body = JSON.stringify(body) | ||
// buffer streams | ||
if (typeof body.pipe === 'function') { | ||
// note: non-binary streams are NOT supported! | ||
body = ctx.response.body = Buffer.concat(await toArray(body)) | ||
if (isJSON(body)) { | ||
ctx.response.body = stringify(body); | ||
body = ctx.response.body; | ||
} else if (isStream(body)) { | ||
// buffer streams | ||
ctx.response.body = await getStream(body); | ||
body = ctx.response.body; | ||
} | ||
@@ -98,3 +112,3 @@ | ||
if ((ctx.response.get('Content-Encoding') || 'identity') !== 'identity') { | ||
throw new Error('Place koa-cache below any compression middleware.') | ||
throw new Error('Place koa-cache below any compression middleware.'); | ||
} | ||
@@ -107,19 +121,25 @@ | ||
etag: ctx.response.get('etag') || null | ||
} | ||
}; | ||
const fresh = ctx.request.fresh | ||
if (fresh) ctx.response.status = 304 | ||
const { fresh } = ctx.request; | ||
if (fresh) ctx.response.status = 304; | ||
if (compressible(obj.type) && ctx.response.length >= threshold) { | ||
obj.gzip = await compress(body) | ||
if (!fresh && ctx.request.acceptsEncodings('gzip', 'identity') === 'gzip') { | ||
ctx.response.body = obj.gzip | ||
ctx.response.set('Content-Encoding', 'gzip') | ||
obj.gzip = await compress(body); | ||
if ( | ||
!fresh && | ||
ctx.request.acceptsEncodings('gzip', 'identity') === 'gzip' | ||
) { | ||
ctx.response.body = obj.gzip; | ||
ctx.response.set('Content-Encoding', 'gzip'); | ||
} | ||
} | ||
if (!ctx.response.get('Content-Encoding')) ctx.response.set('Content-Encoding', 'identity') | ||
if (!ctx.response.get('Content-Encoding')) | ||
ctx.response.set('Content-Encoding', 'identity'); | ||
await set(ctx.cashKey, obj, ctx.cash.maxAge || options.maxAge || 0) | ||
await set(ctx.cashKey, obj, ctx.cash.maxAge || options.maxAge || 0); | ||
} | ||
} | ||
return middleware; | ||
}; |
163
package.json
{ | ||
"name": "koa-cash", | ||
"description": "HTTP response caching for Koa", | ||
"version": "3.0.1-0", | ||
"publishConfig": { | ||
"tag": "next" | ||
"description": "HTTP response caching for Koa. HTTP response caching for Koa. Supports Redis, in-memory store, and more!", | ||
"version": "3.0.1", | ||
"author": "Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)", | ||
"ava": { | ||
"verbose": true, | ||
"serial": true, | ||
"failFast": true | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/koajs/cash/issues", | ||
"email": "me@jongleberry.com" | ||
}, | ||
"commitlint": { | ||
"extends": [ | ||
"@commitlint/config-conventional" | ||
] | ||
}, | ||
"contributors": [ | ||
"Jonathan Ong <me@jongleberry.com> (http://jongleberry.com)", | ||
"Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com)" | ||
], | ||
"dependencies": { | ||
"bluebird": "^3.1.1", | ||
"bytes": "^2.1.0", | ||
"compressible": "^2.0.0", | ||
"koa-is-json": "^1.0.0", | ||
"stream-to-array": "^2.0.0" | ||
"bytes": "^3.1.0", | ||
"compressible": "^2.0.18", | ||
"fast-safe-stringify": "^2.0.7", | ||
"get-stream": "^5.1.0", | ||
"is-stream": "^2.0.0", | ||
"koa-is-json": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"istanbul": "^1.1.0-alpha.1", | ||
"koa": "^2.0.0", | ||
"lru-cache": "^4.0.0", | ||
"mocha": "^3.1.2", | ||
"standard": "^8.6.0", | ||
"supertest": "^1.1.0" | ||
"@commitlint/cli": "latest", | ||
"@commitlint/config-conventional": "latest", | ||
"ava": "latest", | ||
"codecov": "latest", | ||
"cross-env": "latest", | ||
"eslint": "6.x", | ||
"eslint-config-xo-lass": "latest", | ||
"fixpack": "latest", | ||
"husky": "latest", | ||
"into-stream": "^5.1.1", | ||
"koa": "^2.12.0", | ||
"lint-staged": "latest", | ||
"lru-cache": "4.x", | ||
"nyc": "latest", | ||
"remark-cli": "latest", | ||
"remark-preset-github": "latest", | ||
"supertest": "1.x", | ||
"xo": "0.25" | ||
}, | ||
"engines": { | ||
"node": ">= 7.6.0" | ||
"node": ">=8.3" | ||
}, | ||
"scripts": { | ||
"lint": "standard index.js test/**/*.js", | ||
"test": "NODE_ENV=test mocha", | ||
"test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha", | ||
"test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly" | ||
"homepage": "https://github.com/koajs/cash", | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged && npm test", | ||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS" | ||
} | ||
}, | ||
"author": { | ||
"name": "Jonathan Ong", | ||
"email": "me@jongleberry.com", | ||
"url": "http://jongleberry.com", | ||
"twitter": "https://twitter.com/jongleberry" | ||
}, | ||
"repository": "koajs/cash", | ||
"keywords": [ | ||
"alternative", | ||
"amazon", | ||
"aws", | ||
"cache", | ||
"caching", | ||
"cdn", | ||
"cloudfront", | ||
"content", | ||
"database", | ||
"db", | ||
"delivery", | ||
"handler", | ||
"hosting", | ||
"http", | ||
"in-memory", | ||
"ioredis", | ||
"key", | ||
"koa", | ||
"memory", | ||
"middleware", | ||
"network", | ||
"provider", | ||
"redis", | ||
"response", | ||
"responses", | ||
"s3", | ||
"sentinel", | ||
"serve", | ||
"server", | ||
"service", | ||
"session", | ||
"sessions", | ||
"space", | ||
"spaces", | ||
"static", | ||
"storage", | ||
"value" | ||
], | ||
"license": "MIT", | ||
"main": "index.js", | ||
"keywords": [ | ||
"koa", | ||
"cache", | ||
"middleware" | ||
] | ||
"nyc": { | ||
"check-coverage": true, | ||
"lines": 100, | ||
"functions": 100, | ||
"branches": 100, | ||
"reporter": [ | ||
"lcov", | ||
"html", | ||
"text" | ||
] | ||
}, | ||
"prettier": { | ||
"singleQuote": true, | ||
"bracketSpacing": true, | ||
"trailingComma": "none" | ||
}, | ||
"remarkConfig": { | ||
"plugins": [ | ||
"preset-github" | ||
] | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/koajs/cash" | ||
}, | ||
"scripts": { | ||
"ava": "cross-env NODE_ENV=test ava", | ||
"coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov", | ||
"lint": "xo && remark . -qfo", | ||
"nyc": "cross-env NODE_ENV=test nyc ava", | ||
"test": "yarn run lint && yarn run ava", | ||
"test-coverage": "yarn run lint && yarn run nyc" | ||
}, | ||
"xo": { | ||
"prettier": true, | ||
"space": true, | ||
"extends": [ | ||
"xo-lass" | ||
] | ||
} | ||
} |
170
README.md
@@ -1,36 +0,78 @@ | ||
# Koa Cash | ||
# koa-cash | ||
[![NPM version][npm-image]][npm-url] | ||
[![Build status][travis-image]][travis-url] | ||
[![Test coverage][coveralls-image]][coveralls-url] | ||
[![Dependency Status][david-image]][david-url] | ||
[![License][license-image]][license-url] | ||
[![Downloads][downloads-image]][downloads-url] | ||
[![build status](https://img.shields.io/travis/com/koajs/cash.svg)](https://travis-ci.com/koajs/cash) | ||
[![code coverage](https://img.shields.io/codecov/c/github/koajs/cash.svg)](https://codecov.io/gh/koajs/cash) | ||
[![code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) | ||
[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) | ||
[![made with lass](https://img.shields.io/badge/made_with-lass-95CC28.svg)](https://lass.js.org) | ||
[![license](https://img.shields.io/github/license/koajs/cash.svg)](LICENSE) | ||
[![npm downloads](https://img.shields.io/npm/dt/koa-cash.svg)](https://npm.im/koa-cash) | ||
HTTP response caching for Koa. | ||
> HTTP response caching for Koa. Supports Redis, in-memory store, and more! | ||
## Table of Contents | ||
* [Features](#features) | ||
* [Install](#install) | ||
* [Usage](#usage) | ||
* [API](#api) | ||
* [app.use(koaCash(options))](#appusekoacashoptions) | ||
* [const cached = await ctx.cashed(\[maxAge\])](#const-cached--await-ctxcashedmaxage) | ||
* [Notes](#notes) | ||
* [Usage](#usage-1) | ||
* [Contributors](#contributors) | ||
* [License](#license) | ||
## Features | ||
Caches the response based on any arbitrary store you'd like. | ||
- Handles JSON and stream bodies | ||
- Handles gzip compression negotiation | ||
- Handles 304 responses | ||
* Handles JSON and stream bodies | ||
* Handles gzip compression negotiation | ||
* Handles 304 responses | ||
:tada: **Pairs great with [@ladjs/koa-cache-responses](https://github.com/ladjs/koa-cache-responses)** :tada: | ||
## Install | ||
[npm][]: | ||
```sh | ||
npm install koa-cash | ||
``` | ||
[yarn][]: | ||
```sh | ||
yarn add koa-cash | ||
``` | ||
## Usage | ||
```js | ||
app.use(require('koa-cash')({ | ||
// some options | ||
})) | ||
const koaCash = require('koa-cash'); | ||
app.use(async (ctx, next) => { | ||
// ... | ||
app.use(koaCash()) | ||
app.use(async ctx => { | ||
// this response is already cashed if `true` is returned, | ||
// so this middleware will automatically serve this response from cache | ||
if (await ctx.cashed()) return | ||
if (await ctx.cashed()) return; | ||
// set the response body here, | ||
// and the upstream middleware will automatically cache it | ||
ctx.response.body = 'hello world!' | ||
}) | ||
ctx.body = 'hello world!'; | ||
}); | ||
``` | ||
## API | ||
### app.use(require('koa-cash')(options)) | ||
### app.use(koaCash(options)) | ||
@@ -53,46 +95,45 @@ Options are: | ||
function hash(ctx) { | ||
return ctx.request.url | ||
return ctx.response.url; // same as ctx.url | ||
} | ||
``` | ||
`ctx` is the Koa context. By default, it caches based on the URL. | ||
`ctx` is the Koa context and is also passed as an argument. By default, it caches based on the URL. | ||
#### `get()` | ||
Get a value from a store. Can be a regular function or an `async` function, | ||
which returns the cache's value, if any. | ||
Get a value from a store. Must return a Promise, which returns the cache's value, if any. | ||
```js | ||
async function get(key, maxAge) { | ||
return <cached-value> | ||
function get(key, maxAge) { | ||
return Promise; | ||
} | ||
``` | ||
Note that all the `maxAge` stuff must be handled by you. | ||
This module makes no opinion about it. | ||
Note that all the `maxAge` stuff must be handled by you. This module makes no opinion about it. | ||
#### `set()` | ||
Set a value to a store. Can be a regular function or an `async` function. | ||
Set a value to a store. Must return a Promise. | ||
```js | ||
async function set(key, value, maxAge) { | ||
... | ||
function set(key, value, maxAge) { | ||
return Promise; | ||
} | ||
``` | ||
Note: `maxAge` is set by `.cash={ maxAge }`. | ||
If it's not set, then `maxAge` will be `0`, which you should then ignore. | ||
Note: `maxAge` is set by `.cash = { maxAge }`. If it's not set, then `maxAge` will be `0`, which you should then ignore. | ||
#### Example | ||
Using a library like [lru-cache](https://github.com/isaacs/node-lru-cache), | ||
though this would not quite work since it doesn't allow per-key expiration times. | ||
Using a library like [lru-cache](https://github.com/isaacs/node-lru-cache), though this would not quite work since it doesn't allow per-key expiration times. | ||
```js | ||
var cache = require('lru-cache')({ | ||
const koaCash = require('koa-cash'); | ||
const LRU = require('lru-cache'); | ||
const cache = new LRU({ | ||
maxAge: 30000 // global max age | ||
}) | ||
app.use(require('koa-cash')({ | ||
app.use(koaCash({ | ||
get (key, maxAge) { | ||
@@ -107,35 +148,40 @@ return cache.get(key) | ||
### var cached = await ctx.cashed([maxAge]) | ||
See [@ladjs/koa-cache-responses](https://github.com/ladjs/koa-cache-responses) test folder more examples (e.g. Redis with `ioredis`). | ||
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. | ||
### const cached = await ctx.cashed(\[maxAge]) | ||
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. | ||
`maxAge` is the max age passed to `get()`. | ||
If `cached` is `true`, | ||
then the current request has been served from cache and __you should early `return`__. | ||
Otherwise, continue setting `ctx.response.body=` and this will cache the response. | ||
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. | ||
## Notes | ||
- Only `GET` and `HEAD` requests are cached. | ||
- Only `200` responses are cached. | ||
Don't set `304` status codes on these routes - this middleware will handle it for you | ||
- The underlying store should be able to handle `Date` objects as well as `Buffer` objects. | ||
Otherwise, you may have to serialize/deserialize yourself. | ||
* Only `GET` and `HEAD` requests are cached. | ||
* Only `200` responses are cached. Don't set `304` status codes on these routes - this middleware will handle it for you | ||
* The underlying store should be able to handle `Date` objects as well as `Buffer` objects. Otherwise, you may have to serialize/deserialize yourself. | ||
[npm-image]: https://img.shields.io/npm/v/koa-cash.svg?style=flat-square | ||
[npm-url]: https://npmjs.org/package/koa-cash | ||
[github-tag]: http://img.shields.io/github/tag/koajs/cash.svg?style=flat-square | ||
[github-url]: https://github.com/koajs/cash/tags | ||
[travis-image]: https://img.shields.io/travis/koajs/cash.svg?style=flat-square | ||
[travis-url]: https://travis-ci.org/koajs/cash | ||
[coveralls-image]: https://img.shields.io/coveralls/koajs/cash.svg?style=flat-square | ||
[coveralls-url]: https://coveralls.io/r/koajs/cash?branch=master | ||
[david-image]: http://img.shields.io/david/koajs/cash.svg?style=flat-square | ||
[david-url]: https://david-dm.org/koajs/cash | ||
[license-image]: http://img.shields.io/npm/l/koa-cash.svg?style=flat-square | ||
[license-url]: LICENSE | ||
[downloads-image]: http://img.shields.io/npm/dm/koa-cash.svg?style=flat-square | ||
[downloads-url]: https://npmjs.org/package/koa-cash | ||
## Usage | ||
## Contributors | ||
| Name | Website | | ||
| ---------------- | ------------------------- | | ||
| **Jonathan Ong** | <http://jongleberry.com> | | ||
| **Nick Baugh** | <http://niftylettuce.com> | | ||
## License | ||
[MIT](LICENSE) © [Jonathan Ong](http://jongleberry.com) | ||
## | ||
[npm]: https://www.npmjs.com/ | ||
[yarn]: https://yarnpkg.com/ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
23584
10
523
0
0
186
0
6
18
1
+ Addedfast-safe-stringify@^2.0.7
+ Addedget-stream@^5.1.0
+ Addedis-stream@^2.0.0
+ Addedbytes@3.1.2(transitive)
+ Addedend-of-stream@1.4.4(transitive)
+ Addedfast-safe-stringify@2.1.1(transitive)
+ Addedget-stream@5.2.0(transitive)
+ Addedis-stream@2.0.1(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpump@3.0.2(transitive)
+ Addedwrappy@1.0.2(transitive)
- Removedbluebird@^3.1.1
- Removedstream-to-array@^2.0.0
- Removedany-promise@1.3.0(transitive)
- Removedbluebird@3.7.2(transitive)
- Removedbytes@2.5.0(transitive)
- Removedstream-to-array@2.3.0(transitive)
Updatedbytes@^3.1.0
Updatedcompressible@^2.0.18