fastify 4.0.3 → 4.2.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 (47) hide show
  1. package/.eslintrc +1 -0
  2. package/README.md +13 -14
  3. package/docs/Guides/Database.md +7 -7
  4. package/docs/Guides/Delay-Accepting-Requests.md +1 -1
  5. package/docs/Guides/Ecosystem.md +29 -16
  6. package/docs/Guides/Migration-Guide-V4.md +86 -1
  7. package/docs/Guides/Plugins-Guide.md +5 -0
  8. package/docs/Guides/Serverless.md +23 -10
  9. package/docs/Reference/Hooks.md +52 -0
  10. package/docs/Reference/Plugins.md +1 -1
  11. package/docs/Reference/Server.md +1 -1
  12. package/docs/Reference/Type-Providers.md +3 -17
  13. package/docs/Reference/TypeScript.md +65 -28
  14. package/docs/Reference/Validation-and-Serialization.md +11 -0
  15. package/docs/index.md +1 -1
  16. package/fastify.d.ts +3 -3
  17. package/fastify.js +19 -19
  18. package/integration/server.js +27 -0
  19. package/integration/test.sh +23 -0
  20. package/lib/context.js +5 -2
  21. package/lib/error-serializer.js +24 -27
  22. package/lib/handleRequest.js +1 -1
  23. package/lib/reply.js +22 -21
  24. package/lib/route.js +39 -29
  25. package/lib/symbols.js +2 -1
  26. package/lib/validation.js +2 -0
  27. package/package.json +10 -10
  28. package/test/404s.test.js +2 -2
  29. package/test/build/error-serializer.test.js +9 -2
  30. package/test/hooks.test.js +21 -0
  31. package/test/internals/reply.test.js +12 -0
  32. package/test/pretty-print.test.js +3 -3
  33. package/test/reply-error.test.js +1 -1
  34. package/test/schema-feature.test.js +2 -2
  35. package/test/schema-validation.test.js +71 -0
  36. package/test/stream.test.js +1 -1
  37. package/test/types/fastify.test-d.ts +24 -2
  38. package/test/types/instance.test-d.ts +5 -2
  39. package/test/types/register.test-d.ts +77 -2
  40. package/test/types/request.test-d.ts +8 -4
  41. package/test/types/type-provider.test-d.ts +11 -2
  42. package/test/validation-error-handling.test.js +38 -1
  43. package/types/instance.d.ts +59 -91
  44. package/types/register.d.ts +9 -7
  45. package/types/route.d.ts +10 -12
  46. package/types/schema.d.ts +5 -2
  47. package/types/type-provider.d.ts +12 -5
@@ -143,7 +143,7 @@ route-level `request` object.
143
143
  curl localhost:8080/auth?username=admin&password=Password123!
144
144
  ```
145
145
  And it should return back `logged in!`
146
- 6. But wait theres more! The generic interfaces are also available inside route
146
+ 6. But wait there's more! The generic interfaces are also available inside route
147
147
  level hook methods. Modify the previous route by adding a `preValidation`
148
148
  hook:
149
149
  ```typescript
@@ -199,17 +199,18 @@ Below is how to setup schema validation using vanilla `typebox` and
199
199
  #### typebox
200
200
 
201
201
  A useful library for building types and a schema at once is
