fastify 4.0.1 → 4.1.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.
package/.eslintrc CHANGED
@@ -1,3 +1,4 @@
1
1
  {
2
+ "root": true,
2
3
  "extends": "standard"
3
4
  }
package/README.md CHANGED
@@ -56,7 +56,7 @@ developer experience with the least overhead and a powerful plugin architecture.
56
56
  It is inspired by Hapi and Express and as far as we know, it is one of the
57
57
  fastest web frameworks in town.
58
58
 
59
- This branch refers to the upcoming Fastify v4 release. Check out the
59
+ This branch refers to the Fastify v4 release. Check out the
60
60
  [v3.x](https://github.com/fastify/fastify/tree/v3.x) branch for v3.
61
61
 
62
62
  ### Quick start
@@ -104,11 +104,11 @@ project as a dependency:
104
104
 
105
105
  Install with npm:
106
106
  ```sh
107
- npm i fastify@next
107
+ npm i fastify
108
108
  ```
109
109
  Install with yarn:
110
110
  ```sh
111
- yarn add fastify@next
111
+ yarn add fastify
112
112
  ```
113
113
 
114
114
  ### Example
@@ -1,3 +1,4 @@
1
+ /* istanbul ignore file */
1
2
  'use strict'
2
3
 
3
4
  const FJS = require('fast-json-stringify')
@@ -37,7 +37,7 @@ fastify.get('/user/:id', function(req, reply) {
37
37
  )
38
38
  })
39
39
 
