@fastify/compress 8.3.1 → 9.1.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/.c8rc CHANGED
@@ -1,14 +1,10 @@
1
1
  {
2
2
  "all": true,
3
- "exclude": [
4
- "test/",
5
- "coverage/",
6
- "types/"
7
- ],
3
+ "exclude": ["test/", "coverage/", "types/", "eslint.config.js"],
8
4
  "clean": true,
9
5
  "check-coverage": true,
10
6
  "branches": 100,
11
7
  "lines": 100,
12
8
  "functions": 100,
13
9
  "statements": 100
14
- }
10
+ }
@@ -2,12 +2,52 @@ version: 2
2
2
  updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
+ commit-message:
6
+ # Prefix all commit messages with "chore: "
7
+ prefix: "chore"
5
8
  schedule:
6
- interval: "monthly"
9
+ interval: "weekly"
10
+ cooldown:
11
+ default-days: 7
12
+ allow:
13
+ - dependency-name: "*"
14
+ update-types:
15
+ - "version-update:semver-major"
7
16
  open-pull-requests-limit: 10
8
17
 
9
18
  - package-ecosystem: "npm"
10
19
  directory: "/"
20
+ commit-message:
21
+ # Prefix all commit messages with "chore: "
22
+ prefix: "chore"
11
23
  schedule:
12
- interval: "monthly"
24
+ interval: "weekly"
25
+ cooldown:
26
+ default-days: 7
27
+ versioning-strategy: "increase-if-necessary"
28
+ allow:
29
+ - dependency-name: "*"
30
+ update-types:
31
+ - "version-update:semver-major"
32
+ ignore:
33
+ # TODO: remove ignore until neostandard support ESLint 10
34
+ - dependency-name: "eslint"
35
+ - dependency-name: "neostandard"
36
+ - dependency-name: "@stylistic/*"
13
37
  open-pull-requests-limit: 10
38
+ groups:
39
+ # Production dependencies with breaking changes
40
+ dependencies:
41
+ dependency-type: "production"
42
+ # ESLint related dependencies
43
+ dev-dependencies-eslint:
44
+ patterns:
45
+ - "eslint"
46
+ - "neostandard"
47
+ - "@stylistic/*"
48
+ # TypeScript related dependencies
49
+ dev-dependencies-typescript:
50
+ patterns:
51
+ - "@types/*"
52
+ - "tstyche"
53
+ - "typescript"
@@ -27,7 +27,7 @@ jobs:
27
27
  permissions:
28
28
  contents: write
29
29
  pull-requests: write
30
- uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5
30
+ uses: fastify/workflows/.github/workflows/plugins-ci.yml@v6
31
31
  with:
32
32
  license-check: true
33
33
  lint: true
@@ -0,0 +1,19 @@
1
+ name: Lock Threads
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 0 1 * *'
6
+ workflow_dispatch:
7
+
8
+ concurrency:
9
+ group: lock
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ lock-threads:
16
+ permissions:
17
+ issues: write
18
+ pull-requests: write
19
+ uses: fastify/workflows/.github/workflows/lock-threads.yml@v6
package/LICENSE CHANGED
@@ -1,8 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-present The Fastify team
4
-
5
- The Fastify team members are listed at https://github.com/fastify/fastify#team.
3
+ Copyright (c) 2017-present The Fastify team <https://github.com/fastify/fastify#team>
6
4
 
