Socket
Socket
Sign inDemoInstall

fastify

Package Overview
Dependencies
Maintainers
3
Versions
288
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

fastify - npm Package Compare versions

Comparing version 4.18.0 to 4.19.0

docs/Reference/Principles.md

5

docs/Guides/Ecosystem.md

@@ -251,2 +251,4 @@ <h1 align="center">Fastify</h1>

generator & checker.
- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3)
Plugin for better-sqlite3.
- [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your

@@ -262,2 +264,5 @@ routes to the console, so you definitely know which endpoints are available.

to add support for [Bugsnag](https://www.bugsnag.com/) error reporting.
- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman)
Small and efficient cache provider for Node.JS with In-memory, File, Redis
and MongoDB engines for Fastify
- [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support

@@ -264,0 +269,0 @@ for Fastify.

44

docs/Guides/Serverless.md

@@ -31,2 +31,3 @@ <h1 align="center">Serverless</h1>

- [Netlify Lambda](#netlify-lambda)
- [Platformatic Cloud](#platformatic)
- [Vercel](#vercel)

@@ -457,3 +458,44 @@

## Platformatic Cloud
[Platformatic](https://platformatic.dev) provides zero-configuration deployment
for Node.js applications.
To use it now, you should wrap your existing Fastify application inside a
[Platformatic Service](https://oss.platformatic.dev/docs/reference/service/introduction),
by running the following:
```bash
npm create platformatic@latest -- service
```
The wizard would ask you to fill in a few answers:
```
? Where would you like to create your project? .
? Do you want to run npm install? yes
? Do you want to use TypeScript? no
? What port do you want to use? 3042
[13:04:14] INFO: Configuration file platformatic.service.json successfully created.
[13:04:14] INFO: Environment file .env successfully created.
[13:04:14] INFO: Plugins folder "plugins" successfully created.
[13:04:14] INFO: Routes folder "routes" successfully created.
? Do you want to create the github action to deploy this application to Platformatic Cloud dynamic workspace? no
? Do you want to create the github action to deploy this application to Platformatic Cloud static workspace? no
```
Then, head to [Platformatic Cloud](https://platformatic.cloud) and sign in
with your GitHub account.
Create your first application and a static workspace: be careful to download the
API key as an env file, e.g. `yourworkspace.txt`.
Then, you can easily deploy your application with the following commmand:
```bash
platformatic deploy --keys `yuourworkspace.txt`
```
Check out the [Full Guide](https://blog.platformatic.dev/how-to-migrate-a-fastify-app-to-platformatic-service)
on how to wrap Fastify application in Platformatic.
## Vercel

@@ -511,2 +553,2 @@

export default routes;
```
```

@@ -30,2 +30,4 @@ <h1 align="center">Fastify</h1>

- `url` - the URL of the incoming request
- `originalUrl` - similar to `url`, this allows you to access the
original `url` in case of internal re-routing
- `routerMethod` - the method defined for the router that is handling the

@@ -32,0 +34,0 @@ request

@@ -115,2 +115,7 @@ <h1 align="center">Fastify</h1>

version of the endpoint. [Example](#version-constraints).
* `constraints`: defines route restrictions based on request properties or
values, enabling customized matching using
[find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes
built-in `version` and `host` constraints, with support for custom constraint
strategies.
* `prefixTrailingSlash`: string used to determine how to handle passing `/` as a

@@ -117,0 +122,0 @@ route with a prefix.

7

docs/Reference/Server.md

@@ -530,4 +530,4 @@ <h1 align="center">Fastify</h1>

Function for generating the request-id. It will receive the incoming request as
a parameter. This function is expected to be error-free.
Function for generating the request-id. It will receive the _raw_ incoming
request as a parameter. This function is expected to be error-free.

@@ -860,2 +860,5 @@ + Default: `value of 'request-id' header if provided or monotonically increasing

>__Warning__: If utilized improperly, certain Fastify features could be disrupted.
>It is recommended to only use it for attaching listeners.
#### after

@@ -862,0 +865,0 @@ <a id="after"></a>

@@ -14,5 +14,5 @@ <h1 align="center">Fastify</h1>

your project. Each provider uses a different inference library under the hood;
allowing you to select the library most appropriate for your needs. Type
allowing you to select the library most appropriate for your needs. Official Type
Provider packages follow a `@fastify/type-provider-{provider-name}` naming
convention.
convention, and there are several community ones available as well.

@@ -24,2 +24,3 @@ The following inference packages are supported:

- `typebox` - [github](https://github.com/sinclairzx81/typebox)
- `zod` - [github](https://github.com/colinhacks/zod)

@@ -96,2 +97,8 @@ ### Json Schema to Ts

### Zod
See [official documentation](https://github.com/turkerdev/fastify-type-provider-zod)
for Zod type provider instructions.
### Scoped Type-Provider

@@ -98,0 +105,0 @@

@@ -114,3 +114,3 @@ <h1 align="center">Fastify</h1>

1. If you did not complete the previous example, follow steps 1-4 to get set up.
2. Inside `index.ts`, define two interfaces `IQuerystring` and `IHeaders`:
2. Inside `index.ts`, define three interfaces `IQuerystring`,`IHeaders` and `IReply`:
```typescript

@@ -125,4 +125,10 @@ interface IQuerystring {

}
interface IReply {
200: { success: boolean };
302: { url: string };
'4xx': { error: string };
}
```
3. Using the two interfaces, define a new API route and pass them as generics.
3. Using the three interfaces, define a new API route and pass them as generics.
The shorthand route methods (i.e. `.get`) accept a generic object

@@ -137,3 +143,4 @@ `RouteGenericInterface` containing five named properties: `Body`,

Querystring: IQuerystring,
Headers: IHeaders
Headers: IHeaders,
Reply: IReply
}>('/auth', async (request, reply) => {

@@ -144,2 +151,9 @@ const { username, password } = request.query

// chaining .statusCode/.code calls with .send allows type narrowing. For example:
// this works
reply.code(200).send({ success: true });
// but this gives a type error
reply.code(200).send('uh-oh');
// it even works for wildcards
reply.code(404).send({ error: 'Not found' });
return `logged in!`

@@ -161,3 +175,4 @@ })

Querystring: IQuerystring,
Headers: IHeaders
Headers: IHeaders,
Reply: IReply
}>('/auth', {

@@ -180,3 +195,3 @@ preValidation: (request, reply, done) => {

🎉 Good work, now you can define interfaces for each route and have strictly
typed request and reply instances. Other parts of the Fastify type system rely
typed request and reply instances. Other parts of the Fastify type system rely
on generic properties. Make sure to reference the detailed type system

@@ -183,0 +198,0 @@ documentation below to learn more about what is available.

@@ -112,3 +112,3 @@ import * as http from 'http'

jsonShorthand?: boolean;
genReqId?: <RequestGeneric extends RequestGenericInterface = RequestGenericInterface, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault>(req: FastifyRequest<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>, FastifySchema, TypeProvider>) => string,
genReqId?: (req: RawRequestDefaultExpression<RawServer>) => string,
trustProxy?: boolean | string | string[] | number | TrustProxyFunction,

@@ -115,0 +115,0 @@ querystringParser?: (str: string) => { [key: string]: unknown },

'use strict'
const VERSION = '4.18.0'
const VERSION = '4.19.0'

@@ -429,6 +429,9 @@ const Avvio = require('avvio')

hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () {
// No new TCP connections are accepted.
// We must call close on the server even if we are not listening
// otherwise memory will be leaked.
// https://github.com/nodejs/node/issues/48604
instance.server.close(done)
if (fastify[kState].listening) {
// No new TCP connections are accepted
instance.server.close(done)
/* istanbul ignore next: Cannot test this without Node.js core support */

@@ -657,2 +660,7 @@ if (forceCloseConnections === 'idle') {

errorLabel = 'timeout'
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
errorCode = '431'
errorStatus = http.STATUS_CODES[errorCode]
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
errorLabel = 'header_overflow'
} else {

@@ -697,6 +705,9 @@ errorCode = '400'

childLogger.info({ req }, 'incoming request')
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
const reply = new Reply(res, request, childLogger)
if (disableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)

@@ -720,6 +731,9 @@ }

childLogger.info({ req }, 'incoming request')
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
const reply = new Reply(res, request, childLogger)
if (disableRequestLogging === false) {
childLogger.info({ req: request }, 'incoming request')
}
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)

@@ -800,2 +814,3 @@ }

if (rewriteUrl) {
req.originalUrl = req.url
const url = rewriteUrl.call(fastify, req)

@@ -802,0 +817,0 @@ if (typeof url === 'string') {

@@ -284,3 +284,3 @@ 'use strict'

function defaultJsonParser (req, body, done) {
if (body === '' || body == null) {
if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) {
return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)

@@ -287,0 +287,0 @@ }

@@ -360,3 +360,3 @@ 'use strict'

// We create a WeakMap to compile the schema only once
// Its done leazily to avoid add overhead by creating the WeakMap
// Its done lazily to avoid add overhead by creating the WeakMap
// if it is not used

@@ -863,3 +863,3 @@ // TODO: Explore a central cache for all the schemas shared across

* @param {number} statusCode the http status code
* @param {string} contentType the reply content type
* @param {string} [contentType] the reply content type
* @returns {string} the serialized payload

@@ -866,0 +866,0 @@ */

@@ -16,3 +16,4 @@ 'use strict'

kRouteContext,
kPublicRouteContext
kPublicRouteContext,
kRequestOriginalUrl
} = require('./symbols')

@@ -153,2 +154,11 @@ const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')

},
originalUrl: {
get () {
/* istanbul ignore else */
if (!this[kRequestOriginalUrl]) {
this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url
}
return this[kRequestOriginalUrl]
}
},
method: {

@@ -167,3 +177,3 @@ get () {

get () {
return this[kRouteContext].config.url
return this[kRouteContext].config?.url
}

@@ -178,4 +188,4 @@ },

const options = {
method: context.config.method,
url: context.config.url,
method: context.config?.method,
url: context.config?.url,
bodyLimit: (routeLimit || serverLimit),

@@ -193,3 +203,3 @@ attachValidation: context.attachValidation,

get () {
return this[kRouteContext].config.method
return this[kRouteContext].config?.method
}

@@ -199,3 +209,3 @@ },

get () {
return this[kRouteContext][kPublicRouteContext].config
return this[kRouteContext][kPublicRouteContext]?.config
}

@@ -210,3 +220,3 @@ },

get () {
return this[kRouteContext].config.url === undefined
return this[kRouteContext].config?.url === undefined
}

@@ -293,3 +303,3 @@ },

// We create a WeakMap to compile the schema only once
// Its done leazily to avoid add overhead by creating the WeakMap
// Its done lazily to avoid add overhead by creating the WeakMap
// if it is not used

@@ -296,0 +306,0 @@ // TODO: Explore a central cache for all the schemas shared across

@@ -471,3 +471,2 @@ 'use strict'

const reply = new context.Reply(res, request, childLogger)
if (disableRequestLogging === false) {

@@ -474,0 +473,0 @@ childLogger.info({ req: request }, 'incoming request')

@@ -103,5 +103,5 @@ 'use strict'

* Do not setup the compiler more than once
* @param {object} serverOptions: the fastify server option
* @param {object} serverOptions the fastify server options
*/
setupValidator (serverOption) {
setupValidator (serverOptions) {
const isReady = this.validatorCompiler !== undefined && !this.addedSchemas

@@ -111,3 +111,3 @@ if (isReady) {

}
this.validatorCompiler = this.getValidatorBuilder()(this.schemaBucket.getSchemas(), serverOption.ajv)
this.validatorCompiler = this.getValidatorBuilder()(this.schemaBucket.getSchemas(), serverOptions.ajv)
}

@@ -118,5 +118,5 @@

* Do not setup the compiler more than once
* @param {object} serverOptions: the fastify server option
* @param {object} serverOptions the fastify server options
*/
setupSerializer (serverOption) {
setupSerializer (serverOptions) {
const isReady = this.serializerCompiler !== undefined && !this.addedSchemas

@@ -127,3 +127,3 @@ if (isReady) {

this.serializerCompiler = this.getSerializerBuilder()(this.schemaBucket.getSchemas(), serverOption.serializerOpts)
this.serializerCompiler = this.getSerializerBuilder()(this.schemaBucket.getSchemas(), serverOptions.serializerOpts)
}

@@ -130,0 +130,0 @@ }

@@ -150,4 +150,4 @@ 'use strict'

* @param {number} statusCode the http status code
* @param {string} contentType the reply content type
* @returns {function|boolean} the right JSON Schema function to serialize
* @param {string} [contentType] the reply content type
* @returns {function|false} the right JSON Schema function to serialize
* the reply or false if it is not set

@@ -154,0 +154,0 @@ */

@@ -64,3 +64,2 @@ 'use strict'

}
if (host === 'localhost') {

@@ -119,37 +118,64 @@ listenOptions.cb = (err, address) => {

const isMainServerListening = mainServer.listening && serverOpts.serverFactory
let binding = 0
let binded = 0
const primaryAddress = mainServer.address()
for (const adr of addresses) {
if (adr.address !== primaryAddress.address) {
binding++
const secondaryOpts = Object.assign({}, listenOptions, {
host: adr.address,
port: primaryAddress.port,
cb: (_ignoreErr) => {
binded++
if (!isMainServerListening) {
const primaryAddress = mainServer.address()
for (const adr of addresses) {
if (adr.address !== primaryAddress.address) {
binding++
const secondaryOpts = Object.assign({}, listenOptions, {
host: adr.address,
port: primaryAddress.port,
cb: (_ignoreErr) => {
binded++
/* istanbul ignore next: the else won't be taken unless listening fails */
if (!_ignoreErr) {
this[kServerBindings].push(secondaryServer)
}
/* istanbul ignore next: the else won't be taken unless listening fails */
if (!_ignoreErr) {
this[kServerBindings].push(secondaryServer)
// Due to the nature of the feature is not possible to know
// ahead of time the number of bindings that will be made.
// For instance, each binding is hooked to be closed at their own
// pace through the `onClose` hook.
// It also allows them to handle possible connections already
// attached to them if any.
this.onClose((instance, done) => {
if (instance[kState].listening) {
// No new TCP connections are accepted
// We swallow any error from the secondary
// server
secondaryServer.close(() => done())
/* istanbul ignore next: Cannot test this without Node.js core support */
if (serverOpts.forceCloseConnections === 'idle') {
// Not needed in Node 19
secondaryServer.closeIdleConnections()
/* istanbul ignore next: Cannot test this without Node.js core support */
} else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) {
secondaryServer.closeAllConnections()
}
} else {
done()
}
})
}
if (binded === binding) {
// regardless of the error, we are done
onListen()
if (binded === binding) {
// regardless of the error, we are done
onListen()
}
}
}
})
})
const secondaryServer = getServerInstance(serverOpts, httpHandler)
const closeSecondary = () => { secondaryServer.close(() => {}) }
secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
mainServer.on('unref', closeSecondary)
mainServer.on('close', closeSecondary)
mainServer.on('error', closeSecondary)
this[kState].listening = false
listenCallback.call(this, secondaryServer, secondaryOpts)()
const secondaryServer = getServerInstance(serverOpts, httpHandler)
const closeSecondary = () => { secondaryServer.close(() => { }) }
secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
mainServer.on('unref', closeSecondary)
mainServer.on('close', closeSecondary)
mainServer.on('error', closeSecondary)
this[kState].listening = false
listenCallback.call(this, secondaryServer, secondaryOpts)()
}
}
}
// no extra bindings are necessary

@@ -156,0 +182,0 @@ if (binding === 0) {

@@ -33,2 +33,3 @@ 'use strict'

kRequestCacheValidateFns: Symbol('fastify.request.cache.validateFns'),
kRequestOriginalUrl: Symbol('fastify.request.originalUrl'),
// 404

@@ -35,0 +36,0 @@ kFourOhFour: Symbol('fastify.404'),

{
"name": "fastify",
"version": "4.18.0",
"version": "4.19.0",
"description": "Fast and low overhead web framework, for Node.js",

@@ -13,3 +13,3 @@ "main": "fastify.js",

"build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
"coverage": "NODE_OPTIONS=no-network-family-autoselection npm run unit -- --cov --coverage-report=html",
"coverage": "cross-env NODE_OPTIONS=no-network-family-autoselection npm run unit -- --cov --coverage-report=html",
"coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage",

@@ -23,4 +23,4 @@ "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100",

"lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
"prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity",
"test": "npm run lint && NODE_OPTIONS=no-network-family-autoselection npm run unit && npm run test:typescript",
"prepublishOnly": "cross-env PREPUBLISH=true tap --no-check-coverage test/build/**.test.js && npm run test:validator:integrity",
"test": "npm run lint && cross-env NODE_OPTIONS=no-network-family-autoselection npm run unit && npm run test:typescript",
"test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript",

@@ -140,3 +140,3 @@ "test:report": "npm run lint && npm run unit:report && npm run test:typescript",

"@sinclair/typebox": "^0.28.9",
"@sinonjs/fake-timers": "^10.0.2",
"@sinonjs/fake-timers": "^11.0.0",
"@types/node": "^20.1.0",

@@ -151,2 +151,3 @@ "@typescript-eslint/eslint-plugin": "^5.59.2",

"branch-comparer": "^1.1.0",
"cross-env": "^7.0.3",
"eslint": "^8.39.0",

@@ -156,3 +157,3 @@ "eslint-config-standard": "^17.0.0",

"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.1.1",

@@ -169,3 +170,3 @@ "fast-json-body": "^1.1.0",

"license-checker": "^25.0.1",
"markdownlint-cli2": "^0.7.1",
"markdownlint-cli2": "^0.8.1",
"proxyquire": "^2.1.3",

@@ -190,6 +191,6 @@ "pump": "^3.0.0",

"@fastify/fast-json-stringify-compiler": "^4.3.0",
"fast-json-stringify": "^5.7.0",
"abstract-logging": "^2.0.1",
"avvio": "^8.2.1",
"fast-content-type-parse": "^1.0.0",
"fast-json-stringify": "^5.7.0",
"find-my-way": "^7.6.0",

@@ -196,0 +197,0 @@ "light-my-request": "^5.9.1",

@@ -92,9 +92,12 @@ 'use strict'

instance.request({ path: '/', method: 'GET' }),
instance.request({ path: '/', method: 'GET' }),
instance.request({ path: '/', method: 'GET' })
])
t.equal(responses[0].status, 'fulfilled')
t.equal(responses[1].status, 'rejected')
t.equal(responses[1].status, 'fulfilled')
t.equal(responses[2].status, 'rejected')
t.equal(responses[3].status, 'rejected')
await instance.close()
})

@@ -5,4 +5,3 @@ 'use strict'

const http = require('http')
const t = require('tap')
const test = t.test
const { test } = require('tap')
const Fastify = require('..')

@@ -209,3 +208,3 @@ const { Client } = require('undici')

const isV19plus = semver.gte(process.version, '19.0.0')
t.test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, t => {
test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node >= v19.x', { skip: isV19plus }, t => {
const fastify = Fastify({

@@ -248,3 +247,3 @@ return503OnClosing: false,

t.test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => {
test('Current opened connection should NOT continue to work after closing and return "connection: close" header - return503OnClosing: false, skip Node < v19.x', { skip: !isV19plus }, t => {
t.plan(4)

@@ -288,3 +287,3 @@ const fastify = Fastify({

t.test('Current opened connection should not accept new incoming connections', t => {
test('Current opened connection should not accept new incoming connections', t => {
t.plan(3)

@@ -311,3 +310,3 @@ const fastify = Fastify({ forceCloseConnections: false })

t.test('rejected incoming connections should be logged', t => {
test('rejected incoming connections should be logged', t => {
t.plan(2)

@@ -457,2 +456,145 @@ const stream = split(JSON.parse)

test('triggers on-close hook in the right order with multiple bindings', async t => {
const expectedOrder = [1, 2, 3]
const order = []
const fastify = Fastify()
t.plan(1)
// Follows LIFO
fastify.addHook('onClose', () => {
order.push(2)
})
fastify.addHook('onClose', () => {
order.push(1)
})
await fastify.listen({ port: 0 })
await new Promise((resolve, reject) => {
setTimeout(() => {
fastify.close(err => {
order.push(3)
t.match(order, expectedOrder)
if (err) t.error(err)
else resolve()
})
}, 2000)
})
})
test('triggers on-close hook in the right order with multiple bindings (forceCloseConnections - idle)', { skip: noSupport }, async t => {
const expectedPayload = { hello: 'world' }
const timeoutTime = 2 * 60 * 1000
const expectedOrder = [1, 2]
const order = []
const fastify = Fastify({ forceCloseConnections: 'idle' })
fastify.server.setTimeout(timeoutTime)
fastify.server.keepAliveTimeout = timeoutTime
fastify.get('/', async (req, reply) => {
await new Promise((resolve) => {
setTimeout(resolve, 1000)
})
return expectedPayload
})
fastify.addHook('onClose', () => {
order.push(1)
})
await fastify.listen({ port: 0 })
const addresses = fastify.addresses()
const testPlan = (addresses.length * 2) + 1
t.plan(testPlan)
for (const addr of addresses) {
const { family, address, port } = addr
const host = family === 'IPv6' ? `[${address}]` : address
const client = new Client(`http://${host}:${port}`, {
keepAliveTimeout: 1 * 60 * 1000
})
client.request({ path: '/', method: 'GET' })
.then((res) => res.body.json(), err => t.error(err))
.then(json => {
t.match(json, expectedPayload, 'should payload match')
t.notOk(client.closed, 'should client not be closed')
}, err => t.error(err))
}
await new Promise((resolve, reject) => {
setTimeout(() => {
fastify.close(err => {
order.push(2)
t.match(order, expectedOrder)
if (err) t.error(err)
else resolve()
})
}, 2000)
})
})
test('triggers on-close hook in the right order with multiple bindings (forceCloseConnections - true)', { skip: noSupport }, async t => {
const expectedPayload = { hello: 'world' }
const timeoutTime = 2 * 60 * 1000
const expectedOrder = [1, 2]
const order = []
const fastify = Fastify({ forceCloseConnections: true })
fastify.server.setTimeout(timeoutTime)
fastify.server.keepAliveTimeout = timeoutTime
fastify.get('/', async (req, reply) => {
await new Promise((resolve) => {
setTimeout(resolve, 1000)
})
return expectedPayload
})
fastify.addHook('onClose', () => {
order.push(1)
})
await fastify.listen({ port: 0 })
const addresses = fastify.addresses()
const testPlan = (addresses.length * 2) + 1
t.plan(testPlan)
for (const addr of addresses) {
const { family, address, port } = addr
const host = family === 'IPv6' ? `[${address}]` : address
const client = new Client(`http://${host}:${port}`, {
keepAliveTimeout: 1 * 60 * 1000
})
client.request({ path: '/', method: 'GET' })
.then((res) => res.body.json(), err => t.error(err))
.then(json => {
t.match(json, expectedPayload, 'should payload match')
t.notOk(client.closed, 'should client not be closed')
}, err => t.error(err))
}
await new Promise((resolve, reject) => {
setTimeout(() => {
fastify.close(err => {
order.push(2)
t.match(order, expectedOrder)
if (err) t.error(err)
else resolve()
})
}, 2000)
})
})
test('shutsdown while keep-alive connections are active (non-async, custom)', t => {

@@ -459,0 +601,0 @@ t.plan(5)

@@ -14,6 +14,6 @@ 'use strict'

t.plan(localAddresses.length + 3)
t.plan((localAddresses.length - 1) + 3)
const serverFactory = (handler, opts) => {
t.ok(opts.serverFactory, 'it is called twice for every HOST interface')
t.ok(opts.serverFactory, 'it is called once for localhost')

@@ -75,1 +75,12 @@ const server = http.createServer((req, res) => {

})
test('Should accept user defined serverFactory and ignore secondary server creation', async t => {
const server = http.createServer(() => {})
t.teardown(() => new Promise(resolve => server.close(resolve)))
const app = await Fastify({
serverFactory: () => server
})
t.resolves(async () => {
await app.listen({ port: 0 })
})
})
'use strict'
const { Readable } = require('stream')
const { test } = require('tap')

@@ -33,1 +34,42 @@ const Fastify = require('..')

})
test('Custom genReqId function gets raw request as argument', t => {
t.plan(9)
const REQUEST_ID = 'REQ-1234'
const fastify = Fastify({
genReqId: function (req) {
t.notOk('id' in req)
t.notOk('raw' in req)
t.ok(req instanceof Readable)
// http.IncomingMessage does have `rawHeaders` property, but FastifyRequest does not
const index = req.rawHeaders.indexOf('x-request-id')
const xReqId = req.rawHeaders[index + 1]
t.equal(xReqId, REQUEST_ID)
t.equal(req.headers['x-request-id'], REQUEST_ID)
return xReqId
}
})
fastify.get('/', (req, reply) => {
t.equal(req.id, REQUEST_ID)
reply.send({ id: req.id })
})
fastify.listen({ port: 0 }, err => {
t.error(err)
fastify.inject({
method: 'GET',
headers: {
'x-request-id': REQUEST_ID
},
url: 'http://localhost:' + fastify.server.address().port
}, (err, res) => {
t.error(err)
const payload = JSON.parse(res.payload)
t.equal(payload.id, REQUEST_ID)
fastify.close()
})
})
})

@@ -16,6 +16,6 @@ 'use strict'

t.plan(localAddresses.length + 3)
t.plan((localAddresses.length - 1) + 3)
const serverFactory = (handler, opts) => {
t.ok(opts.serverFactory, 'it is called twice for every HOST interface')
t.ok(opts.serverFactory, 'it is called once for localhost')

@@ -22,0 +22,0 @@ const options = {

@@ -9,2 +9,3 @@ 'use strict'

const FormData = require('form-data')
const { Readable } = require('stream')

@@ -459,1 +460,30 @@ test('inject should exist', t => {

})
test('Should not throw on access to routeConfig frameworkErrors handler - FST_ERR_BAD_URL', t => {
t.plan(6)
const fastify = Fastify({
frameworkErrors: function (err, req, res) {
t.ok(typeof req.id === 'string')
t.ok(req.raw instanceof Readable)
t.same(req.routerPath, undefined)
t.same(req.routeConfig, undefined)
res.send(`${err.message} - ${err.code}`)
}
})
fastify.get('/test/:id', (req, res) => {
res.send('{ hello: \'world\' }')
})
fastify.inject(
{
method: 'GET',
url: '/test/%world'
},
(err, res) => {
t.error(err)
t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
}
)
})

@@ -304,3 +304,3 @@ 'use strict'

test('auto status code shoud be 200', t => {
test('auto status code should be 200', t => {
t.plan(3)

@@ -317,3 +317,3 @@ sget({

test('auto type shoud be text/plain', t => {
test('auto type should be text/plain', t => {
t.plan(3)

@@ -320,0 +320,0 @@ sget({

@@ -63,2 +63,3 @@ 'use strict'

t.equal(request.url, '/')
t.equal(request.originalUrl, '/')
t.equal(request.socket, req.socket)

@@ -78,2 +79,61 @@ t.equal(request.protocol, 'http')

test('Request with undefined config', t => {
const headers = {
host: 'hostname'
}
const req = {
method: 'GET',
url: '/',
socket: { remoteAddress: 'ip' },
headers
}
const context = new Context({
schema: {
body: {
type: 'object',
required: ['hello'],
properties: {
hello: { type: 'string' }
}
}
},
server: {
[kReply]: {},
[kRequest]: Request
}
})
req.connection = req.socket
const request = new Request('id', 'params', req, 'query', 'log', context)
t.type(request, Request)
t.type(request.validateInput, Function)
t.type(request.getValidationFunction, Function)
t.type(request.compileValidationSchema, Function)
t.equal(request.id, 'id')
t.equal(request.params, 'params')
t.equal(request.raw, req)
t.equal(request.query, 'query')
t.equal(request.headers, headers)
t.equal(request.log, 'log')
t.equal(request.ip, 'ip')
t.equal(request.ips, undefined)
t.equal(request.hostname, 'hostname')
t.equal(request.body, undefined)
t.equal(request.method, 'GET')
t.equal(request.url, '/')
t.equal(request.originalUrl, '/')
t.equal(request.socket, req.socket)
t.equal(request.protocol, 'http')
t.equal(request.routeSchema, context[kPublicRouteContext].schema)
t.equal(request.routerPath, undefined)
t.equal(request.routerMethod, undefined)
t.equal(request.routeConfig, undefined)
// Aim to not bad property keys (including Symbols)
t.notOk('undefined' in request)
// This will be removed, it's deprecated
t.equal(request.connection, req.connection)
t.end()
})
test('Regular request - hostname from authority', t => {

@@ -80,0 +140,0 @@ t.plan(2)

'use strict'
const split = require('split2')
const test = require('tap').test

@@ -172,2 +173,91 @@ const Fastify = require('../')

test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_BAD_URL', t => {
t.plan(8)
const REQ_ID = 'REQ-1234'
const logStream = split(JSON.parse)
const fastify = Fastify({
frameworkErrors: function (err, req, res) {
t.same(req.id, REQ_ID)
t.same(req.raw.httpVersion, '1.1')
res.send(`${err.message} - ${err.code}`)
},
logger: {
stream: logStream,
serializers: {
req (request) {
t.same(request.id, REQ_ID)
return { httpVersion: request.raw.httpVersion }
}
}
},
genReqId: () => REQ_ID
})
fastify.get('/test/:id', (req, res) => {
res.send('{ hello: \'world\' }')
})
logStream.on('data', (json) => {
t.same(json.msg, 'incoming request')
t.same(json.reqId, REQ_ID)
t.same(json.req.httpVersion, '1.1')
})
fastify.inject(
{
method: 'GET',
url: '/test/%world'
},
(err, res) => {
t.error(err)
t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
}
)
})
test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_BAD_URL', t => {
t.plan(2)
const logStream = split(JSON.parse)
const fastify = Fastify({
disableRequestLogging: true,
frameworkErrors: function (err, req, res) {
res.send(`${err.message} - ${err.code}`)
},
logger: {
stream: logStream,
serializers: {
req () {
t.fail('should not be called')
},
res () {
t.fail('should not be called')
}
}
}
})
fastify.get('/test/:id', (req, res) => {
res.send('{ hello: \'world\' }')
})
logStream.on('data', (json) => {
t.fail('should not be called')
})
fastify.inject(
{
method: 'GET',
url: '/test/%world'
},
(err, res) => {
t.error(err)
t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
}
)
})
test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', t => {

@@ -223,1 +313,132 @@ t.plan(3)

})
test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', t => {
t.plan(8)
const constraint = {
name: 'secret',
storage: function () {
const secrets = {}
return {
get: (secret) => { return secrets[secret] || null },
set: (secret, store) => { secrets[secret] = store }
}
},
deriveConstraint: (req, ctx, done) => {
done(Error('kaboom'))
},
validate () { return true }
}
const REQ_ID = 'REQ-1234'
const logStream = split(JSON.parse)
const fastify = Fastify({
constraints: { secret: constraint },
frameworkErrors: function (err, req, res) {
t.same(req.id, REQ_ID)
t.same(req.raw.httpVersion, '1.1')
res.send(`${err.message} - ${err.code}`)
},
logger: {
stream: logStream,
serializers: {
req (request) {
t.same(request.id, REQ_ID)
return { httpVersion: request.raw.httpVersion }
}
}
},
genReqId: () => REQ_ID
})
fastify.route({
method: 'GET',
url: '/',
constraints: { secret: 'alpha' },
handler: (req, reply) => {
reply.send({ hello: 'from alpha' })
}
})
logStream.on('data', (json) => {
t.same(json.msg, 'incoming request')
t.same(json.reqId, REQ_ID)
t.same(json.req.httpVersion, '1.1')
})
fastify.inject(
{
method: 'GET',
url: '/'
},
(err, res) => {
t.error(err)
t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT')
}
)
})
test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', t => {
t.plan(2)
const constraint = {
name: 'secret',
storage: function () {
const secrets = {}
return {
get: (secret) => { return secrets[secret] || null },
set: (secret, store) => { secrets[secret] = store }
}
},
deriveConstraint: (req, ctx, done) => {
done(Error('kaboom'))
},
validate () { return true }
}
const logStream = split(JSON.parse)
const fastify = Fastify({
constraints: { secret: constraint },
disableRequestLogging: true,
frameworkErrors: function (err, req, res) {
res.send(`${err.message} - ${err.code}`)
},
logger: {
stream: logStream,
serializers: {
req () {
t.fail('should not be called')
},
res () {
t.fail('should not be called')
}
}
}
})
fastify.route({
method: 'GET',
url: '/',
constraints: { secret: 'alpha' },
handler: (req, reply) => {
reply.send({ hello: 'from alpha' })
}
})
logStream.on('data', (json) => {
t.fail('should not be called')
})
fastify.inject(
{
method: 'GET',
url: '/'
},
(err, res) => {
t.error(err)
t.equal(res.body, 'Unexpected error from async constraint - FST_ERR_ASYNC_CONSTRAINT')
}
)
})

@@ -11,2 +11,3 @@ import fastify, {

InjectOptions, FastifyBaseLogger,
RawRequestDefaultExpression,
RouteGenericInterface,

@@ -131,3 +132,8 @@ ValidationResult,

expectAssignable<FastifyInstance>(fastify({ requestIdHeader: false }))
expectAssignable<FastifyInstance>(fastify({ genReqId: () => 'request-id' }))
expectAssignable<FastifyInstance>(fastify({
genReqId: (req) => {
expectType<RawRequestDefaultExpression>(req)
return 'foo'
}
}))
expectAssignable<FastifyInstance>(fastify({ trustProxy: true }))

@@ -134,0 +140,0 @@ expectAssignable<FastifyInstance>(fastify({ querystringParser: () => ({ foo: 'bar' }) }))

@@ -29,2 +29,4 @@ import { expectAssignable, expectDeprecated, expectError, expectNotDeprecated, expectType } from 'tsd'

expectType<string>(server.pluginName)
expectType<Record<string, unknown>>(server.getSchemas())

@@ -301,2 +303,18 @@ expectType<AddressInfo[]>(server.addresses())

})
server.decorate<string>('test', {
getter () {
expectType<FastifyInstance>(this)
return 'foo'
}
})
server.decorate<string>('test', {
getter () {
expectType<FastifyInstance>(this)
return 'foo'
},
setter (x) {
expectType<string>(x)
expectType<FastifyInstance>(this)
}
})

@@ -321,3 +339,55 @@ server.decorateRequest<(x: string, y: number) => void>('test', function (x: string, y: number): void {

}))
expectError(server.decorate<string>('test', {
getter () {
return true
}
}))
expectError(server.decorate<string>('test', {
setter (x) {}
}))
declare module '../../fastify' {
interface FastifyInstance {
typedTestProperty: boolean
typedTestPropertyGetterSetter: string
typedTestMethod (x: string): string
}
}
server.decorate('typedTestProperty', false)
server.decorate('typedTestProperty', {
getter () {
return false
}
})
server.decorate('typedTestProperty', {
getter (): boolean {
return true
},
setter (x) {
expectType<boolean>(x)
expectType<FastifyInstance>(this)
}
})
expectError(server.decorate('typedTestProperty', 'foo'))
expectError(server.decorate('typedTestProperty', {
getter () {
return 'foo'
}
}))
server.decorate('typedTestMethod', function (x) {
expectType<string>(x)
expectType<FastifyInstance>(this)
return 'foo'
})
server.decorate('typedTestMethod', x => x)
expectError(server.decorate('typedTestMethod', function (x: boolean) {
return 'foo'
}))
expectError(server.decorate('typedTestMethod', function (x) {
return true
}))
expectError(server.decorate('typedTestMethod', async function (x) {
return 'foo'
}))
const versionConstraintStrategy = {

@@ -324,0 +394,0 @@ name: 'version',

import { expectType, expectError, expectAssignable } from 'tsd'
import fastify, { RouteHandlerMethod, RouteHandler, RawRequestDefaultExpression, FastifyContext, FastifyContextConfig, FastifyRequest, FastifyReply } from '../../fastify'
import fastify, { RouteHandlerMethod, RouteHandler, RawRequestDefaultExpression, FastifyContext, FastifyContextConfig, FastifyRequest, FastifyReply, FastifySchema, FastifyTypeProviderDefault } from '../../fastify'
import { RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from '../../types/utils'

@@ -8,4 +8,6 @@ import { FastifyLoggerInstance } from '../../types/logger'

import { Buffer } from 'buffer'
import { ReplyTypeConstrainer } from '../../types/reply'
type DefaultSerializationFunction = (payload: {[key: string]: unknown}) => string
type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string
type DefaultFastifyReplyWithCode<Code extends number> = FastifyReply<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault, ReplyTypeConstrainer<RouteGenericInterface['Reply'], Code>>

@@ -18,4 +20,4 @@ const getHandler: RouteHandlerMethod = function (_request, reply) {

expectType<FastifyRequest<RouteGenericInterface, RawServerDefault, RawRequestDefaultExpression>>(reply.request)
expectType<(statusCode: number) => FastifyReply>(reply.code)
expectType<(statusCode: number) => FastifyReply>(reply.status)
expectType<<Code extends number>(statusCode: Code) => DefaultFastifyReplyWithCode<Code>>(reply.code)
expectType<<Code extends number>(statusCode: Code) => DefaultFastifyReplyWithCode<Code>>(reply.status)
expectType<number>(reply.statusCode)

@@ -63,2 +65,11 @@ expectType<boolean>(reply.sent)

interface ReplyHttpCodes {
Reply: {
'1xx': number,
200: 'abc',
201: boolean,
300: { foo: string },
}
}
const typedHandler: RouteHandler<ReplyPayload> = async (request, reply) => {

@@ -109,1 +120,22 @@ expectType<((payload?: ReplyPayload['Reply']) => FastifyReply<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, ReplyPayload>)>(reply.send)

}))
server.get<ReplyHttpCodes>('/get-generic-http-codes-send', async function handler (request, reply) {
reply.code(200).send('abc')
reply.code(201).send(true)
reply.code(300).send({ foo: 'bar' })
reply.code(101).send(123)
})
expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-1', async function handler (request, reply) {
reply.code(200).send('def')
}))
expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-2', async function handler (request, reply) {
reply.code(201).send(0)
}))
expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-3', async function handler (request, reply) {
reply.code(300).send({ foo: 123 })
}))
expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-4', async function handler (request, reply) {
reply.code(100).send('asdasd')
}))
expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-5', async function handler (request, reply) {
reply.code(401).send({ foo: 123 })
}))

