fastify 3.9.2 → 3.12.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 (92) hide show
  1. package/GOVERNANCE.md +1 -1
  2. package/README.md +12 -8
  3. package/SECURITY.md +3 -3
  4. package/docs/ContentTypeParser.md +1 -1
  5. package/docs/Ecosystem.md +16 -6
  6. package/docs/Encapsulation.md +5 -2
  7. package/docs/Fluent-Schema.md +4 -4
  8. package/docs/Getting-Started.md +1 -1
  9. package/docs/Hooks.md +28 -1
  10. package/docs/Lifecycle.md +8 -1
  11. package/docs/Middleware.md +5 -4
  12. package/docs/Reply.md +13 -4
  13. package/docs/Routes.md +4 -3
  14. package/docs/Server.md +78 -4
  15. package/docs/Serverless.md +23 -51
  16. package/docs/TypeScript.md +35 -18
  17. package/docs/Validation-and-Serialization.md +4 -4
  18. package/docs/Write-Plugin.md +4 -4
  19. package/examples/hooks-benchmark.js +12 -12
  20. package/examples/hooks.js +16 -16
  21. package/examples/plugin.js +2 -2
  22. package/examples/route-prefix.js +4 -4
  23. package/fastify.d.ts +16 -1
  24. package/fastify.js +33 -16
  25. package/isolate-0x426d1e0-1227-v8.log +4019 -0
  26. package/isolate-0x4d4c7e0-1988-v8.log +4081 -0
  27. package/lib/errors.js +6 -0
  28. package/lib/headRoute.js +31 -0
  29. package/lib/pluginOverride.js +5 -5
  30. package/lib/pluginUtils.js +7 -6
  31. package/lib/reply.js +14 -2
  32. package/lib/reqIdGenFactory.js +5 -0
  33. package/lib/request.js +1 -1
  34. package/lib/route.js +66 -41
  35. package/lib/schema-compilers.js +5 -3
  36. package/lib/schema-controller.js +106 -0
  37. package/lib/schemas.js +14 -24
  38. package/lib/server.js +1 -0
  39. package/lib/symbols.js +1 -3
  40. package/lib/warnings.js +2 -0
  41. package/lib/wrapThenable.js +2 -1
  42. package/package.json +25 -21
  43. package/test/404s.test.js +120 -120
  44. package/test/500s.test.js +8 -8
  45. package/test/async-await.test.js +29 -1
  46. package/test/close.test.js +8 -8
  47. package/test/context-config.test.js +52 -0
  48. package/test/custom-parser.test.js +8 -8
  49. package/test/decorator.test.js +49 -49
  50. package/test/default-route.test.js +43 -0
  51. package/test/fastify-instance.test.js +2 -2
  52. package/test/fluent-schema.test.js +3 -3
  53. package/test/handler-context.test.js +2 -2
  54. package/test/hooks-async.test.js +3 -3
  55. package/test/hooks.on-ready.test.js +12 -12
  56. package/test/hooks.test.js +75 -32
  57. package/test/http2/closing.test.js +23 -1
  58. package/test/inject.test.js +6 -6
  59. package/test/input-validation.js +2 -2
  60. package/test/internals/hookRunner.test.js +50 -50
  61. package/test/internals/reply.test.js +47 -22
  62. package/test/internals/request.test.js +3 -9
  63. package/test/internals/version.test.js +2 -2
  64. package/test/logger.test.js +30 -30
  65. package/test/middleware.test.js +4 -4
  66. package/test/plugin.helper.js +2 -2
  67. package/test/plugin.test.js +154 -99
  68. package/test/register.test.js +11 -11
  69. package/test/request-error.test.js +2 -2
  70. package/test/route-hooks.test.js +24 -24
  71. package/test/route-prefix.test.js +81 -52
  72. package/test/route.test.js +568 -0
  73. package/test/schema-feature.test.js +168 -38
  74. package/test/schema-serialization.test.js +4 -4
  75. package/test/schema-special-usage.test.js +136 -0
  76. package/test/schema-validation.test.js +7 -7
  77. package/test/skip-reply-send.test.js +315 -0
  78. package/test/stream.test.js +6 -6
  79. package/test/throw.test.js +4 -4
  80. package/test/types/instance.test-d.ts +5 -3
  81. package/test/types/plugin.test-d.ts +7 -7
  82. package/test/types/reply.test-d.ts +1 -0
  83. package/test/types/schema.test-d.ts +15 -0
  84. package/test/validation-error-handling.test.js +5 -5
  85. package/test/versioned-routes.test.js +1 -1
  86. package/types/content-type-parser.d.ts +1 -1
  87. package/types/instance.d.ts +6 -3
  88. package/types/plugin.d.ts +1 -1
  89. package/types/reply.d.ts +1 -0
  90. package/types/route.d.ts +8 -2
  91. package/types/schema.d.ts +3 -0
  92. package/test/skip-reply-send.js +0 -98
