fastify 5.2.2 → 5.3.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.
Files changed (38) hide show
  1. package/SPONSORS.md +0 -1
  2. package/docs/Guides/Ecosystem.md +2 -0
  3. package/docs/Reference/Decorators.md +199 -0
  4. package/docs/Reference/Errors.md +2 -0
  5. package/fastify.js +3 -2
  6. package/lib/decorate.js +18 -3
  7. package/lib/errors.js +4 -0
  8. package/lib/reply.js +17 -2
  9. package/lib/request.js +28 -2
  10. package/lib/validation.js +11 -1
  11. package/package.json +3 -3
  12. package/test/build/error-serializer.test.js +1 -2
  13. package/test/custom-parser.0.test.js +160 -129
  14. package/test/custom-parser.1.test.js +77 -63
  15. package/test/custom-parser.4.test.js +55 -38
  16. package/test/custom-querystring-parser.test.js +46 -28
  17. package/test/decorator.test.js +174 -4
  18. package/test/fastify-instance.test.js +12 -2
  19. package/test/hooks.on-listen.test.js +17 -14
  20. package/test/internals/errors.test.js +14 -1
  21. package/test/listen.2.test.js +4 -1
  22. package/test/logger/instantiation.test.js +89 -96
  23. package/test/logger/logging.test.js +116 -120
  24. package/test/logger/options.test.js +97 -99
  25. package/test/logger/request.test.js +66 -66
  26. package/test/schema-validation.test.js +120 -0
  27. package/test/server.test.js +175 -0
  28. package/test/stream.4.test.js +38 -33
  29. package/test/toolkit.js +31 -0
  30. package/test/types/instance.test-d.ts +3 -0
  31. package/test/types/reply.test-d.ts +1 -0
  32. package/test/types/request.test-d.ts +4 -0
  33. package/test/types/type-provider.test-d.ts +40 -0
  34. package/test/upgrade.test.js +3 -6
  35. package/types/instance.d.ts +2 -0
  36. package/types/reply.d.ts +1 -0
  37. package/types/request.d.ts +2 -0
  38. package/types/type-provider.d.ts +12 -3
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const t = require('tap')
4
- const test = t.test
3
+ const { test } = require('node:test')
5
4
  const sget = require('simple-get').concat
6
5
  const errors = require('http-errors')
7
6
  const JSONStream = require('JSONStream')
@@ -10,7 +9,7 @@ const split = require('split2')
10
9
  const Fastify = require('..')
11
10
  const { kDisableRequestLogging } = require('../lib/symbols.js')
12
11
 
