fastify 5.8.5 → 5.10.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 (126) hide show
  1. package/PROJECT_CHARTER.md +1 -1
  2. package/README.md +7 -10
  3. package/SECURITY.md +1 -1
  4. package/SPONSORS.md +6 -4
  5. package/build/build-validation.js +2 -2
  6. package/docs/Guides/Database.md +0 -28
  7. package/docs/Guides/Delay-Accepting-Requests.md +1 -1
  8. package/docs/Guides/Ecosystem.md +23 -9
  9. package/docs/Guides/Getting-Started.md +2 -2
  10. package/docs/Guides/Migration-Guide-V4.md +2 -2
  11. package/docs/Guides/Migration-Guide-V5.md +1 -1
  12. package/docs/Guides/Plugins-Guide.md +1 -1
  13. package/docs/Guides/Prototype-Poisoning.md +3 -3
  14. package/docs/Guides/Serverless.md +8 -15
  15. package/docs/Guides/Style-Guide.md +1 -1
  16. package/docs/Guides/Write-Plugin.md +1 -1
  17. package/docs/Reference/Encapsulation.md +27 -26
  18. package/docs/Reference/Errors.md +12 -4
  19. package/docs/Reference/HTTP2.md +10 -10
  20. package/docs/Reference/Hooks.md +5 -5
  21. package/docs/Reference/Index.md +14 -16
  22. package/docs/Reference/LTS.md +12 -13
  23. package/docs/Reference/Lifecycle.md +9 -8
  24. package/docs/Reference/Logging.md +47 -39
  25. package/docs/Reference/Middleware.md +21 -25
  26. package/docs/Reference/Principles.md +2 -2
  27. package/docs/Reference/Reply.md +6 -1
  28. package/docs/Reference/Request.md +29 -18
  29. package/docs/Reference/Routes.md +5 -2
  30. package/docs/Reference/Server.md +138 -11
  31. package/docs/Reference/Type-Providers.md +53 -9
  32. package/docs/Reference/TypeScript.md +3 -3
  33. package/docs/Reference/Validation-and-Serialization.md +15 -2
  34. package/docs/Reference/Warnings.md +11 -6
  35. package/eslint.config.js +7 -2
  36. package/fastify.d.ts +13 -3
  37. package/fastify.js +60 -31
  38. package/lib/content-type-parser.js +2 -0
  39. package/lib/content-type.js +34 -1
  40. package/lib/context.js +0 -3
  41. package/lib/decorate.js +11 -3
  42. package/lib/error-handler.js +10 -28
  43. package/lib/error-serializer.js +59 -59
  44. package/lib/errors.js +23 -1
  45. package/lib/four-oh-four.js +22 -19
  46. package/lib/handle-request.js +12 -5
  47. package/lib/log-controller.js +169 -0
  48. package/lib/logger-factory.js +25 -4
  49. package/lib/plugin-override.js +2 -1
  50. package/lib/plugin-utils.js +5 -5
  51. package/lib/reply.js +96 -50
  52. package/lib/req-id-gen-factory.js +4 -1
  53. package/lib/request.js +16 -6
  54. package/lib/route.js +47 -41
  55. package/lib/schema-controller.js +1 -1
  56. package/lib/schemas.js +37 -30
  57. package/lib/symbols.js +4 -2
  58. package/lib/validation.js +10 -13
  59. package/lib/warnings.js +22 -4
  60. package/package.json +15 -17
  61. package/scripts/validate-ecosystem-links.js +1 -0
  62. package/test/bundler/esbuild/package.json +1 -1
  63. package/test/close-pipelining.test.js +1 -2
  64. package/test/content-type.test.js +20 -0
  65. package/test/custom-http-server.test.js +38 -0
  66. package/test/decorator-instance-properties.test.js +63 -0
  67. package/test/diagnostics-channel/async-error-handler.test.js +74 -0
  68. package/test/genReqId.test.js +24 -0
  69. package/test/hooks.test.js +94 -0
  70. package/test/http-methods/get.test.js +1 -1
  71. package/test/http2/plain.test.js +135 -0
  72. package/test/http2/secure-with-fallback.test.js +1 -1
  73. package/test/https/https.test.js +1 -2
  74. package/test/internals/errors.test.js +31 -1
  75. package/test/internals/logger.test.js +322 -0
  76. package/test/internals/plugin.test.js +3 -1
  77. package/test/internals/reply.test.js +35 -4
  78. package/test/internals/request.test.js +37 -10
  79. package/test/internals/schema-controller-perf.test.js +33 -0
  80. package/test/logger/logging.test.js +57 -1
  81. package/test/logger/options.test.js +38 -1
  82. package/test/reply-error.test.js +1 -1
  83. package/test/reply-trailers.test.js +70 -0
  84. package/test/request-media-type.test.js +105 -0
  85. package/test/route-prefix.test.js +34 -0
  86. package/test/router-options.test.js +222 -11
  87. package/test/schema-serialization.test.js +108 -0
  88. package/test/schema-validation.test.js +24 -0
  89. package/test/scripts/validate-ecosystem-links.test.js +40 -57
  90. package/test/stream.4.test.js +2 -2
  91. package/test/throw.test.js +14 -0
  92. package/test/trust-proxy.test.js +70 -30
  93. package/test/types/content-type-parser.tst.ts +70 -0
  94. package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
  95. package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
  96. package/test/types/errors.tst.ts +91 -0
  97. package/test/types/fastify.tst.ts +351 -0
  98. package/test/types/hooks.tst.ts +578 -0
  99. package/test/types/instance.tst.ts +597 -0
  100. package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
  101. package/test/types/plugin.tst.ts +96 -0
  102. package/test/types/register.tst.ts +245 -0
  103. package/test/types/reply.tst.ts +297 -0
  104. package/test/types/request.tst.ts +199 -0
  105. package/test/types/route.tst.ts +576 -0
  106. package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
  107. package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
  108. package/test/types/tsconfig.json +9 -0
  109. package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
  110. package/test/types/using.tst.ts +14 -0
  111. package/types/errors.d.ts +4 -0
  112. package/types/instance.d.ts +2 -0
  113. package/types/logger.d.ts +22 -0
  114. package/types/request.d.ts +23 -2
  115. package/test/types/content-type-parser.test-d.ts +0 -72
  116. package/test/types/errors.test-d.ts +0 -90
  117. package/test/types/fastify.test-d.ts +0 -352
  118. package/test/types/hooks.test-d.ts +0 -550
  119. package/test/types/import.ts +0 -2
  120. package/test/types/instance.test-d.ts +0 -588
  121. package/test/types/plugin.test-d.ts +0 -97
  122. package/test/types/register.test-d.ts +0 -237
  123. package/test/types/reply.test-d.ts +0 -254
  124. package/test/types/request.test-d.ts +0 -188
  125. package/test/types/route.test-d.ts +0 -553
  126. package/test/types/using.test-d.ts +0 -17
