fastify 5.5.0 → 5.6.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/SECURITY.md CHANGED
@@ -110,6 +110,18 @@ Within HackerOne, this is handled through a "public disclosure request".
110
110
  Reference: [HackerOne:
111
111
  Disclosure](https://docs.hackerone.com/hackers/disclosure.html)
112
112
 
113
+ ### Secondary Contact
114
+
115
+ If you do not receive an acknowledgment of your report within 6 business days,
116
+ or if you cannot find a private security contact for the project, you may
117
+ contact the OpenJS Foundation CNA at `security@lists.openjsf.org` for
118
+ assistance.
119
+
120
+ The CNA can help ensure your report is properly acknowledged, assist with
121
+ coordinating disclosure timelines, and assign CVEs when necessary. This is a
122
+ support mechanism to ensure security reports are handled appropriately across
123
+ all OpenJS Foundation projects.
124
+
113
125
  ## The Fastify Security team
114
126
 
115
127
  The core team is responsible for the management of the security program and
package/SPONSORS.md CHANGED
@@ -17,6 +17,7 @@ _Be the first!_
17
17
  - [Val Town, Inc.](https://opencollective.com/valtown)
18
18
  - [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024)
19
19
  - [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship)
20
+ - [Lambdatest](https://www.lambdatest.com/)
20
21
 
21
22
  ## Tier 2
22
23
 
@@ -366,201 +366,68 @@ Will define the `foo` property on the Fastify instance:
366
366
  console.log(fastify.foo) // 'a getter'
367
367
  ```
368
368
 
369
- ### `getDecorator<T>` API
369
+ #### `getDecorator(name)`
370
+ <a id="get-decorator"></a>
370
371
 
371
- Fastify's `getDecorator<T>` API retrieves an existing decorator from the
372
- Fastify instance, `Request`, or `Reply`. If the decorator is not defined, an
373
- `FST_ERR_DEC_UNDECLARED` error is thrown.
372
+ Used to retrieve an existing decorator from the Fastify instance, `Request`, or `Reply`.
373
+ If the decorator is not defined, an `FST_ERR_DEC_UNDECLARED` error is thrown.
374
374
 
375
- #### Use cases
375
+ ```js
376
+ // Get a decorator from the Fastify instance
377
+ const utility = fastify.getDecorator('utility')
376
378
 
377
- **Early Plugin Dependency Validation**
379
+ // Get a decorator from the request object
380
+ const user = request.getDecorator('user')
378
381
 
379
- `getDecorator<T>` on Fastify instance verifies that required decorators are
380
- available at registration time.
382
+ // Get a decorator from the reply object
383
+ const helper = reply.getDecorator('helper')
384
+ ```
381
385
 
382
- For example:
386
+ The `getDecorator` method is useful for dependency validation - it can be used to
387
+ check for required decorators at registration time. If any are missing, it fails
388
+ at boot, ensuring dependencies are available during the request lifecycle.
383
389
 
384
390
  ```js
385
391
  fastify.register(async function (fastify) {
392
+ // Verify the decorator exists before using it
386
393
  const usersRepository = fastify.getDecorator('usersRepository')
387
394
 
388
395
  fastify.get('/users', async function (request, reply) {
389
- // We are sure `usersRepository` exists at runtime
390
396
  return usersRepository.findAll()
391
397
  })
392
398
  })
393
399
  ```
394
400
 
395
- **Handling Missing Decorators**
396
-
397
- Directly accessing a decorator may lead to unexpected behavior if it is not declared:
398
-
399
- ```ts
400
- const user = request.user;
401
- if (user && user.isAdmin) {
402
- // Execute admin tasks.
403
- }
404
- ```
405
-
406
- If `request.user` doesn't exist, then `user` will be set to `undefined`.
407
- This makes it unclear whether the user is unauthenticated or the decorator is missing.
408
-
409
- Using `getDecorator` enforces runtime safety:
410
-
411
- ```ts
412
- // If the decorator is missing, an explicit `FST_ERR_DEC_UNDECLARED`
413
- // error is thrown immediately.
414
- const user = request.getDecorator('user');
415
- if (user && user.isAdmin) {
416
- // Execute admin tasks.
417
- }
418
- ```
419
-
420
- **Alternative to Module Augmentation**
421
-
422
- Decorators are typically typed via module augmentation:
423
-
424
- ```ts
425
- declare module 'fastify' {
426
- interface FastifyInstance {
427
- usersRepository: IUsersRepository
428
- }
429
- interface FastifyRequest {
430
- session: ISession
431
- }
432
- interface FastifyReply {
433
- sendSuccess: SendSuccessFn
434
- }
435
- }
436
- ```
437
-
438
- This approach modifies the Fastify instance globally, which may lead to
439
- conflicts and inconsistent behavior in multi-server setups or with plugin
440
- encapsulation.
441
-
442
- Using `getDecorator<T>` allows to limit types scope:
443
-
444
- ```ts
445
- serverOne.register(async function (fastify) {
446
- const usersRepository = fastify.getDecorator<PostgreUsersRepository>(
447
- 'usersRepository'
448
- )
449
-
450
- fastify.decorateRequest('session', null)
451
- fastify.addHook('onRequest', async (req, reply) => {
452
- // Yes, the request object has a setDecorator method.
453
- // More information will be provided soon.
454
- req.setDecorator('session', { user: 'Jean' })
455
- })
456
-
457
- fastify.get('/me', (request, reply) => {
458
- const session = request.getDecorator<ISession>('session')
459
- reply.send(session)
460
- })
461
- })
462
-
463
- serverTwo.register(async function (fastify) {
464
- const usersRepository = fastify.getDecorator<SqlLiteUsersRepository>(
465
- 'usersRepository'
466
- )
467
-
468
- fastify.decorateReply('sendSuccess', function (data) {
469
- return this.send({ success: true })
470
- })
471
-
472
- fastify.get('/success', async (request, reply) => {
473
- const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
474
- await sendSuccess()
475
- })
476
- })
477
- ```
401
+ > ℹ️ Note: For TypeScript users, `getDecorator` supports generic type parameters.
402
+ > See the [TypeScript documentation](/docs/latest/Reference/TypeScript/) for
403
+ > advanced typing examples.
478
404
 
479
- #### Bound functions inference
405
+ #### `setDecorator(name, value)`
406
+ <a id="set-decorator"></a>
480
407
 
481
- To save time, it's common to infer function types instead of
482
- writing them manually:
408
+ Used to safely update the value of a `Request` decorator.
409
+ If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error is thrown.
483
410
 
484
- ```ts
485
- function sendSuccess (this: FastifyReply) {
486
- return this.send({ success: true })
487
- }
488
-
489
- export type SendSuccess = typeof sendSuccess
490
- ```
491
-
492
- However, `getDecorator` returns functions with the `this`
493
- context already **bound**, meaning the `this` parameter disappears
494
- from the function signature.
495
-
496
- To correctly type it, you should use `OmitThisParameter` utility:
497
-
498
- ```ts
499
- function sendSuccess (this: FastifyReply) {
500
- return this.send({ success: true })
501
- }
502
-
503
- type BoundSendSuccess = OmitThisParameter<typeof sendSuccess>
504
-
505
- fastify.decorateReply('sendSuccess', sendSuccess)
506
- fastify.get('/success', async (request, reply) => {
507
- const sendSuccess = reply.getDecorator<BoundSendSuccess>('sendSuccess')
508
- await sendSuccess()
509
- })
510
- ```
511
-
512
- ### `Request.setDecorator<T>` Method
513
-
514
- The `setDecorator<T>` method provides a safe and convenient way to
515
- update the value of a `Request` decorator.
516
- If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error
517
- is thrown.
518
-
519
- #### Use Cases
520
-
521
- **Runtime Safety**
522
-
523
- A typical way to set a `Request` decorator looks like this:
524
-
525
- ```ts
526
- fastify.decorateRequest('user', '')
527
- fastify.addHook('preHandler', async (req, reply) => {
528
- req.user = 'Bob Dylan'
529
- })
530
- ```
531
-
532
- However, there is no guarantee that the decorator actually exists
533
- unless you manually check beforehand.
534
- Additionally, typos are common, e.g. `account`, `acount`, or `accout`.
535
-
536
- By using `setDecorator`, you are always sure that the decorator exists:
411
+ ```js
412
+ fastify.decorateRequest('user', null)
537
413
 
538
- ```ts
539
- fastify.decorateRequest('user', '')
540
414
  fastify.addHook('preHandler', async (req, reply) => {
541
- // Throws FST_ERR_DEC_UNDECLARED if the decorator does not exist
542
- req.setDecorator('user-with-typo', 'Bob Dylan')
415
+ // Safely set the decorator value
416
+ req.setDecorator('user', 'Bob Dylan')
543
417
  })
544
418
  ```
545
419
 
546
- ---
547
-
548
- **Type Safety**
420
+ The `setDecorator` method provides runtime safety by ensuring the decorator exists
421
+ before setting its value, preventing errors from typos in decorator names.
549
422
 
550
- If the `FastifyRequest` interface does not declare the decorator, you
551
- would typically need to use type assertions:
552
-
553
- ```ts
423
+ ```js
424
+ fastify.decorateRequest('account', null)
554
425
  fastify.addHook('preHandler', async (req, reply) => {
555
- (req as typeof req & { user: string }).user = 'Bob Dylan'
426
+ // This will throw FST_ERR_DEC_UNDECLARED due to typo in decorator name
427
+ req.setDecorator('acount', { id: 123 })
556
428
  })
557
429
  ```
558
430
 
559
- The `setDecorator<T>` method eliminates the need for explicit type
560
- assertions while allowing type safety:
561
-
562
- ```ts
563
- fastify.addHook('preHandler', async (req, reply) => {
564
- req.setDecorator<string>('user', 'Bob Dylan')
565
- })
566
- ```
431
+ > ℹ️ Note: For TypeScript users, see the
432
+ > [TypeScript documentation](/docs/latest/Reference/TypeScript/) for advanced
433
+ > typing examples using `setDecorator<T>`.
@@ -369,7 +369,7 @@ charset must be set explicitly.
369
369
  <a id="getserializationfunction"></a>
370
370
 
371
371
  By calling this function using a provided `schema` or `httpStatus`,
372
- and the optional `contentType`, it will return a `serialzation` function
372
+ and the optional `contentType`, it will return a `serialization` function
373
373
  that can be used to serialize diverse inputs. It returns `undefined` if no
374
374
  serialization function was found using either of the provided inputs.
375
375
 
@@ -636,8 +636,7 @@ has a version set, and will prefer a versioned route to a non-versioned route
636
636
  for the same path. Advanced version ranges and pre-releases currently are not
637
637
  supported.
638
638
 
639
- *Be aware that using this feature will cause a degradation of the overall
640
- performances of the router.*
639
+ > **Note:** using this feature can degrade the router’s performance.
641
640
 
642
641
  ```js
643
642
  fastify.route({
@@ -170,6 +170,12 @@ When set to `true`, upon [`close`](#close) the server will iterate the current
170
170
  persistent connections and [destroy their
171
171
  sockets](https://nodejs.org/dist/latest-v16.x/docs/api/net.html#socketdestroyerror).
172
172
 
173
+ When used with HTTP/2 server, it will also close all active HTTP/2 sessions.
174
+
175
+ > ℹ️ Note:
176
+ > Since Node.js v24 active sessions are closed by default
177
+
178
+
173
179
  > ⚠ Warning:
174
180
  > Connections are not inspected to determine if requests have
175
181
  > been completed.
@@ -966,7 +972,7 @@ Fastify uses [find-my-way](https://github.com/delvedor/find-my-way) which suppor
966
972
  separating the path and query string with a `;` character (code 59), e.g. `/dev;foo=bar`.
967
973
  This decision originated from [delvedor/find-my-way#76]
968
974
  (https://github.com/delvedor/find-my-way/issues/76). Thus, this option will support
969
- backwards compatiblilty for the need to split on `;`. To enable support for splitting
975
+ backwards compatibility for the need to split on `;`. To enable support for splitting
970
976
  on `;` set `useSemicolonDelimiter` to `true`.
971
977
 
972
978
  ```js
@@ -147,7 +147,7 @@ route-level `request` object.
147
147
  reply.code(200).send('uh-oh');
148
148
  // it even works for wildcards
149
149
  reply.code(404).send({ error: 'Not found' });
150
- return `logged in!`
150
+ return { success: true }
151
151
  })
152
152
  ```
153
153
 
@@ -173,7 +173,7 @@ route-level `request` object.
173
173
  }, async (request, reply) => {
174
174
  const customerHeader = request.headers['h-Custom']
175
175
  // do something with request data
176
- return `logged in!`
176
+ return { success: true }
177
177
  })
178
178
  ```
179
179
  7. Build and run and query with the `username` query string option set to
@@ -687,6 +687,142 @@ Or even explicit config on tsconfig
687
687
  }
688
688
  ```
689
689
 
690
+ #### `getDecorator<T>`
691
+
692
+ Fastify's `getDecorator<T>` method retrieves decorators with enhanced type safety.
693
+
694
+ The `getDecorator<T>` method supports generic type parameters for enhanced type safety:
695
+
696
+ ```typescript
697
+ // Type-safe decorator retrieval
698
+ const usersRepository = fastify.getDecorator<IUsersRepository>('usersRepository')
699
+ const session = request.getDecorator<ISession>('session')
700
+ const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
701
+ ```
702
+
703
+ **Alternative to Module Augmentation**
704
+
705
+ Decorators are typically typed via module augmentation:
706
+
707
+ ```typescript
708
+ declare module 'fastify' {
709
+ interface FastifyInstance {
710
+ usersRepository: IUsersRepository
711
+ }
712
+ interface FastifyRequest {
713
+ session: ISession
714
+ }
715
+ interface FastifyReply {
716
+ sendSuccess: SendSuccessFn
717
+ }
718
+ }
719
+ ```
720
+
721
+ This approach modifies the Fastify instance globally, which may lead to conflicts
722
+ and inconsistent behavior in multi-server setups or with plugin encapsulation.
723
+
724
+ Using `getDecorator<T>` allows limiting types scope:
725
+
726
+ ```typescript
727
+ serverOne.register(async function (fastify) {
728
+ const usersRepository = fastify.getDecorator<PostgreUsersRepository>(
729
+ 'usersRepository'
730
+ )
731
+
732
+ fastify.decorateRequest('session', null)
733
+ fastify.addHook('onRequest', async (req, reply) => {
734
+ req.setDecorator('session', { user: 'Jean' })
735
+ })
736
+
737
+ fastify.get('/me', (request, reply) => {
738
+ const session = request.getDecorator<ISession>('session')
739
+ reply.send(session)
740
+ })
741
+ })
742
+
743
+ serverTwo.register(async function (fastify) {
744
+ const usersRepository = fastify.getDecorator<SqlLiteUsersRepository>(
745
+ 'usersRepository'
746
+ )
747
+
748
+ fastify.decorateReply('sendSuccess', function (data) {
749
+ return this.send({ success: true })
750
+ })
751
+
752
+ fastify.get('/success', async (request, reply) => {
753
+ const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
754
+ await sendSuccess()
755
+ })
756
+ })
757
+ ```
758
+
759
+ **Bound Functions Inference**
760
+
761
+ To save time, it is common to infer function types instead of writing them manually:
762
+
763
+ ```typescript
764
+ function sendSuccess (this: FastifyReply) {
765
+ return this.send({ success: true })
766
+ }
767
+
768
+ export type SendSuccess = typeof sendSuccess
769
+ ```
770
+
771
+ However, `getDecorator` returns functions with the `this` context already **bound**,
772
+ meaning the `this` parameter disappears from the function signature.
773
+
774
+ To correctly type it, use the `OmitThisParameter` utility:
775
+
776
+ ```typescript
777
+ function sendSuccess (this: FastifyReply) {
778
+ return this.send({ success: true })
779
+ }
780
+
781
+ type BoundSendSuccess = OmitThisParameter<typeof sendSuccess>
782
+
783
+ fastify.decorateReply('sendSuccess', sendSuccess)
784
+ fastify.get('/success', async (request, reply) => {
785
+ const sendSuccess = reply.getDecorator<BoundSendSuccess>('sendSuccess')
786
+ await sendSuccess()
787
+ })
788
+ ```
789
+
790
+ #### `setDecorator<T>`
791
+
792
+ Fastify's `setDecorator<T>` method provides enhanced type safety for updating request
793
+ decorators.
794
+
795
+ The `setDecorator<T>` method provides enhanced type safety for updating request
796
+ decorators:
797
+
798
+ ```typescript
799
+ fastify.decorateRequest('user', '')
800
+ fastify.addHook('preHandler', async (req, reply) => {
801
+ // Type-safe decorator setting
802
+ req.setDecorator<string>('user', 'Bob Dylan')
803
+ })
804
+ ```
805
+
806
+ **Type Safety Benefits**
807
+
808
+ If the `FastifyRequest` interface does not declare the decorator, type assertions
809
+ are typically needed:
810
+
811
+ ```typescript
812
+ fastify.addHook('preHandler', async (req, reply) => {
813
+ (req as typeof req & { user: string }).user = 'Bob Dylan'
814
+ })
815
+ ```
816
+
817
+ The `setDecorator<T>` method eliminates the need for explicit type assertions
818
+ while providing type safety:
819
+
820
+ ```typescript
821
+ fastify.addHook('preHandler', async (req, reply) => {
822
+ req.setDecorator<string>('user', 'Bob Dylan')
823
+ })
824
+ ```
825
+
690
826
  ## Code Completion In Vanilla JavaScript
691
827
 
692
828
  Vanilla JavaScript can use the published types to provide code completion (e.g.
package/fastify.d.ts CHANGED
@@ -89,6 +89,22 @@ declare namespace fastify {
89
89
 
90
90
  type TrustProxyFunction = (address: string, hop: number) => boolean
91
91
 
92
+ export type FastifyRouterOptions<RawServer extends RawServerBase> = {
93
+ allowUnsafeRegex?: boolean,
94
+ buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object,
95
+ caseSensitive?: boolean,
96
+ constraints?: {
97
+ [name: string]: ConstraintStrategy<FindMyWayVersion<RawServer>, unknown>,
98
+ },
99
+ defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void,
100
+ ignoreDuplicateSlashes?: boolean,
101
+ ignoreTrailingSlash?: boolean,
102
+ maxParamLength?: number,
103
+ onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void,
104
+ querystringParser?: (str: string) => { [key: string]: unknown },
105
+ useSemicolonDelimiter?: boolean,
106
+ }
107
+
92
108
  /**
93
109
  * Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2
94
110
  */
@@ -159,6 +175,7 @@ declare namespace fastify {
159
175
  clientErrorHandler?: (error: ConnectionError, socket: Socket) => void,
160
176
  childLoggerFactory?: FastifyChildLoggerFactory,
161
177
  allowErrorHandlerOverride?: boolean
178
+ routerOptions?: FastifyRouterOptions<RawServer>,
162
179
  }
163
180
 
164
181
  /**
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '5.5.0'
3
+ const VERSION = '5.6.1'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -194,6 +194,7 @@ function fastify (options) {
194
194
 
195
195
  const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
196
196
  const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
197
+ const serverHasCloseHttp2Sessions = typeof server.closeHttp2Sessions === 'function'
197
198
 
198
199
  let forceCloseConnections = options.forceCloseConnections
199
200
  if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
@@ -491,6 +492,10 @@ function fastify (options) {
491
492
  }
492
493
  }
493
494
 
495
+ if (serverHasCloseHttp2Sessions) {
496
+ instance.server.closeHttp2Sessions()
497
+ }
498
+
494
499
  // No new TCP connections are accepted.
495
500
  // We must call close on the server even if we are not listening
496
501
  // otherwise memory will be leaked.
package/lib/server.js CHANGED
@@ -6,7 +6,7 @@ const http2 = require('node:http2')
6
6
  const dns = require('node:dns')
7
7
  const os = require('node:os')
8
8
 
9
- const { kState, kOptions, kServerBindings } = require('./symbols')
9
+ const { kState, kOptions, kServerBindings, kHttp2ServerSessions } = require('./symbols')
10
10
  const { FSTWRN003 } = require('./warnings')
11
11
  const { onListenHookRunner } = require('./hooks')
12
12
  const {
@@ -175,6 +175,9 @@ function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, o
175
175
  if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections === true) {
176
176
  secondaryServer.closeAllConnections()
177
177
  }
178
+ if (typeof secondaryServer.closeHttp2Sessions === 'function') {
179
+ secondaryServer.closeHttp2Sessions()
180
+ }
178
181
  }
179
182
 
180
183
  secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
@@ -283,10 +286,16 @@ function getServerInstance (options, httpHandler) {
283
286
 
284
287
  if (options.http2) {
285
288
  const server = typeof httpsOptions === 'object' ? http2.createSecureServer(httpsOptions, httpHandler) : http2.createServer(options.http, httpHandler)
286
- server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, function closeSession () {
287
- this.close()
289
+ server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, () => {
290
+ session.close()
288
291
  }))
289
292
 
293
+ // This is only needed for Node.js versions < 24.0.0 since Node.js added native
294
+ // closeAllSessions() on server.close() support for HTTP/2 servers in v24.0.0
295
+ if (options.forceCloseConnections === true) {
296
+ server.closeHttp2Sessions = createCloseHttp2SessionsByHttp2Server(server)
297
+ }
298
+
290
299
  server.setTimeout(options.connectionTimeout)
291
300
 
292
301
  return server
@@ -353,3 +362,47 @@ function logServerAddress (server, listenTextResolver) {
353
362
  }
354
363
  return addresses[0]
355
364
  }
365
+
366
+ /**
367
+ * @param {http2.Http2Server} http2Server
368
+ * @returns {() => void}
369
+ */
370
+ function createCloseHttp2SessionsByHttp2Server (http2Server) {
371
+ /**
372
+ * @type {Set<http2.Http2Session>}
373
+ */
374
+ http2Server[kHttp2ServerSessions] = new Set()
375
+
376
+ http2Server.on('session', function (session) {
377
+ session.once('connect', function () {
378
+ http2Server[kHttp2ServerSessions].add(session)
379
+ })
380
+
381
+ session.once('close', function () {
382
+ http2Server[kHttp2ServerSessions].delete(session)
383
+ })
384
+
385
+ session.once('frameError', function (type, code, streamId) {
386
+ if (streamId === 0) {
387
+ // The stream ID is 0, which means that the error is related to the session itself.
388
+ // If the event is not associated with a stream, the Http2Session will be shut down immediately
389
+ http2Server[kHttp2ServerSessions].delete(session)
390
+ }
391
+ })
392
+
393
+ session.once('goaway', function () {
394
+ // The Http2Session instance will be shut down automatically when the 'goaway' event is emitted.
395
+ http2Server[kHttp2ServerSessions].delete(session)
396
+ })
397
+ })
398
+
399
+ return function closeHttp2Sessions () {
400
+ if (http2Server[kHttp2ServerSessions].size === 0) {
401
+ return
402
+ }
403
+
404
+ for (const session of http2Server[kHttp2ServerSessions]) {
405
+ session.close()
406
+ }
407
+ }
408
+ }
package/lib/symbols.js CHANGED
@@ -17,6 +17,7 @@ const keys = {
17
17
  kPluginNameChain: Symbol('fastify.pluginNameChain'),
18
18
  kRouteContext: Symbol('fastify.context'),
19
19
  kGenReqId: Symbol('fastify.genReqId'),
20
+ kHttp2ServerSessions: Symbol('fastify.http2ServerSessions'),
20
21
  // Schema
21
22
  kSchemaController: Symbol('fastify.schemaController'),
22
23
  kSchemaHeaders: Symbol('headers-schema'),
package/lib/warnings.js CHANGED
@@ -43,7 +43,7 @@ const FSTSEC001 = createWarning({
43
43
 
44
44
  const FSTDEP022 = createWarning({
45
45
  name: 'FastifyWarning',
46
- code: 'FSTDPE022',
46
+ code: 'FSTDEP022',
47
47
  message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.',
48
48
  unlimited: true
49
49
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "5.5.0",
3
+ "version": "5.6.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -20,7 +20,7 @@ test('plugin namespace', async t => {
20
20
  // ! the plugin does not know about the namespace
21
21
  reply.send({ utility: app.utility() })
22
22
  })
23
- }, { namepace: 'foo' })
23
+ }, { namespace: 'foo' })
24
24
 
25
25
  // ! but outside the plugin the decorator would be app.foo.utility()
26
26
  t.assert.ok(app.foo.utility)
@@ -8,6 +8,7 @@ const connect = promisify(http2.connect)
8
8
  const { once } = require('node:events')
9
9
  const { buildCertificate } = require('../build-certificate')
10
10
  const { getServerUrl } = require('../helper')
11
+ const { kHttp2ServerSessions } = require('../../lib/symbols')
11
12
 
12
13
  test.before(buildCertificate)
13
14
 
@@ -180,3 +181,90 @@ test('http/2 server side session emits a timeout event', async t => {
180
181
  await p
181
182
  await fastify.close()
182
183
  })
184
+
185
+ test('http/2 sessions closed after closing server', async t => {
186
+ t.plan(1)
187
+ const fastify = Fastify({
188
+ http2: true,
189
+ http2SessionTimeout: 100
190
+ })
191
+ await fastify.listen()
192
+ const url = getServerUrl(fastify)
193
+ const waitSessionConnect = once(fastify.server, 'session')
194
+ const session = http2.connect(url)
195
+ await once(session, 'connect')
196
+ await waitSessionConnect
197
+ const waitSessionClosed = once(session, 'close')
198
+ await fastify.close()
199
+ await waitSessionClosed
200
+ t.assert.strictEqual(session.closed, true)
201
+ })
202
+
203
+ test('http/2 sessions should be closed when setting forceClosedConnections to true', async t => {
204
+ t.plan(2)
205
+ const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true })
206
+ fastify.get('/', () => 'hello world')
207
+ await fastify.listen()
208
+ const client = await connect(getServerUrl(fastify))
209
+ const req = client.request({
210
+ [http2.HTTP2_HEADER_PATH]: '/',
211
+ [http2.HTTP2_HEADER_METHOD]: 'GET'
212
+ })
213
+ await once(req, 'response')
214
+ fastify.close()
215
+ const r2 = client.request({
216
+ [http2.HTTP2_HEADER_PATH]: '/',
217
+ [http2.TTP2_HEADER_METHOD]: 'GET'
218
+ })
219
+ r2.on('error', (err) => {
220
+ t.assert.strictEqual(err.toString(), 'Error [ERR_HTTP2_STREAM_ERROR]: Stream closed with error code NGHTTP2_REFUSED_STREAM')
221
+ })
222
+ await once(r2, 'error')
223
+ r2.end()
224
+ t.assert.strictEqual(client.closed, true)
225
+ client.destroy()
226
+ })
227
+
228
+ test('http/2 sessions should be removed from server[kHttp2ServerSessions] Set on goaway', async t => {
229
+ t.plan(2)
230
+ const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true })
231
+ await fastify.listen()
232
+ const waitSession = once(fastify.server, 'session')
233
+ const client = http2.connect(getServerUrl(fastify))
234
+ const [session] = await waitSession
235
+ const waitGoaway = once(session, 'goaway')
236
+ t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1)
237
+ client.goaway()
238
+ await waitGoaway
239
+ t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 0)
240
+ client.destroy()
241
+ await fastify.close()
242
+ })
243
+
244
+ test('http/2 sessions should be removed from server[kHttp2ServerSessions] Set on frameError', async t => {
245
+ t.plan(2)
246
+ const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true })
247
+ await fastify.listen()
248
+ const waitSession = once(fastify.server, 'session')
249
+ const client = http2.connect(getServerUrl(fastify))
250
+ const [session] = await waitSession
251
+ t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1)
252
+ session.emit('frameError', 0, 0, 0)
253
+ t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 0)
254
+ client.destroy()
255
+ await fastify.close()
256
+ })
257
+
258
+ test('http/2 sessions should not be removed from server[kHttp2ServerSessions] from Set if stream id passed on frameError', async t => {
259
+ t.plan(2)
260
+ const fastify = Fastify({ http2: true, http2SessionTimeout: 100, forceCloseConnections: true })
261
+ await fastify.listen()
262
+ const waitSession = once(fastify.server, 'session')
263
+ const client = http2.connect(getServerUrl(fastify))
264
+ const [session] = await waitSession
265
+ t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1)
266
+ session.emit('frameError', 0, 0, 1)
267
+ t.assert.strictEqual(fastify.server[kHttp2ServerSessions].size, 1)
268
+ client.destroy()
269
+ await fastify.close()
270
+ })
@@ -24,7 +24,7 @@ test('setErrorHandler can be set independently in parent and child scopes', asyn
24
24
  })
