@fastify/compress
Advanced tools
| { | ||
| "permissions": { | ||
| "allow": [ | ||
| "mcp__ripgrep__search" | ||
| ], | ||
| "deny": [] | ||
| } | ||
| } |
+64
| # CLAUDE.md | ||
| This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. | ||
| ## Development Commands | ||
| ### Testing | ||
| - `npm run test` - Run both unit and TypeScript tests | ||
| - `npm run test:unit` - Run unit tests with Node.js test runner | ||
| - `npm run test:coverage` - Run tests with coverage reporting | ||
| - `npm run lint` - Run ESLint for code style checking | ||
| - `npm run lint:fix` - Auto-fix linting issues | ||
| ### Running Single Tests | ||
| Use Node.js test runner with file path: | ||
| ```bash | ||
| node --test test/global-compress.test.js | ||
| ``` | ||
| ## Architecture Overview | ||
| This is a Fastify plugin (`@fastify/compress`) that provides compression and decompression capabilities for HTTP requests and responses. | ||
| ### Core Components | ||
| **Main Plugin (`index.js`)**: 574-line plugin implementation using Fastify's hook system: | ||
| - `onSend` hook for response compression | ||
| - `preParsing` hook for request decompression | ||
| - `onRoute` hook for route-level configuration | ||
| **Utilities (`lib/utils.js`)**: Core compression detection and stream handling: | ||
| - `isZstd()` / `isGzip()` / `isDeflate()` - Magic byte detection (RFC 8878/1952/1950) | ||
| - `isStream()` - Stream type checking | ||
| - `intoAsyncIterator()` - Payload conversion utilities | ||
| **TypeScript Support (`types/index.d.ts`)**: Full type definitions with Fastify module augmentation | ||
| ### Compression System | ||
| **Supported Encodings**: zstd (Node.js 22.15+/23.8+), brotli (`br`), gzip, deflate, identity | ||
| **Priority Order**: zstd > br > gzip > deflate > * (defaults to gzip) > identity | ||
| **Threshold-based**: Default 1024 bytes minimum for compression | ||
| **Content-Type Aware**: Uses `mime-db` for compressible content detection | ||
| ### Configuration Levels | ||
| 1. **Global**: Applied to all routes via plugin options | ||
| 2. **Route-level**: Override global settings in route config | ||
| 3. **Runtime**: Manual compression via `reply.compress()` | ||
| ### Key Dependencies | ||
| - `@fastify/accept-negotiator` - Content encoding negotiation | ||
| - `fastify-plugin` - Plugin registration | ||
| - `mime-db` - MIME type database | ||
| - `minipass`, `pump`, `pumpify` - Stream processing | ||
| ### Testing Structure | ||
| Uses Node.js built-in test runner with describe/test pattern. Test files are organized by functionality: | ||
| - `global-compress.test.js` / `global-decompress.test.js` - Global functionality | ||
| - `routes-compress.test.js` / `routes-decompress.test.js` - Route-level functionality | ||
| - `utils.test.js` - Utility functions | ||
| - `regression/` - Regression tests | ||
| ### Code Style | ||
| Uses `neostandard` (ESLint-based) with TypeScript support. Configuration in `eslint.config.js`. |
+17
-0
@@ -148,2 +148,5 @@ 'use strict' | ||
| } | ||
| if (typeof ((opts.zlib || zlib).createZstdCompress || zlib.createZstdCompress) === 'function') { | ||
| params.compressStream.zstd = () => ((opts.zlib || zlib).createZstdCompress || zlib.createZstdCompress)(params.zlibOptions) | ||
| } | ||
| params.uncompressStream = { | ||
@@ -156,4 +159,12 @@ // Currently params.uncompressStream.br() is never called as we do not have any way to autodetect brotli compression in `fastify-compress` | ||
| } | ||
| if (typeof ((opts.zlib || zlib).createZstdDecompress || zlib.createZstdDecompress) === 'function') { | ||
| // Currently params.uncompressStream.zstd() is never called as we do not have any way to autodetect zstd compression in `fastify-compress` | ||
| // Zstd documentation reference: [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878) | ||
| params.uncompressStream.zstd = /* c8 ignore next */ () => ((opts.zlib || zlib).createZstdDecompress || zlib.createZstdDecompress)(params.zlibOptions) | ||
| } | ||
| const supportedEncodings = ['br', 'gzip', 'deflate', 'identity'] | ||
| if (typeof zlib.createZstdCompress === 'function') { | ||
| supportedEncodings.unshift('zstd') | ||
| } | ||
@@ -189,4 +200,10 @@ params.encodings = Array.isArray(opts.encodings) | ||
| } | ||
| if (typeof (customZlib.createZstdDecompress || zlib.createZstdDecompress) === 'function') { | ||
| params.decompressStream.zstd = customZlib.createZstdDecompress || zlib.createZstdDecompress | ||
| } | ||
| const supportedEncodings = ['br', 'gzip', 'deflate', 'identity'] | ||
| if (typeof zlib.createZstdCompress === 'function') { | ||
| supportedEncodings.unshift('zstd') | ||
| } | ||
@@ -193,0 +210,0 @@ params.encodings = Array.isArray(opts.requestEncodings) |
+15
-1
| 'use strict' | ||
| // https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1 | ||
| function isZstd (buffer) { | ||
| return ( | ||
| typeof buffer === 'object' && | ||
| buffer !== null && | ||
| buffer.length > 3 && | ||
| // Zstd magic number: 0xFD2FB528 (little-endian) | ||
| buffer[0] === 0x28 && | ||
| buffer[1] === 0xb5 && | ||
| buffer[2] === 0x2f && | ||
| buffer[3] === 0xfd | ||
| ) | ||
| } | ||
| // https://datatracker.ietf.org/doc/html/rfc1950#section-2 | ||
@@ -79,2 +93,2 @@ function isDeflate (buffer) { | ||
| module.exports = { isGzip, isDeflate, isStream, intoAsyncIterator } | ||
| module.exports = { isZstd, isGzip, isDeflate, isStream, intoAsyncIterator } |
+3
-1
| MIT License | ||
| Copyright (c) 2017 Fastify | ||
| Copyright (c) 2017-present The Fastify team | ||
| The Fastify team members are listed at https://github.com/fastify/fastify#team. | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
@@ -6,0 +8,0 @@ of this software and associated documentation files (the "Software"), to deal |
+5
-4
| { | ||
| "name": "@fastify/compress", | ||
| "version": "8.0.3", | ||
| "version": "8.1.0", | ||
| "description": "Fastify compression utils", | ||
@@ -27,4 +27,4 @@ "main": "index.js", | ||
| "neostandard": "^0.12.0", | ||
| "tsd": "^0.31.0", | ||
| "typescript": "~5.7.2" | ||
| "tsd": "^0.32.0", | ||
| "typescript": "~5.8.3" | ||
| }, | ||
@@ -45,3 +45,4 @@ "scripts": { | ||
| "gzip", | ||
| "brotli" | ||
| "brotli", | ||
| "zstd" | ||
| ], | ||
@@ -48,0 +49,0 @@ "author": "Tomas Della Vedova - @delvedor (http://delved.org)", |
+25
-9
@@ -8,3 +8,3 @@ # @fastify/compress | ||
| Adds compression utils to [the Fastify `reply` object](https://fastify.dev/docs/latest/Reference/Reply/#reply) and a hook to decompress requests payloads. | ||
| Supports `gzip`, `deflate`, and `brotli`. | ||
| Supports `gzip`, `deflate`, `brotli`, and `zstd` (Node.js 22.15+/23.8+). | ||
@@ -41,7 +41,8 @@ > ℹ️ Note: In large-scale scenarios, use a proxy like Nginx to handle response compression. | ||
| 1. `br` | ||
| 2. `gzip` | ||
| 3. `deflate` | ||
| 4. `*` (no preference — `@fastify/compress` will use `gzip`) | ||
| 5. `identity` (no compression) | ||
| 1. `zstd` (Node.js 22.15+/23.8+) | ||
| 2. `br` | ||
| 3. `gzip` | ||
| 4. `deflate` | ||
| 5. `*` (no preference — `@fastify/compress` will use `gzip`) | ||
| 6. `identity` (no compression) | ||
@@ -180,2 +181,9 @@ If an unsupported encoding is received or the `'accept-encoding'` header is missing, the payload will not be compressed. | ||
| ) | ||
| // Example with zstd support (Node.js 22.15+/23.8+) | ||
| await fastify.register( | ||
| import('@fastify/compress'), | ||
| // Prefer zstd, fallback to brotli, then gzip | ||
| { encodings: ['zstd', 'br', 'gzip'] } | ||
| ) | ||
| ``` | ||
@@ -220,5 +228,6 @@ | ||
| 1. `br` | ||
| 2. `gzip` | ||
| 3. `deflate` | ||
| 1. `zstd` (Node.js 22.15+/23.8+) | ||
| 2. `br` | ||
| 3. `gzip` | ||
| 4. `deflate` | ||
@@ -275,2 +284,9 @@ If an unsupported encoding or invalid payload is received, the plugin throws an error. | ||
| ) | ||
| // Example with zstd support for request decompression (Node.js 22.15+/23.8+) | ||
| await fastify.register( | ||
| import('@fastify/compress'), | ||
| // Support zstd, brotli and gzip for request decompression | ||
| { requestEncodings: ['zstd', 'br', 'gzip'] } | ||
| ) | ||
| ``` | ||
@@ -277,0 +293,0 @@ |
@@ -45,2 +45,29 @@ 'use strict' | ||
| test('using zstd algorithm when `Content-Encoding` request header value is set to `zstd`', async (t) => { | ||
| if (typeof zlib.createZstdCompress !== 'function') { | ||
| t.skip('zstd not supported in this Node.js version') | ||
| return | ||
| } | ||
| t.plan(2) | ||
| const fastify = Fastify() | ||
| await fastify.register(compressPlugin) | ||
| fastify.post('/', (request, reply) => { | ||
| reply.send(request.body.name) | ||
| }) | ||
| const response = await fastify.inject({ | ||
| url: '/', | ||
| method: 'POST', | ||
| headers: { | ||
| 'content-type': 'application/json', | ||
| 'content-encoding': 'zstd' | ||
| }, | ||
| payload: createPayload(zlib.createZstdCompress) | ||
| }) | ||
| t.assert.equal(response.statusCode, 200) | ||
| t.assert.equal(response.body, '@fastify/compress') | ||
| }) | ||
| test('using deflate algorithm when `Content-Encoding` request header value is set to `deflate`', async (t) => { | ||
@@ -47,0 +74,0 @@ t.plan(2) |
+18
-1
@@ -7,3 +7,3 @@ 'use strict' | ||
| const { test } = require('node:test') | ||
| const { isStream, isDeflate, isGzip, intoAsyncIterator } = require('../lib/utils') | ||
| const { isStream, isZstd, isDeflate, isGzip, intoAsyncIterator } = require('../lib/utils') | ||
@@ -52,2 +52,19 @@ test('isStream() utility should be able to detect Streams', async (t) => { | ||
| test('isZstd() utility should be able to detect zstd compressed Buffer', async (t) => { | ||
| t.plan(10) | ||
| const equal = t.assert.equal | ||
| equal(isZstd(Buffer.alloc(0)), false) | ||
| equal(isZstd(Buffer.alloc(1)), false) | ||
| equal(isZstd(Buffer.alloc(2)), false) | ||
| equal(isZstd(Buffer.alloc(3)), false) | ||
| equal(isZstd(Buffer.from([0x28, 0xb5, 0x2f])), false) | ||
| equal(isZstd(Buffer.from([0x28, 0xb5, 0x2f, 0xfd])), true) | ||
| equal(isZstd({}), false) | ||
| equal(isZstd(null), false) | ||
| equal(isZstd(undefined), false) | ||
| equal(isZstd(''), false) | ||
| }) | ||
| test('isGzip() utility should be able to detect gzip compressed Buffer', async (t) => { | ||
@@ -54,0 +71,0 @@ t.plan(10) |
+1
-1
@@ -60,3 +60,3 @@ import { | ||
| type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity' | ||
| type EncodingToken = 'zstd' | 'br' | 'deflate' | 'gzip' | 'identity' | ||
@@ -63,0 +63,0 @@ type CompressibleContentTypeFunction = (contentType: string) => boolean |
@@ -28,4 +28,10 @@ import fastify, { FastifyInstance } from 'fastify' | ||
| const withZstdOptions: FastifyCompressOptions = { | ||
| encodings: ['zstd', 'br', 'gzip', 'deflate', 'identity'], | ||
| requestEncodings: ['zstd', 'br', 'gzip', 'deflate', 'identity'] | ||
| } | ||
| const app: FastifyInstance = fastify() | ||
| app.register(fastifyCompress, withGlobalOptions) | ||
| app.register(fastifyCompress, withZstdOptions) | ||
@@ -115,2 +121,11 @@ app.register(fastifyCompress, { | ||
| // Test that invalid encoding values trigger TypeScript errors | ||
| expectError(fastify().register(fastifyCompress, { | ||
| encodings: ['invalid-encoding'] | ||
| })) | ||
| expectError(fastify().register(fastifyCompress, { | ||
| requestEncodings: ['another-invalid-encoding'] | ||
| })) | ||
| // Instantiation of an app that should trigger a typescript error | ||
@@ -117,0 +132,0 @@ const appThatTriggerAnError = fastify() |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
186553
4.52%21
10.53%4821
2.77%346
4.85%