fastify 5.7.4 → 5.8.1

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.
Files changed (59) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -1
  3. package/SECURITY.md +20 -20
  4. package/build/build-validation.js +2 -0
  5. package/docs/Guides/Ecosystem.md +7 -59
  6. package/docs/Guides/Fluent-Schema.md +2 -1
  7. package/docs/Guides/Migration-Guide-V4.md +9 -8
  8. package/docs/Guides/Migration-Guide-V5.md +1 -1
  9. package/docs/Guides/Serverless.md +1 -1
  10. package/docs/Reference/ContentTypeParser.md +2 -1
  11. package/docs/Reference/Decorators.md +4 -2
  12. package/docs/Reference/Errors.md +5 -0
  13. package/docs/Reference/Hooks.md +85 -23
  14. package/docs/Reference/LTS.md +2 -2
  15. package/docs/Reference/Lifecycle.md +16 -1
  16. package/docs/Reference/Logging.md +11 -7
  17. package/docs/Reference/Middleware.md +3 -2
  18. package/docs/Reference/Reply.md +15 -8
  19. package/docs/Reference/Request.md +17 -1
  20. package/docs/Reference/Routes.md +15 -6
  21. package/docs/Reference/Server.md +135 -14
  22. package/docs/Reference/Type-Providers.md +5 -5
  23. package/docs/Reference/TypeScript.md +14 -10
  24. package/docs/Reference/Validation-and-Serialization.md +28 -1
  25. package/fastify.d.ts +1 -0
  26. package/fastify.js +4 -2
  27. package/lib/config-validator.js +324 -296
  28. package/lib/content-type-parser.js +1 -1
  29. package/lib/content-type.js +9 -3
  30. package/lib/context.js +5 -2
  31. package/lib/errors.js +12 -1
  32. package/lib/reply.js +23 -1
  33. package/lib/request.js +18 -1
  34. package/lib/route.js +45 -4
  35. package/lib/symbols.js +4 -0
  36. package/package.json +5 -5
  37. package/scripts/validate-ecosystem-links.js +179 -0
  38. package/test/content-parser.test.js +25 -1
  39. package/test/content-type.test.js +38 -0
  40. package/test/handler-timeout.test.js +367 -0
  41. package/test/internals/errors.test.js +2 -2
  42. package/test/internals/initial-config.test.js +2 -0
  43. package/test/request-error.test.js +41 -0
  44. package/test/router-options.test.js +42 -0
  45. package/test/scripts/validate-ecosystem-links.test.js +339 -0
  46. package/test/types/dummy-plugin.ts +2 -2
  47. package/test/types/fastify.test-d.ts +1 -0
  48. package/test/types/logger.test-d.ts +17 -18
  49. package/test/types/register.test-d.ts +2 -2
  50. package/test/types/reply.test-d.ts +2 -2
  51. package/test/types/request.test-d.ts +4 -3
  52. package/test/types/route.test-d.ts +6 -0
  53. package/test/types/type-provider.test-d.ts +1 -1
  54. package/test/web-api.test.js +75 -0
  55. package/types/errors.d.ts +2 -0
  56. package/types/logger.d.ts +1 -1
  57. package/types/request.d.ts +2 -0
  58. package/types/route.d.ts +35 -21
  59. package/types/tsconfig.eslint.json +0 -13
@@ -224,6 +224,71 @@ in front.
224
224
  > ℹ️ Note:
225
225
  > At the time of writing, only node >= v14.11.0 supports this option
226
226
 