25
25
  })
26
26
 
27
- test('setErrorHandler can be overriden if allowErrorHandlerOverride is set to true', async t => {
27
+ test('setErrorHandler can be overridden if allowErrorHandlerOverride is set to true', async t => {
28
28
  t.plan(2)
29
29
 
30
30
  const fastify = Fastify()
@@ -244,12 +244,12 @@ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
244
244
  if (hookOrHandler === 'handler') {
245
245
  app.get('/', (req, reply) => {
246
246
  reply.hijack()
247
- throw new Error('This wil be skipped')
247
+ throw new Error('This will be skipped')
248
248
  })
249
249
  } else {
250
250
  app.addHook(hookOrHandler, async (req, reply) => {
251
251
  reply.hijack()
252
- throw new Error('This wil be skipped')
252
+ throw new Error('This will be skipped')
253
253
  })
254
254
  app.get('/', (req, reply) => t.assert.fail('Handler should not be called'))
255
255
  }
@@ -15,6 +15,8 @@ import { FastifyRequest } from '../../types/request'
15
15
  import { FastifySchemaControllerOptions, FastifySchemaCompiler, FastifySerializerCompiler } from '../../types/schema'
16
16
  import { AddressInfo } from 'node:net'
17
17
  import { Bindings, ChildLoggerOptions } from '../../types/logger'
18
+ import { ConstraintStrategy } from 'find-my-way'
19
+ import { FindMyWayVersion } from '../../types/instance'
18
20
 
19
21
  const server = fastify()
20
22
 
@@ -309,7 +311,22 @@ type InitialConfig = Readonly<{
309
311
  requestIdHeader?: string | false,
310
312
  requestIdLogLabel?: string,
311
313
  http2SessionTimeout?: number,
312
- useSemicolonDelimiter?: boolean
314
+ useSemicolonDelimiter?: boolean,
315
+ routerOptions?: {
316
+ allowUnsafeRegex?: boolean,
317
+ buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object,
318
+ caseSensitive?: boolean,
319
+ constraints?: {
320
+ [name: string]: ConstraintStrategy<FindMyWayVersion<RawServerDefault>, unknown>
321
+ }
322
+ defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void,
323
+ ignoreDuplicateSlashes?: boolean,
324
+ ignoreTrailingSlash?: boolean,
325
+ maxParamLength?: number,
326
+ onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void,
327
+ querystringParser?: (str: string) => { [key: string]: unknown },
328
+ useSemicolonDelimiter?: boolean,
329
+ }
313
330
  }>
314
331
 
315
332
  expectType<InitialConfig>(fastify().initialConfig)
@@ -72,6 +72,27 @@ expectAssignable<FastifyInstance>(server.post('/test', {
72
72
  }
73
73
  }, async req => req.body))
74
74
 
75
+ expectAssignable<FastifyInstance>(server.post('/test', {
76
+ validatorCompiler: ({ schema }) => {
77
+ return data => {
78
+ if (!data || data.constructor !== Object) {
79
+ return {
80
+ error: [
81
+ {
82
+ keyword: 'type',
83
+ instancePath: '',
84
+ schemaPath: '#/type',
85
+ params: { type: 'object' },
86
+ message: 'value is not an object'
87
+ }
88
+ ]
89
+ }
90
+ }
91
+ return { value: data }
92
+ }
93
+ }
94
+ }, async req => req.body))
95
+
75
96
  expectAssignable<FastifyInstance>(server.setValidatorCompiler<FastifySchema & { validate: Record<string, unknown> }>(
76
97
  function ({ schema }) {
77
98
  return new Ajv().compile(schema)
@@ -23,6 +23,7 @@ import {
23
23
  SafePromiseLike
24
24
  } from './type-provider'
25
25
  import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils'
26
+ import { FastifyRouterOptions } from '../fastify'
26
27
 
27
28
  export interface PrintRoutesOptions {
28
29
  method?: HTTPMethods;
@@ -603,5 +604,6 @@ export interface FastifyInstance<
603
604
  requestIdLogLabel?: string,
604
605
  http2SessionTimeout?: number,
605
606
  useSemicolonDelimiter?: boolean,
607
+ routerOptions?: FastifyRouterOptions<RawServer>
606
608
  }>
607
609
  }
package/types/logger.d.ts CHANGED
@@ -7,20 +7,24 @@ import { FastifySchema } from './schema'
7
7
  import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider'
8
8
  import { ContextConfigDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault } from './utils'
9
9
 
10
- import pino from 'pino'
10
+ import type {
11
+ BaseLogger,
12
+ LogFn as FastifyLogFn,
13
+ LevelWithSilent as LogLevel,
14
+ Bindings,
15
+ ChildLoggerOptions,
16
+ LoggerOptions as PinoLoggerOptions
17
+ } from 'pino'
11
18
 
12
- /**
13
- * Standard Fastify logging function
14
- */
15
- export type FastifyLogFn = pino.LogFn
16
-
17
- export type LogLevel = pino.LevelWithSilent
18
-
19
- export type Bindings = pino.Bindings
20
-
21
- export type ChildLoggerOptions = pino.ChildLoggerOptions
19
+ export type {
20
+ FastifyLogFn,
21
+ LogLevel,
22
+ Bindings,
23
+ ChildLoggerOptions,
24
+ PinoLoggerOptions
25
+ }
22
26
 
23
- export interface FastifyBaseLogger extends pino.BaseLogger {
27
+ export interface FastifyBaseLogger extends Pick<BaseLogger, 'level' | 'info' | 'error' | 'debug' | 'fatal' | 'warn' | 'trace' | 'silent'> {
24
28
  child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger
25
29
  }
26
30
 
@@ -34,8 +38,6 @@ export interface FastifyLoggerStreamDestination {
34
38
  write(msg: string): void;
35
39
  }
36
40
 
37
- export type PinoLoggerOptions = pino.LoggerOptions
38
-
39
41
  // TODO: once node 18 is EOL, this type can be replaced with plain FastifyReply.
40
42
  /**
41
43
  * Specialized reply type used for the `res` log serializer, since only `statusCode` is passed in certain cases.
package/types/schema.d.ts CHANGED
@@ -33,7 +33,7 @@ export interface FastifySchemaValidationError {
33
33
  }
34
34
 
35
35
  export interface FastifyValidationResult {
36
- (data: any): boolean | SafePromiseLike<any> | { error?: Error, value?: any }
36
+ (data: any): boolean | SafePromiseLike<any> | { error?: Error | FastifySchemaValidationError[], value?: any }
37
37
  errors?: FastifySchemaValidationError[] | null;
38
38
  }
39
39