fastify 3.26.0 → 4.0.0-alpha.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 (129) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +49 -35
  4. package/docs/Guides/Ecosystem.md +2 -1
  5. package/docs/Guides/Prototype-Poisoning.md +3 -3
  6. package/docs/Migration-Guide-V4.md +12 -0
  7. package/docs/Reference/ContentTypeParser.md +8 -1
  8. package/docs/Reference/Errors.md +51 -6
  9. package/docs/Reference/Hooks.md +4 -7
  10. package/docs/Reference/LTS.md +5 -4
  11. package/docs/Reference/Reply.md +23 -22
  12. package/docs/Reference/Request.md +1 -3
  13. package/docs/Reference/Routes.md +17 -10
  14. package/docs/Reference/Server.md +98 -63
  15. package/docs/Reference/TypeScript.md +11 -13
  16. package/docs/Reference/Validation-and-Serialization.md +32 -54
  17. package/docs/Type-Providers.md +257 -0
  18. package/examples/hooks.js +1 -1
  19. package/examples/simple-stream.js +18 -0
  20. package/fastify.d.ts +36 -22
  21. package/fastify.js +72 -53
  22. package/lib/configValidator.js +902 -1023
  23. package/lib/contentTypeParser.js +6 -16
  24. package/lib/context.js +36 -10
  25. package/lib/decorate.js +5 -3
  26. package/lib/error-handler.js +158 -0
  27. package/lib/error-serializer.js +257 -0
  28. package/lib/errors.js +49 -10
  29. package/lib/fourOhFour.js +31 -20
  30. package/lib/handleRequest.js +10 -13
  31. package/lib/hooks.js +14 -9
  32. package/lib/noop-set.js +10 -0
  33. package/lib/pluginOverride.js +0 -3
  34. package/lib/pluginUtils.js +3 -2
  35. package/lib/reply.js +44 -163
  36. package/lib/request.js +13 -10
  37. package/lib/route.js +158 -139
  38. package/lib/schema-controller.js +3 -3
  39. package/lib/schemas.js +27 -1
  40. package/lib/server.js +219 -116
  41. package/lib/symbols.js +6 -4
  42. package/lib/validation.js +2 -1
  43. package/lib/warnings.js +2 -12
  44. package/lib/wrapThenable.js +4 -11
  45. package/package.json +40 -45
  46. package/test/404s.test.js +265 -108
  47. package/test/500s.test.js +2 -2
  48. package/test/async-await.test.js +15 -71
  49. package/test/close.test.js +39 -1
  50. package/test/content-parser.test.js +32 -0
  51. package/test/context-config.test.js +56 -4
  52. package/test/custom-http-server.test.js +14 -7
  53. package/test/custom-parser-async.test.js +0 -65
  54. package/test/custom-parser.test.js +54 -121
  55. package/test/decorator.test.js +1 -3
  56. package/test/delete.test.js +5 -5
  57. package/test/encapsulated-error-handler.test.js +50 -0
  58. package/test/esm/index.test.js +0 -14
  59. package/test/fastify-instance.test.js +4 -4
  60. package/test/fluent-schema.test.js +4 -4
  61. package/test/get.test.js +3 -3
  62. package/test/helper.js +18 -3
  63. package/test/hooks-async.test.js +14 -47
  64. package/test/hooks.on-ready.test.js +9 -4
  65. package/test/hooks.test.js +58 -99
  66. package/test/http2/closing.test.js +5 -11
  67. package/test/http2/unknown-http-method.test.js +3 -9
  68. package/test/https/custom-https-server.test.js +12 -6
  69. package/test/inject.test.js +1 -1
  70. package/test/input-validation.js +2 -2
  71. package/test/internals/all.test.js +2 -2
  72. package/test/internals/contentTypeParser.test.js +4 -4
  73. package/test/internals/handleRequest.test.js +9 -46
  74. package/test/internals/initialConfig.test.js +33 -12
  75. package/test/internals/logger.test.js +1 -1
  76. package/test/internals/reply.test.js +245 -3
  77. package/test/internals/request.test.js +13 -7
  78. package/test/internals/server.test.js +88 -0
  79. package/test/listen.test.js +84 -1
  80. package/test/logger.test.js +98 -58
  81. package/test/maxRequestsPerSocket.test.js +8 -6
  82. package/test/middleware.test.js +2 -25
  83. package/test/noop-set.test.js +19 -0
  84. package/test/nullable-validation.test.js +51 -14
  85. package/test/plugin.test.js +31 -5
  86. package/test/pretty-print.test.js +22 -10
  87. package/test/reply-error.test.js +123 -12
  88. package/test/request-error.test.js +2 -5
  89. package/test/route-hooks.test.js +17 -17
  90. package/test/route-prefix.test.js +2 -1
  91. package/test/route.test.js +216 -20
  92. package/test/router-options.test.js +1 -1
  93. package/test/schema-examples.test.js +11 -5
  94. package/test/schema-feature.test.js +24 -19
  95. package/test/schema-serialization.test.js +50 -9
  96. package/test/schema-special-usage.test.js +14 -81
  97. package/test/schema-validation.test.js +9 -9
  98. package/test/skip-reply-send.test.js +8 -8
  99. package/test/stream.test.js +23 -12
  100. package/test/throw.test.js +8 -5
  101. package/test/trust-proxy.test.js +1 -1
  102. package/test/type-provider.test.js +20 -0
  103. package/test/types/fastify.test-d.ts +12 -18
  104. package/test/types/hooks.test-d.ts +7 -3
  105. package/test/types/import.js +2 -0
  106. package/test/types/import.ts +1 -0
  107. package/test/types/instance.test-d.ts +61 -15
  108. package/test/types/logger.test-d.ts +44 -15
  109. package/test/types/route.test-d.ts +8 -2
  110. package/test/types/schema.test-d.ts +2 -39
  111. package/test/types/type-provider.test-d.ts +417 -0
  112. package/test/validation-error-handling.test.js +9 -9
  113. package/test/versioned-routes.test.js +29 -17
  114. package/test/wrapThenable.test.js +7 -6
  115. package/types/.eslintrc.json +1 -1
  116. package/types/content-type-parser.d.ts +17 -8
  117. package/types/hooks.d.ts +107 -60
  118. package/types/instance.d.ts +137 -105
  119. package/types/logger.d.ts +18 -104
  120. package/types/plugin.d.ts +10 -4
  121. package/types/register.d.ts +1 -1
  122. package/types/reply.d.ts +16 -11
  123. package/types/request.d.ts +10 -5
  124. package/types/route.d.ts +42 -31
  125. package/types/schema.d.ts +15 -1
  126. package/types/type-provider.d.ts +99 -0
  127. package/types/utils.d.ts +1 -1
  128. package/lib/schema-compilers.js +0 -12
  129. package/test/emit-warning.test.js +0 -166
