fastify 5.2.1 → 5.3.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 (85) hide show
  1. package/LICENSE +1 -1
  2. package/PROJECT_CHARTER.md +7 -7
  3. package/README.md +24 -25
  4. package/SPONSORS.md +1 -0
  5. package/docs/Guides/Benchmarking.md +4 -4
  6. package/docs/Guides/Database.md +1 -1
  7. package/docs/Guides/Delay-Accepting-Requests.md +10 -10
  8. package/docs/Guides/Ecosystem.md +7 -1
  9. package/docs/Guides/Fluent-Schema.md +1 -1
  10. package/docs/Guides/Getting-Started.md +9 -5
  11. package/docs/Guides/Index.md +1 -1
  12. package/docs/Guides/Migration-Guide-V4.md +1 -1
  13. package/docs/Guides/Migration-Guide-V5.md +12 -2
  14. package/docs/Guides/Plugins-Guide.md +6 -6
  15. package/docs/Guides/Serverless.md +14 -48
  16. package/docs/Guides/Style-Guide.md +2 -2
  17. package/docs/Guides/Testing.md +2 -2
  18. package/docs/Guides/Write-Plugin.md +2 -3
  19. package/docs/Reference/ContentTypeParser.md +58 -78
  20. package/docs/Reference/Decorators.md +249 -60
  21. package/docs/Reference/Encapsulation.md +28 -33
  22. package/docs/Reference/Errors.md +52 -53
  23. package/docs/Reference/HTTP2.md +7 -7
  24. package/docs/Reference/Hooks.md +31 -30
  25. package/docs/Reference/LTS.md +10 -15
  26. package/docs/Reference/Lifecycle.md +19 -24
  27. package/docs/Reference/Logging.md +59 -56
  28. package/docs/Reference/Middleware.md +19 -19
  29. package/docs/Reference/Plugins.md +55 -71
  30. package/docs/Reference/Principles.md +25 -30
  31. package/docs/Reference/Reply.md +11 -10
  32. package/docs/Reference/Request.md +89 -98
  33. package/docs/Reference/Routes.md +108 -128
  34. package/docs/Reference/Server.md +18 -16
  35. package/docs/Reference/Type-Providers.md +19 -21
  36. package/docs/Reference/TypeScript.md +1 -18
  37. package/docs/Reference/Validation-and-Serialization.md +134 -159
  38. package/docs/Reference/Warnings.md +22 -25
  39. package/fastify.js +3 -2
  40. package/lib/contentTypeParser.js +7 -8
  41. package/lib/decorate.js +18 -3
  42. package/lib/error-handler.js +14 -12
  43. package/lib/errors.js +4 -0
  44. package/lib/headRoute.js +4 -2
  45. package/lib/pluginUtils.js +4 -2
  46. package/lib/reply.js +17 -2
  47. package/lib/request.js +28 -2
  48. package/lib/server.js +5 -0
  49. package/lib/validation.js +1 -1
  50. package/lib/warnings.js +9 -0
  51. package/lib/wrapThenable.js +8 -1
  52. package/package.json +12 -12
  53. package/test/bundler/esbuild/package.json +1 -1
  54. package/test/close.test.js +125 -108
  55. package/test/custom-parser-async.test.js +34 -36
  56. package/test/custom-parser.4.test.js +55 -38
  57. package/test/decorator.test.js +174 -4
  58. package/test/fastify-instance.test.js +12 -2
  59. package/test/genReqId.test.js +125 -174
  60. package/test/has-route.test.js +1 -3
  61. package/test/hooks.on-listen.test.js +17 -14
  62. package/test/internals/content-type-parser.test.js +1 -1
  63. package/test/internals/errors.test.js +14 -1
  64. package/test/issue-4959.test.js +84 -0
  65. package/test/listen.1.test.js +37 -34
  66. package/test/listen.2.test.js +50 -40
  67. package/test/listen.3.test.js +28 -32
  68. package/test/listen.4.test.js +61 -45
  69. package/test/listen.5.test.js +23 -0
  70. package/test/register.test.js +55 -50
  71. package/test/request-error.test.js +114 -94
  72. package/test/route-shorthand.test.js +36 -32
  73. package/test/stream.5.test.js +35 -33
  74. package/test/throw.test.js +87 -91
  75. package/test/toolkit.js +32 -0
  76. package/test/trust-proxy.test.js +23 -23
  77. package/test/types/instance.test-d.ts +4 -0
  78. package/test/types/reply.test-d.ts +1 -0
  79. package/test/types/request.test-d.ts +4 -0
  80. package/test/types/type-provider.test-d.ts +40 -0
  81. package/test/upgrade.test.js +32 -33
  82. package/types/instance.d.ts +6 -0
  83. package/types/reply.d.ts +1 -0
  84. package/types/request.d.ts +2 -0
  85. package/types/type-provider.d.ts +12 -3
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { AsyncResource } = require('node:async_hooks')
4
4
  const { FifoMap: Fifo } = require('toad-cache')