13
- test('Destroying streams prematurely should call abort method', t => {
12
+ test('Destroying streams prematurely should call abort method', (t, testDone) => {
14
13
  t.plan(7)
15
14
 
16
15
  let fastify = null
@@ -23,7 +22,7 @@ test('Destroying streams prematurely should call abort method', t => {
23
22
  }
24
23
  })
25
24
  } catch (e) {
26
- t.fail()
25
+ t.assert.fail()
27
26
  }
28
27
  const stream = require('node:stream')
29
28
  const http = require('node:http')
@@ -31,13 +30,14 @@ test('Destroying streams prematurely should call abort method', t => {
31
30
  // Test that "premature close" errors are logged with level warn
32
31
  logStream.on('data', line => {
33
32
  if (line.res) {
34
- t.equal(line.msg, 'stream closed prematurely')
35
- t.equal(line.level, 30)
33
+ t.assert.strictEqual(line.msg, 'stream closed prematurely')
34
+ t.assert.strictEqual(line.level, 30)
35
+ testDone()
36
36
  }
37
37
  })
38
38
 
39
39
  fastify.get('/', function (request, reply) {
40
- t.pass('Received request')
40
+ t.assert.ok('Received request')
41
41
 
42
42
  let sent = false
43
43
  const reallyLongStream = new stream.Readable({
@@ -50,30 +50,30 @@ test('Destroying streams prematurely should call abort method', t => {
50
50
  })
51
51
  reallyLongStream.destroy = undefined
52
52
  reallyLongStream.close = undefined
53
- reallyLongStream.abort = () => t.ok('called')
53
+ reallyLongStream.abort = () => t.assert.ok('called')
54
54
  reply.send(reallyLongStream)
55
55
  })
56
56
 
57
57
  fastify.listen({ port: 0 }, err => {
58
- t.error(err)
59
- t.teardown(() => { fastify.close() })
58
+ t.assert.ifError(err)
59
+ t.after(() => { fastify.close() })
60
60
 
61
61
  const port = fastify.server.address().port
62
62
 
63
63
  http.get(`http://localhost:${port}`, function (response) {
64
- t.equal(response.statusCode, 200)
64
+ t.assert.strictEqual(response.statusCode, 200)
65
65
  response.on('readable', function () {
66
66
  response.destroy()
67
67
  })
68
68
  // Node bug? Node never emits 'close' here.
69
69
  response.on('aborted', function () {
70
- t.pass('Response closed')
70
+ t.assert.ok('Response closed')
71
71
  })
72
72
  })
73
73
  })
74
74
  })
75
75
 
76
- test('Destroying streams prematurely, log is disabled', t => {
76
+ test('Destroying streams prematurely, log is disabled', (t, testDone) => {
77
77
  t.plan(4)
78
78
 
79
79
  let fastify = null
@@ -82,7 +82,7 @@ test('Destroying streams prematurely, log is disabled', t => {
82
82
  logger: false
83
83
  })
84
84
  } catch (e) {
85
- t.fail()
85
+ t.assert.fail()
86
86
  }
87
87
  const stream = require('node:stream')
88
88
  const http = require('node:http')
@@ -100,30 +100,33 @@ test('Destroying streams prematurely, log is disabled', t => {
100
100
  }
101
101
  })
102
102
  reallyLongStream.destroy = true
103
- reallyLongStream.close = () => t.ok('called')
103
+ reallyLongStream.close = () => {
104
+ t.assert.ok('called')
105
+ testDone()
106
+ }
104
107
  reply.send(reallyLongStream)
105
108
  })
106
109
 
107
110
  fastify.listen({ port: 0 }, err => {
108
- t.error(err)
109
- t.teardown(() => { fastify.close() })
111
+ t.assert.ifError(err)
112
+ t.after(() => { fastify.close() })
110
113
 
111
114
  const port = fastify.server.address().port
112
115
 
113
116
  http.get(`http://localhost:${port}`, function (response) {
114
- t.equal(response.statusCode, 200)
117
+ t.assert.strictEqual(response.statusCode, 200)
115
118
  response.on('readable', function () {
116
119
  response.destroy()
117
120
  })
118
121
  // Node bug? Node never emits 'close' here.
119
122
  response.on('aborted', function () {
120
- t.pass('Response closed')
123
+ t.assert.ok('Response closed')
121
124
  })
122
125
  })
123
126
  })
124
127
  })
125
128
 
126
- test('should respond with a stream1', t => {
129
+ test('should respond with a stream1', (t, testDone) => {
127
130
  t.plan(5)
128
131
  const fastify = Fastify()
129
132
 
@@ -135,25 +138,26 @@ test('should respond with a stream1', t => {
135
138
  })
136
139
 
137
140
  fastify.listen({ port: 0 }, err => {
138
- t.error(err)
139
- t.teardown(() => { fastify.close() })
141
+ t.assert.ifError(err)
142
+ t.after(() => { fastify.close() })
140
143
 
141
144
  sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) {
142
- t.error(err)
143
- t.equal(response.headers['content-type'], 'application/json')
144
- t.equal(response.statusCode, 200)
145
- t.same(JSON.parse(body), [{ hello: 'world' }, { a: 42 }])
145
+ t.assert.ifError(err)
146
+ t.assert.strictEqual(response.headers['content-type'], 'application/json')
147
+ t.assert.strictEqual(response.statusCode, 200)
148
+ t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }])
149
+ testDone()
146
150
  })
147
151
  })
148
152
  })
