koa-compress 5.2.1 → 5.3.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/HISTORY.md +16 -1
- package/README.md +25 -2
- package/lib/index.d.ts +15 -8
- package/lib/index.js +27 -6
- package/package.json +36 -15
package/HISTORY.md
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
5.3.0 / 2026-11-09
|
|
2
|
+
==================
|
|
3
|
+
|
|
4
|
+
* fixed `Blob` bodies, which were sent as `{}`, and compressed `Blob`, `ReadableStream`, and `Response` bodies, thx [yash](https://github.com/yxshee)
|
|
5
|
+
* fixed a compressed stream body that fails partway: its error reaches Koa instead of leaving the response open
|
|
6
|
+
* fixed a 500 when a client refuses every encoding, `identity` included: the response is sent uncompressed
|
|
7
|
+
* added the `rejectUnacceptable` option to respond with `406 Not Acceptable` in that case instead
|
|
8
|
+
* changed: `ReadableStream` and `Response` bodies without `Content-Length` are compressed regardless of `threshold`; set `ctx.compress = false` for live streams, such as server-sent events
|
|
9
|
+
* breaking for strict TypeScript: the `size` argument of threshold and encoding functions is typed `number | undefined`
|
|
10
|
+
* removed the unused `http-errors` dependency
|
|
11
|
+
* updated docs and dependencies
|
|
12
|
+
|
|
13
|
+
5.2.2 / 2026-06-15
|
|
14
|
+
==================
|
|
15
|
+
|
|
16
|
+
* skip compression for `206 Partial Content`, `Content-Range`, and `multipart/byteranges` responses, thx [naveentehrpariya](https://github.com/naveentehrpariya)
|
|
2
17
|
|
|
3
18
|
5.2.1 / 2026-03-12
|
|
4
19
|
==================
|
package/README.md
CHANGED
|
@@ -81,6 +81,14 @@ The HTTP spec treats a missing header as `*` (any encoding is acceptable),
|
|
|
81
81
|
but that causes problems when debugging with tools like `curl` or `wget`.
|
|
82
82
|
Set `defaultEncoding` to `"*"` to restore spec-compliant behavior.
|
|
83
83
|
|
|
84
|
+
### options.rejectUnacceptable\<Boolean\>
|
|
85
|
+
|
|
86
|
+
What to do when the client refuses every encoding the middleware can send, `identity` included (for example, `Accept-Encoding: identity;q=0`).
|
|
87
|
+
By default, the response goes out uncompressed.
|
|
88
|
+
Set `rejectUnacceptable` to `true` to respond with `406 Not Acceptable` instead.
|
|
89
|
+
It applies only to responses that pass the filter and the threshold.
|
|
90
|
+
Default: `false`.
|
|
91
|
+
|
|
84
92
|
## Manually turning compression on and off
|
|
85
93
|
|
|
86
94
|
You can force compression by setting `ctx.compress = true` (bypasses the filter check).
|
|
@@ -94,16 +102,31 @@ app.use((ctx, next) => {
|
|
|
94
102
|
```
|
|
95
103
|
|
|
96
104
|
`ctx.compress` can also be an options object (same shape as the middleware options).
|
|
97
|
-
Its `threshold` and encoding properties override the global defaults for this response.
|
|
105
|
+
Its `threshold`, `rejectUnacceptable`, and encoding properties override the global defaults for this response.
|
|
98
106
|
Note: an options object does **not** bypass the filter check — only `true` does.
|
|
99
107
|
|
|
108
|
+
### Live streams
|
|
109
|
+
|
|
110
|
+
The `threshold` applies to a stream only when you set `Content-Length`.
|
|
111
|
+
Otherwise, Node.js streams, `ReadableStream` bodies, and `Response` bodies are compressed whenever the filter accepts their type.
|
|
112
|
+
A compressor holds data until it has enough to compress, so a live stream, such as server-sent events, can reach the client late.
|
|
113
|
+
To send a live stream as it's written, set `ctx.compress = false`:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
app.use((ctx, next) => {
|
|
117
|
+
ctx.compress = false;
|
|
118
|
+
ctx.type = "text/event-stream";
|
|
119
|
+
ctx.body = events; // a stream that writes each event as it happens
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
100
123
|
## Functional properties
|
|
101
124
|
|
|
102
125
|
The `threshold` and per-encoding options can be functions. They are called
|
|
103
126
|
for every response with three arguments:
|
|
104
127
|
|
|
105
128
|
- `type` — same as `ctx.response.type`
|
|
106
|
-
- `size` —
|
|
129
|
+
- `size` — the response size in bytes, or `undefined` for a stream without `Content-Length`
|
|
107
130
|
- `ctx` — the full Koa context object
|
|
108
131
|
|
|
109
132
|
The function should return a valid value for that property.
|
package/lib/index.d.ts
CHANGED
|
@@ -9,8 +9,8 @@ declare namespace compress {
|
|
|
9
9
|
type ThresholdFunction = (
|
|
10
10
|
/** MIME type of the response */
|
|
11
11
|
type: string,
|
|
12
|
-
/** Size of the response in bytes */
|
|
13
|
-
size: number,
|
|
12
|
+
/** Size of the response in bytes, or `undefined` for a stream without `Content-Length` */
|
|
13
|
+
size: number | undefined,
|
|
14
14
|
/** Context of the request */
|
|
15
15
|
ctx: Koa.Context,
|
|
16
16
|
) => number | string | ThresholdFunction;
|
|
@@ -22,8 +22,8 @@ declare namespace compress {
|
|
|
22
22
|
type ZlibOptionsFunction = (
|
|
23
23
|
/** MIME type of the response */
|
|
24
24
|
type: string,
|
|
25
|
-
/** Size of the response in bytes */
|
|
26
|
-
size: number,
|
|
25
|
+
/** Size of the response in bytes, or `undefined` for a stream without `Content-Length` */
|
|
26
|
+
size: number | undefined,
|
|
27
27
|
/** Context of the request */
|
|
28
28
|
ctx: Koa.Context,
|
|
29
29
|
) => boolean | null | ZlibOptions | ZlibOptionsFunction;
|
|
@@ -35,8 +35,8 @@ declare namespace compress {
|
|
|
35
35
|
type BrotliOptionsFunction = (
|
|
36
36
|
/** MIME type of the response */
|
|
37
37
|
type: string,
|
|
38
|
-
/** Size of the response in bytes */
|
|
39
|
-
size: number,
|
|
38
|
+
/** Size of the response in bytes, or `undefined` for a stream without `Content-Length` */
|
|
39
|
+
size: number | undefined,
|
|
40
40
|
/** Context of the request */
|
|
41
41
|
ctx: Koa.Context,
|
|
42
42
|
) => boolean | null | BrotliOptions | BrotliOptionsFunction;
|
|
@@ -48,8 +48,8 @@ declare namespace compress {
|
|
|
48
48
|
type ZstdOptionsFunction = (
|
|
49
49
|
/** MIME type of the response */
|
|
50
50
|
type: string,
|
|
51
|
-
/** Size of the response in bytes */
|
|
52
|
-
size: number,
|
|
51
|
+
/** Size of the response in bytes, or `undefined` for a stream without `Content-Length` */
|
|
52
|
+
size: number | undefined,
|
|
53
53
|
/** Context of the request */
|
|
54
54
|
ctx: Koa.Context,
|
|
55
55
|
) => boolean | null | ZstdOptions | ZstdOptionsFunction;
|
|
@@ -86,6 +86,13 @@ declare namespace compress {
|
|
|
86
86
|
* Default: `['zstd', 'br', 'gzip', 'deflate', 'identity']`.
|
|
87
87
|
*/
|
|
88
88
|
encodingPreference?: string[];
|
|
89
|
+
/**
|
|
90
|
+
* What to do when the client refuses every encoding the middleware can
|
|
91
|
+
* send, `identity` included (for example, `Accept-Encoding: identity;q=0`):
|
|
92
|
+
* `false` sends the response uncompressed, `true` responds with
|
|
93
|
+
* `406 Not Acceptable`. Default: `false`.
|
|
94
|
+
*/
|
|
95
|
+
rejectUnacceptable?: boolean;
|
|
89
96
|
/**
|
|
90
97
|
* Options passed to `zlib.createDeflate()`, or `false`/`null` to disable.
|
|
91
98
|
* Can be a {@link ZlibOptionsFunction} for per-response values.
|
package/lib/index.js
CHANGED
|
@@ -93,7 +93,11 @@ module.exports = (options = {}) => {
|
|
|
93
93
|
await next()
|
|
94
94
|
|
|
95
95
|
let { body } = ctx
|
|
96
|
-
|
|
96
|
+
let { type, length: size } = ctx.response
|
|
97
|
+
// koa measures web streams as the JSON '{}', but their size is unknown
|
|
98
|
+
if (!ctx.response.has('Content-Length') && (body instanceof ReadableStream || body instanceof Response)) {
|
|
99
|
+
size = undefined
|
|
100
|
+
}
|
|
97
101
|
if (
|
|
98
102
|
// early exit if there's no content body or the body is already encoded
|
|
99
103
|
!body ||
|
|
@@ -101,7 +105,11 @@ module.exports = (options = {}) => {
|
|
|
101
105
|
!ctx.writable ||
|
|
102
106
|
ctx.compress === false ||
|
|
103
107
|
ctx.request.method === 'HEAD' ||
|
|
104
|
-
emptyBodyStatuses.has(
|
|
108
|
+
emptyBodyStatuses.has(ctx.status) ||
|
|
109
|
+
// don't compress partial content: Content-Range describes unencoded byte offsets
|
|
110
|
+
ctx.status === 206 ||
|
|
111
|
+
ctx.response.get('Content-Range') ||
|
|
112
|
+
/^multipart\/byteranges\b/i.test(ctx.response.get('Content-Type') || '') ||
|
|
105
113
|
ctx.response.get('Content-Encoding') ||
|
|
106
114
|
// forced compression or implied
|
|
107
115
|
!(ctx.compress === true || filter(type)) ||
|
|
@@ -149,16 +157,28 @@ module.exports = (options = {}) => {
|
|
|
149
157
|
}
|
|
150
158
|
|
|
151
159
|
const negotiator = new Negotiator(request)
|
|
152
|
-
//
|
|
160
|
+
// undefined when the client refuses identity along with everything we offer
|
|
153
161
|
const encoding = negotiator.encoding(finalEncodings, {
|
|
154
162
|
preferred: encodingPreference
|
|
155
163
|
})
|
|
156
164
|
|
|
165
|
+
if (!encoding && responseOptions.rejectUnacceptable) {
|
|
166
|
+
// koa drops the headers we set when it renders an error
|
|
167
|
+
ctx.throw(406, `Please accept one of: ${finalEncodings.join(', ')}.`, {
|
|
168
|
+
headers: { Vary: 'Accept-Encoding' }
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
// identity === no compression
|
|
158
|
-
if (encoding === 'identity') return
|
|
173
|
+
if (!encoding || encoding === 'identity') return
|
|
159
174
|
|
|
160
175
|
/** begin compression logic **/
|
|
161
176
|
|
|
177
|
+
// web bodies supported by Koa 3: Blob, ReadableStream and Response
|
|
178
|
+
if (body instanceof Blob) body = body.stream()
|
|
179
|
+
else if (body instanceof Response) body = body.body ?? ''
|
|
180
|
+
if (body instanceof ReadableStream) body = Stream.Readable.from(body)
|
|
181
|
+
|
|
162
182
|
// json
|
|
163
183
|
if (isJSON(body)) body = ctx.body = JSON.stringify(body)
|
|
164
184
|
|
|
@@ -169,7 +189,8 @@ module.exports = (options = {}) => {
|
|
|
169
189
|
const compressOptions = { ...encodingMethodDefaultOptions[encoding], ...responseOptions[encoding] }
|
|
170
190
|
const stream = (ctx.body = compress(compressOptions))
|
|
171
191
|
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
// pipeline hands source errors to the compressor, which koa handles
|
|
193
|
+
if (body instanceof Stream) Stream.pipeline(body, stream, () => {})
|
|
194
|
+
else stream.end(body)
|
|
174
195
|
}
|
|
175
196
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koa-compress",
|
|
3
|
-
"description": "
|
|
4
|
-
"
|
|
3
|
+
"description": "Koa middleware that compresses HTTP responses with zstd, Brotli, gzip, or deflate",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"koa",
|
|
6
|
+
"koajs",
|
|
7
|
+
"koa-middleware",
|
|
8
|
+
"middleware",
|
|
9
|
+
"compress",
|
|
10
|
+
"compression",
|
|
11
|
+
"gzip",
|
|
12
|
+
"deflate",
|
|
13
|
+
"brotli",
|
|
14
|
+
"zstd",
|
|
15
|
+
"zstandard",
|
|
16
|
+
"content-encoding",
|
|
17
|
+
"accept-encoding",
|
|
18
|
+
"http",
|
|
19
|
+
"response",
|
|
20
|
+
"stream"
|
|
21
|
+
],
|
|
22
|
+
"version": "5.3.0",
|
|
5
23
|
"type": "commonjs",
|
|
6
24
|
"author": {
|
|
7
25
|
"name": "Jonathan Ong",
|
|
@@ -22,24 +40,26 @@
|
|
|
22
40
|
}
|
|
23
41
|
],
|
|
24
42
|
"license": "MIT",
|
|
25
|
-
"repository":
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/koajs/compress.git"
|
|
46
|
+
},
|
|
26
47
|
"dependencies": {
|
|
27
48
|
"bytes": "^3.1.2",
|
|
28
49
|
"compressible": "^2.0.18",
|
|
29
|
-
"http-errors": "^2.0.1",
|
|
30
50
|
"koa-is-json": "^1.0.0",
|
|
31
|
-
"negotiator": "^1.
|
|
51
|
+
"negotiator": "^1.1.0"
|
|
32
52
|
},
|
|
33
53
|
"devDependencies": {
|
|
34
|
-
"@types/koa": "^3.0.
|
|
35
|
-
"@types/node": "^
|
|
36
|
-
"c8": "^
|
|
37
|
-
"jest": "^
|
|
38
|
-
"koa": "^3.
|
|
54
|
+
"@types/koa": "^3.0.3",
|
|
55
|
+
"@types/node": "^26.6.6",
|
|
56
|
+
"c8": "^9.1.0",
|
|
57
|
+
"jest": "^29.7.0",
|
|
58
|
+
"koa": "^3.3.0",
|
|
39
59
|
"snazzy": "^9.0.0",
|
|
40
60
|
"standard": "^17.1.2",
|
|
41
|
-
"supertest": "^7.
|
|
42
|
-
"typescript": "^
|
|
61
|
+
"supertest": "^7.3.2",
|
|
62
|
+
"typescript": "^7.0.2"
|
|
43
63
|
},
|
|
44
64
|
"scripts": {
|
|
45
65
|
"test": "jest",
|
|
@@ -57,9 +77,6 @@
|
|
|
57
77
|
],
|
|
58
78
|
"main": "lib",
|
|
59
79
|
"types": "lib/index.d.ts",
|
|
60
|
-
"engines": {
|
|
61
|
-
"node": ">= 12"
|
|
62
|
-
},
|
|
63
80
|
"standard": {
|
|
64
81
|
"globals": [
|
|
65
82
|
"afterAll",
|
|
@@ -72,5 +89,9 @@
|
|
|
72
89
|
"expect",
|
|
73
90
|
"it"
|
|
74
91
|
]
|
|
92
|
+
},
|
|
93
|
+
"homepage": "https://github.com/koajs/compress",
|
|
94
|
+
"bugs": {
|
|
95
|
+
"url": "https://github.com/koajs/compress/issues"
|
|
75
96
|
}
|
|
76
97
|
}
|