fastify 5.2.1 → 5.3.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 (85) hide show
  1. package/LICENSE +1 -1
  2. package/PROJECT_CHARTER.md +7 -7
  3. package/README.md +24 -25
  4. package/SPONSORS.md +1 -0
  5. package/docs/Guides/Benchmarking.md +4 -4
  6. package/docs/Guides/Database.md +1 -1
  7. package/docs/Guides/Delay-Accepting-Requests.md +10 -10
  8. package/docs/Guides/Ecosystem.md +7 -1
  9. package/docs/Guides/Fluent-Schema.md +1 -1
  10. package/docs/Guides/Getting-Started.md +9 -5
  11. package/docs/Guides/Index.md +1 -1
  12. package/docs/Guides/Migration-Guide-V4.md +1 -1
  13. package/docs/Guides/Migration-Guide-V5.md +12 -2
  14. package/docs/Guides/Plugins-Guide.md +6 -6
  15. package/docs/Guides/Serverless.md +14 -48
  16. package/docs/Guides/Style-Guide.md +2 -2
  17. package/docs/Guides/Testing.md +2 -2
  18. package/docs/Guides/Write-Plugin.md +2 -3
  19. package/docs/Reference/ContentTypeParser.md +58 -78
  20. package/docs/Reference/Decorators.md +249 -60
  21. package/docs/Reference/Encapsulation.md +28 -33
  22. package/docs/Reference/Errors.md +52 -53
  23. package/docs/Reference/HTTP2.md +7 -7
  24. package/docs/Reference/Hooks.md +31 -30
  25. package/docs/Reference/LTS.md +10 -15
  26. package/docs/Reference/Lifecycle.md +19 -24
  27. package/docs/Reference/Logging.md +59 -56
  28. package/docs/Reference/Middleware.md +19 -19
  29. package/docs/Reference/Plugins.md +55 -71
  30. package/docs/Reference/Principles.md +25 -30
  31. package/docs/Reference/Reply.md +11 -10
  32. package/docs/Reference/Request.md +89 -98
  33. package/docs/Reference/Routes.md +108 -128
  34. package/docs/Reference/Server.md +18 -16
  35. package/docs/Reference/Type-Providers.md +19 -21
  36. package/docs/Reference/TypeScript.md +1 -18
  37. package/docs/Reference/Validation-and-Serialization.md +134 -159
  38. package/docs/Reference/Warnings.md +22 -25
  39. package/fastify.js +3 -2
  40. package/lib/contentTypeParser.js +7 -8
  41. package/lib/decorate.js +18 -3
  42. package/lib/error-handler.js +14 -12
  43. package/lib/errors.js +4 -0
  44. package/lib/headRoute.js +4 -2
  45. package/lib/pluginUtils.js +4 -2
  46. package/lib/reply.js +17 -2
  47. package/lib/request.js +28 -2
  48. package/lib/server.js +5 -0
  49. package/lib/validation.js +1 -1
  50. package/lib/warnings.js +9 -0
  51. package/lib/wrapThenable.js +8 -1
  52. package/package.json +12 -12
  53. package/test/bundler/esbuild/package.json +1 -1
  54. package/test/close.test.js +125 -108
  55. package/test/custom-parser-async.test.js +34 -36
  56. package/test/custom-parser.4.test.js +55 -38
  57. package/test/decorator.test.js +174 -4
  58. package/test/fastify-instance.test.js +12 -2
  59. package/test/genReqId.test.js +125 -174
  60. package/test/has-route.test.js +1 -3
  61. package/test/hooks.on-listen.test.js +17 -14
  62. package/test/internals/content-type-parser.test.js +1 -1
  63. package/test/internals/errors.test.js +14 -1
  64. package/test/issue-4959.test.js +84 -0
  65. package/test/listen.1.test.js +37 -34
  66. package/test/listen.2.test.js +50 -40
  67. package/test/listen.3.test.js +28 -32
  68. package/test/listen.4.test.js +61 -45
  69. package/test/listen.5.test.js +23 -0
  70. package/test/register.test.js +55 -50
  71. package/test/request-error.test.js +114 -94
  72. package/test/route-shorthand.test.js +36 -32
  73. package/test/stream.5.test.js +35 -33
  74. package/test/throw.test.js +87 -91
  75. package/test/toolkit.js +32 -0
  76. package/test/trust-proxy.test.js +23 -23
  77. package/test/types/instance.test-d.ts +4 -0
  78. package/test/types/reply.test-d.ts +1 -0
  79. package/test/types/request.test-d.ts +4 -0
  80. package/test/types/type-provider.test-d.ts +40 -0
  81. package/test/upgrade.test.js +32 -33
  82. package/types/instance.d.ts +6 -0
  83. package/types/reply.d.ts +1 -0
  84. package/types/request.d.ts +2 -0
  85. package/types/type-provider.d.ts +12 -3
