fastify 6.0.0-alpha.5 → 6.0.0-alpha.6

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 (46) hide show
  1. package/README.md +1 -0
  2. package/build/build-validation.js +2 -0
  3. package/docs/Guides/Ecosystem.md +4 -0
  4. package/docs/Guides/Migration-Guide-V5.md +2 -2
  5. package/docs/Guides/Serverless.md +33 -10
  6. package/docs/Reference/Errors.md +13 -0
  7. package/docs/Reference/Hooks.md +13 -0
  8. package/docs/Reference/Reply.md +786 -697
  9. package/docs/Reference/Request.md +303 -107
  10. package/docs/Reference/Server.md +39 -2
  11. package/docs/Reference/Validation-and-Serialization.md +15 -1
  12. package/fastify.d.ts +1 -0
  13. package/fastify.js +35 -1
  14. package/lib/config-validator.js +102 -73
  15. package/lib/content-type-parser.js +14 -2
  16. package/lib/context.js +2 -1
  17. package/lib/decorate.js +4 -3
  18. package/lib/error-handler.js +21 -4
  19. package/lib/error-status.js +4 -2
  20. package/lib/errors.js +15 -9
  21. package/lib/reply.js +12 -11
  22. package/lib/request.js +3 -2
  23. package/lib/route.js +34 -5
  24. package/lib/schemas.js +44 -32
  25. package/package.json +8 -3
  26. package/test/close-disposable.test.js +154 -0
  27. package/test/constrained-routes.test.js +4 -2
  28. package/test/content-parser.test.js +39 -0
  29. package/test/decorator.test.js +29 -0
  30. package/test/diagnostics-channel/error-status.test.js +35 -2
  31. package/test/handler-timeout.test.js +224 -0
  32. package/test/internals/all.test.js +5 -0
  33. package/test/internals/errors.test.js +18 -9
  34. package/test/internals/initial-config.test.js +2 -0
  35. package/test/internals/server.test.js +6 -4
  36. package/test/reply-error.test.js +82 -0
  37. package/test/reply-trailers.test.js +159 -1
  38. package/test/router-compile.test.js +241 -0
  39. package/test/schema-serialization.test.js +120 -7
  40. package/test/types/fastify.tst.ts +4 -0
  41. package/test/types/instance.tst.ts +1 -1
  42. package/test/types/route.tst.ts +16 -0
  43. package/test/types/using.tst.ts +20 -0
  44. package/test/validation-error-handling.test.js +21 -1
  45. package/types/instance.d.ts +5 -1
  46. package/types/route.d.ts +1 -0
@@ -1,42 +1,112 @@
1
1
  <h1 align="center">Fastify</h1>
2
2
 
3
3
  ## Request