@@ -0,0 +1,315 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const split = require('split2')
5
+ const net = require('net')
6
+ const Fastify = require('../fastify')
7
+
8
+ process.removeAllListeners('warning')
9
+
10
+ const lifecycleHooks = [
11
+ 'onRequest',
12
+ 'preParsing',
13
+ 'preValidation',
14
+ 'preHandler',
15
+ 'preSerialization',
16
+ 'onSend',
17
+ 'onTimeout',
18
+ 'onResponse',
19
+ 'onError'
20
+ ]
21
+
22
+ test('skip automatic reply.send() with reply.sent = true and a body', (t) => {
23
+ const stream = split(JSON.parse)
24
+ const app = Fastify({
25
+ logger: {
26
+ stream: stream
27
+ }
28
+ })
29
+
30
+ stream.on('data', (line) => {
31
+ t.notEqual(line.level, 40) // there are no errors
32
+ t.notEqual(line.level, 50) // there are no errors
33
+ })
34
+
35
+ app.get('/', (req, reply) => {
36
+ reply.sent = true
37
+ reply.raw.end('hello world')
38
+
39
+ return Promise.resolve('this will be skipped')
40
+ })
41
+
42
+ return app.inject({
43
+ method: 'GET',
44
+ url: '/'
45
+ }).then((res) => {
46
+ t.equal(res.statusCode, 200)
47
+ t.equal(res.body, 'hello world')
48
+ })
49
+ })
50
+
51
+ test('skip automatic reply.send() with reply.sent = true and no body', (t) => {
52
+ const stream = split(JSON.parse)
53
+ const app = Fastify({
54
+ logger: {
55
+ stream: stream
56
+ }
57
+ })
58
+
59
+ stream.on('data', (line) => {
60
+ t.notEqual(line.level, 40) // there are no error
61
+ t.notEqual(line.level, 50) // there are no error
62
+ })
63
+
64
+ app.get('/', (req, reply) => {
65
+ reply.sent = true
66
+ reply.raw.end('hello world')
67
+
68
+ return Promise.resolve()
69
+ })
70
+
71
+ return app.inject({
72
+ method: 'GET',
73
+ url: '/'
74
+ }).then((res) => {
75
+ t.equal(res.statusCode, 200)
76
+ t.equal(res.body, 'hello world')
77
+ })
78
+ })
79
+
80
+ test('skip automatic reply.send() with reply.sent = true and an error', (t) => {
81
+ const stream = split(JSON.parse)
82
+ const app = Fastify({
83
+ logger: {
84
+ stream: stream
85
+ }
86
+ })
87
+
88
+ let errorSeen = false
89
+
90
+ stream.on('data', (line) => {
91
+ if (line.level === 50) {
92
+ errorSeen = true
93
+ t.equal(line.err.message, 'kaboom')
94
+ t.equal(line.msg, 'Promise errored, but reply.sent = true was set')
95
+ }
96
+ })
97
+
98
+ app.get('/', (req, reply) => {
99
+ reply.sent = true
100
+ reply.raw.end('hello world')
101
+
102
+ return Promise.reject(new Error('kaboom'))
103
+ })
104
+
105
+ return app.inject({
106
+ method: 'GET',
107
+ url: '/'
108
+ }).then((res) => {
109
+ t.equal(errorSeen, true)
110
+ t.equal(res.statusCode, 200)
111
+ t.equal(res.body, 'hello world')
112
+ })
113
+ })
114
+
115
+ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
116
+ const idx = hookOrHandler === 'handler' ? lifecycleHooks.indexOf('preHandler') : lifecycleHooks.indexOf(hookOrHandler)
117
+ const previousHooks = lifecycleHooks.slice(0, idx)
118
+ const nextHooks = lifecycleHooks.slice(idx + 1)
119
+
120
+ test(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, t => {
121
+ t.plan(4)
122
+ const test = t.test
123
+
124
+ test('Sending a response using reply.raw => onResponse hook is called', t => {
125
+ const stream = split(JSON.parse)
126
+ const app = Fastify({
127
+ logger: {
128
+ stream: stream
129
+ }
130
+ })
131
+
132
+ stream.on('data', (line) => {
133
+ t.notEqual(line.level, 40) // there are no errors
134
+ t.notEqual(line.level, 50) // there are no errors
135
+ })
136
+
137
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
138
+
139
+ if (hookOrHandler === 'handler') {
140
+ app.get('/', (req, reply) => {
141
+ reply.hijack()
142
+ reply.raw.end(`hello from ${hookOrHandler}`)
143
+ })
144
+ } else {
145
+ app.addHook(hookOrHandler, async (req, reply) => {
146
+ reply.hijack()
147
+ reply.raw.end(`hello from ${hookOrHandler}`)
148
+ })
149
+ app.get('/', (req, reply) => t.fail('Handler should not be called'))
150
+ }
151
+
152
+ nextHooks.forEach(h => {
153
+ if (h === 'onResponse') {
154
+ app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))
155
+ } else {
156
+ app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))
157
+ }
158
+ })
159
+
160
+ return app.inject({
161
+ method: 'GET',
162
+ url: '/'
163
+ }).then((res) => {
164
+ t.equal(res.statusCode, 200)
165
+ t.equal(res.body, `hello from ${hookOrHandler}`)
166
+ })
167
+ })
168
+
169
+ test('Sending a response using req.socket => onResponse not called', t => {
170
+ const stream = split(JSON.parse)
171
+ const app = Fastify({
172
+ logger: {
173
+ stream: stream
174
+ }
175
+ })
176
+ t.tearDown(() => app.close())
177
+
178
+ stream.on('data', (line) => {
179
+ t.notEqual(line.level, 40) // there are no errors
180
+ t.notEqual(line.level, 50) // there are no errors
181
+ })
182
+
183
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
184
+
185
+ if (hookOrHandler === 'handler') {
186
+ app.get('/', (req, reply) => {
187
+ reply.hijack()
188
+ req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
189
+ req.socket.write(`hello from ${hookOrHandler}`)
190
+ req.socket.end()
191
+ })
192
+ } else {
193
+ app.addHook(hookOrHandler, async (req, reply) => {
194
+ reply.hijack()
195
+ req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
196
+ req.socket.write(`hello from ${hookOrHandler}`)
197
+ req.socket.end()
198
+ })
199
+ app.get('/', (req, reply) => t.fail('Handler should not be called'))
200
+ }
201
+
202
+ nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
203
+
204
+ app.listen(0, err => {
205
+ t.error(err)
206
+ const client = net.createConnection({ port: (app.server.address()).port }, () => {
207
+ client.write('GET / HTTP/1.1\r\n\r\n')
208
+ client.once('data', data => {
209
+ t.match(data.toString(), new RegExp(`hello from ${hookOrHandler}`, 'i'))
210
+ client.end(() => t.end())
211
+ })
212
+ })
213
+ })
214
+ })
215
+
216
+ test('Throwing an error doesnt trigger any hooks', t => {
217
+ const stream = split(JSON.parse)
218
+ const app = Fastify({
219
+ logger: {
220
+ stream: stream
221
+ }
222
+ })
223
+ t.tearDown(() => app.close())
224
+
225
+ let errorSeen = false
226
+ stream.on('data', (line) => {
227
+ if (hookOrHandler === 'handler') {
228
+ if (line.level === 40) {
229
+ errorSeen = true
230
+ t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
231
+ }
232
+ } else {
233
+ t.notEqual(line.level, 40) // there are no errors
234
+ t.notEqual(line.level, 50) // there are no errors
235
+ }
236
+ })
237
+
238
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
239
+
240
+ if (hookOrHandler === 'handler') {
241
+ app.get('/', (req, reply) => {
242
+ reply.hijack()
243
+ throw new Error('This wil be skipped')
244
+ })
245
+ } else {
246
+ app.addHook(hookOrHandler, async (req, reply) => {
247
+ reply.hijack()
248
+ throw new Error('This wil be skipped')
249
+ })
250
+ app.get('/', (req, reply) => t.fail('Handler should not be called'))
251
+ }
252
+
253
+ nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
254
+
255
+ return Promise.race([
256
+ app.inject({ method: 'GET', url: '/' }),
257
+ new Promise((resolve, reject) => setTimeout(resolve, 1000))
258
+ ]).then((err, res) => {
259
+ t.error(err)
260
+ if (hookOrHandler === 'handler') {
261
+ t.equal(errorSeen, true)
262
+ }
263
+ })
264
+ })
265
+
266
+ test('Calling reply.send() after hijacking logs a warning', t => {
267
+ const stream = split(JSON.parse)
268
+ const app = Fastify({
269
+ logger: {
270
+ stream: stream
271
+ }
272
+ })
273
+
274
+ let errorSeen = false
275
+
276
+ stream.on('data', (line) => {
277
+ if (line.level === 40) {
278
+ errorSeen = true
279
+ t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
280
+ }
281
+ })
282
+
283
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
284
+
285
+ if (hookOrHandler === 'handler') {
286
+ app.get('/', (req, reply) => {
287
+ reply.hijack()
288
+ reply.send('hello from reply.send()')
289
+ })
290
+ } else {
291
+ app.addHook(hookOrHandler, async (req, reply) => {
292
+ reply.hijack()
293
+ reply.send('hello from reply.send()')
294
+ })
295
+ app.get('/', (req, reply) => t.fail('Handler should not be called'))
296
+ }
297
+
298
+ nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
299
+
300
+ return Promise.race([
301
+ app.inject({ method: 'GET', url: '/' }),
302
+ new Promise((resolve, reject) => setTimeout(resolve, 1000))
303
+ ]).then((err, res) => {
304
+ t.error(err)
305
+ t.equal(errorSeen, true)
306
+ })
307
+ })
308
+ })
309
+ }
310
+
311
+ testHandlerOrBeforeHandlerHook(test, 'onRequest')
312
+ testHandlerOrBeforeHandlerHook(test, 'preParsing')
313
+ testHandlerOrBeforeHandlerHook(test, 'preValidation')
314
+ testHandlerOrBeforeHandlerHook(test, 'preHandler')
315
+ testHandlerOrBeforeHandlerHook(test, 'handler')
@@ -58,10 +58,10 @@ test('should trigger the onSend hook', t => {
58
58
  reply.send(fs.createReadStream(__filename, 'utf8'))
59
59
  })
