fastify 5.2.2 → 5.3.1
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/SPONSORS.md +0 -1
- package/docs/Guides/Ecosystem.md +2 -0
- package/docs/Reference/Decorators.md +199 -0
- package/docs/Reference/Errors.md +2 -0
- package/fastify.js +3 -2
- package/lib/decorate.js +18 -3
- package/lib/errors.js +4 -0
- package/lib/reply.js +17 -2
- package/lib/request.js +28 -2
- package/lib/validation.js +11 -1
- package/package.json +3 -3
- package/test/build/error-serializer.test.js +1 -2
- package/test/custom-parser.0.test.js +160 -129
- package/test/custom-parser.1.test.js +77 -63
- package/test/custom-parser.4.test.js +55 -38
- package/test/custom-querystring-parser.test.js +46 -28
- package/test/decorator.test.js +174 -4
- package/test/fastify-instance.test.js +12 -2
- package/test/hooks.on-listen.test.js +17 -14
- package/test/internals/errors.test.js +14 -1
- package/test/listen.2.test.js +4 -1
- package/test/logger/instantiation.test.js +89 -96
- package/test/logger/logging.test.js +116 -120
- package/test/logger/options.test.js +97 -99
- package/test/logger/request.test.js +66 -66
- package/test/schema-validation.test.js +120 -0
- package/test/server.test.js +175 -0
- package/test/stream.4.test.js +38 -33
- package/test/toolkit.js +31 -0
- package/test/types/instance.test-d.ts +3 -0
- package/test/types/reply.test-d.ts +1 -0
- package/test/types/request.test-d.ts +4 -0
- package/test/types/type-provider.test-d.ts +40 -0
- package/test/upgrade.test.js +3 -6
- package/types/instance.d.ts +2 -0
- package/types/reply.d.ts +1 -0
- package/types/request.d.ts +2 -0
- package/types/type-provider.d.ts +12 -3
package/test/stream.4.test.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const test = t.test
|
|
3
|
+
const { test } = require('node:test')
|
|
5
4
|
const sget = require('simple-get').concat
|
|
6
5
|
const errors = require('http-errors')
|
|
7
6
|
const JSONStream = require('JSONStream')
|
|
@@ -10,7 +9,7 @@ const split = require('split2')
|
|
|
10
9
|
const Fastify = require('..')
|
|
11
10
|
const { kDisableRequestLogging } = require('../lib/symbols.js')
|
|
12
11
|
|
|
13
|
-
test('Destroying streams prematurely should call abort method', t => {
|
|
12
|
+
test('Destroying streams prematurely should call abort method', (t, testDone) => {
|
|
14
13
|
t.plan(7)
|
|
15
14
|
|
|
16
15
|
let fastify = null
|
|
@@ -23,7 +22,7 @@ test('Destroying streams prematurely should call abort method', t => {
|
|
|
23
22
|
}
|
|
24
23
|
})
|
|
25
24
|
} catch (e) {
|
|
26
|
-
t.fail()
|
|
25
|
+
t.assert.fail()
|
|
27
26
|
}
|
|
28
27
|
const stream = require('node:stream')
|
|
29
28
|
const http = require('node:http')
|
|
@@ -31,13 +30,14 @@ test('Destroying streams prematurely should call abort method', t => {
|
|
|
31
30
|
// Test that "premature close" errors are logged with level warn
|
|
32
31
|
logStream.on('data', line => {
|
|
33
32
|
if (line.res) {
|
|
34
|
-
t.
|
|
35
|
-
t.
|
|
33
|
+
t.assert.strictEqual(line.msg, 'stream closed prematurely')
|
|
34
|
+
t.assert.strictEqual(line.level, 30)
|
|
35
|
+
testDone()
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
fastify.get('/', function (request, reply) {
|
|
40
|
-
t.
|
|
40
|
+
t.assert.ok('Received request')
|
|
41
41
|
|
|
42
42
|
let sent = false
|
|
43
43
|
const reallyLongStream = new stream.Readable({
|
|
@@ -50,30 +50,30 @@ test('Destroying streams prematurely should call abort method', t => {
|
|
|
50
50
|
})
|
|
51
51
|
reallyLongStream.destroy = undefined
|
|
52
52
|
reallyLongStream.close = undefined
|
|
53
|
-
reallyLongStream.abort = () => t.ok('called')
|
|
53
|
+
reallyLongStream.abort = () => t.assert.ok('called')
|
|
54
54
|
reply.send(reallyLongStream)
|
|
55
55
|
})
|
|
56
56
|
|
|
57
57
|
fastify.listen({ port: 0 }, err => {
|
|
58
|
-
t.
|
|
59
|
-
t.
|
|
58
|
+
t.assert.ifError(err)
|
|
59
|
+
t.after(() => { fastify.close() })
|
|
60
60
|
|
|
61
61
|
const port = fastify.server.address().port
|
|
62
62
|
|
|
63
63
|
http.get(`http://localhost:${port}`, function (response) {
|
|
64
|
-
t.
|
|
64
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
65
65
|
response.on('readable', function () {
|
|
66
66
|
response.destroy()
|
|
67
67
|
})
|
|
68
68
|
// Node bug? Node never emits 'close' here.
|
|
69
69
|
response.on('aborted', function () {
|
|
70
|
-
t.
|
|
70
|
+
t.assert.ok('Response closed')
|
|
71
71
|
})
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
test('Destroying streams prematurely, log is disabled', t => {
|
|
76
|
+
test('Destroying streams prematurely, log is disabled', (t, testDone) => {
|
|
77
77
|
t.plan(4)
|
|
78
78
|
|
|
79
79
|
let fastify = null
|
|
@@ -82,7 +82,7 @@ test('Destroying streams prematurely, log is disabled', t => {
|
|
|
82
82
|
logger: false
|
|
83
83
|
})
|
|
84
84
|
} catch (e) {
|
|
85
|
-
t.fail()
|
|
85
|
+
t.assert.fail()
|
|
86
86
|
}
|
|
87
87
|
const stream = require('node:stream')
|
|
88
88
|
const http = require('node:http')
|
|
@@ -100,30 +100,33 @@ test('Destroying streams prematurely, log is disabled', t => {
|
|
|
100
100
|
}
|
|
101
101
|
})
|
|
102
102
|
reallyLongStream.destroy = true
|
|
103
|
-
reallyLongStream.close = () =>
|
|
103
|
+
reallyLongStream.close = () => {
|
|
104
|
+
t.assert.ok('called')
|
|
105
|
+
testDone()
|
|
106
|
+
}
|
|
104
107
|
reply.send(reallyLongStream)
|
|
105
108
|
})
|
|
106
109
|
|
|
107
110
|
fastify.listen({ port: 0 }, err => {
|
|
108
|
-
t.
|
|
109
|
-
t.
|
|
111
|
+
t.assert.ifError(err)
|
|
112
|
+
t.after(() => { fastify.close() })
|
|
110
113
|
|
|
111
114
|
const port = fastify.server.address().port
|
|
112
115
|
|
|
113
116
|
http.get(`http://localhost:${port}`, function (response) {
|
|
114
|
-
t.
|
|
117
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
115
118
|
response.on('readable', function () {
|
|
116
119
|
response.destroy()
|
|
117
120
|
})
|
|
118
121
|
// Node bug? Node never emits 'close' here.
|
|
119
122
|
response.on('aborted', function () {
|
|
120
|
-
t.
|
|
123
|
+
t.assert.ok('Response closed')
|
|
121
124
|
})
|
|
122
125
|
})
|
|
123
126
|
})
|
|
124
127
|
})
|
|
125
128
|
|
|
126
|
-
test('should respond with a stream1', t => {
|
|
129
|
+
test('should respond with a stream1', (t, testDone) => {
|
|
127
130
|
t.plan(5)
|
|
128
131
|
const fastify = Fastify()
|
|
129
132
|
|
|
@@ -135,25 +138,26 @@ test('should respond with a stream1', t => {
|
|
|
135
138
|
})
|
|
136
139
|
|
|
137
140
|
fastify.listen({ port: 0 }, err => {
|
|
138
|
-
t.
|
|
139
|
-
t.
|
|
141
|
+
t.assert.ifError(err)
|
|
142
|
+
t.after(() => { fastify.close() })
|
|
140
143
|
|
|
141
144
|
sget(`http://localhost:${fastify.server.address().port}`, function (err, response, body) {
|
|
142
|
-
t.
|
|
143
|
-
t.
|
|
144
|
-
t.
|
|
145
|
-
t.
|
|
145
|
+
t.assert.ifError(err)
|
|
146
|
+
t.assert.strictEqual(response.headers['content-type'], 'application/json')
|
|
147
|
+
t.assert.strictEqual(response.statusCode, 200)
|
|
148
|
+
t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }])
|
|
149
|
+
testDone()
|
|
146
150
|
})
|
|
147
151
|
})
|
|
148
152
|
})
|
|
149
153
|
|
|
150
|
-
test('return a 404 if the stream emits a 404 error', t => {
|
|
154
|
+
test('return a 404 if the stream emits a 404 error', (t, testDone) => {
|
|
151
155
|
t.plan(5)
|
|
152
156
|
|
|
153
157
|
const fastify = Fastify()
|
|
154
158
|
|
|
155
159
|
fastify.get('/', function (request, reply) {
|
|
156
|
-
t.
|
|
160
|
+
t.assert.ok('Received request')
|
|
157
161
|
|
|
158
162
|
const reallyLongStream = new Readable({
|
|
159
163
|
read: function () {
|
|
@@ -167,15 +171,16 @@ test('return a 404 if the stream emits a 404 error', t => {
|
|
|
167
171
|
})
|
|
168
172
|
|
|
169
173
|
fastify.listen({ port: 0 }, err => {
|
|
170
|
-
t.
|
|
171
|
-
t.
|
|
174
|
+
t.assert.ifError(err)
|
|
175
|
+
t.after(() => { fastify.close() })
|
|
172
176
|
|
|
173
177
|
const port = fastify.server.address().port
|
|
174
178
|
|
|
175
179
|
sget(`http://localhost:${port}`, function (err, response) {
|
|
176
|
-
t.
|
|
177
|
-
t.
|
|
178
|
-
t.
|
|
180
|
+
t.assert.ifError(err)
|
|
181
|
+
t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
|
|
182
|
+
t.assert.strictEqual(response.statusCode, 404)
|
|
183
|
+
testDone()
|
|
179
184
|
})
|
|
180
185
|
})
|
|
181
186
|
})
|
package/test/toolkit.js
CHANGED
|
@@ -30,3 +30,34 @@ exports.waitForCb = function (options) {
|
|
|
30
30
|
|
|
31
31
|
return { stepIn, patience }
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
exports.partialDeepStrictEqual = function partialDeepStrictEqual (actual, expected) {
|
|
35
|
+
if (typeof expected !== 'object' || expected === null) {
|
|
36
|
+
return actual === expected
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof actual !== 'object' || actual === null) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(expected)) {
|
|
44
|
+
if (!Array.isArray(actual)) return false
|
|
45
|
+
if (expected.length > actual.length) return false
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < expected.length; i++) {
|
|
48
|
+
if (!partialDeepStrictEqual(actual[i], expected[i])) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const key of Object.keys(expected)) {
|
|
56
|
+
if (!(key in actual)) return false
|
|
57
|
+
if (!partialDeepStrictEqual(actual[key], expected[key])) {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return true
|
|
63
|
+
}
|
|
@@ -522,6 +522,9 @@ expectError(server.decorateReply('typedTestReplyMethod', async function (x) {
|
|
|
522
522
|
return 'foo'
|
|
523
523
|
}))
|
|
524
524
|
|
|
525
|
+
const foo = server.getDecorator<string>('foo')
|
|
526
|
+
expectType<string>(foo)
|
|
527
|
+
|
|
525
528
|
const versionConstraintStrategy = {
|
|
526
529
|
name: 'version',
|
|
527
530
|
storage: () => ({
|
|
@@ -45,6 +45,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) {
|
|
|
45
45
|
expectAssignable<((input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpStatus?: string) => unknown)>(reply.serializeInput)
|
|
46
46
|
expectAssignable<((input: { [key: string]: unknown }, httpStatus: string) => unknown)>(reply.serializeInput)
|
|
47
47
|
expectType<ContextConfigDefault & FastifyRouteConfig & FastifyContextConfig>(reply.routeOptions.config)
|
|
48
|
+
expectType<string>(reply.getDecorator<string>('foo'))
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
interface ReplyPayload {
|
|
@@ -92,6 +92,10 @@ const getHandler: RouteHandler = function (request, _reply) {
|
|
|
92
92
|
expectAssignable<(schema: { [key: string]: unknown }) => ExpectedGetValidationFunction>(request.getValidationFunction)
|
|
93
93
|
expectAssignable<(input: { [key: string]: unknown }, schema: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput)
|
|
94
94
|
expectAssignable<(input: { [key: string]: unknown }, httpPart?: HTTPRequestPart) => boolean>(request.validateInput)
|
|
95
|
+
expectType<string>(request.getDecorator<string>('foo'))
|
|
96
|
+
expectType<void>(request.setDecorator('foo', 'hello'))
|
|
97
|
+
expectType<void>(request.setDecorator<string>('foo', 'hello'))
|
|
98
|
+
expectError(request.setDecorator<string>('foo', true))
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
const getHandlerWithCustomLogger: RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault, CustomLoggerInterface> = function (request, _reply) {
|
|
@@ -1009,6 +1009,46 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{ Reply:
|
|
|
1009
1009
|
}
|
|
1010
1010
|
))
|
|
1011
1011
|
|
|
1012
|
+
// -------------------------------------------------------------------
|
|
1013
|
+
// RouteGeneric Reply Type Return (Different Status Codes)
|
|
1014
|
+
// -------------------------------------------------------------------
|
|
1015
|
+
|
|
1016
|
+
expectAssignable(server.get<{
|
|
1017
|
+
Reply: {
|
|
1018
|
+
200: string | { msg: string }
|
|
1019
|
+
400: number
|
|
1020
|
+
'5xx': { error: string }
|
|
1021
|
+
}
|
|
1022
|
+
}>(
|
|
1023
|
+
'/',
|
|
1024
|
+
async (_, res) => {
|
|
1025
|
+
const option = 1 as 1 | 2 | 3 | 4
|
|
1026
|
+
switch (option) {
|
|
1027
|
+
case 1: return 'hello'
|
|
1028
|
+
case 2: return { msg: 'hello' }
|
|
1029
|
+
case 3: return 400
|
|
1030
|
+
case 4: return { error: 'error' }
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
))
|
|
1034
|
+
|
|
1035
|
+
// -------------------------------------------------------------------
|
|
1036
|
+
// RouteGeneric Reply Type Return: Non Assignable (Different Status Codes)
|
|
1037
|
+
// -------------------------------------------------------------------
|
|
1038
|
+
|
|
1039
|
+
expectError(server.get<{
|
|
1040
|
+
Reply: {
|
|
1041
|
+
200: string | { msg: string }
|
|
1042
|
+
400: number
|
|
1043
|
+
'5xx': { error: string }
|
|
1044
|
+
}
|
|
1045
|
+
}>(
|
|
1046
|
+
'/',
|
|
1047
|
+
async (_, res) => {
|
|
1048
|
+
return true
|
|
1049
|
+
}
|
|
1050
|
+
))
|
|
1051
|
+
|
|
1012
1052
|
// -------------------------------------------------------------------
|
|
1013
1053
|
// FastifyPlugin: Auxiliary
|
|
1014
1054
|
// -------------------------------------------------------------------
|
package/test/upgrade.test.js
CHANGED
|
@@ -6,14 +6,11 @@ const { connect } = require('node:net')
|
|
|
6
6
|
const { once } = require('node:events')
|
|
7
7
|
const dns = require('node:dns').promises
|
|
8
8
|
|
|
9
|
-
describe('upgrade to both servers', async
|
|
9
|
+
describe('upgrade to both servers', async () => {
|
|
10
10
|
const localAddresses = await dns.lookup('localhost', { all: true })
|
|
11
|
-
|
|
12
|
-
t.skip('requires both IPv4 and IPv6')
|
|
13
|
-
return
|
|
14
|
-
}
|
|
11
|
+
const skip = localAddresses.length === 1 && 'requires both IPv4 and IPv6'
|
|
15
12
|
|
|
16
|
-
await test('upgrade IPv4 and IPv6', async t => {
|
|
13
|
+
await test('upgrade IPv4 and IPv6', { skip }, async t => {
|
|
17
14
|
t.plan(2)
|
|
18
15
|
|
|
19
16
|
const fastify = Fastify()
|
package/types/instance.d.ts
CHANGED
|
@@ -152,6 +152,8 @@ export interface FastifyInstance<
|
|
|
152
152
|
decorateRequest: DecorationMethod<FastifyRequest, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
|
|
153
153
|
decorateReply: DecorationMethod<FastifyReply, FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>>;
|
|
154
154
|
|
|
155
|
+
getDecorator<T>(name: string | symbol): T;
|
|
156
|
+
|
|
155
157
|
hasDecorator(decorator: string | symbol): boolean;
|
|
156
158
|
hasRequestDecorator(decorator: string | symbol): boolean;
|
|
157
159
|
hasReplyDecorator(decorator: string | symbol): boolean;
|
package/types/reply.d.ts
CHANGED
|
@@ -77,4 +77,5 @@ export interface FastifyReply<
|
|
|
77
77
|
) => FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfig, SchemaCompiler, TypeProvider>;
|
|
78
78
|
hasTrailer(key: string): boolean;
|
|
79
79
|
removeTrailer(key: string): FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfig, SchemaCompiler, TypeProvider>;
|
|
80
|
+
getDecorator<T>(name: string | symbol): T;
|
|
80
81
|
}
|
package/types/request.d.ts
CHANGED
|
@@ -87,4 +87,6 @@ export interface FastifyRequest<RouteGeneric extends RouteGenericInterface = Rou
|
|
|
87
87
|
compileValidationSchema(schema: { [key: string]: any }, httpPart?: HTTPRequestPart): ValidationFunction
|
|
88
88
|
validateInput(input: any, schema: { [key: string]: any }, httpPart?: HTTPRequestPart): boolean
|
|
89
89
|
validateInput(input: any, httpPart?: HTTPRequestPart): boolean
|
|
90
|
+
getDecorator<T>(name: string | symbol): T;
|
|
91
|
+
setDecorator<T = unknown>(name: string | symbol, value: T): void;
|
|
90
92
|
}
|
package/types/type-provider.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RouteGenericInterface } from './route'
|
|
2
2
|
import { FastifySchema } from './schema'
|
|
3
|
-
import { RecordKeysToLowercase } from './utils'
|
|
3
|
+
import { HttpKeys, RecordKeysToLowercase } from './utils'
|
|
4
4
|
|
|
5
5
|
// -----------------------------------------------------------------------------------------------
|
|
6
6
|
// TypeProvider
|
|
@@ -80,6 +80,11 @@ export type ResolveFastifyReplyType<TypeProvider extends FastifyTypeProvider, Sc
|
|
|
80
80
|
// FastifyReplyReturnType
|
|
81
81
|
// -----------------------------------------------------------------------------------------------
|
|
82
82
|
|
|
83
|
+
// Resolves the Reply return type by taking a union of response status codes in the generic argument
|
|
84
|
+
type ResolveReplyReturnTypeFromRouteGeneric<RouteGeneric extends RouteGenericInterface> = RouteGeneric extends { Reply: infer Return }
|
|
85
|
+
? keyof Return extends HttpKeys ? Return[keyof Return] | Return : Return
|
|
86
|
+
: unknown
|
|
87
|
+
|
|
83
88
|
// The target reply return type. This type is inferenced on fastify 'routes' via generic argument assignment
|
|
84
89
|
export type ResolveFastifyReplyReturnType<
|
|
85
90
|
TypeProvider extends FastifyTypeProvider,
|
|
@@ -89,8 +94,12 @@ export type ResolveFastifyReplyReturnType<
|
|
|
89
94
|
TypeProvider,
|
|
90
95
|
SchemaCompiler,
|
|
91
96
|
RouteGeneric
|
|
92
|
-
> extends infer
|
|
93
|
-
|
|
97
|
+
> extends infer ReplyType
|
|
98
|
+
? RouteGeneric['Reply'] extends ReplyType
|
|
99
|
+
? ResolveReplyReturnTypeFromRouteGeneric<RouteGeneric> extends infer Return
|
|
100
|
+
? Return | void | Promise<Return | void>
|
|
101
|
+
: unknown
|
|
102
|
+
: ReplyType | void | Promise<ReplyType | void>
|
|
94
103
|
// review: support both async and sync return types
|
|
95
104
|
// (Promise<Return> | Return | Promise<void> | void)
|
|
96
105
|
: unknown
|