fastify 4.9.2 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/docs/Guides/Ecosystem.md +2 -0
- package/docs/Guides/Plugins-Guide.md +0 -18
- package/docs/Guides/Recommendations.md +51 -0
- package/docs/Guides/Write-Type-Provider.md +32 -0
- package/docs/Reference/Decorators.md +7 -1
- package/docs/Reference/Reply.md +15 -7
- package/docs/Reference/Request.md +20 -1
- package/docs/Reference/Server.md +8 -1
- package/docs/Reference/TypeScript.md +1 -1
- package/fastify.d.ts +1 -0
- package/fastify.js +10 -5
- package/lib/contentTypeParser.js +6 -3
- package/lib/context.js +4 -0
- package/lib/errors.js +6 -0
- package/lib/hooks.js +1 -1
- package/lib/reply.js +50 -7
- package/lib/request.js +19 -0
- package/lib/route.js +9 -3
- package/lib/warnings.js +2 -0
- package/package.json +25 -26
- package/test/bodyLimit.test.js +79 -0
- package/test/close-pipelining.test.js +67 -42
- package/test/close.test.js +42 -1
- package/test/hooks-async.test.js +6 -2
- package/test/hooks.on-ready.test.js +2 -1
- package/test/hooks.test.js +20 -4
- package/test/input-validation.js +0 -1
- package/test/internals/hooks.test.js +1 -1
- package/test/reply-trailers.test.js +207 -18
- package/test/request-error.test.js +96 -0
- package/test/types/fastify.test-d.ts +8 -1
- package/test/types/logger.test-d.ts +0 -32
- package/test/types/request.test-d.ts +2 -1
- package/test/types/type-provider.test-d.ts +317 -2
- package/types/request.d.ts +12 -0
- package/types/type-provider.d.ts +5 -3
package/README.md
CHANGED
|
@@ -339,6 +339,8 @@ listed in alphabetical order.
|
|
|
339
339
|
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
|
|
340
340
|
* [__Rafael Gonzaga__](https://github.com/rafaelgss),
|
|
341
341
|
<https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
|
|
342
|
+
* [__Simone Busoli__](https://github.com/simoneb),
|
|
343
|
+
<https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
|
|
342
344
|
|
|
343
345
|
### Great Contributors
|
|
344
346
|
Great contributors on a specific area in the Fastify ecosystem will be invited
|
|
@@ -374,11 +376,15 @@ in the [OpenJS Foundation](https://openjsf.org/).
|
|
|
374
376
|
## Acknowledgements
|
|
375
377
|
|
|
376
378
|
This project is kindly sponsored by:
|
|
377
|
-
- [
|
|
379
|
+
- [NearForm](https://nearform.com)
|
|
380
|
+
- [Platformatic](https://platformatic.dev)
|
|
378
381
|
|
|
379
382
|
Past Sponsors:
|
|
380
383
|
- [LetzDoIt](https://www.letzdoitapp.com/)
|
|
381
384
|
|
|
385
|
+
This list includes all companies that support one or more of the team members
|
|
386
|
+
in the maintainance of this project.
|
|
387
|
+
|
|
382
388
|
## License
|
|
383
389
|
|
|
384
390
|
Licensed under [MIT](./LICENSE).
|
package/docs/Guides/Ecosystem.md
CHANGED
|
@@ -378,6 +378,8 @@ section.
|
|
|
378
378
|
Fastify plugin to parse request language.
|
|
379
379
|
- [`fastify-lcache`](https://github.com/denbon05/fastify-lcache)
|
|
380
380
|
Lightweight cache plugin
|
|
381
|
+
- [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes)
|
|
382
|
+
A simple plugin for Fastify list all available routes.
|
|
381
383
|
- [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from
|
|
382
384
|
a directory and inject the Fastify instance in each file.
|
|
383
385
|
- [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua
|
|
@@ -439,24 +439,6 @@ async function plugin (fastify, opts) {
|
|
|
439
439
|
|
|
440
440
|
export default plugin
|
|
441
441
|
```
|
|
442
|
-
__Note__: Fastify does not support named imports within an ESM context. Instead,
|
|
443
|
-
the `default` export is available.
|
|
444
|
-
|
|
445
|
-
```js
|
|
446
|
-
// server.mjs
|
|
447
|
-
import Fastify from 'fastify'
|
|
448
|
-
|
|
449
|
-
const fastify = Fastify()
|
|
450
|
-
|
|
451
|
-
///...
|
|
452
|
-
|
|
453
|
-
fastify.listen({ port: 3000 }, (err, address) => {
|
|
454
|
-
if (err) {
|
|
455
|
-
fastify.log.error(err)
|
|
456
|
-
process.exit(1)
|
|
457
|
-
}
|
|
458
|
-
})
|
|
459
|
-
```
|
|
460
442
|
|
|
461
443
|
## Handle errors
|
|
462
444
|
<a id="handle-errors"></a>
|
|
@@ -8,6 +8,8 @@ This document contains a set of recommendations when using Fastify.
|
|
|
8
8
|
- [HAProxy](#haproxy)
|
|
9
9
|
- [Nginx](#nginx)
|
|
10
10
|
- [Kubernetes](#kubernetes)
|
|
11
|
+
- [Capacity Planning For Production](#capacity)
|
|
12
|
+
- [Running Multiple Instances](#multiple)
|
|
11
13
|
|
|
12
14
|
## Use A Reverse Proxy
|
|
13
15
|
<a id="reverseproxy"></a>
|
|
@@ -298,3 +300,52 @@ readinessProbe:
|
|
|
298
300
|
timeoutSeconds: 3
|
|
299
301
|
successThreshold: 1
|
|
300
302
|
failureThreshold: 5
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Capacity Planning For Production
|
|
306
|
+
<a id="capacity"></a>
|
|
307
|
+
|
|
308
|
+
In order to rightsize the production environment for your Fastify application,
|
|
309
|
+
it is highly recommended that you perform your own measurements against
|
|
310
|
+
different configurations of the environment, which may
|
|
311
|
+
use real CPU cores, virtual CPU cores (vCPU), or even fractional
|
|
312
|
+
vCPU cores. We will use the term vCPU throughout this
|
|
313
|
+
recommendation to represent any CPU type.
|
|
314
|
+
|
|
315
|
+
Tools such as [k6](https://github.com/grafana/k6)
|
|
316
|
+
or [autocannon](https://github.com/mcollina/autocannon) can be used for
|
|
317
|
+
conducting the necessary performance tests.
|
|
318
|
+
|
|
319
|
+
That said, you may also consider the following as a rule of thumb:
|
|
320
|
+
|
|
321
|
+
* To have the lowest possible latency, 2 vCPU are recommended per app
|
|
322
|
+
instance (e.g., a k8s pod). The second vCPU will mostly be used by the
|
|
323
|
+
garbage collector (GC) and libuv threadpool. This will minimize the latency
|
|
324
|
+
for your users, as well as the memory usage, as the GC will be run more
|
|
325
|
+
frequently. Also, the main thread won't have to stop to let the GC run.
|
|
326
|
+
|
|
327
|
+
* To optimize for throughput (handling the largest possible amount of
|
|
328
|
+
requests per second per vCPU available), consider using a smaller amount of vCPUs
|
|
329
|
+
per app instance. It is totally fine to run Node.js application with 1 vCPU.
|
|
330
|
+
|
|
331
|
+
* You may experiment with an even smaller amount of vCPU, which may provide
|
|
332
|
+
even better throughput in certain use-cases. There are reports of API gateway
|
|
333
|
+
solutions working well with 100m-200m vCPU in Kubernetes.
|
|
334
|
+
|
|
335
|
+
See [Node's Event Loop From the Inside Out ](https://www.youtube.com/watch?v=P9csgxBgaZ8)
|
|
336
|
+
to understand the workings of Node.js in greater detail and make a
|
|
337
|
+
better determination about what your specific application needs.
|
|
338
|
+
|
|
339
|
+
## Running Multiple Instances
|
|
340
|
+
<a id="multiple"></a>
|
|
341
|
+
|
|
342
|
+
There are several use-cases where running multiple Fastify
|
|
343
|
+
apps on the same server might be considered. A common example
|
|
344
|
+
would be exposing metrics endpoints on a separate port,
|
|
345
|
+
to prevent public access, when using a reverse proxy or an ingress
|
|
346
|
+
firewall is not an option.
|
|
347
|
+
|
|
348
|
+
It is perfectly fine to spin up several Fastify instances within the same
|
|
349
|
+
Node.js process and run them concurrently, even in high load systems.
|
|
350
|
+
Each Fastify instance only generates as much load as the traffic it receives,
|
|
351
|
+
plus the memory used for that Fastify instance.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<h1 align="center">Fastify</h1>
|
|
2
|
+
|
|
3
|
+
## How to write your own type provider
|
|
4
|
+
|
|
5
|
+
Things to keep in mind when implementing a custom [type provider](../Reference/Type-Providers.md):
|
|
6
|
+
|
|
7
|
+
### Type Contravariance
|
|
8
|
+
|
|
9
|
+
Whereas exhaustive type narrowing checks normally rely on `never` to represent
|
|
10
|
+
an unreachable state, reduction in type provider interfaces should only be done
|
|
11
|
+
up to `unknown`.
|
|
12
|
+
|
|
13
|
+
The reasoning is that certain methods of `FastifyInstance` are
|
|
14
|
+
contravariant on `TypeProvider`, which can lead to TypeScript surfacing
|
|
15
|
+
assignability issues unless the custom type provider interface is
|
|
16
|
+
substitutible with `FastifyTypeProviderDefault`.
|
|
17
|
+
|
|
18
|
+
For example, `FastifyTypeProviderDefault` will not be assignable to the following:
|
|
19
|
+
```ts
|
|
20
|
+
export interface NotSubstitutibleTypeProvider extends FastifyTypeProvider {
|
|
21
|
+
// bad, nothing is assignable to `never` (except for itself)
|
|
22
|
+
output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Unless changed to:
|
|
27
|
+
```ts
|
|
28
|
+
export interface SubstitutibleTypeProvider extends FastifyTypeProvider {
|
|
29
|
+
// good, anything can be assigned to `unknown`
|
|
30
|
+
output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
@@ -107,7 +107,13 @@ route [route](./Routes.md) handlers:
|
|
|
107
107
|
fastify.decorate('db', new DbConnection())
|
|
108
108
|
|
|
109
109
|
fastify.get('/', async function (request, reply) {
|
|
110
|
-
|
|
110
|
+
// using return
|
|
111
|
+
return { hello: await this.db.query('world') }
|
|
112
|
+
|
|
113
|
+
// or
|
|
114
|
+
// using reply.send()
|
|
115
|
+
reply.send({ hello: await this.db.query('world') })
|
|
116
|
+
await reply
|
|
111
117
|
})
|
|
112
118
|
```
|
|
113
119
|
|
package/docs/Reference/Reply.md
CHANGED
|
@@ -234,8 +234,8 @@ as soon as possible.
|
|
|
234
234
|
*Note: The header `Transfer-Encoding: chunked` will be added once you use the
|
|
235
235
|
trailer. It is a hard requirement for using trailer in Node.js.*
|
|
236
236
|
|
|
237
|
-
*Note:
|
|
238
|
-
|
|
237
|
+
*Note: Any error passed to `done` callback will be ignored. If you interested
|
|
238
|
+
in the error, you can turn on `debug` level logging.*
|
|
239
239
|
|
|
240
240
|
```js
|
|
241
241
|
reply.trailer('server-timing', function() {
|
|
@@ -246,7 +246,15 @@ const { createHash } = require('crypto')
|
|
|
246
246
|
// trailer function also recieve two argument
|
|
247
247
|
// @param {object} reply fastify reply
|
|
248
248
|
// @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent
|
|
249
|
-
|
|
249
|
+
// @param {function} done callback to set trailer value
|
|
250
|
+
reply.trailer('content-md5', function(reply, payload, done) {
|
|
251
|
+
const hash = createHash('md5')
|
|
252
|
+
hash.update(payload)
|
|
253
|
+
done(null, hash.disgest('hex'))
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
// when you prefer async-await
|
|
257
|
+
reply.trailer('content-md5', async function(reply, payload) {
|
|
250
258
|
const hash = createHash('md5')
|
|
251
259
|
hash.update(payload)
|
|
252
260
|
return hash.disgest('hex')
|
|
@@ -488,11 +496,11 @@ console.log(newSerialize === serialize) // false
|
|
|
488
496
|
### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
|
|
489
497
|
<a id="serializeinput"></a>
|
|
490
498
|
|
|
491
|
-
This function will serialize the input data based on the provided schema
|
|
492
|
-
or
|
|
499
|
+
This function will serialize the input data based on the provided schema
|
|
500
|
+
or HTTP status code. If both are provided the `httpStatus` will take precedence.
|
|
493
501
|
|
|
494
|
-
If there is not a serialization function for a given `schema
|
|
495
|
-
function will be compiled forwarding the `httpStatus
|
|
502
|
+
If there is not a serialization function for a given `schema` a new serialization
|
|
503
|
+
function will be compiled, forwarding the `httpStatus` and `contentType` if provided.
|
|
496
504
|
|
|
497
505
|
```js
|
|
498
506
|
reply
|
|
@@ -42,6 +42,17 @@ Request is a core Fastify object containing the following fields:
|
|
|
42
42
|
handling the request
|
|
43
43
|
- `routeConfig` - The route [`config`](./Routes.md#routes-config)
|
|
44
44
|
object.
|
|
45
|
+
- `routeOptions` - The route [`option`](./Routes.md#routes-options) object
|
|
46
|
+
- `bodyLimit` - either server limit or route limit
|
|
47
|
+
- `method` - the http method for the route
|
|
48
|
+
- `url` - the path of the URL to match this route
|
|
49
|
+
- `attachValidation` - attach `validationError` to request
|
|
50
|
+
(if there is a schema defined)
|
|
51
|
+
- `logLevel` - log level defined for this route
|
|
52
|
+
- `version` - a semver compatible string that defines the version of the endpoint
|
|
53
|
+
- `exposeHeadRoute` - creates a sibling HEAD route for any GET routes
|
|
54
|
+
- `prefixTrailingSlash` - string used to determine how to handle passing /
|
|
55
|
+
as a route with a prefix.
|
|
45
56
|
- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
|
|
46
57
|
Returns a validation function for the specified schema or http part,
|
|
47
58
|
if any of either are set or cached.
|
|
@@ -90,7 +101,15 @@ fastify.post('/:params', options, function (request, reply) {
|
|
|
90
101
|
console.log(request.protocol)
|
|
91
102
|
console.log(request.url)
|
|
92
103
|
console.log(request.routerMethod)
|
|
93
|
-
console.log(request.
|
|
104
|
+
console.log(request.routeOptions.bodyLimit)
|
|
105
|
+
console.log(request.routeOptions.method)
|
|
106
|
+
console.log(request.routeOptions.url)
|
|
107
|
+
console.log(request.routeOptions.attachValidation)
|
|
108
|
+
console.log(request.routeOptions.logLevel)
|
|
109
|
+
console.log(request.routeOptions.version)
|
|
110
|
+
console.log(request.routeOptions.exposeHeadRoute)
|
|
111
|
+
console.log(request.routeOptions.prefixTrailingSlash)
|
|
112
|
+
console.log(request.routerPath.logLevel)
|
|
94
113
|
request.log.info('some info')
|
|
95
114
|
})
|
|
96
115
|
```
|
package/docs/Reference/Server.md
CHANGED
|
@@ -674,7 +674,7 @@ The default configuration is explained in the
|
|
|
674
674
|
const fastify = require('fastify')({
|
|
675
675
|
ajv: {
|
|
676
676
|
customOptions: {
|
|
677
|
-
removeAdditional: 'all' // Refer to [ajv options](https://ajv.js.org
|
|
677
|
+
removeAdditional: 'all' // Refer to [ajv options](https://ajv.js.org/options.html#removeadditional)
|
|
678
678
|
},
|
|
679
679
|
plugins: [
|
|
680
680
|
require('ajv-merge-patch'),
|
|
@@ -1426,6 +1426,13 @@ plugins are registered. If you would like to augment the behavior of the default
|
|
|
1426
1426
|
arguments `fastify.setNotFoundHandler()` within the context of these registered
|
|
1427
1427
|
plugins.
|
|
1428
1428
|
|
|
1429
|
+
> Note: Some config properties from the request object will be
|
|
1430
|
+
> undefined inside the custom not found handler. E.g:
|
|
1431
|
+
> `request.routerPath`, `routerMethod` and `context.config`.
|
|
1432
|
+
> This method design goal is to allow calling the common not found route.
|
|
1433
|
+
> To return a per-route customized 404 response, you can do it in
|
|
1434
|
+
> the response itself.
|
|
1435
|
+
|
|
1429
1436
|
#### setErrorHandler
|
|
1430
1437
|
<a id="set-error-handler"></a>
|
|
1431
1438
|
|
|
@@ -843,7 +843,7 @@ const server = fastify()
|
|
|
843
843
|
Check out the Learn By Example - [Getting Started](#getting-started) example for
|
|
844
844
|
a more detailed http server walkthrough.
|
|
845
845
|
|
|
846
|
-
###### Example 2: HTTPS
|
|
846
|
+
###### Example 2: HTTPS server
|
|
847
847
|
|
|
848
848
|
1. Create the following imports from `@types/node` and `fastify`
|
|
849
849
|
```typescript
|
package/fastify.d.ts
CHANGED
|
@@ -183,6 +183,7 @@ type TrustProxyFunction = (address: string, hop: number) => boolean
|
|
|
183
183
|
declare module '@fastify/error' {
|
|
184
184
|
interface FastifyError {
|
|
185
185
|
validation?: ValidationResult[];
|
|
186
|
+
validationContext?: 'body' | 'headers' | 'parameters' | 'querystring';
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
package/fastify.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const VERSION = '4.
|
|
3
|
+
const VERSION = '4.10.0'
|
|
4
4
|
|
|
5
5
|
const Avvio = require('avvio')
|
|
6
6
|
const http = require('http')
|
|
@@ -410,11 +410,12 @@ function fastify (options) {
|
|
|
410
410
|
|
|
411
411
|
/* istanbul ignore next: Cannot test this without Node.js core support */
|
|
412
412
|
if (forceCloseConnections === 'idle') {
|
|
413
|
+
// Not needed in Node 19
|
|
413
414
|
instance.server.closeIdleConnections()
|
|
414
415
|
/* istanbul ignore next: Cannot test this without Node.js core support */
|
|
415
416
|
} else if (serverHasCloseAllConnections && forceCloseConnections) {
|
|
416
417
|
instance.server.closeAllConnections()
|
|
417
|
-
} else {
|
|
418
|
+
} else if (forceCloseConnections === true) {
|
|
418
419
|
for (const conn of fastify[kKeepAliveConnections]) {
|
|
419
420
|
// We must invoke the destroy method instead of merely unreffing
|
|
420
421
|
// the sockets. If we only unref, then the callback passed to
|
|
@@ -565,17 +566,21 @@ function fastify (options) {
|
|
|
565
566
|
function addHook (name, fn) {
|
|
566
567
|
throwIfAlreadyStarted('Cannot call "addHook" when fastify instance is already started!')
|
|
567
568
|
|
|
569
|
+
if (fn == null) {
|
|
570
|
+
throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, fn)
|
|
571
|
+
}
|
|
572
|
+
|
|
568
573
|
if (name === 'onSend' || name === 'preSerialization' || name === 'onError' || name === 'preParsing') {
|
|
569
574
|
if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {
|
|
570
|
-
throw new
|
|
575
|
+
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
|
571
576
|
}
|
|
572
577
|
} else if (name === 'onReady') {
|
|
573
578
|
if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {
|
|
574
|
-
throw new
|
|
579
|
+
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
|
575
580
|
}
|
|
576
581
|
} else {
|
|
577
582
|
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
|
|
578
|
-
throw new
|
|
583
|
+
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
|
579
584
|
}
|
|
580
585
|
}
|
|
581
586
|
|
package/lib/contentTypeParser.js
CHANGED
|
@@ -94,9 +94,8 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
94
94
|
return this.customParsers.get(contentType)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
97
|
+
const parser = this.cache.get(contentType)
|
|
98
|
+
if (parser !== undefined) return parser
|
|
100
99
|
|
|
101
100
|
// eslint-disable-next-line no-var
|
|
102
101
|
for (var i = 0; i !== this.parserList.length; ++i) {
|
|
@@ -190,6 +189,9 @@ function rawBody (request, reply, options, parser, done) {
|
|
|
190
189
|
: Number(request.headers['content-length'])
|
|
191
190
|
|
|
192
191
|
if (contentLength > limit) {
|
|
192
|
+
// We must close the connection as the client is going
|
|
193
|
+
// to send this data anyway
|
|
194
|
+
reply.header('connection', 'close')
|
|
193
195
|
reply.send(new FST_ERR_CTP_BODY_TOO_LARGE())
|
|
194
196
|
return
|
|
195
197
|
}
|
|
@@ -243,6 +245,7 @@ function rawBody (request, reply, options, parser, done) {
|
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) {
|
|
248
|
+
reply.header('connection', 'close')
|
|
246
249
|
reply.send(new FST_ERR_CTP_INVALID_CONTENT_LENGTH())
|
|
247
250
|
return
|
|
248
251
|
}
|
package/lib/context.js
CHANGED
|
@@ -31,6 +31,8 @@ function Context ({
|
|
|
31
31
|
serializerCompiler,
|
|
32
32
|
replySerializer,
|
|
33
33
|
schemaErrorFormatter,
|
|
34
|
+
exposeHeadRoute,
|
|
35
|
+
prefixTrailingSlash,
|
|
34
36
|
server,
|
|
35
37
|
isFastify
|
|
36
38
|
}) {
|
|
@@ -52,6 +54,8 @@ function Context ({
|
|
|
52
54
|
this._parserOptions = {
|
|
53
55
|
limit: bodyLimit || server[kBodyLimit]
|
|
54
56
|
}
|
|
57
|
+
this.exposeHeadRoute = exposeHeadRoute
|
|
58
|
+
this.prefixTrailingSlash = prefixTrailingSlash
|
|
55
59
|
this.logLevel = logLevel || server[kLogLevel]
|
|
56
60
|
this.logSerializers = logSerializers
|
|
57
61
|
this[kFourOhFourContext] = null
|
package/lib/errors.js
CHANGED
|
@@ -101,6 +101,12 @@ const codes = {
|
|
|
101
101
|
500,
|
|
102
102
|
TypeError
|
|
103
103
|
),
|
|
104
|
+
FST_ERR_HOOK_INVALID_ASYNC_HANDLER: createError(
|
|
105
|
+
'FST_ERR_HOOK_INVALID_ASYNC_HANDLER',
|
|
106
|
+
'Async function has too many arguments. Async hooks should not use the \'done\' argument.',
|
|
107
|
+
500,
|
|
108
|
+
TypeError
|
|
109
|
+
),
|
|
104
110
|
|
|
105
111
|
/**
|
|
106
112
|
* Middlewares
|
package/lib/hooks.js
CHANGED
|
@@ -52,7 +52,7 @@ Hooks.prototype.validate = function (hook, fn) {
|
|
|
52
52
|
if (supportedHooks.indexOf(hook) === -1) {
|
|
53
53
|
throw new Error(`${hook} hook not supported!`)
|
|
54
54
|
}
|
|
55
|
-
if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook,
|
|
55
|
+
if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn))
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
Hooks.prototype.add = function (hook, fn) {
|
package/lib/reply.js
CHANGED
|
@@ -570,8 +570,6 @@ function onSendEnd (reply, payload) {
|
|
|
570
570
|
|
|
571
571
|
res.writeHead(statusCode, reply[kReplyHeaders])
|
|
572
572
|
sendTrailer(payload, res, reply)
|
|
573
|
-
// avoid ArgumentsAdaptorTrampoline from V8
|
|
574
|
-
res.end(null, null, null)
|
|
575
573
|
return
|
|
576
574
|
}
|
|
577
575
|
|
|
@@ -600,8 +598,6 @@ function onSendEnd (reply, payload) {
|
|
|
600
598
|
res.write(payload)
|
|
601
599
|
// then send trailers
|
|
602
600
|
sendTrailer(payload, res, reply)
|
|
603
|
-
// avoid ArgumentsAdaptorTrampoline from V8
|
|
604
|
-
res.end(null, null, null)
|
|
605
601
|
}
|
|
606
602
|
|
|
607
603
|
function logStreamError (logger, err, res) {
|
|
@@ -670,14 +666,61 @@ function sendStream (payload, res, reply) {
|
|
|
670
666
|
}
|
|
671
667
|
|
|
672
668
|
function sendTrailer (payload, res, reply) {
|
|
673
|
-
if (reply[kReplyTrailers] === null)
|
|
669
|
+
if (reply[kReplyTrailers] === null) {
|
|
670
|
+
// when no trailer, we close the stream
|
|
671
|
+
res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
|
672
|
+
return
|
|
673
|
+
}
|
|
674
674
|
const trailerHeaders = Object.keys(reply[kReplyTrailers])
|
|
675
675
|
const trailers = {}
|
|
676
|
+
let handled = 0
|
|
677
|
+
let skipped = true
|
|
678
|
+
function send () {
|
|
679
|
+
// add trailers when all handler handled
|
|
680
|
+
/* istanbul ignore else */
|
|
681
|
+
if (handled === 0) {
|
|
682
|
+
res.addTrailers(trailers)
|
|
683
|
+
// we need to properly close the stream
|
|
684
|
+
// after trailers sent
|
|
685
|
+
res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
676
689
|
for (const trailerName of trailerHeaders) {
|
|
677
690
|
if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
|
|
678
|
-
|
|
691
|
+
skipped = false
|
|
692
|
+
handled--
|
|
693
|
+
|
|
694
|
+
function cb (err, value) {
|
|
695
|
+
// TODO: we may protect multiple callback calls
|
|
696
|
+
// or mixing async-await with callback
|
|
697
|
+
handled++
|
|
698
|
+
|
|
699
|
+
// we can safely ignore error for trailer
|
|
700
|
+
// since it does affect the client
|
|
701
|
+
// we log in here only for debug usage
|
|
702
|
+
if (err) reply.log.debug(err)
|
|
703
|
+
else trailers[trailerName] = value
|
|
704
|
+
|
|
705
|
+
// we push the check to the end of event
|
|
706
|
+
// loop, so the registration continue to
|
|
707
|
+
// process.
|
|
708
|
+
process.nextTick(send)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const result = reply[kReplyTrailers][trailerName](reply, payload, cb)
|
|
712
|
+
if (typeof result === 'object' && typeof result.then === 'function') {
|
|
713
|
+
result.then((v) => cb(null, v), cb)
|
|
714
|
+
} else if (result !== null && result !== undefined) {
|
|
715
|
+
// TODO: should be removed in fastify@5
|
|
716
|
+
warning.emit('FSTDEP013')
|
|
717
|
+
cb(null, result)
|
|
718
|
+
}
|
|
679
719
|
}
|
|
680
|
-
|
|
720
|
+
|
|
721
|
+
// when all trailers are skipped
|
|
722
|
+
// we need to close the stream
|
|
723
|
+
if (skipped) res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
|
681
724
|
}
|
|
682
725
|
|
|
683
726
|
function sendStreamTrailer (payload, res, reply) {
|
package/lib/request.js
CHANGED
|
@@ -165,6 +165,25 @@ Object.defineProperties(Request.prototype, {
|
|
|
165
165
|
return this[kRouteContext].config.url
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
|
+
routeOptions: {
|
|
169
|
+
get () {
|
|
170
|
+
const context = this[kRouteContext]
|
|
171
|
+
const routeLimit = context._parserOptions.limit
|
|
172
|
+
const serverLimit = context.server.initialConfig.bodyLimit
|
|
173
|
+
const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined
|
|
174
|
+
const options = {
|
|
175
|
+
method: context.config.method,
|
|
176
|
+
url: context.config.url,
|
|
177
|
+
bodyLimit: (routeLimit || serverLimit),
|
|
178
|
+
attachValidation: context.attachValidation,
|
|
179
|
+
logLevel: context.logLevel,
|
|
180
|
+
exposeHeadRoute: context.exposeHeadRoute,
|
|
181
|
+
prefixTrailingSlash: context.prefixTrailingSlash,
|
|
182
|
+
version
|
|
183
|
+
}
|
|
184
|
+
return Object.freeze(options)
|
|
185
|
+
}
|
|
186
|
+
},
|
|
168
187
|
routerMethod: {
|
|
169
188
|
get () {
|
|
170
189
|
return this[kRouteContext].config.method
|
package/lib/route.js
CHANGED
|
@@ -249,11 +249,11 @@ function buildRouting (options) {
|
|
|
249
249
|
if (Array.isArray(opts[hook])) {
|
|
250
250
|
for (const func of opts[hook]) {
|
|
251
251
|
if (typeof func !== 'function') {
|
|
252
|
-
throw new FST_ERR_HOOK_INVALID_HANDLER(hook,
|
|
252
|
+
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(func))
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
} else if (opts[hook] !== undefined && typeof opts[hook] !== 'function') {
|
|
256
|
-
throw new FST_ERR_HOOK_INVALID_HANDLER(hook,
|
|
256
|
+
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(opts[hook]))
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
}
|
|
@@ -278,6 +278,8 @@ function buildRouting (options) {
|
|
|
278
278
|
replySerializer: this[kReplySerializerDefault],
|
|
279
279
|
validatorCompiler: opts.validatorCompiler,
|
|
280
280
|
serializerCompiler: opts.serializerCompiler,
|
|
281
|
+
exposeHeadRoute: shouldExposeHead,
|
|
282
|
+
prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'),
|
|
281
283
|
server: this,
|
|
282
284
|
isFastify
|
|
283
285
|
})
|
|
@@ -398,11 +400,15 @@ function buildRouting (options) {
|
|
|
398
400
|
if (closing === true) {
|
|
399
401
|
/* istanbul ignore next mac, windows */
|
|
400
402
|
if (req.httpVersionMajor !== 2) {
|
|
401
|
-
res.once('finish', () => req.destroy())
|
|
402
403
|
res.setHeader('Connection', 'close')
|
|
403
404
|
}
|
|
404
405
|
|
|
406
|
+
// TODO remove return503OnClosing after Node v18 goes EOL
|
|
407
|
+
/* istanbul ignore else */
|
|
405
408
|
if (return503OnClosing) {
|
|
409
|
+
// On Node v19 we cannot test this behavior as it won't be necessary
|
|
410
|
+
// anymore. It will close all the idle connections before they reach this
|
|
411
|
+
// stage.
|
|
406
412
|
const headers = {
|
|
407
413
|
'Content-Type': 'application/json',
|
|
408
414
|
'Content-Length': '80'
|
package/lib/warnings.js
CHANGED
|
@@ -23,4 +23,6 @@ warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is dep
|
|
|
23
23
|
|
|
24
24
|
warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in `fastify@5`.')
|
|
25
25
|
|
|
26
|
+
warning.create('FastifyDeprecation', 'FSTDEP013', 'Direct return of "trailers" function is deprecated. Please use "callback" or "async-await" for return value. The support of direct return will removed in `fastify@5`.')
|
|
27
|
+
|
|
26
28
|
module.exports = warning
|
package/package.json
CHANGED
|
@@ -1,32 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.10.0",
|
|
4
4
|
"description": "Fast and low overhead web framework, for Node.js",
|
|
5
5
|
"main": "fastify.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"types": "fastify.d.ts",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
|
|
10
|
-
"benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
|
|
11
|
-
"coverage": "npm run unit -- --cov --coverage-report=html",
|
|
12
|
-
"coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse",
|
|
13
|
-
"coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100",
|
|
14
|
-
"license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"",
|
|
15
|
-
"lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown",
|
|
16
|
-
"lint:fix": "standard --fix",
|
|
17
|
-
"lint:markdown": "markdownlint-cli2",
|
|
18
|
-
"lint:standard": "standard | snazzy",
|
|
19
|
-
"lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
|
|
20
|
-
"prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js",
|
|
21
|
-
"test": "npm run lint && npm run unit && npm run test:typescript",
|
|
22
|
-
"test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
|
|
23
|
-
"test:report": "npm run lint && npm run unit:report && npm run test:typescript",
|
|
24
|
-
"test:typescript": "tsc test/types/import.ts && tsd",
|
|
25
|
-
"test:watch": "npm run unit -- -w --no-coverage-report -R terse",
|
|
26
|
-
"unit": "tap",
|
|
27
|
-
"unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",
|
|
28
|
-
"unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap"
|
|
29
|
-
},
|
|
30
8
|
"repository": {
|
|
31
9
|
"type": "git",
|
|
32
10
|
"url": "git+https://github.com/fastify/fastify.git"
|
|
@@ -126,7 +104,7 @@
|
|
|
126
104
|
"homepage": "https://www.fastify.io/",
|
|
127
105
|
"devDependencies": {
|
|
128
106
|
"@fastify/pre-commit": "^2.0.2",
|
|
129
|
-
"@sinclair/typebox": "^0.
|
|
107
|
+
"@sinclair/typebox": "^0.25.2",
|
|
130
108
|
"@sinonjs/fake-timers": "^9.1.2",
|
|
131
109
|
"@types/node": "^18.7.18",
|
|
132
110
|
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
|
@@ -184,7 +162,7 @@
|
|
|
184
162
|
"rfdc": "^1.3.0",
|
|
185
163
|
"secure-json-parse": "^2.5.0",
|
|
186
164
|
"semver": "^7.3.7",
|
|
187
|
-
"tiny-lru": "^
|
|
165
|
+
"tiny-lru": "^10.0.0"
|
|
188
166
|
},
|
|
189
167
|
"standard": {
|
|
190
168
|
"ignore": [
|
|
@@ -198,5 +176,26 @@
|
|
|
198
176
|
},
|
|
199
177
|
"tsd": {
|
|
200
178
|
"directory": "test/types"
|
|
179
|
+
},
|
|
180
|
+
"scripts": {
|
|
181
|
+
"bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
|
|
182
|
+
"benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
|
|
183
|
+
"coverage": "npm run unit -- --cov --coverage-report=html",
|
|
184
|
+
"coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse",
|
|
185
|
+
"coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100",
|
|
186
|
+
"license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"",
|
|
187
|
+
"lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown",
|
|
188
|
+
"lint:fix": "standard --fix",
|
|
189
|
+
"lint:markdown": "markdownlint-cli2",
|
|
190
|
+
"lint:standard": "standard | snazzy",
|
|
191
|
+
"lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
|
|
192
|
+
"test": "npm run lint && npm run unit && npm run test:typescript",
|
|
193
|
+
"test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
|
|
194
|
+
"test:report": "npm run lint && npm run unit:report && npm run test:typescript",
|
|
195
|
+
"test:typescript": "tsc test/types/import.ts && tsd",
|
|
196
|
+
"test:watch": "npm run unit -- -w --no-coverage-report -R terse",
|
|
197
|
+
"unit": "tap",
|
|
198
|
+
"unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",
|
|
199
|
+
"unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap"
|
|
201
200
|
}
|
|
202
|
-
}
|
|
201
|
+
}
|