fastify 4.20.0 → 4.22.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 (43) hide show
  1. package/README.md +2 -1
  2. package/docs/Guides/Ecosystem.md +5 -3
  3. package/docs/Guides/Getting-Started.md +1 -1
  4. package/docs/Reference/Hooks.md +3 -0
  5. package/docs/Reference/Server.md +199 -181
  6. package/docs/Reference/TypeScript.md +1 -1
  7. package/docs/Reference/Validation-and-Serialization.md +1 -1
  8. package/fastify.d.ts +7 -10
  9. package/fastify.js +3 -3
  10. package/lib/contentTypeParser.js +5 -2
  11. package/lib/error-serializer.js +31 -29
  12. package/lib/errors.js +1 -1
  13. package/lib/pluginOverride.js +10 -3
  14. package/lib/pluginUtils.js +13 -10
  15. package/lib/reply.js +16 -4
  16. package/lib/wrapThenable.js +4 -1
  17. package/package.json +7 -9
  18. package/test/async-await.test.js +1 -1
  19. package/test/bodyLimit.test.js +69 -0
  20. package/test/custom-http-server.test.js +2 -1
  21. package/test/https/custom-https-server.test.js +2 -1
  22. package/test/internals/errors.test.js +2 -2
  23. package/test/internals/plugin.test.js +17 -2
  24. package/test/internals/reply.test.js +33 -2
  25. package/test/plugin.test.js +26 -0
  26. package/test/serial/logger.0.test.js +6 -1
  27. package/test/stream.test.js +4 -4
  28. package/test/types/fastify.test-d.ts +8 -3
  29. package/test/types/hooks.test-d.ts +13 -0
  30. package/test/types/instance.test-d.ts +7 -2
  31. package/test/types/reply.test-d.ts +25 -0
  32. package/test/types/request.test-d.ts +1 -1
  33. package/test/types/type-provider.test-d.ts +82 -1
  34. package/test/wrapThenable.test.js +22 -0
  35. package/types/hooks.d.ts +104 -4
  36. package/types/instance.d.ts +52 -143
  37. package/types/reply.d.ts +8 -6
  38. package/types/request.d.ts +1 -1
  39. package/types/route.d.ts +6 -1
  40. package/types/schema.d.ts +1 -1
  41. package/types/tsconfig.eslint.json +2 -2
  42. package/types/type-provider.d.ts +2 -1
  43. package/types/utils.d.ts +9 -0
@@ -7,7 +7,7 @@ const sget = require('simple-get').concat
7
7
  const fs = require('fs')
8
8
  const resolve = require('path').resolve
9
9
  const zlib = require('zlib')
10
- const pump = require('pump')
10
+ const pipeline = require('stream').pipeline
11
11
  const Fastify = require('..')
12
12
  const errors = require('http-errors')
13
13
  const JSONStream = require('JSONStream')