227
+ ### `handlerTimeout`
228
+ <a id="factory-handler-timeout"></a>
229
+
230
+ + Default: `0` (no timeout)
231
+
232
+ Defines the maximum number of milliseconds allowed for processing a request
233
+ through the entire route lifecycle (from routing through onRequest, parsing,
234
+ validation, handler execution, and serialization). If the response is not sent
235
+ within this time, a `503 Service Unavailable` error is returned and
236
+ `request.signal` is aborted.
237
+
238
+ Unlike `connectionTimeout` and `requestTimeout` (which operate at the socket
239
+ level), `handlerTimeout` is an application-level timeout that works correctly
240
+ with HTTP keep-alive connections. It can be overridden per-route via
241
+ [route options](./Routes.md#routes-options). When set at both levels, the
242
+ route-level value takes precedence. Routes without an explicit `handlerTimeout`
243
+ inherit the server default. Once a server-level timeout is set, individual
244
+ routes cannot opt out of it — they can only override it with a different
245
+ positive integer.
246
+
247
+ The timeout is **cooperative**: when it fires, Fastify sends the 503 error
248
+ response, but the handler's async work continues to run. Use
249
+ [`request.signal`](./Request.md) to detect cancellation and stop ongoing work
250
+ (database queries, HTTP requests, etc.). APIs that accept a `signal` option
251
+ (`fetch()`, database drivers, `stream.pipeline()`) will cancel automatically.
252
+
253
+ The timeout error (`FST_ERR_HANDLER_TIMEOUT`) is sent through the route's
254
+ [error handler](./Routes.md#routes-options), which can be customized per-route
255
+ to change the status code or response body.
256
+
257
+ When `reply.hijack()` is called, the timeout timer is cleared — the handler
258
+ takes full responsibility for the response lifecycle.
259
+
260
+ > ℹ️ Note:
261
+ > `handlerTimeout` does not apply to 404 handlers or custom not-found handlers
262
+ > set via `setNotFoundHandler()`, as they bypass the route handler lifecycle.
263
+
264
+ ```js
265
+ const fastify = require('fastify')({
266
+ handlerTimeout: 10000 // 10s default for all routes
267
+ })
268
+
269
+ // Override per-route
270
+ fastify.get('/slow-report', { handlerTimeout: 120000 }, async (request) => {
271
+ // Use request.signal for cooperative cancellation
272
+ const data = await db.query(longQuery, { signal: request.signal })
273
+ return data
274
+ })
275
+
276
+ // Customize the timeout response
277
+ fastify.get('/custom-timeout', {
278
+ handlerTimeout: 5000,
279
+ errorHandler: (error, request, reply) => {
280
+ if (error.code === 'FST_ERR_HANDLER_TIMEOUT') {
281
+ reply.code(504).send({ error: 'Gateway Timeout' })
282
+ } else {
283
+ reply.send(error)
284
+ }
285
+ }
286
+ }, async (request) => {
287
+ const result = await externalService.call({ signal: request.signal })
288
+ return result
289
+ })
290
+ ```
291
+
227
292
  ### `bodyLimit`
228
293
  <a id="factory-body-limit"></a>
229
294
 
@@ -427,7 +492,8 @@ const fastify = require('fastify')({
427
492
  })
428
493
  ```
429
494
 
430
- > ⚠ Warning: enabling this allows any callers to set `reqId` to a
495
+ > ⚠ Warning:
496
+ > Enabling this allows any callers to set `reqId` to a
431
497
  > value of their choosing.
432
498
  > No validation is performed on `requestIdHeader`.
433
499
 
@@ -521,8 +587,8 @@ controls [avvio](https://www.npmjs.com/package/avvio) 's `timeout` parameter.
521
587
  ### `querystringParser`
522
588
  <a id="factory-querystring-parser"></a>
523
589
 
524
- The default query string parser that Fastify uses is a more performant fork
525
- of Node.js's core `querystring` module called
590
+ The default query string parser that Fastify uses is a more performant fork
591
+ of Node.js's core `querystring` module called
526
592
  [`fast-querystring`](https://github.com/anonrig/fast-querystring).
527
593
 
528
594
  You can use this option to use a custom parser, such as
@@ -566,8 +632,14 @@ define it before the `GET` route.
566
632
 
567
633
  + Default: `true`
568
634
 
569
- Returns 503 after calling `close` server method. If `false`, the server routes
570
- the incoming request as usual.
635
+ When `true`, any request arriving after [`close`](#close) has been called will
636
+ receive a `503 Service Unavailable` response with `Connection: close` header
637
+ (HTTP/1.1). This lets load balancers detect that the server is shutting down and
638
+ stop routing traffic to it.
639
+
640
+ When `false`, requests arriving during the closing phase are routed and
641
+ processed normally. They will still receive a `Connection: close` header so that
642
+ clients do not attempt to reuse the connection.
571
643
 
572
644
  ### `ajv`
573
645
  <a id="factory-ajv"></a>
@@ -745,7 +817,7 @@ function rewriteUrl (req) {
745
817
  <a id="routeroptions"></a>
746
818
 
747
819
  Fastify uses [`find-my-way`](https://github.com/delvedor/find-my-way) for its
748
- HTTP router. The `routerOptions` parameter allows passing
820
+ HTTP router. The `routerOptions` parameter allows passing
749
821
  [`find-my-way` options](https://github.com/delvedor/find-my-way?tab=readme-ov-file#findmywayoptions)
750
822
  to customize the HTTP router within Fastify.
751
823
 
@@ -769,8 +841,8 @@ fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => {
769
841
  <a id="build-pretty-meta"></a>
770
842
 
771
843
  Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which
772
- supports, `buildPrettyMeta` where you can assign a `buildPrettyMeta`
773
- function to sanitize a route's store object to use with the `prettyPrint`
844
+ supports, `buildPrettyMeta` where you can assign a `buildPrettyMeta`
845
+ function to sanitize a route's store object to use with the `prettyPrint`
774
846
  functions. This function should accept a single object and return an object.
775
847
 
776
848
  ```js
@@ -865,7 +937,7 @@ const fastify = require('fastify')({
865
937
  })
866
938
  ```
867
939
 
868
- > **Note**
940
+ > ℹ️ Note:
869
941
  > The `req` and `res` objects passed to `defaultRoute` are the raw Node.js
870
942
  > `IncomingMessage` and `ServerResponse` instances. They do **not** expose the
871
943
  > Fastify-specific methods available on `FastifyRequest`/`FastifyReply` (for
@@ -1029,11 +1101,12 @@ fastify.get('/dev', async (request, reply) => {
1029
1101
 
1030
1102
  * **Default:** `true`
1031
1103
 
1032
- > ⚠ **Warning:** This option will be set to `false` by default
1104
+ > ⚠ Warning:
1105
+ > This option will be set to `false` by default
1033
1106
  > in the next major release.
1034
1107
 
1035
- When set to `false`, it prevents `setErrorHandler` from being called
1036
- multiple times within the same scope, ensuring that the previous error
1108
+ When set to `false`, it prevents `setErrorHandler` from being called
1109
+ multiple times within the same scope, ensuring that the previous error
1037
1110
  handler is not unintentionally overridden.
1038
1111
 
1039
1112
  #### Example of incorrect usage:
@@ -1331,6 +1404,53 @@ fastify.close().then(() => {
1331
1404
  })
1332
1405
  ```
1333
1406
 
1407
+ ##### Shutdown lifecycle
1408
+
1409
+ When `fastify.close()` is called, the following steps happen in order:
1410
+
1411
+ 1. The server is flagged as **closing**. New incoming requests receive a
1412
+ `Connection: close` header (HTTP/1.1) and are handled according to
1413
+ [`return503OnClosing`](#factory-return-503-on-closing).
1414
+ 2. [`preClose`](./Hooks.md#pre-close) hooks execute. The server is still
1415
+ processing in-flight requests at this point.
1416
+ 3. **Connection draining** based on the
1417
+ [`forceCloseConnections`](#forcecloseconnections) option:
1418
+ - `"idle"` — idle keep-alive connections are closed; in-flight requests
1419
+ continue.
1420
+ - `true` — all persistent connections are destroyed immediately.
1421
+ - `false` — no forced closure; idle connections remain open until they time
1422
+ out naturally (see [`keepAliveTimeout`](#keepalivetimeout)).
1423
+ 4. The HTTP server **stops accepting** new TCP connections
1424
+ (`server.close()`). Node.js waits for all in-flight requests to complete
1425
+ before invoking the callback.
1426
+ 5. [`onClose`](./Hooks.md#on-close) hooks execute. All in-flight requests have
1427
+ completed and the server is no longer listening.
1428
+ 6. The `close` callback (or the returned Promise) resolves.
1429
+
1430
+ ```
1431
+ fastify.close() called
1432
+
1433
+ ├─▶ closing = true (new requests receive 503)
1434
+
1435
+ ├─▶ preClose hooks
1436
+ │ (in-flight requests still active)
1437
+
1438
+ ├─▶ Connection draining (forceCloseConnections)
1439
+
1440
+ ├─▶ server.close()
1441
+ │ (waits for in-flight requests to complete)
1442
+
1443
+ ├─▶ onClose hooks
1444
+ │ (server stopped, all requests done)
1445
+
1446
+ └─▶ close callback / Promise resolves
1447
+ ```
1448
+
1449
+ > ℹ️ Note:
1450
+ > Upgraded connections (such as WebSocket) are not tracked by the HTTP
1451
+ > server and will prevent `server.close()` from completing. Close them
1452
+ > explicitly in a [`preClose`](./Hooks.md#pre-close) hook.
1453
+
1334
1454
  #### decorate*
1335
1455
  <a id="decorate"></a>
1336
1456
 
@@ -1391,7 +1511,7 @@ different ways to define a name (in order).
1391
1511
  Example: `pluginFn[Symbol.for('fastify.display-name')] = "Custom Name"`
1392
1512
  3. If you `module.exports` a plugin the filename is used.
1393
1513
  4. If you use a regular [function
1394
- declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Defining_functions)
1514
+ declaration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#defining_functions)
1395
1515
  the function name is used.
1396
1516
 
1397
1517
  *Fallback*: The first two lines of your plugin will represent the plugin name.
@@ -1731,7 +1851,7 @@ set it to 500 before calling the error handler.
1731
1851
  - not found (404) errors. Use [`setNotFoundHandler`](#set-not-found-handler)
1732
1852
  instead.
1733
1853
  - Stream errors thrown during piping into the response socket, as
1734
- headers/response were already sent to the client.
1854
+ headers/response were already sent to the client.
1735
1855
  Use custom in-stream data to signal such errors.
1736
1856
 
1737
1857
  ```js
@@ -2173,6 +2293,7 @@ initial options passed down by the user to the Fastify instance.
2173
2293
  The properties that can currently be exposed are:
2174
2294
  - connectionTimeout
2175
2295
  - keepAliveTimeout
2296
+ - handlerTimeout
2176
2297
  - bodyLimit
2177
2298
  - caseSensitive
2178
2299
  - http2
@@ -63,13 +63,13 @@ server.get('/route', {
63
63
  The following sets up a TypeBox Type Provider:
64
64
 
65
65
  ```bash
66
- $ npm i @fastify/type-provider-typebox
66
+ $ npm i typebox @fastify/type-provider-typebox
67
67
  ```
68
68
 
69
69
  ```typescript
70
70
  import fastify from 'fastify'
71
71
  import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
72
- import { Type } from '@sinclair/typebox'
72
+ import { Type } from 'typebox'
73
73
 
74
74
  const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
75
75
 
@@ -109,7 +109,7 @@ Example:
109
109
  import Fastify from 'fastify'
110
110
  import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
111
111
  import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
112
- import { Type } from '@sinclair/typebox'
112
+ import { Type } from 'typebox'
113
113
 
114
114
  const fastify = Fastify()
115
115
 
@@ -159,7 +159,7 @@ with several scopes, as shown below:
159
159
  ```ts
160
160
  import Fastify from 'fastify'
161
161
  import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
162
- import { Type } from '@sinclair/typebox'
162
+ import { Type } from 'typebox'
163
163
 
164
164
  const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
165
165
 
@@ -221,7 +221,7 @@ server.listen({ port: 3000 })
221
221
 
222
222
  ```ts
223
223
  // routes.ts
224
- import { Type } from '@sinclair/typebox'
224
+ import { Type } from 'typebox'
225
225
  import {
226
226
  FastifyInstance,
227
227
  FastifyBaseLogger,
@@ -58,8 +58,9 @@ in a blank http Fastify server.
58
58
  or use one of the [recommended
59
59
  ones](https://github.com/tsconfig/bases#node-14-tsconfigjson).
60
60
 
61
- *Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid
62
- [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.*
61
+ > ℹ️ Note:
62
+ > Set `target` property in `tsconfig.json` to `es2017` or greater to avoid
63
+ > [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.
63
64
 
64
65
  4. Create an `index.ts` file - this will contain the server code
65
66
  5. Add the following code block to your file:
@@ -216,7 +217,7 @@ providers.
216
217
 
217
218
  #### TypeBox
218
219
 
219
- A useful library for building types and a schema at once is [TypeBox](https://www.npmjs.com/package/@sinclair/typebox).
220
+ A useful library for building types and a schema at once is [TypeBox](https://www.npmjs.com/package/typebox).
220
221
  With TypeBox you define your schema within your code and use them directly as
221
222
  types or schemas as you need them.
222
223
 
@@ -226,14 +227,14 @@ can do it as follows:
226
227
  1. Install `typebox` in your project.
227
228
 
228
229
  ```bash
229
- npm i @sinclair/typebox
230
+ npm i typebox
230
231
  ```
231
232
 
232
233
  2. Define the schema you need with `Type` and create the respective type with
233
234
  `Static`.
234
235
 
235
236
  ```typescript
236
- import { Static, Type } from '@sinclair/typebox'
237
+ import { Static, Type } from 'typebox'
237
238
 
238
239
  export const User = Type.Object({
239
240
  name: Type.String(),
@@ -1621,8 +1622,9 @@ previous hook was `preValidation`, the next hook will be `preSerialization`.
1621
1622
  `preSerialization` is the fifth hook to be executed in the request lifecycle.
1622
1623
  The previous hook was `preHandler`, the next hook will be `onSend`.
1623
1624
 
1624
- Note: the hook is NOT called if the payload is a string, a Buffer, a stream or
1625
- null.
1625
+ > ℹ️ Note:
1626
+ > The hook is NOT called if the payload is a string, a Buffer,
1627
+ > a stream, or null.
1626
1628
 
1627
1629
  ##### fastify.onSendHookHandler< OnSendPayload, [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\<unknown\> | void
1628
1630
 
@@ -1632,8 +1634,9 @@ You can change the payload with the `onSend` hook. It is the sixth hook to be
1632
1634
  executed in the request lifecycle. The previous hook was `preSerialization`, the
1633
1635
  next hook will be `onResponse`.
1634
1636
 
1635
- Note: If you change the payload, you may only change it to a string, a Buffer, a
1636
- stream, or null.
1637
+ > ℹ️ Note:
1638
+ > If you change the payload, you may only change it to a string,
1639
+ > a Buffer, a stream, or null.
1637
1640
 
1638
1641
  ##### fastify.onResponseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
1639
1642
 
@@ -1679,7 +1682,8 @@ created. The hook will be executed before the registered code.
1679
1682
  This hook can be useful if you are developing a plugin that needs to know when a
1680
1683
  plugin context is formed, and you want to operate in that specific context.
1681
1684
 
1682
- Note: This hook will not be called if a plugin is wrapped inside fastify-plugin.
1685
+ > ℹ️ Note:
1686
+ > This hook will not be called if a plugin is wrapped inside fastify-plugin.
1683
1687
 
1684
1688
  ##### fastify.onCloseHookHandler< [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(instance: [FastifyInstance][FastifyInstance], done: (err?: [FastifyError][FastifyError]) => void): Promise\<unknown\> | void
1685
1689
 
@@ -329,6 +329,31 @@ server.setValidatorCompiler(req => {
329
329
  })
330
330
  ```
331
331
 
332
+ When type coercion is enabled, using `anyOf` with nullable primitive types
333
+ can produce unexpected results. For example, a value of `0` or `false` may be
334
+ coerced to `null` because Ajv evaluates `anyOf` schemas in order and applies
335
+ type coercion during matching. This means the `{ "type": "null" }` branch can
336
+ match before the intended type:
337
+
338
+ ```json
339
+ {
340
+ "anyOf": [
341
+ { "type": "null" },
342
+ { "type": "number" }
343
+ ]
344
+ }
345
+ ```
346
+
347
+ To avoid this, use the `nullable` keyword instead of `anyOf` for primitive
348
+ types:
349
+
350
+ ```json
351
+ {
352
+ "type": "number",
353
+ "nullable": true
354
+ }
355
+ ```
356
+
332
357
  For more information, see [Ajv Coercion](https://ajv.js.org/coercion.html).
333
358
 
334
359
  #### Ajv Plugins
@@ -439,7 +464,9 @@ fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
439
464
  return ajv.compile(schema)
440
465
  })
441
466
  ```
442
- > ℹ️ Note: When using a custom validator instance, add schemas to the validator
467
+
468
+ > ℹ️ Note:
469
+ > When using a custom validator instance, add schemas to the validator
443
470
  > instead of Fastify. Fastify's `addSchema` method will not recognize the custom
444
471
  > validator.
445
472
 
package/fastify.d.ts CHANGED
@@ -119,6 +119,7 @@ declare namespace fastify {
119
119
  requestTimeout?: number,
120
120
  pluginTimeout?: number,
121
121
  bodyLimit?: number,
122
+ handlerTimeout?: number,
122
123
  maxParamLength?: number,
123
124
  disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean),
124
125
  exposeHeadRoutes?: boolean,
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '5.7.4'
3
+ const VERSION = '5.8.1'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -32,7 +32,8 @@ const {
32
32
  kKeepAliveConnections,
33
33
  kChildLoggerFactory,
34
34
  kGenReqId,
35
- kErrorHandlerAlreadySet
35
+ kErrorHandlerAlreadySet,
36
+ kHandlerTimeout
36
37
  } = require('./lib/symbols.js')
37
38
 
38
39
  const { createServer } = require('./lib/server')
@@ -147,6 +148,7 @@ function fastify (serverOptions) {
147
148
  [kChildren]: [],
148
149
  [kServerBindings]: [],
149
150
  [kBodyLimit]: options.bodyLimit,
151
+ [kHandlerTimeout]: options.handlerTimeout,
150
152
  [kRoutePrefix]: '',
151
153
  [kLogLevel]: '',
152
154
  [kLogSerializers]: null,