koa-compress 5.2.2 → 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 +22 -5
- package/package.json +35 -14
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 ||
|
|
@@ -153,16 +157,28 @@ module.exports = (options = {}) => {
|
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
const negotiator = new Negotiator(request)
|
|
156
|
-
//
|
|
160
|
+
// undefined when the client refuses identity along with everything we offer
|
|
157
161
|
const encoding = negotiator.encoding(finalEncodings, {
|
|
158
162
|
preferred: encodingPreference
|
|
159
163
|
})
|
|
160
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
|
+
|
|
161
172
|
// identity === no compression
|
|
162
|
-
if (encoding === 'identity') return
|
|
173
|
+
if (!encoding || encoding === 'identity') return
|
|
163
174
|
|
|
164
175
|
/** begin compression logic **/
|
|
165
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
|
+
|
|
166
182
|
// json
|
|
167
183
|
if (isJSON(body)) body = ctx.body = JSON.stringify(body)
|
|
168
184
|
|
|
@@ -173,7 +189,8 @@ module.exports = (options = {}) => {
|
|
|
173
189
|
const compressOptions = { ...encodingMethodDefaultOptions[encoding], ...responseOptions[encoding] }
|
|
174
190
|
const stream = (ctx.body = compress(compressOptions))
|
|
175
191
|
|
|
176
|
-
|
|
177
|
-
|
|
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)
|
|
178
195
|
}
|
|
179
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
54
|
"@types/koa": "^3.0.3",
|
|
35
|
-
"@types/node": "^
|
|
36
|
-
"c8": "^
|
|
37
|
-
"jest": "^
|
|
38
|
-
"koa": "^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
|
}
|