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 CHANGED
@@ -1,4 +1,19 @@
1
- SEE https://github.com/koajs/compress/releases
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` &mdash; same as `ctx.response.type`
106
- - `size` &mdash; same as `ctx.response.length`
129
+ - `size` &mdash; the response size in bytes, or `undefined` for a stream without `Content-Length`
107
130
  - `ctx` &mdash; 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
- const { type, length: size } = ctx.response
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
- // if no encodings are supported, negotiator returns 'identity'
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
- if (body instanceof Stream) return body.pipe(stream)
177
- stream.end(body)
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": "Compress middleware for koa",
4
- "version": "5.2.2",
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": "koajs/compress",
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.0.0"
51
+ "negotiator": "^1.1.0"
32
52
  },
33
53
  "devDependencies": {
34
54
  "@types/koa": "^3.0.3",
35
- "@types/node": "^25.9.3",
36
- "c8": "^11.0.0",
37
- "jest": "^30.4.2",
38
- "koa": "^3.2.1",
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.2.2",
42
- "typescript": "^6.0.3"
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
  }