fastify 3.17.0 → 3.19.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/lib/request.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const proxyAddr = require('@fastify/proxy-addr')
3
+ const proxyAddr = require('proxy-addr')
4
4
  const semver = require('semver')
5
5
  const warning = require('./warnings')
6
6
 
package/lib/route.js CHANGED
@@ -227,7 +227,7 @@ function buildRouting (options) {
227
227
  }
228
228
  const constraints = opts.constraints || {}
229
229
  if (opts.version) {
230
- warning.emit('FSTDEP006')
230
+ warning.emit('FSTDEP008')
231
231
  constraints.version = opts.version
232
232
  }
233
233
 
@@ -296,7 +296,7 @@ function buildRouting (options) {
296
296
  fourOhFour.setContext(this, context)
297
297
 
298
298
  if (opts.schema) {
299
- context.schema = normalizeSchema(context.schema)
299
+ context.schema = normalizeSchema(context.schema, this.initialConfig)
300
300
 
301
301
  const schemaController = this[kSchemaController]
302
302
  if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
package/lib/schemas.js CHANGED
@@ -43,7 +43,7 @@ Schemas.prototype.getSchema = function (schemaId) {
43
43
  return this.store[schemaId]
44
44
  }
45
45
 
46
- function normalizeSchema (routeSchemas) {
46
+ function normalizeSchema (routeSchemas, serverOptions) {
47
47
  if (routeSchemas[kSchemaVisited]) {
48
48
  return routeSchemas
49
49
  }
@@ -67,25 +67,25 @@ function normalizeSchema (routeSchemas) {
67
67
  }
68
68
 
69
69
  if (routeSchemas.body) {
70
- routeSchemas.body = getSchemaAnyway(routeSchemas.body)
70
+ routeSchemas.body = getSchemaAnyway(routeSchemas.body, serverOptions.jsonShorthand)
71
71
  }
72
72
 
73
73
  if (routeSchemas.headers) {
74
- routeSchemas.headers = getSchemaAnyway(routeSchemas.headers)
74
+ routeSchemas.headers = getSchemaAnyway(routeSchemas.headers, serverOptions.jsonShorthand)
75
75
  }
76
76
 
77
77
  if (routeSchemas.querystring) {
78
- routeSchemas.querystring = getSchemaAnyway(routeSchemas.querystring)
78
+ routeSchemas.querystring = getSchemaAnyway(routeSchemas.querystring, serverOptions.jsonShorthand)
79
79
  }
80
80
 
81
81
  if (routeSchemas.params) {
82
- routeSchemas.params = getSchemaAnyway(routeSchemas.params)
82
+ routeSchemas.params = getSchemaAnyway(routeSchemas.params, serverOptions.jsonShorthand)
83
83
  }
84
84
 
85
85
  if (routeSchemas.response) {
86
86
  const httpCodes = Object.keys(routeSchemas.response)
87
87
  for (const code of httpCodes) {
88
- routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code])
88
+ routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand)
89
89
  }
90
90
  }
91
91
 
@@ -110,8 +110,8 @@ function generateFluentSchema (schema) {
110
110
  }
111
111
  }
112
112
 