@@ -4,6 +4,7 @@ const { test, before } = require('node:test')
4
4
  const sget = require('simple-get').concat
5
5
  const fastify = require('..')
6
6
  const helper = require('./helper')
7
+ const { waitForCb } = require('./toolkit')
7
8
 
8
9
  const noop = () => {}
9
10
 
@@ -69,18 +70,14 @@ test('trust proxy, not add properties to node req', (t, done) => {
69
70
  })
70
71
 
71
72
  app.listen({ port: 0 }, (err) => {
72
- app.server.unref()
73
73
  t.assert.ifError(err)
74
74
 
75
- sgetForwardedRequest(app, '1.1.1.1', '/trustproxy', undefined, completed)
76
- sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, completed)
75
+ const completion = waitForCb({ steps: 2 })
77
76
 
78
- let pending = 2
79
- function completed () {
80
- if (--pending === 0) {
81
- done()
82
- }
83
- }
77
+ sgetForwardedRequest(app, '1.1.1.1', '/trustproxy', undefined, completion.stepIn)
78
+ sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxychain', undefined, completion.stepIn)
79
+
80
+ completion.patience.then(done)
84
81
  })
85
82
  })
86
83
 
@@ -89,6 +86,7 @@ test('trust proxy chain', (t, done) => {
89
86
  const app = fastify({
90
87
  trustProxy: [localhost, '192.168.1.1']
91
88
  })
89
+ t.after(() => app.close())
92
90
 
93
91
  app.get('/trustproxychain', function (req, reply) {
94
92
  testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port })
@@ -96,9 +94,7 @@ test('trust proxy chain', (t, done) => {
96
94
  })
97
95
 
98
96
  app.listen({ port: 0 }, (err) => {
99
- app.server.unref()
100
97
  t.assert.ifError(err)
101
- t.after(() => app.close())
102
98
  sgetForwardedRequest(app, '192.168.1.1, 1.1.1.1', '/trustproxychain', undefined, done)
103
99
  })
104
100
  })
@@ -108,15 +104,15 @@ test('trust proxy function', (t, done) => {
108
104
  const app = fastify({
109
105
  trustProxy: (address) => address === localhost
110
106
  })
107
+ t.after(() => app.close())
108
+
111
109
  app.get('/trustproxyfunc', function (req, reply) {
112
110
  testRequestValues(t, req, { ip: '1.1.1.1', host: 'example.com', port: app.server.address().port })
113
111
  reply.code(200).send({ ip: req.ip, host: req.host })
114
112
  })
115
113
 
116
114
  app.listen({ port: 0 }, (err) => {
117
- app.server.unref()
118
115
  t.assert.ifError(err)
119
- t.after(() => app.close())
120
116
  sgetForwardedRequest(app, '1.1.1.1', '/trustproxyfunc', undefined, done)
121
117
  })
122
118
  })
@@ -126,15 +122,15 @@ test('trust proxy number', (t, done) => {
126
122
  const app = fastify({
127
123
  trustProxy: 1
128
124
  })
125
+ t.after(() => app.close())
126
+
129
127
  app.get('/trustproxynumber', function (req, reply) {
130
128
  testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port })
131
129
  reply.code(200).send({ ip: req.ip, host: req.host })
132
130
  })
133
131
 
134
132
  app.listen({ port: 0 }, (err) => {
135
- app.server.unref()
136
133
  t.assert.ifError(err)
137
- t.after(() => app.close())
138
134
  sgetForwardedRequest(app, '2.2.2.2, 1.1.1.1', '/trustproxynumber', undefined, done)
139
135
  })
140
136
  })
@@ -144,15 +140,15 @@ test('trust proxy IP addresses', (t, done) => {
144
140
  const app = fastify({
145
141
  trustProxy: `${localhost}, 2.2.2.2`
146
142
  })
143
+ t.after(() => app.close())
144
+
147
145
  app.get('/trustproxyipaddrs', function (req, reply) {
148
146
  testRequestValues(t, req, { ip: '1.1.1.1', ips: [localhost, '1.1.1.1'], host: 'example.com', port: app.server.address().port })
149
147
  reply.code(200).send({ ip: req.ip, host: req.host })
150
148
  })
151
149
 
152
150
  app.listen({ port: 0 }, (err) => {
153
- app.server.unref()
154
151
  t.assert.ifError(err)
155
- t.after(() => app.close())
156
152
  sgetForwardedRequest(app, '3.3.3.3, 2.2.2.2, 1.1.1.1', '/trustproxyipaddrs', undefined, done)
157
153
  })
158
154
  })
