fastify 4.5.3 → 4.7.0

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 (60) hide show
  1. package/docs/Guides/Ecosystem.md +42 -16
  2. package/docs/Guides/Migration-Guide-V4.md +97 -48
  3. package/docs/Guides/Plugins-Guide.md +44 -0
  4. package/docs/Guides/Write-Plugin.md +1 -1
  5. package/docs/Reference/Logging.md +16 -13
  6. package/docs/Reference/Reply.md +5 -5
  7. package/docs/Reference/Request.md +19 -6
  8. package/docs/Reference/Routes.md +2 -2
  9. package/docs/Reference/Server.md +24 -2
  10. package/docs/Reference/Type-Providers.md +5 -19
  11. package/docs/Reference/TypeScript.md +7 -14
  12. package/examples/typescript-server.ts +1 -1
  13. package/fastify.d.ts +2 -2
  14. package/fastify.js +4 -1
  15. package/lib/contentTypeParser.js +10 -9
  16. package/lib/context.js +27 -3
  17. package/lib/error-handler.js +7 -3
  18. package/lib/error-serializer.js +13 -40
  19. package/lib/handleRequest.js +15 -13
  20. package/lib/reply.js +42 -34
  21. package/lib/request.js +36 -18
  22. package/lib/route.js +25 -10
  23. package/lib/schema-controller.js +7 -1
  24. package/lib/schemas.js +1 -0
  25. package/lib/server.js +6 -1
  26. package/lib/symbols.js +2 -0
  27. package/lib/validation.js +4 -3
  28. package/lib/warnings.js +2 -0
  29. package/package.json +26 -26
  30. package/test/404s.test.js +19 -1
  31. package/test/context-config.test.js +2 -1
  32. package/test/handler-context.test.js +12 -30
  33. package/test/has-route.test.js +77 -0
  34. package/test/internals/contentTypeParser.test.js +3 -3
  35. package/test/internals/handleRequest.test.js +4 -3
  36. package/test/internals/reply-serialize.test.js +9 -9
  37. package/test/internals/reply.test.js +10 -9
  38. package/test/internals/request-validate.test.js +97 -9
  39. package/test/internals/request.test.js +57 -3
  40. package/test/internals/validation.test.js +15 -0
  41. package/test/listen.deprecated.test.js +33 -0
  42. package/test/plugin.test.js +1 -1
  43. package/test/reply-code.test.js +59 -0
  44. package/test/route.test.js +29 -0
  45. package/test/router-options.test.js +33 -66
  46. package/test/schema-feature.test.js +25 -0
  47. package/test/schema-special-usage.test.js +29 -0
  48. package/test/schema-validation.test.js +19 -0
  49. package/test/search.test.js +169 -30
  50. package/test/server.test.js +54 -0
  51. package/test/types/fastify.test-d.ts +9 -0
  52. package/test/types/route.test-d.ts +11 -0
  53. package/test/unsupported-httpversion.test.js +0 -26
  54. package/types/hooks.d.ts +25 -25
  55. package/types/instance.d.ts +27 -21
  56. package/types/logger.d.ts +1 -1
  57. package/types/plugin.d.ts +3 -3
  58. package/types/reply.d.ts +2 -2
  59. package/types/request.d.ts +11 -5
  60. package/types/route.d.ts +9 -9
@@ -148,21 +148,14 @@ fastify.register(pluginWithTypebox)
148
148
 
149
149
  It's also important to mention that once the types don't propagate globally,
150
150
  _currently_ is not possible to avoid multiple registrations on routes when
151
- dealing with several scopes, see bellow:
151
+ dealing with several scopes, see below:
152
152
 