5
- const secureJson = require('secure-json-parse')
5
+ const { parse: secureJsonParse } = require('secure-json-parse')
6
6
  const {
7
7
  kDefaultJsonParse,
8
8
  kContentTypeParser,
@@ -197,7 +197,7 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
197
197
  } else {
198
198
  const result = parser.fn(request, request[kRequestPayloadStream], done)
199
199
 
200
- if (typeof result?.then === 'function') {
200
+ if (result && typeof result.then === 'function') {
201
201
  result.then(body => done(null, body), done)
202
202
  }
203
203
  }
@@ -304,17 +304,16 @@ function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
304
304
  return defaultJsonParser
305
305
 
306
306
  function defaultJsonParser (req, body, done) {
307
- if (body === '' || body == null || (Buffer.isBuffer(body) && body.length === 0)) {
308
- return done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
307
+ if (body.length === 0) {
308
+ done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
309
+ return
309
310
  }
310
- let json
311
311
  try {
312
- json = secureJson.parse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning })
312
+ done(null, secureJsonParse(body, { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning }))
313
313
  } catch (err) {
314
314
  err.statusCode = 400
315
- return done(err, undefined)
315
+ done(err, undefined)
316
316
  }
317
- done(null, json)
318
317
  }
319
318
  }
320
319
 
package/lib/decorate.js CHANGED
@@ -4,7 +4,7 @@ const {
4
4
  kReply,
5
5
  kRequest,
6
6
  kState,
7
- kHasBeenDecorated
7
+ kHasBeenDecorated,
8
8
  } = require('./symbols.js')
9
9
 
