From c5919b00d6a2304b7545819bbf1422a5256d06db Mon Sep 17 00:00:00 2001 From: darkgl0w <31093081+darkgl0w@users.noreply.github.com> Date: Wed, 1 Dec 2021 15:05:09 +0100 Subject: [PATCH] feat: move to `compress` and `decompress` route options and typescript improvements (#199) * test (types): move `index.test-d.ts` file to `test/types` folder * feat!: add `compress` and `decompress` route shorthand configuration properties * test: update routes related tests * types: update typescript types * test (types): actually test typescript types * docs: update documentation * chore: add new npm scripts * test: make sure the old `(de)compress` route option won't break * fix: make sure the old `(de)compress` route option won't break * fix: typo * types: add `package.json` types entry * refactor: use `request` instead of `req` * fix (types): mark `config.(de)compress` use as deprecated --- README.md | 27 +++-- index.d.ts | 96 +++++++++++++----- index.js | 26 +++-- package.json | 18 ++-- test/index.test-d.ts | 35 ------- test/test-routes-compress.js | 178 +++++++++++++++++++++------------ test/test-routes-decompress.js | 105 ++++++++++++++++--- test/types/index.test-d.ts | 113 +++++++++++++++++++++ 8 files changed, 437 insertions(+), 161 deletions(-) delete mode 100644 test/index.test-d.ts create mode 100644 test/types/index.test-d.ts diff --git a/README.md b/README.md index 41c54e4..efd5c77 100644 --- a/README.md +++ b/README.md @@ -51,18 +51,20 @@ fastify.register( // only compress if the payload is above a certain size and use brotli fastify.get('/custom-route', { - config: { - compress: { - threshold: 128 - brotli: brotli - } + compress: { + inflateIfDeflated: true, + threshold: 128, + zlib: { + createBrotliCompress: () => createYourCustomBrotliCompress(), + createGzip: () => createYourCustomGzip(), + createDeflate: () => createYourCustomDeflate() } }, (req, reply) => { // ... }) ``` -Note: Setting `config.compress = false` on any route will disable compression on the route even if global compression is enabled. +Note: Setting `compress = false` on any route will disable compression on the route even if global compression is enabled. ### `reply.compress` This plugin adds a `compress` method to `reply` that accepts a stream or a string, and compresses it based on the `accept-encoding` header. If a JS object is passed in, it will be stringified to JSON. @@ -204,12 +206,15 @@ fastify.register( // Always decompress using gzip fastify.get('/custom-route', { - config: { - decompress: { - forceRequestEncoding: 'gzip' - } + decompress: { + forceRequestEncoding: 'gzip', + zlib: { + createBrotliDecompress: () => createYourCustomBrotliDecompress(), + createGunzip: () => createYourCustomGunzip(), + createInflate: () => createYourCustomInflate() } - }, (req, reply) => { + } +}, (req, reply) => { // ... }) ``` diff --git a/index.d.ts b/index.d.ts index 8fa5ce4..2abb6f1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,31 +1,79 @@ -import { FastifyPlugin, FastifyReply, FastifyRequest } from 'fastify'; -import { Input, InputObject } from 'into-stream'; -import { Stream } from 'stream'; -import { BrotliOptions, ZlibOptions } from 'zlib'; +import { + FastifyPluginCallback, + FastifyReply, + FastifyRequest, + RawServerBase, + RawServerDefault +} from 'fastify' +import * as fastify from 'fastify' +import { Input, InputObject } from 'into-stream' +import { Stream } from 'stream' +import { BrotliOptions, ZlibOptions } from 'zlib' + +type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity'; + +export interface FastifyCompressOptions { + brotliOptions?: BrotliOptions; + customTypes?: RegExp; + encodings?: EncodingToken[]; + forceRequestEncoding?: EncodingToken; + global?: boolean; + inflateIfDeflated?: boolean; + onInvalidRequestPayload?: (encoding: string, request: FastifyRequest, error: Error) => Error | undefined | null; + onUnsupportedEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => string | Buffer | Stream; + onUnsupportedRequestEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => Error | undefined | null; + requestEncodings?: EncodingToken[]; + threshold?: number; + zlib?: unknown; + zlibOptions?: ZlibOptions; +} + +interface RouteCompressOptions extends Pick {} + +interface RouteDecompressOptions extends Pick {} + +interface FastifyCompressRouteOptions { + compress?: RouteCompressOptions | false; + decompress?: RouteDecompressOptions | false; +} + +export interface RouteOptions extends fastify.RouteOptions, FastifyCompressRouteOptions {} + +export interface RoutesConfigCompressOptions { + /** @deprecated `config.compress` is deprecated, use `compress` shorthand option instead */ + compress?: RouteCompressOptions | false; + /** @deprecated `config.decompress` is deprecated, use `decompress` shorthand option instead */ + decompress?: RouteDecompressOptions | false; +} + +declare module 'fastify' { + export interface FastifyContextConfig extends RoutesConfigCompressOptions {} + + export interface RouteShorthandOptions< + RawServer extends RawServerBase = RawServerDefault + > extends FastifyCompressRouteOptions {} -declare module "fastify" { interface FastifyReply { compress(input: Stream | Input | InputObject): void; } -} -type EncodingToken = 'br' | 'deflate' | 'gzip' | 'identity' - -export interface FastifyCompressOptions { - global?: boolean - threshold?: number - customTypes?: RegExp - zlib?: NodeModule - brotliOptions?: BrotliOptions - zlibOptions?: ZlibOptions - inflateIfDeflated?: boolean - onUnsupportedEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => string | Buffer | Stream - encodings?: Array - requestEncodings?: Array - forceRequestEncoding?: EncodingToken -  onUnsupportedRequestEncoding?: (encoding: string, request: FastifyRequest, reply: FastifyReply) => Error | undefined | null - onInvalidRequestPayload?: (encoding: string, request: FastifyRequest, error: Error) => Error | undefined | null + export interface RouteOptions extends FastifyCompressRouteOptions {} } -declare const fastifyCompress: FastifyPlugin -export default fastifyCompress; +declare const fastifyCompress: FastifyPluginCallback +export default fastifyCompress diff --git a/index.js b/index.js index d80ed66..dcc10c5 100644 --- a/index.js +++ b/index.js @@ -52,16 +52,21 @@ function compressPlugin (fastify, opts, next) { // add onSend hook onto each route as needed fastify.addHook('onRoute', (routeOptions) => { - // Manage compression options + // If route config.compress has been set it takes precedence over compress if (routeOptions.config && typeof routeOptions.config.compress !== 'undefined') { - if (typeof routeOptions.config.compress === 'object') { + routeOptions.compress = routeOptions.config.compress + } + + // Manage compression options + if (typeof routeOptions.compress !== 'undefined') { + if (typeof routeOptions.compress === 'object') { const mergedCompressParams = Object.assign( - {}, globalCompressParams, processCompressParams(routeOptions.config.compress) + {}, globalCompressParams, processCompressParams(routeOptions.compress) ) // if the current endpoint has a custom compress configuration ... buildRouteCompress(fastify, mergedCompressParams, routeOptions) - } else if (routeOptions.config.compress === false) { + } else if (routeOptions.compress === false) { // don't apply any compress settings } else { throw new Error('Unknown value for route compress configuration') @@ -76,16 +81,21 @@ function compressPlugin (fastify, opts, next) { buildRouteCompress(fastify, globalCompressParams, routeOptions, true) } - // Manage decompression options + // If route config.decompress has been set it takes precedence over compress if (routeOptions.config && typeof routeOptions.config.decompress !== 'undefined') { - if (typeof routeOptions.config.decompress === 'object') { + routeOptions.decompress = routeOptions.config.decompress + } + + // Manage decompression options + if (typeof routeOptions.decompress !== 'undefined') { + if (typeof routeOptions.decompress === 'object') { // if the current endpoint has a custom compress configuration ... const mergedDecompressParams = Object.assign( - {}, globalDecompressParams, processDecompressParams(routeOptions.config.decompress) + {}, globalDecompressParams, processDecompressParams(routeOptions.decompress) ) buildRouteDecompress(fastify, mergedDecompressParams, routeOptions) - } else if (routeOptions.config.decompress === false) { + } else if (routeOptions.decompress === false) { // don't apply any decompress settings } else { throw new Error('Unknown value for route decompress configuration') diff --git a/package.json b/package.json index e6e9982..ce75cf1 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "3.6.1", "description": "Fastify compression utils", "main": "index.js", + "types": "index.d.ts", "dependencies": { "encoding-negotiator": "^2.0.1", "fastify-plugin": "^3.0.0", @@ -34,11 +35,16 @@ }, "scripts": { "coverage": "npm run unit -- --cov", - "coverage-report": "npm run coverage && tap --coverage-report=lcov", - "lint:typescript": "standard --fix --parser @typescript-eslint/parser --plugin typescript test/types/*.ts", - "test": "standard && npm run unit && npm run typescript", - "typescript": "tsd", - "unit": "tap -J test/*.js" + "coverage-report": "npm run coverage -- --coverage-report=lcov", + "lint": "standard", + "lint:fix": "npm run lint -- --fix", + "lint:typescript": "npm run lint:fix -- --parser @typescript-eslint/parser --plugin typescript test/types/*.ts", + "lint:types": "npm run lint:fix -- --parser @typescript-eslint/parser --plugin typescript *.d.ts", + "test": "npm run lint && npm run unit && npm run test:typescript", + "test:typescript": "tsd", + "unit": "tap -J test/*.js", + "unit:report": "npm run unit -- --cov --coverage-report=html", + "unit:verbose": "npm run unit -- -Rspec" }, "keywords": [ "fastify", @@ -61,6 +67,6 @@ "node": ">=10.16" }, "tsd": { - "directory": "test" + "directory": "test/types" } } diff --git a/test/index.test-d.ts b/test/index.test-d.ts deleted file mode 100644 index e8afbce..0000000 --- a/test/index.test-d.ts +++ /dev/null @@ -1,35 +0,0 @@ -import fastify from 'fastify' -import { createReadStream } from 'fs' -import fastifyCompress from '..' - -const zlib = require('zlib') - -const app = fastify() - -app.register(fastifyCompress, { - global: true, - threshold: 10, - zlib: zlib, - brotliOptions: { - params: { - [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, - [zlib.constants.BROTLI_PARAM_QUALITY]: 4 - } - }, - zlibOptions: { level: 1 }, - inflateIfDeflated: true, - customTypes: /x-protobuf$/, - encodings: ['gzip', 'br', 'identity', 'deflate'], - requestEncodings: ['gzip', 'br', 'identity', 'deflate'], - forceRequestEncoding: 'gzip' -}) - -const appWithoutGlobal = fastify() - -appWithoutGlobal.register(fastifyCompress, { global: false }) - -appWithoutGlobal.get('/', (req, reply) => { - reply - .type('text/plain') - .compress(createReadStream('./package.json')) -}) diff --git a/test/test-routes-compress.js b/test/test-routes-compress.js index 1fde9ef..3ebe170 100644 --- a/test/test-routes-compress.js +++ b/test/test-routes-compress.js @@ -23,13 +23,7 @@ test('should send a deflated data with custom deflate', t => { reply.type('text/plain').compress(createReadStream('./package.json')) }) - fastify.get('/custom', { - config: { - compress: { - zlib: customZlib - } - } - }, (req, reply) => { + fastify.get('/custom', { compress: { zlib: customZlib } }, (req, reply) => { reply.type('text/plain').compress(createReadStream('./package.json')) }) @@ -39,12 +33,12 @@ test('should send a deflated data with custom deflate', t => { headers: { 'accept-encoding': 'deflate' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], 'deflate') - t.notOk(res.headers['content-length'], 'no content length') + t.equal(response.headers['content-encoding'], 'deflate') + t.notOk(response.headers['content-length'], 'no content length') const file = readFileSync('./package.json', 'utf8') - const payload = zlib.inflateSync(res.rawPayload) + const payload = zlib.inflateSync(response.rawPayload) t.equal(payload.toString('utf-8'), file) t.equal(usedCustom, false) t.equal(usedCustomGlobal, true) @@ -57,12 +51,12 @@ test('should send a deflated data with custom deflate', t => { headers: { 'accept-encoding': 'deflate' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], 'deflate') - t.notOk(res.headers['content-length'], 'no content length') + t.equal(response.headers['content-encoding'], 'deflate') + t.notOk(response.headers['content-length'], 'no content length') const file = readFileSync('./package.json', 'utf8') - const payload = zlib.inflateSync(res.rawPayload) + const payload = zlib.inflateSync(response.rawPayload) t.equal(payload.toString('utf-8'), file) t.equal(usedCustom, true) t.equal(usedCustomGlobal, false) @@ -83,13 +77,7 @@ test('should send a gzipped data with custom zlib', t => { reply.type('text/plain').compress(createReadStream('./package.json')) }) - fastify.get('/custom', { - config: { - compress: { - zlib: customZlib - } - } - }, (req, reply) => { + fastify.get('/custom', { compress: { zlib: customZlib } }, (req, reply) => { reply.type('text/plain').compress(createReadStream('./package.json')) }) @@ -99,11 +87,11 @@ test('should send a gzipped data with custom zlib', t => { headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], 'gzip') + t.equal(response.headers['content-encoding'], 'gzip') const file = readFileSync('./package.json', 'utf8') - const payload = zlib.gunzipSync(res.rawPayload) + const payload = zlib.gunzipSync(response.rawPayload) t.equal(payload.toString('utf-8'), file) t.equal(usedCustom, false) t.equal(usedCustomGlobal, true) @@ -116,11 +104,11 @@ test('should send a gzipped data with custom zlib', t => { headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], 'gzip') + t.equal(response.headers['content-encoding'], 'gzip') const file = readFileSync('./package.json', 'utf8') - const payload = zlib.gunzipSync(res.rawPayload) + const payload = zlib.gunzipSync(response.rawPayload) t.equal(payload.toString('utf-8'), file) t.equal(usedCustom, true) t.equal(usedCustomGlobal, false) @@ -141,23 +129,11 @@ test('should not compress when global is false and compressed route exists', t = reply.send({ foo: 1 }) }) - fastify.get('/custom', { - config: { - compress: { - zlib: customZlib - } - } - }, (req, reply) => { + fastify.get('/custom', { compress: { zlib: customZlib } }, (req, reply) => { reply.type('text/plain').compress(createReadStream('./package.json')) }) - fastify.get('/standard', { - config: { - compress: { - threshold: 1 - } - } - }, (req, reply) => { + fastify.get('/standard', { compress: { threshold: 1 } }, (req, reply) => { reply.send({ foo: 1 }) }) @@ -167,10 +143,10 @@ test('should not compress when global is false and compressed route exists', t = headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], undefined) - t.equal(res.rawPayload.toString('utf-8'), JSON.stringify({ foo: 1 })) + t.equal(response.headers['content-encoding'], undefined) + t.equal(response.rawPayload.toString('utf-8'), JSON.stringify({ foo: 1 })) t.equal(usedCustom, false) usedCustom = false @@ -180,11 +156,11 @@ test('should not compress when global is false and compressed route exists', t = headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], 'gzip') + t.equal(response.headers['content-encoding'], 'gzip') const file = readFileSync('./package.json', 'utf8') - const payload = zlib.gunzipSync(res.rawPayload) + const payload = zlib.gunzipSync(response.rawPayload) t.equal(payload.toString('utf-8'), file) t.equal(usedCustom, true) @@ -194,10 +170,10 @@ test('should not compress when global is false and compressed route exists', t = headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], 'gzip') - const payload = zlib.gunzipSync(res.rawPayload) + t.equal(response.headers['content-encoding'], 'gzip') + const payload = zlib.gunzipSync(response.rawPayload) t.equal(payload.toString('utf-8'), JSON.stringify({ foo: 1 })) }) }) @@ -210,12 +186,63 @@ test('should not compress if route compression disabled', t => { fastify.register(compressPlugin, { global: false }) const content = { message: 'Hello World!' } - fastify.get('/', { + fastify.get('/', { compress: false }, (req, reply) => { + reply.send(content) + }) + + fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'accept-encoding': 'gzip' + } + }, (err, response) => { + t.error(err) + t.equal(response.headers['content-encoding'], undefined) + t.equal(response.rawPayload.toString('utf-8'), JSON.stringify(content)) + }) +}) + +test('should throw an error on invalid compression setting', t => { + t.plan(2) + const fastify = Fastify() + fastify.register(compressPlugin, { global: false }) + + fastify.get('/', { compress: 'bad config' }, (req, reply) => { + reply.send('') + }) + + fastify.inject({ + url: '/', + method: 'GET', + headers: { + 'accept-encoding': 'gzip' + } + }, (err, response) => { + t.type(err, Error) + t.equal(err.message, 'Unknown value for route compress configuration') + }) +}) + +test('should work with the old `{ config: compress }` option', t => { + t.plan(10) + let usedCustomGlobal = false + let usedCustom = false + const customZlibGlobal = { createGzip: () => (usedCustomGlobal = true) && zlib.createGzip() } + const customZlib = { createGzip: () => (usedCustom = true) && zlib.createGzip() } + const fastify = Fastify() + fastify.register(compressPlugin, { global: false, zlib: customZlibGlobal }) + + fastify.get('/', (req, reply) => { + reply.type('text/plain').compress(createReadStream('./package.json')) + }) + + fastify.get('/custom', { config: { - compress: false + compress: { zlib: customZlib } } }, (req, reply) => { - reply.send(content) + reply.type('text/plain').compress(createReadStream('./package.json')) }) fastify.inject({ @@ -224,19 +251,44 @@ test('should not compress if route compression disabled', t => { headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.equal(res.headers['content-encoding'], undefined) - t.equal(res.rawPayload.toString('utf-8'), JSON.stringify(content)) + t.equal(response.headers['content-encoding'], 'gzip') + const file = readFileSync('./package.json', 'utf8') + const payload = zlib.gunzipSync(response.rawPayload) + t.equal(payload.toString('utf-8'), file) + t.equal(usedCustom, false) + t.equal(usedCustomGlobal, true) + + usedCustom = false + usedCustomGlobal = false + fastify.inject({ + url: '/custom', + method: 'GET', + headers: { + 'accept-encoding': 'gzip' + } + }, (err, response) => { + t.error(err) + t.equal(response.headers['content-encoding'], 'gzip') + const file = readFileSync('./package.json', 'utf8') + const payload = zlib.gunzipSync(response.rawPayload) + t.equal(payload.toString('utf-8'), file) + t.equal(usedCustom, true) + t.equal(usedCustomGlobal, false) + }) }) }) -test('should throw an error on invalid compression setting', t => { +test('should throw an error: `{ config: compress }` option value takes precedence over `compress` option value', t => { t.plan(2) const fastify = Fastify() fastify.register(compressPlugin, { global: false }) fastify.get('/', { + compress: { + zlib: { createGzip: () => zlib.createGzip() } + }, config: { compress: 'bad config' } @@ -250,7 +302,7 @@ test('should throw an error on invalid compression setting', t => { headers: { 'accept-encoding': 'gzip' } - }, (err, res) => { + }, (err, response) => { t.type(err, Error) t.equal(err.message, 'Unknown value for route compress configuration') }) @@ -261,9 +313,7 @@ test('avoid double onSend', t => { const server = Fastify() - server.register(compressPlugin, { - threshold: 0 - }) + server.register(compressPlugin, { threshold: 0 }) server.register(async function (server) { server.get('/', async (req, _) => { @@ -277,8 +327,8 @@ test('avoid double onSend', t => { headers: { 'accept-encoding': 'br' } - }, (err, res) => { + }, (err, response) => { t.error(err) - t.same(JSON.parse(zlib.brotliDecompressSync(res.rawPayload)), { hi: true }) + t.same(JSON.parse(zlib.brotliDecompressSync(response.rawPayload)), { hi: true }) }) }) diff --git a/test/test-routes-decompress.js b/test/test-routes-decompress.js index 44e2af4..e222117 100644 --- a/test/test-routes-decompress.js +++ b/test/test-routes-decompress.js @@ -36,10 +36,8 @@ test('should decompress a inflated data with custom inflate', t => { }) fastify.post('/custom', { - config: { - decompress: { - zlib: customZlib - } + decompress: { + zlib: customZlib } }, (req, reply) => { reply.send(req.body.name) @@ -98,10 +96,8 @@ test('should decompress a inflated data with custom gzip', t => { }) fastify.post('/custom', { - config: { - decompress: { - zlib: customZlib - } + decompress: { + zlib: customZlib } }, (req, reply) => { reply.send(req.body.name) @@ -157,11 +153,7 @@ test('should not decompress if route decompression disabled', t => { reply.send(req.body.name) }) - fastify.post('/custom', { - config: { - decompress: false - } - }, (req, reply) => { + fastify.post('/custom', { decompress: false }, (req, reply) => { reply.send(req.body.name) }) @@ -208,7 +200,94 @@ test('should throw an error on invalid decompression setting', t => { const fastify = Fastify() fastify.register(compressPlugin, { global: false }) + fastify.post('/', { decompress: 'bad config' }, (req, reply) => { + reply.send(req.body.name) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { + 'content-encoding': 'gzip' + }, + payload: '' + }, (err, res) => { + t.type(err, Error) + t.equal(err.message, 'Unknown value for route decompress configuration') + }) +}) + +test('should work with the old `{ config: decompress }` option', t => { + t.plan(10) + + let usedCustomGlobal = false + let usedCustom = false + + const customZlibGlobal = { createGunzip: () => (usedCustomGlobal = true) && zlib.createGunzip() } + const customZlib = { createGunzip: () => (usedCustom = true) && zlib.createGunzip() } + + const fastify = Fastify() + fastify.register(compressPlugin, { zlib: customZlibGlobal }) + + fastify.post('/', (req, reply) => { + reply.send(req.body.name) + }) + + fastify.post('/custom', { + config: { + decompress: { + zlib: customZlib + } + } + }, (req, reply) => { + reply.send(req.body.name) + }) + + fastify.inject({ + url: '/', + method: 'POST', + headers: { + 'content-type': 'application/json', + 'content-encoding': 'gzip' + }, + payload: createPayload(zlib.createGzip) + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.equal(res.body, 'fastify-compress') + t.equal(usedCustom, false) + t.equal(usedCustomGlobal, true) + + usedCustom = false + usedCustomGlobal = false + + fastify.inject({ + url: '/custom', + method: 'POST', + headers: { + 'content-type': 'application/json', + 'content-encoding': 'gzip' + }, + payload: createPayload(zlib.createGzip) + }, (err, res) => { + t.error(err) + t.equal(res.statusCode, 200) + t.equal(res.body, 'fastify-compress') + t.equal(usedCustom, true) + t.equal(usedCustomGlobal, false) + }) + }) +}) + +test('should throw an error: `{ config: decompress }` option value takes precedence over `decompress` option value', t => { + t.plan(2) + const fastify = Fastify() + fastify.register(compressPlugin, { global: false }) + fastify.post('/', { + decompress: { + zlib: { createGunzip: () => zlib.createGunzip() } + }, config: { decompress: 'bad config' } diff --git a/test/types/index.test-d.ts b/test/types/index.test-d.ts new file mode 100644 index 0000000..779cef1 --- /dev/null +++ b/test/types/index.test-d.ts @@ -0,0 +1,113 @@ +import fastify, { FastifyInstance } from 'fastify' +import { createReadStream } from 'fs' +import { expectError, expectType } from 'tsd' +import * as zlib from 'zlib' +import fastifyCompress, { FastifyCompressOptions } from '../..' + +const stream = createReadStream('./package.json') + +const withGlobalOptions: FastifyCompressOptions = { + global: true, + threshold: 10, + zlib: zlib, + brotliOptions: { + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, + [zlib.constants.BROTLI_PARAM_QUALITY]: 4 + } + }, + zlibOptions: { level: 1 }, + inflateIfDeflated: true, + customTypes: /x-protobuf$/, + encodings: ['gzip', 'br', 'identity', 'deflate'], + requestEncodings: ['gzip', 'br', 'identity', 'deflate'], + forceRequestEncoding: 'gzip' +} + +const app: FastifyInstance = fastify() +app.register(fastifyCompress, withGlobalOptions) + +app.get('/test-one', async (request, reply) => { + expectType(reply.compress(stream)) +}) + +app.get('/test-two', async (request, reply) => { + expectError(reply.compress()) +}) + +// Instanciation of an app without global +const appWithoutGlobal: FastifyInstance = fastify() +appWithoutGlobal.register(fastifyCompress, { global: false }) + +appWithoutGlobal.get('/one', { + compress: { + zlib: { + createGzip: () => zlib.createGzip() + } + }, + decompress: { + forceRequestEncoding: 'gzip', + zlib: { + createGunzip: () => zlib.createGunzip() + } + } +}, (request, reply) => { + expectType(reply.type('text/plain').compress(stream)) +}) + +appWithoutGlobal.get('/two', { + config: { + compress: { + zlib: { + createGzip: () => zlib.createGzip() + } + }, + decompress: { + forceRequestEncoding: 'gzip', + zlib: { + createGunzip: () => zlib.createGunzip() + } + } + } +}, (request, reply) => { + expectType(reply.type('text/plain').compress(stream)) +}) + +expectError( + appWithoutGlobal.get('/throw-a-ts-arg-error-on-shorthand-route', { + compress: 'bad compress route option value', + decompress: 'bad decompress route option value' + }, (request, reply) => { + expectType(reply.type('text/plain').compress(stream)) + }) +) + +expectError( + appWithoutGlobal.route({ + method: 'GET', + path: '/throw-a-ts-arg-error', + compress: 'bad compress route option value', + decompress: 'bad decompress route option value', + handler: (request, reply) => { expectType(reply.type('text/plain').compress(stream)) } + }) +) + +appWithoutGlobal.inject( + { + method: 'GET', + path: '/throw-a-ts-arg-error', + headers: { + 'accept-encoding': 'gzip' + } + }, + (err) => { + expectType(err) + } +) + +// Instanciation of an app that should trigger a typescript error +const appThatTriggerAnError = fastify() +expectError(appThatTriggerAnError.register(fastifyCompress, { + global: true, + thisOptionDoesNotExist: 'trigger a typescript error' +}))