fastify 4.20.0 → 4.21.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 CHANGED
@@ -15,6 +15,7 @@ CI](https://github.com/fastify/fastify/workflows/package-manager-ci/badge.svg?br
15
15
  [![Web
16
16
  SIte](https://github.com/fastify/fastify/workflows/website/badge.svg?branch=main)](https://github.com/fastify/fastify/actions/workflows/website.yml)
17
17
  [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
18
+ [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/7585/badge)](https://bestpractices.coreinfrastructure.org/projects/7585)
18
19
 
19
20
  </div>
20
21
 
@@ -48,7 +49,7 @@ The `main` branch refers to the Fastify `v4` release. Check out the
48
49
 
49
50
 
50
51
 
51
- ### Table of Contents
52
+ ### Table of Contents
52
53
 
53
54
  - [Quick start](#quick-start)
54
55
  - [Install](#install)
@@ -98,6 +98,8 @@ describes the properties available in that options object.
98
98
  ### `http`
99
99
  <a id="factory-http"></a>
100
100
 
101
+ + Default: `null`
102
+
101
103
  An object used to configure the server's listening socket. The options
102
104
  are the same as the Node.js core [`createServer`
103
105
  method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_createserver_options_requestlistener).
@@ -105,20 +107,20 @@ method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_create
105
107
  This option is ignored if options [`http2`](#factory-http2) or
106
108
  [`https`](#factory-https) are set.
107
109
 
108
- + Default: `null`
109
-
110
110
  ### `http2`
111
111
  <a id="factory-http2"></a>
112
112
 
113
+ + Default: `false`
114
+
113
115
  If `true` Node.js core's
114
116
  [HTTP/2](https://nodejs.org/dist/latest-v14.x/docs/api/http2.html) module is
115
117
  used for binding the socket.
116
118
 
117
- + Default: `false`
118
-
119
119
  ### `https`
120
120
  <a id="factory-https"></a>
121
121
 
122
+ + Default: `null`
123
+
122
124
  An object used to configure the server's listening socket for TLS. The options
123
125
  are the same as the Node.js core [`createServer`
124
126
  method](https://nodejs.org/dist/latest-v14.x/docs/api/https.html#https_https_createserver_options_requestlistener).
@@ -126,96 +128,100 @@ When this property is `null`, the socket will not be configured for TLS.
126
128
 
127
129
  This option also applies when the [`http2`](#factory-http2) option is set.
128
130
 
129
- + Default: `null`
130
-
131
131
  ### `connectionTimeout`
132
132
  <a id="factory-connection-timeout"></a>
133
133
 
134
+ + Default: `0` (no timeout)
135
+
134
136
  Defines the server timeout in milliseconds. See documentation for
135
137
  [`server.timeout`
136
138
  property](https://nodejs.org/api/http.html#http_server_timeout) to understand
137
- the effect of this option. When `serverFactory` option is specified, this option
138
- is ignored.
139
+ the effect of this option.
139
140
 
140
- + Default: `0` (no timeout)
141
+ When `serverFactory` option is specified this option is ignored.
141
142
 
142
143
  ### `keepAliveTimeout`
143
144
  <a id="factory-keep-alive-timeout"></a>
144
145
 
146
+ + Default: `72000` (72 seconds)
147
+
145
148
  Defines the server keep-alive timeout in milliseconds. See documentation for
146
149
  [`server.keepAliveTimeout`
147
150
  property](https://nodejs.org/api/http.html#http_server_keepalivetimeout) to
148
151
  understand the effect of this option. This option only applies when HTTP/1 is in
149
- use. Also, when `serverFactory` option is specified, this option is ignored.
152
+ use.
150
153
 
151
- + Default: `72000` (72 seconds)
154
+ When `serverFactory` option is specified this option is ignored.
152
155
 
153
156
  ### `forceCloseConnections`
154
157
  <a id="forcecloseconnections"></a>
155
158
 
159
+ + Default: `"idle"` if the HTTP server allows it, `false` otherwise
160
+
156
161
  When set to `true`, upon [`close`](#close) the server will iterate the current
157
162
  persistent connections and [destroy their
158
163
  sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror).
159
164
 
160
- > Important: connections are not inspected to determine if requests have been
161
- > completed.
165
+ > **Warning**
166
+ > Connections are not inspected to determine if requests have
167
+ > been completed.
162
168
 
163
169
  Fastify will prefer the HTTP server's
164
170
  [`closeAllConnections`](https://nodejs.org/dist/latest-v18.x/docs/api/http.html#servercloseallconnections)
165
- method if supported, otherwise it will use internal connection tracking.
171
+ method if supported, otherwise, it will use internal connection tracking.
166
172
 
167
173
  When set to `"idle"`, upon [`close`](#close) the server will iterate the current
168
174
  persistent connections which are not sending a request or waiting for a response
169
- and destroy their sockets. The value is supported only if the HTTP server
175
+ and destroy their sockets. The value is only supported if the HTTP server
170
176
  supports the
171
177
  [`closeIdleConnections`](https://nodejs.org/dist/latest-v18.x/docs/api/http.html#servercloseidleconnections)
172
178
  method, otherwise attempting to set it will throw an exception.
173
179
 
174
- + Default: `"idle"` if the HTTP server allows it, `false` otherwise
175
-
176
180
  ### `maxRequestsPerSocket`
177
181
  <a id="factory-max-requests-per-socket"></a>
178
182
 
179
- Defines the maximum number of requests socket can handle before closing keep
180
- alive connection. See documentation for [`server.maxRequestsPerSocket`
183
+ + Default: `0` (no limit)
184
+
185
+ Defines the maximum number of requests a socket can handle before closing keep
186
+ alive connection. See [`server.maxRequestsPerSocket`
181
187
  property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_maxrequestspersocket)
182
188
  to understand the effect of this option. This option only applies when HTTP/1.1
183
189
  is in use. Also, when `serverFactory` option is specified, this option is
184
190
  ignored.
185
- > At the time of this writing, only node version greater or equal to 16.10.0
186
- > support this option. Check the Node.js documentation for availability in the
187
- > version you are running.
188
191
 
189
- + Default: `0` (no limit)
192
+ > **Note**
193
+ > At the time of writing, only node >= v16.10.0 supports this option.
190
194
 
191
195
  ### `requestTimeout`
192
196
  <a id="factory-request-timeout"></a>
193
197
 
198
+ + Default: `0` (no limit)
199
+
194
200
  Defines the maximum number of milliseconds for receiving the entire request from
195
- the client. [`server.requestTimeout`
201
+ the client. See [`server.requestTimeout`
196
202
  property](https://nodejs.org/dist/latest/docs/api/http.html#http_server_requesttimeout)
197
- to understand the effect of this option. Also, when `serverFactory` option is
198
- specified, this option is ignored. It must be set to a non-zero value (e.g. 120
199
- seconds) to protect against potential Denial-of-Service attacks in case the
200
- server is deployed without a reverse proxy in front.
201
- > At the time of this writing, only node version greater or equal to 14.11.0
202
- > support this option. Check the Node.js documentation for availability in the
203
- > version you are running.
203
+ to understand the effect of this option.
204
204
 
205
- + Default: `0` (no limit)
205
+ When `serverFactory` option is specified, this option is ignored.
206
+ It must be set to a non-zero value (e.g. 120 seconds) to protect against potential
207
+ Denial-of-Service attacks in case the server is deployed without a reverse proxy
208
+ in front.
209
+
210
+ > **Note**
211
+ > At the time of writing, only node >= v14.11.0 supports this option
206
212
 
207
213
  ### `ignoreTrailingSlash`
208
214
  <a id="factory-ignore-slash"></a>
209
215
 
216
+ + Default: `false`
217
+
210
218
  Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle
211
- routing. By default, Fastify is set to take into account the trailing slashes.
212
- Paths like `/foo` and `/foo/` will be treated as different paths. If you want to
219
+ routing. By default, Fastify will take into account the trailing slashes.
220
+ Paths like `/foo` and `/foo/` are treated as different paths. If you want to
213
221
  change this, set this flag to `true`. That way, both `/foo` and `/foo/` will
214
222
  point to the same route. This option applies to *all* route registrations for
215
223
  the resulting server instance.
216
224
 
217
- + Default: `false`
218
-
219
225
  ```js
220
226
  const fastify = require('fastify')({
221
227
  ignoreTrailingSlash: true
@@ -235,17 +241,17 @@ fastify.get('/bar', function (req, reply) {
235
241
  ### `ignoreDuplicateSlashes`
236
242
  <a id="factory-ignore-duplicate-slashes"></a>
237
243
 
244
+ + Default: `false`
245
+
238
246
  Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) to handle
239
247
  routing. You can use `ignoreDuplicateSlashes` option to remove duplicate slashes
240
- from the path. It removes duplicate slashes in the route path and in the request
248
+ from the path. It removes duplicate slashes in the route path and the request
241
249
  URL. This option applies to *all* route registrations for the resulting server
242
250
  instance.
243
251
 
244
- Note that when `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set
245
- to true, Fastify will remove duplicate slashes, and then trailing slashes,
246
- meaning //a//b//c// will be converted to /a/b/c.
247
-
248
- + Default: `false`
252
+ When `ignoreTrailingSlash` and `ignoreDuplicateSlashes` are both set
253
+ to `true` Fastify will remove duplicate slashes, and then trailing slashes,
254
+ meaning `//a//b//c//` will be converted to `/a/b/c`.
249
255
 
250
256
  ```js
251
257
  const fastify = require('fastify')({
@@ -263,46 +269,45 @@ fastify.get('///foo//bar//', function (req, reply) {
263
269
 
264
270
  You can set a custom length for parameters in parametric (standard, regex, and
265
271
  multi) routes by using `maxParamLength` option; the default value is 100
266
- characters.
272
+ characters. If the maximum length limit is reached, the not found route will
273
+ be invoked.
267
274
 
268
275
  This can be useful especially if you have a regex-based route, protecting you
269
- against [DoS
276
+ against [ReDoS
270
277
  attacks](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS).
271
278
 
272
- *If the maximum length limit is reached, the not found route will be invoked.*
273
-
274
279
  ### `bodyLimit`
275
280
  <a id="factory-body-limit"></a>
276
281
 
277
- Defines the maximum payload, in bytes, the server is allowed to accept.
278
-
279
282
  + Default: `1048576` (1MiB)
280
283
 
284
+ Defines the maximum payload, in bytes, the server is allowed to accept.
285
+
281
286
  ### `onProtoPoisoning`
282
287
  <a id="factory-on-proto-poisoning"></a>
283
288
 
289
+ + Default: `'error'`
290
+
284
291
  Defines what action the framework must take when parsing a JSON object with
285
292
  `__proto__`. This functionality is provided by
286
293
  [secure-json-parse](https://github.com/fastify/secure-json-parse). See
287
294
  [Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more details about
288
295
  prototype poisoning attacks.
289
296
 
290
- Possible values are `'error'`, `'remove'` and `'ignore'`.
291
-
292
- + Default: `'error'`
297
+ Possible values are `'error'`, `'remove'`, or `'ignore'`.
293
298
 
294
299
  ### `onConstructorPoisoning`
295
300
  <a id="factory-on-constructor-poisoning"></a>
296
301
 
302
+ + Default: `'error'`
303
+
297
304
  Defines what action the framework must take when parsing a JSON object with
298
305
  `constructor`. This functionality is provided by
299
306
  [secure-json-parse](https://github.com/fastify/secure-json-parse). See
300
307
  [Prototype Poisoning](../Guides/Prototype-Poisoning.md) for more details about
301
308
  prototype poisoning attacks.
302
309
 
303
- Possible values are `'error'`, `'remove'` and `'ignore'`.
304
-
305
- + Default: `'error'`
310
+ Possible values are `'error'`, `'remove'`, or `'ignore'`.
306
311
 
307
312
  ### `logger`
308
313
  <a id="factory-logger"></a>
@@ -363,13 +368,18 @@ The possible values this property may have are:
363
368
  ### `disableRequestLogging`
364
369
  <a id="factory-disable-request-logging"></a>
365
370
 
366
- By default, when logging is enabled, Fastify will issue an `info` level log
371
+ + Default: `false`
372
+
373
+ When logging is enabled, Fastify will issue an `info` level log
367
374
  message when a request is received and when the response for that request has
368
375
  been sent. By setting this option to `true`, these log messages will be
369
376
  disabled. This allows for more flexible request start and end logging by
370
377
  attaching custom `onRequest` and `onResponse` hooks.
371
378
 
372
- + Default: `false`
379
+ Please note that this option will also disable an error log written by the
380
+ default `onResponse` hook on reply callback errors. Other log messages
381
+ emitted by Fastify will stay enabled, like deprecation warnings and messages
382
+ emitted when requests are received while the server is closing.
373
383
 
374
384
  ```js
375
385
  // Examples of hooks to replicate the disabled functionality.
@@ -384,11 +394,6 @@ fastify.addHook('onResponse', (req, reply, done) => {
384
394
  })
385
395
  ```
386
396
 
387
- Please note that this setting will also disable an error log written by the
388
- default `onResponse` hook on reply callback errors. Other log messages
389
- emitted by Fastify will stay enabled, like deprecation warnings and messages
390
- emitted when requests are received while the server is closing.
391
-
392
397
  ### `serverFactory`
393
398
  <a id="custom-http-server"></a>
394
399
 
@@ -428,13 +433,17 @@ enhance the server instance inside the `serverFactory` function before the
428
433
 
429
434
  + Default: `true`
430
435
 
431
- Internally, and by default, Fastify will automatically infer the root properties
436
+ By default, Fastify will automatically infer the root properties
432
437
  of JSON Schemas if it does not find valid root properties according to the JSON
433
- Schema spec. If you wish to implement your own schema validation compiler, for
434
- example: to parse schemas as JTD instead of JSON Schema, then you can explicitly
438
+ Schema spec. If you wish to implement your own schema validation compiler, to
439
+ parse schemas as JTD instead of JSON Schema for example, then you can explicitly
435
440
  set this option to `false` to make sure the schemas you receive are unmodified
436
441
  and are not being treated internally as JSON Schema.
437
442
 
443
+ Fastify does not throw on invalid schemas so if this option is set to `false`
444
+ in an existing project, check that none of your existing schemas become
445
+ invalid as a result, as they will be treated as catch-alls.
446
+
438
447
  ```js
439
448
  const AjvJTD = require('ajv/dist/jtd'/* only valid for AJV v7+ */)
440
449
  const ajv = new AjvJTD({
@@ -457,21 +466,23 @@ fastify.post('/', {
457
466
  })
458
467
  ```
459
468
 
460
- **Note: Fastify does not currently throw on invalid schemas, so if you turn this
461
- off in an existing project, you need to be careful that none of your existing
462
- schemas become invalid as a result, since they will be treated as a catch-all.**
463
-
464
469
  ### `caseSensitive`
465
470
  <a id="factory-case-sensitive"></a>
466
471
 
467
- By default, value equal to `true`, routes are registered as case sensitive. That
468
- is, `/foo` is not equivalent to `/Foo`. When set to `false`, routes are
469
- registered in a fashion such that `/foo` is equivalent to `/Foo` which is
470
- equivalent to `/FOO`.
472
+ + Default: `true`
473
+
474
+ When `true` routes are registered as case-sensitive. That is, `/foo`
475
+ is not equal to `/Foo`.
476
+ When `false` then routes are case-insensitive.
477
+
478
+ Please note that setting this option to `false` goes against
479
+ [RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1).
471
480
 
472
481
  By setting `caseSensitive` to `false`, all paths will be matched as lowercase,
473
482
  but the route parameters or wildcards will maintain their original letter
474
- casing.
483
+ casing.
484
+ This option does not affect query strings, please refer to
485
+ [`querystringParser`](#querystringparser) to change their handling.
475
486
 
476
487
  ```js
477
488
  fastify.get('/user/:username', (request, reply) => {
@@ -480,19 +491,13 @@ fastify.get('/user/:username', (request, reply) => {
480
491
  })
481
492
  ```
482
493
 
483
- Please note that setting this option to `false` goes against
484
- [RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1).
485
-
486
- Also note, this setting will not affect query strings. If you want to change the
487
- way query strings are handled take a look at
488
- [`querystringParser`](#querystringparser).
489
-
490
-
491
494
  ### `allowUnsafeRegex`
492
495
  <a id="factory-allow-unsafe-regex"></a>
493
496
 
494
- The allowUnsafeRegex setting is false by default, so routes only allow safe
495
- regular expressions. To use unsafe expressions, set allowUnsafeRegex to true.
497
+ + Default `false`
498
+
499
+ Disabled by default, so routes only allow safe regular expressions. To use
500
+ unsafe expressions, set `allowUnsafeRegex` to `true`.
496
501
 
497
502
  ```js
498
503
  fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => {
@@ -500,18 +505,14 @@ fastify.get('/user/:id(^([0-9]+){4}$)', (request, reply) => {
500
505
  })
501
506
  ```
502
507
 
503
- Under the hood: [FindMyWay](https://github.com/delvedor/find-my-way) More info
504
- about safe regexp: [Safe-regex2](https://www.npmjs.com/package/safe-regex2)
505
-
506
-
507
508
  ### `requestIdHeader`
508
509
  <a id="factory-request-id-header"></a>
509
510
 
511
+ + Default: `'request-id'`
512
+
510
513
  The header name used to set the request-id. See [the
511
514
  request-id](./Logging.md#logging-request-id) section.
512
- Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid)
513
-
514
- + Default: `'request-id'`
515
+ Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid).
515
516
 
516
517
  ```js
517
518
  const fastify = require('fastify')({
@@ -519,25 +520,31 @@ const fastify = require('fastify')({
519
520
  //requestIdHeader: false, // -> always use genReqId
520
521
  })
521
522
  ```
523
+
522
524
  ### `requestIdLogLabel`
523
525
  <a id="factory-request-id-log-label"></a>
524
526
 
525
- Defines the label used for the request identifier when logging the request.
526
-
527
527
  + Default: `'reqId'`
528
528
 
529
+ Defines the label used for the request identifier when logging the request.
530
+
529
531
  ### `genReqId`
530
532
  <a id="factory-gen-request-id"></a>
531
533
 
532
- Function for generating the request-id. It will receive the _raw_ incoming
533
- request as a parameter. This function is expected to be error-free.
534
-
535
534
  + Default: `value of 'request-id' header if provided or monotonically increasing
536
535
  integers`
537
536
 
537
+ Function for generating the request-id. It will receive the _raw_ incoming
538
+ request as a parameter. This function is expected to be error-free.
539
+
538
540
  Especially in distributed systems, you may want to override the default ID
539
541
  generation behavior as shown below. For generating `UUID`s you may want to check
540
- out [hyperid](https://github.com/mcollina/hyperid)
542
+ out [hyperid](https://github.com/mcollina/hyperid).
543
+
544
+ > **Note**
545
+ > `genReqId` will be not called if the header set in
546
+ > <code>[requestIdHeader](#requestidheader)</code> is available (defaults to
547
+ > 'request-id').
541
548
 
542
549
  ```js
543
550
  let i = 0
@@ -546,21 +553,9 @@ const fastify = require('fastify')({
546
553
  })
547
554
  ```
548
555
 
549
- **Note: genReqId will _not_ be called if the header set in
550
- <code>[requestIdHeader](#requestidheader)</code> is available (defaults to
551
- 'request-id').**
552
-
553
556
  ### `trustProxy`
554
557
  <a id="factory-trust-proxy"></a>
555
558
 
556
- By enabling the `trustProxy` option, Fastify will know that it is sitting behind
557
- a proxy and that the `X-Forwarded-*` header fields may be trusted, which
558
- otherwise may be easily spoofed.
559
-
560
- ```js
561
- const fastify = Fastify({ trustProxy: true })
562
- ```
563
-
564
559
  + Default: `false`
565
560
  + `true/false`: Trust all proxies (`true`) or do not trust any proxies
566
561
  (`false`).
@@ -575,6 +570,14 @@ const fastify = Fastify({ trustProxy: true })
575
570
  }
576
571
  ```
577
572
 
573
+ By enabling the `trustProxy` option, Fastify will know that it is sitting behind
574
+ a proxy and that the `X-Forwarded-*` header fields may be trusted, which
575
+ otherwise may be easily spoofed.
576
+
577
+ ```js
578
+ const fastify = Fastify({ trustProxy: true })
579
+ ```
580
+
578
581
  For more examples, refer to the
579
582
  [`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package.
580
583
 
@@ -590,28 +593,32 @@ fastify.get('/', (request, reply) => {
590
593
  })
591
594
  ```
592
595
 
593
- **Note: if a request contains multiple <code>x-forwarded-host</code> or
594
- <code>x-forwarded-proto</code> headers, it is only the last one that is used to
595
- derive <code>request.hostname</code> and <code>request.protocol</code>**
596
+ > **Note**
597
+ > If a request contains multiple `x-forwarded-host` or `x-forwarded-proto`
598
+ > headers, it is only the last one that is used to derive `request.hostname`
599
+ > and `request.protocol`.
596
600
 
597
601
  ### `pluginTimeout`
598
602
  <a id="plugin-timeout"></a>
599
603
 
604
+ + Default: `10000`
605
+
600
606
  The maximum amount of time in *milliseconds* in which a plugin can load. If not,
601
607
  [`ready`](#ready) will complete with an `Error` with code
602
608
  `'ERR_AVVIO_PLUGIN_TIMEOUT'`. When set to `0`, disables this check. This
603
609
  controls [avvio](https://www.npmjs.com/package/avvio) 's `timeout` parameter.
604
610
 
605
- + Default: `10000`
606
-
607
611
  ### `querystringParser`
608
612
  <a id="factory-querystring-parser"></a>
609
613
 
610
614
  The default query string parser that Fastify uses is the Node.js's core
611
615
  `querystring` module.
612
616
 
613
- You can change this default setting by passing the option `querystringParser`
614
- and use a custom one, such as [`qs`](https://www.npmjs.com/package/qs).
617
+ You can use this option to use a custom parser, such as
618
+ [`qs`](https://www.npmjs.com/package/qs).
619
+
620
+ If you only want the keys (and not the values) to be case insensitive we
621
+ recommend using a custom parser to convert only the keys to lowercase.
615
622
 
616
623
  ```js
617
624
  const qs = require('qs')
@@ -620,7 +627,7 @@ const fastify = require('fastify')({
620
627
  })
621
628
  ```
622
629
 
623
- You can also use Fastify's default parser but change some handling behaviour,
630
+ You can also use Fastify's default parser but change some handling behavior,
624
631
  like the example below for case insensitive keys and values:
625
632
 
626
633
  ```js
@@ -630,24 +637,21 @@ const fastify = require('fastify')({
630
637
  })
631
638
  ```
632
639
 
633
- Note, if you only want the keys (and not the values) to be case insensitive we
634
- recommend using a custom parser to convert only the keys to lowercase.
635
-
636
640
  ### `exposeHeadRoutes`
637
641
  <a id="exposeHeadRoutes"></a>
638
642
 
643
+ + Default: `true`
644
+
639
645
  Automatically creates a sibling `HEAD` route for each `GET` route defined. If
640
646
  you want a custom `HEAD` handler without disabling this option, make sure to
641
647
  define it before the `GET` route.
642
648
 
643
- + Default: `true`
644
-
645
649
  ### `constraints`
646
650
  <a id="constraints"></a>
647
651
 
648
- Fastify's built in route constraints are provided by `find-my-way`, which allow
649
- constraining routes by `version` or `host`. You are able to add new constraint
650
- strategies, or override the built in strategies by providing a `constraints`
652
+ Fastify's built-in route constraints are provided by `find-my-way`, which
653
+ allows constraining routes by `version` or `host`. You can add new constraint
654
+ strategies, or override the built-in strategies, by providing a `constraints`
651
655
  object with strategies for `find-my-way`. You can find more information on
652
656
  constraint strategies in the
653
657
  [find-my-way](https://github.com/delvedor/find-my-way) documentation.
@@ -676,11 +680,11 @@ const fastify = require('fastify')({
676
680
  ### `return503OnClosing`
677
681
  <a id="factory-return-503-on-closing"></a>
678
682
 
683
+ + Default: `true`
684
+
679
685
  Returns 503 after calling `close` server method. If `false`, the server routes
680
686
  the incoming request as usual.
681
687
 
682
- + Default: `true`
683
-
684
688
  ### `ajv`
685
689
  <a id="factory-ajv"></a>
686
690
 
@@ -709,7 +713,7 @@ const fastify = require('fastify')({
709
713
 
710
714
  Customize the options of the default
711
715
  [`fast-json-stringify`](https://github.com/fastify/fast-json-stringify#options)
712
- instance that serialize the response's payload:
716
+ instance that serializes the response's payload:
713
717
 
714
718
  ```js
715
719
  const fastify = require('fastify')({
@@ -722,15 +726,17 @@ const fastify = require('fastify')({
722
726
  ### `http2SessionTimeout`
723
727
  <a id="http2-session-timeout"></a>
724
728
 
729
+ + Default: `72000`
730
+
725
731
  Set a default
726
- [timeout](https://nodejs.org/api/http2.html#http2_http2session_settimeout_msecs_callback)
727
- to every incoming HTTP/2 session. The session will be closed on the timeout.
728
- Default: `72000` ms.
732
+ [timeout](https://nodejs.org/api/http2.html#http2sessionsettimeoutmsecs-callback)
733
+ to every incoming HTTP/2 session in milliseconds. The session will be closed on
734
+ the timeout.
729
735
 
730
- Note that this is needed to offer the graceful "close" experience when using
736
+ This option is needed to offer a graceful "close" experience when using
731
737
  HTTP/2. The low default has been chosen to mitigate denial of service attacks.
732
738
  When the server is behind a load balancer or can scale automatically this value
733
- can be increased to fit the use case. Node core defaults this to `0`. `
739
+ can be increased to fit the use case. Node core defaults this to `0`.
734
740
 
735
741
  ### `frameworkErrors`
736
742
  <a id="framework-errors"></a>
@@ -741,8 +747,8 @@ Fastify provides default error handlers for the most common use cases. It is
741
747
  possible to override one or more of those handlers with custom code using this
742
748
  option.
743
749
 
744
- *Note: Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at
745
- the moment.*
750
+ > **Note**
751
+ > Only `FST_ERR_BAD_URL` and `FST_ERR_ASYNC_CONSTRAINT` are implemented at present.
746
752
 
747
753
  ```js
748
754
  const fastify = require('fastify')({
@@ -794,10 +800,11 @@ function defaultClientErrorHandler (err, socket) {
794
800
  }
795
801
  ```
796
802
 
797
- *Note: `clientErrorHandler` operates with raw socket. The handler is expected to
798
- return a properly formed HTTP response that includes a status line, HTTP headers
799
- and a message body. Before attempting to write the socket, the handler should
800
- check if the socket is still writable as it may have already been destroyed.*
803
+ > **Note**
804
+ > `clientErrorHandler` operates with raw sockets. The handler is expected to
805
+ > return a properly formed HTTP response that includes a status line, HTTP headers
806
+ > and a message body. Before attempting to write the socket, the handler should
807
+ > check if the socket is still writable as it may have already been destroyed.
801
808
 
802
809
  ```js
803
810
  const fastify = require('fastify')({
@@ -826,9 +833,11 @@ const fastify = require('fastify')({
826
833
  <a id="rewrite-url"></a>
827
834
 
828
835
  Set a sync callback function that must return a string that allows rewriting
829
- URLs.
836
+ URLs. This is useful when you are behind a proxy that changes the URL.
837
+ Rewriting a URL will modify the `url` property of the `req` object.
830
838
 
831
- > Rewriting a URL will modify the `url` property of the `req` object
839
+ Note that `rewriteUrl` is called _before_ routing, it is not encapsulated and it
840
+ is an instance-wide configuration.
832
841
 
833
842
  ```js
834
843
  // @param {object} req The raw Node.js HTTP request, not the `FastifyRequest` object.
@@ -844,9 +853,6 @@ function rewriteUrl (req) {
844
853
  }
845
854
  ```
846
855
 
847
- Note that `rewriteUrl` is called _before_ routing, it is not encapsulated and it
848
- is an instance-wide configuration.
849
-
850
856
  ## Instance
851
857
 
852
858
  ### Server Methods
@@ -858,8 +864,9 @@ is an instance-wide configuration.
858
864
  [server](https://nodejs.org/api/http.html#http_class_http_server) object as
859
865
  returned by the [**`Fastify factory function`**](#factory).
860
866
 
861
- >__Warning__: If utilized improperly, certain Fastify features could be disrupted.
862
- >It is recommended to only use it for attaching listeners.
867
+ > **Warning**
868
+ > If utilized improperly, certain Fastify features could be disrupted.
869
+ > It is recommended to only use it for attaching listeners.
863
870
 
864
871
  #### after
865
872
  <a id="after"></a>
@@ -1057,8 +1064,9 @@ Note that the array contains the `fastify.server.address()` too.
1057
1064
  #### getDefaultRoute
1058
1065
  <a id="getDefaultRoute"></a>
1059
1066
 
1060
- **Notice**: this method is deprecated and should be removed in the next Fastify
1061
- major version.
1067
+ > **Warning**
1068
+ > This method is deprecated and will be removed in the next Fastify
1069
+ > major version.
1062
1070
 
1063
1071
  The `defaultRoute` handler handles requests that do not match any URL specified
1064
1072
  by your Fastify application. This defaults to the 404 handler, but can be
@@ -1072,9 +1080,10 @@ const defaultRoute = fastify.getDefaultRoute()
1072
1080
  #### setDefaultRoute
1073
1081
  <a id="setDefaultRoute"></a>
1074
1082
 
1075
- **Notice**: this method is deprecated and should be removed in the next Fastify
1076
- major version. Please, consider to use `setNotFoundHandler` or a wildcard
1077
- matching route.
1083
+ > **Warning**
1084
+ > This method is deprecated and will be removed in the next Fastify
1085
+ > major version. Please, consider using `setNotFoundHandler` or a wildcard
1086
+ > matching route.
1078
1087
 
1079
1088
  The default 404 handler, or one set using `setNotFoundHandler`, will
1080
1089
  never trigger if the default route is overridden. This sets the handler for the
@@ -1117,9 +1126,9 @@ Method to add routes to the server, it also has shorthand functions, check
1117
1126
  <a id="hasRoute"></a>
1118
1127
 
1119
1128
  Method to check if a route is already registered to the internal router. It
1120
- expects an object as payload. `url` and `method` are mandatory fields. It is
1121
- possible to also specify `constraints`. The method returns true if the route is
1122
- registered, and false if it is not registered.
1129
+ expects an object as the payload. `url` and `method` are mandatory fields. It
1130
+ is possible to also specify `constraints`. The method returns `true` if the
1131
+ route is registered or `false` if not.
1123
1132
 
1124
1133
  ```js
1125
1134
  const routeExists = fastify.hasRoute({
@@ -1220,11 +1229,12 @@ different ways to define a name (in order).
1220
1229
  Newlines are replaced by ` -- `. This will help to identify the root cause when
1221
1230
  you deal with many plugins.
1222
1231
 
1223
- Important: If you have to deal with nested plugins, the name differs with the
1224
- usage of the [fastify-plugin](https://github.com/fastify/fastify-plugin) because
1225
- no new scope is created and therefore we have no place to attach contextual
1226
- data. In that case, the plugin name will represent the boot order of all
1227
- involved plugins in the format of `fastify -> plugin-A -> plugin-B`.
1232
+ > **Warning**
1233
+ > If you have to deal with nested plugins, the name differs with the usage of
1234
+ > the [fastify-plugin](https://github.com/fastify/fastify-plugin) because
1235
+ > no new scope is created and therefore we have no place to attach contextual
1236
+ > data. In that case, the plugin name will represent the boot order of all
1237
+ > involved plugins in the format of `fastify -> plugin-A -> plugin-B`.
1228
1238
 
1229
1239
  #### hasPlugin
1230
1240
  <a id="hasPlugin"></a>
@@ -1327,7 +1337,9 @@ Set the schema error formatter for all routes. See
1327
1337
 
1328
1338
  Set the schema serializer compiler for all routes. See
1329
1339
  [#schema-serializer](./Validation-and-Serialization.md#schema-serializer).
1330
- **Note:** [`setReplySerializer`](#set-reply-serializer) has priority if set!
1340
+
1341
+ > **Note**
1342
+ > [`setReplySerializer`](#set-reply-serializer) has priority if set!
1331
1343
 
1332
1344
  #### validatorCompiler
1333
1345
  <a id="validator-compiler"></a>
@@ -1362,9 +1374,7 @@ This property can be used to fully manage:
1362
1374
  - `compilersFactory`: what module must compile the JSON schemas
1363
1375
 
1364
1376
  It can be useful when your schemas are stored in another data structure that is
1365
- unknown to Fastify. See [issue
1366
- #2446](https://github.com/fastify/fastify/issues/2446) for an example of what
1367
- this property helps to resolve.
1377
+ unknown to Fastify.
1368
1378
 
1369
1379
  Another use case is to tweak all the schemas processing. Doing so it is possible
1370
1380
  to use Ajv v8 JTD or Standalone feature. To use such as JTD or the Standalone
@@ -1404,7 +1414,7 @@ const fastify = Fastify({
1404
1414
  },
1405
1415
 
1406
1416
  /**
1407
- * The compilers factory let you fully control the validator and serializer
1417
+ * The compilers factory lets you fully control the validator and serializer
1408
1418
  * in the Fastify's lifecycle, providing the encapsulation to your compilers.
1409
1419
  */
1410
1420
  compilersFactory: {
@@ -1461,10 +1471,12 @@ lifecycle](./Lifecycle.md#lifecycle). *async-await* is supported as well.
1461
1471
  You can also register [`preValidation`](./Hooks.md#route-hooks) and
1462
1472
  [`preHandler`](./Hooks.md#route-hooks) hooks for the 404 handler.
1463
1473
 
1464
- _Note: The `preValidation` hook registered using this method will run for a
1465
- route that Fastify does not recognize and **not** when a route handler manually
1466
- calls [`reply.callNotFound`](./Reply.md#call-not-found)_. In which case, only
1467
- preHandler will be run.
1474
+ > **Note**
1475
+ > The `preValidation` hook registered using this method will run for a
1476
+ > route that Fastify does not recognize and **not** when a route handler manually
1477
+ > calls [`reply.callNotFound`](./Reply.md#call-not-found). In which case, only
1478
+ > preHandler will be run.
1479
+
1468
1480
 
1469
1481
  ```js
1470
1482
  fastify.setNotFoundHandler({
@@ -1495,8 +1507,9 @@ plugins are registered. If you would like to augment the behavior of the default
1495
1507
  arguments `fastify.setNotFoundHandler()` within the context of these registered
1496
1508
  plugins.
1497
1509
 
1498
- > Note: Some config properties from the request object will be
1499
- > undefined inside the custom not found handler. E.g:
1510
+ > **Note**
1511
+ > Some config properties from the request object will be
1512
+ > undefined inside the custom not found handler. E.g.:
1500
1513
  > `request.routerPath`, `routerMethod` and `context.config`.
1501
1514
  > This method design goal is to allow calling the common not found route.
1502
1515
  > To return a per-route customized 404 response, you can do it in
@@ -1510,11 +1523,12 @@ will be called whenever an error happens. The handler is bound to the Fastify
1510
1523
  instance and is fully encapsulated, so different plugins can set different error
1511
1524
  handlers. *async-await* is supported as well.
1512
1525
 
1513
- *Note: If the error `statusCode` is less than 400, Fastify will automatically
1514
- set it at 500 before calling the error handler.*
1526
+ If the error `statusCode` is less than 400, Fastify will automatically
1527
+ set it to 500 before calling the error handler.
1515
1528
 
1516
- *Also note* that `setErrorHandler` will ***not*** catch any error inside
1517
- an `onResponse` hook because the response has already been sent to the client.
1529
+ > **Note**
1530
+ > `setErrorHandler` will ***not*** catch any error inside
1531
+ > an `onResponse` hook because the response has already been sent to the client.
1518
1532
 
1519
1533
  ```js
1520
1534
  fastify.setErrorHandler(function (error, request, reply) {
@@ -1545,10 +1559,10 @@ if (statusCode >= 500) {
1545
1559
  `fastify.setChildLoggerFactory(factory(logger, bindings, opts, rawReq))`: Set a
1546
1560
  function that will be called when creating a child logger instance for each request
1547
1561
  which allows for modifying or adding child logger bindings and logger options, or
1548
- returning a completely custom child logger implementation.
1562
+ returning a custom child logger implementation.
1549
1563
 
1550
- Child logger bindings have a performance advantage over per-log bindings, because
1551
- they are pre-serialised by Pino when the child logger is created.
1564
+ Child logger bindings have a performance advantage over per-log bindings because
1565
+ they are pre-serialized by Pino when the child logger is created.
1552
1566
 
1553
1567
  The first parameter is the parent logger instance, followed by the default bindings
1554
1568
  and logger options which should be passed to the child logger, and finally
@@ -1616,7 +1630,7 @@ a custom constraint strategy with the same name.
1616
1630
  `fastify.printRoutes()`: Fastify router builds a tree of routes for each HTTP
1617
1631
  method. If you call the prettyPrint without specifying an HTTP method, it will
1618
1632
  merge all the trees into one and print it. The merged tree doesn't represent the
1619
- internal router structure. **Don't use it for debugging.**
1633
+ internal router structure. **Do not use it for debugging.**
1620
1634
 
1621
1635
  *Remember to call it inside or after a `ready` call.*
1622
1636
 
@@ -1658,8 +1672,8 @@ param. Printed tree will represent the internal router structure.
1658
1672
  ```
1659
1673
 
1660
1674
  `fastify.printRoutes({ commonPrefix: false })` will print compressed trees. This
1661
- might useful when you have a large number of routes with common prefixes.
1662
- It doesn't represent the internal router structure. **Don't use it for debugging.**
1675
+ may be useful when you have a large number of routes with common prefixes.
1676
+ It doesn't represent the internal router structure. **Do not use it for debugging.**
1663
1677
 
1664
1678
  ```js
1665
1679
  console.log(fastify.printRoutes({ commonPrefix: false }))
@@ -1752,7 +1766,7 @@ fastify.ready(() => {
1752
1766
  <a id="addContentTypeParser"></a>
1753
1767
 
1754
1768
  `fastify.addContentTypeParser(content-type, options, parser)` is used to pass
1755
- custom parser for a given content type. Useful for adding parsers for custom
1769
+ a custom parser for a given content type. Useful for adding parsers for custom
1756
1770
  content types, e.g. `text/json, application/vnd.oasis.opendocument.text`.
1757
1771
  `content-type` can be a string, string array or RegExp.
1758
1772
 
@@ -1854,7 +1868,7 @@ for more info.
1854
1868
  `fastify.initialConfig`: Exposes a frozen read-only object registering the
1855
1869
  initial options passed down by the user to the Fastify instance.
1856
1870
 
1857
- Currently the properties that can be exposed are:
1871
+ The properties that can currently be exposed are:
1858
1872
  - connectionTimeout
1859
1873
  - keepAliveTimeout
1860
1874
  - bodyLimit
@@ -788,7 +788,7 @@ There are a couple supported import methods with the Fastify type system.
788
788
  Many type definitions share the same generic parameters; they are all
789
789
  documented, in detail, within this section.
790
790
 
791
- Most definitions depend on `@node/types` modules `http`, `https`, and `http2`
791
+ Most definitions depend on `@types/node` modules `http`, `https`, and `http2`
792
792
 
793
793
  ##### RawServer
794
794
  Underlying Node.js server type
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.20.0'
3
+ const VERSION = '4.21.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
package/lib/errors.js CHANGED
@@ -214,7 +214,7 @@ const codes = {
214
214
  ),
215
215
  FST_ERR_REP_ALREADY_SENT: createError(
216
216
  'FST_ERR_REP_ALREADY_SENT',
217
- 'Reply was already sent.'
217
+ 'Reply was already sent, did you forget to "return reply" in "%s" (%s)?'
218
218
  ),
219
219
  FST_ERR_REP_SENT_VALUE: createError(
220
220
  'FST_ERR_REP_SENT_VALUE',
package/lib/reply.js CHANGED
@@ -97,7 +97,7 @@ Object.defineProperties(Reply.prototype, {
97
97
 
98
98
  // We throw only if sent was overwritten from Fastify
99
99
  if (this.sent && this[kReplyHijacked]) {
100
- throw new FST_ERR_REP_ALREADY_SENT()
100
+ throw new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method)
101
101
  }
102
102
 
103
103
  this[kReplyHijacked] = true
@@ -124,7 +124,7 @@ Reply.prototype.send = function (payload) {
124
124
  }
125
125
 
126
126
  if (this.sent) {
127
- this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
127
+ this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) })
128
128
  return this
129
129
  }
130
130
 
@@ -539,6 +539,18 @@ function wrapOnSendEnd (err, request, reply, payload) {
539
539
  }
540
540
  }
541
541
 
542
+ function safeWriteHead (reply, statusCode) {
543
+ const res = reply.raw
544
+ try {
545
+ res.writeHead(statusCode, reply[kReplyHeaders])
546
+ } catch (err) {
547
+ if (err.code === 'ERR_HTTP_HEADERS_SENT') {
548
+ reply.log.warn(`Reply was already sent, did you forget to "return reply" in the "${reply.request.raw.url}" (${reply.request.raw.method}) route?`)
549
+ }
550
+ throw err
551
+ }
552
+ }
553
+
542
554
  function onSendEnd (reply, payload) {
543
555
  const res = reply.raw
544
556
  const req = reply.request
@@ -569,7 +581,7 @@ function onSendEnd (reply, payload) {
569
581
  reply[kReplyHeaders]['content-length'] = '0'
570
582
  }
571
583
 
572
- res.writeHead(statusCode, reply[kReplyHeaders])
584
+ safeWriteHead(reply, statusCode)
573
585
  sendTrailer(payload, res, reply)
574
586
  return
575
587
  }
@@ -594,7 +606,7 @@ function onSendEnd (reply, payload) {
594
606
  }
595
607
  }
596
608
 
597
- res.writeHead(statusCode, reply[kReplyHeaders])
609
+ safeWriteHead(reply, statusCode)
598
610
  // write payload first
599
611
  res.write(payload)
600
612
  // then send trailers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.20.0",
3
+ "version": "4.21.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -11,9 +11,8 @@
11
11
  "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"",
12
12
  "build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
13
13
  "coverage": "npm run unit -- --coverage-report=html",
14
- "coverage:ci": "npm run unit -- --coverage-report=html --no-browser --no-check-coverage",
14
+ "coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage",
15
15
  "coverage:ci-check-coverage": "c8 check-coverage --branches 100 --functions 100 --lines 100 --statements 100",
16
- "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"",
17
16
  "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown",
18
17
  "lint:fix": "standard --fix",
19
18
  "lint:markdown": "markdownlint-cli2",
@@ -162,10 +161,8 @@
162
161
  "joi": "^17.9.2",
163
162
  "json-schema-to-ts": "^2.9.1",
164
163
  "JSONStream": "^1.3.5",
165
- "license-checker": "^25.0.1",
166
164
  "markdownlint-cli2": "^0.8.1",
167
165
  "proxyquire": "^2.1.3",
168
- "pump": "^3.0.0",
169
166
  "self-cert": "^2.0.0",
170
167
  "send": "^0.18.0",
171
168
  "simple-get": "^4.0.1",
@@ -124,7 +124,7 @@ test('ignore the result of the promise if reply.send is called beforehand (objec
124
124
  })
125
125
 
126
126
  test('server logs an error if reply.send is called and a value is returned via async/await', t => {
127
- const lines = ['incoming request', 'request completed', 'Reply already sent']
127
+ const lines = ['incoming request', 'request completed', 'Reply was already sent, did you forget to "return reply" in "/" (GET)?']
128
128
  t.plan(lines.length + 2)
129
129
 
130
130
  const splitStream = split(JSON.parse)
@@ -339,10 +339,10 @@ test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => {
339
339
 
340
340
  test('FST_ERR_REP_ALREADY_SENT', t => {
341
341
  t.plan(5)
342
- const error = new errors.FST_ERR_REP_ALREADY_SENT()
342
+ const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET')
343
343
  t.equal(error.name, 'FastifyError')
344
344
  t.equal(error.code, 'FST_ERR_REP_ALREADY_SENT')
345
- t.equal(error.message, 'Reply was already sent.')
345
+ t.equal(error.message, 'Reply was already sent, did you forget to "return reply" in "/hello" (GET)?')
346
346
  t.equal(error.statusCode, 500)
347
347
  t.ok(error instanceof Error)
348
348
  })
@@ -1508,7 +1508,7 @@ test('should throw error when passing falsy value to reply.sent', t => {
1508
1508
  })
1509
1509
 
1510
1510
  test('should throw error when attempting to set reply.sent more than once', t => {
1511
- t.plan(4)
1511
+ t.plan(3)
1512
1512
  const fastify = Fastify()
1513
1513
 
1514
1514
  fastify.get('/', function (req, reply) {
@@ -1518,7 +1518,6 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1518
1518
  t.fail('must throw')
1519
1519
  } catch (err) {
1520
1520
  t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT')
1521
- t.equal(err.message, 'Reply was already sent.')
1522
1521
  }
1523
1522
  reply.raw.end()
1524
1523
  })
@@ -2088,3 +2087,35 @@ test('invalid response headers and custom error handler', async t => {
2088
2087
 
2089
2088
  await fastify.close()
2090
2089
  })
2090
+
2091
+ test('reply.send will intercept ERR_HTTP_HEADERS_SENT and log an error message', t => {
2092
+ t.plan(2)
2093
+
2094
+ const response = new Writable()
2095
+ Object.assign(response, {
2096
+ setHeader: () => {},
2097
+ hasHeader: () => false,
2098
+ getHeader: () => undefined,
2099
+ writeHead: () => {
2100
+ const err = new Error('kaboom')
2101
+ err.code = 'ERR_HTTP_HEADERS_SENT'
2102
+ throw err
2103
+ },
2104
+ write: () => {},
2105
+ headersSent: true
2106
+ })
2107
+
2108
+ const log = {
2109
+ warn: (msg) => {
2110
+ t.equal(msg, 'Reply was already sent, did you forget to "return reply" in the "/hello" (GET) route?')
2111
+ }
2112
+ }
2113
+
2114
+ const reply = new Reply(response, { [kRouteContext]: { onSend: null }, raw: { url: '/hello', method: 'GET' } }, log)
2115
+
2116
+ try {
2117
+ reply.send('')
2118
+ } catch (err) {
2119
+ t.equal(err.code, 'ERR_HTTP_HEADERS_SENT')
2120
+ }
2121
+ })
@@ -519,7 +519,12 @@ t.test('test log stream', (t) => {
519
519
  })
520
520
 
521
521
  t.test('reply.send logs an error if called twice in a row', async (t) => {
522
- const lines = ['incoming request', 'request completed', 'Reply already sent', 'Reply already sent']
522
+ const lines = [
523
+ 'incoming request',
524
+ 'request completed',
525
+ 'Reply was already sent, did you forget to "return reply" in "/" (GET)?',
526
+ 'Reply was already sent, did you forget to "return reply" in "/" (GET)?'
527
+ ]
523
528
  t.plan(lines.length + 1)
524
529
 
525
530
  const stream = split(JSON.parse)
@@ -7,7 +7,7 @@ const sget = require('simple-get').concat
7
7
  const fs = require('fs')
8
8
  const resolve = require('path').resolve
9
9
  const zlib = require('zlib')
10
- const pump = require('pump')
10
+ const pipeline = require('stream').pipeline
11
11
  const Fastify = require('..')
12
12
  const errors = require('http-errors')
13
13
  const JSONStream = require('JSONStream')
@@ -139,7 +139,7 @@ test('onSend hook stream', t => {
139
139
  const gzStream = zlib.createGzip()
140
140
 
141
141
  reply.header('Content-Encoding', 'gzip')
142
- pump(
142
+ pipeline(
143
143
  fs.createReadStream(resolve(process.cwd() + '/test/stream.test.js'), 'utf8'),
144
144
  gzStream,
145
145
  t.error
@@ -637,7 +637,7 @@ test('should destroy stream when response is ended', t => {
637
637
 
638
638
  fastify.get('/error', function (req, reply) {
639
639
  const reallyLongStream = new stream.Readable({
640
- read: function () {},
640
+ read: function () { },
641
641
  destroy: function (err, callback) {
642
642
  t.ok('called')
643
643
  callback(err)
@@ -763,7 +763,7 @@ test('request terminated should not crash fastify', t => {
763
763
 
764
764
  fastify.get('/', async (req, reply) => {
765
765
  const stream = new Readable()
766
- stream._read = () => {}
766
+ stream._read = () => { }
767
767
  reply.header('content-type', 'text/html; charset=utf-8')
768
768
  reply.header('transfer-encoding', 'chunked')
769
769
  stream.push('<h1>HTML</h1>')
@@ -53,6 +53,10 @@ interface ReplyPayload {
53
53
  };
54
54
  }
55
55
 
56
+ interface ReplyArrayPayload {
57
+ Reply: string[]
58
+ }
59
+
56
60
  interface ReplyUnion {
57
61
  Reply: {
58
62
  success: boolean;
@@ -70,6 +74,14 @@ interface ReplyHttpCodes {
70
74
  }
71
75
  }
72
76
 
77
+ interface InvalidReplyHttpCodes {
78
+ Reply: {
79
+ '1xx': number,
80
+ 200: string,
81
+ 999: boolean,
82
+ }
83
+ }
84
+
73
85
  const typedHandler: RouteHandler<ReplyPayload> = async (request, reply) => {
74
86
  expectType<((payload?: ReplyPayload['Reply']) => FastifyReply<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, ReplyPayload>)>(reply.send)
75
87
  expectType<((payload?: ReplyPayload['Reply']) => FastifyReply<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, ReplyPayload>)>(reply.code(100).send)
@@ -137,3 +149,16 @@ expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-4', a
137
149
  expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-5', async function handler (request, reply) {
138
150
  reply.code(401).send({ foo: 123 })
139
151
  }))
152
+ server.get<ReplyArrayPayload>('/get-generic-array-send', async function handler (request, reply) {
153
+ reply.code(200).send([''])
154
+ })
155
+ expectError(server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
156
+ reply.code(200).send('')
157
+ }))
158
+ server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
159
+ reply.code(200).send({
160
+ '1xx': 0,
161
+ 200: '',
162
+ 999: false
163
+ })
164
+ })
@@ -278,6 +278,62 @@ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
278
278
  }
279
279
  ))
280
280
 
281
+ // -------------------------------------------------------------------
282
+ // Request headers
283
+ // -------------------------------------------------------------------
284
+
285
+ // JsonSchemaToTsProvider
286
+ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
287
+ '/',
288
+ {
289
+ schema: {
290
+ headers: {
291
+ type: 'object',
292
+ properties: {
293
+ lowercase: { type: 'string' },
294
+ UPPERCASE: { type: 'number' },
295
+ camelCase: { type: 'boolean' },
296
+ 'KEBAB-case': { type: 'boolean' },
297
+ PRESERVE_OPTIONAL: { type: 'number' }
298
+ },
299
+ required: ['lowercase', 'UPPERCASE', 'camelCase', 'KEBAB-case']
300
+ } as const
301
+ }
302
+ },
303
+ (req) => {
304
+ expectType<string>(req.headers.lowercase)
305
+ expectType<string | string[] | undefined>(req.headers.UPPERCASE)
306
+ expectType<number>(req.headers.uppercase)
307
+ expectType<boolean>(req.headers.camelcase)
308
+ expectType<boolean>(req.headers['kebab-case'])
309
+ expectType<number | undefined>(req.headers.preserve_optional)
310
+ }
311
+ ))
312
+
313
+ // TypeBoxProvider
314
+ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
315
+ '/',
316
+ {
317
+ schema: {
318
+ headers: Type.Object({
319
+ lowercase: Type.String(),
320
+ UPPERCASE: Type.Number(),
321
+ camelCase: Type.Boolean(),
322
+ 'KEBAB-case': Type.Boolean(),
323
+ PRESERVE_OPTIONAL: Type.Optional(Type.Number())
324
+ })
325
+ }
326
+ },
327
+ (req) => {
328
+ expectType<string>(req.headers.lowercase)
329
+ expectType<string | string[] | undefined>(req.headers.UPPERCASE)
330
+ expectType<number>(req.headers.uppercase)
331
+ expectType<boolean>(req.headers.camelcase)
332
+ expectType<boolean>(req.headers['kebab-case'])
333
+ expectType<number | undefined>(req.headers.preserve_optional)
334
+ }
335
+ ))
336
+
281
337
  // -------------------------------------------------------------------
282
338
  // TypeBox Reply Type
283
339
  // -------------------------------------------------------------------
package/types/reply.d.ts CHANGED
@@ -6,17 +6,19 @@ import { FastifyRequest } from './request'
6
6
  import { RouteGenericInterface } from './route'
7
7
  import { FastifySchema } from './schema'
8
8
  import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider'
9
- import { CodeToReplyKey, ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils'
9
+ import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes } from './utils'
10
10
 
11
11
  export interface ReplyGenericInterface {
12
12
  Reply?: ReplyDefault;
13
13
  }
14
14
 
15
- type ReplyTypeConstrainer<RouteGenericReply, Code extends ReplyKeysToCodes<keyof RouteGenericReply>, ReplyKey = CodeToReplyKey<Code>> =
16
- Code extends keyof RouteGenericReply ? RouteGenericReply[Code] :
17
- [ReplyKey] extends [never] ? unknown :
18
- ReplyKey extends keyof RouteGenericReply ? RouteGenericReply[ReplyKey] :
19
- RouteGenericReply;
15
+ type HttpCodesReplyType = Partial<Record<HttpKeys, unknown>>
16
+
17
+ type ReplyTypeConstrainer<RouteGenericReply, Code extends ReplyKeysToCodes<keyof RouteGenericReply>> =
18
+ RouteGenericReply extends HttpCodesReplyType & Record<Exclude<keyof RouteGenericReply, keyof HttpCodesReplyType>, never> ?
19
+ Code extends keyof RouteGenericReply ? RouteGenericReply[Code] :
20
+ CodeToReplyKey<Code> extends keyof RouteGenericReply ? RouteGenericReply[CodeToReplyKey<Code>] : unknown :
21
+ RouteGenericReply;
20
22
 
21
23
  export type ResolveReplyTypeWithRouteGeneric<RouteGenericReply, Code extends ReplyKeysToCodes<keyof RouteGenericReply>,
22
24
  SchemaCompiler extends FastifySchema = FastifySchema,
@@ -1,5 +1,6 @@
1
1
  import { RouteGenericInterface } from './route'
2
2
  import { FastifySchema } from './schema'
3
+ import { RecordKeysToLowercase } from './utils'
3
4
 
4
5
  // -----------------------------------------------------------------------------------------------
5
6
  // TypeProvider
@@ -51,7 +52,7 @@ export interface FastifyRequestType<Params = unknown, Querystring = unknown, Hea
51
52
  export interface ResolveFastifyRequestType<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> extends FastifyRequestType {
52
53
  params: ResolveRequestParams<TypeProvider, SchemaCompiler, RouteGeneric>,
53
54
  query: ResolveRequestQuerystring<TypeProvider, SchemaCompiler, RouteGeneric>,
54
- headers: ResolveRequestHeaders<TypeProvider, SchemaCompiler, RouteGeneric>,
55
+ headers: RecordKeysToLowercase<ResolveRequestHeaders<TypeProvider, SchemaCompiler, RouteGeneric>>,
55
56
  body: ResolveRequestBody<TypeProvider, SchemaCompiler, RouteGeneric>
56
57
  }
57
58
 
package/types/utils.d.ts CHANGED
@@ -69,3 +69,12 @@ export type ReplyKeysToCodes<Key> = [Key] extends [never] ? number :
69
69
  export type CodeToReplyKey<Code extends number> = `${Code}` extends `${infer FirstDigit extends CodeClasses}${number}`
70
70
  ? `${FirstDigit}xx`
71
71
  : never;
72
+
73
+ export type RecordKeysToLowercase<Input> = Input extends Record<string, unknown>
74
+ ? {
75
+ [Key in keyof Input as Key extends string
76
+ ? Lowercase<Key>
77
+ : Key
78
+ ]: Input[Key];
79
+ }
80
+ : Input;