@fastify/compress 8.0.3 → 8.2.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.
@@ -14,6 +14,11 @@ on:
14
14
  - 'docs/**'
15
15
  - '*.md'
16
16
 
17
+ # This allows a subsequently queued workflow run to interrupt previous runs
18
+ concurrency:
19
+ group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
20
+ cancel-in-progress: true
21
+
17
22
  permissions:
18
23
  contents: read
19
24
 
package/LICENSE CHANGED
@@ -1,6 +1,8 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 Fastify
3
+ Copyright (c) 2017-present The Fastify team
4
+
5
+ The Fastify team members are listed at https://github.com/fastify/fastify#team.
4
6
 
5
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
8
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)
6
6
 
7
7
  Adds compression utils to [the Fastify `reply` object](https://fastify.dev/docs/latest/Reference/Reply/#reply) and a hook to decompress requests payloads.
8
- Supports `gzip`, `deflate`, and `brotli`.
8
+ Supports `gzip`, `deflate`, `brotli`, and `zstd` (Node.js 22.15+/23.8+).
9
9
 
10
10
  > ℹ️ Note: In large-scale scenarios, use a proxy like Nginx to handle response compression.
11
11
 
@@ -37,11 +37,12 @@ This plugin adds two functionalities to Fastify: a compress utility and a global
37
37
 
38
38
  Currently, the following encoding tokens are supported, using the first acceptable token in this order:
39
39
 
40
- 1. `br`
41
- 2. `gzip`
42
- 3. `deflate`
43
- 4. `*` (no preference — `@fastify/compress` will use `gzip`)
44
- 5. `identity` (no compression)
40
+ 1. `zstd` (Node.js 22.15+/23.8+)
41
+ 2. `br`
42
+ 3. `gzip`
43
+ 4. `deflate`
44
+ 5. `*` (no preference — `@fastify/compress` will use `gzip`)
45
+ 6. `identity` (no compression)
45
46
 
46
47
  If an unsupported encoding is received or the `'accept-encoding'` header is missing, the payload will not be compressed.
47
48
  To return an error for unsupported encoding, use the `onUnsupportedEncoding` option.
@@ -105,6 +106,40 @@ app.get('/', (req, reply) => {
105
106
  await app.listen({ port: 3000 })
106
107
  ```
107
108
 
109
+ It's also possible to pass a Fetch API `Response` object or a Web `ReadableStream`. The plugin will automatically extract the body stream from the `Response` or convert the Web stream to a Node.js `Readable` behind the scenes.
110
+
111
+ ```js
112
+ import fastify from 'fastify'
113
+
114
+ const app = fastify()
115
+ await app.register(import('@fastify/compress'), { global: true })
116
+
117
+ app.get('/', async (req, reply) => {
118
+ const resp = new Response('Hello from Fetch Response')
119
+ reply.compress(resp)
120
+ })
121
+ ```
122
+
123
+ ```js
124
+ app.get('/', async (req, reply) => {
125
+ return new Response('Hello from Fetch Response')
126
+ })
127
+ ```
128
+
129
+ ```js
130
+ app.get('/', (req, reply) => {
131
+ const stream = new ReadableStream({
132
+ start (controller) {
133
+ controller.enqueue(new TextEncoder().encode('Hello from Web ReadableStream'))
134
+ controller.close()
135
+ }
136
+ })
137
+
138
+ reply.header('content-type', 'text/plain')
139
+ reply.compress(stream)
140
+ })
141
+ ```
142
+
108
143
  ## Compress Options
109
144
 
110
145
  ### threshold
@@ -175,6 +210,13 @@ await fastify.register(
175
210
  // Only support gzip and deflate, and prefer deflate to gzip
176
211
  { encodings: ['deflate', 'gzip'] }
177
212
  )
213
+
214
+ // Example with zstd support (Node.js 22.15+/23.8+)
215
+ await fastify.register(
216
+ import('@fastify/compress'),
217
+ // Prefer zstd, fallback to brotli, then gzip
218
+ { encodings: ['zstd', 'br', 'gzip'] }
219
+ )
178
220
  ```
179
221
 
180
222
  ### brotliOptions and zlibOptions
@@ -214,14 +256,24 @@ This plugin adds a `preParsing` hook to decompress the request payload based on
214
256
 
215
257
  Currently, the following encoding tokens are supported:
216
258
 
217
- 1. `br`
218
- 2. `gzip`
219
- 3. `deflate`
259
+ - `zstd` (Node.js 22.15+/23.8+)
260
+ - `br`
261
+ - `gzip`
262
+ - `deflate`
220
263
 
221
264
  If an unsupported encoding or invalid payload is received, the plugin throws an error.
222
265
 
223
266
  If the request header is missing, the plugin yields to the next hook.
224
267
 
268
+ ### Supported payload types
269
+
270
+ The plugin supports compressing the following payload types:
271
+
272
+ - Strings and Buffers
273
+ - Node.js streams
274
+ - Response objects (from the Fetch API)
275
+ - ReadableStream objects (from the Web Streams API)
276
+
225
277
  ### Global hook
226
278
 
227
279
  The global request decompression hook is enabled by default. To disable it, pass `{ global: false }`:
@@ -268,6 +320,13 @@ await fastify.register(
268
320
  // Only support gzip
269
321
  { requestEncodings: ['gzip'] }
270
322
  )
323
+
324
+ // Example with zstd support for request decompression (Node.js 22.15+/23.8+)
325
+ await fastify.register(
326
+ import('@fastify/compress'),
327
+ // Support zstd, brotli and gzip for request decompression
328
+ { requestEncodings: ['zstd', 'br', 'gzip'] }
329
+ )
271
330
  ```
272
331
 
273
332
  ### forceRequestEncoding
package/index.js CHANGED
@@ -12,7 +12,7 @@ const { Minipass } = require('minipass')
12
12
  const pumpify = require('pumpify')
13
13
  const { Readable } = require('readable-stream')
14
14
 
15
- const { isStream, isGzip, isDeflate, intoAsyncIterator } = require('./lib/utils')
15
+ const { isStream, isGzip, isDeflate, intoAsyncIterator, isWebReadableStream, isFetchResponse, webStreamToNodeReadable } = require('./lib/utils')
16
16
 
17
17
  const InvalidRequestEncodingError = createError('FST_CP_ERR_INVALID_CONTENT_ENCODING', 'Unsupported Content-Encoding: %s', 415)
18
18
  const InvalidRequestCompressedPayloadError = createError('FST_CP_ERR_INVALID_CONTENT', 'Could not decompress the request payload using the provided encoding', 400)
@@ -145,6 +145,9 @@ function processCompressParams (opts) {
145
145
  gzip: () => ((opts.zlib || zlib).createGzip || zlib.createGzip)(params.zlibOptions),
146
146
  deflate: () => ((opts.zlib || zlib).createDeflate || zlib.createDeflate)(params.zlibOptions)
147
147
  }
148
+ if (typeof ((opts.zlib || zlib).createZstdCompress || zlib.createZstdCompress) === 'function') {
149
+ params.compressStream.zstd = () => ((opts.zlib || zlib).createZstdCompress || zlib.createZstdCompress)(params.zlibOptions)
150
+ }
148
151
  params.uncompressStream = {
149
152
  // Currently params.uncompressStream.br() is never called as we do not have any way to autodetect brotli compression in `fastify-compress`
150
153
  // Brotli documentation reference: [RFC 7932](https://www.rfc-editor.org/rfc/rfc7932)
@@ -152,8 +155,16 @@ function processCompressParams (opts) {
152
155
  gzip: () => ((opts.zlib || zlib).createGunzip || zlib.createGunzip)(params.zlibOptions),
153
156
  deflate: () => ((opts.zlib || zlib).createInflate || zlib.createInflate)(params.zlibOptions)
154
157
  }
158
+ if (typeof ((opts.zlib || zlib).createZstdDecompress || zlib.createZstdDecompress) === 'function') {
159
+ // Currently params.uncompressStream.zstd() is never called as we do not have any way to autodetect zstd compression in `fastify-compress`
160
+ // Zstd documentation reference: [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878)
161
+ params.uncompressStream.zstd = /* c8 ignore next */ () => ((opts.zlib || zlib).createZstdDecompress || zlib.createZstdDecompress)(params.zlibOptions)
162
+ }
155
163
 
156
164
  const supportedEncodings = ['br', 'gzip', 'deflate', 'identity']
165
+ if (typeof zlib.createZstdCompress === 'function') {
166
+ supportedEncodings.unshift('zstd')
167
+ }
157
168
 
158
169
  params.encodings = Array.isArray(opts.encodings)
159
170
  ? supportedEncodings
@@ -184,8 +195,14 @@ function processDecompressParams (opts) {
184
195
  encodings: [],
185
196
  forceEncoding: null
186
197
  }
198
+ if (typeof (customZlib.createZstdDecompress || zlib.createZstdDecompress) === 'function') {
199
+ params.decompressStream.zstd = customZlib.createZstdDecompress || zlib.createZstdDecompress
200
+ }
187
201
 
188
202
  const supportedEncodings = ['br', 'gzip', 'deflate', 'identity']
203
+ if (typeof zlib.createZstdCompress === 'function') {
204
+ supportedEncodings.unshift('zstd')
205
+ }
189
206
 
190
207
  params.encodings = Array.isArray(opts.requestEncodings)
191
208
  ? supportedEncodings
@@ -237,6 +254,7 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
237
254
  if (payload == null) {
238
255
  return next()
239
256
  }
257
+
240
258
  const responseEncoding = reply.getHeader('Content-Encoding')
241
259
  if (responseEncoding && responseEncoding !== 'identity') {
242
260
  // response is already compressed
@@ -273,10 +291,18 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
273
291
  }
274
292
 
275
293
  if (typeof payload.pipe !== 'function') {
276
- if (Buffer.byteLength(payload) < params.threshold) {
277
- return next()
294
+ if (isFetchResponse(payload)) {
295
+ payload = payload.body
296
+ }
297
+
298
+ if (isWebReadableStream(payload)) {
299
+ payload = webStreamToNodeReadable(payload)
300
+ } else {
301
+ if (Buffer.byteLength(payload) < params.threshold) {
302
+ return next()
303
+ }
304
+ payload = Readable.from(intoAsyncIterator(payload))
278
305
  }
279
- payload = Readable.from(intoAsyncIterator(payload))
280
306
  }
281
307
 
282
308
  setVaryHeader(reply)
@@ -391,7 +417,13 @@ function compress (params) {
391
417
  }
392
418
 
393
419
  if (typeof payload.pipe !== 'function') {
394
- if (!Buffer.isBuffer(payload) && typeof payload !== 'string') {
420
+ if (isFetchResponse(payload)) {
421
+ payload = payload.body
422
+ }
423
+
424
+ if (isWebReadableStream(payload)) {
425
+ payload = webStreamToNodeReadable(payload)
426
+ } else if (!Buffer.isBuffer(payload) && typeof payload !== 'string') {
395
427
  payload = this.serialize(payload)
396
428
  }
397
429
  }
package/lib/utils.js CHANGED
@@ -1,5 +1,21 @@
1
1
  'use strict'
2
2
 
3
+ const { Readable: NodeReadable } = require('node:stream')
4
+
5
+ // https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1
6
+ function isZstd (buffer) {
7
+ return (
8
+ typeof buffer === 'object' &&
9
+ buffer !== null &&
10
+ buffer.length > 3 &&
11
+ // Zstd magic number: 0xFD2FB528 (little-endian)
12
+ buffer[0] === 0x28 &&
13
+ buffer[1] === 0xb5 &&
14
+ buffer[2] === 0x2f &&
15
+ buffer[3] === 0xfd
16
+ )
17
+ }
18
+
3
19
  // https://datatracker.ietf.org/doc/html/rfc1950#section-2
4
20
  function isDeflate (buffer) {
5
21
  return (
@@ -35,6 +51,18 @@ function isStream (stream) {
35
51
  return stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'
36
52
  }
37
53
 
54
+ function isWebReadableStream (obj) {
55
+ return obj instanceof ReadableStream
56
+ }
57
+
58
+ function isFetchResponse (obj) {
59
+ return obj instanceof Response
60
+ }
61
+
62
+ function webStreamToNodeReadable (webStream) {
63
+ return NodeReadable.fromWeb(webStream)
64
+ }
65
+
38
66
  /**
39
67
  * Provide a async iteratable for Readable.from
40
68
  */
@@ -76,4 +104,4 @@ async function * intoAsyncIterator (payload) {
76
104
  yield payload
77
105
  }
78
106
 
79
- module.exports = { isGzip, isDeflate, isStream, intoAsyncIterator }
107
+ module.exports = { isZstd, isGzip, isDeflate, isStream, intoAsyncIterator, isWebReadableStream, isFetchResponse, webStreamToNodeReadable }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/compress",
3
- "version": "8.0.3",
3
+ "version": "8.2.0",
4
4
  "description": "Fastify compression utils",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -16,16 +16,15 @@
16
16
  "readable-stream": "^4.5.2"
17
17
  },
18
18
  "devDependencies": {
19
- "@fastify/pre-commit": "^2.1.0",
20
- "@types/node": "^22.0.0",
19
+ "@types/node": "^24.0.8",
21
20
  "adm-zip": "^0.5.12",
22
21
  "c8": "^10.1.2",
23
22
  "eslint": "^9.17.0",
24
23
  "fastify": "^5.0.0",
25
24
  "jsonstream": "^1.0.3",
26
25
  "neostandard": "^0.12.0",
27
- "tsd": "^0.31.0",
28
- "typescript": "~5.7.2"
26
+ "tsd": "^0.33.0",
27
+ "typescript": "~5.9.2"
29
28
  },
30
29
  "scripts": {
31
30
  "lint": "eslint",
@@ -41,7 +40,8 @@
41
40
  "compression",
42
41
  "deflate",
43
42
  "gzip",
44
- "brotli"
43
+ "brotli",
44
+ "zstd"
45
45
  ],
46
46
  "author": "Tomas Della Vedova - @delvedor (http://delved.org)",
47
47
  "contributors": [
@@ -83,7 +83,7 @@
83
83
  "url": "git+https://github.com/fastify/fastify-compress.git"
84
84
  },
85
85
  "tsd": {
86
- "directory": "test/types"
86
+ "directory": "types"
87
87
  },
88
88
  "publishConfig": {
89
89
  "access": "public"
@@ -9,6 +9,7 @@ const JSONStream = require('jsonstream')
9
9
  const Fastify = require('fastify')
10
10
  const compressPlugin = require('../index')
11
11
  const { once } = require('node:events')
12
+ const { ReadableStream: WebReadableStream, Response } = globalThis
12
13
 
13
14
  describe('When `global` is not set, it is `true` by default :', async () => {
14
15
  test('it should compress Buffer data using brotli when `Accept-Encoding` request header is `br`', async (t) => {
@@ -33,6 +34,32 @@ describe('When `global` is not set, it is `true` by default :', async () => {
33
34
  t.assert.equal(payload.toString('utf-8'), buf.toString())
34
35
  })
35
36
 
37
+ test('it should compress Buffer data using zstd when `Accept-Encoding` request header is `zstd`', async (t) => {
38
+ if (typeof zlib.createZstdCompress !== 'function') {
39
+ t.skip('zstd not supported in this Node.js version')
40
+ return
41
+ }
42
+ t.plan(1)
43
+
44
+ const fastify = Fastify()
45
+ await fastify.register(compressPlugin, { threshold: 0 })
46
+
47
+ const buf = Buffer.from('hello world')
48
+ fastify.get('/', (_request, reply) => {
49
+ reply.send(buf)
50
+ })
51
+
52
+ const response = await fastify.inject({
53
+ url: '/',
54
+ method: 'GET',
55
+ headers: {
56
+ 'accept-encoding': 'zstd'
57
+ }
58
+ })
59
+ const payload = zlib.zstdDecompressSync(response.rawPayload)
60
+ t.assert.equal(payload.toString('utf-8'), buf.toString())
61
+ })
62
+
36
63
  test('it should compress Buffer data using deflate when `Accept-Encoding` request header is `deflate`', async (t) => {
37
64
  t.plan(1)
38
65
 
@@ -100,6 +127,32 @@ describe('When `global` is not set, it is `true` by default :', async () => {
100
127
  t.assert.equal(payload.toString('utf-8'), JSON.stringify(json))
101
128
  })
102
129
 
130
+ test('it should compress JSON data using zstd when `Accept-Encoding` request header is `zstd`', async (t) => {
131
+ if (typeof zlib.createZstdCompress !== 'function') {
132
+ t.skip('zstd not supported in this Node.js version')
133
+ return
134
+ }
135
+ t.plan(1)
136
+
137
+ const fastify = Fastify()
138
+ await fastify.register(compressPlugin, { threshold: 0 })
139
+
140
+ const json = { hello: 'world' }
141
+ fastify.get('/', (_request, reply) => {
142
+ reply.send(json)
143
+ })
144
+
145
+ const response = await fastify.inject({
146
+ url: '/',
147
+ method: 'GET',
148
+ headers: {
149
+ 'accept-encoding': 'zstd'
150
+ }
151
+ })
152
+ const payload = zlib.zstdDecompressSync(response.rawPayload)
153
+ t.assert.equal(payload.toString('utf-8'), JSON.stringify(json))
154
+ })
155
+
103
156
  test('it should compress JSON data using deflate when `Accept-Encoding` request header is `deflate`', async (t) => {
104
157
  t.plan(1)
105
158
 
@@ -213,6 +266,54 @@ describe('When `global` is not set, it is `true` by default :', async () => {
213
266
  const payload = zlib.gunzipSync(response.rawPayload)
214
267
  t.assert.equal(payload.toString('utf-8'), 'hello')
215
268
  })
269
+
270
+ test('it should compress a Fetch API Response body', async (t) => {
271
+ t.plan(1)
272
+
273
+ const fastify = Fastify()
274
+ await fastify.register(compressPlugin, { threshold: 0 })
275
+
276
+ const body = 'hello from fetch response'
277
+ fastify.get('/fetch-resp', (_request, reply) => {
278
+ const resp = new Response(body, { headers: { 'content-type': 'text/plain' } })
279
+ reply.send(resp)
280
+ })
281
+
282
+ const response = await fastify.inject({
283
+ url: '/fetch-resp',
284
+ method: 'GET',
285
+ headers: { 'accept-encoding': 'gzip' }
286
+ })
287
+ const payload = zlib.gunzipSync(response.rawPayload)
288
+ t.assert.equal(payload.toString('utf-8'), body)
289
+ })
290
+
291
+ test('it should compress a Web ReadableStream body', async (t) => {
292
+ t.plan(1)
293
+
294
+ const fastify = Fastify()
295
+ await fastify.register(compressPlugin, { threshold: 0 })
296
+
297
+ const body = 'hello from web stream'
298
+ fastify.get('/web-stream', (_request, reply) => {
299
+ const stream = new WebReadableStream({
300
+ start (controller) {
301
+ controller.enqueue(Buffer.from(body))
302
+ controller.close()
303
+ }
304
+ })
305
+ reply.header('content-type', 'text/plain')
306
+ reply.send(stream)
307
+ })
308
+
309
+ const response = await fastify.inject({
310
+ url: '/web-stream',
311
+ method: 'GET',
312
+ headers: { 'accept-encoding': 'gzip' }
313
+ })
314
+ const payload = zlib.gunzipSync(response.rawPayload)
315
+ t.assert.equal(payload.toString('utf-8'), body)
316
+ })
216
317
  })
217
318
 
218
319
  describe('It should send compressed Stream data when `global` is `true` :', async () => {
@@ -42,6 +42,33 @@ describe('It should decompress the request payload :', async () => {
42
42
  t.assert.equal(response.body, '@fastify/compress')
43
43
  })
44
44
 
45
+ test('using zstd algorithm when `Content-Encoding` request header value is set to `zstd`', async (t) => {
46
+ if (typeof zlib.createZstdCompress !== 'function') {
47
+ t.skip('zstd not supported in this Node.js version')
48
+ return
49
+ }
50
+ t.plan(2)
51
+
52
+ const fastify = Fastify()
53
+ await fastify.register(compressPlugin)
54
+
55
+ fastify.post('/', (request, reply) => {
56
+ reply.send(request.body.name)
57
+ })
58
+
59
+ const response = await fastify.inject({
60
+ url: '/',
61
+ method: 'POST',
62
+ headers: {
63
+ 'content-type': 'application/json',
64
+ 'content-encoding': 'zstd'
65
+ },
66
+ payload: createPayload(zlib.createZstdCompress)
67
+ })
68
+ t.assert.equal(response.statusCode, 200)
69
+ t.assert.equal(response.body, '@fastify/compress')
70
+ })
71
+
45
72
  test('using deflate algorithm when `Content-Encoding` request header value is set to `deflate`', async (t) => {
46
73
  t.plan(2)
47
74
 
@@ -440,3 +440,33 @@ test('It should avoid to trigger `onSend` hook twice', async (t) => {
440
440
  })
441
441
  t.assert.deepEqual(JSON.parse(zlib.brotliDecompressSync(response.rawPayload)), { hi: true })
442
442
  })
443
+
444
+ test('reply.compress should handle Fetch Response', async (t) => {
445
+ t.plan(1)
446
+ const fastify = Fastify()
447
+ await fastify.register(compressPlugin, { global: true, threshold: 0 })
448
+ fastify.get('/', (_req, reply) => {
449
+ const r = new Response('from reply.compress', { headers: { 'content-type': 'text/plain' } })
450
+ reply.compress(r)
451
+ })
452
+ const res = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } })
453
+ t.assert.equal(zlib.gunzipSync(res.rawPayload).toString('utf8'), 'from reply.compress')
454
+ })
455
+
456
+ test('reply.compress should handle Web ReadableStream', async (t) => {
457
+ t.plan(1)
458
+ const fastify = Fastify()
459
+ await fastify.register(compressPlugin, { global: true, threshold: 0 })
460
+ fastify.get('/', (_req, reply) => {
461
+ const stream = new ReadableStream({
462
+ start (controller) {
463
+ controller.enqueue(Buffer.from('from webstream'))
464
+ controller.close()
465
+ }
466
+ })
467
+ reply.header('content-type', 'text/plain')
468
+ reply.compress(stream)
469
+ })
470
+ const res = await fastify.inject({ url: '/', method: 'GET', headers: { 'accept-encoding': 'gzip' } })
471
+ t.assert.equal(zlib.gunzipSync(res.rawPayload).toString('utf8'), 'from webstream')
472
+ })
@@ -4,7 +4,7 @@ const { createReadStream } = require('node:fs')
4
4
  const { Socket } = require('node:net')
5
5
  const { Duplex, PassThrough, Readable, Stream, Transform, Writable } = require('node:stream')
6
6
  const { test } = require('node:test')
7
- const { isStream, isDeflate, isGzip, intoAsyncIterator } = require('../lib/utils')
7
+ const { isStream, isZstd, isDeflate, isGzip, intoAsyncIterator } = require('../lib/utils')
8
8
 
9
9
  test('isStream() utility should be able to detect Streams', async (t) => {
10
10
  t.plan(12)
@@ -48,6 +48,23 @@ test('isDeflate() utility should be able to detect deflate compressed Buffer', a
48
48
  equal(isDeflate(''), false)
49
49
  })
50
50
 
51
+ test('isZstd() utility should be able to detect zstd compressed Buffer', async (t) => {
52
+ t.plan(10)
53
+ const equal = t.assert.equal
54
+
55
+ equal(isZstd(Buffer.alloc(0)), false)
56
+ equal(isZstd(Buffer.alloc(1)), false)
57
+ equal(isZstd(Buffer.alloc(2)), false)
58
+ equal(isZstd(Buffer.alloc(3)), false)
59
+ equal(isZstd(Buffer.from([0x28, 0xb5, 0x2f])), false)
60
+ equal(isZstd(Buffer.from([0x28, 0xb5, 0x2f, 0xfd])), true)
61
+
62
+ equal(isZstd({}), false)
63
+ equal(isZstd(null), false)
64
+ equal(isZstd(undefined), false)
65
+ equal(isZstd(''), false)
66
+ })
67
+
51
68
  test('isGzip() utility should be able to detect gzip compressed Buffer', async (t) => {
52
69
  t.plan(10)
53
70
  const equal = t.assert.equal
package/types/index.d.ts CHANGED
@@ -26,7 +26,7 @@ declare module 'fastify' {
26
26
  }
27
27
 
28
28
  interface FastifyReply {
29
- compress(input: Stream | Input): void;
29
+ compress(input: Stream | Input | Response | ReadableStream): void;
30
30
  }
31
31
 
32
32
  export interface RouteOptions {
@@ -57,7 +57,7 @@ type RouteDecompressOptions = Pick<fastifyCompress.FastifyCompressOptions,
57
57
  | 'zlib'
58
58
  >
59
59
 
60
- type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity'
60
+ type EncodingToken = 'zstd' | 'br' | 'deflate' | 'gzip' | 'identity'
61
61
 
62
62
  type CompressibleContentTypeFunction = (contentType: string) => boolean
63
63
 
@@ -25,8 +25,14 @@ const withGlobalOptions: FastifyCompressOptions = {
25
25
  removeContentLengthHeader: true
26
26
  }
27
27
 
28
+ const withZstdOptions: FastifyCompressOptions = {
29
+ encodings: ['zstd', 'br', 'gzip', 'deflate', 'identity'],
30
+ requestEncodings: ['zstd', 'br', 'gzip', 'deflate', 'identity']
31
+ }
32
+
28
33
  const app: FastifyInstance = fastify()
29
34
  app.register(fastifyCompress, withGlobalOptions)
35
+ app.register(fastifyCompress, withZstdOptions)
30
36
 
31
37
  app.register(fastifyCompress, {
32
38
  customTypes: value => value === 'application/json'
@@ -111,9 +117,33 @@ appWithoutGlobal.inject(
111
117
  }
112
118
  )
113
119
 
120
+ // Test that invalid encoding values trigger TypeScript errors
121
+ expectError(fastify().register(fastifyCompress, {
122
+ encodings: ['invalid-encoding']
123
+ }))
124
+
125
+ expectError(fastify().register(fastifyCompress, {
126
+ requestEncodings: ['another-invalid-encoding']
127
+ }))
128
+
114
129
  // Instantiation of an app that should trigger a typescript error
115
130
  const appThatTriggerAnError = fastify()
116
131
  expectError(appThatTriggerAnError.register(fastifyCompress, {
117
132
  global: true,
118
133
  thisOptionDoesNotExist: 'trigger a typescript error'
119
134
  }))
135
+
136
+ app.get('/ts-fetch-response', async (_request, reply) => {
137
+ const resp = new Response('ok', { headers: { 'content-type': 'text/plain' } })
138
+ expectType<void>(reply.compress(resp))
139
+ })
140
+
141
+ app.get('/ts-web-readable-stream', async (_request, reply) => {
142
+ const stream = new ReadableStream({
143
+ start (controller) {
144
+ controller.enqueue(new Uint8Array([1, 2, 3]))
145
+ controller.close()
146
+ }
147
+ })
148
+ expectType<void>(reply.compress(stream))
149
+ })