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.
- package/GOVERNANCE.md +1 -1
- package/README.md +12 -8
- package/SECURITY.md +3 -3
- package/docs/ContentTypeParser.md +1 -1
- package/docs/Ecosystem.md +16 -6
- package/docs/Encapsulation.md +5 -2
- package/docs/Fluent-Schema.md +4 -4
- package/docs/Getting-Started.md +1 -1
- package/docs/Hooks.md +28 -1
- package/docs/Lifecycle.md +8 -1
- package/docs/Middleware.md +5 -4
- package/docs/Reply.md +13 -4
- package/docs/Routes.md +4 -3
- package/docs/Server.md +78 -4
- package/docs/Serverless.md +23 -51
- package/docs/TypeScript.md +35 -18
- package/docs/Validation-and-Serialization.md +4 -4
- package/docs/Write-Plugin.md +4 -4
- package/examples/hooks-benchmark.js +12 -12
- package/examples/hooks.js +16 -16
- package/examples/plugin.js +2 -2
- package/examples/route-prefix.js +4 -4
- package/fastify.d.ts +16 -1
- package/fastify.js +33 -16
- package/isolate-0x426d1e0-1227-v8.log +4019 -0
- package/isolate-0x4d4c7e0-1988-v8.log +4081 -0
- package/lib/errors.js +6 -0
- package/lib/headRoute.js +31 -0
- package/lib/pluginOverride.js +5 -5
- package/lib/pluginUtils.js +7 -6
- package/lib/reply.js +14 -2
- package/lib/reqIdGenFactory.js +5 -0
- package/lib/request.js +1 -1
- package/lib/route.js +66 -41
- package/lib/schema-compilers.js +5 -3
- package/lib/schema-controller.js +106 -0
- package/lib/schemas.js +14 -24
- package/lib/server.js +1 -0
- package/lib/symbols.js +1 -3
- package/lib/warnings.js +2 -0
- package/lib/wrapThenable.js +2 -1
- package/package.json +25 -21
- package/test/404s.test.js +120 -120
- package/test/500s.test.js +8 -8
- package/test/async-await.test.js +29 -1
- package/test/close.test.js +8 -8
- package/test/context-config.test.js +52 -0
- package/test/custom-parser.test.js +8 -8
- package/test/decorator.test.js +49 -49
- package/test/default-route.test.js +43 -0
- package/test/fastify-instance.test.js +2 -2
- package/test/fluent-schema.test.js +3 -3
- package/test/handler-context.test.js +2 -2
- package/test/hooks-async.test.js +3 -3
- package/test/hooks.on-ready.test.js +12 -12
- package/test/hooks.test.js +75 -32
- package/test/http2/closing.test.js +23 -1
- package/test/inject.test.js +6 -6
- package/test/input-validation.js +2 -2
- package/test/internals/hookRunner.test.js +50 -50
- package/test/internals/reply.test.js +47 -22
- package/test/internals/request.test.js +3 -9
- package/test/internals/version.test.js +2 -2
- package/test/logger.test.js +30 -30
- package/test/middleware.test.js +4 -4
- package/test/plugin.helper.js +2 -2
- package/test/plugin.test.js +154 -99
- package/test/register.test.js +11 -11
- package/test/request-error.test.js +2 -2
- package/test/route-hooks.test.js +24 -24
- package/test/route-prefix.test.js +81 -52
- package/test/route.test.js +568 -0
- package/test/schema-feature.test.js +168 -38
- package/test/schema-serialization.test.js +4 -4
- package/test/schema-special-usage.test.js +136 -0
- package/test/schema-validation.test.js +7 -7
- package/test/skip-reply-send.test.js +315 -0
- package/test/stream.test.js +6 -6
- package/test/throw.test.js +4 -4
- package/test/types/instance.test-d.ts +5 -3
- package/test/types/plugin.test-d.ts +7 -7
- package/test/types/reply.test-d.ts +1 -0
- package/test/types/schema.test-d.ts +15 -0
- package/test/validation-error-handling.test.js +5 -5
- package/test/versioned-routes.test.js +1 -1
- package/types/content-type-parser.d.ts +1 -1
- package/types/instance.d.ts +6 -3
- package/types/plugin.d.ts +1 -1
- package/types/reply.d.ts +1 -0
- package/types/route.d.ts +8 -2
- package/types/schema.d.ts +3 -0
- 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
|
package/lib/headRoute.js
ADDED
|
@@ -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
|
+
}
|
package/lib/pluginOverride.js
CHANGED
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
kLogLevel,
|
|
8
8
|
kLogSerializers,
|
|
9
9
|
kHooks,
|
|
10
|
-
|
|
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[
|
|
47
|
-
instance.getSchema = instance[
|
|
48
|
-
instance.getSchemas = instance[
|
|
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
|
|
package/lib/pluginUtils.js
CHANGED
|
@@ -67,21 +67,22 @@ function checkDecorators (fn) {
|
|
|
67
67
|
const meta = getMeta(fn)
|
|
68
68
|
if (!meta) return
|
|
69
69
|
|
|
70
|
-
const decorators = meta
|
|
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
|
|
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
|
|
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)
|
package/lib/reqIdGenFactory.js
CHANGED
|
@@ -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
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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 =
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
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
|
|
265
|
-
if (!opts.validatorCompiler
|
|
266
|
-
|
|
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 ||
|
|
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
|
-
|
|
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 ||
|
|
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
|
}
|
package/lib/schema-compilers.js
CHANGED
|
@@ -43,9 +43,12 @@ function ValidatorCompiler (externalSchemas, options, cache) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
Object.values(externalSchemas)
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
package/lib/wrapThenable.js
CHANGED
|
@@ -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)
|