fastify 3.27.3 → 4.0.0-alpha.2

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 (164) 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/Getting-Started.md +7 -7
  7. package/docs/Guides/Plugins-Guide.md +1 -1
  8. package/docs/Guides/Serverless.md +3 -3
  9. package/docs/Guides/Testing.md +2 -2
  10. package/docs/Migration-Guide-V4.md +12 -0
  11. package/docs/Reference/ContentTypeParser.md +4 -0
  12. package/docs/Reference/Decorators.md +2 -2
  13. package/docs/Reference/Encapsulation.md +2 -2
  14. package/docs/Reference/Errors.md +51 -6
  15. package/docs/Reference/HTTP2.md +3 -3
  16. package/docs/Reference/Hooks.md +4 -7
  17. package/docs/Reference/LTS.md +5 -4
  18. package/docs/Reference/Plugins.md +3 -3
  19. package/docs/Reference/Reply.md +23 -22
  20. package/docs/Reference/Request.md +1 -3
  21. package/docs/Reference/Routes.md +22 -15
  22. package/docs/Reference/Server.md +69 -119
  23. package/docs/Reference/TypeScript.md +20 -22
  24. package/docs/Reference/Validation-and-Serialization.md +30 -55
  25. package/docs/Type-Providers.md +257 -0
  26. package/examples/asyncawait.js +1 -1
  27. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  28. package/examples/benchmark/hooks-benchmark.js +1 -1
  29. package/examples/benchmark/simple.js +1 -1
  30. package/examples/hooks.js +2 -2
  31. package/examples/http2.js +1 -1
  32. package/examples/https.js +1 -1
  33. package/examples/parser.js +1 -1
  34. package/examples/route-prefix.js +1 -1
  35. package/examples/shared-schema.js +1 -1
  36. package/examples/simple-stream.js +18 -0
  37. package/examples/simple.js +1 -1
  38. package/examples/simple.mjs +1 -1
  39. package/examples/typescript-server.ts +1 -1
  40. package/examples/use-plugin.js +1 -1
  41. package/fastify.d.ts +34 -22
  42. package/fastify.js +40 -36
  43. package/lib/configValidator.js +902 -1023
  44. package/lib/contentTypeParser.js +6 -16
  45. package/lib/context.js +36 -10
  46. package/lib/decorate.js +3 -1
  47. package/lib/error-handler.js +158 -0
  48. package/lib/error-serializer.js +257 -0
  49. package/lib/errors.js +43 -9
  50. package/lib/fourOhFour.js +31 -20
  51. package/lib/handleRequest.js +10 -13
  52. package/lib/hooks.js +14 -9
  53. package/lib/pluginOverride.js +0 -3
  54. package/lib/pluginUtils.js +3 -2
  55. package/lib/reply.js +29 -158
  56. package/lib/request.js +13 -10
  57. package/lib/route.js +131 -138
  58. package/lib/schema-controller.js +2 -2
  59. package/lib/schemas.js +27 -1
  60. package/lib/server.js +241 -116
  61. package/lib/symbols.js +4 -3
  62. package/lib/validation.js +2 -1
  63. package/lib/warnings.js +4 -12
  64. package/lib/wrapThenable.js +4 -11
  65. package/package.json +37 -39
  66. package/test/404s.test.js +258 -125
  67. package/test/500s.test.js +3 -3
  68. package/test/als.test.js +1 -1
  69. package/test/async-await.test.js +20 -76
  70. package/test/bodyLimit.test.js +1 -1
  71. package/test/build-certificate.js +6 -7
  72. package/test/case-insensitive.test.js +4 -4
  73. package/test/close-pipelining.test.js +2 -2
  74. package/test/close.test.js +11 -11
  75. package/test/content-parser.test.js +32 -0
  76. package/test/context-config.test.js +52 -0
  77. package/test/custom-http-server.test.js +14 -7
  78. package/test/custom-parser-async.test.js +1 -66
  79. package/test/custom-parser.test.js +92 -159
  80. package/test/custom-querystring-parser.test.js +3 -3
  81. package/test/decorator.test.js +11 -13
  82. package/test/delete.test.js +6 -6
  83. package/test/encapsulated-error-handler.test.js +50 -0
  84. package/test/esm/index.test.js +0 -14
  85. package/test/fastify-instance.test.js +4 -4
  86. package/test/fluent-schema.test.js +4 -4
  87. package/test/genReqId.test.js +1 -1
  88. package/test/get.test.js +4 -4
  89. package/test/handler-context.test.js +2 -2
  90. package/test/head.test.js +1 -1
  91. package/test/helper.js +19 -4
  92. package/test/hooks-async.test.js +15 -48
  93. package/test/hooks.on-ready.test.js +10 -5
  94. package/test/hooks.test.js +78 -119
  95. package/test/http2/closing.test.js +10 -16
  96. package/test/http2/constraint.test.js +1 -1
  97. package/test/http2/head.test.js +1 -1
  98. package/test/http2/plain.test.js +1 -1
  99. package/test/http2/secure-with-fallback.test.js +1 -1
  100. package/test/http2/secure.test.js +1 -1
  101. package/test/http2/unknown-http-method.test.js +4 -10
  102. package/test/https/custom-https-server.test.js +12 -6
  103. package/test/https/https.test.js +1 -1
  104. package/test/input-validation.js +3 -3
  105. package/test/internals/handleRequest.test.js +6 -43
  106. package/test/internals/initialConfig.test.js +41 -12
  107. package/test/internals/logger.test.js +2 -2
  108. package/test/internals/reply.test.js +281 -40
  109. package/test/internals/request.test.js +13 -7
  110. package/test/internals/server.test.js +88 -0
  111. package/test/listen.deprecated.test.js +202 -0
  112. package/test/listen.test.js +118 -150
  113. package/test/logger.test.js +82 -42
  114. package/test/maxRequestsPerSocket.test.js +8 -6
  115. package/test/middleware.test.js +2 -25
  116. package/test/nullable-validation.test.js +53 -16
  117. package/test/output-validation.test.js +1 -1
  118. package/test/plugin.test.js +47 -21
  119. package/test/pretty-print.test.js +22 -10
  120. package/test/promises.test.js +1 -1
  121. package/test/proto-poisoning.test.js +6 -6
  122. package/test/register.test.js +3 -3
  123. package/test/reply-error.test.js +126 -15
  124. package/test/request-error.test.js +3 -6
  125. package/test/route-hooks.test.js +18 -18
  126. package/test/route-prefix.test.js +2 -1
  127. package/test/route.test.js +206 -22
  128. package/test/router-options.test.js +2 -2
  129. package/test/schema-examples.test.js +11 -5
  130. package/test/schema-feature.test.js +25 -20
  131. package/test/schema-serialization.test.js +9 -9
  132. package/test/schema-special-usage.test.js +5 -153
  133. package/test/schema-validation.test.js +9 -9
  134. package/test/skip-reply-send.test.js +2 -2
  135. package/test/stream.test.js +82 -23
  136. package/test/throw.test.js +8 -5
  137. package/test/trust-proxy.test.js +6 -6
  138. package/test/type-provider.test.js +20 -0
  139. package/test/types/fastify.test-d.ts +10 -18
  140. package/test/types/import.js +2 -0
  141. package/test/types/import.ts +1 -0
  142. package/test/types/instance.test-d.ts +68 -17
  143. package/test/types/logger.test-d.ts +44 -15
  144. package/test/types/reply.test-d.ts +2 -1
  145. package/test/types/route.test-d.ts +8 -2
  146. package/test/types/schema.test-d.ts +2 -39
  147. package/test/types/type-provider.test-d.ts +417 -0
  148. package/test/url-rewriting.test.js +3 -3
  149. package/test/validation-error-handling.test.js +8 -8
  150. package/test/versioned-routes.test.js +30 -18
  151. package/test/wrapThenable.test.js +7 -6
  152. package/types/content-type-parser.d.ts +17 -8
  153. package/types/hooks.d.ts +102 -59
  154. package/types/instance.d.ts +244 -118
  155. package/types/logger.d.ts +18 -104
  156. package/types/plugin.d.ts +10 -4
  157. package/types/reply.d.ts +18 -12
  158. package/types/request.d.ts +10 -5
  159. package/types/route.d.ts +42 -31
  160. package/types/schema.d.ts +1 -1
  161. package/types/type-provider.d.ts +99 -0
  162. package/types/utils.d.ts +1 -1
  163. package/lib/schema-compilers.js +0 -12
  164. package/test/emit-warning.test.js +0 -166
