fastify 4.2.0 → 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.
package/README.md CHANGED
@@ -38,18 +38,18 @@ resources of your server, knowing that you are serving the highest number of
38
38
  requests as possible, without sacrificing security validations and handy
39
39
  development?
40
40
 
41
- - [Quick start](./README.md#quick-start)
42
- - [Install](./README.md#install)
43
- - [Example](./README.md#example)
44
- - [Fastify v1.x and v2.x](./README.md#fastify-v1x-and-v2x)
45
- - [Core features](./README.md#core-features)
46
- - [Benchmarks](./README.md#benchmarks)
47
- - [Documentation](./README.md#documentation)
48
- - [Ecosystem](./README.md#ecosystem)
49
- - [Support](./README.md#support)
50
- - [Team](./README.md#team)
51
- - [Hosted by](./README.md#hosted-by)
52
- - [License](./README.md#license)
41
+ - [Quick start](#quick-start)
42
+ - [Install](#install)
43
+ - [Example](#example)
44
+ - [Fastify v1.x and v2.x](#fastify-v1x-and-v2x)
45
+ - [Core features](#core-features)
46
+ - [Benchmarks](#benchmarks)
47
+ - [Documentation](#documentation)
48
+ - [Ecosystem](#ecosystem)
49
+ - [Support](#support)
50
+ - [Team](#team)
51
+ - [Hosted by](#hosted-by)
52
+ - [License](#license)
53
53
 
54
54
  Enter Fastify. Fastify is a web framework highly focused on providing the best
55
55
  developer experience with the least overhead and a powerful plugin architecture.
@@ -99,8 +99,7 @@ generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli).
99
99
 
100
100
  ### Install
101
101
 
102
- If installing in an existing project, then Fastify can be installed into the
103
- project as a dependency:
102
+ To install Fastify in an existing project as a dependency:
104
103
 
105
104
  Install with npm:
106
105
  ```sh
@@ -204,7 +204,7 @@ function knexPlugin(fastify, options, done) {
204
204
  done()
205
205
  }
206
206
 
207
- export default fp(plugin, { name: 'fastify-knex-example' })
207
+ export default fp(knexPlugin, { name: 'fastify-knex-example' })
208
208
  ```
209
209
 
210
210
  ### Writing a plugin for a database engine
@@ -213,7 +213,7 @@ In this example, we will create a basic Fastify MySQL plugin from scratch (it is
213
213
  a stripped-down example, please use the official plugin in production).
214
214
 
215
215
  ```javascript
216
- const fp = require('fp')
216
+ const fp = require('fastify-plugin')
217
217
  const mysql = require('mysql2/promise')
218
218
 
219
219
  function fastifyMysql(fastify, options, done) {
@@ -138,8 +138,8 @@ section.
138
138
  Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your
139
139
  Fastify API graceful.
140
140
  - [`@h4ad/serverless-adapter`](https://github.com/H4ad/serverless-adapter)
141
- Run REST APIs and other web applications using your existing Node.js
142
- application framework (Express, Koa, Hapi and Fastify), on top of AWS Lambda,
141
+ Run REST APIs and other web applications using your existing Node.js
142
+ application framework (Express, Koa, Hapi and Fastify), on top of AWS Lambda,
143
143
  Huawei and many other clouds.
144
144
  - [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics)
145
145
  Minimalistic and opinionated plugin that collects usage/process metrics and
@@ -151,12 +151,12 @@ section.
151
151
  A plugin to close the server gracefully
152
152
  - [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger)
153
153
  A plugin to enable compact request logging for Fastify
154
+ - [`@mgcrea/fastify-session`](https://github.com/mgcrea/fastify-session) Session
155
+ plugin for Fastify that supports both stateless and stateful sessions
154
156
  - [`@mgcrea/fastify-session-redis-store`](https://github.com/mgcrea/fastify-session-redis-store)
155
157
  Redis store for @mgcrea/fastify-session using ioredis
156
158
  - [`@mgcrea/fastify-session-sodium-crypto`](https://github.com/mgcrea/fastify-session-sodium-crypto)
157
159
  Fast sodium-based crypto for @mgcrea/fastify-session
158
- - [`@mgcrea/fastify-session`](https://github.com/mgcrea/fastify-session) Session
159
- plugin for Fastify that supports both stateless and stateful sessions
160
160
  - [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact)
161
161
  A custom compact pino-base prettifier
162
162
  - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs)
@@ -169,6 +169,8 @@ section.
169
169
  - [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware
170
170
  for CLS-based request ID generation. An out-of-the-box solution for adding
171
171
  request IDs into your logs.
172
+ - [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for
173
+ waterline. Decorates Fastify with waterline models.
172
174
  - [`fastify-405`](https://github.com/Eomm/fastify-405) Fastify plugin that adds
173
175
  405 HTTP status to your routes
174
176
  - [`fastify-allow`](https://github.com/mattbishop/fastify-allow) Fastify plugin
@@ -287,13 +289,13 @@ section.
287
289
  good Fastify sessions plugin focused on speed.
288
290
  - [`fastify-google-cloud-storage`](https://github.com/carlozamagni/fastify-google-cloud-storage)
289
291
  Fastify plugin that exposes a GCP Cloud Storage client instance.
292
+ - [`fastify-graceful-shutdown`](https://github.com/hemerajs/fastify-graceful-shutdown)
293
+ Shutdown Fastify gracefully and asynchronously.
290
294
  - [`fastify-grant`](https://github.com/simov/fastify-grant)
291
295
  Authentication/Authorization plugin for Fastify that supports 200+ OAuth
292
296
  Providers.
293
297
  - [`fastify-guard`](https://github.com/hsynlms/fastify-guard) A Fastify plugin
294
298
  that protects endpoints by checking authenticated user roles and/or scopes.
295
- - [`fastify-graceful-shutdown`](https://github.com/hemerajs/fastify-graceful-shutdown)
296
- Shutdown Fastify gracefully and asynchronously.
297
299
  - [`fastify-hasura`](https://github.com/ManUtopiK/fastify-hasura) A Fastify
298
300
  plugin to have fun with [Hasura](https://github.com/hasura/graphql-engine).
299
301
  - [`fastify-healthcheck`](https://github.com/smartiniOnGitHub/fastify-healthcheck)
@@ -301,20 +303,19 @@ section.
301
303
  - [`fastify-hemera`](https://github.com/hemerajs/fastify-hemera) Fastify Hemera
302
304
  plugin, for writing reliable & fault-tolerant microservices with
303
305
  [nats.io](https://nats.io/).
306
+ - [`fastify-http-client`](https://github.com/kenuyx/fastify-http-client) Plugin
307
+ to send HTTP(s) requests. Built upon [urllib](https://github.com/node-modules/urllib).
304
308
  - [`fastify-http-context`](https://github.com/thorough-developer/fastify-http-context)
305
309
  Fastify plugin for "simulating" a thread of execution to allow for true HTTP
306
310
  context to take place per API call within the Fastify lifecycle of calls.
311
+ - [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced)
312
+ An error handling plugin for Fastify that uses enhanced HTTP errors.
307
313
  - [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect
308
314
  HTTP requests to HTTPS, both using the same port number, or different response
309
315
  on HTTP and HTTPS.
310
- - [`fastify-http-client`](https://github.com/kenuyx/fastify-http-client) Plugin
311
- to send HTTP(s) requests. Built upon
312
- [urllib](https://github.com/node-modules/urllib).
313
- - [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced)
314
- An error handling plugin for Fastify that uses enhanced HTTP errors.
315
316
  - [`fastify-https-redirect`](https://github.com/tomsvogel/fastify-https-redirect)
316
317
  Fastify plugin for auto-redirect from HTTP to HTTPS.
317
- - [`fatify-impressions`](https://github.com/manju4ever/fastify-impressions)
318
+ - [`fastify-impressions`](https://github.com/manju4ever/fastify-impressions)
318
319
  Fastify plugin to track impressions of all the routes.
319
320
  - [`fastify-influxdb`](https://github.com/alex-ppg/fastify-influxdb) Fastify
320
321
  InfluxDB plugin connecting to an InfluxDB instance via the Influx default
@@ -408,10 +409,10 @@ section.
408
409
  - [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify
409
410
  OrientDB connection plugin, with which you can share the OrientDB connection
410
411
  across every part of your server.
411
- - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker
412
- thread pool plugin using [Piscina](https://github.com/piscinajs/piscina).
413
412
  - [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo)
414
413
  Fastify plugin for memoize responses by expressive settings.
414
+ - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker
415
+ thread pool plugin using [Piscina](https://github.com/piscinajs/piscina).
415
416
  - [`fastify-polyglot`](https://github.com/heply/fastify-polyglot) A plugin to
416
417
  handle i18n using
417
418
  [node-polyglot](https://www.npmjs.com/package/node-polyglot).
@@ -444,10 +445,10 @@ section.
444
445
  - [`fastify-register-routes`](https://github.com/israeleriston/fastify-register-routes)
445
446
  Plugin to automatically load routes from a specified path and optionally limit
446
447
  loaded file names by a regular expression.
447
- - [`fastify-response-time`](https://github.com/lolo32/fastify-response-time) Add
448
- `X-Response-Time` header at each request for Fastify, in milliseconds.
449
448
  - [`fastify-response-caching`](https://github.com/codeaholicguy/fastify-response-caching)
450
449
  A Fastify plugin for caching the response.
450
+ - [`fastify-response-time`](https://github.com/lolo32/fastify-response-time) Add
451
+ `X-Response-Time` header at each request for Fastify, in milliseconds.
451
452
  - [`fastify-resty`](https://github.com/FastifyResty/fastify-resty) Fastify-based
452
453
  web framework with REST API routes auto-generation for TypeORM entities using
453
454
  DI and decorators.
@@ -518,8 +519,6 @@ section.
518
519
  should use.
519
520
  - [`fastify-wamp-router`](https://github.com/lependu/fastify-wamp-router) Web
520
521
  Application Messaging Protocol router for Fastify.
521
- - [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for
522
- waterline. Decorates Fastify with waterline models.
523
522
  - [`fastify-webpack-hmr`](https://github.com/lependu/fastify-webpack-hmr)
524
523
  Webpack hot module reloading plugin for Fastify.
525
524
  - [`fastify-webpack-hot`](https://github.com/gajus/fastify-webpack-hot) Webpack
@@ -552,7 +551,11 @@ section.
552
551
  Fastify.
553
552
  - [`sequelize-fastify`](https://github.com/hsynlms/sequelize-fastify) A simple
554
553
  and lightweight Sequelize plugin for Fastify.
554
+ - [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple
555
+ and updated Typeorm plugin for use with Fastify.
555
556
 
556
557
  #### [Community Tools](#community-tools)
557
558
  - [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration
558
559
  generator by directory structure.
560
+ - [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to
561
+ generate JSON Schema from TypeScript interfaces.
@@ -65,6 +65,35 @@ Starting from v4, all the `GET` routes will create a sibling `HEAD` route.
65
65
  You can revert this behaviour by setting the server's option `exposeHeadRoutes`
66
66
  to `false`.
67
67
 
68
+ ### Synchronous route definitions
69
+
70
+ The route registration has been made synchronous from v4.
71
+ This change was done to provide better error reporting for route definition.
72
+ As a result if you specify an `onRoute` hook in a plugin you should either:
73
+ * wrap your routes in a plugin (recommended)
74
+ * use `await register(...)`
75
+
76
+ For example refactor this:
77
+ ```
78
+ fastify.register((instance, opts, done) => {
79
+ instance.addHook('onRoute', (routeOptions) => {
80
+ const { path, method } = routeOptions;
81
+ console.log({ path, method });
82
+ });
83
+ done();
84
+ });
85
+ ```
86
+ Into this:
87
+ ```
88
+ await fastify.register((instance, opts, done) => {
89
+ instance.addHook('onRoute', (routeOptions) => {
90
+ const { path, method } = routeOptions;
91
+ console.log({ path, method });
92
+ });
93
+ done();
94
+ });
95
+ ```
96
+
68
97
  ## Non Breaking Changes
69
98
 
70
99
  ### Change of schema for multiple types
@@ -196,6 +196,11 @@ fastify.get('/html', (request, reply) => {
196
196
  reply.html({ hello: 'world' })
197
197
  })
198
198
  ```
199
+ Reminder that the `this` keyword is not available on *arrow functions*,
200
+ so when passing functions in *`decorateReply`* and *`decorateRequest`* as
201
+ a utility that also needs access to the `request` and `reply` instance,
202
+ a function that is defined using the `function` keyword is needed instead
203
+ of an *arrow function expression*.
199
204
 
200
205
  In the same way you can do this for the `request` object:
201
206
  ```js
@@ -70,14 +70,7 @@ import { Type } from '@sinclair/typebox'
70
70
 
71
71
  import fastify from 'fastify'
72
72
 
73
- const server = fastify({
74
- ajv: {
75
- customOptions: {
76
- strict: 'log',
77
- keywords: ['kind', 'modifier'],
78
- },
79
- },
80
- }).withTypeProvider<TypeBoxTypeProvider>()
73
+ const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
81
74
 
82
75
  server.get('/route', {
83
76
  schema: {
@@ -94,13 +87,6 @@ server.get('/route', {
94
87
  })
95
88
  ```
96
89
 
97
- TypeBox uses the properties `kind` and `modifier` internally. These properties
98
- are not strictly valid JSON schema which will cause `AJV@7` and newer versions
99
- to throw an invalid schema error. To remove the error it's either necessary to
100
- omit the properties by using
101
- [`Type.Strict()`](https://github.com/sinclairzx81/typebox#strict) or use the AJV
102
- options for adding custom keywords.
103
-
104
90
  See also the [TypeBox
105
91
  documentation](https://github.com/sinclairzx81/typebox#validation) on how to set
106
92
  up AJV to work with TypeBox.
@@ -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
@@ -403,7 +403,7 @@ definitions.
403
403
  #### json-schema-to-ts
404
404
 
405
405
  If you do not want to generate types from your schemas, but want to use them
406
- diretly from your code, you can use the package
406
+ directly from your code, you can use the package
407
407
  [json-schema-to-ts](https://www.npmjs.com/package/json-schema-to-ts).
408
408
 
409
409
  You can install it as dev-dependency.
@@ -661,6 +661,30 @@ However, there are a couple of suggestions to help improve this experience:
661
661
  [npm-check](https://www.npmjs.com/package/npm-check) to verify plugin
662
662
  dependencies are being used somewhere in your project.
663
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
+
664
688
  ## Code Completion In Vanilla JavaScript
665
689
 
666
690
  Vanilla JavaScript can use the published types to provide code completion (e.g.
@@ -1378,7 +1402,7 @@ FastifyError is a custom error object that includes status code and validation
1378
1402
  results.
1379
1403
 
1380
1404
  It extends the Node.js `Error` type, and adds two additional, optional
1381
- properties: `statusCode: number` and `validation: ValiationResult[]`.
1405
+ properties: `statusCode: number` and `validation: ValidationResult[]`.
1382
1406
 
1383
1407
  ##### fastify.ValidationResult
1384
1408
 
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.2.0'
3
+ const VERSION = '4.2.1'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -74,24 +74,24 @@ class Serializer {
74
74
  return bool === null ? 'null' : this.asBoolean(bool)
75
75
  }
76
76
 
77
- asDatetime (date) {
78
- const quotes = '"'
77
+ asDateTime (date) {
78
+ if (date === null) return '""'
79
79
  if (date instanceof Date) {
80
- return quotes + date.toISOString() + quotes
80
+ return '"' + date.toISOString() + '"'
81
81
  }
82
- return this.asString(date)
82
+ throw new Error(`The value "${date}" cannot be converted to a date-time.`)
83
83
  }
84
84
 
85
- asDatetimeNullable (date) {
86
- return date === null ? 'null' : this.asDatetime(date)
85
+ asDateTimeNullable (date) {
86
+ return date === null ? 'null' : this.asDateTime(date)
87
87
  }
88
88
 
89
89
  asDate (date) {
90
- const quotes = '"'
90
+ if (date === null) return '""'
91
91
  if (date instanceof Date) {
92
- 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) + '"'
93
93
  }
94
- return this.asString(date)
94
+ throw new Error(`The value "${date}" cannot be converted to a date.`)
95
95
  }
96
96
 
97
97
  asDateNullable (date) {
@@ -99,11 +99,11 @@ class Serializer {
99
99
  }
100
100
 
101
101
  asTime (date) {
102
- const quotes = '"'
102
+ if (date === null) return '""'
103
103
  if (date instanceof Date) {
104
- 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) + '"'
105
105
  }
106
- return this.asString(date)
106
+ throw new Error(`The value "${date}" cannot be converted to a time.`)
107
107
  }
108
108
 
109
109
  asTimeNullable (date) {
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,8 +327,7 @@ 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()
334
- return this
330
+ return this.header('location', url).code(code).send()
335
331
  }
336
332
 
337
333
  Reply.prototype.callNotFound = function () {
@@ -485,9 +481,12 @@ function onSendEnd (reply, payload) {
485
481
  }
486
482
 
487
483
  if (reply[kReplyTrailers] === null) {
488
- if (!reply[kReplyHeaders]['content-length']) {
489
- reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
490
- } 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
+ ) {
491
490
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
492
491
  }
493
492
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -17,7 +17,7 @@
17
17
  "lint:markdown": "markdownlint-cli2",
18
18
  "lint:standard": "standard | snazzy",
19
19
  "lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
20
- "prepublishOnly": "tap --no-check-coverage test/build/**.test.js",
20
+ "prepublishOnly": "PREPUBLISH=true tap --no-check-coverage test/build/**.test.js",
21
21
  "test": "npm run lint && npm run unit && npm run test:typescript",
22
22
  "test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
23
23
  "test:report": "npm run lint && npm run unit:report && npm run test:typescript",
@@ -126,7 +126,7 @@
126
126
  "homepage": "https://www.fastify.io/",
127
127
  "devDependencies": {
128
128
  "@fastify/pre-commit": "^2.0.2",
129
- "@sinclair/typebox": "^0.23.5",
129
+ "@sinclair/typebox": "^0.24.9",
130
130
  "@sinonjs/fake-timers": "^9.1.2",
131
131
  "@types/node": "^18.0.0",
132
132
  "@typescript-eslint/eslint-plugin": "^5.27.0",
@@ -147,7 +147,7 @@
147
147
  "eslint-plugin-promise": "^6.0.0",
148
148
  "fast-json-body": "^1.1.0",
149
149
  "fast-json-stringify": "^5.0.0",
150
- "fastify-plugin": "^3.0.1",
150
+ "fastify-plugin": "^4.0.0",
151
151
  "fluent-json-schema": "^3.1.0",
152
152
  "form-data": "^4.0.0",
153
153
  "frameguard": "^4.0.0",
@@ -170,14 +170,14 @@
170
170
  "split2": "^4.1.0",
171
171
  "standard": "^17.0.0-2",
172
172
  "tap": "^16.2.0",
173
- "tsd": "^0.21.0",
173
+ "tsd": "^0.22.0",
174
174
  "typescript": "^4.7.2",
175
175
  "undici": "^5.4.0",
176
176
  "x-xss-protection": "^2.0.0",
177
177
  "yup": "^0.32.11"
178
178
  },
179
179
  "dependencies": {
180
- "@fastify/ajv-compiler": "^3.1.0",
180
+ "@fastify/ajv-compiler": "^3.1.1",
181
181
  "@fastify/error": "^3.0.0",
182
182
  "@fastify/fast-json-stringify-compiler": "^4.0.0",
183
183
  "abstract-logging": "^2.0.1",
@@ -23,7 +23,9 @@ test('check generated code syntax', async (t) => {
23
23
  t.equal(result[0].fatalErrorCount, 0)
24
24
  })
25
25
 
26
- test('ensure the current error serializer is latest', async (t) => {
26
+ const isPrebublish = !!process.env.PREPUBLISH
27
+
28
+ test('ensure the current error serializer is latest', { skip: !isPrebublish }, async (t) => {
27
29
  t.plan(1)
28
30
 
29
31
  const current = await fs.promises.readFile(path.resolve('lib/error-serializer.js'))
@@ -4,6 +4,7 @@ const { test } = require('tap')
4
4
  const Fastify = require('..')
5
5
 
6
6
  const AJV = require('ajv')
7
+ const Schema = require('fluent-json-schema')
7
8
 
8
9
  const customSchemaCompilers = {
9
10
  body: new AJV({
@@ -946,3 +947,73 @@ test('Custom AJV settings on different parameters - pt2', t => {
946
947
  }
947
948
  })
948
949
  })
950
+
951
+ test("The same $id in route's schema must not overwrite others", t => {
952
+ t.plan(4)
953
+ const fastify = Fastify()
954
+
955
+ const UserSchema = Schema.object()
956
+ .id('http://mydomain.com/user')
957
+ .title('User schema')
958
+ .description('Contains all user fields')
959
+ .prop('id', Schema.integer())
960
+ .prop('username', Schema.string().minLength(4))
961
+ .prop('firstName', Schema.string().minLength(1))
962
+ .prop('lastName', Schema.string().minLength(1))
963
+ .prop('fullName', Schema.string().minLength(1))
964
+ .prop('email', Schema.string())
965
+ .prop('password', Schema.string().minLength(6))
966
+ .prop('bio', Schema.string())
967
+
968
+ const userCreateSchema = UserSchema.only([
969
+ 'username',
970
+ 'firstName',
971
+ 'lastName',
972
+ 'email',
973
+ 'bio',
974
+ 'password',
975
+ 'password_confirm'
976
+ ])
977
+ .required([
978
+ 'username',
979
+ 'firstName',
980
+ 'lastName',
981
+ 'email',
982
+ 'bio',
983
+ 'password'
984
+ ])
985
+
986
+ const userPatchSchema = UserSchema.only([
987
+ 'firstName',
988
+ 'lastName',
989
+ 'bio'
990
+ ])
991
+
992
+ fastify
993
+ .patch('/user/:id', {
994
+ schema: { body: userPatchSchema },
995
+ handler: () => { return 'ok' }
996
+ })
997
+ .post('/user', {
998
+ schema: { body: userCreateSchema },
999
+ handler: () => { return 'ok' }
1000
+ })
1001
+
1002
+ fastify.inject({
1003
+ method: 'POST',
1004
+ url: '/user',
1005
+ body: {}
1006
+ }, (err, res) => {
1007
+ t.error(err)
1008
+ t.same(res.json().message, "body must have required property 'username'")
1009
+ })
1010
+
1011
+ fastify.inject({
1012
+ url: '/user/1',
1013
+ method: 'PATCH',
1014
+ body: {}
1015
+ }, (err, res) => {
1016
+ t.error(err)
1017
+ t.same(res.payload, 'ok')
1018
+ })
1019
+ })
@@ -194,7 +194,18 @@ expectAssignable<FastifyInstance>(fastify({ frameworkErrors: () => { } }))
194
194
  expectAssignable<FastifyInstance>(fastify({
195
195
  rewriteUrl: (req) => req.url === '/hi' ? '/hello' : req.url!
196
196
  }))
197
- expectAssignable<FastifyInstance>(fastify({ schemaErrorFormatter: (errors, dataVar) => new Error() }))
197
+ expectAssignable<FastifyInstance>(fastify({
198
+ schemaErrorFormatter: (errors, dataVar) => {
199
+ console.log(
200
+ errors[0].keyword.toLowerCase(),
201
+ errors[0].message?.toLowerCase(),
202
+ errors[0].params,
203
+ errors[0].instancePath.toLowerCase(),
204
+ errors[0].schemaPath.toLowerCase()
205
+ )
206
+ return new Error()
207
+ }
208
+ }))
198
209
  expectAssignable<FastifyInstance>(fastify({
199
210
  clientErrorHandler: (err, socket) => {
200
211
  expectType<ConnectionError>(err)
@@ -11,7 +11,7 @@ import { HookHandlerDoneFunction } from '../../types/hooks'
11
11
  import { FastifyReply } from '../../types/reply'
12
12
  import { FastifyRequest } from '../../types/request'
13
13
  import { DefaultRoute } from '../../types/route'
14
- import { FastifySchemaControllerOptions } from '../../types/schema'
14
+ import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema'
15
15
 
16
16
  const server = fastify()
17
17
 
@@ -325,3 +325,6 @@ expectType<void>(server.addConstraintStrategy(versionConstraintStrategy))
325
325
  expectType<boolean>(server.hasConstraintStrategy(versionConstraintStrategy.name))
326
326
 
327
327
  expectAssignable<DefaultRoute<RawRequestDefaultExpression, RawReplyDefaultExpression>>(server.getDefaultRoute())
328
+
329
+ expectType<FastifySchemaCompiler<any> | undefined>(server.validatorCompiler)
330
+ expectType<FastifySerializerCompiler<any> | undefined>(server.serializerCompiler)
@@ -494,12 +494,17 @@ test('the custom error formatter context must be the server instance in options'
494
494
  })
495
495
 
496
496
  test('should call custom error formatter', t => {
497
- t.plan(6)
497
+ t.plan(9)
498
498
 
499
499
  const fastify = Fastify({
500
500
  schemaErrorFormatter: (errors, dataVar) => {
501
501
  t.equal(errors.length, 1)
502
502
  t.equal(errors[0].message, "must have required property 'name'")
503
+ t.equal(errors[0].keyword, 'required')
504
+ t.equal(errors[0].schemaPath, '#/required')
505
+ t.same(errors[0].params, {
506
+ missingProperty: 'name'
507
+ })
503
508
  t.equal(dataVar, 'body')
504
509
  return new Error('my error')
505
510
  }
@@ -1,6 +1,6 @@
1
- import * as http from 'http'
2
1
  import { FastifyError } from '@fastify/error'
3
2
  import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
3
+ import * as http from 'http'
4
4
  import { CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse } from 'light-my-request'
5
5
  import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction, removeAllContentTypeParsers, removeContentTypeParser } from './content-type-parser'
6
6
  import { onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler } from './hooks'
@@ -30,6 +30,52 @@ export interface PrintRoutesOptions {
30
30
  includeHooks?: boolean
31
31
  }
32
32
 
33
+ export interface FastifyListenOptions {
34
+ /**
35
+ * Default to `0` (picks the first available open port).
36
+ */
37
+ port?: number;
38
+ /**
39
+ * Default to `localhost`.
40
+ */
41
+ host?: string;
42
+ /**
43
+ * Will be ignored if `port` is specified.
44
+ * @see [Identifying paths for IPC connections](https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections).
45
+ */
46
+ path?: string;
47
+ /**
48
+ * Specify the maximum length of the queue of pending connections.
49
+ * The actual length will be determined by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux.
50
+ * Default to `511`.
51
+ */
52
+ backlog?: number;
53
+ /**
54
+ * Default to `false`.
55
+ */
56
+ exclusive?: boolean;
57
+ /**
58
+ * For IPC servers makes the pipe readable for all users.
59
+ * Default to `false`.
60
+ */
61
+ readableAll?: boolean;
62
+ /**
63
+ * For IPC servers makes the pipe writable for all users.
64
+ * Default to `false`.
65
+ */
66
+ writableAll?: boolean;
67
+ /**
68
+ * For TCP servers, setting `ipv6Only` to `true` will disable dual-stack support, i.e., binding to host `::` won't make `0.0.0.0` be bound.
69
+ * Default to `false`.
70
+ */
71
+ ipv6Only?: boolean;
72
+ /**
73
+ * An AbortSignal that may be used to close a listening server.
74
+ * @since This option is available only in Node.js v15.6.0 and greater
75
+ */
76
+ signal?: AbortSignal;
77
+ }
78
+
33
79
  type NotInInterface<Key, _Interface> = Key extends keyof _Interface ? never : Key
34
80
  type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
35
81
 
@@ -93,96 +139,8 @@ export interface FastifyInstance<
93
139
  inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;
94
140
  inject(): LightMyRequestChain;
95
141
 
96
- listen(opts: {
97
- /**
98
- * Default to `0` (picks the first available open port).
99
- */
100
- port?: number;
101
- /**
102
- * Default to `localhost`.
103
- */
104
- host?: string;
105
- /**
106
- * Will be ignored if `port` is specified.
107
- * @see [Identifying paths for IPC connections](https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections).
108
- */
109
- path?: string;
110
- /**
111
- * Specify the maximum length of the queue of pending connections.
112
- * The actual length will be determined by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux.
113
- * Default to `511`.
114
- */
115
- backlog?: number;
116
- /**
117
- * Default to `false`.
118
- */
119
- exclusive?: boolean;
120
- /**
121
- * For IPC servers makes the pipe readable for all users.
122
- * Default to `false`.
123
- */
124
- readableAll?: boolean;
125
- /**
126
- * For IPC servers makes the pipe writable for all users.
127
- * Default to `false`.
128
- */
129
- writableAll?: boolean;
130
- /**
131
- * For TCP servers, setting `ipv6Only` to `true` will disable dual-stack support, i.e., binding to host `::` won't make `0.0.0.0` be bound.
132
- * Default to `false`.
133
- */
134
- ipv6Only?: boolean;
135
- /**
136
- * An AbortSignal that may be used to close a listening server.
137
- * @since This option is available only in Node.js v15.6.0 and greater
138
- */
139
- signal?: AbortSignal;
140
- }, callback: (err: Error|null, address: string) => void): void;
141
- listen(opts?: {
142
- /**
143
- * Default to `0` (picks the first available open port).
144
- */
145
- port?: number;
146
- /**
147
- * Default to `localhost`.
148
- */
149
- host?: string;
150
- /**
151
- * Will be ignored if `port` is specified.
152
- * @see [Identifying paths for IPC connections](https://nodejs.org/api/net.html#identifying-paths-for-ipc-connections).
153
- */
154
- path?: string;
155
- /**
156
- * Specify the maximum length of the queue of pending connections.
157
- * The actual length will be determined by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn` on Linux.
158
- * Default to `511`.
159
- */
160
- backlog?: number;
161
- /**
162
- * Default to `false`.
163
- */
164
- exclusive?: boolean;
165
- /**
166
- * For IPC servers makes the pipe readable for all users.
167
- * Default to `false`.
168
- */
169
- readableAll?: boolean;
170
- /**
171
- * For IPC servers makes the pipe writable for all users.
172
- * Default to `false`.
173
- */
174
- writableAll?: boolean;
175
- /**
176
- * For TCP servers, setting `ipv6Only` to `true` will disable dual-stack support, i.e., binding to host `::` won't make `0.0.0.0` be bound.
177
- * Default to `false`.
178
- */
179
- ipv6Only?: boolean;
180
- /**
181
- * An AbortSignal that may be used to close a listening server.
182
- * @since This option is available only in Node.js v15.6.0 and greater
183
- */
184
- signal?: AbortSignal;
185
- }): Promise<string>;
142
+ listen(opts: FastifyListenOptions, callback: (err: Error | null, address: string) => void): void;
143
+ listen(opts?: FastifyListenOptions): Promise<string>;
186
144
  listen(callback: (err: Error | null, address: string) => void): void;
187
145
 
188
146
  /**
@@ -550,11 +508,21 @@ export interface FastifyInstance<
550
508
  handler: (this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, error: TError, request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfigDefault, SchemaCompiler, TypeProvider>) => any | Promise<any>
551
509
  ): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
552
510
 
511
+ /**
512
+ * Fastify schema validator for all routes.
513
+ */
514
+ validatorCompiler: FastifySchemaCompiler<any> | undefined;
515
+
553
516
  /**
554
517
  * Set the schema validator for all routes.
555
518
  */
556
519
  setValidatorCompiler<T = FastifySchema>(schemaCompiler: FastifySchemaCompiler<T>): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
557
520
 
521
+ /**
522
+ * Fastify schema serializer for all routes.
523
+ */
524
+ serializerCompiler: FastifySerializerCompiler<any> | undefined;
525
+
558
526
  /**
559
527
  * Set the schema serializer for all routes.
560
528
  */
package/types/schema.d.ts CHANGED
@@ -23,8 +23,11 @@ export interface FastifyRouteSchemaDef<T> {
23
23
  }
24
24
 
25
25
  export interface FastifySchemaValidationError {
26
- message?: string;
26
+ keyword: string;
27
27
  instancePath: string;
28
+ schemaPath: string;
29
+ params: Record<string, string | string[]>;
30
+ message?: string;
28
31
  }
29
32
 
30
33
  export interface FastifyValidationResult {