fastify 5.8.5 → 5.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.
Files changed (126) hide show
  1. package/PROJECT_CHARTER.md +1 -1
  2. package/README.md +7 -10
  3. package/SECURITY.md +1 -1
  4. package/SPONSORS.md +6 -4
  5. package/build/build-validation.js +2 -2
  6. package/docs/Guides/Database.md +0 -28
  7. package/docs/Guides/Delay-Accepting-Requests.md +1 -1
  8. package/docs/Guides/Ecosystem.md +23 -9
  9. package/docs/Guides/Getting-Started.md +2 -2
  10. package/docs/Guides/Migration-Guide-V4.md +2 -2
  11. package/docs/Guides/Migration-Guide-V5.md +1 -1
  12. package/docs/Guides/Plugins-Guide.md +1 -1
  13. package/docs/Guides/Prototype-Poisoning.md +3 -3
  14. package/docs/Guides/Serverless.md +8 -15
  15. package/docs/Guides/Style-Guide.md +1 -1
  16. package/docs/Guides/Write-Plugin.md +1 -1
  17. package/docs/Reference/Encapsulation.md +27 -26
  18. package/docs/Reference/Errors.md +12 -4
  19. package/docs/Reference/HTTP2.md +10 -10
  20. package/docs/Reference/Hooks.md +5 -5
  21. package/docs/Reference/Index.md +14 -16
  22. package/docs/Reference/LTS.md +12 -13
  23. package/docs/Reference/Lifecycle.md +9 -8
  24. package/docs/Reference/Logging.md +47 -39
  25. package/docs/Reference/Middleware.md +21 -25
  26. package/docs/Reference/Principles.md +2 -2
  27. package/docs/Reference/Reply.md +6 -1
  28. package/docs/Reference/Request.md +29 -18
  29. package/docs/Reference/Routes.md +5 -2
  30. package/docs/Reference/Server.md +138 -11
  31. package/docs/Reference/Type-Providers.md +53 -9
  32. package/docs/Reference/TypeScript.md +3 -3
  33. package/docs/Reference/Validation-and-Serialization.md +15 -2
  34. package/docs/Reference/Warnings.md +11 -6
  35. package/eslint.config.js +7 -2
  36. package/fastify.d.ts +13 -3
  37. package/fastify.js +60 -31
  38. package/lib/content-type-parser.js +2 -0
  39. package/lib/content-type.js +34 -1
  40. package/lib/context.js +0 -3
  41. package/lib/decorate.js +11 -3
  42. package/lib/error-handler.js +10 -28
  43. package/lib/error-serializer.js +59 -59
  44. package/lib/errors.js +23 -1
  45. package/lib/four-oh-four.js +22 -19
  46. package/lib/handle-request.js +12 -5
  47. package/lib/log-controller.js +169 -0
  48. package/lib/logger-factory.js +25 -4
  49. package/lib/plugin-override.js +2 -1
  50. package/lib/plugin-utils.js +5 -5
  51. package/lib/reply.js +96 -50
  52. package/lib/req-id-gen-factory.js +4 -1
  53. package/lib/request.js +16 -6
  54. package/lib/route.js +47 -41
  55. package/lib/schema-controller.js +1 -1
  56. package/lib/schemas.js +37 -30
  57. package/lib/symbols.js +4 -2
  58. package/lib/validation.js +10 -13
  59. package/lib/warnings.js +22 -4
  60. package/package.json +15 -17
  61. package/scripts/validate-ecosystem-links.js +1 -0
  62. package/test/bundler/esbuild/package.json +1 -1
  63. package/test/close-pipelining.test.js +1 -2
  64. package/test/content-type.test.js +20 -0
  65. package/test/custom-http-server.test.js +38 -0
  66. package/test/decorator-instance-properties.test.js +63 -0
  67. package/test/diagnostics-channel/async-error-handler.test.js +74 -0
  68. package/test/genReqId.test.js +24 -0
  69. package/test/hooks.test.js +94 -0
  70. package/test/http-methods/get.test.js +1 -1
  71. package/test/http2/plain.test.js +135 -0
  72. package/test/http2/secure-with-fallback.test.js +1 -1
  73. package/test/https/https.test.js +1 -2
  74. package/test/internals/errors.test.js +31 -1
  75. package/test/internals/logger.test.js +322 -0
  76. package/test/internals/plugin.test.js +3 -1
  77. package/test/internals/reply.test.js +35 -4
  78. package/test/internals/request.test.js +37 -10
  79. package/test/internals/schema-controller-perf.test.js +33 -0
  80. package/test/logger/logging.test.js +57 -1
  81. package/test/logger/options.test.js +38 -1
  82. package/test/reply-error.test.js +1 -1
  83. package/test/reply-trailers.test.js +70 -0
  84. package/test/request-media-type.test.js +105 -0
  85. package/test/route-prefix.test.js +34 -0
  86. package/test/router-options.test.js +222 -11
  87. package/test/schema-serialization.test.js +108 -0
  88. package/test/schema-validation.test.js +24 -0
  89. package/test/scripts/validate-ecosystem-links.test.js +40 -57
  90. package/test/stream.4.test.js +2 -2
  91. package/test/throw.test.js +14 -0
  92. package/test/trust-proxy.test.js +70 -30
  93. package/test/types/content-type-parser.tst.ts +70 -0
  94. package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
  95. package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
  96. package/test/types/errors.tst.ts +91 -0
  97. package/test/types/fastify.tst.ts +351 -0
  98. package/test/types/hooks.tst.ts +578 -0
  99. package/test/types/instance.tst.ts +597 -0
  100. package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
  101. package/test/types/plugin.tst.ts +96 -0
  102. package/test/types/register.tst.ts +245 -0
  103. package/test/types/reply.tst.ts +297 -0
  104. package/test/types/request.tst.ts +199 -0
  105. package/test/types/route.tst.ts +576 -0
  106. package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
  107. package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
  108. package/test/types/tsconfig.json +9 -0
  109. package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
  110. package/test/types/using.tst.ts +14 -0
  111. package/types/errors.d.ts +4 -0
  112. package/types/instance.d.ts +2 -0
  113. package/types/logger.d.ts +22 -0
  114. package/types/request.d.ts +23 -2
  115. package/test/types/content-type-parser.test-d.ts +0 -72
  116. package/test/types/errors.test-d.ts +0 -90
  117. package/test/types/fastify.test-d.ts +0 -352
  118. package/test/types/hooks.test-d.ts +0 -550
  119. package/test/types/import.ts +0 -2
  120. package/test/types/instance.test-d.ts +0 -588
  121. package/test/types/plugin.test-d.ts +0 -97
  122. package/test/types/register.test-d.ts +0 -237
  123. package/test/types/reply.test-d.ts +0 -254
  124. package/test/types/request.test-d.ts +0 -188
  125. package/test/types/route.test-d.ts +0 -553
  126. package/test/types/using.test-d.ts +0 -17