40
- fastify.listen(3000, err => {
40
+ fastify.listen({ port: 3000 }, err => {
41
41
  if (err) throw err
42
42
  console.log(`server listening on ${fastify.server.address().port}`)
43
43
  })
@@ -64,7 +64,7 @@ fastify.get('/user/:id', function (req, reply) {
64
64
  )
65
65
  })
66
66
 
67
- fastify.listen(3000, err => {
67
+ fastify.listen({ port: 3000 }, err => {
68
68
  if (err) throw err
69
69
  console.log(`server listening on ${fastify.server.address().port}`)
70
70
  })
@@ -98,7 +98,7 @@ fastify.post('/foo', function (req, reply) {
98
98
  })
99
99
  })
100
100
 
101
- fastify.listen(3000, err => {
101
+ fastify.listen({ port: 3000 }, err => {
102
102
  if (err) throw err
103
103
  console.log(`server listening on ${fastify.server.address().port}`)
104
104
  })
@@ -145,7 +145,7 @@ fastify.get('/user/:id', function (req, reply) {
145
145
  })
146
146
  })
147
147
 
148
- fastify.listen(3000, err => {
148
+ fastify.listen({ port: 3000 }, err => {
149
149
  if (err) throw err
150
150
  })
151
151
  ```
@@ -172,7 +172,7 @@ fastify.post('/foo', async function (req, reply) {
172
172
  return { status: 'ok' }
173
173
  })
174
174
 
175
- fastify.listen(3000, err => {
175
+ fastify.listen({ port: 3000 }, err => {
176
176
  if (err) throw err
177
177
  console.log(`server listening on ${fastify.server.address().port}`)
178
178
  })
@@ -346,6 +346,9 @@ section.
346
346
  minification and transformation of responses.
347
347
  - [`fastify-mongo-memory`](https://github.com/chapuletta/fastify-mongo-memory)
348
348
  Fastify MongoDB in Memory Plugin for testing support.
349
+ - [`fastify-mongodb-sanitizer`](https://github.com/KlemenKozelj/fastify-mongodb-sanitizer)
350
+ Fastify plugin that sanitizes client input to prevent
351
+ potential MongoDB query injection attacks.
349
352
  - [`fastify-mongoose-api`](https://github.com/jeka-kiselyov/fastify-mongoose-api)
350
353
  Fastify plugin to create REST API methods based on Mongoose MongoDB models.
351
354
  - [`fastify-mongoose-driver`](https://github.com/alex-ppg/fastify-mongoose)
@@ -14,14 +14,31 @@ Starting this version of Fastify, we have deprecated the use of `app.use()`. We
14
14
  have decided not to support the use of middlewares. Both
15
15
  [`@fastify/middie`](https://github.com/fastify/middie) and
16
16
  [`@fastify/express`](https://github.com/fastify/fastify-express) will still be
17
- there and maintained. Use Fastify's [hooks](./Reference/Hooks.md) instead.
17
+ there and maintained. Use Fastify's [hooks](../Reference/Hooks.md) instead.
18
+
19
+ ### `reply.res` moved to `reply.raw`
20
+
21
+ If you previously used the `reply.res` attribute to access the underlying Request
22
+ object you'll instead need to depend on `reply.raw`.
23
+
24
+ ### Need to `return reply` to signal a "fork" of the promise chain
25
+
26
+ In some situations, like when a response is sent asynchronously or when you're
27
+ just not explicitly returning a response, you'll need to return the `reply`
28
+ argument from your router handler.
29
+
30
+ ### `exposeHeadRoutes` true by default
31
+
32
+ Starting from v4, all the `GET` routes will create a sibling `HEAD` route.
33
+ You can revert this behaviour by setting the server's option `exposeHeadRoutes`
34
+ to `false`.
18
35
 
19
36
  ## Non Breaking Changes
20
37
 
21
38
  ### Change of schema for multiple types
22
39
 
23
40
 
24
- Since Fastiy v4 has upgraded to Ajv v8. The "type" keywords with multiple types
41
+ Since Fastify v4 has upgraded to Ajv v8. The "type" keywords with multiple types
25
42
  (other than with "null") are prohibited. Read more
26
43
  ['here'](https://ajv.js.org/strict-mode.html#strict-types)
27
44
 
@@ -47,8 +47,8 @@ A "month" is defined as 30 consecutive days.
47
47
  | :------ | :----------- | :-------------- | :------------------- |
48
48
  | 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 |
49
49
  | 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 |
50
- | 3.0.0 | 2020-07-07 | TBD | 10, 12, 14, 16, 18 |
51
- | 4.0.0 | TBD | TBD | 14, 16, 18 |
50
+ | 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 |
51
+ | 4.0.0 | 2022-06-08 | TBD | 14, 16, 18 |
52
52
 
53
53
  ### CI tested operating systems
54
54
  <a id="supported-os"></a>
@@ -1278,7 +1278,7 @@ const fastify = Fastify({
1278
1278
  */
1279
1279
  bucket: function factory (parentSchemas) {
1280
1280
  return {
1281
- addSchema (inputSchema) {
1281
+ add (inputSchema) {
1282
1282
  // This function must store the schema added by the user.
1283
1283
  // This function is invoked when `fastify.addSchema()` is called.
1284
1284
  },
@@ -30,11 +30,11 @@ $ npm i @fastify/type-provider-json-schema-to-ts
30
30
  ```
31
31
 
32
32
  ```typescript
33
- import { JsonSchemaToTsTypeProvider } from '@fastify/type-provider-json-schema-to-ts'
33
+ import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
34
34
 
35
35
  import fastify from 'fastify'
36
36
 
37
- const server = fastify().withTypeProvider<JsonSchemaToTsTypeProvider>()
37
+ const server = fastify().withTypeProvider<JsonSchemaToTsProvider>()
38
38
 
39
39
  server.get('/route', {
40
40
  schema: {
@@ -65,7 +65,8 @@ $ npm i @fastify/type-provider-typebox
65
65
  ```
66
66
 
67
67
  ```typescript
68
- import { TypeBoxTypeProvider, Type } from '@fastify/type-provider-typebox'
68
+ import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
69
+ import { Type } from '@sinclair/typebox'
69
70
 
70
71
  import fastify from 'fastify'
71
72
 
package/fastify.d.ts CHANGED
@@ -146,7 +146,7 @@ export type FastifyServerOptions<
146
146
  },
147
147
  schemaController?: {
148
148
  bucket?: (parentSchemas?: unknown) => {
149
- addSchema(schema: unknown): FastifyInstance;
149
+ add(schema: unknown): FastifyInstance;
150
150
  getSchema(schemaId: string): unknown;
151
151
  getSchemas(): Record<string, unknown>;
152
152
  };
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.0.1'
3
+ const VERSION = '4.1.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -237,35 +237,35 @@ function fastify (options) {
237
237
  getDefaultRoute: router.getDefaultRoute.bind(router),
238
238
  setDefaultRoute: router.setDefaultRoute.bind(router),
239
239
  // routes shorthand methods
240
- delete: function _delete (url, opts, handler) {
241
- return router.prepareRoute.call(this, 'DELETE', url, opts, handler)
240
+ delete: function _delete (url, options, handler) {
241
+ return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })
242
242
  },
243
- get: function _get (url, opts, handler) {
244
- return router.prepareRoute.call(this, 'GET', url, opts, handler)
243
+ get: function _get (url, options, handler) {
244
+ return router.prepareRoute.call(this, { method: 'GET', url, options, handler })
245
245
  },
246
- head: function _head (url, opts, handler) {
247
- return router.prepareRoute.call(this, 'HEAD', url, opts, handler)
246
+ head: function _head (url, options, handler) {
247
+ return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })
248
248
  },
249
- patch: function _patch (url, opts, handler) {
250
- return router.prepareRoute.call(this, 'PATCH', url, opts, handler)
249
+ patch: function _patch (url, options, handler) {
250
+ return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })
251
251
  },
252
- post: function _post (url, opts, handler) {
253
- return router.prepareRoute.call(this, 'POST', url, opts, handler)
252
+ post: function _post (url, options, handler) {
253
+ return router.prepareRoute.call(this, { method: 'POST', url, options, handler })
254
254
  },
255
- put: function _put (url, opts, handler) {
256
- return router.prepareRoute.call(this, 'PUT', url, opts, handler)
255
+ put: function _put (url, options, handler) {
256
+ return router.prepareRoute.call(this, { method: 'PUT', url, options, handler })
257
257
  },
258
- options: function _options (url, opts, handler) {
259
- return router.prepareRoute.call(this, 'OPTIONS', url, opts, handler)
258
+ options: function _options (url, options, handler) {
259
+ return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
260
260
  },
261
- all: function _all (url, opts, handler) {
262
- return router.prepareRoute.call(this, supportedMethods, url, opts, handler)
261
+ all: function _all (url, options, handler) {
262
+ return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler })
263
263
  },
264
264
  // extended route
265
- route: function _route (opts) {
265
+ route: function _route (options) {
266
266
  // we need the fastify object that we are producing so we apply a lazy loading of the function,
267
267
  // otherwise we should bind it after the declaration
268
- return router.route.call(this, opts)
268
+ return router.route.call(this, { options })
269
269
  },
270
270
  // expose logger instance
271
271
  log: logger,
@@ -1,15 +1,183 @@
1
1
  // This file is autogenerated by build/build-error-serializer.js, do not edit
2
2
  /* istanbul ignore file */
3
3
 
4
- 'use strict'
4
+ 'use strict'
5
5
 
6
- const Serializer = require('fast-json-stringify/serializer')
7
- const buildAjv = require('fast-json-stringify/ajv')
6
+
7
+
8
+ class Serializer {
9
+ constructor (options = {}) {
10
+ switch (options.rounding) {
11
+ case 'floor':
12
+ this.parseInteger = Math.floor
13
+ break
14
+ case 'ceil':
15
+ this.parseInteger = Math.ceil
16
+ break
17
+ case 'round':
18
+ this.parseInteger = Math.round
19
+ break
20
+ default:
21
+ this.parseInteger = Math.trunc
22
+ break
23
+ }
24
+ }
8
25
 
9
- const serializer = new Serializer({"mode":"standalone"})
10
- const ajv = buildAjv({})
26
+ asAny (i) {
27
+ return JSON.stringify(i)
28
+ }
29
+
30
+ asNull () {
31
+ return 'null'
32
+ }
11
33
 
34
+ asInteger (i) {
35
+ if (typeof i === 'bigint') {
36
+ return i.toString()
37
+ } else if (Number.isInteger(i)) {
38
+ return '' + i
39
+ } else {
40
+ /* eslint no-undef: "off" */
41
+ const integer = this.parseInteger(i)
42
+ if (Number.isNaN(integer)) {
43
+ throw new Error(`The value "${i}" cannot be converted to an integer.`)
44
+ } else {
45
+ return '' + integer
46
+ }
47
+ }
48
+ }
49
+
50
+ asIntegerNullable (i) {
51
+ return i === null ? 'null' : this.asInteger(i)
52
+ }
53
+
54
+ asNumber (i) {
55
+ const num = Number(i)
56
+ if (Number.isNaN(num)) {
57
+ throw new Error(`The value "${i}" cannot be converted to a number.`)
58
+ } else {
59
+ return '' + num
60
+ }
61
+ }
62
+
63
+ asNumberNullable (i) {
64
+ return i === null ? 'null' : this.asNumber(i)
65
+ }
66
+
67
+ asBoolean (bool) {
68
+ return bool && 'true' || 'false' // eslint-disable-line
69
+ }
70
+
71
+ asBooleanNullable (bool) {
72
+ return bool === null ? 'null' : this.asBoolean(bool)
73
+ }
74
+
75
+ asDatetime (date, skipQuotes) {
76
+ const quotes = skipQuotes === true ? '' : '"'
77
+ if (date instanceof Date) {
78
+ return quotes + date.toISOString() + quotes
79
+ }
80
+ return this.asString(date, skipQuotes)
81
+ }
82
+
83
+ asDatetimeNullable (date, skipQuotes) {
84
+ return date === null ? 'null' : this.asDatetime(date, skipQuotes)
85
+ }
86
+
87
+ asDate (date, skipQuotes) {
88
+ const quotes = skipQuotes === true ? '' : '"'
89
+ if (date instanceof Date) {
90
+ return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
91
+ }
92
+ return this.asString(date, skipQuotes)
93
+ }
12
94
 
95
+ asDateNullable (date, skipQuotes) {
96
+ return date === null ? 'null' : this.asDate(date, skipQuotes)
97
+ }
98
+
99
+ asTime (date, skipQuotes) {
100
+ const quotes = skipQuotes === true ? '' : '"'
101
+ if (date instanceof Date) {
102
+ return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
103
+ }
104
+ return this.asString(date, skipQuotes)
105
+ }
106
+
107
+ asTimeNullable (date, skipQuotes) {
108
+ return date === null ? 'null' : this.asTime(date, skipQuotes)
109
+ }
110
+
111
+ asString (str, skipQuotes) {
112
+ const quotes = skipQuotes === true ? '' : '"'
113
+ if (str instanceof Date) {
114
+ return quotes + str.toISOString() + quotes
115
+ } else if (str === null) {
116
+ return quotes + quotes
117
+ } else if (str instanceof RegExp) {
118
+ str = str.source
119
+ } else if (typeof str !== 'string') {
120
+ str = str.toString()
121
+ }
122
+ // If we skipQuotes it means that we are using it as test
123
+ // no need to test the string length for the render
124
+ if (skipQuotes) {
125
+ return str
126
+ }
127
+
128
+ if (str.length < 42) {
129
+ return this.asStringSmall(str)
130
+ } else {
131
+ return JSON.stringify(str)
132
+ }
133
+ }
134
+
135
+ asStringNullable (str) {
136
+ return str === null ? 'null' : this.asString(str)
137
+ }
138
+
139
+ // magically escape strings for json
140
+ // relying on their charCodeAt
141
+ // everything below 32 needs JSON.stringify()
142
+ // every string that contain surrogate needs JSON.stringify()
143
+ // 34 and 92 happens all the time, so we
144
+ // have a fast case for them
145
+ asStringSmall (str) {
146
+ const l = str.length
147
+ let result = ''
148
+ let last = 0
149
+ let found = false
150
+ let surrogateFound = false
151
+ let point = 255
152
+ // eslint-disable-next-line
153
+ for (var i = 0; i < l && point >= 32; i++) {
154
+ point = str.charCodeAt(i)
155
+ if (point >= 0xD800 && point <= 0xDFFF) {
156
+ // The current character is a surrogate.
157
+ surrogateFound = true
158
+ }
159
+ if (point === 34 || point === 92) {
160
+ result += str.slice(last, i) + '\\'
161
+ last = i
162
+ found = true
163
+ }
164
+ }
165
+
166
+ if (!found) {
167
+ result = str
168
+ } else {
169
+ result += str.slice(last)
170
+ }
171
+ return ((point < 32) || (surrogateFound === true)) ? JSON.stringify(str) : '"' + result + '"'
172
+ }
173
+ }
174
+
175
+
176
+
177
+ const serializer = new Serializer({"mode":"standalone"})
178
+
179
+
180
+
13
181
  function main (input) {
14
182
  let json = ''
15
183
  json += anonymous0(input)
@@ -81,5 +249,5 @@ const ajv = buildAjv({})
81
249
 
82
250
 
83
251
 
84
- module.exports = main
85
-
252
+ module.exports = main
253
+
package/lib/reply.js CHANGED
@@ -291,7 +291,7 @@ Reply.prototype.removeTrailer = function (key) {
291
291
 
292
292
  Reply.prototype.code = function (code) {
293
293
  const intValue = parseInt(code)
294
- if (isNaN(intValue) || intValue < 100 || intValue > 600) {
294
+ if (isNaN(intValue) || intValue < 100 || intValue > 599) {
295
295
  throw new FST_ERR_BAD_STATUS_CODE(code || String(code))
296
296
  }
297
297
 
@@ -331,10 +331,12 @@ Reply.prototype.redirect = function (code, url) {
331
331
  }
332
332
 
333
333
  this.header('location', url).code(code).send()
334
+ return this
334
335
  }
335
336
 
336
337
  Reply.prototype.callNotFound = function () {
337
338
  notFound(this)
339
+ return this
338
340
  }
339
341
 
340
342
  Reply.prototype.getResponseTime = function () {
package/lib/route.js CHANGED
@@ -113,7 +113,7 @@ function buildRouting (options) {
113
113
  }
114
114
 
115
115
  // Convert shorthand to extended route declaration
116
- function prepareRoute (method, url, options, handler) {
116
+ function prepareRoute ({ method, url, options, handler }) {
117
117
  if (typeof url !== 'string') {
118
118
  throw new FST_ERR_INVALID_URL(typeof url)
119
119
  }
@@ -140,11 +140,11 @@ function buildRouting (options) {
140
140
  handler: handler || (options && options.handler)
141
141
  })
142
142
 
143
- return route.call(this, options)
143
+ return route.call(this, { options })
144
144
  }
145
145
 
146
146
  // Route management
147
- function route (options) {
147
+ function route ({ options }) {
148
148
  // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
149
149
  const opts = { ...options }
150
150
 
@@ -176,30 +176,30 @@ function buildRouting (options) {
176
176
  if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
177
177
  switch (opts.prefixTrailingSlash) {
178
178
  case 'slash':
179
- addNewRoute.call(this, path)
179
+ addNewRoute.call(this, { path })
180
180
  break
181
181
  case 'no-slash':
182
- addNewRoute.call(this, '')
182
+ addNewRoute.call(this, { path: '' })
183
183
  break
184
184
  case 'both':
185
185
  default:
186
- addNewRoute.call(this, '')
186
+ addNewRoute.call(this, { path: '' })
187
187
  // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
188
188
  if (ignoreTrailingSlash !== true && (ignoreDuplicateSlashes !== true || !prefix.endsWith('/'))) {
189
- addNewRoute.call(this, path, true)
189
+ addNewRoute.call(this, { path, prefixing: true })
190
190
  }
191
191
  }
192
192
  } else if (path[0] === '/' && prefix.endsWith('/')) {
193
193
  // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
194
- addNewRoute.call(this, path.slice(1))
194
+ addNewRoute.call(this, { path: path.slice(1) })
195
195
  } else {
196
- addNewRoute.call(this, path)
196
+ addNewRoute.call(this, { path })
197
197
  }
198
198
 
199
199
  // chainable api
200
200
  return this
201
201
 
202
- function addNewRoute (path, prefixing = false) {
202
+ function addNewRoute ({ path, prefixing = false }) {
203
203
  const url = prefix + path
204
204
 
205
205
  opts.url = url
@@ -326,7 +326,7 @@ function buildRouting (options) {
326
326
 
327
327
  if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
328
328
  const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
329
- prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
329
+ prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers } })
330
330
  } else if (headRouteExists && exposeHeadRoute) {
331
331
  warning.emit('FSTDEP007')
332
332
  }
package/lib/server.js CHANGED
@@ -41,7 +41,15 @@ function createServer (options, httpHandler) {
41
41
  listenOptions.cb = cb
42
42
  }
43
43
 
44
- const { host = 'localhost' } = listenOptions
44
+ // If we have a path specified, don't default host to 'localhost' so we don't end up listening
45
+ // on both path and host
46
+ // See https://github.com/fastify/fastify/issues/4007
47
+ let host
48
+ if (listenOptions.path == null) {
49
+ host = listenOptions.host ?? 'localhost'
50
+ } else {
51
+ host = listenOptions.host
52
+ }
45
53
  if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) {
46
54
  listenOptions.host = host
47
55
  }
@@ -11,9 +11,14 @@ function wrapThenable (thenable, reply) {
11
11
  return
12
12
  }
13
13
 
14
- // this is for async functions that
15
- // are using reply.send directly
16
- if (payload !== undefined || reply.sent === false) {
14
+ // this is for async functions that are using reply.send directly
15
+ //
16
+ // since wrap-thenable will be called when using reply.send directly
17
+ // without actual return. the response can be sent already or
18
+ // the request may be terminated during the reply. in this situation,
19
+ // it require an extra checking of request.aborted to see whether
20
+ // the request is killed by client.
21
+ if (payload !== undefined || (reply.sent === false && reply.raw.headersSent === false && reply.request.raw.aborted === false)) {
17
22
  // we use a try-catch internally to avoid adding a catch to another
18
23
  // promise, increase promise perf by 10%
19
24
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -17,7 +17,7 @@
17
17
  "lint:markdown": "markdownlint-cli2",
18
18
  "lint:standard": "standard | snazzy",
19
19
  "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
20
- "prepublishOnly": "tap --no-check-coverage test/internals/version.test.js",
20
+ "prepublishOnly": "tap --no-check-coverage test/build/**.test.js",
21
21
  "test": "npm run lint && npm run unit && npm run test:typescript",
22
22
  "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
23
23
  "test:report": "npm run lint && npm run unit:report && npm run test:typescript",
@@ -128,7 +128,7 @@
128
128
  "@fastify/pre-commit": "^2.0.2",
129
129
  "@sinclair/typebox": "^0.23.5",
130
130
  "@sinonjs/fake-timers": "^9.1.2",
131
- "@types/node": "^17.0.38",
131
+ "@types/node": "^18.0.0",
132
132
  "@typescript-eslint/eslint-plugin": "^5.27.0",
133
133
  "@typescript-eslint/parser": "^5.27.0",
134
134
  "ajv": "^8.11.0",
@@ -146,7 +146,7 @@
146
146
  "eslint-plugin-n": "^15.2.0",
147
147
  "eslint-plugin-promise": "^6.0.0",
148
148
  "fast-json-body": "^1.1.0",
149
- "fast-json-stringify": "^4.1.0",
149
+ "fast-json-stringify": "^4.2.0",
150
150
  "fastify-plugin": "^3.0.1",
151
151
  "fluent-json-schema": "^3.1.0",
152
152
  "form-data": "^4.0.0",
@@ -170,7 +170,7 @@
170
170
  "split2": "^4.1.0",
171
171
  "standard": "^17.0.0-2",
172
172
  "tap": "^16.2.0",
173
- "tsd": "^0.20.0",
173
+ "tsd": "^0.21.0",
174
174
  "typescript": "^4.7.2",
175
175
  "undici": "^5.4.0",
176
176
  "x-xss-protection": "^2.0.0",
@@ -179,7 +179,7 @@
179
179
  "dependencies": {
180
180
  "@fastify/ajv-compiler": "^3.1.0",
181
181
  "@fastify/error": "^3.0.0",
182
- "@fastify/fast-json-stringify-compiler": "^3.0.0",
182
+ "@fastify/fast-json-stringify-compiler": "^3.0.1",
183
183
  "abstract-logging": "^2.0.1",
184
184
  "avvio": "^8.1.3",
185
185
  "find-my-way": "^6.3.0",
package/test/404s.test.js CHANGED
@@ -1320,7 +1320,7 @@ test('preHandler option for setNotFoundHandler', t => {
1320
1320
 
1321
1321
  // https://github.com/fastify/fastify/issues/2229
1322
1322
  t.test('preHandler hook in setNotFoundHandler should be called when callNotFound', { timeout: 40000 }, t => {
1323
- t.plan(2)
1323
+ t.plan(3)
1324
1324
  const fastify = Fastify()
1325
1325
 
1326
1326
  fastify.setNotFoundHandler({
@@ -1333,7 +1333,7 @@ test('preHandler option for setNotFoundHandler', t => {
1333
1333
  })
1334
1334
 
1335
1335
  fastify.post('/', function (req, reply) {
1336
- reply.callNotFound()
1336
+ t.equal(reply.callNotFound(), reply)
1337
1337
  })
1338
1338
 
1339
1339
  fastify.inject({
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const fs = require('fs')
6
+ const path = require('path')
7
+
8
+ const { code } = require('../../build/build-error-serializer')
9
+
10
+ function unifyLineBreak (str) {
11
+ return str.toString().replace(/\r\n/g, '\n')
12
+ }
13
+
14
+ test('check generated code syntax', async (t) => {
15
+ t.plan(1)
16
+
17
+ // standard is a esm, we import it like this
18
+ const { default: standard } = await import('standard')
19
+ const result = await standard.lintText(code)
20
+
21
+ // if there are any invalid syntax
22
+ // fatal count will be greater than 0
23
+ t.equal(result[0].fatalErrorCount, 0)
24
+ })
25
+
26
+ test('ensure the current error serializer is latest', async (t) => {
27
+ t.plan(1)
28
+
29
+ const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js'))
30
+
31
+ // line break should not be a problem depends on system
32
+ t.equal(unifyLineBreak(current), unifyLineBreak(code))
33
+ })
@@ -4,7 +4,7 @@ const fs = require('fs')
4
4
  const path = require('path')
5
5
  const t = require('tap')
6
6
  const test = t.test
7
- const fastify = require('../..')()
7
+ const fastify = require('../../fastify')()
8
8
 
9
9
  test('should be the same as package.json', t => {
10
10
  t.plan(1)
@@ -233,6 +233,10 @@ test('within an instance', t => {
233
233
  reply.redirect('/')
234
234
  })
235
235
 
236
+ fastify.get('/redirect-async', async function (req, reply) {
237
+ return reply.redirect('/')
238
+ })
239
+
236
240
  fastify.get('/redirect-code', function (req, reply) {
237
241
  reply.redirect(301, '/')
238
242
  })
@@ -412,6 +416,14 @@ test('within an instance', t => {
412
416
  })
413
417
  })
414
418
 
419
+ test('redirect with async function to `/` - 10', t => {
420
+ t.plan(1)
421
+
422
+ http.get('http://localhost:' + fastify.server.address().port + '/redirect-async', function (response) {
423
+ t.equal(response.statusCode, 302)
424
+ })
425
+ })
426
+
415
427
  t.end()
416
428
  })
417
429
  })
@@ -196,14 +196,28 @@ if (os.platform() !== 'win32') {
196
196
  const fastify = Fastify()
197
197
  t.teardown(fastify.close.bind(fastify))
198
198
 
199
- const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').substr(2, 8)}-server.sock`)
199
+ const sockFile = path.join(os.tmpdir(), `${(Math.random().toString(16) + '0000000').slice(2, 10)}-server.sock`)
200
200
  try {
201
201
  fs.unlinkSync(sockFile)
202
202
  } catch (e) { }
203
203
 
204
204
  fastify.listen({ path: sockFile }, (err, address) => {
205
205
  t.error(err)
206
- t.equal(sockFile, fastify.server.address())
206
+ t.strictSame(fastify.addresses(), [sockFile])
207
+ t.equal(address, sockFile)
208
+ })
209
+ })
210
+ } else {
211
+ test('listen on socket', t => {
212
+ t.plan(3)
213
+ const fastify = Fastify()
214
+ t.teardown(fastify.close.bind(fastify))
215
+
216
+ const sockFile = `\\\\.\\pipe\\${(Math.random().toString(16) + '0000000').slice(2, 10)}-server-sock`
217
+
218
+ fastify.listen({ path: sockFile }, (err, address) => {
219
+ t.error(err)
220
+ t.strictSame(fastify.addresses(), [sockFile])
207
221
  t.equal(address, sockFile)
208
222
  })
209
223
  })
@@ -520,7 +520,13 @@ const invalidErrorCodes = [
520
520
  undefined,
521
521
  null,
522
522
  'error_code',
523
- 700 // out of the 100-600 range
523
+
524
+ // out of the 100-599 range:
525
+ 0,
526
+ 1,
527
+ 99,
528
+ 600,
529
+ 700
524
530
  ]
525
531
  invalidErrorCodes.forEach((invalidCode) => {
526
532
  test(`should throw error if error code is ${invalidCode}`, t => {
@@ -741,3 +741,76 @@ test('reply.send handles aborted requests', t => {
741
741
  }, 1)
742
742
  })
743
743
  })
744
+
745
+ test('request terminated should not crash fastify', t => {
746
+ t.plan(10)
747
+
748
+ const spyLogger = {
749
+ level: 'error',
750
+ fatal: () => { },
751
+ error: () => {
752
+ t.fail('should not log an error')
753
+ },
754
+ warn: () => { },
755
+ info: () => { },
756
+ debug: () => { },
757
+ trace: () => { },
758
+ child: () => { return spyLogger }
759
+ }
760
+ const fastify = Fastify({
761
+ logger: spyLogger
762
+ })
763
+
764
+ fastify.get('/', async (req, reply) => {
765
+ const stream = new Readable()
766
+ stream._read = () => {}
767
+ reply.header('content-type', 'text/html; charset=utf-8')
768
+ reply.header('transfer-encoding', 'chunked')
769
+ stream.push('<h1>HTML</h1>')
770
+
771
+ reply.send(stream)
772
+
773
+ await new Promise((resolve) => { setTimeout(resolve, 100).unref() })
774
+
775
+ stream.push('<h1>should disply on second stream</h1>')
776
+ stream.push(null)
777
+ return reply
778
+ })
779
+
780
+ fastify.listen({ port: 0 }, err => {
781
+ t.error(err)
782
+ t.teardown(() => { fastify.close() })
783
+
784
+ const port = fastify.server.address().port
785
+ const http = require('http')
786
+ const req = http.get(`http://localhost:${port}`, function (res) {
787
+ const { statusCode, headers } = res
788
+ t.equal(statusCode, 200)
789
+ t.equal(headers['content-type'], 'text/html; charset=utf-8')
790
+ t.equal(headers['transfer-encoding'], 'chunked')
791
+ res.on('data', function (chunk) {
792
+ t.equal(chunk.toString(), '<h1>HTML</h1>')
793
+ })
794
+
795
+ setTimeout(() => {
796
+ req.destroy()
797
+
798
+ // the server is not crash, we can connect it
799
+ http.get(`http://localhost:${port}`, function (res) {
800
+ const { statusCode, headers } = res
801
+ t.equal(statusCode, 200)
802
+ t.equal(headers['content-type'], 'text/html; charset=utf-8')
803
+ t.equal(headers['transfer-encoding'], 'chunked')
804
+ let payload = ''
805
+ res.on('data', function (chunk) {
806
+ payload += chunk.toString()
807
+ })
808
+ res.on('end', function () {
809
+ t.equal(payload, '<h1>HTML</h1><h1>should disply on second stream</h1>')
810
+ t.pass('should end properly')
811
+ })
812
+ })
813
+ }, 1)
814
+ })
815
+ })
816
+ })
@@ -133,7 +133,7 @@ expectError(server.setErrorHandler(invalidErrorHandler))
133
133
  server.setSchemaController({
134
134
  bucket: (parentSchemas: unknown) => {
135
135
  return {
136
- addSchema (schema: unknown) {
136
+ add (schema: unknown) {
137
137
  expectType<unknown>(schema)
138
138
  expectType<FastifyInstance>(server.addSchema({ type: 'null' }))
139
139
  return server.addSchema({ type: 'null' })
@@ -1,7 +1,21 @@
1
1
  import { expectAssignable, expectError, expectType } from 'tsd'
2
- import fastify, { FastifyInstance, FastifyPluginAsync } from '../../fastify'
2
+ import { IncomingMessage, Server, ServerResponse } from 'http'
3
+ import { Http2Server, Http2ServerRequest, Http2ServerResponse } from 'http2'
4
+ import fastify, { FastifyInstance, FastifyError, FastifyLoggerInstance, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions, RawServerDefault } from '../../fastify'
3
5
 
4
- const testPluginOptsAsync: FastifyPluginAsync = async function (_instance, _opts) { }
6
+ const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { }
7
+ const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { }
8
+
9
+ const testPluginOpts: FastifyPluginCallback = function (instance, opts, done) { }
10
+ const testPluginOptsAsync: FastifyPluginAsync = async function (instance, opts) { }
11
+
12
+ const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { }
13
+ const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { }
14
+
15
+ interface TestOptions extends FastifyPluginOptions {
16
+ option1: string;
17
+ option2: boolean;
18
+ }
5
19
 
6
20
  // Type validation
7
21
  expectError(fastify().register(testPluginOptsAsync, { prefix: 1 }))
@@ -26,3 +40,64 @@ expectAssignable<FastifyInstance>(
26
40
  expectType<FastifyInstance>(instance)
27
41
  })
28
42
  )
43
+
44
+ // With Http2
45
+ const serverWithHttp2 = fastify({ http2: true })
46
+ type ServerWithHttp2 = FastifyInstance<Http2Server, Http2ServerRequest, Http2ServerResponse>
47
+ const testPluginWithHttp2: FastifyPluginCallback<TestOptions, Http2Server> = function (instance, opts, done) { }
48
+ const testPluginWithHttp2Async: FastifyPluginAsync<TestOptions, Http2Server> = async function (instance, opts) { }
49
+ const testPluginWithHttp2WithType = (instance: ServerWithHttp2, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { }
50
+ const testPluginWithHttp2WithTypeAsync = async (instance: ServerWithHttp2, opts: FastifyPluginOptions) => { }
51
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginCallback))
52
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginAsync))
53
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginOpts))
54
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginOptsAsync))
55
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginOptsWithType))
56
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginOptsWithTypeAsync))
57
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginWithHttp2))
58
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginWithHttp2Async))
59
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginWithHttp2WithType))
60
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(testPluginWithHttp2WithTypeAsync))
61
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register((instance) => {
62
+ expectAssignable<FastifyInstance>(instance)
63
+ }))
64
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register((instance: ServerWithHttp2) => {
65
+ expectAssignable<ServerWithHttp2>(instance)
66
+ }))
67
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(async (instance) => {
68
+ expectAssignable<FastifyInstance>(instance)
69
+ }))
70
+ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(async (instance: ServerWithHttp2) => {
71
+ expectAssignable<ServerWithHttp2>(instance)
72
+ }))
73
+
74
+ // With Type Provider
75
+ type TestTypeProvider = { input: 'test', output: 'test' }
76
+ const serverWithTypeProvider = fastify().withTypeProvider<TestTypeProvider>()
77
+ type ServerWithTypeProvider = FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance, TestTypeProvider>
78
+ const testPluginWithTypeProvider: FastifyPluginCallback<TestOptions, RawServerDefault, TestTypeProvider> = function (instance, opts, done) { }
79
+ const testPluginWithTypeProviderAsync: FastifyPluginAsync<TestOptions, RawServerDefault, TestTypeProvider> = async function (instance, opts) { }
80
+ const testPluginWithTypeProviderWithType = (instance: ServerWithTypeProvider, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { }
81
+ const testPluginWithTypeProviderWithTypeAsync = async (instance: ServerWithTypeProvider, opts: FastifyPluginOptions) => { }
82
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginCallback))
83
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginAsync))
84
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginOpts))
85
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginOptsAsync))
86
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginOptsWithType))
87
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginOptsWithTypeAsync))
88
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginWithTypeProvider))
89
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginWithTypeProviderAsync))
90
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginWithTypeProviderWithType))
91
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(testPluginWithTypeProviderWithTypeAsync))
92
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register((instance) => {
93
+ expectAssignable<FastifyInstance>(instance)
94
+ }))
95
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register((instance: ServerWithTypeProvider) => {
96
+ expectAssignable<ServerWithTypeProvider>(instance)
97
+ }))
98
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(async (instance) => {
99
+ expectAssignable<FastifyInstance>(instance)
100
+ }))
101
+ expectAssignable<ServerWithTypeProvider>(serverWithTypeProvider.register(async (instance: ServerWithTypeProvider) => {
102
+ expectAssignable<ServerWithTypeProvider>(instance)
103
+ }))
@@ -1,6 +1,8 @@
1
1
  import { FastifyPluginOptions, FastifyPluginCallback, FastifyPluginAsync } from './plugin'