7
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
8
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -396,9 +396,8 @@ await fastify.register(
396
396
 
397
397
  ## Acknowledgments
398
398
 
399
- Past sponsors:
400
-
401
- - [LetzDoIt](http://www.letzdoitapp.com/)
399
+ Past sp
400
+ - LetzDoIt
402
401
 
403
402
  ## License
404
403
 
package/index.js CHANGED
@@ -2,17 +2,24 @@
2
2
 
3
3
  const zlib = require('node:zlib')
4
4
  const { inherits, format } = require('node:util')
5
+ const { pipeline, compose } = require('node:stream')
5
6
 
6
7
  const fp = require('fastify-plugin')
7
8
  const encodingNegotiator = require('@fastify/accept-negotiator')
8
- const pump = require('pump')
9
9
  const mimedb = require('mime-db')
10
- const peek = require('peek-stream')
11
10
  const { Minipass } = require('minipass')
12
- const pumpify = require('pumpify')
13
11
  const { Readable } = require('readable-stream')
14
12
 
15
- const { isStream, isGzip, isDeflate, intoAsyncIterator, isWebReadableStream, isFetchResponse, webStreamToNodeReadable } = require('./lib/utils')
13
+ const {
14
+ isStream,
15
+ isGzip,
16
+ isDeflate,
17
+ intoAsyncIterator,
18
+ isWebReadableStream,
19
+ isFetchResponse,
20
+ webStreamToNodeReadable,
21
+ createPeekTransform
22
+ } = require('./lib/utils')
16
23
 
17
24
  const InvalidRequestEncodingError = createError('FST_CP_ERR_INVALID_CONTENT_ENCODING', 'Unsupported Content-Encoding: %s', 415)
18
25
  const InvalidRequestCompressedPayloadError = createError('FST_CP_ERR_INVALID_CONTENT', 'Could not decompress the request payload using the provided encoding', 400)
@@ -50,11 +57,6 @@ function fastifyCompress (fastify, opts, next) {
50
57
 
51
58
  // add onSend hook onto each route as needed
52
59
  fastify.addHook('onRoute', (routeOptions) => {
53
- // If route config.compress has been set it takes precedence over compress
54
- if (routeOptions.config?.compress !== undefined) {
55
- routeOptions.compress = routeOptions.config.compress
56
- }
57
-
58
60
  // Manage compression options
59
61
  if (routeOptions.compress !== undefined) {
60
62
  if (typeof routeOptions.compress === 'object') {
@@ -79,11 +81,6 @@ function fastifyCompress (fastify, opts, next) {
79
81
  buildRouteCompress(fastify, globalCompressParams, routeOptions, true)
80
82
  }
81
83
 
82
- // If route config.decompress has been set it takes precedence over compress
83
- if (routeOptions.config?.decompress !== undefined) {
84
- routeOptions.decompress = routeOptions.config.decompress
85
- }
86
-
87
84
  // Manage decompression options
88
85
  if (routeOptions.decompress !== undefined) {
89
86
  if (typeof routeOptions.decompress === 'object') {
@@ -269,6 +266,9 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
269
266
  const noCompress =
270
267
  // don't compress on x-no-compression header
271
268
  (req.headers['x-no-compression'] !== undefined) ||
269
+ // don't compress partial content: Content-Range describes unencoded byte offsets
270
+ (reply.statusCode === 206) ||
271
+ (reply.getHeader('Content-Range') !== undefined) ||
272
272
  // don't compress if not one of the indicated compressible types
273
273
  (shouldCompress(reply.getHeader('Content-Type') || 'application/json', params.compressibleTypes) === false) ||
274
274
  // don't compress on missing or identity `accept-encoding` header
@@ -289,7 +289,7 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
289
289
  encoding === undefined
290
290
  ? reply.removeHeader('Content-Encoding')
291
291
  : reply.header('Content-Encoding', 'identity')
292
- pump(stream, payload = unzipStream(params.uncompressStream), onEnd.bind(reply))
292
+ pipeline(stream, payload = unzipStream(params.uncompressStream), onEnd.bind(reply))
293
293
  }
294
294
  return next(null, payload)
295
295
  }
@@ -316,7 +316,7 @@ function buildRouteCompress (_fastify, params, routeOptions, decorateOnly) {
316
316
  }
317
317
 
318
318
  stream = zipStream(params.compressStream, encoding)
319
- pump(payload, stream, onEnd.bind(reply))
319
+ pipeline(payload, stream, onEnd.bind(reply))
320
320
  next(null, stream)
321
321
  }
322
322
  }
@@ -378,7 +378,10 @@ function buildRouteDecompress (_fastify, params, routeOptions) {
378
378
  raw.on('data', trackEncodedLength.bind(decompresser))
379
379
  raw.on('end', removeEncodedLengthTracking)
380
380
 
381
- next(null, pump(raw, decompresser))
381
+ pipeline(raw, decompresser, () => {
382
+ // Cleanup callback - decompression errors are handled by decompresser's error handler
383
+ })
384
+ next(null, decompresser)
382
385
  }
383
386
  }
384
387
 
@@ -415,7 +418,7 @@ function compress (params) {
415
418
  encoding === undefined
416
419
  ? this.removeHeader('Content-Encoding')
417
420
  : this.header('Content-Encoding', 'identity')
418
- pump(stream, payload = unzipStream(params.uncompressStream), onEnd.bind(this))
421
+ pipeline(stream, payload = unzipStream(params.uncompressStream), onEnd.bind(this))
419
422
  }
420
423
  return this.send(payload)
421
424
  }
@@ -446,7 +449,7 @@ function compress (params) {
446
449
  }
447
450
 
448
451
  stream = zipStream(params.compressStream, encoding)
449
- pump(payload, stream, onEnd.bind(this))
452
+ pipeline(payload, stream, onEnd.bind(this))
450
453
  return this.send(stream)
451
454
  }
452
455
  }
@@ -464,7 +467,17 @@ function setVaryHeader (reply) {
464
467
  }
465
468
 
466
469
  function onEnd (err) {
467
- if (err) this.log.error(err)
470
+ // Client disconnection during streaming is expected and handled by Fastify.
471
+ // Do not log "premature close" errors at error level since they are not
472
+ // actual errors - they occur when clients disconnect mid-response.
473
+ // Node's native stream.pipeline emits ERR_STREAM_PREMATURE_CLOSE with the
474
+ // message 'Premature close', while the legacy pump/end-of-stream packages
475
+ // emit 'premature close' without a code, so both variants are checked.
476
+ // See: https://github.com/fastify/fastify-compress/issues/382
477
+ // See: https://github.com/fastify/fastify-compress/issues/410
478
+ if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE' && err.message !== 'premature close') {
479
+ this.log.error(err)
480
+ }
468
481
  }