package/fastify.d.ts CHANGED
@@ -6,7 +6,7 @@ import { Socket } from 'node:net'
6
6
  import { BuildCompilerFromPool, ValidatorFactory } from '@fastify/ajv-compiler'
7
7
  import { FastifyError } from '@fastify/error'
8
8
  import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler'
9
- import { Config as FindMyWayConfig, ConstraintStrategy, HTTPVersion } from 'find-my-way'
9
+ import { ConstraintStrategy, Config as FindMyWayConfig, HTTPVersion } from 'find-my-way'
10
10
  import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request'
11
11
 
12
12
  import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, FastifyContentTypeParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction } from './types/content-type-parser'
@@ -17,6 +17,7 @@ import { FastifyInstance, FastifyListenOptions, PrintRoutesOptions } from './typ
17
17
  import {
18
18
  FastifyBaseLogger,
19
19
  FastifyChildLoggerFactory,
20
+ LogController as LogControllerClass,
20
21
  FastifyLogFn,
21
22
  FastifyLoggerInstance,
22
23
  FastifyLoggerOptions,
@@ -28,7 +29,7 @@ import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './type
28
29
  import { FastifyReply } from './types/reply'
29
30
  import { FastifyRequest, RequestGenericInterface } from './types/request'
30
31
  import { RouteGenericInterface, RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route'
31
- import { FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema'
32
+ import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, FastifySerializerCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema'
32
33
  import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/server-factory'
33
34
  import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider'
34
35
  import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './types/utils'
@@ -44,6 +45,7 @@ type Fastify = typeof fastify
44
45
 
45
46
  declare namespace fastify {
46
47
  export const errorCodes: FastifyErrorCodes
48
+ export { LogControllerClass as LogController }
47
49
 
48
50
  export type FastifyHttp2SecureOptions<
49
51
  Server extends http2.Http2SecureServer,
@@ -90,7 +92,7 @@ declare namespace fastify {
90
92
 
91
93
  type TrustProxyFunction = (address: string, hop: number) => boolean
92
94
 
93
- export type FastifyRouterOptions<RawServer extends RawServerBase> = Omit<FindMyWayConfigForServer<RawServer>, 'defaultRoute' | 'onBadUrl' | 'querystringParser'> & {
95
+ export type FastifyRouterOptions<RawServer extends RawServerBase> = Omit<FindMyWayConfigForServer<RawServer>, 'defaultRoute' | 'onBadUrl' | 'onMaxParamLength' | 'querystringParser'> & {
94
96
  defaultRoute?: (
95
97
  req: RawRequestDefaultExpression<RawServer>,
96
98
  res: RawReplyDefaultExpression<RawServer>
@@ -100,6 +102,11 @@ declare namespace fastify {
100
102
  req: RawRequestDefaultExpression<RawServer>,
101
103
  res: RawReplyDefaultExpression<RawServer>
102
104
  ) => void,
105
+ onMaxParamLength?: (
106
+ path: string,
107
+ req: RawRequestDefaultExpression<RawServer>,
108
+ res: RawReplyDefaultExpression<RawServer>
109
+ ) => void,
103
110
  querystringParser?: (str: string) => { [key: string]: unknown }
104
111
  }
105
112
 
@@ -121,7 +128,9 @@ declare namespace fastify {
121
128
  bodyLimit?: number,
122
129
  handlerTimeout?: number,
123
130
  maxParamLength?: number,
131
+ /** @deprecated Use the `logController` option with `disableRequestLogging` or `isLogDisabled` override instead. Will be removed in `fastify@6`. */
124
132
  disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean),
133
+ logController?: LogControllerClass,
125
134
  exposeHeadRoutes?: boolean,
126
135
  onProtoPoisoning?: ProtoAction,
127
136
  onConstructorPoisoning?: ConstructorAction,
@@ -132,6 +141,7 @@ declare namespace fastify {
132
141
  caseSensitive?: boolean,
133
142
  allowUnsafeRegex?: boolean,
134
143
  requestIdHeader?: string | false,
144
+ /** @deprecated Use the `logController` option with `requestIdLogLabel` instead. Will be removed in `fastify@6`. */
135
145
  requestIdLogLabel?: string;
136
146
  useSemicolonDelimiter?: boolean,
137
147
  genReqId?: (req: RawRequestDefaultExpression<RawServer>) => string,
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '5.8.5'
3
+ const VERSION = '5.10.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -33,7 +33,8 @@ const {
33
33
  kChildLoggerFactory,
34
34
  kGenReqId,
35
35
  kErrorHandlerAlreadySet,
36
- kHandlerTimeout
36
+ kHandlerTimeout,
37
+ kLogController
37
38
  } = require('./lib/symbols.js')
38
39
 
39
40
  const { createServer } = require('./lib/server')
@@ -44,7 +45,7 @@ const decorator = require('./lib/decorate')
44
45
  const ContentTypeParser = require('./lib/content-type-parser.js')
45
46
  const SchemaController = require('./lib/schema-controller')
46
47
  const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
47
- const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
48
+ const { createChildLogger, defaultChildLoggerFactory, createLogger, createLogController, LogController } = require('./lib/logger-factory')
48
49
  const pluginUtils = require('./lib/plugin-utils.js')
49
50
  const { getGenReqId, reqIdGenFactory } = require('./lib/req-id-gen-factory.js')
50
51
  const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
@@ -63,6 +64,7 @@ const { defaultInitOptions } = getSecuredInitialConfig
63
64
  const {
64
65
  FST_ERR_ASYNC_CONSTRAINT,
65
66
  FST_ERR_BAD_URL,
67
+ FST_ERR_MAX_PARAM_LENGTH,
66
68
  FST_ERR_OPTIONS_NOT_OBJ,
67
69
  FST_ERR_QSP_NOT_FN,
68
70
  FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN,
@@ -78,7 +80,7 @@ const {
78
80
  } = errorCodes
79
81
 
80
82
  const { buildErrorHandler } = require('./lib/error-handler.js')
81
- const { FSTWRN004 } = require('./lib/warnings.js')
83
+ const { FSTWRN004, FSTDEP023, FSTDEP024 } = require('./lib/warnings.js')
82
84
 
83
85
  const initChannel = diagnostics.channel('fastify.initialization')
84
86
 
@@ -89,10 +91,10 @@ function fastify (serverOptions) {
89
91
  const {
90
92
  options,
91
93
  genReqId,
92
- disableRequestLogging,
94
+ logController,
93
95
  hasLogger,
94
96
  initialConfig
95
- } = processOptions(serverOptions, defaultRoute, onBadUrl)
97
+ } = processOptions(serverOptions, defaultRoute, onBadUrl, onMaxParamLength)
96
98
 
97
99
  // Default router
98
100
  const router = buildRouting(options.routerOptions)
@@ -215,6 +217,7 @@ function fastify (serverOptions) {
215
217
  },
216
218
  // expose logger instance
217
219
  log: options.logger,
220
+ [kLogController]: logController,
218
221
  // type provider
219
222
  withTypeProvider,
220
223
  // hooks
@@ -381,8 +384,7 @@ function fastify (serverOptions) {
381
384
  hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () {
382
385
  if (fastify[kState].listening) {
383
386
  /* istanbul ignore next: Cannot test this without Node.js core support */
384
- if (forceCloseConnections === 'idle') {
385
- // Not needed in Node 19
387
+ if (forceCloseConnections === 'idle' && options.serverFactory) {
386
388
  instance.server.closeIdleConnections()
387
389
  /* istanbul ignore next: Cannot test this without Node.js core support */
388
390
  } else if (serverHasCloseAllConnections && forceCloseConnections) {
@@ -423,8 +425,8 @@ function fastify (serverOptions) {
423
425
  })
424
426
  })
425
427
 
426
- // Create bad URL context
427
- const onBadUrlContext = new Context({
428
+ // Create route event (bad URL, max param length, etc) context
429
+ const routeEventContext = new Context({
428
430
  server: fastify,
429
431
  config: {}
430
432
  })
@@ -637,16 +639,13 @@ function fastify (serverOptions) {
637
639
 
638
640
  function onBadUrl (path, req, res) {
639
641
  if (options.frameworkErrors) {
640
- const id = getGenReqId(onBadUrlContext.server, req)
641
- const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
642
+ const id = getGenReqId(routeEventContext.server, req)
643
+ const childLogger = createChildLogger(routeEventContext, options.logger, req, id)
642
644
 
643
- const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
645
+ const request = new Request(id, null, req, null, childLogger, routeEventContext)
644
646
  const reply = new Reply(res, request, childLogger)
645
647
 
646
- const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging
647
- if (resolvedDisableRequestLogging === false) {
648
- childLogger.info({ req: request }, 'incoming request')
649
- }
648
+ routeEventContext.server[kLogController].incomingRequest(request, reply)
650
649
 
651
650
  return options.frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
652
651
  }
@@ -663,21 +662,43 @@ function fastify (serverOptions) {
663
662
  res.end(body)
664
663
  }
665
664
 
665
+ function onMaxParamLength (path, req, res) {
666
+ if (options.frameworkErrors) {
667
+ const id = getGenReqId(routeEventContext.server, req)
668
+ const childLogger = createChildLogger(routeEventContext, options.logger, req, id)
669
+
670
+ const request = new Request(id, null, req, null, childLogger, routeEventContext)
671
+ const reply = new Reply(res, request, childLogger)
672
+
673
+ routeEventContext.server[kLogController].incomingRequest(request, reply)
674
+
675
+ return options.frameworkErrors(new FST_ERR_MAX_PARAM_LENGTH(path), request, reply)
676
+ }
677
+ const body = JSON.stringify({
678
+ error: 'Bad Request',
679
+ code: 'FST_ERR_MAX_PARAM_LENGTH',
680
+ message: `'${path}' is exceeding the max param length`,
681
+ statusCode: 414
682
+ })
683
+ res.writeHead(414, {
684
+ 'Content-Type': 'application/json',
685
+ 'Content-Length': Buffer.byteLength(body)
686
+ })
687
+ res.end(body)
688
+ }
689
+
666
690
  function buildAsyncConstraintCallback (isAsync, req, res) {
667
691
  if (isAsync === false) return undefined
668
692
  return function onAsyncConstraintError (err) {
669
693
  if (err) {
670
694
  if (options.frameworkErrors) {
671
- const id = getGenReqId(onBadUrlContext.server, req)
672
- const childLogger = createChildLogger(onBadUrlContext, options.logger, req, id)
695
+ const id = getGenReqId(routeEventContext.server, req)
696
+ const childLogger = createChildLogger(routeEventContext, options.logger, req, id)
673
697
 
674
- const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
698
+ const request = new Request(id, null, req, null, childLogger, routeEventContext)
675
699
  const reply = new Reply(res, request, childLogger)
676
700
 
677
- const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(req) : disableRequestLogging
678
- if (resolvedDisableRequestLogging === false) {
679
- childLogger.info({ req: request }, 'incoming request')
680
- }
701
+ routeEventContext.server[kLogController].incomingRequest(request, reply)
681
702
 
682
703
  return options.frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
683
704
  }
@@ -748,7 +769,7 @@ function fastify (serverOptions) {
748
769
  if (!options.allowErrorHandlerOverride && this[kErrorHandlerAlreadySet]) {
749
770
  throw new FST_ERR_ERROR_HANDLER_ALREADY_SET()
750
771
  } else if (this[kErrorHandlerAlreadySet]) {
751
- FSTWRN004("To disable this behavior, set 'allowErrorHandlerOverride' to false or ignore this message. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride")
772
+ FSTWRN004()
752
773
  }
753
774
 
754
775
  this[kErrorHandlerAlreadySet] = true
@@ -821,7 +842,7 @@ function fastify (serverOptions) {
821
842
  }
822
843
  }
823
844
 
824
- function processOptions (options, defaultRoute, onBadUrl) {
845
+ function processOptions (options, defaultRoute, onBadUrl, onMaxParamLength) {
825
846
  // Options validations
826
847
  if (options && typeof options !== 'object') {
827
848
  throw new FST_ERR_OPTIONS_NOT_OBJ()
@@ -848,9 +869,13 @@ function processOptions (options, defaultRoute, onBadUrl) {
848
869
 
849
870
  const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
850
871
  const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
851
- const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
872
+ if (options.requestIdLogLabel !== undefined) {
873
+ FSTDEP024()
874
+ }
852
875
  options.bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
853
- const disableRequestLogging = options.disableRequestLogging || false
876
+ if (options.disableRequestLogging !== undefined) {
877
+ FSTDEP023()
878
+ }
854
879
 
855
880
  const ajvOptions = Object.assign({
856
881
  customOptions: {},
@@ -866,6 +891,10 @@ function processOptions (options, defaultRoute, onBadUrl) {
866
891
 
867
892
  const { logger, hasLogger } = createLogger(options)
868
893
 
894
+ // the internal logger uses the input logger to execute the logging. This allows the user
895
+ // to customize every internal log line
896
+ const logController = createLogController(options)
897
+
869
898
  // Update the options with the fixed values
870
899
  options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
871
900
  options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
@@ -873,8 +902,6 @@ function processOptions (options, defaultRoute, onBadUrl) {
873
902
  options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
874
903
  options.logger = logger
875
904
  options.requestIdHeader = requestIdHeader
876
- options.requestIdLogLabel = requestIdLogLabel
877
- options.disableRequestLogging = disableRequestLogging
878
905
  options.ajv = ajvOptions
879
906
  options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
880
907
  options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
@@ -890,6 +917,7 @@ function processOptions (options, defaultRoute, onBadUrl) {
890
917
  options.routerOptions = buildRouterOptions(options, {
891
918
  defaultRoute,
892
919
  onBadUrl,
920
+ onMaxParamLength,
893
921
  ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash,
894
922
  ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes,
895
923
  maxParamLength: defaultInitOptions.maxParamLength,
@@ -901,7 +929,7 @@ function processOptions (options, defaultRoute, onBadUrl) {
901
929
  return {
902
930
  options,
903
931
  genReqId,
904
- disableRequestLogging,
932
+ logController,
905
933
  hasLogger,
906
934
  initialConfig
907
935
  }
@@ -981,5 +1009,6 @@ function validateSchemaErrorFormatter (schemaErrorFormatter) {
981
1009
  */
982
1010
  module.exports = fastify
983
1011
  module.exports.errorCodes = errorCodes
1012
+ module.exports.LogController = LogController
984
1013
  module.exports.fastify = fastify
985
1014
  module.exports.default = fastify
@@ -118,6 +118,8 @@ ContentTypeParser.prototype.existingParser = function (contentType) {
118
118
 
119
119
  ContentTypeParser.prototype.getParser = function (contentType) {
120
120
  if (typeof contentType === 'string') {
121
+ const cached = this.cache.get(contentType)
122
+ if (cached !== undefined) return cached
121
123
  contentType = new ContentType(contentType)
122
124
  }
123
125
  const ct = contentType.toString()
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { LruMap: Lru } = require('toad-cache')
4
+
3
5
  /**
4
6
  * keyValuePairsReg is used to split the parameters list into associated
5
7
  * key value pairings. The leading `(?:^|;)\s*` anchor ensures the regex
@@ -35,6 +37,12 @@ const typeNameReg = /^[\w!#$%&'*+.^`|~-]+$/
35
37
  */
36
38
  const subtypeNameReg = /^[\w!#$%&'*+.^`|~-]+\s*$/
37
39
 
40
+ /**
41
+ * Content-Type internal shared cache
42
+ * @type {Lru<ContentType>}
43
+ */
44
+ const cache = new Lru(100)
45
+
38
46
  /**
39
47
  * ContentType parses and represents the value of the content-type header.
40
48
  *
@@ -49,6 +57,27 @@ class ContentType {
49
57
  #parameters = new Map()
50
58
  #string
51
59
 
60
+ /**
61
+ * The shared cache of ContentType instances. The cache is used to avoid
62
+ * creating multiple instances of ContentType for the same header value.
63
+ * @type {Lru<ContentType>}
64
+ */
65
+ static get cache () { return cache }
66
+
67
+ /**
68
+ * Create a ContentType instance from a header value. If the value has been
69
+ * previously parsed, the cached instance will be returned.
70
+ * @param {string} headerValue
71
+ * @returns {ContentType | undefined}
72
+ */
73
+ static from (headerValue) {
74
+ let contentType = cache.get(headerValue)
75
+ if (contentType !== undefined) return contentType
76
+ contentType = new ContentType(headerValue)
77
+ cache.set(headerValue, contentType)
78
+ return contentType
79
+ }
80
+
52
81
  constructor (headerValue) {
53
82
  if (headerValue == null || headerValue === '' || headerValue === 'undefined') {
54
83
  return
@@ -106,7 +135,11 @@ class ContentType {
106
135
 
107
136
  let matches = keyValuePairsReg.exec(paramsList)
108
137
  while (matches) {
109
- const key = matches[1]
138
+ // https://httpwg.org/specs/rfc9110.html#parameter
139
+ // Parameter names are case-insensitive.
140
+ const key = matches[1].toLowerCase()
141
+ // Parameter values might or might not be case-sensitive,
142
+ // depending on the semantics of the parameter name.
110
143
  const value = matches[2]
111
144
  if (value[0] === '"') {
112
145
  if (value.at(-1) !== '"') {
package/lib/context.js CHANGED
@@ -6,7 +6,6 @@ const {
6
6
  kSchemaErrorFormatter,
7
7
  kErrorHandler,
8
8
  kChildLoggerFactory,
9
- kOptions,
10
9
  kReply,
11
10
  kRequest,
12
11
  kBodyLimit,
@@ -24,7 +23,6 @@ function Context ({
24
23
  schema,
25
24
  handler,
26
25
  config,
27
- requestIdLogLabel,
28
26
  childLoggerFactory,
29
27
  errorHandler,
30
28
  bodyLimit,
@@ -56,7 +54,6 @@ function Context ({
56
54
  this.onRequestAbort = null
57
55
  this.config = config
58
56
  this.errorHandler = errorHandler || server[kErrorHandler]
59
- this.requestIdLogLabel = requestIdLogLabel || server[kOptions].requestIdLogLabel
60
57
  this.childLoggerFactory = childLoggerFactory || server[kChildLoggerFactory]
61
58
  this._middie = null
62
59
  this._parserOptions = {
package/lib/decorate.js CHANGED
@@ -47,7 +47,7 @@ function getInstanceDecorator (name) {
47
47
 
48
48
  function decorateConstructor (konstructor, name, fn, dependencies) {
49
49
  const instance = konstructor.prototype
50
- if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) {
50
+ if (Object.hasOwn(instance, name) || hasKey(konstructor, name) || hasInstanceProperty(konstructor, name)) {
51
51
  throw new FST_ERR_DEC_ALREADY_PRESENT(name)
52
52
  }
53
53
 
@@ -87,19 +87,27 @@ function checkExistence (instance, name) {
87
87
  }
88
88
 
89
89
  function hasKey (fn, name) {
90
- if (fn.props) {
91
- return fn.props.find(({ key }) => key === name)
90
+ return fn.props ? fn.props.some(p => p.key === name) : false
91
+ }
92
+
93
+ function hasInstanceProperty (konstructor, name) {
94
+ let k = konstructor
95
+ while (k) {
96
+ if (k.instanceProperties && k.instanceProperties.has(name)) return true
97
+ k = k.parent
92
98
  }
93
99
  return false
94
100
  }
95
101
 
96
102
  function checkRequestExistence (name) {
97
103
  if (name && hasKey(this[kRequest], name)) return true
104
+ if (name && hasInstanceProperty(this[kRequest], name)) return true
98
105
  return checkExistence(this[kRequest].prototype, name)
99
106
  }
100
107
 
101
108
  function checkReplyExistence (name) {
102
109
  if (name && hasKey(this[kReply], name)) return true
110
+ if (name && hasInstanceProperty(this[kReply], name)) return true
103
111
  return checkExistence(this[kReply].prototype, name)
104
112
  }
105
113
 
@@ -8,7 +8,8 @@ const {
8
8
  kReplyNextErrorHandler,
9
9
  kReplyIsRunningOnErrorHook,
10
10
  kRouteContext,
11
- kDisableRequestLogging
11
+ kLogController,
12
+ kDiagnosticsStore
12
13
  } = require('./symbols.js')
13
14
 
14
15
  const {
@@ -19,7 +20,6 @@ const {
19
20
  const { getSchemaSerializer } = require('./schemas')
20
21
 
21
22
  const serializeError = require('./error-serializer')
22
-
23
23
  const rootErrorHandler = {
24
24
  func: defaultErrorHandler,
25
25
  toJSON () {
@@ -36,12 +36,7 @@ function handleError (reply, error, cb) {
36
36
  try {
37
37
  reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
38
38
  } catch (error) {
39
- if (!reply.log[kDisableRequestLogging]) {
40
- reply.log.warn(
41
- { req: reply.request, res: reply, err: error },
42
- error?.message
43
- )
44
- }
39
+ reply.server[kLogController].writeHeadError(error, reply.request, reply)
45
40
  reply.raw.writeHead(reply.raw.statusCode)
46
41
  }
47
42
  reply.raw.end(payload)
@@ -69,7 +64,8 @@ function handleError (reply, error, cb) {
69
64
  const result = func(error, reply.request, reply)
70
65
  if (result !== undefined) {
71
66
  if (result !== null && typeof result.then === 'function') {
72
- wrapThenable(result, reply)
67
+ const store = reply[kDiagnosticsStore] || null
68
+ wrapThenable(result, reply, store)
73
69
  } else {
74
70
  reply.send(result)
75
71
  }
@@ -82,21 +78,7 @@ function handleError (reply, error, cb) {
82
78
  function defaultErrorHandler (error, request, reply) {
83
79
  setErrorHeaders(error, reply)
84
80
  setErrorStatusCode(reply, error)
85
- if (reply.statusCode < 500) {
86
- if (!reply.log[kDisableRequestLogging]) {
87
- reply.log.info(
88
- { res: reply, err: error },
89
- error?.message
90
- )
91
- }
92
- } else {
93
- if (!reply.log[kDisableRequestLogging]) {
94
- reply.log.error(
95
- { req: request, res: reply, err: error },
96
- error?.message
97
- )
98
- }
99
- }
81
+ request.server[kLogController].defaultErrorLog(error, request, reply)
100
82
  reply.send(error)
101
83
  }
102
84
 
@@ -122,10 +104,10 @@ function fallbackErrorHandler (error, reply, cb) {
122
104
  }))
123
105
  }
124
106
  } catch (err) {
125
- if (!reply.log[kDisableRequestLogging]) {
126
- // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
127
- reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
128
- }
107
+ // error is always FST_ERR_SCH_SERIALIZATION_BUILD
108
+ // because this is called from route/compileSchemasForSerialization
109
+ reply.server[kLogController].serializerError(err, reply.request, reply, { statusCode: res.statusCode })
110
+
129
111
  reply.code(500)
130
112
  payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message))
131
113
  }
@@ -40,93 +40,93 @@ const asInteger = serializer.asInteger.bind(serializer)
40
40
  const JSON_STR_NULL = 'null'
41
41
 
42
42
 
43
-
44
- // #
45
- function anonymous0 (input) {
46
- const obj = (input && typeof input.toJSON === 'function')
43
+ // #
44
+ function anonymous0 (input) {
45
+ const obj = (input && typeof input.toJSON === 'function')
47
46
  ? input.toJSON()
48
47
  : input
49
48
 
50
- if (obj === null) return JSON_STR_EMPTY_OBJECT
49
+ if (obj === null) return JSON_STR_EMPTY_OBJECT
50
+ let json = ''
51
51
 
52
- let value
53
- let json = JSON_STR_BEGIN_OBJECT
54
- let addComma = false
52
+ json += JSON_STR_BEGIN_OBJECT
53
+ let addComma_0 = false
55
54
 
56
- value = obj["statusCode"]
57
- if (value !== undefined) {
58
- !addComma && (addComma = true) || (json += JSON_STR_COMMA)
59
- json += "\"statusCode\":"
60
- json += asNumber(value)
61
- }
55
+ const value_statusCode_1 = obj["statusCode"]
56
+ if (value_statusCode_1 !== undefined) {
57
+ !addComma_0 && (addComma_0 = true) || (json += JSON_STR_COMMA)
58
+ json += "\"statusCode\":"
59
+ json += asNumber(value_statusCode_1)
60
+ }
62
61
 
63
- value = obj["code"]
64
- if (value !== undefined) {
65
- !addComma && (addComma = true) || (json += JSON_STR_COMMA)
66
- json += "\"code\":"
67
-
68
- if (typeof value !== 'string') {
69
- if (value === null) {
62
+ const value_code_2 = obj["code"]
63
+ if (value_code_2 !== undefined) {
64
+ !addComma_0 && (addComma_0 = true) || (json += JSON_STR_COMMA)
65
+ json += "\"code\":"
66
+
67
+ if (typeof value_code_2 !== 'string') {
68
+ if (value_code_2 === null) {
70
69
  json += JSON_STR_EMPTY_STRING
71
- } else if (value instanceof Date) {
72
- json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
73
- } else if (value instanceof RegExp) {
74
- json += asString(value.source)
70
+ } else if (value_code_2 instanceof Date) {
71
+ json += JSON_STR_QUOTE + value_code_2.toISOString() + JSON_STR_QUOTE
72
+ } else if (value_code_2 instanceof RegExp) {
73
+ json += asString(value_code_2.source)
75
74
  } else {
76
- json += asString(value.toString())
75
+ json += asString(value_code_2.toString())
77
76
  }
78
77
  } else {
79
- json += asString(value)
78
+ json += asString(value_code_2)
80
79
  }
81
80
 
82
- }
81
+ }
83
82
 
84
- value = obj["error"]
85
- if (value !== undefined) {
86
- !addComma && (addComma = true) || (json += JSON_STR_COMMA)
87
- json += "\"error\":"
88
-
89
- if (typeof value !== 'string') {
90
- if (value === null) {
83
+ const value_error_3 = obj["error"]
84
+ if (value_error_3 !== undefined) {
85
+ !addComma_0 && (addComma_0 = true) || (json += JSON_STR_COMMA)
86
+ json += "\"error\":"
87
+
88
+ if (typeof value_error_3 !== 'string') {
89
+ if (value_error_3 === null) {
91
90
  json += JSON_STR_EMPTY_STRING
92
- } else if (value instanceof Date) {
93
- json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
94
- } else if (value instanceof RegExp) {
95
- json += asString(value.source)
91
+ } else if (value_error_3 instanceof Date) {
92
+ json += JSON_STR_QUOTE + value_error_3.toISOString() + JSON_STR_QUOTE
93
+ } else if (value_error_3 instanceof RegExp) {
94
+ json += asString(value_error_3.source)
96
95
  } else {
97
- json += asString(value.toString())
96
+ json += asString(value_error_3.toString())
98
97
  }
99
98
  } else {
100
- json += asString(value)
99
+ json += asString(value_error_3)
101
100
  }
102
101
 
103
- }
102
+ }
104
103
 
105
- value = obj["message"]
106
- if (value !== undefined) {
107
- !addComma && (addComma = true) || (json += JSON_STR_COMMA)
108
- json += "\"message\":"
109
-
110
- if (typeof value !== 'string') {
111
- if (value === null) {
104
+ const value_message_4 = obj["message"]
105
+ if (value_message_4 !== undefined) {
106
+ !addComma_0 && (addComma_0 = true) || (json += JSON_STR_COMMA)
107
+ json += "\"message\":"
108
+
109
+ if (typeof value_message_4 !== 'string') {
110
+ if (value_message_4 === null) {
112
111
  json += JSON_STR_EMPTY_STRING
113
- } else if (value instanceof Date) {
114
- json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
115
- } else if (value instanceof RegExp) {
116
- json += asString(value.source)
112
+ } else if (value_message_4 instanceof Date) {
113
+ json += JSON_STR_QUOTE + value_message_4.toISOString() + JSON_STR_QUOTE
114
+ } else if (value_message_4 instanceof RegExp) {
115
+ json += asString(value_message_4.source)
117
116
  } else {
118
- json += asString(value.toString())
117
+ json += asString(value_message_4.toString())
119
118
  }
120
119
  } else {
121
- json += asString(value)
120
+ json += asString(value_message_4)
122
121
  }
123
122
 
124
- }
123
+ }
125
124
 
126
- return json + JSON_STR_END_OBJECT
127
-
128
- }
125
+ json += JSON_STR_END_OBJECT
129
126
 
127
+ return json
128
+ }
129
+
130
130
  const main = anonymous0
131
131
  return main
132
132