fastify 4.27.0 → 4.28.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@
17
17
  - [.trailer(key, function)](#trailerkey-function)
18
18
  - [.hasTrailer(key)](#hastrailerkey)
19
19
  - [.removeTrailer(key)](#removetrailerkey)
20
- - [.redirect([code ,] dest)](#redirectcode--dest)
20
+ - [.redirect(dest, [code ,])](#redirectdest--code)
21
21
  - [.callNotFound()](#callnotfound)
22
22
  - [.getResponseTime()](#getresponsetime)
23
23
  - [.type(contentType)](#typecontenttype)
@@ -62,8 +62,8 @@ since the request was received by Fastify.
62
62
  - `.hasTrailer(key)` - Determine if a trailer has been set.
63
63
  - `.removeTrailer(key)` - Remove the value of a previously set trailer.
64
64
  - `.type(value)` - Sets the header `Content-Type`.
65
- - `.redirect([code,] dest)` - Redirect to the specified URL, the status code is
66
- optional (default to `302`).
65
+ - `.redirect(dest, [code,])` - Redirect to the specified URL, the status code is
66
+ optional (defaults to `302`).
67
67
  - `.callNotFound()` - Invokes the custom not found handler.
68
68
  - `.serialize(payload)` - Serializes the specified payload using the default
69
69
  JSON serializer or using the custom serializer (if one is set) and returns the
@@ -299,7 +299,7 @@ reply.getTrailer('server-timing') // undefined
299
299
  ```
300
300
 
301
301
 
302
- ### .redirect([code ,] dest)
302
+ ### .redirect(dest, [code ,])
303
303
  <a id="redirect"></a>
304
304
 
305
305
  Redirects a request to the specified URL, the status code is optional, default
@@ -320,7 +320,7 @@ reply.redirect('/home')
320
320
  Example (no `reply.code()` call) sets status code to `303` and redirects to
321
321
  `/home`
322
322
  ```js
323
- reply.redirect(303, '/home')
323
+ reply.redirect('/home', 303)
324
324
  ```
325
325
 
326
326
  Example (`reply.code()` call) sets status code to `303` and redirects to `/home`
@@ -330,7 +330,7 @@ reply.code(303).redirect('/home')
330
330
 
331
331
  Example (`reply.code()` call) sets status code to `302` and redirects to `/home`
332
332
  ```js
333
- reply.code(303).redirect(302, '/home')
333
+ reply.code(303).redirect('/home', 302)
334
334
  ```
335
335
 
336
336
  ### .callNotFound()
@@ -24,6 +24,7 @@
24
24
  - [FSTDEP018](#FSTDEP018)
25
25
  - [FSTDEP019](#FSTDEP019)
26
26
  - [FSTDEP020](#FSTDEP020)
27
+ - [FSTDEP021](#FSTDEP021)
27
28
 
28
29
 
29
30
  ## Warnings
@@ -90,3 +91,4 @@ Deprecation codes are further supported by the Node.js CLI options:
90
91
  | <a id="FSTDEP018">FSTDEP018</a> | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
91
92
  | <a id="FSTDEP019">FSTDEP019</a> | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) |
92
93
  | <a id="FSTDEP020">FSTDEP020</a> | You are using the deprecated `reply.getReponseTime()` method. | Use the `reply.elapsedTime` property instead. | [#5263](https://github.com/fastify/fastify/pull/5263) |
94
+ | <a id="FSTDEP021">FSTDEP021</a> | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) |
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.27.0'
3
+ const VERSION = '4.28.1'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -2,10 +2,8 @@
2
2
  /* istanbul ignore file */
3
3
 
4
4
  'use strict'
5
- const { dependencies } = require('fast-json-stringify/lib/standalone')
6
-
7
- const { Serializer, Validator } = dependencies
8
5
 
6
+ const Serializer = require('fast-json-stringify/lib/serializer')
9
7
  const serializerState = {"mode":"standalone"}
10
8
  const serializer = Serializer.restoreFromState(serializerState)
11
9
 
@@ -15,6 +13,18 @@
15
13
  module.exports = function anonymous(validator,serializer
16
14
  ) {
17
15
 
16
+ const JSON_STR_BEGIN_OBJECT = '{'
17
+ const JSON_STR_END_OBJECT = '}'
18
+ const JSON_STR_BEGIN_ARRAY = '['
19
+ const JSON_STR_END_ARRAY = ']'
20
+ const JSON_STR_COMMA = ','
21
+ const JSON_STR_COLONS = ':'
22
+ const JSON_STR_QUOTE = '"'
23
+ const JSON_STR_EMPTY_OBJECT = JSON_STR_BEGIN_OBJECT + JSON_STR_END_OBJECT
24
+ const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY
25
+ const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE
26
+ const JSON_STR_NULL = 'null'
27
+
18
28
 
19
29
 
20
30
  // #
@@ -23,36 +33,83 @@
23
33
  ? input.toJSON()
24
34
  : input
25
35
 
26
- if (obj === null) return '{}'
36
+ if (obj === null) return JSON_STR_EMPTY_OBJECT
27
37
 
28
- let json = '{'
38
+ let value
39
+ let json = JSON_STR_BEGIN_OBJECT
29
40
  let addComma = false
30
41
 
31
- if (obj["statusCode"] !== undefined) {
32
- !addComma && (addComma = true) || (json += ',')
42
+ value = obj["statusCode"]
43
+ if (value !== undefined) {
44
+ !addComma && (addComma = true) || (json += JSON_STR_COMMA)
33
45
  json += "\"statusCode\":"
34
- json += serializer.asNumber(obj["statusCode"])
46
+ json += serializer.asNumber(value)
35
47
  }
36
48
 
37
- if (obj["code"] !== undefined) {
38
- !addComma && (addComma = true) || (json += ',')
49
+ value = obj["code"]
50
+ if (value !== undefined) {
51
+ !addComma && (addComma = true) || (json += JSON_STR_COMMA)
39
52
  json += "\"code\":"
40
- json += serializer.asString(obj["code"])
53
+
54
+ if (typeof value !== 'string') {
55
+ if (value === null) {
56
+ json += JSON_STR_EMPTY_STRING
57
+ } else if (value instanceof Date) {
58
+ json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
59
+ } else if (value instanceof RegExp) {
60
+ json += serializer.asString(value.source)
61
+ } else {
62
+ json += serializer.asString(value.toString())
63
+ }
64
+ } else {
65
+ json += serializer.asString(value)
66
+ }
67
+
41
68
  }
42
69
 
43
- if (obj["error"] !== undefined) {
44
- !addComma && (addComma = true) || (json += ',')
70
+ value = obj["error"]
71
+ if (value !== undefined) {
72
+ !addComma && (addComma = true) || (json += JSON_STR_COMMA)
45
73
  json += "\"error\":"
46
- json += serializer.asString(obj["error"])
74
+
75
+ if (typeof value !== 'string') {
76
+ if (value === null) {
77
+ json += JSON_STR_EMPTY_STRING
78
+ } else if (value instanceof Date) {
79
+ json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
80
+ } else if (value instanceof RegExp) {
81
+ json += serializer.asString(value.source)
82
+ } else {
83
+ json += serializer.asString(value.toString())
84
+ }
85
+ } else {
86
+ json += serializer.asString(value)
87
+ }
88
+
47
89
  }
48
90
 
49
- if (obj["message"] !== undefined) {
50
- !addComma && (addComma = true) || (json += ',')
91
+ value = obj["message"]
92
+ if (value !== undefined) {
93
+ !addComma && (addComma = true) || (json += JSON_STR_COMMA)
51
94
  json += "\"message\":"
52
- json += serializer.asString(obj["message"])
95
+
96
+ if (typeof value !== 'string') {
97
+ if (value === null) {
98
+ json += JSON_STR_EMPTY_STRING
99
+ } else if (value instanceof Date) {
100
+ json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
101
+ } else if (value instanceof RegExp) {
102
+ json += serializer.asString(value.source)
103
+ } else {
104
+ json += serializer.asString(value.toString())
105
+ }
106
+ } else {
107
+ json += serializer.asString(value)
108
+ }
109
+
53
110
  }
54
111
 
55
- return json + '}'
112
+ return json + JSON_STR_END_OBJECT
56
113
 
57
114
  }
58
115
 
package/lib/reply.js CHANGED
@@ -55,7 +55,7 @@ const {
55
55
  FST_ERR_MISSING_SERIALIZATION_FN,
56
56
  FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
57
57
  } = require('./errors')
58
- const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020 } = require('./warnings')
58
+ const { FSTDEP010, FSTDEP013, FSTDEP019, FSTDEP020, FSTDEP021 } = require('./warnings')
59
59
 
60
60
  const toString = Object.prototype.toString
61
61
 
@@ -457,9 +457,15 @@ Reply.prototype.type = function (type) {
457
457
  return this
458
458
  }
459
459
 
460
- Reply.prototype.redirect = function (code, url) {
461
- if (typeof code === 'string') {
462
- url = code
460
+ Reply.prototype.redirect = function (url, code) {
461
+ if (typeof url === 'number') {
462
+ FSTDEP021()
463
+ const temp = code
464
+ code = url
465
+ url = temp
466
+ }
467
+
468
+ if (!code) {
463
469
  code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
464
470
  }
465
471
 
package/lib/route.js CHANGED
@@ -168,7 +168,11 @@ function buildRouting (options) {
168
168
  }
169
169
 
170
170
  function hasRoute ({ options }) {
171
- return findRoute(options) !== null
171
+ const normalizedMethod = options.method?.toUpperCase() ?? ''
172
+ return findRoute({
173
+ ...options,
174
+ method: normalizedMethod
175
+ }) !== null
172
176
  }
173
177
 
174
178
  function findRoute (options) {
package/lib/server.js CHANGED
@@ -220,6 +220,7 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
220
220
  function listenCallback (server, listenOptions) {
221
221
  const wrap = (err) => {
222
222
  server.removeListener('error', wrap)
223
+ server.removeListener('listening', wrap)
223
224
  if (!err) {
224
225
  const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)
225
226
  listenOptions.cb(null, address)
@@ -240,7 +241,8 @@ function listenCallback (server, listenOptions) {
240
241
 
241
242
  server.once('error', wrap)
242
243
  if (!this[kState].closing) {
243
- server.listen(listenOptions, wrap)
244
+ server.once('listening', wrap)
245
+ server.listen(listenOptions)
244
246
  this[kState].listening = true
245
247
  }
246
248
  }
@@ -255,25 +257,33 @@ function listenPromise (server, listenOptions) {
255
257
 
256
258
  return this.ready().then(() => {
257
259
  let errEventHandler
260
+ let listeningEventHandler
261
+ function cleanup () {
262
+ server.removeListener('error', errEventHandler)
263
+ server.removeListener('listening', listeningEventHandler)
264
+ }
258
265
  const errEvent = new Promise((resolve, reject) => {
259
266
  errEventHandler = (err) => {
267
+ cleanup()
260
268
  this[kState].listening = false
261
269
  reject(err)
262
270
  }
263
271
  server.once('error', errEventHandler)
264
272
  })
265
- const listen = new Promise((resolve, reject) => {
266
- server.listen(listenOptions, () => {
267
- server.removeListener('error', errEventHandler)
273
+ const listeningEvent = new Promise((resolve, reject) => {
274
+ listeningEventHandler = () => {
275
+ cleanup()
276
+ this[kState].listening = true
268
277
  resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText))
269
- })
270
- // we set it afterwards because listen can throw
271
- this[kState].listening = true
278
+ }
279
+ server.once('listening', listeningEventHandler)
272
280
  })
273
281
 
282
+ server.listen(listenOptions)
283
+
274
284
  return Promise.race([
275
285
  errEvent, // e.g invalid port range error is always emitted before the server listening
276
- listen
286
+ listeningEvent
277
287
  ])
278
288
  })
279
289
  }
package/lib/warnings.js CHANGED
@@ -82,6 +82,11 @@ const FSTDEP020 = createDeprecation({
82
82
  message: 'You are using the deprecated "reply.getResponseTime()" method. Use the "reply.elapsedTime" property instead. Method "reply.getResponseTime()" will be removed in `fastify@5`.'
83
83
  })
84
84
 
85
+ const FSTDEP021 = createDeprecation({
86
+ code: 'FSTDEP021',
87
+ message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'
88
+ })
89
+
85
90
  const FSTWRN001 = createWarning({
86
91
  name: 'FastifyWarning',
87
92
  code: 'FSTWRN001',
@@ -113,6 +118,7 @@ module.exports = {
113
118
  FSTDEP018,
114
119
  FSTDEP019,
115
120
  FSTDEP020,
121
+ FSTDEP021,
116
122
  FSTWRN001,
117
123
  FSTWRN002
118
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.27.0",
3
+ "version": "4.28.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -36,8 +36,15 @@ test('Should return 503 while closing - pipelining', async t => {
36
36
  await instance.close()
37
37
  })
38
38
 
39
- const isNodeVersionGte1819 = semver.gte(process.version, '18.19.0')
40
- test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip Node >= v18.19.x', { skip: isNodeVersionGte1819 }, async t => {
39
+ // default enable of idle closing idle connection is accidentally backported to 18.19.0 and fixed in 18.20.3
40
+ // Refs: https://github.com/nodejs/node/releases/tag/v18.20.3
41
+ const isNodeDefaultClosingIdleConnection =
42
+ (
43
+ semver.gte(process.version, '18.19.0') &&
44
+ semver.lt(process.version, '18.20.3')
45
+ ) ||
46
+ semver.gte(process.version, '19.0.0')
47
+ test('Should not return 503 while closing - pipelining - return503OnClosing: false, skip when Node default closing idle connection', { skip: isNodeDefaultClosingIdleConnection }, async t => {
41
48
  const fastify = Fastify({
42
49
  return503OnClosing: false,
43
50
  forceCloseConnections: false
@@ -67,8 +74,8 @@ test('Should not return 503 while closing - pipelining - return503OnClosing: fal
67
74
  await instance.close()
68
75
  })
69
76
 
70
- test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip Node < v18.19.x', { skip: !isNodeVersionGte1819 }, async t => {
71
- // Since Node v18, we will always invoke server.closeIdleConnections()
77
+ test('Should close the socket abruptly - pipelining - return503OnClosing: false, skip when Node not default closing idle connection', { skip: !isNodeDefaultClosingIdleConnection }, async t => {
78
+ // Since Node v19, we will always invoke server.closeIdleConnections()
72
79
  // therefore our socket will be closed
73
80
  const fastify = Fastify({
74
81
  return503OnClosing: false,
@@ -5,7 +5,7 @@ const test = t.test
5
5
  const Fastify = require('../fastify')
6
6
 
7
7
  test('hasRoute', t => {
8
- t.plan(4)
8
+ t.plan(5)
9
9
  const test = t.test
10
10
  const fastify = Fastify()
11
11
 
@@ -74,4 +74,20 @@ test('hasRoute', t => {
74
74
  url: '/example/12345.png'
75
75
  }), true)
76
76
  })
77
+
78
+ test('hasRoute - finds a route even if method is not uppercased', t => {
79
+ t.plan(1)
80
+ fastify.route({
81
+ method: 'GET',
82
+ url: '/equal',
83
+ handler: function (req, reply) {
84
+ reply.send({ hello: 'world' })
85
+ }
86
+ })
87
+
88
+ t.equal(fastify.hasRoute({
89
+ method: 'get',
90
+ url: '/equal'
91
+ }), true)
92
+ })
77
93
  })
@@ -19,7 +19,7 @@ const {
19
19
  } = require('../../lib/symbols')
20
20
  const fs = require('node:fs')
21
21
  const path = require('node:path')
22
- const { FSTDEP010, FSTDEP019, FSTDEP020 } = require('../../lib/warnings')
22
+ const { FSTDEP010, FSTDEP019, FSTDEP020, FSTDEP021 } = require('../../lib/warnings')
23
23
 
24
24
  const agent = new http.Agent({ keepAlive: false })
25
25
 
@@ -250,7 +250,7 @@ test('within an instance', t => {
250
250
  })
251
251
 
252
252
  fastify.get('/redirect-code', function (req, reply) {
253
- reply.redirect(301, '/')
253
+ reply.redirect('/', 301)
254
254
  })
255
255
 
256
256
  fastify.get('/redirect-code-before-call', function (req, reply) {
@@ -258,7 +258,7 @@ test('within an instance', t => {
258
258
  })
259
259
 
260
260
  fastify.get('/redirect-code-before-call-overwrite', function (req, reply) {
261
- reply.code(307).redirect(302, '/')
261
+ reply.code(307).redirect('/', 302)
262
262
  })
263
263
 
264
264
  fastify.get('/custom-serializer', function (req, reply) {
@@ -2094,6 +2094,36 @@ test('redirect to an invalid URL should not crash the server', async t => {
2094
2094
  await fastify.close()
2095
2095
  })
2096
2096
 
2097
+ test('redirect with deprecated signature should warn', t => {
2098
+ t.plan(4)
2099
+
2100
+ process.removeAllListeners('warning')
2101
+ process.on('warning', onWarning)
2102
+ function onWarning (warning) {
2103
+ t.equal(warning.name, 'DeprecationWarning')
2104
+ t.equal(warning.code, FSTDEP021.code)
2105
+ }
2106
+
2107
+ const fastify = Fastify()
2108
+
2109
+ fastify.get('/', (req, reply) => {
2110
+ reply.redirect(302, '/new')
2111
+ })
2112
+
2113
+ fastify.get('/new', (req, reply) => {
2114
+ reply.send('new')
2115
+ })
2116
+
2117
+ fastify.inject({ method: 'GET', url: '/' }, (err, res) => {
2118
+ t.error(err)
2119
+ t.pass()
2120
+
2121
+ process.removeListener('warning', onWarning)
2122
+ })
2123
+
2124
+ FSTDEP021.emitted = false
2125
+ })
2126
+
2097
2127
  test('invalid response headers should not crash the server', async t => {
2098
2128
  const fastify = Fastify()
2099
2129
  fastify.route({
@@ -0,0 +1,99 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const net = require('node:net')
5
+ const Fastify = require('../fastify')
6
+ const { once } = require('node:events')
7
+
8
+ function createDeferredPromise () {
9
+ const promise = {}
10
+ promise.promise = new Promise((resolve) => {
11
+ promise.resolve = resolve
12
+ })
13
+ return promise
14
+ }
15
+
16
+ test('same port conflict and success should not fire callback multiple times - callback', async (t) => {
17
+ t.plan(7)
18
+ const server = net.createServer()
19
+ server.listen({ port: 0, host: '127.0.0.1' })
20
+ await once(server, 'listening')
21
+ const option = { port: server.address().port, host: server.address().address }
22
+ let count = 0
23
+ const fastify = Fastify()
24
+ const promise = createDeferredPromise()
25
+ function callback (err) {
26
+ switch (count) {
27
+ case 6: {
28
+ // success in here
29
+ t.error(err)
30
+ fastify.close((err) => {
31
+ t.error(err)
32
+ promise.resolve()
33
+ })
34
+ break
35
+ }
36
+ case 5: {
37
+ server.close()
38
+ setTimeout(() => {
39
+ fastify.listen(option, callback)
40
+ }, 100)
41
+ break
42
+ }
43
+ default: {
44
+ // expect error
45
+ t.equal(err.code, 'EADDRINUSE')
46
+ setTimeout(() => {
47
+ fastify.listen(option, callback)
48
+ }, 100)
49
+ }
50
+ }
51
+ count++
52
+ }
53
+ fastify.listen(option, callback)
54
+ await promise.promise
55
+ })
56
+
57
+ test('same port conflict and success should not fire callback multiple times - promise', async (t) => {
58
+ t.plan(5)
59
+ const server = net.createServer()
60
+ server.listen({ port: 0, host: '127.0.0.1' })
61
+ await once(server, 'listening')
62
+ const option = { port: server.address().port, host: server.address().address }
63
+ const fastify = Fastify()
64
+
65
+ try {
66
+ await fastify.listen(option)
67
+ } catch (err) {
68
+ t.equal(err.code, 'EADDRINUSE')
69
+ }
70
+ try {
71
+ await fastify.listen(option)
72
+ } catch (err) {
73
+ t.equal(err.code, 'EADDRINUSE')
74
+ }
75
+ try {
76
+ await fastify.listen(option)
77
+ } catch (err) {
78
+ t.equal(err.code, 'EADDRINUSE')
79
+ }
80
+ try {
81
+ await fastify.listen(option)
82
+ } catch (err) {
83
+ t.equal(err.code, 'EADDRINUSE')
84
+ }
85
+ try {
86
+ await fastify.listen(option)
87
+ } catch (err) {
88
+ t.equal(err.code, 'EADDRINUSE')
89
+ }
90
+
91
+ server.close()
92
+
93
+ await once(server, 'close')
94
+
95
+ // when ever we can listen, and close properly
96
+ // which means there is no problem on the callback
97
+ await fastify.listen()
98
+ await fastify.close()
99
+ })
@@ -1,2 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
@@ -29,7 +29,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) {
29
29
  expectAssignable<() => { [key: string]: number | string | string[] | undefined }>(reply.getHeaders)
30
30
  expectAssignable<(key: string) => FastifyReply>(reply.removeHeader)
31
31
  expectAssignable<(key: string) => boolean>(reply.hasHeader)
32
- expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect)
32
+ expectType<{(statusCode: number, url: string): FastifyReply;(url: string, statusCode?: number): FastifyReply;}>(reply.redirect)
33
33
  expectType<() => FastifyReply>(reply.hijack)
34
34
  expectType<() => void>(reply.callNotFound)
35
35
  // Test reply.getResponseTime() deprecation
@@ -1052,7 +1052,7 @@ expectAssignable(server.withTypeProvider<InlineHandlerProvider>().get(
1052
1052
  // Handlers: Auxiliary
1053
1053
  // -------------------------------------------------------------------
1054
1054
 
1055
- interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: 'handler-auxiliary' }
1055
+ interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: this['input'] }
1056
1056
 
1057
1057
  // Auxiliary handlers are likely shared for multiple routes and thus should infer as unknown due to potential varying parameters
1058
1058
  function auxiliaryHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction): void {
@@ -1063,7 +1063,7 @@ expectAssignable(server.withTypeProvider<AuxiliaryHandlerProvider>().get(
1063
1063
  '/',
1064
1064
  {
1065
1065
  onRequest: auxiliaryHandler,
1066
- schema: { body: null }
1066
+ schema: { body: 'handler-auxiliary' }
1067
1067
  },
1068
1068
  (req) => {
1069
1069
  expectType<'handler-auxiliary'>(req.body)
package/types/reply.d.ts CHANGED
@@ -57,9 +57,11 @@ export interface FastifyReply<
57
57
  getHeaders(): Record<HttpHeader, number | string | string[] | undefined>;
58
58
  removeHeader(key: HttpHeader): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
59
59
  hasHeader(key: HttpHeader): boolean;
60
- // Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param
60
+ /**
61
+ * @deprecated The `reply.redirect()` method has a new signature: `reply.reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'.
62
+ */
61
63
  redirect(statusCode: number, url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
62
- redirect(url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
64
+ redirect(url: string, statusCode?: number): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
63
65
  hijack(): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>;
64
66
  callNotFound(): void;
65
67
  /**
package/types/route.d.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  FastifyTypeProviderDefault,
13
13
  ResolveFastifyReplyReturnType
14
14
  } from './type-provider'
15
- import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils'
15
+ import { ContextConfigDefault, HTTPMethods, NoInferCompat, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils'
16
16
 
17
17
  export interface FastifyRouteConfig {
18
18
  url: string;
@@ -48,8 +48,8 @@ export interface RouteShorthandOptions<
48
48
  attachValidation?: boolean;
49
49
  exposeHeadRoute?: boolean;
50
50
 
51
- validatorCompiler?: FastifySchemaCompiler<SchemaCompiler>;
52
- serializerCompiler?: FastifySerializerCompiler<SchemaCompiler>;
51
+ validatorCompiler?: FastifySchemaCompiler<NoInferCompat<SchemaCompiler>>;
52
+ serializerCompiler?: FastifySerializerCompiler<NoInferCompat<SchemaCompiler>>;
53
53
  bodyLimit?: number;
54
54
  logLevel?: LogLevel;
55
55
  config?: Omit<FastifyRequestContext<ContextConfig>['config'], 'url' | 'method'>;
@@ -59,33 +59,33 @@ export interface RouteShorthandOptions<
59
59
  errorHandler?: (
60
60
  this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>,
61
61
  error: FastifyError,
62
- request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider, ContextConfig, Logger>,
63
- reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider>
62
+ request: FastifyRequest<RouteGeneric, RawServer, RawRequest, NoInferCompat<SchemaCompiler>, TypeProvider, ContextConfig, Logger>,
63
+ reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider>
64
64
  ) => void;
65
65
  childLoggerFactory?: FastifyChildLoggerFactory<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
66
66
  schemaErrorFormatter?: SchemaErrorFormatter;
67
67
 
68
68
  // hooks
69
- onRequest?: onRequestMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
70
- | onRequestMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
71
- preParsing?: preParsingMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
72
- | preParsingMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
73
- preValidation?: preValidationMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
74
- | preValidationMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
75
- preHandler?: preHandlerMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
76
- | preHandlerMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
77
- preSerialization?: preSerializationMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
78
- | preSerializationMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
79
- onSend?: onSendMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
80
- | onSendMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
81
- onResponse?: onResponseMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
82
- | onResponseMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
83
- onTimeout?: onTimeoutMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
84
- | onTimeoutMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
85
- onError?: onErrorMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, FastifyError, SchemaCompiler, TypeProvider, Logger>
86
- | onErrorMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, FastifyError, SchemaCompiler, TypeProvider, Logger>[];
87
- onRequestAbort?: onRequestAbortMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>
88
- | onRequestAbortMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>[];
69
+ onRequest?: onRequestMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
70
+ | onRequestMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
71
+ preParsing?: preParsingMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
72
+ | preParsingMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
73
+ preValidation?: preValidationMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
74
+ | preValidationMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
75
+ preHandler?: preHandlerMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
76
+ | preHandlerMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
77
+ preSerialization?: preSerializationMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
78
+ | preSerializationMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
79
+ onSend?: onSendMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
80
+ | onSendMetaHookHandler<unknown, RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
81
+ onResponse?: onResponseMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
82
+ | onResponseMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
83
+ onTimeout?: onTimeoutMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
84
+ | onTimeoutMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
85
+ onError?: onErrorMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, FastifyError, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
86
+ | onErrorMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, FastifyError, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
87
+ onRequestAbort?: onRequestAbortMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>
88
+ | onRequestAbortMetaHookHandler<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>[];
89
89
  }
90
90
  /**
91
91
  * Route handler method declaration.
@@ -162,7 +162,7 @@ export interface RouteOptions<
162
162
  > extends RouteShorthandOptions<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger> {
163
163
  method: HTTPMethods | HTTPMethods[];
164
164
  url: string;
165
- handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger>;
165
+ handler: RouteHandlerMethod<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig, NoInferCompat<SchemaCompiler>, TypeProvider, Logger>;
166
166
  }
167
167
 
168
168
  export type RouteHandler<
package/types/utils.d.ts CHANGED
@@ -88,3 +88,8 @@ type OmitIndexSignature<T> = {
88
88
  * Use this type only for input values, not for output values.
89
89
  */
90
90
  export type HttpHeader = keyof OmitIndexSignature<http.OutgoingHttpHeaders> | (string & Record<never, never>);
91
+
92
+ // cheat for similar (same?) behavior as NoInfer but for TS <5.4
93
+ export type NoInferCompat<SC> = {
94
+ [K in keyof SC]: SC[K]
95
+ };
package/EXPENSE_POLICY.md DELETED
@@ -1,105 +0,0 @@
1
- # Expense Policy
2
-
3
- Fastify collaborators accept donations through the [Open Collective](https://opencollective.com/fastify/)
4
- platform and [GitHub Sponsors](https://github.com/sponsors/fastify)
5
- to enhance the project and support the community.
6
-
7
- This Collective is run by and for the benefit of the independent contributors to
8
- the Fastify open source software project.
9
- This Collective is not endorsed or administered by OpenJS Foundation, Inc.
10
- (the “OpenJS Foundation”). The OpenJS Foundation does not receive or have
11
- control over any funds contributed. The OpenJS Foundation does not direct or
12
- otherwise supervise the actions of any contributor to the Fastify project,
13
- and all donations made will be expended for the private benefit of or otherwise
14
- to reimburse individuals that do not have an employer/employee, contractor, or
15
- other agency relationship with the OpenJS Foundation.
16
- The Fastify marks used herein are used under license from the OpenJS Foundation
17
- for the benefit of the open source software community.
18
-
19
- The admins of the Fastify Collective are the [lead maintainers](./GOVERNANCE.md)
20
- of the project.
21
-
22
- This document outlines the process for requesting reimbursement or an invoice
23
- for expenses.
24
-
25
- ## Reimbursement
26
-
27
- Reimbursement is applicable for expenses already paid, such as:
28
-
29
- - Stickers
30
- - Gadgets
31
- - Hosting
32
-
33
- **Before making any purchases**, initiate a [new discussion](https://github.com/orgs/fastify/discussions)
34
- in the `fastify` organization with the following information:
35
-
36
- - What is needed
37
- - Why it is needed
38
- - Cost
39
- - Deadline
40
-
41
- Once the discussion is approved by a lead maintainer and with no unresolved objections,
42
- the purchase can proceed, and an expense can be submitted to the [Open Collective][submit].
43
- This process takes a minimum of 3 business days from the request to allow time for
44
- discussion approval.
45
-
46
- The discussion helps prevent misunderstandings and ensures the expense is not rejected.
47
- As a project under the OpenJS Foundation, Fastify benefits from the Foundation's
48
- resources, including servers, domains, and [travel funds](https://github.com/openjs-foundation/community-fund/tree/main/programs/travel-fund).
49
-
50
- Always seek approval first.
51
-
52
- ## Invoice
53
-
54
- Invoices are for services provided to the Fastify project, such as PR reviews,
55
- documentation, etc.
56
- A VAT number is not required to submit an invoice.
57
- Refer to the [Open Collective documentation][openc_docs] for details.
58
-
59
- ### Adding a bounty to an issue
60
-
61
- Issues become eligible for a bounty when the core team adds the `bounty` label,
62
- with the amount determined by the core team based on `estimated hours * rate`
63
- (suggested $50 per hour).
64
-
65
- > Example: If the estimated time to fix the issue is 2 hours,
66
- > the bounty will be $100.
67
-
68
- To add a bounty:
69
-
70
- - Apply the `bounty` label to the issue
71
- - Comment on the issue with the bounty amount
72
- - Edit the first comment of the issue using this template:
73
-
74
- ```
75
- ## 💰 Bounty
76
-
77
- This issue has a bounty of [$AMOUNT](LINK TO THE BOUNTY COMMENT).
78
- _Read more about [the bounty program](./EXPENSE_POLICY.md)_
79
- ```
80
-
81
- For discussions on bounties or determining amounts, open a [new discussion](https://github.com/orgs/fastify/discussions/new?category=bounty).
82
-
83
- ### Outstanding contributions
84
-
85
- The lead team can decide to add a bounty to an issue or PR not labeled as `bounty`
86
- if the contribution is outstanding.
87
-
88
- ### Claiming a bounty
89
-
90
- To claim a bounty:
91
-
92
- - Submit a PR that fixes the issue
93
- - If multiple submissions exist, a core member will choose the best solution
94
- - Once merged, the PR author can claim the bounty by:
95
- - Submitting an expense to the [Open Collective][submit] with the PR link
96
- - Adding a comment on the PR with a link to their Open Collective expense to
97
- ensure the claimant is the issue resolver
98
- - The expense will be validated by a lead maintainer and then the payment will be
99
- processed by Open Collective
100
-
101
- If the Open Collective budget is insufficient, the expense will be rejected.
102
- Unclaimed bounties are available for other issues.
103
-
104
- [submit]: https://opencollective.com/fastify/expenses/new
105
- [openc_docs]: https://docs.oscollective.org/how-it-works/basics/invoice-and-reimbursement-examples