469
482
 
470
483
  function trackEncodedLength (chunk) {
@@ -549,26 +562,26 @@ function maybeUnzip (payload, serialize) {
549
562
  }
550
563
 
551
564
  function zipStream (deflate, encoding) {
552
- return peek({ newline: false, maxBuffer: 10 }, function (data, swap) {
565
+ return createPeekTransform(function (data) {
553
566
  switch (isCompressed(data)) {
554
- case 1: return swap(null, new Minipass())
555
- case 2: return swap(null, new Minipass())
567
+ case 1: return new Minipass()
568
+ case 2: return new Minipass()
556
569
  }
557
- return swap(null, deflate[encoding]())
570
+ return deflate[encoding]()
558
571
  })
559
572
  }
560
573
 
561
574
  function unzipStream (inflate, maxRecursion) {
562
575
  if (!(maxRecursion >= 0)) maxRecursion = 3
563
- return peek({ newline: false, maxBuffer: 10 }, function (data, swap) {
576
+ return createPeekTransform(function (data) {
564
577
  // This path is never taken, when `maxRecursion` < 0 it is automatically set back to 3
565
578
  /* c8 ignore next */
566
- if (maxRecursion < 0) return swap(new Error('Maximum recursion reached'))
579
+ if (maxRecursion < 0) throw new Error('Maximum recursion reached')
567
580
  switch (isCompressed(data)) {
568
- case 1: return swap(null, pumpify(inflate.gzip(), unzipStream(inflate, maxRecursion - 1)))
569
- case 2: return swap(null, pumpify(inflate.deflate(), unzipStream(inflate, maxRecursion - 1)))
581
+ case 1: return compose(inflate.gzip(), unzipStream(inflate, maxRecursion - 1))
582
+ case 2: return compose(inflate.deflate(), unzipStream(inflate, maxRecursion - 1))
570
583
  }
571
- return swap(null, new Minipass())
584
+ return new Minipass()
572
585
  })
573
586
  }
574
587
 
package/lib/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { Readable: NodeReadable } = require('node:stream')
4
+ const { Transform } = require('readable-stream')
4
5
 
5
6
  // https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1
6
7
  function isZstd (buffer) {
@@ -104,4 +105,92 @@ async function * intoAsyncIterator (payload) {
104
105
  yield payload
105
106
  }
106
107
 
107
- module.exports = { isZstd, isGzip, isDeflate, isStream, intoAsyncIterator, isWebReadableStream, isFetchResponse, webStreamToNodeReadable }
108
+ function createPeekTransform (swapStream, peekSize = 10) {
109
+ const buffers = []
110
+ let bufferSize = 0
111
+ let downstream = null
112
+ let flushCallback = null
113
+
114
+ const _transform = new Transform({
115
+ transform (chunk, encoding, callback) {
116
+ // when downstream already selected, directly passthrough
117
+ if (downstream !== null) return downstream.write(chunk, encoding, callback)
118
+
119
+ bufferSize += chunk.length
120
+ buffers.push(chunk)
121
+
122
+ // read more data when buffer size smaller than peeksize
123
+ if (bufferSize < peekSize) return callback()
124
+
125
+ wireDownStream(callback)
126
+ },
127
+ flush (callback) {
128
+ // used in "end" event to notify the transform finished flush
129
+ flushCallback = callback
130
+ if (downstream !== null) {
131
+ // all data should be flushed to downstream already
132
+ // we end the stream
133
+ downstream.end()
134
+ } else {
135
+ // flush internal buffers and end the downstream
136
+ wireDownStream((error) => {
137
+ if (error) {
138
+ flushCallback = null
139
+ callback(error)
140
+ } else {
141
+ downstream.end()
142
+ }
143
+ })
144
+ }
145
+ }
146
+ })
147
+
148
+ function onData (chunk) {
149
+ _transform.push(chunk)
150
+ }
151
+
152
+ function onError (error) {
153
+ _transform.destroy(error)
154
+ }
155
+
156
+ function onEnd () {
157
+ flushCallback && flushCallback()
158
+ // cleanup
159
+ downstream.off('data', onData)
160
+ downstream.off('error', onError)
161
+ downstream.off('end', onEnd)
162
+ downstream = null
163
+ flushCallback = null
164
+
165
+ _transform.push(null)
166
+ }
167
+
168
+ function wireDownStream (callback) {
169
+ const data = Buffer.concat(buffers)
170
+ try {
171
+ downstream = swapStream(data.subarray(0, peekSize))
172
+ downstream.on('data', onData)
173
+ downstream.on('error', onError)
174
+ downstream.on('end', onEnd)
175
+ downstream.write(data, callback)
176
+ // cleanup
177
+ buffers.length = 0
178
+ } catch (error) {
179
+ callback(error)
180
+ }
181
+ }
182
+
183
+ return _transform
184
+ }
185
+
186
+ module.exports = {
187
+ isZstd,
188
+ isGzip,
189
+ isDeflate,
190
+ isStream,
191
+ intoAsyncIterator,
192
+ isWebReadableStream,
193
+ isFetchResponse,
194
+ webStreamToNodeReadable,
195
+ createPeekTransform
196
+ }
package/package.json CHANGED
@@ -1,36 +1,33 @@
1
1
  {
2
2
  "name": "@fastify/compress",
3
- "version": "8.3.1",
3
+ "version": "9.1.0",
4
4
  "description": "Fastify compression utils",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
7
7
  "types": "types/index.d.ts",
8
8
  "dependencies": {
9
9
  "@fastify/accept-negotiator": "^2.0.0",
10
- "fastify-plugin": "^5.0.0",
10
+ "fastify-plugin": "^6.0.0",
11
11
  "mime-db": "^1.52.0",
12
12
  "minipass": "^7.0.4",
13
- "peek-stream": "^1.1.3",
14
- "pump": "^3.0.0",
15
- "pumpify": "^2.0.1",
16
13
  "readable-stream": "^4.5.2"
17
14
  },
18
15
  "devDependencies": {
19
- "@types/node": "^24.0.8",
16
+ "@types/node": "^26.0.1",
20
17
  "adm-zip": "^0.5.12",
21
- "c8": "^10.1.2",
18
+ "c8": "^11.0.0",
22
19
  "eslint": "^9.17.0",
23
20
  "fastify": "^5.0.0",
24
21
  "jsonstream": "^1.0.3",
25
- "neostandard": "^0.12.0",
26
- "tsd": "^0.33.0",
27
- "typescript": "~5.9.2"
22
+ "neostandard": "^0.13.0",
23
+ "tstyche": "^7.1.0",
24
+ "typescript": "~6.0.2"
28
25
  },
29
26
  "scripts": {
30
27
  "lint": "eslint",
31
28
  "lint:fix": "eslint --fix",
32
29
  "test": "npm run test:unit && npm run test:typescript",
33
- "test:typescript": "tsd",
30
+ "test:typescript": "tstyche",
34
31
  "test:unit": "node --test",
35
32
  "test:coverage": "c8 node --test && c8 report --reporter=html",
36
33
  "test:unit:verbose": "npm run test:unit -- -Rspec"
@@ -43,7 +40,7 @@
43
40
  "brotli",
44
41
  "zstd"
45
42
  ],
46
- "author": "Tomas Della Vedova - @delvedor (http://delved.org)",
43
+ "author": "Tomas Della Vedova - @delvedor (https://delvedor.dev)",
47
44
  "contributors": [
48
45
  {
49
46
  "name": "Matteo Collina",
@@ -82,9 +79,6 @@
82
79
  "type": "git",
83
80
  "url": "git+https://github.com/fastify/fastify-compress.git"
84
81
  },
85
- "tsd": {
86
- "directory": "types"
87
- },
88
82
  "publishConfig": {
89
83
  "access": "public"
90
84
  }
@@ -682,6 +682,41 @@ describe('When a custom `zlib` option is provided, it should compress data :`',
682
682
  t.assert.equal(response.headers['content-encoding'], 'gzip')
683
683
  t.assert.equal(payload.toString('utf-8'), file)
684
684
  })
685
+
686
+ test('using the custom `createZstdCompress()` method', async (t) => {
687
+ if (typeof zlib.createZstdCompress !== 'function') {
688
+ t.skip('zstd not supported in this Node.js version')
689
+ return
690
+ }
691
+ t.plan(4)
692
+
693
+ let usedCustom = false
694
+ const customZlib = { createZstdCompress: () => (usedCustom = true) && zlib.createZstdCompress() }
695
+
696
+ const fastify = Fastify()
697
+ await fastify.register(compressPlugin, { global: true, zlib: customZlib })
698
+
699
+ fastify.get('/', (_request, reply) => {
700
+ reply
701
+ .type('text/plain')
702
+ .compress(createReadStream('./package.json'))
703
+ })
704
+
705
+ const response = await fastify.inject({
706
+ url: '/',
707
+ method: 'GET',
708
+ headers: {
709
+ 'accept-encoding': 'zstd'
710
+ }
711
+ })
712
+ t.assert.equal(usedCustom, true)
713
+
714
+ const file = readFileSync('./package.json', 'utf8')
715
+ const payload = zlib.zstdDecompressSync(response.rawPayload)
716
+ t.assert.equal(response.headers.vary, 'accept-encoding')
717
+ t.assert.equal(response.headers['content-encoding'], 'zstd')
718
+ t.assert.equal(payload.toString('utf-8'), file)
719
+ })
685
720
  })
686
721
 
687
722
  describe('When a malformed custom `zlib` option is provided, it should compress data :', async () => {
@@ -765,6 +800,37 @@ describe('When a malformed custom `zlib` option is provided, it should compress
765
800
  const payload = zlib.gunzipSync(response.rawPayload)
766
801
  t.assert.equal(payload.toString('utf-8'), 'hello')
767
802
  })
803
+
804
+ test('using the fallback default Node.js core `zlib.createZstdCompress()` method', async (t) => {
805
+ if (typeof zlib.createZstdCompress !== 'function') {
806
+ t.skip('zstd not supported in this Node.js version')
807
+ return
808
+ }
809
+ t.plan(1)
810
+
811
+ const fastify = Fastify()
812
+ await fastify.register(compressPlugin, {
813
+ global: true,
814
+ threshold: 0,
815
+ zlib: true // will trigger a fallback on the default zlib.createZstdCompress
816
+ })
817
+
818
+ fastify.get('/', (_request, reply) => {
819
+ reply
820
+ .type('text/plain')
821
+ .compress('hello')
822
+ })
823
+
824
+ const response = await fastify.inject({
825
+ url: '/',
826
+ method: 'GET',
827
+ headers: {
828
+ 'accept-encoding': 'zstd'
829
+ }
830
+ })
831
+ const payload = zlib.zstdDecompressSync(response.rawPayload)
832
+ t.assert.equal(payload.toString('utf-8'), 'hello')
833
+ })
768
834
  })
769
835
 
770
836
  describe('When `inflateIfDeflated` is `true` and `X-No-Compression` request header is `true` :', async () => {
@@ -3548,3 +3614,56 @@ test('It should demonstrate globalDecompression controls decompression independe
3548
3614
  t.assert.equal(response.statusCode, 400)
3549
3615
  t.assert.ok(response.body.includes('Content-Length') || response.body.includes('Bad Request'))
3550
3616
  })
3617
+
3618
+ describe('Range Responses', () => {
3619
+ test('it should not compress 206 Partial Content responses', async (t) => {
3620
+ t.plan(4)
3621
+ const fastify = Fastify()
3622
+ await fastify.register(compressPlugin, { threshold: 0 })
3623
+
3624
+ fastify.get('/partial', (request, reply) => {
3625
+ reply
3626
+ .status(206)
3627
+ .header('Content-Type', 'text/plain')
3628
+ .header('Content-Range', 'bytes 0-4/12')
3629
+ .header('Content-Length', '5')
3630
+ .send('hello')
3631
+ })
3632
+
3633
+ const response = await fastify.inject({
3634
+ url: '/partial',
3635
+ method: 'GET',
3636
+ headers: { 'accept-encoding': 'gzip' }
3637
+ })
3638
+
3639
+ t.assert.equal(response.statusCode, 206)
3640
+ t.assert.equal(response.headers['content-encoding'], undefined)
3641
+ t.assert.equal(response.headers['content-range'], 'bytes 0-4/12')
3642
+ t.assert.equal(response.body, 'hello')
3643
+ })
3644
+
3645
+ test('it should not compress responses carrying a Content-Range header', async (t) => {
3646
+ t.plan(4)
3647
+ const fastify = Fastify()
3648
+ await fastify.register(compressPlugin, { threshold: 0 })
3649
+
3650
+ fastify.get('/content-range', (request, reply) => {
3651
+ reply
3652
+ .header('Content-Type', 'text/plain')
3653
+ .header('Content-Range', 'bytes 0-4/12')
3654
+ .header('Content-Length', '5')
3655
+ .send('hello')
3656
+ })
3657
+
3658
+ const response = await fastify.inject({
3659
+ url: '/content-range',
3660
+ method: 'GET',
3661
+ headers: { 'accept-encoding': 'gzip' }
3662
+ })
3663
+
3664
+ t.assert.equal(response.statusCode, 200)
3665
+ t.assert.equal(response.headers['content-encoding'], undefined)
3666
+ t.assert.equal(response.headers['content-range'], 'bytes 0-4/12')
3667
+ t.assert.equal(response.body, 'hello')
3668
+ })
3669
+ })
@@ -2,9 +2,9 @@
2
2
 
3
3
  const { test, describe } = require('node:test')
4
4
  const { createReadStream } = require('node:fs')
5
+ const { pipeline } = require('node:stream')
5
6
  const path = require('node:path')
6
7
  const zlib = require('node:zlib')
7
- const pump = require('pump')
8
8
  const Fastify = require('fastify')
9
9
  const compressPlugin = require('../index')
10
10
 
@@ -12,7 +12,9 @@ function createPayload (compressor) {
12
12
  let payload = createReadStream(path.resolve(__dirname, '../package.json'))
13
13
 
14
14
  if (compressor) {
15
- payload = pump(payload, compressor())
15
+ const compressed = compressor()
16
+ pipeline(payload, compressed, () => {})
17
+ payload = compressed
16
18
  }
17
19
 
18
20
  return payload