@@ -162,6 +158,8 @@ test('trust proxy protocol', (t, done) => {
162
158
  const app = fastify({
163
159
  trustProxy: true
164
160
  })
161
+ t.after(() => app.close())
162
+
165
163
  app.get('/trustproxyprotocol', function (req, reply) {
166
164
  testRequestValues(t, req, { ip: '1.1.1.1', protocol: 'lorem', host: 'example.com', port: app.server.address().port })
167
165
  reply.code(200).send({ ip: req.ip, host: req.host })
@@ -175,14 +173,16 @@ test('trust proxy protocol', (t, done) => {
175
173
  reply.code(200).send({ ip: req.ip, host: req.host })
176
174
  })
177
175
 
178
- t.after(() => app.close())
179
-
180
176
  app.listen({ port: 0 }, (err) => {
181
- app.server.unref()
182
177
  t.assert.ifError(err)
183
- sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem')
184
- sgetForwardedRequest(app, '1.1.1.1', '/trustproxynoprotocol')
178
+
179
+ const completion = waitForCb({ steps: 3 })
180
+ sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocol', 'lorem', completion.stepIn)
181
+ sgetForwardedRequest(app, '1.1.1.1', '/trustproxynoprotocol', undefined, completion.stepIn)
182
+
185
183
  // Allow for sgetForwardedRequest requests above to finish
186
- setTimeout(() => sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', done))
184
+ setTimeout(() => sgetForwardedRequest(app, '1.1.1.1', '/trustproxyprotocols', 'ipsum, dolor', completion.stepIn))
185
+
186
+ completion.patience.then(done)
187
187
  })
188
188
  })
@@ -36,6 +36,7 @@ expectType<unknown>(server.getSchema('SchemaId'))
36
36
  expectType<string>(server.printRoutes())
37
37
  expectType<string>(server.printPlugins())
38
38
  expectType<string>(server.listeningOrigin)
39
+ expectType<string[]>(server.supportedMethods)
39
40
 
40
41
  expectAssignable<FastifyInstance>(
41
42
  server.setErrorHandler(function (error, request, reply) {
@@ -521,6 +522,9 @@ expectError(server.decorateReply('typedTestReplyMethod', async function (x) {
521
522
  return 'foo'
522
523
  }))
523
524
 
525
+ const foo = server.getDecorator<string>('foo')
526
+ expectType<string>(foo)
527
+
524
528
  const versionConstraintStrategy = {
525
529
  name: 'version',
526
530
  storage: () => ({
@@ -45,6 +45,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) {
45
45
  expectAssignable<((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)>(reply.serializeInput)
46
46
  expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput)
47
47
  expectType<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>(reply.routeOptions.config)
48
+ expectType<string>(reply.getDecorator<string>('foo'))
48
49
  }
49
50
 
50
51
  interface ReplyPayload {
@@ -92,6 +92,10 @@ const getHandler: RouteHandler = function (request, _reply) {
92
92
  expectAssignable<(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction>(request.getValidationFunction)
93
93
  expectAssignable<(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput)
94
94
  expectAssignable<(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput)
95
+ expectType<string>(request.getDecorator<string>('foo'))
96
+ expectType<void>(request.setDecorator('foo', 'hello'))
97
+ expectType<void>(request.setDecorator<string>('foo', 'hello'))
98
+ expectError(request.setDecorator<string>('foo', true))
95
99
  }
96
100
 
97
101
  const getHandlerWithCustomLogger: RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault, CustomLoggerInterface> = function (request, _reply) {
@@ -1009,6 +1009,46 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{ Reply:
1009
1009
  }
1010
1010
  ))
1011
1011
 
1012
+ // -------------------------------------------------------------------
1013
+ // RouteGeneric Reply Type Return (Different Status Codes)
1014
+ // -------------------------------------------------------------------
1015
+
1016
+ expectAssignable(server.get<{
1017
+ Reply: {
1018
+ 200: string | { msg: string }
1019
+ 400: number
1020
+ '5xx': { error: string }
1021
+ }
1022
+ }>(
1023
+ '/',
1024
+ async (_, res) => {
1025
+ const option = 1 as 1 | 2 | 3 | 4
1026
+ switch (option) {
1027
+ case 1: return 'hello'
1028
+ case 2: return { msg: 'hello' }
1029
+ case 3: return 400
1030
+ case 4: return { error: 'error' }
1031
+ }
1032
+ }
1033
+ ))
1034
+
1035
+ // -------------------------------------------------------------------
1036
+ // RouteGeneric Reply Type Return: Non Assignable (Different Status Codes)
1037
+ // -------------------------------------------------------------------
1038
+
1039
+ expectError(server.get<{
1040
+ Reply: {
1041
+ 200: string | { msg: string }
1042
+ 400: number
1043
+ '5xx': { error: string }
1044
+ }
1045
+ }>(
1046
+ '/',
1047
+ async (_, res) => {
1048
+ return true
1049
+ }
1050
+ ))
1051
+
1012
1052
  // -------------------------------------------------------------------
1013
1053
  // FastifyPlugin: Auxiliary
1014
1054
  // -------------------------------------------------------------------
@@ -1,53 +1,52 @@
1
1
  'use strict'
2
2
 
3
- const { test, skip } = require('tap')
3
+ const { describe, test } = require('node:test')
4
4
  const Fastify = require('..')
5
5
  const { connect } = require('node:net')
6
6
  const { once } = require('node:events')
7
7
  const dns = require('node:dns').promises
8
8
 
9
- async function setup () {
9
+ describe('upgrade to both servers', async () => {
10
10
  const localAddresses = await dns.lookup('localhost', { all: true })
11
- if (localAddresses.length === 1) {
12
- skip('requires both IPv4 and IPv6')
13
- return
14
- }
11
+ const skip = localAddresses.length === 1 && 'requires both IPv4 and IPv6'
15
12
 
16
- test('upgrade to both servers', async t => {
13
+ await test('upgrade IPv4 and IPv6', { skip }, async t => {
17
14
  t.plan(2)
18
- const app = Fastify()
19
- app.server.on('upgrade', (req, socket, head) => {
20
- t.pass(`upgrade event ${JSON.stringify(socket.address())}`)
15
+
16
+ const fastify = Fastify()
17
+ fastify.server.on('upgrade', (req, socket, head) => {
18
+ t.assert.ok(`upgrade event ${JSON.stringify(socket.address())}`)
21
19
  socket.end()
22
20
  })
23
- app.get('/', (req, res) => {
21
+
22
+ fastify.get('/', (req, res) => {
23
+ res.send()
24
24
  })
25
- await app.listen()
26
- t.teardown(app.close.bind(app))
25
+
26
+ await fastify.listen()
27
+ t.after(() => fastify.close())
27
28
 
28
29
  {
29
- const client = connect(app.server.address().port, '127.0.0.1')
30
- client.write('GET / HTTP/1.1\r\n')
31
- client.write('Upgrade: websocket\r\n')
32
- client.write('Connection: Upgrade\r\n')
33
- client.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n')
34
- client.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n')
35
- client.write('Sec-WebSocket-Version: 13\r\n\r\n')
36
- client.write('\r\n\r\n')
37
- await once(client, 'close')
30
+ const clientIPv4 = connect(fastify.server.address().port, '127.0.0.1')
31
+ clientIPv4.write('GET / HTTP/1.1\r\n')
32
+ clientIPv4.write('Upgrade: websocket\r\n')
33
+ clientIPv4.write('Connection: Upgrade\r\n')
34
+ clientIPv4.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n')
35
+ clientIPv4.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n')
36
+ clientIPv4.write('Sec-WebSocket-Version: 13\r\n\r\n')
37
+ clientIPv4.write('\r\n\r\n')
38
+ await once(clientIPv4, 'close')
38
39
  }
39
40
 
40
41
  {
41
- const client = connect(app.server.address().port, '::1')
42
- client.write('GET / HTTP/1.1\r\n')
43
- client.write('Upgrade: websocket\r\n')
44
- client.write('Connection: Upgrade\r\n')
45
- client.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n')
46
- client.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n')
47
- client.write('Sec-WebSocket-Version: 13\r\n\r\n')
48
- await once(client, 'close')
42
+ const clientIPv6 = connect(fastify.server.address().port, '::1')
43
+ clientIPv6.write('GET / HTTP/1.1\r\n')
44
+ clientIPv6.write('Upgrade: websocket\r\n')
45
+ clientIPv6.write('Connection: Upgrade\r\n')
46
+ clientIPv6.write('Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n')
47
+ clientIPv6.write('Sec-WebSocket-Protocol: com.xxx.service.v1\r\n')
48
+ clientIPv6.write('Sec-WebSocket-Version: 13\r\n\r\n')
49
+ await once(clientIPv6, 'close')
49
50
  }
50
51
  })
51
- }
52
-
53
- setup()
52
+ })
@@ -152,6 +152,8 @@ export interface FastifyInstance<
152
152
  decorateRequest: DecorationMethod<FastifyRequest, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
153
153
  decorateReply: DecorationMethod<FastifyReply, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
154
154
 
155
+ getDecorator<T>(name: string | symbol): T;
156
+
155
157
  hasDecorator(decorator: string | symbol): boolean;
156
158
  hasRequestDecorator(decorator: string | symbol): boolean;
157
159
  hasReplyDecorator(decorator: string | symbol): boolean;
@@ -548,6 +550,10 @@ export interface FastifyInstance<
548
550
  * Remove all content type parsers, including the default ones
549
551
  */
550
552
  removeAllContentTypeParsers: removeAllContentTypeParsers
553
+ /**
554
+ * Returns an array of strings containing the list of supported HTTP methods
555
+ */
556
+ supportedMethods: string[]
551
557
  /**
552
558
  * Add a non-standard HTTP method
553
559
  *
package/types/reply.d.ts CHANGED
@@ -77,4 +77,5 @@ export interface FastifyReply<
77
77
  ) => FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfig, SchemaCompiler, TypeProvider>;
78
78
  hasTrailer(key: string): boolean;
79
79
  removeTrailer(key: string): FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfig, SchemaCompiler, TypeProvider>;
80
+ getDecorator<T>(name: string | symbol): T;
80
81
  }
@@ -87,4 +87,6 @@ export interface FastifyRequest<RouteGeneric extends RouteGenericInterface = Rou
87
87
  compileValidationSchema(schema: { [key: string]: any }, httpPart?: HTTPRequestPart): ValidationFunction
88
88
  validateInput(input: any, schema: { [key: string]: any }, httpPart?: HTTPRequestPart): boolean
89
89
  validateInput(input: any, httpPart?: HTTPRequestPart): boolean
90
+ getDecorator<T>(name: string | symbol): T;
91
+ setDecorator<T = unknown>(name: string | symbol, value: T): void;
90
92
  }
@@ -1,6 +1,6 @@
1
1
  import { RouteGenericInterface } from './route'
2
2
  import { FastifySchema } from './schema'
3
- import { RecordKeysToLowercase } from './utils'
3
+ import { HttpKeys, RecordKeysToLowercase } from './utils'
4
4
 
5
5
  // -----------------------------------------------------------------------------------------------
6
6
  // TypeProvider
@@ -80,6 +80,11 @@ export type ResolveFastifyReplyType<TypeProvider extends FastifyTypeProvider, Sc
80
80
  // FastifyReplyReturnType
81
81
  // -----------------------------------------------------------------------------------------------
82
82
 
83
+ // Resolves the Reply return type by taking a union of response status codes in the generic argument
84
+ type ResolveReplyReturnTypeFromRouteGeneric<RouteGeneric extends RouteGenericInterface> = RouteGeneric extends { Reply: infer Return }
85
+ ? keyof Return extends HttpKeys ? Return[keyof Return] | Return : Return
86
+ : unknown
87
+
83
88
  // The target reply return type. This type is inferenced on fastify 'routes' via generic argument assignment
84
89
  export type ResolveFastifyReplyReturnType<
85
90
  TypeProvider extends FastifyTypeProvider,
@@ -89,8 +94,12 @@ export type ResolveFastifyReplyReturnType<
89
94
  TypeProvider,
90
95
  SchemaCompiler,
91
96
  RouteGeneric
92
- > extends infer Return ?
93
- (Return | void | Promise<Return | void>)
97
+ > extends infer ReplyType
98
+ ? RouteGeneric['Reply'] extends ReplyType
99
+ ? ResolveReplyReturnTypeFromRouteGeneric<RouteGeneric> extends infer Return
100
+ ? Return | void | Promise<Return | void>
101
+ : unknown
102
+ : ReplyType | void | Promise<ReplyType | void>
94
103
  // review: support both async and sync return types
95
104
  // (Promise<Return> | Return | Promise<void> | void)
96
105
  : unknown