@fastify/compress 8.1.0 → 8.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.
@@ -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/README.md CHANGED
@@ -57,6 +57,23 @@ await fastify.register(
57
57
  { global: false }
58
58
  )
59
59
  ```
60
+
61
+ If only compression or decompression is required, set the `globalCompression` or `globalDecompression` config flags to `false` respectively (both are `true` by default).
62
+
63
+ ```js
64
+ await fastify.register(
65
+ import('@fastify/compress'),
66
+ // Disable compression but keep decompression enabled (default behavior for globalDecompression is true)
67
+ { globalCompression: false }
68
+ )
69
+
70
+ // Disable decompression but keep compression enabled
71
+ await fastify.register(
72
+ import('@fastify/compress'),
73
+ { globalDecompression: false }
74
+ )
75
+ ```
76
+
60
77
  Fastify encapsulation can be used to set global compression but run it only in a subset of routes by wrapping them inside a plugin.
61
78
 
62
79
  > ℹ️ Note: If using `@fastify/compress` plugin together with `@fastify/static` plugin, `@fastify/compress` must be registered (with *global hook*) **before** registering `@fastify/static`.
@@ -106,6 +123,40 @@ app.get('/', (req, reply) => {
106
123
  await app.listen({ port: 3000 })
107
124
  ```
108
125
 
126
+ 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.
127
+
128
+ ```js
129
+ import fastify from 'fastify'
130
+
131
+ const app = fastify()
132
+ await app.register(import('@fastify/compress'), { global: true })
133
+
134
+ app.get('/', async (req, reply) => {
135
+ const resp = new Response('Hello from Fetch Response')
136
+ reply.compress(resp)
137
+ })
138
+ ```
139
+
140
+ ```js
141
+ app.get('/', async (req, reply) => {
142
+ return new Response('Hello from Fetch Response')
143
+ })
144
+ ```
145
+
146
+ ```js
147
+ app.get('/', (req, reply) => {
148
+ const stream = new ReadableStream({
149
+ start (controller) {
150
+ controller.enqueue(new TextEncoder().encode('Hello from Web ReadableStream'))
151
+ controller.close()
152
+ }
153
+ })
154
+
155
+ reply.header('content-type', 'text/plain')
156
+ reply.compress(stream)
157
+ })
158
+ ```
159
+
109
160
  ## Compress Options
110
161
 
111
162
  ### threshold
@@ -222,15 +273,24 @@ This plugin adds a `preParsing` hook to decompress the request payload based on
222
273
 
223
274
  Currently, the following encoding tokens are supported:
224
275
 
225
- 1. `zstd` (Node.js 22.15+/23.8+)
226
- 2. `br`
227
- 3. `gzip`
228
- 4. `deflate`
276
+ - `zstd` (Node.js 22.15+/23.8+)
277
+ - `br`
278
+ - `gzip`
279
+ - `deflate`
229
280
 
230
281
  If an unsupported encoding or invalid payload is received, the plugin throws an error.
231
282
 
232
283
  If the request header is missing, the plugin yields to the next hook.
233
284
 
285
+ ### Supported payload types
286
+
287
+ The plugin supports compressing the following payload types:
288
+
289
+ - Strings and Buffers
290
+ - Node.js streams
291
+ - Response objects (from the Fetch API)
292
+ - ReadableStream objects (from the Web Streams API)
293
+
234
294
  ### Global hook
235
295
 
236
296
  The global request decompression hook is enabled by default. To disable it, pass `{ global: false }`:
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)
@@ -124,7 +124,9 @@ function processCompressParams (opts) {
124
124
  }
125
125
 
126
126
  const params = {
127
- global: (typeof opts.global === 'boolean') ? opts.global : true
127
+ global: (typeof opts.globalCompression === 'boolean')
128
+ ? opts.globalCompression
129
+ : (typeof opts.global === 'boolean') ? opts.global : true
128
130
  }
129
131
 
130
132
  params.removeContentLengthHeader = typeof opts.removeContentLengthHeader === 'boolean' ? opts.removeContentLengthHeader : true
