fastify 5.11.1 → 5.11.3
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/README.md +7 -7
- package/docs/Reference/Encapsulation.md +31 -2
- package/docs/Reference/Errors.md +76 -3
- package/docs/Reference/Request.md +18 -6
- package/docs/Reference/Routes.md +13 -0
- package/fastify.js +1 -1
- package/lib/content-type-parser.js +4 -0
- package/lib/decorate.js +1 -1
- package/lib/log-controller.js +1 -1
- package/lib/reply.js +7 -3
- package/package.json +1 -1
- package/test/content-parser.test.js +32 -0
- package/test/decorator-instance-properties.test.js +49 -0
- package/test/reply-trailers.test.js +92 -4
- package/types/logger.d.ts +1 -1
- package/.pi/bash-confirm-whitelist.json +0 -180
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ mkdir my-app
|
|
|
73
73
|
cd my-app
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
Generate a
|
|
76
|
+
Generate a Fastify project with `npm init`:
|
|
77
77
|
|
|
78
78
|
```sh
|
|
79
79
|
npm init fastify
|
|
@@ -121,7 +121,7 @@ import Fastify from 'fastify'
|
|
|
121
121
|
const fastify = Fastify({
|
|
122
122
|
logger: true
|
|
123
123
|
})
|
|
124
|
-
//
|
|
124
|
+
// CommonJS
|
|
125
125
|
const fastify = require('fastify')({
|
|
126
126
|
logger: true
|
|
127
127
|
})
|
|
@@ -147,7 +147,7 @@ import Fastify from 'fastify'
|
|
|
147
147
|
const fastify = Fastify({
|
|
148
148
|
logger: true
|
|
149
149
|
})
|
|
150
|
-
//
|
|
150
|
+
// CommonJS
|
|
151
151
|
const fastify = require('fastify')({
|
|
152
152
|
logger: true
|
|
153
153
|
})
|
|
@@ -181,7 +181,7 @@ If you learn best by reading code, explore the official [demo](https://github.co
|
|
|
181
181
|
### Core features
|
|
182
182
|
|
|
183
183
|
- **Highly performant:** as far as we know, Fastify is one of the fastest web
|
|
184
|
-
frameworks in town, depending on the code complexity we can serve
|
|
184
|
+
frameworks in town, depending on the code complexity we can serve more than 76
|
|
185
185
|
thousand requests per second.
|
|
186
186
|
- **Extensible:** Fastify is fully extensible via its hooks, plugins, and
|
|
187
187
|
decorators.
|
|
@@ -189,7 +189,7 @@ If you learn best by reading code, explore the official [demo](https://github.co
|
|
|
189
189
|
Schema](https://json-schema.org/) to validate your routes and serialize your
|
|
190
190
|
outputs. Internally Fastify compiles the schema in a highly performant
|
|
191
191
|
function.
|
|
192
|
-
- **Logging:** logs are extremely important but are costly; we chose the best
|
|
192
|
+
- **Logging:** logs are extremely important, but are costly; we chose the best
|
|
193
193
|
logger to almost remove this cost, [Pino](https://github.com/pinojs/pino)!
|
|
194
194
|
- **Developer friendly:** the framework is built to be very expressive and help
|
|
195
195
|
developers in their daily use without sacrificing performance and
|
|
@@ -212,8 +212,8 @@ second average
|
|
|
212
212
|
| - | | | |
|
|
213
213
|
| `http.Server` | 16.14.2 | ✗ | 74,513 |
|
|
214
214
|
|
|
215
|
-
These benchmarks taken using https://github.com/fastify/benchmarks. This is
|
|
216
|
-
synthetic "hello world" benchmark that aims to evaluate the framework overhead.
|
|
215
|
+
These benchmarks were taken using https://github.com/fastify/benchmarks. This is
|
|
216
|
+
a synthetic "hello world" benchmark that aims to evaluate the framework overhead.
|
|
217
217
|
The overhead that each framework has on your application depends on your
|
|
218
218
|
application. You should __always__ benchmark if performance matters to you.
|
|
219
219
|
|
|
@@ -140,7 +140,7 @@ update the code as follows:
|
|
|
140
140
|
'use strict'
|
|
141
141
|
|
|
142
142
|
const fastify = require('fastify')()
|
|
143
|
-
const
|
|
143
|
+
const fp = require('fastify-plugin')
|
|
144
144
|
|
|
145
145
|
fastify.decorateRequest('answer', 42)
|
|
146
146
|
|
|
@@ -161,7 +161,7 @@ fastify.register(async function publicContext (childServer) {
|
|
|
161
161
|
}
|
|
162
162
|
})
|
|
163
163
|
|
|
164
|
-
childServer.register(
|
|
164
|
+
childServer.register(fp(grandchildContext))
|
|
165
165
|
|
|
166
166
|
async function grandchildContext (grandchildServer) {
|
|
167
167
|
grandchildServer.decorateRequest('bar', 'bar')
|
|
@@ -192,4 +192,33 @@ curl http://127.0.0.1:8000/three
|
|
|
192
192
|
# {"answer":42,"foo":"foo","bar":"bar"}
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
`fastify-plugin` breaks encapsulation only for the plugin it wraps. Plugins
|
|
196
|
+
registered inside it without `fastify-plugin` still create new encapsulated
|
|
197
|
+
contexts:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
'use strict'
|
|
201
|
+
|
|
202
|
+
const fastify = require('fastify')()
|
|
203
|
+
const fp = require('fastify-plugin')
|
|
204
|
+
|
|
205
|
+
fastify.register(fp(async function sharedContext (childServer) {
|
|
206
|
+
childServer.decorate('foo', 'foo')
|
|
207
|
+
|
|
208
|
+
childServer.register(async function encapsulatedContext (grandchildServer) {
|
|
209
|
+
grandchildServer.decorate('bar', 'bar')
|
|
210
|
+
})
|
|
211
|
+
}))
|
|
212
|
+
|
|
213
|
+
await fastify.ready()
|
|
214
|
+
|
|
215
|
+
console.log(fastify.foo) // 'foo'
|
|
216
|
+
console.log(fastify.bar) // undefined
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The `foo` decorator is available in the root context because it is added
|
|
220
|
+
directly by the plugin wrapped with `fastify-plugin`. The nested `register`
|
|
221
|
+
call still creates a grandchild context, so the `bar` decorator remains
|
|
222
|
+
available only in that context and its children.
|
|
223
|
+
|
|
195
224
|
[fastify-plugin]: https://github.com/fastify/fastify-plugin
|
package/docs/Reference/Errors.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- [Errors In Fastify](#errors-in-fastify)
|
|
12
12
|
- [Errors In Input Data](#errors-in-input-data)
|
|
13
13
|
- [Catching Uncaught Errors In Fastify](#catching-uncaught-errors-in-fastify)
|
|
14
|
+
- [What The Default Error Handler Sends](#what-the-default-error-handler-sends)
|
|
14
15
|
- [Errors In Fastify Lifecycle Hooks And A Custom Error Handler](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler)
|
|
15
16
|
- [Fastify Error Codes](#fastify-error-codes)
|
|
16
17
|
- [FST_ERR_NOT_FOUND](#fst_err_not_found)
|
|
@@ -142,10 +143,82 @@ performance. This includes:
|
|
|
142
143
|
})`
|
|
143
144
|
|
|
144
145
|
In both cases, the error will be caught safely and routed to Fastify's default
|
|
145
|
-
error handler, resulting in a
|
|
146
|
+
error handler, resulting in a `500 Internal Server Error` response.
|
|
146
147
|
|
|
147
|
-
|
|
148
|
-
|
|
148
|
+
#### What The Default Error Handler Sends
|
|
149
|
+
The default error handler serializes the error into a JSON body with the
|
|
150
|
+
`statusCode`, `error`, and `message` properties, plus `code` when the error
|
|
151
|
+
carries one:
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
app.get('/', async () => { throw new Error('kaboom') })
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"statusCode": 500,
|
|
160
|
+
"error": "Internal Server Error",
|
|
161
|
+
"message": "kaboom"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The `error` property is the generic HTTP status text, but **`message` is
|
|
166
|
+
`error.message` verbatim**. This applies to every status code, including `500`.
|
|
167
|
+
Fastify's built-in error serializer emits only these four properties, so the
|
|
168
|
+
stack trace is not part of the default payload — but a route-level response
|
|
169
|
+
schema replaces that serializer, and one that declares a `stack` property will
|
|
170
|
+
serialize it.
|
|
171
|
+
|
|
172
|
+
> Security:
|
|
173
|
+
> Because `message` and `code` are forwarded as-is, errors thrown by libraries
|
|
174
|
+
> deeper in your application are exposed to the client. A database driver
|
|
175
|
+
> error, for example, can leak schema details and query text:
|
|
176
|
+
>
|
|
177
|
+
> ```json
|
|
178
|
+
> {
|
|
179
|
+
> "statusCode": 500,
|
|
180
|
+
> "code": "ER_BAD_FIELD_ERROR",
|
|
181
|
+
> "error": "Internal Server Error",
|
|
182
|
+
> "message": "Unknown column 'username' in 'field list'"
|
|
183
|
+
> }
|
|
184
|
+
> ```
|
|
185
|
+
>
|
|
186
|
+
> Fastify does not distinguish between development and production here. If
|
|
187
|
+
> unexpected errors must not reach the client, handle this explicitly.
|
|
188
|
+
|
|
189
|
+
Register a [`setErrorHandler`](./Server.md#seterrorhandler) to replace the
|
|
190
|
+
message for errors you did not raise deliberately, while letting the ones you
|
|
191
|
+
did pass through:
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
app.setErrorHandler(function (error, request, reply) {
|
|
195
|
+
// Errors with a statusCode below 500 were raised deliberately by this
|
|
196
|
+
// application, as were validation errors. A status code below 500 is not on
|
|
197
|
+
// its own a guarantee that the message is safe to expose — narrow this
|
|
198
|
+
// condition if any of yours are not.
|
|
199
|
+
if (error.validation || (error.statusCode && error.statusCode < 500)) {
|
|
200
|
+
return reply.send(error)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Anything else is unexpected: log it, but do not describe it to the client.
|
|
204
|
+
this.log.error({ err: error }, 'unhandled error')
|
|
205
|
+
reply.status(500).send({
|
|
206
|
+
statusCode: 500,
|
|
207
|
+
error: 'Internal Server Error',
|
|
208
|
+
message: 'Internal Server Error'
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The handler above is registered on the root instance, so it applies to every
|
|
214
|
+
route. Error handlers are encapsulated; for how they resolve across plugin
|
|
215
|
+
contexts, see
|
|
216
|
+
[the next section](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler).
|
|
217
|
+
|
|
218
|
+
Note that a route-level response schema is still applied to whatever the error
|
|
219
|
+
handler sends, and will reshape the payload accordingly — including properties
|
|
220
|
+
the built-in error serializer would have omitted, such as `stack`. See
|
|
221
|
+
[Serialization](./Validation-and-Serialization.md#serialization).
|
|
149
222
|
|
|
150
223
|
### Errors In Fastify Lifecycle Hooks And A Custom Error Handler
|
|
151
224
|
|
|
@@ -9,7 +9,11 @@ Request is a core Fastify object containing the following fields:
|
|
|
9
9
|
- `body` - The request payload, see [Content-Type Parser](./ContentTypeParser.md)
|
|
10
10
|
for details on what request payloads Fastify natively parses and how to support
|
|
11
11
|
other content types.
|
|
12
|
-
- `params` - The params matching the URL.
|
|
12
|
+
- `params` - The params matching the URL. Values are percent-decoded
|
|
13
|
+
(for example `%20` becomes a space, and `%2f` becomes `/`). Decoded
|
|
14
|
+
values may contain `.`, `..`, `/`, or other characters; treat them as
|
|
15
|
+
untrusted input. See the security note below and
|
|
16
|
+
[Routes - Url building](./Routes.md#url-building).
|
|
13
17
|
- [`headers`](#headers) - The headers getter and setter.
|
|
14
18
|
- `raw` - The incoming HTTP request from Node core.
|
|
15
19
|
- `server` - The Fastify server instance, scoped to the current
|
|
@@ -35,13 +39,21 @@ Request is a core Fastify object containing the following fields:
|
|
|
35
39
|
[`trustProxy`](./Server.md#factory-trust-proxy) is enabled).
|
|
36
40
|
|
|
37
41
|
> ⚠️ Security:
|
|
42
|
+
> `request.params`, `request.query`, `request.headers`, and `request.body`
|
|
43
|
+
> are untrusted network input. Route parameter values are percent-decoded
|
|
44
|
+
> before your handler runs, so a segment like `..%2ffile` becomes `../file`
|
|
45
|
+
> in `request.params`. Do not use parameter values as filesystem paths,
|
|
46
|
+
> template names, or redirect targets without validating or containing
|
|
47
|
+
> them. Prefer [`@fastify/static`](https://github.com/fastify/fastify-static)
|
|
48
|
+
> (or `reply.sendFile`) when serving files from a root directory.
|
|
49
|
+
>
|
|
38
50
|
> `request.ip`, `request.ips`, `request.host`, `request.hostname`,
|
|
39
51
|
> `request.port`, and `request.protocol` come from request metadata
|
|
40
|
-
> (socket and/or forwarding headers) and should be treated as
|
|
41
|
-
> Fastify does not perform security validation for
|
|
42
|
-
> If these values are used in security-sensitive decisions,
|
|
43
|
-
> be validated explicitly (for example: trusted proxy
|
|
44
|
-
> allow-lists, strict parsing, and normalization).
|
|
52
|
+
> (socket and/or forwarding headers) and should also be treated as
|
|
53
|
+
> untrusted input. Fastify does not perform security validation for
|
|
54
|
+
> business logic. If these values are used in security-sensitive decisions,
|
|
55
|
+
> they must be validated explicitly (for example: trusted proxy
|
|
56
|
+
> configuration, allow-lists, strict parsing, and normalization).
|
|
45
57
|
|
|
46
58
|
- `method` - The method of the incoming request.
|
|
47
59
|
- `url` - The URL of the incoming request.
|
package/docs/Reference/Routes.md
CHANGED
|
@@ -328,6 +328,19 @@ Prefer a single parameter approach, especially on routes that are on the hot
|
|
|
328
328
|
path of your application. For more details, see
|
|
329
329
|
[find-my-way](https://github.com/delvedor/find-my-way).
|
|
330
330
|
|
|
331
|
+
> ⚠️ Security:
|
|
332
|
+
> Fastify (via find-my-way) percent-decodes route parameters and wildcards
|
|
333
|
+
> before they reach your handler. Encoded separators in a segment are
|
|
334
|
+
> decoded in the parameter value: for a route `/download/:file`, a request
|
|
335
|
+
> to `/download/..%2fsecret.txt` yields
|
|
336
|
+
> `request.params.file === '../secret.txt'`. Parameters are untrusted
|
|
337
|
+
> input. Do not pass them to `path.join`, `fs` APIs, template engines, or
|
|
338
|
+
> redirects without validation or path containment. To serve files from a
|
|
339
|
+
> directory root, use
|
|
340
|
+
> [`@fastify/static`](https://github.com/fastify/fastify-static) instead of
|
|
341
|
+
> joining `request.params` into a filesystem path yourself. See also
|
|
342
|
+
> [Request](./Request.md).
|
|
343
|
+
|
|
331
344
|
To include a colon in a path without declaring a parameter, use a double colon.
|
|
332
345
|
For example:
|
|
333
346
|
```js
|
package/fastify.js
CHANGED
|
@@ -145,6 +145,10 @@ ContentTypeParser.prototype.getParser = function (contentType) {
|
|
|
145
145
|
|
|
146
146
|
for (let j = 0; j !== this.parserRegExpList.length; ++j) {
|
|
147
147
|
const parserRegExp = this.parserRegExpList[j]
|
|
148
|
+
// A parser registered with a `g`/`y` flagged RegExp keeps a mutable
|
|
149
|
+
// lastIndex between calls, so a plain `.test()` would skip part of the
|
|
150
|
+
// next content type and match inconsistently. Reset it before testing.
|
|
151
|
+
parserRegExp.lastIndex = 0
|
|
148
152
|
if (parserRegExp.test(ct)) {
|
|
149
153
|
parser = this.customParsers.get(parserRegExp.toString())
|
|
150
154
|
this.cache.set(ct, parser)
|
package/lib/decorate.js
CHANGED
|
@@ -121,7 +121,7 @@ function checkDependencies (instance, name, deps) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
for (let i = 0; i !== deps.length; ++i) {
|
|
124
|
-
if (!checkExistence(instance, deps[i])) {
|
|
124
|
+
if (!checkExistence(instance, deps[i]) && !hasInstanceProperty(instance, deps[i])) {
|
|
125
125
|
throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])
|
|
126
126
|
}
|
|
127
127
|
}
|
package/lib/log-controller.js
CHANGED
|
@@ -54,7 +54,7 @@ class LogController {
|
|
|
54
54
|
* Logs the outcome of a completed request.
|
|
55
55
|
* Uses `error` level when an error is present, `info` otherwise.
|
|
56
56
|
*
|
|
57
|
-
* @param {Error | null} error Error that occurred during the response, if any.
|
|
57
|
+
* @param {Error | null | undefined} error Error that occurred during the response, if any.
|
|
58
58
|
* @param {object} request Fastify request object.
|
|
59
59
|
* @param {object} reply Fastify reply object.
|
|
60
60
|
* @param {object} [metadata] Extra contextual data (unused).
|
package/lib/reply.js
CHANGED
|
@@ -595,9 +595,13 @@ function onSendEnd (reply, payload) {
|
|
|
595
595
|
header += ' '
|
|
596
596
|
header += trailerName
|
|
597
597
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
598
|
+
if (header !== '') {
|
|
599
|
+
// it must be chunked for trailer to work
|
|
600
|
+
reply.header('Transfer-Encoding', 'chunked')
|
|
601
|
+
reply.header('Trailer', header.trim())
|
|
602
|
+
} else {
|
|
603
|
+
reply[kReplyTrailers] = null
|
|
604
|
+
}
|
|
601
605
|
}
|
|
602
606
|
|
|
603
607
|
// since Response contain status code, headers and body,
|
package/package.json
CHANGED
|
@@ -578,6 +578,38 @@ test('content-type match parameters - regexp', async t => {
|
|
|
578
578
|
})
|
|
579
579
|
})
|
|
580
580
|
|
|
581
|
+
test('content-type match - RegExp with global flag', async t => {
|
|
582
|
+
t.plan(4)
|
|
583
|
+
|
|
584
|
+
const fastify = Fastify()
|
|
585
|
+
fastify.addContentTypeParser(/^application\/.+\+xml$/g, { parseAs: 'string' }, function (request, body, done) {
|
|
586
|
+
done(null, body)
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
fastify.post('/', async (request) => request.body)
|
|
590
|
+
|
|
591
|
+
// Two distinct content types that both match the parser. A RegExp with the
|
|
592
|
+
// `g` flag keeps a mutable lastIndex between test() calls, so the second
|
|
593
|
+
// content type must still match rather than fall through to 415.
|
|
594
|
+
const first = await fastify.inject({
|
|
595
|
+
method: 'POST',
|
|
596
|
+
path: '/',
|
|
597
|
+
headers: { 'content-type': 'application/vnd.a+xml' },
|
|
598
|
+
body: '<a/>'
|
|
599
|
+
})
|
|
600
|
+
const second = await fastify.inject({
|
|
601
|
+
method: 'POST',
|
|
602
|
+
path: '/',
|
|
603
|
+
headers: { 'content-type': 'application/vnd.b+xml' },
|
|
604
|
+
body: '<b/>'
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
t.assert.strictEqual(first.statusCode, 200)
|
|
608
|
+
t.assert.strictEqual(first.payload, '<a/>')
|
|
609
|
+
t.assert.strictEqual(second.statusCode, 200)
|
|
610
|
+
t.assert.strictEqual(second.payload, '<b/>')
|
|
611
|
+
})
|
|
612
|
+
|
|
581
613
|
test('content-type fail when parameters not match - string 1', async t => {
|
|
582
614
|
t.plan(1)
|
|
583
615
|
|
|
@@ -61,3 +61,52 @@ test('decorateRequest still works for non-built-in names', (t, done) => {
|
|
|
61
61
|
done()
|
|
62
62
|
})
|
|
63
63
|
})
|
|
64
|
+
|
|
65
|
+
test('decorateRequest accepts built-in request properties as dependencies', t => {
|
|
66
|
+
t.plan(6)
|
|
67
|
+
const fastify = Fastify()
|
|
68
|
+
for (const name of ['id', 'params', 'raw', 'query', 'log', 'body']) {
|
|
69
|
+
t.assert.doesNotThrow(() => fastify.decorateRequest(`uses_${name}`, null, [name]))
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('decorateReply accepts built-in reply properties as dependencies', t => {
|
|
74
|
+
t.plan(3)
|
|
75
|
+
const fastify = Fastify()
|
|
76
|
+
for (const name of ['raw', 'request', 'log']) {
|
|
77
|
+
t.assert.doesNotThrow(() => fastify.decorateReply(`uses_${name}`, null, [name]))
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('decorateRequest accepts built-in properties as dependencies inside a plugin', (t, done) => {
|
|
82
|
+
t.plan(2)
|
|
83
|
+
const fastify = Fastify()
|
|
84
|
+
fastify.register(async (instance) => {
|
|
85
|
+
instance.decorateRequest('scopedExtra', null, ['body'])
|
|
86
|
+
t.assert.equal(instance.hasRequestDecorator('scopedExtra'), true)
|
|
87
|
+
})
|
|
88
|
+
fastify.ready((err) => {
|
|
89
|
+
t.assert.ifError(err)
|
|
90
|
+
done()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('decorateRequest accepts built-in properties mixed with user decorators as dependencies', t => {
|
|
95
|
+
t.plan(1)
|
|
96
|
+
const fastify = Fastify()
|
|
97
|
+
fastify.decorateRequest('userProp', null)
|
|
98
|
+
t.assert.doesNotThrow(() => fastify.decorateRequest('mixed', null, ['userProp', 'body']))
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('decorateRequest still throws FST_ERR_DEC_MISSING_DEPENDENCY for unknown dependencies', t => {
|
|
102
|
+
t.plan(2)
|
|
103
|
+
const fastify = Fastify()
|
|
104
|
+
t.assert.throws(
|
|
105
|
+
() => fastify.decorateRequest('withUnknown', null, ['nonExistent']),
|
|
106
|
+
(err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY'
|
|
107
|
+
)
|
|
108
|
+
t.assert.throws(
|
|
109
|
+
() => fastify.decorateReply('withUnknown', null, ['body']),
|
|
110
|
+
(err) => err.code === 'FST_ERR_DEC_MISSING_DEPENDENCY'
|
|
111
|
+
)
|
|
112
|
+
})
|
|
@@ -142,6 +142,37 @@ test('send trailers when payload is stream', (t, testDone) => {
|
|
|
142
142
|
})
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
+
test('remove trailer while stream is being consumed', (t, testDone) => {
|
|
146
|
+
t.plan(5)
|
|
147
|
+
|
|
148
|
+
const fastify = Fastify()
|
|
149
|
+
|
|
150
|
+
fastify.get('/', function (request, reply) {
|
|
151
|
+
const stream = Readable.from((function * () {
|
|
152
|
+
reply.removeTrailer('ETag')
|
|
153
|
+
yield 'hello'
|
|
154
|
+
})())
|
|
155
|
+
|
|
156
|
+
reply.trailer('ETag', function () {
|
|
157
|
+
t.assert.fail('removed trailer should not be called')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
reply.send(stream)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
fastify.inject({
|
|
164
|
+
method: 'GET',
|
|
165
|
+
url: '/'
|
|
166
|
+
}, (error, res) => {
|
|
167
|
+
t.assert.ifError(error)
|
|
168
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
169
|
+
t.assert.strictEqual(res.payload, 'hello')
|
|
170
|
+
t.assert.strictEqual(res.headers.trailer, 'etag')
|
|
171
|
+
t.assert.strictEqual(res.trailers.etag, undefined)
|
|
172
|
+
testDone()
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
145
176
|
test('send trailers when using async-await', (t, testDone) => {
|
|
146
177
|
t.plan(5)
|
|
147
178
|
|
|
@@ -395,10 +426,67 @@ test('remove all trailers', (t, testDone) => {
|
|
|
395
426
|
}, (error, res) => {
|
|
396
427
|
t.assert.ifError(error)
|
|
397
428
|
t.assert.strictEqual(res.statusCode, 200)
|
|
398
|
-
t.assert.
|
|
399
|
-
t.assert.
|
|
400
|
-
t.assert.
|
|
401
|
-
t.assert.
|
|
429
|
+
t.assert.strictEqual(res.headers.trailer, undefined)
|
|
430
|
+
t.assert.strictEqual(res.trailers.etag, undefined)
|
|
431
|
+
t.assert.strictEqual(res.trailers['should-not-call'], undefined)
|
|
432
|
+
t.assert.strictEqual(res.headers['content-length'], '0')
|
|
433
|
+
testDone()
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
test('remove some trailers should keep trailer mode for the remaining ones', (t, testDone) => {
|
|
438
|
+
t.plan(6)
|
|
439
|
+
|
|
440
|
+
const fastify = Fastify()
|
|
441
|
+
|
|
442
|
+
fastify.get('/', function (request, reply) {
|
|
443
|
+
reply.trailer('ETag', function () {
|
|
444
|
+
t.assert.fail('removed trailer should not be called')
|
|
445
|
+
})
|
|
446
|
+
reply.removeTrailer('ETag')
|
|
447
|
+
reply.trailer('Content-MD5', function (reply, payload, done) {
|
|
448
|
+
done(null, 'custom-md5')
|
|
449
|
+
})
|
|
450
|
+
reply.send('hello')
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
fastify.inject({
|
|
454
|
+
method: 'GET',
|
|
455
|
+
url: '/'
|
|
456
|
+
}, (error, res) => {
|
|
457
|
+
t.assert.ifError(error)
|
|
458
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
459
|
+
t.assert.strictEqual(res.headers.trailer, 'content-md5')
|
|
460
|
+
t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked')
|
|
461
|
+
t.assert.strictEqual(res.headers['content-length'], undefined)
|
|
462
|
+
t.assert.strictEqual(res.trailers['content-md5'], 'custom-md5')
|
|
463
|
+
testDone()
|
|
464
|
+
})
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
test('remove all trailers should behave like no trailers were registered', (t, testDone) => {
|
|
468
|
+
t.plan(6)
|
|
469
|
+
|
|
470
|
+
const fastify = Fastify()
|
|
471
|
+
|
|
472
|
+
fastify.get('/', function (request, reply) {
|
|
473
|
+
reply.trailer('ETag', function () {
|
|
474
|
+
t.assert.fail('removed trailer should not be called')
|
|
475
|
+
})
|
|
476
|
+
reply.removeTrailer('ETag')
|
|
477
|
+
reply.send('hello')
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
fastify.inject({
|
|
481
|
+
method: 'GET',
|
|
482
|
+
url: '/'
|
|
483
|
+
}, (error, res) => {
|
|
484
|
+
t.assert.ifError(error)
|
|
485
|
+
t.assert.strictEqual(res.statusCode, 200)
|
|
486
|
+
t.assert.strictEqual(res.headers.trailer, undefined)
|
|
487
|
+
t.assert.strictEqual(res.headers['transfer-encoding'], undefined)
|
|
488
|
+
t.assert.strictEqual(res.headers['content-length'], '5')
|
|
489
|
+
t.assert.strictEqual(res.trailers.etag, undefined)
|
|
402
490
|
testDone()
|
|
403
491
|
})
|
|
404
492
|
})
|
package/types/logger.d.ts
CHANGED
|
@@ -124,7 +124,7 @@ export declare class LogController {
|
|
|
124
124
|
isLogDisabled (request: FastifyRequest): boolean
|
|
125
125
|
incomingRequest (request: FastifyRequest, reply: FastifyReply, metadata?: Record<string, unknown>): void
|
|
126
126
|
requestCompleted (
|
|
127
|
-
error: Error | null,
|
|
127
|
+
error: Error | null | undefined,
|
|
128
128
|
request: FastifyRequest,
|
|
129
129
|
reply: FastifyReply,
|
|
130
130
|
metadata?: Record<string, unknown>
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"entries": [
|
|
3
|
-
{
|
|
4
|
-
"type": "exact",
|
|
5
|
-
"value": "git fetch --all --prune --quiet && echo FETCH_OK || echo FETCH_FAILED",
|
|
6
|
-
"addedAt": "2026-02-07T10:03:03.909Z",
|
|
7
|
-
"note": "Always accept",
|
|
8
|
-
"source": "user"
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"type": "exact",
|
|
12
|
-
"value": "npm install --ignore-scripts",
|
|
13
|
-
"addedAt": "2026-02-07T10:21:13.104Z",
|
|
14
|
-
"note": "Always accept",
|
|
15
|
-
"source": "user"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"type": "exact",
|
|
19
|
-
"value": "npm run coverage:ci-check-coverage",
|
|
20
|
-
"addedAt": "2026-02-07T15:10:08.412Z",
|
|
21
|
-
"note": "Always accept",
|
|
22
|
-
"source": "user"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"type": "exact",
|
|
26
|
-
"value": "node --test test/web-api.test.js",
|
|
27
|
-
"addedAt": "2026-02-07T15:24:46.872Z",
|
|
28
|
-
"note": "Always accept",
|
|
29
|
-
"source": "user"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"type": "exact",
|
|
33
|
-
"value": "gpg --list-secret-keys --keyid-format=long || true && git config --get user.signingkey && git config --get commit.gpgsign",
|
|
34
|
-
"addedAt": "2026-02-11T04:42:44.716Z",
|
|
35
|
-
"note": "Always accept",
|
|
36
|
-
"source": "user"
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"type": "exact",
|
|
40
|
-
"value": "cd /home/matteo/repositories/fastify && npm run test:typescript",
|
|
41
|
-
"addedAt": "2026-02-19T20:22:59.298Z",
|
|
42
|
-
"note": "Always accept",
|
|
43
|
-
"source": "user"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"type": "exact",
|
|
47
|
-
"value": "cd /home/matteo/repositories/fastify && git checkout -- types/route.d.ts",
|
|
48
|
-
"addedAt": "2026-02-19T20:23:45.912Z",
|
|
49
|
-
"note": "Always accept",
|
|
50
|
-
"source": "user"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"type": "exact",
|
|
54
|
-
"value": "cd /home/matteo/repositories/fastify && git status --short && git diff -- types/route.d.ts test/types/route.test-d.ts | sed -n '1,220p'",
|
|
55
|
-
"addedAt": "2026-02-19T20:25:10.175Z",
|
|
56
|
-
"note": "Always accept",
|
|
57
|
-
"source": "user"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"type": "exact",
|
|
61
|
-
"value": "cd /home/matteo/repositories/fastify && git checkout -- types/route.d.ts test/types/route.test-d.ts && npm run test:typescript",
|
|
62
|
-
"addedAt": "2026-02-19T20:25:56.421Z",
|
|
63
|
-
"note": "Always accept",
|
|
64
|
-
"source": "user"
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"type": "exact",
|
|
68
|
-
"value": "cd /home/matteo/repositories/fastify && npm run lint:eslint -- types/route.d.ts test/types/route.test-d.ts",
|
|
69
|
-
"addedAt": "2026-02-19T20:27:12.547Z",
|
|
70
|
-
"note": "Always accept",
|
|
71
|
-
"source": "user"
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"type": "exact",
|
|
75
|
-
"value": "cd /home/matteo/repositories/fastify && git checkout -b fix/6513-route-hook-meta-return",
|
|
76
|
-
"addedAt": "2026-02-19T20:27:31.569Z",
|
|
77
|
-
"note": "Always accept",
|
|
78
|
-
"source": "user"
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"type": "exact",
|
|
82
|
-
"value": "cd /home/matteo/repositories/fastify && git add types/route.d.ts test/types/route.test-d.ts && git commit -m \"fix(types): allow async route hooks in shorthand options\"",
|
|
83
|
-
"addedAt": "2026-02-19T20:27:35.261Z",
|
|
84
|
-
"note": "Always accept",
|
|
85
|
-
"source": "user"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"type": "exact",
|
|
89
|
-
"value": "cd /home/matteo/repositories/fastify && npx tsc /tmp/ctx-test2.ts --noEmit --strict",
|
|
90
|
-
"addedAt": "2026-02-19T20:44:45.451Z",
|
|
91
|
-
"note": "Always accept",
|
|
92
|
-
"source": "user"
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
"type": "exact",
|
|
96
|
-
"value": "cd /home/matteo/repositories/fastify && npx tsd --files test/types/hooks.test-d.ts",
|
|
97
|
-
"addedAt": "2026-02-19T20:47:16.815Z",
|
|
98
|
-
"note": "Always accept",
|
|
99
|
-
"source": "user"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"type": "exact",
|
|
103
|
-
"value": "cd /home/matteo/repositories/fastify && npx tsd --files test/types/route.test-d.ts",
|
|
104
|
-
"addedAt": "2026-02-19T20:47:37.607Z",
|
|
105
|
-
"note": "Always accept",
|
|
106
|
-
"source": "user"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"type": "exact",
|
|
110
|
-
"value": "cd /home/matteo/repositories/fastify && npx tsd",
|
|
111
|
-
"addedAt": "2026-02-19T20:48:17.812Z",
|
|
112
|
-
"note": "Always accept",
|
|
113
|
-
"source": "user"
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
"type": "exact",
|
|
117
|
-
"value": "cd /home/matteo/repositories/fastify && npm run lint:eslint -- types/route.d.ts && npx tsd --files test/types/hooks.test-d.ts && npx tsd --files test/types/route.test-d.ts",
|
|
118
|
-
"addedAt": "2026-02-19T20:51:05.173Z",
|
|
119
|
-
"note": "Always accept",
|
|
120
|
-
"source": "user"
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
"type": "exact",
|
|
124
|
-
"value": "which gh && gh --version && gh auth status",
|
|
125
|
-
"addedAt": "2026-02-20T07:03:43.023Z",
|
|
126
|
-
"note": "Always accept",
|
|
127
|
-
"source": "user"
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
"type": "exact",
|
|
131
|
-
"value": "gh api repos/fastify/fastify/rulesets",
|
|
132
|
-
"addedAt": "2026-02-22T22:29:22.020Z",
|
|
133
|
-
"note": "Always accept",
|
|
134
|
-
"source": "user"
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"type": "exact",
|
|
138
|
-
"value": "node --test test/fastify-instance.test.js",
|
|
139
|
-
"addedAt": "2026-02-23T07:23:34.147Z",
|
|
140
|
-
"note": "Always accept",
|
|
141
|
-
"source": "user"
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
"type": "exact",
|
|
145
|
-
"value": "nl -ba scripts/validate-ecosystem-links.js | sed -n '1,220p'",
|
|
146
|
-
"addedAt": "2026-02-23T07:23:55.927Z",
|
|
147
|
-
"note": "Always accept",
|
|
148
|
-
"source": "user"
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"type": "pattern",
|
|
152
|
-
"value": "^npx\\s+eslint\\s+[\\w./~-]+\\s+[\\w./~-]+\\s+[\\w./~-]+\\s+[\\w./~-]+$",
|
|
153
|
-
"addedAt": "2026-03-09T12:41:11.065Z",
|
|
154
|
-
"note": "Always accept generic",
|
|
155
|
-
"source": "ai"
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
"type": "pattern",
|
|
159
|
-
"value": "^node\\s+--test\\s+[\\w./~-]+\\s+[\\w./~-]+$",
|
|
160
|
-
"addedAt": "2026-03-09T12:41:58.425Z",
|
|
161
|
-
"note": "Always accept generic",
|
|
162
|
-
"source": "ai"
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
"type": "pattern",
|
|
166
|
-
"value": "^npm\\s+run\\s+test:typescript$",
|
|
167
|
-
"addedAt": "2026-03-09T12:42:48.283Z",
|
|
168
|
-
"note": "Always accept generic",
|
|
169
|
-
"source": "ai"
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
"type": "exact",
|
|
173
|
-
"value": "gh search code \"request.host org:fastify\" --limit 20 --json repository,path,url",
|
|
174
|
-
"addedAt": "2026-03-09T21:03:22.953Z",
|
|
175
|
-
"note": "Always accept exact",
|
|
176
|
-
"source": "user"
|
|
177
|
-
}
|
|
178
|
-
],
|
|
179
|
-
"version": 2
|
|
180
|
-
}
|