fastify 3.7.0 → 3.8.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 (66) hide show
  1. package/docs/Ecosystem.md +6 -1
  2. package/docs/Hooks.md +0 -5
  3. package/docs/Recommendations.md +17 -0
  4. package/docs/Reply.md +2 -2
  5. package/docs/Request.md +1 -1
  6. package/docs/Routes.md +19 -1
  7. package/docs/Server.md +2 -0
  8. package/docs/Style-Guide.md +180 -0
  9. package/docs/TypeScript.md +359 -359
  10. package/examples/parser.js +1 -1
  11. package/fastify.js +8 -8
  12. package/lib/contentTypeParser.js +12 -11
  13. package/lib/context.js +4 -3
  14. package/lib/decorate.js +1 -1
  15. package/lib/fourOhFour.js +4 -4
  16. package/lib/handleRequest.js +5 -5
  17. package/lib/hooks.js +4 -4
  18. package/lib/logger.js +6 -6
  19. package/lib/pluginUtils.js +1 -1
  20. package/lib/reply.js +24 -21
  21. package/lib/reqIdGenFactory.js +2 -2
  22. package/lib/request.js +8 -5
  23. package/lib/route.js +9 -8
  24. package/lib/schemas.js +1 -1
  25. package/lib/server.js +5 -5
  26. package/lib/validation.js +8 -8
  27. package/package.json +8 -8
  28. package/test/404s.test.js +15 -15
  29. package/test/async-await.test.js +7 -7
  30. package/test/custom-parser-async.test.js +2 -2
  31. package/test/custom-parser.test.js +8 -8
  32. package/test/helper.js +1 -1
  33. package/test/hooks.test.js +6 -6
  34. package/test/http2/head.test.js +1 -1
  35. package/test/http2/plain.test.js +1 -1
  36. package/test/http2/secure-with-fallback.test.js +1 -1
  37. package/test/http2/secure.test.js +1 -1
  38. package/test/http2/unknown-http-method.test.js +1 -1
  39. package/test/https/https.test.js +2 -1
  40. package/test/inject.test.js +2 -2
  41. package/test/internals/all.test.js +1 -1
  42. package/test/internals/hookRunner.test.js +1 -1
  43. package/test/internals/logger.test.js +1 -1
  44. package/test/internals/reply.test.js +62 -7
  45. package/test/internals/request.test.js +23 -0
  46. package/test/listen.test.js +12 -0
  47. package/test/logger.test.js +10 -10
  48. package/test/nullable-validation.test.js +108 -0
  49. package/test/pretty-print.test.js +9 -14
  50. package/test/reply-error.test.js +2 -2
  51. package/test/route-hooks.test.js +10 -10
  52. package/test/stream.test.js +11 -11
  53. package/test/types/fastify.test-d.ts +1 -2
  54. package/test/types/logger.test-d.ts +1 -1
  55. package/test/types/route.test-d.ts +5 -0
  56. package/test/versioned-routes.test.js +55 -0
  57. package/types/.eslintrc.json +4 -1
  58. package/types/content-type-parser.d.ts +0 -15
  59. package/types/hooks.d.ts +138 -16
  60. package/types/instance.d.ts +81 -2
  61. package/types/logger.d.ts +1 -1
  62. package/types/plugin.d.ts +5 -7
  63. package/types/register.d.ts +8 -8
  64. package/types/route.d.ts +38 -58
  65. package/types/schema.d.ts +4 -4
  66. package/types/serverFactory.d.ts +9 -9
@@ -2,6 +2,7 @@
2
2
 
3
3
  const t = require('tap')
4
4
  const test = t.test
5
+ const sget = require('simple-get').concat
5
6
  const Fastify = require('..')
6
7
 
7
8
  test('nullable string', t => {
@@ -50,3 +51,110 @@ test('nullable string', t => {
50
51
  t.same(res.payload.hello, null)
51
52
  })
52
53
  })