@@ -184,7 +186,9 @@ function processDecompressParams (opts) {
184
186
  const customZlib = opts.zlib || zlib
185
187
 
186
188
  const params = {
187
- global: (typeof opts.global === 'boolean') ? opts.global : true,
189
+ global: (typeof opts.globalDecompression === 'boolean')
190
+ ? opts.globalDecompression
191
+ : (typeof opts.global === 'boolean') ? opts.global : true,
188
192
  onUnsupportedRequestEncoding: opts.onUnsupportedRequestEncoding,
189
193
  onInvalidRequestPayload: opts.onInvalidRequestPayload,
190
194
  decompressStream: {
@@ -254,6 +258,7 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
254
258
  if (payload == null) {
255
259
  return next()
256
260
  }
261
+
257
262
  const responseEncoding = reply.getHeader('Content-Encoding')
258
263
  if (responseEncoding && responseEncoding !== 'identity') {
259
264
  // response is already compressed
@@ -290,10 +295,18 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
290
295
  }
291
296
 
292
297
  if (typeof payload.pipe !== 'function') {
293
- if (Buffer.byteLength(payload) < params.threshold) {
294
- return next()
298
+ if (isFetchResponse(payload)) {
299
+ payload = payload.body
300
+ }
301
+
302
+ if (isWebReadableStream(payload)) {
303
+ payload = webStreamToNodeReadable(payload)
304
+ } else {
305
+ if (Buffer.byteLength(payload) < params.threshold) {
306
+ return next()
307
+ }
308
+ payload = Readable.from(intoAsyncIterator(payload))
295
309
  }
296
- payload = Readable.from(intoAsyncIterator(payload))
297
310
  }
298
311
 
299
312
  setVaryHeader(reply)
@@ -408,7 +421,13 @@ function compress (params) {
408
421
  }
409
422
 
410
423
  if (typeof payload.pipe !== 'function') {
411
- if (!Buffer.isBuffer(payload) && typeof payload !== 'string') {
424
+ if (isFetchResponse(payload)) {
425
+ payload = payload.body
426
+ }
427
+
428
+ if (isWebReadableStream(payload)) {
429
+ payload = webStreamToNodeReadable(payload)
430
+ } else if (!Buffer.isBuffer(payload) && typeof payload !== 'string') {
412
431
  payload = this.serialize(payload)
413
432
  }
414
433
  }
package/lib/utils.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { Readable: NodeReadable } = require('node:stream')
4
+
3
5
  // https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1
4
6
  function isZstd (buffer) {
5
7
  return (
@@ -49,6 +51,18 @@ function isStream (stream) {
49
51
  return stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'
50
52
  }
51
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
+
52
66
  /**
53
67
  * Provide a async iteratable for Readable.from
54
68
  */
@@ -90,4 +104,4 @@ async function * intoAsyncIterator (payload) {
90
104
  yield payload
91
105
  }
92
106
 
93
- module.exports = { isZstd, 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.1.0",
3
+ "version": "8.3.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.32.0",
28
- "typescript": "~5.8.3"
26
+ "tsd": "^0.33.0",
27
+ "typescript": "~5.9.2"
29
28
  },
30
29
  "scripts": {
31
30
  "lint": "eslint",
@@ -84,7 +83,7 @@
84
83
  "url": "git+https://github.com/fastify/fastify-compress.git"
85
84
  },
86
85
  "tsd": {
87
- "directory": "test/types"
86
+ "directory": "types"
88
87
  },
89
88
  "publishConfig": {
90
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) => {
@@ -265,6 +266,54 @@ describe('When `global` is not set, it is `true` by default :', async () => {
265
266
  const payload = zlib.gunzipSync(response.rawPayload)
266
267
  t.assert.equal(payload.toString('utf-8'), 'hello')
267
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
+ })
268
317
  })
269
318
 
270
319
  describe('It should send compressed Stream data when `global` is `true` :', async () => {
@@ -3350,3 +3399,152 @@ for (const contentType of notByDefaultSupportedContentTypes) {
3350
3399
  t.assert.equal(response.rawPayload.toString('utf-8'), file)
3351
3400
  })
3352
3401
  }
3402
+
3403
+ test('It should support separate globalCompression and globalDecompression settings', async (t) => {
3404
+ t.plan(3)
3405
+
3406
+ // Test case: disable compression but enable decompression globally
3407
+ const fastify = Fastify()
3408
+ await fastify.register(compressPlugin, {
3409
+ globalCompression: false,
3410
+ globalDecompression: true
3411
+ })
3412
+
3413
+ const testData = { message: 'test data for decompression only' }
3414
+
3415
+ fastify.get('/', (request, reply) => {
3416
+ reply.send(testData)
3417
+ })
3418
+
3419
+ // Test that compression is disabled
3420
+ const getResponse = await fastify.inject({
3421
+ url: '/',
3422
+ method: 'GET',
3423
+ headers: {
3424
+ 'accept-encoding': 'gzip, deflate, br'
3425
+ }
3426
+ })
3427
+
3428
+ t.assert.equal(getResponse.statusCode, 200)
3429
+ // No compression should happen since globalCompression is false
3430
+ t.assert.equal(getResponse.headers['content-encoding'], undefined)
3431
+ t.assert.deepEqual(getResponse.json(), testData)
3432
+ })
3433
+
3434
+ test('It should enable decompression when globalDecompression is true', async (t) => {
3435
+ t.plan(2)
3436
+
3437
+ // Test case: enable decompression globally
3438
+ const fastify = Fastify()
3439
+ await fastify.register(compressPlugin, {
3440
+ globalCompression: false,
3441
+ globalDecompression: true
3442
+ })
3443
+
3444
+ // Create a compressed request payload to test decompression
3445
+ const compressedPayload = zlib.gzipSync(JSON.stringify({ input: 'compressed data' }))
3446
+
3447
+ fastify.post('/', (request, reply) => {
3448
+ // This should be decompressed automatically
3449
+ reply.send({ received: request.body })
3450
+ })
3451
+
3452
+ // Test that decompression is enabled
3453
+ const postResponse = await fastify.inject({
3454
+ url: '/',
3455
+ method: 'POST',
3456
+ headers: {
3457
+ 'content-encoding': 'gzip',
3458
+ 'content-type': 'application/json'
3459
+ },
3460
+ payload: compressedPayload
3461
+ })
3462
+
3463
+ t.assert.equal(postResponse.statusCode, 200)
3464
+ // Decompression should work since globalDecompression is true
3465
+ t.assert.deepEqual(postResponse.json(), { received: { input: 'compressed data' } })
3466
+ })
3467
+
3468
+ test('It should fall back to global option when specific options are not provided', async (t) => {
3469
+ t.plan(2)
3470
+
3471
+ // Test that global: false affects both compression and decompression
3472
+ const fastify = Fastify()
3473
+ await fastify.register(compressPlugin, {
3474
+ global: false
3475
+ })
3476
+
3477
+ const testData = { message: 'test data' }
3478
+
3479
+ fastify.get('/', (request, reply) => {
3480
+ reply.send(testData)
3481
+ })
3482
+
3483
+ const response = await fastify.inject({
3484
+ url: '/',
3485
+ method: 'GET',
3486
+ headers: {
3487
+ 'accept-encoding': 'gzip, deflate, br'
3488
+ }
3489
+ })
3490
+
3491
+ t.assert.equal(response.statusCode, 200)
3492
+ // No compression should happen since global is false
3493
+ t.assert.equal(response.headers['content-encoding'], undefined)
3494
+ })
3495
+
3496
+ test('It should demonstrate globalCompression overrides global setting', async (t) => {
3497
+ t.plan(2)
3498
+
3499
+ // Test that globalCompression: true overrides global: false
3500
+ const fastify = Fastify()
3501
+ await fastify.register(compressPlugin, {
3502
+ global: false,
3503
+ globalCompression: true,
3504
+ threshold: 0
3505
+ })
3506
+
3507
+ fastify.get('/', (request, reply) => {
3508
+ reply.send({ message: 'globalCompression overrides global' })
3509
+ })
3510
+
3511
+ const response = await fastify.inject({
3512
+ url: '/',
3513
+ method: 'GET',
3514
+ headers: { 'accept-encoding': 'gzip' }
3515
+ })
3516
+
3517
+ t.assert.equal(response.statusCode, 200)
3518
+ t.assert.equal(response.headers['content-encoding'], 'gzip') // Compression enabled despite global: false
3519
+ })
3520
+
3521
+ test('It should demonstrate globalDecompression controls decompression independently', async (t) => {
3522
+ t.plan(2)
3523
+
3524
+ // Test globalDecompression: false disables decompression even with global: true
3525
+ const fastify = Fastify()
3526
+ await fastify.register(compressPlugin, {
3527
+ global: true,
3528
+ globalDecompression: false
3529
+ })
3530
+
3531
+ const compressedPayload = zlib.gzipSync(JSON.stringify({ test: 'decompression' }))
3532
+
3533
+ fastify.post('/', (request, reply) => {
3534
+ reply.send({ received: request.body })
3535
+ })
3536
+
3537
+ const response = await fastify.inject({
3538
+ url: '/',
3539
+ method: 'POST',
3540
+ headers: {
3541
+ 'content-encoding': 'gzip',
3542
+ 'content-type': 'application/json'
3543
+ },
3544
+ payload: compressedPayload
3545
+ })
3546
+
3547
+ // Should get 400 error since decompression is disabled and compressed data can't be parsed
3548
+ t.assert.equal(response.statusCode, 400)
3549
+ t.assert.ok(response.body.includes('Content-Length') || response.body.includes('Bad Request'))
3550
+ })
@@ -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
+ })
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 {
@@ -77,6 +77,8 @@ declare namespace fastifyCompress {
77
77
  encodings?: EncodingToken[];
78
78
  forceRequestEncoding?: EncodingToken;
79
79
  global?: boolean;
80
+ globalDecompression?: boolean;
81
+ globalCompression?: boolean;
80
82
  inflateIfDeflated?: boolean;
81
83
  onInvalidRequestPayload?: (encoding: string, request: FastifyRequest, error: Error) => Error | undefined | null;
82
84
  onUnsupportedEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => string | Buffer | Stream;
@@ -132,3 +132,18 @@ expectError(appThatTriggerAnError.register(fastifyCompress, {
132
132
  global: true,
133
133
  thisOptionDoesNotExist: 'trigger a typescript error'
134
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
+ })
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__ripgrep__search"
5
- ],
6
- "deny": []
7
- }
8
- }
package/CLAUDE.md DELETED
@@ -1,64 +0,0 @@
1
- # CLAUDE.md
2
-
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
-
5
- ## Development Commands
6
-
7
- ### Testing
8
- - `npm run test` - Run both unit and TypeScript tests
9
- - `npm run test:unit` - Run unit tests with Node.js test runner
10
- - `npm run test:coverage` - Run tests with coverage reporting
11
- - `npm run lint` - Run ESLint for code style checking
12
- - `npm run lint:fix` - Auto-fix linting issues
13
-
14
- ### Running Single Tests
15
- Use Node.js test runner with file path:
16
- ```bash
17
- node --test test/global-compress.test.js
18
- ```
19
-
20
- ## Architecture Overview
21
-
22
- This is a Fastify plugin (`@fastify/compress`) that provides compression and decompression capabilities for HTTP requests and responses.
23
-
24
- ### Core Components
25
-
26
- **Main Plugin (`index.js`)**: 574-line plugin implementation using Fastify's hook system:
27
- - `onSend` hook for response compression
28
- - `preParsing` hook for request decompression
29
- - `onRoute` hook for route-level configuration
30
-
31
- **Utilities (`lib/utils.js`)**: Core compression detection and stream handling:
32
- - `isZstd()` / `isGzip()` / `isDeflate()` - Magic byte detection (RFC 8878/1952/1950)
33
- - `isStream()` - Stream type checking
34
- - `intoAsyncIterator()` - Payload conversion utilities
35
-
36
- **TypeScript Support (`types/index.d.ts`)**: Full type definitions with Fastify module augmentation
37
-
38
- ### Compression System
39
-
40
- **Supported Encodings**: zstd (Node.js 22.15+/23.8+), brotli (`br`), gzip, deflate, identity
41
- **Priority Order**: zstd > br > gzip > deflate > * (defaults to gzip) > identity
42
- **Threshold-based**: Default 1024 bytes minimum for compression
43
- **Content-Type Aware**: Uses `mime-db` for compressible content detection
44
-
45
- ### Configuration Levels
46
- 1. **Global**: Applied to all routes via plugin options
47
- 2. **Route-level**: Override global settings in route config
48
- 3. **Runtime**: Manual compression via `reply.compress()`
49
-
50
- ### Key Dependencies
51
- - `@fastify/accept-negotiator` - Content encoding negotiation
52
- - `fastify-plugin` - Plugin registration
53
- - `mime-db` - MIME type database
54
- - `minipass`, `pump`, `pumpify` - Stream processing
55
-
56
- ### Testing Structure
57
- Uses Node.js built-in test runner with describe/test pattern. Test files are organized by functionality:
58
- - `global-compress.test.js` / `global-decompress.test.js` - Global functionality
59
- - `routes-compress.test.js` / `routes-decompress.test.js` - Route-level functionality
60
- - `utils.test.js` - Utility functions
61
- - `regression/` - Regression tests
62
-
63
- ### Code Style
64
- Uses `neostandard` (ESLint-based) with TypeScript support. Configuration in `eslint.config.js`.