153
153
  ```ts
154
154
  import Fastify from 'fastify'
155
155
  import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
156
156
  import { Type } from '@sinclair/typebox'
157
157
 
158
- const server = Fastify({
159
- ajv: {
160
- customOptions: {
161
- strict: 'log',
162
- keywords: ['kind', 'modifier'],
163
- },
164
- },
165
- }).withTypeProvider<TypeBoxTypeProvider>()
158
+ const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
166
159
 
167
160
  server.register(plugin1) // wrong
168
161
  server.register(plugin2) // correct
@@ -213,14 +206,7 @@ import Fastify from 'fastify'
213
206
  import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
214
207
  import { registerRoutes } from './routes'
215
208
 
216
- const server = Fastify({
217
- ajv: {
218
- customOptions: {
219
- strict: 'log',
220
- keywords: ['kind', 'modifier'],
221
- },
222
- },
223
- }).withTypeProvider<TypeBoxTypeProvider>()
209
+ const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
224
210
 
225
211
  registerRoutes(server)
226
212
 
@@ -232,7 +218,7 @@ server.listen({ port: 3000 })
232
218
  import { Type } from '@sinclair/typebox'
233
219
  import {
234
220
  FastifyInstance,
235
- FastifyLoggerInstance,
221
+ FastifyBaseLogger,
236
222
  RawReplyDefaultExpression,
237
223
  RawRequestDefaultExpression,
238
224
  RawServerDefault
@@ -243,7 +229,7 @@ type FastifyTypebox = FastifyInstance<
243
229
  RawServerDefault,
244
230
  RawRequestDefaultExpression<RawServerDefault>,
245
231
  RawReplyDefaultExpression<RawServerDefault>,
246
- FastifyLoggerInstance,
232
+ FastifyBaseLogger,
247
233
  TypeBoxTypeProvider
248
234
  >;
249
235
 
@@ -61,6 +61,11 @@ in a blank http Fastify server.
61
61
  *Note: Set `target` property in `tsconfig.json` to `es2017` or greater to avoid
62
62
  [FastifyDeprecation](https://github.com/fastify/fastify/issues/3284) warning.*
63
63
 
64
+ *Note 2: Avoid using ```"moduleResolution": "NodeNext"``` in tsconfig.json with
65
+ ```"type": "module"``` in package.json. This combination is currently not
66
+ supported by fastify typing system.
67
+ [ts(2349)](https://github.com/fastify/fastify/issues/4241) warning.*
68
+
64
69
  4. Create an `index.ts` file - this will contain the server code
65
70
  5. Add the following code block to your file:
66
71
  ```typescript
@@ -254,18 +259,6 @@ can do it as follows:
254
259
  )
