fastify 3.27.4 → 4.0.0-alpha.3

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 (168) hide show
  1. package/.taprc +3 -0
  2. package/README.md +7 -7
  3. package/build/build-error-serializer.js +27 -0
  4. package/build/build-validation.js +47 -35
  5. package/docs/Guides/Database.md +320 -0
  6. package/docs/Guides/Ecosystem.md +9 -0
  7. package/docs/Guides/Getting-Started.md +7 -7
  8. package/docs/Guides/Plugins-Guide.md +1 -1
  9. package/docs/Guides/Serverless.md +3 -3
  10. package/docs/Guides/Testing.md +2 -2
  11. package/docs/Migration-Guide-V4.md +12 -0
  12. package/docs/Reference/ContentTypeParser.md +4 -0
  13. package/docs/Reference/Decorators.md +2 -2
  14. package/docs/Reference/Encapsulation.md +2 -2
  15. package/docs/Reference/Errors.md +51 -6
  16. package/docs/Reference/HTTP2.md +3 -3
  17. package/docs/Reference/Hooks.md +4 -7
  18. package/docs/Reference/LTS.md +5 -4
  19. package/docs/Reference/Plugins.md +3 -3
  20. package/docs/Reference/Reply.md +73 -22
  21. package/docs/Reference/Request.md +1 -3
  22. package/docs/Reference/Routes.md +22 -15
  23. package/docs/Reference/Server.md +69 -119
  24. package/docs/Reference/TypeScript.md +20 -22
  25. package/docs/Reference/Validation-and-Serialization.md +30 -55
  26. package/docs/Type-Providers.md +257 -0
  27. package/examples/asyncawait.js +1 -1
  28. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  29. package/examples/benchmark/hooks-benchmark.js +1 -1
  30. package/examples/benchmark/simple.js +1 -1
  31. package/examples/hooks.js +2 -2
  32. package/examples/http2.js +1 -1
  33. package/examples/https.js +1 -1
  34. package/examples/parser.js +13 -3
  35. package/examples/route-prefix.js +1 -1
  36. package/examples/shared-schema.js +1 -1
  37. package/examples/simple-stream.js +18 -0
  38. package/examples/simple.js +1 -1
  39. package/examples/simple.mjs +1 -1
  40. package/examples/typescript-server.ts +1 -1
  41. package/examples/use-plugin.js +1 -1
  42. package/fastify.d.ts +34 -22
  43. package/fastify.js +40 -36
  44. package/lib/configValidator.js +902 -1023
  45. package/lib/contentTypeParser.js +6 -16
  46. package/lib/context.js +36 -10
  47. package/lib/decorate.js +3 -1
  48. package/lib/error-handler.js +158 -0
  49. package/lib/error-serializer.js +257 -0
  50. package/lib/errors.js +51 -9
  51. package/lib/fourOhFour.js +31 -20
  52. package/lib/handleRequest.js +10 -13
  53. package/lib/hooks.js +14 -9
  54. package/lib/pluginOverride.js +0 -3
  55. package/lib/pluginUtils.js +3 -2
  56. package/lib/reply.js +121 -175
  57. package/lib/request.js +13 -10
  58. package/lib/route.js +131 -138
  59. package/lib/schema-controller.js +2 -2
  60. package/lib/schemas.js +27 -1
  61. package/lib/server.js +242 -116
  62. package/lib/symbols.js +5 -3
  63. package/lib/validation.js +11 -9
  64. package/lib/warnings.js +4 -12
  65. package/lib/wrapThenable.js +4 -11
  66. package/package.json +37 -39
  67. package/test/404s.test.js +258 -125
  68. package/test/500s.test.js +3 -3
  69. package/test/als.test.js +1 -1
  70. package/test/async-await.test.js +20 -76
  71. package/test/bodyLimit.test.js +1 -1
  72. package/test/build-certificate.js +6 -7
  73. package/test/case-insensitive.test.js +4 -4
  74. package/test/close-pipelining.test.js +2 -2
  75. package/test/close.test.js +11 -11
  76. package/test/content-parser.test.js +32 -0
  77. package/test/context-config.test.js +52 -0
  78. package/test/custom-http-server.test.js +14 -7
  79. package/test/custom-parser-async.test.js +1 -66
  80. package/test/custom-parser.test.js +92 -159
  81. package/test/custom-querystring-parser.test.js +3 -3
  82. package/test/decorator.test.js +11 -13
  83. package/test/delete.test.js +6 -6
  84. package/test/encapsulated-error-handler.test.js +50 -0
  85. package/test/esm/index.test.js +0 -14
  86. package/test/fastify-instance.test.js +4 -4
  87. package/test/fluent-schema.test.js +4 -4
  88. package/test/genReqId.test.js +1 -1
  89. package/test/get.test.js +4 -4
  90. package/test/handler-context.test.js +2 -2
  91. package/test/head.test.js +1 -1
  92. package/test/helper.js +19 -4
  93. package/test/hooks-async.test.js +15 -48
  94. package/test/hooks.on-ready.test.js +10 -5
  95. package/test/hooks.test.js +78 -119
  96. package/test/http2/closing.test.js +10 -16
  97. package/test/http2/constraint.test.js +1 -1
  98. package/test/http2/head.test.js +1 -1
  99. package/test/http2/plain.test.js +1 -1
  100. package/test/http2/secure-with-fallback.test.js +1 -1
  101. package/test/http2/secure.test.js +1 -1
  102. package/test/http2/unknown-http-method.test.js +4 -10
  103. package/test/https/custom-https-server.test.js +12 -6
  104. package/test/https/https.test.js +1 -1
  105. package/test/input-validation.js +3 -3
  106. package/test/internals/handleRequest.test.js +6 -43
  107. package/test/internals/initialConfig.test.js +41 -12
  108. package/test/internals/logger.test.js +2 -2
  109. package/test/internals/reply.test.js +317 -48
  110. package/test/internals/request.test.js +13 -7
  111. package/test/internals/server.test.js +88 -0
  112. package/test/listen.deprecated.test.js +202 -0
  113. package/test/listen.test.js +140 -145
  114. package/test/logger.test.js +82 -42
  115. package/test/maxRequestsPerSocket.test.js +8 -6
  116. package/test/middleware.test.js +2 -25
  117. package/test/nullable-validation.test.js +53 -16
  118. package/test/output-validation.test.js +1 -1
  119. package/test/plugin.test.js +47 -21
  120. package/test/pretty-print.test.js +22 -10
  121. package/test/promises.test.js +1 -1
  122. package/test/proto-poisoning.test.js +6 -6
  123. package/test/register.test.js +3 -3
  124. package/test/reply-error.test.js +126 -15
  125. package/test/reply-trailers.test.js +270 -0
  126. package/test/request-error.test.js +3 -6
  127. package/test/route-hooks.test.js +18 -18
  128. package/test/route-prefix.test.js +2 -1
  129. package/test/route.test.js +206 -22
  130. package/test/router-options.test.js +2 -2
  131. package/test/schema-examples.test.js +11 -5
  132. package/test/schema-feature.test.js +25 -20
  133. package/test/schema-serialization.test.js +9 -9
  134. package/test/schema-special-usage.test.js +5 -153
  135. package/test/schema-validation.test.js +9 -9
  136. package/test/skip-reply-send.test.js +2 -2
  137. package/test/stream.test.js +82 -23
  138. package/test/throw.test.js +8 -5
  139. package/test/trust-proxy.test.js +6 -6
  140. package/test/type-provider.test.js +20 -0
  141. package/test/types/fastify.test-d.ts +10 -18
  142. package/test/types/hooks.test-d.ts +61 -5
  143. package/test/types/import.js +2 -0
  144. package/test/types/import.ts +1 -0
  145. package/test/types/instance.test-d.ts +68 -17
  146. package/test/types/logger.test-d.ts +44 -15
  147. package/test/types/reply.test-d.ts +2 -1
  148. package/test/types/request.test-d.ts +71 -1
  149. package/test/types/route.test-d.ts +8 -2
  150. package/test/types/schema.test-d.ts +2 -39
  151. package/test/types/type-provider.test-d.ts +424 -0
  152. package/test/url-rewriting.test.js +3 -3
  153. package/test/validation-error-handling.test.js +8 -8
  154. package/test/versioned-routes.test.js +30 -18
  155. package/test/wrapThenable.test.js +7 -6
  156. package/types/content-type-parser.d.ts +17 -8
  157. package/types/hooks.d.ts +182 -85
  158. package/types/instance.d.ts +286 -118
  159. package/types/logger.d.ts +18 -104
  160. package/types/plugin.d.ts +10 -4
  161. package/types/reply.d.ts +18 -12
  162. package/types/request.d.ts +13 -8
  163. package/types/route.d.ts +62 -34
  164. package/types/schema.d.ts +1 -1
  165. package/types/type-provider.d.ts +99 -0
  166. package/types/utils.d.ts +1 -1
  167. package/lib/schema-compilers.js +0 -12
  168. package/test/emit-warning.test.js +0 -166