149
153
 
150
- test('return a 404 if the stream emits a 404 error', t => {
154
+ test('return a 404 if the stream emits a 404 error', (t, testDone) => {
151
155
  t.plan(5)
152
156
 
153
157
  const fastify = Fastify()
154
158
 
155
159
  fastify.get('/', function (request, reply) {
156
- t.pass('Received request')
160
+ t.assert.ok('Received request')
157
161
 
158
162
  const reallyLongStream = new Readable({
159
163
  read: function () {
@@ -167,15 +171,16 @@ test('return a 404 if the stream emits a 404 error', t => {
167
171
  })
168
172
 
169
173
  fastify.listen({ port: 0 }, err => {
170
- t.error(err)
171
- t.teardown(() => { fastify.close() })
174
+ t.assert.ifError(err)
175
+ t.after(() => { fastify.close() })
172
176
 
173
177
  const port = fastify.server.address().port
174
178
 
175
179
  sget(`http://localhost:${port}`, function (err, response) {
176
- t.error(err)
177
- t.equal(response.headers['content-type'], 'application/json; charset=utf-8')
178
- t.equal(response.statusCode, 404)
180
+ t.assert.ifError(err)
181
+ t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
182
+ t.assert.strictEqual(response.statusCode, 404)
183
+ testDone()
179
184
  })
180
185
  })
181
186
  })
package/test/toolkit.js CHANGED
@@ -30,3 +30,34 @@ exports.waitForCb = function (options) {
30
30
 
31
31
  return { stepIn, patience }
32
32
  }
33
+
34
+ exports.partialDeepStrictEqual = function partialDeepStrictEqual (actual, expected) {
35
+ if (typeof expected !== 'object' || expected === null) {
36
+ return actual === expected
37
+ }
38
+
39
+ if (typeof actual !== 'object' || actual === null) {
40
+ return false
41
+ }
42
+
43
+ if (Array.isArray(expected)) {
44
+ if (!Array.isArray(actual)) return false
45
+ if (expected.length > actual.length) return false
46
+
47
+ for (let i = 0; i < expected.length; i++) {
48
+ if (!partialDeepStrictEqual(actual[i], expected[i])) {
49
+ return false
50
+ }
51
+ }
52
+ return true
53
+ }
54
+
55
+ for (const key of Object.keys(expected)) {
56
+ if (!(key in actual)) return false
57
+ if (!partialDeepStrictEqual(actual[key], expected[key])) {
58
+ return false
59
+ }
60
+ }
61
+
62
+ return true
63
+ }
@@ -522,6 +522,9 @@ expectError(server.decorateReply('typedTestReplyMethod', async function (x) {
522
522
  return 'foo'
523
523
  }))
524
524
 
525
+ const foo = server.getDecorator<string>('foo')
526
+ expectType<string>(foo)
527
+
525
528
  const versionConstraintStrategy = {
526
529
  name: 'version',
527
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
  // -------------------------------------------------------------------
@@ -6,14 +6,11 @@ const { connect } = require('node:net')
6
6
  const { once } = require('node:events')
7
7
  const dns = require('node:dns').promises
8
8
 
9
- describe('upgrade to both servers', async t => {
9
+ describe('upgrade to both servers', async () => {
10
10
  const localAddresses = await dns.lookup('localhost', { all: true })
11
- if (localAddresses.length === 1) {
12
- t.skip('requires both IPv4 and IPv6')
13
- return
14
- }
11
+ const skip = localAddresses.length === 1 && 'requires both IPv4 and IPv6'
15
12
 
16
- await test('upgrade IPv4 and IPv6', async t => {
13
+ await test('upgrade IPv4 and IPv6', { skip }, async t => {
17
14
  t.plan(2)
18
15
 
19
16
  const fastify = Fastify()
@@ -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;
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