fastify 5.11.2 → 6.0.0-alpha.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 (64) hide show
  1. package/README.md +9 -9
  2. package/SPONSORS.md +1 -0
  3. package/build/build-validation.js +20 -34
  4. package/docs/Guides/Getting-Started.md +2 -2
  5. package/docs/Guides/Serverless.md +34 -2
  6. package/docs/Reference/Encapsulation.md +31 -2
  7. package/docs/Reference/Errors.md +81 -6
  8. package/docs/Reference/Hooks.md +0 -4
  9. package/docs/Reference/LTS.md +6 -5
  10. package/docs/Reference/Request.md +18 -6
  11. package/docs/Reference/Routes.md +13 -0
  12. package/docs/Reference/Server.md +8 -88
  13. package/docs/Reference/TypeScript.md +3 -49
  14. package/docs/Reference/Warnings.md +4 -11
  15. package/fastify.d.ts +10 -24
  16. package/fastify.js +17 -34
  17. package/lib/config-validator.js +368 -475
  18. package/lib/content-type-parser.js +4 -0
  19. package/lib/decorate.js +1 -1
  20. package/lib/errors.js +9 -3
  21. package/lib/log-controller.js +3 -5
  22. package/lib/logger-factory.js +1 -1
  23. package/lib/reply.js +7 -3
  24. package/lib/route.js +4 -9
  25. package/lib/server.js +2 -3
  26. package/lib/warnings.js +2 -46
  27. package/package.json +2 -3
  28. package/test/404s.test.js +0 -49
  29. package/test/allow-unsafe-regex.test.js +12 -4
  30. package/test/case-insensitive.test.js +12 -4
  31. package/test/content-parser.test.js +32 -0
  32. package/test/custom-querystring-parser.test.js +0 -16
  33. package/test/decorator-instance-properties.test.js +49 -0
  34. package/test/hooks.test.js +6 -1
  35. package/test/http-methods/custom-http-methods.test.js +12 -17
  36. package/test/http-methods/trace.test.js +1 -1
  37. package/test/https/custom-https-server.test.js +1 -1
  38. package/test/internals/errors.test.js +15 -5
  39. package/test/internals/initial-config.test.js +56 -38
  40. package/test/internals/logger.test.js +17 -4
  41. package/test/internals/reply.test.js +1 -1
  42. package/test/internals/request.test.js +8 -24
  43. package/test/logger/logging.test.js +2 -121
  44. package/test/logger/request.test.js +1 -36
  45. package/test/plugin.4.test.js +5 -4
  46. package/test/reply-trailers.test.js +92 -4
  47. package/test/route-prefix.test.js +46 -18
  48. package/test/route.1.test.js +2 -2
  49. package/test/router-options.test.js +25 -523
  50. package/test/set-error-handler.test.js +7 -9
  51. package/test/types/errors.tst.ts +2 -1
  52. package/test/types/fastify.tst.ts +44 -48
  53. package/test/types/instance.tst.ts +4 -5
  54. package/test/types/logger.tst.ts +3 -7
  55. package/test/use-semicolon-delimiter.test.js +0 -60
  56. package/types/context.d.ts +0 -20
  57. package/types/errors.d.ts +2 -1
  58. package/types/instance.d.ts +0 -4
  59. package/types/logger.d.ts +2 -17
  60. package/types/plugin.d.ts +0 -9
  61. package/types/request.d.ts +0 -4
  62. package/.pi/bash-confirm-whitelist.json +0 -180
  63. package/lib/promise.js +0 -23
  64. package/test/internals/promise.test.js +0 -63