60
60
 
61
- fastify.addHook('onSend', (req, reply, payload, next) => {
61
+ fastify.addHook('onSend', (req, reply, payload, done) => {
62
62
  t.ok(payload._readableState)
63
63
  reply.header('Content-Type', 'application/javascript')
64
- next()
64
+ done()
65
65
  })
66
66
 
67
67
  fastify.inject({
@@ -83,7 +83,7 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir
83
83
  })
84
84
 
85
85
  let counter = 0
86
- fastify.addHook('onSend', (req, reply, payload, next) => {
86
+ fastify.addHook('onSend', (req, reply, payload, done) => {
87
87
  if (counter === 0) {
88
88
  t.ok(payload._readableState)
89
89
  } else if (counter === 1) {
@@ -91,7 +91,7 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir
91
91
  t.strictEqual(error.statusCode, 500)
92
92
  }
93
93
  counter++
94
- next()
94
+ done()
95
95
  })
96
96
 
97
97
  fastify.listen(0, err => {
@@ -114,7 +114,7 @@ test('onSend hook stream', t => {
114
114
  reply.send({ hello: 'world' })
115
115
  })
116
116
 
117
- fastify.addHook('onSend', (req, reply, payload, next) => {
117
+ fastify.addHook('onSend', (req, reply, payload, done) => {
118
118
  const gzStream = zlib.createGzip()
119
119
 
120
120
  reply.header('Content-Encoding', 'gzip')
@@ -123,7 +123,7 @@ test('onSend hook stream', t => {
123
123
  gzStream,
124
124
  t.error
125
125
  )
126
- next(null, gzStream)
126
+ done(null, gzStream)
127
127
  })
128
128
 
129
129
  fastify.inject({
@@ -60,9 +60,9 @@ test('Fastify should throw for an invalid schema, printing the error route - bod
60
60
  }
61
61
 
62
62
  const fastify = Fastify()
63
- fastify.register((instance, opts, next) => {
63
+ fastify.register((instance, opts, done) => {
64
64
  instance.post('/form', { schema: { body: badSchema } }, () => {})
65
- next()
65
+ done()
66
66
  }, { prefix: 'hello' })
67
67
 
68
68
  fastify.ready(err => {
@@ -155,11 +155,11 @@ test('Should not throw on duplicate decorator encapsulation', t => {
155
155
 
156
156
  fastify.decorate('foo2', foo2Obj)
157
157
 
158
- fastify.register(function (fastify, opts, next) {
158
+ fastify.register(function (fastify, opts, done) {
159
159
  t.notThrow(() => {
160
160
  fastify.decorate('foo2', foo2Obj)
161
161
  })
162
- next()
162
+ done()
163
163
  })
164
164
 
165
165
  fastify.ready()
@@ -1,4 +1,4 @@
1
- import fastify, { FastifyError, FastifyInstance } from '../../fastify'
1
+ import fastify, { FastifyError, FastifyInstance, ValidationResult } from '../../fastify'
2
2
  import { expectAssignable, expectError, expectType } from 'tsd'
3
3
 
4
4
  const server = fastify()
@@ -28,6 +28,8 @@ expectAssignable<FastifyInstance>(
28
28
  })
29
29
  )
30
30
 
31
+ expectType<ValidationResult[] | undefined>(FastifyError().validation)
32
+
31
33
  function fastifyErrorHandler (this: FastifyInstance, error: FastifyError) {}
32
34
  server.setErrorHandler(fastifyErrorHandler)
33
35
 
@@ -43,8 +45,8 @@ server.setReplySerializer(function (payload, statusCode) {
43
45
  return 'serialized'
44
46
  })
45
47
 
46
- function invalidReplySerialzer (payload: number, statusCode: string) {}
47
- expectError(server.setReplySerializer(invalidReplySerialzer))
48
+ function invalidReplySerializer (payload: number, statusCode: string) {}
49
+ expectError(server.setReplySerializer(invalidReplySerializer))
48
50
 
49
51
  function serializerWithInvalidReturn (payload: unknown, statusCode: number) {}
50
52
  expectError(server.setReplySerializer(serializerWithInvalidReturn))
@@ -10,10 +10,10 @@ interface TestOptions extends FastifyPluginOptions {
10
10
  option1: string;
11
11
  option2: boolean;
12
12
  }
13
- const testPluginOpts: FastifyPluginCallback<TestOptions> = function (instance, opts, next) { }
13
+ const testPluginOpts: FastifyPluginCallback<TestOptions> = function (instance, opts, done) { }
14
14
  const testPluginOptsAsync: FastifyPluginAsync<TestOptions> = async function (instance, opts) { }
15
15
 
16
- const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, next: (error?: FastifyError) => void) => { }
16
+ const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { }
17
17
  const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { }
18
18
 
19
19
  expectError(fastify().register(testPluginOpts, {})) // error because missing required options from generic declaration
@@ -22,14 +22,14 @@ expectError(fastify().register(testPluginOptsAsync, {})) // error because missin
22
22
  expectAssignable<FastifyInstance>(fastify().register(testPluginOpts, { option1: '', option2: true }))
23
23
  expectAssignable<FastifyInstance>(fastify().register(testPluginOptsAsync, { option1: '', option2: true }))
24
24
 
25
- expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, next) { }))
26
- expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, next) { }, () => { }))
27
- expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, next) { }, { logLevel: 'info', prefix: 'foobar' }))
25
+ expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, done) { }))
26
+ expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, done) { }, () => { }))
27
+ expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, done) { }, { logLevel: 'info', prefix: 'foobar' }))
28
28
 
