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.
- package/EXPENSE_POLICY.md +105 -0
- package/GOVERNANCE.md +2 -103
- package/LICENSE +1 -1
- package/README.md +13 -9
- package/SECURITY.md +2 -157
- package/SPONSORS.md +20 -0
- package/build/build-validation.js +3 -1
- package/docs/Guides/Ecosystem.md +29 -9
- package/docs/Guides/Getting-Started.md +16 -3
- package/docs/Guides/Style-Guide.md +7 -7
- package/docs/Reference/Decorators.md +1 -1
- package/docs/Reference/Errors.md +63 -1
- package/docs/Reference/Hooks.md +1 -1
- package/docs/Reference/Logging.md +3 -3
- package/docs/Reference/Reply.md +70 -1
- package/docs/Reference/Server.md +90 -0
- package/docs/Reference/Warnings.md +17 -2
- package/fastify.d.ts +3 -2
- package/fastify.js +25 -7
- package/lib/configValidator.js +62 -33
- package/lib/contentTypeParser.js +9 -2
- package/lib/error-handler.js +1 -1
- package/lib/error-serializer.js +30 -29
- package/lib/errors.js +6 -1
- package/lib/fourOhFour.js +4 -3
- package/lib/hooks.js +1 -5
- package/lib/reply.js +68 -10
- package/lib/reqIdGenFactory.js +5 -0
- package/lib/route.js +22 -6
- package/lib/schema-controller.js +37 -4
- package/lib/symbols.js +1 -0
- package/lib/warnings.js +6 -0
- package/package.json +18 -6
- package/test/async_hooks.test.js +69 -0
- package/test/findRoute.test.js +135 -0
- package/test/genReqId.test.js +392 -0
- package/test/hooks.on-listen.test.js +66 -14
- package/test/hooks.on-ready.test.js +1 -1
- package/test/internals/errors.test.js +17 -7
- package/test/internals/initialConfig.test.js +7 -3
- package/test/internals/reply.test.js +80 -5
- package/test/plugin.4.test.js +3 -3
- package/test/pretty-print.test.js +1 -1
- package/test/schema-serialization.test.js +41 -0
- package/test/schema-validation.test.js +115 -6
- package/test/serialize-response.test.js +187 -0
- package/test/types/instance.test-d.ts +14 -1
- package/test/types/reply.test-d.ts +4 -2
- package/test/types/request.test-d.ts +1 -1
- package/test/types/route.test-d.ts +15 -1
- package/test/useSemicolonDelimiter.test.js +113 -0
- package/test/web-api.test.js +208 -0
- package/types/instance.d.ts +23 -10
- package/types/reply.d.ts +4 -0
- package/types/request.d.ts +5 -4
package/lib/schema-controller.js
CHANGED
|
@@ -39,12 +39,10 @@ function buildSchemaController (parentSchemaCtrl, opts) {
|
|
|
39
39
|
|
|
40
40
|
class SchemaController {
|
|
41
41
|
constructor (parent, options) {
|
|
42
|
-
this.opts = options ||
|
|
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.
|
|
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": "
|
|
11
|
-
"benchmark:parser": "
|
|
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://
|
|
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.
|
|
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": "^
|
|
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
|
+
})
|