@@ -66,2 +66,3 @@ import { expectAssignable, expectType } from 'tsd'

expectType<string>(request.url)
expectType<string>(request.originalUrl)
expectType<string>(request.method)

@@ -68,0 +69,0 @@ expectType<string>(request.routerPath)

@@ -77,3 +77,3 @@ import fastify, { FastifyInstance, FastifyRequest, FastifyReply, RouteHandlerMethod } from '../../fastify'

fastify()[lowerCaseMethod]<RouteGeneric, RouteSpecificContextConfigType>('/', { config: { foo: 'bar', bar: 100, extra: true } }, (req, res) => {
fastify()[lowerCaseMethod]<RouteGeneric, RouteSpecificContextConfigType>('/', { config: { foo: 'bar', bar: 100, extra: true, url: '/', method: lowerCaseMethod } }, (req, res) => {
expectType<BodyInterface>(req.body)

@@ -86,5 +86,9 @@ expectType<QuerystringInterface>(req.query)

expectType<boolean>(req.context.config.extra)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<boolean>(res.context.config.extra)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
})

@@ -95,3 +99,3 @@

method: method as HTTPMethods,
config: { foo: 'bar', bar: 100 },
config: { foo: 'bar', bar: 100, url: '/', method: method as HTTPMethods },
prefixTrailingSlash: 'slash',

@@ -105,4 +109,8 @@ onRequest: (req, res, done) => { // these handlers are tested in `hooks.test-d.ts`

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -116,4 +124,8 @@ preParsing: (req, res, payload, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
expectType<RequestPayload>(payload)

@@ -130,4 +142,8 @@ expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done)

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -141,4 +157,8 @@ preHandler: (req, res, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -152,4 +172,8 @@ onResponse: (req, res, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
expectType<number>(res.statusCode)

@@ -164,4 +188,8 @@ },

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -175,4 +203,8 @@ preSerialization: (req, res, payload, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -186,4 +218,8 @@ onSend: (req, res, payload, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -197,4 +233,8 @@ handler: (req, res) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
}

@@ -206,3 +246,3 @@ })

method: method as HTTPMethods,
config: { foo: 'bar', bar: 100 },
config: { foo: 'bar', bar: 100, url: '/', method: method as HTTPMethods },
prefixTrailingSlash: 'slash',

@@ -216,4 +256,8 @@ onRequest: async (req, res, done) => { // these handlers are tested in `hooks.test-d.ts`

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -227,4 +271,8 @@ preParsing: async (req, res, payload, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
expectType<RequestPayload>(payload)

@@ -241,4 +289,8 @@ expectAssignable<(err?: FastifyError | null, res?: RequestPayload) => void>(done)

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -252,4 +304,8 @@ preHandler: async (req, res, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -263,4 +319,8 @@ onResponse: async (req, res, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
expectType<number>(res.statusCode)

@@ -275,4 +335,8 @@ },

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -286,4 +350,8 @@ preSerialization: async (req, res, payload, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -297,4 +365,8 @@ onSend: async (req, res, payload, done) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
},

@@ -308,4 +380,8 @@ handler: (req, res) => {

expectType<number>(req.context.config.bar)
expectType<string>(req.context.config.url)
expectType<HTTPMethods | HTTPMethods[]>(req.context.config.method)
expectType<string>(res.context.config.foo)
expectType<number>(res.context.config.bar)
expectType<string>(req.routeConfig.url)
expectType<HTTPMethods | HTTPMethods[]>(req.routeConfig.method)
}

@@ -312,0 +388,0 @@ })

@@ -107,1 +107,36 @@ 'use strict'

})
test('Should rewrite url but keep originalUrl unchanged', t => {
t.plan(7)
const fastify = Fastify({
rewriteUrl (req) {
t.equal(req.url, '/this-would-404-without-url-rewrite')
t.equal(req.originalUrl, '/this-would-404-without-url-rewrite')
return '/'
}
})
fastify.route({
method: 'GET',
url: '/',
handler: (req, reply) => {
t.equal(req.originalUrl, '/this-would-404-without-url-rewrite')
reply.send({ hello: 'world' })
}
})
fastify.listen({ port: 0 }, function (err) {
t.error(err)
sget({
method: 'GET',
url: 'http://localhost:' + fastify.server.address().port + '/this-would-404-without-url-rewrite'
}, (err, response, body) => {
t.error(err)
t.same(JSON.parse(body), { hello: 'world' })
t.equal(response.statusCode, 200)
})
})
t.teardown(() => fastify.close())
})

@@ -397,3 +397,3 @@ 'use strict'

test('Bas accept version (server)', t => {
test('Bad accept version (server)', t => {
t.plan(5)

@@ -400,0 +400,0 @@ const fastify = Fastify()

import { ContextConfigDefault } from './utils'
import { FastifyRouteConfig } from './route'
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface FastifyContextConfig {
export interface FastifyContextConfig extends FastifyRouteConfig {
}

@@ -6,0 +7,0 @@

@@ -88,2 +88,22 @@ import { FastifyError } from '@fastify/error'

type GetterSetter<This, T> = T | {
getter: (this: This) => T,
setter?: (this: This, value: T) => void
}
type DecorationMethod<This, Return = This> = {
<
// Need to disable "no-use-before-define" to maintain backwards compatibility, as else decorate<Foo> would suddenly mean something new
// eslint-disable-next-line no-use-before-define
T extends (P extends keyof This ? This[P] : unknown),
P extends string | symbol = string | symbol
>(property: P,
value: GetterSetter<This, T extends (...args: any[]) => any
? (this: This, ...args: Parameters<T>) => ReturnType<T>
: T
>,
dependencies?: string[]
): Return;
}
/**

@@ -100,2 +120,3 @@ * Fastify server instance. Returned by the core `fastify()` method.

server: RawServer;
pluginName: string;
prefix: string;

@@ -119,23 +140,6 @@ version: string;

// should be able to define something useful with the decorator getter/setter pattern using Generics to enforce the users function returns what they expect it to
decorate<T>(property: string | symbol,
value: T extends (...args: any[]) => any
? (this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, ...args: Parameters<T>) => ReturnType<T>
: T,
dependencies?: string[]
): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
decorate: DecorationMethod<FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
decorateRequest: DecorationMethod<FastifyRequest, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
decorateReply: DecorationMethod<FastifyReply, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
decorateRequest<T>(property: string | symbol,
value: T extends (...args: any[]) => any
? (this: FastifyRequest, ...args: Parameters<T>) => ReturnType<T>
: T,
dependencies?: string[]
): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
decorateReply<T>(property: string | symbol,
value: T extends (...args: any[]) => any
? (this: FastifyReply, ...args: Parameters<T>) => ReturnType<T>
: T,
dependencies?: string[]
): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
hasDecorator(decorator: string | symbol): boolean;

@@ -142,0 +146,0 @@ hasRequestDecorator(decorator: string | symbol): boolean;

@@ -1,10 +0,10 @@

import { RawReplyDefaultExpression, RawServerBase, RawServerDefault, ContextConfigDefault, RawRequestDefaultExpression, ReplyDefault } from './utils'
import { FastifyReplyType, ResolveFastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider'
import { Buffer } from 'buffer'
import { FastifyContext } from './context'
import { FastifyInstance } from './instance'
import { FastifyBaseLogger } from './logger'
import { FastifyRequest } from './request'
import { RouteGenericInterface } from './route'
import { FastifyInstance } from './instance'
import { FastifySchema } from './schema'
import { Buffer } from 'buffer'
import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider'
import { CodeToReplyKey, ContextConfigDefault, ReplyKeysToCodes, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault } from './utils'

@@ -15,2 +15,6 @@ export interface ReplyGenericInterface {

export type ReplyTypeConstrainer<RouteGenericReply, Code extends ReplyKeysToCodes<keyof RouteGenericReply>> =
Code extends keyof RouteGenericReply ? RouteGenericReply[Code] :
CodeToReplyKey<Code> extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey<Code>] :
unknown;
/**

@@ -35,4 +39,4 @@ * FastifyReply is an instance of the standard http or http2 reply types.

server: FastifyInstance;
code(statusCode: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
status(statusCode: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
code<Code extends ReplyKeysToCodes<keyof RouteGeneric['Reply']>>(statusCode: Code): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, ReplyTypeConstrainer<RouteGeneric['Reply'], Code>>;
status<Code extends ReplyKeysToCodes<keyof RouteGeneric['Reply']>>(statusCode: Code): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, ReplyTypeConstrainer<RouteGeneric['Reply'], Code>>;
statusCode: number;

@@ -39,0 +43,0 @@ sent: boolean;

@@ -76,2 +76,3 @@ import { ErrorObject } from '@fastify/ajv-compiler'

readonly url: string;
readonly originalUrl: string;
readonly protocol: 'http' | 'https';

@@ -78,0 +79,0 @@ readonly method: string;

@@ -16,2 +16,7 @@ import { FastifyInstance } from './instance'

export interface FastifyRouteConfig {
url: string;
method: HTTPMethods | HTTPMethods[];
}
export interface RouteGenericInterface extends RequestGenericInterface, ReplyGenericInterface {}

@@ -18,0 +23,0 @@

@@ -48,1 +48,25 @@ import * as http from 'http'

export type ReplyDefault = unknown
/**
* Helpers for determining the type of the response payload based on the code
*/
type StringAsNumber<T extends string> = T extends `${infer N extends number}` ? N : never;
type CodeClasses = 1 | 2 | 3 | 4 | 5;
type Digit = 0 |1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type HttpCodes = StringAsNumber<`${CodeClasses}${Digit}${Digit}`>;
type HttpKeys = HttpCodes | `${Digit}xx`;
export type StatusCodeReply = {
// eslint-disable-next-line no-unused-vars
[Key in HttpKeys]?: unknown;
};
// weird TS quirk: https://stackoverflow.com/questions/58977876/generic-conditional-type-resolves-to-never-when-the-generic-type-is-set-to-never
export type ReplyKeysToCodes<Key> = [Key] extends [never] ? number :
Key extends HttpCodes ? Key :
Key extends `${infer X extends CodeClasses}xx` ?
StringAsNumber<`${X}${Digit}${Digit}`> : number;
export type CodeToReplyKey<Code extends number> = `${Code}` extends `${infer FirstDigit extends CodeClasses}${number}`
? `${FirstDigit}xx`
: never;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc