fastify 4.25.2 → 4.26.1

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 (55) hide show
  1. package/EXPENSE_POLICY.md +105 -0
  2. package/GOVERNANCE.md +2 -103
  3. package/LICENSE +1 -1
  4. package/README.md +13 -9
  5. package/SECURITY.md +2 -157
  6. package/SPONSORS.md +20 -0
  7. package/build/build-validation.js +3 -1
  8. package/docs/Guides/Ecosystem.md +29 -9
  9. package/docs/Guides/Getting-Started.md +16 -3
  10. package/docs/Guides/Style-Guide.md +7 -7
  11. package/docs/Reference/Decorators.md +1 -1
  12. package/docs/Reference/Errors.md +63 -1
  13. package/docs/Reference/Hooks.md +1 -1
  14. package/docs/Reference/Logging.md +3 -3
  15. package/docs/Reference/Reply.md +70 -1
  16. package/docs/Reference/Server.md +90 -0
  17. package/docs/Reference/Warnings.md +17 -2
  18. package/fastify.d.ts +3 -2
  19. package/fastify.js +25 -7
  20. package/lib/configValidator.js +62 -33
  21. package/lib/contentTypeParser.js +9 -2
  22. package/lib/error-handler.js +1 -1
  23. package/lib/error-serializer.js +30 -29
  24. package/lib/errors.js +6 -1
  25. package/lib/fourOhFour.js +4 -3
  26. package/lib/hooks.js +1 -5
  27. package/lib/reply.js +68 -10
  28. package/lib/reqIdGenFactory.js +5 -0
  29. package/lib/route.js +22 -6
  30. package/lib/schema-controller.js +37 -4
  31. package/lib/symbols.js +1 -0
  32. package/lib/warnings.js +6 -0
  33. package/package.json +18 -6
  34. package/test/async_hooks.test.js +69 -0
  35. package/test/findRoute.test.js +135 -0
  36. package/test/genReqId.test.js +392 -0
  37. package/test/hooks.on-listen.test.js +66 -14
  38. package/test/hooks.on-ready.test.js +1 -1
  39. package/test/internals/errors.test.js +17 -7
  40. package/test/internals/initialConfig.test.js +7 -3
  41. package/test/internals/reply.test.js +80 -5
  42. package/test/plugin.4.test.js +3 -3
  43. package/test/pretty-print.test.js +1 -1
  44. package/test/schema-serialization.test.js +41 -0
  45. package/test/schema-validation.test.js +115 -6
  46. package/test/serialize-response.test.js +187 -0
  47. package/test/types/instance.test-d.ts +14 -1
  48. package/test/types/reply.test-d.ts +4 -2
  49. package/test/types/request.test-d.ts +1 -1
  50. package/test/types/route.test-d.ts +15 -1
  51. package/test/useSemicolonDelimiter.test.js +113 -0
  52. package/test/web-api.test.js +208 -0
  53. package/types/instance.d.ts +23 -10
  54. package/types/reply.d.ts +4 -0
  55. package/types/request.d.ts +5 -4