54
+
55
+ test('object or null body', t => {
56
+ t.plan(5)
57
+
58
+ const fastify = Fastify()
59
+
60
+ fastify.route({
61
+ method: 'POST',
62
+ url: '/',
63
+ handler: (req, reply) => {
64
+ t.strictEqual(req.body, null)
65
+ reply.code(200).send({ requestBody: req.body })
66
+ },
67
+ schema: {
68
+ body: {
69
+ type: ['object', 'null'],
70
+ properties: {
71
+ hello: {
72
+ type: 'string',
73
+ format: 'email'
74
+ }
75
+ }
76
+ },
77
+ response: {
78
+ 200: {
79
+ type: 'object',
80
+ nullable: true,
81
+ properties: {
82
+ requestBody: {
83
+ type: 'string',
84
+ format: 'email',
85
+ nullable: true
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ })
92
+
93
+ fastify.listen(0, (err) => {
94
+ fastify.server.unref()
95
+ t.error(err)
96
+
97
+ sget({
98
+ method: 'POST',
99
+ url: 'http://localhost:' + fastify.server.address().port
100
+ }, (err, response, body) => {
101
+ t.error(err)
102
+ t.strictEqual(response.statusCode, 200)
103
+ t.deepEqual(JSON.parse(body), { requestBody: null })
104
+ })
105
+ })
106
+ })
107
+
108
+ test('nullable body', t => {
109
+ t.plan(5)
110
+
111
+ const fastify = Fastify()
112
+
113
+ fastify.route({
114
+ method: 'POST',
115
+ url: '/',
116
+ handler: (req, reply) => {
117
+ t.strictEqual(req.body, null)
118
+ reply.code(200).send({ requestBody: req.body })
119
+ },
120
+ schema: {
121
+ body: {
122
+ type: 'object',
123
+ nullable: true,
124
+ properties: {
125
+ hello: {
126
+ type: 'string',
127
+ format: 'email'
128
+ }
129
+ }
130
+ },
131
+ response: {
132
+ 200: {
133
+ type: 'object',
134
+ nullable: true,
135
+ properties: {
136
+ requestBody: {
137
+ type: 'string',
138
+ format: 'email',
139
+ nullable: true
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ })
146
+
147
+ fastify.listen(0, (err) => {
148
+ fastify.server.unref()
149
+ t.error(err)
150
+
151
+ sget({
152
+ method: 'POST',
153
+ url: 'http://localhost:' + fastify.server.address().port
154
+ }, (err, response, body) => {
155
+ t.error(err)
156
+ t.strictEqual(response.statusCode, 200)
157
+ t.deepEqual(JSON.parse(body), { requestBody: null })
158
+ })
159
+ })
160
+ })
@@ -39,10 +39,8 @@ test('pretty print - parametric routes', t => {
39
39
 
40
40
  const expected = `└── /
41
41
  ├── test (GET)
42
- │ └── /
43
- └── :hello (GET)
44
- └── hello/
45
- └── :world (GET)
42
+ │ └── /:hello (GET)
43
+ └── hello/:world (GET)
46
44
  `
47
45
 
48
46
  t.is(typeof tree, 'string')
@@ -62,12 +60,11 @@ test('pretty print - mixed parametric routes', t => {
62
60
  fastify.ready(() => {
63
61
  const tree = fastify.printRoutes()
64
62
 
65
- const expected = `└── /
66
- └── test (GET)
67
- └── /
68
- └── :hello (GET)
69
- :hello (POST)
70
- └── /world (GET)
63
+ const expected = `└── /test (GET)
64
+ └── /
65
+ └── :hello (GET)
66
+ :hello (POST)
67
+ └── /world (GET)
71
68
  `
72
69
 
73
70
  t.is(typeof tree, 'string')
@@ -88,10 +85,8 @@ test('pretty print - wildcard routes', t => {
88
85
 
89
86
  const expected = `└── /
90
87
  ├── test (GET)
91
- │ └── /
92
- └── * (GET)
93
- └── hello/
94
- └── * (GET)
88
+ │ └── /* (GET)
89
+ └── hello/* (GET)
95
90
  `
96
91
 
97
92
  t.is(typeof tree, 'string')
@@ -111,7 +111,7 @@ test('Should reply 400 on client error', t => {
111
111
  const client = net.connect(fastify.server.address().port)
112
112
  client.end('oooops!')
113
113
 
114
- var chunks = ''
114
+ let chunks = ''
115
115
  client.on('data', chunk => {
116
116
  chunks += chunk
117
117
  })
@@ -160,7 +160,7 @@ test('Should set the response from client error handler', t => {
160
160
  const client = net.connect(fastify.server.address().port)
161
161
  client.end('oooops!')
162
162
 
163
- var chunks = ''
163
+ let chunks = ''
164
164
  client.on('data', chunk => {
165
165
  chunks += chunk
166
166
  })
@@ -34,7 +34,7 @@ function testExecutionHook (hook) {
34
34
  payload: { hello: 'world' }
35
35
  }, (err, res) => {
36
36
  t.error(err)
37
- var payload = JSON.parse(res.payload)
37
+ const payload = JSON.parse(res.payload)
38
38
  t.deepEqual(payload, { hello: 'world' })
39
39
  })
40
40
  })
@@ -169,7 +169,7 @@ function testBeforeHandlerHook (hook) {
169
169
  payload: { hello: 'world' }
170
170
  }, (err, res) => {
171
171
  t.error(err)
172
- var payload = JSON.parse(res.payload)
172
+ const payload = JSON.parse(res.payload)
173
173
  t.deepEqual(payload, { hello: 'earth' })
174
174
  })
175
175
 
@@ -179,7 +179,7 @@ function testBeforeHandlerHook (hook) {
179
179
  payload: { hello: 'world' }
180
180
  }, (err, res) => {
181
181
  t.error(err)
182
- var payload = JSON.parse(res.payload)
182
+ const payload = JSON.parse(res.payload)
183
183
  t.deepEqual(payload, { hello: 'world' })
184
184
  })
185
185
  })
@@ -202,7 +202,7 @@ function testBeforeHandlerHook (hook) {
202
202
  payload: { hello: 'world' }
203
203
  }, (err, res) => {
204
204
  t.error(err)
205
- var payload = JSON.parse(res.payload)
205
+ const payload = JSON.parse(res.payload)
206
206
  t.equal(res.statusCode, 500)
207
207
  t.deepEqual(payload, {
208
208
  message: 'kaboom',
@@ -284,7 +284,7 @@ function testBeforeHandlerHook (hook) {
284
284
  payload: { hello: 'world' }
285
285
  }, (err, res) => {
286
286
  t.error(err)
287
- var payload = JSON.parse(res.payload)
287
+ const payload = JSON.parse(res.payload)
288
288
  t.equal(res.statusCode, 401)
289
289
  t.deepEqual(payload, {
290
290
  message: 'go away',
@@ -316,7 +316,7 @@ function testBeforeHandlerHook (hook) {
316
316
  payload: { hello: 'world' }
317
317
  }, (err, res) => {
318
318
  t.error(err)
319
- var payload = JSON.parse(res.payload)
319
+ const payload = JSON.parse(res.payload)
320
320
  t.deepEqual(payload, { foo: 43 })
321
321
  })
322
322
  })
@@ -343,7 +343,7 @@ function testBeforeHandlerHook (hook) {
343
343
  payload: { hello: 'world' }
344
344
  }, (err, res) => {
345
345
  t.error(err)
346
- var payload = JSON.parse(res.payload)
346
+ const payload = JSON.parse(res.payload)
347
347
  t.deepEqual(payload, { foo: 43 })
348
348
  })
349
349
  })
@@ -385,7 +385,7 @@ test('preValidation option should be called before preHandler hook', t => {
385
385
  payload: { hello: 'world' }
386
386
  }, (err, res) => {
387
387
  t.error(err)
388
- var payload = JSON.parse(res.payload)
388
+ const payload = JSON.parse(res.payload)
389
389
  t.deepEqual(payload, { hello: 'world' })
390
390
  })
391
391
  })
@@ -436,7 +436,7 @@ test('preParsing option should be called before preValidation hook', t => {
436
436
  payload: { hello: 'world' }
437
437
  }, (err, res) => {
438
438
  t.error(err)
439
- var payload = JSON.parse(res.payload)
439
+ const payload = JSON.parse(res.payload)
440
440
  t.deepEqual(payload, { hello: 'world' })
441
441
  })
442
442
  })
@@ -492,7 +492,7 @@ test('onRequest option should be called before preParsing', t => {
492
492
  payload: { hello: 'world' }
493
493
  }, (err, res) => {
494
494
  t.error(err)
495
- var payload = JSON.parse(res.payload)
495
+ const payload = JSON.parse(res.payload)
496
496
  t.deepEqual(payload, { hello: 'world' })
497
497
  })
498
498
  })
@@ -168,8 +168,8 @@ test('Destroying streams prematurely', t => {
168
168
  fastify.get('/', function (request, reply) {
169
169
  t.pass('Received request')
170
170
 
171
- var sent = false
172
- var reallyLongStream = new stream.Readable({
171
+ let sent = false
172
+ const reallyLongStream = new stream.Readable({
173
173
  read: function () {
174
174
  if (!sent) {
175
175
  this.push(Buffer.from('hello\n'))
@@ -185,7 +185,7 @@ test('Destroying streams prematurely', t => {
185
185
  t.error(err)
186
186
  fastify.server.unref()
187
187
 
188
- var port = fastify.server.address().port
188
+ const port = fastify.server.address().port
189
189
 
190
190
  http.get(`http://localhost:${port}`, function (response) {
191
191
  t.strictEqual(response.statusCode, 200)
@@ -230,8 +230,8 @@ test('Destroying streams prematurely should call close method', t => {
230
230
  fastify.get('/', function (request, reply) {
231
231
  t.pass('Received request')
232
232
 
233
- var sent = false
234
- var reallyLongStream = new stream.Readable({
233
+ let sent = false
234
+ const reallyLongStream = new stream.Readable({
235
235
  read: function () {
236
236
  if (!sent) {
237
237
  this.push(Buffer.from('hello\n'))
@@ -248,7 +248,7 @@ test('Destroying streams prematurely should call close method', t => {
248
248
  t.error(err)
249
249
  fastify.server.unref()
250
250
 
251
- var port = fastify.server.address().port
251
+ const port = fastify.server.address().port
252
252
 
253
253
  http.get(`http://localhost:${port}`, function (response) {
254
254
  t.strictEqual(response.statusCode, 200)
@@ -292,8 +292,8 @@ test('Destroying streams prematurely should call abort method', t => {
292
292
  fastify.get('/', function (request, reply) {
293
293
  t.pass('Received request')
294
294
 
295
- var sent = false
296
- var reallyLongStream = new stream.Readable({
295
+ let sent = false
296
+ const reallyLongStream = new stream.Readable({
297
297
  read: function () {
298
298
  if (!sent) {
299
299
  this.push(Buffer.from('hello\n'))
@@ -311,7 +311,7 @@ test('Destroying streams prematurely should call abort method', t => {
311
311
  t.error(err)
312
312
  fastify.server.unref()
313
313
 
314
- var port = fastify.server.address().port
314
+ const port = fastify.server.address().port
315
315
 
316
316
  http.get(`http://localhost:${port}`, function (response) {
317
317
  t.strictEqual(response.statusCode, 200)
@@ -358,7 +358,7 @@ test('return a 404 if the stream emits a 404 error', t => {
358
358
  fastify.get('/', function (request, reply) {
359
359
  t.pass('Received request')
360
360
 
361
- var reallyLongStream = new Readable({
361
+ const reallyLongStream = new Readable({
362
362
  read: function () {
363
363
  setImmediate(() => {
364
364
  this.emit('error', new errors.NotFound())
@@ -373,7 +373,7 @@ test('return a 404 if the stream emits a 404 error', t => {
373
373
  t.error(err)
374
374
  fastify.server.unref()
375
375
 
376
- var port = fastify.server.address().port
376
+ const port = fastify.server.address().port
377
377
 
378
378
  sget(`http://localhost:${port}`, function (err, response) {
379
379
  t.error(err)
@@ -2,8 +2,7 @@ import fastify, {
2
2
  FastifyInstance,
3
3
  FastifyPlugin,
4
4
  FastifyPluginAsync,
5
- FastifyPluginCallback,
6
- FastifyPluginOptions
5
+ FastifyPluginCallback
7
6
  } from '../../fastify'
8
7
  import * as http from 'http'
9
8
  import * as https from 'https'
@@ -20,7 +20,7 @@ class CustomLoggerImpl implements CustomLogger {
20
20
  customMethod (msg: string, ...args: unknown[]) { console.log(msg, args) }
21
21
 
22
22
  // Implementation signature must be compatible with all overloads of FastifyLogFn
23
- info (arg1: string | object, arg2?: string | unknown, ...args: unknown[]): void {
23
+ info (arg1: string | Record<string, unknown>, arg2?: string | unknown, ...args: unknown[]): void {
24
24
  console.log(arg1, arg2, ...args)
25
25
  }
26
26
 
@@ -69,6 +69,7 @@ type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete'
69
69
  url: '/',
70
70
  method: method as HTTPMethods,
71
71
  config: { foo: 'bar', bar: 100 },
72
+ prefixTrailingSlash: 'slash',
72
73
  onRequest: (req, res, done) => { // these handlers are tested in `hooks.test-d.ts`
73
74
  expectType<BodyInterface>(req.body)
74
75
  expectType<QuerystringInterface>(req.query)
@@ -179,3 +180,7 @@ expectType<FastifyInstance>(fastify().route({
179
180
  handler: routeHandler,
180
181
  schemaErrorFormatter: (errors, dataVar) => new Error('')
181
182
  }))
183
+
184
+ expectError(fastify().route({
185
+ prefixTrailingSlash: true // Not a valid value
186
+ }))
@@ -6,6 +6,7 @@ const Fastify = require('..')
6
6
  const sget = require('simple-get').concat
7
7
  const http = require('http')
8
8
  const split = require('split2')
9
+ const append = require('vary').append
9
10
 
10
11
  test('Should register a versioned route', t => {
11
12
  t.plan(11)
@@ -476,3 +477,57 @@ test('Should register a versioned route with custome versioning strategy', t =>
476
477
  t.strictEqual(res.statusCode, 404)
477
478
  })
478
479
  })
480
+
481
+ test('Vary header check (for documentation example)', t => {
482
+ t.plan(8)
483
+ const fastify = Fastify()
484
+ fastify.addHook('onSend', async (req, reply) => {
485
+ if (req.headers['accept-version']) { // or the custom header you are using
486
+ let value = reply.getHeader('Vary') || ''
487
+ const header = Array.isArray(value) ? value.join(', ') : String(value)
488
+ if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using
489
+ reply.header('Vary', value)
490
+ }
491
+ }
492
+ })
493
+
494
+ fastify.route({
495
+ method: 'GET',
496
+ url: '/',
497
+ handler: (req, reply) => {
498
+ reply.send({ hello: 'world' })
499
+ }
500
+ })
501
+
502
+ fastify.route({
503
+ method: 'GET',
504
+ url: '/',
505
+ version: '1.2.0',
506
+ handler: (req, reply) => {
507
+ reply.send({ hello: 'world' })
508
+ }
509
+ })
510
+
511
+ fastify.inject({
512
+ method: 'GET',
513
+ url: '/',
514
+ headers: {
515
+ 'Accept-Version': '1.x'
516
+ }
517
+ }, (err, res) => {
518
+ t.error(err)
519
+ t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
520
+ t.strictEqual(res.statusCode, 200)
521
+ t.strictEqual(res.headers.vary, 'Accept-Version')
522
+ })
523
+
524
+ fastify.inject({
525
+ method: 'GET',
526
+ url: '/'
527
+ }, (err, res) => {
528
+ t.error(err)
529
+ t.deepEqual(JSON.parse(res.payload), { hello: 'world' })
530
+ t.strictEqual(res.statusCode, 200)
531
+ t.strictEqual(res.headers.vary, undefined)
532
+ })
533
+ })
@@ -31,11 +31,14 @@
31
31
  "files": ["*.test-d.ts"],
32
32
  "rules": {
33
33
  "no-unused-vars": "off",
34
- "handle-callback-err": "off",
34
+ "node/handle-callback-err": "off",
35
35
  "@typescript-eslint/no-empty-function": "off",
36
36
  "@typescript-eslint/explicit-function-return-type": "off",
37
37
  "@typescript-eslint/no-unused-vars": "off",
38
38
  "@typescript-eslint/no-non-null-assertion": "off"
39
+ },
40
+ "globals": {
41
+ "NodeJS": "readonly"
39
42
  }
40
43
  }
41
44
  ]
@@ -1,6 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
- import { Buffer } from 'buffer'
4
1
  import { RawServerBase, RawServerDefault, RawRequestDefaultExpression } from './utils'
5
2
  import { FastifyRequest } from './request'
6
3
  import { RouteGenericInterface } from './route'
@@ -42,19 +39,7 @@ export interface AddContentTypeParser<
42
39
  },
43
40
  parser: FastifyContentTypeParser<RawServer, RawRequest>
44
41
  ): void;
45
- }
46
-
47
- export interface AddContentTypeParser<
48
- RawServer extends RawServerBase = RawServerDefault,
49
- RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>
50
- > {
51
42
  (contentType: string | string[], parser: FastifyContentTypeParser<RawServer, RawRequest>): void;
52
- }
53
-
54
- export interface AddContentTypeParser<
55
- RawServer extends RawServerBase = RawServerDefault,
56
- RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>
57
- >{
58
43
  <parseAs extends string | Buffer>(
59
44
  contentType: string | string[],
60
45
  opts: {