@@ -0,0 +1,297 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { expect } from 'tstyche'
3
+ import fastify, {
4
+ FastifyContextConfig,
5
+ FastifyReply,
6
+ FastifyRequest,
7
+ FastifySchema,
8
+ FastifyTypeProviderDefault,
9
+ RawRequestDefaultExpression,
10
+ RouteHandler,
11
+ RouteHandlerMethod
12
+ } from '../../fastify.js'
13
+ import { FastifyInstance } from '../../types/instance.js'
14
+ import { FastifyBaseLogger } from '../../types/logger.js'
15
+ import { ResolveReplyTypeWithRouteGeneric } from '../../types/reply.js'
16
+ import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route.js'
17
+ import { ContextConfigDefault, RawReplyDefaultExpression, RawServerDefault } from '../../types/utils.js'
18
+
19
+ type DefaultSerializationFunction = (payload: { [key: string]: unknown }) => string
20
+ type DefaultFastifyReplyWithCode<Code extends number> = FastifyReply<RouteGenericInterface, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault, ResolveReplyTypeWithRouteGeneric<RouteGenericInterface['Reply'], Code, FastifySchema, FastifyTypeProviderDefault>>
21
+
22
+ const getHandler: RouteHandlerMethod = function (_request, reply) {
23
+ expect(reply.raw).type.toBe<RawReplyDefaultExpression>()
24
+ expect(reply.log).type.toBe<FastifyBaseLogger>()
25
+ expect(reply.request).type.toBe<
26
+ FastifyRequest<RouteGenericInterface, RawServerDefault, RawRequestDefaultExpression>
27
+ >()
28
+ expect(reply.code).type.toBe<<Code extends number>(statusCode: Code) => DefaultFastifyReplyWithCode<Code>>()
29
+ expect(reply.status).type.toBe<<Code extends number>(statusCode: Code) => DefaultFastifyReplyWithCode<Code>>()
30
+ expect(reply.code(100 as number).send).type.toBe<(...args: [payload?: unknown]) => FastifyReply>()
31
+ expect(reply.elapsedTime).type.toBe<number>()
32
+ expect(reply.statusCode).type.toBe<number>()
33
+ expect(reply.sent).type.toBe<boolean>()
34
+ expect(reply.writeEarlyHints).type.toBe<
35
+ (hints: Record<string, string | string[]>, callback?: (() => void) | undefined) => void
36
+ >()
37
+ expect(reply.send).type.toBe<((...args: [payload?: unknown]) => FastifyReply)>()
38
+ expect(reply.header).type.toBeAssignableTo<(key: string, value: any) => FastifyReply>()
39
+ expect(reply.headers).type.toBeAssignableTo<(values: { [key: string]: any }) => FastifyReply>()
40
+ expect(reply.getHeader).type.toBeAssignableTo<(key: string) => number | string | string[] | undefined>()
41
+ expect(reply.getHeaders).type.toBeAssignableTo<() => { [key: string]: number | string | string[] | undefined }>()
42
+ expect(reply.removeHeader).type.toBeAssignableTo<(key: string) => FastifyReply>()
43
+ expect(reply.hasHeader).type.toBeAssignableTo<(key: string) => boolean>()
44
+ expect(reply.redirect).type.toBe<(url: string, statusCode?: number) => FastifyReply>()
45
+ expect(reply.hijack).type.toBe<() => FastifyReply>()
46
+ expect(reply.callNotFound).type.toBe<() => void>()
47
+ expect(reply.type).type.toBe<(contentType: string) => FastifyReply>()
48
+ expect(reply.serializer).type.toBe<(fn: (payload: any) => string) => FastifyReply>()
49
+ expect(reply.serialize).type.toBe<(payload: any) => string | ArrayBuffer | Buffer>()
50
+ expect(reply.then).type.toBe<(fulfilled: () => void, rejected: (err: Error) => void) => void>()
51
+ expect(reply.trailer).type.toBe<
52
+ (
53
+ key: string,
54
+ fn: ((reply: FastifyReply, payload: string | Buffer | null) => Promise<string>) |
55
+ ((reply: FastifyReply, payload: string | Buffer | null,
56
+ done: (err: Error | null, value?: string) => void) => void)
57
+ ) => FastifyReply
58
+ >()
59
+ expect(reply.hasTrailer).type.toBe<(key: string) => boolean>()
60
+ expect(reply.removeTrailer).type.toBe<(key: string) => FastifyReply>()
61
+ expect(reply.server).type.toBe<FastifyInstance>()
62
+ expect(reply.getSerializationFunction).type.toBeAssignableTo<
63
+ ((httpStatus: string) => DefaultSerializationFunction | undefined)
64
+ >()
65
+ expect(reply.getSerializationFunction).type.toBeAssignableTo<
66
+ ((schema: { [key: string]: unknown }) => DefaultSerializationFunction | undefined)
67
+ >()
68
+ expect(reply.compileSerializationSchema).type.toBeAssignableTo<
69
+ ((schema: { [key: string]: unknown }, httpStatus?: string) => DefaultSerializationFunction)
70
+ >()
71
+ expect(reply.serializeInput).type.toBeAssignableTo<
72
+ ((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)
73
+ >()
74
+ expect(reply.serializeInput).type.toBeAssignableTo<
75
+ ((input: { [key: string]: unknown }, httpStatus: string) => unknown)
76
+ >()
77
+ expect(reply.routeOptions.config).type.toBe<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>()
78
+ expect(reply.getDecorator<string>('foo')).type.toBe<string>()
79
+ }
80
+
81
+ interface ReplyPayload {
82
+ Reply: {
83
+ test: boolean;
84
+ };
85
+ }
86
+
87
+ interface ReplyArrayPayload {
88
+ Reply: string[]
89
+ }
90
+
91
+ interface ReplyUnion {
92
+ Reply: {
93
+ success: boolean;
94
+ } | {
95
+ error: string;
96
+ }
97
+ }
98
+
99
+ interface ReplyHttpCodes {
100
+ Reply: {
101
+ '1xx': number,
102
+ 200: 'abc',
103
+ 201: boolean,
104
+ 300: { foo: string },
105
+ }
106
+ }
107
+
108
+ interface InvalidReplyHttpCodes {
109
+ Reply: {
110
+ '1xx': number,
111
+ 200: string,
112
+ 999: boolean,
113
+ }
114
+ }
115
+
116
+ interface ReplyVoid {
117
+ Reply: void;
118
+ }
119
+
120
+ interface ReplyUndefined {
121
+ Reply: undefined;
122
+ }
123
+
124
+ // Issue #5534 scenario: 204 No Content should allow empty send(), 201 Created should require payload
125
+ // Note: `204: undefined` gets converted to `unknown` via UndefinedToUnknown in type-provider.d.ts,
126
+ // meaning send() is optional but send({}) is also allowed. Use `void` instead of `undefined`
127
+ // if you want stricter "no payload allowed" semantics.
128
+ interface ReplyHttpCodesWithNoContent {
129
+ Reply: {
130
+ 201: { id: string };
131
+ 204: undefined;
132
+ }
133
+ }
134
+
135
+ const typedHandler: RouteHandler<ReplyPayload> = async (request, reply) => {
136
+ // When Reply type is specified, send() requires a payload argument
137
+ expect(reply.send).type.toBe<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply<ReplyPayload, RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>>)>()
138
+ expect(reply.code(100).send).type.toBe<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply<ReplyPayload, RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>>)>()
139
+ }
140
+
141
+ const server = fastify()
142
+ server.get('/get', getHandler)
143
+ server.get('/typed', typedHandler)
144
+ server.get<ReplyPayload>('/get-generic-send', async function handler (request, reply) {
145
+ reply.send({ test: true })
146
+ })
147
+ // When Reply type is specified, send() requires a payload - calling without arguments should error
148
+ server.get<ReplyPayload>('/get-generic-send-missing-payload', async function handler (request, reply) {
149
+ reply
150
+ // @ts-expect-error Expected 1 arguments, but got 0.
151
+ .send()
152
+ })
153
+ server.get<ReplyPayload>('/get-generic-return', async function handler (request, reply) {
154
+ return { test: false }
155
+ })
156
+ server.get<ReplyPayload>('/get-generic-send-error', async function handler (request, reply) {
157
+ reply.send({
158
+ // @ts-expect-error 'foo' does not exist in type '{ test: boolean; }'.
159
+ foo: 'bar'
160
+ })
161
+ })
162
+ // @ts-expect-error No overload matches this call.
163
+ server.get<ReplyPayload>('/get-generic-return-error', async function handler (request, reply) {
164
+ return { foo: 'bar' }
165
+ })
166
+ server.get<ReplyUnion>('/get-generic-union-send', async function handler (request, reply) {
167
+ if (0 as number === 0) {
168
+ reply.send({ success: true })
169
+ } else {
170
+ reply.send({ error: 'error' })
171
+ }
172
+ })
173
+ server.get<ReplyUnion>('/get-generic-union-return', async function handler (request, reply) {
174
+ if (0 as number === 0) {
175
+ return { success: true }
176
+ } else {
177
+ return { error: 'error' }
178
+ }
179
+ })
180
+ server.get<ReplyUnion>('/get-generic-union-send-error-1', async function handler (request, reply) {
181
+ reply.send({
182
+ // @ts-expect-error 'successes' does not exist in type '{ success: boolean; } | { error: string; }'.
183
+ successes: true
184
+ })
185
+ })
186
+ server.get<ReplyUnion>('/get-generic-union-send-error-2', async function handler (request, reply) {
187
+ reply.send({
188
+ // @ts-expect-error Type 'number' is not assignable to type 'string'.
189
+ error: 500
190
+ })
191
+ })
192
+ // @ts-expect-error No overload matches this call.
193
+ server.get<ReplyUnion>('/get-generic-union-return-error-1', async function handler (request, reply) {
194
+ return { successes: true }
195
+ })
196
+ // @ts-expect-error No overload matches this call.
197
+ server.get<ReplyUnion>('/get-generic-union-return-error-2', async function handler (request, reply) {
198
+ return { error: 500 }
199
+ })
200
+ server.get<ReplyHttpCodes>('/get-generic-http-codes-send', async function handler (request, reply) {
201
+ reply.code(200).send('abc')
202
+ reply.code(201).send(true)
203
+ reply.code(300).send({ foo: 'bar' })
204
+ reply.code(101).send(123)
205
+ })
206
+ server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-1', async function handler (request, reply) {
207
+ reply.code(200)
208
+ // @ts-expect-error Argument of type '"def"' is not assignable to parameter of type '"abc"'.
209
+ .send('def')
210
+ })
211
+ server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-2', async function handler (request, reply) {
212
+ reply.code(201)
213
+ // @ts-expect-error Argument of type 'number' is not assignable to parameter of type 'boolean'.
214
+ .send(0)
215
+ })
216
+ server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-3', async function handler (request, reply) {
217
+ reply.code(300).send({
218
+ // @ts-expect-error Type 'number' is not assignable to type 'string'.
219
+ foo: 123
220
+ })
221
+ })
222
+ server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-4', async function handler (request, reply) {
223
+ reply.code(100)
224
+ // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'.
225
+ .send('asdasd')
226
+ })
227
+ server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-5', async function handler (request, reply) {
228
+ reply
229
+ // @ts-expect-error Argument of type '401' is not assignable to parameter of type '100 | 101 | 102 | ... | 300'.
230
+ .code(401).send({
231
+ // @ts-expect-error Type 'number' is not assignable to type 'string'.
232
+ foo: 123
233
+ })
234
+ })
235
+ server.get<ReplyArrayPayload>('/get-generic-array-send', async function handler (request, reply) {
236
+ reply.code(200).send([''])
237
+ })
238
+ server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
239
+ reply.code(200)
240
+ // @ts-expect-error Argument of type 'string' is not assignable to parameter of type '{ '1xx': number; 200: string; 999: boolean; }'.
241
+ .send('')
242
+ })
243
+ server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
244
+ reply.code(200).send({
245
+ '1xx': 0,
246
+ 200: '',
247
+ 999: false
248
+ })
249
+ })
250
+
251
+ const httpHeaderHandler: RouteHandlerMethod = function (_request, reply) {
252
+ // accept is a header provided by @types/node
253
+ reply.getHeader('accept')
254
+ expect(reply.getHeaders()).type.toHaveProperty('accept')
255
+ reply.hasHeader('accept')
256
+ reply.header('accept', 'test')
257
+ reply.headers({ accept: 'test' })
258
+ reply.removeHeader('accept')
259
+
260
+ // x-fastify-test is not a header provided by @types/node
261
+ // and should not result in a typing error
262
+ reply.getHeader('x-fastify-test')
263
+ expect(reply.getHeaders()).type.toHaveProperty('x-fastify-test')
264
+ reply.hasHeader('x-fastify-test')
265
+ reply.header('x-fastify-test', 'test')
266
+ reply.headers({ 'x-fastify-test': 'test' })
267
+ reply.removeHeader('x-fastify-test')
268
+ }
269
+
270
+ // Test: send() without arguments is valid when no Reply type is specified (default unknown)
271
+ server.get('/get-no-type-send-empty', async function handler (request, reply) {
272
+ reply.send()
273
+ })
274
+
275
+ // Test: send() without arguments is valid when Reply type is void
276
+ server.get<ReplyVoid>('/get-void-send-empty', async function handler (request, reply) {
277
+ reply.send()
278
+ })
279
+
280
+ // Test: send() without arguments is valid when Reply type is undefined
281
+ server.get<ReplyUndefined>('/get-undefined-send-empty', async function handler (request, reply) {
282
+ reply.send()
283
+ })
284
+
285
+ // Issue #5534 scenario: HTTP status codes with 204 No Content
286
+ server.get<ReplyHttpCodesWithNoContent>('/get-http-codes-no-content', async function handler (request, reply) {
287
+ // 204 No Content - send() without payload is valid because Reply is undefined
288
+ reply.code(204).send()
289
+ // 201 Created - send() requires payload
290
+ reply.code(201).send({ id: '123' })
291
+ })
292
+ // 201 Created without payload should error
293
+ server.get<ReplyHttpCodesWithNoContent>('/get-http-codes-201-missing-payload', async function handler (request, reply) {
294
+ reply.code(201)
295
+ // @ts-expect-error Expected 1 arguments, but got 0.
296
+ .send()
297
+ })
@@ -0,0 +1,199 @@
1
+ import { expect } from 'tstyche'
2
+ import fastify, {
3
+ ContextConfigDefault,
4
+ FastifyContextConfig,
5
+ FastifyLogFn,
6
+ FastifySchema,
7
+ FastifyTypeProviderDefault,
8
+ RawReplyDefaultExpression,
9
+ RawRequestDefaultExpression,
10
+ RawServerDefault,
11
+ RequestBodyDefault,
12
+ RequestGenericInterface,
13
+ RouteHandler,
14
+ RouteHandlerMethod,
15
+ SafePromiseLike
16
+ } from '../../fastify.js'
17
+ import { FastifyInstance } from '../../types/instance.js'
18
+ import { FastifyBaseLogger } from '../../types/logger.js'
19
+ import { FastifyReply } from '../../types/reply.js'
20
+ import { FastifyRequest, RequestRouteOptions, ValidationFunction } from '../../types/request.js'
21
+ import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route.js'
22
+ import { RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from '../../types/utils.js'
23
+
24
+ interface RequestBody {
25
+ content: string;
26
+ }
27
+
28
+ interface RequestQuerystring {
29
+ from: string;
30
+ }
31
+
32
+ interface RequestParams {
33
+ id: number;
34
+ }
35
+
36
+ interface RequestHeaders {
37
+ 'x-foobar': string;
38
+ }
39
+
40
+ interface RequestData extends RequestGenericInterface {
41
+ Body: RequestBody;
42
+ Querystring: RequestQuerystring;
43
+ Params: RequestParams;
44
+ Headers: RequestHeaders;
45
+ }
46
+
47
+ type Handler = RouteHandler<RequestData>
48
+
49
+ type CustomRequest = FastifyRequest<{
50
+ Body: RequestBody | undefined;
51
+ Querystring: RequestQuerystring;
52
+ Params: RequestParams;
53
+ Headers: RequestHeaders;
54
+ }>
55
+
56
+ type HTTPRequestPart = 'body' | 'query' | 'querystring' | 'params' | 'headers'
57
+ type ExpectedGetValidationFunction = (input: { [key: string]: unknown }) => boolean
58
+
59
+ interface CustomLoggerInterface extends FastifyBaseLogger {
60
+ foo: FastifyLogFn; // custom severity logger method
61
+ }
62
+
63
+ const getHandler: RouteHandler = function (request, _reply) {
64
+ expect(request.url).type.toBe<string>()
65
+ expect(request.originalUrl).type.toBe<string>()
66
+ expect(request.method).type.toBe<string>()
67
+ expect(request.routeOptions).type.toBe<Readonly<RequestRouteOptions>>()
68
+ expect(request.is404).type.toBe<boolean>()
69
+ expect(request.hostname).type.toBe<string>()
70
+ expect(request.host).type.toBe<string>()
71
+ expect(request.port).type.toBe<number | null>()
72
+ expect(request.ip).type.toBe<string>()
73
+ expect(request.ips).type.toBe<string[] | undefined>()
74
+ expect(request.raw).type.toBe<RawRequestDefaultExpression>()
75
+ expect(request.body).type.toBe<RequestBodyDefault>()
76
+ expect(request.params).type.toBe<RequestParamsDefault>()
77
+ expect(request.routeOptions.config).type.toBe<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>()
78
+ expect(request.routeOptions.schema).type.toBe<FastifySchema | undefined>()
79
+ expect(request.routeOptions.handler).type.toBe<RouteHandlerMethod>()
80
+ expect(request.routeOptions.url).type.toBe<string | undefined>()
81
+ expect(request.routeOptions.version).type.toBe<string | undefined>()
82
+
83
+ expect(request.headers).type.toBe<RequestHeadersDefault & RawRequestDefaultExpression['headers']>()
84
+ request.headers = {}
85
+
86
+ expect(request.query).type.toBe<RequestQuerystringDefault>()
87
+ expect(request.id).type.toBe<string>()
88
+ expect(request.log).type.toBe<FastifyBaseLogger>()
89
+ expect(request.socket).type.toBe<RawRequestDefaultExpression['socket']>()
90
+ expect(request.signal).type.toBe<AbortSignal>()
91
+ expect(request.validationError).type.toBe<Error & { validation: any; validationContext: string } | undefined>()
92
+ expect(request.server).type.toBe<FastifyInstance>()
93
+ expect(request.getValidationFunction).type.toBeAssignableTo<
94
+ (httpPart: HTTPRequestPart) => ExpectedGetValidationFunction | undefined
95
+ >()
96
+ expect(request.getValidationFunction).type.toBeAssignableTo<
97
+ (schema: { [key: string]: unknown }) => ExpectedGetValidationFunction | undefined
98
+ >()
99
+ expect(request.getValidationFunction('body')).type.toBe<ValidationFunction | undefined>()
100
+ expect(request.getValidationFunction({ type: 'object' })).type.toBe<ValidationFunction | undefined>()
101
+ expect(request.validateInput).type.toBeAssignableTo<
102
+ (input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean
103
+ >()
104
+ expect(request.validateInput).type.toBeAssignableTo<
105
+ (input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean
106
+ >()
107
+ expect(request.getDecorator<string>('foo')).type.toBe<string>()
108
+ expect(request.setDecorator('foo', 'hello')).type.toBe<void>()
109
+ expect(request.setDecorator<string>('foo', 'hello')).type.toBe<void>()
110
+ expect(request.setDecorator<string>).type.not.toBeCallableWith('foo', true)
111
+ }
112
+
113
+ const getHandlerWithCustomLogger: RouteHandlerMethod<
114
+ RawServerDefault,
115
+ RawRequestDefaultExpression,
116
+ RawReplyDefaultExpression,
117
+ RouteGenericInterface,
118
+ ContextConfigDefault,
119
+ FastifySchema,
120
+ FastifyTypeProviderDefault,
121
+ CustomLoggerInterface
122
+ > = function (request, _reply) {
123
+ expect(request.log).type.toBe<CustomLoggerInterface>()
124
+ }
125
+
126
+ const postHandler: Handler = function (request) {
127
+ expect(request.body).type.toBe<RequestBody>()
128
+ expect(request.params).type.toBe<RequestParams>()
129
+ expect(
130
+ request.headers
131
+ ).type.toBe<RequestHeaders & RawRequestDefaultExpression['headers']>()
132
+ expect(request.query).type.toBe<RequestQuerystring>()
133
+ expect(request.body.content).type.toBe<string>()
134
+ expect(request.query.from).type.toBe<string>()
135
+ expect(request.params.id).type.toBe<number>()
136
+ expect(request.headers['x-foobar']).type.toBe<string>()
137
+ expect(request.server).type.toBe<FastifyInstance>()
138
+ expect(request.routeOptions.config).type.toBe<FastifyContextConfig & FastifyRouteConfig>()
139
+ }
140
+
141
+ function putHandler (request: CustomRequest, reply: FastifyReply) {
142
+ expect(request.body).type.toBe<RequestBody | undefined>()
143
+ expect(request.params).type.toBe<RequestParams>()
144
+ expect(request.headers).type.toBe<RequestHeaders & RawRequestDefaultExpression['headers']>()
145
+ expect(request.query).type.toBe<RequestQuerystring>()
146
+ if (request.body === undefined) {
147
+ expect(request.body).type.toBe<undefined>()
148
+ } else {
149
+ expect(request.body.content).type.toBe<string>()
150
+ }
151
+ expect(request.query.from).type.toBe<string>()
152
+ expect(request.params.id).type.toBe<number>()
153
+ expect(request.headers['x-foobar']).type.toBe<string>()
154
+ expect(request.server).type.toBe<FastifyInstance>()
155
+ expect(request.routeOptions.config).type.toBe<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>()
156
+ }
157
+
158
+ const server = fastify()
159
+ server.get('/get', getHandler)
160
+ server.post('/post', postHandler)
161
+ server.put('/put', putHandler)
162
+
163
+ const customLogger: CustomLoggerInterface = {
164
+ level: 'info',
165
+ silent: () => { },
166
+ info: () => { },
167
+ warn: () => { },
168
+ error: () => { },
169
+ fatal: () => { },
170
+ trace: () => { },
171
+ debug: () => { },
172
+ foo: () => { }, // custom severity logger method
173
+ child: () => customLogger
174
+ }
175
+
176
+ const serverWithCustomLogger = fastify({ loggerInstance: customLogger })
177
+
178
+ expect(serverWithCustomLogger).type.not.toBeAssignableTo<
179
+ FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
180
+ & Promise<
181
+ FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
182
+ >
183
+ >()
184
+
185
+ expect(serverWithCustomLogger).type.toBeAssignableTo<
186
+ FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
187
+ & PromiseLike<
188
+ FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
189
+ >
190
+ >()
191
+
192
+ expect(serverWithCustomLogger).type.toBe<
193
+ FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
194
+ & SafePromiseLike<
195
+ FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
196
+ >
197
+ >()
198
+
199
+ serverWithCustomLogger.get('/get', getHandlerWithCustomLogger)