29
29
  expectAssignable<FastifyInstance>(fastify().register(import('./dummy-plugin')))
30
30
  expectAssignable<FastifyInstance>(fastify().register(import('./dummy-plugin'), { foo: 1 }))
31
31
 
32
- const testPluginCallback: FastifyPluginCallback = function (instance, opts, next) { }
32
+ const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { }
33
33
  expectAssignable<FastifyInstance>(fastify().register(testPluginCallback, {}))
34
34
 
35
35
  const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { }
@@ -39,7 +39,7 @@ expectAssignable<FastifyInstance>(fastify().register(function (instance, opts):
39
39
  expectAssignable<FastifyInstance>(fastify().register(async function (instance, opts) { }, () => { }))
40
40
  expectAssignable<FastifyInstance>(fastify().register(async function (instance, opts) { }, { logLevel: 'info', prefix: 'foobar' }))
41
41
 
42
- expectError(fastify().register(function (instance, opts, next) { }, { logLevel: '' })) // must use a valid logLevel
42
+ expectError(fastify().register(function (instance, opts, done) { }, { logLevel: '' })) // must use a valid logLevel
43
43
 
44
44
  const httpsServer = fastify({ https: {} })
45
45
  expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(httpsServer)
@@ -21,6 +21,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) {
21
21
  expectType<(key: string) => void>(reply.removeHeader)
22
22
  expectType<(key: string) => boolean>(reply.hasHeader)
23
23
  expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect)
24
+ expectType<() => FastifyReply>(reply.hijack)
24
25
  expectType<() => void>(reply.callNotFound)
25
26
  expectType<() => number>(reply.getResponseTime)
26
27
  expectType<(contentType: string) => FastifyReply>(reply.type)
@@ -36,6 +36,21 @@ expectAssignable<FastifyInstance>(server.setValidatorCompiler(({ schema }) => {
36
36
  return new Ajv().compile(schema)
37
37
  }))
38
38
 
39
+ expectAssignable<FastifyInstance>(server.setSerializerCompiler(() => {
40
+ return data => JSON.stringify(data)
41
+ }))
42
+
43
+ expectAssignable<FastifyInstance>(server.post('/test', {
44
+ validatorCompiler: ({ schema }) => {
45
+ return data => {
46
+ if (!data || data.constructor !== Object) {
47
+ return { error: new Error('value is not an object') }
48
+ }
49
+ return { value: data }
50
+ }
51
+ }
52
+ }, async req => req.body))
53
+
39
54
  expectError(server.get(
40
55
  '/unknown-schema-prop',
41
56
  {
@@ -219,7 +219,7 @@ test('should respect when attachValidation is explicitly set to false', t => {
219
219
  })
220
220
  })
221
221
 
222
- test('Attached validation error should take precendence over setErrorHandler', t => {
222
+ test('Attached validation error should take precedence over setErrorHandler', t => {
223
223
  t.plan(3)
224
224
 
225
225
  const fastify = Fastify()
@@ -692,7 +692,7 @@ test('plugin override', t => {
692
692
  }
693
693
  })
694
694
 
695
- fastify.register((instance, opts, next) => {
695
+ fastify.register((instance, opts, done) => {
696
696
  instance.setSchemaErrorFormatter((errors, dataVar) => {
697
697
  return new Error('C')
698
698
  })
@@ -708,12 +708,12 @@ test('plugin override', t => {
708
708
 
709
709
  instance.post('/c', { schema }, echoBody)
710
710
 
711
- instance.register((subinstance, opts, next) => {
711
+ instance.register((subinstance, opts, done) => {
712
712
  subinstance.post('/stillC', { schema }, echoBody)
713
- next()
713
+ done()
714
714
  })
715
715
 
716
- next()
716
+ done()
717
717
  })
718
718
 
719
719
  fastify.post('/b', { schema }, echoBody)
@@ -404,7 +404,7 @@ test('test log stream', t => {
404
404
  })
405
405
  })
406
406
 
407
- test('Should register a versioned route with custome versioning strategy', t => {
407
+ test('Should register a versioned route with custom versioning strategy', t => {
408
408
  t.plan(8)
409
409
 
410
410
  const versioning = {
@@ -5,7 +5,7 @@ import { RouteGenericInterface } from './route'
5
5
  type ContentTypeParserDoneFunction = (err: Error | null, body?: any) => void
6
6
 
7
7
  /**
8
- * Body parser method that operatoes on request body
8
+ * Body parser method that operators on request body
9
9
  */
10
10
  export type FastifyBodyParser<
11
11
  RawBody extends string | Buffer,
@@ -1,6 +1,6 @@
1
1
  import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request'
2
- import { RouteOptions, RouteShorthandMethod, RouteGenericInterface } from './route'
3
- import { FastifySchemaCompiler, FastifySchemaValidationError } from './schema'
2
+ import { RouteOptions, RouteShorthandMethod, RouteGenericInterface, DefaultRoute } from './route'
3
+ import { FastifySchemaCompiler, FastifySchemaValidationError, FastifySerializerCompiler } from './schema'
4
4
  import { RawServerBase, RawRequestDefaultExpression, RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from './utils'
5
5
  import { FastifyLoggerInstance } from './logger'
6
6
  import { FastifyRegister } from './register'
@@ -59,6 +59,9 @@ export interface FastifyInstance<
59
59
 
60
60
  register: FastifyRegister<FastifyInstance<RawServer, RawRequest, RawReply, Logger> & PromiseLike<undefined>>;
61
61
 
62
+ getDefaultRoute: DefaultRoute<RawRequest, RawReply>;
63
+ setDefaultRoute(defaultRoute: DefaultRoute<RawRequest, RawReply>): void;
64
+
62
65
  route<
63
66
  RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
64
67
  ContextConfig = ContextConfigDefault
@@ -332,7 +335,7 @@ export interface FastifyInstance<
332
335
  /**
333
336
  * Set the schema serializer for all routes.
334
337
  */
335
- setSerializerCompiler(schemaCompiler: FastifySchemaCompiler): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
338
+ setSerializerCompiler(schemaCompiler: FastifySerializerCompiler): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
336
339
 
337
340
  /**
338
341
  * Set the reply serializer for all routes.
package/types/plugin.d.ts CHANGED
@@ -11,7 +11,7 @@ export type FastifyPluginOptions = Record<string, any>
11
11
  export type FastifyPluginCallback<Options extends FastifyPluginOptions = Record<never, never>, Server extends RawServerBase = RawServerDefault> = (
12
12
  instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>>,
13
13
  opts: Options,
14
- next: (err?: Error) => void
14
+ done: (err?: Error) => void
15
15
  ) => void
16
16
 
17
17
  /**
package/types/reply.d.ts CHANGED
@@ -40,6 +40,7 @@ export interface FastifyReply<
40
40
  // Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param
41
41
  redirect(statusCode: number, url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
42
42
  redirect(url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
43
+ hijack(): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
43
44
  callNotFound(): void;
44
45
  getResponseTime(): number;
45
46
  type(contentType: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
package/types/route.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FastifyInstance } from './instance'
2
2
  import { FastifyRequest, RequestGenericInterface } from './request'
3
3
  import { FastifyReply, ReplyGenericInterface } from './reply'
4
- import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError } from './schema'
4
+ import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, FastifySerializerCompiler } from './schema'
5
5
  import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils'
6
6
  import { LogLevel } from './logger'
7
7
  import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler } from './hooks'
@@ -21,8 +21,9 @@ export interface RouteShorthandOptions<
21
21
  > {
22
22
  schema?: FastifySchema;
23
23
  attachValidation?: boolean;
24
+ exposeHeadRoute?: boolean;
24
25
  validatorCompiler?: FastifySchemaCompiler;
25
- serializerCompiler?: FastifySchemaCompiler;
26
+ serializerCompiler?: FastifySerializerCompiler;
26
27
  bodyLimit?: number;
27
28
  logLevel?: LogLevel;
28
29
  config?: ContextConfig;
@@ -121,3 +122,8 @@ export type RouteHandler<
121
122
  request: FastifyRequest<RouteGeneric, RawServer, RawRequest>,
122
123
  reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>
123
124
  ) => void | Promise<RouteGeneric['Reply'] | void>
125
+
126
+ export type DefaultRoute<Request, Reply> = (
127
+ req: Request,
128
+ res: Reply,
129
+ ) => void;
package/types/schema.d.ts CHANGED
@@ -26,6 +26,7 @@ export interface FastifySchemaValidationError {
26
26
  }
27
27
 
28
28
  export interface FastifyValidationResult {
29
+ (data: any): boolean | PromiseLike<any> | { error?: Error, value?: any }
29
30
  errors?: FastifySchemaValidationError[] | null;
30
31
  }
31
32
 
@@ -33,3 +34,5 @@ export interface FastifyValidationResult {
33
34
  * Compiler for FastifySchema Type
34
35
  */
35
36
  export type FastifySchemaCompiler = (routeSchema: FastifyRouteSchemaDef) => FastifyValidationResult
37
+
38
+ export type FastifySerializerCompiler = (routeSchema: FastifyRouteSchemaDef) => (data: any) => string