@@ -139,7 +139,7 @@ test('onSend hook stream', t => {
139
139
  const gzStream = zlib.createGzip()
140
140
 
141
141
  reply.header('Content-Encoding', 'gzip')
142
- pump(
142
+ pipeline(
143
143
  fs.createReadStream(resolve(process.cwd() + '/test/stream.test.js'), 'utf8'),
144
144
  gzStream,
145
145
  t.error
@@ -637,7 +637,7 @@ test('should destroy stream when response is ended', t => {
637
637
 
638
638
  fastify.get('/error', function (req, reply) {
639
639
  const reallyLongStream = new stream.Readable({
640
- read: function () {},
640
+ read: function () { },
641
641
  destroy: function (err, callback) {
642
642
  t.ok('called')
643
643
  callback(err)
@@ -763,7 +763,7 @@ test('request terminated should not crash fastify', t => {
763
763
 
764
764
  fastify.get('/', async (req, reply) => {
765
765
  const stream = new Readable()
766
- stream._read = () => {}
766
+ stream._read = () => { }
767
767
  reply.header('content-type', 'text/html; charset=utf-8')
768
768
  reply.header('transfer-encoding', 'chunked')
769
769
  stream.push('<h1>HTML</h1>')
@@ -10,7 +10,6 @@ import fastify, {
10
10
  InjectOptions, FastifyBaseLogger,
11
11
  RawRequestDefaultExpression,
12
12
  RouteGenericInterface,
13
- ValidationResult,
14
13
  FastifyErrorCodes,
15
14
  FastifyError
16
15
  } from '../../fastify'
@@ -18,7 +17,7 @@ import { ErrorObject as AjvErrorObject } from 'ajv'
18
17
  import * as http from 'http'
19
18
  import * as https from 'https'
20
19
  import * as http2 from 'http2'
21
- import { expectType, expectError, expectAssignable } from 'tsd'
20
+ import { expectType, expectError, expectAssignable, expectNotAssignable } from 'tsd'
22
21
  import { FastifyLoggerInstance } from '../../types/logger'
23
22
  import { Socket } from 'net'
24
23
 
@@ -244,7 +243,13 @@ const ajvErrorObject: AjvErrorObject = {
244
243
  params: {},
245
244
  message: ''
246
245
  }
247
- expectAssignable<ValidationResult>(ajvErrorObject)
246
+ expectNotAssignable<AjvErrorObject>({
247
+ keyword: '',
248
+ instancePath: '',
249
+ schemaPath: '',
250
+ params: '',
251
+ message: ''
252
+ })
248
253
 
249
254
  expectAssignable<FastifyError['validation']>([ajvErrorObject])
250
255
  expectAssignable<FastifyError['validationContext']>('body')
@@ -392,3 +392,16 @@ server.addHook('preClose', function (done) {
392
392
  server.addHook('preClose', async function () {
393
393
  expectType<FastifyInstance>(this)
394
394
  })
395
+
396
+ expectError(server.addHook('onClose', async function (instance, done) {}))
397
+ expectError(server.addHook('onError', async function (request, reply, error, done) {}))
398
+ expectError(server.addHook('onReady', async function (done) {}))
399
+ expectError(server.addHook('onRequest', async function (request, reply, done) {}))
400
+ expectError(server.addHook('onRequestAbort', async function (request, done) {}))
401
+ expectError(server.addHook('onResponse', async function (request, reply, done) {}))
402
+ expectError(server.addHook('onSend', async function (request, reply, payload, done) {}))
403
+ expectError(server.addHook('onTimeout', async function (request, reply, done) {}))
404
+ expectError(server.addHook('preClose', async function (done) {}))
405
+ expectError(server.addHook('preHandler', async function (request, reply, done) {}))
406
+ expectError(server.addHook('preSerialization', async function (request, reply, payload, done) {}))
407
+ expectError(server.addHook('preValidation', async function (request, reply, done) {}))
@@ -6,7 +6,8 @@ import fastify, {
6
6
  FastifyInstance,
7
7
  RawReplyDefaultExpression,
8
8
  RawRequestDefaultExpression,
9
- RawServerDefault
9
+ RawServerDefault,
10
+ RouteGenericInterface
10
11
  } from '../../fastify'
11
12
  import { HookHandlerDoneFunction } from '../../types/hooks'
12
13
  import { FastifyReply } from '../../types/reply'
@@ -257,9 +258,13 @@ expectNotDeprecated(server.listen({ port: 3000, host: '::/0', ipv6Only: true },
257
258
 
258
259
  expectAssignable<void>(server.routing({} as RawRequestDefaultExpression, {} as RawReplyDefaultExpression))
259
260
 
260
- expectType<FastifyInstance>(fastify().get('/', {
261
+ expectType<FastifyInstance>(fastify().get<RouteGenericInterface, { contextKey: string }>('/', {
261
262
  handler: () => {},
262
263
  errorHandler: (error, request, reply) => {
264
+ expectAssignable<FastifyError>(error)
265
+ expectAssignable<FastifyRequest>(request)
266
+ expectAssignable<{ contextKey: string }>(request.routeConfig)
267
+ expectAssignable<FastifyReply>(reply)
263
268
  expectAssignable<void>(server.errorHandler(error, request, reply))
264
269
  }
265
270
  }))
@@ -53,6 +53,10 @@ interface ReplyPayload {
53
53
  };
54
54
  }
55
55
 
56
+ interface ReplyArrayPayload {
57
+ Reply: string[]
58
+ }
59
+
56
60
  interface ReplyUnion {
57
61
  Reply: {
58
62
  success: boolean;
@@ -70,6 +74,14 @@ interface ReplyHttpCodes {
70
74
  }
71
75
  }
72
76
 
77
+ interface InvalidReplyHttpCodes {
78
+ Reply: {
79
+ '1xx': number,
80
+ 200: string,
81
+ 999: boolean,
82
+ }
83
+ }
84
+
73
85
  const typedHandler: RouteHandler<ReplyPayload> = async (request, reply) => {
74
86
  expectType<((payload?: ReplyPayload['Reply']) => FastifyReply<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, ReplyPayload>)>(reply.send)
75
87
  expectType<((payload?: ReplyPayload['Reply']) => FastifyReply<RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>, ReplyPayload>)>(reply.code(100).send)
@@ -137,3 +149,16 @@ expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-4', a
137
149
  expectError(server.get<ReplyHttpCodes>('/get-generic-http-codes-send-error-5', async function handler (request, reply) {
138
150
  reply.code(401).send({ foo: 123 })
139
151
  }))
152
+ server.get<ReplyArrayPayload>('/get-generic-array-send', async function handler (request, reply) {
153
+ reply.code(200).send([''])
154
+ })
155
+ expectError(server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
156
+ reply.code(200).send('')
157
+ }))
158
+ server.get<InvalidReplyHttpCodes>('get-invalid-http-codes-reply-error', async function handler (request, reply) {
159
+ reply.code(200).send({
160
+ '1xx': 0,
161
+ 200: '',
162
+ 999: false
163
+ })
164
+ })
@@ -83,7 +83,7 @@ const getHandler: RouteHandler = function (request, _reply) {
83
83
  request.headers = {}
84
84
 
85
85
  expectType<RequestQuerystringDefault>(request.query)
86
- expectType<any>(request.id)
86
+ expectType<string>(request.id)
87
87
  expectType<FastifyLoggerInstance>(request.log)
88
88
  expectType<RawRequestDefaultExpression['socket']>(request.socket)
89
89
  expectType<Error & { validation: any; validationContext: string } | undefined>(request.validationError)
@@ -3,7 +3,8 @@ import fastify, {
3
3
  HookHandlerDoneFunction,
4
4
  FastifyRequest,
5
5
  FastifyReply,
6
- FastifyInstance
6
+ FastifyInstance,
7
+ FastifyError
7
8
  } from '../../fastify'
8
9
  import { expectAssignable, expectError, expectType } from 'tsd'
9
10
  import { IncomingHttpHeaders } from 'http'
@@ -79,6 +80,14 @@ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
79
80
  y: Type.Number(),
80
81
  z: Type.Number()
81
82
  })
83
+ },
84
+ errorHandler: (error, request, reply) => {
85
+ expectType<FastifyError>(error)
86
+ expectAssignable<FastifyRequest>(request)
87
+ expectType<number>(request.body.x)
88
+ expectType<number>(request.body.y)
89
+ expectType<number>(request.body.z)
90
+ expectAssignable<FastifyReply>(reply)
82
91
  }
83
92
  },
84
93
  (req) => {
@@ -108,6 +117,14 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
108
117
  z: { type: 'boolean' }
109
118
  }
110
119
  } as const
120
+ },
121
+ errorHandler: (error, request, reply) => {
122
+ expectType<FastifyError>(error)
123
+ expectAssignable<FastifyRequest>(request)
124
+ expectType<number | undefined>(request.body.x)
125
+ expectType<string | undefined>(request.body.y)
126
+ expectType<boolean | undefined>(request.body.z)
127
+ expectAssignable<FastifyReply>(reply)
111
128
  }
112
129
  },
113
130
  (req) => {
@@ -135,6 +152,14 @@ expectAssignable(server.withTypeProvider<TypeBoxProvider>().withTypeProvider<Jso
135
152
  z: { type: 'boolean' }
136
153
  }
137
154
  } as const
155
+ },
156
+ errorHandler: (error, request, reply) => {
157
+ expectType<FastifyError>(error)
158
+ expectAssignable<FastifyRequest>(request)
159
+ expectType<number | undefined>(request.body.x)
160
+ expectType<string | undefined>(request.body.y)
161
+ expectType<boolean | undefined>(request.body.z)
162
+ expectAssignable<FastifyReply>(reply)
138
163
  }
139
164
  },
140
165
  (req) => {
@@ -278,6 +303,62 @@ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
278
303
  }
279
304
  ))
280
305
 
306
+ // -------------------------------------------------------------------
307
+ // Request headers
308
+ // -------------------------------------------------------------------
309
+
310
+ // JsonSchemaToTsProvider
311
+ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
312
+ '/',
313
+ {
314
+ schema: {
315
+ headers: {
316
+ type: 'object',
317
+ properties: {
318
+ lowercase: { type: 'string' },
319
+ UPPERCASE: { type: 'number' },
320
+ camelCase: { type: 'boolean' },
321
+ 'KEBAB-case': { type: 'boolean' },
322
+ PRESERVE_OPTIONAL: { type: 'number' }
323
+ },
324
+ required: ['lowercase', 'UPPERCASE', 'camelCase', 'KEBAB-case']
325
+ } as const
326
+ }
327
+ },
328
+ (req) => {
329
+ expectType<string>(req.headers.lowercase)
330
+ expectType<string | string[] | undefined>(req.headers.UPPERCASE)
331
+ expectType<number>(req.headers.uppercase)
332
+ expectType<boolean>(req.headers.camelcase)
333
+ expectType<boolean>(req.headers['kebab-case'])
334
+ expectType<number | undefined>(req.headers.preserve_optional)
335
+ }
336
+ ))
337
+
338
+ // TypeBoxProvider
339
+ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
340
+ '/',
341
+ {
342
+ schema: {
343
+ headers: Type.Object({
344
+ lowercase: Type.String(),
345
+ UPPERCASE: Type.Number(),
346
+ camelCase: Type.Boolean(),
347
+ 'KEBAB-case': Type.Boolean(),
348
+ PRESERVE_OPTIONAL: Type.Optional(Type.Number())
349
+ })
350
+ }
351
+ },
352
+ (req) => {
353
+ expectType<string>(req.headers.lowercase)
354
+ expectType<string | string[] | undefined>(req.headers.UPPERCASE)
355
+ expectType<number>(req.headers.uppercase)
356
+ expectType<boolean>(req.headers.camelcase)
357
+ expectType<boolean>(req.headers['kebab-case'])
358
+ expectType<number | undefined>(req.headers.preserve_optional)
359
+ }
360
+ ))
361
+
281
362
  // -------------------------------------------------------------------
282
363
  // TypeBox Reply Type
283
364
  // -------------------------------------------------------------------
@@ -27,3 +27,25 @@ test('should reject immediately when reply[kReplyHijacked] is true', t => {
27
27
  const thenable = Promise.reject(new Error('Reply sent already'))
28
28
  wrapThenable(thenable, reply)
29
29
  })
30
+
31
+ test('should not send the payload if the raw socket was destroyed but not aborted', async t => {
32
+ const reply = {
33
+ sent: false,
34
+ raw: {
35
+ headersSent: false
36
+ },
37
+ request: {
38
+ raw: {
39
+ aborted: false,
40
+ destroyed: true
41
+ }
42
+ },
43
+ send () {
44
+ t.fail('should not send')
45
+ }
46
+ }
47
+ const thenable = Promise.resolve()
48
+ wrapThenable(thenable, reply)
49
+
50
+ await thenable
51
+ })
package/types/hooks.d.ts CHANGED
@@ -190,7 +190,7 @@ interface DoneFuncWithErrOrRes {
190
190
  * Note: the hook is NOT called if the payload is a string, a Buffer, a stream or null.
191
191
  */
192
192
  export interface preSerializationHookHandler<
193
- PreSerializationPayload,
193
+ PreSerializationPayload = unknown,
194
194
  RawServer extends RawServerBase = RawServerDefault,
195
195
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
196
196
  RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
@@ -210,7 +210,7 @@ export interface preSerializationHookHandler<
210
210
  }
211
211
 
212
212
  export interface preSerializationAsyncHookHandler<
213
- PreSerializationPayload,
213
+ PreSerializationPayload = unknown,
214
214
  RawServer extends RawServerBase = RawServerDefault,
215
215
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
216
216
  RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
@@ -233,7 +233,7 @@ export interface preSerializationAsyncHookHandler<
233
233
  * Note: If you change the payload, you may only change it to a string, a Buffer, a stream, or null.
234
234
  */
235
235
  export interface onSendHookHandler<
236
- OnSendPayload,
236
+ OnSendPayload = unknown,
237
237
  RawServer extends RawServerBase = RawServerDefault,
238
238
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
239
239
  RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
@@ -253,7 +253,7 @@ export interface onSendHookHandler<
253
253
  }
254
254
 
255
255
  export interface onSendAsyncHookHandler<
256
- OnSendPayload,
256
+ OnSendPayload = unknown,
257
257
  RawServer extends RawServerBase = RawServerDefault,
258
258
  RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
259
259
  RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>,
@@ -432,6 +432,66 @@ export interface onRequestAbortAsyncHookHandler<
432
432
  ): Promise<unknown>;
433
433
  }
434
434
 
435
+ export type LifecycleHook = 'onRequest'
436
+ | 'preParsing'
437
+ | 'preValidation'
438
+ | 'preHandler'
439
+ | 'preSerialization'
440
+ | 'onSend'
441
+ | 'onResponse'
442
+ | 'onRequest'
443
+ | 'onError'
444
+ | 'onTimeout'
445
+ | 'onRequestAbort'
446
+
447
+ export type LifecycleHookLookup<K extends LifecycleHook> = K extends 'onRequest'
448
+ ? onRequestHookHandler
449
+ : K extends 'preParsing'
450
+ ? preParsingHookHandler
451
+ : K extends 'preValidation'
452
+ ? preValidationHookHandler
453
+ : K extends 'preHandler'
454
+ ? preHandlerHookHandler
455
+ : K extends 'preSerialization'
456
+ ? preSerializationHookHandler
457
+ : K extends 'onSend'
458
+ ? onSendHookHandler
459
+ : K extends 'onResponse'
460
+ ? onResponseHookHandler
461
+ : K extends 'onRequest'
462
+ ? onRequestHookHandler
463
+ : K extends 'onError'
464
+ ? onErrorHookHandler
465
+ : K extends 'onTimeout'
466
+ ? onTimeoutHookHandler
467
+ : K extends 'onRequestAbort'
468
+ ? onRequestAbortHookHandler
469
+ : never
470
+
471
+ export type LifecycleHookAsyncLookup<K extends LifecycleHook> = K extends 'onRequest'
472
+ ? onRequestAsyncHookHandler
473
+ : K extends 'preParsing'
474
+ ? preParsingAsyncHookHandler
475
+ : K extends 'preValidation'
476
+ ? preValidationAsyncHookHandler
477
+ : K extends 'preHandler'
478
+ ? preHandlerAsyncHookHandler
479
+ : K extends 'preSerialization'
480
+ ? preSerializationAsyncHookHandler
481
+ : K extends 'onSend'
482
+ ? onSendAsyncHookHandler
483
+ : K extends 'onResponse'
484
+ ? onResponseAsyncHookHandler
485
+ : K extends 'onRequest'
486
+ ? onRequestAsyncHookHandler
487
+ : K extends 'onError'
488
+ ? onErrorAsyncHookHandler
489
+ : K extends 'onTimeout'
490
+ ? onTimeoutAsyncHookHandler
491
+ : K extends 'onRequestAbort'
492
+ ? onRequestAbortAsyncHookHandler
493
+ : never
494
+
435
495
  // Application Hooks
436
496
 
437
497
  /**
@@ -555,3 +615,43 @@ export interface preCloseAsyncHookHandler<
555
615
  this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>,
556
616
  ): Promise<unknown>;
557
617
  }
618
+
619
+ export type ApplicationHook = 'onRoute'
620
+ | 'onRegister'
621
+ | 'onReady'
622
+ | 'onClose'
623
+ | 'preClose'
624
+
625
+ export type ApplicationHookLookup<K extends ApplicationHook> = K extends 'onRegister'
626
+ ? onRegisterHookHandler
627
+ : K extends 'onReady'
628
+ ? onReadyHookHandler
629
+ : K extends 'onClose'
630
+ ? onCloseHookHandler
631
+ : K extends 'preClose'
632
+ ? preCloseHookHandler
633
+ : K extends 'onRoute'
634
+ ? onRouteHookHandler
635
+ : never
636
+
637
+ export type ApplicationHookAsyncLookup<K extends ApplicationHook> = K extends 'onRegister'
638
+ ? onRegisterHookHandler
639
+ : K extends 'onReady'
640
+ ? onReadyAsyncHookHandler
641
+ : K extends 'onClose'
642
+ ? onCloseAsyncHookHandler
643
+ : K extends 'preClose'
644
+ ? preCloseAsyncHookHandler
645
+ : never
646
+
647
+ export type HookLookup <K extends ApplicationHook | LifecycleHook> = K extends ApplicationHook
648
+ ? ApplicationHookLookup<K>
649
+ : K extends LifecycleHook
650
+ ? LifecycleHookLookup<K>
651
+ : never
652
+
653
+ export type HookAsyncLookup <K extends ApplicationHook | LifecycleHook> = K extends ApplicationHook
654
+ ? ApplicationHookAsyncLookup<K>
655
+ : K extends LifecycleHook
656
+ ? LifecycleHookAsyncLookup<K>
657
+ : never