2
2
  import { LogLevel } from './logger'
3
3
  import { FastifyInstance } from './instance'
4
+ import { RawServerBase } from './utils'
5
+ import { FastifyTypeProvider, RawServerDefault } from '../fastify'
4
6
 
5
7
  export interface RegisterOptions {
6
8
  prefix?: string;
@@ -15,17 +17,17 @@ export type FastifyRegisterOptions<Options> = (RegisterOptions & Options) | ((in
15
17
  *
16
18
  * Function for adding a plugin to fastify. The options are inferred from the passed in FastifyPlugin parameter.
17
19
  */
18
- export interface FastifyRegister<T = void> {
19
- <Options extends FastifyPluginOptions>(
20
- plugin: FastifyPluginCallback<Options>,
20
+ export interface FastifyRegister<T = void, RawServer extends RawServerBase = RawServerDefault, TypeProviderDefault extends FastifyTypeProvider = FastifyTypeProvider> {
21
+ <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault>(
22
+ plugin: FastifyPluginCallback<Options, Server, TypeProvider>,
21
23
  opts?: FastifyRegisterOptions<Options>
22
24
  ): T;
23
- <Options extends FastifyPluginOptions>(
24
- plugin: FastifyPluginAsync<Options>,
25
+ <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault>(
26
+ plugin: FastifyPluginAsync<Options, Server, TypeProvider>,
25
27
  opts?: FastifyRegisterOptions<Options>
26
28
  ): T;
27
- <Options extends FastifyPluginOptions>(
28
- plugin: FastifyPluginCallback<Options> | FastifyPluginAsync<Options> | Promise<{ default: FastifyPluginCallback<Options> }> | Promise<{ default: FastifyPluginAsync<Options> }>,
29
+ <Options extends FastifyPluginOptions, Server extends RawServerBase = RawServer, TypeProvider extends FastifyTypeProvider = TypeProviderDefault>(
30
+ plugin: FastifyPluginCallback<Options, Server, TypeProvider> | FastifyPluginAsync<Options, Server, TypeProvider> | Promise<{ default: FastifyPluginCallback<Options, Server, TypeProvider> }> | Promise<{ default: FastifyPluginAsync<Options, Server, TypeProvider> }>,
29
31
  opts?: FastifyRegisterOptions<Options>
30
32
  ): T;
31
33
  }
package/types/schema.d.ts CHANGED
@@ -41,7 +41,7 @@ export type FastifySerializerCompiler<T> = (routeSchema: FastifyRouteSchemaDef<T
41
41
 
42
42
  export interface FastifySchemaControllerOptions{
43
43
  bucket?: (parentSchemas?: unknown) => {
44
- addSchema(schema: unknown): FastifyInstance;
44
+ add(schema: unknown): FastifyInstance;
45
45
  getSchema(schemaId: string): unknown;
46
46
  getSchemas(): Record<string, unknown>;
47
47
  };