@@ -3,7 +3,6 @@
3
3
  const t = require('tap')
4
4
  const Fastify = require('../..')
5
5
  const http2 = require('http2')
6
- const semver = require('semver')
7
6
  const { promisify } = require('util')
8
7
  const connect = promisify(http2.connect)
9
8
  const { once } = require('events')
@@ -37,8 +36,7 @@ t.test('http/2 request while fastify closing', t => {
37
36
  t.error(err)
38
37
  fastify.server.unref()
39
38
 
40
- // Skipped because there is likely a bug on Node 8.
41
- t.test('return 200', { skip: semver.lt(process.versions.node, '10.15.0') }, t => {
39
+ t.test('return 200', t => {
42
40
  const url = getUrl(fastify)
43
41
  const session = http2.connect(url, function () {
44
42
  this.request({
@@ -85,8 +83,7 @@ t.test('http/2 request while fastify closing - return503OnClosing: false', t =>
85
83
  t.error(err)
86
84
  fastify.server.unref()
87
85
 
88
- // Skipped because there is likely a bug on Node 8.
89
- t.test('return 200', { skip: semver.lt(process.versions.node, '10.15.0') }, t => {
86
+ t.test('return 200', t => {
90
87
  const url = getUrl(fastify)
91
88
  const session = http2.connect(url, function () {
92
89
  this.request({
@@ -115,8 +112,7 @@ t.test('http/2 request while fastify closing - return503OnClosing: false', t =>
115
112
  })
116
113
  })
117
114
 
118
- // Skipped because there is likely a bug on Node 8.
119
- t.test('http/2 closes successfully with async await', { skip: semver.lt(process.versions.node, '10.15.0') }, async t => {
115
+ t.test('http/2 closes successfully with async await', async t => {
120
116
  const fastify = Fastify({
121
117
  http2SessionTimeout: 100,
122
118
  http2: true
@@ -131,8 +127,7 @@ t.test('http/2 closes successfully with async await', { skip: semver.lt(process.
131
127
  await fastify.close()
132
128
  })
133
129
 
134
- // Skipped because there is likely a bug on Node 8.
135
- t.test('https/2 closes successfully with async await', { skip: semver.lt(process.versions.node, '10.15.0') }, async t => {
130
+ t.test('https/2 closes successfully with async await', async t => {
136
131
  const fastify = Fastify({
137
132
  http2SessionTimeout: 100,
138
133
  http2: true,
@@ -151,8 +146,7 @@ t.test('https/2 closes successfully with async await', { skip: semver.lt(process
151
146
  await fastify.close()
152
147
  })
153
148
 
154
- // Skipped because there is likely a bug on Node 8.
155
- t.test('http/2 server side session emits a timeout event', { skip: semver.lt(process.versions.node, '10.15.0') }, async t => {
149
+ t.test('http/2 server side session emits a timeout event', async t => {
156
150
  let _resolve
157
151
  const p = new Promise((resolve) => { _resolve = resolve })
158
152
 
@@ -6,15 +6,9 @@ const Fastify = require('../..')
6
6
  const h2url = require('h2url')
7
7
  const msg = { hello: 'world' }
8
8
 
9
- let fastify
10
- try {
11
- fastify = Fastify({
12
- http2: true
13
- })
14
- t.pass('http2 successfully loaded')
15
- } catch (e) {
16
- t.fail('http2 loading failed', e)
17
- }
9
+ const fastify = Fastify({
10
+ http2: true
11
+ })
18
12
 
19
13
  fastify.get('/', function (req, reply) {
20
14
  reply.code(200).send(msg)
@@ -5,15 +5,18 @@ const test = t.test
5
5
  const Fastify = require('../..')
6
6
  const https = require('https')
7
7
  const sget = require('simple-get').concat
8
+ const dns = require('dns').promises
8
9
 
9
10
  const { buildCertificate } = require('../build-certificate')
10
11
  t.before(buildCertificate)
11
12
 
12
- test('Should support a custom https server', t => {
13
- t.plan(6)
13
+ test('Should support a custom https server', async t => {
14
+ const localAddresses = await dns.lookup('localhost', { all: true })
15
+
16
+ t.plan(localAddresses.length + 3)
14
17
 
15
18
  const serverFactory = (handler, opts) => {
16
- t.ok(opts.serverFactory)
19
+ t.ok(opts.serverFactory, 'it is called twice for every HOST interface')
17
20
 
18
21
  const options = {
19
22
  key: global.context.key,
@@ -37,17 +40,20 @@ test('Should support a custom https server', t => {
37
40
  reply.send({ hello: 'world' })
38
41
  })
39
42
 
40
- fastify.listen(0, err => {
41
- t.error(err)
43
+ await fastify.listen(0)
42
44
 
45
+ await new Promise((resolve, reject) => {
43
46
  sget({
44
47
  method: 'GET',
45
48
  url: 'https://localhost:' + fastify.server.address().port,
46
49
  rejectUnauthorized: false
47
50
  }, (err, response, body) => {
48
- t.error(err)
51
+ if (err) {
52
+ return reject(err)
53
+ }
49
54
  t.equal(response.statusCode, 200)
50
55
  t.same(JSON.parse(body), { hello: 'world' })
56
+ resolve()
51
57
  })
52
58
  })
53
59
  })
@@ -186,7 +186,7 @@ test('inject post request', t => {
186
186
  fastify.inject({
187
187
  method: 'POST',
188
188
  url: '/',
189
- payload: payload
189
+ payload
190
190
  }, (err, res) => {
191
191
  t.error(err)
192
192
  t.same(payload, JSON.parse(res.payload))
@@ -3,7 +3,7 @@
3
3
 
4
4
  const sget = require('simple-get').concat
5
5
  const Ajv = require('ajv')
6
- const Joi = require('@hapi/joi')
6
+ const Joi = require('joi')
7
7
  const yup = require('yup')
8
8
 
9
9
  module.exports.payloadMethod = function (method, t) {
@@ -182,7 +182,7 @@ module.exports.payloadMethod = function (method, t) {
182
182
  t.equal(response.statusCode, 400)
183
183
  t.same(body, {
184
184
  error: 'Bad Request',
185
- message: 'body.hello should be integer',
185
+ message: 'body/hello must be integer',
186
186
  statusCode: 400
187
187
  })
188
188
  })
@@ -19,7 +19,7 @@ test('fastify.all should add all the methods to the same url', t => {
19
19
  function injectRequest (method) {
20
20
  const options = {
21
21
  url: '/',
22
- method: method
22
+ method
23
23
  }
24
24
 
25
25
  if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
@@ -29,7 +29,7 @@ test('fastify.all should add all the methods to the same url', t => {
29
29
  fastify.inject(options, (err, res) => {
30
30
  t.error(err)
31
31
  const payload = JSON.parse(res.payload)
32
- t.same(payload, { method: method })
32
+ t.same(payload, { method })
33
33
  })
34
34
  }
35
35
  })
@@ -32,8 +32,8 @@ test('rawBody function', t => {
32
32
 
33
33
  res.log = { error: () => { }, info: () => { } }
34
34
  const context = {
35
- Reply: Reply,
36
- Request: Request,
35
+ Reply,
36
+ Request,
37
37
  preHandler: [],
38
38
  onSend: [],
39
39
  _parserOptions: {
@@ -85,8 +85,8 @@ test('Should support Webpack and faux modules', t => {
85
85
 
86
86
  res.log = { error: () => { }, info: () => { } }
87
87
  const context = {
88
- Reply: Reply,
89
- Request: Request,
88
+ Reply,
89
+ Request,
90
90
  preHandler: [],
91
91
  onSend: [],
92
92
  _parserOptions: {
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('tap')
4
- const semver = require('semver')
5
4
  const handleRequest = require('../../lib/handleRequest')
6
5
  const internals = require('../../lib/handleRequest')[Symbol.for('internals')]
7
6
  const Request = require('../../lib/request')
@@ -40,13 +39,8 @@ test('handleRequest function - invoke with error', t => {
40
39
  })
41
40
 
42
41
  test('handler function - invalid schema', t => {
43
- t.plan(2)
42
+ t.plan(1)
44
43
  const res = {}
45
- res.end = () => {
46
- t.equal(res.statusCode, 400)
47
- t.pass()
48
- }
49
- res.writeHead = () => {}
50
44
  res.log = { error: () => {}, info: () => {} }
51
45
  const context = {
52
46
  config: {
@@ -61,9 +55,10 @@ test('handler function - invalid schema', t => {
61
55
  }
62
56
  }
63
57
  },
58
+ errorHandler: { func: () => { t.pass('errorHandler called') } },
64
59
  handler: () => {},
65
- Reply: Reply,
66
- Request: Request,
60
+ Reply,
61
+ Request,
67
62
  preValidation: [],
68
63
  preHandler: [],
69
64
  onSend: [],
@@ -93,8 +88,8 @@ test('handler function - reply', t => {
93
88
  reply.code(204)
94
89
  reply.send(undefined)
95
90
  },
96
- Reply: Reply,
97
- Request: Request,
91
+ Reply,
92
+ Request,
98
93
  preValidation: [],
99
94
  preHandler: [],
100
95
  onSend: [],
@@ -108,39 +103,7 @@ test('handler function - preValidationCallback with finished response', t => {
108
103
  t.plan(0)
109
104
  const res = {}
110
105
  // Be sure to check only `writableEnded` where is available
111
- if (semver.gte(process.versions.node, '12.9.0')) {
112
- res.writableEnded = true
113
- } else {
114
- res.writable = false
115
- res.finished = true
116
- }
117
- res.end = () => {
118
- t.fail()
119
- }
120
- res.writeHead = () => {}
121
- const context = {
122
- handler: (req, reply) => {
123
- t.fail()
124
- reply.send(undefined)
125
- },
126
- Reply: Reply,
127
- Request: Request,
128
- preValidation: null,
129
- preHandler: [],
130
- onSend: [],
131
- onError: []
132
- }
133
- buildSchema(context, schemaValidator)
134
- internals.handler({}, new Reply(res, { context }))
135
- })
136
-
137
- test('handler function - preValidationCallback with finished response (< v12.9.0)', t => {
138
- t.plan(0)
139
- const res = {}
140
- // Be sure to check only `writableEnded` where is available
141
- res.writable = false
142
- res.finished = true
143
-
106
+ res.writableEnded = true
144
107
  res.end = () => {
145
108
  t.fail()
146
109
  }
@@ -150,8 +113,8 @@ test('handler function - preValidationCallback with finished response (< v12.9.0
150
113
  t.fail()
151
114
  reply.send(undefined)
152
115
  },
153
- Reply: Reply,
154
- Request: Request,
116
+ Reply,
117
+ Request,
155
118
  preValidation: null,
156
119
  preHandler: [],
157
120
  onSend: [],
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { test, before } = require('tap')
4
4
  const Fastify = require('../..')
5
+ const helper = require('../helper')
5
6
  const http = require('http')
6
7
  const pino = require('pino')
7
8
  const split = require('split2')
@@ -9,10 +10,17 @@ const deepClone = require('rfdc')({ circles: true, proto: false })
9
10
  const { deepFreezeObject } = require('../../lib/initialConfigValidation').utils
10
11
 
11
12
  const { buildCertificate } = require('../build-certificate')
12
- before(buildCertificate)
13
13
 
14
14
  process.removeAllListeners('warning')
15
15
 
16
+ let localhost
17
+ let localhostForURL
18
+
19
+ before(async function () {
20
+ await buildCertificate();
21
+ [localhost, localhostForURL] = await helper.getLoopbackHost()
22
+ })
23
+
16
24
  test('Fastify.initialConfig is an object', t => {
17
25
  t.plan(1)
18
26
  t.type(Fastify().initialConfig, 'object')
@@ -23,7 +31,8 @@ test('without options passed to Fastify, initialConfig should expose default val
23
31
 
24
32
  const fastifyDefaultOptions = {
25
33
  connectionTimeout: 0,
26
- keepAliveTimeout: 5000,
34
+ keepAliveTimeout: 72000,
35
+ forceCloseConnections: false,
27
36
  maxRequestsPerSocket: 0,
28
37
  requestTimeout: 0,
29
38
  bodyLimit: 1024 * 1024,
@@ -37,7 +46,8 @@ test('without options passed to Fastify, initialConfig should expose default val
37
46
  pluginTimeout: 10000,
38
47
  requestIdHeader: 'request-id',
39
48
  requestIdLogLabel: 'reqId',
40
- http2SessionTimeout: 5000
49
+ http2SessionTimeout: 72000,
50
+ exposeHeadRoutes: true
41
51
  }
42
52
 
43
53
  t.same(Fastify().initialConfig, fastifyDefaultOptions)
@@ -81,7 +91,7 @@ test('Fastify.initialConfig should expose all options', t => {
81
91
  ignoreTrailingSlash: true,
82
92
  maxParamLength: 200,
83
93
  connectionTimeout: 0,
84
- keepAliveTimeout: 5000,
94
+ keepAliveTimeout: 72000,
85
95
  bodyLimit: 1049600,
86
96
  onProtoPoisoning: 'remove',
87
97
  serverFactory,
@@ -103,11 +113,11 @@ test('Fastify.initialConfig should expose all options', t => {
103
113
 
104
114
  const fastify = Fastify(options)
105
115
  t.equal(fastify.initialConfig.http2, true)
106
- t.equal(fastify.initialConfig.https, true)
116
+ t.equal(fastify.initialConfig.https, true, 'for security reason the key cert is hidden')
107
117
  t.equal(fastify.initialConfig.ignoreTrailingSlash, true)
108
118
  t.equal(fastify.initialConfig.maxParamLength, 200)
109
119
  t.equal(fastify.initialConfig.connectionTimeout, 0)
110
- t.equal(fastify.initialConfig.keepAliveTimeout, 5000)
120
+ t.equal(fastify.initialConfig.keepAliveTimeout, 72000)
111
121
  t.equal(fastify.initialConfig.bodyLimit, 1049600)
112
122
  t.equal(fastify.initialConfig.onProtoPoisoning, 'remove')
113
123
  t.equal(fastify.initialConfig.caseSensitive, true)
@@ -157,10 +167,19 @@ test('We must avoid shallow freezing and ensure that the whole object is freezed
157
167
  t.type(error, TypeError)
158
168
  t.equal(error.message, "Cannot assign to read only property 'allowHTTP1' of object '#<Object>'")
159
169
  t.ok(error.stack)
160
- t.pass()
170
+ t.same(fastify.initialConfig.https, {
171
+ allowHTTP1: true
172
+ }, 'key cert removed')
161
173
  }
162
174
  })
163
175
 
176
+ test('https value check', t => {
177
+ t.plan(1)
178
+
179
+ const fastify = Fastify({})
180
+ t.notOk(fastify.initialConfig.https)
181
+ })
182
+
164
183
  test('Return an error if options do not match the validation schema', t => {
165
184
  t.plan(6)
166
185
 
@@ -171,7 +190,7 @@ test('Return an error if options do not match the validation schema', t => {
171
190
  } catch (error) {
172
191
  t.type(error, Error)
173
192
  t.equal(error.name, 'FastifyError')
174
- t.equal(error.message, 'Invalid initialization options: \'["should be boolean"]\'')
193
+ t.equal(error.message, 'Invalid initialization options: \'["must be boolean"]\'')
175
194
  t.equal(error.code, 'FST_ERR_INIT_OPTS_INVALID')
176
195
  t.ok(error.stack)
177
196
  t.pass()
@@ -241,7 +260,8 @@ test('Should not have issues when passing stream options to Pino.js', t => {
241
260
  t.type(fastify, 'object')
242
261
  t.same(fastify.initialConfig, {
243
262
  connectionTimeout: 0,
244
- keepAliveTimeout: 5000,
263
+ keepAliveTimeout: 72000,
264
+ forceCloseConnections: false,
245
265
  maxRequestsPerSocket: 0,
246
266
  requestTimeout: 0,
247
267
  bodyLimit: 1024 * 1024,
@@ -255,7 +275,8 @@ test('Should not have issues when passing stream options to Pino.js', t => {
255
275
  pluginTimeout: 10000,
256
276
  requestIdHeader: 'request-id',
257
277
  requestIdLogLabel: 'reqId',
258
- http2SessionTimeout: 5000
278
+ http2SessionTimeout: 72000,
279
+ exposeHeadRoutes: true
259
280
  })
260
281
  } catch (error) {
261
282
  t.fail()
@@ -287,11 +308,11 @@ test('Should not have issues when passing stream options to Pino.js', t => {
287
308
  })
288
309
  })
289
310
 
290
- fastify.listen(0, err => {
311
+ fastify.listen(0, localhost, err => {
291
312
  t.error(err)
292
313
  fastify.server.unref()
293
314
 
294
- http.get('http://localhost:' + fastify.server.address().port)
315
+ http.get(`http://${localhostForURL}:${fastify.server.address().port}`)
295
316
  })
296
317
  })
297
318
 
@@ -103,7 +103,7 @@ test('The logger should error if both stream and file destination are given', t
103
103
  Fastify({
104
104
  logger: {
105
105
  level: 'info',
106
- stream: stream,
106
+ stream,
107
107
  file: '/test'
108
108
  }
109
109
  })