@@ -39,12 +39,10 @@ function buildSchemaController (parentSchemaCtrl, opts) {
39
39
 
40
40
  class SchemaController {
41
41
  constructor (parent, options) {
42
- this.opts = options || (parent && parent.opts)
42
+ this.opts = options || parent?.opts
43
43
  this.addedSchemas = false
44
44
 
45
45
  this.compilersFactory = this.opts.compilersFactory
46
- this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false
47
- this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false
48
46
 
49
47
  if (parent) {
50
48
  this.schemaBucket = this.opts.bucket(parent.getSchemas())
@@ -55,6 +53,8 @@ class SchemaController {
55
53
  this.parent = parent
56
54
  } else {
57
55
  this.schemaBucket = this.opts.bucket()
56
+ this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false
57
+ this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false
58
58
  }
59
59
  }
60
60
 
@@ -72,13 +72,46 @@ class SchemaController {
72
72
  return this.schemaBucket.getSchemas()
73
73
  }
74
74
 
75
- // Schema Controller compilers holder
76
75
  setValidatorCompiler (validatorCompiler) {
76
+ // Set up as if the fixed validator compiler had been provided
77
+ // by a custom 'options.compilersFactory.buildValidator' that
78
+ // always returns the same compiler object. This is required because:
79
+ //
80
+ // - setValidatorCompiler must immediately install a compiler to preserve
81
+ // legacy behavior
82
+ // - setupValidator will recreate compilers from builders in some
83
+ // circumstances, so we have to install this adapter to make it
84
+ // behave the same if the legacy API is used
85
+ //
86
+ // The cloning of the compilersFactory object is necessary because
87
+ // we are aliasing the parent compilersFactory if none was provided
88
+ // to us (see constructor.)
89
+ this.compilersFactory = Object.assign(
90
+ {},
91
+ this.compilersFactory,
92
+ { buildValidator: () => validatorCompiler })
77
93
  this.validatorCompiler = validatorCompiler
78
94
  this.isCustomValidatorCompiler = true
79
95
  }
80
96
 
81
97
  setSerializerCompiler (serializerCompiler) {
98
+ // Set up as if the fixed serializer compiler had been provided
99
+ // by a custom 'options.compilersFactory.buildSerializer' that
100
+ // always returns the same compiler object. This is required because:
101
+ //
102
+ // - setSerializerCompiler must immediately install a compiler to preserve
103
+ // legacy behavior
104
+ // - setupSerializer will recreate compilers from builders in some
105
+ // circumstances, so we have to install this adapter to make it
106
+ // behave the same if the legacy API is used
107
+ //
108
+ // The cloning of the compilersFactory object is necessary because
109
+ // we are aliasing the parent compilersFactory if none was provided
110
+ // to us (see constructor.)
111
+ this.compilersFactory = Object.assign(
112
+ {},
113
+ this.compilersFactory,
114
+ { buildSerializer: () => serializerCompiler })
82
115
  this.serializerCompiler = serializerCompiler
83
116
  this.isCustomSerializerCompiler = true
84
117
  }
package/lib/symbols.js CHANGED
@@ -16,6 +16,7 @@ const keys = {
16
16
  kPluginNameChain: Symbol('fastify.pluginNameChain'),
17
17
  kRouteContext: Symbol('fastify.context'),
18
18
  kPublicRouteContext: Symbol('fastify.routeOptions'),
19
+ kGenReqId: Symbol('fastify.genReqId'),
19
20
  // Schema
20
21
  kSchemaController: Symbol('fastify.schemaController'),
21
22
  kSchemaHeaders: Symbol('headers-schema'),
package/lib/warnings.js CHANGED
@@ -77,6 +77,11 @@ const FSTDEP019 = createDeprecation({
77
77
  message: 'reply.context property access is deprecated. Please use "request.routeOptions.config" or "request.routeOptions.schema" instead for accessing Route settings. The "reply.context" will be removed in `fastify@5`.'
78
78
  })
79
79
 
80
+ const FSTDEP020 = createDeprecation({
81
+ code: 'FSTDEP020',
82
+ message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.'
83
+ })
84
+
80
85
  const FSTWRN001 = createWarning({
81
86
  name: 'FastifyWarning',
82
87
  code: 'FSTWRN001',
@@ -107,6 +112,7 @@ module.exports = {
107
112
  FSTDEP017,
108
113
  FSTDEP018,
109
114
  FSTDEP019,
115
+ FSTDEP020,
110
116
  FSTWRN001,
111
117
  FSTWRN002
112
118
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.25.2",
3
+ "version": "4.26.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
7
7
  "types": "fastify.d.ts",
8
8
  "scripts": {
9
9
  "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
10
- "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
11
- "benchmark:parser": "npx concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"npx autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"",
10
+ "benchmark": "concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
11
+ "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"",
12
12
  "build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
13
13
  "coverage": "npm run unit -- --coverage-report=html",
14
14
  "coverage:ci": "c8 --reporter=lcov tap --coverage-report=html --no-browser --no-check-coverage",
@@ -141,7 +141,17 @@
141
141
  "bugs": {
142
142
  "url": "https://github.com/fastify/fastify/issues"
143
143
  },
144
- "homepage": "https://www.fastify.dev/",
144
+ "homepage": "https://fastify.dev/",
145
+ "funding": [
146
+ {
147
+ "type": "github",
148
+ "url": "https://github.com/sponsors/fastify"
149
+ },
150
+ {
151
+ "type": "opencollective",
152
+ "url": "https://opencollective.com/fastify"
153
+ }
154
+ ],
145
155
  "devDependencies": {
146
156
  "@fastify/pre-commit": "^2.0.2",
147
157
  "@sinclair/typebox": "^0.31.17",
@@ -154,8 +164,10 @@
154
164
  "ajv-formats": "^2.1.1",
155
165
  "ajv-i18n": "^4.2.0",
156
166
  "ajv-merge-patch": "^5.0.1",
167
+ "autocannon": "^7.14.0",
157
168
  "branch-comparer": "^1.1.0",
158
169
  "c8": "^8.0.1",
170
+ "concurrently": "^8.2.2",
159
171
  "cross-env": "^7.0.3",
160
172
  "eslint": "^8.51.0",
161
173
  "eslint-config-standard": "^17.1.0",
@@ -192,10 +204,10 @@
192
204
  "@fastify/error": "^3.4.0",
193
205
  "@fastify/fast-json-stringify-compiler": "^4.3.0",
194
206
  "abstract-logging": "^2.0.1",
195
- "avvio": "^8.2.1",
207
+ "avvio": "^8.3.0",
196
208
  "fast-content-type-parse": "^1.1.0",
197
209
  "fast-json-stringify": "^5.8.0",
198
- "find-my-way": "^7.7.0",
210
+ "find-my-way": "^8.0.0",
199
211
  "light-my-request": "^5.11.0",
200
212
  "pino": "^8.17.0",
201
213
  "process-warning": "^3.0.0",
@@ -0,0 +1,69 @@
1
+ 'use strict'
2
+
3
+ const { createHook } = require('node:async_hooks')
4
+ const t = require('tap')
5
+ const Fastify = require('..')
6
+ const sget = require('simple-get').concat
7
+
8
+ const remainingIds = new Set()
9
+
10
+ createHook({
11
+ init (asyncId, type, triggerAsyncId, resource) {
12
+ if (type === 'content-type-parser:run') {
13
+ remainingIds.add(asyncId)
14
+ }
15
+ },
16
+ destroy (asyncId) {
17
+ remainingIds.delete(asyncId)
18
+ }
19
+ })
20
+
21
+ const app = Fastify({ logger: false })
22
+
23
+ app.get('/', function (request, reply) {
24
+ reply.send({ id: 42 })
25
+ })
26
+
27
+ app.post('/', function (request, reply) {
28
+ reply.send({ id: 42 })
29
+ })
30
+
31
+ app.listen({ port: 0 }, function (err, address) {
32
+ t.error(err)
33
+
34
+ sget({
35
+ method: 'POST',
36
+ url: 'http://localhost:' + app.server.address().port,
37
+ body: {
38
+ hello: 'world'
39
+ },
40
+ json: true
41
+ }, (err, response, body) => {
42
+ t.error(err)
43
+ t.equal(response.statusCode, 200)
44
+
45
+ sget({
46
+ method: 'POST',
47
+ url: 'http://localhost:' + app.server.address().port,
48
+ body: {
49
+ hello: 'world'
50
+ },
51
+ json: true
52
+ }, (err, response, body) => {
53
+ t.error(err)
54
+ t.equal(response.statusCode, 200)
55
+
56
+ sget({
57
+ method: 'GET',
58
+ url: 'http://localhost:' + app.server.address().port,
59
+ json: true
60
+ }, (err, response, body) => {
61
+ t.error(err)
62
+ t.equal(response.statusCode, 200)
63
+ app.close()
64
+ t.equal(remainingIds.size, 0)
65
+ t.end()
66
+ })
67
+ })
68
+ })
69
+ })
@@ -0,0 +1,135 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const Fastify = require('..')
5
+ const fastifyPlugin = require('fastify-plugin')
6
+
7
+ test('findRoute should return null when route cannot be found due to a different method', t => {
8
+ t.plan(1)
9
+ const fastify = Fastify()
10
+
11
+ fastify.get('/artists/:artistId', {
12
+ schema: {
13
+ params: { artistId: { type: 'integer' } }
14
+ },
15
+ handler: (req, reply) => reply.send(typeof req.params.artistId)
16
+ })
17
+
18
+ t.equal(fastify.findRoute({
19
+ method: 'POST',
20
+ url: '/artists/:artistId'
21
+ }), null)
22
+ })
23
+
24
+ test('findRoute should return an immutable route to avoid leaking and runtime route modifications', t => {
25
+ t.plan(1)
26
+ const fastify = Fastify()
27
+
28
+ fastify.get('/artists/:artistId', {
29
+ schema: {
30
+ params: { artistId: { type: 'integer' } }
31
+ },
32
+ handler: (req, reply) => reply.send(typeof req.params.artistId)
33
+ })
34
+
35
+ let route = fastify.findRoute({
36
+ method: 'GET',
37
+ url: '/artists/:artistId'
38
+ })
39
+
40
+ route.params = {
41
+ ...route.params,
42
+ id: ':id'
43
+ }
44
+
45
+ route = fastify.findRoute({
46
+ method: 'GET',
47
+ url: '/artists/:artistId'
48
+ })
49
+ t.same(route.params, { artistId: ':artistId' })
50
+ })
51
+
52
+ test('findRoute should return null when route cannot be found due to a different path', t => {
53
+ t.plan(1)
54
+ const fastify = Fastify()
55
+
56
+ fastify.get('/artists/:artistId', {
57
+ schema: {
58
+ params: { artistId: { type: 'integer' } }
59
+ },
60
+ handler: (req, reply) => reply.send(typeof req.params.artistId)
61
+ })
62
+
63
+ t.equal(fastify.findRoute({
64
+ method: 'GET',
65
+ url: '/books/:bookId'
66
+ }), null)
67
+ })
68
+
69
+ test('findRoute should return the route when found', t => {
70
+ t.plan(1)
71
+ const fastify = Fastify()
72
+
73
+ const handler = (req, reply) => reply.send(typeof req.params.artistId)
74
+
75
+ fastify.get('/artists/:artistId', {
76
+ schema: {
77
+ params: { artistId: { type: 'integer' } }
78
+ },
79
+ handler
80
+ })
81
+
82
+ const route = fastify.findRoute({
83
+ method: 'GET',
84
+ url: '/artists/:artistId'
85
+ })
86
+
87
+ t.same(route.params, { artistId: ':artistId' })
88
+ })
89
+
90
+ test('findRoute should work correctly when used within plugins', t => {
91
+ t.plan(1)
92
+ const fastify = Fastify()
93
+ const handler = (req, reply) => reply.send(typeof req.params.artistId)
94
+ fastify.get('/artists/:artistId', {
95
+ schema: {
96
+ params: { artistId: { type: 'integer' } }
97
+ },
98
+ handler
99
+ })
100
+
101
+ function validateRoutePlugin (instance, opts, done) {
102
+ const validateParams = function () {
103
+ return instance.findRoute({
104
+ method: 'GET',
105
+ url: '/artists/:artistId'
106
+ }) !== null
107
+ }
108
+ instance.decorate('validateRoutes', { validateParams })
109
+ done()
110
+ }
111
+
112
+ fastify.register(fastifyPlugin(validateRoutePlugin))
113
+
114
+ fastify.ready(() => {
115
+ t.equal(fastify.validateRoutes.validateParams(), true)
116
+ })
117
+ })
118
+
119
+ test('findRoute should not expose store', t => {
120
+ t.plan(1)
121
+ const fastify = Fastify()
122
+
123
+ fastify.get('/artists/:artistId', {
124
+ schema: {
125
+ params: { artistId: { type: 'integer' } }
126
+ },
127
+ handler: (req, reply) => reply.send(typeof req.params.artistId)
128
+ })
129
+
130
+ const route = fastify.findRoute({
131
+ method: 'GET',
132
+ url: '/artists/:artistId'
133
+ })
134
+ t.equal(route.store, undefined)
135
+ })