@@ -0,0 +1,270 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const Fastify = require('..')
6
+ const { Readable } = require('stream')
7
+ const { createHash } = require('crypto')
8
+
9
+ test('send trailers when payload is empty string', t => {
10
+ t.plan(4)
11
+
12
+ const fastify = Fastify()
13
+
14
+ fastify.get('/', function (request, reply) {
15
+ reply.trailer('ETag', function (reply, payload) {
16
+ return 'custom-etag'
17
+ })
18
+ reply.send('')
19
+ })
20
+
21
+ fastify.inject({
22
+ method: 'GET',
23
+ url: '/'
24
+ }, (error, res) => {
25
+ t.error(error)
26
+ t.equal(res.statusCode, 200)
27
+ t.equal(res.headers.trailer, 'etag')
28
+ t.equal(res.trailers.etag, 'custom-etag')
29
+ })
30
+ })
31
+
32
+ test('send trailers when payload is empty buffer', t => {
33
+ t.plan(4)
34
+
35
+ const fastify = Fastify()
36
+
37
+ fastify.get('/', function (request, reply) {
38
+ reply.trailer('ETag', function (reply, payload) {
39
+ return 'custom-etag'
40
+ })
41
+ reply.send(Buffer.alloc(0))
42
+ })
43
+
44
+ fastify.inject({
45
+ method: 'GET',
46
+ url: '/'
47
+ }, (error, res) => {
48
+ t.error(error)
49
+ t.equal(res.statusCode, 200)
50
+ t.equal(res.headers.trailer, 'etag')
51
+ t.equal(res.trailers.etag, 'custom-etag')
52
+ })
53
+ })
54
+
55
+ test('send trailers when payload is undefined', t => {
56
+ t.plan(4)
57
+
58
+ const fastify = Fastify()
59
+
60
+ fastify.get('/', function (request, reply) {
61
+ reply.trailer('ETag', function (reply, payload) {
62
+ return 'custom-etag'
63
+ })
64
+ reply.send(undefined)
65
+ })
66
+
67
+ fastify.inject({
68
+ method: 'GET',
69
+ url: '/'
70
+ }, (error, res) => {
71
+ t.error(error)
72
+ t.equal(res.statusCode, 200)
73
+ t.equal(res.headers.trailer, 'etag')
74
+ t.equal(res.trailers.etag, 'custom-etag')
75
+ })
76
+ })
77
+
78
+ test('send trailers when payload is json', t => {
79
+ t.plan(6)
80
+
81
+ const fastify = Fastify()
82
+ const data = JSON.stringify({ hello: 'world' })
83
+ const hash = createHash('md5')
84
+ hash.update(data)
85
+ const md5 = hash.digest('hex')
86
+
87
+ fastify.get('/', function (request, reply) {
88
+ reply.trailer('Content-MD5', function (reply, payload) {
89
+ t.equal(data, payload)
90
+ const hash = createHash('md5')
91
+ hash.update(payload)
92
+ return hash.digest('hex')
93
+ })
94
+ reply.send(data)
95
+ })
96
+
97
+ fastify.inject({
98
+ method: 'GET',
99
+ url: '/'
100
+ }, (error, res) => {
101
+ t.error(error)
102
+ t.equal(res.statusCode, 200)
103
+ t.equal(res.headers['transfer-encoding'], 'chunked')
104
+ t.equal(res.headers.trailer, 'content-md5')
105
+ t.equal(res.trailers['content-md5'], md5)
106
+ })
107
+ })
108
+
109
+ test('send trailers when payload is stream', t => {
110
+ t.plan(6)
111
+
112
+ const fastify = Fastify()
113
+
114
+ fastify.get('/', function (request, reply) {
115
+ reply.trailer('ETag', function (reply, payload) {
116
+ t.same(payload, null)
117
+ return 'custom-etag'
118
+ })
119
+ const stream = Readable.from([JSON.stringify({ hello: 'world' })])
120
+ reply.send(stream)
121
+ })
122
+
123
+ fastify.inject({
124
+ method: 'GET',
125
+ url: '/'
126
+ }, (error, res) => {
127
+ t.error(error)
128
+ t.equal(res.statusCode, 200)
129
+ t.equal(res.headers['transfer-encoding'], 'chunked')
130
+ t.equal(res.headers.trailer, 'etag')
131
+ t.equal(res.trailers.etag, 'custom-etag')
132
+ })
133
+ })
134
+
135
+ test('removeTrailer', t => {
136
+ t.plan(5)
137
+
138
+ const fastify = Fastify()
139
+
140
+ fastify.get('/', function (request, reply) {
141
+ reply.removeTrailer('ETag') // remove nothing
142
+ reply.trailer('ETag', function (reply, payload) {
143
+ return 'custom-etag'
144
+ })
145
+ reply.trailer('Should-Not-Call', function (reply, payload) {
146
+ t.fail('it should not called as this trailer is removed')
147
+ return 'should-not-call'
148
+ })
149
+ reply.removeTrailer('Should-Not-Call')
150
+ reply.send(undefined)
151
+ })
152
+
153
+ fastify.inject({
154
+ method: 'GET',
155
+ url: '/'
156
+ }, (error, res) => {
157
+ t.error(error)
158
+ t.equal(res.statusCode, 200)
159
+ t.equal(res.headers.trailer, 'etag')
160
+ t.equal(res.trailers.etag, 'custom-etag')
161
+ t.notOk(res.trailers['should-not-call'])
162
+ })
163
+ })
164
+
165
+ test('hasTrailer', t => {
166
+ t.plan(9)
167
+
168
+ const fastify = Fastify()
169
+
170
+ fastify.get('/', function (request, reply) {
171
+ t.equal(reply.hasTrailer('ETag'), false)
172
+ reply.trailer('ETag', function (reply, payload) {
173
+ return 'custom-etag'
174
+ })
175
+ t.equal(reply.hasTrailer('ETag'), true)
176
+ reply.trailer('Should-Not-Call', function (reply, payload) {
177
+ t.fail('it should not called as this trailer is removed')
178
+ return 'should-not-call'
179
+ })
180
+ t.equal(reply.hasTrailer('Should-Not-Call'), true)
181
+ reply.removeTrailer('Should-Not-Call')
182
+ t.equal(reply.hasTrailer('Should-Not-Call'), false)
183
+ reply.send(undefined)
184
+ })
185
+
186
+ fastify.inject({
187
+ method: 'GET',
188
+ url: '/'
189
+ }, (error, res) => {
190
+ t.error(error)
191
+ t.equal(res.statusCode, 200)
192
+ t.equal(res.headers.trailer, 'etag')
193
+ t.equal(res.trailers.etag, 'custom-etag')
194
+ t.notOk(res.trailers['should-not-call'])
195
+ })
196
+ })
197
+
198
+ test('throw error when trailer header name is not allowed', t => {
199
+ const INVALID_TRAILERS = [
200
+ 'transfer-encoding',
201
+ 'content-length',
202
+ 'host',
203
+ 'cache-control',
204
+ 'max-forwards',
205
+ 'te',
206
+ 'authorization',
207
+ 'set-cookie',
208
+ 'content-encoding',
209
+ 'content-type',
210
+ 'content-range',
211
+ 'trailer'
212
+ ]
213
+ t.plan(INVALID_TRAILERS.length + 2)
214
+
215
+ const fastify = Fastify()
216
+
217
+ fastify.get('/', function (request, reply) {
218
+ for (const key of INVALID_TRAILERS) {
219
+ try {
220
+ reply.trailer(key, () => {})
221
+ } catch (err) {
222
+ t.equal(err.message, `Called reply.trailer with an invalid header name: ${key}`)
223
+ }
224
+ }
225
+ reply.send('')
226
+ })
227
+
228
+ fastify.inject({
229
+ method: 'GET',
230
+ url: '/'
231
+ }, (error, res) => {
232
+ t.error(error)
233
+ t.equal(res.statusCode, 200)
234
+ })
235
+ })
236
+
237
+ test('throw error when trailer header value is not function', t => {
238
+ const INVALID_TRAILERS_VALUE = [
239
+ undefined,
240
+ null,
241
+ true,
242
+ false,
243
+ 'invalid',
244
+ [],
245
+ new Date(),
246
+ {}
247
+ ]
248
+ t.plan(INVALID_TRAILERS_VALUE.length + 2)
249
+
250
+ const fastify = Fastify()
251
+
252
+ fastify.get('/', function (request, reply) {
253
+ for (const value of INVALID_TRAILERS_VALUE) {
254
+ try {
255
+ reply.trailer('invalid', value)
256
+ } catch (err) {
257
+ t.equal(err.message, `Called reply.trailer('invalid', fn) with an invalid type: ${typeof value}. Expected a function.`)
258
+ }
259
+ }
260
+ reply.send('')
261
+ })
262
+
263
+ fastify.inject({
264
+ method: 'GET',
265
+ url: '/'
266
+ }, (error, res) => {
267
+ t.error(error)
268
+ t.equal(res.statusCode, 200)
269
+ })
270
+ })
@@ -43,7 +43,7 @@ test('default 400 on request error with custom error handler', t => {
43
43
 
44
44
  fastify.setErrorHandler(function (err, request, reply) {
45
45
  t.type(request, 'object')
46
- t.type(request, fastify[kRequest])
46
+ t.type(request, fastify[kRequest].parent)
47
47
  reply
48
48
  .code(err.statusCode)
49
49
  .type('application/json; charset=utf-8')
@@ -105,7 +105,7 @@ test('default clientError handler ignores ECONNRESET', t => {
105
105
  })
106
106
  })
107
107
 
108
- fastify.listen(0, function (err) {
108
+ fastify.listen({ port: 0 }, function (err) {
109
109
  t.error(err)
110
110
  fastify.server.unref()
111
111
 
@@ -132,10 +132,7 @@ test('default clientError handler ignores sockets in destroyed state', t => {
132
132
 
133
133
  const fastify = Fastify({
134
134
  bodyLimit: 1,
135
- keepAliveTimeout: 100,
136
- logger: {
137
- level: 'trace'
138
- }
135
+ keepAliveTimeout: 100
139
136
  })
140
137
  fastify.server.on('clientError', () => {
141
138
  // this handler is called after default handler, so we can make sure end was not called
@@ -7,11 +7,11 @@ const Fastify = require('../')
7
7
 
8
8
  process.removeAllListeners('warning')
9
9
 
10
- function endRouteHook (doneOrPayload, done) {
10
+ function endRouteHook (doneOrPayload, done, doneValue) {
11
11
  if (typeof doneOrPayload === 'function') {
12
- doneOrPayload()
12
+ doneOrPayload(doneValue)
13
13
  } else {
14
- done()
14
+ done(doneValue)
15
15
  }
16
16
  }
17
17
 
@@ -152,9 +152,9 @@ function testBeforeHandlerHook (hook) {
152
152
  const fastify = Fastify()
153
153
 
154
154
  fastify.post('/', {
155
- [hook]: (req, reply, done) => {
155
+ [hook]: (req, reply, doneOrPayload, done) => {
156
156
  req.hello = 'earth'
157
- done()
157
+ endRouteHook(doneOrPayload, done)
158
158
  }
159
159
  }, (req, reply) => {
160
160
  reply.send({ hello: req.hello })
@@ -190,8 +190,8 @@ function testBeforeHandlerHook (hook) {
190
190
  const fastify = Fastify()
191
191
 
192
192
  fastify.post('/', {
193
- [hook]: (req, reply, done) => {
194
- done(new Error('kaboom'))
193
+ [hook]: (req, reply, doneOrPayload, done) => {
194
+ endRouteHook(doneOrPayload, done, new Error('kaboom'))
195
195
  }
196
196
  }, (req, reply) => {
197
197
  reply.send(req.body)
@@ -221,7 +221,7 @@ function testBeforeHandlerHook (hook) {
221
221
 
222
222
  fastify.setErrorHandler(async (error, request, reply) => {
223
223
  t.same(error, myError, 'the error object throws by the user')
224
- reply.send({ this: 'is', my: 'error' })
224
+ return reply.code(500).send({ this: 'is', my: 'error' })
225
225
  })
226
226
 
227
227
  fastify.get('/', {
@@ -271,9 +271,9 @@ function testBeforeHandlerHook (hook) {
271
271
  const fastify = Fastify()
272
272
 
273
273
  fastify.post('/', {
274
- [hook]: (req, reply, done) => {
274
+ [hook]: (req, reply, doneOrPayload, done) => {
275
275
  reply.code(401)
276
- done(new Error('go away'))
276
+ endRouteHook(doneOrPayload, done, new Error('go away'))
277
277
  }
278
278
  }, (req, reply) => {
279
279
  reply.send(req.body)
@@ -302,10 +302,10 @@ function testBeforeHandlerHook (hook) {
302
302
  fastify.decorate('foo', 42)
303
303
 
304
304
  fastify.post('/', {
305
- [hook]: function (req, reply, done) {
305
+ [hook]: function (req, reply, doneOrPayload, done) {
306
306
  t.equal(this.foo, 42)
307
307
  this.foo += 1
308
- done()
308
+ endRouteHook(doneOrPayload, done)
309
309
  }
310
310
  }, function (req, reply) {
311
311
  reply.send({ foo: this.foo })
@@ -329,10 +329,10 @@ function testBeforeHandlerHook (hook) {
329
329
  fastify.decorate('foo', 42)
330
330
 
331
331
  fastify.post('/', {
332
- [hook]: [function (req, reply, done) {
332
+ [hook]: [function (req, reply, doneOrPayload, done) {
333
333
  t.equal(this.foo, 42)
334
334
  this.foo += 1
335
- done()
335
+ endRouteHook(doneOrPayload, done)
336
336
  }]
337
337
  }, function (req, reply) {
338
338
  reply.send({ foo: this.foo })
@@ -423,7 +423,7 @@ test('preParsing option should be called before preValidation hook', t => {
423
423
  })
424
424
 
425
425
  fastify.post('/', {
426
- preParsing: (req, reply, done) => {
426
+ preParsing: (req, reply, payload, done) => {
427
427
  req.called = true
428
428
  done()
429
429
  }
@@ -473,7 +473,7 @@ test('onRequest option should be called before preParsing', t => {
473
473
  t.plan(3)
474
474
  const fastify = Fastify()
475
475
 
476
- fastify.addHook('preParsing', (req, reply, done) => {
476
+ fastify.addHook('preParsing', (req, reply, payload, done) => {
477
477
  t.ok(req.called)
478
478
  done()
479
479
  })
@@ -503,14 +503,14 @@ test('onTimeout on route', t => {
503
503
  const fastify = Fastify({ connectionTimeout: 500 })
504
504
 
505
505
  fastify.get('/timeout', {
506
- async handler (request, reply) { },
506
+ handler (request, reply) { },
507
507
  onTimeout (request, reply, done) {
508
508
  t.pass('onTimeout called')
509
509
  done()
510
510
  }
511
511
  })
512
512
 
513
- fastify.listen(0, (err, address) => {
513
+ fastify.listen({ port: 0 }, (err, address) => {
514
514
  t.error(err)
515
515
  t.teardown(() => fastify.close())
516
516
 
@@ -565,7 +565,8 @@ test('matches only /prefix/ with a / route - prefixTrailingSlash: "slash", igno
565
565
  test('calls onRoute only once when prefixing', async t => {
566
566
  t.plan(1)
567
567
  const fastify = Fastify({
568
- ignoreTrailingSlash: false
568
+ ignoreTrailingSlash: false,
569
+ exposeHeadRoutes: false
569
570
  })
570
571
 
571
572
  let onRouteCalled = 0