fastify 3.16.0 → 3.18.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 CHANGED
@@ -80,7 +80,7 @@ The consensus to grant a new candidate Collaborator status is reached when:
80
80
  - at least one of the Lead Maintainers approve
81
81
  - at least two of the Team Members approve
82
82
 
83
- After these conditions are satisfied, the [onboarding process](#onboarding-collaborators) may start.
83
+ After these conditions are satisfied, the [onboarding process](CONTRIBUTING.md#onboarding-collaborators) may start.
84
84
 
85
85
 
86
86
  ## Lead Maintainers nominations
package/docs/Ecosystem.md CHANGED
@@ -63,6 +63,7 @@ Plugins maintained by the Fastify team are listed under [Core](#core) while plug
63
63
  - [`@mgcrea/fastify-session-sodium-crypto`](https://github.com/mgcrea/fastify-session-sodium-crypto) Fast sodium-based crypto for @mgcrea/fastify-session
64
64
  - [`@mgcrea/fastify-session`](https://github.com/mgcrea/fastify-session) Session plugin for Fastify that supports both stateless and stateful sessions
65
65
  - [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact) A custom compact pino-base prettifier
66
+ - [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs) SeaweedFS for Fastify
66
67
  - [`apollo-server-fastify`](https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify) Run an [Apollo Server](https://github.com/apollographql/apollo-server) to serve GraphQL with Fastify.
67
68
  - [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for Kubernetes Liveness and Readiness Probes.
68
69
  - [`cls-rtracer`](https://github.com/puzpuzpuz/cls-rtracer) Fastify middleware for CLS-based request ID generation. An out-of-the-box solution for adding request IDs into your logs.
package/docs/Reply.md CHANGED
@@ -5,6 +5,7 @@
5
5
  - [Introduction](#introduction)
6
6
  - [.code(statusCode)](#codestatuscode)
7
7
  - [.statusCode](#statusCode)
8
+ - [.server](#server)
8
9
  - [.header(key, value)](#headerkey-value)
9
10
  - [.headers(object)](#headersobject)
10
11
  - [.getHeader(key)](#getheaderkey)
@@ -38,6 +39,7 @@ and properties:
38
39
  - `.code(statusCode)` - Sets the status code.
39
40
  - `.status(statusCode)` - An alias for `.code(statusCode)`.
40
41
  - `.statusCode` - Read and set the HTTP status code.
42
+ - `.server` - A reference to the fastify instance object.
41
43
  - `.header(name, value)` - Sets a response header.
42
44
  - `.headers(object)` - Sets all the keys of the object as response headers.
43
45
  - `.getHeader(name)` - Retrieve value of already set header.
@@ -87,6 +89,20 @@ if (reply.statusCode >= 299) {
87
89
  }
88
90
  ```
89
91
 
92
+ <a name="server"></a>
93
+ ### .server
94
+ The Fastify server instance, scoped to the current [encapsulation context](Encapsulation.md).
95
+
96
+ ```js
97
+ fastify.decorate('util', function util () {
98
+ return 'foo'
99
+ })
100
+
101
+ fastify.get('/', async function (req, rep) {
102
+ return rep.server.util() // foo
103
+ })
104
+ ```
105
+
90
106
  <a name="header"></a>
91
107
  ### .header(key, value)
92
108
  Sets a response header. If the value is omitted or undefined, it is coerced
package/docs/Request.md CHANGED
@@ -9,6 +9,7 @@ Request is a core Fastify object containing the following fields:
9
9
  - `headers` - the headers
10
10
  - `raw` - the incoming HTTP request from Node core
11
11
  - `req` *(deprecated, use `.raw` instead)* - the incoming HTTP request from Node core
12
+ - `server` - The Fastify server instance, scoped to the current [encapsulation context](Encapsulation.md)
12
13
  - `id` - the request id
13
14
  - `log` - the logger instance of the incoming request
14
15
  - `ip` - the IP address of the incoming request
@@ -31,6 +32,7 @@ fastify.post('/:params', options, function (request, reply) {
31
32
  console.log(request.params)
32
33
  console.log(request.headers)
33
34
  console.log(request.raw)
35
+ console.log(request.server)
34
36
  console.log(request.id)
35
37
  console.log(request.ip)
36
38
  console.log(request.ips)
package/docs/Server.md CHANGED
@@ -288,7 +288,7 @@ const fastify = Fastify({ trustProxy: true })
288
288
  }
289
289
  ```
290
290
 
291
- For more examples, refer to the [`@fastify/proxy-addr`](https://www.npmjs.com/package/@fastify/proxy-addr) package.
291
+ For more examples, refer to the [`proxy-addr`](https://www.npmjs.com/package/proxy-addr) package.
292
292
 
293
293
  You may access the `ip`, `ips`, `hostname` and `protocol` values on the [`request`](Request.md) object.
294
294
 
@@ -967,6 +967,8 @@ fastify.register(function (instance, options, done) {
967
967
  }, { prefix: '/v1' })
968
968
  ```
969
969
 
970
+ Fastify calls setNotFoundHandler to add a default 404 handler at startup before plugins are registered. If you would like to augment the behavior of the default 404 handler, for example with plugins, you can call setNotFoundHandler with no arguments `fastify.setNotFoundHandler()` within the context of these registered plugins.
971
+
970
972
  <a name="set-error-handler"></a>
971
973
  #### setErrorHandler
972
974
 
@@ -1026,6 +1028,31 @@ fastify.ready(() => {
1026
1028
  })
1027
1029
  ```
1028
1030
 
1031
+ `fastify.printRoutes({ includeMeta: (true | []) })` will display properties from the `route.store` object for each displayed route. This can be an `array` of keys (e.g. `['onRequest', Symbol('key')]`), or `true` to display all properties. A shorthand option, `fastify.printRoutes({ includeHooks: true })` will include all [hooks](https://www.fastify.io/docs/latest/Hooks/).
1032
+
1033
+ ```js
1034
+ console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] }))
1035
+ // └── /
1036
+ // ├── test (GET)
1037
+ // │ • (onRequest) ["anonymous()","namedFunction()"]
1038
+ // │ • (metaProperty) "value"
1039
+ // │ └── /hello (GET)
1040
+ // └── hel
1041
+ // ├── lo/world (GET)
1042
+ // │ • (onTimeout) ["anonymous()"]
1043
+ // └── licopter (GET)
1044
+
1045
+ console.log(fastify.printRoutes({ includeHooks: true }))
1046
+ // └── /
1047
+ // ├── test (GET)
1048
+ // │ • (onRequest) ["anonymous()","namedFunction()"]
1049
+ // │ └── /hello (GET)
1050
+ // └── hel
1051
+ // ├── lo/world (GET)
1052
+ // │ • (onTimeout) ["anonymous()"]
1053
+ // └── licopter (GET)
1054
+ ```
1055
+
1029
1056
  <a name="print-plugins"></a>
1030
1057
  #### printPlugins
1031
1058
 
@@ -1042,9 +1069,9 @@ fastify.ready(() => {
1042
1069
  console.error(fastify.printPlugins())
1043
1070
  // will output the following to stderr:
1044
1071
  // └── root
1045
- // ├── foo
1046
- // │ └── bar
1047
- // └── baz
1072
+ // ├── foo
1073
+ // │ └── bar
1074
+ // └── baz
1048
1075
  })
1049
1076
  ```
1050
1077
 
@@ -265,6 +265,11 @@ In the last example we used interfaces to define the types for the request query
265
265
  const { username, password } = request.query
266
266
  done(username !== 'admin' ? new Error('Must be admin') : undefined)
267
267
  }
268
+ // or if using async
269
+ // preValidation: async (request, reply) => {
270
+ // const { username, password } = request.query
271
+ // return username !== "admin" ? new Error("Must be admin") : undefined;
272
+ // }
268
273
  }, async (request, reply) => {
269
274
  const customerHeader = request.headers['h-Custom']
270
275
  // do something with request data
@@ -281,13 +286,15 @@ In the last example we used interfaces to define the types for the request query
281
286
  querystring: QuerystringSchema,
282
287
  headers: HeadersSchema
283
288
  },
284
- preHandler: (request, reply) => {
289
+ preHandler: (request, reply, done) => {
285
290
  const { username, password } = request.query
286
291
  const customerHeader = request.headers['h-Custom']
292
+ done()
287
293
  },
288
294
  handler: (request, reply) => {
289
295
  const { username, password } = request.query
290
296
  const customerHeader = request.headers['h-Custom']
297
+ reply.status(200).send({username});
291
298
  }
292
299
  })
293
300
 
@@ -331,6 +338,7 @@ const todo = {
331
338
  With the provided type `FromSchema` you can build a type from your schema and use it in your handler.
332
339
 
333
340
  ```typescript
341
+ import { FromSchema } from "json-schema-to-ts";
334
342
  fastify.post<{ Body: FromSchema<typeof todo> }>(
335
343
  '/todo',
336
344
  {
package/fastify.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as http from 'http'
2
2
  import * as http2 from 'http2'
3
3
  import * as https from 'https'
4
- import * as LightMyRequest from 'light-my-request'
5
4
  import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
6
5
 
7
6
  import { FastifyRequest, RequestGenericInterface } from './types/request'
@@ -159,6 +158,7 @@ export interface ValidationResult {
159
158
  }
160
159
 
161
160
  /* Export all additional types */
161
+ export type { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request'
162
162
  export { FastifyRequest, RequestGenericInterface } from './types/request'
163
163
  export { FastifyReply } from './types/reply'
164
164
  export { FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin } from './types/plugin'
package/fastify.js CHANGED
@@ -4,9 +4,8 @@ const Avvio = require('avvio')
4
4
  const http = require('http')
5
5
  const querystring = require('querystring')
6
6
  let lightMyRequest
7
- let version
8
- let versionLoaded = false
9
7
 
8
+ const { version } = require('./package.json')
10
9
  const {
11
10
  kAvvioBoot,
12
11
  kChildren,
@@ -35,7 +34,7 @@ const supportedMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT', 'OPTI
35
34
  const decorator = require('./lib/decorate')
36
35
  const ContentTypeParser = require('./lib/contentTypeParser')
37
36
  const SchemaController = require('./lib/schema-controller')
38
- const { Hooks, hookRunnerApplication } = require('./lib/hooks')
37
+ const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
39
38
  const { createLogger } = require('./lib/logger')
40
39
  const pluginUtils = require('./lib/pluginUtils')
41
40
  const reqIdGenFactory = require('./lib/reqIdGenFactory')
@@ -58,6 +57,19 @@ const onBadUrlContext = {
58
57
  onError: []
59
58
  }
60
59
 
60
+ function defaultBuildPrettyMeta (route) {
61
+ // return a shallow copy of route's sanitized context
62
+
63
+ const cleanKeys = {}
64
+ const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
65
+
66
+ allowedProps.concat(supportedHooks).forEach(k => {
67
+ cleanKeys[k] = route.store[k]
68
+ })
69
+
70
+ return Object.assign({}, cleanKeys)
71
+ }
72
+
61
73
  function defaultErrorHandler (error, request, reply) {
62
74
  if (reply.statusCode < 500) {
63
75
  reply.log.info(
@@ -158,7 +170,8 @@ function fastify (options) {
158
170
  constraints: constraints,
159
171
  ignoreTrailingSlash: options.ignoreTrailingSlash || defaultInitOptions.ignoreTrailingSlash,
160
172
  maxParamLength: options.maxParamLength || defaultInitOptions.maxParamLength,
161
- caseSensitive: options.caseSensitive
173
+ caseSensitive: options.caseSensitive,
174
+ buildPrettyMeta: defaultBuildPrettyMeta
162
175
  }
163
176
  })
164
177
 
@@ -278,7 +291,7 @@ function fastify (options) {
278
291
  // fake http injection
279
292
  inject: inject,
280
293
  // pretty print of the registered routes
281
- printRoutes: router.printRoutes,
294
+ printRoutes,
282
295
  // custom error handling
283
296
  setNotFoundHandler: setNotFoundHandler,
284
297
  setErrorHandler: setErrorHandler,
@@ -286,6 +299,9 @@ function fastify (options) {
286
299
  initialConfig
287
300
  }
288
301
 
302
+ fastify[kReply].prototype.server = fastify
303
+ fastify[kRequest].prototype.server = fastify
304
+
289
305
  Object.defineProperties(fastify, {
290
306
  pluginName: {
291
307
  get () {
@@ -306,9 +322,6 @@ function fastify (options) {
306
322
  },
307
323
  version: {
308
324
  get () {
309
- if (versionLoaded === false) {
310
- version = loadVersion()
311
- }
312
325
  return version
313
326
  }
314
327
  },
@@ -623,6 +636,12 @@ function fastify (options) {
623
636
  this[kErrorHandler] = func.bind(this)
624
637
  return this
625
638
  }
639
+
640
+ function printRoutes (opts = {}) {
641
+ // includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
642
+ opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
643
+ return router.printRoutes(opts)
644
+ }
626
645
  }
627
646
 
628
647
  function validateSchemaErrorFormatter (schemaErrorFormatter) {
@@ -652,20 +671,6 @@ function wrapRouting (httpHandler, { rewriteUrl, logger }) {
652
671
  }
653
672
  }
654
673
 
655
- function loadVersion () {
656
- versionLoaded = true
657
- const fs = require('fs')
658
- const path = require('path')
659
- try {
660
- const pkgPath = path.join(__dirname, 'package.json')
661
- fs.accessSync(pkgPath, fs.constants.R_OK)
662
- const pkg = JSON.parse(fs.readFileSync(pkgPath))
663
- return pkg.name === 'fastify' ? pkg.version : undefined
664
- } catch (e) {
665
- return undefined
666
- }
667
- }
668
-
669
674
  /**
670
675
  * These export configurations enable JS and TS developers
671
676
  * to consumer fastify in whatever way best suits their needs.
@@ -75,7 +75,9 @@ function handler (request, reply) {
75
75
  }
76
76
 
77
77
  function preValidationCallback (err, request, reply) {
78
- if (reply.sent === true) return
78
+ if (reply.sent === true ||
79
+ reply.raw.writableEnded === true ||
80
+ reply.raw.writable === false) return
79
81
 
80
82
  if (err != null) {
81
83
  reply.send(err)
@@ -107,7 +109,9 @@ function preValidationCallback (err, request, reply) {
107
109
  }
108
110
 
109
111
  function preHandlerCallback (err, request, reply) {
110
- if (reply.sent) return
112
+ if (reply.sent ||
113
+ reply.raw.writableEnded === true ||
114
+ reply.raw.writable === false) return
111
115
 
112
116
  if (err != null) {
113
117
  reply.send(err)
package/lib/hooks.js CHANGED
@@ -244,5 +244,6 @@ module.exports = {
244
244
  onSendHookRunner,
245
245
  hookIterator,
246
246
  hookRunnerApplication,
247
- lifecycleHooks
247
+ lifecycleHooks,
248
+ supportedHooks
248
249
  }
@@ -37,8 +37,13 @@ module.exports = function override (old, fn, opts) {
37
37
  old[kChildren].push(instance)
38
38
  instance.ready = old[kAvvioBoot].bind(instance)
39
39
  instance[kChildren] = []
40
+
40
41
  instance[kReply] = Reply.buildReply(instance[kReply])
42
+ instance[kReply].prototype.server = instance
43
+
41
44
  instance[kRequest] = Request.buildRequest(instance[kRequest])
45
+ instance[kRequest].prototype.server = instance
46
+
42
47
  instance[kContentTypeParser] = ContentTypeParser.helpers.buildContentTypeParser(instance[kContentTypeParser])
43
48
  instance[kHooks] = buildHooks(instance[kHooks])
44
49
  instance[kRoutePrefix] = buildRoutePrefix(instance[kRoutePrefix], opts.prefix)
@@ -109,9 +109,7 @@ function registerPluginName (fn) {
109
109
 
110
110
  function registerPlugin (fn) {
111
111
  registerPluginName.call(this, fn)
112
- if (this.version !== undefined) {
113
- checkVersion.call(this, fn)
114
- }
112
+ checkVersion.call(this, fn)
115
113
  checkDecorators.call(this, fn)
116
114
  checkDependencies.call(this, fn)
117
115
  return shouldSkipOverride(fn)
package/lib/reply.js CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  kSchemaResponse,
9
9
  kFourOhFourContext,
10
10
  kReplyErrorHandlerCalled,
11
+ kReplySent,
11
12
  kReplySentOverwritten,
12
13
  kReplyStartTime,
13
14
  kReplySerializer,
@@ -51,6 +52,7 @@ const warning = require('./warnings')
51
52
 
52
53
  function Reply (res, request, log) {
53
54
  this.raw = res
55
+ this[kReplySent] = false
54
56
  this[kReplySerializer] = null
55
57
  this[kReplyErrorHandlerCalled] = false
56
58
  this[kReplyIsError] = false
@@ -77,26 +79,19 @@ Object.defineProperties(Reply.prototype, {
77
79
  sent: {
78
80
  enumerable: true,
79
81
  get () {
80
- // We are checking whether reply was manually marked as sent or the
81
- // response has ended. The response.writableEnded property was added in
82
- // Node.js v12.9.0. Since fastify supports older Node.js versions as well,
83
- // we have to take response.finished property in consideration when
84
- // applicable. The response.finished will be always true when the request
85
- // method is HEAD and http2 is used, so we have to combine that with
86
- // response.headersSent.
87
- // TODO: remove fallback when the lowest supported Node.js version >= v12.9.0
88
- return (this[kReplySentOverwritten] || (typeof this.raw.writableEnded !== 'undefined' ? this.raw.writableEnded : (this.raw.headersSent && this.raw.finished))) === true
82
+ return this[kReplySent]
89
83
  },
90
84
  set (value) {
91
85
  if (value !== true) {
92
86
  throw new FST_ERR_REP_SENT_VALUE()
93
87
  }
94
88
 
95
- if (this.sent) {
89
+ if (this[kReplySent]) {
96
90
  throw new FST_ERR_REP_ALREADY_SENT()
97
91
  }
98
92
 
99
93
  this[kReplySentOverwritten] = true
94
+ this[kReplySent] = true
100
95
  }
101
96
  },
102
97
  statusCode: {
@@ -106,11 +101,15 @@ Object.defineProperties(Reply.prototype, {
106
101
  set (value) {
107
102
  this.code(value)
108
103
  }
104
+ },
105
+ server: {
106
+ value: null,
107
+ writable: true
109
108
  }
110
109
  })
111
110
 
112
111
  Reply.prototype.hijack = function () {
113
- this[kReplySentOverwritten] = true
112
+ this[kReplySent] = true
114
113
  return this
115
114
  }
116
115
 
@@ -119,7 +118,7 @@ Reply.prototype.send = function (payload) {
119
118
  throw new FST_ERR_SEND_INSIDE_ONERR()
120
119
  }
121
120
 
122
- if (this.sent) {
121
+ if (this[kReplySent]) {
123
122
  this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT() }, 'Reply already sent')
124
123
  return this
125
124
  }
@@ -378,6 +377,7 @@ function preserializeHookEnd (err, request, reply, payload) {
378
377
  }
379
378
 
380
379
  function onSendHook (reply, payload) {
380
+ reply[kReplySent] = true
381
381
  if (reply.context.onSend !== null) {
382
382
  onSendHookRunner(
383
383
  reply.context.onSend,
@@ -405,6 +405,8 @@ function onSendEnd (reply, payload) {
405
405
  const statusCode = res.statusCode
406
406
 
407
407
  if (payload === undefined || payload === null) {
408
+ reply[kReplySent] = true
409
+
408
410
  // according to https://tools.ietf.org/html/rfc7230#section-3.3.2
409
411
  // we cannot send a content-length for 304 and 204, and all status code
410
412
  // < 200.
@@ -434,6 +436,8 @@ function onSendEnd (reply, payload) {
434
436
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
435
437
  }
436
438
 
439
+ reply[kReplySent] = true
440
+
437
441
  res.writeHead(statusCode, reply[kReplyHeaders])
438
442
 
439
443
  // avoid ArgumentsAdaptorTrampoline from V8
@@ -503,6 +507,7 @@ function sendStream (payload, res, reply) {
503
507
  }
504
508
 
505
509
  function onErrorHook (reply, error, cb) {
510
+ reply[kReplySent] = true
506
511
  if (reply.context.onError !== null && reply[kReplyErrorHandlerCalled] === true) {
507
512
  reply[kReplyIsRunningOnErrorHook] = true
508
513
  onSendHookRunner(
@@ -538,6 +543,7 @@ function handleError (reply, error, cb) {
538
543
 
539
544
  const errorHandler = reply.context.errorHandler
540
545
  if (errorHandler && reply[kReplyErrorHandlerCalled] === false) {
546
+ reply[kReplySent] = false
541
547
  reply[kReplyIsError] = false
542
548
  reply[kReplyErrorHandlerCalled] = true
543
549
  reply[kReplyHeaders]['content-length'] = undefined
@@ -583,6 +589,7 @@ function handleError (reply, error, cb) {
583
589
  return
584
590
  }
585
591
 
592
+ reply[kReplySent] = true
586
593
  res.writeHead(res.statusCode, reply[kReplyHeaders])
587
594
  res.end(payload)
588
595
  }
@@ -644,6 +651,7 @@ function buildReply (R) {
644
651
  this.raw = res
645
652
  this[kReplyIsError] = false
646
653
  this[kReplyErrorHandlerCalled] = false
654
+ this[kReplySent] = false
647
655
  this[kReplySentOverwritten] = false
648
656
  this[kReplySerializer] = null
649
657
  this.request = request
@@ -656,6 +664,7 @@ function buildReply (R) {
656
664
  }
657
665
 
658
666
  function notFound (reply) {
667
+ reply[kReplySent] = false
659
668
  reply[kReplyIsError] = false
660
669
 
661
670
  if (reply.context[kFourOhFourContext] === null) {
package/lib/request.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const proxyAddr = require('@fastify/proxy-addr')
3
+ const proxyAddr = require('proxy-addr')
4
4
  const semver = require('semver')
5
5
  const warning = require('./warnings')
6
6
 
@@ -164,6 +164,10 @@ Object.defineProperties(Request.prototype, {
164
164
  get () {
165
165
  return this.raw.headers
166
166
  }
167
+ },
168
+ server: {
169
+ value: null,
170
+ writable: true
167
171
  }
168
172
  })
169
173
 
package/lib/route.js CHANGED
@@ -227,7 +227,7 @@ function buildRouting (options) {
227
227
  }
228
228
  const constraints = opts.constraints || {}
229
229
  if (opts.version) {
230
- warning.emit('FSTDEP006')
230
+ warning.emit('FSTDEP008')
231
231
  constraints.version = opts.version
232
232
  }
233
233
 
package/lib/symbols.js CHANGED
@@ -30,6 +30,7 @@ const keys = {
30
30
  kReplyIsError: Symbol('fastify.reply.isError'),
31
31
  kReplyHeaders: Symbol('fastify.reply.headers'),
32
32
  kReplyHasStatusCode: Symbol('fastify.reply.hasStatusCode'),
33
+ kReplySent: Symbol('fastify.reply.sent'),
33
34
  kReplySentOverwritten: Symbol('fastify.reply.sentOverwritten'),
34
35
  kReplyStartTime: Symbol('fastify.reply.startTime'),
35
36
  kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'),
@@ -2,6 +2,7 @@
2
2
 
3
3
  const {
4
4
  kReplyIsError,
5
+ kReplySent,
5
6
  kReplySentOverwritten
6
7
  } = require('./symbols')
7
8
 
@@ -15,24 +16,26 @@ function wrapThenable (thenable, reply) {
15
16
 
16
17
  // this is for async functions that
17
18
  // are using reply.send directly
18
- if (payload !== undefined || (reply.raw.statusCode === 204 && reply.sent === false)) {
19
+ if (payload !== undefined || (reply.raw.statusCode === 204 && reply[kReplySent] === false)) {
19
20
  // we use a try-catch internally to avoid adding a catch to another
20
21
  // promise, increase promise perf by 10%
21
22
  try {
22
23
  reply.send(payload)
23
24
  } catch (err) {
25
+ reply[kReplySent] = false
24
26
  reply[kReplyIsError] = true
25
27
  reply.send(err)
26
28
  }
27
- } else if (reply.sent === false) {
29
+ } else if (reply[kReplySent] === false) {
28
30
  reply.log.error({ err: new FST_ERR_PROMISE_NOT_FULFILLED() }, "Promise may not be fulfilled with 'undefined' when statusCode is not 204")
29
31
  }
30
32
  }, function (err) {
31
- if (reply.sent === true) {
33
+ if (reply[kReplySentOverwritten] === true || reply.sent === true) {
32
34
  reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
33
35
  return
34
36
  }
35
37
 
38
+ reply[kReplySent] = false
36
39
  reply[kReplyIsError] = true
37
40
  reply.send(err)
38
41
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -177,7 +177,6 @@
177
177
  },
178
178
  "dependencies": {
179
179
  "@fastify/ajv-compiler": "^1.0.0",
180
- "@fastify/proxy-addr": "^3.0.0",
181
180
  "abstract-logging": "^2.0.0",
182
181
  "avvio": "^7.1.2",
183
182
  "fast-json-stringify": "^2.5.2",
@@ -187,6 +186,7 @@
187
186
  "flatstr": "^1.0.12",
188
187
  "light-my-request": "^4.2.0",
189
188
  "pino": "^6.2.1",
189
+ "proxy-addr": "^2.0.7",
190
190
  "readable-stream": "^3.4.0",
191
191
  "rfdc": "^1.1.4",
192
192
  "secure-json-parse": "^2.0.0",
@@ -838,3 +838,42 @@ test('decorate* should not emit warning if string,bool,numbers are passed', t =>
838
838
  fastify.decorateReply('test_undefined', undefined)
839
839
  t.end('Done')
840
840
  })
841
+
842
+ test('Request/reply decorators should be able to access the server instance', async t => {
843
+ t.plan(6)
844
+
845
+ const server = require('..')({ logger: false })
846
+ server.decorateRequest('assert', rootAssert)
847
+ server.decorateReply('assert', rootAssert)
848
+
849
+ server.get('/root-assert', async (req, rep) => {
850
+ req.assert()
851
+ rep.assert()
852
+ return 'done'
853
+ })
854
+
855
+ server.register(async instance => {
856
+ instance.decorateRequest('assert', nestedAssert)
857
+ instance.decorateReply('assert', nestedAssert)
858
+ instance.decorate('foo', 'bar')
859
+
860
+ instance.get('/nested-assert', async (req, rep) => {
861
+ req.assert()
862
+ rep.assert()
863
+ return 'done'
864
+ })
865
+ })
866
+
867
+ await server.inject({ method: 'GET', url: '/root-assert' })
868
+ await server.inject({ method: 'GET', url: '/nested-assert' })
869
+
870
+ // ----
871
+ function rootAssert () {
872
+ t.equal(this.server, server)
873
+ }
874
+
875
+ function nestedAssert () {
876
+ t.not(this.server, server)
877
+ t.equal(this.server.foo, 'bar')
878
+ }
879
+ })
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('tap')
4
+ const semver = require('semver')
4
5
  const handleRequest = require('../../lib/handleRequest')
5
6
  const internals = require('../../lib/handleRequest')[Symbol.for('internals')]
6
7
  const Request = require('../../lib/request')
@@ -107,8 +108,12 @@ test('handler function - preValidationCallback with finished response', t => {
107
108
  t.plan(0)
108
109
  const res = {}
109
110
  // Be sure to check only `writableEnded` where is available
110
- res.writableEnded = true
111
-
111
+ if (semver.gte(process.versions.node, '12.9.0')) {
112
+ res.writableEnded = true
113
+ } else {
114
+ res.writable = false
115
+ res.finished = true
116
+ }
112
117
  res.end = () => {
113
118
  t.fail()
114
119
  }
@@ -133,7 +138,7 @@ test('handler function - preValidationCallback with finished response (< v12.9.0
133
138
  t.plan(0)
134
139
  const res = {}
135
140
  // Be sure to check only `writableEnded` where is available
136
- res.headersSent = true
141
+ res.writable = false
137
142
  res.finished = true
138
143
 
139
144
  res.end = () => {