10
10
  const {
@@ -12,7 +12,8 @@ const {
12
12
  FST_ERR_DEC_MISSING_DEPENDENCY,
13
13
  FST_ERR_DEC_AFTER_START,
14
14
  FST_ERR_DEC_REFERENCE_TYPE,
15
- FST_ERR_DEC_DEPENDENCY_INVALID_TYPE
15
+ FST_ERR_DEC_DEPENDENCY_INVALID_TYPE,
16
+ FST_ERR_DEC_UNDECLARED,
16
17
  } = require('./errors')
17
18
 
18
19
  function decorate (instance, name, fn, dependencies) {
@@ -32,6 +33,18 @@ function decorate (instance, name, fn, dependencies) {
32
33
  }
33
34
  }
34
35
 
36
+ function getInstanceDecorator (name) {
37
+ if (!checkExistence(this, name)) {
38
+ throw new FST_ERR_DEC_UNDECLARED(name, 'instance')
39
+ }
40
+
41
+ if (typeof this[name] === 'function') {
42
+ return this[name].bind(this)
43
+ }
44
+
45
+ return this[name]
46
+ }
47
+
35
48
  function decorateConstructor (konstructor, name, fn, dependencies) {
36
49
  const instance = konstructor.prototype
37
50
  if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) {
@@ -133,5 +146,7 @@ module.exports = {
133
146
  existReply: checkReplyExistence,
134
147
  dependencies: checkDependencies,
135
148
  decorateReply,
136
- decorateRequest
149
+ decorateRequest,
150
+ getInstanceDecorator,
151
+ hasKey
137
152
  }
@@ -110,18 +110,20 @@ function fallbackErrorHandler (error, reply, cb) {
110
110
  let payload
111
111
  try {
112
112
  const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
113
- payload = (serializerFn === false)
114
- ? serializeError({
115
- error: statusCodes[statusCode + ''],
116
- code: error.code,
117
- message: error.message,
118
- statusCode
119
- })
120
- : serializerFn(Object.create(error, {
121
- error: { value: statusCodes[statusCode + ''] },
122
- message: { value: error.message },
123
- statusCode: { value: statusCode }
124
- }))
113
+ if (serializerFn === false) {
114
+ payload = serializeError({
115
+ error: statusCodes[statusCode + ''],
116
+ code: error.code,
117
+ message: error.message,
118
+ statusCode
119
+ })
120
+ } else {
121
+ payload = serializerFn(Object.create(error, {
122
+ error: { value: statusCodes[statusCode + ''] },
123
+ message: { value: error.message },
124
+ statusCode: { value: statusCode }
125
+ }))
126
+ }
125
127
  } catch (err) {
126
128
  if (!reply.log[kDisableRequestLogging]) {
127
129
  // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
package/lib/errors.js CHANGED
@@ -149,6 +149,10 @@ const codes = {
149
149
  'FST_ERR_DEC_REFERENCE_TYPE',
150
150
  "The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead."
151
151
  ),
152
+ FST_ERR_DEC_UNDECLARED: createError(
153
+ 'FST_ERR_DEC_UNDECLARED',
154
+ "No decorator '%s' has been declared on %s."
155
+ ),
152
156
 
153
157
  /**
154
158
  * hooks
package/lib/headRoute.js CHANGED
@@ -3,7 +3,8 @@ function headRouteOnSendHandler (req, reply, payload, done) {
3
3
  // If payload is undefined
4
4
  if (payload === undefined) {
5
5
  reply.header('content-length', '0')
6
- return done(null, null)
6
+ done(null, null)
7
+ return
7
8
  }
8
9
 
9
10
  if (typeof payload.resume === 'function') {
@@ -11,7 +12,8 @@ function headRouteOnSendHandler (req, reply, payload, done) {
11
12
  reply.log.error({ err }, 'Error on Stream found for HEAD route')
12
13
  })
13
14
  payload.resume()
14
- return done(null, null)
15
+ done(null, null)
16
+ return
15
17
  }
16
18
 
17
19
  const size = '' + Buffer.byteLength(payload)
@@ -13,6 +13,8 @@ const {
13
13
  FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
14
14
  } = require('./errors')
15
15
 
16
+ const rcRegex = /-(?:rc|pre|alpha).+$/u
17
+
16
18
  function getMeta (fn) {
17
19
  return fn[Symbol.for('plugin-meta')]
18
20
  }
@@ -106,11 +108,11 @@ function _checkDecorators (that, instance, decorators, name) {
106
108
 
107
109
  function checkVersion (fn) {
108
110
  const meta = getMeta(fn)
109
- if (meta == null || meta?.fastify == null) return
111
+ if (meta?.fastify == null) return
110
112
 
111
113
  const requiredVersion = meta.fastify
112
114
 
113
- const fastifyRc = /-(?:rc|pre|alpha).+$/.test(this.version)
115
+ const fastifyRc = rcRegex.test(this.version)
114
116
  if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {
115
117
  // A Fastify release candidate phase is taking place. In order to reduce
116
118
  // the effort needed to test plugins with the RC, we allow plugins targeting
package/lib/reply.js CHANGED
@@ -22,7 +22,7 @@ const {
22
22
  kReplyCacheSerializeFns,
23
23
  kSchemaController,
24
24
  kOptions,
25
- kRouteContext
25
+ kRouteContext,
26
26
  } = require('./symbols.js')
27
27
  const {
28
28
  onSendHookRunner,
@@ -52,8 +52,10 @@ const {
52
52
  FST_ERR_BAD_TRAILER_NAME,
53
53
  FST_ERR_BAD_TRAILER_VALUE,
54
54
  FST_ERR_MISSING_SERIALIZATION_FN,
55
- FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
55
+ FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN,
56
+ FST_ERR_DEC_UNDECLARED,
56
57
  } = require('./errors')
58
+ const decorators = require('./decorate')
57
59
 
58
60
  const toString = Object.prototype.toString
59
61
 
@@ -475,6 +477,19 @@ Reply.prototype.then = function (fulfilled, rejected) {
475
477
  })
476
478
  }
477
479
 
480
+ Reply.prototype.getDecorator = function (name) {
481
+ if (!decorators.hasKey(this, name) && !decorators.exist(this, name)) {
482
+ throw new FST_ERR_DEC_UNDECLARED(name, 'reply')
483
+ }
484
+
485
+ const decorator = this[name]
486
+ if (typeof decorator === 'function') {
487
+ return decorator.bind(this)
488
+ }
489
+
490
+ return decorator
491
+ }
492
+
478
493
  function preSerializationHook (reply, payload) {
479
494
  if (reply[kRouteContext].preSerialization !== null) {
480
495
  preSerializationHookRunner(
package/lib/request.js CHANGED
@@ -11,9 +11,10 @@ const {
11
11
  kOptions,
12
12
  kRequestCacheValidateFns,
13
13
  kRouteContext,
14
- kRequestOriginalUrl
14
+ kRequestOriginalUrl,
15
15
  } = require('./symbols')
16
- const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
16
+ const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors')
17
+ const decorators = require('./decorate')
17
18
 
18
19
  const HTTP_PART_SYMBOL_MAP = {
19
20
  body: kSchemaBody,
@@ -141,6 +142,12 @@ function buildRequestWithTrustProxy (R, trustProxy) {
141
142
  return _Request
142
143
  }
143
144
 
145
+ function assertsRequestDecoration (request, name) {
146
+ if (!decorators.hasKey(request, name) && !decorators.exist(request, name)) {
147
+ throw new FST_ERR_DEC_UNDECLARED(name, 'request')
148
+ }
149
+ }
150
+
144
151
  Object.defineProperties(Request.prototype, {
145
152
  server: {
146
153
  get () {
@@ -343,6 +350,25 @@ Object.defineProperties(Request.prototype, {
343
350
 
344
351
  return validate(input)
345
352
  }
353
+ },
354
+ getDecorator: {
355
+ value: function (name) {
356
+ assertsRequestDecoration(this, name)
357
+
358
+ const decorator = this[name]
359
+ if (typeof decorator === 'function') {
360
+ return decorator.bind(this)
361
+ }
362
+
363
+ return decorator
364
+ }
365
+ },
366
+ setDecorator: {
367
+ value: function (name, value) {
368
+ assertsRequestDecoration(this, name)
369
+
370
+ this[name] = value
371
+ }
346
372
  }
347
373
  })
348
374
 
package/lib/server.js CHANGED
@@ -6,6 +6,7 @@ const dns = require('node:dns')
6
6
  const os = require('node:os')
7
7
 
8
8
  const { kState, kOptions, kServerBindings } = require('./symbols')
9
+ const { FSTWRN003 } = require('./warnings')
9
10
  const { onListenHookRunner } = require('./hooks')
10
11
  const {
11
12
  FST_ERR_HTTP2_INVALID_VERSION,
@@ -29,6 +30,10 @@ function createServer (options, httpHandler) {
29
30
  cb = undefined
30
31
  ) {
31
32
  if (typeof cb === 'function') {
33
+ if (cb.constructor.name === 'AsyncFunction') {
34
+ FSTWRN003('listen method')
35
+ }
36
+
32
37
  listenOptions.cb = cb
33
38
  }
34
39
  if (listenOptions.signal) {
package/lib/validation.js CHANGED
@@ -119,7 +119,7 @@ function validateParam (validatorFunction, request, paramName) {
119
119
  const isUndefined = request[paramName] === undefined
120
120
  const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName])
121
121
 
122
- if (ret?.then) {
122
+ if (ret && typeof ret.then === 'function') {
123
123
  return ret
124
124
  .then((res) => { return answer(res) })
125
125
  .catch(err => { return err }) // return as simple error (not throw)
package/lib/warnings.js CHANGED
@@ -8,6 +8,7 @@ const { createWarning } = require('process-warning')
8
8
  * - FSTSEC001
9
9
  *
10
10
  * Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused.
11
+ * Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT not be reused.
11
12
  */
12
13
 
13
14
  const FSTWRN001 = createWarning({
@@ -17,6 +18,13 @@ const FSTWRN001 = createWarning({
17
18
  unlimited: true
18
19
  })
19
20
 
21
+ const FSTWRN003 = createWarning({
22
+ name: 'FastifyWarning',
23
+ code: 'FSTWRN003',
24
+ message: 'The %s mixes async and callback styles that may lead to unhandled rejections. Please use only one of them.',
25
+ unlimited: true
26
+ })
27
+
20
28
  const FSTSEC001 = createWarning({
21
29
  name: 'FastifySecurity',
22
30
  code: 'FSTSEC001',
@@ -26,5 +34,6 @@ const FSTSEC001 = createWarning({
26
34
 
27
35
  module.exports = {
28
36
  FSTWRN001,
37
+ FSTWRN003,
29
38
  FSTSEC001
30
39
  }
@@ -27,7 +27,14 @@ function wrapThenable (thenable, reply, store) {
27
27
  // the request may be terminated during the reply. in this situation,
28
28
  // it require an extra checking of request.aborted to see whether
29
29
  // the request is killed by client.
30
- if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) {
30
+ if (payload !== undefined || //
31
+ (reply.sent === false && //
32
+ reply.raw.headersSent === false &&
33
+ reply.request.raw.aborted === false &&
34
+ reply.request.socket &&
35
+ !reply.request.socket.destroyed
36
+ )
37
+ ) {
31
38
  // we use a try-catch internally to avoid adding a catch to another
32
39
  // promise, increase promise perf by 10%
33
40
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "5.2.1",
3
+ "version": "5.3.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -161,20 +161,20 @@
161
161
  "devDependencies": {
162
162
  "@fastify/pre-commit": "^2.1.0",
163
163
  "@jsumners/line-reporter": "^1.0.1",
164
- "@sinclair/typebox": "^0.33.4",
164
+ "@sinclair/typebox": "^0.34.13",
165
165
  "@sinonjs/fake-timers": "^11.2.2",
166
- "@stylistic/eslint-plugin": "^2.1.0",
167
- "@stylistic/eslint-plugin-js": "^2.1.0",
166
+ "@stylistic/eslint-plugin": "^4.1.0",
167
+ "@stylistic/eslint-plugin-js": "^4.1.0",
168
168
  "@types/node": "^22.0.0",
169
169
  "ajv": "^8.12.0",
170
170
  "ajv-errors": "^3.0.0",
171
171
  "ajv-formats": "^3.0.1",
172
172
  "ajv-i18n": "^4.2.0",
173
173
  "ajv-merge-patch": "^5.0.1",
174
- "autocannon": "^7.15.0",
175
- "borp": "^0.18.0",
174
+ "autocannon": "^8.0.0",
175
+ "borp": "^0.19.0",
176
176
  "branch-comparer": "^1.1.0",
177
- "concurrently": "^8.2.2",
177
+ "concurrently": "^9.1.2",
178
178
  "cross-env": "^7.0.3",
179
179
  "eslint": "^9.0.0",
180
180
  "fast-json-body": "^1.1.0",
@@ -185,15 +185,15 @@
185
185
  "joi": "^17.12.3",
186
186
  "json-schema-to-ts": "^3.0.1",
187
187
  "JSONStream": "^1.3.5",
188
- "markdownlint-cli2": "^0.13.0",
189
- "neostandard": "^0.11.3",
188
+ "markdownlint-cli2": "^0.17.1",
189
+ "neostandard": "^0.12.0",
190
190
  "node-forge": "^1.3.1",
191
191
  "proxyquire": "^2.1.3",
192
192
  "simple-get": "^4.0.1",
193
193
  "split2": "^4.2.0",
194
194
  "tap": "^21.0.0",
195
195
  "tsd": "^0.31.0",
196
- "typescript": "~5.4.5",
196
+ "typescript": "~5.8.2",
197
197
  "undici": "^6.13.0",
198
198
  "vary": "^1.1.2",
199
199
  "yup": "^1.4.0"
@@ -209,9 +209,9 @@
209
209
  "find-my-way": "^9.0.0",
210
210
  "light-my-request": "^6.0.0",
211
211
  "pino": "^9.0.0",
212
- "process-warning": "^4.0.0",
212
+ "process-warning": "^5.0.0",
213
213
  "rfdc": "^1.3.1",
214
- "secure-json-parse": "^3.0.1",
214
+ "secure-json-parse": "^4.0.0",
215
215
  "semver": "^7.6.0",
216
216
  "toad-cache": "^3.7.0"
217
217
  },
@@ -5,6 +5,6 @@
5
5
  "test": "npm run bundle && node bundler-test.js"
6
6
  },
7
7
  "devDependencies": {
8
- "esbuild": "^0.14.11"
8
+ "esbuild": "^0.25.0"
9
9
  }
10
10
  }