@@ -145,6 +145,10 @@ ContentTypeParser.prototype.getParser = function (contentType) {
145
145
 
146
146
  for (let j = 0; j !== this.parserRegExpList.length; ++j) {
147
147
  const parserRegExp = this.parserRegExpList[j]
148
+ // A parser registered with a `g`/`y` flagged RegExp keeps a mutable
149
+ // lastIndex between calls, so a plain `.test()` would skip part of the
150
+ // next content type and match inconsistently. Reset it before testing.
151
+ parserRegExp.lastIndex = 0
148
152
  if (parserRegExp.test(ct)) {
149
153
  parser = this.customParsers.get(parserRegExp.toString())
150
154
  this.cache.set(ct, parser)
package/lib/decorate.js CHANGED
@@ -121,7 +121,7 @@ function checkDependencies (instance, name, deps) {
121
121
  }
122
122
 
123
123
  for (let i = 0; i !== deps.length; ++i) {
124
- if (!checkExistence(instance, deps[i])) {
124
+ if (!checkExistence(instance, deps[i]) && !hasInstanceProperty(instance, deps[i])) {
125
125
  throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])
126
126
  }
127
127
  }
package/lib/errors.js CHANGED
@@ -416,6 +416,12 @@ const codes = {
416
416
  500,
417
417
  TypeError
418
418
  ),
419
+ FST_ERR_ROUTE_METHOD_ALREADY_SUPPORTED: createError(
420
+ 'FST_ERR_ROUTE_METHOD_ALREADY_SUPPORTED',
421
+ 'Method "%s" is already supported. Use `overrideExisting: true` to override it.',
422
+ 500,
423
+ TypeError
424
+ ),
419
425
  FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError(
420
426
  'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED',
421
427
  '%s method is not supported.',
@@ -475,10 +481,10 @@ const codes = {
475
481
  ),
476
482
  FST_ERR_REOPENED_SERVER: createError(
477
483
  'FST_ERR_REOPENED_SERVER',
478
- 'Fastify is already listening'
484
+ 'Fastify is already started'
479
485
  ),
480
- FST_ERR_INSTANCE_ALREADY_LISTENING: createError(
481
- 'FST_ERR_INSTANCE_ALREADY_LISTENING',
486
+ FST_ERR_INSTANCE_ALREADY_STARTED: createError(
487
+ 'FST_ERR_INSTANCE_ALREADY_STARTED',
482
488
  'Fastify instance is already listening. %s'
483
489
  ),
484
490
 
@@ -1,7 +1,5 @@
1
1
  'use strict'
2
2
 
3
- const { defaultInitOptions } = require('./initial-config-validation')
4
-
5
3
  /**
6
4
  * Internal logger layer that wraps Fastify's log calls with
7
5
  * request-lifecycle awareness (disable-check, level selection).
@@ -20,9 +18,9 @@ class LogController {
20
18
  */
21
19
  constructor (options) {
22
20
  const opts = options || {}
23
- this.disableRequestLogging = opts.disableRequestLogging || defaultInitOptions.disableRequestLogging
21
+ this.disableRequestLogging = opts.disableRequestLogging || false
24
22
  this.isDisableRequestLoggingFunction = typeof this.disableRequestLogging === 'function'
25
- this.requestIdLogLabel = opts.requestIdLogLabel || defaultInitOptions.requestIdLogLabel
23
+ this.requestIdLogLabel = opts.requestIdLogLabel || 'reqId'
26
24
  }
27
25
 
28
26
  /**
@@ -54,7 +52,7 @@ class LogController {
54
52
  * Logs the outcome of a completed request.
55
53
  * Uses `error` level when an error is present, `info` otherwise.
56
54
  *
57
- * @param {Error | null} error Error that occurred during the response, if any.
55
+ * @param {Error | null | undefined} error Error that occurred during the response, if any.
58
56
  * @param {object} request Fastify request object.
59
57
  * @param {object} reply Fastify reply object.
60
58
  * @param {object} [metadata] Extra contextual data (unused).
@@ -132,7 +132,7 @@ function createLogger (options) {
132
132
  function createLogController (options) {
133
133
  const userController = options.logController
134
134
  if (!userController) {
135
- return new LogController(options)
135
+ return new LogController()
136
136
  }
137
137
 
138
138
  if (userController instanceof LogController) {
package/lib/reply.js CHANGED
@@ -595,9 +595,13 @@ function onSendEnd (reply, payload) {
595
595
  header += ' '
596
596
  header += trailerName
597
597
  }
598
- // it must be chunked for trailer to work
599
- reply.header('Transfer-Encoding', 'chunked')
600
- reply.header('Trailer', header.trim())
598
+ if (header !== '') {
599
+ // it must be chunked for trailer to work
600
+ reply.header('Transfer-Encoding', 'chunked')
601
+ reply.header('Trailer', header.trim())
602
+ } else {
603
+ reply[kReplyTrailers] = null
604
+ }
601
605
  }
602
606
 
603
607
  // since Response contain status code, headers and body,
package/lib/route.js CHANGED
@@ -6,6 +6,7 @@ const handleRequest = require('./handle-request.js')
6
6
  const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
7
7
  const { normalizeSchema } = require('./schemas')
8
8
  const { parseHeadOnSendHandlers } = require('./head-route.js')
9
+ const { defaultInitOptions } = require('./initial-config-validation.js')
9
10
 
10
11
  const {
11
12
  compileSchemasForValidation,
@@ -57,8 +58,6 @@ const {
57
58
  const { buildErrorHandler } = require('./error-handler')
58
59
  const { createChildLogger, defaultChildLoggerFactory } = require('./logger-factory.js')
59
60
  const { getGenReqId } = require('./req-id-gen-factory.js')
60
- const { FSTDEP022 } = require('./warnings')
61
-
62
61
  const routerKeys = [
63
62
  'allowUnsafeRegex',
64
63
  'buildPrettyMeta',
@@ -663,15 +662,11 @@ function buildRouterOptions (options, defaultOptions) {
663
662
  ? Object.create(null)
664
663
  : Object.assign(Object.create(null), options.routerOptions)
665
664
 
666
- const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key))
667
-
668
- if (usedDeprecatedOptions.length > 0) {
669
- FSTDEP022(usedDeprecatedOptions.join(', '))
670
- }
671
-
672
665
  for (const key of routerKeys) {
673
666
  if (!Object.hasOwn(routerOptions, key)) {
674
- routerOptions[key] = options[key] ?? defaultOptions[key]
667
+ // defaultInitOptions are non-function default
668
+ // defaultOptions are function default
669
+ routerOptions[key] = options[key] ?? defaultInitOptions.routerOptions[key] ?? defaultOptions[key]
675
670
  }
676
671
  }
677
672
 
package/lib/server.js CHANGED
@@ -16,7 +16,6 @@ const {
16
16
  FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE
17
17
  } = require('./errors')
18
18
  const noopSet = require('./noop-set')
19
- const PonyPromise = require('./promise')
20
19
 
21
20
  module.exports.createServer = createServer
22
21
 
@@ -106,7 +105,7 @@ function createServer (options, httpHandler) {
106
105
  if (cb === undefined) {
107
106
  const listening = listenPromise.call(this, server, listenOptions)
108
107
  return listening.then(address => {
109
- const { promise, resolve } = PonyPromise.withResolvers()
108
+ const { promise, resolve } = Promise.withResolvers()
110
109
  if (host === 'localhost') {
111
110
  multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
112
111
  this[kState].listening = true
@@ -277,7 +276,7 @@ function listenPromise (server, listenOptions) {
277
276
  // skip listen when aborted during ready
278
277
  if (this[kState].aborted) return
279
278
 
280
- const { promise, resolve, reject } = PonyPromise.withResolvers()
279
+ const { promise, resolve, reject } = Promise.withResolvers()
281
280
 
282
281
  const errEventHandler = (err) => {
283
282
  cleanup()
package/lib/warnings.js CHANGED
@@ -3,13 +3,9 @@
3
3
  const { createWarning } = require('process-warning')
4
4
 
5
5
  /**
6
- * Deprecation codes:
6
+ * Warning codes:
7
7
  * - FSTWRN001
8
8
  * - FSTSEC001
9
- * - FSTDEP022
10
- * - FSTDEP023
11
- * - FSTDEP024
12
- * - FSTDEP025
13
9
  *
14
10
  * Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT be reused.
15
11
  * - FSTDEP022 - FSTDEP025 are used by v5 and MUST NOT be reused.
@@ -30,13 +26,6 @@ const FSTWRN003 = createWarning({
30
26
  unlimited: true
31
27
  })
32
28
 
33
- const FSTWRN004 = createWarning({
34
- name: 'FastifyWarning',
35
- code: 'FSTWRN004',
36
- message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs. To disable this behavior, set \'allowErrorHandlerOverride\' to false. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride',
37
- unlimited: true
38
- })
39
-
40
29
  const FSTSEC001 = createWarning({
41
30
  name: 'FastifySecurity',
42
31
  code: 'FSTSEC001',
@@ -44,41 +33,8 @@ const FSTSEC001 = createWarning({
44
33
  unlimited: true
45
34
  })
46
35
 
47
- const FSTDEP022 = createWarning({
48
- name: 'FastifyWarning',
49
- code: 'FSTDEP022',
50
- message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.',
51
- unlimited: true
52
- })
53
-
54
- const FSTDEP023 = createWarning({
55
- name: 'FastifyDeprecation',
56
- code: 'FSTDEP023',
57
- message: 'disableRequestLogging option is deprecated. Use the logController option with disableRequestLogging or isLogDisabled override instead. The disableRequestLogging top-level option will be removed in `fastify@6`.',
58
- unlimited: true
59
- })
60
-
61
- const FSTDEP024 = createWarning({
62
- name: 'FastifyDeprecation',
63
- code: 'FSTDEP024',
64
- message: 'requestIdLogLabel option is deprecated. Use the logController option with requestIdLogLabel instead. The requestIdLogLabel top-level option will be removed in `fastify@6`.',
65
- unlimited: true
66
- })
67
-
68
- const FSTDEP025 = createWarning({
69
- name: 'FastifyDeprecation',
70
- code: 'FSTDEP025',
71
- message: 'Calling addHttpMethod for existing method "%s" without { overrideExisting: true } is deprecated. In Fastify v6, this will throw an error.',
72
- unlimited: true
73
- })
74
-
75
36
  module.exports = {
76
37
  FSTWRN001,
77
38
  FSTWRN003,
78
- FSTWRN004,
79
- FSTSEC001,
80
- FSTDEP022,
81
- FSTDEP023,
82
- FSTDEP024,
83
- FSTDEP025
39
+ FSTSEC001
84
40
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "5.11.2",
3
+ "version": "6.0.0-alpha.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -167,7 +167,6 @@
167
167
  ],
168
168
  "devDependencies": {
169
169
  "@jsumners/line-reporter": "^1.0.1",
170
- "@sinonjs/fake-timers": "^11.2.2",
171
170
  "@stylistic/eslint-plugin": "^5.1.0",
172
171
  "@stylistic/eslint-plugin-js": "^4.1.0",
173
172
  "@types/node": "^26.0.1",
@@ -199,7 +198,7 @@
199
198
  "tstyche": "^7.0.0",
200
199
  "typebox": "^1.0.81",
201
200
  "typescript": "~6.0.2",
202
- "undici": "^7.11.0",
201
+ "undici": "^8.0.0",
203
202
  "vary": "^1.1.2",
204
203
  "yup": "^1.4.0"
205
204
  },
package/test/404s.test.js CHANGED
@@ -1984,52 +1984,3 @@ test('hooks are applied to not found handlers /3', async t => {
1984
1984
  const { statusCode } = await fastify.inject('/')
1985
1985
  t.assert.strictEqual(statusCode, 401)
1986
1986
  })
1987
-
1988
- test('should honor disableRequestLogging function for 404', async t => {
1989
- t.plan(3)
1990
-
1991
- const Writable = require('node:stream').Writable
1992
-
1993
- const logStream = new Writable()
1994
- logStream.logs = []
1995
- logStream._write = function (chunk, encoding, callback) {
1996
- this.logs.push(JSON.parse(chunk.toString()))
1997
- callback()
1998
- }
1999
-
2000
- const fastify = Fastify({
2001
- logger: {
2002
- level: 'info',
2003
- stream: logStream
2004
- },
2005
- disableRequestLogging: (req) => {
2006
- // Disable logging for URLs containing 'silent'
2007
- return req.url.includes('silent')
2008
- }
2009
- })
2010
-
2011
- fastify.get('/', function (req, reply) {
2012
- reply.send({ hello: 'world' })
2013
- })
2014
-
2015
- t.after(() => { fastify.close() })
2016
-
2017
- // First request to a non-existent route (no 'silent' in URL) - should log
2018
- const response1 = await fastify.inject({
2019
- method: 'GET',
2020
- url: '/not-found'
2021
- })
2022
- t.assert.strictEqual(response1.statusCode, 404)
2023
-
2024
- // Second request to a non-existent route with 'silent' in URL - should not log
2025
- const response2 = await fastify.inject({
2026
- method: 'GET',
2027
- url: '/silent-route'
2028
- })
2029
- t.assert.strictEqual(response2.statusCode, 404)
2030
-
2031
- // Check logs: first request should have logged, second should not
2032
- // We expect: incoming request, Route not found info, request completed (for first request only)
2033
- const infoLogs = logStream.logs.filter(log => log.msg && log.msg.includes('Route GET:/not-found not found'))
2034
- t.assert.strictEqual(infoLogs.length, 1, 'Should log 404 info only for non-silent route')
2035
- })
@@ -7,7 +7,9 @@ test('allow unsafe regex', async t => {
7
7
  t.plan(2)
8
8
 
9
9
  const fastify = Fastify({
10
- allowUnsafeRegex: false
10
+ routerOptions: {
11
+ allowUnsafeRegex: false
12
+ }
11
13
  })
12
14
  t.after(() => fastify.close())
13
15
 
@@ -26,7 +28,9 @@ test('allow unsafe regex not match', async t => {
26
28
  t.plan(1)
27
29
 
28
30
  const fastify = Fastify({
29
- allowUnsafeRegex: false
31
+ routerOptions: {
32
+ allowUnsafeRegex: false
33
+ }
30
34
  })
31
35
  t.after(() => fastify.close())
32
36
 
@@ -44,7 +48,9 @@ test('allow unsafe regex not safe', (t, done) => {
44
48
  t.plan(1)
45
49
 
46
50
  const fastify = Fastify({
47
- allowUnsafeRegex: false
51
+ routerOptions: {
52
+ allowUnsafeRegex: false
53
+ }
48
54
  })
49
55
  t.after(() => fastify.close())
50
56
 
@@ -74,7 +80,9 @@ test('allow unsafe regex allow unsafe', async t => {
74
80
  t.plan(3)
75
81
 
76
82
  const fastify = Fastify({
77
- allowUnsafeRegex: true
83
+ routerOptions: {
84
+ allowUnsafeRegex: true
85
+ }
78
86
  })
79
87
  t.after(() => fastify.close())
80
88
 
@@ -7,7 +7,9 @@ test('case insensitive', async (t) => {
7
7
  t.plan(3)
8
8
 
9
9
  const fastify = Fastify({
10
- caseSensitive: false
10
+ routerOptions: {
11
+ caseSensitive: false
12
+ }
11
13
  })
12
14
  t.after(() => fastify.close())
13
15
 
@@ -30,7 +32,9 @@ test('case insensitive inject', async t => {
30
32
  t.plan(2)
31
33
 
32
34
  const fastify = Fastify({
33
- caseSensitive: false
35
+ routerOptions: {
36
+ caseSensitive: false
37
+ }
34
38
  })
35
39
  t.after(() => fastify.close())
36
40
 
@@ -55,7 +59,9 @@ test('case insensitive (parametric)', async (t) => {
55
59
  t.plan(4)
56
60
 
57
61
  const fastify = Fastify({
58
- caseSensitive: false
62
+ routerOptions: {
63
+ caseSensitive: false
64
+ }
59
65
  })
60
66
  t.after(() => fastify.close())
61
67
 
@@ -81,7 +87,9 @@ test('case insensitive (wildcard)', async (t) => {
81
87
  t.plan(4)
82
88
 
83
89
  const fastify = Fastify({
84
- caseSensitive: false
90
+ routerOptions: {
91
+ caseSensitive: false
92
+ }
85
93
  })
86
94
  t.after(() => fastify.close())
87
95
 
@@ -578,6 +578,38 @@ test('content-type match parameters - regexp', async t => {
578
578
  })
579
579
  })
580
580
 
581
+ test('content-type match - RegExp with global flag', async t => {
582
+ t.plan(4)
583
+
584
+ const fastify = Fastify()
585
+ fastify.addContentTypeParser(/^application\/.+\+xml$/g, { parseAs: 'string' }, function (request, body, done) {
586
+ done(null, body)
587
+ })
588
+
589
+ fastify.post('/', async (request) => request.body)
590
+
591
+ // Two distinct content types that both match the parser. A RegExp with the
592
+ // `g` flag keeps a mutable lastIndex between test() calls, so the second
593
+ // content type must still match rather than fall through to 415.
594
+ const first = await fastify.inject({
595
+ method: 'POST',
596
+ path: '/',
597
+ headers: { 'content-type': 'application/vnd.a+xml' },
598
+ body: '<a/>'
599
+ })
600
+ const second = await fastify.inject({
601
+ method: 'POST',
602
+ path: '/',
603
+ headers: { 'content-type': 'application/vnd.b+xml' },
604
+ body: '<b/>'
605
+ })
606
+
607
+ t.assert.strictEqual(first.statusCode, 200)
608
+ t.assert.strictEqual(first.payload, '<a/>')
609
+ t.assert.strictEqual(second.statusCode, 200)
610
+ t.assert.strictEqual(second.payload, '<b/>')
611
+ })
612
+
581
613
  test('content-type fail when parameters not match - string 1', async t => {
582
614
  t.plan(1)
583
615
 
@@ -94,22 +94,6 @@ test('Querystring without value', async t => {
94
94
  t.assert.strictEqual(injectResponse.statusCode, 200)
95
95
  })
96
96
 
97
- test('Custom querystring parser should be a function', t => {
98
- t.plan(1)
99
-
100
- try {
101
- Fastify({
102
- querystringParser: 10
103
- })
104
- t.assert.fail('Should throw')
105
- } catch (err) {
106
- t.assert.strictEqual(
107
- err.message,
108
- "querystringParser option should be a function, instead got 'number'"
109
- )
110
- }
111
- })
112
-
113
97
  test('Custom querystring parser should be a function', t => {
114
98
  t.plan(1)
115
99
 
@@ -61,3 +61,52 @@ test('decorateRequest still works for non-built-in names', (t, done) => {
61
61
  done()
62
62
  })
63
63
  })
64
+
65
+ test('decorateRequest accepts built-in request properties as dependencies', t => {
66
+ t.plan(6)
67
+ const fastify = Fastify()
68
+ for (const name of ['id', 'params', 'raw', 'query', 'log', 'body']) {
69
+ t.assert.doesNotThrow(() => fastify.decorateRequest(`uses_${name}`, null, [name]))
70
+ }
71
+ })
72
+
73
+ test('decorateReply accepts built-in reply properties as dependencies', t => {
74
+ t.plan(3)
75
+ const fastify = Fastify()
76
+ for (const name of ['raw', 'request', 'log']) {
77
+ t.assert.doesNotThrow(() => fastify.decorateReply(`uses_${name}`, null, [name]))
78
+ }
79
+ })
80
+
81
+ test('decorateRequest accepts built-in properties as dependencies inside a plugin', (t, done) => {
82
+ t.plan(2)
83
+ const fastify = Fastify()
84
+ fastify.register(async (instance) => {
85
+ instance.decorateRequest('scopedExtra', null, ['body'])
86
+ t.assert.equal(instance.hasRequestDecorator('scopedExtra'), true)
87
+ })
88
+ fastify.ready((err) => {
89
+ t.assert.ifError(err)
90
+ done()
91
+ })
92
+ })
93
+
94
+ test('decorateRequest accepts built-in properties mixed with user decorators as dependencies', t => {
95
+ t.plan(1)
96
+ const fastify = Fastify()
97
+ fastify.decorateRequest('userProp', null)
98
+ t.assert.doesNotThrow(() => fastify.decorateRequest('mixed', null, ['userProp', 'body']))
99
+ })
100
+
101
+ test('decorateRequest still throws FST_ERR_DEC_MISSING_DEPENDENCY for unknown dependencies', t => {
102
+ t.plan(2)
103
+ const fastify = Fastify()
104
+ t.assert.throws(
105
+ () => fastify.decorateRequest('withUnknown', null, ['nonExistent']),
106
+ (err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY'
107
+ )
108
+ t.assert.throws(
109
+ () => fastify.decorateReply('withUnknown', null, ['body']),
110
+ (err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY'
111
+ )
112
+ })
@@ -675,7 +675,12 @@ test('onRoute hook should be called once when prefixTrailingSlash', (t, testDone
675
675
  let onRouteCalled = 0
676
676
  let routePatched = 0
677
677
 
678
- const fastify = Fastify({ ignoreTrailingSlash: false, exposeHeadRoutes: false })
678
+ const fastify = Fastify({
679
+ exposeHeadRoutes: false,
680
+ routerOptions: {
681
+ ignoreTrailingSlash: false
682
+ }
683
+ })
679
684
 
680
685
  // a plugin that patches route options, similar to fastify-compress
681
686
  fastify.register(fp(function myPlugin (instance, opts, next) {
@@ -113,36 +113,31 @@ test('addHttpMethod rejects fake http method', t => {
113
113
  t.assert.throws(() => { fastify.addHttpMethod('FOOO') }, /Provided method is invalid!/)
114
114
  })
115
115
 
116
- test('addHttpMethod warns when overriding an existing method', (t, done) => {
116
+ test('addHttpMethod rejects an implicit override of an existing method', t => {
117
117
  const fastify = Fastify()
118
- const onWarning = warning => {
119
- t.assert.strictEqual(warning.name, 'FastifyDeprecation')
120
- t.assert.strictEqual(warning.code, 'FSTDEP025')
121
- done()
122
- }
123
- process.once('warning', onWarning)
124
118
 
125
119
  t.after(() => {
126
120
  fastify.close()
127
- process.removeListener('warning', onWarning)
128
121
  })
129
122
 
130
- fastify.addHttpMethod('GET', { hasBody: true })
123
+ t.assert.throws(
124
+ () => fastify.addHttpMethod('GET', { hasBody: true }),
125
+ {
126
+ code: 'FST_ERR_ROUTE_METHOD_ALREADY_SUPPORTED',
127
+ message: 'Method "GET" is already supported. Use `overrideExisting: true` to override it.'
128
+ }
129
+ )
131
130
  })
132
131
 
133
- test('addHttpMethod does not warn when overriding an existing method explicitly', t => {
134
- const doNotWarn = () => {
135
- t.assert.fail('should not warn')
136
- }
137
- process.on('warning', doNotWarn)
138
-
132
+ test('addHttpMethod allows an explicit override of an existing method', t => {
139
133
  const fastify = Fastify()
140
134
  t.after(() => {
141
135
  fastify.close()
142
- process.removeListener('warning', doNotWarn)
143
136
  })
144
137
 
145
- fastify.addHttpMethod('POST', { overrideExisting: true })
138
+ t.assert.doesNotThrow(() => {
139
+ fastify.addHttpMethod('POST', { overrideExisting: true })
140
+ })
146
141
  })
147
142
 
148
143
  test('addHttpMethod can change an existing method body behavior', async t => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { test } = require('node:test')
4
4
  const fastify = require('../../fastify')()
5
- fastify.addHttpMethod('TRACE')
5
+ fastify.addHttpMethod('TRACE', { overrideExisting: true })
6
6
 
7
7
  test('shorthand - trace', t => {
8
8
  t.plan(1)
@@ -5,7 +5,7 @@ const Fastify = require('../..')
5
5
  const https = require('node:https')
6
6
  const dns = require('node:dns').promises
7
7
  const { buildCertificate } = require('../build-certificate')
8
- const { Agent } = require('undici')
8
+ const { Agent, fetch } = require('undici')
9
9
 
10
10
  async function setup () {
11
11
  await buildCertificate()
@@ -5,7 +5,7 @@ const errors = require('../../lib/errors')
5
5
  const { readFileSync } = require('node:fs')
6
6
  const { resolve } = require('node:path')
7
7
 
8
- const expectedErrors = 94
8
+ const expectedErrors = 95
9
9
 
10
10
  test(`should expose ${expectedErrors} errors`, t => {
11
11
  t.plan(1)
@@ -710,6 +710,16 @@ test('FST_ERR_ROUTE_METHOD_INVALID', t => {
710
710
  t.assert.ok(error instanceof TypeError)
711
711
  })
712
712
 
713
+ test('FST_ERR_ROUTE_METHOD_ALREADY_SUPPORTED', t => {
714
+ t.plan(5)
715
+ const error = new errors.FST_ERR_ROUTE_METHOD_ALREADY_SUPPORTED('GET')
716
+ t.assert.strictEqual(error.name, 'FastifyError')
717
+ t.assert.strictEqual(error.code, 'FST_ERR_ROUTE_METHOD_ALREADY_SUPPORTED')
718
+ t.assert.strictEqual(error.message, 'Method "GET" is already supported. Use `overrideExisting: true` to override it.')
719
+ t.assert.strictEqual(error.statusCode, 500)
720
+ t.assert.ok(error instanceof TypeError)
721
+ })
722
+
713
723
  test('FST_ERR_ROUTE_METHOD_NOT_SUPPORTED', t => {
714
724
  t.plan(5)
715
725
  const error = new errors.FST_ERR_ROUTE_METHOD_NOT_SUPPORTED()
@@ -805,16 +815,16 @@ test('FST_ERR_REOPENED_SERVER', t => {
805
815
  const error = new errors.FST_ERR_REOPENED_SERVER()
806
816
  t.assert.strictEqual(error.name, 'FastifyError')
807
817
  t.assert.strictEqual(error.code, 'FST_ERR_REOPENED_SERVER')
808
- t.assert.strictEqual(error.message, 'Fastify is already listening')
818
+ t.assert.strictEqual(error.message, 'Fastify is already started')
809
819
  t.assert.strictEqual(error.statusCode, 500)
810
820
  t.assert.ok(error instanceof Error)
811
821
  })
812
822
 
813
- test('FST_ERR_INSTANCE_ALREADY_LISTENING', t => {
823
+ test('FST_ERR_INSTANCE_ALREADY_STARTED', t => {
814
824
  t.plan(5)
815
- const error = new errors.FST_ERR_INSTANCE_ALREADY_LISTENING()
825
+ const error = new errors.FST_ERR_INSTANCE_ALREADY_STARTED()
816
826
  t.assert.strictEqual(error.name, 'FastifyError')
817
- t.assert.strictEqual(error.code, 'FST_ERR_INSTANCE_ALREADY_LISTENING')
827
+ t.assert.strictEqual(error.code, 'FST_ERR_INSTANCE_ALREADY_STARTED')
818
828
  t.assert.strictEqual(error.message, 'Fastify instance is already listening. %s')
819
829
  t.assert.strictEqual(error.statusCode, 500)
820
830
  t.assert.ok(error instanceof Error)