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
package/lib/errors.js CHANGED
@@ -200,6 +200,12 @@ const codes = {
200
200
  "'%s' is not a valid url component",
201
201
  400
202
202
  ),
203
+ FST_ERR_DEFAULT_ROUTE_INVALID_TYPE: createError(
204
+ 'FST_ERR_DEFAULT_ROUTE_INVALID_TYPE',
205
+ 'The defaultRoute type should be a function',
206
+ 500,
207
+ TypeError
208
+ ),
203
209
 
204
210
  /**
205
211
  * again listen when close server
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+ function headRouteOnSendHandler (req, reply, payload, done) {
3
+ // If payload is undefined
4
+ if (payload === undefined) {
5
+ reply.header('content-length', '0')
6
+ return done(null, null)
7
+ }
8
+
9
+ if (typeof payload.resume === 'function') {
10
+ payload.on('error', (err) => {
11
+ reply.log.error({ err }, 'Error on Stream found for HEAD route')
12
+ })
13
+ payload.resume()
14
+ return done(null, null)
15
+ }
16
+
17
+ const size = '' + Buffer.byteLength(payload)
18
+
19
+ reply.header('content-length', size)
20
+
21
+ done(null, null)
22
+ }
23
+
24
+ function parseHeadOnSendHandlers (onSendHandlers) {
25
+ if (onSendHandlers == null) return headRouteOnSendHandler
26
+ return Array.isArray(onSendHandlers) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler]
27
+ }
28
+
29
+ module.exports = {
30
+ parseHeadOnSendHandlers
31
+ }
@@ -7,7 +7,7 @@ const {
7
7
  kLogLevel,
8
8
  kLogSerializers,
9
9
  kHooks,
10
- kSchemas,
10
+ kSchemaController,
11
11
  kContentTypeParser,
12
12
  kReply,
13
13
  kRequest,
@@ -17,9 +17,9 @@ const {
17
17
 
18
18
  const Reply = require('./reply')
19
19
  const Request = require('./request')
20
+ const SchemaController = require('./schema-controller')
20
21
  const ContentTypeParser = require('./contentTypeParser')
21
22
  const { buildHooks } = require('./hooks')
22
- const { buildSchemas } = require('./schemas')
23
23
  const pluginUtils = require('./pluginUtils')
24
24
 
25
25
  // Function that runs the encapsulation magic.
@@ -43,9 +43,9 @@ module.exports = function override (old, fn, opts) {
43
43
  instance[kHooks] = buildHooks(instance[kHooks])
44
44
  instance[kRoutePrefix] = buildRoutePrefix(instance[kRoutePrefix], opts.prefix)
45
45
  instance[kLogLevel] = opts.logLevel || instance[kLogLevel]
46
- instance[kSchemas] = buildSchemas(old[kSchemas])
47
- instance.getSchema = instance[kSchemas].getSchema.bind(instance[kSchemas])
48
- instance.getSchemas = instance[kSchemas].getSchemas.bind(instance[kSchemas])
46
+ instance[kSchemaController] = SchemaController.buildSchemaController(old[kSchemaController])
47
+ instance.getSchema = instance[kSchemaController].getSchema.bind(instance[kSchemaController])
48
+ instance.getSchemas = instance[kSchemaController].getSchemas.bind(instance[kSchemaController])
49
49
  instance[pluginUtils.registeredPlugins] = Object.create(instance[pluginUtils.registeredPlugins])
50
50
  instance[kPluginNameChain] = [pluginUtils.getPluginName(fn) || pluginUtils.getFuncPreview(fn)]
51
51
 
@@ -67,21 +67,22 @@ function checkDecorators (fn) {
67
67
  const meta = getMeta(fn)
68
68
  if (!meta) return
69
69
 
70
- const decorators = meta.decorators
70
+ const { decorators, name } = meta
71
71
  if (!decorators) return
72
72
 
73
- if (decorators.fastify) _checkDecorators.call(this, 'Fastify', decorators.fastify)
74
- if (decorators.reply) _checkDecorators.call(this[kReply], 'Reply', decorators.reply)
75
- if (decorators.request) _checkDecorators.call(this[kRequest], 'Request', decorators.request)
73
+ if (decorators.fastify) _checkDecorators.call(this, 'Fastify', decorators.fastify, name)
74
+ if (decorators.reply) _checkDecorators.call(this[kReply], 'Reply', decorators.reply, name)
75
+ if (decorators.request) _checkDecorators.call(this[kRequest], 'Request', decorators.request, name)
76
76
  }
77
77
 
78
- function _checkDecorators (instance, decorators) {
78
+ function _checkDecorators (instance, decorators, name) {
79
79
  assert(Array.isArray(decorators), 'The decorators should be an array of strings')
80
80
 
81
81
  decorators.forEach(decorator => {
82
+ const withPluginName = typeof name === 'string' ? ` required by '${name}'` : ''
82
83
  assert(
83
84
  instance === 'Fastify' ? decorator in this : decorator in this.prototype,
84
- `The decorator '${decorator}' is not present in ${instance}`
85
+ `The decorator '${decorator}'${withPluginName} is not present in ${instance}`
85
86
  )
86
87
  })
87
88
  }
package/lib/reply.js CHANGED
@@ -104,6 +104,11 @@ Object.defineProperties(Reply.prototype, {
104
104
  }
105
105
  })
106
106
 
107
+ Reply.prototype.hijack = function () {
108
+ this[kReplySent] = true
109
+ return this
110
+ }
111
+
107
112
  Reply.prototype.send = function (payload) {
108
113
  if (this[kReplyIsRunningOnErrorHook] === true) {
109
114
  throw new FST_ERR_SEND_INSIDE_ONERR()
@@ -144,7 +149,12 @@ Reply.prototype.send = function (payload) {
144
149
  }
145
150
 
146
151
  if (this[kReplySerializer] !== null) {
147
- payload = this[kReplySerializer](payload)
152
+ if (typeof payload !== 'string') {
153
+ preserializeHook(this, payload)
154
+ return this
155
+ } else {
156
+ payload = this[kReplySerializer](payload)
157
+ }
148
158
 
149
159
  // The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
150
160
  } else if (hasContentType === false || contentType.indexOf('json') > -1) {
@@ -342,7 +352,9 @@ function preserializeHookEnd (err, request, reply, payload) {
342
352
  return
343
353
  }
344
354
 
345
- if (reply.context && reply.context[kReplySerializerDefault]) {
355
+ if (reply[kReplySerializer] !== null) {
356
+ payload = reply[kReplySerializer](payload)
357
+ } else if (reply.context && reply.context[kReplySerializerDefault]) {
346
358
  payload = reply.context[kReplySerializerDefault](payload, reply.raw.statusCode)
347
359
  } else {
348
360
  payload = serialize(reply.context, payload, reply.raw.statusCode)
@@ -1,6 +1,11 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = function () {
4
+ // 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
5
+ // With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
6
+ // This is very likely to happen in real-world applications, hence the limit is enforced.
7
+ // Growing beyond this value will make the id generation slower and cause a deopt.
8
+ // In the worst cases, it will become a float, losing accuracy.
4
9
  const maxInt = 2147483647
5
10
  let nextReqId = 0
6
11
  return function genReqId (req) {
package/lib/request.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const proxyAddr = require('proxy-addr')
3
+ const proxyAddr = require('@fastify/proxy-addr')
4
4
  const semver = require('semver')
5
5
  const warning = require('./warnings')
6
6
 
package/lib/route.js CHANGED
@@ -6,10 +6,7 @@ const handleRequest = require('./handleRequest')
6
6
  const { hookRunner, hookIterator, lifecycleHooks } = require('./hooks')
7
7
  const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTIONS']
8
8
  const { normalizeSchema } = require('./schemas')
9
- const {
10
- ValidatorSelector,
11
- SerializerCompiler: buildDefaultSerializer
12
- } = require('./schema-compilers')
9
+ const { parseHeadOnSendHandlers } = require('./headRoute')
13
10
  const warning = require('./warnings')
14
11
 
15
12
  const {
@@ -19,7 +16,8 @@ const {
19
16
 
20
17
  const {
21
18
  FST_ERR_SCH_VALIDATION_BUILD,
22
- FST_ERR_SCH_SERIALIZATION_BUILD
19
+ FST_ERR_SCH_SERIALIZATION_BUILD,
20
+ FST_ERR_DEFAULT_ROUTE_INVALID_TYPE
23
21
  } = require('./errors')
24
22
 
25
23
  const {
@@ -28,10 +26,8 @@ const {
28
26
  kLogSerializers,
29
27
  kHooks,
30
28
  kHooksDeprecatedPreParsing,
31
- kSchemas,
29
+ kSchemaController,
32
30
  kOptions,
33
- kValidatorCompiler,
34
- kSerializerCompiler,
35
31
  kContentTypeParser,
36
32
  kReply,
37
33
  kReplySerializerDefault,
@@ -59,7 +55,7 @@ function buildRouting (options) {
59
55
  let disableRequestLogging
60
56
  let ignoreTrailingSlash
61
57
  let return503OnClosing
62
- let buildPerformanceValidator
58
+ let globalExposeHeadRoutes
63
59
 
64
60
  let closing = false
65
61
 
@@ -72,6 +68,7 @@ function buildRouting (options) {
72
68
  setupResponseListeners = fastifyArgs.setupResponseListeners
73
69
  throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
74
70
 
71
+ globalExposeHeadRoutes = options.exposeHeadRoutes
75
72
  requestIdHeader = options.requestIdHeader
76
73
  querystringParser = options.querystringParser
77
74
  requestIdLogLabel = options.requestIdLogLabel
@@ -79,11 +76,20 @@ function buildRouting (options) {
79
76
  disableRequestLogging = options.disableRequestLogging
80
77
  ignoreTrailingSlash = options.ignoreTrailingSlash
81
78
  return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true
82
- buildPerformanceValidator = ValidatorSelector()
83
79
  },
84
80
  routing: router.lookup.bind(router), // router func to find the right handler to call
85
81
  route, // configure a route in the fastify instance
86
82
  prepareRoute,
83
+ getDefaultRoute: function () {
84
+ return router.defaultRoute
85
+ },
86
+ setDefaultRoute: function (defaultRoute) {
87
+ if (typeof defaultRoute !== 'function') {
88
+ throw new FST_ERR_DEFAULT_ROUTE_INVALID_TYPE()
89
+ }
90
+
91
+ router.defaultRoute = defaultRoute
92
+ },
87
93
  routeHandler,
88
94
  closeRoutes: () => { closing = true },
89
95
  printRoutes: router.prettyPrint.bind(router)
@@ -109,6 +115,7 @@ function buildRouting (options) {
109
115
  options = Object.assign({}, options, {
110
116
  method,
111
117
  url,
118
+ path: url,
112
119
  handler: handler || (options && options.handler)
113
120
  })
114
121
 
@@ -149,34 +156,41 @@ function buildRouting (options) {
149
156
 
150
157
  this.after((notHandledErr, done) => {
151
158
  const path = opts.url || opts.path
152
- if (path === '/' && prefix.length > 0) {
159
+ if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
153
160
  switch (opts.prefixTrailingSlash) {
154
161
  case 'slash':
155
- afterRouteAdded.call(this, path, notHandledErr, done)
162
+ afterRouteAdded.call(this, { path }, notHandledErr, done)
156
163
  break
157
164
  case 'no-slash':
158
- afterRouteAdded.call(this, '', notHandledErr, done)
165
+ afterRouteAdded.call(this, { path: '' }, notHandledErr, done)
159
166
  break
160
167
  case 'both':
161
168
  default:
162
- afterRouteAdded.call(this, '', notHandledErr, done)
169
+ afterRouteAdded.call(this, { path: '' }, notHandledErr, done)
163
170
  // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
164
171
  if (ignoreTrailingSlash !== true) {
165
- afterRouteAdded.call(this, path, notHandledErr, done)
172
+ afterRouteAdded.call(this, { path, prefixing: true }, notHandledErr, done)
166
173
  }
167
174
  }
168
175
  } else if (path[0] === '/' && prefix.endsWith('/')) {
169
176
  // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
170
- afterRouteAdded.call(this, path.slice(1), notHandledErr, done)
177
+ afterRouteAdded.call(this, { path: path.slice(1) }, notHandledErr, done)
171
178
  } else {
172
- afterRouteAdded.call(this, path, notHandledErr, done)
179
+ afterRouteAdded.call(this, { path }, notHandledErr, done)
173
180
  }
174
181
  })
175
182
 
176
183
  // chainable api
177
184
  return this
178
185
 
179
- function afterRouteAdded (path, notHandledErr, done) {
186
+ /**
187
+ * This function sets up a new route, its log serializers, and triggers route hooks.
188
+ *
189
+ * @param {object} opts contains route `path` and `prefixing` flag which indicates if this is an auto-prefixed route, e.g. `fastify.register(routes, { prefix: '/foo' })`
190
+ * @param {*} notHandledErr error object to be passed back to the original invoker
191
+ * @param {*} done callback
192
+ */
193
+ function afterRouteAdded ({ path, prefixing = false }, notHandledErr, done) {
180
194
  const url = prefix + path
181
195
 
182
196
  opts.url = url
@@ -193,19 +207,23 @@ function buildRouting (options) {
193
207
  opts.attachValidation = false
194
208
  }
195
209
 
210
+ if (prefixing === false) {
196
211
  // run 'onRoute' hooks
197
- for (const hook of this[kHooks].onRoute) {
198
- try {
199
- hook.call(this, opts)
200
- } catch (error) {
201
- done(error)
202
- return
212
+ for (const hook of this[kHooks].onRoute) {
213
+ try {
214
+ hook.call(this, opts)
215
+ } catch (error) {
216
+ done(error)
217
+ return
218
+ }
203
219
  }
204
220
  }
205
221
 
206
- const config = opts.config || {}
207
- config.url = url
208
- config.method = opts.method
222
+ const config = {
223
+ ...opts.config,
224
+ url,
225
+ method: opts.method
226
+ }
209
227
 
210
228
  const context = new Context(
211
229
  opts.schema,
@@ -223,6 +241,8 @@ function buildRouting (options) {
223
241
  opts.schemaErrorFormatter || this[kSchemaErrorFormatter]
224
242
  )
225
243
 
244
+ const headRouteExists = router.find('HEAD', path) != null
245
+
226
246
  try {
227
247
  router.on(opts.method, opts.url, { version: opts.version }, routeHandler, context)
228
248
  } catch (err) {
@@ -230,8 +250,19 @@ function buildRouting (options) {
230
250
  return
231
251
  }
232
252
 
253
+ const { exposeHeadRoute } = opts
254
+ const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
255
+ const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
256
+
257
+ if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
258
+ const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
259
+ prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
260
+ } else if (headRouteExists && exposeHeadRoute) {
261
+ warning.emit('FSTDEP007')
262
+ }
263
+
233
264
  // It can happen that a user registers a plugin with some hooks *after*
234
- // the route registration. To be sure to also load also those hooks,
265
+ // the route registration. To be sure to also load those hooks,
235
266
  // we must listen for the avvio's preReady event, and update the context object accordingly.
236
267
  avvio.once('preReady', () => {
237
268
  for (const hook of lifecycleHooks) {
@@ -261,27 +292,21 @@ function buildRouting (options) {
261
292
  if (opts.schema) {
262
293
  context.schema = normalizeSchema(context.schema)
263
294
 
264
- const schemaBucket = this[kSchemas]
265
- if (!opts.validatorCompiler && (
266
- !this[kValidatorCompiler] ||
267
- (this[kValidatorCompiler] && schemaBucket.hasNewSchemas()))) {
268
- // if the instance doesn't have a validator, build the default one for the single fastify instance
269
- this.setValidatorCompiler(buildPerformanceValidator(schemaBucket.getSchemas(), this[kOptions].ajv))
295
+ const schemaController = this[kSchemaController]
296
+ if (!opts.validatorCompiler) {
297
+ schemaController.setupValidator(this[kOptions])
270
298
  }
271
299
  try {
272
- compileSchemasForValidation(context, opts.validatorCompiler || this[kValidatorCompiler])
300
+ compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler)
273
301
  } catch (error) {
274
302
  throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
275
303
  }
276
304
 
277
- if (opts.schema.response &&
278
- !opts.serializerCompiler &&
279
- (!this[kSerializerCompiler] || (this[kSerializerCompiler] && schemaBucket.hasNewSchemas()))) {
280
- // if the instance doesn't have a serializer, build the default one for the single fastify instance
281
- this.setSerializerCompiler(buildDefaultSerializer(schemaBucket.getSchemas()))
305
+ if (opts.schema.response && !opts.serializerCompiler) {
306
+ schemaController.setupSerializer(this[kOptions])
282
307
  }
283
308
  try {
284
- compileSchemasForSerialization(context, opts.serializerCompiler || this[kSerializerCompiler])
309
+ compileSchemasForSerialization(context, opts.serializerCompiler || schemaController.serializerCompiler)
285
310
  } catch (error) {
286
311
  throw new FST_ERR_SCH_SERIALIZATION_BUILD(opts.method, url, error.message)
287
312
  }
@@ -43,9 +43,12 @@ function ValidatorCompiler (externalSchemas, options, cache) {
43
43
  }
44
44
  }
45
45
 
46
- Object.values(externalSchemas).forEach(s => ajv.addSchema(s))
46
+ const sourceSchemas = Object.values(externalSchemas)
47
+ for (const extSchema of sourceSchemas) {
48
+ ajv.addSchema(extSchema)
49
+ }
47
50
 
48
- return ({ schema, method, url, httpPart }) => {
51
+ return function ({ schema, method, url, httpPart }) {
49
52
  return ajv.compile(schema)
50
53
  }
51
54
  }
@@ -56,6 +59,5 @@ function SerializerCompiler (externalSchemas) {
56
59
  }
57
60
  }
58
61
 
59
- module.exports.ValidatorCompiler = ValidatorCompiler
60
62
  module.exports.ValidatorSelector = ValidatorSelector
61
63
  module.exports.SerializerCompiler = SerializerCompiler
@@ -0,0 +1,106 @@
1
+ 'use strict'
2
+
3
+ const { buildSchemas } = require('./schemas')
4
+ const {
5
+ ValidatorSelector,
6
+ SerializerCompiler: buildDefaultSerializer
7
+ } = require('./schema-compilers')
8
+
9
+ /**
10
+ * Called at every fastify context that is being created.
11
+ * @param {object} parentSchemaCtrl: the SchemaController instance of the Fastify parent context
12
+ * @return {object}:a new SchemaController
13
+ */
14
+ function buildSchemaController (parentSchemaCtrl, opts) {
15
+ if (parentSchemaCtrl) {
16
+ return new SchemaController(parentSchemaCtrl, opts)
17
+ }
18
+
19
+ const option = Object.assign({
20
+ bucket: buildSchemas,
21
+ compilersFactory: {
22
+ buildValidator: ValidatorSelector(),
23
+ buildSerializer: buildDefaultSerializer
24
+ }
25
+ }, opts)
26
+
27
+ return new SchemaController(undefined, option)
28
+ }
29
+
30
+ class SchemaController {
31
+ constructor (parent, options) {
32
+ this.opts = options || (parent && parent.opts)
33
+ this.addedSchemas = false
34
+
35
+ this.compilersFactory = this.opts.compilersFactory
36
+
37
+ if (parent) {
38
+ this.schemaBucket = this.opts.bucket(parent.getSchemas())
39
+ this.validatorCompiler = parent.getValidatorCompiler()
40
+ this.serializerCompiler = parent.getSerializerCompiler()
41
+ this.parent = parent
42
+ } else {
43
+ this.schemaBucket = this.opts.bucket()
44
+ }
45
+ }
46
+
47
+ // Bucket interface
48
+ add (schema) {
49
+ this.addedSchemas = true
50
+ return this.schemaBucket.add(schema)
51
+ }
52
+
53
+ getSchema (schemaId) {
54
+ return this.schemaBucket.getSchema(schemaId)
55
+ }
56
+
57
+ getSchemas () {
58
+ return this.schemaBucket.getSchemas()
59
+ }
60
+
61
+ // Schema Controller compilers holder
62
+ setValidatorCompiler (validatorCompiler) {
63
+ this.validatorCompiler = validatorCompiler
64
+ }
65
+
66
+ setSerializerCompiler (serializerCompiler) {
67
+ this.serializerCompiler = serializerCompiler
68
+ }
69
+
70
+ getValidatorCompiler () {
71
+ return this.validatorCompiler || (this.parent && this.parent.getValidatorCompiler())
72
+ }
73
+
74
+ getSerializerCompiler () {
75
+ return this.serializerCompiler || (this.parent && this.parent.getSerializerCompiler())
76
+ }
77
+
78
+ /**
79
+ * This method will be called when a validator must be setup.
80
+ * Do not setup the compiler more than once
81
+ * @param {object} serverOptions: the fastify server option
82
+ */
83
+ setupValidator (serverOption) {
84
+ const isReady = this.validatorCompiler !== undefined && !this.addedSchemas
85
+ if (isReady) {
86
+ return
87
+ }
88
+ this.validatorCompiler = this.compilersFactory.buildValidator(this.schemaBucket.getSchemas(), serverOption.ajv)
89
+ }
90
+
91
+ /**
92
+ * This method will be called when a serializer must be setup.
93
+ * Do not setup the compiler more than once
94
+ * @param {object} serverOptions: the fastify server option
95
+ */
96
+ setupSerializer (serverOption) {
97
+ const isReady = this.serializerCompiler !== undefined && !this.addedSchemas
98
+ if (isReady) {
99
+ return
100
+ }
101
+ this.serializerCompiler = this.compilersFactory.buildSerializer(this.schemaBucket.getSchemas())
102
+ }
103
+ }
104
+
105
+ module.exports = SchemaController
106
+ module.exports.buildSchemaController = buildSchemaController
package/lib/schemas.js CHANGED
@@ -10,13 +10,14 @@ const {
10
10
  FST_ERR_SCH_DUPLICATE
11
11
  } = require('./errors')
12
12
 
13
- function Schemas () {
14
- this.store = {}
15
- this.newSchemasAdded = false
13
+ const SCHEMAS_SOURCE = ['params', 'body', 'querystring', 'query', 'headers']
14
+
15
+ function Schemas (initStore) {
16
+ this.store = initStore || {}
16
17
  }
17
18
 
18
19
  Schemas.prototype.add = function (inputSchema) {
19
- const schema = fastClone((inputSchema.isFluentSchema || inputSchema[kFluentSchema])
20
+ const schema = fastClone((inputSchema.isFluentSchema || inputSchema.isFluentJSONSchema || inputSchema[kFluentSchema])
20
21
  ? inputSchema.valueOf()
21
22
  : inputSchema
22
23
  )
@@ -32,7 +33,6 @@ Schemas.prototype.add = function (inputSchema) {
32
33
  }
33
34
 
34
35
  this.store[id] = schema
35
- this.newSchemasAdded = true
36
36
  }
37
37
 
38
38
  Schemas.prototype.getSchemas = function () {
@@ -43,10 +43,6 @@ Schemas.prototype.getSchema = function (schemaId) {
43
43
  return this.store[schemaId]
44
44
  }
45
45
 
46
- Schemas.prototype.hasNewSchemas = function () {
47
- return this.newSchemasAdded
48
- }
49
-
50
46
  function normalizeSchema (routeSchemas) {
51
47
  if (routeSchemas[kSchemaVisited]) {
52
48
  return routeSchemas
@@ -87,9 +83,10 @@ function normalizeSchema (routeSchemas) {
87
83
  }
88
84
 
89
85
  if (routeSchemas.response) {
90
- Object.keys(routeSchemas.response).forEach(code => {
86
+ const httpCodes = Object.keys(routeSchemas.response)
87
+ for (const code of httpCodes) {
91
88
  routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code])
92
- })
89
+ }
93
90
  }
94
91
 
95
92
  routeSchemas[kSchemaVisited] = true
@@ -97,18 +94,19 @@ function normalizeSchema (routeSchemas) {
97
94
  }
98
95
 
99
96
  function generateFluentSchema (schema) {
100
- ;['params', 'body', 'querystring', 'query', 'headers'].forEach(key => {
97
+ for (const key of SCHEMAS_SOURCE) {
101
98
  if (schema[key] && (schema[key].isFluentSchema || schema[key][kFluentSchema])) {
102
99
  schema[key] = schema[key].valueOf()
103
100
  }
104
- })
101
+ }
105
102
 
106
103
  if (schema.response) {
107
- Object.keys(schema.response).forEach(code => {
104
+ const httpCodes = Object.keys(schema.response)
105
+ for (const code of httpCodes) {
108
106
  if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) {
109
107
  schema.response[code] = schema.response[code].valueOf()
110
108
  }
111
- })
109
+ }
112
110
  }
113
111
  }
114
112
 
@@ -123,15 +121,7 @@ function getSchemaAnyway (schema) {
123
121
  return schema
124
122
  }
125
123
 
126
- function buildSchemas (s) {
127
- const schema = new Schemas()
128
- Object.values(s.getSchemas()).forEach(_ => schema.add(_))
129
- schema.newSchemasAdded = false
130
- return schema
131
- }
132
-
133
124
  module.exports = {
134
- Schemas,
135
- buildSchemas,
125
+ buildSchemas (initStore) { return new Schemas(initStore) },
136
126
  normalizeSchema
137
127
  }
package/lib/server.js CHANGED
@@ -17,6 +17,7 @@ function createServer (options, httpHandler) {
17
17
  } else if (options.https) {
18
18
  if (options.http2) {
19
19
  server = http2().createSecureServer(options.https, httpHandler)
20
+ server.on('session', sessionTimeout(options.http2SessionTimeout))
20
21
  } else {
21
22
  server = https.createServer(options.https, httpHandler)
22
23
  server.keepAliveTimeout = options.keepAliveTimeout
package/lib/symbols.js CHANGED
@@ -9,15 +9,13 @@ const keys = {
9
9
  kLogSerializers: Symbol('fastify.logSerializers'),
10
10
  kHooks: Symbol('fastify.hooks'),
11
11
  kHooksDeprecatedPreParsing: Symbol('fastify.hooks.DeprecatedPreParsing'),
12
- kSchemas: Symbol('fastify.schemas'),
12
+ kSchemaController: Symbol('fastify.schemaController'),
13
13
  kSchemaHeaders: Symbol('headers-schema'),
14
14
  kSchemaParams: Symbol('params-schema'),
15
15
  kSchemaQuerystring: Symbol('querystring-schema'),
16
16
  kSchemaBody: Symbol('body-schema'),
17
17
  kSchemaResponse: Symbol('response-schema'),
18
- kValidatorCompiler: Symbol('fastify.validatorCompiler'),
19
18
  kSchemaErrorFormatter: Symbol('fastify.schemaErrorFormatter'),
20
- kSerializerCompiler: Symbol('fastify.serializerCompiler'),
21
19
  kReplySerializerDefault: Symbol('fastify.replySerializerDefault'),
22
20
  kContentTypeParser: Symbol('fastify.contentTypeParser'),
23
21
  kReply: Symbol('fastify.Reply'),
package/lib/warnings.js CHANGED
@@ -23,4 +23,6 @@ warning.create('FastifyDeprecation', 'FSTDEP005', 'You are accessing the depreca
23
23
 
24
24
  warning.create('FastifyDeprecation', 'FSTDEP006', 'You are decorating Request/Reply with a reference type. This reference is shared amongst all requests. Use onRequest hook instead. Property: %s')
25
25
 
26
+ warning.create('FastifyDeprecation', 'FSTDEP007', 'You are trying to set a HEAD route using "exposeHeadRoute" route flag when a sibling route is already set. See documentation for more info.')
27
+
26
28
  module.exports = warning
@@ -30,10 +30,11 @@ function wrapThenable (thenable, reply) {
30
30
  reply.log.error({ err: new FST_ERR_PROMISE_NOT_FULFILLED() }, "Promise may not be fulfilled with 'undefined' when statusCode is not 204")
31
31
  }
32
32
  }, function (err) {
33
- if (reply[kReplySentOverwritten] === true) {
33
+ if (reply[kReplySentOverwritten] === true || reply.sent === true) {
34
34
  reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
35
35
  return
36
36
  }
37
+
37
38
  reply[kReplySent] = false
38
39
  reply[kReplyIsError] = true
39
40
  reply.send(err)