fastify 4.9.2 → 4.10.0

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