113
- function getSchemaAnyway (schema) {
114
- if (schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
113
+ function getSchemaAnyway (schema, jsonShorthand) {
114
+ if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
115
115
  if (!schema.type && !schema.properties) {
116
116
  return {
117
117
  type: 'object',
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.17.0",
3
+ "version": "3.19.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
7
7
  "types": "fastify.d.ts",
8
8
  "scripts": {
9
9
  "bench": "branchcmp -r 2 -g -s \"npm run benchmark\"",
10
- "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 5 -p 10 localhost:3000/\"",
10
+ "benchmark": "npx concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"npx autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
11
11
  "coverage": "npm run unit -- --cov --coverage-report=html",
12
12
  "license-checker": "license-checker --production --onlyAllow=\"MIT;ISC;BSD-3-Clause;BSD-2-Clause\"",
13
13
  "lint": "npm run lint:standard && npm run lint:typescript",
@@ -114,25 +114,22 @@
114
114
  "url": "https://github.com/fastify/fastify/issues"
115
115
  },
116
116
  "homepage": "https://www.fastify.io/",
117
- "engines": {
118
- "node": ">=10.16.0"
119
- },
120
117
  "devDependencies": {
121
118
  "@fastify/ajv-compiler-8": "github:fastify/ajv-compiler#ajv-8",
119
+ "@fastify/pre-commit": "^2.0.1",
122
120
  "@hapi/joi": "^17.1.1",
123
121
  "@sinonjs/fake-timers": "^7.0.0",
124
- "@types/node": "^15.3.0",
122
+ "@types/node": "^16.0.0",
125
123
  "@types/pino": "^6.0.1",
126
124
  "@typescript-eslint/eslint-plugin": "^4.5.0",
127
125
  "@typescript-eslint/parser": "^4.5.0",
126
+ "JSONStream": "^1.3.5",
128
127
  "ajv": "^6.0.0",
129
128
  "ajv-errors": "^1.0.1",
130
129
  "ajv-i18n": "^3.5.0",
131
130
  "ajv-merge-patch": "^4.1.0",
132
131
  "ajv-pack": "^0.3.1",
133
- "autocannon": "^7.0.0",
134
132
  "branch-comparer": "^1.0.2",
135
- "concurrently": "^6.0.0",
136
133
  "cors": "^2.8.5",
137
134
  "coveralls": "^3.1.0",
138
135
  "dns-prefetch-control": "^0.3.0",
@@ -154,10 +151,8 @@
154
151
  "hsts": "^2.2.0",
155
152
  "http-errors": "^1.7.1",
156
153
  "ienoopen": "^1.1.0",
157
- "JSONStream": "^1.3.5",
158
154
  "license-checker": "^25.0.1",
159
155
  "pem": "^1.14.4",
160
- "pre-commit": "^1.2.2",
161
156
  "proxyquire": "^2.1.3",
162
157
  "pump": "^3.0.0",
163
158
  "send": "^0.17.1",
@@ -177,7 +172,6 @@
177
172
  },
178
173
  "dependencies": {
179
174
  "@fastify/ajv-compiler": "^1.0.0",
180
- "@fastify/proxy-addr": "^3.0.0",
181
175
  "abstract-logging": "^2.0.0",
182
176
  "avvio": "^7.1.2",
183
177
  "fast-json-stringify": "^2.5.2",
@@ -187,6 +181,7 @@
187
181
  "flatstr": "^1.0.12",
188
182
  "light-my-request": "^4.2.0",
189
183
  "pino": "^6.2.1",
184
+ "proxy-addr": "^2.0.7",
190
185
  "readable-stream": "^3.4.0",
191
186
  "rfdc": "^1.1.4",
192
187
  "secure-json-parse": "^2.0.0",
@@ -4,6 +4,7 @@ const net = require('net')
4
4
  const t = require('tap')
5
5
  const test = t.test
6
6
  const Fastify = require('..')
7
+ const { Client } = require('undici')
7
8
 
8
9
  test('close callback', t => {
9
10
  t.plan(4)
@@ -238,27 +239,23 @@ t.test('Current opened connection should continue to work after closing and retu
238
239
  })
239
240
 
240
241
  t.test('Current opened connection should not accept new incoming connections', t => {
242
+ t.plan(3)
241
243
  const fastify = Fastify()
242
-
243
244
  fastify.get('/', (req, reply) => {
244
245
  fastify.close()
245
- reply.send({ hello: 'world' })
246
+ setTimeout(() => {
247
+ reply.send({ hello: 'world' })
248
+ }, 250)
246
249
  })
247
250
 
248
251
  fastify.listen(0, err => {
249
252
  t.error(err)
250
-
251
- const port = fastify.server.address().port
252
- const client = net.createConnection({ port: port }, () => {
253
- client.write('GET / HTTP/1.1\r\n\r\n')
254
-
255
- const newConnection = net.createConnection({ port: port })
256
- newConnection.on('error', err => {
257
- t.ok(err)
258
- t.ok(['ECONNREFUSED', 'ECONNRESET'].includes(err.code))
259
-
260
- client.end(() => { t.end() })
261
- })
253
+ const instance = new Client('http://localhost:' + fastify.server.address().port)
254
+ instance.request({ path: '/', method: 'GET' }).then(data => {
255
+ t.equal(data.statusCode, 200)
256
+ })
257
+ instance.request({ path: '/', method: 'GET' }).then(data => {
258
+ t.equal(data.statusCode, 503)
262
259
  })
263
260
  })
264
261
  })
@@ -27,6 +27,7 @@ test('without options passed to Fastify, initialConfig should expose default val
27
27
  bodyLimit: 1024 * 1024,
28
28
  caseSensitive: true,
29
29
  disableRequestLogging: false,
30
+ jsonShorthand: true,
30
31
  ignoreTrailingSlash: false,
31
32
  maxParamLength: 100,
32
33
  onProtoPoisoning: 'error',
@@ -242,6 +243,7 @@ test('Should not have issues when passing stream options to Pino.js', t => {
242
243
  bodyLimit: 1024 * 1024,
243
244
  caseSensitive: true,
244
245
  disableRequestLogging: false,
246
+ jsonShorthand: true,
245
247
  ignoreTrailingSlash: true,
246
248
  maxParamLength: 100,
247
249
  onProtoPoisoning: 'error',
@@ -81,6 +81,9 @@ test('build schema - payload schema', t => {
81
81
 
82
82
  test('build schema - avoid repeated normalize schema', t => {
83
83
  t.plan(3)
84
+ const serverConfig = {
85
+ jsonShorthand: true
86
+ }
84
87
  const opts = {
85
88
  schema: {
86
89
  query: {
@@ -91,14 +94,17 @@ test('build schema - avoid repeated normalize schema', t => {
91
94
  }
92
95
  }
93
96
  }
94
- opts.schema = normalizeSchema(opts.schema)
97
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
95
98
  t.not(kSchemaVisited, undefined)
96
99
  t.equal(opts.schema[kSchemaVisited], true)
97
- t.equal(opts.schema, normalizeSchema(opts.schema))
100
+ t.equal(opts.schema, normalizeSchema(opts.schema, serverConfig))
98
101
  })
99
102
 
100
103
  test('build schema - query schema', t => {
101
104
  t.plan(2)
105
+ const serverConfig = {
106
+ jsonShorthand: true
107
+ }
102
108
  const opts = {
103
109
  schema: {
104
110
  query: {
@@ -109,7 +115,7 @@ test('build schema - query schema', t => {
109
115
  }
110
116
  }
111
117
  }
112
- opts.schema = normalizeSchema(opts.schema)
118
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
113
119
  validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
114
120
  t.type(opts[symbols.querystringSchema].schema.type, 'string')
115
121
  t.equal(typeof opts[symbols.querystringSchema], 'function')
@@ -117,6 +123,9 @@ test('build schema - query schema', t => {
117
123
 
118
124
  test('build schema - query schema abbreviated', t => {
119
125
  t.plan(2)
126
+ const serverConfig = {
127
+ jsonShorthand: true
128
+ }
120
129
  const opts = {
121
130
  schema: {
122
131
  query: {
@@ -124,7 +133,7 @@ test('build schema - query schema abbreviated', t => {
124
133
  }
125
134
  }
126
135
  }
127
- opts.schema = normalizeSchema(opts.schema)
136
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
128
137
  validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
129
138
  t.type(opts[symbols.querystringSchema].schema.type, 'string')
130
139
  t.equal(typeof opts[symbols.querystringSchema], 'function')
@@ -149,6 +158,9 @@ test('build schema - querystring schema', t => {
149
158
 
150
159
  test('build schema - querystring schema abbreviated', t => {
151
160
  t.plan(2)
161
+ const serverConfig = {
162
+ jsonShorthand: true
163
+ }
152
164
  const opts = {
153
165
  schema: {
154
166
  querystring: {
@@ -156,7 +168,7 @@ test('build schema - querystring schema abbreviated', t => {
156
168
  }
157
169
  }
158
170
  }
159
- opts.schema = normalizeSchema(opts.schema)
171
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
160
172
  validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
161
173
  t.type(opts[symbols.querystringSchema].schema.type, 'string')
162
174
  t.equal(typeof opts[symbols.querystringSchema], 'function')
@@ -165,6 +177,9 @@ test('build schema - querystring schema abbreviated', t => {
165
177
  test('build schema - must throw if querystring and query schema exist', t => {
166
178
  t.plan(2)
167
179
  try {
180
+ const serverConfig = {
181
+ jsonShorthand: true
182
+ }
168
183
  const opts = {
169
184
  schema: {
170
185
  query: {
@@ -181,7 +196,7 @@ test('build schema - must throw if querystring and query schema exist', t => {
181
196
  }
182
197
  }
183
198
  }
184
- opts.schema = normalizeSchema(opts.schema)
199
+ opts.schema = normalizeSchema(opts.schema, serverConfig)
185
200
  } catch (err) {
186
201
  t.equal(err.code, 'FST_ERR_SCH_DUPLICATE')
187
202
  t.equal(err.message, 'Schema with \'querystring\' already present!')
@@ -121,3 +121,92 @@ test('pretty print - nested plugins', t => {
121
121
  t.match(tree, 'baz')
122
122
  })
123
123
  })
124
+
125
+ test('pretty print - commonPrefix', t => {
126
+ t.plan(4)
127
+
128
+ const fastify = Fastify()
129
+ fastify.get('/hello', () => {})
130
+ fastify.put('/hello', () => {})
131
+ fastify.get('/helicopter', () => {})
132
+
133
+ fastify.ready(() => {
134
+ const radixTree = fastify.printRoutes()
135
+ const flatTree = fastify.printRoutes({ commonPrefix: false })
136
+
137
+ const radixExpected = `└── /
138
+ ├── hel
139
+ │ ├── lo (GET)
140
+ │ └── icopter (GET)
141
+ └── hello (PUT)
142
+ `
143
+ const flatExpected = `└── / (-)
144
+ ├── helicopter (GET)
145
+ └── hello (GET, PUT)
146
+ `
147
+ t.equal(typeof radixTree, 'string')
148
+ t.equal(typeof flatTree, 'string')
149
+ t.equal(radixTree, radixExpected)
150
+ t.equal(flatTree, flatExpected)
151
+ })
152
+ })
153
+
154
+ test('pretty print - includeMeta, includeHooks', t => {
155
+ t.plan(6)
156
+
157
+ const fastify = Fastify()
158
+ const onTimeout = () => {}
159
+ fastify.get('/hello', () => {})
160
+ fastify.put('/hello', () => {})
161
+ fastify.get('/helicopter', () => {})
162
+
163
+ fastify.addHook('onRequest', () => {})
164
+ fastify.addHook('onTimeout', onTimeout)
165
+
166
+ fastify.ready(() => {
167
+ const radixTree = fastify.printRoutes({ includeHooks: true, includeMeta: ['errorHandler'] })
168
+ const flatTree = fastify.printRoutes({ commonPrefix: false, includeHooks: true, includeMeta: ['errorHandler'] })
169
+ const hooksOnly = fastify.printRoutes({ commonPrefix: false, includeHooks: true })
170
+
171
+ const radixExpected = `└── /
172
+ ├── hel
173
+ │ ├── lo (GET)
174
+ │ │ • (onTimeout) ["onTimeout()"]
175
+ │ │ • (onRequest) ["anonymous()"]
176
+ │ │ • (errorHandler) "defaultErrorHandler()"
177
+ │ └── icopter (GET)
178
+ │ • (onTimeout) ["onTimeout()"]
179
+ │ • (onRequest) ["anonymous()"]
180
+ │ • (errorHandler) "defaultErrorHandler()"
181
+ └── hello (PUT)
182
+ • (onTimeout) ["onTimeout()"]
183
+ • (onRequest) ["anonymous()"]
184
+ • (errorHandler) "defaultErrorHandler()"
185
+ `
186
+ const flatExpected = `└── / (-)
187
+ ├── helicopter (GET)
188
+ │ • (onTimeout) ["onTimeout()"]
189
+ │ • (onRequest) ["anonymous()"]
190
+ │ • (errorHandler) "defaultErrorHandler()"
191
+ └── hello (GET, PUT)
192
+ • (onTimeout) ["onTimeout()"]
193
+ • (onRequest) ["anonymous()"]
194
+ • (errorHandler) "defaultErrorHandler()"
195
+ `
196
+
197
+ const hooksOnlyExpected = `└── / (-)
198
+ ├── helicopter (GET)
199
+ │ • (onTimeout) ["onTimeout()"]
200
+ │ • (onRequest) ["anonymous()"]
201
+ └── hello (GET, PUT)
202
+ • (onTimeout) ["onTimeout()"]
203
+ • (onRequest) ["anonymous()"]
204
+ `
205
+ t.equal(typeof radixTree, 'string')
206
+ t.equal(typeof flatTree, 'string')
207
+ t.equal(typeof hooksOnlyExpected, 'string')
208
+ t.equal(radixTree, radixExpected)
209
+ t.equal(flatTree, flatExpected)
210
+ t.equal(hooksOnly, hooksOnlyExpected)
211
+ })
212
+ })
@@ -472,3 +472,56 @@ invalidErrorCodes.forEach((invalidCode) => {
472
472
  })
473
473
  })
474
474
  })
475
+
476
+ test('status code should be set to 500 and return an error json payload if route handler throws any non Error object expression', async t => {
477
+ t.plan(2)
478
+ const fastify = Fastify()
479
+
480
+ fastify.get('/', () => {
481
+ /* eslint-disable-next-line */
482
+ throw { foo: 'bar' }
483
+ })
484
+
485
+ // ----
486
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
487
+ t.equal(reply.statusCode, 500)
488
+ t.equal(JSON.parse(reply.body).foo, 'bar')
489
+ })
490
+
491
+ test('should preserve the status code set by the user if an expression is thrown in a sync route', async t => {
492
+ t.plan(2)
493
+ const fastify = Fastify()
494
+
495
+ fastify.get('/', (_, rep) => {
496
+ rep.status(501)
497
+
498
+ /* eslint-disable-next-line */
499
+ throw { foo: 'bar' }
500
+ })
501
+
502
+ // ----
503
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
504
+ t.equal(reply.statusCode, 501)
505
+ t.equal(JSON.parse(reply.body).foo, 'bar')
506
+ })
507
+
508
+ test('should trigger error handlers if a sync route throws any non-error object', async t => {
509
+ t.plan(3)
510
+
511
+ const fastify = Fastify()
512
+
513
+ fastify.get('/', () => {
514
+ /* eslint-disable-next-line */
515
+ throw { foo: 'bar' }
516
+ })
517
+
518
+ fastify.setErrorHandler(async (error) => {
519
+ t.ok(error)
520
+ return error
521
+ })
522
+
523
+ // ----
524
+ const reply = await fastify.inject({ method: 'GET', url: '/' })
525
+ t.equal(reply.statusCode, 500)
526
+ t.equal(JSON.parse(reply.body).foo, 'bar')
527
+ })
@@ -71,6 +71,18 @@ test('Fastify should throw for an invalid schema, printing the error route - bod
71
71
  })
72
72
  })
73
73
 
74
+ test('Fastify should throw for an invalid shorthand option type', t => {
75
+ t.plan(3)
76
+ try {
77
+ Fastify({ jsonShorthand: 'hello' })
78
+ t.fail()
79
+ } catch (e) {
80
+ t.equal(e.code, 'FST_ERR_INIT_OPTS_INVALID')
81
+ t.match(e.message, /should be boolean/)
82
+ t.pass()
83
+ }
84
+ })
85
+
74
86
  test('Should throw on unsupported method', t => {
75
87
  t.plan(1)
76
88
  const fastify = Fastify()
@@ -3,12 +3,15 @@ import fastify, {
3
3
  FastifyInstance,
4
4
  FastifyPlugin,
5
5
  FastifyPluginAsync,
6
- FastifyPluginCallback
6
+ FastifyPluginCallback,
7
+ LightMyRequestChain,
8
+ LightMyRequestResponse,
9
+ LightMyRequestCallback,
10
+ InjectOptions
7
11
  } from '../../fastify'
8
12
  import * as http from 'http'
9
13
  import * as https from 'https'
10
14
  import * as http2 from 'http2'
11
- import { Chain as LightMyRequestChain } from 'light-my-request'
12
15
  import { expectType, expectError, expectAssignable } from 'tsd'
13
16
  import { FastifyLoggerInstance } from '../../types/logger'
14
17
  import { Socket } from 'net'
@@ -21,12 +24,20 @@ expectType<FastifyInstance<http.Server, http.IncomingMessage, http.ServerRespons
21
24
  expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({ https: {} }))
22
25
  // http2 server
23
26
  expectType<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse> & PromiseLike<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>>>(fastify({ http2: true, http2SessionTimeout: 1000 }))
24
- expectType<FastifyInstance<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> & PromiseLike<FastifyInstance<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse>>>(fastify({ http2: true, https: {} }))
27
+ expectType<FastifyInstance<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse> & PromiseLike<FastifyInstance<http2.Http2SecureServer, http2.Http2ServerRequest, http2.Http2ServerResponse>>>(fastify({ http2: true, https: {}, http2SessionTimeout: 1000 }))
25
28
  expectType<LightMyRequestChain>(fastify({ http2: true, https: {} }).inject())
26
29
 
27
30
  expectError(fastify<http2.Http2Server>({ http2: false })) // http2 option must be true
28
31
  expectError(fastify<http2.Http2SecureServer>({ http2: false })) // http2 option must be true
29
32
 
33
+ // light-my-request
34
+ expectAssignable<InjectOptions>({ query: '' })
35
+ fastify({ http2: true, https: {} }).inject().then((resp) => {
36
+ expectAssignable<LightMyRequestResponse>(resp)
37
+ })
38
+ const lightMyRequestCallback: LightMyRequestCallback = (err: Error, response: LightMyRequestResponse) => {}
39
+ fastify({ http2: true, https: {} }).inject({}, lightMyRequestCallback)
40
+
30
41
  // server options
31
42
  expectAssignable<FastifyInstance<http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>>(fastify({ http2: true }))
32
43
  expectAssignable<FastifyInstance>(fastify({ ignoreTrailingSlash: true }))