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/lib/errors.js CHANGED
@@ -243,6 +243,13 @@ const codes = {
243
243
  TypeError
244
244
  ),
245
245
 
246
+ FST_ERR_LOG_INVALID_LOG_CONTROLLER: createError(
247
+ 'FST_ERR_LOG_INVALID_LOG_CONTROLLER',
248
+ "logController option must be an instance of LogController, got '%s'.",
249
+ 500,
250
+ TypeError
251
+ ),
252
+
246
253
  /**
247
254
  * reply
248
255
  */
@@ -364,6 +371,12 @@ const codes = {
364
371
  400,
365
372
  URIError
366
373
  ),
374
+ FST_ERR_MAX_PARAM_LENGTH: createError(
375
+ 'FST_ERR_MAX_PARAM_LENGTH',
376
+ "'%s' is exceeding the max param length",
377
+ 414,
378
+ URIError
379
+ ),
367
380
  FST_ERR_ASYNC_CONSTRAINT: createError(
368
381
  'FST_ERR_ASYNC_CONSTRAINT',
369
382
  'Unexpected error from async constraint',
@@ -408,6 +421,12 @@ const codes = {
408
421
  '%s method is not supported.',
409
422
  500
410
423
  ),
424
+ FST_ERR_ROUTE_LOG_LEVEL_INVALID: createError(
425
+ 'FST_ERR_ROUTE_LOG_LEVEL_INVALID',
426
+ "Log level for '%s:%s' route must be a valid logger level. Received: '%s'",
427
+ 500,
428
+ TypeError
429
+ ),
411
430
  FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED: createError(
412
431
  'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED',
413
432
  'Body validation schema for %s:%s route is not supported!',
@@ -470,7 +489,10 @@ const codes = {
470
489
  500,
471
490
  TypeError
472
491
  ),
473
-
492
+ FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED: createError(
493
+ 'FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED',
494
+ "The dependency '%s' of plugin '%s' is not registered"
495
+ ),
474
496
  /**
475
497
  * Avvio Errors
476
498
  */
@@ -11,7 +11,8 @@ const {
11
11
  kFourOhFourLevelInstance,
12
12
  kFourOhFourContext,
13
13
  kHooks,
14
- kErrorHandler
14
+ kErrorHandler,
15
+ kLogController
15
16
  } = require('./symbols.js')
16
17
  const { lifecycleHooks } = require('./hooks')
17
18
  const { buildErrorHandler } = require('./error-handler.js')
@@ -29,11 +30,15 @@ const { getGenReqId } = require('./req-id-gen-factory.js')
29
30
  * kFourOhFourContext: the context in the reply object where the handler will be executed
30
31
  */
31
32
  function fourOhFour (options) {
32
- const { logger, disableRequestLogging } = options
33
+ const { logger } = options
33
34
 
34
35
  // 404 router, used for handling encapsulated 404 handlers
35
- const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack })
36
- let _onBadUrlHandler = null
36
+ const router = FindMyWay({
37
+ onBadUrl: createRouteEventHandler(),
38
+ onMaxParamLength: createRouteEventHandler(),
39
+ defaultRoute: fourOhFourFallBack
40
+ })
41
+ let _routeEventHandler = null
37
42
 
38
43
  return { router, setNotFoundHandler, setContext, arrange404 }
39
44
 
@@ -43,32 +48,29 @@ function fourOhFour (options) {
43
48
  instance[kCanSetNotFoundHandler] = true
44
49
  // we need to bind instance for the context
45
50
  router.onBadUrl = router.onBadUrl.bind(instance)
51
+ router.onMaxParamLength = router.onMaxParamLength.bind(instance)
46
52
  router.defaultRoute = router.defaultRoute.bind(instance)
47
53
  }
48
54
 
49
55
  function basic404 (request, reply) {
56
+ request.server[kLogController].routeNotFound(request, reply)
50
57
  const { url, method } = request.raw
51
- const message = `Route ${method}:${url} not found`
52
- const resolvedDisableRequestLogging = typeof disableRequestLogging === 'function' ? disableRequestLogging(request.raw) : disableRequestLogging
53
- if (!resolvedDisableRequestLogging) {
54
- request.log.info(message)
55
- }
56
58
  reply.code(404).send({
57
- message,
59
+ message: `Route ${method}:${url} not found`,
58
60
  error: 'Not Found',
59
61
  statusCode: 404
60
62
  })
61
63
  }
62
64
 
63
- function createOnBadUrl () {
64
- return function onBadUrl (path, req, res) {
65
+ function createRouteEventHandler () {
66
+ return function onRouteEvent (path, req, res) {
65
67
  const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
66
68
  const id = getGenReqId(fourOhFourContext.server, req)
67
69
  const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
68
70
  const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
69
71
  const reply = new Reply(res, request, childLogger)
70
72
 
71
- _onBadUrlHandler(request, reply)
73
+ _routeEventHandler(request, reply)
72
74
  }
73
75
  }
74
76
 
@@ -121,12 +123,12 @@ function fourOhFour (options) {
121
123
  if (handler) {
122
124
  this[kFourOhFourLevelInstance][kCanSetNotFoundHandler] = false
123
125
  handler = handler.bind(this)
124
- // update onBadUrl handler
125
- _onBadUrlHandler = handler
126
+ // update route event handler
127
+ _routeEventHandler = handler
126
128
  } else {
127
129
  handler = basic404
128
- // update onBadUrl handler
129
- _onBadUrlHandler = basic404
130
+ // update route event handler
131
+ _routeEventHandler = basic404
130
132
  }
131
133
 
132
134
  this.after((notHandledErr, done) => {
@@ -176,11 +178,12 @@ function fourOhFour (options) {
176
178
  const id = getGenReqId(fourOhFourContext.server, req)
177
179
  const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
178
180
 
179
- childLogger.info({ req }, 'incoming request')
180
-
181
181
  const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
182
182
  const reply = new Reply(res, request, childLogger)
183
183
 
184
+ fourOhFourContext.server[kLogController].incomingRequest(request, reply)
185
+
186
+ // Since this could be a bug, we log it without checking if the logging is disabled
184
187
  request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
185
188
  request.log.warn(router.prettyPrint())
186
189
  reply.code(404).send(new FST_ERR_NOT_FOUND())
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const diagnostics = require('node:diagnostics_channel')
4
- const ContentType = require('./content-type')
5
4
  const wrapThenable = require('./wrap-thenable')
6
5
  const { validate: validateSchema } = require('./validation')
7
6
  const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
@@ -11,8 +10,11 @@ const {
11
10
  kReplyIsError,
12
11
  kRouteContext,
13
12
  kFourOhFourContext,
14
- kSupportedHTTPMethods
13
+ kSupportedHTTPMethods,
14
+ kRequestContentType,
15
+ kDiagnosticsStore
15
16
  } = require('./symbols')
17
+ const ContentType = require('./content-type')
16
18
 
17
19
  const channels = diagnostics.tracingChannel('fastify.request.handler')
18
20
 
@@ -51,13 +53,17 @@ function handleRequest (err, request, reply) {
51
53
  return
52
54
  }
53
55
 
54
- const contentType = new ContentType(ctHeader)
55
- if (contentType.isValid === false) {
56
+ // Conditional assignment to avoid creating a new ContentType instance
57
+ // It can be assigned when accessing the .mediaType in hooks
58
+ if (!request[kRequestContentType]) {
59
+ request[kRequestContentType] = ContentType.from(ctHeader)
60
+ }
61
+ if (request[kRequestContentType].isValid === false) {
56
62
  reply[kReplyIsError] = true
57
63
  reply.status(415).send(new FST_ERR_CTP_INVALID_MEDIA_TYPE())
58
64
  return
59
65
  }
60
- request[kRouteContext].contentTypeParser.run(contentType.toString(), handler, request, reply)
66
+ request[kRouteContext].contentTypeParser.run(request[kRequestContentType].toString(), handler, request, reply)
61
67
  return
62
68
  }
63
69
 
@@ -142,6 +148,7 @@ function preHandlerCallback (err, request, reply) {
142
148
  method: context.config.method
143
149
  }
144
150
  }
151
+ reply[kDiagnosticsStore] = store
145
152
  channels.start.runStores(store, preHandlerCallbackInner, undefined, err, request, reply, store)
146
153
  }
147
154
  }
@@ -0,0 +1,169 @@
1
+ 'use strict'
2
+
3
+ const { defaultInitOptions } = require('./initial-config-validation')
4
+
5
+ /**
6
+ * Internal logger layer that wraps Fastify's log calls with
7
+ * request-lifecycle awareness (disable-check, level selection).
8
+ * Keeps the surface intentionally small so hot paths stay fast.
9
+ *
10
+ * Users can extend this class to customize internal log lines.
11
+ */
12
+ class LogController {
13
+ /**
14
+ * @param {object} [options]
15
+ * @param {boolean | ((req: object) => boolean)} [options.disableRequestLogging=false]
16
+ * When `true` (or a function returning `true`), per-request log lines
17
+ * (incoming, completed, errors) are suppressed.
18
+ * @param {string} [options.requestIdLogLabel='reqId']
19
+ * The label used for the request identifier when logging the request.
20
+ */
21
+ constructor (options) {
22
+ const opts = options || {}
23
+ this.disableRequestLogging = opts.disableRequestLogging || defaultInitOptions.disableRequestLogging
24
+ this.isDisableRequestLoggingFunction = typeof this.disableRequestLogging === 'function'
25
+ this.requestIdLogLabel = opts.requestIdLogLabel || defaultInitOptions.requestIdLogLabel
26
+ }
27
+
28
+ /**
29
+ * Checks whether request logging is disabled for the given request.
30
+ *
31
+ * @param {object} req Raw or Fastify request object.
32
+ * @returns {boolean} `true` when logging should be skipped.
33
+ */
34
+ isLogDisabled (req) {
35
+ return this.isDisableRequestLoggingFunction
36
+ ? this.disableRequestLogging(req)
37
+ : this.disableRequestLogging
38
+ }
39
+
40
+ /**
41
+ * Logs an incoming request at `info` level.
42
+ *
43
+ * @param {object} request Fastify request object.
44
+ * @param {object} reply Fastify reply object.
45
+ * @param {object} [metadata] Extra contextual data (unused).
46
+ */
47
+ incomingRequest (request, reply, metadata) {
48
+ if (this.isLogDisabled(request)) { return }
49
+
50
+ request.log.info({ req: request }, 'incoming request')
51
+ }
52
+
53
+ /**
54
+ * Logs the outcome of a completed request.
55
+ * Uses `error` level when an error is present, `info` otherwise.
56
+ *
57
+ * @param {Error | null} error Error that occurred during the response, if any.
58
+ * @param {object} request Fastify request object.
59
+ * @param {object} reply Fastify reply object.
60
+ * @param {object} [metadata] Extra contextual data (unused).
61
+ */
62
+ requestCompleted (error, request, reply, metadata) {
63
+ if (this.isLogDisabled(request)) { return }
64
+
65
+ if (error) {
66
+ reply.log.error({ res: reply, err: error, responseTime: reply.elapsedTime }, 'request errored')
67
+ } else {
68
+ reply.log.info({ res: reply, responseTime: reply.elapsedTime }, 'request completed')
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Logs an error handled by the default error handler.
74
+ * Uses `error` for 5xx status codes, `info` for 4xx.
75
+ *
76
+ * @param {Error} error The error being handled.
77
+ * @param {object} request Fastify request object.
78
+ * @param {object} reply Fastify reply object (must have `statusCode`).
79
+ * @param {object} [metadata] Extra contextual data (unused).
80
+ */
81
+ defaultErrorLog (error, request, reply, metadata) {
82
+ if (this.isLogDisabled(request)) { return }
83
+
84
+ if (reply.statusCode >= 500) {
85
+ reply.log.error({ req: request, res: reply, err: error }, error?.message)
86
+ } else {
87
+ reply.log.info({ res: reply, err: error }, error?.message)
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Logs stream-level errors that occur after headers have been sent.
93
+ * Premature closes are logged at `info`; other errors at `warn`.
94
+ *
95
+ * @param {Error} error The stream error.
96
+ * @param {object} request Fastify request object.
97
+ * @param {object} reply Fastify reply object.
98
+ * @param {object} [metadata] Extra contextual data (unused).
99
+ */
100
+ streamError (error, request, reply, metadata) {
101
+ if (this.isLogDisabled(request)) { return }
102
+
103
+ if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
104
+ reply.log.info({ res: reply }, 'stream closed prematurely')
105
+ } else {
106
+ reply.log.warn({ err: error }, 'response terminated with an error with headers already sent')
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Logs a "route not found" message at `info` level.
112
+ * Used by the default 404 handler.
113
+ *
114
+ * @param {object} request Fastify request object.
115
+ * @param {object} reply Fastify reply object.
116
+ * @param {object} [metadata] Extra contextual data (unused).
117
+ */
118
+ routeNotFound (request, reply, metadata) {
119
+ if (this.isLogDisabled(request)) { return }
120
+
121
+ const { url, method } = request.raw
122
+ request.log.info(`Route ${method}:${url} not found`)
123
+ }
124
+
125
+ /**
126
+ * Logs a warning when writeHead fails during error handling.
127
+ *
128
+ * @param {Error} error The error thrown by writeHead.
129
+ * @param {object} request Fastify request object.
130
+ * @param {object} reply Fastify reply object.
131
+ * @param {object} [metadata] Extra contextual data (unused).
132
+ */
133
+ writeHeadError (error, request, reply, metadata) {
134
+ if (this.isLogDisabled(request)) { return }
135
+
136
+ reply.log.warn(
137
+ { req: request, res: reply, err: error },
138
+ error?.message
139
+ )
140
+ }
141
+
142
+ /**
143
+ * Logs an error when the serializer for a given status code fails.
144
+ *
145
+ * @param {Error} error The serialization error.
146
+ * @param {object} request Fastify request object.
147
+ * @param {object} reply Fastify reply object.
148
+ * @param {object} metadata Extra contextual data.
149
+ * @param {number} metadata.statusCode The status code that triggered the serializer.
150
+ */
151
+ serializerError (error, request, reply, metadata) {
152
+ if (this.isLogDisabled(request)) { return }
153
+
154
+ reply.log.error({ err: error, statusCode: metadata.statusCode }, 'The serializer for the given status code failed')
155
+ }
156
+
157
+ /**
158
+ * Logs a 503 Service Unavailable when the server is closing.
159
+ * Always emitted (not gated by `disableRequestLogging`).
160
+ *
161
+ * @param {import('../fastify').FastifyBaseLogger} logger
162
+ * @param {import('../fastify').FastifyInstance} server
163
+ */
164
+ serviceUnavailable (logger, server) {
165
+ logger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing')
166
+ }
167
+ }
168
+
169
+ module.exports = { LogController }
@@ -1,11 +1,18 @@
1
1
  'use strict'
2
2
 
3
+ const { performance } = require('node:perf_hooks')
4
+
3
5
  const {
4
6
  FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
5
7
  FST_ERR_LOG_INVALID_LOGGER_CONFIG,
6
8
  FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
7
- FST_ERR_LOG_INVALID_LOGGER
9
+ FST_ERR_LOG_INVALID_LOGGER,
10
+ FST_ERR_LOG_INVALID_LOG_CONTROLLER
8
11
  } = require('./errors')
12
+ const {
13
+ kLogController
14
+ } = require('./symbols.js')
15
+ const { LogController } = require('./log-controller.js')
9
16
 
10
17
  /**
11
18
  * Utility for creating a child logger with the appropriate bindings, logger factory
@@ -21,7 +28,7 @@ const {
21
28
  */
22
29
  function createChildLogger (context, logger, req, reqId, loggerOpts) {
23
30
  const loggerBindings = {
24
- [context.requestIdLogLabel]: reqId
31
+ [context.server[kLogController].requestIdLogLabel]: reqId
25
32
  }
26
33
  const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
27
34
 
@@ -122,15 +129,29 @@ function createLogger (options) {
122
129
  return { logger, hasLogger: true }
123
130
  }
124
131
 
132
+ function createLogController (options) {
133
+ const userController = options.logController
134
+ if (!userController) {
135
+ return new LogController(options)
136
+ }
137
+
138
+ if (userController instanceof LogController) {
139
+ return userController
140
+ }
141
+
142
+ throw new FST_ERR_LOG_INVALID_LOG_CONTROLLER(typeof userController)
143
+ }
144
+
125
145
  function now () {
126
- const ts = process.hrtime()
127
- return (ts[0] * 1e3) + (ts[1] / 1e6)
146
+ return performance.now()
128
147
  }
129
148
 
130
149
  module.exports = {
131
150
  createChildLogger,
132
151
  defaultChildLoggerFactory,
152
+ createLogController,
133
153
  createLogger,
154
+ LogController,
134
155
  validateLogger,
135
156
  now
136
157
  }
@@ -82,7 +82,8 @@ function buildRoutePrefix (instancePrefix, pluginPrefix) {
82
82
  if (instancePrefix.endsWith('/') && pluginPrefix[0] === '/') {
83
83
  // Remove the extra '/' to avoid: '/first//second'
84
84
  pluginPrefix = pluginPrefix.slice(1)
85
- } else if (pluginPrefix[0] !== '/') {
85
+ } else if (pluginPrefix[0] !== '/' && !instancePrefix.endsWith('/')) {
86
+ // Add the missing '/' to avoid: '/firstsecond'
86
87
  pluginPrefix = '/' + pluginPrefix
87
88
  }
88
89
 
@@ -10,7 +10,8 @@ const { exist, existReply, existRequest } = require('./decorate.js')
10
10
  const {
11
11
  FST_ERR_PLUGIN_VERSION_MISMATCH,
12
12
  FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,
13
- FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
13
+ FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER,
14
+ FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED
14
15
  } = require('./errors.js')
15
16
 
16
17
  const rcRegex = /-(?:rc|pre|alpha).+$/u
@@ -70,10 +71,9 @@ function checkDependencies (fn) {
70
71
  assert(Array.isArray(dependencies), 'The dependencies should be an array of strings')
71
72
 
72
73
  dependencies.forEach(dependency => {
73
- assert(
74
- this[kRegisteredPlugins].indexOf(dependency) > -1,
75
- `The dependency '${dependency}' of plugin '${meta.name}' is not registered`
76
- )
74
+ if (!this[kRegisteredPlugins].includes(dependency)) {
75
+ throw new FST_ERR_PLUGIN_DEPENDENCY_NOT_REGISTERED(dependency, meta.name)
76
+ }
77
77
  })
78
78
  }
79
79