4
+ - [Introduction](#introduction)
5
+ - [Request](#request-object)
6
+ - [Properties](#properties)
7
+ - [.body](#body)
8
+ - [.query](#query)
9
+ - [.params](#params)
10
+ - [.headers](#headers)
11
+ - [.id](#id)
12
+ - [.log](#log)
13
+ - [.method](#method)
14
+ - [.url](#url)
15
+ - [.routeOptions](#routeoptions)
16
+ - [.server](#server)
17
+ - [.raw](#raw)
18
+ - [.ip](#ip)
19
+ - [.ips](#ips)
20
+ - [.host](#host)
21
+ - [.hostname](#hostname)
22
+ - [.port](#port)
23
+ - [.protocol](#protocol)
24
+ - [.originalUrl](#originalurl)
25
+ - [.mediaType](#mediatype)
26
+ - [.signal](#signal)
27
+ - [.socket](#socket)
28
+ - [.is404](#is404)
29
+ - [.validationError](#validationerror)
30
+ - [Methods](#methods)
31
+ - [.getDecorator(name)](#getdecorator)
32
+ - [.setDecorator(name, value)](#setdecorator)
33
+ - [.getValidationFunction(schema | httpPart)](#getvalidationfunction)
34
+ - [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema)
35
+ - [.validateInput(data, [schema | httpPart], [httpPart])](#validate)
36
+
37
+ ### Introduction
38
+ <a id="introduction"></a>
39
+
4
40
  The first parameter of the handler function is `Request`.
5
41
 
6
42
  Request is a core Fastify object containing the following fields:
7
- - `query` - The parsed querystring, its format is specified by
8
- [`querystringParser`](./Server.md#querystringparser).
9
- - `body` - The request payload, see [Content-Type Parser](./ContentTypeParser.md)
10
- for details on what request payloads Fastify natively parses and how to support
11
- other content types.
12
- - `params` - The params matching the URL. Values are percent-decoded
13
- (for example `%20` becomes a space, and `%2f` becomes `/`). Decoded
14
- values may contain `.`, `..`, `/`, or other characters; treat them as
15
- untrusted input. See the security note below and
16
- [Routes - Url building](./Routes.md#url-building).
43
+ - `query` - The parsed querystring.
44
+ - `body` - The request payload.
45
+ - `params` - The params matching the URL.
17
46
  - [`headers`](#headers) - The headers getter and setter.
18
47
  - `raw` - The incoming HTTP request from Node core.
19
- - `server` - The Fastify server instance, scoped to the current
20
- [encapsulation context](./Encapsulation.md).
48
+ - `server` - The Fastify server instance.
21
49
  - `id` - The request ID.
22
50
  - `log` - The logger instance of the incoming request.
23
- - `ip` - The IP address of the incoming request. This value is taken from
24
- `socket.remoteAddress` (or from `X-Forwarded-For` when
25
- [`trustProxy`](./Server.md#factory-trust-proxy) is enabled).
26
- - `ips` - An array of IP addresses, ordered from closest to furthest, from
27
- `X-Forwarded-For` (only when
28
- [`trustProxy`](./Server.md#factory-trust-proxy) is enabled).
29
- - `host` - The host of the incoming request (derived from `X-Forwarded-Host`
30
- when [`trustProxy`](./Server.md#factory-trust-proxy) is enabled). For HTTP/2
31
- compatibility, it returns `:authority` if no host header exists. The host
32
- header may return an empty string if `requireHostHeader` is `false`, not
33
- provided with HTTP/1.0, or removed by schema validation.
51
+ - `ip` - The IP address of the incoming request.
52
+ - `ips` - An array of IP addresses from `X-Forwarded-For`.
53
+ - `host` - The host of the incoming request.
34
54
  - `hostname` - The hostname parsed from `request.host`.
35
- - `port` - The port parsed from `request.host`, which may refer to the port the
36
- server is listening on.
37
- - `protocol` - The protocol of the incoming request (`https` or `http`). This
38
- value comes from `socket.encrypted` (or `X-Forwarded-Proto` when
39
- [`trustProxy`](./Server.md#factory-trust-proxy) is enabled).
55
+ - `port` - The port parsed from `request.host`.
56
+ - `protocol` - The protocol of the incoming request (`https` or `http`).
57
+ - `method` - The method of the incoming request.
58
+ - `url` - The URL of the incoming request.
59
+ - `originalUrl` - The original URL of the incoming request.
60
+ - `mediaType` - The media type extracted from `Content-Type` header.
61
+ - `is404` - `true` if request is being handled by 404 handler, `false` otherwise.
62
+ - `socket` - The underlying connection of the incoming request.
63
+ - `signal` - An `AbortSignal` that aborts on handler timeout or client
64
+ disconnect.
65
+ - `routeOptions` - The route [`option`](./Routes.md#routes-options) object.
66
+ - `validationError` - The validation error, when `attachValidation` is set.
67
+ - `getDecorator(name)` - Returns the value of a request decorator.
68
+ - `setDecorator(name, value)` - Sets the value of a request decorator.
69
+ - [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
70
+ Returns a validation function for the specified schema or HTTP part, if
71
+ set or cached.
72
+ - [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) -
73
+ Compiles the specified schema and returns a validation function using the
74
+ default (or customized) `ValidationCompiler`. The optional `httpPart` is
75
+ forwarded to the `ValidationCompiler` if provided, defaults to `null`.
76
+ - [.validateInput(data, schema | httpPart, [httpPart])](#validate) -
77
+ Validates the input using the specified schema or HTTP part and returns
78
+ `true` if the input is valid, `false` otherwise. If `httpPart` is provided,
79
+ the function uses the validator for that HTTP part. Defaults to `null`.
80
+
81
+ ```js
82
+ fastify.post('/:params', options, function (request, reply) {
83
+ console.log(request.body)
84
+ console.log(request.query)
85
+ console.log(request.params)
86
+ console.log(request.headers)
87
+ console.log(request.raw)
88
+ console.log(request.server)
89
+ console.log(request.id)
90
+ console.log(request.ip)
91
+ console.log(request.ips)
92
+ console.log(request.host)
93
+ console.log(request.hostname)
94
+ console.log(request.port)
95
+ console.log(request.protocol)
96
+ console.log(request.url)
97
+ console.log(request.routeOptions.method)
98
+ console.log(request.routeOptions.bodyLimit)
99
+ console.log(request.routeOptions.handlerTimeout)
100
+ console.log(request.routeOptions.url)
101
+ console.log(request.routeOptions.attachValidation)
102
+ console.log(request.routeOptions.logLevel)
103
+ console.log(request.routeOptions.version)
104
+ console.log(request.routeOptions.exposeHeadRoute)
105
+ console.log(request.routeOptions.prefixTrailingSlash)
106
+ console.log(request.routeOptions.config)
107
+ request.log.info('some info')
108
+ })
109
+ ```
40
110
 
41
111
  > ⚠️ Security:
42
112
  > `request.params`, `request.query`, `request.headers`, and `request.body`
@@ -55,51 +125,32 @@ Request is a core Fastify object containing the following fields:
55
125
  > they must be validated explicitly (for example: trusted proxy
56
126
  > configuration, allow-lists, strict parsing, and normalization).
57
127
 
58
- - `method` - The method of the incoming request.
59
- - `url` - The URL of the incoming request.
60
- - `originalUrl` - Similar to `url`, allows access to the original `url` in
61
- case of internal re-routing.
62
- - `mediaType` - The media type extracted from `Content-Type` header. When `Content-Type`
63
- header is missing, it will return `undefined`.
64
- - `is404` - `true` if request is being handled by 404 handler, `false` otherwise.
65
- - `socket` - The underlying connection of the incoming request.
66
- - `signal` - An `AbortSignal` that aborts when the handler timeout
67
- fires or the client disconnects. Created lazily on first access, so
68
- there is zero overhead when not used. When
69
- [`handlerTimeout`](./Server.md#factory-handler-timeout) is configured,
70
- the signal is pre-created and also aborts on timeout. Pass it to
71
- `fetch()`, database queries, or any API accepting a `signal` option
72
- for cooperative cancellation. On timeout, `signal.reason` is the
73
- `FST_ERR_HANDLER_TIMEOUT` error; on client disconnect it is a generic
74
- `AbortError`. Check `signal.reason.code` to distinguish the two cases.
75
- - `routeOptions` - The route [`option`](./Routes.md#routes-options) object.
76
- - `bodyLimit` - Either server limit or route limit.
77
- - `handlerTimeout` - The handler timeout configured for this route.
78
- - `config` - The [`config`](./Routes.md#routes-config) object for this route.
79
- - `method` - The HTTP method for the route.
80
- - `url` - The path of the URL to match this route.
81
- - `handler` - The handler for this route.
82
- - `attachValidation` - Attach `validationError` to request (if there is
83
- a schema defined).
84
- - `logLevel` - Log level defined for this route.
85
- - `schema` - The JSON schemas definition for this route.
86
- - `version` - A semver compatible string that defines the version of the endpoint.
87
- - `exposeHeadRoute` - Creates a sibling HEAD route for any GET routes.
88
- - `prefixTrailingSlash` - String used to determine how to handle passing `/`
89
- as a route with a prefix.
90
- - [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
91
- Returns a validation function for the specified schema or HTTP part, if
92
- set or cached.
93
- - [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) -
94
- Compiles the specified schema and returns a validation function using the
95
- default (or customized) `ValidationCompiler`. The optional `httpPart` is
96
- forwarded to the `ValidationCompiler` if provided, defaults to `null`.
97
- - [.validateInput(data, schema | httpPart, [httpPart])](#validate) -
98
- Validates the input using the specified schema or HTTP part and returns
99
- `true` if the input is valid, `false` otherwise. If `httpPart` is provided,
100
- the function uses the validator for that HTTP part. Defaults to `null`.
128
+ ### Request
129
+ <a id="request-object"></a>
130
+
131
+ #### Properties
132
+ <a id="properties"></a>
133
+
134
+ ##### .body
135
+
136
+ The request payload. See [Content-Type Parser](./ContentTypeParser.md) for
137
+ details on what request payloads Fastify natively parses and how to support
138
+ other content types.
139
+
140
+ ##### .query
101
141
 
102
- ### Headers
142
+ The parsed querystring. Its format is specified by
143
+ [`querystringParser`](./Server.md#querystringparser).
144
+
145
+ ##### .params
146
+
147
+ The params matching the URL. Values are percent-decoded (for example `%20`
148
+ becomes a space, and `%2f` becomes `/`). Decoded values may contain `.`, `..`,
149
+ `/`, or other characters; treat them as untrusted input. See the
150
+ [security note](#introduction) and
151
+ [Routes - Url building](./Routes.md#url-building).
152
+
153
+ ##### .headers
103
154
 
104
155
  The `request.headers` is a getter that returns an object with the headers of the
105
156
  incoming request. Set custom headers as follows:
@@ -112,7 +163,7 @@ request.headers = {
112
163
  ```
113
164
 
114
165
  This operation adds new values to the request headers, accessible via
115
- `request.headers.bar`. Standard request headers remain accessible via
166
+ `request.headers.foo`. Standard request headers remain accessible via
116
167
  `request.raw.headers`.
117
168
 
118
169
  For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added
@@ -130,41 +181,186 @@ to headers on `not found` routes.
130
181
  > your application needs an allow-listed header subset, build and consume it
131
182
  > explicitly from the validated data or from `request.raw.headers`.
132
183
 
184
+ ##### .id
185
+
186
+ The request ID. It is generated by [`genReqId`](./Server.md#genreqid), or read
187
+ from the header configured with [`requestIdHeader`](./Server.md#requestidheader)
188
+ when the incoming request includes it.
189
+
190
+ ##### .log
191
+
192
+ The logger instance of the incoming request. By default, it is a child logger of
193
+ the server logger that adds the request ID (`reqId`) to every log line. See
194
+ [`childLoggerFactory`](./Server.md#childLoggerFactory) to customize it.
195
+
196
+ ##### .method
197
+
198
+ The method of the incoming request.
199
+
200
+ ##### .url
201
+
202
+ The URL of the incoming request.
203
+
204
+ ##### .routeOptions
205
+
206
+ The route [`option`](./Routes.md#routes-options) object.
207
+ - `bodyLimit` - Either server limit or route limit.
208
+ - `handlerTimeout` - The handler timeout configured for this route.
209
+ - `config` - The [`config`](./Routes.md#routes-config) object for this route.
210
+ - `method` - The HTTP method for the route.
211
+ - `url` - The path of the URL to match this route.
212
+ - `handler` - The handler for this route.
213
+ - `attachValidation` - Attach `validationError` to request (if there is
214
+ a schema defined).
215
+ - `logLevel` - Log level defined for this route.
216
+ - `schema` - The JSON schemas definition for this route.
217
+ - `version` - A semver compatible string that defines the version of the
218
+ endpoint.
219
+ - `exposeHeadRoute` - Creates a sibling HEAD route for any GET routes.
220
+ - `prefixTrailingSlash` - String used to determine how to handle passing `/`
221
+ as a route with a prefix.
222
+
223
+ ##### .server
224
+
225
+ The Fastify server instance, scoped to the current
226
+ [encapsulation context](./Encapsulation.md).
227
+
228
+ ##### .raw
229
+
230
+ The incoming HTTP request from Node core.
231
+
232
+ ##### .ip
233
+
234
+ The IP address of the incoming request. This value is taken from
235
+ `socket.remoteAddress` (or from `X-Forwarded-For` when
236
+ [`trustProxy`](./Server.md#factory-trust-proxy) is enabled). It is `undefined`
237
+ when the underlying socket is not available.
238
+
239
+ ##### .ips
240
+
241
+ An array of IP addresses, ordered from closest to furthest, from
242
+ `X-Forwarded-For` (only when [`trustProxy`](./Server.md#factory-trust-proxy) is
243
+ enabled). It is `undefined` when `trustProxy` is not enabled.
244
+
245
+ ##### .host
246
+
247
+ The host of the incoming request (derived from `X-Forwarded-Host` when
248
+ [`trustProxy`](./Server.md#factory-trust-proxy) is enabled). For HTTP/2
249
+ compatibility, it returns `:authority` if no host header exists. The host header
250
+ may return an empty string if `requireHostHeader` is `false`, not provided with
251
+ HTTP/1.0, or removed by schema validation.
252
+
253
+ ##### .hostname
254
+
255
+ The hostname parsed from `request.host`. For IPv6 hosts, the brackets are kept
256
+ (for example, `[::1]`).
257
+
258
+ ##### .port
259
+
260
+ The port parsed from `request.host`, which may refer to the port the server is
261
+ listening on. It is `null` when the host does not include a port.
262
+
263
+ ##### .protocol
264
+
265
+ The protocol of the incoming request (`https` or `http`). This value comes from
266
+ `socket.encrypted` (or `X-Forwarded-Proto` when
267
+ [`trustProxy`](./Server.md#factory-trust-proxy) is enabled). It is `undefined`
268
+ when the underlying socket is not available.
269
+
270
+ ##### .originalUrl
271
+
272
+ Similar to `url`, allows access to the original `url` in case of internal
273
+ re-routing.
274
+
275
+ ##### .mediaType
276
+
277
+ The media type extracted from `Content-Type` header. When `Content-Type` header
278
+ is missing, it will return `undefined`.
279
+
280
+ ##### .signal
281
+
282
+ An `AbortSignal` that aborts when the handler timeout fires or the client
283
+ disconnects. Created lazily on first access, so there is zero overhead when not
284
+ used. When [`handlerTimeout`](./Server.md#factory-handler-timeout) is
285
+ configured, the signal is pre-created and also aborts on timeout. Pass it to
286
+ `fetch()`, database queries, or any API accepting a `signal` option for
287
+ cooperative cancellation. On timeout, `signal.reason` is the
288
+ `FST_ERR_HANDLER_TIMEOUT` error; on client disconnect it is a generic
289
+ `AbortError`. Check `signal.reason.code` to distinguish the two cases.
290
+
291
+ ##### .socket
292
+
293
+ The underlying connection of the incoming request.
294
+
295
+ ##### .is404
296
+
297
+ `true` if request is being handled by 404 handler, `false` otherwise.
298
+
299
+ ##### .validationError
300
+
301
+ The validation error of the request. It is set only when the route has the
302
+ [`attachValidation`](./Routes.md#routes-options) option enabled and the
303
+ validation fails; otherwise it is `undefined`. In that case, Fastify calls the
304
+ handler instead of replying with a `400` error. See
305
+ [Validation and Serialization](./Validation-and-Serialization.md#error-handling)
306
+ for the properties of the error object.
307
+
133
308
  ```js
134
- fastify.post('/:params', options, function (request, reply) {
135
- console.log(request.body)
136
- console.log(request.query)
137
- console.log(request.params)
138
- console.log(request.headers)
139
- console.log(request.raw)
140
- console.log(request.server)
141
- console.log(request.id)
142
- console.log(request.ip)
143
- console.log(request.ips)
144
- console.log(request.host)
145
- console.log(request.hostname)
146
- console.log(request.port)
147
- console.log(request.protocol)
148
- console.log(request.url)
149
- console.log(request.routeOptions.method)
150
- console.log(request.routeOptions.bodyLimit)
151
- console.log(request.routeOptions.handlerTimeout)
152
- console.log(request.routeOptions.url)
153
- console.log(request.routeOptions.attachValidation)
154
- console.log(request.routeOptions.logLevel)
155
- console.log(request.routeOptions.version)
156
- console.log(request.routeOptions.exposeHeadRoute)
157
- console.log(request.routeOptions.prefixTrailingSlash)
158
- console.log(request.routeOptions.config)
159
- request.log.info('some info')
309
+ fastify.post('/', { schema, attachValidation: true }, function (request, reply) {
310
+ if (request.validationError) {
311
+ // `request.validationError.validationContext` is the failed part,
312
+ // for example `body`
313
+ reply.code(422).send(request.validationError)
314
+ return
315
+ }
316
+ reply.send({ hello: 'world' })
317
+ })
318
+ ```
319
+
320
+ #### Methods
321
+ <a id="methods"></a>
322
+
323
+ ##### .getDecorator(name)
324
+ <a id="getdecorator"></a>
325
+
326
+ Returns the value of a request decorator declared with
327
+ [`decorateRequest`](./Decorators.md#decoraterequestname-value-dependencies).
328
+ If the decorator is a function, it is returned bound to the request. It throws
329
+ an `FST_ERR_DEC_UNDECLARED` error if the decorator has not been declared. See
330
+ [`getDecorator`](./Decorators.md#getdecoratorname) for more details.
331
+
332
+ ```js
333
+ fastify.decorateRequest('user', null)
334
+
335
+ fastify.get('/', function (request, reply) {
336
+ const user = request.getDecorator('user')
337
+ reply.send({ user })
160
338
  })
161
339
  ```
162
- ### .getValidationFunction(schema | httpPart)
340
+
341
+ ##### .setDecorator(name, value)
342
+ <a id="setdecorator"></a>
343
+
344
+ Sets the value of a request decorator declared with
345
+ [`decorateRequest`](./Decorators.md#decoraterequestname-value-dependencies).
346
+ It throws an `FST_ERR_DEC_UNDECLARED` error if the decorator has not been
347
+ declared. See [`setDecorator`](./Decorators.md#setdecoratorname-value) for more
348
+ details.
349
+
350
+ ```js
351
+ fastify.decorateRequest('user', null)
352
+
353
+ fastify.addHook('preHandler', async (request) => {
354
+ request.setDecorator('user', 'Bob Dylan')
355
+ })
356
+ ```
357
+
358
+ ##### .getValidationFunction(schema | httpPart)
163
359
  <a id="getvalidationfunction"></a>
164
360
 
165
361
  By calling this function with a provided `schema` or `httpPart`, it returns a
166
362
  `validation` function to validate diverse inputs. It returns `undefined` if no
167
- serialization function is found using the provided inputs.
363
+ validation function is found using the provided inputs.
168
364
 
169
365
  This function has an `errors` property. Errors encountered during the last
170
366
  validation are assigned to `errors`.
@@ -190,10 +386,10 @@ console.log(validate({ foo: 0.5 })) // false
190
386
  console.log(validate.errors) // validation errors
191
387
  ```
192
388
 
193
- See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
389
+ See [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema)
194
390
  for more information on compiling validation schemas.
195
391
 
196
- ### .compileValidationSchema(schema, [httpPart])
392
+ ##### .compileValidationSchema(schema, [httpPart])
197
393
  <a id="compilevalidationschema"></a>
198
394
 
199
395
  This function compiles a validation schema and returns a function to validate data.
@@ -231,8 +427,8 @@ const validate = request
231
427
  type: 'string'
232
428
  }
233
429
  }
234
- }, 200)
235
- console.log(validate({ hello: 'world' })) // false
430
+ }, 'body')
431
+ console.log(validate({ foo: {} })) // false
236
432
  console.log(validate.errors) // validation errors
237
433
  ```
238
434
 
@@ -280,7 +476,7 @@ const newValidate = request.compileValidationSchema(newSchema)
280
476
  console.log(newValidate === validate) // false
281
477
  ```
282
478
 
283
- ### .validateInput(data, [schema | httpPart], [httpPart])
479
+ ##### .validateInput(data, [schema | httpPart], [httpPart])
284
480
  <a id="validate"></a>
285
481
 
286
482
  This function validates the input based on the provided schema or HTTP part. If
@@ -318,5 +514,5 @@ request
318
514
  .validateInput({ hello: 'world'}, 'query') // false
319
515
  ```
320
516
 
321
- See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
517
+ See [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema)
322
518
  for more information on compiling validation schemas.
@@ -41,6 +41,7 @@ describes the properties available in that options object.
41
41
  - [`allowUnsafeRegex`](#allowunsaferegex)
42
42
  - [`buildPrettyMeta`](#buildprettymeta)
43
43
  - [`caseSensitive`](#casesensitive)
44
+ - [`compile`](#compile)
44
45
  - [`constraints`](#constraints)
45
46
  - [`defaultRoute`](#defaultroute)
46
47
  - [`ignoreDuplicateSlashes`](#ignoreduplicateslashes)
@@ -920,6 +921,36 @@ fastify.get('/user/:username', (request, reply) => {
920
921
  })
921
922
  ```
922
923
 
924
+ ### `compile`
925
+ <a id="compile"></a>
926
+
927
+ + Default: `false`
928
+
929
+ When `true`, Fastify calls the
930
+ [`compile()`](https://github.com/delvedor/find-my-way#compiler-mode) method of
931
+ `find-my-way` once all the routes are registered, right before the
932
+ [`onReady`](./Hooks.md#onready) hooks run. This applies to both the main
933
+ router and the router used for [not found handlers](#set-not-found-handler).
934
+ Each HTTP method's routing tree is
935
+ turned into generated JavaScript instead of being walked for every request,
936
+ which makes route lookups faster. Matching semantics are the same as with the
937
+ default tree walk.
938
+
939
+ ```js
940
+ const fastify = require('fastify')({
941
+ routerOptions: {
942
+ compile: true
943
+ }
944
+ })
945
+ ```
946
+
947
+ Keep in mind that:
948
+
949
+ + the compiled matcher works on the raw request URL; URLs containing
950
+ percent-encoded characters fall back to the decoded path and are not faster;
951
+ + every route generates its own code, so applications with thousands of routes
952
+ produce a large amount of JavaScript, which needs to warm up per route.
953
+
923
954
  ### `constraints`
924
955
  <a id="constraints"></a>
925
956
 
@@ -1044,9 +1075,14 @@ multi) routes by using `maxParamLength` option; the default value is 100
1044
1075
  characters. If the maximum length limit is reached, the not found route will
1045
1076
  be invoked.
1046
1077
 
1047
- This can be useful especially if you have a regex-based route, protecting you
1048
- against [ReDoS
1078
+ For regex routes, the limit applies to the values captured by the regular
1079
+ expression, and it is checked only after the expression has run against the
1080
+ whole path segment. `maxParamLength` therefore does not limit the input a route
1081
+ regex processes and does not protect against [ReDoS
1049
1082
  attacks](https://community.owasp.org/attacks/Regular_expression_Denial_of_Service_-_ReDoS).
1083
+ To guard against ReDoS, keep [`allowUnsafeRegex`](#allow-unsafe-regex)
1084
+ disabled so that potentially catastrophic regular expressions are rejected when
1085
+ the route is registered.
1050
1086
 
1051
1087
 
1052
1088
  ### `onBadUrl`
@@ -2382,6 +2418,7 @@ The properties that can currently be exposed are:
2382
2418
  - allowUnsafeRegex
2383
2419
  - buildPrettyMeta
2384
2420
  - caseSensitive
2421
+ - compile
2385
2422
  - constraints
2386
2423
  - defaultRoute
2387
2424
  - ignoreDuplicateSlashes
@@ -799,7 +799,7 @@ For example:
799
799
  const schema = {
800
800
  response: {
801
801
  200: {
802
- description: 'Response schema that support different content types'
802
+ description: 'Response schema that support different content types',
803
803
  content: {
804
804
  'application/json': {
805
805
  schema: {
@@ -851,6 +851,15 @@ const schema = {
851
851
  fastify.post('/url', { schema }, handler)
852
852
  ```
853
853
 
854
+ > When the route declares a per-content-type response schema through `content`,
855
+ > Fastify matches the response `Content-Type` against the declared keys and falls
856
+ > back to `*/*` (if present). If the response `Content-Type` is not declared and
857
+ > there is no `*/*` key, serialization **fails closed**: Fastify throws
858
+ > `FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN` and replies with a `500` error,
859
+ > instead of silently serializing the payload with `JSON.stringify` (which would
860
+ > bypass the response schema). Every `Content-Type` a route may respond with must
861
+ > be declared, or the route must define a `*/*` catch-all.
862
+
854
863
  #### Serializer Compiler
855
864
  <a id="schema-serializer"></a>
856
865
 
@@ -931,6 +940,11 @@ message has to be rebuilt from the raw validation result:
931
940
  - `validationContext` is the part of the request that failed validation
932
941
  (`body`, `params`, `querystring` or `headers`).
933
942
  - `code` is `FST_ERR_VALIDATION` and `statusCode` is `400`.
943
+ - When the validator reports a list of errors (as Ajv does) and the default
944
+ [`schemaErrorFormatter`](#schemaerrorformatter) is used, the error is an
945
+ instance of [`FST_ERR_VALIDATION`](./Errors.md#fst_err_validation) without a
946
+ [stack trace](./Errors.md#errors-in-input-data). Errors returned by the
947
+ validator itself, or by a custom formatter, are used as they are.
934
948
 
935
949
  ```js
936
950
  const fastify = Fastify()
package/fastify.d.ts CHANGED
@@ -96,6 +96,7 @@ declare namespace fastify {
96
96
  type TrustProxyFunction = (address: string, hop: number) => boolean
97
97
 
98
98
  export type FastifyRouterOptions<RawServer extends RawServerBase> = Omit<FindMyWayConfigForServer<RawServer>, 'defaultRoute' | 'onBadUrl' | 'onMaxParamLength' | 'querystringParser' | 'constraints'> & {
99
+ compile?: boolean,
99
100
  constraints?: {
100
101
  [name: string]: ConstraintStrategy<FindMyWayVersion<RawServer>, unknown>,
101
102
  },
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '6.0.0-alpha.4'
3
+ const VERSION = '6.0.0-alpha.6'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -83,6 +83,23 @@ const { buildErrorHandler } = require('./lib/error-handler.js')
83
83
 
84
84
  const initChannel = diagnostics.channel('fastify.initialization')
85
85
 
86
+ function asyncDisposableHook (disposable) {
87
+ return function (instance, done) {
88
+ disposable[Symbol.asyncDispose]().then(() => done(), done)
89
+ }
90
+ }
91
+
92
+ function disposableHook (disposable) {
93
+ return function (instance, done) {
94
+ try {
95
+ disposable[Symbol.dispose]()
96
+ done()
97
+ } catch (err) {
98
+ done(err)
99
+ }
100
+ }
101
+ }
102
+
86
103
  /**
87
104
  * @param {import('./fastify.js').FastifyServerOptions} serverOptions
88
105
  */
@@ -381,6 +398,13 @@ function fastify (serverOptions) {
381
398
 
382
399
  // cache the closing value, since we are checking it in an hot path
383
400
  avvio.once('preReady', () => {
401
+ // All the routes are registered at this point. Routes added or removed
402
+ // afterwards make find-my-way compile the changed trees again lazily.
403
+ if (options.routerOptions.compile === true) {
404
+ router.compileRoutes()
405
+ fourOhFour.router.compile()
406
+ }
407
+
384
408
  fastify.onClose((instance, done) => {
385
409
  fastify[kState].closing = true
386
410
  router.closeRoutes()
@@ -598,6 +622,16 @@ function fastify (serverOptions) {
598
622
  }
599
623
  }
600
624
 
625
+ if (name === 'onClose' && typeof fn !== 'function') {
626
+ if (typeof fn[Symbol.asyncDispose] === 'function') {
627
+ fn = asyncDisposableHook(fn)
628
+ } else if (typeof fn[Symbol.dispose] === 'function') {
629
+ fn = disposableHook(fn)
630
+ } else {
631
+ throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, Object.prototype.toString.call(fn))
632
+ }
633
+ }
634
+
601
635
  if (name === 'onClose') {
602
636
  this.onClose(fn.bind(this))
603
637
  } else if (name === 'onReady' || name === 'onListen' || name === 'onRoute' || name === 'preClose') {