202
- [typebox](https://www.npmjs.com/package/@sinclair/typebox). With typebox you
203
- define your schema within your code and use them directly as types or schemas as
204
- you need them.
202
+ [typebox](https://www.npmjs.com/package/@sinclair/typebox) along with
203
+ [fastify-type-provider-typebox](https://github.com/fastify/fastify-type-provider-typebox).
204
+ With typebox you define your schema within your code and use them
205
+ directly as types or schemas as you need them.
205
206
 
206
207
  When you want to use it for validation of some payload in a fastify route you
207
208
  can do it as follows:
208
209
 
209
- 1. Install `typebox` in your project.
210
+ 1. Install `typebox` and `fastify-type-provider-typebox` in your project.
210
211
 
211
212
  ```bash
212
- npm i @sinclair/typebox
213
+ npm i @sinclair/typebox @fastify/type-provider-typebox
213
214
  ```
214
215
 
215
216
  2. Define the schema you need with `Type` and create the respective type with
@@ -218,40 +219,52 @@ can do it as follows:
218
219
  ```typescript
219
220
  import { Static, Type } from '@sinclair/typebox'
220
221
 
221
- const User = Type.Object({
222
+ export const User = Type.Object({
222
223
  name: Type.String(),
223
- mail: Type.Optional(Type.String({ format: "email" })),
224
- });
225
- type UserType = Static<typeof User>;
224
+ mail: Type.Optional(Type.String({ format: 'email' })),
225
+ })
226
+
227
+ export type UserType = Static<typeof User>
226
228
  ```
227
229
 
228
230
  3. Use the defined type and schema during the definition of your route
229
231
 
230
232
  ```typescript
231
- const app = fastify();
233
+ import Fastify from 'fastify'
234
+ import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
235
+ // ...
236
+
237
+ const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()
232
238
 
233
- app.post<{ Body: UserType; Reply: UserType }>(
234
- "/",
239
+ app.post<{ Body: UserType, Reply: UserType }>(
240
+ '/',
235
241
  {
236
242
  schema: {
237
243
  body: User,
238
244
  response: {
239
- 200: User,
245
+ 200: User
240
246
  },
241
247
  },
242
248
  },
243
249
  (request, reply) => {
244
- const { body: user } = request;
245
- /* user has type
246
- * const user: StaticProperties<{
247
- * name: TString;
248
- * mail: TOptional<TString>;
249
- * }>
250
- */
251
- //...
252
- reply.status(200).send(user);
250
+ // The `name` and `mail` types are automatically inferred
251
+ const { name, mail } = request.body;
252
+ reply.status(200).send({ name, mail });
253
+ }
254
+ )
255
+ ```
256
+
257
+ **Note** For Ajv version 7 and above is required to use the `ajvTypeBoxPlugin`:
258
+
259
+ ```typescript
260
+ import Fastify from 'fastify'
261
+ import { ajvTypeBoxPlugin, TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
262
+
263
+ const fastify = Fastify({
264
+ ajv: {
265
+ plugins: [ajvTypeBoxPlugin]
253
266
  }
254
- );
267
+ }).withTypeProvider<TypeBoxTypeProvider>()
255
268
  ```
256
269
 
257
270
  #### Schemas in JSON Files
@@ -390,7 +403,7 @@ definitions.
390
403
  #### json-schema-to-ts
391
404
 
392
405
  If you do not want to generate types from your schemas, but want to use them
393
- diretly from your code, you can use the package
406
+ directly from your code, you can use the package
394
407
  [json-schema-to-ts](https://www.npmjs.com/package/json-schema-to-ts).
395
408
 
396
409
  You can install it as dev-dependency.
@@ -648,6 +661,30 @@ However, there are a couple of suggestions to help improve this experience:
648
661
  [npm-check](https://www.npmjs.com/package/npm-check) to verify plugin
649
662
  dependencies are being used somewhere in your project.
650
663
 
664
+ Note that using `require` will not load the type definitions properly and may
665
+ cause type errors.
666
+ TypeScript can only identify the types that are directly imported into code,
667
+ which means that you can use require inline with import on top. For example:
668
+
669
+ ```typescript
670
+ import 'plugin' // here will trigger the type augmentation.
671
+
672
+ fastify.register(require('plugin'))
673
+ ```
674
+
675
+ ```typescript
676
+ import plugin from 'plugin' // here will trigger the type augmentation.
677
+
678
+ fastify.register(plugin)
679
+ ```
680
+
681
+ Or even explicit config on tsconfig
682
+ ```jsonc
683
+ {
684
+ "types": ["plugin"] // we force TypeScript to import the types
685
+ }
686
+ ```
687
+
651
688
  ## Code Completion In Vanilla JavaScript
652
689
 
653
690
  Vanilla JavaScript can use the published types to provide code completion (e.g.
@@ -1359,17 +1396,17 @@ A method for checking the existence of a type parser of a certain content type
1359
1396
 
1360
1397
  ##### fastify.FastifyError
1361
1398
 
1362
- [src](https://github.com/fastify/fastify/blob/main/types/error.d.ts#L17)
1399
+ [src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L179)
1363
1400
 
1364
1401
  FastifyError is a custom error object that includes status code and validation
1365
1402
  results.
1366
1403
 
1367
1404
  It extends the Node.js `Error` type, and adds two additional, optional
1368
- properties: `statusCode: number` and `validation: ValiationResult[]`.
1405
+ properties: `statusCode: number` and `validation: ValidationResult[]`.
1369
1406
 
1370
1407
  ##### fastify.ValidationResult
1371
1408
 
1372
- [src](https://github.com/fastify/fastify/blob/main/types/error.d.ts#L4)
1409
+ [src](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L184)
1373
1410
 
1374
1411
  The route validation internally relies upon Ajv, which is a high-performance
1375
1412
  JSON schema validator.
@@ -484,6 +484,17 @@ fastify.post('/the/url', {
484
484
  }, handler)
485
485
  ```
486
486
 
487
+ ##### .statusCode property
488
+
489
+ All validation errors will be added a `.statusCode` property set to `400`. This guarantees
490
+ that the default error handler will set the status code of the response to `400`.
491
+
492
+ ```js
493
+ fastify.setErrorHandler(function (error, request, reply) {
494
+ request.log.error(error, `This error has status code ${error.statusCode}`)
495
+ reply.status(error.statusCode).send(error)
496
+ })
497
+ ```
487
498
 
488
499
  ##### Validation messages with other validation libraries
489
500
 
package/docs/index.md CHANGED
@@ -16,7 +16,7 @@ Complete newcomers to Fastify should first read our [Getting
16
16
  Started](./Guides/Getting-Started.md) guide.
17
17
 
18
18
  Developers experienced with Fastify should consult the [reference
19
- documentation](./Reference/index.md) directly to find the topic they are seeking
19
+ documentation](./Reference/Index.md) directly to find the topic they are seeking
20
20
  more information about.
21
21
 
22
22
  ## Additional Documentation
package/fastify.d.ts CHANGED
@@ -146,7 +146,7 @@ export type FastifyServerOptions<
146
146
  },
147
147
  schemaController?: {
148
148
  bucket?: (parentSchemas?: unknown) => {
149
- addSchema(schema: unknown): FastifyInstance;
149
+ add(schema: unknown): FastifyInstance;
150
150
  getSchema(schemaId: string): unknown;
151
151
  getSchemas(): Record<string, unknown>;
152
152
  };
@@ -183,10 +183,10 @@ declare module '@fastify/error' {
183
183
 
184
184
  export interface ValidationResult {
185
185
  keyword: string;
186
- dataPath: string;
186
+ instancePath: string;
187
187
  schemaPath: string;
188
188
  params: Record<string, string | string[]>;
189
- message: string;
189
+ message?: string;
190
190
  }
191
191
 
192
192
  /* Export all additional types */
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.0.3'
3
+ const VERSION = '4.2.1'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -237,35 +237,35 @@ function fastify (options) {
237
237
  getDefaultRoute: router.getDefaultRoute.bind(router),
238
238
  setDefaultRoute: router.setDefaultRoute.bind(router),
239
239
  // routes shorthand methods
240
- delete: function _delete (url, opts, handler) {
241
- return router.prepareRoute.call(this, 'DELETE', url, opts, handler)
240
+ delete: function _delete (url, options, handler) {
241
+ return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })
242
242
  },
243
- get: function _get (url, opts, handler) {
244
- return router.prepareRoute.call(this, 'GET', url, opts, handler)
243
+ get: function _get (url, options, handler) {
244
+ return router.prepareRoute.call(this, { method: 'GET', url, options, handler })
245
245
  },
246
- head: function _head (url, opts, handler) {
247
- return router.prepareRoute.call(this, 'HEAD', url, opts, handler)
246
+ head: function _head (url, options, handler) {
247
+ return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })
248
248
  },
249
- patch: function _patch (url, opts, handler) {
250
- return router.prepareRoute.call(this, 'PATCH', url, opts, handler)
249
+ patch: function _patch (url, options, handler) {
250
+ return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })
251
251
  },
252
- post: function _post (url, opts, handler) {
253
- return router.prepareRoute.call(this, 'POST', url, opts, handler)
252
+ post: function _post (url, options, handler) {
253
+ return router.prepareRoute.call(this, { method: 'POST', url, options, handler })
254
254
  },
255
- put: function _put (url, opts, handler) {
256
- return router.prepareRoute.call(this, 'PUT', url, opts, handler)
255
+ put: function _put (url, options, handler) {
256
+ return router.prepareRoute.call(this, { method: 'PUT', url, options, handler })
257
257
  },
258
- options: function _options (url, opts, handler) {
259
- return router.prepareRoute.call(this, 'OPTIONS', url, opts, handler)
258
+ options: function _options (url, options, handler) {
259
+ return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
260
260
  },
261
- all: function _all (url, opts, handler) {
262
- return router.prepareRoute.call(this, supportedMethods, url, opts, handler)
261
+ all: function _all (url, options, handler) {
262
+ return router.prepareRoute.call(this, { method: supportedMethods, url, options, handler })
263
263
  },
264
264
  // extended route
265
- route: function _route (opts) {
265
+ route: function _route (options) {
266
266
  // we need the fastify object that we are producing so we apply a lazy loading of the function,
267
267
  // otherwise we should bind it after the declaration
268
- return router.route.call(this, opts)
268
+ return router.route.call(this, { options })
269
269
  },
270
270
  // expose logger instance
271
271
  log: logger,
@@ -0,0 +1,27 @@
1
+ const Fastify = require('../fastify')
2
+
3
+ const fastify = Fastify()
4
+
5
+ fastify.listen({
6
+ host: '::',
7
+ port: 3000
8
+ })
9
+
10
+ fastify.get('/', async function (request, reply) {
11
+ reply.code(200).send({ data: 'home page' })
12
+ })
13
+
14
+ fastify.post('/post/:id', async function (request, reply) {
15
+ const { id } = request.params
16
+ reply.code(201).send({ data: `${id}` })
17
+ })
18
+
19
+ fastify.put('/put/:id', async function (request, reply) {
20
+ const { id } = request.params
21
+ reply.code(200).send({ data: `${id}` })
22
+ })
23
+
24
+ fastify.delete('/delete/:id', async function (request, reply) {
25
+ const { id } = request.params
26
+ reply.code(204).send({ data: `${id}` })
27
+ })
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/bash
2
+
3
+ set -e
4
+
5
+ NUMBER=$RANDOM
6
+ curl -i -X GET -H 'Content-Type: application/json' localhost:3000/ > GET
7
+ if [[ ! $(cat GET | head -1| cut -f2 -d" ") == "200" || ! $(cat GET | tail -1| cut -f4 -d"\"") == "home page" ]] ; then
8
+ exit 1
9
+ fi;
10
+ curl -i -X POST -H 'Content-Type: application/json' localhost:3000/post/$NUMBER --data {} > POST
11
+ if [[ ! $(cat POST | head -1| cut -f2 -d" ") == "201" || ! $(cat POST | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
12
+ exit 1
13
+ fi;
14
+ curl -i -X PUT -H 'Content-Type: application/json' localhost:3000/put/$NUMBER --data {} > PUT
15
+ if [[ ! $(cat PUT | head -1| cut -f2 -d" ") == "200" || ! $(cat PUT | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
16
+ exit 1
17
+ fi;
18
+ curl -i -X DELETE -H 'Content-Type: application/json' localhost:3000/delete/$NUMBER --data {} > DELETE
19
+ if [[ ! $(cat DELETE | head -1| cut -f2 -d" ") == "204" ]]; then
20
+ exit 1
21
+ fi;
22
+
23
+ rm -f GET POST PUT DELETE
package/lib/context.js CHANGED
@@ -9,7 +9,8 @@ const {
9
9
  kRequest,
10
10
  kBodyLimit,
11
11
  kLogLevel,
12
- kContentTypeParser
12
+ kContentTypeParser,
13
+ kRouteByFastify
13
14
  } = require('./symbols.js')
14
15
 
15
16
  // Objects that holds the context of every request
@@ -25,7 +26,8 @@ function Context ({
25
26
  attachValidation,
26
27
  replySerializer,
27
28
  schemaErrorFormatter,
28
- server
29
+ server,
30
+ isFastify
29
31
  }) {
30
32
  this.schema = schema
31
33
  this.handler = handler
@@ -50,6 +52,7 @@ function Context ({
50
52
  this.attachValidation = attachValidation
51
53
  this[kReplySerializerDefault] = replySerializer
52
54
  this.schemaErrorFormatter = schemaErrorFormatter || server[kSchemaErrorFormatter] || defaultSchemaErrorFormatter
55
+ this[kRouteByFastify] = isFastify
53
56
 
54
57
  this.server = server
55
58
  }
@@ -39,7 +39,7 @@ class Serializer {
39
39
  } else {
40
40
  /* eslint no-undef: "off" */
41
41
  const integer = this.parseInteger(i)
42
- if (Number.isNaN(integer)) {
42
+ if (Number.isNaN(integer) || !Number.isFinite(integer)) {
43
43
  throw new Error(`The value "${i}" cannot be converted to an integer.`)
44
44
  } else {
45
45
  return '' + integer
@@ -55,6 +55,8 @@ class Serializer {
55
55
  const num = Number(i)
56
56
  if (Number.isNaN(num)) {
57
57
  throw new Error(`The value "${i}" cannot be converted to a number.`)
58
+ } else if (!Number.isFinite(num)) {
59
+ return null
58
60
  } else {
59
61
  return '' + num
60
62
  }
@@ -72,44 +74,44 @@ class Serializer {
72
74
  return bool === null ? 'null' : this.asBoolean(bool)
73
75
  }
74
76
 
75
- asDatetime (date, skipQuotes) {
76
- const quotes = skipQuotes === true ? '' : '"'
77
+ asDateTime (date) {
78
+ if (date === null) return '""'
77
79
  if (date instanceof Date) {
78
- return quotes + date.toISOString() + quotes
80
+ return '"' + date.toISOString() + '"'
79
81
  }
80
- return this.asString(date, skipQuotes)
82
+ throw new Error(`The value "${date}" cannot be converted to a date-time.`)
81
83
  }
82
84
 
83
- asDatetimeNullable (date, skipQuotes) {
84
- return date === null ? 'null' : this.asDatetime(date, skipQuotes)
85
+ asDateTimeNullable (date) {
86
+ return date === null ? 'null' : this.asDateTime(date)
85
87
  }
86
88
 
87
- asDate (date, skipQuotes) {
88
- const quotes = skipQuotes === true ? '' : '"'
89
+ asDate (date) {
90
+ if (date === null) return '""'
89
91
  if (date instanceof Date) {
90
- return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes
92
+ return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + '"'
91
93
  }
92
- return this.asString(date, skipQuotes)
94
+ throw new Error(`The value "${date}" cannot be converted to a date.`)
93
95
  }
94
96
 
95
- asDateNullable (date, skipQuotes) {
96
- return date === null ? 'null' : this.asDate(date, skipQuotes)
97
+ asDateNullable (date) {
98
+ return date === null ? 'null' : this.asDate(date)
97
99
  }
98
100
 
99
- asTime (date, skipQuotes) {
100
- const quotes = skipQuotes === true ? '' : '"'
101
+ asTime (date) {
102
+ if (date === null) return '""'
101
103
  if (date instanceof Date) {
102
- return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes
104
+ return '"' + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + '"'
103
105
  }
104
- return this.asString(date, skipQuotes)
106
+ throw new Error(`The value "${date}" cannot be converted to a time.`)
105
107
  }
106
108
 
107
- asTimeNullable (date, skipQuotes) {
108
- return date === null ? 'null' : this.asTime(date, skipQuotes)
109
+ asTimeNullable (date) {
110
+ return date === null ? 'null' : this.asTime(date)
109
111
  }
110
112
 
111
- asString (str, skipQuotes) {
112
- const quotes = skipQuotes === true ? '' : '"'
113
+ asString (str) {
114
+ const quotes = '"'
113
115
  if (str instanceof Date) {
114
116
  return quotes + str.toISOString() + quotes
115
117
  } else if (str === null) {
@@ -119,11 +121,6 @@ class Serializer {
119
121
  } else if (typeof str !== 'string') {
120
122
  str = str.toString()
121
123
  }
122
- // If we skipQuotes it means that we are using it as test
123
- // no need to test the string length for the render
124
- if (skipQuotes) {
125
- return str
126
- }
127
124
 
128
125
  if (str.length < 42) {
129
126
  return this.asStringSmall(str)
@@ -185,7 +182,7 @@ class Serializer {
185
182
  }
186
183
 
187
184
  function anonymous0 (input) {
188
- // main
185
+ // #
189
186
 
190
187
  var obj = (input && typeof input.toJSON === 'function')
191
188
  ? input.toJSON()
@@ -90,7 +90,7 @@ function preValidationCallback (err, request, reply) {
90
90
  const result = validateSchema(reply.context, request)
91
91
  if (result) {
92
92
  if (reply.context.attachValidation === false) {
93
- reply.code(400).send(result)
93
+ reply.send(result)
94
94
  return
95
95
  }
96
96
 
package/lib/reply.js CHANGED
@@ -203,10 +203,8 @@ Reply.prototype.getHeaders = function () {
203
203
 
204
204
  Reply.prototype.hasHeader = function (key) {
205
205
  key = key.toLowerCase()
206
- if (this[kReplyHeaders][key] !== undefined) {
207
- return true
208
- }
209
- return this.raw.hasHeader(key)
206
+
207
+ return this[kReplyHeaders][key] !== undefined || this.raw.hasHeader(key)
210
208
  }
211
209
 
212
210
  Reply.prototype.removeHeader = function (key) {
@@ -216,25 +214,24 @@ Reply.prototype.removeHeader = function (key) {
216
214
  return this
217
215
  }
218
216
 
219
- Reply.prototype.header = function (key, value) {
220
- const _key = key.toLowerCase()
221
-
222
- // default the value to ''
223
- value = value === undefined ? '' : value
217
+ Reply.prototype.header = function (key, value = '') {
218
+ key = key.toLowerCase()
224
219
 
225
- if (this[kReplyHeaders][_key] && _key === 'set-cookie') {
220
+ if (this[kReplyHeaders][key] && key === 'set-cookie') {
226
221
  // https://tools.ietf.org/html/rfc7230#section-3.2.2
227
- if (typeof this[kReplyHeaders][_key] === 'string') {
228
- this[kReplyHeaders][_key] = [this[kReplyHeaders][_key]]
222
+ if (typeof this[kReplyHeaders][key] === 'string') {
223
+ this[kReplyHeaders][key] = [this[kReplyHeaders][key]]
229
224
  }
225
+
230
226
  if (Array.isArray(value)) {
231
- Array.prototype.push.apply(this[kReplyHeaders][_key], value)
227
+ this[kReplyHeaders][key].push(...value)
232
228
  } else {
233
- this[kReplyHeaders][_key].push(value)
229
+ this[kReplyHeaders][key].push(value)
234
230
  }
235
231
  } else {
236
- this[kReplyHeaders][_key] = value
232
+ this[kReplyHeaders][key] = value
237
233
  }
234
+
238
235
  return this
239
236
  }
240
237
 
@@ -245,6 +242,7 @@ Reply.prototype.headers = function (headers) {
245
242
  const key = keys[i]
246
243
  this.header(key, headers[key])
247
244
  }
245
+
248
246
  return this
249
247
  }
250
248
 
@@ -279,8 +277,7 @@ Reply.prototype.trailer = function (key, fn) {
279
277
  }
280
278
 
281
279
  Reply.prototype.hasTrailer = function (key) {
282
- if (this[kReplyTrailers] === null) return false
283
- return this[kReplyTrailers][key.toLowerCase()] !== undefined
280
+ return this[kReplyTrailers]?.[key.toLowerCase()] !== undefined
284
281
  }
285
282
 
286
283
  Reply.prototype.removeTrailer = function (key) {
@@ -330,11 +327,12 @@ Reply.prototype.redirect = function (code, url) {
330
327
  code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
331
328
  }
332
329
 
333
- this.header('location', url).code(code).send()
330
+ return this.header('location', url).code(code).send()
334
331
  }
335
332
 
336
333
  Reply.prototype.callNotFound = function () {
337
334
  notFound(this)
335
+ return this
338
336
  }
339
337
 
340
338
  Reply.prototype.getResponseTime = function () {
@@ -483,9 +481,12 @@ function onSendEnd (reply, payload) {
483
481
  }
484
482
 
485
483
  if (reply[kReplyTrailers] === null) {
486
- if (!reply[kReplyHeaders]['content-length']) {
487
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
488
- } else if (req.raw.method !== 'HEAD' && reply[kReplyHeaders]['content-length'] !== Buffer.byteLength(payload)) {
484
+ const contentLength = reply[kReplyHeaders]['content-length']
485
+ if (!contentLength ||
486
+ (req.raw.method !== 'HEAD' &&
487
+ parseInt(contentLength, 10) !== Buffer.byteLength(payload)
488
+ )
489
+ ) {
489
490
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
490
491
  }
491
492
  }