package/lib/server.js CHANGED
@@ -1,16 +1,213 @@
1
1
  'use strict'
2
2
 
3
- const assert = require('assert')
4
3
  const http = require('http')
5
4
  const https = require('https')
5
+ const dns = require('dns')
6
6
 
7
- const { kState, kOptions } = require('./symbols')
7
+ const warnings = require('./warnings')
8
+ const { kState, kOptions, kServerBindings } = require('./symbols')
8
9
  const { FST_ERR_HTTP2_INVALID_VERSION, FST_ERR_REOPENED_CLOSE_SERVER, FST_ERR_REOPENED_SERVER } = require('./errors')
9
10
 
11
+ module.exports = createServer
12
+
10
13
  function createServer (options, httpHandler) {
11
- assert(options, 'Missing options')
12
- assert(httpHandler, 'Missing http handler')
14
+ const server = getServerInstance(options, httpHandler)
15
+
16
+ return { server, listen }
17
+
18
+ // `this` is the Fastify object
19
+ function listen (listenOptions, ...args) {
20
+ let cb = args.slice(-1).pop()
21
+ // When the variadic signature deprecation is complete, the function
22
+ // declaration should become:
23
+ // function listen (listenOptions = { port: 0, host: 'localhost' }, cb = undefined)
24
+ // Upon doing so, the `normalizeListenArgs` function is no longer needed,
25
+ // and all of this preamble to feed it correctly also no longer needed.
26
+ const firstArgType = Object.prototype.toString.call(arguments[0])
27
+ if (arguments.length === 0) {
28
+ listenOptions = normalizeListenArgs([])
29
+ } else if (arguments.length > 0 && (firstArgType !== '[object Object]' && firstArgType !== '[object Function]')) {
30
+ warnings.emit('FSTDEP011')
31
+ listenOptions = normalizeListenArgs(Array.from(arguments))
32
+ cb = listenOptions.cb
33
+ } else if (args.length > 1) {
34
+ // `.listen(obj, a, ..., n, callback )`
35
+ warnings.emit('FSTDEP011')
36
+ // Deal with `.listen(port, host, backlog, [cb])`
37
+ const hostPath = listenOptions.path ? [listenOptions.path] : [listenOptions.port ?? 0, listenOptions.host ?? 'localhost']
38
+ Object.assign(listenOptions, normalizeListenArgs([...hostPath, ...args]))
39
+ } else {
40
+ listenOptions.cb = cb
41
+ }
42
+
43
+ const { host = 'localhost' } = listenOptions
44
+ if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) {
45
+ listenOptions.host = host
46
+ }
47
+
48
+ if (host === 'localhost') {
49
+ listenOptions.cb = (err, address) => {
50
+ if (err) {
51
+ // the server did not start
52
+ cb(err, address)
53
+ return
54
+ }
55
+
56
+ multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
57
+ this[kState].listening = true
58
+ cb(null, address)
59
+ })
60
+ }
61
+ }
62
+
63
+ // https://github.com/nodejs/node/issues/9390
64
+ // If listening to 'localhost', listen to both 127.0.0.1 or ::1 if they are available.
65
+ // If listening to 127.0.0.1, only listen to 127.0.0.1.
66
+ // If listening to ::1, only listen to ::1.
67
+
68
+ if (cb === undefined) {
69
+ const listening = listenPromise.call(this, server, listenOptions)
70
+ /* istanbul ignore else */
71
+ if (host === 'localhost') {
72
+ return listening.then(address => {
73
+ return new Promise((resolve, reject) => {
74
+ multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
75
+ this[kState].listening = true
76
+ resolve(address)
77
+ })
78
+ })
79
+ })
80
+ }
81
+ }
82
+
83
+ this.ready(listenCallback.call(this, server, listenOptions))
84
+ }
85
+ }
86
+
87
+ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) {
88
+ // the main server is started, we need to start the secondary servers
89
+ this[kState].listening = false
90
+
91
+ // let's check if we need to bind additional addresses
92
+ dns.lookup(listenOptions.host, { all: true }, (dnsErr, addresses) => {
93
+ if (dnsErr) {
94
+ // not blocking the main server listening
95
+ // this.log.warn('dns.lookup error:', dnsErr)
96
+ onListen()
97
+ return
98
+ }
99
+
100
+ let binding = 0
101
+ let binded = 0
102
+ const primaryAddress = mainServer.address()
103
+ for (const adr of addresses) {
104
+ if (adr.address !== primaryAddress.address) {
105
+ binding++
106
+ const secondaryOpts = Object.assign({}, listenOptions, {
107
+ host: adr.address,
108
+ port: primaryAddress.port,
109
+ cb: (_ignoreErr) => {
110
+ binded++
111
+
112
+ if (!_ignoreErr) {
113
+ this[kServerBindings].push(secondaryServer)
114
+ }
115
+
116
+ if (binded === binding) {
117
+ // regardless of the error, we are done
118
+ onListen()
119
+ }
120
+ }
121
+ })
122
+
123
+ const secondaryServer = getServerInstance(serverOpts, httpHandler)
124
+ const closeSecondary = () => { secondaryServer.close(() => {}) }
125
+ mainServer.on('unref', closeSecondary)
126
+ mainServer.on('close', closeSecondary)
127
+ mainServer.on('error', closeSecondary)
128
+ listenCallback.call(this, secondaryServer, secondaryOpts)()
129
+ }
130
+ }
131
+
132
+ // no extra bindings are necessary
133
+ if (binding === 0) {
134
+ onListen()
135
+ return
136
+ }
137
+
138
+ // in test files we are using unref so we need to propagate the unref event
139
+ // to the secondary servers. It is valid only when the user is
140
+ // listening on localhost
141
+ const originUnref = mainServer.unref
142
+ /* istanbul ignore next */
143
+ mainServer.unref = function () {
144
+ originUnref.call(mainServer)
145
+ mainServer.emit('unref')
146
+ }
147
+ })
148
+ }
149
+
150
+ function listenCallback (server, listenOptions) {
151
+ const wrap = (err) => {
152
+ server.removeListener('error', wrap)
153
+ if (!err) {
154
+ const address = logServerAddress.call(this, server)
155
+ listenOptions.cb(null, address)
156
+ } else {
157
+ this[kState].listening = false
158
+ listenOptions.cb(err, null)
159
+ }
160
+ }
161
+
162
+ return (err) => {
163
+ if (err != null) return listenOptions.cb(err)
164
+
165
+ if (this[kState].listening && this[kState].closing) {
166
+ return listenOptions.cb(new FST_ERR_REOPENED_CLOSE_SERVER(), null)
167
+ } else if (this[kState].listening) {
168
+ return listenOptions.cb(new FST_ERR_REOPENED_SERVER(), null)
169
+ }
170
+
171
+ server.once('error', wrap)
172
+ server.listen(listenOptions, wrap)
173
+
174
+ this[kState].listening = true
175
+ }
176
+ }
177
+
178
+ function listenPromise (server, listenOptions) {
179
+ if (this[kState].listening && this[kState].closing) {
180
+ return Promise.reject(new FST_ERR_REOPENED_CLOSE_SERVER())
181
+ } else if (this[kState].listening) {
182
+ return Promise.reject(new FST_ERR_REOPENED_SERVER())
183
+ }
13
184
 
185
+ return this.ready().then(() => {
186
+ let errEventHandler
187
+ const errEvent = new Promise((resolve, reject) => {
188
+ errEventHandler = (err) => {
189
+ this[kState].listening = false
190
+ reject(err)
191
+ }
192
+ server.once('error', errEventHandler)
193
+ })
194
+ const listen = new Promise((resolve, reject) => {
195
+ server.listen(listenOptions, () => {
196
+ server.removeListener('error', errEventHandler)
197
+ resolve(logServerAddress.call(this, server))
198
+ })
199
+ // we set it afterwards because listen can throw
200
+ this[kState].listening = true
201
+ })
202
+
203
+ return Promise.race([
204
+ errEvent, // e.g invalid port range error is always emitted before the server listening
205
+ listen
206
+ ])
207
+ })
208
+ }
209
+
210
+ function getServerInstance (options, httpHandler) {
14
211
  let server = null
15
212
  if (options.serverFactory) {
16
213
  server = options.serverFactory(httpHandler, options)
@@ -41,123 +238,53 @@ function createServer (options, httpHandler) {
41
238
  if (!options.serverFactory) {
42
239
  server.setTimeout(options.connectionTimeout)
43
240
  }
241
+ return server
242
+ }
44
243
 
45
- return { server, listen }
46
-
47
- // `this` is the Fastify object
48
- function listen () {
49
- const normalizeListenArgs = (args) => {
50
- if (args.length === 0) {
51
- return { port: 0, host: 'localhost' }
52
- }
53
-
54
- const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined
55
- const options = { cb }
56
-
57
- const firstArg = args[0]
58
- const argsLength = args.length
59
- const lastArg = args[argsLength - 1]
60
- /* Deal with listen (options) || (handle[, backlog]) */
61
- if (typeof firstArg === 'object' && firstArg !== null) {
62
- options.backlog = argsLength > 1 ? lastArg : undefined
63
- Object.assign(options, firstArg)
64
- } else if (typeof firstArg === 'string' && isNaN(firstArg)) {
65
- /* Deal with listen (pipe[, backlog]) */
66
- options.path = firstArg
67
- options.backlog = argsLength > 1 ? lastArg : undefined
68
- } else {
69
- /* Deal with listen ([port[, host[, backlog]]]) */
70
- options.port = argsLength >= 1 && firstArg ? firstArg : 0
71
- // This will listen to what localhost is.
72
- // It can be 127.0.0.1 or ::1, depending on the operating system.
73
- // Fixes https://github.com/fastify/fastify/issues/1022.
74
- options.host = argsLength >= 2 && args[1] ? args[1] : 'localhost'
75
- options.backlog = argsLength >= 3 ? args[2] : undefined
76
- }
77
-
78
- return options
79
- }
80
-
81
- const listenOptions = normalizeListenArgs(Array.from(arguments))
82
- const cb = listenOptions.cb
83
-
84
- const wrap = err => {
85
- server.removeListener('error', wrap)
86
- if (!err) {
87
- const address = logServerAddress()
88
- cb(null, address)
89
- } else {
90
- this[kState].listening = false
91
- cb(err, null)
92
- }
93
- }
244
+ function normalizeListenArgs (args) {
245
+ if (args.length === 0) {
246
+ return { port: 0, host: 'localhost' }
247
+ }
94
248
 
95
- const listenPromise = (listenOptions) => {
96
- if (this[kState].listening && this[kState].closing) {
97
- return Promise.reject(new FST_ERR_REOPENED_CLOSE_SERVER())
98
- } else if (this[kState].listening) {
99
- return Promise.reject(new FST_ERR_REOPENED_SERVER())
100
- }
249
+ const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined
250
+ const options = { cb }
101
251
 
102
- return this.ready().then(() => {
103
- let errEventHandler
104
- const errEvent = new Promise((resolve, reject) => {
105
- errEventHandler = (err) => {
106
- this[kState].listening = false
107
- reject(err)
108
- }
109
- server.once('error', errEventHandler)
110
- })
111
- const listen = new Promise((resolve, reject) => {
112
- server.listen(listenOptions, () => {
113
- server.removeListener('error', errEventHandler)
114
- resolve(logServerAddress())
115
- })
116
- // we set it afterwards because listen can throw
117
- this[kState].listening = true
118
- })
252
+ const firstArg = args[0]
253
+ const argsLength = args.length
254
+ const lastArg = args[argsLength - 1]
255
+ if (typeof firstArg === 'string' && isNaN(firstArg)) {
256
+ /* Deal with listen (pipe[, backlog]) */
257
+ options.path = firstArg
258
+ options.backlog = argsLength > 1 ? lastArg : undefined
259
+ } else {
260
+ /* Deal with listen ([port[, host[, backlog]]]) */
261
+ options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : 0
262
+ // This will listen to what localhost is.
263
+ // It can be 127.0.0.1 or ::1, depending on the operating system.
264
+ // Fixes https://github.com/fastify/fastify/issues/1022.
265
+ options.host = argsLength >= 2 && args[1] ? args[1] : 'localhost'
266
+ options.backlog = argsLength >= 3 ? args[2] : undefined
267
+ }
119
268
 
120
- return Promise.race([
121
- errEvent, // e.g invalid port range error is always emitted before the server listening
122
- listen
123
- ])
124
- })
125
- }
269
+ return options
270
+ }
126
271
 
127
- const logServerAddress = () => {
128
- let address = server.address()
129
- const isUnixSocket = typeof address === 'string'
130
- /* istanbul ignore next */
131
- if (!isUnixSocket) {
132
- if (address.address.indexOf(':') === -1) {
133
- address = address.address + ':' + address.port
134
- } else {
135
- address = '[' + address.address + ']:' + address.port
136
- }
137
- }
138
- /* istanbul ignore next */
139
- address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address
140
- this.log.info('Server listening at ' + address)
141
- return address
272
+ function logServerAddress (server) {
273
+ let address = server.address()
274
+ const isUnixSocket = typeof address === 'string'
275
+ /* istanbul ignore next */
276
+ if (!isUnixSocket) {
277
+ if (address.address.indexOf(':') === -1) {
278
+ address = address.address + ':' + address.port
279
+ } else {
280
+ address = '[' + address.address + ']:' + address.port
142
281
  }
143
-
144
- if (cb === undefined) return listenPromise(listenOptions)
145
-
146
- this.ready(err => {
147
- if (err != null) return cb(err)
148
-
149
- if (this[kState].listening && this[kState].closing) {
150
- return cb(new FST_ERR_REOPENED_CLOSE_SERVER(), null)
151
- } else if (this[kState].listening) {
152
- return cb(new FST_ERR_REOPENED_SERVER(), null)
153
- }
154
-
155
- server.once('error', wrap)
156
- server.listen(listenOptions, wrap)
157
-
158
- this[kState].listening = true
159
- })
160
282
  }
283
+ /* istanbul ignore next */
284
+ address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address
285
+
286
+ this.log.info('Server listening at ' + address)
287
+ return address
161
288
  }
162
289
 
163
290
  function http2 () {
@@ -177,5 +304,3 @@ function sessionTimeout (timeout) {
177
304
  function close () {
178
305
  this.close()
179
306
  }
180
-
181
- module.exports = { createServer }
package/lib/symbols.js CHANGED
@@ -3,12 +3,12 @@
3
3
  const keys = {
4
4
  kAvvioBoot: Symbol('fastify.avvioBoot'),
5
5
  kChildren: Symbol('fastify.children'),
6
+ kServerBindings: Symbol('fastify.serverBindings'),
6
7
  kBodyLimit: Symbol('fastify.bodyLimit'),
7
8
  kRoutePrefix: Symbol('fastify.routePrefix'),
8
9
  kLogLevel: Symbol('fastify.logLevel'),
9
10
  kLogSerializers: Symbol('fastify.logSerializers'),
10
11
  kHooks: Symbol('fastify.hooks'),
11
- kHooksDeprecatedPreParsing: Symbol('fastify.hooks.DeprecatedPreParsing'),
12
12
  kSchemaController: Symbol('fastify.schemaController'),
13
13
  kSchemaHeaders: Symbol('headers-schema'),
14
14
  kSchemaParams: Symbol('params-schema'),
@@ -31,9 +31,9 @@ const keys = {
31
31
  kReplyIsError: Symbol('fastify.reply.isError'),
32
32
  kReplyHeaders: Symbol('fastify.reply.headers'),
33
33
  kReplyHasStatusCode: Symbol('fastify.reply.hasStatusCode'),
34
- kReplySent: Symbol('fastify.reply.sent'),
35
- kReplySentOverwritten: Symbol('fastify.reply.sentOverwritten'),
34
+ kReplyHijacked: Symbol('fastify.reply.hijacked'),
36
35
  kReplyStartTime: Symbol('fastify.reply.startTime'),
36
+ kReplyNextErrorHandler: Symbol('fastify.reply.nextErrorHandler'),
37
37
  kReplyEndTime: Symbol('fastify.reply.endTime'),
38
38
  kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'),
39
39
  kReplyIsRunningOnErrorHook: Symbol('fastify.reply.isRunningOnErrorHook'),
@@ -45,6 +45,7 @@ const keys = {
45
45
  // This symbol is only meant to be used for fastify tests and should not be used for any other purpose
46
46
  kTestInternals: Symbol('fastify.testInternals'),
47
47
  kErrorHandler: Symbol('fastify.errorHandler'),
48
+ kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
48
49
  kKeepAliveConnections: Symbol('fastify.keepAliveConnections')
49
50
  }
50
51
 
package/lib/validation.js CHANGED
@@ -68,7 +68,8 @@ function compileSchemasForValidation (context, compile) {
68
68
  }
69
69
 
70
70
  function validateParam (validatorFunction, request, paramName) {
71
- const ret = validatorFunction && validatorFunction(request[paramName])
71
+ const isUndefined = request[paramName] === undefined
72
+ const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName])
72
73
  if (ret === false) return validatorFunction.errors
73
74
  if (ret && ret.error) return ret.error
74
75
  if (ret && ret.value) request[paramName] = ret.value
package/lib/warnings.js CHANGED
@@ -4,21 +4,9 @@ const warning = require('process-warning')()
4
4
 
5
5
  /**
6
6
  * Deprecation codes:
7
- * - FSTDEP001
8
- * - FSTDEP002
9
- * - FSTDEP003
10
- * - FSTDEP004
11
7
  * - FSTDEP005
12
8
  */
13
9
 
14
- warning.create('FastifyDeprecation', 'FSTDEP001', 'You are accessing the Node.js core request object via "request.req", Use "request.raw" instead.')
15
-
16
- warning.create('FastifyDeprecation', 'FSTDEP002', 'You are accessing the Node.js core response object via "reply.res", Use "reply.raw" instead.')
17
-
18
- warning.create('FastifyDeprecation', 'FSTDEP003', 'You are using the legacy Content Type Parser function signature. Use the one suggested in the documentation instead.')
19
-
20
- warning.create('FastifyDeprecation', 'FSTDEP004', 'You are using the legacy preParsing hook signature. Use the one suggested in the documentation instead.')
21
-
22
10
  warning.create('FastifyDeprecation', 'FSTDEP005', 'You are accessing the deprecated "request.connection" property. Use "request.socket" instead.')
23
11
 
24
12
  warning.create('FastifyDeprecation', 'FSTDEP006', 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s')
@@ -29,4 +17,8 @@ warning.create('FastifyDeprecation', 'FSTDEP008', 'You are using route constrain
29
17
 
30
18
  warning.create('FastifyDeprecation', 'FSTDEP009', 'You are using a custom route versioning strategy via the server { versioning: "..." } option, use { constraints: { version: "..." } } option instead.')
31
19
 
20
+ warning.create('FastifyDeprecation', 'FSTDEP010', 'Modifying the "reply.sent" property is deprecated. Use the "reply.hijack()" method instead.')
21
+
22
+ warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.')
23
+
32
24
  module.exports = warning
@@ -2,40 +2,33 @@
2
2
 
3
3
  const {
4
4
  kReplyIsError,
5
- kReplySent,
6
- kReplySentOverwritten
5
+ kReplyHijacked
7
6
  } = require('./symbols')
8
7
 
9
- const { FST_ERR_PROMISE_NOT_FULFILLED } = require('./errors')
10
-
11
8
  function wrapThenable (thenable, reply) {
12
9
  thenable.then(function (payload) {
13
- if (reply[kReplySentOverwritten] === true) {
10
+ if (reply[kReplyHijacked] === true) {
14
11
  return
15
12
  }
16
13
 
17
14
  // this is for async functions that
18
15
  // are using reply.send directly
19
- if (payload !== undefined || (reply.raw.statusCode === 204 && reply[kReplySent] === false)) {
16
+ if (payload !== undefined || reply.sent === false) {
20
17
  // we use a try-catch internally to avoid adding a catch to another
21
18
  // promise, increase promise perf by 10%
22
19
  try {
23
20
  reply.send(payload)
24
21
  } catch (err) {
25
- reply[kReplySent] = false
26
22
  reply[kReplyIsError] = true
27
23
  reply.send(err)
28
24
  }
29
- } else if (reply[kReplySent] === false) {
30
- reply.log.error({ err: new FST_ERR_PROMISE_NOT_FULFILLED() }, "Promise may not be fulfilled with 'undefined' when statusCode is not 204")
31
25
  }
32
26
  }, function (err) {
33
- if (reply[kReplySentOverwritten] === true || reply.sent === true) {
27
+ if (reply.sent === true) {
34
28
  reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
35
29
  return
36
30
  }
37
31
 
38
- reply[kReplySent] = false
39
32
  reply[kReplyIsError] = true
40
33
  reply.send(err)
41
34
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.27.3",
3
+ "version": "4.0.0-alpha.2",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -9,6 +9,8 @@
9
9
  "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
10
10
  "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
11
11
  "coverage": "npm run unit -- --cov --coverage-report=html",
12
+ "coverage:ci": "npm run unit -- --cov --coverage-report=html --no-browser --no-check-coverage -R terse",
13
+ "coverage:ci-check-coverage": "nyc check-coverage --branches 100 --functions 100 --lines 100 --statements 100",
12
14
  "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"",
13
15
  "lint": "npm run lint:standard && npm run lint:typescript",
14
16
  "lint:fix": "standard --fix",
@@ -18,10 +20,11 @@
18
20
  "test": "npm run lint && npm run unit && npm run test:typescript",
19
21
  "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
20
22
  "test:report": "npm run lint && npm run unit:report && npm run test:typescript",
21
- "test:typescript": "tsd",
22
- "unit": "tap -J test/*.test.js test/*/*.test.js",
23
+ "test:typescript": "tsc test/types/import.ts && tsd",
24
+ "test:watch": "npm run unit -- -w --no-coverage-report -R terse",
25
+ "unit": "tap",
23
26
  "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",
24
- "unit:report": "tap -J test/*.test.js test/*/*.test.js --cov --coverage-report=html --coverage-report=cobertura | tee out.tap"
27
+ "unit:report": "tap --cov --coverage-report=html --coverage-report=cobertura | tee out.tap"
25
28
  },
26
29
  "repository": {
27
30
  "type": "git",
@@ -121,24 +124,19 @@
121
124
  },
122
125
  "homepage": "https://www.fastify.io/",
123
126
  "devDependencies": {
124
- "@fastify/ajv-compiler-8": "npm:@fastify/ajv-compiler@^2.0.0",
125
127
  "@fastify/pre-commit": "^2.0.1",
126
- "@hapi/joi": "^17.1.1",
128
+ "@sinclair/typebox": "^0.23.1",
127
129
  "@sinonjs/fake-timers": "^9.1.0",
128
- "@types/node": "^16.0.0",
129
- "@types/pino": "^6.0.1",
130
- "@typescript-eslint/eslint-plugin": "^5.0.0",
131
- "@typescript-eslint/parser": "^5.0.0",
132
- "JSONStream": "^1.3.5",
133
- "ajv": "^6.0.0",
134
- "ajv-errors": "^1.0.1",
130
+ "@types/node": "^17.0.18",
131
+ "@typescript-eslint/eslint-plugin": "^5.7.0",
132
+ "@typescript-eslint/parser": "^5.7.0",
133
+ "ajv": "^8.10.0",
134
+ "ajv-errors": "^3.0.0",
135
135
  "ajv-formats": "^2.1.1",
136
- "ajv-i18n": "^3.5.0",
137
- "ajv-merge-patch": "^4.1.0",
138
- "ajv-pack": "^0.3.1",
139
- "branch-comparer": "^1.0.2",
136
+ "ajv-i18n": "^4.2.0",
137
+ "ajv-merge-patch": "^5.0.1",
138
+ "branch-comparer": "^1.1.0",
140
139
  "cors": "^2.8.5",
141
- "coveralls": "^3.1.0",
142
140
  "dns-prefetch-control": "^0.3.0",
143
141
  "eslint": "^8.0.1",
144
142
  "eslint-config-standard": "^17.0.0-1",
@@ -147,46 +145,45 @@
147
145
  "eslint-plugin-n": "^14.0.0",
148
146
  "eslint-plugin-promise": "^6.0.0",
149
147
  "fast-json-body": "^1.1.0",
148
+ "fast-json-stringify": "^3.0.0",
150
149
  "fastify-plugin": "^3.0.0",
151
- "fluent-json-schema": "^3.0.0",
150
+ "fluent-json-schema": "^3.0.1",
152
151
  "form-data": "^4.0.0",
153
152
  "frameguard": "^4.0.0",
154
153
  "h2url": "^0.2.0",
155
154
  "helmet": "^5.0.1",
156
155
  "hide-powered-by": "^1.1.0",
157
- "hsts": "^2.2.0",
158
156
  "http-errors": "^2.0.0",
159
- "ienoopen": "^1.1.0",
157
+ "joi": "^17.5.0",
158
+ "json-schema-to-ts": "^1.6.4",
159
+ "JSONStream": "^1.3.5",
160
160
  "license-checker": "^25.0.1",
161
- "pem": "^1.14.4",
162
161
  "proxyquire": "^2.1.3",
163
162
  "pump": "^3.0.0",
164
- "send": "^0.17.1",
163
+ "self-cert": "^2.0.0",
164
+ "send": "^0.17.2",
165
165
  "serve-static": "^1.14.1",
166
166
  "simple-get": "^4.0.0",
167
167
  "snazzy": "^9.0.0",
168
168
  "split2": "^4.1.0",
169
169
  "standard": "^17.0.0-2",
170
- "tap": "^15.1.1",
171
- "tap-mocha-reporter": "^5.0.1",
172
- "then-sleep": "^1.0.1",
173
- "tsd": "^0.19.0",
174
- "typescript": "^4.0.2",
175
- "undici": "^3.3.6",
170
+ "tap": "^16.0.0",
171
+ "tsd": "^0.19.1",
172
+ "typescript": "^4.5.4",
173
+ "undici": "^4.11.3",
176
174
  "x-xss-protection": "^2.0.0",
177
- "yup": "^0.32.0"
175
+ "yup": "^0.32.11"
178
176
  },
179
177
  "dependencies": {
180
- "@fastify/ajv-compiler": "^1.0.0",
181
- "abstract-logging": "^2.0.0",
182
- "avvio": "^7.1.2",
183
- "fast-json-stringify": "^2.5.2",
184
- "fastify-error": "^0.3.0",
178
+ "@fastify/ajv-compiler": "^3.1.0",
179
+ "@fastify/fast-json-stringify-compiler": "^1.0.0",
180
+ "abstract-logging": "^2.0.1",
181
+ "avvio": "^8.1.0",
182
+ "fastify-error": "^1.0.0",
183
+ "find-my-way": "^5.1.0",
184
+ "light-my-request": "^4.7.0",
185
+ "pino": "^7.5.1",
185
186
  "process-warning": "^1.0.0",
186
- "find-my-way": "^4.5.0",
187
- "flatstr": "^1.0.12",
188
- "light-my-request": "^4.2.0",
189
- "pino": "^6.13.0",
190
187
  "proxy-addr": "^2.0.7",
191
188
  "rfdc": "^1.1.4",
192
189
  "secure-json-parse": "^2.0.0",
@@ -196,6 +193,7 @@
196
193
  "standard": {
197
194
  "ignore": [
198
195
  "lib/configValidator.js",
196
+ "lib/error-serializer.js",
199
197
  "fastify.d.ts",
200
198
  "types/*",
201
199
  "test/types/*",