255
260
  ```
256
261
 
257
- **Note** For Ajv version 7 and above is required to use the `ajvTypeBoxPlugin`:
258
-
259
- ```typescript
260
- import Fastify from 'fastify'
261
- import { ajvTypeBoxPlugin, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
262
-
263
- const fastify = Fastify({
264
- ajv: {
265
- plugins: [ajvTypeBoxPlugin]
266
- }
267
- }).withTypeProvider<TypeBoxTypeProvider>()
268
- ```
269
262
 
270
263
  #### Schemas in JSON Files
271
264
 
@@ -1237,8 +1230,8 @@ const plugin: FastifyPlugin<{
1237
1230
  option2: boolean;
1238
1231
  }> = function (instance, opts, done) { }
1239
1232
 
1240
- fastify().register(plugin, {}) // Error - options object is missing required properties
1241
- fastify().register(plugin, { option1: '', option2: true }) // OK - options object contains required properties
1233
+ server().register(plugin, {}) // Error - options object is missing required properties
1234
+ server().register(plugin, { option1: '', option2: true }) // OK - options object contains required properties
1242
1235
  ```
1243
1236
 
1244
1237
  See the Learn By Example, [Plugins](#plugins) section for more detailed examples
@@ -56,7 +56,7 @@ const opts: RouteShorthandOptions = {
56
56
  };
57
57
 
58
58
  // Add our route handler with correct types
59
- server.get<{
59
+ server.post<{
60
60
  Querystring: PingQuerystring;
61
61
  Params: PingParams;
62
62
  Headers: PingHeaders;
package/fastify.d.ts CHANGED
@@ -84,7 +84,7 @@ export type FastifyHttpsOptions<
84
84
  Server extends https.Server,
85
85
  Logger extends FastifyBaseLogger = FastifyLoggerInstance
86
86
  > = FastifyServerOptions<Server, Logger> & {
87
- https: https.ServerOptions
87
+ https: https.ServerOptions | null
88
88
  }
89
89
 
90
90
  type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
@@ -197,7 +197,7 @@ export { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, Fastif
197
197
  export { FastifyListenOptions, FastifyInstance, PrintRoutesOptions } from './types/instance'
198
198
  export { FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel } from './types/logger'
199
199
  export { FastifyContext, FastifyContextConfig } from './types/context'
200
- export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route'
200
+ export { RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface } from './types/route'
201
201
  export * from './types/register'
202
202
  export { FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction } from './types/content-type-parser'
203
203
  export { FastifyError } from '@fastify/error'
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.5.3'
3
+ const VERSION = '4.7.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -267,6 +267,9 @@ function fastify (options) {
267
267
  // otherwise we should bind it after the declaration
268
268
  return router.route.call(this, { options })
269
269
  },
270
+ hasRoute: function _route (options) {
271
+ return router.hasRoute.call(this, { options })
272
+ },
270
273
  // expose logger instance
271
274
  log: logger,
272
275
  // type provider
@@ -1,11 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { AsyncResource } = require('async_hooks')
4
- let lru = require('tiny-lru')
5
- // Needed to handle Webpack and faux modules
6
- // See https://github.com/fastify/fastify/issues/2356
7
- // and https://github.com/fastify/fastify/discussions/2907.
8
- lru = typeof lru === 'function' ? lru : lru.default
4
+ const lru = require('tiny-lru').lru
9
5
 
10
6
  const secureJson = require('secure-json-parse')
11
7
  const {
@@ -15,7 +11,8 @@ const {
15
11
  kRequestPayloadStream,
16
12
  kState,
17
13
  kTestInternals,
18
- kReplyIsError
14
+ kReplyIsError,
15
+ kRouteContext
19
16
  } = require('./symbols')
20
17
 
21
18
  const {
@@ -149,12 +146,16 @@ ContentTypeParser.prototype.run = function (contentType, handler, request, reply
149
146
  const resource = new AsyncResource('content-type-parser:run', request)
150
147
 
151
148
  if (parser === undefined) {
152
- reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
149
+ if (request.is404) {
150
+ handler(request, reply)
151
+ } else {
152
+ reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
153
+ }
153
154
  } else if (parser.asString === true || parser.asBuffer === true) {
154
155
  rawBody(
155
156
  request,
156
157
  reply,
157
- reply.context._parserOptions,
158
+ reply[kRouteContext]._parserOptions,
158
159
  parser,
159
160
  done
160
161
  )
@@ -185,7 +186,7 @@ function rawBody (request, reply, options, parser, done) {
185
186
  const limit = options.limit === null ? parser.bodyLimit : options.limit
186
187
  const contentLength = request.headers['content-length'] === undefined
187
188
  ? NaN
188
- : Number.parseInt(request.headers['content-length'], 10)
189
+ : Number(request.headers['content-length'])
189
190
 
190
191
  if (contentLength > limit) {
191
192
  reply.send(new FST_ERR_CTP_BODY_TOO_LARGE())
package/lib/context.js CHANGED
@@ -12,10 +12,11 @@ const {
12
12
  kContentTypeParser,
13
13
  kRouteByFastify,
14
14
  kRequestValidateWeakMap,
15
- kReplySerializeWeakMap
15
+ kReplySerializeWeakMap,
16
+ kPublicRouteContext
16
17
  } = require('./symbols.js')
17
18
 
18
- // Objects that holds the context of every request
19
+ // Object that holds the context of every request
19
20
  // Every route holds an instance of this object.
20
21
  function Context ({
21
22
  schema,
@@ -56,7 +57,10 @@ function Context ({
56
57
  this[kFourOhFourContext] = null
57
58
  this.attachValidation = attachValidation
58
59
  this[kReplySerializerDefault] = replySerializer
59
- this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter
60
+ this.schemaErrorFormatter =
61
+ schemaErrorFormatter ||
62
+ server[kSchemaErrorFormatter] ||
63
+ defaultSchemaErrorFormatter
60
64
  this[kRouteByFastify] = isFastify
61
65
 
62
66
  this[kRequestValidateWeakMap] = null
@@ -64,9 +68,29 @@ function Context ({
64
68
  this.validatorCompiler = validatorCompiler || null
65
69
  this.serializerCompiler = serializerCompiler || null
66
70
 
71
+ // Route + Userland configurations for the route
72
+ this[kPublicRouteContext] = getPublicRouteContext(this)
73
+
67
74
  this.server = server
68
75
  }
69
76
 
77
+ function getPublicRouteContext (context) {
78
+ return Object.create(null, {
79
+ schema: {
80
+ enumerable: true,
81
+ get () {
82
+ return context.schema
83
+ }
84
+ },
85
+ config: {
86
+ enumerable: true,
87
+ get () {
88
+ return context.config
89
+ }
90
+ }
91
+ })
92
+ }
93
+
70
94
  function defaultSchemaErrorFormatter (errors, dataVar) {
71
95
  let text = ''
72
96
  const separator = ', '
@@ -3,7 +3,11 @@
3
3
  const statusCodes = require('http').STATUS_CODES
4
4
  const wrapThenable = require('./wrapThenable')
5
5
  const {
6
- kReplyHeaders, kReplyNextErrorHandler, kReplyIsRunningOnErrorHook, kReplyHasStatusCode
6
+ kReplyHeaders,
7
+ kReplyNextErrorHandler,
8
+ kReplyIsRunningOnErrorHook,
9
+ kReplyHasStatusCode,
10
+ kRouteContext
7
11
  } = require('./symbols.js')
8
12
 
9
13
  const {
@@ -24,7 +28,7 @@ const rootErrorHandler = {
24
28
  function handleError (reply, error, cb) {
25
29
  reply[kReplyIsRunningOnErrorHook] = false
26
30
 
27
- const context = reply.context
31
+ const context = reply[kRouteContext]
28
32
  if (reply[kReplyNextErrorHandler] === false) {
29
33
  fallbackErrorHandler(error, reply, function (reply, payload) {
30
34
  try {
@@ -90,7 +94,7 @@ function fallbackErrorHandler (error, reply, cb) {
90
94
  const statusCode = reply.statusCode
91
95
  let payload
92
96
  try {
93
- const serializerFn = getSchemaSerializer(reply.context, statusCode)
97
+ const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode)
94
98
  payload = (serializerFn === false)
95
99
  ? serializeError({
96
100
  error: statusCodes[statusCode + ''],
@@ -23,14 +23,6 @@ class Serializer {
23
23
  }
24
24
  }
25
25
 
26
- asAny (i) {
27
- return JSON.stringify(i)
28
- }
29
-
30
- asNull () {
31
- return 'null'
32
- }
33
-
34
26
  asInteger (i) {
35
27
  if (typeof i === 'bigint') {
36
28
  return i.toString()
@@ -47,10 +39,6 @@ class Serializer {
47
39
  }
48
40
  }
49
41
 
50
- asIntegerNullable (i) {
51
- return i === null ? 'null' : this.asInteger(i)
52
- }
53
-
54
42
  asNumber (i) {
55
43
  const num = Number(i)
56
44
  if (Number.isNaN(num)) {
@@ -62,54 +50,43 @@ class Serializer {
62
50
  }
63
51
  }
64
52
 
65
- asNumberNullable (i) {
66
- return i === null ? 'null' : this.asNumber(i)
67
- }
68
-
69
53
  asBoolean (bool) {
70
54
  return bool && 'true' || 'false' // eslint-disable-line
71
55
  }
72
56
 
73
- asBooleanNullable (bool) {
74
- return bool === null ? 'null' : this.asBoolean(bool)
75
- }
76
-
77
57
  asDateTime (date) {
78
58
  if (date === null) return '""'
79
59
  if (date instanceof Date) {
80
60
  return '"' + date.toISOString() + '"'
81
61
  }
62
+ if (typeof date === 'string') {
63
+ return '"' + date + '"'
64
+ }
82
65
  throw new Error(`The value "${date}" cannot be converted to a date-time.`)
83
66
  }
84
67
 
85
- asDateTimeNullable (date) {
86
- return date === null ? 'null' : this.asDateTime(date)
87
- }
88
-
89
68
  asDate (date) {
90
69
  if (date === null) return '""'
91
70
  if (date instanceof Date) {
92
71
  return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"'
93
72
  }
73
+ if (typeof date === 'string') {
74
+ return '"' + date + '"'
75
+ }
94
76
  throw new Error(`The value "${date}" cannot be converted to a date.`)
95
77
  }
96
78
 
97
- asDateNullable (date) {
98
- return date === null ? 'null' : this.asDate(date)
99
- }
100
-
101
79
  asTime (date) {
102
80
  if (date === null) return '""'
103
81
  if (date instanceof Date) {
104
82
  return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"'
105
83
  }
84
+ if (typeof date === 'string') {
85
+ return '"' + date + '"'
86
+ }
106
87
  throw new Error(`The value "${date}" cannot be converted to a time.`)
107
88
  }
108
89
 
109
- asTimeNullable (date) {
110
- return date === null ? 'null' : this.asTime(date)
111
- }
112
-
113
90
  asString (str) {
114
91
  const quotes = '"'
115
92
  if (str instanceof Date) {
@@ -129,10 +106,6 @@ class Serializer {
129
106
  }
130
107
  }
131
108
 
132
- asStringNullable (str) {
133
- return str === null ? 'null' : this.asString(str)
134
- }
135
-
136
109
  // magically escape strings for json
137
110
  // relying on their charCodeAt
138
111
  // everything below 32 needs JSON.stringify()
@@ -200,7 +173,7 @@ class Serializer {
200
173
  }
201
174
 
202
175
  json += "\"statusCode\"" + ':'
203
- json += serializer.asNumber.bind(serializer)(obj["statusCode"])
176
+ json += serializer.asNumber(obj["statusCode"])
204
177
  }
205
178
 
206
179
  if (obj["code"] !== undefined) {
@@ -212,7 +185,7 @@ class Serializer {
212
185
  }
213
186
 
214
187
  json += "\"code\"" + ':'
215
- json += serializer.asString.bind(serializer)(obj["code"])
188
+ json += serializer.asString(obj["code"])
216
189
  }
217
190
 
218
191
  if (obj["error"] !== undefined) {
@@ -224,7 +197,7 @@ class Serializer {
224
197
  }
225
198
 
226
199
  json += "\"error\"" + ':'
227
- json += serializer.asString.bind(serializer)(obj["error"])
200
+ json += serializer.asString(obj["error"])
228
201
  }
229
202
 
230
203
  if (obj["message"] !== undefined) {
@@ -236,7 +209,7 @@ class Serializer {
236
209
  }
237
210
 
238
211
  json += "\"message\"" + ':'
239
- json += serializer.asString.bind(serializer)(obj["message"])
212
+ json += serializer.asString(obj["message"])
240
213
  }
241
214
 
242
215
  json += '}'
@@ -4,7 +4,8 @@ const { validate: validateSchema } = require('./validation')
4
4
  const { hookRunner, hookIterator } = require('./hooks')
5
5
  const wrapThenable = require('./wrapThenable')
6
6
  const {
7
- kReplyIsError
7
+ kReplyIsError,
8
+ kRouteContext
8
9
  } = require('./symbols')
9
10
 
10
11
  function handleRequest (err, request, reply) {
@@ -17,15 +18,16 @@ function handleRequest (err, request, reply) {
17
18
 
18
19
  const method = request.raw.method
19
20
  const headers = request.headers
21
+ const context = request[kRouteContext]
20
22
 
21
- if (method === 'GET' || method === 'HEAD' || method === 'SEARCH') {
23
+ if (method === 'GET' || method === 'HEAD') {
22
24
  handler(request, reply)
23
25
  return
24
26
  }
25
27
 
26
28
  const contentType = headers['content-type']
27
29
 
28
- if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE') {
30
+ if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH') {
29
31
  if (contentType === undefined) {
30
32
  if (
31
33
  headers['transfer-encoding'] === undefined &&
@@ -33,10 +35,10 @@ function handleRequest (err, request, reply) {
33
35
  ) { // Request has no body to parse
34
36
  handler(request, reply)
35
37
  } else {
36
- reply.context.contentTypeParser.run('', handler, request, reply)
38
+ context.contentTypeParser.run('', handler, request, reply)
37
39
  }
38
40
  } else {
39
- reply.context.contentTypeParser.run(contentType, handler, request, reply)
41
+ context.contentTypeParser.run(contentType, handler, request, reply)
40
42
  }
41
43
  return
42
44
  }
@@ -49,7 +51,7 @@ function handleRequest (err, request, reply) {
49
51
  headers['content-length'] !== undefined
50
52
  )
51
53
  ) {
52
- reply.context.contentTypeParser.run(contentType, handler, request, reply)
54
+ context.contentTypeParser.run(contentType, handler, request, reply)
53
55
  } else {
54
56
  handler(request, reply)
55
57
  }
@@ -62,9 +64,9 @@ function handleRequest (err, request, reply) {
62
64
 
63
65
  function handler (request, reply) {
64
66
  try {
65
- if (reply.context.preValidation !== null) {
67
+ if (request[kRouteContext].preValidation !== null) {
66
68
  hookRunner(
67
- reply.context.preValidation,
69
+ request[kRouteContext].preValidation,
68
70
  hookIterator,
69
71
  request,
70
72
  reply,
@@ -87,9 +89,9 @@ function preValidationCallback (err, request, reply) {
87
89
  return
88
90
  }
89
91
 
90
- const result = validateSchema(reply.context, request)
92
+ const result = validateSchema(reply[kRouteContext], request)
91
93
  if (result) {
92
- if (reply.context.attachValidation === false) {
94
+ if (reply[kRouteContext].attachValidation === false) {
93
95
  reply.send(result)
94
96
  return
95
97
  }
@@ -98,9 +100,9 @@ function preValidationCallback (err, request, reply) {
98
100
  }
99
101
 
100
102
  // preHandler hook
101
- if (reply.context.preHandler !== null) {
103
+ if (request[kRouteContext].preHandler !== null) {
102
104
  hookRunner(
103
- reply.context.preHandler,
105
+ request[kRouteContext].preHandler,
104
106
  hookIterator,
105
107
  request,
106
108
  reply,
@@ -123,7 +125,7 @@ function preHandlerCallback (err, request, reply) {
123
125
  let result
124
126
 
125
127
  try {
126
- result = reply.context.handler(request, reply)
128
+ result = request[kRouteContext].handler(request, reply)
127
129
  } catch (err) {
128
130
  reply[kReplyIsError] = true
129
131
  reply.send(err)