基本schema测试
This commit is contained in:
3
node_modules/fastify/.borp.yaml
generated
vendored
Normal file
3
node_modules/fastify/.borp.yaml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
files:
|
||||
- 'test/**/*.test.js'
|
||||
- 'test/**/*.test.mjs'
|
||||
22
node_modules/fastify/.markdownlint-cli2.yaml
generated
vendored
Normal file
22
node_modules/fastify/.markdownlint-cli2.yaml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# See https://github.com/DavidAnson/markdownlint-cli2
|
||||
config:
|
||||
# Disable all rules by default.
|
||||
default: false
|
||||
|
||||
# Enforce line length.
|
||||
MD013:
|
||||
line_length: 80
|
||||
code_block_line_length: 120
|
||||
headings: false
|
||||
tables: false
|
||||
strict: false
|
||||
stern: false
|
||||
|
||||
globs:
|
||||
- '**/*.md'
|
||||
|
||||
ignores:
|
||||
- 'node_modules/**'
|
||||
- 'test/**/*.md'
|
||||
|
||||
noProgress: true
|
||||
1
node_modules/fastify/.prettierignore
generated
vendored
Normal file
1
node_modules/fastify/.prettierignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*
|
||||
4
node_modules/fastify/GOVERNANCE.md
generated
vendored
Normal file
4
node_modules/fastify/GOVERNANCE.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Fastify Project Governance
|
||||
|
||||
Please see Fastify's [organization-wide governance
|
||||
](https://github.com/fastify/.github/blob/main/GOVERNANCE.md) document.
|
||||
24
node_modules/fastify/LICENSE
generated
vendored
Normal file
24
node_modules/fastify/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-present The Fastify Team
|
||||
|
||||
The Fastify team members are listed at https://github.com/fastify/fastify#team
|
||||
and in the README file.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
126
node_modules/fastify/PROJECT_CHARTER.md
generated
vendored
Normal file
126
node_modules/fastify/PROJECT_CHARTER.md
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
# Fastify Charter
|
||||
|
||||
The Fastify project aims to build a fast and low-overhead web framework for
|
||||
Node.js.
|
||||
|
||||
|
||||
## Section 0: Guiding Principles
|
||||
|
||||
The Fastify project is part of the [OpenJS Foundation][openjs foundation]. It
|
||||
operates transparently, openly, collaboratively, and ethically. Project
|
||||
proposals, timelines, and status must not merely be open, but also easily
|
||||
visible to outsiders.
|
||||
|
||||
|
||||
## Section 1: Scope
|
||||
|
||||
Fastify is a web framework highly focused on providing the best developer
|
||||
experience with the least overhead and a plugin architecture.
|
||||
|
||||
### 1.1: In-scope
|
||||
|
||||
+ Develop a web framework for Node.js with a focus on developer experience,
|
||||
performance, and extensibility.
|
||||
+ Plugin Architecture
|
||||
+ Support web protocols
|
||||
+ Official plugins for common user requirements
|
||||
+ Documentation:
|
||||
+ Project (policies, processes, and releases)
|
||||
+ Guides and Tutorials
|
||||
+ Framework API
|
||||
+ Website
|
||||
+ Write easier APIs for developers
|
||||
+ Tools:
|
||||
+ CI services
|
||||
+ Bots to improve overall efficiency
|
||||
+ Releases
|
||||
+ Support:
|
||||
+ Community
|
||||
+ Users's issues and questions
|
||||
+ Contributors's pull request review
|
||||
|
||||
### 1.2: Out-of-Scope
|
||||
|
||||
+ Support versions of Node.js at EOL (end of life) stage
|
||||
+ Support serverless architecture
|
||||
+ Contributions that violate the [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
|
||||
## Section 2: Relationship with OpenJS Foundation CPC.
|
||||
|
||||
Technical leadership for the projects within the [OpenJS Foundation][openjs
|
||||
foundation] is delegated to the projects through their project charters by the
|
||||
[OpenJS Foundation Cross-Project Council](https://openjsf.org/about/governance/)
|
||||
(CPC). In the case of the Fastify project, it is delegated to the [Fastify
|
||||
Collaborators](README.md#team). The OpenJS Foundation's business leadership is
|
||||
the Board of Directors (the "Board").
|
||||
|
||||
This Fastify Charter reflects a carefully constructed balanced role for the
|
||||
Collaborators and the CPC in the governance of the OpenJS Foundation. The
|
||||
charter amendment process is for the Fastify Collaborators to propose change
|
||||
using a majority of the full Fastify Organization, the proposed changes
|
||||
being subject to review and approval by the CPC. The CPC may additionally make
|
||||
amendments to the Collaborators charter at any time, though the CPC will not
|
||||
interfere with day-to-day discussions, votes, or meetings of the Fastify
|
||||
Organization.
|
||||
|
||||
|
||||
### 2.1 Other Formal Project Relationships
|
||||
|
||||
Section Intentionally Left Blank
|
||||
|
||||
|
||||
## Section 3: Fastify Governing Body
|
||||
|
||||
Fastify is governed by its [maintainers](README.md#team). See [how it is
|
||||
structured](GOVERNANCE.md) for more information.
|
||||
|
||||
|
||||
## Section 4: Roles & Responsibilities
|
||||
|
||||
The roles and responsibilities of Fastify's maintainers are described in [the
|
||||
project organization](GOVERNANCE.md).
|
||||
|
||||
### Section 4.1 Project Operations & Management
|
||||
|
||||
Section Intentionally Left Blank
|
||||
|
||||
### Section 4.2: Decision-making, Voting, and/or Elections
|
||||
|
||||
**Decision-making**
|
||||
|
||||
Fastify's features can be discussed in GitHub issues and/or projects. Consensus
|
||||
on a discussion is reached when there is no objection by any collaborators.
|
||||
|
||||
When there is no consensus, Lead Maintainers will have the final say on the
|
||||
topic.
|
||||
|
||||
**Voting, and/or Elections**
|
||||
|
||||
These processes are described in the [GOVERNANCE](GOVERNANCE.md) document.
|
||||
|
||||
### Section 4.3: Other Project Roles
|
||||
|
||||
Section Intentionally Left Blank
|
||||
|
||||
## Section 5: Definitions
|
||||
|
||||
+ *Contributors*: contribute code or other artifacts, but do not have the right
|
||||
to commit to the code base. Contributors work with the project’s Collaborators
|
||||
to have code committed to the code base. Contributors should rarely be
|
||||
encumbered by the Fastify Collaborators and never by the CPC or OpenJS
|
||||
Foundation Board.
|
||||
|
||||
+ *Collaborators*: contribute code and other artifacts, have the right to commit
|
||||
to the code base, and release plugin projects. Collaborators follow the
|
||||
[CONTRIBUTING](CONTRIBUTING.md) guidelines to manage the project. A
|
||||
Collaborator could be encumbered by other Fastify Collaborators and never by
|
||||
the CPC or OpenJS Foundation Board.
|
||||
|
||||
+ *Lead Maintainers*: founders of the project, contribute code and other
|
||||
artifacts, have the right to commit to the code base and release the project.
|
||||
Lead Maintainers follow the [CONTRIBUTING](CONTRIBUTING.md) guidelines to
|
||||
manage the project. A Lead Maintainer will be encumbered by the Fastify
|
||||
Collaborators and by the CPC or OpenJS Foundation Board.
|
||||
|
||||
[openjs foundation]: https://openjsf.org
|
||||
421
node_modules/fastify/README.md
generated
vendored
Normal file
421
node_modules/fastify/README.md
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
<div align="center"> <a href="https://fastify.dev/">
|
||||
<img
|
||||
src="https://github.com/fastify/graphics/raw/HEAD/fastify-landscape-outlined.svg"
|
||||
width="650"
|
||||
height="auto"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/fastify/fastify/actions/workflows/ci.yml)
|
||||
[](https://github.com/fastify/fastify/actions/workflows/package-manager-ci.yml)
|
||||
[](https://github.com/fastify/fastify/actions/workflows/website.yml)
|
||||
[](https://github.com/neostandard/neostandard)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/7585)
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://www.npmjs.com/package/fastify)
|
||||
[](https://www.npmjs.com/package/fastify)
|
||||
[](https://github.com/fastify/fastify/blob/main/SECURITY.md)
|
||||
[](https://discord.gg/fastify)
|
||||
[](https://gitpod.io/#https://github.com/fastify/fastify)
|
||||
[](https://github.com/sponsors/fastify#sponsors)
|
||||
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
An efficient server implies a lower cost of the infrastructure, better
|
||||
responsiveness under load, and happy users. How can you efficiently handle the
|
||||
resources of your server, knowing that you are serving the highest number of
|
||||
requests possible, without sacrificing security validations and handy
|
||||
development?
|
||||
|
||||
Enter Fastify. Fastify is a web framework highly focused on providing the best
|
||||
developer experience with the least overhead and a powerful plugin architecture.
|
||||
It is inspired by Hapi and Express and as far as we know, it is one of the
|
||||
fastest web frameworks in town.
|
||||
|
||||
The `main` branch refers to the Fastify `v5` release.
|
||||
Check out the [`4.x` branch](https://github.com/fastify/fastify/tree/4.x) for `v4`.
|
||||
|
||||
### Table of Contents
|
||||
|
||||
- [Quick start](#quick-start)
|
||||
- [Install](#install)
|
||||
- [Example](#example)
|
||||
- [Core features](#core-features)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Documentation](#documentation)
|
||||
- [Ecosystem](#ecosystem)
|
||||
- [Support](#support)
|
||||
- [Team](#team)
|
||||
- [Hosted by](#hosted-by)
|
||||
- [License](#license)
|
||||
|
||||
|
||||
### Quick start
|
||||
|
||||
Create a folder and make it your current working directory:
|
||||
|
||||
```sh
|
||||
mkdir my-app
|
||||
cd my-app
|
||||
```
|
||||
|
||||
Generate a fastify project with `npm init`:
|
||||
|
||||
```sh
|
||||
npm init fastify
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
To start the app in dev mode:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
For production mode:
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
Under the hood `npm init` downloads and runs [Fastify
|
||||
Create](https://github.com/fastify/create-fastify), which in turn uses the
|
||||
generate functionality of [Fastify CLI](https://github.com/fastify/fastify-cli).
|
||||
|
||||
|
||||
### Install
|
||||
|
||||
To install Fastify in an existing project as a dependency:
|
||||
|
||||
```sh
|
||||
npm i fastify
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
```js
|
||||
// Require the framework and instantiate it
|
||||
|
||||
// ESM
|
||||
import Fastify from 'fastify'
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
// CommonJs
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
// Declare a route
|
||||
fastify.get('/', (request, reply) => {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
// Run the server!
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) throw err
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
With async-await:
|
||||
|
||||
```js
|
||||
// ESM
|
||||
import Fastify from 'fastify'
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
// CommonJs
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.get('/', async (request, reply) => {
|
||||
reply.type('application/json').code(200)
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) throw err
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
Do you want to know more? Head to the <a
|
||||
href="./docs/Guides/Getting-Started.md"><code><b>Getting Started</b></code></a>.
|
||||
If you learn best by reading code, explore the official [demo](https://github.com/fastify/demo).
|
||||
|
||||
> ## Note
|
||||
> `.listen` binds to the local host, `localhost`, interface by default
|
||||
> (`127.0.0.1` or `::1`, depending on the operating system configuration). If
|
||||
> you are running Fastify in a container (Docker,
|
||||
> [GCP](https://cloud.google.com/), etc.), you may need to bind to `0.0.0.0`. Be
|
||||
> careful when listening on all interfaces; it comes with inherent
|
||||
> [security
|
||||
> risks](https://web.archive.org/web/20170711105010/https://snyk.io/blog/mongodb-hack-and-secure-defaults/).
|
||||
> See [the documentation](./docs/Reference/Server.md#listen) for more
|
||||
> information.
|
||||
|
||||
### Core features
|
||||
|
||||
- **Highly performant:** as far as we know, Fastify is one of the fastest web
|
||||
frameworks in town, depending on the code complexity we can serve up to 76+
|
||||
thousand requests per second.
|
||||
- **Extensible:** Fastify is fully extensible via its hooks, plugins, and
|
||||
decorators.
|
||||
- **Schema-based:** even if it is not mandatory we recommend using [JSON
|
||||
Schema](https://json-schema.org/) to validate your routes and serialize your
|
||||
outputs. Internally Fastify compiles the schema in a highly performant
|
||||
function.
|
||||
- **Logging:** logs are extremely important but are costly; we chose the best
|
||||
logger to almost remove this cost, [Pino](https://github.com/pinojs/pino)!
|
||||
- **Developer friendly:** the framework is built to be very expressive and help
|
||||
developers in their daily use without sacrificing performance and
|
||||
security.
|
||||
|
||||
### Benchmarks
|
||||
|
||||
__Machine:__ EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD.
|
||||
|
||||
__Method:__: `autocannon -c 100 -d 40 -p 10 localhost:3000` * 2, taking the
|
||||
second average
|
||||
|
||||
| Framework | Version | Router? | Requests/sec |
|
||||
| :----------------- | :------------------------- | :----------: | ------------: |
|
||||
| Express | 4.17.3 | ✓ | 14,200 |
|
||||
| hapi | 20.2.1 | ✓ | 42,284 |
|
||||
| Restify | 8.6.1 | ✓ | 50,363 |
|
||||
| Koa | 2.13.0 | ✗ | 54,272 |
|
||||
| **Fastify** | **4.0.0** | **✓** | **77,193** |
|
||||
| - | | | |
|
||||
| `http.Server` | 16.14.2 | ✗ | 74,513 |
|
||||
|
||||
These benchmarks taken using https://github.com/fastify/benchmarks. This is a
|
||||
synthetic "hello world" benchmark that aims to evaluate the framework overhead.
|
||||
The overhead that each framework has on your application depends on your
|
||||
application. You should __always__ benchmark if performance matters to you.
|
||||
|
||||
## Documentation
|
||||
* [__`Getting Started`__](./docs/Guides/Getting-Started.md)
|
||||
* [__`Guides`__](./docs/Guides/Index.md)
|
||||
* [__`Server`__](./docs/Reference/Server.md)
|
||||
* [__`Routes`__](./docs/Reference/Routes.md)
|
||||
* [__`Encapsulation`__](./docs/Reference/Encapsulation.md)
|
||||
* [__`Logging`__](./docs/Reference/Logging.md)
|
||||
* [__`Middleware`__](./docs/Reference/Middleware.md)
|
||||
* [__`Hooks`__](./docs/Reference/Hooks.md)
|
||||
* [__`Decorators`__](./docs/Reference/Decorators.md)
|
||||
* [__`Validation and Serialization`__](./docs/Reference/Validation-and-Serialization.md)
|
||||
* [__`Fluent Schema`__](./docs/Guides/Fluent-Schema.md)
|
||||
* [__`Lifecycle`__](./docs/Reference/Lifecycle.md)
|
||||
* [__`Reply`__](./docs/Reference/Reply.md)
|
||||
* [__`Request`__](./docs/Reference/Request.md)
|
||||
* [__`Errors`__](./docs/Reference/Errors.md)
|
||||
* [__`Content Type Parser`__](./docs/Reference/ContentTypeParser.md)
|
||||
* [__`Plugins`__](./docs/Reference/Plugins.md)
|
||||
* [__`Testing`__](./docs/Guides/Testing.md)
|
||||
* [__`Benchmarking`__](./docs/Guides/Benchmarking.md)
|
||||
* [__`How to write a good plugin`__](./docs/Guides/Write-Plugin.md)
|
||||
* [__`Plugins Guide`__](./docs/Guides/Plugins-Guide.md)
|
||||
* [__`HTTP2`__](./docs/Reference/HTTP2.md)
|
||||
* [__`Long Term Support`__](./docs/Reference/LTS.md)
|
||||
* [__`TypeScript and types support`__](./docs/Reference/TypeScript.md)
|
||||
* [__`Serverless`__](./docs/Guides/Serverless.md)
|
||||
* [__`Recommendations`__](./docs/Guides/Recommendations.md)
|
||||
|
||||
## Ecosystem
|
||||
|
||||
- [Core](./docs/Guides/Ecosystem.md#core) - Core plugins maintained by the
|
||||
_Fastify_ [team](#team).
|
||||
- [Community](./docs/Guides/Ecosystem.md#community) - Community-supported
|
||||
plugins.
|
||||
- [Live Examples](https://github.com/fastify/example) - Multirepo with a broad
|
||||
set of real working examples.
|
||||
- [Discord](https://discord.gg/D3FZYPy) - Join our discord server and chat with
|
||||
the maintainers.
|
||||
|
||||
## Support
|
||||
Please visit [Fastify help](https://github.com/fastify/help) to view prior
|
||||
support issues and to ask new support questions.
|
||||
|
||||
Version 3 of Fastify and lower are EOL and will not receive any security or bug fixes.
|
||||
|
||||
Fastify's partner, HeroDevs, provides commercial security fixes for all
|
||||
unsupported versions at [https://herodevs.com/support/fastify-nes][hd-link].
|
||||
Fastify's supported version matrix is available in the
|
||||
[Long Term Support][lts-link] documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
Whether reporting bugs, discussing improvements and new ideas, or writing code,
|
||||
we welcome contributions from anyone and everyone. Please read the [CONTRIBUTING](./CONTRIBUTING.md)
|
||||
guidelines before submitting pull requests.
|
||||
|
||||
## Team
|
||||
|
||||
_Fastify_ is the result of the work of a great community. Team members are
|
||||
listed in alphabetical order.
|
||||
|
||||
**Lead Maintainers:**
|
||||
* [__Matteo Collina__](https://github.com/mcollina),
|
||||
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
|
||||
* [__Tomas Della Vedova__](https://github.com/delvedor),
|
||||
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
|
||||
* [__KaKa Ng__](https://github.com/climba03003),
|
||||
<https://www.npmjs.com/~climba03003>
|
||||
* [__Manuel Spigolon__](https://github.com/eomm),
|
||||
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
|
||||
* [__James Sumners__](https://github.com/jsumners),
|
||||
<https://twitter.com/jsumners79>, <https://www.npmjs.com/~jsumners>
|
||||
|
||||
### Fastify Core team
|
||||
* [__Aras Abbasi__](https://github.com/uzlopak),
|
||||
<https://www.npmjs.com/~uzlopak>
|
||||
* [__Harry Brundage__](https://github.com/airhorns/),
|
||||
<https://twitter.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
|
||||
* [__Matteo Collina__](https://github.com/mcollina),
|
||||
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
|
||||
* [__Gürgün Dayıoğlu__](https://github.com/gurgunday),
|
||||
<https://www.npmjs.com/~gurgunday>
|
||||
* [__Tomas Della Vedova__](https://github.com/delvedor),
|
||||
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
|
||||
* [__Carlos Fuentes__](https://github.com/metcoder95),
|
||||
<https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
|
||||
* [__Vincent Le Goff__](https://github.com/zekth)
|
||||
* [__Luciano Mammino__](https://github.com/lmammino),
|
||||
<https://twitter.com/loige>, <https://www.npmjs.com/~lmammino>
|
||||
* [__Jean Michelet__](https://github.com/jean-michelet),
|
||||
<https://www.npmjs.com/~jean-michelet>
|
||||
* [__KaKa Ng__](https://github.com/climba03003),
|
||||
<https://www.npmjs.com/~climba03003>
|
||||
* [__Luis Orbaiceta__](https://github.com/luisorbaiceta),
|
||||
<https://twitter.com/luisorbai>, <https://www.npmjs.com/~luisorbaiceta>
|
||||
* [__Maksim Sinik__](https://github.com/fox1t),
|
||||
<https://twitter.com/maksimsinik>, <https://www.npmjs.com/~fox1t>
|
||||
* [__Manuel Spigolon__](https://github.com/eomm),
|
||||
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
|
||||
* [__James Sumners__](https://github.com/jsumners),
|
||||
<https://twitter.com/jsumners79>, <https://www.npmjs.com/~jsumners>
|
||||
|
||||
### Fastify Plugins team
|
||||
* [__Harry Brundage__](https://github.com/airhorns/),
|
||||
<https://twitter.com/harrybrundage>, <https://www.npmjs.com/~airhorns>
|
||||
* [__Simone Busoli__](https://github.com/simoneb),
|
||||
<https://twitter.com/simonebu>, <https://www.npmjs.com/~simoneb>
|
||||
* [__Dan Castillo__](https://github.com/dancastillo),
|
||||
<https://www.npmjs.com/~dancastillo>
|
||||
* [__Matteo Collina__](https://github.com/mcollina),
|
||||
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
|
||||
* [__Gürgün Dayıoğlu__](https://github.com/gurgunday),
|
||||
<https://www.npmjs.com/~gurgunday>
|
||||
* [__Tomas Della Vedova__](https://github.com/delvedor),
|
||||
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
|
||||
* [__Carlos Fuentes__](https://github.com/metcoder95),
|
||||
<https://twitter.com/metcoder95>, <https://www.npmjs.com/~metcoder95>
|
||||
* [__Vincent Le Goff__](https://github.com/zekth)
|
||||
* [__Jean Michelet__](https://github.com/jean-michelet),
|
||||
<https://www.npmjs.com/~jean-michelet>
|
||||
* [__KaKa Ng__](https://github.com/climba03003),
|
||||
<https://www.npmjs.com/~climba03003>
|
||||
* [__Maksim Sinik__](https://github.com/fox1t),
|
||||
<https://twitter.com/maksimsinik>, <https://www.npmjs.com/~fox1t>
|
||||
* [__Frazer Smith__](https://github.com/Fdawgs), <https://www.npmjs.com/~fdawgs>
|
||||
* [__Manuel Spigolon__](https://github.com/eomm),
|
||||
<https://twitter.com/manueomm>, <https://www.npmjs.com/~eomm>
|
||||
|
||||
### Emeritus Contributors
|
||||
Great contributors to a specific area of the Fastify ecosystem will be invited
|
||||
to join this group by Lead Maintainers when they decide to step down from the
|
||||
active contributor's group.
|
||||
|
||||
* [__Tommaso Allevi__](https://github.com/allevo),
|
||||
<https://twitter.com/allevitommaso>, <https://www.npmjs.com/~allevo>
|
||||
* [__Ethan Arrowood__](https://github.com/Ethan-Arrowood/),
|
||||
<https://twitter.com/arrowoodtech>, <https://www.npmjs.com/~ethan_arrowood>
|
||||
* [__Çağatay Çalı__](https://github.com/cagataycali),
|
||||
<https://twitter.com/cagataycali>, <https://www.npmjs.com/~cagataycali>
|
||||
* [__David Mark Clements__](https://github.com/davidmarkclements),
|
||||
<https://twitter.com/davidmarkclem>,
|
||||
<https://www.npmjs.com/~davidmarkclements>
|
||||
* [__dalisoft__](https://github.com/dalisoft), <https://twitter.com/dalisoft>,
|
||||
<https://www.npmjs.com/~dalisoft>
|
||||
* [__Dustin Deus__](https://github.com/StarpTech),
|
||||
<https://twitter.com/dustindeus>, <https://www.npmjs.com/~starptech>
|
||||
* [__Denis Fäcke__](https://github.com/SerayaEryn),
|
||||
<https://twitter.com/serayaeryn>, <https://www.npmjs.com/~serayaeryn>
|
||||
* [__Rafael Gonzaga__](https://github.com/rafaelgss),
|
||||
<https://twitter.com/_rafaelgss>, <https://www.npmjs.com/~rafaelgss>
|
||||
* [__Trivikram Kamat__](https://github.com/trivikr),
|
||||
<https://twitter.com/trivikram>, <https://www.npmjs.com/~trivikr>
|
||||
* [__Ayoub El Khattabi__](https://github.com/AyoubElk),
|
||||
<https://twitter.com/ayoubelkh>, <https://www.npmjs.com/~ayoubelk>
|
||||
* [__Cemre Mengu__](https://github.com/cemremengu),
|
||||
<https://twitter.com/cemremengu>, <https://www.npmjs.com/~cemremengu>
|
||||
* [__Salman Mitha__](https://github.com/salmanm),
|
||||
<https://www.npmjs.com/~salmanm>
|
||||
* [__Nathan Woltman__](https://github.com/nwoltman),
|
||||
<https://twitter.com/NathanWoltman>, <https://www.npmjs.com/~nwoltman>
|
||||
|
||||
## Hosted by
|
||||
|
||||
[<img
|
||||
src="https://github.com/openjs-foundation/artwork/blob/main/openjs_foundation/openjs_foundation-logo-horizontal-color.png?raw=true"
|
||||
width="250px;"/>](https://openjsf.org/projects)
|
||||
|
||||
We are an [At-Large
|
||||
Project](https://github.com/openjs-foundation/cross-project-council/blob/HEAD/PROJECT_PROGRESSION.md#at-large-projects)
|
||||
in the [OpenJS Foundation](https://openjsf.org/).
|
||||
|
||||
## Sponsors
|
||||
|
||||
Support this project by becoming a [SPONSOR](./SPONSORS.md)!
|
||||
Fastify has an [Open Collective](https://opencollective.com/fastify)
|
||||
page where we accept and manage financial contributions.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This project is kindly sponsored by:
|
||||
- [NearForm](https://nearform.com)
|
||||
- [Platformatic](https://platformatic.dev)
|
||||
|
||||
Past Sponsors:
|
||||
- [LetzDoIt](https://www.letzdoitapp.com/)
|
||||
|
||||
This list includes all companies that support one or more team members
|
||||
in maintaining this project.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under [MIT](./LICENSE).
|
||||
|
||||
For your convenience, here is a list of all the licenses of our production
|
||||
dependencies:
|
||||
- MIT
|
||||
- ISC
|
||||
- BSD-3-Clause
|
||||
- BSD-2-Clause
|
||||
|
||||
[hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=github_readme
|
||||
[lts-link]: https://fastify.dev/docs/latest/Reference/LTS/
|
||||
160
node_modules/fastify/SECURITY.md
generated
vendored
Normal file
160
node_modules/fastify/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
# Security Policy
|
||||
|
||||
This document describes the management of vulnerabilities for the Fastify
|
||||
project and its official plugins.
|
||||
|
||||
## Reporting vulnerabilities
|
||||
|
||||
Individuals who find potential vulnerabilities in Fastify are invited to
|
||||
complete a vulnerability report via the dedicated pages:
|
||||
|
||||
1. [HackerOne](https://hackerone.com/fastify)
|
||||
2. [GitHub Security Advisory](https://github.com/fastify/fastify/security/advisories/new)
|
||||
|
||||
### Strict measures when reporting vulnerabilities
|
||||
|
||||
It is of the utmost importance that you read carefully and follow these
|
||||
guidelines to ensure the ecosystem as a whole isn't disrupted due to improperly
|
||||
reported vulnerabilities:
|
||||
|
||||
* Avoid creating new "informative" reports. Only create new
|
||||
reports on a vulnerability if you are absolutely sure this should be
|
||||
tagged as an actual vulnerability. Third-party vendors and individuals are
|
||||
tracking any new vulnerabilities reported in HackerOne or GitHub and will flag
|
||||
them as such for their customers (think about snyk, npm audit, ...).
|
||||
* Security reports should never be created and triaged by the same person. If
|
||||
you are creating a report for a vulnerability that you found, or on
|
||||
behalf of someone else, there should always be a 2nd Security Team member who
|
||||
triages it. If in doubt, invite more Fastify Collaborators to help triage the
|
||||
validity of the report. In any case, the report should follow the same process
|
||||
as outlined below of inviting the maintainers to review and accept the
|
||||
vulnerability.
|
||||
* ***Do not*** attempt to show CI/CD vulnerabilities by creating new pull
|
||||
requests to any of the Fastify organization's repositories. Doing so will
|
||||
result in a [content report][cr] to GitHub as an unsolicited exploit.
|
||||
The proper way to provide such reports is by creating a new repository,
|
||||
configured in the same manner as the repository you would like to submit
|
||||
a report about, and with a pull request to your own repository showing
|
||||
the proof of concept.
|
||||
|
||||
[cr]: https://docs.github.com/en/communities/maintaining-your-safety-on-github/reporting-abuse-or-spam#reporting-an-issue-or-pull-request
|
||||
|
||||
### Vulnerabilities found outside this process
|
||||
|
||||
⚠ The Fastify project does not support any reporting outside the process mentioned
|
||||
in this document.
|
||||
|
||||
## Handling vulnerability reports
|
||||
|
||||
When a potential vulnerability is reported, the following actions are taken:
|
||||
|
||||
### Triage
|
||||
|
||||
**Delay:** 4 business days
|
||||
|
||||
Within 4 business days, a member of the security team provides a first answer to
|
||||
the individual who submitted the potential vulnerability. The possible responses
|
||||
can be:
|
||||
|
||||
* **Acceptance**: what was reported is considered as a new vulnerability
|
||||
* **Rejection**: what was reported is not considered as a new vulnerability
|
||||
* **Need more information**: the security team needs more information in order to
|
||||
evaluate what was reported.
|
||||
|
||||
Triaging should include updating issue fields:
|
||||
* Asset - set/create the module affected by the report
|
||||
* Severity - TBD, currently left empty
|
||||
|
||||
Reference: [HackerOne: Submitting
|
||||
Reports](https://docs.hackerone.com/hackers/submitting-reports.html)
|
||||
|
||||
### Correction follow-up
|
||||
|
||||
**Delay:** 90 days
|
||||
|
||||
When a vulnerability is confirmed, a member of the security team volunteers to
|
||||
follow up on this report.
|
||||
|
||||
With the help of the individual who reported the vulnerability, they contact the
|
||||
maintainers of the vulnerable package to make them aware of the vulnerability.
|
||||
The maintainers can be invited as participants to the reported issue.
|
||||
|
||||
With the package maintainer, they define a release date for the publication of
|
||||
the vulnerability. Ideally, this release date should not happen before the
|
||||
package has been patched.
|
||||
|
||||
The report's vulnerable versions upper limit should be set to:
|
||||
* `*` if there is no fixed version available by the time of publishing the
|
||||
report.
|
||||
* the last vulnerable version. For example: `<=1.2.3` if a fix exists in `1.2.4`
|
||||
|
||||
### Publication
|
||||
|
||||
**Delay:** 90 days
|
||||
|
||||
Within 90 days after the triage date, the vulnerability must be made public.
|
||||
|
||||
**Severity**: Vulnerability severity is assessed using [CVSS
|
||||
v.3](https://www.first.org/cvss/user-guide). More information can be found on
|
||||
[HackerOne documentation](https://docs.hackerone.com/hackers/severity.html)
|
||||
|
||||
If the package maintainer is actively developing a patch, an additional delay
|
||||
can be added with the approval of the security team and the individual who
|
||||
reported the vulnerability.
|
||||
|
||||
At this point, a CVE should be requested through the selected platform through
|
||||
the UI, which should include the Report ID and a summary.
|
||||
|
||||
Within HackerOne, this is handled through a "public disclosure request".
|
||||
|
||||
Reference: [HackerOne:
|
||||
Disclosure](https://docs.hackerone.com/hackers/disclosure.html)
|
||||
|
||||
## The Fastify Security team
|
||||
|
||||
The core team is responsible for the management of the security program and
|
||||
this policy and process.
|
||||
|
||||
Members of this team are expected to keep all information that they have
|
||||
privileged access to by being on the team completely private to the team. This
|
||||
includes agreeing to not notify anyone outside the team of issues that have not
|
||||
yet been disclosed publicly, including the existence of issues, expectations of
|
||||
upcoming releases, and patching of any issues other than in the process of their
|
||||
work as a member of the Fastify Core team.
|
||||
|
||||
### Members
|
||||
|
||||
* [__Matteo Collina__](https://github.com/mcollina),
|
||||
<https://twitter.com/matteocollina>, <https://www.npmjs.com/~matteo.collina>
|
||||
* [__Tomas Della Vedova__](https://github.com/delvedor),
|
||||
<https://twitter.com/delvedor>, <https://www.npmjs.com/~delvedor>
|
||||
* [__Vincent Le Goff__](https://github.com/zekth)
|
||||
* [__KaKa Ng__](https://github.com/climba03003)
|
||||
* [__James Sumners__](https://github.com/jsumners),
|
||||
<https://twitter.com/jsumners79>, <https://www.npmjs.com/~jsumners>
|
||||
|
||||
## OpenSSF CII Best Practices
|
||||
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/7585)
|
||||
|
||||
There are three “tiers”: passing, silver, and gold.
|
||||
|
||||
### Passing
|
||||
We meet 100% of the “passing” criteria.
|
||||
|
||||
### Silver
|
||||
We meet 87% of the “silver” criteria. The gaps are as follows:
|
||||
- we do not have a DCO or a CLA process for contributions.
|
||||
- we do not currently document
|
||||
“what the user can and cannot expect in terms of security” for our project.
|
||||
- we do not currently document ”the architecture (aka high-level design)”
|
||||
for our project.
|
||||
|
||||
### Gold
|
||||
We meet 70% of the “gold” criteria. The gaps are as follows:
|
||||
- we do not yet have the “silver” badge; see all the gaps above.
|
||||
- We do not include a copyright or license statement in each source file.
|
||||
Efforts are underway to change this archaic practice into a
|
||||
suggestion instead of a hard requirement.
|
||||
- There are a few unanswered questions around cryptography that are
|
||||
waiting for clarification.
|
||||
23
node_modules/fastify/SPONSORS.md
generated
vendored
Normal file
23
node_modules/fastify/SPONSORS.md
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Sponsors
|
||||
|
||||
All active sponsors of Fastify are listed here, in order of contribution!
|
||||
Our sponsors are the reason why we can work on some issues or features
|
||||
that otherwise would be impossible to do.
|
||||
|
||||
If you want to become a sponsor, please check out our [Open Collective page](https://opencollective.com/fastify)
|
||||
or [GitHub Sponsors](https://github.com/sponsors/fastify)!
|
||||
|
||||
## Tier 4
|
||||
|
||||
_Be the first!_
|
||||
|
||||
## Tier 3
|
||||
|
||||
- [Mercedes-Benz Group](https://github.com/mercedes-benz)
|
||||
- [Val Town, Inc.](https://opencollective.com/valtown)
|
||||
- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024)
|
||||
- [Lokalise - A Localization and Translation Software Tool](https://lokalise.com/?utm_source=Fastify_GH&utm_medium=sponsorship)
|
||||
|
||||
## Tier 2
|
||||
|
||||
_Be the first!_
|
||||
35
node_modules/fastify/build/build-error-serializer.js
generated
vendored
Normal file
35
node_modules/fastify/build/build-error-serializer.js
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/* istanbul ignore file */
|
||||
'use strict'
|
||||
|
||||
const FJS = require('fast-json-stringify')
|
||||
const path = require('node:path')
|
||||
const fs = require('node:fs')
|
||||
|
||||
const code = FJS({
|
||||
type: 'object',
|
||||
properties: {
|
||||
statusCode: { type: 'number' },
|
||||
code: { type: 'string' },
|
||||
error: { type: 'string' },
|
||||
message: { type: 'string' }
|
||||
}
|
||||
}, { mode: 'standalone' })
|
||||
|
||||
const file = path.join(__dirname, '..', 'lib', 'error-serializer.js')
|
||||
|
||||
const moduleCode = `// This file is autogenerated by build/build-error-serializer.js, do not edit
|
||||
/* c8 ignore start */
|
||||
${code}
|
||||
/* c8 ignore stop */
|
||||
`
|
||||
|
||||
/* c8 ignore start */
|
||||
if (require.main === module) {
|
||||
fs.writeFileSync(file, moduleCode)
|
||||
console.log(`Saved ${file} file successfully`)
|
||||
} else {
|
||||
module.exports = {
|
||||
code: moduleCode
|
||||
}
|
||||
}
|
||||
/* c8 ignore stop */
|
||||
168
node_modules/fastify/build/build-validation.js
generated
vendored
Normal file
168
node_modules/fastify/build/build-validation.js
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
'use strict'
|
||||
|
||||
const AjvStandaloneCompiler = require('@fastify/ajv-compiler/standalone')
|
||||
const { _ } = require('ajv')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const factory = AjvStandaloneCompiler({
|
||||
readMode: false,
|
||||
storeFunction (routeOpts, schemaValidationCode) {
|
||||
const moduleCode = `// This file is autogenerated by build/build-validation.js, do not edit
|
||||
/* c8 ignore start */
|
||||
${schemaValidationCode}
|
||||
|
||||
module.exports.defaultInitOptions = ${JSON.stringify(defaultInitOptions)}
|
||||
/* c8 ignore stop */
|
||||
`
|
||||
|
||||
const file = path.join(__dirname, '..', 'lib', 'configValidator.js')
|
||||
fs.writeFileSync(file, moduleCode)
|
||||
console.log(`Saved ${file} file successfully`)
|
||||
}
|
||||
})
|
||||
|
||||
const defaultInitOptions = {
|
||||
connectionTimeout: 0, // 0 sec
|
||||
keepAliveTimeout: 72000, // 72 seconds
|
||||
forceCloseConnections: undefined, // keep-alive connections
|
||||
maxRequestsPerSocket: 0, // no limit
|
||||
requestTimeout: 0, // no limit
|
||||
bodyLimit: 1024 * 1024, // 1 MiB
|
||||
caseSensitive: true,
|
||||
allowUnsafeRegex: false,
|
||||
disableRequestLogging: false,
|
||||
ignoreTrailingSlash: false,
|
||||
ignoreDuplicateSlashes: false,
|
||||
maxParamLength: 100,
|
||||
onProtoPoisoning: 'error',
|
||||
onConstructorPoisoning: 'error',
|
||||
pluginTimeout: 10000,
|
||||
requestIdHeader: false,
|
||||
requestIdLogLabel: 'reqId',
|
||||
http2SessionTimeout: 72000, // 72 seconds
|
||||
exposeHeadRoutes: true,
|
||||
useSemicolonDelimiter: false,
|
||||
allowErrorHandlerOverride: true, // TODO: set to false in v6
|
||||
routerOptions: {
|
||||
ignoreTrailingSlash: false,
|
||||
ignoreDuplicateSlashes: false,
|
||||
maxParamLength: 100,
|
||||
allowUnsafeRegex: false,
|
||||
useSemicolonDelimiter: false
|
||||
}
|
||||
}
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
connectionTimeout: { type: 'integer', default: defaultInitOptions.connectionTimeout },
|
||||
keepAliveTimeout: { type: 'integer', default: defaultInitOptions.keepAliveTimeout },
|
||||
forceCloseConnections: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
pattern: 'idle'
|
||||
},
|
||||
{
|
||||
type: 'boolean'
|
||||
}
|
||||
]
|
||||
},
|
||||
maxRequestsPerSocket: { type: 'integer', default: defaultInitOptions.maxRequestsPerSocket, nullable: true },
|
||||
requestTimeout: { type: 'integer', default: defaultInitOptions.requestTimeout },
|
||||
bodyLimit: { type: 'integer', default: defaultInitOptions.bodyLimit },
|
||||
caseSensitive: { type: 'boolean', default: defaultInitOptions.caseSensitive },
|
||||
allowUnsafeRegex: { type: 'boolean', default: defaultInitOptions.allowUnsafeRegex },
|
||||
http2: { type: 'boolean' },
|
||||
https: {
|
||||
if: {
|
||||
not: {
|
||||
oneOf: [
|
||||
{ type: 'boolean' },
|
||||
{ type: 'null' },
|
||||
{
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['allowHTTP1'],
|
||||
properties: {
|
||||
allowHTTP1: { type: 'boolean' }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
then: { setDefaultValue: true }
|
||||
},
|
||||
ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.ignoreTrailingSlash },
|
||||
ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.ignoreDuplicateSlashes },
|
||||
disableRequestLogging: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
maxParamLength: { type: 'integer', default: defaultInitOptions.maxParamLength },
|
||||
onProtoPoisoning: { type: 'string', default: defaultInitOptions.onProtoPoisoning },
|
||||
onConstructorPoisoning: { type: 'string', default: defaultInitOptions.onConstructorPoisoning },
|
||||
pluginTimeout: { type: 'integer', default: defaultInitOptions.pluginTimeout },
|
||||
requestIdHeader: { anyOf: [{ type: 'boolean' }, { type: 'string' }], default: defaultInitOptions.requestIdHeader },
|
||||
requestIdLogLabel: { type: 'string', default: defaultInitOptions.requestIdLogLabel },
|
||||
http2SessionTimeout: { type: 'integer', default: defaultInitOptions.http2SessionTimeout },
|
||||
exposeHeadRoutes: { type: 'boolean', default: defaultInitOptions.exposeHeadRoutes },
|
||||
useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.useSemicolonDelimiter },
|
||||
routerOptions: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
ignoreTrailingSlash: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreTrailingSlash },
|
||||
ignoreDuplicateSlashes: { type: 'boolean', default: defaultInitOptions.routerOptions.ignoreDuplicateSlashes },
|
||||
maxParamLength: { type: 'integer', default: defaultInitOptions.routerOptions.maxParamLength },
|
||||
allowUnsafeRegex: { type: 'boolean', default: defaultInitOptions.routerOptions.allowUnsafeRegex },
|
||||
useSemicolonDelimiter: { type: 'boolean', default: defaultInitOptions.routerOptions.useSemicolonDelimiter }
|
||||
}
|
||||
},
|
||||
constraints: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'object',
|
||||
required: ['name', 'storage', 'validate', 'deriveConstraint'],
|
||||
additionalProperties: true,
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
storage: {},
|
||||
validate: {},
|
||||
deriveConstraint: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const compiler = factory({}, {
|
||||
customOptions: {
|
||||
code: {
|
||||
source: true,
|
||||
lines: true,
|
||||
optimize: 3
|
||||
},
|
||||
removeAdditional: true,
|
||||
useDefaults: true,
|
||||
coerceTypes: true,
|
||||
keywords: [
|
||||
{
|
||||
keyword: 'setDefaultValue',
|
||||
$data: true,
|
||||
// error: false,
|
||||
modifying: true,
|
||||
valid: true,
|
||||
code (keywordCxt) {
|
||||
const { gen, it, schemaValue } = keywordCxt
|
||||
const logicCode = gen.assign(_`${it.parentData}[${it.parentDataProperty}]`, schemaValue)
|
||||
return logicCode
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
compiler({ schema })
|
||||
11
node_modules/fastify/build/sync-version.js
generated
vendored
Normal file
11
node_modules/fastify/build/sync-version.js
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
// package.json:version -> fastify.js:VERSION
|
||||
const { version } = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString('utf8'))
|
||||
|
||||
const fastifyJs = path.join(__dirname, '..', 'fastify.js')
|
||||
|
||||
fs.writeFileSync(fastifyJs, fs.readFileSync(fastifyJs).toString('utf8').replace(/const\s*VERSION\s*=.*/, `const VERSION = '${version}'`))
|
||||
60
node_modules/fastify/docs/Guides/Benchmarking.md
generated
vendored
Normal file
60
node_modules/fastify/docs/Guides/Benchmarking.md
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Benchmarking
|
||||
Benchmarking is important if you want to measure how a change can affect your
|
||||
application's performance. We provide a simple way to benchmark your
|
||||
application from the point of view of a user and contributor. The setup allows
|
||||
you to automate benchmarks in different branches and on different Node.js
|
||||
versions.
|
||||
|
||||
The modules we will use:
|
||||
- [Autocannon](https://github.com/mcollina/autocannon): An HTTP/1.1 benchmarking
|
||||
tool written in node.
|
||||
- [Branch-comparer](https://github.com/StarpTech/branch-comparer): Checkout
|
||||
multiple git branches, execute scripts, and log the results.
|
||||
- [Concurrently](https://github.com/open-cli-tools/concurrently): Run commands
|
||||
concurrently.
|
||||
- [Npx](https://github.com/npm/npx): NPM package runner used to run scripts
|
||||
against different Node.js Versions and execute local binaries. Shipped with
|
||||
npm@5.2.0.
|
||||
|
||||
## Simple
|
||||
|
||||
### Run the test in the current branch
|
||||
```sh
|
||||
npm run benchmark
|
||||
```
|
||||
|
||||
### Run the test against different Node.js versions ✨
|
||||
```sh
|
||||
npx -p node@10 -- npm run benchmark
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
### Run the test in different branches
|
||||
```sh
|
||||
branchcmp --rounds 2 --script "npm run benchmark"
|
||||
```
|
||||
|
||||
### Run the test in different branches against different Node.js versions ✨
|
||||
```sh
|
||||
branchcmp --rounds 2 --script "npm run benchmark"
|
||||
```
|
||||
|
||||
### Compare current branch with main (Gitflow)
|
||||
```sh
|
||||
branchcmp --rounds 2 --gitflow --script "npm run benchmark"
|
||||
```
|
||||
or
|
||||
```sh
|
||||
npm run bench
|
||||
```
|
||||
|
||||
### Run different examples
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
```sh
|
||||
branchcmp --rounds 2 -s "node ./node_modules/concurrently -k -s first \"node ./examples/asyncawait.js\" \"node ./node_modules/autocannon -c 100 -d 5 -p 10 localhost:3000/\""
|
||||
```
|
||||
<!-- markdownlint-enable -->
|
||||
321
node_modules/fastify/docs/Guides/Database.md
generated
vendored
Normal file
321
node_modules/fastify/docs/Guides/Database.md
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Database
|
||||
|
||||
Fastify's ecosystem provides a handful of
|
||||
plugins for connecting to various database engines.
|
||||
This guide covers engines that have Fastify
|
||||
plugins maintained within the Fastify organization.
|
||||
|
||||
> If a plugin for your database of choice does not exist
|
||||
> you can still use the database as Fastify is database agnostic.
|
||||
> By following the examples of the database plugins listed in this guide,
|
||||
> a plugin can be written for the missing database engine.
|
||||
|
||||
> If you would like to write your own Fastify plugin
|
||||
> please take a look at the [plugins guide](./Plugins-Guide.md)
|
||||
|
||||
### [MySQL](https://github.com/fastify/fastify-mysql)
|
||||
|
||||
Install the plugin by running `npm i @fastify/mysql`.
|
||||
|
||||
*Usage:*
|
||||
|
||||
```javascript
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.register(require('@fastify/mysql'), {
|
||||
connectionString: 'mysql://root@localhost/mysql'
|
||||
})
|
||||
|
||||
fastify.get('/user/:id', function(req, reply) {
|
||||
fastify.mysql.query(
|
||||
'SELECT id, username, hash, salt FROM users WHERE id=?', [req.params.id],
|
||||
function onResult (err, result) {
|
||||
reply.send(err || result)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) throw err
|
||||
console.log(`server listening on ${fastify.server.address().port}`)
|
||||
})
|
||||
```
|
||||
|
||||
### [Postgres](https://github.com/fastify/fastify-postgres)
|
||||
Install the plugin by running `npm i pg @fastify/postgres`.
|
||||
|
||||
*Example*:
|
||||
|
||||
```javascript
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.register(require('@fastify/postgres'), {
|
||||
connectionString: 'postgres://postgres@localhost/postgres'
|
||||
})
|
||||
|
||||
fastify.get('/user/:id', function (req, reply) {
|
||||
fastify.pg.query(
|
||||
'SELECT id, username, hash, salt FROM users WHERE id=$1', [req.params.id],
|
||||
function onResult (err, result) {
|
||||
reply.send(err || result)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) throw err
|
||||
console.log(`server listening on ${fastify.server.address().port}`)
|
||||
})
|
||||
```
|
||||
|
||||
### [Redis](https://github.com/fastify/fastify-redis)
|
||||
Install the plugin by running `npm i @fastify/redis`
|
||||
|
||||
*Usage:*
|
||||
|
||||
```javascript
|
||||
'use strict'
|
||||
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.register(require('@fastify/redis'), { host: '127.0.0.1' })
|
||||
// or
|
||||
fastify.register(require('@fastify/redis'), { url: 'redis://127.0.0.1', /* other redis options */ })
|
||||
|
||||
fastify.get('/foo', function (req, reply) {
|
||||
const { redis } = fastify
|
||||
redis.get(req.query.key, (err, val) => {
|
||||
reply.send(err || val)
|
||||
})
|
||||
})
|
||||
|
||||
fastify.post('/foo', function (req, reply) {
|
||||
const { redis } = fastify
|
||||
redis.set(req.body.key, req.body.value, (err) => {
|
||||
reply.send(err || { status: 'ok' })
|
||||
})
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) throw err
|
||||
console.log(`server listening on ${fastify.server.address().port}`)
|
||||
})
|
||||
```
|
||||
|
||||
By default `@fastify/redis` doesn't close
|
||||
the client connection when Fastify server shuts down.
|
||||
To opt-in to this behavior, register the client like so:
|
||||
|
||||
```javascript
|
||||
fastify.register(require('@fastify/redis'), {
|
||||
client: redis,
|
||||
closeClient: true
|
||||
})
|
||||
```
|
||||
|
||||
### [Mongo](https://github.com/fastify/fastify-mongodb)
|
||||
Install the plugin by running `npm i @fastify/mongodb`
|
||||
|
||||
*Usage:*
|
||||
```javascript
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.register(require('@fastify/mongodb'), {
|
||||
// force to close the mongodb connection when app stopped
|
||||
// the default value is false
|
||||
forceClose: true,
|
||||
|
||||
url: 'mongodb://mongo/mydb'
|
||||
})
|
||||
|
||||
fastify.get('/user/:id', async function (req, reply) {
|
||||
// Or this.mongo.client.db('mydb').collection('users')
|
||||
const users = this.mongo.db.collection('users')
|
||||
|
||||
// if the id is an ObjectId format, you need to create a new ObjectId
|
||||
const id = this.mongo.ObjectId(req.params.id)
|
||||
try {
|
||||
const user = await users.findOne({ id })
|
||||
return user
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) throw err
|
||||
})
|
||||
```
|
||||
|
||||
### [LevelDB](https://github.com/fastify/fastify-leveldb)
|
||||
Install the plugin by running `npm i @fastify/leveldb`
|
||||
|
||||
*Usage:*
|
||||
```javascript
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.register(
|
||||
require('@fastify/leveldb'),
|
||||
{ name: 'db' }
|
||||
)
|
||||
|
||||
fastify.get('/foo', async function (req, reply) {
|
||||
const val = await this.level.db.get(req.query.key)
|
||||
return val
|
||||
})
|
||||
|
||||
fastify.post('/foo', async function (req, reply) {
|
||||
await this.level.db.put(req.body.key, req.body.value)
|
||||
return { status: 'ok' }
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) throw err
|
||||
console.log(`server listening on ${fastify.server.address().port}`)
|
||||
})
|
||||
```
|
||||
|
||||
### Writing plugin for a database library
|
||||
We could write a plugin for a database
|
||||
library too (e.g. Knex, Prisma, or TypeORM).
|
||||
We will use [Knex](https://knexjs.org/) in our example.
|
||||
|
||||
```javascript
|
||||
'use strict'
|
||||
|
||||
const fp = require('fastify-plugin')
|
||||
const knex = require('knex')
|
||||
|
||||
function knexPlugin(fastify, options, done) {
|
||||
if(!fastify.knex) {
|
||||
const knex = knex(options)
|
||||
fastify.decorate('knex', knex)
|
||||
|
||||
fastify.addHook('onClose', (fastify, done) => {
|
||||
if (fastify.knex === knex) {
|
||||
fastify.knex.destroy(done)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
export default fp(knexPlugin, { name: 'fastify-knex-example' })
|
||||
```
|
||||
|
||||
### Writing a plugin for a database engine
|
||||
|
||||
In this example, we will create a basic Fastify MySQL plugin from scratch (it is
|
||||
a stripped-down example, please use the official plugin in production).
|
||||
|
||||
```javascript
|
||||
const fp = require('fastify-plugin')
|
||||
const mysql = require('mysql2/promise')
|
||||
|
||||
function fastifyMysql(fastify, options, done) {
|
||||
const connection = mysql.createConnection(options)
|
||||
|
||||
if (!fastify.mysql) {
|
||||
fastify.decorate('mysql', connection)
|
||||
}
|
||||
|
||||
fastify.addHook('onClose', (fastify, done) => connection.end().then(done).catch(done))
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
export default fp(fastifyMysql, { name: 'fastify-mysql-example' })
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
Database schema migrations are an integral part of database management and
|
||||
development. Migrations provide a repeatable and testable way to modify a
|
||||
database's schema and prevent data loss.
|
||||
|
||||
As stated at the beginning of the guide, Fastify is database agnostic and any
|
||||
Node.js database migration tool can be used with it. We will give an example of
|
||||
using [Postgrator](https://www.npmjs.com/package/postgrator) which has support
|
||||
for Postgres, MySQL, SQL Server and SQLite. For MongoDB migrations, please check
|
||||
[migrate-mongo](https://www.npmjs.com/package/migrate-mongo).
|
||||
|
||||
#### [Postgrator](https://www.npmjs.com/package/postgrator)
|
||||
|
||||
Postgrator is Node.js SQL migration tool that uses a directory of SQL scripts to
|
||||
alter the database schema. Each file in a migrations folder needs to follow the
|
||||
pattern: ` [version].[action].[optional-description].sql`.
|
||||
|
||||
**version:** must be an incrementing number (e.g. `001` or a timestamp).
|
||||
|
||||
**action:** should be `do` or `undo`. `do` implements the version, `undo`
|
||||
reverts it. Think about it like `up` and `down` in other migration tools.
|
||||
|
||||
**optional-description** describes which changes migration makes. Although
|
||||
optional, it should be used for all migrations as it makes it easier for
|
||||
everyone to know which changes are made in a migration.
|
||||
|
||||
In our example, we are going to have a single migration that creates a `users`
|
||||
table and we are going to use `Postgrator` to run the migration.
|
||||
|
||||
> Run `npm i pg postgrator` to install dependencies needed for the
|
||||
> example.
|
||||
|
||||
```sql
|
||||
// 001.do.create-users-table.sql
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY NOT NULL,
|
||||
created_at DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
firstName TEXT NOT NULL,
|
||||
lastName TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
```javascript
|
||||
const pg = require('pg')
|
||||
const Postgrator = require('postgrator')
|
||||
const path = require('node:path')
|
||||
|
||||
async function migrate() {
|
||||
const client = new pg.Client({
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
database: 'example',
|
||||
user: 'example',
|
||||
password: 'example',
|
||||
});
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
|
||||
const postgrator = new Postgrator({
|
||||
migrationPattern: path.join(__dirname, '/migrations/*'),
|
||||
driver: 'pg',
|
||||
database: 'example',
|
||||
schemaTable: 'migrations',
|
||||
currentSchema: 'public', // Postgres and MS SQL Server only
|
||||
execQuery: (query) => client.query(query),
|
||||
});
|
||||
|
||||
const result = await postgrator.migrate()
|
||||
|
||||
if (result.length === 0) {
|
||||
console.log(
|
||||
'No migrations run for schema "public". Already at the latest one.'
|
||||
)
|
||||
}
|
||||
|
||||
console.log('Migration done.')
|
||||
|
||||
process.exitCode = 0
|
||||
} catch(err) {
|
||||
console.error(err)
|
||||
process.exitCode = 1
|
||||
}
|
||||
|
||||
await client.end()
|
||||
}
|
||||
|
||||
migrate()
|
||||
```
|
||||
608
node_modules/fastify/docs/Guides/Delay-Accepting-Requests.md
generated
vendored
Normal file
608
node_modules/fastify/docs/Guides/Delay-Accepting-Requests.md
generated
vendored
Normal file
@@ -0,0 +1,608 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
# Delay Accepting Requests
|
||||
|
||||
## Introduction
|
||||
|
||||
Fastify provides several [hooks](../Reference/Hooks.md) useful for a variety of
|
||||
situations. One of them is the [`onReady`](../Reference/Hooks.md#onready) hook,
|
||||
which is useful for executing tasks *right before* the server starts accepting
|
||||
new requests. There isn't, though, a direct mechanism to handle scenarios in
|
||||
which you'd like the server to start accepting **specific** requests and denying
|
||||
all others, at least up to some point.
|
||||
|
||||
Say, for instance, your server needs to authenticate with an OAuth provider to
|
||||
start serving requests. To do that it'd need to engage in the [OAuth
|
||||
Authorization Code
|
||||
Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow),
|
||||
which would require it to listen to two requests from the authentication
|
||||
provider:
|
||||
|
||||
1. the Authorization Code webhook
|
||||
2. the tokens webhook
|
||||
|
||||
Until the authorization flow is done you wouldn't be able to serve customer
|
||||
requests. What to do then?
|
||||
|
||||
There are several solutions for achieving that kind of behavior. Here we'll
|
||||
introduce one of such techniques and, hopefully, you'll be able to get things
|
||||
rolling asap!
|
||||
|
||||
## Solution
|
||||
|
||||
### Overview
|
||||
|
||||
The proposed solution is one of many possible ways of dealing with this scenario
|
||||
and many similar to it. It relies solely on Fastify, so no fancy infrastructure
|
||||
tricks or third-party libraries will be necessary.
|
||||
|
||||
To simplify things we won't be dealing with a precise OAuth flow but, instead,
|
||||
simulate a scenario in which some key is needed to serve a request and that key
|
||||
can only be retrieved in runtime by authenticating with an external provider.
|
||||
|
||||
The main goal here is to deny requests that would otherwise fail **as early as
|
||||
possible** and with some **meaningful context**. That's both useful for the
|
||||
server (fewer resources allocated to a bound-to-fail task) and for the client
|
||||
(they get some meaningful information and don't need to wait long for it).
|
||||
|
||||
That will be achieved by wrapping into a custom plugin two main features:
|
||||
|
||||
1. the mechanism for authenticating with the provider
|
||||
[decorating](../Reference/Decorators.md) the `fastify` object with the
|
||||
authentication key (`magicKey` from here onward)
|
||||
1. the mechanism for denying requests that would, otherwise, fail
|
||||
|
||||
### Hands-on
|
||||
|
||||
For this sample solution we'll be using the following:
|
||||
|
||||
- `node.js v16.14.2`
|
||||
- `npm 8.5.0`
|
||||
- `fastify 4.0.0-rc.1`
|
||||
- `fastify-plugin 3.0.1`
|
||||
- `undici 5.0.0`
|
||||
|
||||
Say we have the following base server set up at first:
|
||||
|
||||
```js
|
||||
const Fastify = require('fastify')
|
||||
|
||||
const provider = require('./provider')
|
||||
|
||||
const server = Fastify({ logger: true })
|
||||
const USUAL_WAIT_TIME_MS = 5000
|
||||
|
||||
server.get('/ping', function (request, reply) {
|
||||
reply.send({ error: false, ready: request.server.magicKey !== null })
|
||||
})
|
||||
|
||||
server.post('/webhook', function (request, reply) {
|
||||
// It's good practice to validate webhook requests come from
|
||||
// who you expect. This is skipped in this sample for the sake
|
||||
// of simplicity
|
||||
|
||||
const { magicKey } = request.body
|
||||
request.server.magicKey = magicKey
|
||||
request.log.info('Ready for customer requests!')
|
||||
|
||||
reply.send({ error: false })
|
||||
})
|
||||
|
||||
server.get('/v1*', async function (request, reply) {
|
||||
try {
|
||||
const data = await provider.fetchSensitiveData(request.server.magicKey)
|
||||
return { customer: true, error: false }
|
||||
} catch (error) {
|
||||
request.log.error({
|
||||
error,
|
||||
message: 'Failed at fetching sensitive data from provider',
|
||||
})
|
||||
|
||||
reply.statusCode = 500
|
||||
return { customer: null, error: true }
|
||||
}
|
||||
})
|
||||
|
||||
server.decorate('magicKey')
|
||||
|
||||
server.listen({ port: '1234' }, () => {
|
||||
provider.thirdPartyMagicKeyGenerator(USUAL_WAIT_TIME_MS)
|
||||
.catch((error) => {
|
||||
server.log.error({
|
||||
error,
|
||||
message: 'Got an error while trying to get the magic key!'
|
||||
})
|
||||
|
||||
// Since we won't be able to serve requests, might as well wrap
|
||||
// things up
|
||||
server.close(() => process.exit(1))
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Our code is simply setting up a Fastify server with a few routes:
|
||||
|
||||
- a `/ping` route that specifies whether the service is ready or not to serve
|
||||
requests by checking if the `magicKey` has been set up
|
||||
- a `/webhook` endpoint for our provider to reach back to us when they're ready
|
||||
to share the `magicKey`. The `magicKey` is, then, saved into the previously set
|
||||
decorator on the `fastify` object
|
||||
- a catchall `/v1*` route to simulate what would have been customer-initiated
|
||||
requests. These requests rely on us having a valid `magicKey`
|
||||
|
||||
The `provider.js` file, simulating actions of an external provider, is as
|
||||
follows:
|
||||
|
||||
```js
|
||||
const { fetch } = require('undici')
|
||||
const { setTimeout } = require('node:timers/promises')
|
||||
|
||||
const MAGIC_KEY = '12345'
|
||||
|
||||
const delay = setTimeout
|
||||
|
||||
exports.thirdPartyMagicKeyGenerator = async (ms) => {
|
||||
// Simulate processing delay
|
||||
await delay(ms)
|
||||
|
||||
// Simulate webhook request to our server
|
||||
const { status } = await fetch(
|
||||
'http://localhost:1234/webhook',
|
||||
{
|
||||
body: JSON.stringify({ magicKey: MAGIC_KEY }),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (status !== 200) {
|
||||
throw new Error('Failed to fetch magic key')
|
||||
}
|
||||
}
|
||||
|
||||
exports.fetchSensitiveData = async (key) => {
|
||||
// Simulate processing delay
|
||||
await delay(700)
|
||||
const data = { sensitive: true }
|
||||
|
||||
if (key === MAGIC_KEY) {
|
||||
return data
|
||||
}
|
||||
|
||||
throw new Error('Invalid key')
|
||||
}
|
||||
```
|
||||
|
||||
The most important snippet here is the `thirdPartyMagicKeyGenerator` function,
|
||||
which will wait for 5 seconds and, then, make the POST request to our `/webhook`
|
||||
endpoint.
|
||||
|
||||
When our server spins up we start listening to new connections without having
|
||||
our `magicKey` set up. Until we receive the webhook request from our external
|
||||
provider (in this example we're simulating a 5 second delay) all our requests
|
||||
under the `/v1*` path (customer requests) will fail. Worse than that: they'll
|
||||
fail after we've reached out to our provider with an invalid key and got an
|
||||
error from them. That wasted time and resources for us and our customers.
|
||||
Depending on the kind of application we're running and on the request rate we're
|
||||
expecting this delay is not acceptable or, at least, very annoying.
|
||||
|
||||
Of course, that could be simply mitigated by checking whether or not the
|
||||
`magicKey` has been set up before hitting the provider in the `/v1*` handler.
|
||||
Sure, but that would lead to bloat in the code. And imagine we have dozens of
|
||||
different routes, with different controllers, that require that key. Should we
|
||||
repeatedly add that check to all of them? That's error-prone and there are more
|
||||
elegant solutions.
|
||||
|
||||
What we'll do to improve this setup overall is create a
|
||||
[`Plugin`](../Reference/Plugins.md) that'll be solely responsible for making
|
||||
sure we both:
|
||||
|
||||
- do not accept requests that would otherwise fail until we're ready for them
|
||||
- make sure we reach out to our provider as soon as possible
|
||||
|
||||
This way we'll make sure all our setup regarding this specific _business rule_
|
||||
is placed on a single entity, instead of scattered all across our code base.
|
||||
|
||||
With the changes to improve this behavior, the code will look like this:
|
||||
|
||||
##### index.js
|
||||
|
||||
```js
|
||||
const Fastify = require('fastify')
|
||||
|
||||
const customerRoutes = require('./customer-routes')
|
||||
const { setup, delay } = require('./delay-incoming-requests')
|
||||
|
||||
const server = new Fastify({ logger: true })
|
||||
|
||||
server.register(setup)
|
||||
|
||||
// Non-blocked URL
|
||||
server.get('/ping', function (request, reply) {
|
||||
reply.send({ error: false, ready: request.server.magicKey !== null })
|
||||
})
|
||||
|
||||
// Webhook to handle the provider's response - also non-blocked
|
||||
server.post('/webhook', function (request, reply) {
|
||||
// It's good practice to validate webhook requests really come from
|
||||
// whoever you expect. This is skipped in this sample for the sake
|
||||
// of simplicity
|
||||
|
||||
const { magicKey } = request.body
|
||||
request.server.magicKey = magicKey
|
||||
request.log.info('Ready for customer requests!')
|
||||
|
||||
reply.send({ error: false })
|
||||
})
|
||||
|
||||
// Blocked URLs
|
||||
// Mind we're building a new plugin by calling the `delay` factory with our
|
||||
// customerRoutes plugin
|
||||
server.register(delay(customerRoutes), { prefix: '/v1' })
|
||||
|
||||
server.listen({ port: '1234' })
|
||||
```
|
||||
|
||||
##### provider.js
|
||||
|
||||
```js
|
||||
const { fetch } = require('undici')
|
||||
const { setTimeout } = require('node:timers/promises')
|
||||
|
||||
const MAGIC_KEY = '12345'
|
||||
|
||||
const delay = setTimeout
|
||||
|
||||
exports.thirdPartyMagicKeyGenerator = async (ms) => {
|
||||
// Simulate processing delay
|
||||
await delay(ms)
|
||||
|
||||
// Simulate webhook request to our server
|
||||
const { status } = await fetch(
|
||||
'http://localhost:1234/webhook',
|
||||
{
|
||||
body: JSON.stringify({ magicKey: MAGIC_KEY }),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (status !== 200) {
|
||||
throw new Error('Failed to fetch magic key')
|
||||
}
|
||||
}
|
||||
|
||||
exports.fetchSensitiveData = async (key) => {
|
||||
// Simulate processing delay
|
||||
await delay(700)
|
||||
const data = { sensitive: true }
|
||||
|
||||
if (key === MAGIC_KEY) {
|
||||
return data
|
||||
}
|
||||
|
||||
throw new Error('Invalid key')
|
||||
}
|
||||
```
|
||||
|
||||
##### delay-incoming-requests.js
|
||||
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
|
||||
const provider = require('./provider')
|
||||
|
||||
const USUAL_WAIT_TIME_MS = 5000
|
||||
|
||||
async function setup(fastify) {
|
||||
// As soon as we're listening for requests, let's work our magic
|
||||
fastify.server.on('listening', doMagic)
|
||||
|
||||
// Set up the placeholder for the magicKey
|
||||
fastify.decorate('magicKey')
|
||||
|
||||
// Our magic -- important to make sure errors are handled. Beware of async
|
||||
// functions outside `try/catch` blocks
|
||||
// If an error is thrown at this point and not captured it'll crash the
|
||||
// application
|
||||
function doMagic() {
|
||||
fastify.log.info('Doing magic!')
|
||||
|
||||
provider.thirdPartyMagicKeyGenerator(USUAL_WAIT_TIME_MS)
|
||||
.catch((error) => {
|
||||
fastify.log.error({
|
||||
error,
|
||||
message: 'Got an error while trying to get the magic key!'
|
||||
})
|
||||
|
||||
// Since we won't be able to serve requests, might as well wrap
|
||||
// things up
|
||||
fastify.close(() => process.exit(1))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const delay = (routes) =>
|
||||
function (fastify, opts, done) {
|
||||
// Make sure customer requests won't be accepted if the magicKey is not
|
||||
// available
|
||||
fastify.addHook('onRequest', function (request, reply, next) {
|
||||
if (!request.server.magicKey) {
|
||||
reply.statusCode = 503
|
||||
reply.header('Retry-After', USUAL_WAIT_TIME_MS)
|
||||
reply.send({ error: true, retryInMs: USUAL_WAIT_TIME_MS })
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
// Register to-be-delayed routes
|
||||
fastify.register(routes, opts)
|
||||
|
||||
done()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setup: fp(setup),
|
||||
delay,
|
||||
}
|
||||
```
|
||||
|
||||
##### customer-routes.js
|
||||
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
|
||||
const provider = require('./provider')
|
||||
|
||||
module.exports = fp(async function (fastify) {
|
||||
fastify.get('*', async function (request ,reply) {
|
||||
try {
|
||||
const data = await provider.fetchSensitiveData(request.server.magicKey)
|
||||
return { customer: true, error: false }
|
||||
} catch (error) {
|
||||
request.log.error({
|
||||
error,
|
||||
message: 'Failed at fetching sensitive data from provider',
|
||||
})
|
||||
|
||||
reply.statusCode = 500
|
||||
return { customer: null, error: true }
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
There is a very specific change on the previously existing files that is worth
|
||||
mentioning: Beforehand we were using the `server.listen` callback to start the
|
||||
authentication process with the external provider and we were decorating the
|
||||
`server` object right before initializing the server. That was bloating our
|
||||
server initialization setup with unnecessary code and didn't have much to do
|
||||
with starting the Fastify server. It was a business logic that didn't have its
|
||||
specific place in the code base.
|
||||
|
||||
Now we've implemented the `delayIncomingRequests` plugin in the
|
||||
`delay-incoming-requests.js` file. That's, in truth, a module split into two
|
||||
different plugins that will build up to a single use-case. That's the brains of
|
||||
our operation. Let's walk through what the plugins do:
|
||||
|
||||
##### setup
|
||||
|
||||
The `setup` plugin is responsible for making sure we reach out to our provider
|
||||
asap and store the `magicKey` somewhere available to all our handlers.
|
||||
|
||||
```js
|
||||
fastify.server.on('listening', doMagic)
|
||||
```
|
||||
|
||||
As soon as the server starts listening (very similar behavior to adding a piece
|
||||
of code to the `server.listen`'s callback function) a `listening` event is
|
||||
emitted (for more info refer to
|
||||
https://nodejs.org/api/net.html#event-listening). We use that to reach out to
|
||||
our provider as soon as possible, with the `doMagic` function.
|
||||
|
||||
```js
|
||||
fastify.decorate('magicKey')
|
||||
```
|
||||
|
||||
The `magicKey` decoration is also part of the plugin now. We initialize it with
|
||||
a placeholder, waiting for the valid value to be retrieved.
|
||||
|
||||
##### delay
|
||||
|
||||
`delay` is not a plugin itself. It's actually a plugin *factory*. It expects a
|
||||
Fastify plugin with `routes` and exports the actual plugin that'll handle
|
||||
enveloping those routes with an `onRequest` hook that will make sure no requests
|
||||
are handled until we're ready for them.
|
||||
|
||||
```js
|
||||
const delay = (routes) =>
|
||||
function (fastify, opts, done) {
|
||||
// Make sure customer requests won't be accepted if the magicKey is not
|
||||
// available
|
||||
fastify.addHook('onRequest', function (request, reply, next) {
|
||||
if (!request.server.magicKey) {
|
||||
reply.statusCode = 503
|
||||
reply.header('Retry-After', USUAL_WAIT_TIME_MS)
|
||||
reply.send({ error: true, retryInMs: USUAL_WAIT_TIME_MS })
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
// Register to-be-delayed routes
|
||||
fastify.register(routes, opts)
|
||||
|
||||
done()
|
||||
}
|
||||
```
|
||||
|
||||
Instead of updating every single controller that might use the `magicKey`, we
|
||||
simply make sure that no route that's related to customer requests will be
|
||||
served until we have everything ready. And there's more: we fail **FAST** and
|
||||
have the possibility of giving the customer meaningful information, like how
|
||||
long they should wait before retrying the request. Going even further, by
|
||||
issuing a [`503` status
|
||||
code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) we're
|
||||
signaling to our infrastructure components (namely load balancers) that we're
|
||||
still not ready to take incoming requests and they should redirect traffic to
|
||||
other instances, if available. Additionally, we are providing a `Retry-After`
|
||||
header with the time in milliseconds the client should wait before retrying.
|
||||
|
||||
It's noteworthy that we didn't use the `fastify-plugin` wrapper in the `delay`
|
||||
factory. That's because we wanted the `onRequest` hook to only be set within
|
||||
that specific scope and not to the scope that called it (in our case, the main
|
||||
`server` object defined in `index.js`). `fastify-plugin` sets the
|
||||
`skip-override` hidden property, which has a practical effect of making whatever
|
||||
changes we make to our `fastify` object available to the upper scope. That's
|
||||
also why we used it with the `customerRoutes` plugin: we wanted those routes to
|
||||
be available to its calling scope, the `delay` plugin. For more info on that
|
||||
subject refer to [Plugins](../Reference/Plugins.md#handle-the-scope).
|
||||
|
||||
Let's see how that behaves in action. If we fired our server up with `node
|
||||
index.js` and made a few requests to test things out. These were the logs we'd
|
||||
see (some bloat was removed to ease things up):
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
```sh
|
||||
{"time":1650063793316,"msg":"Doing magic!"}
|
||||
{"time":1650063793316,"msg":"Server listening at http://127.0.0.1:1234"}
|
||||
{"time":1650063795030,"reqId":"req-1","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51928},"msg":"incoming request"}
|
||||
{"time":1650063795033,"reqId":"req-1","res":{"statusCode":503},"responseTime":2.5721680000424385,"msg":"request completed"}
|
||||
{"time":1650063796248,"reqId":"req-2","req":{"method":"GET","url":"/ping","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51930},"msg":"incoming request"}
|
||||
{"time":1650063796248,"reqId":"req-2","res":{"statusCode":200},"responseTime":0.4802369996905327,"msg":"request completed"}
|
||||
{"time":1650063798377,"reqId":"req-3","req":{"method":"POST","url":"/webhook","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51932},"msg":"incoming request"}
|
||||
{"time":1650063798379,"reqId":"req-3","msg":"Ready for customer requests!"}
|
||||
{"time":1650063798379,"reqId":"req-3","res":{"statusCode":200},"responseTime":1.3567829988896847,"msg":"request completed"}
|
||||
{"time":1650063799858,"reqId":"req-4","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51934},"msg":"incoming request"}
|
||||
{"time":1650063800561,"reqId":"req-4","res":{"statusCode":200},"responseTime":702.4662979990244,"msg":"request completed"}
|
||||
```
|
||||
<!-- markdownlint-enable -->
|
||||
|
||||
Let's focus on a few parts:
|
||||
|
||||
```sh
|
||||
{"time":1650063793316,"msg":"Doing magic!"}
|
||||
{"time":1650063793316,"msg":"Server listening at http://127.0.0.1:1234"}
|
||||
```
|
||||
|
||||
These are the initial logs we'd see as soon as the server started. We reach out
|
||||
to the external provider as early as possible within a valid time window (we
|
||||
couldn't do that before the server was ready to receive connections).
|
||||
|
||||
While the server is still not ready, a few requests are attempted:
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
```sh
|
||||
{"time":1650063795030,"reqId":"req-1","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51928},"msg":"incoming request"}
|
||||
{"time":1650063795033,"reqId":"req-1","res":{"statusCode":503},"responseTime":2.5721680000424385,"msg":"request completed"}
|
||||
{"time":1650063796248,"reqId":"req-2","req":{"method":"GET","url":"/ping","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51930},"msg":"incoming request"}
|
||||
{"time":1650063796248,"reqId":"req-2","res":{"statusCode":200},"responseTime":0.4802369996905327,"msg":"request completed"}
|
||||
```
|
||||
<!-- markdownlint-enable -->
|
||||
|
||||
The first one (`req-1`) was a `GET /v1`, that failed (**FAST** - `responseTime`
|
||||
is in `ms`) with our `503` status code and the meaningful information in the
|
||||
response. Below is the response for that request:
|
||||
|
||||
```sh
|
||||
HTTP/1.1 503 Service Unavailable
|
||||
Connection: keep-alive
|
||||
Content-Length: 31
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Fri, 15 Apr 2022 23:03:15 GMT
|
||||
Keep-Alive: timeout=5
|
||||
Retry-After: 5000
|
||||
|
||||
{
|
||||
"error": true,
|
||||
"retryInMs": 5000
|
||||
}
|
||||
```
|
||||
|
||||
Then we attempted a new request (`req-2`), which was a `GET /ping`. As expected,
|
||||
since that was not one of the requests we asked our plugin to filter, it
|
||||
succeeded. That could also be used as a means of informing an interested party
|
||||
whether or not we were ready to serve requests with the `ready` field. Although
|
||||
`/ping` is more commonly associated with *liveness* checks and that would be
|
||||
the responsibility of a *readiness* check. The curious reader can get more info
|
||||
on these terms in the article
|
||||
["Kubernetes best practices: Setting up health checks with readiness and liveness probes"](
|
||||
https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-setting-up-health-checks-with-readiness-and-liveness-probes).
|
||||
|
||||
Below is the response to that request:
|
||||
|
||||
```sh
|
||||
HTTP/1.1 200 OK
|
||||
Connection: keep-alive
|
||||
Content-Length: 29
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Fri, 15 Apr 2022 23:03:16 GMT
|
||||
Keep-Alive: timeout=5
|
||||
|
||||
{
|
||||
"error": false,
|
||||
"ready": false
|
||||
}
|
||||
```
|
||||
|
||||
After that, there were more interesting log messages:
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
```sh
|
||||
{"time":1650063798377,"reqId":"req-3","req":{"method":"POST","url":"/webhook","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51932},"msg":"incoming request"}
|
||||
{"time":1650063798379,"reqId":"req-3","msg":"Ready for customer requests!"}
|
||||
{"time":1650063798379,"reqId":"req-3","res":{"statusCode":200},"responseTime":1.3567829988896847,"msg":"request completed"}
|
||||
```
|
||||
<!-- markdownlint-enable -->
|
||||
|
||||
This time it was our simulated external provider hitting us to let us know
|
||||
authentication had gone well and telling us what our `magicKey` was. We saved
|
||||
that into our `magicKey` decorator and celebrated with a log message saying we
|
||||
were now ready for customers to hit us!
|
||||
|
||||
<!-- markdownlint-disable -->
|
||||
```sh
|
||||
{"time":1650063799858,"reqId":"req-4","req":{"method":"GET","url":"/v1","hostname":"localhost:1234","remoteAddress":"127.0.0.1","remotePort":51934},"msg":"incoming request"}
|
||||
{"time":1650063800561,"reqId":"req-4","res":{"statusCode":200},"responseTime":702.4662979990244,"msg":"request completed"}
|
||||
```
|
||||
<!-- markdownlint-enable -->
|
||||
|
||||
Finally, a final `GET /v1` request was made and, this time, it succeeded. Its
|
||||
response was the following:
|
||||
|
||||
```sh
|
||||
HTTP/1.1 200 OK
|
||||
Connection: keep-alive
|
||||
Content-Length: 31
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Date: Fri, 15 Apr 2022 23:03:20 GMT
|
||||
Keep-Alive: timeout=5
|
||||
|
||||
{
|
||||
"customer": true,
|
||||
"error": false
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Specifics of the implementation will vary from one problem to another, but the
|
||||
main goal of this guide was to show a very specific use case of an issue that
|
||||
could be solved within Fastify's ecosystem.
|
||||
|
||||
This guide is a tutorial on the use of plugins, decorators, and hooks to solve
|
||||
the problem of delaying serving specific requests on our application. It's not
|
||||
production-ready, as it keeps local state (the `magicKey`) and it's not
|
||||
horizontally scalable (we don't want to flood our provider, right?). One way of
|
||||
improving it would be storing the `magicKey` somewhere else (perhaps a cache
|
||||
database?).
|
||||
|
||||
The keywords here were [Decorators](../Reference/Decorators.md),
|
||||
[Hooks](../Reference/Hooks.md), and [Plugins](../Reference/Plugins.md).
|
||||
Combining what Fastify has to offer can lead to very ingenious and creative
|
||||
solutions to a wide variety of problems. Let's be creative! :)
|
||||
172
node_modules/fastify/docs/Guides/Detecting-When-Clients-Abort.md
generated
vendored
Normal file
172
node_modules/fastify/docs/Guides/Detecting-When-Clients-Abort.md
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
# Detecting When Clients Abort
|
||||
|
||||
## Introduction
|
||||
|
||||
Fastify provides request events to trigger at certain points in a request's
|
||||
lifecycle. However, there isn't a built-in mechanism to
|
||||
detect unintentional client disconnection scenarios such as when the client's
|
||||
internet connection is interrupted. This guide covers methods to detect if
|
||||
and when a client intentionally aborts a request.
|
||||
|
||||
Keep in mind, Fastify's `clientErrorHandler` is not designed to detect when a
|
||||
client aborts a request. This works in the same way as the standard Node HTTP
|
||||
module, which triggers the `clientError` event when there is a bad request or
|
||||
exceedingly large header data. When a client aborts a request, there is no
|
||||
error on the socket and the `clientErrorHandler` will not be triggered.
|
||||
|
||||
## Solution
|
||||
|
||||
### Overview
|
||||
|
||||
The proposed solution is a possible way of detecting when a client
|
||||
intentionally aborts a request, such as when a browser is closed or the HTTP
|
||||
request is aborted from your client application. If there is an error in your
|
||||
application code that results in the server crashing, you may require
|
||||
additional logic to avoid a false abort detection.
|
||||
|
||||
The goal here is to detect when a client intentionally aborts a connection
|
||||
so your application logic can proceed accordingly. This can be useful for
|
||||
logging purposes or halting business logic.
|
||||
|
||||
### Hands-on
|
||||
|
||||
Say we have the following base server set up:
|
||||
|
||||
```js
|
||||
import Fastify from 'fastify';
|
||||
|
||||
const sleep = async (time) => {
|
||||
return await new Promise(resolve => setTimeout(resolve, time || 1000));
|
||||
}
|
||||
|
||||
const app = Fastify({
|
||||
logger: {
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
app.addHook('onRequest', async (request, reply) => {
|
||||
request.raw.on('close', () => {
|
||||
if (request.raw.aborted) {
|
||||
app.log.info('request closed')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/', async (request, reply) => {
|
||||
await sleep(3000)
|
||||
reply.code(200).send({ ok: true })
|
||||
})
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
await app.listen({ port: 3000 })
|
||||
} catch (err) {
|
||||
app.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
start()
|
||||
```
|
||||
|
||||
Our code is setting up a Fastify server which includes the following
|
||||
functionality:
|
||||
|
||||
- Accepting requests at http://localhost:3000, with a 3 second delayed response
|
||||
of `{ ok: true }`.
|
||||
- An onRequest hook that triggers when every request is received.
|
||||
- Logic that triggers in the hook when the request is closed.
|
||||
- Logging that occurs when the closed request property `aborted` is true.
|
||||
|
||||
Whilst the `aborted` property has been deprecated, `destroyed` is not a
|
||||
suitable replacement as the
|
||||
[Node.js documentation suggests](https://nodejs.org/api/http.html#requestaborted).
|
||||
A request can be `destroyed` for various reasons, such as when the server closes
|
||||
the connection. The `aborted` property is still the most reliable way to detect
|
||||
when a client intentionally aborts a request.
|
||||
|
||||
You can also perform this logic outside of a hook, directly in a specific route.
|
||||
|
||||
```js
|
||||
app.get('/', async (request, reply) => {
|
||||
request.raw.on('close', () => {
|
||||
if (request.raw.aborted) {
|
||||
app.log.info('request closed')
|
||||
}
|
||||
})
|
||||
await sleep(3000)
|
||||
reply.code(200).send({ ok: true })
|
||||
})
|
||||
```
|
||||
|
||||
At any point in your business logic, you can check if the request has been
|
||||
aborted and perform alternative actions.
|
||||
|
||||
```js
|
||||
app.get('/', async (request, reply) => {
|
||||
await sleep(3000)
|
||||
if (request.raw.aborted) {
|
||||
// do something here
|
||||
}
|
||||
await sleep(3000)
|
||||
reply.code(200).send({ ok: true })
|
||||
})
|
||||
```
|
||||
|
||||
A benefit to adding this in your application code is that you can log Fastify
|
||||
details such as the reqId, which may be unavailable in lower-level code that
|
||||
only has access to the raw request information.
|
||||
|
||||
### Testing
|
||||
|
||||
To test this functionality you can use an app like Postman and cancel your
|
||||
request within 3 seconds. Alternatively, you can use Node to send an HTTP
|
||||
request with logic to abort the request before 3 seconds. Example:
|
||||
|
||||
```js
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000', { signal });
|
||||
const body = await response.text();
|
||||
console.log(body);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
|
||||
setTimeout(() => {
|
||||
controller.abort()
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
With either approach, you should see the Fastify log appear at the moment the
|
||||
request is aborted.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Specifics of the implementation will vary from one problem to another, but the
|
||||
main goal of this guide was to show a very specific use case of an issue that
|
||||
could be solved within Fastify's ecosystem.
|
||||
|
||||
You can listen to the request close event and determine if the request was
|
||||
aborted or if it was successfully delivered. You can implement this solution
|
||||
in an onRequest hook or directly in an individual route.
|
||||
|
||||
This approach will not trigger in the event of internet disruption, and such
|
||||
detection would require additional business logic. If you have flawed backend
|
||||
application logic that results in a server crash, then you could trigger a
|
||||
false detection. The `clientErrorHandler`, either by default or with custom
|
||||
logic, is not intended to handle this scenario and will not trigger when the
|
||||
client aborts a request.
|
||||
765
node_modules/fastify/docs/Guides/Ecosystem.md
generated
vendored
Normal file
765
node_modules/fastify/docs/Guides/Ecosystem.md
generated
vendored
Normal file
@@ -0,0 +1,765 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Ecosystem
|
||||
|
||||
Plugins maintained by the Fastify team are listed under [Core](#core) while
|
||||
plugins maintained by the community are listed in the [Community](#community)
|
||||
section.
|
||||
|
||||
#### [Core](#core)
|
||||
|
||||
- [`@fastify/accepts`](https://github.com/fastify/fastify-accepts) to have
|
||||
[accepts](https://www.npmjs.com/package/accepts) in your request object.
|
||||
- [`@fastify/accepts-serializer`](https://github.com/fastify/fastify-accepts-serializer)
|
||||
to serialize to output according to the `Accept` header.
|
||||
- [`@fastify/auth`](https://github.com/fastify/fastify-auth) Run multiple auth
|
||||
functions in Fastify.
|
||||
- [`@fastify/autoload`](https://github.com/fastify/fastify-autoload) Require all
|
||||
plugins in a directory.
|
||||
- [`@fastify/awilix`](https://github.com/fastify/fastify-awilix) Dependency
|
||||
injection support for Fastify, based on
|
||||
[awilix](https://github.com/jeffijoe/awilix).
|
||||
- [`@fastify/aws-lambda`](https://github.com/fastify/aws-lambda-fastify) allows
|
||||
you to easily build serverless web applications/services and RESTful APIs
|
||||
using Fastify on top of AWS Lambda and Amazon API Gateway.
|
||||
- [`@fastify/basic-auth`](https://github.com/fastify/fastify-basic-auth) Basic
|
||||
auth plugin for Fastify.
|
||||
- [`@fastify/bearer-auth`](https://github.com/fastify/fastify-bearer-auth)
|
||||
Bearer auth plugin for Fastify.
|
||||
- [`@fastify/caching`](https://github.com/fastify/fastify-caching) General
|
||||
server-side cache and ETag support.
|
||||
- [`@fastify/circuit-breaker`](https://github.com/fastify/fastify-circuit-breaker)
|
||||
A low overhead circuit breaker for your routes.
|
||||
- [`@fastify/compress`](https://github.com/fastify/fastify-compress) Fastify
|
||||
compression utils.
|
||||
- [`@fastify/cookie`](https://github.com/fastify/fastify-cookie) Parse and set
|
||||
cookie headers.
|
||||
- [`@fastify/cors`](https://github.com/fastify/fastify-cors) Enables the use of
|
||||
CORS in a Fastify application.
|
||||
- [`@fastify/csrf-protection`](https://github.com/fastify/csrf-protection) A
|
||||
plugin for adding
|
||||
[CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection to
|
||||
Fastify.
|
||||
- [`@fastify/elasticsearch`](https://github.com/fastify/fastify-elasticsearch)
|
||||
Plugin to share the same ES client.
|
||||
- [`@fastify/env`](https://github.com/fastify/fastify-env) Load and check
|
||||
configuration.
|
||||
- [`@fastify/etag`](https://github.com/fastify/fastify-etag) Automatically
|
||||
generate ETags for HTTP responses.
|
||||
- [`@fastify/express`](https://github.com/fastify/fastify-express) Express
|
||||
compatibility layer for Fastify.
|
||||
- [`@fastify/flash`](https://github.com/fastify/fastify-flash) Set and get flash
|
||||
messages using the session.
|
||||
- [`@fastify/formbody`](https://github.com/fastify/fastify-formbody) Plugin to
|
||||
parse x-www-form-urlencoded bodies.
|
||||
- [`@fastify/funky`](https://github.com/fastify/fastify-funky) Makes functional
|
||||
programming in Fastify more convenient. Adds support for Fastify routes
|
||||
returning functional structures, such as Either, Task or plain parameterless
|
||||
function.
|
||||
- [`@fastify/helmet`](https://github.com/fastify/fastify-helmet) Important
|
||||
security headers for Fastify.
|
||||
- [`@fastify/hotwire`](https://github.com/fastify/fastify-hotwire) Use the
|
||||
Hotwire pattern with Fastify.
|
||||
- [`@fastify/http-proxy`](https://github.com/fastify/fastify-http-proxy) Proxy
|
||||
your HTTP requests to another server, with hooks.
|
||||
- [`@fastify/jwt`](https://github.com/fastify/fastify-jwt) JWT utils for
|
||||
Fastify, internally uses [fast-jwt](https://github.com/nearform/fast-jwt).
|
||||
- [`@fastify/kafka`](https://github.com/fastify/fastify-kafka) Plugin to interact
|
||||
with Apache Kafka.
|
||||
- [`@fastify/leveldb`](https://github.com/fastify/fastify-leveldb) Plugin to
|
||||
share a common LevelDB connection across Fastify.
|
||||
- [`@fastify/middie`](https://github.com/fastify/middie) Middleware engine for
|
||||
Fastify.
|
||||
- [`@fastify/mongodb`](https://github.com/fastify/fastify-mongodb) Fastify
|
||||
MongoDB connection plugin, with which you can share the same MongoDB
|
||||
connection pool across every part of your server.
|
||||
- [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart
|
||||
support for Fastify.
|
||||
- [`@fastify/mysql`](https://github.com/fastify/fastify-mysql) Fastify MySQL
|
||||
connection plugin.
|
||||
- [`@fastify/nextjs`](https://github.com/fastify/fastify-nextjs) React
|
||||
server-side rendering support for Fastify with
|
||||
[Next](https://github.com/zeit/next.js/).
|
||||
- [`@fastify/oauth2`](https://github.com/fastify/fastify-oauth2) Wrap around
|
||||
[`simple-oauth2`](https://github.com/lelylan/simple-oauth2).
|
||||
- [`@fastify/one-line-logger`](https://github.com/fastify/one-line-logger) Formats
|
||||
Fastify's logs into a nice one-line message.
|
||||
- [`@fastify/otel`](https://github.com/fastify/otel) OpenTelemetry
|
||||
instrumentation library.
|
||||
- [`@fastify/passport`](https://github.com/fastify/fastify-passport) Use Passport
|
||||
strategies to authenticate requests and protect route.
|
||||
- [`@fastify/postgres`](https://github.com/fastify/fastify-postgres) Fastify
|
||||
PostgreSQL connection plugin, with this you can share the same PostgreSQL
|
||||
connection pool in every part of your server.
|
||||
- [`@fastify/rate-limit`](https://github.com/fastify/fastify-rate-limit) A low
|
||||
overhead rate limiter for your routes.
|
||||
- [`@fastify/redis`](https://github.com/fastify/fastify-redis) Fastify Redis
|
||||
connection plugin, with which you can share the same Redis connection across
|
||||
every part of your server.
|
||||
- [`@fastify/reply-from`](https://github.com/fastify/fastify-reply-from) Plugin
|
||||
to forward the current HTTP request to another server.
|
||||
- [`@fastify/request-context`](https://github.com/fastify/fastify-request-context)
|
||||
Request-scoped storage, based on
|
||||
[AsyncLocalStorage](https://nodejs.org/api/async_hooks.html#async_hooks_class_asynclocalstorage)
|
||||
(with fallback to [cls-hooked](https://github.com/Jeff-Lewis/cls-hooked)),
|
||||
providing functionality similar to thread-local storages.
|
||||
- [`@fastify/response-validation`](https://github.com/fastify/fastify-response-validation)
|
||||
A simple plugin that enables response validation for Fastify.
|
||||
- [`@fastify/routes`](https://github.com/fastify/fastify-routes) Plugin that
|
||||
provides a `Map` of routes.
|
||||
- [`@fastify/routes-stats`](https://github.com/fastify/fastify-routes-stats)
|
||||
Provide stats for routes using `node:perf_hooks`.
|
||||
- [`@fastify/schedule`](https://github.com/fastify/fastify-schedule) Plugin for
|
||||
scheduling periodic jobs, based on
|
||||
[toad-scheduler](https://github.com/kibertoad/toad-scheduler).
|
||||
- [`@fastify/secure-session`](https://github.com/fastify/fastify-secure-session)
|
||||
Create a secure stateless cookie session for Fastify.
|
||||
- [`@fastify/sensible`](https://github.com/fastify/fastify-sensible) Defaults
|
||||
for Fastify that everyone can agree on. It adds some useful decorators such as
|
||||
HTTP errors and assertions, but also more request and reply methods.
|
||||
- [`@fastify/session`](https://github.com/fastify/session) a session plugin for
|
||||
Fastify.
|
||||
- [`@fastify/static`](https://github.com/fastify/fastify-static) Plugin for
|
||||
serving static files as fast as possible.
|
||||
- [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) Plugin for
|
||||
serving Swagger/OpenAPI documentation for Fastify, supporting dynamic
|
||||
generation.
|
||||
- [`@fastify/swagger-ui`](https://github.com/fastify/fastify-swagger-ui) Plugin
|
||||
for serving Swagger UI.
|
||||
- [`@fastify/throttle`](https://github.com/fastify/fastify-throttle) Plugin for
|
||||
throttling the download speed of a request.
|
||||
- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
|
||||
Fastify
|
||||
[type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
|
||||
for [json-schema-to-ts](https://github.com/ThomasAribart/json-schema-to-ts).
|
||||
- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
|
||||
Fastify
|
||||
[type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
|
||||
for [Typebox](https://github.com/sinclairzx81/typebox).
|
||||
- [`@fastify/under-pressure`](https://github.com/fastify/under-pressure) Measure
|
||||
process load with automatic handling of _"Service Unavailable"_ plugin for
|
||||
Fastify.
|
||||
- [`@fastify/url-data`](https://github.com/fastify/fastify-url-data) Decorate
|
||||
the `Request` object with a method to access raw URL components.
|
||||
- [`@fastify/view`](https://github.com/fastify/point-of-view) Templates
|
||||
rendering (_ejs, pug, handlebars, marko_) plugin support for Fastify.
|
||||
- [`@fastify/vite`](https://github.com/fastify/fastify-vite) Integration with
|
||||
[Vite](https://vitejs.dev/), allows for serving SPA/MPA/SSR Vite applications.
|
||||
- [`@fastify/websocket`](https://github.com/fastify/fastify-websocket) WebSocket
|
||||
support for Fastify. Built upon [ws](https://github.com/websockets/ws).
|
||||
- [`@fastify/zipkin`](https://github.com/fastify/fastify-zipkin) Plugin
|
||||
for Zipkin distributed tracing system.
|
||||
|
||||
#### [Community](#community)
|
||||
|
||||
> ℹ️ Note:
|
||||
> Fastify community plugins are part of the broader community efforts,
|
||||
> and we are thankful for these contributions. However, they are not
|
||||
> maintained by the Fastify team.
|
||||
> Use them at your own discretion.
|
||||
> If you find malicious code, please
|
||||
> [open an issue](https://github.com/fastify/fastify/issues/new/choose) or
|
||||
> submit a PR to remove the plugin from the list.
|
||||
|
||||
- [`@aaroncadillac/crudify-mongo`](https://github.com/aaroncadillac/crudify-mongo)
|
||||
A simple way to add a crud in your fastify project.
|
||||
- [`@applicazza/fastify-nextjs`](https://github.com/applicazza/fastify-nextjs)
|
||||
Alternate Fastify and Next.js integration.
|
||||
- [`@blastorg/fastify-aws-dynamodb-cache`](https://github.com/blastorg/fastify-aws-dynamodb-cache)
|
||||
A plugin to help with caching API responses using AWS DynamoDB.
|
||||
- [`@clerk/fastify`](https://github.com/clerkinc/javascript/tree/main/packages/fastify)
|
||||
Add authentication and user management to your Fastify application with Clerk.
|
||||
- [`@coobaha/typed-fastify`](https://github.com/Coobaha/typed-fastify) Strongly
|
||||
typed routes with a runtime validation using JSON schema generated from types.
|
||||
- [`@dnlup/fastify-doc`](https://github.com/dnlup/fastify-doc) A plugin for
|
||||
sampling process metrics.
|
||||
- [`@dnlup/fastify-traps`](https://github.com/dnlup/fastify-traps) A plugin to
|
||||
close the server gracefully on `SIGINT` and `SIGTERM` signals.
|
||||
- [`@eropple/fastify-openapi3`](https://github.com/eropple/fastify-openapi3) Provides
|
||||
easy, developer-friendly OpenAPI 3.1 specs + doc explorer based on your routes.
|
||||
- [`@ethicdevs/fastify-custom-session`](https://github.com/EthicDevs/fastify-custom-session)
|
||||
A plugin lets you use session and decide only where to load/save from/to. Has
|
||||
great TypeScript support + built-in adapters for common ORMs/databases (Firebase,
|
||||
Prisma Client, Postgres (wip), InMemory) and you can easily make your own adapter!
|
||||
- [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server)
|
||||
A plugin to easily create git server and make one/many Git repositories available
|
||||
for clone/fetch/push through the standard `git` (over http) commands.
|
||||
- [`@exortek/fastify-mongo-sanitize`](https://github.com/ExorTek/fastify-mongo-sanitize)
|
||||
A Fastify plugin that protects against No(n)SQL injection by sanitizing data.
|
||||
- [`@exortek/remix-fastify`](https://github.com/ExorTek/remix-fastify)
|
||||
Fastify plugin for Remix.
|
||||
- [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id)
|
||||
Fastify Request ID Plugin
|
||||
- [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner)
|
||||
Fastify typeorm QueryRunner plugin
|
||||
- [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server)
|
||||
Tiny (~5k), Fast, KISS, and dependency-free Node.js library to make your
|
||||
Fastify API graceful.
|
||||
- [`@h4ad/serverless-adapter`](https://github.com/H4ad/serverless-adapter)
|
||||
Run REST APIs and other web applications using your existing Node.js
|
||||
application framework (Express, Koa, Hapi and Fastify), on top of AWS Lambda,
|
||||
Huawei and many other clouds.
|
||||
- [`@hey-api/openapi-ts`](https://heyapi.dev/openapi-ts/plugins/fastify)
|
||||
The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more.
|
||||
- [`@immobiliarelabs/fastify-metrics`](https://github.com/immobiliare/fastify-metrics)
|
||||
Minimalistic and opinionated plugin that collects usage/process metrics and
|
||||
dispatches to [statsd](https://github.com/statsd/statsd).
|
||||
- [`@inaiat/fastify-papr`](https://github.com/inaiat/fastify-papr)
|
||||
A plugin to integrate [Papr](https://github.com/plexinc/papr),
|
||||
the MongoDB ORM for TypeScript & MongoDB, with Fastify.
|
||||
- [`@jerome1337/fastify-enforce-routes-pattern`](https://github.com/Jerome1337/fastify-enforce-routes-pattern)
|
||||
A Fastify plugin that enforces naming pattern for routes path.
|
||||
- [`@joggr/fastify-prisma`](https://github.com/joggrdocs/fastify-prisma)
|
||||
A plugin for accessing an instantiated PrismaClient on your server.
|
||||
- [`@mgcrea/fastify-graceful-exit`](https://github.com/mgcrea/fastify-graceful-exit)
|
||||
A plugin to close the server gracefully
|
||||
- [`@mgcrea/fastify-request-logger`](https://github.com/mgcrea/fastify-request-logger)
|
||||
A plugin to enable compact request logging for Fastify
|
||||
- [`@mgcrea/fastify-session`](https://github.com/mgcrea/fastify-session) Session
|
||||
plugin for Fastify that supports both stateless and stateful sessions
|
||||
- [`@mgcrea/fastify-session-redis-store`](https://github.com/mgcrea/fastify-session-redis-store)
|
||||
Redis store for @mgcrea/fastify-session using ioredis
|
||||
- [`@mgcrea/fastify-session-sodium-crypto`](https://github.com/mgcrea/fastify-session-sodium-crypto)
|
||||
Fast sodium-based crypto for @mgcrea/fastify-session
|
||||
- [`@mgcrea/pino-pretty-compact`](https://github.com/mgcrea/pino-pretty-compact)
|
||||
A custom compact pino-base prettifier
|
||||
- [`@pybot/fastify-autoload`](https://github.com/kunal097/fastify-autoload)
|
||||
Plugin to generate routes automatically with valid json content
|
||||
- [`@scalar/fastify-api-reference`](https://github.com/scalar/scalar/tree/main/integrations/fastify)
|
||||
Beautiful OpenAPI/Swagger API references for Fastify
|
||||
- [`@trubavuong/fastify-seaweedfs`](https://github.com/trubavuong/fastify-seaweedfs)
|
||||
SeaweedFS for Fastify
|
||||
- [`apitally`](https://github.com/apitally/apitally-js) Fastify plugin to
|
||||
integrate with [Apitally](https://apitally.io/fastify), an API analytics,
|
||||
logging and monitoring tool.
|
||||
- [`arecibo`](https://github.com/nucleode/arecibo) Fastify ping responder for
|
||||
Kubernetes Liveness and Readiness Probes.
|
||||
- [`aws-xray-sdk-fastify`](https://github.com/aws/aws-xray-sdk-node/tree/master/sdk_contrib/fastify)
|
||||
A Fastify plugin to log requests and subsegments through AWSXray.
|
||||
- [`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.
|
||||
- [`electron-server`](https://github.com/anonrig/electron-server) A plugin for
|
||||
using Fastify without the need of consuming a port on Electron apps.
|
||||
- [`fast-water`](https://github.com/tswayne/fast-water) A Fastify plugin for
|
||||
waterline. Decorates Fastify with waterline models.
|
||||
- [`fastify-204`](https://github.com/Shiva127/fastify-204) Fastify plugin that
|
||||
return 204 status on empty response.
|
||||
- [`fastify-405`](https://github.com/Eomm/fastify-405) Fastify plugin that adds
|
||||
405 HTTP status to your routes
|
||||
- [`fastify-allow`](https://github.com/mattbishop/fastify-allow) Fastify plugin
|
||||
that automatically adds an Allow header to responses with routes. Also sends
|
||||
405 responses for routes that have a handler but not for the request's method.
|
||||
- [`fastify-amqp`](https://github.com/RafaelGSS/fastify-amqp) Fastify AMQP
|
||||
connection plugin, to use with RabbitMQ or another connector. Just a wrapper
|
||||
to [`amqplib`](https://github.com/squaremo/amqp.node).
|
||||
- [`fastify-amqp-async`](https://github.com/kffl/fastify-amqp-async) Fastify
|
||||
AMQP plugin with a Promise-based API provided by
|
||||
[`amqplib-as-promised`](https://github.com/twawszczak/amqplib-as-promised).
|
||||
- [`fastify-angular-universal`](https://github.com/exequiel09/fastify-angular-universal)
|
||||
Angular server-side rendering support using
|
||||
[`@angular/platform-server`](https://github.com/angular/angular/tree/master/packages/platform-server)
|
||||
for Fastify
|
||||
- [`fastify-api-key`](https://github.com/arkerone/fastify-api-key) Fastify
|
||||
plugin to authenticate HTTP requests based on API key and signature
|
||||
- [`fastify-appwrite`](https://github.com/Dev-Manny/fastify-appwrite) Fastify
|
||||
Plugin for interacting with Appwrite server.
|
||||
- [`fastify-asyncforge`](https://github.com/mcollina/fastify-asyncforge) Plugin
|
||||
to access Fastify instance, logger, request and reply from Node.js [Async
|
||||
Local Storage](https://nodejs.org/api/async_context.html#class-asynclocalstorage).
|
||||
- [`fastify-at-mysql`](https://github.com/mateonunez/fastify-at-mysql) Fastify
|
||||
MySQL plugin with auto SQL injection attack prevention.
|
||||
- [`fastify-at-postgres`](https://github.com/mateonunez/fastify-at-postgres) Fastify
|
||||
Postgres plugin with auto SQL injection attack prevention.
|
||||
- [`fastify-auth0-verify`](https://github.com/nearform/fastify-auth0-verify):
|
||||
Auth0 verification plugin for Fastify, internally uses
|
||||
[fastify-jwt](https://npm.im/fastify-jwt) and
|
||||
[jsonwebtoken](https://npm.im/jsonwebtoken).
|
||||
- [`fastify-autocrud`](https://github.com/paranoiasystem/fastify-autocrud)
|
||||
Plugin to auto-generate CRUD routes as fast as possible.
|
||||
- [`fastify-autoroutes`](https://github.com/GiovanniCardamone/fastify-autoroutes)
|
||||
Plugin to scan and load routes based on filesystem path from a custom
|
||||
directory.
|
||||
- [`fastify-aws-sns`](https://github.com/gzileni/fastify-aws-sns) Fastify plugin
|
||||
for AWS Simple Notification Service (AWS SNS) that coordinates and manages
|
||||
the delivery or sending of messages to subscribing endpoints or clients.
|
||||
- [`fastify-aws-timestream`](https://github.com/gzileni/fastify-aws-timestream)
|
||||
Fastify plugin for managing databases, tables, and querying and creating
|
||||
scheduled queries with AWS Timestream.
|
||||
- [`fastify-axios`](https://github.com/davidedantonio/fastify-axios) Plugin to
|
||||
send HTTP requests via [axios](https://github.com/axios/axios).
|
||||
- [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for
|
||||
development servers that require Babel transformations of JavaScript sources.
|
||||
- [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash
|
||||
generator & checker.
|
||||
- [`fastify-better-sqlite3`](https://github.com/punkish/fastify-better-sqlite3)
|
||||
Plugin for better-sqlite3.
|
||||
- [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your
|
||||
routes to the console, so you definitely know which endpoints are available.
|
||||
- [`fastify-bookshelf`](https://github.com/butlerx/fastify-bookshelfjs) Fastify
|
||||
plugin to add [bookshelf.js](https://bookshelfjs.org/) ORM support.
|
||||
- [`fastify-boom`](https://github.com/jeromemacias/fastify-boom) Fastify plugin
|
||||
to add [boom](https://github.com/hapijs/boom) support.
|
||||
- [`fastify-bree`](https://github.com/climba03003/fastify-bree) Fastify plugin
|
||||
to add [bree](https://github.com/breejs/bree) support.
|
||||
- [`fastify-bugsnag`](https://github.com/ZigaStrgar/fastify-bugsnag) Fastify plugin
|
||||
to add support for [Bugsnag](https://www.bugsnag.com/) error reporting.
|
||||
- [`fastify-cacheman`](https://gitlab.com/aalfiann/fastify-cacheman)
|
||||
Small and efficient cache provider for Node.js with In-memory, File, Redis
|
||||
and MongoDB engines for Fastify
|
||||
- [`fastify-casbin`](https://github.com/nearform/fastify-casbin) Casbin support
|
||||
for Fastify.
|
||||
- [`fastify-casbin-rest`](https://github.com/nearform/fastify-casbin-rest)
|
||||
Casbin support for Fastify based on a RESTful model.
|
||||
- [`fastify-casl`](https://github.com/Inlecom/fastify-casl) Fastify
|
||||
[CASL](https://github.com/stalniy/casl) plugin that supports ACL-like
|
||||
protection of endpoints via either a preSerialization & preHandler hook,
|
||||
sanitizing the inputs and outputs of your application based on user rights.
|
||||
- [`fastify-cloudevents`](https://github.com/smartiniOnGitHub/fastify-cloudevents)
|
||||
Fastify plugin to generate and forward Fastify events in the Cloudevents
|
||||
format.
|
||||
- [`fastify-cloudflare-turnstile`](https://github.com/112RG/fastify-cloudflare-turnstile)
|
||||
Fastify plugin for CloudFlare Turnstile.
|
||||
- [`fastify-cloudinary`](https://github.com/Vanilla-IceCream/fastify-cloudinary)
|
||||
Plugin to share a common Cloudinary connection across Fastify.
|
||||
- [`fastify-cockroachdb`](https://github.com/alex-ppg/fastify-cockroachdb)
|
||||
Fastify plugin to connect to a CockroachDB PostgreSQL instance via the
|
||||
Sequelize ORM.
|
||||
- [`fastify-constraints`](https://github.com/nearform/fastify-constraints)
|
||||
Fastify plugin to add constraints to multiple routes
|
||||
- [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify
|
||||
plugin to add CouchDB support via [nano](https://github.com/apache/nano).
|
||||
- [`fastify-crud-generator`](https://github.com/beliven-it/fastify-crud-generator)
|
||||
A plugin to rapidly generate CRUD routes for any entity.
|
||||
- [`fastify-custom-healthcheck`](https://github.com/gkampitakis/fastify-custom-healthcheck)
|
||||
Fastify plugin to add health route in your server that asserts custom
|
||||
functions.
|
||||
- [`fastify-decorators`](https://github.com/L2jLiga/fastify-decorators) Fastify
|
||||
plugin that provides the set of TypeScript decorators.
|
||||
- [`fastify-delay-request`](https://github.com/climba03003/fastify-delay-request)
|
||||
Fastify plugin that allows requests to be delayed whilst a task the response is
|
||||
dependent on is run, such as a resource intensive process.
|
||||
- [`fastify-disablecache`](https://github.com/Fdawgs/fastify-disablecache)
|
||||
Fastify plugin to disable client-side caching, inspired by
|
||||
[nocache](https://github.com/helmetjs/nocache).
|
||||
- [`fastify-dynamodb`](https://github.com/matrus2/fastify-dynamodb) AWS DynamoDB
|
||||
plugin for Fastify. It exposes
|
||||
[AWS.DynamoDB.DocumentClient()](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html)
|
||||
object.
|
||||
- [`fastify-dynareg`](https://github.com/greguz/fastify-dynareg) Dynamic plugin
|
||||
register for Fastify.
|
||||
- [`fastify-envalid`](https://github.com/alemagio/fastify-envalid) Fastify
|
||||
plugin to integrate [envalid](https://github.com/af/envalid) in your Fastify
|
||||
project.
|
||||
- [`fastify-error-page`](https://github.com/hemerajs/fastify-error-page) Fastify
|
||||
plugin to print errors in structured HTML to the browser.
|
||||
- [`fastify-esso`](https://github.com/patrickpissurno/fastify-esso) The easiest
|
||||
authentication plugin for Fastify, with built-in support for Single sign-on
|
||||
(and great documentation).
|
||||
- [`fastify-event-bus`](https://github.com/Shiva127/fastify-event-bus) Event bus
|
||||
support for Fastify. Built upon [js-event-bus](https://github.com/bcerati/js-event-bus).
|
||||
- [`fastify-evervault`](https://github.com/Briscoooe/fastify-evervault/) Fastify
|
||||
plugin for instantiating and encapsulating the
|
||||
[Evervault](https://evervault.com/) client.
|
||||
- [`fastify-explorer`](https://github.com/Eomm/fastify-explorer) Get control of
|
||||
your decorators across all the encapsulated contexts.
|
||||
- [`fastify-favicon`](https://github.com/smartiniOnGitHub/fastify-favicon)
|
||||
Fastify plugin to serve default favicon.
|
||||
- [`fastify-feature-flags`](https://gitlab.com/m03geek/fastify-feature-flags)
|
||||
Fastify feature flags plugin with multiple providers support (e.g. env,
|
||||
[config](https://lorenwest.github.io/node-config/),
|
||||
[unleash](https://unleash.github.io/)).
|
||||
- [`fastify-file-routes`](https://github.com/spa5k/fastify-file-routes) Get
|
||||
Next.js based file system routing into fastify.
|
||||
- [`fastify-file-upload`](https://github.com/huangang/fastify-file-upload)
|
||||
Fastify plugin for uploading files.
|
||||
- [`fastify-firebase`](https://github.com/now-ims/fastify-firebase) Fastify
|
||||
plugin for [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup)
|
||||
to Fastify so you can easily use Firebase Auth, Firestore, Cloud Storage,
|
||||
Cloud Messaging, and more.
|
||||
- [`fastify-firebase-auth`](https://github.com/oxsav/fastify-firebase-auth)
|
||||
Firebase Authentication for Fastify supporting all of the methods relating to
|
||||
the authentication API.
|
||||
- [`fastify-formidable`](https://github.com/climba03003/fastify-formidable)
|
||||
Handy plugin to provide multipart support and fastify-swagger integration.
|
||||
- [`fastify-gcloud-trace`](https://github.com/mkinoshi/fastify-gcloud-trace)
|
||||
[Google Cloud Trace API](https://cloud.google.com/trace/docs/reference)
|
||||
Connector for Fastify.
|
||||
- [`fastify-get-head`](https://github.com/MetCoder95/fastify-get-head) Small
|
||||
plugin to set a new HEAD route handler for each GET route previously
|
||||
registered in Fastify.
|
||||
- [`fastify-get-only`](https://github.com/DanieleFedeli/fastify-get-only) Small
|
||||
plugin used to make fastify accept only GET requests
|
||||
- [`fastify-good-sessions`](https://github.com/Phara0h/fastify-good-sessions) A
|
||||
good Fastify sessions plugin focused on speed.
|
||||
- [`fastify-google-cloud-storage`](https://github.com/carlozamagni/fastify-google-cloud-storage)
|
||||
Fastify plugin that exposes a GCP Cloud Storage client instance.
|
||||
- [`fastify-graceful-shutdown`](https://github.com/hemerajs/fastify-graceful-shutdown)
|
||||
Shutdown Fastify gracefully and asynchronously.
|
||||
- [`fastify-grant`](https://github.com/simov/fastify-grant)
|
||||
Authentication/Authorization plugin for Fastify that supports 200+ OAuth
|
||||
Providers.
|
||||
- [`fastify-guard`](https://github.com/hsynlms/fastify-guard) A Fastify plugin
|
||||
that protects endpoints by checking authenticated user roles and/or scopes.
|
||||
- [`fastify-hana`](https://github.com/yoav0gal/fastify-hana) connects your
|
||||
application to [`SAP-HANA`](https://help.sap.com/docs/SAP_HANA_CLIENT).
|
||||
- [`fastify-hashids`](https://github.com/andersonjoseph/fastify-hashids) A Fastify
|
||||
plugin to encode/decode IDs using [hashids](https://github.com/niieani/hashids.js).
|
||||
- [`fastify-hasura`](https://github.com/ManUtopiK/fastify-hasura) A Fastify
|
||||
plugin to have fun with [Hasura](https://github.com/hasura/graphql-engine).
|
||||
- [`fastify-healthcheck`](https://github.com/smartiniOnGitHub/fastify-healthcheck)
|
||||
Fastify plugin to serve a health check route and a probe script.
|
||||
- [`fastify-hemera`](https://github.com/hemerajs/fastify-hemera) Fastify Hemera
|
||||
plugin, for writing reliable & fault-tolerant microservices with
|
||||
[nats.io](https://nats.io/).
|
||||
- [`fastify-hl7`](https://github.com/Bugs5382/fastify-hl7) A Fastify Plugin to
|
||||
create a server, build, and send HL7 formatted Hl7 messages. Using
|
||||
[node-hl7-client](https://github.com/Bugs5382/node-hl7-client) and
|
||||
[node-hl7-server](https://github.com/Bugs5382/node-hl7-server) as the
|
||||
underlining technology to do this.
|
||||
- [`fastify-http-client`](https://github.com/kenuyx/fastify-http-client) Plugin
|
||||
to send HTTP(s) requests. Built upon [urllib](https://github.com/node-modules/urllib).
|
||||
- [`fastify-http-context`](https://github.com/thorough-developer/fastify-http-context)
|
||||
Fastify plugin for "simulating" a thread of execution to allow for true HTTP
|
||||
context to take place per API call within the Fastify lifecycle of calls.
|
||||
- [`fastify-http-errors-enhanced`](https://github.com/ShogunPanda/fastify-http-errors-enhanced)
|
||||
An error handling plugin for Fastify that uses enhanced HTTP errors.
|
||||
- [`fastify-http2https`](https://github.com/lolo32/fastify-http2https) Redirect
|
||||
HTTP requests to HTTPS, both using the same port number, or different response
|
||||
on HTTP and HTTPS.
|
||||
- [`fastify-https-always`](https://github.com/mattbishop/fastify-https-always)
|
||||
Lightweight, proxy-aware redirect plugin from HTTP to HTTPS.
|
||||
- [`fastify-https-redirect`](https://github.com/tomsvogel/fastify-https-redirect)
|
||||
Fastify plugin for auto-redirect from HTTP to HTTPS.
|
||||
- [`fastify-i18n`](https://github.com/Vanilla-IceCream/fastify-i18n)
|
||||
Internationalization plugin for Fastify. Built upon node-polyglot.
|
||||
- [`fastify-impressions`](https://github.com/manju4ever/fastify-impressions)
|
||||
Fastify plugin to track impressions of all the routes.
|
||||
- [`fastify-influxdb`](https://github.com/alex-ppg/fastify-influxdb) Fastify
|
||||
InfluxDB plugin connecting to an InfluxDB instance via the Influx default
|
||||
package.
|
||||
- [`fastify-ip`](https://github.com/metcoder95/fastify-ip) A plugin
|
||||
for Fastify that allows you to infer a request ID by a
|
||||
given set of custom Request headers.
|
||||
- [`fastify-json-to-xml`](https://github.com/Fdawgs/fastify-json-to-xml) Fastify
|
||||
plugin to serialize JSON responses into XML.
|
||||
- [`fastify-jwt-authz`](https://github.com/Ethan-Arrowood/fastify-jwt-authz) JWT
|
||||
user scope verifier.
|
||||
- [`fastify-jwt-webapp`](https://github.com/charlesread/fastify-jwt-webapp) JWT
|
||||
authentication for Fastify-based web apps.
|
||||
- [`fastify-kafkajs`](https://github.com/kffl/fastify-kafkajs) Fastify plugin
|
||||
that adds support for KafkaJS - a modern Apache Kafka client library.
|
||||
- [`fastify-keycloak-adapter`](https://github.com/yubinTW/fastify-keycloak-adapter)
|
||||
A keycloak adapter for a Fastify app.
|
||||
- [`fastify-knexjs`](https://github.com/chapuletta/fastify-knexjs) Fastify
|
||||
plugin for supporting KnexJS Query Builder.
|
||||
- [`fastify-knexjs-mock`](https://github.com/chapuletta/fastify-knexjs-mock)
|
||||
Fastify Mock KnexJS for testing support.
|
||||
- [`fastify-koa`](https://github.com/rozzilla/fastify-koa) Convert Koa
|
||||
middlewares into Fastify plugins
|
||||
- [`fastify-kubernetes`](https://github.com/greguz/fastify-kubernetes) Fastify
|
||||
Kubernetes client plugin.
|
||||
- [`fastify-kysely`](https://github.com/alenap93/fastify-kysely) Fastify
|
||||
plugin for supporting Kysely type-safe query builder.
|
||||
- [`fastify-language-parser`](https://github.com/lependu/fastify-language-parser)
|
||||
Fastify plugin to parse request language.
|
||||
- [`fastify-lcache`](https://github.com/denbon05/fastify-lcache)
|
||||
Lightweight cache plugin
|
||||
- [`fastify-list-routes`](https://github.com/chuongtrh/fastify-list-routes)
|
||||
A simple plugin for Fastify to list all available routes.
|
||||
- [`fastify-lm`](https://github.com/galiprandi/fastify-lm#readme)
|
||||
Use OpenAI, Claude, Google, Deepseek, and others LMs with one Fastify plugin.
|
||||
- [`fastify-loader`](https://github.com/TheNoim/fastify-loader) Load routes from
|
||||
a directory and inject the Fastify instance in each file.
|
||||
- [`fastify-log-controller`](https://github.com/Eomm/fastify-log-controller/)
|
||||
changes the log level of your Fastify server at runtime.
|
||||
- [`fastify-lured`](https://github.com/lependu/fastify-lured) Plugin to load lua
|
||||
scripts with [fastify-redis](https://github.com/fastify/fastify-redis) and
|
||||
[lured](https://github.com/enobufs/lured).
|
||||
A plugin to implement [Lyra](https://github.com/LyraSearch/lyra) search engine
|
||||
on Fastify.
|
||||
- [`fastify-mailer`](https://github.com/coopflow/fastify-mailer) Plugin to
|
||||
initialize and encapsulate [Nodemailer](https://nodemailer.com)'s transporters
|
||||
instances in Fastify.
|
||||
- [`fastify-markdown`](https://github.com/freezestudio/fastify-markdown) Plugin
|
||||
to markdown support.
|
||||
- [`fastify-method-override`](https://github.com/corsicanec82/fastify-method-override)
|
||||
Plugin for Fastify, which allows the use of HTTP verbs, such as DELETE, PATCH,
|
||||
HEAD, PUT, OPTIONS in case the client doesn't support them.
|
||||
- [`fastify-metrics`](https://gitlab.com/m03geek/fastify-metrics) Plugin for
|
||||
exporting [Prometheus](https://prometheus.io) metrics.
|
||||
- [`fastify-minify`](https://github.com/Jelenkee/fastify-minify) Plugin for
|
||||
minification and transformation of responses.
|
||||
- [`fastify-mongo-memory`](https://github.com/chapuletta/fastify-mongo-memory)
|
||||
Fastify MongoDB in Memory Plugin for testing support.
|
||||
- [`fastify-mongodb-sanitizer`](https://github.com/KlemenKozelj/fastify-mongodb-sanitizer)
|
||||
Fastify plugin that sanitizes client input to prevent
|
||||
potential MongoDB query injection attacks.
|
||||
- [`fastify-mongoose-api`](https://github.com/jeka-kiselyov/fastify-mongoose-api)
|
||||
Fastify plugin to create REST API methods based on Mongoose MongoDB models.
|
||||
- [`fastify-mongoose-driver`](https://github.com/alex-ppg/fastify-mongoose)
|
||||
Fastify Mongoose plugin that connects to a MongoDB via the Mongoose plugin
|
||||
with support for Models.
|
||||
- [`fastify-mqtt`](https://github.com/love-lena/fastify-mqtt) Plugin to share
|
||||
[mqtt](https://www.npmjs.com/package/mqtt) client across Fastify.
|
||||
- [`fastify-msgpack`](https://github.com/kenriortega/fastify-msgpack) Fastify
|
||||
and MessagePack, together at last. Uses @msgpack/msgpack by default.
|
||||
- [`fastify-msgraph-webhook`](https://github.com/flower-of-the-bridges/fastify-msgraph-change-notifications-webhook)
|
||||
to manage
|
||||
[MS Graph Change Notifications webhooks](https://learn.microsoft.com/it-it/graph/change-notifications-delivery-webhooks?tabs=http).
|
||||
- [`fastify-multer`](https://github.com/fox1t/fastify-multer) Multer is a plugin
|
||||
for handling multipart/form-data, which is primarily used for uploading files.
|
||||
- [`fastify-multilingual`](https://github.com/gbrugger/fastify-multilingual) Unobtrusively
|
||||
decorates fastify request with Polyglot.js for i18n.
|
||||
- [`fastify-nats`](https://github.com/mahmed8003/fastify-nats) Plugin to share
|
||||
[NATS](https://nats.io) client across Fastify.
|
||||
- [`fastify-next-auth`](https://github.com/wobsoriano/fastify-next-auth)
|
||||
NextAuth.js plugin for Fastify.
|
||||
- [`fastify-no-additional-properties`](https://github.com/greguz/fastify-no-additional-properties)
|
||||
Add `additionalProperties: false` by default to your JSON Schemas.
|
||||
- [`fastify-no-icon`](https://github.com/jsumners/fastify-no-icon) Plugin to
|
||||
eliminate thrown errors for `/favicon.ico` requests.
|
||||
- [`fastify-normalize-request-reply`](https://github.com/ericrglass/fastify-normalize-request-reply)
|
||||
Plugin to normalize the request and reply to the Express version 4.x request
|
||||
and response, which allows use of middleware, like swagger-stats, that was
|
||||
originally written for Express.
|
||||
- [`fastify-now`](https://github.com/yonathan06/fastify-now) Structure your
|
||||
endpoints in a folder and load them dynamically with Fastify.
|
||||
- [`fastify-nuxtjs`](https://github.com/gomah/fastify-nuxtjs) Vue server-side
|
||||
rendering support for Fastify with Nuxt.js Framework.
|
||||
- [`fastify-oas`](https://gitlab.com/m03geek/fastify-oas) Generates OpenAPI 3.0+
|
||||
documentation from routes schemas for Fastify.
|
||||
- [`fastify-objectionjs`](https://github.com/jarcodallo/fastify-objectionjs)
|
||||
Plugin for the Fastify framework that provides integration with objectionjs
|
||||
ORM.
|
||||
- [`fastify-objectionjs-classes`](https://github.com/kamikazechaser/fastify-objectionjs-classes)
|
||||
Plugin to cherry-pick classes from objectionjs ORM.
|
||||
- [`fastify-opaque-apake`](https://github.com/squirrelchat/fastify-opaque-apake)
|
||||
A Fastify plugin to implement the OPAQUE aPAKE protocol. Uses
|
||||
[@squirrelchat/opaque-wasm-server](https://github.com/squirrelchat/opaque-wasm).
|
||||
- [`fastify-openapi-docs`](https://github.com/ShogunPanda/fastify-openapi-docs)
|
||||
A Fastify plugin that generates OpenAPI spec automatically.
|
||||
- [`fastify-openapi-glue`](https://github.com/seriousme/fastify-openapi-glue)
|
||||
Glue for OpenAPI specifications in Fastify, autogenerates routes based on an
|
||||
OpenAPI Specification.
|
||||
- [`fastify-opentelemetry`](https://github.com/autotelic/fastify-opentelemetry)
|
||||
A Fastify plugin that uses the [OpenTelemetry
|
||||
API](https://github.com/open-telemetry/opentelemetry-js-api) to provide
|
||||
request tracing.
|
||||
- [`fastify-oracle`](https://github.com/cemremengu/fastify-oracle) Attaches an
|
||||
[`oracledb`](https://github.com/oracle/node-oracledb) connection pool to a
|
||||
Fastify server instance.
|
||||
- [`fastify-orama`](https://github.com/mateonunez/fastify-orama)
|
||||
- [`fastify-orientdb`](https://github.com/mahmed8003/fastify-orientdb) Fastify
|
||||
OrientDB connection plugin, with which you can share the OrientDB connection
|
||||
across every part of your server.
|
||||
- [`fastify-osm`](https://github.com/gzileni/fastify-osm) Fastify
|
||||
OSM plugin to run overpass queries by OpenStreetMap.
|
||||
- [`fastify-override`](https://github.com/matthyk/fastify-override)
|
||||
Fastify plugin to override decorators, plugins and hooks for testing purposes
|
||||
- [`fastify-passkit-webservice`](https://github.com/alexandercerutti/fastify-passkit-webservice)
|
||||
A set of Fastify plugins to integrate Apple Wallet Web Service specification
|
||||
- [`fastify-peekaboo`](https://github.com/simone-sanfratello/fastify-peekaboo)
|
||||
Fastify plugin for memoize responses by expressive settings.
|
||||
- [`fastify-permissions`](https://github.com/pckrishnadas88/fastify-permissions)
|
||||
Route-level permission middleware for Fastify supports
|
||||
custom permission checks.
|
||||
- [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker
|
||||
thread pool plugin using [Piscina](https://github.com/piscinajs/piscina).
|
||||
- [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to
|
||||
handle i18n using
|
||||
[node-polyglot](https://www.npmjs.com/package/node-polyglot).
|
||||
- [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile)
|
||||
Plugin to integrate [PostGraphile](https://www.graphile.org/postgraphile/) in
|
||||
a Fastify project.
|
||||
- [`fastify-postgres-dot-js`](https://github.com/kylerush/fastify-postgresjs) Fastify
|
||||
PostgreSQL connection plugin that uses [Postgres.js](https://github.com/porsager/postgres).
|
||||
- [`fastify-prettier`](https://github.com/hsynlms/fastify-prettier) A Fastify
|
||||
plugin that uses [prettier](https://github.com/prettier/prettier) under the
|
||||
hood to beautify outgoing responses and/or other things in the Fastify server.
|
||||
- [`fastify-print-routes`](https://github.com/ShogunPanda/fastify-print-routes)
|
||||
A Fastify plugin that prints all available routes.
|
||||
- [`fastify-protobufjs`](https://github.com/kenriortega/fastify-protobufjs)
|
||||
Fastify and protobufjs, together at last. Uses protobufjs by default.
|
||||
- [`fastify-qrcode`](https://github.com/chonla/fastify-qrcode) This plugin
|
||||
utilizes [qrcode](https://github.com/soldair/node-qrcode) to generate QR Code.
|
||||
- [`fastify-qs`](https://github.com/vanodevium/fastify-qs) A plugin for Fastify
|
||||
that adds support for parsing URL query parameters with
|
||||
[qs](https://github.com/ljharb/qs).
|
||||
- [`fastify-rabbitmq`](https://github.com/Bugs5382/fastify-rabbitmq) Fastify
|
||||
RabbitMQ plugin that uses
|
||||
[node-rabbitmq-client](https://github.com/cody-greene/node-rabbitmq-client)
|
||||
plugin as a wrapper.
|
||||
- [`fastify-racing`](https://github.com/metcoder95/fastify-racing) Fastify's
|
||||
plugin that adds support to handle an aborted request asynchronous.
|
||||
- [`fastify-ravendb`](https://github.com/nearform/fastify-ravendb) RavenDB
|
||||
connection plugin. It exposes the same `DocumentStore` (or multiple ones)
|
||||
across the whole Fastify application.
|
||||
- [`fastify-raw-body`](https://github.com/Eomm/fastify-raw-body) Add the
|
||||
`request.rawBody` field.
|
||||
- [`fastify-rbac`](https://gitlab.com/m03geek/fastify-rbac) Fastify role-based
|
||||
access control plugin.
|
||||
- [`fastify-recaptcha`](https://github.com/qwertyforce/fastify-recaptcha)
|
||||
Fastify plugin for reCAPTCHA verification.
|
||||
- [`fastify-redis-channels`](https://github.com/hearit-io/fastify-redis-channels)
|
||||
A plugin for fast, reliable, and scalable channels implementation based on
|
||||
Redis streams.
|
||||
- [`fastify-redis-session`](https://github.com/mohammadraufzahed/fastify-redis-session)
|
||||
Redis Session plugin for fastify.
|
||||
- [`fastify-register-routes`](https://github.com/israeleriston/fastify-register-routes)
|
||||
Plugin to automatically load routes from a specified path and optionally limit
|
||||
loaded file names by a regular expression.
|
||||
- [`fastify-response-caching`](https://github.com/codeaholicguy/fastify-response-caching)
|
||||
A Fastify plugin for caching the response.
|
||||
- [`fastify-response-time`](https://github.com/lolo32/fastify-response-time) Add
|
||||
`X-Response-Time` header at each request for Fastify, in milliseconds.
|
||||
- [`fastify-resty`](https://github.com/FastifyResty/fastify-resty) Fastify-based
|
||||
web framework with REST API routes auto-generation for TypeORM entities using
|
||||
DI and decorators.
|
||||
- [`fastify-reverse-routes`](https://github.com/dimonnwc3/fastify-reverse-routes)
|
||||
Fastify reverse routes plugin, allows to defined named routes and build path
|
||||
using name and parameters.
|
||||
- [`fastify-rob-config`](https://github.com/jeromemacias/fastify-rob-config)
|
||||
Fastify Rob-Config integration.
|
||||
- [`fastify-route-group`](https://github.com/TakNePoidet/fastify-route-group)
|
||||
Convenient grouping and inheritance of routes.
|
||||
- [`fastify-route-preset`](https://github.com/inyourtime/fastify-route-preset)
|
||||
A Fastify plugin that enables you to create route configurations that can be
|
||||
applied to multiple routes.
|
||||
- [`fastify-s3-buckets`](https://github.com/kibertoad/fastify-s3-buckets)
|
||||
Ensure the existence of defined S3 buckets on the application startup.
|
||||
- [`fastify-schema-constraint`](https://github.com/Eomm/fastify-schema-constraint)
|
||||
Choose the JSON schema to use based on request parameters.
|
||||
- [`fastify-schema-to-typescript`](https://github.com/thomasthiebaud/fastify-schema-to-typescript)
|
||||
Generate typescript types based on your JSON/YAML validation schemas so they
|
||||
are always in sync.
|
||||
- [`fastify-sentry`](https://github.com/alex-ppg/fastify-sentry) Fastify plugin
|
||||
to add the Sentry SDK error handler to requests.
|
||||
- [`fastify-sequelize`](https://github.com/lyquocnam/fastify-sequelize) Fastify
|
||||
plugin work with Sequelize (adapter for Node.js -> Sqlite, Mysql, Mssql,
|
||||
Postgres).
|
||||
- [`fastify-server-session`](https://github.com/jsumners/fastify-server-session)
|
||||
A session plugin with support for arbitrary backing caches via
|
||||
`fastify-caching`.
|
||||
- [`fastify-shared-schema`](https://github.com/Adibla/fastify-shared-schema) Plugin
|
||||
for sharing schemas between different routes.
|
||||
- [`fastify-slonik`](https://github.com/Unbuttun/fastify-slonik) Fastify Slonik
|
||||
plugin, with this you can use slonik in every part of your server.
|
||||
- [`fastify-slow-down`](https://github.com/nearform/fastify-slow-down) A plugin
|
||||
to delay the response from the server.
|
||||
- [`fastify-socket.io`](https://github.com/alemagio/fastify-socket.io) a
|
||||
Socket.io plugin for Fastify.
|
||||
- [`fastify-split-validator`](https://github.com/MetCoder95/fastify-split-validator)
|
||||
Small plugin to allow you use multiple validators in one route based on each
|
||||
HTTP part of the request.
|
||||
- [`fastify-sqlite`](https://github.com/Eomm/fastify-sqlite) connects your
|
||||
application to a sqlite3 database.
|
||||
- [`fastify-sqlite-typed`](https://github.com/yoav0gal/fastify-sqlite-typed) connects
|
||||
your application to a SQLite database with full Typescript support.
|
||||
- [`fastify-sse`](https://github.com/lolo32/fastify-sse) to provide Server-Sent
|
||||
Events with `reply.sse( … )` to Fastify.
|
||||
- [`fastify-sse-v2`](https://github.com/nodefactoryio/fastify-sse-v2) to provide
|
||||
Server-Sent Events using Async Iterators (supports newer versions of Fastify).
|
||||
- [`fastify-ssr-vite`](https://github.com/nineohnine/fastify-ssr-vite) A simple
|
||||
plugin for setting up server side rendering with vite.
|
||||
- [`fastify-stripe`](https://github.com/coopflow/fastify-stripe) Plugin to
|
||||
initialize and encapsulate [Stripe
|
||||
Node.js](https://github.com/stripe/stripe-node) instances in Fastify.
|
||||
- [`fastify-supabase`](https://github.com/coopflow/fastify-supabase) Plugin to
|
||||
initialize and encapsulate [Supabase](https://github.com/supabase/supabase-js)
|
||||
instances in Fastify.
|
||||
- [`fastify-tls-keygen`](https://gitlab.com/sebdeckers/fastify-tls-keygen)
|
||||
Automatically generate a browser-compatible, trusted, self-signed,
|
||||
localhost-only, TLS certificate.
|
||||
- [`fastify-tokenize`](https://github.com/Bowser65/fastify-tokenize)
|
||||
[Tokenize](https://github.com/Bowser65/Tokenize) plugin for Fastify that
|
||||
removes the pain of managing authentication tokens, with built-in integration
|
||||
for `fastify-auth`.
|
||||
- [`fastify-totp`](https://github.com/beliven-it/fastify-totp) A plugin to handle
|
||||
TOTP (e.g. for 2FA).
|
||||
- [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools)
|
||||
Useful functions for Twitch Extension Backend Services (EBS).
|
||||
- [`fastify-type-provider-effect-schema`](https://github.com/daotl/fastify-type-provider-effect-schema)
|
||||
Fastify
|
||||
[type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
|
||||
for [@effect/schema](https://github.com/effect-ts/schema).
|
||||
- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)
|
||||
Fastify
|
||||
[type provider](https://fastify.dev/docs/latest/Reference/Type-Providers/)
|
||||
for [zod](https://github.com/colinhacks/zod).
|
||||
- [`fastify-typeorm-plugin`](https://github.com/inthepocket/fastify-typeorm-plugin)
|
||||
Fastify plugin to work with TypeORM.
|
||||
- [`fastify-user-agent`](https://github.com/Eomm/fastify-user-agent) parses your
|
||||
request's `user-agent` header.
|
||||
- [`fastify-uws`](https://github.com/geut/fastify-uws) A Fastify plugin to
|
||||
use the web server [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js).
|
||||
- [`fastify-vhost`](https://github.com/patrickpissurno/fastify-vhost) Proxy
|
||||
subdomain HTTP requests to another server (useful if you want to point
|
||||
multiple subdomains to the same IP address, while running different servers on
|
||||
the same machine).
|
||||
- [`fastify-vite`](https://github.com/galvez/fastify-vite)
|
||||
[Vite](https://vitejs.dev/) plugin for Fastify with SSR data support.
|
||||
- [`fastify-vue-plugin`](https://github.com/TheNoim/fastify-vue)
|
||||
[Nuxt.js](https://nuxtjs.org) plugin for Fastify. Control the routes nuxt
|
||||
should use.
|
||||
- [`fastify-wamp-router`](https://github.com/lependu/fastify-wamp-router) Web
|
||||
Application Messaging Protocol router for Fastify.
|
||||
- [`fastify-web-response`](https://github.com/erfanium/fastify-web-response)
|
||||
Enables returning web streams objects `Response` and `ReadableStream` in routes.
|
||||
- [`fastify-webpack-hmr`](https://github.com/lependu/fastify-webpack-hmr)
|
||||
Webpack hot module reloading plugin for Fastify.
|
||||
- [`fastify-webpack-hot`](https://github.com/gajus/fastify-webpack-hot) Webpack
|
||||
Hot Module Replacement for Fastify.
|
||||
- [`fastify-ws`](https://github.com/gj/fastify-ws) WebSocket integration for
|
||||
Fastify — with support for WebSocket lifecycle hooks instead of a single
|
||||
handler function. Built upon [ws](https://github.com/websockets/ws) and
|
||||
[uws](https://github.com/uNetworking/uWebSockets).
|
||||
- [`fastify-xml-body-parser`](https://github.com/NaturalIntelligence/fastify-xml-body-parser)
|
||||
Parse XML payload / request body into JS / JSON object.
|
||||
- [`http-wizard`](https://github.com/flodlc/http-wizard)
|
||||
Exports a typescript API client for your Fastify API and ensures fullstack type
|
||||
safety for your project.
|
||||
- [`i18next-http-middleware`](https://github.com/i18next/i18next-http-middleware#fastify-usage)
|
||||
An [i18next](https://www.i18next.com) based i18n (internationalization)
|
||||
middleware to be used with Node.js web frameworks like Express or Fastify and
|
||||
also for Deno.
|
||||
- [`k-fastify-gateway`](https://github.com/jkyberneees/fastify-gateway) API
|
||||
Gateway plugin for Fastify, a low footprint implementation that uses the
|
||||
`fastify-reply-from` HTTP proxy library.
|
||||
- [`mercurius`](https://mercurius.dev/) A fully-featured and performant GraphQL
|
||||
server implementation for Fastify.
|
||||
- [`nstats`](https://github.com/Phara0h/nstats) A fast and compact way to get
|
||||
all your network and process stats for your node application. Websocket,
|
||||
HTTP/S, and prometheus compatible!
|
||||
- [`oas-fastify`](https://github.com/ahmadnassri/node-oas-fastify) OAS 3.x to
|
||||
Fastify routes automation. Automatically generates route handlers with fastify
|
||||
configuration and validation.
|
||||
- [`openapi-validator-middleware`](https://github.com/PayU/openapi-validator-middleware#fastify)
|
||||
Swagger and OpenAPI 3.0 spec-based request validation middleware that supports
|
||||
Fastify.
|
||||
- [`pubsub-http-handler`](https://github.com/simenandre/pubsub-http-handler) A Fastify
|
||||
plugin to easily create Google Cloud PubSub endpoints.
|
||||
- [`sequelize-fastify`](https://github.com/hsynlms/sequelize-fastify) A simple
|
||||
and lightweight Sequelize plugin for Fastify.
|
||||
- [`typeorm-fastify-plugin`](https://github.com/jclemens24/fastify-typeorm) A simple
|
||||
and updated Typeorm plugin for use with Fastify.
|
||||
|
||||
|
||||
#### [Community Tools](#community-tools)
|
||||
|
||||
- [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows)
|
||||
Reusable workflows for use in the Fastify plugin
|
||||
- [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration
|
||||
generator by directory structure.
|
||||
- [`fastify-flux`](https://github.com/Jnig/fastify-flux) Tool for building
|
||||
Fastify APIs using decorators and convert Typescript interface to JSON Schema.
|
||||
- [`jeasx`](https://www.jeasx.dev)
|
||||
A flexible server-rendering framework built on Fastify
|
||||
that leverages asynchronous JSX to simplify web development.
|
||||
- [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to
|
||||
generate JSON Schema from TypeScript interfaces.
|
||||
- [`vite-plugin-fastify`](https://github.com/Vanilla-IceCream/vite-plugin-fastify)
|
||||
Fastify plugin for Vite with Hot-module Replacement.
|
||||
- [`vite-plugin-fastify-routes`](https://github.com/Vanilla-IceCream/vite-plugin-fastify-routes)
|
||||
File-based routing for Fastify applications using Vite.
|
||||
|
||||
126
node_modules/fastify/docs/Guides/Fluent-Schema.md
generated
vendored
Normal file
126
node_modules/fastify/docs/Guides/Fluent-Schema.md
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Fluent Schema
|
||||
|
||||
The [Validation and
|
||||
Serialization](../Reference/Validation-and-Serialization.md) documentation
|
||||
outlines all parameters accepted by Fastify to set up JSON Schema Validation to
|
||||
validate the input, and JSON Schema Serialization to optimize the output.
|
||||
|
||||
[`fluent-json-schema`](https://github.com/fastify/fluent-json-schema) can be
|
||||
used to simplify this task while allowing the reuse of constants.
|
||||
|
||||
### Basic settings
|
||||
|
||||
```js
|
||||
const S = require('fluent-json-schema')
|
||||
|
||||
// You can have an object like this, or query a DB to get the values
|
||||
const MY_KEYS = {
|
||||
KEY1: 'ONE',
|
||||
KEY2: 'TWO'
|
||||
}
|
||||
|
||||
const bodyJsonSchema = S.object()
|
||||
.prop('someKey', S.string())
|
||||
.prop('someOtherKey', S.number())
|
||||
.prop('requiredKey', S.array().maxItems(3).items(S.integer()).required())
|
||||
.prop('nullableKey', S.mixed([S.TYPES.NUMBER, S.TYPES.NULL]))
|
||||
.prop('multipleTypesKey', S.mixed([S.TYPES.BOOLEAN, S.TYPES.NUMBER]))
|
||||
.prop('multipleRestrictedTypesKey', S.oneOf([S.string().maxLength(5), S.number().minimum(10)]))
|
||||
.prop('enumKey', S.enum(Object.values(MY_KEYS)))
|
||||
.prop('notTypeKey', S.not(S.array()))
|
||||
|
||||
const queryStringJsonSchema = S.object()
|
||||
.prop('name', S.string())
|
||||
.prop('excitement', S.integer())
|
||||
|
||||
const paramsJsonSchema = S.object()
|
||||
.prop('par1', S.string())
|
||||
.prop('par2', S.integer())
|
||||
|
||||
const headersJsonSchema = S.object()
|
||||
.prop('x-foo', S.string().required())
|
||||
|
||||
// Note that there is no need to call `.valueOf()`!
|
||||
const schema = {
|
||||
body: bodyJsonSchema,
|
||||
querystring: queryStringJsonSchema, // (or) query: queryStringJsonSchema
|
||||
params: paramsJsonSchema,
|
||||
headers: headersJsonSchema
|
||||
}
|
||||
|
||||
fastify.post('/the/url', { schema }, handler)
|
||||
```
|
||||
|
||||
### Reuse
|
||||
|
||||
With `fluent-json-schema`, you can manipulate your schemas more easily and
|
||||
programmatically and then reuse them thanks to the `addSchema()` method. You can
|
||||
refer to the schema in two different manners that are detailed in the
|
||||
[Validation and
|
||||
Serialization](../Reference/Validation-and-Serialization.md#adding-a-shared-schema)
|
||||
documentation.
|
||||
|
||||
Here are some usage examples:
|
||||
|
||||
**`$ref-way`**: refer to an external schema.
|
||||
|
||||
```js
|
||||
const addressSchema = S.object()
|
||||
.id('#address')
|
||||
.prop('line1').required()
|
||||
.prop('line2')
|
||||
.prop('country').required()
|
||||
.prop('city').required()
|
||||
.prop('zipcode').required()
|
||||
|
||||
const commonSchemas = S.object()
|
||||
.id('https://fastify/demo')
|
||||
.definition('addressSchema', addressSchema)
|
||||
.definition('otherSchema', otherSchema) // You can add any schemas you need
|
||||
|
||||
fastify.addSchema(commonSchemas)
|
||||
|
||||
const bodyJsonSchema = S.object()
|
||||
.prop('residence', S.ref('https://fastify/demo#address')).required()
|
||||
.prop('office', S.ref('https://fastify/demo#/definitions/addressSchema')).required()
|
||||
|
||||
const schema = { body: bodyJsonSchema }
|
||||
|
||||
fastify.post('/the/url', { schema }, handler)
|
||||
```
|
||||
|
||||
|
||||
**`replace-way`**: refer to a shared schema to replace before the validation
|
||||
process.
|
||||
|
||||
```js
|
||||
const sharedAddressSchema = {
|
||||
$id: 'sharedAddress',
|
||||
type: 'object',
|
||||
required: ['line1', 'country', 'city', 'zipcode'],
|
||||
properties: {
|
||||
line1: { type: 'string' },
|
||||
line2: { type: 'string' },
|
||||
country: { type: 'string' },
|
||||
city: { type: 'string' },
|
||||
zipcode: { type: 'string' }
|
||||
}
|
||||
}
|
||||
fastify.addSchema(sharedAddressSchema)
|
||||
|
||||
const bodyJsonSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
vacation: 'sharedAddress#'
|
||||
}
|
||||
}
|
||||
|
||||
const schema = { body: bodyJsonSchema }
|
||||
|
||||
fastify.post('/the/url', { schema }, handler)
|
||||
```
|
||||
|
||||
NB You can mix up the `$ref-way` and the `replace-way` when using
|
||||
`fastify.addSchema`.
|
||||
620
node_modules/fastify/docs/Guides/Getting-Started.md
generated
vendored
Normal file
620
node_modules/fastify/docs/Guides/Getting-Started.md
generated
vendored
Normal file
@@ -0,0 +1,620 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Getting Started
|
||||
|
||||
Hello! Thank you for checking out Fastify!
|
||||
|
||||
This document aims to be a gentle introduction to the framework and its
|
||||
features. It is an elementary preface with examples and links to other parts of
|
||||
the documentation.
|
||||
|
||||
Let's start!
|
||||
|
||||
### Install
|
||||
<a id="install"></a>
|
||||
|
||||
Install with npm:
|
||||
```sh
|
||||
npm i fastify
|
||||
```
|
||||
|
||||
Install with yarn:
|
||||
```sh
|
||||
yarn add fastify
|
||||
```
|
||||
|
||||
### Your first server
|
||||
<a id="first-server"></a>
|
||||
|
||||
Let's write our first server:
|
||||
```js
|
||||
// Require the framework and instantiate it
|
||||
|
||||
// ESM
|
||||
import Fastify from 'fastify'
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
// CommonJs
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
// Declare a route
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
// Run the server!
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
> If you are using ECMAScript Modules (ESM) in your project, be sure to
|
||||
> include "type": "module" in your package.json.
|
||||
>```js
|
||||
>{
|
||||
> "type": "module"
|
||||
>}
|
||||
>```
|
||||
|
||||
Do you prefer to use `async/await`? Fastify supports it out-of-the-box.
|
||||
|
||||
```js
|
||||
// ESM
|
||||
import Fastify from 'fastify'
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
// CommonJs
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.get('/', async (request, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
/**
|
||||
* Run the server!
|
||||
*/
|
||||
const start = async () => {
|
||||
try {
|
||||
await fastify.listen({ port: 3000 })
|
||||
} catch (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
start()
|
||||
```
|
||||
|
||||
Awesome, that was easy.
|
||||
|
||||
Unfortunately, writing a complex application requires significantly more code
|
||||
than this example. A classic problem when you are building a new application is
|
||||
how to handle multiple files, asynchronous bootstrapping, and the architecture
|
||||
of your code.
|
||||
|
||||
Fastify offers an easy platform that helps to solve all of the problems outlined
|
||||
above, and more!
|
||||
|
||||
> **Note**
|
||||
> The above examples, and subsequent examples in this document, default to
|
||||
> listening *only* on the localhost `127.0.0.1` interface. To listen on all
|
||||
> available IPv4 interfaces the example should be modified to listen on
|
||||
> `0.0.0.0` like so:
|
||||
>
|
||||
> ```js
|
||||
> fastify.listen({ port: 3000, host: '0.0.0.0' }, function (err, address) {
|
||||
> if (err) {
|
||||
> fastify.log.error(err)
|
||||
> process.exit(1)
|
||||
> }
|
||||
> fastify.log.info(`server listening on ${address}`)
|
||||
> })
|
||||
> ```
|
||||
>
|
||||
> Similarly, specify `::1` to accept only local connections via IPv6. Or specify
|
||||
> `::` to accept connections on all IPv6 addresses, and, if the operating system
|
||||
> supports it, also on all IPv4 addresses.
|
||||
>
|
||||
> When deploying to a Docker (or another type of) container using `0.0.0.0` or
|
||||
> `::` would be the easiest method for exposing the application.
|
||||
>
|
||||
> Note that when using `0.0.0.0`, the address provided in the callback argument
|
||||
> above will be the first address the wildcard refers to.
|
||||
|
||||
### Your first plugin
|
||||
<a id="first-plugin"></a>
|
||||
|
||||
As with JavaScript, where everything is an object, with Fastify everything is a
|
||||
plugin.
|
||||
|
||||
Before digging into it, let's see how it works!
|
||||
|
||||
Let's declare our basic server, but instead of declaring the route inside the
|
||||
entry point, we'll declare it in an external file (check out the [route
|
||||
declaration](../Reference/Routes.md) docs).
|
||||
```js
|
||||
// ESM
|
||||
import Fastify from 'fastify'
|
||||
import firstRoute from './our-first-route.js'
|
||||
/**
|
||||
* @type {import('fastify').FastifyInstance} Instance of Fastify
|
||||
*/
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.register(firstRoute)
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
```js
|
||||
// CommonJs
|
||||
/**
|
||||
* @type {import('fastify').FastifyInstance} Instance of Fastify
|
||||
*/
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.register(require('./our-first-route'))
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
// our-first-route.js
|
||||
|
||||
/**
|
||||
* Encapsulates the routes
|
||||
* @param {FastifyInstance} fastify Encapsulated Fastify Instance
|
||||
* @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
|
||||
*/
|
||||
async function routes (fastify, options) {
|
||||
fastify.get('/', async (request, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
}
|
||||
|
||||
//ESM
|
||||
export default routes;
|
||||
|
||||
// CommonJs
|
||||
module.exports = routes
|
||||
```
|
||||
In this example, we used the `register` API, which is the core of the Fastify
|
||||
framework. It is the only way to add routes, plugins, et cetera.
|
||||
|
||||
At the beginning of this guide, we noted that Fastify provides a foundation that
|
||||
assists with asynchronous bootstrapping of your application. Why is this
|
||||
important?
|
||||
|
||||
Consider the scenario where a database connection is needed to handle data
|
||||
storage. The database connection needs to be available before the server is
|
||||
accepting connections. How do we address this problem?
|
||||
|
||||
A typical solution is to use a complex callback, or promises - a system that
|
||||
will mix the framework API with other libraries and the application code.
|
||||
|
||||
Fastify handles this internally, with minimum effort!
|
||||
|
||||
Let's rewrite the above example with a database connection.
|
||||
|
||||
|
||||
First, install `fastify-plugin` and `@fastify/mongodb`:
|
||||
|
||||
```sh
|
||||
npm i fastify-plugin @fastify/mongodb
|
||||
```
|
||||
|
||||
**server.js**
|
||||
```js
|
||||
// ESM
|
||||
import Fastify from 'fastify'
|
||||
import dbConnector from './our-db-connector.js'
|
||||
import firstRoute from './our-first-route.js'
|
||||
|
||||
/**
|
||||
* @type {import('fastify').FastifyInstance} Instance of Fastify
|
||||
*/
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
fastify.register(dbConnector)
|
||||
fastify.register(firstRoute)
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
```js
|
||||
// CommonJs
|
||||
/**
|
||||
* @type {import('fastify').FastifyInstance} Instance of Fastify
|
||||
*/
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
|
||||
fastify.register(require('./our-db-connector'))
|
||||
fastify.register(require('./our-first-route'))
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
**our-db-connector.js**
|
||||
```js
|
||||
// ESM
|
||||
import fastifyPlugin from 'fastify-plugin'
|
||||
import fastifyMongo from '@fastify/mongodb'
|
||||
|
||||
/**
|
||||
* @param {FastifyInstance} fastify
|
||||
* @param {Object} options
|
||||
*/
|
||||
async function dbConnector (fastify, options) {
|
||||
fastify.register(fastifyMongo, {
|
||||
url: 'mongodb://localhost:27017/test_database'
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapping a plugin function with fastify-plugin exposes the decorators
|
||||
// and hooks, declared inside the plugin to the parent scope.
|
||||
export default fastifyPlugin(dbConnector)
|
||||
|
||||
```
|
||||
|
||||
```js
|
||||
// CommonJs
|
||||
/**
|
||||
* @type {import('fastify-plugin').FastifyPlugin}
|
||||
*/
|
||||
const fastifyPlugin = require('fastify-plugin')
|
||||
|
||||
|
||||
/**
|
||||
* Connects to a MongoDB database
|
||||
* @param {FastifyInstance} fastify Encapsulated Fastify Instance
|
||||
* @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
|
||||
*/
|
||||
async function dbConnector (fastify, options) {
|
||||
fastify.register(require('@fastify/mongodb'), {
|
||||
url: 'mongodb://localhost:27017/test_database'
|
||||
})
|
||||
}
|
||||
|
||||
// Wrapping a plugin function with fastify-plugin exposes the decorators
|
||||
// and hooks, declared inside the plugin to the parent scope.
|
||||
module.exports = fastifyPlugin(dbConnector)
|
||||
|
||||
```
|
||||
|
||||
**our-first-route.js**
|
||||
```js
|
||||
/**
|
||||
* A plugin that provide encapsulated routes
|
||||
* @param {FastifyInstance} fastify encapsulated fastify instance
|
||||
* @param {Object} options plugin options, refer to https://fastify.dev/docs/latest/Reference/Plugins/#plugin-options
|
||||
*/
|
||||
async function routes (fastify, options) {
|
||||
const collection = fastify.mongo.db.collection('test_collection')
|
||||
|
||||
fastify.get('/', async (request, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
fastify.get('/animals', async (request, reply) => {
|
||||
const result = await collection.find().toArray()
|
||||
if (result.length === 0) {
|
||||
throw new Error('No documents found')
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
fastify.get('/animals/:animal', async (request, reply) => {
|
||||
const result = await collection.findOne({ animal: request.params.animal })
|
||||
if (!result) {
|
||||
throw new Error('Invalid value')
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const animalBodyJsonSchema = {
|
||||
type: 'object',
|
||||
required: ['animal'],
|
||||
properties: {
|
||||
animal: { type: 'string' },
|
||||
},
|
||||
}
|
||||
|
||||
const schema = {
|
||||
body: animalBodyJsonSchema,
|
||||
}
|
||||
|
||||
fastify.post('/animals', { schema }, async (request, reply) => {
|
||||
// we can use the `request.body` object to get the data sent by the client
|
||||
const result = await collection.insertOne({ animal: request.body.animal })
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = routes
|
||||
```
|
||||
|
||||
Wow, that was fast!
|
||||
|
||||
Let's recap what we have done here since we've introduced some new concepts.
|
||||
|
||||
As you can see, we used `register` for both the database connector and the
|
||||
registration of the routes.
|
||||
|
||||
This is one of the best features of Fastify, it will load your plugins in the
|
||||
same order you declare them, and it will load the next plugin only once the
|
||||
current one has been loaded. In this way, we can register the database connector
|
||||
in the first plugin and use it in the second *(read
|
||||
[here](../Reference/Plugins.md#handle-the-scope) to understand how to handle the
|
||||
scope of a plugin)*.
|
||||
|
||||
Plugin loading starts when you call `fastify.listen()`, `fastify.inject()` or
|
||||
`fastify.ready()`
|
||||
|
||||
The MongoDB plugin uses the `decorate` API to add custom objects to the Fastify
|
||||
instance, making them available for use everywhere. Use of this API is
|
||||
encouraged to facilitate easy code reuse and to decrease code or logic
|
||||
duplication.
|
||||
|
||||
To dig deeper into how Fastify plugins work, how to develop new plugins, and for
|
||||
details on how to use the whole Fastify API to deal with the complexity of
|
||||
asynchronously bootstrapping an application, read [the hitchhiker's guide to
|
||||
plugins](./Plugins-Guide.md).
|
||||
|
||||
### Loading order of your plugins
|
||||
<a id="plugin-loading-order"></a>
|
||||
|
||||
To guarantee consistent and predictable behavior of your application, we highly
|
||||
recommend to always load your code as shown below:
|
||||
```
|
||||
└── plugins (from the Fastify ecosystem)
|
||||
└── your plugins (your custom plugins)
|
||||
└── decorators
|
||||
└── hooks
|
||||
└── your services
|
||||
```
|
||||
In this way, you will always have access to all of the properties declared in
|
||||
the current scope.
|
||||
|
||||
As discussed previously, Fastify offers a solid encapsulation model, to help you
|
||||
build your application as independent services. If you want to
|
||||
register a plugin only for a subset of routes, you just have to replicate the
|
||||
above structure.
|
||||
```
|
||||
└── plugins (from the Fastify ecosystem)
|
||||
└── your plugins (your custom plugins)
|
||||
└── decorators
|
||||
└── hooks
|
||||
└── your services
|
||||
│
|
||||
└── service A
|
||||
│ └── plugins (from the Fastify ecosystem)
|
||||
│ └── your plugins (your custom plugins)
|
||||
│ └── decorators
|
||||
│ └── hooks
|
||||
│ └── your services
|
||||
│
|
||||
└── service B
|
||||
└── plugins (from the Fastify ecosystem)
|
||||
└── your plugins (your custom plugins)
|
||||
└── decorators
|
||||
└── hooks
|
||||
└── your services
|
||||
```
|
||||
|
||||
### Validate your data
|
||||
<a id="validate-data"></a>
|
||||
|
||||
Data validation is extremely important and a core concept of the framework.
|
||||
|
||||
To validate incoming requests, Fastify uses [JSON
|
||||
Schema](https://json-schema.org/).
|
||||
|
||||
Let's look at an example demonstrating validation for routes:
|
||||
```js
|
||||
/**
|
||||
* @type {import('fastify').RouteShorthandOptions}
|
||||
* @const
|
||||
*/
|
||||
const opts = {
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
someKey: { type: 'string' },
|
||||
someOtherKey: { type: 'number' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify.post('/', opts, async (request, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
```
|
||||
This example shows how to pass an options object to the route, which accepts a
|
||||
`schema` key that contains all of the schemas for route, `body`, `querystring`,
|
||||
`params`, and `headers`.
|
||||
|
||||
Read [Validation and
|
||||
Serialization](../Reference/Validation-and-Serialization.md) to learn more.
|
||||
|
||||
### Serialize your data
|
||||
<a id="serialize-data"></a>
|
||||
|
||||
Fastify has first-class support for JSON. It is extremely optimized to parse
|
||||
JSON bodies and serialize JSON output.
|
||||
|
||||
To speed up JSON serialization (yes, it is slow!) use the `response` key of the
|
||||
schema option as shown in the following example:
|
||||
```js
|
||||
/**
|
||||
* @type {import('fastify').RouteShorthandOptions}
|
||||
* @const
|
||||
*/
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify.get('/', opts, async (request, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
```
|
||||
By specifying a schema as shown, you can speed up serialization by a factor of
|
||||
2-3. This also helps to protect against leakage of potentially sensitive data,
|
||||
since Fastify will serialize only the data present in the response schema. Read
|
||||
[Validation and Serialization](../Reference/Validation-and-Serialization.md) to
|
||||
learn more.
|
||||
|
||||
### Parsing request payloads
|
||||
<a id="request-payload"></a>
|
||||
|
||||
Fastify parses `'application/json'` and `'text/plain'` request payloads
|
||||
natively, with the result accessible from the [Fastify
|
||||
request](../Reference/Request.md) object at `request.body`.
|
||||
|
||||
The following example returns the parsed body of a request back to the client:
|
||||
|
||||
```js
|
||||
/**
|
||||
* @type {import('fastify').RouteShorthandOptions}
|
||||
*/
|
||||
const opts = {}
|
||||
fastify.post('/', opts, async (request, reply) => {
|
||||
return request.body
|
||||
})
|
||||
```
|
||||
|
||||
Read [Content-Type Parser](../Reference/ContentTypeParser.md) to learn more
|
||||
about Fastify's default parsing functionality and how to support other content
|
||||
types.
|
||||
|
||||
### Extend your server
|
||||
<a id="extend-server"></a>
|
||||
|
||||
Fastify is built to be extremely extensible and minimal, we believe that a
|
||||
bare-bones framework is all that is necessary to make great applications
|
||||
possible.
|
||||
|
||||
In other words, Fastify is not a "batteries included" framework, and relies on
|
||||
an amazing [ecosystem](./Ecosystem.md)!
|
||||
|
||||
### Test your server
|
||||
<a id="test-server"></a>
|
||||
|
||||
Fastify does not offer a testing framework, but we do recommend a way to write
|
||||
your tests that uses the features and architecture of Fastify.
|
||||
|
||||
Read the [testing](./Testing.md) documentation to learn more!
|
||||
|
||||
### Run your server from CLI
|
||||
<a id="cli"></a>
|
||||
|
||||
Fastify also has CLI integration via
|
||||
[fastify-cli](https://github.com/fastify/fastify-cli),
|
||||
a separate tool for scaffolding and managing Fastify projects.
|
||||
|
||||
First, install `fastify-cli`:
|
||||
|
||||
```sh
|
||||
npm i fastify-cli
|
||||
```
|
||||
|
||||
You can also install it globally with `-g`.
|
||||
|
||||
Then, add the following lines to `package.json`:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"start": "fastify start server.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And create your server file(s):
|
||||
```js
|
||||
// server.js
|
||||
'use strict'
|
||||
|
||||
module.exports = async function (fastify, opts) {
|
||||
fastify.get('/', async (request, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then run your server with:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
### Slides and Videos
|
||||
<a id="slides"></a>
|
||||
|
||||
- Slides
|
||||
- [Take your HTTP server to ludicrous
|
||||
speed](https://mcollina.github.io/take-your-http-server-to-ludicrous-speed)
|
||||
by [@mcollina](https://github.com/mcollina)
|
||||
- [What if I told you that HTTP can be
|
||||
fast](https://delvedor.github.io/What-if-I-told-you-that-HTTP-can-be-fast)
|
||||
by [@delvedor](https://github.com/delvedor)
|
||||
|
||||
- Videos
|
||||
- [Take your HTTP server to ludicrous
|
||||
speed](https://www.youtube.com/watch?v=5z46jJZNe8k) by
|
||||
[@mcollina](https://github.com/mcollina)
|
||||
- [What if I told you that HTTP can be
|
||||
fast](https://www.webexpo.net/prague2017/talk/what-if-i-told-you-that-http-can-be-fast/)
|
||||
by [@delvedor](https://github.com/delvedor)
|
||||
43
node_modules/fastify/docs/Guides/Index.md
generated
vendored
Normal file
43
node_modules/fastify/docs/Guides/Index.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Guides Table Of Contents
|
||||
<a id="guides-toc"></a>
|
||||
|
||||
This table of contents is in alphabetical order.
|
||||
|
||||
+ [Benchmarking](./Benchmarking.md): This guide introduces how to benchmark
|
||||
applications based on Fastify.
|
||||
+ [Contributing](./Contributing.md): Details how to participate in the
|
||||
development of Fastify, and shows how to setup an environment compatible with
|
||||
the project's code style.
|
||||
+ [Delay Accepting Requests](./Delay-Accepting-Requests.md): A practical guide
|
||||
on how to delay serving requests to specific routes until some condition is
|
||||
met in your application. This guide focuses on solving the problem using
|
||||
[`Hooks`](../Reference/Hooks.md), [`Decorators`](../Reference/Decorators.md),
|
||||
and [`Plugins`](../Reference/Plugins.md).
|
||||
+ [Detecting When Clients Abort](./Detecting-When-Clients-Abort.md): A
|
||||
practical guide on detecting if and when a client aborts a request.
|
||||
+ [Ecosystem](./Ecosystem.md): Lists all core plugins and many known community
|
||||
plugins.
|
||||
+ [Fluent Schema](./Fluent-Schema.md): Shows how JSON Schema can be
|
||||
written with a fluent API and used in Fastify.
|
||||
+ [Getting Started](./Getting-Started.md): Introduction tutorial for Fastify.
|
||||
This is where beginners should start.
|
||||
+ [Migration Guide (v4)](./Migration-Guide-V4.md): Details how to migrate to
|
||||
Fastify v4 from earlier versions.
|
||||
+ [Migration Guide (v3)](./Migration-Guide-V3.md): Details how to migrate to
|
||||
Fastify v3 from earlier versions.
|
||||
+ [Plugins Guide](./Plugins-Guide.md): An informal introduction to writing
|
||||
Fastify plugins.
|
||||
+ [Prototype Poisoning](./Prototype-Poisoning.md): A description of how the
|
||||
prototype poisoning attack works and is mitigated.
|
||||
+ [Recommendations](./Recommendations.md): Recommendations for how to deploy
|
||||
Fastify into production environments.
|
||||
+ [Serverless](./Serverless.md): Details on how to deploy Fastify applications
|
||||
in various Function as a Service (FaaS) environments.
|
||||
+ [Style Guide](./Style-Guide.md): Explains the writing style we use for the
|
||||
Fastify documentation for those who want to contribute documentation.
|
||||
+ [Testing](./Testing.md): Explains how to write unit tests for Fastify
|
||||
applications.
|
||||
+ [Write Plugin](./Write-Plugin.md): A set of guidelines for what the Fastify
|
||||
team considers good practices for writing a Fastify plugin.
|
||||
287
node_modules/fastify/docs/Guides/Migration-Guide-V3.md
generated
vendored
Normal file
287
node_modules/fastify/docs/Guides/Migration-Guide-V3.md
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
# V3 Migration Guide
|
||||
|
||||
This guide is intended to help with migration from Fastify v2 to v3.
|
||||
|
||||
Before beginning please ensure that any deprecation warnings from v2 are fixed.
|
||||
All v2 deprecations have been removed and they will no longer work after
|
||||
upgrading. ([#1750](https://github.com/fastify/fastify/pull/1750))
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### Changed middleware support ([#2014](https://github.com/fastify/fastify/pull/2014))
|
||||
|
||||
From Fastify v3, middleware support does not come out-of-the-box with the
|
||||
framework itself.
|
||||
|
||||
If you use Express middleware in your application, please install and register
|
||||
the [`@fastify/express`](https://github.com/fastify/fastify-express) or
|
||||
[`@fastify/middie`](https://github.com/fastify/middie) plugin before doing so.
|
||||
|
||||
**v2:**
|
||||
|
||||
```js
|
||||
// Using the Express `cors` middleware in Fastify v2.
|
||||
fastify.use(require('cors')());
|
||||
```
|
||||
|
||||
**v3:**
|
||||
|
||||
```js
|
||||
// Using the Express `cors` middleware in Fastify v3.
|
||||
await fastify.register(require('@fastify/express'));
|
||||
fastify.use(require('cors')());
|
||||
```
|
||||
|
||||
### Changed logging serialization ([#2017](https://github.com/fastify/fastify/pull/2017))
|
||||
|
||||
The logging [Serializers](../Reference/Logging.md) have been updated to now
|
||||
Fastify [`Request`](../Reference/Request.md) and
|
||||
[`Reply`](../Reference/Reply.md) objects instead of native ones.
|
||||
|
||||
Any custom serializers must be updated if they rely upon `request` or `reply`
|
||||
properties that are present on the native objects but not the Fastify objects.
|
||||
|
||||
**v2:**
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
serializers: {
|
||||
res(res) {
|
||||
return {
|
||||
statusCode: res.statusCode,
|
||||
customProp: res.customProp
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**v3:**
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
serializers: {
|
||||
res(reply) {
|
||||
return {
|
||||
statusCode: reply.statusCode, // No change required
|
||||
customProp: reply.raw.customProp // Log custom property from res object
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Changed schema substitution ([#2023](https://github.com/fastify/fastify/pull/2023))
|
||||
|
||||
The non-standard `replace-way` shared schema support has been removed. This
|
||||
feature has been replaced with JSON Schema specification compliant `$ref` based
|
||||
substitution. To help understand this change read [Validation and Serialization
|
||||
in Fastify
|
||||
v3](https://dev.to/eomm/validation-and-serialization-in-fastify-v3-2e8l).
|
||||
|
||||
**v2:**
|
||||
|
||||
```js
|
||||
const schema = {
|
||||
body: 'schemaId#'
|
||||
};
|
||||
fastify.route({ method, url, schema, handler });
|
||||
```
|
||||
|
||||
**v3:**
|
||||
|
||||
```js
|
||||
const schema = {
|
||||
body: {
|
||||
$ref: 'schemaId#'
|
||||
}
|
||||
};
|
||||
fastify.route({ method, url, schema, handler });
|
||||
```
|
||||
|
||||
### Changed schema validation options ([#2023](https://github.com/fastify/fastify/pull/2023))
|
||||
|
||||
The `setSchemaCompiler` and `setSchemaResolver` options have been replaced with
|
||||
the `setValidatorCompiler` to enable future tooling improvements. To help
|
||||
understand this change read [Validation and Serialization in Fastify
|
||||
v3](https://dev.to/eomm/validation-and-serialization-in-fastify-v3-2e8l).
|
||||
|
||||
**v2:**
|
||||
|
||||
```js
|
||||
const fastify = Fastify();
|
||||
const ajv = new AJV();
|
||||
ajv.addSchema(schemaA);
|
||||
ajv.addSchema(schemaB);
|
||||
|
||||
fastify.setSchemaCompiler(schema => ajv.compile(schema));
|
||||
fastify.setSchemaResolver(ref => ajv.getSchema(ref).schema);
|
||||
```
|
||||
|
||||
**v3:**
|
||||
|
||||
```js
|
||||
const fastify = Fastify();
|
||||
const ajv = new AJV();
|
||||
ajv.addSchema(schemaA);
|
||||
ajv.addSchema(schemaB);
|
||||
|
||||
fastify.setValidatorCompiler(({ schema, method, url, httpPart }) =>
|
||||
ajv.compile(schema)
|
||||
);
|
||||
```
|
||||
|
||||
### Changed preParsing hook behavior ([#2286](https://github.com/fastify/fastify/pull/2286))
|
||||
|
||||
From Fastify v3, the behavior of the `preParsing` hook will change slightly
|
||||
to support request payload manipulation.
|
||||
|
||||
The hook now takes an additional argument, `payload`, and therefore the new hook
|
||||
signature is `fn(request, reply, payload, done)` or `async fn(request, reply,
|
||||
payload)`.
|
||||
|
||||
The hook can optionally return a new stream via `done(null, stream)` or
|
||||
returning the stream in case of async functions.
|
||||
|
||||
If the hook returns a new stream, it will be used instead of the original one in
|
||||
subsequent hooks. A sample use case for this is handling compressed requests.
|
||||
|
||||
The new stream should add the `receivedEncodedLength` property to the stream
|
||||
that should reflect the actual data size received from the client. For instance,
|
||||
in a compressed request it should be the size of the compressed payload. This
|
||||
property can (and should) be dynamically updated during `data` events.
|
||||
|
||||
The old syntax of Fastify v2 without payload is supported but it is deprecated.
|
||||
|
||||
### Changed hooks behavior ([#2004](https://github.com/fastify/fastify/pull/2004))
|
||||
|
||||
From Fastify v3, the behavior of `onRoute` and `onRegister` hooks will change
|
||||
slightly to support hook encapsulation.
|
||||
|
||||
- `onRoute` - The hook will be called asynchronously. The hook is now inherited
|
||||
when registering a new plugin within the same encapsulation scope. Thus, this
|
||||
hook should be registered _before_ registering any plugins.
|
||||
- `onRegister` - Same as the onRoute hook. The only difference is that now the
|
||||
very first call will no longer be the framework itself, but the first
|
||||
registered plugin.
|
||||
|
||||
### Changed Content Type Parser syntax ([#2286](https://github.com/fastify/fastify/pull/2286))
|
||||
|
||||
In Fastify v3 the content type parsers now have a single signature for parsers.
|
||||
|
||||
The new signatures are `fn(request, payload, done)` or `async fn(request,
|
||||
payload)`. Note that `request` is now a Fastify request, not an
|
||||
`IncomingMessage`. The payload is, by default, a stream. If the `parseAs` option
|
||||
is used in `addContentTypeParser`, then `payload` reflects the option value
|
||||
(string or buffer).
|
||||
|
||||
The old signatures `fn(req, [done])` or `fn(req, payload, [done])` (where `req`
|
||||
is `IncomingMessage`) are still supported but are deprecated.
|
||||
|
||||
### Changed TypeScript support
|
||||
|
||||
The type system was changed in Fastify version 3. The new type system introduces
|
||||
generic constraining and defaulting, plus a new way to define schema types such
|
||||
as a request body, querystring, and more!
|
||||
|
||||
**v2:**
|
||||
|
||||
```ts
|
||||
interface PingQuerystring {
|
||||
foo?: number;
|
||||
}
|
||||
|
||||
interface PingParams {
|
||||
bar?: string;
|
||||
}
|
||||
|
||||
interface PingHeaders {
|
||||
a?: string;
|
||||
}
|
||||
|
||||
interface PingBody {
|
||||
baz?: string;
|
||||
}
|
||||
|
||||
server.get<PingQuerystring, PingParams, PingHeaders, PingBody>(
|
||||
'/ping/:bar',
|
||||
opts,
|
||||
(request, reply) => {
|
||||
console.log(request.query); // This is of type `PingQuerystring`
|
||||
console.log(request.params); // This is of type `PingParams`
|
||||
console.log(request.headers); // This is of type `PingHeaders`
|
||||
console.log(request.body); // This is of type `PingBody`
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
**v3:**
|
||||
|
||||
```ts
|
||||
server.get<{
|
||||
Querystring: PingQuerystring;
|
||||
Params: PingParams;
|
||||
Headers: PingHeaders;
|
||||
Body: PingBody;
|
||||
}>('/ping/:bar', opts, async (request, reply) => {
|
||||
console.log(request.query); // This is of type `PingQuerystring`
|
||||
console.log(request.params); // This is of type `PingParams`
|
||||
console.log(request.headers); // This is of type `PingHeaders`
|
||||
console.log(request.body); // This is of type `PingBody`
|
||||
});
|
||||
```
|
||||
|
||||
### Manage uncaught exception ([#2073](https://github.com/fastify/fastify/pull/2073))
|
||||
|
||||
In sync route handlers, if an error was thrown the server crashed by design
|
||||
without calling the configured `.setErrorHandler()`. This has changed and now
|
||||
all unexpected errors in sync and async routes are managed.
|
||||
|
||||
**v2:**
|
||||
|
||||
```js
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
// this is NOT called
|
||||
reply.send(error)
|
||||
})
|
||||
fastify.get('/', (request, reply) => {
|
||||
const maybeAnArray = request.body.something ? [] : 'I am a string'
|
||||
maybeAnArray.substr() // Thrown: [].substr is not a function and crash the server
|
||||
})
|
||||
```
|
||||
|
||||
**v3:**
|
||||
|
||||
```js
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
// this IS called
|
||||
reply.send(error)
|
||||
})
|
||||
fastify.get('/', (request, reply) => {
|
||||
const maybeAnArray = request.body.something ? [] : 'I am a string'
|
||||
maybeAnArray.substr() // Thrown: [].substr is not a function, but it is handled
|
||||
})
|
||||
```
|
||||
|
||||
## Further additions and improvements
|
||||
|
||||
- Hooks now have consistent context regardless of how they are registered
|
||||
([#2005](https://github.com/fastify/fastify/pull/2005))
|
||||
- Deprecated `request.req` and `reply.res` for
|
||||
[`request.raw`](../Reference/Request.md) and
|
||||
[`reply.raw`](../Reference/Reply.md)
|
||||
([#2008](https://github.com/fastify/fastify/pull/2008))
|
||||
- Removed `modifyCoreObjects` option
|
||||
([#2015](https://github.com/fastify/fastify/pull/2015))
|
||||
- Added [`connectionTimeout`](../Reference/Server.md#factory-connection-timeout)
|
||||
option ([#2086](https://github.com/fastify/fastify/pull/2086))
|
||||
- Added [`keepAliveTimeout`](../Reference/Server.md#factory-keep-alive-timeout)
|
||||
option ([#2086](https://github.com/fastify/fastify/pull/2086))
|
||||
- Added async-await support for [plugins](../Reference/Plugins.md#async-await)
|
||||
([#2093](https://github.com/fastify/fastify/pull/2093))
|
||||
- Added the feature to throw object as error
|
||||
([#2134](https://github.com/fastify/fastify/pull/2134))
|
||||
270
node_modules/fastify/docs/Guides/Migration-Guide-V4.md
generated
vendored
Normal file
270
node_modules/fastify/docs/Guides/Migration-Guide-V4.md
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
# V4 Migration Guide
|
||||
|
||||
This guide is intended to help with migration from Fastify v3 to v4.
|
||||
|
||||
Before migrating to v4, please ensure that you have fixed all deprecation
|
||||
warnings from v3. All v3 deprecations have been removed and they will no longer
|
||||
work after upgrading.
|
||||
|
||||
## Codemods
|
||||
### Fastify v4 Codemods
|
||||
|
||||
To help with the upgrade, we’ve worked with the team at
|
||||
[Codemod](https://github.com/codemod-com/codemod) to
|
||||
publish codemods that will automatically update your code to many of
|
||||
the new APIs and patterns in Fastify v4.
|
||||
|
||||
Run the following
|
||||
[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to
|
||||
automatically update your code to Fastify v4:
|
||||
|
||||
```
|
||||
npx codemod@latest fastify/4/migration-recipe
|
||||
```
|
||||
|
||||
This will run the following codemods:
|
||||
|
||||
- [`fastify/4/remove-app-use`](https://go.codemod.com/fastify-4-remove-app-use)
|
||||
- [`fastify/4/reply-raw-access`](https://go.codemod.com/fastify-4-reply-raw-access)
|
||||
- [`fastify/4/wrap-routes-plugin`](https://go.codemod.com/fastify-4-wrap-routes-plugin)
|
||||
- [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls)
|
||||
|
||||
Each of these codemods automates the changes listed in the v4 migration guide.
|
||||
For a complete list of available Fastify codemods and further details,
|
||||
see [Codemod Registry](https://go.codemod.com/fastify).
|
||||
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Error handling composition ([#3261](https://github.com/fastify/fastify/pull/3261))
|
||||
|
||||
When an error is thrown in an async error handler function, the upper-level
|
||||
error handler is executed if set. If there is no upper-level error handler,
|
||||
the default will be executed as it was previously:
|
||||
|
||||
```js
|
||||
import Fastify from 'fastify'
|
||||
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.register(async fastify => {
|
||||
fastify.setErrorHandler(async err => {
|
||||
console.log(err.message) // 'kaboom'
|
||||
throw new Error('caught')
|
||||
})
|
||||
|
||||
fastify.get('/encapsulated', async () => {
|
||||
throw new Error('kaboom')
|
||||
})
|
||||
})
|
||||
|
||||
fastify.setErrorHandler(async err => {
|
||||
console.log(err.message) // 'caught'
|
||||
throw new Error('wrapped')
|
||||
})
|
||||
|
||||
const res = await fastify.inject('/encapsulated')
|
||||
console.log(res.json().message) // 'wrapped'
|
||||
```
|
||||
|
||||
>The root error handler is Fastify’s generic error handler.
|
||||
>This error handler will use the headers and status code in the Error object,
|
||||
>if they exist. **The headers and status code will not be automatically set if
|
||||
>a custom error handler is provided**.
|
||||
|
||||
### Removed `app.use()` ([#3506](https://github.com/fastify/fastify/pull/3506))
|
||||
|
||||
With v4 of Fastify, `app.use()` has been removed and the use of middleware is
|
||||
no longer supported.
|
||||
|
||||
If you need to use middleware, use
|
||||
[`@fastify/middie`](https://github.com/fastify/middie) or
|
||||
[`@fastify/express`](https://github.com/fastify/fastify-express), which will
|
||||
continue to be maintained.
|
||||
However, it is strongly recommended that you migrate to Fastify's [hooks](../Reference/Hooks.md).
|
||||
|
||||
> **Note**: Codemod remove `app.use()` with:
|
||||
>
|
||||
> ```bash
|
||||
> npx codemod@latest fastify/4/remove-app-use
|
||||
> ```
|
||||
|
||||
### `reply.res` moved to `reply.raw`
|
||||
|
||||
If you previously used the `reply.res` attribute to access the underlying Request
|
||||
object you will now need to use `reply.raw`.
|
||||
|
||||
> **Note**: Codemod `reply.res` to `reply.raw` with:
|
||||
>
|
||||
> ```bash
|
||||
> npx codemod@latest fastify/4/reply-raw-access
|
||||
> ```
|
||||
|
||||
### Need to `return reply` to signal a "fork" of the promise chain
|
||||
|
||||
In some situations, like when a response is sent asynchronously or when you are
|
||||
not explicitly returning a response, you will now need to return the `reply`
|
||||
argument from your router handler.
|
||||
|
||||
### `exposeHeadRoutes` true by default
|
||||
|
||||
Starting with v4, every `GET` route will create a sibling `HEAD` route.
|
||||
You can revert this behavior by setting `exposeHeadRoutes: false` in the server options.
|
||||
|
||||
### Synchronous route definitions ([#2954](https://github.com/fastify/fastify/pull/2954))
|
||||
|
||||
To improve error reporting in route definitions, route registration is now synchronous.
|
||||
As a result, if you specify an `onRoute` hook in a plugin you should now either:
|
||||
* wrap your routes in a plugin (recommended)
|
||||
|
||||
For example, refactor this:
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.addHook('onRoute', (routeOptions) => {
|
||||
const { path, method } = routeOptions;
|
||||
console.log({ path, method });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
fastify.get('/', (request, reply) => { reply.send('hello') });
|
||||
```
|
||||
|
||||
Into this:
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.addHook('onRoute', (routeOptions) => {
|
||||
const { path, method } = routeOptions;
|
||||
console.log({ path, method });
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.get('/', (request, reply) => { reply.send('hello') });
|
||||
done();
|
||||
});
|
||||
```
|
||||
> **Note**: Codemod synchronous route definitions with:
|
||||
>
|
||||
> ```bash
|
||||
> npx codemod@latest fastify/4/wrap-routes-plugin
|
||||
> ```
|
||||
|
||||
* use `await register(...)`
|
||||
|
||||
For example, refactor this:
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.addHook('onRoute', (routeOptions) => {
|
||||
const { path, method } = routeOptions;
|
||||
console.log({ path, method });
|
||||
});
|
||||
done();
|
||||
});
|
||||
```
|
||||
|
||||
Into this:
|
||||
```js
|
||||
await fastify.register((instance, opts, done) => {
|
||||
instance.addHook('onRoute', (routeOptions) => {
|
||||
const { path, method } = routeOptions;
|
||||
console.log({ path, method });
|
||||
});
|
||||
done();
|
||||
});
|
||||
```
|
||||
|
||||
> **Note**: Codemod 'await register(...)' with:
|
||||
>
|
||||
> ```bash
|
||||
> npx codemod@latest fastify/4/await-register-calls
|
||||
> ```
|
||||
|
||||
|
||||
### Optional URL parameters
|
||||
|
||||
If you've already used any implicitly optional parameters, you'll get a 404
|
||||
error when trying to access the route. You will now need to declare the
|
||||
optional parameters explicitly.
|
||||
|
||||
For example, if you have the same route for listing and showing a post,
|
||||
refactor this:
|
||||
```js
|
||||
fastify.get('/posts/:id', (request, reply) => {
|
||||
const { id } = request.params;
|
||||
});
|
||||
```
|
||||
|
||||
Into this:
|
||||
```js
|
||||
fastify.get('/posts/:id?', (request, reply) => {
|
||||
const { id } = request.params;
|
||||
});
|
||||
```
|
||||
|
||||
## Non-Breaking Changes
|
||||
|
||||
### Deprecation of variadic `.listen()` signature
|
||||
|
||||
The [variadic signature](https://en.wikipedia.org/wiki/Variadic_function) of the
|
||||
`fastify.listen()` method is now deprecated.
|
||||
|
||||
Before this release, the following invocations of this method were valid:
|
||||
|
||||
- `fastify.listen(8000)`
|
||||
- `fastify.listen(8000, ‘127.0.0.1’)`
|
||||
- `fastify.listen(8000, ‘127.0.0.1’, 511)`
|
||||
- `fastify.listen(8000, (err) => { if (err) throw err })`
|
||||
- `fastify.listen({ port: 8000 }, (err) => { if (err) throw err })`
|
||||
|
||||
With Fastify v4, only the following invocations are valid:
|
||||
|
||||
- `fastify.listen()`
|
||||
- `fastify.listen({ port: 8000 })`
|
||||
- `fastify.listen({ port: 8000 }, (err) => { if (err) throw err })`
|
||||
|
||||
### Change of schema for multiple types
|
||||
|
||||
Ajv has been upgraded to v8 in Fastify v4, meaning "type" keywords with multiple
|
||||
types other than "null"
|
||||
[are now prohibited](https://ajv.js.org/strict-mode.html#strict-types).
|
||||
|
||||
You may encounter a console warning such as:
|
||||
```sh
|
||||
strict mode: use allowUnionTypes to allow union type keyword at "#/properties/image" (strictTypes)
|
||||
```
|
||||
|
||||
As such, schemas like below will need to be changed from:
|
||||
```js
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
api_key: { type: 'string' },
|
||||
image: { type: ['object', 'array'] }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Into:
|
||||
```js
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
api_key: { type: 'string' },
|
||||
image: {
|
||||
anyOf: [
|
||||
{ type: 'array' },
|
||||
{ type: 'object' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add `reply.trailers` methods ([#3794](https://github.com/fastify/fastify/pull/3794))
|
||||
|
||||
Fastify now supports the [HTTP Trailer] response headers.
|
||||
|
||||
|
||||
[HTTP Trailer]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer
|
||||
718
node_modules/fastify/docs/Guides/Migration-Guide-V5.md
generated
vendored
Normal file
718
node_modules/fastify/docs/Guides/Migration-Guide-V5.md
generated
vendored
Normal file
@@ -0,0 +1,718 @@
|
||||
# V5 Migration Guide
|
||||
|
||||
This guide is intended to help with migration from Fastify v4 to v5.
|
||||
|
||||
Before migrating to v5, please ensure that you have fixed all deprecation
|
||||
warnings from v4. All v4 deprecations have been removed and will no longer
|
||||
work after upgrading.
|
||||
|
||||
## Long Term Support Cycle
|
||||
|
||||
Fastify v5 will only support Node.js v20+. If you are using an older version of
|
||||
Node.js, you will need to upgrade to a newer version to use Fastify v5.
|
||||
|
||||
Fastify v4 is still supported until June 30, 2025. If you are unable to upgrade,
|
||||
you should consider buying an end-of-life support plan from HeroDevs.
|
||||
|
||||
### Why Node.js v20?
|
||||
|
||||
Fastify v5 will only support Node.js v20+ because it has significant differences
|
||||
compared to v18, such as
|
||||
better support for `node:test`. This allows us to provide a better developer
|
||||
experience and streamline maintenance.
|
||||
|
||||
Node.js v18 will exit Long Term Support on April 30, 2025, so you should be planning
|
||||
to upgrade to v20 anyway.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Full JSON Schema is now required for `querystring`, `params` and `body` and response schemas
|
||||
|
||||
Starting with v5, Fastify will require a full JSON schema for the `querystring`,
|
||||
`params` and `body` schema. Note that the `jsonShortHand` option has been
|
||||
removed as well.
|
||||
|
||||
If the default JSON Schema validator is used, you will need
|
||||
to provide a full JSON schema for the
|
||||
`querystring`, `params`, `body`, and `response` schemas,
|
||||
including the `type` property.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route', {
|
||||
schema: {
|
||||
querystring: {
|
||||
name: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}, (req, reply) => {
|
||||
reply.send({ hello: req.query.name });
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route', {
|
||||
schema: {
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' }
|
||||
},
|
||||
required: ['name']
|
||||
}
|
||||
}
|
||||
}, (req, reply) => {
|
||||
reply.send({ hello: req.query.name });
|
||||
});
|
||||
```
|
||||
|
||||
See [#5586](https://github.com/fastify/fastify/pull/5586) for more details
|
||||
|
||||
Note that it's still possible to override the JSON Schema validator to
|
||||
use a different format, such as Zod. This change simplifies that as well.
|
||||
|
||||
This change helps with integration of other tools, such as
|
||||
[`@fastify/swagger`](https://github.com/fastify/fastify-swagger).
|
||||
|
||||
### New logger constructor signature
|
||||
|
||||
In Fastify v4, Fastify accepted the options to build a pino
|
||||
logger in the `logger` option, as well as a custom logger instance.
|
||||
This was the source of significant confusion.
|
||||
|
||||
As a result, the `logger` option will not accept a custom logger anymore in v5.
|
||||
To use a custom logger, you should use the `loggerInstance` option instead:
|
||||
|
||||
```js
|
||||
// v4
|
||||
const logger = require('pino')();
|
||||
const fastify = require('fastify')({
|
||||
logger
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
const loggerInstance = require('pino')();
|
||||
const fastify = require('fastify')({
|
||||
loggerInstance
|
||||
});
|
||||
```
|
||||
|
||||
### `useSemicolonDelimiter` false by default
|
||||
|
||||
Starting with v5, Fastify instances will no longer default to supporting the use
|
||||
of semicolon delimiters in the query string as they did in v4.
|
||||
This is due to it being non-standard
|
||||
behavior and not adhering to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.4).
|
||||
|
||||
If you still wish to use semicolons as delimiters, you can do so by
|
||||
setting `useSemicolonDelimiter: true` in the server configuration.
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
useSemicolonDelimiter: true
|
||||
});
|
||||
```
|
||||
|
||||
### The parameters object no longer has a prototype
|
||||
|
||||
In v4, the `parameters` object had a prototype. This is no longer the case in v5.
|
||||
This means that you can no longer access properties inherited from `Object` on
|
||||
the `parameters` object, such as `toString` or `hasOwnProperty`.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route/:name', (req, reply) => {
|
||||
console.log(req.params.hasOwnProperty('name')); // true
|
||||
return { hello: req.params.name };
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route/:name', (req, reply) => {
|
||||
console.log(Object.hasOwn(req.params, 'name')); // true
|
||||
return { hello: req.params.name };
|
||||
});
|
||||
```
|
||||
|
||||
This increases the security of the application by hardening against prototype
|
||||
pollution attacks.
|
||||
|
||||
### Type Providers now differentiate between validator and serializer schemas
|
||||
|
||||
In v4, the type providers had the same types for both validation and serialization.
|
||||
In v5, the type providers have been split into two separate types: `ValidatorSchema`
|
||||
and `SerializerSchema`.
|
||||
|
||||
[`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
|
||||
and
|
||||
[`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
|
||||
have already been updated: upgrade to the latest version to get the new types.
|
||||
If you are using a custom type provider, you will need to modify it like
|
||||
the following:
|
||||
|
||||
```
|
||||
--- a/index.ts
|
||||
+++ b/index.ts
|
||||
@@ -11,7 +11,8 @@ import {
|
||||
import { FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema } from 'json-schema-to-ts'
|
||||
|
||||
export interface JsonSchemaToTsProvider<
|
||||
Options extends FromSchemaOptions = FromSchemaDefaultOptions
|
||||
> extends FastifyTypeProvider {
|
||||
- output: this['input'] extends JSONSchema ? FromSchema<this['input'], Options> : unknown;
|
||||
+ validator: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
|
||||
+ serializer: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
|
||||
}
|
||||
```
|
||||
|
||||
### Changes to the .listen() method
|
||||
|
||||
The variadic argument signature of the `.listen()` method has been removed.
|
||||
This means that you can no longer call `.listen()` with a variable number of arguments.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.listen(8000)
|
||||
```
|
||||
|
||||
Will become:
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.listen({ port: 8000 })
|
||||
```
|
||||
|
||||
This was already deprecated in v4 as `FSTDEP011`, so you should have already updated
|
||||
your code to use the new signature.
|
||||
|
||||
### Direct return of trailers has been removed
|
||||
|
||||
In v4, you could directly return trailers from a handler.
|
||||
This is no longer possible in v5.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route', (req, reply) => {
|
||||
reply.trailer('ETag', function (reply, payload) {
|
||||
return 'custom-etag'
|
||||
})
|
||||
reply.send('')
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route', (req, reply) => {
|
||||
reply.trailer('ETag', async function (reply, payload) {
|
||||
return 'custom-etag'
|
||||
})
|
||||
reply.send('')
|
||||
});
|
||||
```
|
||||
|
||||
A callback could also be used.
|
||||
This was already deprecated in v4 as `FSTDEP013`,
|
||||
so you should have already updated your code to use the new signature.
|
||||
|
||||
### Streamlined access to route definition
|
||||
|
||||
All deprecated properties relating to accessing the route definition have been removed
|
||||
and are now accessed via `request.routeOptions`.
|
||||
|
||||
| Code | Description | How to solve | Discussion |
|
||||
| ---- | ----------- | ------------ | ---------- |
|
||||
| FSTDEP012 | You are trying to access the deprecated `request.context` property. | Use `request.routeOptions.config` or `request.routeOptions.schema`. | [#4216](https://github.com/fastify/fastify/pull/4216) [#5084](https://github.com/fastify/fastify/pull/5084) |
|
||||
| FSTDEP015 | You are accessing the deprecated `request.routeSchema` property. | Use `request.routeOptions.schema`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
|
||||
| FSTDEP016 | You are accessing the deprecated `request.routeConfig` property. | Use `request.routeOptions.config`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
|
||||
| FSTDEP017 | You are accessing the deprecated `request.routerPath` property. | Use `request.routeOptions.url`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
|
||||
| FSTDEP018 | You are accessing the deprecated `request.routerMethod` property. | Use `request.routeOptions.method`. | [#4470](https://github.com/fastify/fastify/pull/4470) |
|
||||
| FSTDEP019 | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) |
|
||||
|
||||
See [#5616](https://github.com/fastify/fastify/pull/5616) for more information.
|
||||
|
||||
### `reply.redirect()` has a new signature
|
||||
|
||||
The `reply.redirect()` method has a new signature:
|
||||
`reply.redirect(url: string, code?: number)`.
|
||||
|
||||
```js
|
||||
// v4
|
||||
reply.redirect(301, '/new-route')
|
||||
```
|
||||
|
||||
Change it to:
|
||||
|
||||
```js
|
||||
// v5
|
||||
reply.redirect('/new-route', 301)
|
||||
```
|
||||
|
||||
This was already deprecated in v4 as `FSTDEP021`, so you should have already
|
||||
updated your code to use the new signature.
|
||||
|
||||
|
||||
### Modifying `reply.sent` is now forbidden
|
||||
|
||||
In v4, you could modify the `reply.sent` property to prevent the response from
|
||||
being sent.
|
||||
This is no longer possible in v5, use `reply.hijack()` instead.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route', (req, reply) => {
|
||||
reply.sent = true;
|
||||
reply.raw.end('hello');
|
||||
});
|
||||
```
|
||||
|
||||
Change it to:
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route', (req, reply) => {
|
||||
reply.hijack();
|
||||
reply.raw.end('hello');
|
||||
});
|
||||
```
|
||||
|
||||
This was already deprecated in v4 as `FSTDEP010`, so you should have already
|
||||
updated your code to use the new signature.
|
||||
|
||||
### Constraints for route versioning signature changes
|
||||
|
||||
We changed the signature for route versioning constraints.
|
||||
The `version` and `versioning` options have been removed and you should
|
||||
use the `constraints` option instead.
|
||||
|
||||
| Code | Description | How to solve | Discussion |
|
||||
| ---- | ----------- | ------------ | ---------- |
|
||||
| FSTDEP008 | You are using route constraints via the route `{version: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) |
|
||||
| FSTDEP009 | You are using a custom route versioning strategy via the server `{versioning: "..."}` option. | Use `{constraints: {version: "..."}}` option. | [#2682](https://github.com/fastify/fastify/pull/2682) |
|
||||
|
||||
### `HEAD` routes requires to register before `GET` when `exposeHeadRoutes: true`
|
||||
|
||||
We have a more strict requirement for custom `HEAD` route when
|
||||
`exposeHeadRoutes: true`.
|
||||
|
||||
When you provides a custom `HEAD` route, you must either explicitly
|
||||
set `exposeHeadRoutes` to `false`
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route', {
|
||||
|
||||
}, (req, reply) => {
|
||||
reply.send({ hello: 'world' });
|
||||
});
|
||||
|
||||
fastify.head('/route', (req, reply) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route', {
|
||||
exposeHeadRoutes: false
|
||||
}, (req, reply) => {
|
||||
reply.send({ hello: 'world' });
|
||||
});
|
||||
|
||||
fastify.head('/route', (req, reply) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
or place the `HEAD` route before `GET`.
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.head('/route', (req, reply) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
fastify.get('/route', {
|
||||
|
||||
}, (req, reply) => {
|
||||
reply.send({ hello: 'world' });
|
||||
});
|
||||
```
|
||||
|
||||
This was changed in [#2700](https://github.com/fastify/fastify/pull/2700),
|
||||
and the old behavior was deprecated in v4 as `FSTDEP007`.
|
||||
|
||||
### Removed `request.connection`
|
||||
|
||||
The `request.connection` property has been removed in v5.
|
||||
You should use `request.socket` instead.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route', (req, reply) => {
|
||||
console.log(req.connection.remoteAddress);
|
||||
return { hello: 'world' };
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route', (req, reply) => {
|
||||
console.log(req.socket.remoteAddress);
|
||||
return { hello: 'world' };
|
||||
});
|
||||
```
|
||||
|
||||
This was already deprecated in v4 as `FSTDEP05`, so you should
|
||||
have already updated your code to use the new signature.
|
||||
|
||||
### `reply.getResponseTime()` has been removed, use `reply.elapsedTime` instead
|
||||
|
||||
The `reply.getResponseTime()` method has been removed in v5.
|
||||
You should use `reply.elapsedTime` instead.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/route', (req, reply) => {
|
||||
console.log(reply.getResponseTime());
|
||||
return { hello: 'world' };
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.get('/route', (req, reply) => {
|
||||
console.log(reply.elapsedTime);
|
||||
return { hello: 'world' };
|
||||
});
|
||||
```
|
||||
|
||||
This was already deprecated in v4 as `FSTDEP20`, so you should have already
|
||||
updated your code to use the new signature.
|
||||
|
||||
### `fastify.hasRoute()` now matches the behavior of `find-my-way`
|
||||
|
||||
The `fastify.hasRoute()` method now matches the behavior of `find-my-way`
|
||||
and requires the route definition to be passed as it is defined in the route.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.get('/example/:file(^\\d+).png', function (request, reply) { })
|
||||
|
||||
console.log(fastify.hasRoute({
|
||||
method: 'GET',
|
||||
url: '/example/12345.png'
|
||||
)); // true
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
|
||||
fastify.get('/example/:file(^\\d+).png', function (request, reply) { })
|
||||
|
||||
console.log(fastify.hasRoute({
|
||||
method: 'GET',
|
||||
url: '/example/:file(^\\d+).png'
|
||||
)); // true
|
||||
```
|
||||
|
||||
### Removal of some non-standard HTTP methods
|
||||
|
||||
We have removed the following HTTP methods from Fastify:
|
||||
- `PROPFIND`
|
||||
- `PROPPATCH`
|
||||
- `MKCOL`
|
||||
- `COPY`
|
||||
- `MOVE`
|
||||
- `LOCK`
|
||||
- `UNLOCK`
|
||||
- `TRACE`
|
||||
- `SEARCH`
|
||||
|
||||
It's now possible to add them back using the `addHttpMethod` method.
|
||||
|
||||
```js
|
||||
const fastify = Fastify()
|
||||
|
||||
// add a new http method on top of the default ones:
|
||||
fastify.addHttpMethod('REBIND')
|
||||
|
||||
// add a new HTTP method that accepts a body:
|
||||
fastify.addHttpMethod('REBIND', { hasBody: true })
|
||||
|
||||
// reads the HTTP methods list:
|
||||
fastify.supportedMethods // returns a string array
|
||||
```
|
||||
|
||||
See [#5567](https://github.com/fastify/fastify/pull/5567) for more
|
||||
information.
|
||||
|
||||
### Removed support from reference types in decorators
|
||||
|
||||
Decorating Request/Reply with a reference type (`Array`, `Object`)
|
||||
is now prohibited as this reference is shared amongst all requests.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.decorateRequest('myObject', { hello: 'world' });
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.decorateRequest('myObject');
|
||||
fastify.addHook('onRequest', async (req, reply) => {
|
||||
req.myObject = { hello: 'world' };
|
||||
});
|
||||
```
|
||||
|
||||
or turn it into a function
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.decorateRequest('myObject', () => ({ hello: 'world' }));
|
||||
```
|
||||
|
||||
or as a getter
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.decorateRequest('myObject', {
|
||||
getter () {
|
||||
return { hello: 'world' }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
See [#5462](https://github.com/fastify/fastify/pull/5462) for more information.
|
||||
|
||||
### Remove support for DELETE with a `Content-Type: application/json` header and an empty body
|
||||
|
||||
In v4, Fastify allowed `DELETE` requests with a `Content-Type: application/json`
|
||||
header and an empty body was accepted.
|
||||
This is no longer allowed in v5.
|
||||
|
||||
See [#5419](https://github.com/fastify/fastify/pull/5419) for more information.
|
||||
|
||||
### Plugins cannot mix callback/promise API anymore
|
||||
|
||||
In v4, plugins could mix the callback and promise API, leading to unexpected behavior.
|
||||
This is no longer allowed in v5.
|
||||
|
||||
```js
|
||||
// v4
|
||||
fastify.register(async function (instance, opts, done) {
|
||||
done();
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.register(async function (instance, opts) {
|
||||
return;
|
||||
});
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
// v5
|
||||
fastify.register(function (instance, opts, done) {
|
||||
done();
|
||||
});
|
||||
```
|
||||
|
||||
### Requests now have `host`, `hostname`, and `port`, and `hostname` no longer includes the port number
|
||||
|
||||
In Fastify v4, `req.hostname` would include both the hostname and the
|
||||
server’s port, so locally it might have the value `localhost:1234`.
|
||||
With v5, we aligned to the Node.js URL object and now include `host`, `hostname`,
|
||||
and `port` properties. `req.host` has the same value as `req.hostname` did in v4,
|
||||
while `req.hostname` includes the hostname _without_ a port if a port is present,
|
||||
and `req.port` contains just the port number.
|
||||
See [#4766](https://github.com/fastify/fastify/pull/4766)
|
||||
and [#4682](https://github.com/fastify/fastify/issues/4682) for more information.
|
||||
|
||||
### Removes `getDefaultRoute` and `setDefaultRoute` methods
|
||||
|
||||
The `getDefaultRoute` and `setDefaultRoute` methods have been removed in v5.
|
||||
|
||||
See [#4485](https://github.com/fastify/fastify/pull/4485)
|
||||
and [#4480](https://github.com/fastify/fastify/pull/4485)
|
||||
for more information.
|
||||
This was already deprecated in v4 as `FSTDEP014`,
|
||||
so you should have already updated your code.
|
||||
|
||||
## New Features
|
||||
|
||||
### Diagnostic Channel support
|
||||
|
||||
Fastify v5 now supports the [Diagnostics Channel](https://nodejs.org/api/diagnostics_channel.html)
|
||||
API natively
|
||||
and provides a way to trace the lifecycle of a request.
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const diagnostics = require('node:diagnostics_channel')
|
||||
const Fastify = require('fastify')
|
||||
|
||||
diagnostics.subscribe('tracing:fastify.request.handler:start', (msg) => {
|
||||
console.log(msg.route.url) // '/:id'
|
||||
console.log(msg.route.method) // 'GET'
|
||||
})
|
||||
|
||||
diagnostics.subscribe('tracing:fastify.request.handler:end', (msg) => {
|
||||
// msg is the same as the one emitted by the 'tracing:fastify.request.handler:start' channel
|
||||
console.log(msg)
|
||||
})
|
||||
|
||||
diagnostics.subscribe('tracing:fastify.request.handler:error', (msg) => {
|
||||
// in case of error
|
||||
})
|
||||
|
||||
const fastify = Fastify()
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/:id',
|
||||
handler: function (req, reply) {
|
||||
return { hello: 'world' }
|
||||
}
|
||||
})
|
||||
|
||||
fastify.listen({ port: 0 }, async function () {
|
||||
const result = await fetch(fastify.listeningOrigin + '/7')
|
||||
|
||||
t.assert.ok(result.ok)
|
||||
t.assert.strictEqual(response.status, 200)
|
||||
t.assert.deepStrictEqual(await result.json(), { hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
See the [documentation](https://github.com/fastify/fastify/blob/main/docs/Reference/Hooks.md#diagnostics-channel-hooks)
|
||||
and [#5252](https://github.com/fastify/fastify/pull/5252) for additional details.
|
||||
|
||||
## Contributors
|
||||
|
||||
The complete list of contributors, across all of the core
|
||||
Fastify packages, is provided below. Please consider
|
||||
contributing to those that are capable of accepting sponsorships.
|
||||
|
||||
| Contributor | Sponsor Link | Packages |
|
||||
| --- | --- | --- |
|
||||
| 10xLaCroixDrinker | [❤️ sponsor](https://github.com/sponsors/10xLaCroixDrinker) | fastify-cli |
|
||||
| Bram-dc | | fastify; fastify-swagger |
|
||||
| BrianValente | | fastify |
|
||||
| BryanAbate | | fastify-cli |
|
||||
| Cadienvan | [❤️ sponsor](https://github.com/sponsors/Cadienvan) | fastify |
|
||||
| Cangit | | fastify |
|
||||
| Cyberlane | | fastify-elasticsearch |
|
||||
| Eomm | [❤️ sponsor](https://github.com/sponsors/Eomm) | ajv-compiler; fastify; fastify-awilix; fastify-diagnostics-channel; fastify-elasticsearch; fastify-hotwire; fastify-mongodb; fastify-nextjs; fastify-swagger-ui; under-pressure |
|
||||
| EstebanDalelR | [❤️ sponsor](https://github.com/sponsors/EstebanDalelR) | fastify-cli |
|
||||
| Fdawgs | [❤️ sponsor](https://github.com/sponsors/Fdawgs) | aws-lambda-fastify; csrf-protection; env-schema; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-cli; fastify-cookie; fastify-cors; fastify-diagnostics-channel; fastify-elasticsearch; fastify-env; fastify-error; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-hotwire; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-mongodb; fastify-multipart; fastify-mysql; fastify-nextjs; fastify-oauth2; fastify-passport; fastify-plugin; fastify-postgres; fastify-rate-limit; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-sensible; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; forwarded; middie; point-of-view; process-warning; proxy-addr; safe-regex2; secure-json-parse; under-pressure |
|
||||
| Gehbt | | fastify-secure-session |
|
||||
| Gesma94 | | fastify-routes-stats |
|
||||
| H4ad | [❤️ sponsor](https://github.com/sponsors/H4ad) | aws-lambda-fastify |
|
||||
| JohanManders | | fastify-secure-session |
|
||||
| LiviaMedeiros | | fastify |
|
||||
| Momy93 | | fastify-secure-session |
|
||||
| MunifTanjim | | fastify-swagger-ui |
|
||||
| Nanosync | | fastify-secure-session |
|
||||
| RafaelGSS | [❤️ sponsor](https://github.com/sponsors/RafaelGSS) | fastify; under-pressure |
|
||||
| Rantoledo | | fastify |
|
||||
| SMNBLMRR | | fastify |
|
||||
| SimoneDevkt | | fastify-cli |
|
||||
| Tony133 | | fastify |
|
||||
| Uzlopak | [❤️ sponsor](https://github.com/sponsors/Uzlopak) | fastify; fastify-autoload; fastify-diagnostics-channel; fastify-hotwire; fastify-nextjs; fastify-passport; fastify-plugin; fastify-rate-limit; fastify-routes; fastify-static; fastify-swagger-ui; point-of-view; under-pressure |
|
||||
| Zamiell | | fastify-secure-session |
|
||||
| aadito123 | | fastify |
|
||||
| aaroncadillac | [❤️ sponsor](https://github.com/sponsors/aaroncadillac) | fastify |
|
||||
| aarontravass | | fastify |
|
||||
| acro5piano | [❤️ sponsor](https://github.com/sponsors/acro5piano) | fastify-secure-session |
|
||||
| adamward459 | | fastify-cli |
|
||||
| adrai | [❤️ sponsor](https://github.com/sponsors/adrai) | aws-lambda-fastify |
|
||||
| alenap93 | | fastify |
|
||||
| alexandrucancescu | | fastify-nextjs |
|
||||
| anthonyringoet | | aws-lambda-fastify |
|
||||
| arshcodemod | | fastify |
|
||||
| autopulated | | point-of-view |
|
||||
| barbieri | | fastify |
|
||||
| beyazit | | fastify |
|
||||
| big-kahuna-burger | [❤️ sponsor](https://github.com/sponsors/big-kahuna-burger) | fastify-cli; fastify-compress; fastify-helmet |
|
||||
| bilalshareef | | fastify-routes |
|
||||
| blue86321 | | fastify-swagger-ui |
|
||||
| bodinsamuel | | fastify-rate-limit |
|
||||
| busybox11 | [❤️ sponsor](https://github.com/sponsors/busybox11) | fastify |
|
||||
| climba03003 | | csrf-protection; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-compress; fastify-cors; fastify-env; fastify-etag; fastify-flash; fastify-formbody; fastify-http-proxy; fastify-mongodb; fastify-swagger-ui; fastify-url-data; fastify-websocket; middie |
|
||||
| dancastillo | [❤️ sponsor](https://github.com/sponsors/dancastillo) | fastify; fastify-basic-auth; fastify-caching; fastify-circuit-breaker; fastify-cors; fastify-helmet; fastify-passport; fastify-response-validation; fastify-routes; fastify-schedule |
|
||||
| danny-andrews | | fastify-kafka |
|
||||
| davidcralph | [❤️ sponsor](https://github.com/sponsors/davidcralph) | csrf-protection |
|
||||
| davideroffo | | under-pressure |
|
||||
| dhensby | | fastify-cli |
|
||||
| dmkng | | fastify |
|
||||
| domdomegg | | fastify |
|
||||
| faustman | | fastify-cli |
|
||||
| floridemai | | fluent-json-schema |
|
||||
| fox1t | | fastify-autoload |
|
||||
| giuliowaitforitdavide | | fastify |
|
||||
| gunters63 | | fastify-reply-from |
|
||||
| gurgunday | | fastify; fastify-circuit-breaker; fastify-cookie; fastify-multipart; fastify-mysql; fastify-rate-limit; fastify-response-validation; fastify-sensible; fastify-swagger-ui; fluent-json-schema; middie; proxy-addr; safe-regex2; secure-json-parse |
|
||||
| ildella | | under-pressure |
|
||||
| james-kaguru | | fastify |
|
||||
| jcbain | | fastify-http-proxy |
|
||||
| jdhollander | | fastify-swagger-ui |
|
||||
| jean-michelet | | fastify; fastify-autoload; fastify-cli; fastify-mysql; fastify-sensible |
|
||||
| johaven | | fastify-multipart |
|
||||
| jordanebelanger | | fastify-plugin |
|
||||
| jscheffner | | fastify |
|
||||
| jsprw | | fastify-secure-session |
|
||||
| jsumners | [❤️ sponsor](https://github.com/sponsors/jsumners) | ajv-compiler; avvio; csrf-protection; env-schema; fast-json-stringify; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-autoload; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-compress; fastify-cookie; fastify-cors; fastify-env; fastify-error; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-multipart; fastify-mysql; fastify-oauth2; fastify-plugin; fastify-postgres; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-sensible; fastify-static; fastify-swagger; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; forwarded; light-my-request; middie; process-warning; proxy-addr; safe-regex2; secure-json-parse; under-pressure |
|
||||
| karankraina | | under-pressure |
|
||||
| kerolloz | [❤️ sponsor](https://github.com/sponsors/kerolloz) | fastify-jwt |
|
||||
| kibertoad | | fastify-rate-limit |
|
||||
| kukidon-dev | | fastify-passport |
|
||||
| kunal097 | | fastify |
|
||||
| lamweili | | fastify-sensible |
|
||||
| lemonclown | | fastify-mongodb |
|
||||
| liuhanqu | | fastify |
|
||||
| matthyk | | fastify-plugin |
|
||||
| mch-dsk | | fastify |
|
||||
| mcollina | [❤️ sponsor](https://github.com/sponsors/mcollina) | ajv-compiler; avvio; csrf-protection; fastify; fastify-accepts; fastify-accepts-serializer; fastify-auth; fastify-autoload; fastify-awilix; fastify-basic-auth; fastify-bearer-auth; fastify-caching; fastify-circuit-breaker; fastify-cli; fastify-compress; fastify-cookie; fastify-cors; fastify-diagnostics-channel; fastify-elasticsearch; fastify-env; fastify-etag; fastify-express; fastify-flash; fastify-formbody; fastify-funky; fastify-helmet; fastify-http-proxy; fastify-jwt; fastify-kafka; fastify-leveldb; fastify-multipart; fastify-mysql; fastify-oauth2; fastify-passport; fastify-plugin; fastify-postgres; fastify-rate-limit; fastify-redis; fastify-reply-from; fastify-request-context; fastify-response-validation; fastify-routes; fastify-routes-stats; fastify-schedule; fastify-secure-session; fastify-static; fastify-swagger; fastify-swagger-ui; fastify-url-data; fastify-websocket; fastify-zipkin; fluent-json-schema; light-my-request; middie; point-of-view; proxy-addr; secure-json-parse; under-pressure |
|
||||
| melroy89 | [❤️ sponsor](https://github.com/sponsors/melroy89) | under-pressure |
|
||||
| metcoder95 | [❤️ sponsor](https://github.com/sponsors/metcoder95) | fastify-elasticsearch |
|
||||
| mhamann | | fastify-cli |
|
||||
| mihaur | | fastify-elasticsearch |
|
||||
| mikesamm | | fastify |
|
||||
| mikhael-abdallah | | secure-json-parse |
|
||||
| miquelfire | [❤️ sponsor](https://github.com/sponsors/miquelfire) | fastify-routes |
|
||||
| miraries | | fastify-swagger-ui |
|
||||
| mohab-sameh | | fastify |
|
||||
| monish001 | | fastify |
|
||||
| moradebianchetti81 | | fastify |
|
||||
| mouhannad-sh | | aws-lambda-fastify |
|
||||
| multivoltage | | point-of-view |
|
||||
| muya | [❤️ sponsor](https://github.com/sponsors/muya) | under-pressure |
|
||||
| mweberxyz | | point-of-view |
|
||||
| nflaig | | fastify |
|
||||
| nickfla1 | | avvio |
|
||||
| o-az | | process-warning |
|
||||
| ojeytonwilliams | | csrf-protection |
|
||||
| onosendi | | fastify-formbody |
|
||||
| philippviereck | | fastify |
|
||||
| pip77 | | fastify-mongodb |
|
||||
| puskin94 | | fastify |
|
||||
| remidewitte | | fastify |
|
||||
| rozzilla | | fastify |
|
||||
| samialdury | | fastify-cli |
|
||||
| sknetl | | fastify-cors |
|
||||
| sourcecodeit | | fastify |
|
||||
| synapse | | env-schema |
|
||||
| timursaurus | | secure-json-parse |
|
||||
| tlhunter | | fastify |
|
||||
| tlund101 | | fastify-rate-limit |
|
||||
| ttshivers | | fastify-http-proxy |
|
||||
| voxpelli | [❤️ sponsor](https://github.com/sponsors/voxpelli) | fastify |
|
||||
| weixinwu | | fastify-cli |
|
||||
| zetaraku | | fastify-cli |
|
||||
520
node_modules/fastify/docs/Guides/Plugins-Guide.md
generated
vendored
Normal file
520
node_modules/fastify/docs/Guides/Plugins-Guide.md
generated
vendored
Normal file
@@ -0,0 +1,520 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
# The hitchhiker's guide to plugins
|
||||
First of all, `DON'T PANIC`!
|
||||
|
||||
Fastify was built from the beginning to be an extremely modular system. We built
|
||||
a powerful API that allows you to add methods and utilities to Fastify by
|
||||
creating a namespace. We built a system that creates an encapsulation model,
|
||||
which allows you to split your application into multiple microservices at any
|
||||
moment, without the need to refactor the entire application.
|
||||
|
||||
**Table of contents**
|
||||
- [The hitchhiker's guide to plugins](#the-hitchhikers-guide-to-plugins)
|
||||
- [Register](#register)
|
||||
- [Decorators](#decorators)
|
||||
- [Hooks](#hooks)
|
||||
- [How to handle encapsulation and
|
||||
distribution](#how-to-handle-encapsulation-and-distribution)
|
||||
- [ESM support](#esm-support)
|
||||
- [Handle errors](#handle-errors)
|
||||
- [Custom errors](#custom-errors)
|
||||
- [Emit Warnings](#emit-warnings)
|
||||
- [Let's start!](#lets-start)
|
||||
|
||||
## Register
|
||||
<a id="register"></a>
|
||||
|
||||
As with JavaScript, where everything is an object, in Fastify everything is a
|
||||
plugin.
|
||||
|
||||
Your routes, your utilities, and so on are all plugins. To add a new plugin,
|
||||
whatever its functionality may be, in Fastify you have a nice and unique API:
|
||||
[`register`](../Reference/Plugins.md).
|
||||
```js
|
||||
fastify.register(
|
||||
require('./my-plugin'),
|
||||
{ options }
|
||||
)
|
||||
```
|
||||
`register` creates a new Fastify context, which means that if you perform any
|
||||
changes on the Fastify instance, those changes will not be reflected in the
|
||||
context's ancestors. In other words, encapsulation!
|
||||
|
||||
*Why is encapsulation important?*
|
||||
|
||||
Well, let's say you are creating a new disruptive startup, what do you do? You
|
||||
create an API server with all your stuff, everything in the same place, a
|
||||
monolith!
|
||||
|
||||
Ok, you are growing very fast and you want to change your architecture and try
|
||||
microservices. Usually, this implies a huge amount of work, because of cross
|
||||
dependencies and a lack of separation of concerns in the codebase.
|
||||
|
||||
Fastify helps you in that regard. Thanks to the encapsulation model, it will
|
||||
completely avoid cross dependencies and will help you structure your code into
|
||||
cohesive blocks.
|
||||
|
||||
*Let's return to how to correctly use `register`.*
|
||||
|
||||
As you probably know, the required plugins must expose a single function with
|
||||
the following signature
|
||||
```js
|
||||
module.exports = function (fastify, options, done) {}
|
||||
```
|
||||
Where `fastify` is the encapsulated Fastify instance, `options` is the options
|
||||
object, and `done` is the function you **must** call when your plugin is ready.
|
||||
|
||||
Fastify's plugin model is fully reentrant and graph-based, it handles
|
||||
asynchronous code without any problems and it enforces both the load and close
|
||||
order of plugins. *How?* Glad you asked, check out
|
||||
[`avvio`](https://github.com/mcollina/avvio)! Fastify starts loading the plugin
|
||||
__after__ `.listen()`, `.inject()` or `.ready()` are called.
|
||||
|
||||
Inside a plugin you can do whatever you want, register routes and utilities (we
|
||||
will see this in a moment), and do nested registers, just remember to call `done`
|
||||
when everything is set up!
|
||||
```js
|
||||
module.exports = function (fastify, options, done) {
|
||||
fastify.get('/plugin', (request, reply) => {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
done()
|
||||
}
|
||||
```
|
||||
|
||||
Well, now you know how to use the `register` API and how it works, but how do we
|
||||
add new functionality to Fastify and even better, share them with other
|
||||
developers?
|
||||
|
||||
## Decorators
|
||||
<a id="decorators"></a>
|
||||
|
||||
Okay, let's say that you wrote a utility that is so good that you decided to
|
||||
make it available along with all your code. How would you do it? Probably
|
||||
something like the following:
|
||||
```js
|
||||
// your-awesome-utility.js
|
||||
module.exports = function (a, b) {
|
||||
return a + b
|
||||
}
|
||||
```
|
||||
```js
|
||||
const util = require('./your-awesome-utility')
|
||||
console.log(util('that is ', 'awesome'))
|
||||
```
|
||||
Now you will import your utility in every file you need it in. (And do not
|
||||
forget that you will probably also need it in your tests).
|
||||
|
||||
Fastify offers you a more elegant and comfortable way to do this, *decorators*.
|
||||
Creating a decorator is extremely easy, just use the
|
||||
[`decorate`](../Reference/Decorators.md) API:
|
||||
```js
|
||||
fastify.decorate('util', (a, b) => a + b)
|
||||
```
|
||||
Now you can access your utility just by calling `fastify.util` whenever you need
|
||||
it - even inside your test.
|
||||
|
||||
And here starts the magic; do you remember how just now we were talking about
|
||||
encapsulation? Well, using `register` and `decorate` in conjunction enables
|
||||
exactly that, let me show you an example to clarify this:
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.decorate('util', (a, b) => a + b)
|
||||
console.log(instance.util('that is ', 'awesome'))
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.register((instance, opts, done) => {
|
||||
console.log(instance.util('that is ', 'awesome')) // This will throw an error
|
||||
|
||||
done()
|
||||
})
|
||||
```
|
||||
Inside the second register call `instance.util` will throw an error because
|
||||
`util` exists only inside the first register context.
|
||||
|
||||
Let's step back for a moment and dig deeper into this: every time you use the
|
||||
`register` API, a new context is created that avoids the negative situations
|
||||
mentioned above.
|
||||
|
||||
Do note that encapsulation applies to the ancestors and siblings, but not the
|
||||
children.
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.decorate('util', (a, b) => a + b)
|
||||
console.log(instance.util('that is ', 'awesome'))
|
||||
|
||||
fastify.register((instance, opts, done) => {
|
||||
console.log(instance.util('that is ', 'awesome')) // This will not throw an error
|
||||
done()
|
||||
})
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.register((instance, opts, done) => {
|
||||
console.log(instance.util('that is ', 'awesome')) // This will throw an error
|
||||
|
||||
done()
|
||||
})
|
||||
```
|
||||
*Take home message: if you need a utility that is available in every part of
|
||||
your application, take care that it is declared in the root scope of your
|
||||
application. If that is not an option, you can use the `fastify-plugin` utility
|
||||
as described [here](#distribution).*
|
||||
|
||||
`decorate` is not the only API that you can use to extend the server
|
||||
functionality, you can also use `decorateRequest` and `decorateReply`.
|
||||
|
||||
*`decorateRequest` and `decorateReply`? Why do we need them if we already have
|
||||
`decorate`?*
|
||||
|
||||
Good question, we added them to make Fastify more developer-friendly. Let's see
|
||||
an example:
|
||||
```js
|
||||
fastify.decorate('html', payload => {
|
||||
return generateHtml(payload)
|
||||
})
|
||||
|
||||
fastify.get('/html', (request, reply) => {
|
||||
reply
|
||||
.type('text/html')
|
||||
.send(fastify.html({ hello: 'world' }))
|
||||
})
|
||||
```
|
||||
It works, but it could be much better!
|
||||
```js
|
||||
fastify.decorateReply('html', function (payload) {
|
||||
this.type('text/html') // This is the 'Reply' object
|
||||
this.send(generateHtml(payload))
|
||||
})
|
||||
|
||||
fastify.get('/html', (request, reply) => {
|
||||
reply.html({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
Reminder that the `this` keyword is not available on *arrow functions*,
|
||||
so when passing functions in *`decorateReply`* and *`decorateRequest`* as
|
||||
a utility that also needs access to the `request` and `reply` instance,
|
||||
a function that is defined using the `function` keyword is needed instead
|
||||
of an *arrow function expression*.
|
||||
|
||||
You can do the same for the `request` object:
|
||||
```js
|
||||
fastify.decorate('getHeader', (req, header) => {
|
||||
return req.headers[header]
|
||||
})
|
||||
|
||||
fastify.addHook('preHandler', (request, reply, done) => {
|
||||
request.isHappy = fastify.getHeader(request.raw, 'happy')
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.get('/happiness', (request, reply) => {
|
||||
reply.send({ happy: request.isHappy })
|
||||
})
|
||||
```
|
||||
Again, it works, but it can be much better!
|
||||
```js
|
||||
fastify.decorateRequest('setHeader', function (header) {
|
||||
this.isHappy = this.headers[header]
|
||||
})
|
||||
|
||||
fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!
|
||||
|
||||
fastify.addHook('preHandler', (request, reply, done) => {
|
||||
request.setHeader('happy')
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.get('/happiness', (request, reply) => {
|
||||
reply.send({ happy: request.isHappy })
|
||||
})
|
||||
```
|
||||
|
||||
We have seen how to extend server functionality and how to handle the
|
||||
encapsulation system, but what if you need to add a function that must be
|
||||
executed whenever the server "[emits](../Reference/Lifecycle.md)" an
|
||||
event?
|
||||
|
||||
## Hooks
|
||||
<a id="hooks"></a>
|
||||
|
||||
You just built an amazing utility, but now you need to execute that for every
|
||||
request, this is what you will likely do:
|
||||
```js
|
||||
fastify.decorate('util', (request, key, value) => { request[key] = value })
|
||||
|
||||
fastify.get('/plugin1', (request, reply) => {
|
||||
fastify.util(request, 'timestamp', new Date())
|
||||
reply.send(request)
|
||||
})
|
||||
|
||||
fastify.get('/plugin2', (request, reply) => {
|
||||
fastify.util(request, 'timestamp', new Date())
|
||||
reply.send(request)
|
||||
})
|
||||
```
|
||||
I think we all agree that this is terrible. Repeated code, awful readability and
|
||||
it cannot scale.
|
||||
|
||||
So what can you do to avoid this annoying issue? Yes, you are right, use a
|
||||
[hook](../Reference/Hooks.md)!
|
||||
|
||||
```js
|
||||
fastify.decorate('util', (request, key, value) => { request[key] = value })
|
||||
|
||||
fastify.addHook('preHandler', (request, reply, done) => {
|
||||
fastify.util(request, 'timestamp', new Date())
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.get('/plugin1', (request, reply) => {
|
||||
reply.send(request)
|
||||
})
|
||||
|
||||
fastify.get('/plugin2', (request, reply) => {
|
||||
reply.send(request)
|
||||
})
|
||||
```
|
||||
Now for every request, you will run your utility. You can register as many hooks
|
||||
as you need.
|
||||
|
||||
Sometimes you want a hook that should be executed for just a subset of routes,
|
||||
how can you do that? Yep, encapsulation!
|
||||
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.decorate('util', (request, key, value) => { request[key] = value })
|
||||
|
||||
instance.addHook('preHandler', (request, reply, done) => {
|
||||
instance.util(request, 'timestamp', new Date())
|
||||
done()
|
||||
})
|
||||
|
||||
instance.get('/plugin1', (request, reply) => {
|
||||
reply.send(request)
|
||||
})
|
||||
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.get('/plugin2', (request, reply) => {
|
||||
reply.send(request)
|
||||
})
|
||||
```
|
||||
Now your hook will run just for the first route!
|
||||
|
||||
An alternative approach is to make use of the [onRoute hook](../Reference/Hooks.md#onroute)
|
||||
to customize application routes dynamically from inside the plugin. Every time
|
||||
a new route is registered, you can read and modify the route options. For example,
|
||||
based on a [route config option](../Reference/Routes.md#routes-options):
|
||||
|
||||
```js
|
||||
fastify.register((instance, opts, done) => {
|
||||
instance.decorate('util', (request, key, value) => { request[key] = value })
|
||||
|
||||
function handler(request, reply, done) {
|
||||
instance.util(request, 'timestamp', new Date())
|
||||
done()
|
||||
}
|
||||
|
||||
instance.addHook('onRoute', (routeOptions) => {
|
||||
if (routeOptions.config && routeOptions.config.useUtil === true) {
|
||||
// set or add our handler to the route preHandler hook
|
||||
if (!routeOptions.preHandler) {
|
||||
routeOptions.preHandler = [handler]
|
||||
return
|
||||
}
|
||||
if (Array.isArray(routeOptions.preHandler)) {
|
||||
routeOptions.preHandler.push(handler)
|
||||
return
|
||||
}
|
||||
routeOptions.preHandler = [routeOptions.preHandler, handler]
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {
|
||||
reply.send(request)
|
||||
})
|
||||
|
||||
fastify.get('/plugin2', (request, reply) => {
|
||||
reply.send(request)
|
||||
})
|
||||
|
||||
done()
|
||||
})
|
||||
```
|
||||
|
||||
This variant becomes extremely useful if you plan to distribute your plugin, as
|
||||
described in the next section.
|
||||
|
||||
As you probably noticed by now, `request` and `reply` are not the standard
|
||||
Node.js *request* and *response* objects, but Fastify's objects.
|
||||
|
||||
|
||||
## How to handle encapsulation and distribution
|
||||
<a id="distribution"></a>
|
||||
|
||||
Perfect, now you know (almost) all of the tools that you can use to extend
|
||||
Fastify. Nevertheless, chances are that you came across one big issue: how is
|
||||
distribution handled?
|
||||
|
||||
The preferred way to distribute a utility is to wrap all your code inside a
|
||||
`register`. Using this, your plugin can support asynchronous bootstrapping
|
||||
*(since `decorate` is a synchronous API)*, in the case of a database connection
|
||||
for example.
|
||||
|
||||
*Wait, what? Didn't you tell me that `register` creates an encapsulation and
|
||||
that the stuff I create inside will not be available outside?*
|
||||
|
||||
Yes, I said that. However, what I didn't tell you is that you can tell Fastify
|
||||
to avoid this behavior with the
|
||||
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module.
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
const dbClient = require('db-client')
|
||||
|
||||
function dbPlugin (fastify, opts, done) {
|
||||
dbClient.connect(opts.url, (err, conn) => {
|
||||
fastify.decorate('db', conn)
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = fp(dbPlugin)
|
||||
```
|
||||
You can also tell `fastify-plugin` to check the installed version of Fastify, in
|
||||
case you need a specific API.
|
||||
|
||||
As we mentioned earlier, Fastify starts loading its plugins __after__
|
||||
`.listen()`, `.inject()` or `.ready()` are called and as such, __after__ they
|
||||
have been declared. This means that, even though the plugin may inject variables
|
||||
to the external Fastify instance via [`decorate`](../Reference/Decorators.md),
|
||||
the decorated variables will not be accessible before calling `.listen()`,
|
||||
`.inject()`, or `.ready()`.
|
||||
|
||||
In case you rely on a variable injected by a preceding plugin and want to pass
|
||||
that in the `options` argument of `register`, you can do so by using a function
|
||||
instead of an object:
|
||||
```js
|
||||
const fastify = require('fastify')()
|
||||
const fp = require('fastify-plugin')
|
||||
const dbClient = require('db-client')
|
||||
|
||||
function dbPlugin (fastify, opts, done) {
|
||||
dbClient.connect(opts.url, (err, conn) => {
|
||||
fastify.decorate('db', conn)
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
fastify.register(fp(dbPlugin), { url: 'https://example.com' })
|
||||
fastify.register(require('your-plugin'), parent => {
|
||||
return { connection: parent.db, otherOption: 'foo-bar' }
|
||||
})
|
||||
```
|
||||
In the above example, the `parent` variable of the function passed in as the
|
||||
second argument of `register` is a copy of the **external Fastify instance**
|
||||
that the plugin was registered at. This means that we can access any
|
||||
variables that were injected by preceding plugins in the order of declaration.
|
||||
|
||||
## ESM support
|
||||
<a id="esm-support"></a>
|
||||
|
||||
ESM is supported as well from [Node.js
|
||||
`v13.3.0`](https://nodejs.org/api/esm.html) and above! Just export your plugin
|
||||
as an ESM module and you are good to go!
|
||||
|
||||
```js
|
||||
// plugin.mjs
|
||||
async function plugin (fastify, opts) {
|
||||
fastify.get('/', async (req, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
}
|
||||
|
||||
export default plugin
|
||||
```
|
||||
|
||||
## Handle errors
|
||||
<a id="handle-errors"></a>
|
||||
|
||||
One of your plugins may fail during startup. Maybe you expect it
|
||||
and you have a custom logic that will be triggered in that case. How can you
|
||||
implement this? The `after` API is what you need. `after` simply registers a
|
||||
callback that will be executed just after a register, and it can take up to
|
||||
three parameters.
|
||||
|
||||
The callback changes based on the parameters you are giving:
|
||||
|
||||
1. If no parameter is given to the callback and there is an error, that error
|
||||
will be passed to the next error handler.
|
||||
1. If one parameter is given to the callback, that parameter will be the error
|
||||
object.
|
||||
1. If two parameters are given to the callback, the first will be the error
|
||||
object; the second will be the done callback.
|
||||
1. If three parameters are given to the callback, the first will be the error
|
||||
object, the second will be the top-level context unless you have specified
|
||||
both server and override, in that case, the context will be what the override
|
||||
returns, and the third the done callback.
|
||||
|
||||
Let's see how to use it:
|
||||
```js
|
||||
fastify
|
||||
.register(require('./database-connector'))
|
||||
.after(err => {
|
||||
if (err) throw err
|
||||
})
|
||||
```
|
||||
|
||||
## Custom errors
|
||||
<a id="custom-errors"></a>
|
||||
|
||||
If your plugin needs to expose custom errors, you can easily generate consistent
|
||||
error objects across your codebase and plugins with the
|
||||
[`@fastify/error`](https://github.com/fastify/fastify-error) module.
|
||||
|
||||
```js
|
||||
const createError = require('@fastify/error')
|
||||
const CustomError = createError('ERROR_CODE', 'message')
|
||||
console.log(new CustomError())
|
||||
```
|
||||
|
||||
## Emit Warnings
|
||||
<a id="emit-warnings"></a>
|
||||
|
||||
If you want to deprecate an API, or you want to warn the user about a specific
|
||||
use case, you can use the
|
||||
[`process-warning`](https://github.com/fastify/process-warning) module.
|
||||
|
||||
```js
|
||||
const warning = require('process-warning')()
|
||||
warning.create('MyPluginWarning', 'MP_ERROR_CODE', 'message')
|
||||
warning.emit('MP_ERROR_CODE')
|
||||
```
|
||||
|
||||
## Let's start!
|
||||
<a id="start"></a>
|
||||
|
||||
Awesome, now you know everything you need to know about Fastify and its plugin
|
||||
system to start building your first plugin, and please if you do, tell us! We
|
||||
will add it to the [*ecosystem*](https://github.com/fastify/fastify#ecosystem)
|
||||
section of our documentation!
|
||||
|
||||
If you want to see some real-world examples, check out:
|
||||
- [`@fastify/view`](https://github.com/fastify/point-of-view) Templates
|
||||
rendering (*ejs, pug, handlebars, marko*) plugin support for Fastify.
|
||||
- [`@fastify/mongodb`](https://github.com/fastify/fastify-mongodb) Fastify
|
||||
MongoDB connection plugin, with this you can share the same MongoDB connection
|
||||
pool in every part of your server.
|
||||
- [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart
|
||||
support for Fastify
|
||||
- [`@fastify/helmet`](https://github.com/fastify/fastify-helmet) Important
|
||||
security headers for Fastify
|
||||
|
||||
|
||||
*Do you feel like something is missing here? Let us know! :)*
|
||||
383
node_modules/fastify/docs/Guides/Prototype-Poisoning.md
generated
vendored
Normal file
383
node_modules/fastify/docs/Guides/Prototype-Poisoning.md
generated
vendored
Normal file
@@ -0,0 +1,383 @@
|
||||
> The following is an article written by Eran Hammer.
|
||||
> It is reproduced here for posterity [with permission](https://github.com/fastify/fastify/issues/1426#issuecomment-817957913).
|
||||
> It has been reformatted from the original HTML source to Markdown source,
|
||||
> but otherwise remains the same. The original HTML can be retrieved from the
|
||||
> above permission link.
|
||||
|
||||
## History behind prototype poisoning
|
||||
<a id="pp"></a>
|
||||
|
||||
Based on the article by Eran Hammer,the issue is created by a web security bug.
|
||||
It is also a perfect illustration of the efforts required to maintain
|
||||
open-source software and the limitations of existing communication channels.
|
||||
|
||||
But first, if we use a JavaScript framework to process incoming JSON data, take
|
||||
a moment to read up on [Prototype Poisoning](https://medium.com/intrinsic/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)
|
||||
in general, and the specific
|
||||
[technical details](https://github.com/hapijs/hapi/issues/3916) of this issue.
|
||||
This could be a critical issue so, we might need to verify your own code first.
|
||||
It focuses on specific framework however, any solution that uses `JSON.parse()`
|
||||
to process external data is potentially at risk.
|
||||
|
||||
### BOOM
|
||||
<a id="pp-boom"></a>
|
||||
|
||||
The engineering team at Lob (long time generous supporters of my work!) reported
|
||||
a critical security vulnerability they identified in our data validation
|
||||
module — [joi](https://github.com/hapijs/joi). They provided some technical
|
||||
details and a proposed solution.
|
||||
|
||||
The main purpose of a data validation library is to ensure the output fully
|
||||
complies with the rules defined. If it doesn't, validation fails. If it passes,
|
||||
we can blindly trust that the data you are working with is safe. In fact, most
|
||||
developers treat validated input as completely safe from a system integrity
|
||||
perspective which is crucial!
|
||||
|
||||
In our case, the Lob team provided an example where some data was able to escape
|
||||
by the validation logic and pass through undetected. This is the worst possible
|
||||
defect a validation library can have.
|
||||
|
||||
### Prototype in a nutshell
|
||||
<a id="pp-nutshell"></a>
|
||||
|
||||
To understand this, we need to understand how JavaScript works a bit.
|
||||
Every object in JavaScript can have a prototype. It is a set of methods and
|
||||
properties it "inherits" from another object. I have put inherits in quotes
|
||||
because JavaScript isn't really an object-oriented language. It is a prototype-
|
||||
based object-oriented language.
|
||||
|
||||
A long time ago, for a bunch of irrelevant reasons, someone decided that it
|
||||
would be a good idea to use the special property name `__proto__` to access (and
|
||||
set) an object's prototype. This has since been deprecated but nevertheless,
|
||||
fully supported.
|
||||
|
||||
To demonstrate:
|
||||
|
||||
```
|
||||
> const a = { b: 5 };
|
||||
> a.b;
|
||||
5
|
||||
> a.__proto__ = { c: 6 };
|
||||
> a.c;
|
||||
6
|
||||
> a;
|
||||
{ b: 5 }
|
||||
```
|
||||
|
||||
The object doesn't have a `c` property, but its prototype does.
|
||||
When validating the object, the validation library ignores the prototype and
|
||||
only validates the object's own properties. This allows `c` to sneak in via the
|
||||
prototype.
|
||||
|
||||
Another important part is the way `JSON.parse()` — a utility
|
||||
provided by the language to convert JSON formatted text into
|
||||
objects — handles this magic `__proto__` property name.
|
||||
|
||||
```
|
||||
> const text = '{"b": 5, "__proto__": { "c": 6 }}';
|
||||
> const a = JSON.parse(text);
|
||||
> a;
|
||||
{b: 5, __proto__: { c: 6 }}
|
||||
```
|
||||
Notice how `a` has a `__proto__` property. This is not a prototype reference. It
|
||||
is a simple object property key, just like `b`. As we've seen from the first
|
||||
example, we can't actually create this key through assignment as that invokes
|
||||
the prototype magic and sets an actual prototype. `JSON.parse()` however, sets a
|
||||
simple property with that poisonous name.
|
||||
|
||||
By itself, the object created by `JSON.parse()` is perfectly safe. It doesn't
|
||||
have a prototype of its own. It has a seemingly harmless property that just
|
||||
happens to overlap with a built-in JavaScript magic name.
|
||||
|
||||
However, other methods are not as lucky:
|
||||
|
||||
```
|
||||
> const x = Object.assign({}, a);
|
||||
> x;
|
||||
{ b: 5}
|
||||
> x.c;
|
||||
6;
|
||||
```
|
||||
|
||||
If we take the `a` object created earlier by `JSON.parse()` and pass it to the
|
||||
helpful `Object.assign()` method (used to perform a shallow copy of all the top
|
||||
level properties of `a` into the provided empty `{}` object), the magic
|
||||
`__proto__` property "leaks" and becomes `x` 's actual prototype.
|
||||
|
||||
Surprise!
|
||||
|
||||
If you get some external text input and parse it with `JSON.parse()`
|
||||
then perform some simple manipulation of that object (e.g shallow clone and add
|
||||
an `id` ), and pass it to our validation library, it would sneak in undetected
|
||||
via `__proto__`.
|
||||
|
||||
### Oh joi!
|
||||
<a id="pp-oh-joi"></a>
|
||||
|
||||
The first question is, of course, why does the validation module **joi** ignore
|
||||
the prototype and let potentially harmful data through? We asked ourselves the
|
||||
same question and our instant thought was "it was an oversight". A bug - a really
|
||||
big mistake. The joi module should not have allowed this to happen. But…
|
||||
|
||||
While joi is used primarily for validating web input data, it also has a
|
||||
significant user base using it to validate internal objects, some of which have
|
||||
prototypes. The fact that joi ignores the prototype is a helpful "feature". It
|
||||
allows validating the object's own properties while ignoring what could be a
|
||||
very complicated prototype structure (with many methods and literal properties).
|
||||
|
||||
Any solution at the joi level would mean breaking some currently working code.
|
||||
|
||||
### The right thing
|
||||
<a id="pp-right-thing"></a>
|
||||
|
||||
At this point, we were looking at a devastatingly bad security vulnerability.
|
||||
Right up there in the upper echelons of epic security failures. All we knew is
|
||||
that our extremely popular data validation library fails to block harmful data,
|
||||
and that this data is trivial to sneak through. All you need to do is add
|
||||
`__proto__` and some crap to a JSON input and send it on its way to an
|
||||
application built using our tools.
|
||||
|
||||
(Dramatic pause)
|
||||
|
||||
We knew we had to fix joi to prevent this but given the scale of this issue, we
|
||||
had to do it in a way that will put a fix out without drawing too much attention
|
||||
to it — without making it too easy to exploit — at least for a few days until
|
||||
most systems received the update.
|
||||
|
||||
Sneaking a fix isn't the hardest thing to accomplish. If you combine it with an
|
||||
otherwise purposeless refactor of the code, and throw in a few unrelated bug
|
||||
fixes and maybe a cool new feature, you can publish a new version without
|
||||
drawing attention to the real issue being fixed.
|
||||
|
||||
The problem was, the right fix was going to break valid use cases. You see, joi
|
||||
has no way of knowing if you want it to ignore the prototype you set, or block
|
||||
the prototype set by an attacker. A solution that fixes the exploit will break
|
||||
code and breaking code tends to get a lot of attention.
|
||||
|
||||
On the other hand, if we released a proper ([semantically
|
||||
versioned](https://semver.org/)) fix, mark it as a breaking change, and add a
|
||||
new API to explicitly tell joi what you want it to do with the prototype, we
|
||||
will share with the world how to exploit this vulnerability while also making it
|
||||
more time consuming for systems to upgrade (breaking changes never get applied
|
||||
automatically by build tools).
|
||||
|
||||
|
||||
### A detour
|
||||
<a id="pp-detour"></a>
|
||||
|
||||
While the issue at hand was about incoming request payloads, we had to pause and
|
||||
check if it could also impact data coming via the query string, cookies, and
|
||||
headers. Basically, anything that gets serialized into objects from text.
|
||||
|
||||
We quickly confirmed node default query string parser was fine as well as its
|
||||
header parser. I identified one potential issue with base64-encoded JSON cookies
|
||||
as well as the usage of custom query string parsers. We also wrote some tests to
|
||||
confirm that the most popular third-party query string parser —
|
||||
[qs](https://www.npmjs.com/package/qs) — was not vulnerable (it is not!).
|
||||
|
||||
### A development
|
||||
<a id="pp-a-development"></a>
|
||||
|
||||
Throughout this triage, we just assumed that the offending input with its
|
||||
poisoned prototype was coming into joi from hapi, the web framework connecting
|
||||
the hapi.js ecosystem. Further investigation by the Lob team found that the
|
||||
problem was a bit more nuanced.
|
||||
|
||||
hapi used `JSON.parse()` to process incoming data. It first set the result
|
||||
object as a `payload` property of the incoming request, and then passed that
|
||||
same object for validation by joi before being passed to the application
|
||||
business logic for processing. Since `JSON.parse()` doesn't actually leak the
|
||||
`__proto__` property, it would arrive to joi with an invalid key and fail
|
||||
validation.
|
||||
|
||||
However, hapi provides two extension points where the payload data can be
|
||||
inspected (and processed) prior to validation. It is all properly documented and
|
||||
well understood by most developers. The extension points are there to allow you
|
||||
to interact with the raw inputs prior to validation for legitimate (and often
|
||||
security related) reasons.
|
||||
|
||||
If during one of these two extension points, a developer used `Object.assign()`
|
||||
or a similar method on the payload, the `__proto__` property would leak and
|
||||
become an actual prototype.
|
||||
|
||||
### Sigh of relief
|
||||
<a id="pp-sigh-of-relief"></a>
|
||||
|
||||
We were now dealing with a much different level of awfulness. Manipulating the
|
||||
payload object prior to validation is not common which meant this was no longer
|
||||
a doomsday scenario. It was still potentially catastrophic but the exposure
|
||||
dropped from every joi user to some very specific implementations.
|
||||
|
||||
We were no longer looking at a secretive joi release. The issue in joi is still
|
||||
there, but we can now address it properly with a new API and breaking release
|
||||
over the next few weeks.
|
||||
|
||||
We also knew that we can easily mitigate this vulnerability at the framework
|
||||
level since it knows which data is coming from the outside and which is
|
||||
internally generated. The framework is really the only piece that can protect
|
||||
developers against making such unexpected mistakes.
|
||||
|
||||
### Good news, bad news, no news?
|
||||
<a id="pp-good-news-no-news"></a>
|
||||
|
||||
The good news was that this wasn't our fault. It wasn't a bug in hapi or joi. It
|
||||
was only possible through a complex combination of actions that was not unique
|
||||
to hapi or joi. This can happen with every other JavaScript framework. If hapi
|
||||
is broken, then the world is broken.
|
||||
|
||||
Great — we solved the blame game.
|
||||
|
||||
The bad news is that when there is nothing to blame (other than JavaScript
|
||||
itself), it is much harder getting it fixed.
|
||||
|
||||
The first question people ask once a security issue is found is if there is
|
||||
going to be a CVE published. A CVE — Common Vulnerabilities and Exposures — is a
|
||||
[database](https://cve.mitre.org/) of known security issues. It is a critical
|
||||
component of web security. The benefit of publishing a CVE is that it
|
||||
immediately triggers alarms and informs and often breaks automated builds until
|
||||
the issue is resolved.
|
||||
|
||||
But what do we pin this to?
|
||||
|
||||
Probably, nothing. We are still debating whether we should tag some versions of
|
||||
hapi with a warning. The "we" is the node security process. Since we now have a
|
||||
new version of hapi that mitigate the problem by default, it can be considered a
|
||||
fix. But because the fix isn't to a problem in hapi itself, it is not exactly
|
||||
kosher to declare older versions harmful.
|
||||
|
||||
Publishing an advisory on previous versions of hapi for the sole purpose of
|
||||
nudging people into awareness and upgrade is an abuse of the advisory process.
|
||||
I'm personally fine with abusing it for the purpose of improving security but
|
||||
that's not my call. As of this writing, it is still being debated.
|
||||
|
||||
### The solution business
|
||||
<a id="pp-solution-business"></a>
|
||||
|
||||
Mitigating the issue wasn't hard. Making it scale and safe was a bit more
|
||||
involved. Since we knew where harmful data can enter the system, and we knew
|
||||
where we used the problematic `JSON.parse()` we could replace it with a safe
|
||||
implementation.
|
||||
|
||||
One problem. Validating data can be costly and we are now planning on validating
|
||||
every incoming JSON text. The built-in `JSON.parse()` implementation is fast.
|
||||
Really really fast. It is unlikely we can build a replacement that will be more
|
||||
secure and anywhere as fast. Especially not overnight and without introducing
|
||||
new bugs.
|
||||
|
||||
It was obvious we were going to wrap the existing `JSON.parse()` method with
|
||||
some additional logic. We just had to make sure it was not adding too much
|
||||
overhead. This isn't just a performance consideration but also a security one.
|
||||
If we make it easy to slow down a system by simply sending specific data, we
|
||||
make it easy to execute a [DoS
|
||||
attack](https://en.wikipedia.org/wiki/Denial-of-service_attack) at very low
|
||||
cost.
|
||||
|
||||
I came up with a stupidly simple solution: first parse the text using the
|
||||
existing tools. If this didn't fail, scan the original raw text for the
|
||||
offending string "__proto__". Only if we find it, perform an actual scan of the
|
||||
object. We can't block every reference to "__proto__" — sometimes it is
|
||||
perfectly valid value (like when writing about it here and sending this text
|
||||
over to Medium for publication).
|
||||
|
||||
This made the "happy path" practically as fast as before. It just added one
|
||||
function call, a quick text scan (again, very fast built-in implementation), and
|
||||
a conditional return. The solution had negligible impact on the vast majority of
|
||||
data expected to pass through it.
|
||||
|
||||
Next problem. The prototype property doesn't have to be at the top level of the
|
||||
incoming object. It can be nested deep inside. This means we cannot just check
|
||||
for the presence of it at the top level. We need to recursively iterate through
|
||||
the object.
|
||||
|
||||
While recursive functions are a favorite tool, they could be disastrous when
|
||||
writing security-conscious code. You see, recursive function increase the size
|
||||
of the runtime call stack. The more times you loop, the longer the call stack
|
||||
gets. At some point — KABOOM— you reach the maximum length and the process dies.
|
||||
|
||||
If you cannot guarantee the shape of the incoming data, recursive iteration
|
||||
becomes an open threat. An attacker only needs to craft a deep enough object to
|
||||
crash your servers.
|
||||
|
||||
I used a flat loop implementation that is both more memory efficient (less
|
||||
function calls, less passing of temporary arguments) and more secure. I am not
|
||||
pointing this out to brag, but to highlight how basic engineering practices can
|
||||
create (or avoid) security pitfalls.
|
||||
|
||||
### Putting it to the test
|
||||
<a id="pp-putting-to-test"></a>
|
||||
|
||||
I sent the code to two people. First to [Nathan
|
||||
LaFreniere](https://github.com/nlf) to double check the security properties of
|
||||
the solution, and then to [Matteo Collina](https://github.com/mcollina) to
|
||||
review the performance. They are among the very best at what they do and often
|
||||
my go-to people.
|
||||
|
||||
The performance benchmarks confirmed that the "happy path" was practically
|
||||
unaffected. The interesting findings was that removing the offending values was
|
||||
faster then throwing an exception. This raised the question of what should be
|
||||
the default behavior of the new module — which I called
|
||||
[**bourne**](https://github.com/hapijs/bourne) — error or sanitize.
|
||||
|
||||
The concern, again, was exposing the application to a DoS attack. If sending a
|
||||
request with `__proto__` makes things 500% slower, that could be an easy vector
|
||||
to exploit. But after a bit more testing we confirmed that sending **any**
|
||||
invalid JSON text was creating a very similar cost.
|
||||
|
||||
In other words, if you parse JSON, invalid values are going to cost you more,
|
||||
regardless of what makes them invalid. It is also important to remember that
|
||||
while the benchmark showed the significant % cost of scanning suspected objects,
|
||||
the actual cost in CPU time was still in the fraction of milliseconds. Important
|
||||
to note and measure but not actually harmful.
|
||||
|
||||
### hapi ever-after
|
||||
<a id="pp-hapi-ever-after"></a>
|
||||
|
||||
There are a bunch of things to be grateful for.
|
||||
|
||||
The initial disclosure by the Lob team was perfect. It was reported privately,
|
||||
to the right people, with the right information. They followed up with
|
||||
additional findings, and gave us the time and space to resolve it the right way.
|
||||
Lob also was a major sponsor of my work on hapi over the years and that
|
||||
financial support is critical to allow everything else to happen. More on that
|
||||
in a bit.
|
||||
|
||||
Triage was stressful but staffed with the right people. Having folks like
|
||||
[Nicolas Morel](https://github.com/Marsup), Nathan, and Matteo, available and
|
||||
eager to help is critical. This isn't easy to deal with without the pressure,
|
||||
but with it, mistakes are likely without proper team collaboration.
|
||||
|
||||
We got lucky with the actual vulnerability. What started up looking like a
|
||||
catastrophic problem, ended up being a delicate but straight-forward problem to
|
||||
address.
|
||||
|
||||
We also got lucky by having full access to mitigate it at the source — didn't
|
||||
need to send emails to some unknown framework maintainer and hope for a quick
|
||||
answer. hapi's total control over all of its dependencies proved its usefulness
|
||||
and security again. Not using [hapi](https://hapi.dev)? [Maybe you
|
||||
should](https://hueniverse.com/why-you-should-consider-hapi-6163689bd7c2).
|
||||
|
||||
### The after in happy ever-after
|
||||
<a id="pp-after-ever-after"></a>
|
||||
|
||||
This is where I have to take advantage of this incident to reiterate the cost
|
||||
and need for sustainable and secure open source.
|
||||
|
||||
My time alone on this one issue exceeded 20 hours. That's half a working week.
|
||||
It came at the end of a month were I already spent over 30 hours publishing a
|
||||
new major release of hapi (most of the work was done in December). This puts me
|
||||
at a personal financial loss of over $5000 this month (I had to cut back on paid
|
||||
client work to make time for it).
|
||||
|
||||
If you rely on code I maintain, this is exactly the level of support, quality,
|
||||
and commitment you want (and lets be honest — expect). Most of you take it for
|
||||
granted — not just my work but the work of hundreds of other dedicated open
|
||||
source maintainers.
|
||||
|
||||
Because this work is important, I decided to try and make it not just
|
||||
financially sustainable but to grow and expand it. There is so much to improve.
|
||||
This is exactly what motivates me to implement the new [commercial licensing
|
||||
plan](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898)
|
||||
coming in March. You can read more about it
|
||||
[here](https://web.archive.org/web/20190201220503/https://hueniverse.com/on-hapi-licensing-a-preview-f982662ee898).
|
||||
|
||||
|
||||
353
node_modules/fastify/docs/Guides/Recommendations.md
generated
vendored
Normal file
353
node_modules/fastify/docs/Guides/Recommendations.md
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Recommendations
|
||||
|
||||
This document contains a set of recommendations when using Fastify.
|
||||
|
||||
- [Use A Reverse Proxy](#use-a-reverse-proxy)
|
||||
- [HAProxy](#haproxy)
|
||||
- [Nginx](#nginx)
|
||||
- [Kubernetes](#kubernetes)
|
||||
- [Capacity Planning For Production](#capacity)
|
||||
- [Running Multiple Instances](#multiple)
|
||||
|
||||
## Use A Reverse Proxy
|
||||
<a id="reverseproxy"></a>
|
||||
|
||||
Node.js is an early adopter of frameworks shipping with an easy-to-use web
|
||||
server within the standard library. Previously, with languages like PHP or
|
||||
Python, one would need either a web server with specific support for the
|
||||
language or the ability to set up some sort of [CGI gateway][cgi] that works
|
||||
with the language. With Node.js, one can write an application that _directly_
|
||||
handles HTTP requests. As a result, the temptation is to write applications that
|
||||
handle requests for multiple domains, listen on multiple ports (i.e. HTTP _and_
|
||||
HTTPS), and then expose these applications directly to the Internet to handle
|
||||
requests.
|
||||
|
||||
The Fastify team **strongly** considers this to be an anti-pattern and extremely
|
||||
bad practice:
|
||||
|
||||
1. It adds unnecessary complexity to the application by diluting its focus.
|
||||
2. It prevents [horizontal scalability][scale-horiz].
|
||||
|
||||
See [Why should I use a Reverse Proxy if Node.js is Production Ready?][why-use]
|
||||
for a more thorough discussion of why one should opt to use a reverse proxy.
|
||||
|
||||
For a concrete example, consider the situation where:
|
||||
|
||||
1. The app needs multiple instances to handle load.
|
||||
1. The app needs TLS termination.
|
||||
1. The app needs to redirect HTTP requests to HTTPS.
|
||||
1. The app needs to serve multiple domains.
|
||||
1. The app needs to serve static resources, e.g. jpeg files.
|
||||
|
||||
There are many reverse proxy solutions available, and your environment may
|
||||
dictate the solution to use, e.g. AWS or GCP. Given the above, we could use
|
||||
[HAProxy][haproxy] or [Nginx][nginx] to solve these requirements:
|
||||
|
||||
### HAProxy
|
||||
|
||||
```conf
|
||||
# The global section defines base HAProxy (engine) instance configuration.
|
||||
global
|
||||
log /dev/log syslog
|
||||
maxconn 4096
|
||||
chroot /var/lib/haproxy
|
||||
user haproxy
|
||||
group haproxy
|
||||
|
||||
# Set some baseline TLS options.
|
||||
tune.ssl.default-dh-param 2048
|
||||
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
|
||||
ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
|
||||
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11
|
||||
ssl-default-server-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS
|
||||
|
||||
# Each defaults section defines options that will apply to each subsequent
|
||||
# subsection until another defaults section is encountered.
|
||||
defaults
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
retries 3
|
||||
option redispatch
|
||||
# The following option makes haproxy close connections to backend servers
|
||||
# instead of keeping them open. This can alleviate unexpected connection
|
||||
# reset errors in the Node process.
|
||||
option http-server-close
|
||||
maxconn 2000
|
||||
timeout connect 5000
|
||||
timeout client 50000
|
||||
timeout server 50000
|
||||
|
||||
# Enable content compression for specific content types.
|
||||
compression algo gzip
|
||||
compression type text/html text/plain text/css application/javascript
|
||||
|
||||
# A "frontend" section defines a public listener, i.e. an "http server"
|
||||
# as far as clients are concerned.
|
||||
frontend proxy
|
||||
# The IP address here would be the _public_ IP address of the server.
|
||||
# Here, we use a private address as an example.
|
||||
bind 10.0.0.10:80
|
||||
# This redirect rule will redirect all traffic that is not TLS traffic
|
||||
# to the same incoming request URL on the HTTPS port.
|
||||
redirect scheme https code 308 if !{ ssl_fc }
|
||||
# Technically this use_backend directive is useless since we are simply
|
||||
# redirecting all traffic to this frontend to the HTTPS frontend. It is
|
||||
# merely included here for completeness sake.
|
||||
use_backend default-server
|
||||
|
||||
# This frontend defines our primary, TLS only, listener. It is here where
|
||||
# we will define the TLS certificates to expose and how to direct incoming
|
||||
# requests.
|
||||
frontend proxy-ssl
|
||||
# The `/etc/haproxy/certs` directory in this example contains a set of
|
||||
# certificate PEM files that are named for the domains the certificates are
|
||||
# issued for. When HAProxy starts, it will read this directory, load all of
|
||||
# the certificates it finds here, and use SNI matching to apply the correct
|
||||
# certificate to the connection.
|
||||
bind 10.0.0.10:443 ssl crt /etc/haproxy/certs
|
||||
|
||||
# Here we define rule pairs to handle static resources. Any incoming request
|
||||
# that has a path starting with `/static`, e.g.
|
||||
# `https://one.example.com/static/foo.jpeg`, will be redirected to the
|
||||
# static resources server.
|
||||
acl is_static path -i -m beg /static
|
||||
use_backend static-backend if is_static
|
||||
|
||||
# Here we define rule pairs to direct requests to appropriate Node.js
|
||||
# servers based on the requested domain. The `acl` line is used to match
|
||||
# the incoming hostname and define a boolean indicating if it is a match.
|
||||
# The `use_backend` line is used to direct the traffic if the boolean is
|
||||
# true.
|
||||
acl example1 hdr_sub(Host) one.example.com
|
||||
use_backend example1-backend if example1
|
||||
|
||||
acl example2 hdr_sub(Host) two.example.com
|
||||
use_backend example2-backend if example2
|
||||
|
||||
# Finally, we have a fallback redirect if none of the requested hosts
|
||||
# match the above rules.
|
||||
default_backend default-server
|
||||
|
||||
# A "backend" is used to tell HAProxy where to request information for the
|
||||
# proxied request. These sections are where we will define where our Node.js
|
||||
# apps live and any other servers for things like static assets.
|
||||
backend default-server
|
||||
# In this example we are defaulting unmatched domain requests to a single
|
||||
# backend server for all requests. Notice that the backend server does not
|
||||
# have to be serving TLS requests. This is called "TLS termination": the TLS
|
||||
# connection is "terminated" at the reverse proxy.
|
||||
# It is possible to also proxy to backend servers that are themselves serving
|
||||
# requests over TLS, but that is outside the scope of this example.
|
||||
server server1 10.10.10.2:80
|
||||
|
||||
# This backend configuration will serve requests for `https://one.example.com`
|
||||
# by proxying requests to three backend servers in a round-robin manner.
|
||||
backend example1-backend
|
||||
server example1-1 10.10.11.2:80
|
||||
server example1-2 10.10.11.2:80
|
||||
server example2-2 10.10.11.3:80
|
||||
|
||||
# This one serves requests for `https://two.example.com`
|
||||
backend example2-backend
|
||||
server example2-1 10.10.12.2:80
|
||||
server example2-2 10.10.12.2:80
|
||||
server example2-3 10.10.12.3:80
|
||||
|
||||
# This backend handles the static resources requests.
|
||||
backend static-backend
|
||||
server static-server1 10.10.9.2:80
|
||||
```
|
||||
|
||||
[cgi]: https://en.wikipedia.org/wiki/Common_Gateway_Interface
|
||||
[scale-horiz]: https://en.wikipedia.org/wiki/Scalability#Horizontal
|
||||
[why-use]: https://web.archive.org/web/20190821102906/https://medium.com/intrinsic/why-should-i-use-a-reverse-proxy-if-node-js-is-production-ready-5a079408b2ca
|
||||
[haproxy]: https://www.haproxy.org/
|
||||
|
||||
### Nginx
|
||||
|
||||
```nginx
|
||||
# This upstream block groups 3 servers into one named backend fastify_app
|
||||
# with 2 primary servers distributed via round-robin
|
||||
# and one backup which is used when the first 2 are not reachable
|
||||
# This also assumes your fastify servers are listening on port 80.
|
||||
# more info: https://nginx.org/en/docs/http/ngx_http_upstream_module.html
|
||||
upstream fastify_app {
|
||||
server 10.10.11.1:80;
|
||||
server 10.10.11.2:80;
|
||||
server 10.10.11.3:80 backup;
|
||||
}
|
||||
|
||||
# This server block asks NGINX to respond with a redirect when
|
||||
# an incoming request from port 80 (typically plain HTTP), to
|
||||
# the same request URL but with HTTPS as protocol.
|
||||
# This block is optional, and usually used if you are handling
|
||||
# SSL termination in NGINX, like in the example here.
|
||||
server {
|
||||
# default server is a special parameter to ask NGINX
|
||||
# to set this server block to the default for this address/port
|
||||
# which in this case is any address and port 80
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
# With a server_name directive you can also ask NGINX to
|
||||
# use this server block only with matching server name(s)
|
||||
# listen 80;
|
||||
# listen [::]:80;
|
||||
# server_name example.tld;
|
||||
|
||||
# This matches all paths from the request and responds with
|
||||
# the redirect mentioned above.
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# This server block asks NGINX to respond to requests from
|
||||
# port 443 with SSL enabled and accept HTTP/2 connections.
|
||||
# This is where the request is then proxied to the fastify_app
|
||||
# server group via port 3000.
|
||||
server {
|
||||
# This listen directive asks NGINX to accept requests
|
||||
# coming to any address, port 443, with SSL.
|
||||
listen 443 ssl default_server;
|
||||
listen [::]:443 ssl default_server;
|
||||
|
||||
# With a server_name directive you can also ask NGINX to
|
||||
# use this server block only with matching server name(s)
|
||||
# listen 443 ssl;
|
||||
# listen [::]:443 ssl;
|
||||
# server_name example.tld;
|
||||
|
||||
# Enable HTTP/2 support
|
||||
http2 on;
|
||||
|
||||
# Your SSL/TLS certificate (chain) and secret key in the PEM format
|
||||
ssl_certificate /path/to/fullchain.pem;
|
||||
ssl_certificate_key /path/to/private.pem;
|
||||
|
||||
# A generic best practice baseline for based
|
||||
# on https://ssl-config.mozilla.org/
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:FastifyApp:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
# This tells NGINX to only accept TLS 1.3, which should be fine
|
||||
# with most modern browsers including IE 11 with certain updates.
|
||||
# If you want to support older browsers you might need to add
|
||||
# additional fallback protocols.
|
||||
ssl_protocols TLSv1.3;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# This adds a header that tells browsers to only ever use HTTPS
|
||||
# with this server.
|
||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
||||
|
||||
# The following directives are only necessary if you want to
|
||||
# enable OCSP Stapling.
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
ssl_trusted_certificate /path/to/chain.pem;
|
||||
|
||||
# Custom nameserver to resolve upstream server names
|
||||
# resolver 127.0.0.1;
|
||||
|
||||
# This section matches all paths and proxies it to the backend server
|
||||
# group specified above. Note the additional headers that forward
|
||||
# information about the original request. You might want to set
|
||||
# trustProxy to the address of your NGINX server so the X-Forwarded
|
||||
# fields are used by fastify.
|
||||
location / {
|
||||
# more info: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
|
||||
proxy_http_version 1.1;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# This is the directive that proxies requests to the specified server.
|
||||
# If you are using an upstream group, then you do not need to specify a port.
|
||||
# If you are directly proxying to a server e.g.
|
||||
# proxy_pass http://127.0.0.1:3000 then specify a port.
|
||||
proxy_pass http://fastify_app;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[nginx]: https://nginx.org/
|
||||
|
||||
## Kubernetes
|
||||
<a id="kubernetes"></a>
|
||||
|
||||
The `readinessProbe` uses ([by
|
||||
default](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes))
|
||||
the pod IP as the hostname. Fastify listens on `127.0.0.1` by default. The probe
|
||||
will not be able to reach the application in this case. To make it work,
|
||||
the application must listen on `0.0.0.0` or specify a custom hostname in
|
||||
the `readinessProbe.httpGet` spec, as per the following example:
|
||||
|
||||
```yaml
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 4000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 3
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
```
|
||||
|
||||
## Capacity Planning For Production
|
||||
<a id="capacity"></a>
|
||||
|
||||
In order to rightsize the production environment for your Fastify application,
|
||||
it is highly recommended that you perform your own measurements against
|
||||
different configurations of the environment, which may
|
||||
use real CPU cores, virtual CPU cores (vCPU), or even fractional
|
||||
vCPU cores. We will use the term vCPU throughout this
|
||||
recommendation to represent any CPU type.
|
||||
|
||||
Tools such as [k6](https://github.com/grafana/k6)
|
||||
or [autocannon](https://github.com/mcollina/autocannon) can be used for
|
||||
conducting the necessary performance tests.
|
||||
|
||||
That said, you may also consider the following as a rule of thumb:
|
||||
|
||||
* To have the lowest possible latency, 2 vCPU are recommended per app
|
||||
instance (e.g., a k8s pod). The second vCPU will mostly be used by the
|
||||
garbage collector (GC) and libuv threadpool. This will minimize the latency
|
||||
for your users, as well as the memory usage, as the GC will be run more
|
||||
frequently. Also, the main thread won't have to stop to let the GC run.
|
||||
|
||||
* To optimize for throughput (handling the largest possible amount of
|
||||
requests per second per vCPU available), consider using a smaller amount of vCPUs
|
||||
per app instance. It is totally fine to run Node.js applications with 1 vCPU.
|
||||
|
||||
* You may experiment with an even smaller amount of vCPU, which may provide
|
||||
even better throughput in certain use-cases. There are reports of API gateway
|
||||
solutions working well with 100m-200m vCPU in Kubernetes.
|
||||
|
||||
See [Node's Event Loop From the Inside Out ](https://www.youtube.com/watch?v=P9csgxBgaZ8)
|
||||
to understand the workings of Node.js in greater detail and make a
|
||||
better determination about what your specific application needs.
|
||||
|
||||
## Running Multiple Instances
|
||||
<a id="multiple"></a>
|
||||
|
||||
There are several use-cases where running multiple Fastify
|
||||
apps on the same server might be considered. A common example
|
||||
would be exposing metrics endpoints on a separate port,
|
||||
to prevent public access, when using a reverse proxy or an ingress
|
||||
firewall is not an option.
|
||||
|
||||
It is perfectly fine to spin up several Fastify instances within the same
|
||||
Node.js process and run them concurrently, even in high load systems.
|
||||
Each Fastify instance only generates as much load as the traffic it receives,
|
||||
plus the memory used for that Fastify instance.
|
||||
586
node_modules/fastify/docs/Guides/Serverless.md
generated
vendored
Normal file
586
node_modules/fastify/docs/Guides/Serverless.md
generated
vendored
Normal file
@@ -0,0 +1,586 @@
|
||||
<h1 align="center">Serverless</h1>
|
||||
|
||||
Run serverless applications and REST APIs using your existing Fastify
|
||||
application. You may need to make code changes to work on your
|
||||
serverless platform of choice. This document contains a small guide
|
||||
for the most popular serverless providers and how to use
|
||||
Fastify with them.
|
||||
|
||||
#### Should you use Fastify in a serverless platform?
|
||||
|
||||
That is up to you! Keep in mind, functions as a service should always use
|
||||
small and focused functions, but you can also run an entire web application with
|
||||
them. It is important to remember that the bigger the application the slower the
|
||||
initial boot will be. The best way to run Fastify applications in serverless
|
||||
environments is to use platforms like Google Cloud Run, AWS Fargate, Azure
|
||||
Container Instances, and Vercel where the server can handle multiple requests
|
||||
at the same time and make full use of Fastify's features.
|
||||
|
||||
One of the best features of using Fastify in serverless applications is the ease
|
||||
of development. In your local environment, you will always run the Fastify
|
||||
application directly without the need for any additional tools, while the same
|
||||
code will be executed in your serverless platform of choice with an additional
|
||||
snippet of code.
|
||||
|
||||
### Contents
|
||||
|
||||
- [AWS](#aws)
|
||||
- [Genezio](#genezio)
|
||||
- [Google Cloud Functions](#google-cloud-functions)
|
||||
- [Google Firebase Functions](#google-firebase-functions)
|
||||
- [Google Cloud Run](#google-cloud-run)
|
||||
- [Netlify Lambda](#netlify-lambda)
|
||||
- [Vercel](#vercel)
|
||||
|
||||
## AWS
|
||||
|
||||
To integrate with AWS, you have two choices of library:
|
||||
|
||||
- Using [@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify)
|
||||
which only adds API Gateway support but has heavy optimizations for fastify.
|
||||
- Using [@h4ad/serverless-adapter](https://github.com/H4ad/serverless-adapter)
|
||||
which is a little slower as it creates an HTTP request for each AWS event but
|
||||
has support for more AWS services such as: AWS SQS, AWS SNS and others.
|
||||
|
||||
So you can decide which option is best for you, but you can test both libraries.
|
||||
|
||||
### Using @fastify/aws-lambda
|
||||
|
||||
The sample provided allows you to easily build serverless web
|
||||
applications/services and RESTful APIs using Fastify on top of AWS Lambda and
|
||||
Amazon API Gateway.
|
||||
|
||||
#### app.js
|
||||
|
||||
```js
|
||||
const fastify = require('fastify');
|
||||
|
||||
function init() {
|
||||
const app = fastify();
|
||||
app.get('/', (request, reply) => reply.send({ hello: 'world' }));
|
||||
return app;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
// called directly i.e. "node app"
|
||||
init().listen({ port: 3000 }, (err) => {
|
||||
if (err) console.error(err);
|
||||
console.log('server listening on 3000');
|
||||
});
|
||||
} else {
|
||||
// required as a module => executed on aws lambda
|
||||
module.exports = init;
|
||||
}
|
||||
```
|
||||
|
||||
When executed in your lambda function we do not need to listen to a specific
|
||||
port, so we just export the wrapper function `init` in this case. The
|
||||
[`lambda.js`](#lambdajs) file will use this export.
|
||||
|
||||
When you execute your Fastify application like always, i.e. `node app.js` *(the
|
||||
detection for this could be `require.main === module`)*, you can normally listen
|
||||
to your port, so you can still run your Fastify function locally.
|
||||
|
||||
#### lambda.js
|
||||
|
||||
```js
|
||||
const awsLambdaFastify = require('@fastify/aws-lambda')
|
||||
const init = require('./app');
|
||||
|
||||
const proxy = awsLambdaFastify(init())
|
||||
// or
|
||||
// const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] })
|
||||
|
||||
exports.handler = proxy;
|
||||
// or
|
||||
// exports.handler = (event, context, callback) => proxy(event, context, callback);
|
||||
// or
|
||||
// exports.handler = (event, context) => proxy(event, context);
|
||||
// or
|
||||
// exports.handler = async (event, context) => proxy(event, context);
|
||||
```
|
||||
|
||||
We just require
|
||||
[@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify) (make sure
|
||||
you install the dependency `npm i @fastify/aws-lambda`) and our
|
||||
[`app.js`](#appjs) file and call the exported `awsLambdaFastify` function with
|
||||
the `app` as the only parameter. The resulting `proxy` function has the correct
|
||||
signature to be used as a lambda `handler` function. This way all the incoming
|
||||
events (API Gateway requests) are passed to the `proxy` function of
|
||||
[@fastify/aws-lambda](https://github.com/fastify/aws-lambda-fastify).
|
||||
|
||||
#### Example
|
||||
|
||||
An example deployable with
|
||||
[claudia.js](https://claudiajs.com/tutorials/serverless-express.html) can be
|
||||
found
|
||||
[here](https://github.com/claudiajs/example-projects/tree/master/fastify-app-lambda).
|
||||
|
||||
### Considerations
|
||||
|
||||
- API Gateway does not support streams yet, so you are not able to handle
|
||||
[streams](../Reference/Reply.md#streams).
|
||||
- API Gateway has a timeout of 29 seconds, so it is important to provide a reply
|
||||
during this time.
|
||||
|
||||
#### Beyond API Gateway
|
||||
|
||||
If you need to integrate with more AWS services, take a look at
|
||||
[@h4ad/serverless-adapter](https://viniciusl.com.br/serverless-adapter/docs/main/frameworks/fastify)
|
||||
on Fastify to find out how to integrate.
|
||||
|
||||
## Genezio
|
||||
|
||||
[Genezio](https://genezio.com/) is a platform designed to simplify the deployment
|
||||
of serverless applications to the cloud.
|
||||
|
||||
[Genezio has a dedicated guide for deploying a Fastify application.](https://genezio.com/docs/frameworks/fastify/)
|
||||
|
||||
## Google Cloud Functions
|
||||
|
||||
### Creation of Fastify instance
|
||||
```js
|
||||
const fastify = require("fastify")({
|
||||
logger: true // you can also define the level passing an object configuration to logger: {level: 'debug'}
|
||||
});
|
||||
```
|
||||
|
||||
### Add Custom `contentTypeParser` to Fastify instance
|
||||
|
||||
As explained [in issue
|
||||
#946](https://github.com/fastify/fastify/issues/946#issuecomment-766319521),
|
||||
since the Google Cloud Functions platform parses the body of the request before
|
||||
it arrives at the Fastify instance, troubling the body request in case of `POST`
|
||||
and `PATCH` methods, you need to add a custom [`Content-Type
|
||||
Parser`](../Reference/ContentTypeParser.md) to mitigate this behavior.
|
||||
|
||||
```js
|
||||
fastify.addContentTypeParser('application/json', {}, (req, body, done) => {
|
||||
done(null, body.body);
|
||||
});
|
||||
```
|
||||
|
||||
### Define your endpoint (examples)
|
||||
|
||||
A simple `GET` endpoint:
|
||||
```js
|
||||
fastify.get('/', async (request, reply) => {
|
||||
reply.send({message: 'Hello World!'})
|
||||
})
|
||||
```
|
||||
|
||||
Or a more complete `POST` endpoint with schema validation:
|
||||
```js
|
||||
fastify.route({
|
||||
method: 'POST',
|
||||
url: '/hello',
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string'}
|
||||
},
|
||||
required: ['name']
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {type: 'string'}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
handler: async (request, reply) => {
|
||||
const { name } = request.body;
|
||||
reply.code(200).send({
|
||||
message: `Hello ${name}!`
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Implement and export the function
|
||||
|
||||
Final step, implement the function to handle the request and pass it to Fastify
|
||||
by emitting `request` event to `fastify.server`:
|
||||
|
||||
```js
|
||||
const fastifyFunction = async (request, reply) => {
|
||||
await fastify.ready();
|
||||
fastify.server.emit('request', request, reply)
|
||||
}
|
||||
|
||||
exports.fastifyFunction = fastifyFunction;
|
||||
```
|
||||
|
||||
### Local test
|
||||
|
||||
Install [Google Functions Framework for
|
||||
Node.js](https://github.com/GoogleCloudPlatform/functions-framework-nodejs).
|
||||
|
||||
You can install it globally:
|
||||
```bash
|
||||
npm i -g @google-cloud/functions-framework
|
||||
```
|
||||
|
||||
Or as a development library:
|
||||
```bash
|
||||
npm i -D @google-cloud/functions-framework
|
||||
```
|
||||
|
||||
Then you can run your function locally with Functions Framework:
|
||||
```bash
|
||||
npx @google-cloud/functions-framework --target=fastifyFunction
|
||||
```
|
||||
|
||||
Or add this command to your `package.json` scripts:
|
||||
```json
|
||||
"scripts": {
|
||||
...
|
||||
"dev": "npx @google-cloud/functions-framework --target=fastifyFunction"
|
||||
...
|
||||
}
|
||||
```
|
||||
and run it with `npm run dev`.
|
||||
|
||||
### Deploy
|
||||
```bash
|
||||
gcloud functions deploy fastifyFunction \
|
||||
--runtime nodejs14 --trigger-http --region $GOOGLE_REGION --allow-unauthenticated
|
||||
```
|
||||
|
||||
#### Read logs
|
||||
```bash
|
||||
gcloud functions logs read
|
||||
```
|
||||
|
||||
#### Example request to `/hello` endpoint
|
||||
```bash
|
||||
curl -X POST https://$GOOGLE_REGION-$GOOGLE_PROJECT.cloudfunctions.net/me \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{ "name": "Fastify" }'
|
||||
{"message":"Hello Fastify!"}
|
||||
```
|
||||
|
||||
### References
|
||||
- [Google Cloud Functions - Node.js Quickstart
|
||||
](https://cloud.google.com/functions/docs/quickstart-nodejs)
|
||||
|
||||
## Google Firebase Functions
|
||||
|
||||
Follow this guide if you want to use Fastify as the HTTP framework for
|
||||
Firebase Functions instead of the vanilla JavaScript router provided with
|
||||
`onRequest(async (req, res) => {}`.
|
||||
|
||||
### The onRequest() handler
|
||||
|
||||
We use the `onRequest` function to wrap our Fastify application instance.
|
||||
|
||||
As such, we'll begin with importing it to the code:
|
||||
|
||||
```js
|
||||
const { onRequest } = require("firebase-functions/v2/https")
|
||||
```
|
||||
|
||||
### Creation of Fastify instance
|
||||
|
||||
Create the Fastify instance and encapsulate the returned application instance
|
||||
in a function that will register routes, await the server's processing of
|
||||
plugins, hooks, and other settings. As follows:
|
||||
|
||||
```js
|
||||
const fastify = require("fastify")({
|
||||
logger: true,
|
||||
})
|
||||
|
||||
const fastifyApp = async (request, reply) => {
|
||||
await registerRoutes(fastify)
|
||||
await fastify.ready()
|
||||
fastify.server.emit("request", request, reply)
|
||||
}
|
||||
```
|
||||
|
||||
### Add Custom `contentTypeParser` to Fastify instance and define endpoints
|
||||
|
||||
Firebase Function's HTTP layer already parses the request
|
||||
and makes a JSON payload available. It also provides access
|
||||
to the raw body, unparsed, which is useful for calculating
|
||||
request signatures to validate HTTP webhooks.
|
||||
|
||||
Add as follows to the `registerRoutes()` function:
|
||||
|
||||
```js
|
||||
async function registerRoutes (fastify) {
|
||||
fastify.addContentTypeParser("application/json", {}, (req, payload, done) => {
|
||||
// useful to include the request's raw body on the `req` object that will
|
||||
// later be available in your other routes so you can calculate the HMAC
|
||||
// if needed
|
||||
req.rawBody = payload.rawBody
|
||||
|
||||
// payload.body is already the parsed JSON so we just fire the done callback
|
||||
// with it
|
||||
done(null, payload.body)
|
||||
})
|
||||
|
||||
// define your endpoints here...
|
||||
fastify.post("/some-route-here", async (request, reply) => {})
|
||||
|
||||
fastify.get('/', async (request, reply) => {
|
||||
reply.send({message: 'Hello World!'})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Export the function using Firebase onRequest
|
||||
|
||||
Final step is to export the Fastify app instance to Firebase's own
|
||||
`onRequest()` function so it can pass the request and reply objects to it:
|
||||
|
||||
```js
|
||||
exports.app = onRequest(fastifyApp)
|
||||
```
|
||||
|
||||
### Local test
|
||||
|
||||
Install the Firebase tools functions so you can use the CLI:
|
||||
|
||||
```bash
|
||||
npm i -g firebase-tools
|
||||
```
|
||||
|
||||
Then you can run your function locally with:
|
||||
|
||||
```bash
|
||||
firebase emulators:start --only functions
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
Deploy your Firebase Functions with:
|
||||
|
||||
```bash
|
||||
firebase deploy --only functions
|
||||
```
|
||||
|
||||
#### Read logs
|
||||
|
||||
Use the Firebase tools CLI:
|
||||
|
||||
```bash
|
||||
firebase functions:log
|
||||
```
|
||||
|
||||
### References
|
||||
- [Fastify on Firebase Functions](https://github.com/lirantal/lemon-squeezy-firebase-webhook-fastify/blob/main/package.json)
|
||||
- [An article about HTTP webhooks on Firebase Functions and Fastify: A Practical Case Study with Lemon Squeezy](https://lirantal.com/blog/http-webhooks-firebase-functions-fastify-practical-case-study-lemon-squeezy)
|
||||
|
||||
## Google Cloud Run
|
||||
|
||||
Unlike AWS Lambda or Google Cloud Functions, Google Cloud Run is a serverless
|
||||
**container** environment. Its primary purpose is to provide an
|
||||
infrastructure-abstracted environment to run arbitrary containers. As a result,
|
||||
Fastify can be deployed to Google Cloud Run with little-to-no code changes from
|
||||
the way you would write your Fastify app normally.
|
||||
|
||||
*Follow the steps below to deploy to Google Cloud Run if you are already
|
||||
familiar with gcloud or just follow their
|
||||
[quickstart](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)*.
|
||||
|
||||
### Adjust Fastify server
|
||||
|
||||
For Fastify to properly listen for requests within the container, be
|
||||
sure to set the correct port and address:
|
||||
|
||||
```js
|
||||
function build() {
|
||||
const fastify = Fastify({ trustProxy: true })
|
||||
return fastify
|
||||
}
|
||||
|
||||
async function start() {
|
||||
// Google Cloud Run will set this environment variable for you, so
|
||||
// you can also use it to detect if you are running in Cloud Run
|
||||
const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined
|
||||
|
||||
// You must listen on the port Cloud Run provides
|
||||
const port = process.env.PORT || 3000
|
||||
|
||||
// You must listen on all IPV4 addresses in Cloud Run
|
||||
const host = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined
|
||||
|
||||
try {
|
||||
const server = build()
|
||||
const address = await server.listen({ port, host })
|
||||
console.log(`Listening on ${address}`)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
|
||||
if (require.main === module) {
|
||||
start()
|
||||
}
|
||||
```
|
||||
|
||||
### Add a Dockerfile
|
||||
|
||||
You can add any valid `Dockerfile` that packages and runs a Node app. A basic
|
||||
`Dockerfile` can be found in the official [gcloud
|
||||
docs](https://github.com/knative/docs/blob/2d654d1fd6311750cc57187a86253c52f273d924/docs/serving/samples/hello-world/helloworld-nodejs/Dockerfile).
|
||||
|
||||
```Dockerfile
|
||||
# Use the official Node.js 10 image.
|
||||
# https://hub.docker.com/_/node
|
||||
FROM node:10
|
||||
|
||||
# Create and change to the app directory.
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy application dependency manifests to the container image.
|
||||
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
|
||||
# Copying this separately prevents re-running npm install on every code change.
|
||||
COPY package*.json ./
|
||||
|
||||
# Install production dependencies.
|
||||
RUN npm i --production
|
||||
|
||||
# Copy local code to the container image.
|
||||
COPY . .
|
||||
|
||||
# Run the web service on container startup.
|
||||
CMD [ "npm", "start" ]
|
||||
```
|
||||
|
||||
### Add a .dockerignore
|
||||
|
||||
To keep build artifacts out of your container (which keeps it small and improves
|
||||
build times) add a `.dockerignore` file like the one below:
|
||||
|
||||
```dockerignore
|
||||
Dockerfile
|
||||
README.md
|
||||
node_modules
|
||||
npm-debug.log
|
||||
```
|
||||
|
||||
### Submit build
|
||||
|
||||
Next, submit your app to be built into a Docker image by running the following
|
||||
command (replacing `PROJECT-ID` and `APP-NAME` with your GCP project id and an
|
||||
app name):
|
||||
|
||||
```bash
|
||||
gcloud builds submit --tag gcr.io/PROJECT-ID/APP-NAME
|
||||
```
|
||||
|
||||
### Deploy Image
|
||||
|
||||
After your image has built, you can deploy it with the following command:
|
||||
|
||||
```bash
|
||||
gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed
|
||||
```
|
||||
|
||||
Your app will be accessible from the URL GCP provides.
|
||||
|
||||
## netlify-lambda
|
||||
|
||||
First, please perform all preparation steps related to **AWS Lambda**.
|
||||
|
||||
Create a folder called `functions`, then create `server.js` (and your endpoint
|
||||
path will be `server.js`) inside the `functions` folder.
|
||||
|
||||
### functions/server.js
|
||||
|
||||
```js
|
||||
export { handler } from '../lambda.js'; // Change `lambda.js` path to your `lambda.js` path
|
||||
```
|
||||
|
||||
### netlify.toml
|
||||
|
||||
```toml
|
||||
[build]
|
||||
# This will be run the site build
|
||||
command = "npm run build:functions"
|
||||
# This is the directory is publishing to netlify's CDN
|
||||
# and this is directory of your front of your app
|
||||
# publish = "build"
|
||||
# functions build directory
|
||||
functions = "functions-build" # always appends `-build` folder to your `functions` folder for builds
|
||||
```
|
||||
|
||||
### webpack.config.netlify.js
|
||||
|
||||
**Do not forget to add this Webpack config, or else problems may occur**
|
||||
|
||||
```js
|
||||
const nodeExternals = require('webpack-node-externals');
|
||||
const dotenv = require('dotenv-safe');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const env = process.env.NODE_ENV || 'production';
|
||||
const dev = env === 'development';
|
||||
|
||||
if (dev) {
|
||||
dotenv.config({ allowEmptyValues: true });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mode: env,
|
||||
devtool: dev ? 'eval-source-map' : 'none',
|
||||
externals: [nodeExternals()],
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/.netlify': {
|
||||
target: 'http://localhost:9000',
|
||||
pathRewrite: { '^/.netlify/functions': '' }
|
||||
}
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: []
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.APP_ROOT_PATH': JSON.stringify('/'),
|
||||
'process.env.NETLIFY_ENV': true,
|
||||
'process.env.CONTEXT': env
|
||||
})
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### Scripts
|
||||
|
||||
Add this command to your `package.json` *scripts*
|
||||
|
||||
```json
|
||||
"scripts": {
|
||||
...
|
||||
"build:functions": "netlify-lambda build functions --config ./webpack.config.netlify.js"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Then it should work fine.
|
||||
|
||||
## Vercel
|
||||
|
||||
[Vercel](https://vercel.com) fully supports deploying Fastify applications.
|
||||
Additionally, with Vercel's
|
||||
[Fluid compute](https://vercel.com/docs/functions/fluid-compute), you can combine
|
||||
server-like concurrency with the autoscaling properties of traditional
|
||||
serverless functions.
|
||||
|
||||
Get started with the
|
||||
[Fastify Node.js template on Vercel](
|
||||
https://vercel.com/templates/other/fastify-serverless-function).
|
||||
|
||||
[Fluid compute](https://vercel.com/docs/functions/fluid-compute) currently
|
||||
requires an explicit opt-in. Learn more about enabling Fluid compute
|
||||
[here](
|
||||
https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute).
|
||||
246
node_modules/fastify/docs/Guides/Style-Guide.md
generated
vendored
Normal file
246
node_modules/fastify/docs/Guides/Style-Guide.md
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
# Fastify Style Guide
|
||||
|
||||
## Welcome
|
||||
|
||||
Welcome to *Fastify Style Guide*. This guide is here to provide you with a
|
||||
conventional writing style for users writing developer documentation on our Open
|
||||
Source framework. Each topic is precise and well explained to help you write
|
||||
documentation users can easily understand and implement.
|
||||
|
||||
## Who is this guide for?
|
||||
|
||||
This guide is for anyone who loves to build with Fastify or wants to contribute
|
||||
to our documentation. You do not need to be an expert in writing technical
|
||||
documentation. This guide is here to help you.
|
||||
|
||||
Visit the [contribute](https://fastify.dev/contribute) page on our website or
|
||||
read the
|
||||
[CONTRIBUTING.md](https://github.com/fastify/fastify/blob/main/CONTRIBUTING.md)
|
||||
file on GitHub to join our Open Source folks.
|
||||
|
||||
## Before you write
|
||||
|
||||
You need to know the following:
|
||||
|
||||
* JavaScript
|
||||
* Node.js
|
||||
* Git
|
||||
* GitHub
|
||||
* Markdown
|
||||
* HTTP
|
||||
* NPM
|
||||
|
||||
### Consider your Audience
|
||||
|
||||
Before you start writing, think about your audience. In this case, your audience
|
||||
should already know HTTP, JavaScript, NPM, and Node.js. It is necessary to keep
|
||||
your readers in mind because they are the ones consuming your content. You want
|
||||
to give as much useful information as possible. Consider the vital things they
|
||||
need to know and how they can understand them. Use words and references that
|
||||
readers can relate to easily. Ask for feedback from the community, it can help
|
||||
you write better documentation that focuses on the user and what you want to
|
||||
achieve.
|
||||
|
||||
### Get straight to the point
|
||||
|
||||
Give your readers a clear and precise action to take. Start with what is most
|
||||
important. This way, you can help them find what they need faster. Mostly,
|
||||
readers tend to read the first content on a page, and many will not scroll
|
||||
further.
|
||||
|
||||
**Example**
|
||||
|
||||
Less like this: Colons are very important to register a parametric path. It lets
|
||||
the framework know there is a new parameter created. You can place the colon
|
||||
before the parameter name so the parametric path can be created.
|
||||
|
||||
More Like this: To register a parametric path, put a colon before the parameter
|
||||
name. Using a colon lets the framework know it is a parametric path and not a
|
||||
static path.
|
||||
|
||||
### Avoid adding video or image content
|
||||
|
||||
|
||||
Do not add videos or screenshots to the documentation. It is easier to keep
|
||||
under version control. Videos and images will eventually end up becoming
|
||||
outdated as new updates keep developing. Instead, make a referral link or a
|
||||
YouTube video. You can add links by using `[Title](www.websitename.com)` in the
|
||||
markdown.
|
||||
|
||||
**Example**
|
||||
|
||||
```
|
||||
To learn more about hooks, see [Fastify hooks](https://fastify.dev/docs/latest/Reference/Hooks/).
|
||||
```
|
||||
|
||||
Result:
|
||||
>To learn more about hooks, see [Fastify
|
||||
>hooks](https://fastify.dev/docs/latest/Reference/Hooks/).
|
||||
|
||||
|
||||
|
||||
### Avoid plagiarism
|
||||
|
||||
Make sure you avoid copying other people's work. Keep it as original as
|
||||
possible. You can learn from what they have done and reference where it is from
|
||||
if you use a particular quote from their work.
|
||||
|
||||
|
||||
## Word Choice
|
||||
|
||||
There are a few things you need to use and avoid when writing your documentation
|
||||
to improve readability for readers and make documentation neat, direct, and
|
||||
clean.
|
||||
|
||||
|
||||
### When to use the second person "you" as the pronoun
|
||||
|
||||
When writing articles or guides, your content should communicate directly to
|
||||
readers in the second person ("you") addressed form. It is easier to give them
|
||||
direct instruction on what to do on a particular topic. To see an example, visit
|
||||
the [Plugins Guide](./Plugins-Guide.md).
|
||||
|
||||
**Example**
|
||||
|
||||
Less like this: we can use the following plugins.
|
||||
|
||||
More like this: You can use the following plugins.
|
||||
|
||||
> According to [Wikipedia](#), ***You*** is usually a second person pronoun.
|
||||
> Also, used to refer to an indeterminate person, as a more common alternative
|
||||
> to a very formal indefinite pronoun.
|
||||
|
||||
## When to avoid the second person "you" as the pronoun
|
||||
|
||||
One of the main rules of formal writing such as reference documentation, or API
|
||||
documentation, is to avoid the second person ("you") or directly addressing the
|
||||
reader.
|
||||
|
||||
**Example**
|
||||
|
||||
Less like this: You can use the following recommendation as an example.
|
||||
|
||||
More like this: As an example, the following recommendations should be
|
||||
referenced.
|
||||
|
||||
To view a live example, refer to the [Decorators](../Reference/Decorators.md)
|
||||
reference document.
|
||||
|
||||
|
||||
### Avoid using contractions
|
||||
|
||||
Contractions are the shortened version of written and spoken forms of a word,
|
||||
i.e. using "don't" instead of "do not". Avoid contractions to provide a more
|
||||
formal tone.
|
||||
|
||||
### Avoid using condescending terms
|
||||
|
||||
Condescending terms are words that include:
|
||||
|
||||
* Just
|
||||
* Easy
|
||||
* Simply
|
||||
* Basically
|
||||
* Obviously
|
||||
|
||||
The reader may not find it easy to use Fastify's framework and plugins; avoid
|
||||
words that make it sound simple, easy, offensive, or insensitive. Not everyone
|
||||
who reads the documentation has the same level of understanding.
|
||||
|
||||
|
||||
### Starting with a verb
|
||||
|
||||
Mostly start your description with a verb, which makes it simple and precise for
|
||||
the reader to follow. Prefer using present tense because it is easier to read
|
||||
and understand than the past or future tense.
|
||||
|
||||
**Example**
|
||||
|
||||
Less like this: There is a need for Node.js to be installed before you can be
|
||||
able to use Fastify.
|
||||
|
||||
More like this: Install Node.js to make use of Fastify.
|
||||
|
||||
### Grammatical moods
|
||||
|
||||
Grammatical moods are a great way to express your writing. Avoid sounding too
|
||||
bossy while making a direct statement. Know when to switch between indicative,
|
||||
imperative, and subjunctive moods.
|
||||
|
||||
|
||||
**Indicative** - Use when making a factual statement or question.
|
||||
|
||||
Example: Since there is no testing framework available, "Fastify recommends ways
|
||||
to write tests".
|
||||
|
||||
**Imperative** - Use when giving instructions, actions, commands, or when you
|
||||
write your headings.
|
||||
|
||||
Example: Install dependencies before starting development.
|
||||
|
||||
|
||||
**Subjunctive** - Use when making suggestions, hypotheses, or non-factual
|
||||
statements.
|
||||
|
||||
Example: Reading the documentation on our website is recommended to get
|
||||
comprehensive knowledge of the framework.
|
||||
|
||||
### Use **active** voice instead of **passive**
|
||||
|
||||
Using active voice is a more compact and direct way of conveying your
|
||||
documentation.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
Passive: The node dependencies and packages are installed by npm.
|
||||
|
||||
Active: npm installs packages and node dependencies.
|
||||
|
||||
## Writing Style
|
||||
|
||||
### Documentation titles
|
||||
|
||||
When creating a new guide, API, or reference in the `/docs/` directory, use
|
||||
short titles that best describe the topic of your documentation. Name your files
|
||||
in kebab-cases and avoid Raw or camelCase. To learn more about kebab-case you
|
||||
can visit this medium article on [Case
|
||||
Styles](https://medium.com/better-programming/string-case-styles-camel-pascal-snake-and-kebab-case-981407998841).
|
||||
|
||||
**Examples**:
|
||||
|
||||
>`hook-and-plugins.md`,
|
||||
|
||||
`adding-test-plugins.md`,
|
||||
|
||||
`removing-requests.md`.
|
||||
|
||||
### Hyperlinks
|
||||
|
||||
Hyperlinks should have a clear title of what they reference. Here is how your
|
||||
hyperlink should look:
|
||||
|
||||
```MD
|
||||
<!-- More like this -->
|
||||
|
||||
// Add clear & brief description
|
||||
[Fastify Plugins] (https://fastify.dev/docs/latest/Plugins/)
|
||||
|
||||
<!--Less like this -->
|
||||
|
||||
// incomplete description
|
||||
[Fastify] (https://fastify.dev/docs/latest/Plugins/)
|
||||
|
||||
// Adding title in link brackets
|
||||
[](https://fastify.dev/docs/latest/Plugins/ "fastify plugin")
|
||||
|
||||
// Empty title
|
||||
[](https://fastify.dev/docs/latest/Plugins/)
|
||||
|
||||
// Adding links localhost URLs instead of using code strings (``)
|
||||
[http://localhost:3000/](http://localhost:3000/)
|
||||
|
||||
```
|
||||
|
||||
Include in your documentation as many essential references as possible, but
|
||||
avoid having numerous links when writing for beginners to avoid distractions.
|
||||
481
node_modules/fastify/docs/Guides/Testing.md
generated
vendored
Normal file
481
node_modules/fastify/docs/Guides/Testing.md
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
||||
<h1 style="text-align: center;">Fastify</h1>
|
||||
|
||||
# Testing
|
||||
<a id="testing"></a>
|
||||
|
||||
Testing is one of the most important parts of developing an application. Fastify
|
||||
is very flexible when it comes to testing and is compatible with most testing
|
||||
frameworks (such as [Node Test Runner](https://nodejs.org/api/test.html),
|
||||
which is used in the examples below).
|
||||
|
||||
## Application
|
||||
|
||||
Let's `cd` into a fresh directory called 'testing-example' and type `npm init
|
||||
-y` in our terminal.
|
||||
|
||||
Run `npm i fastify && npm i pino-pretty -D`
|
||||
|
||||
### Separating concerns makes testing easy
|
||||
|
||||
First, we are going to separate our application code from our server code:
|
||||
|
||||
**app.js**:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fastify = require('fastify')
|
||||
|
||||
function build(opts={}) {
|
||||
const app = fastify(opts)
|
||||
app.get('/', async function (request, reply) {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
```
|
||||
|
||||
**server.js**:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const server = require('./app')({
|
||||
logger: {
|
||||
level: 'info',
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
server.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) {
|
||||
server.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Benefits of using fastify.inject()
|
||||
|
||||
Fastify comes with built-in support for fake HTTP injection thanks to
|
||||
[`light-my-request`](https://github.com/fastify/light-my-request).
|
||||
|
||||
Before introducing any tests, we will use the `.inject` method to make a fake
|
||||
request to our route:
|
||||
|
||||
**app.test.js**:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const build = require('./app')
|
||||
|
||||
const test = async () => {
|
||||
const app = build()
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/'
|
||||
})
|
||||
|
||||
console.log('status code: ', response.statusCode)
|
||||
console.log('body: ', response.body)
|
||||
}
|
||||
test()
|
||||
```
|
||||
|
||||
First, our code will run inside an asynchronous function, giving us access to
|
||||
async/await.
|
||||
|
||||
`.inject` ensures all registered plugins have booted up and our application is
|
||||
ready to test. Finally, we pass the request method we want to use and a route.
|
||||
Using await we can store the response without a callback.
|
||||
|
||||
|
||||
|
||||
Run the test file in your terminal `node app.test.js`
|
||||
|
||||
```sh
|
||||
status code: 200
|
||||
body: {"hello":"world"}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Testing with HTTP injection
|
||||
|
||||
Now we can replace our `console.log` calls with actual tests!
|
||||
|
||||
In your `package.json` change the "test" script to:
|
||||
|
||||
`"test": "node --test --watch"`
|
||||
|
||||
**app.test.js**:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const { test } = require('node:test')
|
||||
const build = require('./app')
|
||||
|
||||
test('requests the "/" route', async t => {
|
||||
t.plan(1)
|
||||
const app = build()
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/'
|
||||
})
|
||||
t.assert.strictEqual(response.statusCode, 200, 'returns a status code of 200')
|
||||
})
|
||||
```
|
||||
|
||||
Finally, run `npm test` in the terminal and see your test results!
|
||||
|
||||
The `inject` method can do much more than a simple GET request to a URL:
|
||||
```js
|
||||
fastify.inject({
|
||||
method: String,
|
||||
url: String,
|
||||
query: Object,
|
||||
payload: Object,
|
||||
headers: Object,
|
||||
cookies: Object
|
||||
}, (error, response) => {
|
||||
// your tests
|
||||
})
|
||||
```
|
||||
|
||||
`.inject` methods can also be chained by omitting the callback function:
|
||||
|
||||
```js
|
||||
fastify
|
||||
.inject()
|
||||
.get('/')
|
||||
.headers({ foo: 'bar' })
|
||||
.query({ foo: 'bar' })
|
||||
.end((err, res) => { // the .end call will trigger the request
|
||||
console.log(res.payload)
|
||||
})
|
||||
```
|
||||
|
||||
or in the promisified version
|
||||
|
||||
```js
|
||||
fastify
|
||||
.inject({
|
||||
method: String,
|
||||
url: String,
|
||||
query: Object,
|
||||
payload: Object,
|
||||
headers: Object,
|
||||
cookies: Object
|
||||
})
|
||||
.then(response => {
|
||||
// your tests
|
||||
})
|
||||
.catch(err => {
|
||||
// handle error
|
||||
})
|
||||
```
|
||||
|
||||
Async await is supported as well!
|
||||
```js
|
||||
try {
|
||||
const res = await fastify.inject({ method: String, url: String, payload: Object, headers: Object })
|
||||
// your tests
|
||||
} catch (err) {
|
||||
// handle error
|
||||
}
|
||||
```
|
||||
|
||||
#### Another Example:
|
||||
|
||||
**app.js**
|
||||
```js
|
||||
const Fastify = require('fastify')
|
||||
|
||||
function buildFastify () {
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
return fastify
|
||||
}
|
||||
|
||||
module.exports = buildFastify
|
||||
```
|
||||
|
||||
**test.js**
|
||||
```js
|
||||
const { test } = require('node:test')
|
||||
const buildFastify = require('./app')
|
||||
|
||||
test('GET `/` route', t => {
|
||||
t.plan(4)
|
||||
|
||||
const fastify = buildFastify()
|
||||
|
||||
// At the end of your tests it is highly recommended to call `.close()`
|
||||
// to ensure that all connections to external services get closed.
|
||||
t.after(() => fastify.close())
|
||||
|
||||
fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/'
|
||||
}, (err, response) => {
|
||||
t.assert.ifError(err)
|
||||
t.assert.strictEqual(response.statusCode, 200)
|
||||
t.assert.strictEqual(response.headers['content-type'], 'application/json; charset=utf-8')
|
||||
t.assert.deepStrictEqual(response.json(), { hello: 'world' })
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Testing with a running server
|
||||
Fastify can also be tested after starting the server with `fastify.listen()` or
|
||||
after initializing routes and plugins with `fastify.ready()`.
|
||||
|
||||
#### Example:
|
||||
|
||||
Uses **app.js** from the previous example.
|
||||
|
||||
**test-listen.js** (testing with [`undici`](https://www.npmjs.com/package/undici))
|
||||
```js
|
||||
const { test } = require('node:test')
|
||||
const { Client } = require('undici')
|
||||
const buildFastify = require('./app')
|
||||
|
||||
test('should work with undici', async t => {
|
||||
t.plan(2)
|
||||
|
||||
const fastify = buildFastify()
|
||||
|
||||
await fastify.listen()
|
||||
|
||||
const client = new Client(
|
||||
'http://localhost:' + fastify.server.address().port, {
|
||||
keepAliveTimeout: 10,
|
||||
keepAliveMaxTimeout: 10
|
||||
}
|
||||
)
|
||||
|
||||
t.after(() => {
|
||||
fastify.close()
|
||||
client.close()
|
||||
})
|
||||
|
||||
const response = await client.request({ method: 'GET', path: '/' })
|
||||
|
||||
t.assert.strictEqual(await response.body.text(), '{"hello":"world"}')
|
||||
t.assert.strictEqual(response.statusCode, 200)
|
||||
})
|
||||
```
|
||||
|
||||
Alternatively, starting with Node.js 18,
|
||||
[`fetch`](https://nodejs.org/docs/latest-v18.x/api/globals.html#fetch)
|
||||
may be used without requiring any extra dependencies:
|
||||
|
||||
**test-listen.js**
|
||||
```js
|
||||
const { test } = require('node:test')
|
||||
const buildFastify = require('./app')
|
||||
|
||||
test('should work with fetch', async t => {
|
||||
t.plan(3)
|
||||
|
||||
const fastify = buildFastify()
|
||||
|
||||
t.after(() => fastify.close())
|
||||
|
||||
await fastify.listen()
|
||||
|
||||
const response = await fetch(
|
||||
'http://localhost:' + fastify.server.address().port
|
||||
)
|
||||
|
||||
t.assert.strictEqual(response.status, 200)
|
||||
t.assert.strictEqual(
|
||||
response.headers.get('content-type'),
|
||||
'application/json; charset=utf-8'
|
||||
)
|
||||
const jsonResult = await response.json()
|
||||
t.assert.strictEqual(jsonResult.hello, 'world')
|
||||
})
|
||||
```
|
||||
|
||||
**test-ready.js** (testing with
|
||||
[`SuperTest`](https://www.npmjs.com/package/supertest))
|
||||
```js
|
||||
const { test } = require('node:test')
|
||||
const supertest = require('supertest')
|
||||
const buildFastify = require('./app')
|
||||
|
||||
test('GET `/` route', async (t) => {
|
||||
const fastify = buildFastify()
|
||||
|
||||
t.after(() => fastify.close())
|
||||
|
||||
await fastify.ready()
|
||||
|
||||
const response = await supertest(fastify.server)
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', 'application/json; charset=utf-8')
|
||||
t.assert.deepStrictEqual(response.body, { hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
### How to inspect node tests
|
||||
1. Isolate your test by passing the `{only: true}` option
|
||||
```javascript
|
||||
test('should ...', {only: true}, t => ...)
|
||||
```
|
||||
2. Run `node --test`
|
||||
```bash
|
||||
> node --test --test-only --inspect-brk test/<test-file.test.js>
|
||||
```
|
||||
- `--test-only` specifies to run tests with the `only` option enabled
|
||||
- `--inspect-brk` will launch the node debugger
|
||||
3. In VS Code, create and launch a `Node.js: Attach` debug configuration. No
|
||||
modification should be necessary.
|
||||
|
||||
Now you should be able to step through your test file (and the rest of
|
||||
`Fastify`) in your code editor.
|
||||
|
||||
|
||||
|
||||
## Plugins
|
||||
Let's `cd` into a fresh directory called 'testing-plugin-example' and type `npm init
|
||||
-y` in our terminal.
|
||||
|
||||
Run `npm i fastify fastify-plugin`
|
||||
|
||||
**plugin/myFirstPlugin.js**:
|
||||
|
||||
```js
|
||||
const fP = require("fastify-plugin")
|
||||
|
||||
async function myPlugin(fastify, options) {
|
||||
fastify.decorateRequest("helloRequest", "Hello World")
|
||||
fastify.decorate("helloInstance", "Hello Fastify Instance")
|
||||
}
|
||||
|
||||
module.exports = fP(myPlugin)
|
||||
```
|
||||
|
||||
A basic example of a Plugin. See [Plugin Guide](./Plugins-Guide.md)
|
||||
|
||||
**test/myFirstPlugin.test.js**:
|
||||
|
||||
```js
|
||||
const Fastify = require("fastify");
|
||||
const { test } = require("node:test");
|
||||
const myPlugin = require("../plugin/myFirstPlugin");
|
||||
|
||||
test("Test the Plugin Route", async t => {
|
||||
// Create a mock fastify application to test the plugin
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.register(myPlugin)
|
||||
|
||||
// Add an endpoint of your choice
|
||||
fastify.get("/", async (request, reply) => {
|
||||
return ({ message: request.helloRequest })
|
||||
})
|
||||
|
||||
// Use fastify.inject to fake a HTTP Request
|
||||
const fastifyResponse = await fastify.inject({
|
||||
method: "GET",
|
||||
url: "/"
|
||||
})
|
||||
|
||||
console.log('status code: ', fastifyResponse.statusCode)
|
||||
console.log('body: ', fastifyResponse.body)
|
||||
})
|
||||
```
|
||||
Learn more about [```fastify.inject()```](#benefits-of-using-fastifyinject).
|
||||
Run the test file in your terminal `node test/myFirstPlugin.test.js`
|
||||
|
||||
```sh
|
||||
status code: 200
|
||||
body: {"message":"Hello World"}
|
||||
```
|
||||
|
||||
Now we can replace our `console.log` calls with actual tests!
|
||||
|
||||
In your `package.json` change the "test" script to:
|
||||
|
||||
`"test": "node --test --watch"`
|
||||
|
||||
Create the test for the endpoint.
|
||||
|
||||
**test/myFirstPlugin.test.js**:
|
||||
|
||||
```js
|
||||
const Fastify = require("fastify");
|
||||
const { test } = require("node:test");
|
||||
const myPlugin = require("../plugin/myFirstPlugin");
|
||||
|
||||
test("Test the Plugin Route", async t => {
|
||||
// Specifies the number of test
|
||||
t.plan(2)
|
||||
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.register(myPlugin)
|
||||
|
||||
fastify.get("/", async (request, reply) => {
|
||||
return ({ message: request.helloRequest })
|
||||
})
|
||||
|
||||
const fastifyResponse = await fastify.inject({
|
||||
method: "GET",
|
||||
url: "/"
|
||||
})
|
||||
|
||||
t.assert.strictEqual(fastifyResponse.statusCode, 200)
|
||||
t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
|
||||
})
|
||||
```
|
||||
|
||||
Finally, run `npm test` in the terminal and see your test results!
|
||||
|
||||
Test the ```.decorate()``` and ```.decorateRequest()```.
|
||||
|
||||
**test/myFirstPlugin.test.js**:
|
||||
|
||||
```js
|
||||
const Fastify = require("fastify");
|
||||
const { test }= require("node:test");
|
||||
const myPlugin = require("../plugin/myFirstPlugin");
|
||||
|
||||
test("Test the Plugin Route", async t => {
|
||||
t.plan(5)
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.register(myPlugin)
|
||||
|
||||
fastify.get("/", async (request, reply) => {
|
||||
// Testing the fastify decorators
|
||||
t.assert.ifError(request.helloRequest)
|
||||
t.assert.ok(request.helloRequest, "Hello World")
|
||||
t.assert.ok(fastify.helloInstance, "Hello Fastify Instance")
|
||||
return ({ message: request.helloRequest })
|
||||
})
|
||||
|
||||
const fastifyResponse = await fastify.inject({
|
||||
method: "GET",
|
||||
url: "/"
|
||||
})
|
||||
t.assert.strictEqual(fastifyResponse.statusCode, 200)
|
||||
t.assert.deepStrictEqual(JSON.parse(fastifyResponse.body), { message: "Hello World" })
|
||||
})
|
||||
```
|
||||
103
node_modules/fastify/docs/Guides/Write-Plugin.md
generated
vendored
Normal file
103
node_modules/fastify/docs/Guides/Write-Plugin.md
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
<h1 style="text-align: center;">Fastify</h1>
|
||||
|
||||
# How to write a good plugin
|
||||
First, thank you for deciding to write a plugin for Fastify. Fastify is a
|
||||
minimal framework and plugins are its strength, so thank you.
|
||||
|
||||
The core principles of Fastify are performance, low overhead, and providing a
|
||||
good experience to our users. When writing a plugin, it is important to keep
|
||||
these principles in mind. Therefore, in this document, we will analyze what
|
||||
characterizes a quality plugin.
|
||||
|
||||
*Need some inspiration? You can use the label ["plugin
|
||||
suggestion"](https://github.com/fastify/fastify/issues?q=is%3Aissue+is%3Aopen+label%3A%22plugin+suggestion%22)
|
||||
in our issue tracker!*
|
||||
|
||||
## Code
|
||||
Fastify uses different techniques to optimize its code, many of which are
|
||||
documented in our Guides. We highly recommend you read [the hitchhiker's guide
|
||||
to plugins](./Plugins-Guide.md) to discover all the APIs you can use to build
|
||||
your plugin and learn how to use them.
|
||||
|
||||
Do you have a question or need some advice? We are more than happy to help you!
|
||||
Just open an issue in our [help repository](https://github.com/fastify/help).
|
||||
|
||||
Once you submit a plugin to our [ecosystem list](./Ecosystem.md), we will review
|
||||
your code and help you improve it if necessary.
|
||||
|
||||
## Documentation
|
||||
Documentation is extremely important. If your plugin is not well documented we
|
||||
will not accept it to the ecosystem list. Lack of quality documentation makes it
|
||||
more difficult for people to use your plugin, and will likely result in it going
|
||||
unused.
|
||||
|
||||
If you want to see some good examples of how to document a plugin take a look
|
||||
at:
|
||||
- [`@fastify/caching`](https://github.com/fastify/fastify-caching)
|
||||
- [`@fastify/compress`](https://github.com/fastify/fastify-compress)
|
||||
- [`@fastify/cookie`](https://github.com/fastify/fastify-cookie)
|
||||
- [`@fastify/under-pressure`](https://github.com/fastify/under-pressure)
|
||||
- [`@fastify/view`](https://github.com/fastify/point-of-view)
|
||||
|
||||
## License
|
||||
You can license your plugin as you prefer, we do not enforce any kind of
|
||||
license.
|
||||
|
||||
We prefer the [MIT license](https://choosealicense.com/licenses/mit/) because we
|
||||
think it allows more people to use the code freely. For a list of alternative
|
||||
licenses see the [OSI list](https://opensource.org/licenses) or GitHub's
|
||||
[choosealicense.com](https://choosealicense.com/).
|
||||
|
||||
## Examples
|
||||
Always put an example file in your repository. Examples are very helpful for
|
||||
users and give a very fast way to test your plugin. Your users will be grateful.
|
||||
|
||||
## Test
|
||||
A plugin **must** be thoroughly tested to verify that is working properly.
|
||||
|
||||
A plugin without tests will not be accepted to the ecosystem list. A lack of
|
||||
tests does not inspire trust nor guarantee that the code will continue to work
|
||||
among different versions of its dependencies.
|
||||
|
||||
We do not enforce any testing library. We use [`node:test`](https://nodejs.org/api/test.html)
|
||||
since it offers out-of-the-box parallel testing and code coverage, but it is up
|
||||
to you to choose your library of preference.
|
||||
We highly recommend you read the [Plugin Testing](./Testing.md#plugins) to
|
||||
learn about how to test your plugins.
|
||||
|
||||
## Code Linter
|
||||
It is not mandatory, but we highly recommend you use a code linter in your
|
||||
plugin. It will ensure a consistent code style and help you to avoid many
|
||||
errors.
|
||||
|
||||
We use [`standard`](https://standardjs.com/) since it works without the need to
|
||||
configure it and is very easy to integrate into a test suite.
|
||||
|
||||
## Continuous Integration
|
||||
It is not mandatory, but if you release your code as open source, it helps to
|
||||
use Continuous Integration to ensure contributions do not break your plugin and
|
||||
to show that the plugin works as intended. Both
|
||||
[CircleCI](https://circleci.com/) and [GitHub
|
||||
Actions](https://github.com/features/actions) are free for open source projects
|
||||
and easy to set up.
|
||||
|
||||
In addition, you can enable services like [Dependabot](https://github.com/dependabot),
|
||||
which will help you keep your dependencies up to date and discover if a new
|
||||
release of Fastify has some issues with your plugin.
|
||||
|
||||
## Let's start!
|
||||
Awesome, now you know everything you need to know about how to write a good
|
||||
plugin for Fastify! After you have built one (or more!) let us know! We will add
|
||||
it to the [ecosystem](https://github.com/fastify/fastify#ecosystem) section of
|
||||
our documentation!
|
||||
|
||||
If you want to see some real world examples, check out:
|
||||
- [`@fastify/view`](https://github.com/fastify/point-of-view) Templates
|
||||
rendering (*ejs, pug, handlebars, marko*) plugin support for Fastify.
|
||||
- [`@fastify/mongodb`](https://github.com/fastify/fastify-mongodb) Fastify
|
||||
MongoDB connection plugin, with this you can share the same MongoDB connection
|
||||
pool in every part of your server.
|
||||
- [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart
|
||||
support for Fastify.
|
||||
- [`@fastify/helmet`](https://github.com/fastify/fastify-helmet) Important
|
||||
security headers for Fastify.
|
||||
34
node_modules/fastify/docs/Guides/Write-Type-Provider.md
generated
vendored
Normal file
34
node_modules/fastify/docs/Guides/Write-Type-Provider.md
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## How to write your own type provider
|
||||
|
||||
Things to keep in mind when implementing a custom [type provider](../Reference/Type-Providers.md):
|
||||
|
||||
### Type Contravariance
|
||||
|
||||
Whereas exhaustive type narrowing checks normally rely on `never` to represent
|
||||
an unreachable state, reduction in type provider interfaces should only be done
|
||||
up to `unknown`.
|
||||
|
||||
The reasoning is that certain methods of `FastifyInstance` are
|
||||
contravariant on `TypeProvider`, which can lead to TypeScript surfacing
|
||||
assignability issues unless the custom type provider interface is
|
||||
substitutable with `FastifyTypeProviderDefault`.
|
||||
|
||||
For example, `FastifyTypeProviderDefault` will not be assignable to the following:
|
||||
```ts
|
||||
export interface NotSubstitutableTypeProvider extends FastifyTypeProvider {
|
||||
// bad, nothing is assignable to `never` (except for itself)
|
||||
validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
|
||||
serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
|
||||
}
|
||||
```
|
||||
|
||||
Unless changed to:
|
||||
```ts
|
||||
export interface SubstitutableTypeProvider extends FastifyTypeProvider {
|
||||
// good, anything can be assigned to `unknown`
|
||||
validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
|
||||
serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
|
||||
}
|
||||
```
|
||||
255
node_modules/fastify/docs/Reference/ContentTypeParser.md
generated
vendored
Normal file
255
node_modules/fastify/docs/Reference/ContentTypeParser.md
generated
vendored
Normal file
@@ -0,0 +1,255 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## `Content-Type` Parser
|
||||
Fastify natively supports `'application/json'` and `'text/plain'` content types
|
||||
with a default charset of `utf-8`. These default parsers can be changed or
|
||||
removed.
|
||||
|
||||
Unsupported content types will throw an `FST_ERR_CTP_INVALID_MEDIA_TYPE` error.
|
||||
|
||||
To support other content types, use the `addContentTypeParser` API or an
|
||||
existing [plugin](https://fastify.dev/ecosystem/).
|
||||
|
||||
As with other APIs, `addContentTypeParser` is encapsulated in the scope in which
|
||||
it is declared. If declared in the root scope, it is available everywhere; if
|
||||
declared in a plugin, it is available only in that scope and its children.
|
||||
|
||||
Fastify automatically adds the parsed request payload to the [Fastify
|
||||
request](./Request.md) object, accessible via `request.body`.
|
||||
|
||||
Note that for `GET` and `HEAD` requests, the payload is never parsed. For
|
||||
`OPTIONS` and `DELETE` requests, the payload is parsed only if a valid
|
||||
`content-type` header is provided. Unlike `POST`, `PUT`, and `PATCH`, the
|
||||
[catch-all](#catch-all) parser is not executed, and the payload is simply not
|
||||
parsed.
|
||||
|
||||
> ⚠ Warning:
|
||||
> When using regular expressions to detect `Content-Type`, it is important to
|
||||
> ensure proper detection. For example, to match `application/*`, use
|
||||
> `/^application\/([\w-]+);?/` to match the
|
||||
> [essence MIME type](https://mimesniff.spec.whatwg.org/#mime-type-miscellaneous)
|
||||
> only.
|
||||
|
||||
### Usage
|
||||
```js
|
||||
fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
|
||||
jsoffParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
// Handle multiple content types with the same function
|
||||
fastify.addContentTypeParser(['text/xml', 'application/xml'], function (request, payload, done) {
|
||||
xmlParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
// Async is also supported in Node versions >= 8.0.0
|
||||
fastify.addContentTypeParser('application/jsoff', async function (request, payload) {
|
||||
const res = await jsoffParserAsync(payload)
|
||||
|
||||
return res
|
||||
})
|
||||
|
||||
// Handle all content types that matches RegExp
|
||||
fastify.addContentTypeParser(/^image\/([\w-]+);?/, function (request, payload, done) {
|
||||
imageParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
// Can use default JSON/Text parser for different content Types
|
||||
fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore'))
|
||||
```
|
||||
|
||||
Fastify first tries to match a content-type parser with a `string` value before
|
||||
trying to find a matching `RegExp`. For overlapping content types, it starts
|
||||
with the last one configured and ends with the first (last in, first out).
|
||||
To specify a general content type more precisely, first specify the general
|
||||
type, then the specific one, as shown below.
|
||||
|
||||
```js
|
||||
// Here only the second content type parser is called because its value also matches the first one
|
||||
fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} )
|
||||
fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => {} )
|
||||
|
||||
// Here the desired behavior is achieved because fastify first tries to match the
|
||||
// `application/vnd.custom+xml` content type parser
|
||||
fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => {} )
|
||||
fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} )
|
||||
```
|
||||
|
||||
### Using addContentTypeParser with fastify.register
|
||||
When using `addContentTypeParser` with `fastify.register`, avoid `await`
|
||||
when registering routes. Using `await` makes route registration asynchronous,
|
||||
potentially registering routes before `addContentTypeParser` is set.
|
||||
|
||||
#### Correct Usage
|
||||
```js
|
||||
const fastify = require('fastify')();
|
||||
|
||||
|
||||
fastify.register((fastify, opts) => {
|
||||
fastify.addContentTypeParser('application/json', function (request, payload, done) {
|
||||
jsonParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
fastify.get('/hello', async (req, res) => {});
|
||||
});
|
||||
```
|
||||
|
||||
In addition to `addContentTypeParser`, the `hasContentTypeParser`,
|
||||
`removeContentTypeParser`, and `removeAllContentTypeParsers` APIs are available.
|
||||
|
||||
#### hasContentTypeParser
|
||||
|
||||
Use the `hasContentTypeParser` API to check if a specific content type parser
|
||||
exists.
|
||||
|
||||
```js
|
||||
if (!fastify.hasContentTypeParser('application/jsoff')){
|
||||
fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
|
||||
jsoffParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### removeContentTypeParser
|
||||
|
||||
`removeContentTypeParser` can remove a single content type or an array of
|
||||
content types, supporting both `string` and `RegExp`.
|
||||
|
||||
```js
|
||||
fastify.addContentTypeParser('text/xml', function (request, payload, done) {
|
||||
xmlParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
// Removes the both built-in content type parsers so that only the content type parser for text/html is available
|
||||
fastify.removeContentTypeParser(['application/json', 'text/plain'])
|
||||
```
|
||||
|
||||
#### removeAllContentTypeParsers
|
||||
The `removeAllContentTypeParsers` API removes all existing content type parsers
|
||||
eliminating the need to specify each one individually. This API supports
|
||||
encapsulation and is useful for registering a
|
||||
[catch-all content type parser](#catch-all) that should be executed for every
|
||||
content type, ignoring built-in parsers.
|
||||
|
||||
```js
|
||||
fastify.removeAllContentTypeParsers()
|
||||
|
||||
fastify.addContentTypeParser('text/xml', function (request, payload, done) {
|
||||
xmlParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: `function(req, done)` and `async function(req)` are
|
||||
> still supported but deprecated.
|
||||
|
||||
#### Body Parser
|
||||
The request body can be parsed in two ways. First, add a custom content type
|
||||
parser and handle the request stream. Or second, use the `parseAs` option in the
|
||||
`addContentTypeParser` API, specifying `'string'` or `'buffer'`. Fastify will
|
||||
handle the stream, check the [maximum size](./Server.md#factory-body-limit) of
|
||||
the body, and the content length. If the limit is exceeded, the custom parser
|
||||
will not be invoked.
|
||||
```js
|
||||
fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
|
||||
try {
|
||||
const json = JSON.parse(body)
|
||||
done(null, json)
|
||||
} catch (err) {
|
||||
err.statusCode = 400
|
||||
done(err, undefined)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See
|
||||
[`example/parser.js`](https://github.com/fastify/fastify/blob/main/examples/parser.js)
|
||||
for an example.
|
||||
|
||||
##### Custom Parser Options
|
||||
+ `parseAs` (string): `'string'` or `'buffer'` to designate how the incoming
|
||||
data should be collected. Default: `'buffer'`.
|
||||
+ `bodyLimit` (number): The maximum payload size, in bytes, that the custom
|
||||
parser will accept. Defaults to the global body limit passed to the [`Fastify
|
||||
factory function`](./Server.md#bodylimit).
|
||||
|
||||
#### Catch-All
|
||||
To catch all requests regardless of content type, use the `'*'` content type:
|
||||
```js
|
||||
fastify.addContentTypeParser('*', function (request, payload, done) {
|
||||
let data = ''
|
||||
payload.on('data', chunk => { data += chunk })
|
||||
payload.on('end', () => {
|
||||
done(null, data)
|
||||
})
|
||||
})
|
||||
```
|
||||
All requests without a corresponding content type parser will be handled by
|
||||
this function.
|
||||
|
||||
This is also useful for piping the request stream. Define a content parser like:
|
||||
|
||||
```js
|
||||
fastify.addContentTypeParser('*', function (request, payload, done) {
|
||||
done()
|
||||
})
|
||||
```
|
||||
|
||||
And then access the core HTTP request directly for piping:
|
||||
|
||||
```js
|
||||
app.post('/hello', (request, reply) => {
|
||||
reply.send(request.raw)
|
||||
})
|
||||
```
|
||||
|
||||
Here is a complete example that logs incoming [json
|
||||
line](https://jsonlines.org/) objects:
|
||||
|
||||
```js
|
||||
const split2 = require('split2')
|
||||
const pump = require('pump')
|
||||
|
||||
fastify.addContentTypeParser('*', (request, payload, done) => {
|
||||
done(null, pump(payload, split2(JSON.parse)))
|
||||
})
|
||||
|
||||
fastify.route({
|
||||
method: 'POST',
|
||||
url: '/api/log/jsons',
|
||||
handler: (req, res) => {
|
||||
req.body.on('data', d => console.log(d)) // log every incoming object
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For piping file uploads, check out
|
||||
[`@fastify/multipart`](https://github.com/fastify/fastify-multipart).
|
||||
|
||||
To execute the content type parser on all content types, call
|
||||
`removeAllContentTypeParsers` first.
|
||||
|
||||
```js
|
||||
// Without this call, the request body with the content type application/json would be processed by the built-in JSON parser
|
||||
fastify.removeAllContentTypeParsers()
|
||||
|
||||
fastify.addContentTypeParser('*', function (request, payload, done) {
|
||||
const data = ''
|
||||
payload.on('data', chunk => { data += chunk })
|
||||
payload.on('end', () => {
|
||||
done(null, data)
|
||||
})
|
||||
})
|
||||
```
|
||||
566
node_modules/fastify/docs/Reference/Decorators.md
generated
vendored
Normal file
566
node_modules/fastify/docs/Reference/Decorators.md
generated
vendored
Normal file
@@ -0,0 +1,566 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Decorators
|
||||
|
||||
The decorators API customizes core Fastify objects, such as the server instance
|
||||
and any request and reply objects used during the HTTP request lifecycle. It
|
||||
can attach any type of property to core objects, e.g., functions, plain
|
||||
objects, or native types.
|
||||
|
||||
This API is *synchronous*. Defining a decoration asynchronously could result in
|
||||
the Fastify instance booting before the decoration completes. To register an
|
||||
asynchronous decoration, use the `register` API with `fastify-plugin`. See the
|
||||
[Plugins](./Plugins.md) documentation for more details.
|
||||
|
||||
Decorating core objects with this API allows the underlying JavaScript engine to
|
||||
optimize the handling of server, request, and reply objects. This is
|
||||
accomplished by defining the shape of all such object instances before they are
|
||||
instantiated and used. As an example, the following is not recommended because
|
||||
it will change the shape of objects during their lifecycle:
|
||||
|
||||
```js
|
||||
// Bad example! Continue reading.
|
||||
|
||||
// Attach a user property to the incoming request before the request
|
||||
// handler is invoked.
|
||||
fastify.addHook('preHandler', function (req, reply, done) {
|
||||
req.user = 'Bob Dylan'
|
||||
done()
|
||||
})
|
||||
|
||||
// Use the attached user property in the request handler.
|
||||
fastify.get('/', function (req, reply) {
|
||||
reply.send(`Hello, ${req.user}`)
|
||||
})
|
||||
```
|
||||
|
||||
The above example mutates the request object after instantiation, causing the
|
||||
JavaScript engine to deoptimize access. Using the decoration API avoids this
|
||||
deoptimization:
|
||||
|
||||
```js
|
||||
// Decorate request with a 'user' property
|
||||
fastify.decorateRequest('user', '')
|
||||
|
||||
// Update our property
|
||||
fastify.addHook('preHandler', (req, reply, done) => {
|
||||
req.user = 'Bob Dylan'
|
||||
done()
|
||||
})
|
||||
// And finally access it
|
||||
fastify.get('/', (req, reply) => {
|
||||
reply.send(`Hello, ${req.user}!`)
|
||||
})
|
||||
```
|
||||
|
||||
Keep the initial shape of a decorated field close to its future dynamic value.
|
||||
Initialize a decorator as `''` for strings and `null` for objects or functions.
|
||||
This works only with value types; reference types will throw an error during
|
||||
Fastify startup. See [decorateRequest](#decorate-request) and
|
||||
[JavaScript engine fundamentals: Shapes
|
||||
and Inline Caches](https://mathiasbynens.be/notes/shapes-ics)
|
||||
for more information.
|
||||
|
||||
### Usage
|
||||
<a id="usage"></a>
|
||||
|
||||
#### `decorate(name, value, [dependencies])`
|
||||
<a id="decorate"></a>
|
||||
|
||||
This method customizes the Fastify [server](./Server.md) instance.
|
||||
|
||||
For example, to attach a new method to the server instance:
|
||||
|
||||
```js
|
||||
fastify.decorate('utility', function () {
|
||||
// Something very useful
|
||||
})
|
||||
```
|
||||
|
||||
Non-function values can also be attached to the server instance:
|
||||
|
||||
```js
|
||||
fastify.decorate('conf', {
|
||||
db: 'some.db',
|
||||
port: 3000
|
||||
})
|
||||
```
|
||||
|
||||
To access decorated properties, use the name provided to the decoration API:
|
||||
|
||||
```js
|
||||
fastify.utility()
|
||||
|
||||
console.log(fastify.conf.db)
|
||||
```
|
||||
|
||||
The decorated [Fastify server](./Server.md) is bound to `this` in
|
||||
[route](./Routes.md) handlers:
|
||||
|
||||
```js
|
||||
fastify.decorate('db', new DbConnection())
|
||||
|
||||
fastify.get('/', async function (request, reply) {
|
||||
// using return
|
||||
return { hello: await this.db.query('world') }
|
||||
|
||||
// or
|
||||
// using reply.send()
|
||||
reply.send({ hello: await this.db.query('world') })
|
||||
await reply
|
||||
})
|
||||
```
|
||||
|
||||
The `dependencies` parameter is an optional list of decorators that the
|
||||
decorator being defined relies upon. This list contains the names of other
|
||||
decorators. In the following example, the "utility" decorator depends on the
|
||||
"greet" and "hi" decorators:
|
||||
|
||||
```js
|
||||
async function greetDecorator (fastify, opts) {
|
||||
fastify.decorate('greet', () => {
|
||||
return 'greet message'
|
||||
})
|
||||
}
|
||||
|
||||
async function hiDecorator (fastify, opts) {
|
||||
fastify.decorate('hi', () => {
|
||||
return 'hi message'
|
||||
})
|
||||
}
|
||||
|
||||
async function utilityDecorator (fastify, opts) {
|
||||
fastify.decorate('utility', () => {
|
||||
return `${fastify.greet()} | ${fastify.hi()}`
|
||||
})
|
||||
}
|
||||
|
||||
fastify.register(fastifyPlugin(greetDecorator, { name: 'greet' }))
|
||||
fastify.register(fastifyPlugin(hiDecorator, { name: 'hi' }))
|
||||
fastify.register(fastifyPlugin(utilityDecorator, { dependencies: ['greet', 'hi'] }))
|
||||
|
||||
fastify.get('/', function (req, reply) {
|
||||
// Response: {"hello":"greet message | hi message"}
|
||||
reply.send({ hello: fastify.utility() })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) throw err
|
||||
})
|
||||
```
|
||||
|
||||
Using an arrow function breaks the binding of `this` to
|
||||
the `FastifyInstance`.
|
||||
|
||||
If a dependency is not satisfied, the `decorate` method throws an exception.
|
||||
The dependency check occurs before the server instance boots, not during
|
||||
runtime.
|
||||
|
||||
#### `decorateReply(name, value, [dependencies])`
|
||||
<a id="decorate-reply"></a>
|
||||
|
||||
This API adds new methods/properties to the core `Reply` object:
|
||||
|
||||
```js
|
||||
fastify.decorateReply('utility', function () {
|
||||
// Something very useful
|
||||
})
|
||||
```
|
||||
|
||||
Using an arrow function will break the binding of `this` to the Fastify
|
||||
`Reply` instance.
|
||||
|
||||
Using `decorateReply` will throw and error if used with a reference type:
|
||||
|
||||
```js
|
||||
// Don't do this
|
||||
fastify.decorateReply('foo', { bar: 'fizz'})
|
||||
```
|
||||
In this example, the object reference would be shared with all requests, and
|
||||
**any mutation will impact all requests, potentially creating security
|
||||
vulnerabilities or memory leaks**. Fastify blocks this.
|
||||
|
||||
To achieve proper encapsulation across requests configure a new value for each
|
||||
incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest).
|
||||
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
|
||||
async function myPlugin (app) {
|
||||
app.decorateReply('foo')
|
||||
app.addHook('onRequest', async (req, reply) => {
|
||||
reply.foo = { bar: 42 }
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = fp(myPlugin)
|
||||
```
|
||||
|
||||
See [`decorate`](#decorate) for information about the `dependencies` parameter.
|
||||
|
||||
#### `decorateRequest(name, value, [dependencies])`
|
||||
<a id="decorate-request"></a>
|
||||
|
||||
As with [`decorateReply`](#decorate-reply), this API adds new methods/properties
|
||||
to the core `Request` object:
|
||||
|
||||
```js
|
||||
fastify.decorateRequest('utility', function () {
|
||||
// something very useful
|
||||
})
|
||||
```
|
||||
|
||||
Using an arrow function will break the binding of `this` to the Fastify
|
||||
`Request` instance.
|
||||
|
||||
Using `decorateRequest` will emit an error if used with a reference type:
|
||||
|
||||
```js
|
||||
// Don't do this
|
||||
fastify.decorateRequest('foo', { bar: 'fizz'})
|
||||
```
|
||||
In this example, the object reference would be shared with all requests, and
|
||||
**any mutation will impact all requests, potentially creating security
|
||||
vulnerabilities or memory leaks**. Fastify blocks this.
|
||||
|
||||
To achieve proper encapsulation across requests configure a new value for each
|
||||
incoming request in the [`'onRequest'` hook](./Hooks.md#onrequest).
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
|
||||
async function myPlugin (app) {
|
||||
app.decorateRequest('foo')
|
||||
app.addHook('onRequest', async (req, reply) => {
|
||||
req.foo = { bar: 42 }
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = fp(myPlugin)
|
||||
```
|
||||
|
||||
The hook solution is more flexible and allows for more complex initialization
|
||||
because more logic can be added to the `onRequest` hook.
|
||||
|
||||
Another approach is to use the getter/setter pattern, but it requires 2 decorators:
|
||||
|
||||
```js
|
||||
fastify.decorateRequest('my_decorator_holder') // define the holder
|
||||
fastify.decorateRequest('user', {
|
||||
getter () {
|
||||
this.my_decorator_holder ??= {} // initialize the holder
|
||||
return this.my_decorator_holder
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/', async function (req, reply) {
|
||||
req.user.access = 'granted'
|
||||
// other code
|
||||
})
|
||||
```
|
||||
|
||||
This ensures that the `user` property is always unique for each request.
|
||||
|
||||
See [`decorate`](#decorate) for information about the `dependencies` parameter.
|
||||
|
||||
#### `hasDecorator(name)`
|
||||
<a id="has-decorator"></a>
|
||||
|
||||
Used to check for the existence of a server instance decoration:
|
||||
|
||||
```js
|
||||
fastify.hasDecorator('utility')
|
||||
```
|
||||
|
||||
#### hasRequestDecorator
|
||||
<a id="has-request-decorator"></a>
|
||||
|
||||
Used to check for the existence of a Request decoration:
|
||||
|
||||
```js
|
||||
fastify.hasRequestDecorator('utility')
|
||||
```
|
||||
|
||||
#### hasReplyDecorator
|
||||
<a id="has-reply-decorator"></a>
|
||||
|
||||
Used to check for the existence of a Reply decoration:
|
||||
|
||||
```js
|
||||
fastify.hasReplyDecorator('utility')
|
||||
```
|
||||
|
||||
### Decorators and Encapsulation
|
||||
<a id="decorators-encapsulation"></a>
|
||||
|
||||
Defining a decorator (using `decorate`, `decorateRequest`, or `decorateReply`)
|
||||
with the same name more than once in the same **encapsulated** context will
|
||||
throw an exception. For example, the following will throw:
|
||||
|
||||
```js
|
||||
const server = require('fastify')()
|
||||
|
||||
server.decorateReply('view', function (template, args) {
|
||||
// Amazing view rendering engine
|
||||
})
|
||||
|
||||
server.get('/', (req, reply) => {
|
||||
reply.view('/index.html', { hello: 'world' })
|
||||
})
|
||||
|
||||
// Somewhere else in our codebase, we define another
|
||||
// view decorator. This throws.
|
||||
server.decorateReply('view', function (template, args) {
|
||||
// Another rendering engine
|
||||
})
|
||||
|
||||
server.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
|
||||
But this will not:
|
||||
|
||||
```js
|
||||
const server = require('fastify')()
|
||||
|
||||
server.decorateReply('view', function (template, args) {
|
||||
// Amazing view rendering engine.
|
||||
})
|
||||
|
||||
server.register(async function (server, opts) {
|
||||
// We add a view decorator to the current encapsulated
|
||||
// plugin. This will not throw as outside of this encapsulated
|
||||
// plugin view is the old one, while inside it is the new one.
|
||||
server.decorateReply('view', function (template, args) {
|
||||
// Another rendering engine
|
||||
})
|
||||
|
||||
server.get('/', (req, reply) => {
|
||||
reply.view('/index.page', { hello: 'world' })
|
||||
})
|
||||
}, { prefix: '/bar' })
|
||||
|
||||
server.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
### Getters and Setters
|
||||
<a id="getters-setters"></a>
|
||||
|
||||
Decorators accept special "getter/setter" objects with `getter` and optional
|
||||
`setter` functions. This allows defining properties via decorators,
|
||||
for example:
|
||||
|
||||
```js
|
||||
fastify.decorate('foo', {
|
||||
getter () {
|
||||
return 'a getter'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Will define the `foo` property on the Fastify instance:
|
||||
|
||||
```js
|
||||
console.log(fastify.foo) // 'a getter'
|
||||
```
|
||||
|
||||
### `getDecorator<T>` API
|
||||
|
||||
Fastify's `getDecorator<T>` API retrieves an existing decorator from the
|
||||
Fastify instance, `Request`, or `Reply`. If the decorator is not defined, an
|
||||
`FST_ERR_DEC_UNDECLARED` error is thrown.
|
||||
|
||||
#### Use cases
|
||||
|
||||
**Early Plugin Dependency Validation**
|
||||
|
||||
`getDecorator<T>` on Fastify instance verifies that required decorators are
|
||||
available at registration time.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
fastify.register(async function (fastify) {
|
||||
const usersRepository = fastify.getDecorator('usersRepository')
|
||||
|
||||
fastify.get('/users', async function (request, reply) {
|
||||
// We are sure `usersRepository` exists at runtime
|
||||
return usersRepository.findAll()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Handling Missing Decorators**
|
||||
|
||||
Directly accessing a decorator may lead to unexpected behavior if it is not declared:
|
||||
|
||||
```ts
|
||||
const user = request.user;
|
||||
if (user && user.isAdmin) {
|
||||
// Execute admin tasks.
|
||||
}
|
||||
```
|
||||
|
||||
If `request.user` doesn't exist, then `user` will be set to `undefined`.
|
||||
This makes it unclear whether the user is unauthenticated or the decorator is missing.
|
||||
|
||||
Using `getDecorator` enforces runtime safety:
|
||||
|
||||
```ts
|
||||
// If the decorator is missing, an explicit `FST_ERR_DEC_UNDECLARED`
|
||||
// error is thrown immediately.
|
||||
const user = request.getDecorator('user');
|
||||
if (user && user.isAdmin) {
|
||||
// Execute admin tasks.
|
||||
}
|
||||
```
|
||||
|
||||
**Alternative to Module Augmentation**
|
||||
|
||||
Decorators are typically typed via module augmentation:
|
||||
|
||||
```ts
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
usersRepository: IUsersRepository
|
||||
}
|
||||
interface FastifyRequest {
|
||||
session: ISession
|
||||
}
|
||||
interface FastifyReply {
|
||||
sendSuccess: SendSuccessFn
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This approach modifies the Fastify instance globally, which may lead to
|
||||
conflicts and inconsistent behavior in multi-server setups or with plugin
|
||||
encapsulation.
|
||||
|
||||
Using `getDecorator<T>` allows to limit types scope:
|
||||
|
||||
```ts
|
||||
serverOne.register(async function (fastify) {
|
||||
const usersRepository = fastify.getDecorator<PostgreUsersRepository>(
|
||||
'usersRepository'
|
||||
)
|
||||
|
||||
fastify.decorateRequest('session', null)
|
||||
fastify.addHook('onRequest', async (req, reply) => {
|
||||
// Yes, the request object has a setDecorator method.
|
||||
// More information will be provided soon.
|
||||
req.setDecorator('session', { user: 'Jean' })
|
||||
})
|
||||
|
||||
fastify.get('/me', (request, reply) => {
|
||||
const session = request.getDecorator<ISession>('session')
|
||||
reply.send(session)
|
||||
})
|
||||
})
|
||||
|
||||
serverTwo.register(async function (fastify) {
|
||||
const usersRepository = fastify.getDecorator<SqlLiteUsersRepository>(
|
||||
'usersRepository'
|
||||
)
|
||||
|
||||
fastify.decorateReply('sendSuccess', function (data) {
|
||||
return this.send({ success: true })
|
||||
})
|
||||
|
||||
fastify.get('/success', async (request, reply) => {
|
||||
const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
|
||||
await sendSuccess()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Bound functions inference
|
||||
|
||||
To save time, it's common to infer function types instead of
|
||||
writing them manually:
|
||||
|
||||
```ts
|
||||
function sendSuccess (this: FastifyReply) {
|
||||
return this.send({ success: true })
|
||||
}
|
||||
|
||||
export type SendSuccess = typeof sendSuccess
|
||||
```
|
||||
|
||||
However, `getDecorator` returns functions with the `this`
|
||||
context already **bound**, meaning the `this` parameter disappears
|
||||
from the function signature.
|
||||
|
||||
To correctly type it, you should use `OmitThisParameter` utility:
|
||||
|
||||
```ts
|
||||
function sendSuccess (this: FastifyReply) {
|
||||
return this.send({ success: true })
|
||||
}
|
||||
|
||||
type BoundSendSuccess = OmitThisParameter<typeof sendSuccess>
|
||||
|
||||
fastify.decorateReply('sendSuccess', sendSuccess)
|
||||
fastify.get('/success', async (request, reply) => {
|
||||
const sendSuccess = reply.getDecorator<BoundSendSuccess>('sendSuccess')
|
||||
await sendSuccess()
|
||||
})
|
||||
```
|
||||
|
||||
### `Request.setDecorator<T>` Method
|
||||
|
||||
The `setDecorator<T>` method provides a safe and convenient way to
|
||||
update the value of a `Request` decorator.
|
||||
If the decorator does not exist, a `FST_ERR_DEC_UNDECLARED` error
|
||||
is thrown.
|
||||
|
||||
#### Use Cases
|
||||
|
||||
**Runtime Safety**
|
||||
|
||||
A typical way to set a `Request` decorator looks like this:
|
||||
|
||||
```ts
|
||||
fastify.decorateRequest('user', '')
|
||||
fastify.addHook('preHandler', async (req, reply) => {
|
||||
req.user = 'Bob Dylan'
|
||||
})
|
||||
```
|
||||
|
||||
However, there is no guarantee that the decorator actually exists
|
||||
unless you manually check beforehand.
|
||||
Additionally, typos are common, e.g. `account`, `acount`, or `accout`.
|
||||
|
||||
By using `setDecorator`, you are always sure that the decorator exists:
|
||||
|
||||
```ts
|
||||
fastify.decorateRequest('user', '')
|
||||
fastify.addHook('preHandler', async (req, reply) => {
|
||||
// Throws FST_ERR_DEC_UNDECLARED if the decorator does not exist
|
||||
req.setDecorator('user-with-typo', 'Bob Dylan')
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Type Safety**
|
||||
|
||||
If the `FastifyRequest` interface does not declare the decorator, you
|
||||
would typically need to use type assertions:
|
||||
|
||||
```ts
|
||||
fastify.addHook('preHandler', async (req, reply) => {
|
||||
(req as typeof req & { user: string }).user = 'Bob Dylan'
|
||||
})
|
||||
```
|
||||
|
||||
The `setDecorator<T>` method eliminates the need for explicit type
|
||||
assertions while allowing type safety:
|
||||
|
||||
```ts
|
||||
fastify.addHook('preHandler', async (req, reply) => {
|
||||
req.setDecorator<string>('user', 'Bob Dylan')
|
||||
})
|
||||
```
|
||||
190
node_modules/fastify/docs/Reference/Encapsulation.md
generated
vendored
Normal file
190
node_modules/fastify/docs/Reference/Encapsulation.md
generated
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Encapsulation
|
||||
<a id="encapsulation"></a>
|
||||
|
||||
A fundamental feature of Fastify is the "encapsulation context." It governs
|
||||
which [decorators](./Decorators.md), registered [hooks](./Hooks.md), and
|
||||
[plugins](./Plugins.md) are available to [routes](./Routes.md). A visual
|
||||
representation of the encapsulation context is shown in the following figure:
|
||||
|
||||

|
||||
|
||||
In the figure above, there are several entities:
|
||||
|
||||
1. The _root context_
|
||||
2. Three _root plugins_
|
||||
3. Two _child contexts_, each with:
|
||||
* Two _child plugins_
|
||||
* One _grandchild context_, each with:
|
||||
- Three _child plugins_
|
||||
|
||||
Every _child context_ and _grandchild context_ has access to the _root plugins_.
|
||||
Within each _child context_, the _grandchild contexts_ have access to the
|
||||
_child plugins_ registered within the containing _child context_, but the
|
||||
containing _child context_ **does not** have access to the _child plugins_
|
||||
registered within its _grandchild context_.
|
||||
|
||||
Given that everything in Fastify is a [plugin](./Plugins.md) except for the
|
||||
_root context_, every "context" and "plugin" in this example is a plugin
|
||||
that can consist of decorators, hooks, plugins, and routes. To put this
|
||||
example into concrete terms, consider a basic scenario of a REST API server
|
||||
with three routes: the first route (`/one`) requires authentication, the
|
||||
second route (`/two`) does not, and the third route (`/three`) has access to
|
||||
the same context as the second route. Using [@fastify/bearer-auth][bearer] to
|
||||
provide authentication, the code for this example is as follows:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.decorateRequest('answer', 42)
|
||||
|
||||
fastify.register(async function authenticatedContext (childServer) {
|
||||
childServer.register(require('@fastify/bearer-auth'), { keys: ['abc123'] })
|
||||
|
||||
childServer.route({
|
||||
path: '/one',
|
||||
method: 'GET',
|
||||
handler (request, response) {
|
||||
response.send({
|
||||
answer: request.answer,
|
||||
// request.foo will be undefined as it is only defined in publicContext
|
||||
foo: request.foo,
|
||||
// request.bar will be undefined as it is only defined in grandchildContext
|
||||
bar: request.bar
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
fastify.register(async function publicContext (childServer) {
|
||||
childServer.decorateRequest('foo', 'foo')
|
||||
|
||||
childServer.route({
|
||||
path: '/two',
|
||||
method: 'GET',
|
||||
handler (request, response) {
|
||||
response.send({
|
||||
answer: request.answer,
|
||||
foo: request.foo,
|
||||
// request.bar will be undefined as it is only defined in grandchildContext
|
||||
bar: request.bar
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
childServer.register(async function grandchildContext (grandchildServer) {
|
||||
grandchildServer.decorateRequest('bar', 'bar')
|
||||
|
||||
grandchildServer.route({
|
||||
path: '/three',
|
||||
method: 'GET',
|
||||
handler (request, response) {
|
||||
response.send({
|
||||
answer: request.answer,
|
||||
foo: request.foo,
|
||||
bar: request.bar
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
fastify.listen({ port: 8000 })
|
||||
```
|
||||
|
||||
The server example above demonstrates the encapsulation concepts from the
|
||||
original diagram:
|
||||
|
||||
1. Each _child context_ (`authenticatedContext`, `publicContext`, and
|
||||
`grandchildContext`) has access to the `answer` request decorator defined in
|
||||
the _root context_.
|
||||
2. Only the `authenticatedContext` has access to the `@fastify/bearer-auth`
|
||||
plugin.
|
||||
3. Both the `publicContext` and `grandchildContext` have access to the `foo`
|
||||
request decorator.
|
||||
4. Only the `grandchildContext` has access to the `bar` request decorator.
|
||||
|
||||
To see this, start the server and issue requests:
|
||||
|
||||
```sh
|
||||
# curl -H 'authorization: Bearer abc123' http://127.0.0.1:8000/one
|
||||
{"answer":42}
|
||||
# curl http://127.0.0.1:8000/two
|
||||
{"answer":42,"foo":"foo"}
|
||||
# curl http://127.0.0.1:8000/three
|
||||
{"answer":42,"foo":"foo","bar":"bar"}
|
||||
```
|
||||
|
||||
[bearer]: https://github.com/fastify/fastify-bearer-auth
|
||||
|
||||
## Sharing Between Contexts
|
||||
<a id="shared-context"></a>
|
||||
|
||||
Each context in the prior example inherits _only_ from its parent contexts. Parent
|
||||
contexts cannot access entities within their descendant contexts. If needed,
|
||||
encapsulation can be broken using [fastify-plugin][fastify-plugin], making
|
||||
anything registered in a descendant context available to the parent context.
|
||||
|
||||
To allow `publicContext` access to the `bar` decorator in `grandchildContext`,
|
||||
rewrite the code as follows:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fastify = require('fastify')()
|
||||
const fastifyPlugin = require('fastify-plugin')
|
||||
|
||||
fastify.decorateRequest('answer', 42)
|
||||
|
||||
// `authenticatedContext` omitted for clarity
|
||||
|
||||
fastify.register(async function publicContext (childServer) {
|
||||
childServer.decorateRequest('foo', 'foo')
|
||||
|
||||
childServer.route({
|
||||
path: '/two',
|
||||
method: 'GET',
|
||||
handler (request, response) {
|
||||
response.send({
|
||||
answer: request.answer,
|
||||
foo: request.foo,
|
||||
bar: request.bar
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
childServer.register(fastifyPlugin(grandchildContext))
|
||||
|
||||
async function grandchildContext (grandchildServer) {
|
||||
grandchildServer.decorateRequest('bar', 'bar')
|
||||
|
||||
grandchildServer.route({
|
||||
path: '/three',
|
||||
method: 'GET',
|
||||
handler (request, response) {
|
||||
response.send({
|
||||
answer: request.answer,
|
||||
foo: request.foo,
|
||||
bar: request.bar
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
fastify.listen({ port: 8000 })
|
||||
```
|
||||
|
||||
Restarting the server and re-issuing the requests for `/two` and `/three`:
|
||||
|
||||
```sh
|
||||
# curl http://127.0.0.1:8000/two
|
||||
{"answer":42,"foo":"foo","bar":"bar"}
|
||||
# curl http://127.0.0.1:8000/three
|
||||
{"answer":42,"foo":"foo","bar":"bar"}
|
||||
```
|
||||
|
||||
[fastify-plugin]: https://github.com/fastify/fastify-plugin
|
||||
372
node_modules/fastify/docs/Reference/Errors.md
generated
vendored
Normal file
372
node_modules/fastify/docs/Reference/Errors.md
generated
vendored
Normal file
@@ -0,0 +1,372 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Errors
|
||||
<a id="errors"></a>
|
||||
|
||||
**Table of contents**
|
||||
- [Errors](#errors)
|
||||
- [Error Handling In Node.js](#error-handling-in-nodejs)
|
||||
- [Uncaught Errors](#uncaught-errors)
|
||||
- [Catching Errors In Promises](#catching-errors-in-promises)
|
||||
- [Errors In Fastify](#errors-in-fastify)
|
||||
- [Errors In Input Data](#errors-in-input-data)
|
||||
- [Catching Uncaught Errors In Fastify](#catching-uncaught-errors-in-fastify)
|
||||
- [Errors In Fastify Lifecycle Hooks And A Custom Error Handler](#errors-in-fastify-lifecycle-hooks-and-a-custom-error-handler)
|
||||
- [Fastify Error Codes](#fastify-error-codes)
|
||||
- [FST_ERR_NOT_FOUND](#fst_err_not_found)
|
||||
- [FST_ERR_OPTIONS_NOT_OBJ](#fst_err_options_not_obj)
|
||||
- [FST_ERR_QSP_NOT_FN](#fst_err_qsp_not_fn)
|
||||
- [FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN](#fst_err_schema_controller_bucket_opt_not_fn)
|
||||
- [FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN](#fst_err_schema_error_formatter_not_fn)
|
||||
- [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ](#fst_err_ajv_custom_options_opt_not_obj)
|
||||
- [FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR](#fst_err_ajv_custom_options_opt_not_arr)
|
||||
- [FST_ERR_CTP_ALREADY_PRESENT](#fst_err_ctp_already_present)
|
||||
- [FST_ERR_CTP_INVALID_TYPE](#fst_err_ctp_invalid_type)
|
||||
- [FST_ERR_CTP_EMPTY_TYPE](#fst_err_ctp_empty_type)
|
||||
- [FST_ERR_CTP_INVALID_HANDLER](#fst_err_ctp_invalid_handler)
|
||||
- [FST_ERR_CTP_INVALID_PARSE_TYPE](#fst_err_ctp_invalid_parse_type)
|
||||
- [FST_ERR_CTP_BODY_TOO_LARGE](#fst_err_ctp_body_too_large)
|
||||
- [FST_ERR_CTP_INVALID_MEDIA_TYPE](#fst_err_ctp_invalid_media_type)
|
||||
- [FST_ERR_CTP_INVALID_CONTENT_LENGTH](#fst_err_ctp_invalid_content_length)
|
||||
- [FST_ERR_CTP_EMPTY_JSON_BODY](#fst_err_ctp_empty_json_body)
|
||||
- [FST_ERR_CTP_INVALID_JSON_BODY](#fst_err_ctp_invalid_json_body)
|
||||
- [FST_ERR_CTP_INSTANCE_ALREADY_STARTED](#fst_err_ctp_instance_already_started)
|
||||
- [FST_ERR_INSTANCE_ALREADY_LISTENING](#fst_err_instance_already_listening)
|
||||
- [FST_ERR_DEC_ALREADY_PRESENT](#fst_err_dec_already_present)
|
||||
- [FST_ERR_DEC_DEPENDENCY_INVALID_TYPE](#fst_err_dec_dependency_invalid_type)
|
||||
- [FST_ERR_DEC_MISSING_DEPENDENCY](#fst_err_dec_missing_dependency)
|
||||
- [FST_ERR_DEC_AFTER_START](#fst_err_dec_after_start)
|
||||
- [FST_ERR_DEC_REFERENCE_TYPE](#fst_err_dec_reference_type)
|
||||
- [FST_ERR_DEC_UNDECLARED](#fst_err_dec_undeclared)
|
||||
- [FST_ERR_HOOK_INVALID_TYPE](#fst_err_hook_invalid_type)
|
||||
- [FST_ERR_HOOK_INVALID_HANDLER](#fst_err_hook_invalid_handler)
|
||||
- [FST_ERR_HOOK_INVALID_ASYNC_HANDLER](#fst_err_hook_invalid_async_handler)
|
||||
- [FST_ERR_HOOK_NOT_SUPPORTED](#fst_err_hook_not_supported)
|
||||
- [FST_ERR_MISSING_MIDDLEWARE](#fst_err_missing_middleware)
|
||||
- [FST_ERR_HOOK_TIMEOUT](#fst_err_hook_timeout)
|
||||
- [FST_ERR_LOG_INVALID_DESTINATION](#fst_err_log_invalid_destination)
|
||||
- [FST_ERR_LOG_INVALID_LOGGER](#fst_err_log_invalid_logger)
|
||||
- [FST_ERR_LOG_INVALID_LOGGER_INSTANCE](#fst_err_log_invalid_logger_instance)
|
||||
- [FST_ERR_LOG_INVALID_LOGGER_CONFIG](#fst_err_log_invalid_logger_config)
|
||||
- [FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED](#fst_err_log_logger_and_logger_instance_provided)
|
||||
- [FST_ERR_REP_INVALID_PAYLOAD_TYPE](#fst_err_rep_invalid_payload_type)
|
||||
- [FST_ERR_REP_RESPONSE_BODY_CONSUMED](#fst_err_rep_response_body_consumed)
|
||||
- [FST_ERR_REP_READABLE_STREAM_LOCKED](#fst_err_rep_readable_stream_locked)
|
||||
- [FST_ERR_REP_ALREADY_SENT](#fst_err_rep_already_sent)
|
||||
- [FST_ERR_REP_SENT_VALUE](#fst_err_rep_sent_value)
|
||||
- [FST_ERR_SEND_INSIDE_ONERR](#fst_err_send_inside_onerr)
|
||||
- [FST_ERR_SEND_UNDEFINED_ERR](#fst_err_send_undefined_err)
|
||||
- [FST_ERR_BAD_STATUS_CODE](#fst_err_bad_status_code)
|
||||
- [FST_ERR_BAD_TRAILER_NAME](#fst_err_bad_trailer_name)
|
||||
- [FST_ERR_BAD_TRAILER_VALUE](#fst_err_bad_trailer_value)
|
||||
- [FST_ERR_FAILED_ERROR_SERIALIZATION](#fst_err_failed_error_serialization)
|
||||
- [FST_ERR_MISSING_SERIALIZATION_FN](#fst_err_missing_serialization_fn)
|
||||
- [FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN](#fst_err_missing_contenttype_serialization_fn)
|
||||
- [FST_ERR_REQ_INVALID_VALIDATION_INVOCATION](#fst_err_req_invalid_validation_invocation)
|
||||
- [FST_ERR_SCH_MISSING_ID](#fst_err_sch_missing_id)
|
||||
- [FST_ERR_SCH_ALREADY_PRESENT](#fst_err_sch_already_present)
|
||||
- [FST_ERR_SCH_CONTENT_MISSING_SCHEMA](#fst_err_sch_content_missing_schema)
|
||||
- [FST_ERR_SCH_DUPLICATE](#fst_err_sch_duplicate)
|
||||
- [FST_ERR_SCH_VALIDATION_BUILD](#fst_err_sch_validation_build)
|
||||
- [FST_ERR_SCH_SERIALIZATION_BUILD](#fst_err_sch_serialization_build)
|
||||
- [FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX](#fst_err_sch_response_schema_not_nested_2xx)
|
||||
- [FST_ERR_INIT_OPTS_INVALID](#fst_err_init_opts_invalid)
|
||||
- [FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE](#fst_err_force_close_connections_idle_not_available)
|
||||
- [FST_ERR_DUPLICATED_ROUTE](#fst_err_duplicated_route)
|
||||
- [FST_ERR_BAD_URL](#fst_err_bad_url)
|
||||
- [FST_ERR_ASYNC_CONSTRAINT](#fst_err_async_constraint)
|
||||
- [FST_ERR_INVALID_URL](#fst_err_invalid_url)
|
||||
- [FST_ERR_ROUTE_OPTIONS_NOT_OBJ](#fst_err_route_options_not_obj)
|
||||
- [FST_ERR_ROUTE_DUPLICATED_HANDLER](#fst_err_route_duplicated_handler)
|
||||
- [FST_ERR_ROUTE_HANDLER_NOT_FN](#fst_err_route_handler_not_fn)
|
||||
- [FST_ERR_ROUTE_MISSING_HANDLER](#fst_err_route_missing_handler)
|
||||
- [FST_ERR_ROUTE_METHOD_INVALID](#fst_err_route_method_invalid)
|
||||
- [FST_ERR_ROUTE_METHOD_NOT_SUPPORTED](#fst_err_route_method_not_supported)
|
||||
- [FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED](#fst_err_route_body_validation_schema_not_supported)
|
||||
- [FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT](#fst_err_route_body_limit_option_not_int)
|
||||
- [FST_ERR_ROUTE_REWRITE_NOT_STR](#fst_err_route_rewrite_not_str)
|
||||
- [FST_ERR_REOPENED_CLOSE_SERVER](#fst_err_reopened_close_server)
|
||||
- [FST_ERR_REOPENED_SERVER](#fst_err_reopened_server)
|
||||
- [FST_ERR_PLUGIN_VERSION_MISMATCH](#fst_err_plugin_version_mismatch)
|
||||
- [FST_ERR_PLUGIN_CALLBACK_NOT_FN](#fst_err_plugin_callback_not_fn)
|
||||
- [FST_ERR_PLUGIN_NOT_VALID](#fst_err_plugin_not_valid)
|
||||
- [FST_ERR_ROOT_PLG_BOOTED](#fst_err_root_plg_booted)
|
||||
- [FST_ERR_PARENT_PLUGIN_BOOTED](#fst_err_parent_plugin_booted)
|
||||
- [FST_ERR_PLUGIN_TIMEOUT](#fst_err_plugin_timeout)
|
||||
- [FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE](#fst_err_plugin_not_present_in_instance)
|
||||
- [FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER](#fst_err_plugin_invalid_async_handler)
|
||||
- [FST_ERR_VALIDATION](#fst_err_validation)
|
||||
- [FST_ERR_LISTEN_OPTIONS_INVALID](#fst_err_listen_options_invalid)
|
||||
- [FST_ERR_ERROR_HANDLER_NOT_FN](#fst_err_error_handler_not_fn)
|
||||
- [FST_ERR_ERROR_HANDLER_ALREADY_SET](#fst_err_error_handler_already_set)
|
||||
|
||||
### Error Handling In Node.js
|
||||
<a id="error-handling"></a>
|
||||
|
||||
#### Uncaught Errors
|
||||
In Node.js, uncaught errors can cause memory leaks, file descriptor leaks, and
|
||||
other major production issues.
|
||||
[Domains](https://nodejs.org/en/docs/guides/domain-postmortem/) were a failed
|
||||
attempt to fix this.
|
||||
|
||||
Given that it is not possible to process all uncaught errors sensibly, the best
|
||||
way to deal with them is to
|
||||
[crash](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly).
|
||||
|
||||
#### Catching Errors In Promises
|
||||
When using promises, attach a `.catch()` handler synchronously.
|
||||
|
||||
### Errors In Fastify
|
||||
Fastify follows an all-or-nothing approach and aims to be lean and optimal. The
|
||||
developer is responsible for ensuring errors are handled properly.
|
||||
|
||||
#### Errors In Input Data
|
||||
Most errors result from unexpected input data, so it is recommended to
|
||||
[validate input data against a JSON schema](./Validation-and-Serialization.md).
|
||||
|
||||
#### Catching Uncaught Errors In Fastify
|
||||
Fastify tries to catch as many uncaught errors as possible without hindering
|
||||
performance. This includes:
|
||||
|
||||
1. synchronous routes, e.g. `app.get('/', () => { throw new Error('kaboom') })`
|
||||
2. `async` routes, e.g. `app.get('/', async () => { throw new Error('kaboom')
|
||||
})`
|
||||
|
||||
In both cases, the error will be caught safely and routed to Fastify's default
|
||||
error handler, resulting in a generic `500 Internal Server Error` response.
|
||||
|
||||
To customize this behavior, use
|
||||
[`setErrorHandler`](./Server.md#seterrorhandler).
|
||||
|
||||
### Errors In Fastify Lifecycle Hooks And A Custom Error Handler
|
||||
|
||||
From the [Hooks documentation](./Hooks.md#manage-errors-from-a-hook):
|
||||
> If you get an error during the execution of your hook, just pass it to
|
||||
> `done()` and Fastify will automatically close the request and send the
|
||||
> appropriate error code to the user.
|
||||
|
||||
When a custom error handler is defined through
|
||||
[`setErrorHandler`](./Server.md#seterrorhandler), it will receive the error
|
||||
passed to the `done()` callback or through other supported automatic error
|
||||
handling mechanisms. If `setErrorHandler` is used multiple times, the error will
|
||||
be routed to the most precedent handler within the error
|
||||
[encapsulation context](./Encapsulation.md). Error handlers are fully
|
||||
encapsulated, so a `setErrorHandler` call within a plugin will limit the error
|
||||
handler to that plugin's context.
|
||||
|
||||
The root error handler is Fastify's generic error handler. This error handler
|
||||
will use the headers and status code in the `Error` object, if they exist. The
|
||||
headers and status code will not be automatically set if a custom error handler
|
||||
is provided.
|
||||
|
||||
The following should be considered when using a custom error handler:
|
||||
|
||||
- `reply.send(data)` behaves as in [regular route handlers](./Reply.md#senddata)
|
||||
- objects are serialized, triggering the `preSerialization` lifecycle hook if
|
||||
defined
|
||||
- strings, buffers, and streams are sent to the client with appropriate headers
|
||||
(no serialization)
|
||||
|
||||
- Throwing a new error in a custom error handler will call the parent
|
||||
`errorHandler`.
|
||||
- The `onError` hook will be triggered once for the first error thrown
|
||||
- An error will not be triggered twice from a lifecycle hook. Fastify
|
||||
internally monitors error invocation to avoid infinite loops for errors
|
||||
thrown in the reply phases of the lifecycle (those after the route handler)
|
||||
|
||||
When using Fastify's custom error handling through
|
||||
[`setErrorHandler`](./Server.md#seterrorhandler), be aware of how errors are
|
||||
propagated between custom and default error handlers.
|
||||
|
||||
If a plugin's error handler re-throws an error that is not an instance of
|
||||
[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error),
|
||||
it will not propagate to the parent context error handler. Instead, it will be
|
||||
caught by the default error handler. This can be seen in the `/bad` route of the
|
||||
example below.
|
||||
|
||||
To ensure consistent error handling, throw instances of `Error`. For example,
|
||||
replace `throw 'foo'` with `throw new Error('foo')` in the `/bad` route to
|
||||
ensure errors propagate through the custom error handling chain as intended.
|
||||
This practice helps avoid potential pitfalls when working with custom error
|
||||
handling in Fastify.
|
||||
|
||||
For example:
|
||||
```js
|
||||
const Fastify = require('fastify')
|
||||
|
||||
// Instantiate the framework
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
|
||||
// Register parent error handler
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
reply.status(500).send({ ok: false })
|
||||
})
|
||||
|
||||
fastify.register((app, options, next) => {
|
||||
// Register child error handler
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
throw error
|
||||
})
|
||||
|
||||
fastify.get('/bad', async () => {
|
||||
// Throws a non-Error type, 'bar'
|
||||
throw 'foo'
|
||||
})
|
||||
|
||||
fastify.get('/good', async () => {
|
||||
// Throws an Error instance, 'bar'
|
||||
throw new Error('bar')
|
||||
})
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
// Run the server
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is listening at ${address}
|
||||
})
|
||||
```
|
||||
|
||||
### Fastify Error Codes
|
||||
<a id="fastify-error-codes"></a>
|
||||
|
||||
You can access `errorCodes` for mapping:
|
||||
```js
|
||||
// ESM
|
||||
import { errorCodes } from 'fastify'
|
||||
|
||||
// CommonJS
|
||||
const errorCodes = require('fastify').errorCodes
|
||||
```
|
||||
|
||||
For example:
|
||||
```js
|
||||
const Fastify = require('fastify')
|
||||
|
||||
// Instantiate the framework
|
||||
const fastify = Fastify({
|
||||
logger: true
|
||||
})
|
||||
|
||||
// Declare a route
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.code('bad status code').send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.setErrorHandler(function (error, request, reply) {
|
||||
if (error instanceof Fastify.errorCodes.FST_ERR_BAD_STATUS_CODE) {
|
||||
// Log error
|
||||
this.log.error(error)
|
||||
// Send error response
|
||||
reply.status(500).send({ ok: false })
|
||||
} else {
|
||||
// Fastify will use parent error handler to handle this
|
||||
reply.send(error)
|
||||
}
|
||||
})
|
||||
|
||||
// Run the server!
|
||||
fastify.listen({ port: 3000 }, function (err, address) {
|
||||
if (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
// Server is now listening on ${address}
|
||||
})
|
||||
```
|
||||
|
||||
Below is a table with all the error codes used by Fastify.
|
||||
|
||||
| Code | Description | How to solve | Discussion |
|
||||
|------|-------------|--------------|------------|
|
||||
| <a id="fst_err_not_found">FST_ERR_NOT_FOUND</a> | 404 Not Found | - | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_options_not_obj">FST_ERR_OPTIONS_NOT_OBJ</a> | Fastify options wrongly specified. | Fastify options should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_qsp_not_fn">FST_ERR_QSP_NOT_FN</a> | QueryStringParser wrongly specified. | QueryStringParser option should be a function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_schema_controller_bucket_opt_not_fn">FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN</a> | SchemaController.bucket wrongly specified. | SchemaController.bucket option should be a function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_schema_error_formatter_not_fn">FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN</a> | SchemaErrorFormatter option wrongly specified. | SchemaErrorFormatter option should be a non async function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_ajv_custom_options_opt_not_obj">FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ</a> | ajv.customOptions wrongly specified. | ajv.customOptions option should be an object. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_ajv_custom_options_opt_not_arr">FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR</a> | ajv.plugins option wrongly specified. | ajv.plugins option should be an array. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_ctp_already_present">FST_ERR_CTP_ALREADY_PRESENT</a> | The parser for this content type was already registered. | Use a different content type or delete the already registered parser. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_invalid_type">FST_ERR_CTP_INVALID_TYPE</a> | `Content-Type` wrongly specified | The `Content-Type` should be a string. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_empty_type">FST_ERR_CTP_EMPTY_TYPE</a> | `Content-Type` is an empty string. | `Content-Type` cannot be an empty string. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_invalid_handler">FST_ERR_CTP_INVALID_HANDLER</a> | Invalid handler for the content type. | Use a different handler. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_invalid_parse_type">FST_ERR_CTP_INVALID_PARSE_TYPE</a> | The provided parse type is not supported. | Accepted values are <code>string</code> or <code>buffer</code>. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_body_too_large">FST_ERR_CTP_BODY_TOO_LARGE</a> | The request body is larger than the provided limit. | Increase the limit in the Fastify server instance setting: [bodyLimit](./Server.md#bodylimit) | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_invalid_media_type">FST_ERR_CTP_INVALID_MEDIA_TYPE</a> | The received media type is not supported (i.e. there is no suitable `Content-Type` parser for it). | Use a different content type. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_invalid_content_length">FST_ERR_CTP_INVALID_CONTENT_LENGTH</a> | Request body size did not match <code>Content-Length</code>. | Check the request body size and the <code>Content-Length</code> header. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_ctp_empty_json_body">FST_ERR_CTP_EMPTY_JSON_BODY</a> | Body is not valid JSON but content-type is set to <code>application/json</code>. | Check if the request body is valid JSON. | [#5925](https://github.com/fastify/fastify/pull/5925) |
|
||||
| <a id="fst_err_ctp_invalid_json_body">FST_ERR_CTP_INVALID_JSON_BODY</a> | Body cannot be empty when content-type is set to <code>application/json</code>. | Check the request body. | [#1253](https://github.com/fastify/fastify/pull/1253) |
|
||||
| <a id="fst_err_ctp_instance_already_started">FST_ERR_CTP_INSTANCE_ALREADY_STARTED</a> | Fastify is already started. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_instance_already_listening">FST_ERR_INSTANCE_ALREADY_LISTENING</a> | Fastify instance is already listening. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_dec_already_present">FST_ERR_DEC_ALREADY_PRESENT</a> | A decorator with the same name is already registered. | Use a different decorator name. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_dec_dependency_invalid_type">FST_ERR_DEC_DEPENDENCY_INVALID_TYPE</a> | The dependencies of decorator must be of type `Array`. | Use an array for the dependencies. | [#3090](https://github.com/fastify/fastify/pull/3090) |
|
||||
| <a id="fst_err_dec_missing_dependency">FST_ERR_DEC_MISSING_DEPENDENCY</a> | The decorator cannot be registered due to a missing dependency. | Register the missing dependency. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_dec_after_start">FST_ERR_DEC_AFTER_START</a> | The decorator cannot be added after start. | Add the decorator before starting the server. | [#2128](https://github.com/fastify/fastify/pull/2128) |
|
||||
| <a id="fst_err_dec_reference_type">FST_ERR_DEC_REFERENCE_TYPE</a> | The decorator cannot be a reference type. | Define the decorator with a getter/setter interface or an empty decorator with a hook. | [#5462](https://github.com/fastify/fastify/pull/5462) |
|
||||
| <a id="fst_err_dec_undeclared">FST_ERR_DEC_UNDECLARED</a> | An attempt was made to access a decorator that has not been declared. | Declare the decorator before using it. | [#](https://github.com/fastify/fastify/pull/)
|
||||
| <a id="fst_err_hook_invalid_type">FST_ERR_HOOK_INVALID_TYPE</a> | The hook name must be a string. | Use a string for the hook name. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_hook_invalid_handler">FST_ERR_HOOK_INVALID_HANDLER</a> | The hook callback must be a function. | Use a function for the hook callback. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_hook_invalid_async_handler">FST_ERR_HOOK_INVALID_ASYNC_HANDLER</a> | Async function has too many arguments. Async hooks should not use the `done` argument. | Remove the `done` argument from the async hook. | [#4367](https://github.com/fastify/fastify/pull/4367) |
|
||||
| <a id="fst_err_hook_not_supported">FST_ERR_HOOK_NOT_SUPPORTED</a> | The hook is not supported. | Use a supported hook. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_missing_middleware">FST_ERR_MISSING_MIDDLEWARE</a> | You must register a plugin for handling middlewares, visit [`Middleware`](./Middleware.md) for more info. | Register a plugin for handling middlewares. | [#2014](https://github.com/fastify/fastify/pull/2014) |
|
||||
| <a id="fst_err_hook_timeout">FST_ERR_HOOK_TIMEOUT</a> | A callback for a hook timed out. | Increase the timeout for the hook. | [#3106](https://github.com/fastify/fastify/pull/3106) |
|
||||
| <a id="fst_err_log_invalid_destination">FST_ERR_LOG_INVALID_DESTINATION</a> | The logger does not accept the specified destination. | Use a `'stream'` or a `'file'` as the destination. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_log_invalid_logger">FST_ERR_LOG_INVALID_LOGGER</a> | The logger should have all these methods: `'info'`, `'error'`, `'debug'`, `'fatal'`, `'warn'`, `'trace'`, `'child'`. | Use a logger with all the required methods. | [#4520](https://github.com/fastify/fastify/pull/4520) |
|
||||
| <a id="fst_err_log_invalid_logger_instance">FST_ERR_LOG_INVALID_LOGGER_INSTANCE</a> | The `loggerInstance` only accepts a logger instance, not a configuration object. | To pass a configuration object, use `'logger'` instead. | [#5020](https://github.com/fastify/fastify/pull/5020) |
|
||||
| <a id="fst_err_log_invalid_logger_config">FST_ERR_LOG_INVALID_LOGGER_CONFIG</a> | The logger option only accepts a configuration object, not a logger instance. | To pass an instance, use `'loggerInstance'` instead. | [#5020](https://github.com/fastify/fastify/pull/5020) |
|
||||
| <a id="fst_err_log_logger_and_logger_instance_provided">FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED</a> | You cannot provide both `'logger'` and `'loggerInstance'`. | Please provide only one option. | [#5020](https://github.com/fastify/fastify/pull/5020) |
|
||||
| <a id="fst_err_rep_invalid_payload_type">FST_ERR_REP_INVALID_PAYLOAD_TYPE</a> | Reply payload can be either a `string` or a `Buffer`. | Use a `string` or a `Buffer` for the payload. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_rep_response_body_consumed">FST_ERR_REP_RESPONSE_BODY_CONSUMED</a> | Using `Response` as reply payload, but the body is being consumed. | Make sure you don't consume the `Response.body` | [#5286](https://github.com/fastify/fastify/pull/5286) |
|
||||
| <a id="fst_err_rep_readable_stream_locked">FST_ERR_REP_READABLE_STREAM_LOCKED</a> | Using `ReadableStream` as reply payload, but locked with another reader. | Make sure you don't call the `Readable.getReader` before sending or release lock with `reader.releaseLock()` before sending. | [#5920](https://github.com/fastify/fastify/pull/5920) |
|
||||
| <a id="fst_err_rep_already_sent">FST_ERR_REP_ALREADY_SENT</a> | A response was already sent. | - | [#1336](https://github.com/fastify/fastify/pull/1336) |
|
||||
| <a id="fst_err_rep_sent_value">FST_ERR_REP_SENT_VALUE</a> | The only possible value for `reply.sent` is `true`. | - | [#1336](https://github.com/fastify/fastify/pull/1336) |
|
||||
| <a id="fst_err_send_inside_onerr">FST_ERR_SEND_INSIDE_ONERR</a> | You cannot use `send` inside the `onError` hook. | - | [#1348](https://github.com/fastify/fastify/pull/1348) |
|
||||
| <a id="fst_err_send_undefined_err">FST_ERR_SEND_UNDEFINED_ERR</a> | Undefined error has occurred. | - | [#2074](https://github.com/fastify/fastify/pull/2074) |
|
||||
| <a id="fst_err_bad_status_code">FST_ERR_BAD_STATUS_CODE</a> | The status code is not valid. | Use a valid status code. | [#2082](https://github.com/fastify/fastify/pull/2082) |
|
||||
| <a id="fst_err_bad_trailer_name">FST_ERR_BAD_TRAILER_NAME</a> | Called `reply.trailer` with an invalid header name. | Use a valid header name. | [#3794](https://github.com/fastify/fastify/pull/3794) |
|
||||
| <a id="fst_err_bad_trailer_value">FST_ERR_BAD_TRAILER_VALUE</a> | Called `reply.trailer` with an invalid type. Expected a function. | Use a function. | [#3794](https://github.com/fastify/fastify/pull/3794) |
|
||||
| <a id="fst_err_failed_error_serialization">FST_ERR_FAILED_ERROR_SERIALIZATION</a> | Failed to serialize an error. | - | [#4601](https://github.com/fastify/fastify/pull/4601) |
|
||||
| <a id="fst_err_missing_serialization_fn">FST_ERR_MISSING_SERIALIZATION_FN</a> | Missing serialization function. | Add a serialization function. | [#3970](https://github.com/fastify/fastify/pull/3970) |
|
||||
| <a id="fst_err_missing_contenttype_serialization_fn">FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN</a> | Missing `Content-Type` serialization function. | Add a serialization function. | [#4264](https://github.com/fastify/fastify/pull/4264) |
|
||||
| <a id="fst_err_req_invalid_validation_invocation">FST_ERR_REQ_INVALID_VALIDATION_INVOCATION</a> | Invalid validation invocation. Missing validation function for HTTP part nor schema provided. | Add a validation function. | [#3970](https://github.com/fastify/fastify/pull/3970) |
|
||||
| <a id="fst_err_sch_missing_id">FST_ERR_SCH_MISSING_ID</a> | The schema provided does not have `$id` property. | Add a `$id` property. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_sch_already_present">FST_ERR_SCH_ALREADY_PRESENT</a> | A schema with the same `$id` already exists. | Use a different `$id`. | [#1168](https://github.com/fastify/fastify/pull/1168) |
|
||||
| <a id="fst_err_sch_content_missing_schema">FST_ERR_SCH_CONTENT_MISSING_SCHEMA</a> | A schema is missing for the corresponding content type. | Add a schema. | [#4264](https://github.com/fastify/fastify/pull/4264) |
|
||||
| <a id="fst_err_sch_duplicate">FST_ERR_SCH_DUPLICATE</a> | Schema with the same attribute already present! | Use a different attribute. | [#1954](https://github.com/fastify/fastify/pull/1954) |
|
||||
| <a id="fst_err_sch_validation_build">FST_ERR_SCH_VALIDATION_BUILD</a> | The JSON schema provided for validation to a route is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) |
|
||||
| <a id="fst_err_sch_serialization_build">FST_ERR_SCH_SERIALIZATION_BUILD</a> | The JSON schema provided for serialization of a route response is not valid. | Fix the JSON schema. | [#2023](https://github.com/fastify/fastify/pull/2023) |
|
||||
| <a id="fst_err_sch_response_schema_not_nested_2xx">FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX</a> | Response schemas should be nested under a valid status code (2XX). | Use a valid status code. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_init_opts_invalid">FST_ERR_INIT_OPTS_INVALID</a> | Invalid initialization options. | Use valid initialization options. | [#1471](https://github.com/fastify/fastify/pull/1471) |
|
||||
| <a id="fst_err_force_close_connections_idle_not_available">FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE</a> | Cannot set forceCloseConnections to `idle` as your HTTP server does not support `closeIdleConnections` method. | Use a different value for `forceCloseConnections`. | [#3925](https://github.com/fastify/fastify/pull/3925) |
|
||||
| <a id="fst_err_duplicated_route">FST_ERR_DUPLICATED_ROUTE</a> | The HTTP method already has a registered controller for that URL. | Use a different URL or register the controller for another HTTP method. | [#2954](https://github.com/fastify/fastify/pull/2954) |
|
||||
| <a id="fst_err_bad_url">FST_ERR_BAD_URL</a> | The router received an invalid URL. | Use a valid URL. | [#2106](https://github.com/fastify/fastify/pull/2106) |
|
||||
| <a id="fst_err_async_constraint">FST_ERR_ASYNC_CONSTRAINT</a> | The router received an error when using asynchronous constraints. | - | [#4323](https://github.com/fastify/fastify/pull/4323) |
|
||||
| <a id="fst_err_invalid_url">FST_ERR_INVALID_URL</a> | URL must be a string. | Use a string for the URL. | [#3653](https://github.com/fastify/fastify/pull/3653) |
|
||||
| <a id="fst_err_route_options_not_obj">FST_ERR_ROUTE_OPTIONS_NOT_OBJ</a> | Options for the route must be an object. | Use an object for the route options. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_duplicated_handler">FST_ERR_ROUTE_DUPLICATED_HANDLER</a> | Duplicate handler for the route is not allowed. | Use a different handler. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_handler_not_fn">FST_ERR_ROUTE_HANDLER_NOT_FN</a> | Handler for the route must be a function. | Use a function for the handler. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_missing_handler">FST_ERR_ROUTE_MISSING_HANDLER</a> | Missing handler function for the route. | Add a handler function. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_method_invalid">FST_ERR_ROUTE_METHOD_INVALID</a> | Method is not a valid value. | Use a valid value for the method. | [#4750](https://github.com/fastify/fastify/pull/4750) |
|
||||
| <a id="fst_err_route_method_not_supported">FST_ERR_ROUTE_METHOD_NOT_SUPPORTED</a> | Method is not supported for the route. | Use a supported method. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_body_validation_schema_not_supported">FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED</a> | Body validation schema route is not supported. | Use a different different method for the route. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_body_limit_option_not_int">FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT</a> | `bodyLimit` option must be an integer. | Use an integer for the `bodyLimit` option. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_route_rewrite_not_str">FST_ERR_ROUTE_REWRITE_NOT_STR</a> | `rewriteUrl` needs to be of type `string`. | Use a string for the `rewriteUrl`. | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_reopened_close_server">FST_ERR_REOPENED_CLOSE_SERVER</a> | Fastify has already been closed and cannot be reopened. | - | [#2415](https://github.com/fastify/fastify/pull/2415) |
|
||||
| <a id="fst_err_reopened_server">FST_ERR_REOPENED_SERVER</a> | Fastify is already listening. | - | [#2415](https://github.com/fastify/fastify/pull/2415) |
|
||||
| <a id="fst_err_plugin_version_mismatch">FST_ERR_PLUGIN_VERSION_MISMATCH</a> | Installed Fastify plugin mismatched expected version. | Use a compatible version of the plugin. | [#2549](https://github.com/fastify/fastify/pull/2549) |
|
||||
| <a id="fst_err_plugin_callback_not_fn">FST_ERR_PLUGIN_CALLBACK_NOT_FN</a> | Callback for a hook is not a function. | Use a function for the callback. | [#3106](https://github.com/fastify/fastify/pull/3106) |
|
||||
| <a id="fst_err_plugin_not_valid">FST_ERR_PLUGIN_NOT_VALID</a> | Plugin must be a function or a promise. | Use a function or a promise for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) |
|
||||
| <a id="fst_err_root_plg_booted">FST_ERR_ROOT_PLG_BOOTED</a> | Root plugin has already booted. | - | [#3106](https://github.com/fastify/fastify/pull/3106) |
|
||||
| <a id="fst_err_parent_plugin_booted">FST_ERR_PARENT_PLUGIN_BOOTED</a> | Impossible to load plugin because the parent (mapped directly from `avvio`) | - | [#3106](https://github.com/fastify/fastify/pull/3106) |
|
||||
| <a id="fst_err_plugin_timeout">FST_ERR_PLUGIN_TIMEOUT</a> | Plugin did not start in time. | Increase the timeout for the plugin. | [#3106](https://github.com/fastify/fastify/pull/3106) |
|
||||
| <a id="fst_err_plugin_not_present_in_instance">FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE</a> | The decorator is not present in the instance. | - | [#4554](https://github.com/fastify/fastify/pull/4554) |
|
||||
| <a id="fst_err_plugin_invalid_async_handler">FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER</a> | The plugin being registered mixes async and callback styles. | - | [#5141](https://github.com/fastify/fastify/pull/5141) |
|
||||
| <a id="fst_err_validation">FST_ERR_VALIDATION</a> | The Request failed the payload validation. | Check the request payload. | [#4824](https://github.com/fastify/fastify/pull/4824) |
|
||||
| <a id="fst_err_listen_options_invalid">FST_ERR_LISTEN_OPTIONS_INVALID</a> | Invalid listen options. | Check the listen options. | [#4886](https://github.com/fastify/fastify/pull/4886) |
|
||||
| <a id="fst_err_error_handler_not_fn">FST_ERR_ERROR_HANDLER_NOT_FN</a> | Error Handler must be a function | Provide a function to `setErrorHandler`. | [#5317](https://github.com/fastify/fastify/pull/5317) | <a id="fst_err_error_handler_already_set">FST_ERR_ERROR_HANDLER_ALREADY_SET</a> | Error Handler already set in this scope. Set `allowErrorHandlerOverride: true` to allow overriding. | By default, `setErrorHandler` can only be called once per encapsulation context. | [#6097](https://github.com/fastify/fastify/pull/6098) |
|
||||
94
node_modules/fastify/docs/Reference/HTTP2.md
generated
vendored
Normal file
94
node_modules/fastify/docs/Reference/HTTP2.md
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## HTTP2
|
||||
|
||||
_Fastify_ supports HTTP2 over HTTPS (h2) or plaintext (h2c).
|
||||
|
||||
Currently, none of the HTTP2-specific APIs are available through _Fastify_, but
|
||||
Node's `req` and `res` can be accessed through the `Request` and `Reply`
|
||||
interfaces. PRs are welcome.
|
||||
|
||||
### Secure (HTTPS)
|
||||
|
||||
HTTP2 is supported in all modern browsers __only over a secure connection__:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const fastify = require('fastify')({
|
||||
http2: true,
|
||||
https: {
|
||||
key: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.cert'))
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.code(200).send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
[ALPN negotiation](https://datatracker.ietf.org/doc/html/rfc7301) allows
|
||||
support for both HTTPS and HTTP/2 over the same socket.
|
||||
Node core `req` and `res` objects can be either
|
||||
[HTTP/1](https://nodejs.org/api/http.html) or
|
||||
[HTTP/2](https://nodejs.org/api/http2.html). _Fastify_ supports this out of the
|
||||
box:
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const fastify = require('fastify')({
|
||||
http2: true,
|
||||
https: {
|
||||
allowHTTP1: true, // fallback support for HTTP1
|
||||
key: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.cert'))
|
||||
}
|
||||
})
|
||||
|
||||
// this route can be accessed through both protocols
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.code(200).send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
Test the new server with:
|
||||
|
||||
```
|
||||
$ npx h2url https://localhost:3000
|
||||
```
|
||||
|
||||
### Plain or insecure
|
||||
|
||||
For microservices, HTTP2 can connect in plain text, but this is not
|
||||
supported by browsers.
|
||||
|
||||
```js
|
||||
'use strict'
|
||||
|
||||
const fastify = require('fastify')({
|
||||
http2: true
|
||||
})
|
||||
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.code(200).send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
Test the new server with:
|
||||
|
||||
```
|
||||
$ npx h2url http://localhost:3000
|
||||
```
|
||||
|
||||
896
node_modules/fastify/docs/Reference/Hooks.md
generated
vendored
Normal file
896
node_modules/fastify/docs/Reference/Hooks.md
generated
vendored
Normal file
@@ -0,0 +1,896 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Hooks
|
||||
|
||||
Hooks are registered with the `fastify.addHook` method and allow you to listen
|
||||
to specific events in the application or request/response lifecycle. You have to
|
||||
register a hook before the event is triggered, otherwise, the event is lost.
|
||||
|
||||
By using hooks you can interact directly with the lifecycle of Fastify. There
|
||||
are Request/Reply hooks and application hooks:
|
||||
|
||||
- [Request/Reply Hooks](#requestreply-hooks)
|
||||
- [onRequest](#onrequest)
|
||||
- [preParsing](#preparsing)
|
||||
- [preValidation](#prevalidation)
|
||||
- [preHandler](#prehandler)
|
||||
- [preSerialization](#preserialization)
|
||||
- [onError](#onerror)
|
||||
- [onSend](#onsend)
|
||||
- [onResponse](#onresponse)
|
||||
- [onTimeout](#ontimeout)
|
||||
- [onRequestAbort](#onrequestabort)
|
||||
- [Manage Errors from a hook](#manage-errors-from-a-hook)
|
||||
- [Respond to a request from a hook](#respond-to-a-request-from-a-hook)
|
||||
- [Application Hooks](#application-hooks)
|
||||
- [onReady](#onready)
|
||||
- [onListen](#onlisten)
|
||||
- [onClose](#onclose)
|
||||
- [preClose](#preclose)
|
||||
- [onRoute](#onroute)
|
||||
- [onRegister](#onregister)
|
||||
- [Scope](#scope)
|
||||
- [Route level hooks](#route-level-hooks)
|
||||
- [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties)
|
||||
- [Diagnostics Channel Hooks](#diagnostics-channel-hooks)
|
||||
|
||||
> ℹ️ Note: The `done` callback is not available when using `async`/`await` or
|
||||
> returning a `Promise`. If you do invoke a `done` callback in this situation
|
||||
> unexpected behavior may occur, e.g. duplicate invocation of handlers.
|
||||
|
||||
## Request/Reply Hooks
|
||||
|
||||
[Request](./Request.md) and [Reply](./Reply.md) are the core Fastify objects.
|
||||
|
||||
`done` is the function to continue with the [lifecycle](./Lifecycle.md).
|
||||
|
||||
It is easy to understand where each hook is executed by looking at the
|
||||
[lifecycle page](./Lifecycle.md).
|
||||
|
||||
Hooks are affected by Fastify's encapsulation, and can thus be applied to
|
||||
selected routes. See the [Scopes](#scope) section for more information.
|
||||
|
||||
There are eight different hooks that you can use in Request/Reply *(in order of
|
||||
execution)*:
|
||||
|
||||
### onRequest
|
||||
```js
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('onRequest', async (request, reply) => {
|
||||
// Some code
|
||||
await asyncMethod()
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: In the [onRequest](#onrequest) hook, `request.body` will always be
|
||||
> `undefined`, because the body parsing happens before the
|
||||
> [preValidation](#prevalidation) hook.
|
||||
|
||||
### preParsing
|
||||
|
||||
If you are using the `preParsing` hook, you can transform the request payload
|
||||
stream before it is parsed. It receives the request and reply objects as other
|
||||
hooks, and a stream with the current request payload.
|
||||
|
||||
If it returns a value (via `return` or via the callback function), it must
|
||||
return a stream.
|
||||
|
||||
For instance, you can decompress the request body:
|
||||
|
||||
```js
|
||||
fastify.addHook('preParsing', (request, reply, payload, done) => {
|
||||
// Some code
|
||||
done(null, newPayload)
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('preParsing', async (request, reply, payload) => {
|
||||
// Some code
|
||||
await asyncMethod()
|
||||
return newPayload
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: In the [preParsing](#preparsing) hook, `request.body` will always be
|
||||
> `undefined`, because the body parsing happens before the
|
||||
> [preValidation](#prevalidation) hook.
|
||||
|
||||
> ℹ️ Note: You should also add a `receivedEncodedLength` property to the
|
||||
> returned stream. This property is used to correctly match the request payload
|
||||
> with the `Content-Length` header value. Ideally, this property should be updated
|
||||
> on each received chunk.
|
||||
|
||||
> ℹ️ Note: The size of the returned stream is checked to not exceed the limit
|
||||
> set in [`bodyLimit`](./Server.md#bodylimit) option.
|
||||
|
||||
### preValidation
|
||||
|
||||
If you are using the `preValidation` hook, you can change the payload before it
|
||||
is validated. For example:
|
||||
|
||||
```js
|
||||
fastify.addHook('preValidation', (request, reply, done) => {
|
||||
request.body = { ...request.body, importantKey: 'randomString' }
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('preValidation', async (request, reply) => {
|
||||
const importantKey = await generateRandomString()
|
||||
request.body = { ...request.body, importantKey }
|
||||
})
|
||||
```
|
||||
|
||||
### preHandler
|
||||
|
||||
The `preHandler` hook allows you to specify a function that is executed before
|
||||
a routes's handler.
|
||||
|
||||
```js
|
||||
fastify.addHook('preHandler', (request, reply, done) => {
|
||||
// some code
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('preHandler', async (request, reply) => {
|
||||
// Some code
|
||||
await asyncMethod()
|
||||
})
|
||||
```
|
||||
### preSerialization
|
||||
|
||||
If you are using the `preSerialization` hook, you can change (or replace) the
|
||||
payload before it is serialized. For example:
|
||||
|
||||
```js
|
||||
fastify.addHook('preSerialization', (request, reply, payload, done) => {
|
||||
const err = null
|
||||
const newPayload = { wrapped: payload }
|
||||
done(err, newPayload)
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('preSerialization', async (request, reply, payload) => {
|
||||
return { wrapped: payload }
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: The hook is NOT called if the payload is a `string`, a `Buffer`, a
|
||||
> `stream`, or `null`.
|
||||
|
||||
### onError
|
||||
```js
|
||||
fastify.addHook('onError', (request, reply, error, done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('onError', async (request, reply, error) => {
|
||||
// Useful for custom error logging
|
||||
// You should not use this hook to update the error
|
||||
})
|
||||
```
|
||||
This hook is useful if you need to do some custom error logging or add some
|
||||
specific header in case of error.
|
||||
|
||||
It is not intended for changing the error, and calling `reply.send` will throw
|
||||
an exception.
|
||||
|
||||
This hook will be executed before
|
||||
the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler).
|
||||
|
||||
> ℹ️ Note: Unlike the other hooks, passing an error to the `done` function is not
|
||||
> supported.
|
||||
|
||||
### onSend
|
||||
If you are using the `onSend` hook, you can change the payload. For example:
|
||||
|
||||
```js
|
||||
fastify.addHook('onSend', (request, reply, payload, done) => {
|
||||
const err = null;
|
||||
const newPayload = payload.replace('some-text', 'some-new-text')
|
||||
done(err, newPayload)
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('onSend', async (request, reply, payload) => {
|
||||
const newPayload = payload.replace('some-text', 'some-new-text')
|
||||
return newPayload
|
||||
})
|
||||
```
|
||||
|
||||
You can also clear the payload to send a response with an empty body by
|
||||
replacing the payload with `null`:
|
||||
|
||||
```js
|
||||
fastify.addHook('onSend', (request, reply, payload, done) => {
|
||||
reply.code(304)
|
||||
const newPayload = null
|
||||
done(null, newPayload)
|
||||
})
|
||||
```
|
||||
|
||||
> You can also send an empty body by replacing the payload with the empty string
|
||||
> `''`, but be aware that this will cause the `Content-Length` header to be set
|
||||
> to `0`, whereas the `Content-Length` header will not be set if the payload is
|
||||
> `null`.
|
||||
|
||||
> ℹ️ Note: If you change the payload, you may only change it to a `string`, a
|
||||
> `Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`.
|
||||
|
||||
|
||||
### onResponse
|
||||
```js
|
||||
fastify.addHook('onResponse', (request, reply, done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('onResponse', async (request, reply) => {
|
||||
// Some code
|
||||
await asyncMethod()
|
||||
})
|
||||
```
|
||||
|
||||
The `onResponse` hook is executed when a response has been sent, so you will not
|
||||
be able to send more data to the client. It can however be useful for sending
|
||||
data to external services, for example, to gather statistics.
|
||||
|
||||
> ℹ️ Note: Setting `disableRequestLogging` to `true` will disable any error log
|
||||
> inside the `onResponse` hook. In this case use `try - catch` to log errors.
|
||||
|
||||
### onTimeout
|
||||
|
||||
```js
|
||||
fastify.addHook('onTimeout', (request, reply, done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('onTimeout', async (request, reply) => {
|
||||
// Some code
|
||||
await asyncMethod()
|
||||
})
|
||||
```
|
||||
`onTimeout` is useful if you need to monitor the request timed out in your
|
||||
service (if the `connectionTimeout` property is set on the Fastify instance).
|
||||
The `onTimeout` hook is executed when a request is timed out and the HTTP socket
|
||||
has been hung up. Therefore, you will not be able to send data to the client.
|
||||
|
||||
### onRequestAbort
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequestAbort', (request, done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
```
|
||||
Or `async/await`:
|
||||
```js
|
||||
fastify.addHook('onRequestAbort', async (request) => {
|
||||
// Some code
|
||||
await asyncMethod()
|
||||
})
|
||||
```
|
||||
The `onRequestAbort` hook is executed when a client closes the connection before
|
||||
the entire request has been processed. Therefore, you will not be able to send
|
||||
data to the client.
|
||||
|
||||
> ℹ️ Note: Client abort detection is not completely reliable.
|
||||
> See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md)
|
||||
|
||||
### Manage Errors from a hook
|
||||
If you get an error during the execution of your hook, just pass it to `done()`
|
||||
and Fastify will automatically close the request and send the appropriate error
|
||||
code to the user.
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
done(new Error('Some error'))
|
||||
})
|
||||
```
|
||||
|
||||
If you want to pass a custom error code to the user, just use `reply.code()`:
|
||||
```js
|
||||
fastify.addHook('preHandler', (request, reply, done) => {
|
||||
reply.code(400)
|
||||
done(new Error('Some error'))
|
||||
})
|
||||
```
|
||||
*The error will be handled by [`Reply`](./Reply.md#errors).*
|
||||
|
||||
Or if you're using `async/await` you can just throw an error:
|
||||
```js
|
||||
fastify.addHook('onRequest', async (request, reply) => {
|
||||
throw new Error('Some error')
|
||||
})
|
||||
```
|
||||
|
||||
### Respond to a request from a hook
|
||||
|
||||
If needed, you can respond to a request before you reach the route handler, for
|
||||
example when implementing an authentication hook. Replying from a hook implies
|
||||
that the hook chain is __stopped__ and the rest of the hooks and handlers are
|
||||
not executed. If the hook is using the callback approach, i.e. it is not an
|
||||
`async` function or it returns a `Promise`, it is as simple as calling
|
||||
`reply.send()` and avoiding calling the callback. If the hook is `async`,
|
||||
`reply.send()` __must__ be called _before_ the function returns or the promise
|
||||
resolves, otherwise, the request will proceed. When `reply.send()` is called
|
||||
outside of the promise chain, it is important to `return reply` otherwise the
|
||||
request will be executed twice.
|
||||
|
||||
It is important to __not mix callbacks and `async`/`Promise`__, otherwise the
|
||||
hook chain will be executed twice.
|
||||
|
||||
If you are using `onRequest` or `preHandler` use `reply.send`.
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.send('Early response')
|
||||
})
|
||||
|
||||
// Works with async functions too
|
||||
fastify.addHook('preHandler', async (request, reply) => {
|
||||
setTimeout(() => {
|
||||
reply.send({ hello: 'from prehandler' })
|
||||
})
|
||||
return reply // mandatory, so the request is not executed further
|
||||
// Commenting the line above will allow the hooks to continue and fail with FST_ERR_REP_ALREADY_SENT
|
||||
})
|
||||
```
|
||||
|
||||
If you want to respond with a stream, you should avoid using an `async` function
|
||||
for the hook. If you must use an `async` function, your code will need to follow
|
||||
the pattern in
|
||||
[test/hooks-async.js](https://github.com/fastify/fastify/blob/94ea67ef2d8dce8a955d510cd9081aabd036fa85/test/hooks-async.js#L269-L275).
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
const stream = fs.createReadStream('some-file', 'utf8')
|
||||
reply.send(stream)
|
||||
})
|
||||
```
|
||||
|
||||
If you are sending a response without `await` on it, make sure to always `return
|
||||
reply`:
|
||||
|
||||
```js
|
||||
fastify.addHook('preHandler', async (request, reply) => {
|
||||
setImmediate(() => { reply.send('hello') })
|
||||
|
||||
// This is needed to signal the handler to wait for a response
|
||||
// to be sent outside of the promise chain
|
||||
return reply
|
||||
})
|
||||
|
||||
fastify.addHook('preHandler', async (request, reply) => {
|
||||
// the @fastify/static plugin will send a file asynchronously,
|
||||
// so we should return reply
|
||||
reply.sendFile('myfile')
|
||||
return reply
|
||||
})
|
||||
```
|
||||
|
||||
## Application Hooks
|
||||
|
||||
You can hook into the application-lifecycle as well.
|
||||
|
||||
- [onReady](#onready)
|
||||
- [onListen](#onlisten)
|
||||
- [onClose](#onclose)
|
||||
- [preClose](#preclose)
|
||||
- [onRoute](#onroute)
|
||||
- [onRegister](#onregister)
|
||||
|
||||
### onReady
|
||||
Triggered before the server starts listening for requests and when `.ready()` is
|
||||
invoked. It cannot change the routes or add new hooks. Registered hook functions
|
||||
are executed serially. Only after all `onReady` hook functions have completed
|
||||
will the server start listening for requests. Hook functions accept one
|
||||
argument: a callback, `done`, to be invoked after the hook function is complete.
|
||||
Hook functions are invoked with `this` bound to the associated Fastify instance.
|
||||
|
||||
```js
|
||||
// callback style
|
||||
fastify.addHook('onReady', function (done) {
|
||||
// Some code
|
||||
const err = null;
|
||||
done(err)
|
||||
})
|
||||
|
||||
// or async/await style
|
||||
fastify.addHook('onReady', async function () {
|
||||
// Some async code
|
||||
await loadCacheFromDatabase()
|
||||
})
|
||||
```
|
||||
|
||||
### onListen
|
||||
|
||||
Triggered when the server starts listening for requests. The hooks run one
|
||||
after another. If a hook function causes an error, it is logged and
|
||||
ignored, allowing the queue of hooks to continue. Hook functions accept one
|
||||
argument: a callback, `done`, to be invoked after the hook function is
|
||||
complete. Hook functions are invoked with `this` bound to the associated
|
||||
Fastify instance.
|
||||
|
||||
This is an alternative to `fastify.server.on('listening', () => {})`.
|
||||
|
||||
```js
|
||||
// callback style
|
||||
fastify.addHook('onListen', function (done) {
|
||||
// Some code
|
||||
const err = null;
|
||||
done(err)
|
||||
})
|
||||
|
||||
// or async/await style
|
||||
fastify.addHook('onListen', async function () {
|
||||
// Some async code
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: This hook will not run when the server is started using
|
||||
> fastify.inject()` or `fastify.ready()`.
|
||||
|
||||
### onClose
|
||||
<a id="on-close"></a>
|
||||
|
||||
Triggered when `fastify.close()` is invoked to stop the server, after all in-flight
|
||||
HTTP requests have been completed.
|
||||
It is useful when [plugins](./Plugins.md) need a "shutdown" event, for example,
|
||||
to close an open connection to a database.
|
||||
|
||||
The hook function takes the Fastify instance as a first argument,
|
||||
and a `done` callback for synchronous hook functions.
|
||||
```js
|
||||
// callback style
|
||||
fastify.addHook('onClose', (instance, done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
|
||||
// or async/await style
|
||||
fastify.addHook('onClose', async (instance) => {
|
||||
// Some async code
|
||||
await closeDatabaseConnections()
|
||||
})
|
||||
```
|
||||
|
||||
### preClose
|
||||
<a id="pre-close"></a>
|
||||
|
||||
Triggered when `fastify.close()` is invoked to stop the server, before all in-flight
|
||||
HTTP requests have been completed.
|
||||
It is useful when [plugins](./Plugins.md) have set up some state attached
|
||||
to the HTTP server that would prevent the server to close.
|
||||
_It is unlikely you will need to use this hook_,
|
||||
use the [`onClose`](#onclose) for the most common case.
|
||||
|
||||
```js
|
||||
// callback style
|
||||
fastify.addHook('preClose', (done) => {
|
||||
// Some code
|
||||
done()
|
||||
})
|
||||
|
||||
// or async/await style
|
||||
fastify.addHook('preClose', async () => {
|
||||
// Some async code
|
||||
await removeSomeServerState()
|
||||
})
|
||||
```
|
||||
|
||||
### onRoute
|
||||
<a id="on-route"></a>
|
||||
|
||||
Triggered when a new route is registered. Listeners are passed a [`routeOptions`](./Routes.md#routes-options)
|
||||
object as the sole parameter. The interface is synchronous, and, as such, the
|
||||
listeners are not passed a callback. This hook is encapsulated.
|
||||
|
||||
```js
|
||||
fastify.addHook('onRoute', (routeOptions) => {
|
||||
//Some code
|
||||
routeOptions.method
|
||||
routeOptions.schema
|
||||
routeOptions.url // the complete URL of the route, it will include the prefix if any
|
||||
routeOptions.path // `url` alias
|
||||
routeOptions.routePath // the URL of the route without the prefix
|
||||
routeOptions.bodyLimit
|
||||
routeOptions.logLevel
|
||||
routeOptions.logSerializers
|
||||
routeOptions.prefix
|
||||
})
|
||||
```
|
||||
|
||||
If you are authoring a plugin and you need to customize application routes, like
|
||||
modifying the options or adding new route hooks, this is the right place.
|
||||
|
||||
```js
|
||||
fastify.addHook('onRoute', (routeOptions) => {
|
||||
function onPreSerialization(request, reply, payload, done) {
|
||||
// Your code
|
||||
done(null, payload)
|
||||
}
|
||||
// preSerialization can be an array or undefined
|
||||
routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization]
|
||||
})
|
||||
```
|
||||
|
||||
To add more routes within an onRoute hook, the routes must
|
||||
be tagged correctly. The hook will run into an infinite loop if
|
||||
not tagged. The recommended approach is shown below.
|
||||
|
||||
```js
|
||||
const kRouteAlreadyProcessed = Symbol('route-already-processed')
|
||||
|
||||
fastify.addHook('onRoute', function (routeOptions) {
|
||||
const { url, method } = routeOptions
|
||||
|
||||
const isAlreadyProcessed = (routeOptions.custom && routeOptions.custom[kRouteAlreadyProcessed]) || false
|
||||
|
||||
if (!isAlreadyProcessed) {
|
||||
this.route({
|
||||
url,
|
||||
method,
|
||||
custom: {
|
||||
[kRouteAlreadyProcessed]: true
|
||||
},
|
||||
handler: () => {}
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
For more details, see this [issue](https://github.com/fastify/fastify/issues/4319).
|
||||
|
||||
### onRegister
|
||||
<a id="on-register"></a>
|
||||
|
||||
Triggered when a new plugin is registered and a new encapsulation context is
|
||||
created. The hook will be executed **before** the registered code.
|
||||
|
||||
This hook can be useful if you are developing a plugin that needs to know when a
|
||||
plugin context is formed, and you want to operate in that specific context, thus
|
||||
this hook is encapsulated.
|
||||
|
||||
> ℹ️ Note: This hook will not be called if a plugin is wrapped inside
|
||||
> [`fastify-plugin`](https://github.com/fastify/fastify-plugin).
|
||||
```js
|
||||
fastify.decorate('data', [])
|
||||
|
||||
fastify.register(async (instance, opts) => {
|
||||
instance.data.push('hello')
|
||||
console.log(instance.data) // ['hello']
|
||||
|
||||
instance.register(async (instance, opts) => {
|
||||
instance.data.push('world')
|
||||
console.log(instance.data) // ['hello', 'world']
|
||||
}, { prefix: '/hola' })
|
||||
}, { prefix: '/ciao' })
|
||||
|
||||
fastify.register(async (instance, opts) => {
|
||||
console.log(instance.data) // []
|
||||
}, { prefix: '/hello' })
|
||||
|
||||
fastify.addHook('onRegister', (instance, opts) => {
|
||||
// Create a new array from the old one
|
||||
// but without keeping the reference
|
||||
// allowing the user to have encapsulated
|
||||
// instances of the `data` property
|
||||
instance.data = instance.data.slice()
|
||||
|
||||
// the options of the new registered instance
|
||||
console.log(opts.prefix)
|
||||
})
|
||||
```
|
||||
|
||||
## Scope
|
||||
<a id="scope"></a>
|
||||
|
||||
Except for [onClose](#onclose), all hooks are encapsulated. This means that you
|
||||
can decide where your hooks should run by using `register` as explained in the
|
||||
[plugins guide](../Guides/Plugins-Guide.md). If you pass a function, that
|
||||
function is bound to the right Fastify context and from there you have full
|
||||
access to the Fastify API.
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequest', function (request, reply, done) {
|
||||
const self = this // Fastify context
|
||||
done()
|
||||
})
|
||||
```
|
||||
|
||||
Note that the Fastify context in each hook is the same as the plugin where the
|
||||
route was registered, for example:
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequest', async function (req, reply) {
|
||||
if (req.raw.url === '/nested') {
|
||||
assert.strictEqual(this.foo, 'bar')
|
||||
} else {
|
||||
assert.strictEqual(this.foo, undefined)
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/', async function (req, reply) {
|
||||
assert.strictEqual(this.foo, undefined)
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
fastify.register(async function plugin (fastify, opts) {
|
||||
fastify.decorate('foo', 'bar')
|
||||
|
||||
fastify.get('/nested', async function (req, reply) {
|
||||
assert.strictEqual(this.foo, 'bar')
|
||||
return { hello: 'world' }
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Warn: if you declare the function with an [arrow
|
||||
function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions),
|
||||
the `this` will not be Fastify, but the one of the current scope.
|
||||
|
||||
|
||||
## Route level hooks
|
||||
<a id="route-hooks"></a>
|
||||
|
||||
You can declare one or more custom lifecycle hooks ([onRequest](#onrequest),
|
||||
[onResponse](#onresponse), [preParsing](#preparsing),
|
||||
[preValidation](#prevalidation), [preHandler](#prehandler),
|
||||
[preSerialization](#preserialization), [onSend](#onsend),
|
||||
[onTimeout](#ontimeout), and [onError](#onerror)) hook(s) that will be
|
||||
**unique** for the route. If you do so, those hooks are always executed as the
|
||||
last hook in their category.
|
||||
|
||||
This can be useful if you need to implement authentication, where the
|
||||
[preParsing](#preparsing) or [preValidation](#prevalidation) hooks are exactly
|
||||
what you need. Multiple route-level hooks can also be specified as an array.
|
||||
|
||||
```js
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
// Your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('onResponse', (request, reply, done) => {
|
||||
// your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('preParsing', (request, reply, done) => {
|
||||
// Your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('preValidation', (request, reply, done) => {
|
||||
// Your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('preHandler', (request, reply, done) => {
|
||||
// Your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('preSerialization', (request, reply, payload, done) => {
|
||||
// Your code
|
||||
done(null, payload)
|
||||
})
|
||||
|
||||
fastify.addHook('onSend', (request, reply, payload, done) => {
|
||||
// Your code
|
||||
done(null, payload)
|
||||
})
|
||||
|
||||
fastify.addHook('onTimeout', (request, reply, done) => {
|
||||
// Your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('onError', (request, reply, error, done) => {
|
||||
// Your code
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
schema: { ... },
|
||||
onRequest: function (request, reply, done) {
|
||||
// This hook will always be executed after the shared `onRequest` hooks
|
||||
done()
|
||||
},
|
||||
// // Example with an async hook. All hooks support this syntax
|
||||
//
|
||||
// onRequest: async function (request, reply) {
|
||||
// // This hook will always be executed after the shared `onRequest` hooks
|
||||
// await ...
|
||||
// }
|
||||
onResponse: function (request, reply, done) {
|
||||
// this hook will always be executed after the shared `onResponse` hooks
|
||||
done()
|
||||
},
|
||||
preParsing: function (request, reply, done) {
|
||||
// This hook will always be executed after the shared `preParsing` hooks
|
||||
done()
|
||||
},
|
||||
preValidation: function (request, reply, done) {
|
||||
// This hook will always be executed after the shared `preValidation` hooks
|
||||
done()
|
||||
},
|
||||
preHandler: function (request, reply, done) {
|
||||
// This hook will always be executed after the shared `preHandler` hooks
|
||||
done()
|
||||
},
|
||||
// // Example with an array. All hooks support this syntax.
|
||||
//
|
||||
// preHandler: [function (request, reply, done) {
|
||||
// // This hook will always be executed after the shared `preHandler` hooks
|
||||
// done()
|
||||
// }],
|
||||
preSerialization: (request, reply, payload, done) => {
|
||||
// This hook will always be executed after the shared `preSerialization` hooks
|
||||
done(null, payload)
|
||||
},
|
||||
onSend: (request, reply, payload, done) => {
|
||||
// This hook will always be executed after the shared `onSend` hooks
|
||||
done(null, payload)
|
||||
},
|
||||
onTimeout: (request, reply, done) => {
|
||||
// This hook will always be executed after the shared `onTimeout` hooks
|
||||
done()
|
||||
},
|
||||
onError: (request, reply, error, done) => {
|
||||
// This hook will always be executed after the shared `onError` hooks
|
||||
done()
|
||||
},
|
||||
handler: function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: Both options also accept an array of functions.
|
||||
|
||||
## Using Hooks to Inject Custom Properties
|
||||
<a id="using-hooks-to-inject-custom-properties"></a>
|
||||
|
||||
You can use a hook to inject custom properties into incoming requests.
|
||||
This is useful for reusing processed data from hooks in controllers.
|
||||
|
||||
A very common use case is, for example, checking user authentication based
|
||||
on their token and then storing their recovered data into
|
||||
the [Request](./Request.md) instance. This way, your controllers can read it
|
||||
easily with `request.authenticatedUser` or whatever you want to call it.
|
||||
That's how it might look like:
|
||||
|
||||
```js
|
||||
fastify.addHook('preParsing', async (request) => {
|
||||
request.authenticatedUser = {
|
||||
id: 42,
|
||||
name: 'Jane Doe',
|
||||
role: 'admin'
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/me/is-admin', async function (req, reply) {
|
||||
return { isAdmin: req.authenticatedUser?.role === 'admin' || false }
|
||||
})
|
||||
```
|
||||
|
||||
Note that `.authenticatedUser` could actually be any property name
|
||||
chosen by yourself. Using your own custom property prevents you
|
||||
from mutating existing properties, which
|
||||
would be a dangerous and destructive operation. So be careful and
|
||||
make sure your property is entirely new, also using this approach
|
||||
only for very specific and small cases like this example.
|
||||
|
||||
Regarding TypeScript in this example, you'd need to update the
|
||||
`FastifyRequest` core interface to include your new property typing
|
||||
(for more about it, see [TypeScript](./TypeScript.md) page), like:
|
||||
|
||||
```ts
|
||||
interface AuthenticatedUser { /* ... */ }
|
||||
|
||||
declare module 'fastify' {
|
||||
export interface FastifyRequest {
|
||||
authenticatedUser?: AuthenticatedUser;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Although this is a very pragmatic approach, if you're trying to do
|
||||
something more complex that changes these core objects, then
|
||||
consider creating a custom [Plugin](./Plugins.md) instead.
|
||||
|
||||
## Diagnostics Channel Hooks
|
||||
|
||||
One [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html)
|
||||
publish event, `'fastify.initialization'`, happens at initialization time. The
|
||||
Fastify instance is passed into the hook as a property of the object passed in.
|
||||
At this point, the instance can be interacted with to add hooks, plugins,
|
||||
routes, or any other sort of modification.
|
||||
|
||||
For example, a tracing package might do something like the following (which is,
|
||||
of course, a simplification). This would be in a file loaded in the
|
||||
initialization of the tracking package, in the typical "require instrumentation
|
||||
tools first" fashion.
|
||||
|
||||
```js
|
||||
const tracer = /* retrieved from elsewhere in the package */
|
||||
const dc = require('node:diagnostics_channel')
|
||||
const channel = dc.channel('fastify.initialization')
|
||||
const spans = new WeakMap()
|
||||
|
||||
channel.subscribe(function ({ fastify }) {
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
const span = tracer.startSpan('fastify.request.handler')
|
||||
spans.set(request, span)
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.addHook('onResponse', (request, reply, done) => {
|
||||
const span = spans.get(request)
|
||||
span.finish()
|
||||
done()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: The TracingChannel class API is currently experimental and may undergo
|
||||
> breaking changes even in semver-patch releases of Node.js.
|
||||
|
||||
Five other events are published on a per-request basis following the
|
||||
[Tracing Channel](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel)
|
||||
nomenclature. The list of the channel names and the event they receive is:
|
||||
|
||||
- `tracing:fastify.request.handler:start`: Always fires
|
||||
- `{ request: Request, reply: Reply, route: { url, method } }`
|
||||
- `tracing:fastify.request.handler:end`: Always fires
|
||||
- `{ request: Request, reply: Reply, route: { url, method }, async: Bool }`
|
||||
- `tracing:fastify.request.handler:asyncStart`: Fires for promise/async handlers
|
||||
- `{ request: Request, reply: Reply, route: { url, method } }`
|
||||
- `tracing:fastify.request.handler:asyncEnd`: Fires for promise/async handlers
|
||||
- `{ request: Request, reply: Reply, route: { url, method } }`
|
||||
- `tracing:fastify.request.handler:error`: Fires when an error occurs
|
||||
- `{ request: Request, reply: Reply, route: { url, method }, error: Error }`
|
||||
|
||||
The object instance remains the same for all events associated with a given
|
||||
request. All payloads include a `request` and `reply` property which are an
|
||||
instance of Fastify's `Request` and `Reply` instances. They also include a
|
||||
`route` property which is an object with the matched `url` pattern (e.g.
|
||||
`/collection/:id`) and the `method` HTTP method (e.g. `GET`). The `:start` and
|
||||
`:end` events always fire for requests. If a request handler is an `async`
|
||||
function or one that returns a `Promise` then the `:asyncStart` and `:asyncEnd`
|
||||
events also fire. Finally, the `:error` event contains an `error` property
|
||||
associated with the request's failure.
|
||||
|
||||
These events can be received like so:
|
||||
|
||||
```js
|
||||
const dc = require('node:diagnostics_channel')
|
||||
const channel = dc.channel('tracing:fastify.request.handler:start')
|
||||
channel.subscribe((msg) => {
|
||||
console.log(msg.request, msg.reply)
|
||||
})
|
||||
```
|
||||
73
node_modules/fastify/docs/Reference/Index.md
generated
vendored
Normal file
73
node_modules/fastify/docs/Reference/Index.md
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Core Documents
|
||||
<a id="reference-core-docs"></a>
|
||||
|
||||
For the full table of contents (TOC), see [below](#reference-toc). The following
|
||||
list is a subset of the full TOC that detail core Fastify APIs and concepts in
|
||||
order of most likely importance to the reader:
|
||||
|
||||
+ [Server](./Server.md): Documents the core Fastify API. Includes documentation
|
||||
for the factory function and the object returned by the factory function.
|
||||
+ [Lifecycle](./Lifecycle.md): Explains the Fastify request lifecycle and
|
||||
illustrates where [Hooks](./Hooks.md) are available for integrating with it.
|
||||
+ [Routes](./Routes.md): Details how to register routes with Fastify and how
|
||||
Fastify builds and evaluates the routing trie.
|
||||
+ [Request](./Request.md): Details Fastify's request object that is passed into
|
||||
each request handler.
|
||||
+ [Reply](./Reply.md): Details Fastify's response object available to each
|
||||
request handler.
|
||||
+ [Validation and Serialization](./Validation-and-Serialization.md): Details
|
||||
Fastify's support for validating incoming data and how Fastify serializes data
|
||||
for responses.
|
||||
+ [Plugins](./Plugins.md): Explains Fastify's plugin architecture and API.
|
||||
+ [Encapsulation](./Encapsulation.md): Explains a core concept upon which all
|
||||
Fastify plugins are built.
|
||||
+ [Decorators](./Decorators.md): Explains the server, request, and response
|
||||
decorator APIs.
|
||||
+ [Hooks](./Hooks.md): Details the API by which Fastify plugins can inject
|
||||
themselves into Fastify's handling of the request lifecycle.
|
||||
|
||||
|
||||
## Reference Documentation Table Of Contents
|
||||
<a id="reference-toc"></a>
|
||||
|
||||
This table of contents is in alphabetical order.
|
||||
|
||||
+ [Content Type Parser](./ContentTypeParser.md): Documents Fastify's default
|
||||
content type parser and how to add support for new content types.
|
||||
+ [Decorators](./Decorators.md): Explains the server, request, and response
|
||||
decorator APIs.
|
||||
+ [Encapsulation](./Encapsulation.md): Explains a core concept upon which all
|
||||
Fastify plugins are built.
|
||||
+ [Errors](./Errors.md): Details how Fastify handles errors and lists the
|
||||
standard set of errors Fastify generates.
|
||||
+ [Hooks](./Hooks.md): Details the API by which Fastify plugins can inject
|
||||
themselves into Fastify's handling of the request lifecycle.
|
||||
+ [HTTP2](./HTTP2.md): Details Fastify's HTTP2 support.
|
||||
+ [Lifecycle](./Lifecycle.md): Explains the Fastify request lifecycle and
|
||||
illustrates where [Hooks](./Hooks.md) are available for integrating with it.
|
||||
+ [Logging](./Logging.md): Details Fastify's included logging and how to
|
||||
customize it.
|
||||
+ [Long Term Support](./LTS.md): Explains Fastify's long term support (LTS)
|
||||
guarantee and the exceptions possible to the [semver](https://semver.org)
|
||||
contract.
|
||||
+ [Middleware](./Middleware.md): Details Fastify's support for Express.js style
|
||||
middleware.
|
||||
+ [Plugins](./Plugins.md): Explains Fastify's plugin architecture and API.
|
||||
+ [Reply](./Reply.md): Details Fastify's response object available to each
|
||||
request handler.
|
||||
+ [Request](./Request.md): Details Fastify's request object that is passed into
|
||||
each request handler.
|
||||
+ [Routes](./Routes.md): Details how to register routes with Fastify and how
|
||||
Fastify builds and evaluates the routing trie.
|
||||
+ [Server](./Server.md): Documents the core Fastify API. Includes documentation
|
||||
for the factory function and the object returned by the factory function.
|
||||
+ [TypeScript](./TypeScript.md): Documents Fastify's TypeScript support and
|
||||
provides recommendations for writing applications in TypeScript that utilize
|
||||
Fastify.
|
||||
+ [Validation and Serialization](./Validation-and-Serialization.md): Details
|
||||
Fastify's support for validating incoming data and how Fastify serializes data
|
||||
for responses.
|
||||
+ [Warnings](./Warnings.md): Details the warnings Fastify emits and how to
|
||||
solve them.
|
||||
86
node_modules/fastify/docs/Reference/LTS.md
generated
vendored
Normal file
86
node_modules/fastify/docs/Reference/LTS.md
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Long Term Support
|
||||
<a id="lts"></a>
|
||||
|
||||
Fastify's Long Term Support (LTS) is provided according to the schedule laid out
|
||||
in this document:
|
||||
|
||||
1. Major releases, "X" release of [semantic versioning][semver] X.Y.Z release
|
||||
versions, are supported for a minimum period of six months from their release
|
||||
date. The release date of any specific version can be found at
|
||||
[https://github.com/fastify/fastify/releases](https://github.com/fastify/fastify/releases).
|
||||
2. Major releases will receive security updates for an additional six months
|
||||
from the release of the next major release. After this period we will still
|
||||
review and release security fixes as long as they are provided by the
|
||||
community and they do not violate other constraints, e.g. minimum supported
|
||||
Node.js version.
|
||||
3. Major releases will be tested and verified against all Node.js release lines
|
||||
that are supported by the [Node.js LTS
|
||||
policy](https://github.com/nodejs/Release) within the LTS period of that
|
||||
given Fastify release line. This implies that only the latest Node.js release
|
||||
of a given line is supported.
|
||||
4. In addition to Node.js runtime, major releases of Fastify will also be tested
|
||||
and verified against alternative runtimes that are compatible with Node.js.
|
||||
The maintenance teams of these alternative runtimes are responsible for ensuring
|
||||
and guaranteeing these tests work properly.
|
||||
1. [N|Solid](https://docs.nodesource.com/docs/product_suite) tests and
|
||||
verifies each Fastify major release against current N|Solid LTS versions.
|
||||
NodeSource ensures Fastify compatibility with N|Solid, aligning with the
|
||||
support scope of N|Solid LTS versions at the time of the Fastify release.
|
||||
This guarantees N|Solid users can confidently use Fastify.
|
||||
|
||||
A "month" is defined as 30 consecutive days.
|
||||
|
||||
> ## Security Releases and Semver
|
||||
>
|
||||
> As a consequence of providing long-term support for major releases, there are
|
||||
> occasions where we need to release breaking changes as a _minor_ version
|
||||
> release. Such changes will _always_ be noted in the [release
|
||||
> notes](https://github.com/fastify/fastify/releases).
|
||||
>
|
||||
> To avoid automatically receiving breaking security updates it is possible to
|
||||
> use the tilde (`~`) range qualifier. For example, to get patches for the 3.15
|
||||
> release, and avoid automatically updating to the 3.16 release, specify the
|
||||
> dependency as `"fastify": "~3.15.x"`. This will leave your application
|
||||
> vulnerable, so please use it with caution.
|
||||
|
||||
### Security Support Beyond LTS
|
||||
|
||||
Fastify's partner, HeroDevs, provides commercial security support through the
|
||||
OpenJS Ecosystem Sustainability Program for versions of Fastify that are EOL.
|
||||
For more information, see their [Never Ending Support][hd-link] service.
|
||||
|
||||
### Schedule
|
||||
<a id="lts-schedule"></a>
|
||||
|
||||
| Version | Release Date | End Of LTS Date | Node.js | Nsolid(Node) |
|
||||
| :------ | :----------- | :-------------- | :----------------- | :------------- |
|
||||
| 1.0.0 | 2018-03-06 | 2019-09-01 | 6, 8, 9, 10, 11 | |
|
||||
| 2.0.0 | 2019-02-25 | 2021-01-31 | 6, 8, 10, 12, 14 | |
|
||||
| 3.0.0 | 2020-07-07 | 2023-06-30 | 10, 12, 14, 16, 18 | v5(18) |
|
||||
| 4.0.0 | 2022-06-08 | 2025-06-30 | 14, 16, 18, 20, 22 | v5(18), v5(20) |
|
||||
| 5.0.0 | 2024-09-17 | TBD | 20, 22 | v5(20) |
|
||||
|
||||
### CI tested operating systems
|
||||
<a id="supported-os"></a>
|
||||
|
||||
Fastify uses GitHub Actions for CI testing, please refer to [GitHub's
|
||||
documentation regarding workflow
|
||||
runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources)
|
||||
for further details on what the latest virtual environment is in relation to the
|
||||
YAML workflow labels below:
|
||||
|
||||
| OS | YAML Workflow Label | Package Manager | Node.js | Nsolid(Node) |
|
||||
| ------- | ------------------- | --------------- | ----------- | ------------- |
|
||||
| Linux | `ubuntu-latest` | npm | 20 | v5(20) |
|
||||
| Linux | `ubuntu-latest` | yarn,pnpm | 20 | v5(20) |
|
||||
| Windows | `windows-latest` | npm | 20 | v5(20) |
|
||||
| MacOS | `macos-latest` | npm | 20 | v5(20) |
|
||||
|
||||
Using [yarn](https://yarnpkg.com/) might require passing the `--ignore-engines`
|
||||
flag.
|
||||
|
||||
[semver]: https://semver.org/
|
||||
|
||||
[hd-link]: https://www.herodevs.com/support/fastify-nes?utm_source=fastify&utm_medium=link&utm_campaign=eol_support_fastify
|
||||
84
node_modules/fastify/docs/Reference/Lifecycle.md
generated
vendored
Normal file
84
node_modules/fastify/docs/Reference/Lifecycle.md
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Lifecycle
|
||||
<a id="lifecycle"></a>
|
||||
|
||||
This schema shows the internal lifecycle of Fastify.
|
||||
|
||||
The right branch of each section shows the next phase of the lifecycle. The left
|
||||
branch shows the corresponding error code generated if the parent throws an
|
||||
error. All errors are automatically handled by Fastify.
|
||||
|
||||
```
|
||||
Incoming Request
|
||||
│
|
||||
└─▶ Routing
|
||||
│
|
||||
└─▶ Instance Logger
|
||||
│
|
||||
4**/5** ◀─┴─▶ onRequest Hook
|
||||
│
|
||||
4**/5** ◀─┴─▶ preParsing Hook
|
||||
│
|
||||
4**/5** ◀─┴─▶ Parsing
|
||||
│
|
||||
4**/5** ◀─┴─▶ preValidation Hook
|
||||
│
|
||||
400 ◀─┴─▶ Validation
|
||||
│
|
||||
4**/5** ◀─┴─▶ preHandler Hook
|
||||
│
|
||||
4**/5** ◀─┴─▶ User Handler
|
||||
│
|
||||
└─▶ Reply
|
||||
│
|
||||
4**/5** ◀─┴─▶ preSerialization Hook
|
||||
│
|
||||
└─▶ onSend Hook
|
||||
│
|
||||
4**/5** ◀─┴─▶ Outgoing Response
|
||||
│
|
||||
└─▶ onResponse Hook
|
||||
```
|
||||
|
||||
Before or during the `User Handler`, `reply.hijack()` can be called to:
|
||||
- Prevent Fastify from running subsequent hooks and the user handler
|
||||
- Prevent Fastify from sending the response automatically
|
||||
|
||||
If `reply.raw` is used to send a response, `onResponse` hooks will still
|
||||
be executed.
|
||||
|
||||
## Reply Lifecycle
|
||||
<a id="reply-lifecycle"></a>
|
||||
|
||||
When the user handles the request, the result may be:
|
||||
|
||||
- In an async handler: it returns a payload or throws an `Error`
|
||||
- In a sync handler: it sends a payload or an `Error` instance
|
||||
|
||||
If the reply was hijacked, all subsequent steps are skipped. Otherwise, when
|
||||
submitted, the data flow is as follows:
|
||||
|
||||
```
|
||||
★ schema validation Error
|
||||
│
|
||||
└─▶ schemaErrorFormatter
|
||||
│
|
||||
reply sent ◀── JSON ─┴─ Error instance
|
||||
│
|
||||
│ ★ throw an Error
|
||||
★ send or return │ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
reply sent ◀── JSON ─┴─ Error instance ──▶ onError Hook ◀───────┘
|
||||
│
|
||||
reply sent ◀── JSON ─┴─ Error instance ──▶ setErrorHandler
|
||||
│
|
||||
└─▶ reply sent
|
||||
```
|
||||
|
||||
`reply sent` means the JSON payload will be serialized by one of the following:
|
||||
- The [reply serializer](./Server.md#setreplyserializer) if set
|
||||
- The [serializer compiler](./Server.md#setserializercompiler) if a JSON schema
|
||||
is set for the HTTP status code
|
||||
- The default `JSON.stringify` function
|
||||
260
node_modules/fastify/docs/Reference/Logging.md
generated
vendored
Normal file
260
node_modules/fastify/docs/Reference/Logging.md
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Logging
|
||||
|
||||
### Enable Logging
|
||||
Logging is disabled by default. Enable it by passing `{ logger: true }` or
|
||||
`{ logger: { level: 'info' } }` when creating a Fastify instance. Note that if
|
||||
the logger is disabled, it cannot be enabled at runtime.
|
||||
[abstract-logging](https://www.npmjs.com/package/abstract-logging) is used for
|
||||
this purpose.
|
||||
|
||||
As Fastify is focused on performance, it uses
|
||||
[pino](https://github.com/pinojs/pino) as its logger, with the default log
|
||||
level set to `'info'` when enabled.
|
||||
|
||||
#### Basic logging setup
|
||||
Enabling the production JSON logger:
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: true
|
||||
})
|
||||
```
|
||||
|
||||
#### Environment-Specific Configuration
|
||||
Enabling the logger with appropriate configuration for local development,
|
||||
production, and test environments requires more configuration:
|
||||
|
||||
```js
|
||||
const envToLogger = {
|
||||
development: {
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
},
|
||||
production: true,
|
||||
test: false,
|
||||
}
|
||||
const fastify = require('fastify')({
|
||||
logger: envToLogger[environment] ?? true // defaults to true if no entry matches in the map
|
||||
})
|
||||
```
|
||||
⚠️ `pino-pretty` needs to be installed as a dev dependency. It is not included
|
||||
by default for performance reasons.
|
||||
|
||||
### Usage
|
||||
The logger can be used in route handlers as follows:
|
||||
|
||||
```js
|
||||
fastify.get('/', options, function (request, reply) {
|
||||
request.log.info('Some info about the current request')
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
Trigger new logs outside route handlers using the Pino instance from the Fastify
|
||||
instance:
|
||||
```js
|
||||
fastify.log.info('Something important happened!');
|
||||
```
|
||||
|
||||
#### Passing Logger Options
|
||||
To pass options to the logger, provide them to Fastify. See the
|
||||
[Pino documentation](https://github.com/pinojs/pino/blob/master/docs/api.md#options)
|
||||
for available options. To specify a file destination, use:
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
level: 'info',
|
||||
file: '/path/to/file' // Will use pino.destination()
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/', options, function (request, reply) {
|
||||
request.log.info('Some info about the current request')
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
To pass a custom stream to the Pino instance, add a `stream` field to the logger
|
||||
object:
|
||||
|
||||
```js
|
||||
const split = require('split2')
|
||||
const stream = split(JSON.parse)
|
||||
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
level: 'info',
|
||||
stream: stream
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Advanced Logger Configuration
|
||||
|
||||
<a id="logging-request-id"></a>
|
||||
#### Request ID Tracking
|
||||
By default, Fastify adds an ID to every request for easier tracking. If the
|
||||
`requestIdHeader` option is set and the corresponding header is present, its
|
||||
value is used; otherwise, a new incremental ID is generated. See Fastify Factory
|
||||
[`requestIdHeader`](./Server.md#factory-request-id-header) and Fastify Factory
|
||||
[`genReqId`](./Server.md#genreqid) for customization options.
|
||||
|
||||
#### Serializers
|
||||
The default logger uses standard serializers for objects with `req`, `res`, and
|
||||
`err` properties. The `req` object is the Fastify [`Request`](./Request.md)
|
||||
object, and the `res` object is the Fastify [`Reply`](./Reply.md) object. This
|
||||
behavior can be customized with custom serializers.
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
serializers: {
|
||||
req (request) {
|
||||
return { url: request.url }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
For example, the response payload and headers could be logged using the approach
|
||||
below (not recommended):
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
},
|
||||
serializers: {
|
||||
res (reply) {
|
||||
// The default
|
||||
return {
|
||||
statusCode: reply.statusCode
|
||||
}
|
||||
},
|
||||
req (request) {
|
||||
return {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
path: request.routeOptions.url,
|
||||
parameters: request.params,
|
||||
// Including headers in the log could violate privacy laws,
|
||||
// e.g., GDPR. Use the "redact" option to remove sensitive
|
||||
// fields. It could also leak authentication data in the logs.
|
||||
headers: request.headers
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> ℹ️ Note: In some cases, the [`Reply`](./Reply.md) object passed to the `res`
|
||||
> serializer cannot be fully constructed. When writing a custom `res`
|
||||
> serializer, check for the existence of any properties on `reply` aside from
|
||||
> `statusCode`, which is always present. For example, verify the existence of
|
||||
> `getHeaders` before calling it:
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({
|
||||
logger: {
|
||||
transport: {
|
||||
target: 'pino-pretty'
|
||||
},
|
||||
serializers: {
|
||||
res (reply) {
|
||||
// The default
|
||||
return {
|
||||
statusCode: reply.statusCode,
|
||||
headers: typeof reply.getHeaders === 'function'
|
||||
? reply.getHeaders()
|
||||
: {}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> ℹ️ Note: The body cannot be serialized inside a `req` method because the
|
||||
request is serialized when the child logger is created. At that time, the body
|
||||
is not yet parsed.
|
||||
|
||||
See the following approach to log `req.body`:
|
||||
|
||||
```js
|
||||
app.addHook('preHandler', function (req, reply, done) {
|
||||
if (req.body) {
|
||||
req.log.info({ body: req.body }, 'parsed body')
|
||||
}
|
||||
done()
|
||||
})
|
||||
```
|
||||
|
||||
> ℹ️ Note: Ensure serializers never throw errors, as this can cause the Node
|
||||
> process to exit. See the
|
||||
> [Pino documentation](https://getpino.io/#/docs/api?id=opt-serializers) for more
|
||||
> information.
|
||||
|
||||
*Any logger other than Pino will ignore this option.*
|
||||
|
||||
### Using Custom Loggers
|
||||
A custom logger instance can be supplied by passing it as `loggerInstance`. The
|
||||
logger must conform to the Pino interface, with methods: `info`, `error`,
|
||||
`debug`, `fatal`, `warn`, `trace`, `silent`, `child`, and a string property
|
||||
`level`.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
const log = require('pino')({ level: 'info' })
|
||||
const fastify = require('fastify')({ loggerInstance: log })
|
||||
|
||||
log.info('does not have request information')
|
||||
|
||||
fastify.get('/', function (request, reply) {
|
||||
request.log.info('includes request information, but is the same logger instance as `log`')
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
*The logger instance for the current request is available in every part of the
|
||||
[lifecycle](./Lifecycle.md).*
|
||||
|
||||
### Log Redaction
|
||||
|
||||
[Pino](https://getpino.io) supports low-overhead log redaction for obscuring
|
||||
values of specific properties in recorded logs. For example, log all HTTP
|
||||
headers except the `Authorization` header for security:
|
||||
|
||||
```js
|
||||
const fastify = Fastify({
|
||||
logger: {
|
||||
stream: stream,
|
||||
redact: ['req.headers.authorization'],
|
||||
level: 'info',
|
||||
serializers: {
|
||||
req (request) {
|
||||
return {
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
headers: request.headers,
|
||||
host: request.host,
|
||||
remoteAddress: request.ip,
|
||||
remotePort: request.socket.remotePort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See https://getpino.io/#/docs/redaction for more details.
|
||||
78
node_modules/fastify/docs/Reference/Middleware.md
generated
vendored
Normal file
78
node_modules/fastify/docs/Reference/Middleware.md
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Middleware
|
||||
|
||||
Starting with Fastify v3.0.0, middleware is not supported out of the box and
|
||||
requires an external plugin such as
|
||||
[`@fastify/express`](https://github.com/fastify/fastify-express) or
|
||||
[`@fastify/middie`](https://github.com/fastify/middie).
|
||||
|
||||
|
||||
An example of registering the
|
||||
[`@fastify/express`](https://github.com/fastify/fastify-express) plugin to `use`
|
||||
Express middleware:
|
||||
|
||||
```js
|
||||
await fastify.register(require('@fastify/express'))
|
||||
fastify.use(require('cors')())
|
||||
fastify.use(require('dns-prefetch-control')())
|
||||
fastify.use(require('frameguard')())
|
||||
fastify.use(require('hsts')())
|
||||
fastify.use(require('ienoopen')())
|
||||
fastify.use(require('x-xss-protection')())
|
||||
```
|
||||
|
||||
[`@fastify/middie`](https://github.com/fastify/middie) can also be used,
|
||||
which provides support for simple Express-style middleware with improved
|
||||
performance:
|
||||
|
||||
```js
|
||||
await fastify.register(require('@fastify/middie'))
|
||||
fastify.use(require('cors')())
|
||||
```
|
||||
|
||||
Middleware can be encapsulated, allowing control over where it runs using
|
||||
`register` as explained in the [plugins guide](../Guides/Plugins-Guide.md).
|
||||
|
||||
Fastify middleware does not expose the `send` method or other methods specific
|
||||
to the Fastify [Reply](./Reply.md#reply) instance. This is because Fastify wraps
|
||||
the incoming `req` and `res` Node instances using the
|
||||
[Request](./Request.md#request) and [Reply](./Reply.md#reply) objects
|
||||
internally, but this is done after the middleware phase. To create middleware,
|
||||
use the Node `req` and `res` instances. Alternatively, use the `preHandler` hook
|
||||
that already has the Fastify [Request](./Request.md#request) and
|
||||
[Reply](./Reply.md#reply) instances. For more information, see
|
||||
[Hooks](./Hooks.md#hooks).
|
||||
|
||||
#### Restrict middleware execution to certain paths
|
||||
<a id="restrict-usage"></a>
|
||||
|
||||
To run middleware under certain paths, pass the path as the first parameter to
|
||||
`use`.
|
||||
|
||||
> ℹ️ Note: This does not support routes with parameters
|
||||
> (e.g. `/user/:id/comments`) and wildcards are not supported in multiple paths.
|
||||
|
||||
```js
|
||||
const path = require('node:path')
|
||||
const serveStatic = require('serve-static')
|
||||
|
||||
// Single path
|
||||
fastify.use('/css', serveStatic(path.join(__dirname, '/assets')))
|
||||
|
||||
// Wildcard path
|
||||
fastify.use('/css/(.*)', serveStatic(path.join(__dirname, '/assets')))
|
||||
|
||||
// Multiple paths
|
||||
fastify.use(['/css', '/js'], serveStatic(path.join(__dirname, '/assets')))
|
||||
```
|
||||
|
||||
### Alternatives
|
||||
|
||||
Fastify offers alternatives to commonly used middleware, such as
|
||||
[`@fastify/helmet`](https://github.com/fastify/fastify-helmet) for
|
||||
[`helmet`](https://github.com/helmetjs/helmet),
|
||||
[`@fastify/cors`](https://github.com/fastify/fastify-cors) for
|
||||
[`cors`](https://github.com/expressjs/cors), and
|
||||
[`@fastify/static`](https://github.com/fastify/fastify-static) for
|
||||
[`serve-static`](https://github.com/expressjs/serve-static).
|
||||
236
node_modules/fastify/docs/Reference/Plugins.md
generated
vendored
Normal file
236
node_modules/fastify/docs/Reference/Plugins.md
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Plugins
|
||||
Fastify can be extended with plugins, which can be a set of routes, a server
|
||||
[decorator](./Decorators.md), or other functionality. Use the `register` API to
|
||||
add one or more plugins.
|
||||
|
||||
By default, `register` creates a *new scope*, meaning changes to the Fastify
|
||||
instance (via `decorate`) will not affect the current context ancestors, only
|
||||
its descendants. This feature enables plugin *encapsulation* and *inheritance*,
|
||||
creating a *directed acyclic graph* (DAG) and avoiding cross-dependency issues.
|
||||
|
||||
The [Getting Started](../Guides/Getting-Started.md#your-first-plugin) guide
|
||||
includes an example of using this API:
|
||||
```js
|
||||
fastify.register(plugin, [options])
|
||||
```
|
||||
|
||||
### Plugin Options
|
||||
<a id="plugin-options"></a>
|
||||
|
||||
The optional `options` parameter for `fastify.register` supports a predefined
|
||||
set of options that Fastify itself will use, except when the plugin has been
|
||||
wrapped with [fastify-plugin](https://github.com/fastify/fastify-plugin). This
|
||||
options object will also be passed to the plugin upon invocation, regardless of
|
||||
whether or not the plugin has been wrapped. The currently supported list of
|
||||
Fastify specific options is:
|
||||
|
||||
+ [`logLevel`](./Routes.md#custom-log-level)
|
||||
+ [`logSerializers`](./Routes.md#custom-log-serializer)
|
||||
+ [`prefix`](#route-prefixing-option)
|
||||
|
||||
These options will be ignored when used with fastify-plugin.
|
||||
|
||||
To avoid collisions, a plugin should consider namespacing its options. For
|
||||
example, a plugin `foo` might be registered like so:
|
||||
|
||||
```js
|
||||
fastify.register(require('fastify-foo'), {
|
||||
prefix: '/foo',
|
||||
foo: {
|
||||
fooOption1: 'value',
|
||||
fooOption2: 'value'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If collisions are not a concern, the plugin may accept the options object as-is:
|
||||
|
||||
```js
|
||||
fastify.register(require('fastify-foo'), {
|
||||
prefix: '/foo',
|
||||
fooOption1: 'value',
|
||||
fooOption2: 'value'
|
||||
})
|
||||
```
|
||||
|
||||
The `options` parameter can also be a `Function` evaluated at plugin registration,
|
||||
providing access to the Fastify instance via the first argument:
|
||||
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
|
||||
fastify.register(fp((fastify, opts, done) => {
|
||||
fastify.decorate('foo_bar', { hello: 'world' })
|
||||
|
||||
done()
|
||||
}))
|
||||
|
||||
// The opts argument of fastify-foo will be { hello: 'world' }
|
||||
fastify.register(require('fastify-foo'), parent => parent.foo_bar)
|
||||
```
|
||||
|
||||
The Fastify instance passed to the function is the latest state of the **external
|
||||
Fastify instance** the plugin was declared on, allowing access to variables
|
||||
injected via [`decorate`](./Decorators.md) by preceding plugins according to the
|
||||
**order of registration**. This is useful if a plugin depends on changes made to
|
||||
the Fastify instance by a preceding plugin, such as utilizing an existing database
|
||||
connection.
|
||||
|
||||
Keep in mind that the Fastify instance passed to the function is the same as the
|
||||
one passed into the plugin, a copy of the external Fastify instance rather than a
|
||||
reference. Any usage of the instance will behave the same as it would if called
|
||||
within the plugin's function. For example, if `decorate` is called, the decorated
|
||||
variables will be available within the plugin's function unless it was wrapped
|
||||
with [`fastify-plugin`](https://github.com/fastify/fastify-plugin).
|
||||
|
||||
#### Route Prefixing option
|
||||
<a id="route-prefixing-option"></a>
|
||||
|
||||
If an option with the key `prefix` and a `string` value is passed, Fastify will
|
||||
use it to prefix all the routes inside the register. For more info, check
|
||||
[here](./Routes.md#route-prefixing).
|
||||
|
||||
Be aware that if routes are wrapped with
|
||||
[`fastify-plugin`](https://github.com/fastify/fastify-plugin), this option will
|
||||
not work (see the [workaround](./Routes.md#fastify-plugin)).
|
||||
|
||||
#### Error handling
|
||||
<a id="error-handling"></a>
|
||||
|
||||
Error handling is done by [avvio](https://github.com/mcollina/avvio#error-handling).
|
||||
|
||||
As a general rule, handle errors in the next `after` or `ready` block, otherwise
|
||||
they will be caught inside the `listen` callback.
|
||||
|
||||
```js
|
||||
fastify.register(require('my-plugin'))
|
||||
|
||||
// `after` will be executed once
|
||||
// the previous declared `register` has finished
|
||||
fastify.after(err => console.log(err))
|
||||
|
||||
// `ready` will be executed once all the registers declared
|
||||
// have finished their execution
|
||||
fastify.ready(err => console.log(err))
|
||||
|
||||
// `listen` is a special ready,
|
||||
// so it behaves in the same way
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
```
|
||||
|
||||
### async/await
|
||||
<a id="async-await"></a>
|
||||
|
||||
*async/await* is supported by `after`, `ready`, and `listen`, as well as
|
||||
`fastify` being a Thenable.
|
||||
|
||||
```js
|
||||
await fastify.register(require('my-plugin'))
|
||||
|
||||
await fastify.after()
|
||||
|
||||
await fastify.ready()
|
||||
|
||||
await fastify.listen({ port: 3000 })
|
||||
```
|
||||
Using `await` when registering a plugin loads the plugin and its dependencies,
|
||||
"finalizing" the encapsulation process. Any mutations to the plugin after it and
|
||||
its dependencies have been loaded will not be reflected in the parent instance.
|
||||
|
||||
#### ESM support
|
||||
<a id="esm-support"></a>
|
||||
|
||||
ESM is supported from [Node.js `v13.3.0`](https://nodejs.org/api/esm.html)
|
||||
and above.
|
||||
|
||||
```js
|
||||
// main.mjs
|
||||
import Fastify from 'fastify'
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.register(import('./plugin.mjs'))
|
||||
|
||||
fastify.listen({ port: 3000 }, console.log)
|
||||
|
||||
|
||||
// plugin.mjs
|
||||
async function plugin (fastify, opts) {
|
||||
fastify.get('/', async (req, reply) => {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
}
|
||||
|
||||
export default plugin
|
||||
```
|
||||
|
||||
### Create a plugin
|
||||
<a id="create-plugin"></a>
|
||||
|
||||
Creating a plugin is easy. Create a function that takes three parameters: the
|
||||
`fastify` instance, an `options` object, and the `done` callback.
|
||||
|
||||
Example:
|
||||
```js
|
||||
module.exports = function (fastify, opts, done) {
|
||||
fastify.decorate('utility', function () {})
|
||||
|
||||
fastify.get('/', handler)
|
||||
|
||||
done()
|
||||
}
|
||||
```
|
||||
`register` can also be used inside another `register`:
|
||||
```js
|
||||
module.exports = function (fastify, opts, done) {
|
||||
fastify.decorate('utility', function () {})
|
||||
|
||||
fastify.get('/', handler)
|
||||
|
||||
fastify.register(require('./other-plugin'))
|
||||
|
||||
done()
|
||||
}
|
||||
```
|
||||
|
||||
Remember, `register` always creates a new Fastify scope. If this is not needed,
|
||||
read the following section.
|
||||
|
||||
### Handle the scope
|
||||
<a id="handle-scope"></a>
|
||||
|
||||
If `register` is used only to extend server functionality with
|
||||
[`decorate`](./Decorators.md), tell Fastify not to create a new scope. Otherwise,
|
||||
changes will not be accessible in the upper scope.
|
||||
|
||||
There are two ways to avoid creating a new context:
|
||||
- Use the [`fastify-plugin`](https://github.com/fastify/fastify-plugin) module
|
||||
- Use the `'skip-override'` hidden property
|
||||
|
||||
Using the `fastify-plugin` module is recommended, as it solves this problem and
|
||||
allows passing a version range of Fastify that the plugin will support:
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
|
||||
module.exports = fp(function (fastify, opts, done) {
|
||||
fastify.decorate('utility', function () {})
|
||||
done()
|
||||
}, '0.x')
|
||||
```
|
||||
Check the [`fastify-plugin`](https://github.com/fastify/fastify-plugin)
|
||||
documentation to learn more about how to use this module.
|
||||
|
||||
If not using `fastify-plugin`, the `'skip-override'` hidden property can be used,
|
||||
but it is not recommended. Future Fastify API changes will be your responsibility
|
||||
to update, whilst `fastify-plugin` ensures backward compatibility.
|
||||
```js
|
||||
function yourPlugin (fastify, opts, done) {
|
||||
fastify.decorate('utility', function () {})
|
||||
done()
|
||||
}
|
||||
yourPlugin[Symbol.for('skip-override')] = true
|
||||
module.exports = yourPlugin
|
||||
```
|
||||
73
node_modules/fastify/docs/Reference/Principles.md
generated
vendored
Normal file
73
node_modules/fastify/docs/Reference/Principles.md
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# Technical Principles
|
||||
|
||||
Every decision in the Fastify framework and its official plugins is guided by
|
||||
the following technical principles:
|
||||
|
||||
1. “Zero” overhead in production
|
||||
2. “Good” developer experience
|
||||
3. Works great for small & big projects alike
|
||||
4. Easy to migrate to microservices (or even serverless) and back
|
||||
5. Security & data validation
|
||||
6. If something could be a plugin, it likely should be
|
||||
7. Easily testable
|
||||
8. Do not monkeypatch core
|
||||
9. Semantic versioning & Long Term Support
|
||||
10. Specification adherence
|
||||
|
||||
## "Zero" Overhead in Production
|
||||
|
||||
Fastify aims to implement features with minimal overhead. This is achieved by
|
||||
using fast algorithms, data structures, and JavaScript-specific features.
|
||||
|
||||
Since JavaScript does not offer zero-overhead data structures, this principle
|
||||
can conflict with providing a great developer experience and additional features,
|
||||
as these usually incur some overhead.
|
||||
|
||||
## "Good" Developer Experience
|
||||
|
||||
Fastify aims to provide the best developer experience at its performance point.
|
||||
It offers a great out-of-the-box experience that is flexible enough to adapt to
|
||||
various situations.
|
||||
|
||||
For example, binary addons are forbidden because most JavaScript developers do
|
||||
not have access to a compiler.
|
||||
|
||||
## Works great for small and big projects alike
|
||||
|
||||
Most applications start small and become more complex over time. Fastify aims to
|
||||
grow with this complexity, providing advanced features to structure codebases.
|
||||
|
||||
## Easy to migrate to microservices (or even serverless) and back
|
||||
|
||||
Route deployment should not matter. The framework should "just work".
|
||||
|
||||
## Security and Data Validation
|
||||
|
||||
A web framework is the first point of contact with untrusted data and must act
|
||||
as the first line of defense for the system.
|
||||
|
||||
## If something could be a plugin, it likely should
|
||||
|
||||
Recognizing the infinite use cases for an HTTP framework, catering to all in a
|
||||
single module would make the codebase unmaintainable. Therefore, hooks and
|
||||
options are provided to customize the framework as needed.
|
||||
|
||||
## Easily testable
|
||||
|
||||
Testing Fastify applications should be a first-class concern.
|
||||
|
||||
## Do not monkeypatch core
|
||||
|
||||
Monkeypatching Node.js APIs or installing globals that alter the runtime makes
|
||||
building modular applications harder and limits Fastify's use cases. Other
|
||||
frameworks do this; Fastify does not.
|
||||
|
||||
## Semantic Versioning and Long Term Support
|
||||
|
||||
A clear [Long Term Support strategy is provided](./LTS.md) to inform developers when
|
||||
to upgrade.
|
||||
|
||||
## Specification adherence
|
||||
|
||||
In doubt, we chose the strict behavior as defined by the relevant
|
||||
Specifications.
|
||||
979
node_modules/fastify/docs/Reference/Reply.md
generated
vendored
Normal file
979
node_modules/fastify/docs/Reference/Reply.md
generated
vendored
Normal file
@@ -0,0 +1,979 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Reply
|
||||
- [Reply](#reply)
|
||||
- [Introduction](#introduction)
|
||||
- [.code(statusCode)](#codestatuscode)
|
||||
- [.elapsedTime](#elapsedtime)
|
||||
- [.statusCode](#statuscode)
|
||||
- [.server](#server)
|
||||
- [.header(key, value)](#headerkey-value)
|
||||
- [.headers(object)](#headersobject)
|
||||
- [.getHeader(key)](#getheaderkey)
|
||||
- [.getHeaders()](#getheaders)
|
||||
- [.removeHeader(key)](#removeheaderkey)
|
||||
- [.hasHeader(key)](#hasheaderkey)
|
||||
- [.writeEarlyHints(hints, callback)](#writeearlyhintshints-callback)
|
||||
- [.trailer(key, function)](#trailerkey-function)
|
||||
- [.hasTrailer(key)](#hastrailerkey)
|
||||
- [.removeTrailer(key)](#removetrailerkey)
|
||||
- [.redirect(dest, [code ,])](#redirectdest--code)
|
||||
- [.callNotFound()](#callnotfound)
|
||||
- [.type(contentType)](#typecontenttype)
|
||||
- [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus)
|
||||
- [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus)
|
||||
- [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus)
|
||||
- [.serializer(func)](#serializerfunc)
|
||||
- [.raw](#raw)
|
||||
- [.sent](#sent)
|
||||
- [.hijack()](#hijack)
|
||||
- [.send(data)](#senddata)
|
||||
- [Objects](#objects)
|
||||
- [Strings](#strings)
|
||||
- [Streams](#streams)
|
||||
- [Buffers](#buffers)
|
||||
- [TypedArrays](#typedarrays)
|
||||
- [ReadableStream](#readablestream)
|
||||
- [Response](#response)
|
||||
- [Errors](#errors)
|
||||
- [Type of the final payload](#type-of-the-final-payload)
|
||||
- [Async-Await and Promises](#async-await-and-promises)
|
||||
- [.then(fulfilled, rejected)](#thenfulfilled-rejected)
|
||||
|
||||
### Introduction
|
||||
<a id="introduction"></a>
|
||||
|
||||
The second parameter of the handler function is `Reply`. Reply is a core Fastify
|
||||
object that exposes the following functions and properties:
|
||||
|
||||
- `.code(statusCode)` - Sets the status code.
|
||||
- `.status(statusCode)` - An alias for `.code(statusCode)`.
|
||||
- `.statusCode` - Read and set the HTTP status code.
|
||||
- `.elapsedTime` - Returns the amount of time passed
|
||||
since the request was received by Fastify.
|
||||
- `.server` - A reference to the fastify instance object.
|
||||
- `.header(name, value)` - Sets a response header.
|
||||
- `.headers(object)` - Sets all the keys of the object as response headers.
|
||||
- `.getHeader(name)` - Retrieve value of already set header.
|
||||
- `.getHeaders()` - Gets a shallow copy of all current response headers.
|
||||
- `.removeHeader(key)` - Remove the value of a previously set header.
|
||||
- `.hasHeader(name)` - Determine if a header has been set.
|
||||
- `.writeEarlyHints(hints, callback)` - Sends early hints to the user
|
||||
while the response is being prepared.
|
||||
- `.trailer(key, function)` - Sets a response trailer.
|
||||
- `.hasTrailer(key)` - Determine if a trailer has been set.
|
||||
- `.removeTrailer(key)` - Remove the value of a previously set trailer.
|
||||
- `.type(value)` - Sets the header `Content-Type`.
|
||||
- `.redirect(dest, [code,])` - Redirect to the specified URL, the status code is
|
||||
optional (defaults to `302`).
|
||||
- `.callNotFound()` - Invokes the custom not found handler.
|
||||
- `.serialize(payload)` - Serializes the specified payload using the default
|
||||
JSON serializer or using the custom serializer (if one is set) and returns the
|
||||
serialized payload.
|
||||
- `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization
|
||||
function for the specified schema or http status, if any of either are set.
|
||||
- `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles
|
||||
the specified schema and returns a serialization function using the default
|
||||
(or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded
|
||||
to the `SerializerCompiler` if provided, default to `undefined`.
|
||||
- `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes
|
||||
the specified data using the specified schema and returns the serialized payload.
|
||||
If the optional `httpStatus`, and `contentType` are provided, the function
|
||||
will use the serializer function given for that specific content type and
|
||||
HTTP Status Code. Default to `undefined`.
|
||||
- `.serializer(function)` - Sets a custom serializer for the payload.
|
||||
- `.send(payload)` - Sends the payload to the user, could be a plain text, a
|
||||
buffer, JSON, stream, or an Error object.
|
||||
- `.sent` - A boolean value that you can use if you need to know if `send` has
|
||||
already been called.
|
||||
- `.hijack()` - interrupt the normal request lifecycle.
|
||||
- `.raw` - The
|
||||
[`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse)
|
||||
from Node core.
|
||||
- `.log` - The logger instance of the incoming request.
|
||||
- `.request` - The incoming request.
|
||||
|
||||
```js
|
||||
fastify.get('/', options, function (request, reply) {
|
||||
// Your code
|
||||
reply
|
||||
.code(200)
|
||||
.header('Content-Type', 'application/json; charset=utf-8')
|
||||
.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
### .code(statusCode)
|
||||
<a id="code"></a>
|
||||
|
||||
If not set via `reply.code`, the resulting `statusCode` will be `200`.
|
||||
|
||||
### .elapsedTime
|
||||
<a id="elapsedTime"></a>
|
||||
|
||||
Invokes the custom response time getter to calculate the amount of time passed
|
||||
since the request was received by Fastify.
|
||||
|
||||
```js
|
||||
const milliseconds = reply.elapsedTime
|
||||
```
|
||||
|
||||
### .statusCode
|
||||
<a id="statusCode"></a>
|
||||
|
||||
This property reads and sets the HTTP status code. It is an alias for
|
||||
`reply.code()` when used as a setter.
|
||||
```js
|
||||
if (reply.statusCode >= 299) {
|
||||
reply.statusCode = 500
|
||||
}
|
||||
```
|
||||
|
||||
### .server
|
||||
<a id="server"></a>
|
||||
|
||||
The Fastify server instance, scoped to the current [encapsulation
|
||||
context](./Encapsulation.md).
|
||||
|
||||
```js
|
||||
fastify.decorate('util', function util () {
|
||||
return 'foo'
|
||||
})
|
||||
|
||||
fastify.get('/', async function (req, rep) {
|
||||
return rep.server.util() // foo
|
||||
})
|
||||
```
|
||||
|
||||
### .header(key, value)
|
||||
<a id="header"></a>
|
||||
|
||||
Sets a response header. If the value is omitted or undefined, it is coerced to
|
||||
`''`.
|
||||
|
||||
> ℹ️ Note: The header's value must be properly encoded using
|
||||
> [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
|
||||
> or similar modules such as
|
||||
> [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid characters
|
||||
> will result in a 500 `TypeError` response.
|
||||
|
||||
For more information, see
|
||||
[`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_response_setheader_name_value).
|
||||
|
||||
- ### set-cookie
|
||||
<a id="set-cookie"></a>
|
||||
|
||||
- When sending different values as a cookie with `set-cookie` as the key,
|
||||
every value will be sent as a cookie instead of replacing the previous
|
||||
value.
|
||||
|
||||
```js
|
||||
reply.header('set-cookie', 'foo');
|
||||
reply.header('set-cookie', 'bar');
|
||||
```
|
||||
- The browser will only consider the latest reference of a key for the
|
||||
`set-cookie` header. This is done to avoid parsing the `set-cookie` header
|
||||
when added to a reply and speeds up the serialization of the reply.
|
||||
|
||||
- To reset the `set-cookie` header, you need to make an explicit call to
|
||||
`reply.removeHeader('set-cookie')`, read more about `.removeHeader(key)`
|
||||
[here](#removeheaderkey).
|
||||
|
||||
|
||||
|
||||
### .headers(object)
|
||||
<a id="headers"></a>
|
||||
|
||||
Sets all the keys of the object as response headers.
|
||||
[`.header`](#headerkey-value) will be called under the hood.
|
||||
```js
|
||||
reply.headers({
|
||||
'x-foo': 'foo',
|
||||
'x-bar': 'bar'
|
||||
})
|
||||
```
|
||||
|
||||
### .getHeader(key)
|
||||
<a id="getHeader"></a>
|
||||
|
||||
Retrieves the value of a previously set header.
|
||||
```js
|
||||
reply.header('x-foo', 'foo') // setHeader: key, value
|
||||
reply.getHeader('x-foo') // 'foo'
|
||||
```
|
||||
|
||||
### .getHeaders()
|
||||
<a id="getHeaders"></a>
|
||||
|
||||
Gets a shallow copy of all current response headers, including those set via the
|
||||
raw `http.ServerResponse`. Note that headers set via Fastify take precedence
|
||||
over those set via `http.ServerResponse`.
|
||||
|
||||
```js
|
||||
reply.header('x-foo', 'foo')
|
||||
reply.header('x-bar', 'bar')
|
||||
reply.raw.setHeader('x-foo', 'foo2')
|
||||
reply.getHeaders() // { 'x-foo': 'foo', 'x-bar': 'bar' }
|
||||
```
|
||||
|
||||
### .removeHeader(key)
|
||||
<a id="getHeader"></a>
|
||||
|
||||
Remove the value of a previously set header.
|
||||
```js
|
||||
reply.header('x-foo', 'foo')
|
||||
reply.removeHeader('x-foo')
|
||||
reply.getHeader('x-foo') // undefined
|
||||
```
|
||||
|
||||
### .hasHeader(key)
|
||||
<a id="hasHeader"></a>
|
||||
|
||||
Returns a boolean indicating if the specified header has been set.
|
||||
|
||||
### .writeEarlyHints(hints, callback)
|
||||
<a id="writeEarlyHints"></a>
|
||||
|
||||
Sends early hints to the client. Early hints allow the client to
|
||||
start processing resources before the final response is sent.
|
||||
This can improve performance by allowing the client to preload
|
||||
or preconnect to resources while the server is still generating the response.
|
||||
|
||||
The hints parameter is an object containing the early hint key-value pairs.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
reply.writeEarlyHints({
|
||||
Link: '</styles.css>; rel=preload; as=style'
|
||||
});
|
||||
```
|
||||
|
||||
The optional callback parameter is a function that will be called
|
||||
once the hint is sent or if an error occurs.
|
||||
|
||||
### .trailer(key, function)
|
||||
<a id="trailer"></a>
|
||||
|
||||
Sets a response trailer. Trailer is usually used when you need a header that
|
||||
requires heavy resources to be sent after the `data`, for example,
|
||||
`Server-Timing` and `Etag`. It can ensure the client receives the response data
|
||||
as soon as possible.
|
||||
|
||||
> ℹ️ Note: The header `Transfer-Encoding: chunked` will be added once you use
|
||||
> the trailer. It is a hard requirement for using trailer in Node.js.
|
||||
|
||||
> ℹ️ Note: Any error passed to `done` callback will be ignored. If you interested
|
||||
> in the error, you can turn on `debug` level logging.*
|
||||
|
||||
```js
|
||||
reply.trailer('server-timing', function() {
|
||||
return 'db;dur=53, app;dur=47.2'
|
||||
})
|
||||
|
||||
const { createHash } = require('node:crypto')
|
||||
// trailer function also receive two argument
|
||||
// @param {object} reply fastify reply
|
||||
// @param {string|Buffer|null} payload payload that already sent, note that it will be null when stream is sent
|
||||
// @param {function} done callback to set trailer value
|
||||
reply.trailer('content-md5', function(reply, payload, done) {
|
||||
const hash = createHash('md5')
|
||||
hash.update(payload)
|
||||
done(null, hash.digest('hex'))
|
||||
})
|
||||
|
||||
// when you prefer async-await
|
||||
reply.trailer('content-md5', async function(reply, payload) {
|
||||
const hash = createHash('md5')
|
||||
hash.update(payload)
|
||||
return hash.digest('hex')
|
||||
})
|
||||
```
|
||||
|
||||
### .hasTrailer(key)
|
||||
<a id="hasTrailer"></a>
|
||||
|
||||
Returns a boolean indicating if the specified trailer has been set.
|
||||
|
||||
### .removeTrailer(key)
|
||||
<a id="removeTrailer"></a>
|
||||
|
||||
Remove the value of a previously set trailer.
|
||||
```js
|
||||
reply.trailer('server-timing', function() {
|
||||
return 'db;dur=53, app;dur=47.2'
|
||||
})
|
||||
reply.removeTrailer('server-timing')
|
||||
reply.getTrailer('server-timing') // undefined
|
||||
```
|
||||
|
||||
|
||||
### .redirect(dest, [code ,])
|
||||
<a id="redirect"></a>
|
||||
|
||||
Redirects a request to the specified URL, the status code is optional, default
|
||||
to `302` (if status code is not already set by calling `code`).
|
||||
|
||||
> ℹ️ Note: The input URL must be properly encoded using
|
||||
> [`encodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI)
|
||||
> or similar modules such as
|
||||
> [`encodeurl`](https://www.npmjs.com/package/encodeurl). Invalid URLs will
|
||||
> result in a 500 `TypeError` response.
|
||||
|
||||
Example (no `reply.code()` call) sets status code to `302` and redirects to
|
||||
`/home`
|
||||
```js
|
||||
reply.redirect('/home')
|
||||
```
|
||||
|
||||
Example (no `reply.code()` call) sets status code to `303` and redirects to
|
||||
`/home`
|
||||
```js
|
||||
reply.redirect('/home', 303)
|
||||
```
|
||||
|
||||
Example (`reply.code()` call) sets status code to `303` and redirects to `/home`
|
||||
```js
|
||||
reply.code(303).redirect('/home')
|
||||
```
|
||||
|
||||
Example (`reply.code()` call) sets status code to `302` and redirects to `/home`
|
||||
```js
|
||||
reply.code(303).redirect('/home', 302)
|
||||
```
|
||||
|
||||
### .callNotFound()
|
||||
<a id="call-not-found"></a>
|
||||
|
||||
Invokes the custom not found handler. Note that it will only call `preHandler`
|
||||
hook specified in [`setNotFoundHandler`](./Server.md#set-not-found-handler).
|
||||
|
||||
```js
|
||||
reply.callNotFound()
|
||||
```
|
||||
|
||||
### .type(contentType)
|
||||
<a id="type"></a>
|
||||
|
||||
Sets the content type for the response. This is a shortcut for
|
||||
`reply.header('Content-Type', 'the/type')`.
|
||||
|
||||
```js
|
||||
reply.type('text/html')
|
||||
```
|
||||
If the `Content-Type` has a JSON subtype, and the charset parameter is not set,
|
||||
`utf-8` will be used as the charset by default. For other content types, the
|
||||
charset must be set explicitly.
|
||||
|
||||
### .getSerializationFunction(schema | httpStatus, [contentType])
|
||||
<a id="getserializationfunction"></a>
|
||||
|
||||
By calling this function using a provided `schema` or `httpStatus`,
|
||||
and the optional `contentType`, it will return a `serialzation` function
|
||||
that can be used to serialize diverse inputs. It returns `undefined` if no
|
||||
serialization function was found using either of the provided inputs.
|
||||
|
||||
This heavily depends of the `schema#responses` attached to the route, or
|
||||
the serialization functions compiled by using `compileSerializationSchema`.
|
||||
|
||||
```js
|
||||
const serialize = reply
|
||||
.getSerializationFunction({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
})
|
||||
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
const serialize = reply
|
||||
.getSerializationFunction(200)
|
||||
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
const serialize = reply
|
||||
.getSerializationFunction(200, 'application/json')
|
||||
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
||||
```
|
||||
|
||||
See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
|
||||
for more information on how to compile serialization schemas.
|
||||
|
||||
### .compileSerializationSchema(schema, [httpStatus], [contentType])
|
||||
<a id="compileserializationschema"></a>
|
||||
|
||||
This function will compile a serialization schema and
|
||||
return a function that can be used to serialize data.
|
||||
The function returned (a.k.a. _serialization function_) returned is compiled
|
||||
by using the provided `SerializerCompiler`. Also this is cached by using
|
||||
a `WeakMap` for reducing compilation calls.
|
||||
|
||||
The optional parameters `httpStatus` and `contentType`, if provided,
|
||||
are forwarded directly to the `SerializerCompiler`, so it can be used
|
||||
to compile the serialization function if a custom `SerializerCompiler` is used.
|
||||
|
||||
This heavily depends of the `schema#responses` attached to the route, or
|
||||
the serialization functions compiled by using `compileSerializationSchema`.
|
||||
|
||||
```js
|
||||
const serialize = reply
|
||||
.compileSerializationSchema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
})
|
||||
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
const serialize = reply
|
||||
.compileSerializationSchema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}, 200)
|
||||
serialize({ foo: 'bar' }) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
const serialize = reply
|
||||
.compileSerializationSchema({
|
||||
'3xx': {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
name: { type: 'string' },
|
||||
phone: { type: 'number' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, '3xx', 'application/json')
|
||||
serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'
|
||||
```
|
||||
|
||||
Note that you should be careful when using this function, as it will cache
|
||||
the compiled serialization functions based on the schema provided. If the
|
||||
schemas provided is mutated or changed, the serialization functions will not
|
||||
detect that the schema has been altered and for instance it will reuse the
|
||||
previously compiled serialization function based on the reference of the schema
|
||||
previously provided.
|
||||
|
||||
If there's a need to change the properties of a schema, always opt to create
|
||||
a totally new object, otherwise the implementation won't benefit from the cache
|
||||
mechanism.
|
||||
|
||||
:Using the following schema as example:
|
||||
```js
|
||||
const schema1 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Not*
|
||||
```js
|
||||
const serialize = reply.compileSerializationSchema(schema1)
|
||||
|
||||
// Later on...
|
||||
schema1.properties.foo.type. = 'integer'
|
||||
const newSerialize = reply.compileSerializationSchema(schema1)
|
||||
|
||||
console.log(newSerialize === serialize) // true
|
||||
```
|
||||
|
||||
*Instead*
|
||||
```js
|
||||
const serialize = reply.compileSerializationSchema(schema1)
|
||||
|
||||
// Later on...
|
||||
const newSchema = Object.assign({}, schema1)
|
||||
newSchema.properties.foo.type = 'integer'
|
||||
|
||||
const newSerialize = reply.compileSerializationSchema(newSchema)
|
||||
|
||||
console.log(newSerialize === serialize) // false
|
||||
```
|
||||
|
||||
### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
|
||||
<a id="serializeinput"></a>
|
||||
|
||||
This function will serialize the input data based on the provided schema
|
||||
or HTTP status code. If both are provided the `httpStatus` will take precedence.
|
||||
|
||||
If there is not a serialization function for a given `schema` a new serialization
|
||||
function will be compiled, forwarding the `httpStatus` and `contentType` if provided.
|
||||
|
||||
```js
|
||||
reply
|
||||
.serializeInput({ foo: 'bar'}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
reply
|
||||
.serializeInput({ foo: 'bar'}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}, 200) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
reply
|
||||
.serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}'
|
||||
|
||||
// or
|
||||
|
||||
reply
|
||||
.serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'
|
||||
```
|
||||
|
||||
See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
|
||||
for more information on how to compile serialization schemas.
|
||||
|
||||
### .serializer(func)
|
||||
<a id="serializer"></a>
|
||||
|
||||
By default, `.send()` will JSON-serialize any value that is not one of `Buffer`,
|
||||
`stream`, `string`, `undefined`, or `Error`. If you need to replace the default
|
||||
serializer with a custom serializer for a particular request, you can do so with
|
||||
the `.serializer()` utility. Be aware that if you are using a custom serializer,
|
||||
you must set a custom `'Content-Type'` header.
|
||||
|
||||
```js
|
||||
reply
|
||||
.header('Content-Type', 'application/x-protobuf')
|
||||
.serializer(protoBuf.serialize)
|
||||
```
|
||||
|
||||
Note that you don't need to use this utility inside a `handler` because Buffers,
|
||||
streams, and strings (unless a serializer is set) are considered to already be
|
||||
serialized.
|
||||
|
||||
```js
|
||||
reply
|
||||
.header('Content-Type', 'application/x-protobuf')
|
||||
.send(protoBuf.serialize(data))
|
||||
```
|
||||
|
||||
See [`.send()`](#send) for more information on sending different types of
|
||||
values.
|
||||
|
||||
### .raw
|
||||
<a id="raw"></a>
|
||||
|
||||
This is the
|
||||
[`http.ServerResponse`](https://nodejs.org/dist/latest-v20.x/docs/api/http.html#http_class_http_serverresponse)
|
||||
from Node core. Whilst you are using the Fastify `Reply` object, the use of
|
||||
`Reply.raw` functions is at your own risk as you are skipping all the Fastify
|
||||
logic of handling the HTTP response. e.g.:
|
||||
|
||||
```js
|
||||
app.get('/cookie-2', (req, reply) => {
|
||||
reply.setCookie('session', 'value', { secure: false }) // this will not be used
|
||||
|
||||
// in this case we are using only the nodejs http server response object
|
||||
reply.raw.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||
reply.raw.write('ok')
|
||||
reply.raw.end()
|
||||
})
|
||||
```
|
||||
Another example of the misuse of `Reply.raw` is explained in
|
||||
[Reply](#getheaders).
|
||||
|
||||
### .sent
|
||||
<a id="sent"></a>
|
||||
|
||||
As the name suggests, `.sent` is a property to indicate if a response has been
|
||||
sent via `reply.send()`. It will also be `true` in case `reply.hijack()` was
|
||||
used.
|
||||
|
||||
In case a route handler is defined as an async function or it returns a promise,
|
||||
it is possible to call `reply.hijack()` to indicate that the automatic
|
||||
invocation of `reply.send()` once the handler promise resolve should be skipped.
|
||||
By calling `reply.hijack()`, an application claims full responsibility for the
|
||||
low-level request and response. Moreover, hooks will not be invoked.
|
||||
|
||||
*Modifying the `.sent` property directly is deprecated. Please use the
|
||||
aforementioned `.hijack()` method to achieve the same effect.*
|
||||
|
||||
### .hijack()
|
||||
<a name="hijack"></a>
|
||||
|
||||
Sometimes you might need to halt the execution of the normal request lifecycle
|
||||
and handle sending the response manually.
|
||||
|
||||
To achieve this, Fastify provides the `reply.hijack()` method that can be called
|
||||
during the request lifecycle (At any point before `reply.send()` is called), and
|
||||
allows you to prevent Fastify from sending the response, and from running the
|
||||
remaining hooks (and user handler if the reply was hijacked before).
|
||||
|
||||
```js
|
||||
app.get('/', (req, reply) => {
|
||||
reply.hijack()
|
||||
reply.raw.end('hello world')
|
||||
|
||||
return Promise.resolve('this will be skipped')
|
||||
})
|
||||
```
|
||||
|
||||
If `reply.raw` is used to send a response back to the user, the `onResponse`
|
||||
hooks will still be executed.
|
||||
|
||||
### .send(data)
|
||||
<a id="send"></a>
|
||||
|
||||
As the name suggests, `.send()` is the function that sends the payload to the
|
||||
end user.
|
||||
|
||||
#### Objects
|
||||
<a id="send-object"></a>
|
||||
|
||||
As noted above, if you are sending JSON objects, `send` will serialize the
|
||||
object with
|
||||
[fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) if you
|
||||
set an output schema, otherwise, `JSON.stringify()` will be used.
|
||||
```js
|
||||
fastify.get('/json', options, function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
#### Strings
|
||||
<a id="send-string"></a>
|
||||
|
||||
If you pass a string to `send` without a `Content-Type`, it will be sent as
|
||||
`text/plain; charset=utf-8`. If you set the `Content-Type` header and pass a
|
||||
string to `send`, it will be serialized with the custom serializer if one is
|
||||
set, otherwise, it will be sent unmodified (unless the `Content-Type` header is
|
||||
set to `application/json; charset=utf-8`, in which case it will be
|
||||
JSON-serialized like an object — see the section above).
|
||||
```js
|
||||
fastify.get('/json', options, function (request, reply) {
|
||||
reply.send('plain string')
|
||||
})
|
||||
```
|
||||
|
||||
#### Streams
|
||||
<a id="send-streams"></a>
|
||||
|
||||
If you are sending a stream and you have not set a `'Content-Type'` header,
|
||||
*send* will set it to `'application/octet-stream'`.
|
||||
|
||||
As noted above, streams are considered to be pre-serialized, so they will be
|
||||
sent unmodified without response validation.
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fastify.get('/streams', function (request, reply) {
|
||||
const stream = fs.createReadStream('some-file', 'utf8')
|
||||
reply.header('Content-Type', 'application/octet-stream')
|
||||
reply.send(stream)
|
||||
})
|
||||
```
|
||||
When using async-await you will need to return or await the reply object:
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fastify.get('/streams', async function (request, reply) {
|
||||
const stream = fs.createReadStream('some-file', 'utf8')
|
||||
reply.header('Content-Type', 'application/octet-stream')
|
||||
return reply.send(stream)
|
||||
})
|
||||
```
|
||||
|
||||
#### Buffers
|
||||
<a id="send-buffers"></a>
|
||||
|
||||
If you are sending a buffer and you have not set a `'Content-Type'` header,
|
||||
*send* will set it to `'application/octet-stream'`.
|
||||
|
||||
As noted above, Buffers are considered to be pre-serialized, so they will be
|
||||
sent unmodified without response validation.
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fastify.get('/streams', function (request, reply) {
|
||||
fs.readFile('some-file', (err, fileBuffer) => {
|
||||
reply.send(err || fileBuffer)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
When using async-await you will need to return or await the reply object:
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fastify.get('/streams', async function (request, reply) {
|
||||
fs.readFile('some-file', (err, fileBuffer) => {
|
||||
reply.send(err || fileBuffer)
|
||||
})
|
||||
return reply
|
||||
})
|
||||
```
|
||||
|
||||
#### TypedArrays
|
||||
<a id="send-typedarrays"></a>
|
||||
|
||||
`send` manages TypedArray like a Buffer, and sets the `'Content-Type'`
|
||||
header to `'application/octet-stream'` if not already set.
|
||||
|
||||
As noted above, TypedArray/Buffers are considered to be pre-serialized, so they
|
||||
will be sent unmodified without response validation.
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fastify.get('/streams', function (request, reply) {
|
||||
const typedArray = new Uint16Array(10)
|
||||
reply.send(typedArray)
|
||||
})
|
||||
```
|
||||
|
||||
#### ReadableStream
|
||||
<a id="send-readablestream"></a>
|
||||
|
||||
`ReadableStream` will be treated as a node stream mentioned above,
|
||||
the content is considered to be pre-serialized, so they will be
|
||||
sent unmodified without response validation.
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
const { ReadableStream } = require('node:stream/web')
|
||||
|
||||
fastify.get('/streams', function (request, reply) {
|
||||
const stream = fs.createReadStream('some-file')
|
||||
reply.header('Content-Type', 'application/octet-stream')
|
||||
reply.send(ReadableStream.from(stream))
|
||||
})
|
||||
```
|
||||
|
||||
#### Response
|
||||
<a id="send-response"></a>
|
||||
|
||||
`Response` allows to manage the reply payload, status code and
|
||||
headers in one place. The payload provided inside `Response` is
|
||||
considered to be pre-serialized, so they will be sent unmodified
|
||||
without response validation.
|
||||
|
||||
Please be aware when using `Response`, the status code and headers
|
||||
will not directly reflect to `reply.statusCode` and `reply.getHeaders()`.
|
||||
Such behavior is based on `Response` only allow `readonly` status
|
||||
code and headers. The data is not allow to be bi-direction editing,
|
||||
and may confuse when checking the `payload` in `onSend` hooks.
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
const { ReadableStream } = require('node:stream/web')
|
||||
|
||||
fastify.get('/streams', function (request, reply) {
|
||||
const stream = fs.createReadStream('some-file')
|
||||
const readableStream = ReadableStream.from(stream)
|
||||
const response = new Response(readableStream, {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/octet-stream' }
|
||||
})
|
||||
reply.send(response)
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
#### Errors
|
||||
<a id="errors"></a>
|
||||
|
||||
If you pass to *send* an object that is an instance of *Error*, Fastify will
|
||||
automatically create an error structured as the following:
|
||||
|
||||
```js
|
||||
{
|
||||
error: String // the HTTP error message
|
||||
code: String // the Fastify error code
|
||||
message: String // the user error message
|
||||
statusCode: Number // the HTTP status code
|
||||
}
|
||||
```
|
||||
|
||||
You can add custom properties to the Error object, such as `headers`, that will
|
||||
be used to enhance the HTTP response.
|
||||
|
||||
> ℹ️ Note: If you are passing an error to `send` and the statusCode is less than
|
||||
> 400, Fastify will automatically set it at 500.
|
||||
|
||||
Tip: you can simplify errors by using the
|
||||
[`http-errors`](https://npm.im/http-errors) module or
|
||||
[`@fastify/sensible`](https://github.com/fastify/fastify-sensible) plugin to
|
||||
generate errors:
|
||||
|
||||
```js
|
||||
fastify.get('/', function (request, reply) {
|
||||
reply.send(httpErrors.Gone())
|
||||
})
|
||||
```
|
||||
|
||||
To customize the JSON error output you can do it by:
|
||||
|
||||
- setting a response JSON schema for the status code you need
|
||||
- add the additional properties to the `Error` instance
|
||||
|
||||
Notice that if the returned status code is not in the response schema list, the
|
||||
default behavior will be applied.
|
||||
|
||||
```js
|
||||
fastify.get('/', {
|
||||
schema: {
|
||||
response: {
|
||||
501: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
statusCode: { type: 'number' },
|
||||
code: { type: 'string' },
|
||||
error: { type: 'string' },
|
||||
message: { type: 'string' },
|
||||
time: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, function (request, reply) {
|
||||
const error = new Error('This endpoint has not been implemented')
|
||||
error.time = 'it will be implemented in two weeks'
|
||||
reply.code(501).send(error)
|
||||
})
|
||||
```
|
||||
|
||||
If you want to customize error handling, check out
|
||||
[`setErrorHandler`](./Server.md#seterrorhandler) API.
|
||||
|
||||
> ℹ️ Note: you are responsible for logging when customizing the error handler.
|
||||
|
||||
API:
|
||||
|
||||
```js
|
||||
fastify.setErrorHandler(function (error, request, reply) {
|
||||
request.log.warn(error)
|
||||
const statusCode = error.statusCode >= 400 ? error.statusCode : 500
|
||||
reply
|
||||
.code(statusCode)
|
||||
.type('text/plain')
|
||||
.send(statusCode >= 500 ? 'Internal server error' : error.message)
|
||||
})
|
||||
```
|
||||
|
||||
Beware that calling `reply.send(error)` in your custom error handler will send
|
||||
the error to the default error handler.
|
||||
Check out the [Reply Lifecycle](./Lifecycle.md#reply-lifecycle)
|
||||
for more information.
|
||||
|
||||
The not found errors generated by the router will use the
|
||||
[`setNotFoundHandler`](./Server.md#setnotfoundhandler)
|
||||
|
||||
API:
|
||||
|
||||
```js
|
||||
fastify.setNotFoundHandler(function (request, reply) {
|
||||
reply
|
||||
.code(404)
|
||||
.type('text/plain')
|
||||
.send('a custom not found')
|
||||
})
|
||||
```
|
||||
|
||||
#### Type of the final payload
|
||||
<a id="payload-type"></a>
|
||||
|
||||
The type of the sent payload (after serialization and going through any
|
||||
[`onSend` hooks](./Hooks.md#onsend)) must be one of the following types,
|
||||
otherwise, an error will be thrown:
|
||||
|
||||
- `string`
|
||||
- `Buffer`
|
||||
- `stream`
|
||||
- `undefined`
|
||||
- `null`
|
||||
|
||||
#### Async-Await and Promises
|
||||
<a id="async-await-promise"></a>
|
||||
|
||||
Fastify natively handles promises and supports async-await.
|
||||
|
||||
*Note that in the following examples we are not using reply.send.*
|
||||
```js
|
||||
const { promisify } = require('node:util')
|
||||
const delay = promisify(setTimeout)
|
||||
|
||||
fastify.get('/promises', options, function (request, reply) {
|
||||
return delay(200).then(() => { return { hello: 'world' }})
|
||||
})
|
||||
|
||||
fastify.get('/async-await', options, async function (request, reply) {
|
||||
await delay(200)
|
||||
return { hello: 'world' }
|
||||
})
|
||||
```
|
||||
|
||||
Rejected promises default to a `500` HTTP status code. Reject the promise, or
|
||||
`throw` in an `async function`, with an object that has `statusCode` (or
|
||||
`status`) and `message` properties to modify the reply.
|
||||
|
||||
```js
|
||||
fastify.get('/teapot', async function (request, reply) {
|
||||
const err = new Error()
|
||||
err.statusCode = 418
|
||||
err.message = 'short and stout'
|
||||
throw err
|
||||
})
|
||||
|
||||
fastify.get('/botnet', async function (request, reply) {
|
||||
throw { statusCode: 418, message: 'short and stout' }
|
||||
// will return to the client the same json
|
||||
})
|
||||
```
|
||||
|
||||
If you want to know more please review
|
||||
[Routes#async-await](./Routes.md#async-await).
|
||||
|
||||
### .then(fulfilled, rejected)
|
||||
<a id="then"></a>
|
||||
|
||||
As the name suggests, a `Reply` object can be awaited upon, i.e. `await reply`
|
||||
will wait until the reply is sent. The `await` syntax calls the `reply.then()`.
|
||||
|
||||
`reply.then(fulfilled, rejected)` accepts two parameters:
|
||||
|
||||
- `fulfilled` will be called when a response has been fully sent,
|
||||
- `rejected` will be called if the underlying stream had an error, e.g. the
|
||||
socket has been destroyed.
|
||||
|
||||
For more details, see:
|
||||
|
||||
- https://github.com/fastify/fastify/issues/1864 for the discussion about this
|
||||
feature
|
||||
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
|
||||
for the signature
|
||||
279
node_modules/fastify/docs/Reference/Request.md
generated
vendored
Normal file
279
node_modules/fastify/docs/Reference/Request.md
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Request
|
||||
The first parameter of the handler function is `Request`.
|
||||
|
||||
Request is a core Fastify object containing the following fields:
|
||||
- `query` - The parsed querystring, its format is specified by
|
||||
[`querystringParser`](./Server.md#querystringparser).
|
||||
- `body` - The request payload, see [Content-Type Parser](./ContentTypeParser.md)
|
||||
for details on what request payloads Fastify natively parses and how to support
|
||||
other content types.
|
||||
- `params` - The params matching the URL.
|
||||
- [`headers`](#headers) - The headers getter and setter.
|
||||
- `raw` - The incoming HTTP request from Node core.
|
||||
- `server` - The Fastify server instance, scoped to the current
|
||||
[encapsulation context](./Encapsulation.md).
|
||||
- `id` - The request ID.
|
||||
- `log` - The logger instance of the incoming request.
|
||||
- `ip` - The IP address of the incoming request.
|
||||
- `ips` - An array of the IP addresses, ordered from closest to furthest, in the
|
||||
`X-Forwarded-For` header of the incoming request (only when the
|
||||
[`trustProxy`](./Server.md#factory-trust-proxy) option is enabled).
|
||||
- `host` - The host of the incoming request (derived from `X-Forwarded-Host`
|
||||
header when the [`trustProxy`](./Server.md#factory-trust-proxy) option is
|
||||
enabled). For HTTP/2 compatibility, it returns `:authority` if no host header
|
||||
exists. The host header may return an empty string if `requireHostHeader` is
|
||||
`false`, not provided with HTTP/1.0, or removed by schema validation.
|
||||
- `hostname` - The hostname derived from the `host` property of the incoming request.
|
||||
- `port` - The port from the `host` property, which may refer to the port the
|
||||
server is listening on.
|
||||
- `protocol` - The protocol of the incoming request (`https` or `http`).
|
||||
- `method` - The method of the incoming request.
|
||||
- `url` - The URL of the incoming request.
|
||||
- `originalUrl` - Similar to `url`, allows access to the original `url` in
|
||||
case of internal re-routing.
|
||||
- `is404` - `true` if request is being handled by 404 handler, `false` otherwise.
|
||||
- `socket` - The underlying connection of the incoming request.
|
||||
- `context` - Deprecated, use `request.routeOptions.config` instead. A Fastify
|
||||
internal object. Do not use or modify it directly. It is useful to access one
|
||||
special key:
|
||||
- `context.config` - The route [`config`](./Routes.md#routes-config) object.
|
||||
- `routeOptions` - The route [`option`](./Routes.md#routes-options) object.
|
||||
- `bodyLimit` - Either server limit or route limit.
|
||||
- `config` - The [`config`](./Routes.md#routes-config) object for this route.
|
||||
- `method` - The HTTP method for the route.
|
||||
- `url` - The path of the URL to match this route.
|
||||
- `handler` - The handler for this route.
|
||||
- `attachValidation` - Attach `validationError` to request (if there is
|
||||
a schema defined).
|
||||
- `logLevel` - Log level defined for this route.
|
||||
- `schema` - The JSON schemas definition for this route.
|
||||
- `version` - A semver compatible string that defines the version of the endpoint.
|
||||
- `exposeHeadRoute` - Creates a sibling HEAD route for any GET routes.
|
||||
- `prefixTrailingSlash` - String used to determine how to handle passing `/`
|
||||
as a route with a prefix.
|
||||
- [.getValidationFunction(schema | httpPart)](#getvalidationfunction) -
|
||||
Returns a validation function for the specified schema or HTTP part, if
|
||||
set or cached.
|
||||
- [.compileValidationSchema(schema, [httpPart])](#compilevalidationschema) -
|
||||
Compiles the specified schema and returns a validation function using the
|
||||
default (or customized) `ValidationCompiler`. The optional `httpPart` is
|
||||
forwarded to the `ValidationCompiler` if provided, defaults to `null`.
|
||||
- [.validateInput(data, schema | httpPart, [httpPart])](#validate) -
|
||||
Validates the input using the specified schema and returns the serialized
|
||||
payload. If `httpPart` is provided, the function uses the serializer for
|
||||
that HTTP Status Code. Defaults to `null`.
|
||||
|
||||
### Headers
|
||||
|
||||
The `request.headers` is a getter that returns an object with the headers of the
|
||||
incoming request. Set custom headers as follows:
|
||||
|
||||
```js
|
||||
request.headers = {
|
||||
'foo': 'bar',
|
||||
'baz': 'qux'
|
||||
}
|
||||
```
|
||||
|
||||
This operation adds new values to the request headers, accessible via
|
||||
`request.headers.bar`. Standard request headers remain accessible via
|
||||
`request.raw.headers`.
|
||||
|
||||
For performance reasons, `Symbol('fastify.RequestAcceptVersion')` may be added
|
||||
to headers on `not found` routes.
|
||||
|
||||
> ℹ️ Note: Schema validation may mutate the `request.headers` and
|
||||
> `request.raw.headers` objects, causing the headers to become empty.
|
||||
|
||||
```js
|
||||
fastify.post('/:params', options, function (request, reply) {
|
||||
console.log(request.body)
|
||||
console.log(request.query)
|
||||
console.log(request.params)
|
||||
console.log(request.headers)
|
||||
console.log(request.raw)
|
||||
console.log(request.server)
|
||||
console.log(request.id)
|
||||
console.log(request.ip)
|
||||
console.log(request.ips)
|
||||
console.log(request.host)
|
||||
console.log(request.hostname)
|
||||
console.log(request.port)
|
||||
console.log(request.protocol)
|
||||
console.log(request.url)
|
||||
console.log(request.routeOptions.method)
|
||||
console.log(request.routeOptions.bodyLimit)
|
||||
console.log(request.routeOptions.method)
|
||||
console.log(request.routeOptions.url)
|
||||
console.log(request.routeOptions.attachValidation)
|
||||
console.log(request.routeOptions.logLevel)
|
||||
console.log(request.routeOptions.version)
|
||||
console.log(request.routeOptions.exposeHeadRoute)
|
||||
console.log(request.routeOptions.prefixTrailingSlash)
|
||||
console.log(request.routeOptions.logLevel)
|
||||
request.log.info('some info')
|
||||
})
|
||||
```
|
||||
### .getValidationFunction(schema | httpPart)
|
||||
<a id="getvalidationfunction"></a>
|
||||
|
||||
By calling this function with a provided `schema` or `httpPart`, it returns a
|
||||
`validation` function to validate diverse inputs. It returns `undefined` if no
|
||||
serialization function is found using the provided inputs.
|
||||
|
||||
This function has an `errors` property. Errors encountered during the last
|
||||
validation are assigned to `errors`.
|
||||
|
||||
```js
|
||||
const validate = request
|
||||
.getValidationFunction({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(validate({ foo: 'bar' })) // true
|
||||
console.log(validate.errors) // null
|
||||
|
||||
// or
|
||||
|
||||
const validate = request
|
||||
.getValidationFunction('body')
|
||||
console.log(validate({ foo: 0.5 })) // false
|
||||
console.log(validate.errors) // validation errors
|
||||
```
|
||||
|
||||
See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
|
||||
for more information on compiling validation schemas.
|
||||
|
||||
### .compileValidationSchema(schema, [httpPart])
|
||||
<a id="compilevalidationschema"></a>
|
||||
|
||||
This function compiles a validation schema and returns a function to validate data.
|
||||
The returned function (a.k.a. _validation function_) is compiled using the provided
|
||||
[`SchemaController#ValidationCompiler`](./Server.md#schema-controller). A `WeakMap`
|
||||
is used to cache this, reducing compilation calls.
|
||||
|
||||
The optional parameter `httpPart`, if provided, is forwarded to the
|
||||
`ValidationCompiler`, allowing it to compile the validation function if a custom
|
||||
`ValidationCompiler` is provided for the route.
|
||||
|
||||
This function has an `errors` property. Errors encountered during the last
|
||||
validation are assigned to `errors`.
|
||||
|
||||
```js
|
||||
const validate = request
|
||||
.compileValidationSchema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(validate({ foo: 'bar' })) // true
|
||||
console.log(validate.errors) // null
|
||||
|
||||
// or
|
||||
|
||||
const validate = request
|
||||
.compileValidationSchema({
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}, 200)
|
||||
console.log(validate({ hello: 'world' })) // false
|
||||
console.log(validate.errors) // validation errors
|
||||
```
|
||||
|
||||
Be careful when using this function, as it caches compiled validation functions
|
||||
based on the provided schema. If schemas are mutated or changed, the validation
|
||||
functions will not detect the alterations and will reuse the previously compiled
|
||||
validation function, as the cache is based on the schema's reference.
|
||||
|
||||
If schema properties need to be changed, create a new schema object to benefit
|
||||
from the cache mechanism.
|
||||
|
||||
Using the following schema as an example:
|
||||
```js
|
||||
const schema1 = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*Not*
|
||||
```js
|
||||
const validate = request.compileValidationSchema(schema1)
|
||||
|
||||
// Later on...
|
||||
schema1.properties.foo.type. = 'integer'
|
||||
const newValidate = request.compileValidationSchema(schema1)
|
||||
|
||||
console.log(newValidate === validate) // true
|
||||
```
|
||||
|
||||
*Instead*
|
||||
```js
|
||||
const validate = request.compileValidationSchema(schema1)
|
||||
|
||||
// Later on...
|
||||
const newSchema = Object.assign({}, schema1)
|
||||
newSchema.properties.foo.type = 'integer'
|
||||
|
||||
const newValidate = request.compileValidationSchema(newSchema)
|
||||
|
||||
console.log(newValidate === validate) // false
|
||||
```
|
||||
|
||||
### .validateInput(data, [schema | httpPart], [httpPart])
|
||||
<a id="validate"></a>
|
||||
|
||||
This function validates the input based on the provided schema or HTTP part. If
|
||||
both are provided, the `httpPart` parameter takes precedence.
|
||||
|
||||
If no validation function exists for a given `schema`, a new validation function
|
||||
will be compiled, forwarding the `httpPart` if provided.
|
||||
|
||||
```js
|
||||
request
|
||||
.validateInput({ foo: 'bar'}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}) // true
|
||||
|
||||
// or
|
||||
|
||||
request
|
||||
.validateInput({ foo: 'bar'}, {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}, 'body') // true
|
||||
|
||||
// or
|
||||
|
||||
request
|
||||
.validateInput({ hello: 'world'}, 'query') // false
|
||||
```
|
||||
|
||||
See [.compileValidationSchema(schema, [httpStatus])](#compileValidationSchema)
|
||||
for more information on compiling validation schemas.
|
||||
793
node_modules/fastify/docs/Reference/Routes.md
generated
vendored
Normal file
793
node_modules/fastify/docs/Reference/Routes.md
generated
vendored
Normal file
@@ -0,0 +1,793 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Routes
|
||||
|
||||
The route methods configure the endpoints of the application. Routes can be
|
||||
declared using the shorthand method or the full declaration.
|
||||
|
||||
- [Full declaration](#full-declaration)
|
||||
- [Routes options](#routes-options)
|
||||
- [Shorthand declaration](#shorthand-declaration)
|
||||
- [Url building](#url-building)
|
||||
- [Async Await](#async-await)
|
||||
- [Promise resolution](#promise-resolution)
|
||||
- [Route Prefixing](#route-prefixing)
|
||||
- [Handling of / route inside prefixed
|
||||
plugins](#handling-of--route-inside-prefixed-plugins)
|
||||
- [Custom Log Level](#custom-log-level)
|
||||
- [Custom Log Serializer](#custom-log-serializer)
|
||||
- [Config](#config)
|
||||
- [Constraints](#constraints)
|
||||
- [Version Constraints](#version-constraints)
|
||||
- [Host Constraints](#host-constraints)
|
||||
|
||||
### Full declaration
|
||||
<a id="full-declaration"></a>
|
||||
|
||||
```js
|
||||
fastify.route(options)
|
||||
```
|
||||
|
||||
### Routes options
|
||||
<a id="options"></a>
|
||||
|
||||
* `method`: currently it supports `GET`, `HEAD`, `TRACE`, `DELETE`,
|
||||
`OPTIONS`, `PATCH`, `PUT` and `POST`. To accept more methods,
|
||||
the [`addHttpMethod`](./Server.md#addHttpMethod) must be used.
|
||||
It could also be an array of methods.
|
||||
* `url`: the path of the URL to match this route (alias: `path`).
|
||||
* `schema`: an object containing the schemas for the request and response. They
|
||||
need to be in [JSON Schema](https://json-schema.org/) format, check
|
||||
[here](./Validation-and-Serialization.md) for more info.
|
||||
|
||||
* `body`: validates the body of the request if it is a POST, PUT, PATCH,
|
||||
TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method.
|
||||
* `querystring` or `query`: validates the querystring. This can be a complete
|
||||
JSON Schema object, with the property `type` of `object` and `properties`
|
||||
object of parameters, or simply the values of what would be contained in the
|
||||
`properties` object as shown below.
|
||||
* `params`: validates the params.
|
||||
* `response`: filter and generate a schema for the response, setting a schema
|
||||
allows us to have 10-20% more throughput.
|
||||
* `exposeHeadRoute`: creates a sibling `HEAD` route for any `GET` routes.
|
||||
Defaults to the value of [`exposeHeadRoutes`](./Server.md#exposeHeadRoutes)
|
||||
instance option. If you want a custom `HEAD` handler without disabling this
|
||||
option, make sure to define it before the `GET` route.
|
||||
* `attachValidation`: attach `validationError` to request, if there is a schema
|
||||
validation error, instead of sending the error to the error handler. The
|
||||
default [error format](https://ajv.js.org/api.html#error-objects) is the Ajv
|
||||
one.
|
||||
* `onRequest(request, reply, done)`: a [function](./Hooks.md#onrequest) called
|
||||
as soon as a request is received, it could also be an array of functions.
|
||||
* `preParsing(request, reply, payload, done)`: a
|
||||
[function](./Hooks.md#preparsing) called before parsing the request, it could
|
||||
also be an array of functions.
|
||||
* `preValidation(request, reply, done)`: a [function](./Hooks.md#prevalidation)
|
||||
called after the shared `preValidation` hooks, useful if you need to perform
|
||||
authentication at route level for example, it could also be an array of
|
||||
functions.
|
||||
* `preHandler(request, reply, done)`: a [function](./Hooks.md#prehandler) called
|
||||
just before the request handler, it could also be an array of functions.
|
||||
* `preSerialization(request, reply, payload, done)`: a
|
||||
[function](./Hooks.md#preserialization) called just before the serialization,
|
||||
it could also be an array of functions.
|
||||
* `onSend(request, reply, payload, done)`: a [function](./Hooks.md#route-hooks)
|
||||
called right before a response is sent, it could also be an array of
|
||||
functions.
|
||||
* `onResponse(request, reply, done)`: a [function](./Hooks.md#onresponse) called
|
||||
when a response has been sent, so you will not be able to send more data to
|
||||
the client. It could also be an array of functions.
|
||||
* `onTimeout(request, reply, done)`: a [function](./Hooks.md#ontimeout) called
|
||||
when a request is timed out and the HTTP socket has been hung up.
|
||||
* `onError(request, reply, error, done)`: a [function](./Hooks.md#onerror)
|
||||
called when an Error is thrown or sent to the client by the route handler.
|
||||
* `handler(request, reply)`: the function that will handle this request. The
|
||||
[Fastify server](./Server.md) will be bound to `this` when the handler is
|
||||
called. Note: using an arrow function will break the binding of `this`.
|
||||
* `errorHandler(error, request, reply)`: a custom error handler for the scope of
|
||||
the request. Overrides the default error global handler, and anything set by
|
||||
[`setErrorHandler`](./Server.md#seterrorhandler), for requests to the route.
|
||||
To access the default handler, you can access `instance.errorHandler`. Note
|
||||
that this will point to fastify's default `errorHandler` only if a plugin
|
||||
hasn't overridden it already.
|
||||
* `childLoggerFactory(logger, binding, opts, rawReq)`: a custom factory function
|
||||
that will be called to produce a child logger instance for every request.
|
||||
See [`childLoggerFactory`](./Server.md#childloggerfactory) for more info.
|
||||
Overrides the default logger factory, and anything set by
|
||||
[`setChildLoggerFactory`](./Server.md#setchildloggerfactory), for requests to
|
||||
the route. To access the default factory, you can access
|
||||
`instance.childLoggerFactory`. Note that this will point to Fastify's default
|
||||
`childLoggerFactory` only if a plugin hasn't overridden it already.
|
||||
* `validatorCompiler({ schema, method, url, httpPart })`: function that builds
|
||||
schemas for request validations. See the [Validation and
|
||||
Serialization](./Validation-and-Serialization.md#schema-validator)
|
||||
documentation.
|
||||
* `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`:
|
||||
function that builds schemas for response serialization. See the [Validation and
|
||||
Serialization](./Validation-and-Serialization.md#schema-serializer)
|
||||
documentation.
|
||||
* `schemaErrorFormatter(errors, dataVar)`: function that formats the errors from
|
||||
the validation compiler. See the [Validation and
|
||||
Serialization](./Validation-and-Serialization.md#error-handling)
|
||||
documentation. Overrides the global schema error formatter handler, and
|
||||
anything set by `setSchemaErrorFormatter`, for requests to the route.
|
||||
* `bodyLimit`: prevents the default JSON body parser from parsing request bodies
|
||||
larger than this number of bytes. Must be an integer. You may also set this
|
||||
option globally when first creating the Fastify instance with
|
||||
`fastify(options)`. Defaults to `1048576` (1 MiB).
|
||||
* `logLevel`: set log level for this route. See below.
|
||||
* `logSerializers`: set serializers to log for this route.
|
||||
* `config`: object used to store custom configuration.
|
||||
* `version`: a [semver](https://semver.org/) compatible string that defined the
|
||||
version of the endpoint. [Example](#version-constraints).
|
||||
* `constraints`: defines route restrictions based on request properties or
|
||||
values, enabling customized matching using
|
||||
[find-my-way](https://github.com/delvedor/find-my-way) constraints. Includes
|
||||
built-in `version` and `host` constraints, with support for custom constraint
|
||||
strategies.
|
||||
* `prefixTrailingSlash`: string used to determine how to handle passing `/` as a
|
||||
route with a prefix.
|
||||
* `both` (default): Will register both `/prefix` and `/prefix/`.
|
||||
* `slash`: Will register only `/prefix/`.
|
||||
* `no-slash`: Will register only `/prefix`.
|
||||
|
||||
Note: this option does not override `ignoreTrailingSlash` in
|
||||
[Server](./Server.md) configuration.
|
||||
|
||||
* `request` is defined in [Request](./Request.md).
|
||||
|
||||
* `reply` is defined in [Reply](./Reply.md).
|
||||
|
||||
> ℹ️ Note: The documentation for `onRequest`, `preParsing`, `preValidation`,
|
||||
> `preHandler`, `preSerialization`, `onSend`, and `onResponse` is detailed in
|
||||
> [Hooks](./Hooks.md). To send a response before the request is handled by the
|
||||
> `handler`, see [Respond to a request from
|
||||
> a hook](./Hooks.md#respond-to-a-request-from-a-hook).
|
||||
|
||||
Example:
|
||||
```js
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
schema: {
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
excitement: { type: 'integer' }
|
||||
}
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handler: function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Shorthand declaration
|
||||
<a id="shorthand-declaration"></a>
|
||||
|
||||
The above route declaration is more *Hapi*-like, but if you prefer an
|
||||
*Express/Restify* approach, we support it as well:
|
||||
|
||||
`fastify.get(path, [options], handler)`
|
||||
|
||||
`fastify.head(path, [options], handler)`
|
||||
|
||||
`fastify.post(path, [options], handler)`
|
||||
|
||||
`fastify.put(path, [options], handler)`
|
||||
|
||||
`fastify.delete(path, [options], handler)`
|
||||
|
||||
`fastify.options(path, [options], handler)`
|
||||
|
||||
`fastify.patch(path, [options], handler)`
|
||||
|
||||
Example:
|
||||
```js
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fastify.get('/', opts, (request, reply) => {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
|
||||
`fastify.all(path, [options], handler)` will add the same handler to all the
|
||||
supported methods.
|
||||
|
||||
The handler may also be supplied via the `options` object:
|
||||
```js
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handler: function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
}
|
||||
}
|
||||
fastify.get('/', opts)
|
||||
```
|
||||
|
||||
> ℹ️ Note: Specifying the handler in both `options` and as the third parameter to
|
||||
> the shortcut method throws a duplicate `handler` error.
|
||||
|
||||
### Url building
|
||||
<a id="url-building"></a>
|
||||
|
||||
Fastify supports both static and dynamic URLs.
|
||||
|
||||
To register a **parametric** path, use a *colon* before the parameter name. For
|
||||
**wildcard**, use a *star*. Static routes are always checked before parametric
|
||||
and wildcard routes.
|
||||
|
||||
```js
|
||||
// parametric
|
||||
fastify.get('/example/:userId', function (request, reply) {
|
||||
// curl ${app-url}/example/12345
|
||||
// userId === '12345'
|
||||
const { userId } = request.params;
|
||||
// your code here
|
||||
})
|
||||
fastify.get('/example/:userId/:secretToken', function (request, reply) {
|
||||
// curl ${app-url}/example/12345/abc.zHi
|
||||
// userId === '12345'
|
||||
// secretToken === 'abc.zHi'
|
||||
const { userId, secretToken } = request.params;
|
||||
// your code here
|
||||
})
|
||||
|
||||
// wildcard
|
||||
fastify.get('/example/*', function (request, reply) {})
|
||||
```
|
||||
|
||||
Regular expression routes are supported, but slashes must be escaped.
|
||||
Take note that RegExp is also very expensive in terms of performance!
|
||||
```js
|
||||
// parametric with regexp
|
||||
fastify.get('/example/:file(^\\d+).png', function (request, reply) {
|
||||
// curl ${app-url}/example/12345.png
|
||||
// file === '12345'
|
||||
const { file } = request.params;
|
||||
// your code here
|
||||
})
|
||||
```
|
||||
|
||||
It is possible to define more than one parameter within the same couple of slash
|
||||
("/"). Such as:
|
||||
```js
|
||||
fastify.get('/example/near/:lat-:lng/radius/:r', function (request, reply) {
|
||||
// curl ${app-url}/example/near/15°N-30°E/radius/20
|
||||
// lat === "15°N"
|
||||
// lng === "30°E"
|
||||
// r ==="20"
|
||||
const { lat, lng, r } = request.params;
|
||||
// your code here
|
||||
})
|
||||
```
|
||||
*Remember in this case to use the dash ("-") as parameters separator.*
|
||||
|
||||
Finally, it is possible to have multiple parameters with RegExp:
|
||||
```js
|
||||
fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, reply) {
|
||||
// curl ${app-url}/example/at/08h24m
|
||||
// hour === "08"
|
||||
// minute === "24"
|
||||
const { hour, minute } = request.params;
|
||||
// your code here
|
||||
})
|
||||
```
|
||||
In this case as parameter separator it is possible to use whatever character is
|
||||
not matched by the regular expression.
|
||||
|
||||
The last parameter can be made optional by adding a question mark ("?") to the
|
||||
end of the parameter name.
|
||||
```js
|
||||
fastify.get('/example/posts/:id?', function (request, reply) {
|
||||
const { id } = request.params;
|
||||
// your code here
|
||||
})
|
||||
```
|
||||
In this case, `/example/posts` and `/example/posts/1` are both valid. The
|
||||
optional param will be `undefined` if not specified.
|
||||
|
||||
Having a route with multiple parameters may negatively affect performance.
|
||||
Prefer a single parameter approach, especially on routes that are on the hot
|
||||
path of your application. For more details, see
|
||||
[find-my-way](https://github.com/delvedor/find-my-way).
|
||||
|
||||
To include a colon in a path without declaring a parameter, use a double colon.
|
||||
For example:
|
||||
```js
|
||||
fastify.post('/name::verb') // will be interpreted as /name:verb
|
||||
```
|
||||
|
||||
### Async Await
|
||||
<a id="async-await"></a>
|
||||
|
||||
Are you an `async/await` user? We have you covered!
|
||||
```js
|
||||
fastify.get('/', options, async function (request, reply) {
|
||||
const data = await getData()
|
||||
const processed = await processData(data)
|
||||
return processed
|
||||
})
|
||||
```
|
||||
|
||||
As shown, `reply.send` is not called to send data back to the user. Simply
|
||||
return the body and you are done!
|
||||
|
||||
If needed, you can also send data back with `reply.send`. In this case, do not
|
||||
forget to `return reply` or `await reply` in your `async` handler to avoid race
|
||||
conditions.
|
||||
|
||||
```js
|
||||
fastify.get('/', options, async function (request, reply) {
|
||||
const data = await getData()
|
||||
const processed = await processData(data)
|
||||
return reply.send(processed)
|
||||
})
|
||||
```
|
||||
|
||||
If the route is wrapping a callback-based API that will call `reply.send()`
|
||||
outside of the promise chain, it is possible to `await reply`:
|
||||
|
||||
```js
|
||||
fastify.get('/', options, async function (request, reply) {
|
||||
setImmediate(() => {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
await reply
|
||||
})
|
||||
```
|
||||
|
||||
Returning reply also works:
|
||||
|
||||
```js
|
||||
fastify.get('/', options, async function (request, reply) {
|
||||
setImmediate(() => {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
return reply
|
||||
})
|
||||
```
|
||||
|
||||
> ⚠ Warning:
|
||||
> * When using both `return value` and `reply.send(value)`, the first one takes
|
||||
> precedence, the second is discarded, and a *warn* log is emitted.
|
||||
> * Calling `reply.send()` outside of the promise is possible but requires special
|
||||
> attention. See [promise-resolution](#promise-resolution).
|
||||
> * `undefined` cannot be returned. See [promise-resolution](#promise-resolution).
|
||||
|
||||
### Promise resolution
|
||||
<a id="promise-resolution"></a>
|
||||
|
||||
If the handler is an `async` function or returns a promise, be aware of the
|
||||
special behavior to support callback and promise control-flow. When the
|
||||
handler's promise resolves, the reply is automatically sent with its value
|
||||
unless you explicitly await or return `reply` in the handler.
|
||||
|
||||
1. If using `async/await` or promises but responding with `reply.send`:
|
||||
- **Do** `return reply` / `await reply`.
|
||||
- **Do not** forget to call `reply.send`.
|
||||
2. If using `async/await` or promises:
|
||||
- **Do not** use `reply.send`.
|
||||
- **Do** return the value to send.
|
||||
|
||||
This approach supports both `callback-style` and `async-await` with minimal
|
||||
trade-off. However, it is recommended to use only one style for consistent
|
||||
error handling within your application.
|
||||
|
||||
> ℹ️ Note: Every async function returns a promise by itself.
|
||||
|
||||
### Route Prefixing
|
||||
<a id="route-prefixing"></a>
|
||||
|
||||
Sometimes maintaining multiple versions of the same API is necessary. A common
|
||||
approach is to prefix routes with the API version number, e.g., `/v1/user`.
|
||||
Fastify offers a fast and smart way to create different versions of the same API
|
||||
without changing all the route names by hand, called *route prefixing*. Here is
|
||||
how it works:
|
||||
|
||||
```js
|
||||
// server.js
|
||||
const fastify = require('fastify')()
|
||||
|
||||
fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
|
||||
fastify.register(require('./routes/v2/users'), { prefix: '/v2' })
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
```js
|
||||
// routes/v1/users.js
|
||||
module.exports = function (fastify, opts, done) {
|
||||
fastify.get('/user', handler_v1)
|
||||
done()
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// routes/v2/users.js
|
||||
module.exports = function (fastify, opts, done) {
|
||||
fastify.get('/user', handler_v2)
|
||||
done()
|
||||
}
|
||||
```
|
||||
Fastify will not complain about using the same name for two different routes
|
||||
because it handles the prefix automatically at compilation time. This ensures
|
||||
performance is not affected.
|
||||
|
||||
Now clients will have access to the following routes:
|
||||
- `/v1/user`
|
||||
- `/v2/user`
|
||||
|
||||
This can be done multiple times and works for nested `register`. Route
|
||||
parameters are also supported.
|
||||
|
||||
To use a prefix for all routes, place them inside a plugin:
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')()
|
||||
|
||||
const route = {
|
||||
method: 'POST',
|
||||
url: '/login',
|
||||
handler: () => {},
|
||||
schema: {},
|
||||
}
|
||||
|
||||
fastify.register(function (app, _, done) {
|
||||
app.get('/users', () => {})
|
||||
app.route(route)
|
||||
|
||||
done()
|
||||
}, { prefix: '/v1' }) // global route prefix
|
||||
|
||||
await fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
### Route Prefixing and fastify-plugin
|
||||
<a id="fastify-plugin"></a>
|
||||
|
||||
If using [`fastify-plugin`](https://github.com/fastify/fastify-plugin) to wrap
|
||||
routes, this option will not work. To make it work, wrap a plugin in a plugin:
|
||||
```js
|
||||
const fp = require('fastify-plugin')
|
||||
const routes = require('./lib/routes')
|
||||
|
||||
module.exports = fp(async function (app, opts) {
|
||||
app.register(routes, {
|
||||
prefix: '/v1',
|
||||
})
|
||||
}, {
|
||||
name: 'my-routes'
|
||||
})
|
||||
```
|
||||
|
||||
#### Handling of / route inside prefixed plugins
|
||||
|
||||
The `/` route behaves differently based on whether the prefix ends with `/`.
|
||||
For example, with a prefix `/something/`, adding a `/` route matches only
|
||||
`/something/`. With a prefix `/something`, adding a `/` route matches both
|
||||
`/something` and `/something/`.
|
||||
|
||||
See the `prefixTrailingSlash` route option above to change this behavior.
|
||||
|
||||
### Custom Log Level
|
||||
<a id="custom-log-level"></a>
|
||||
|
||||
Different log levels can be set for routes in Fastify by passing the `logLevel`
|
||||
option to the plugin or route with the desired
|
||||
[value](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string).
|
||||
|
||||
Be aware that setting `logLevel` at the plugin level also affects
|
||||
[`setNotFoundHandler`](./Server.md#setnotfoundhandler) and
|
||||
[`setErrorHandler`](./Server.md#seterrorhandler).
|
||||
|
||||
```js
|
||||
// server.js
|
||||
const fastify = require('fastify')({ logger: true })
|
||||
|
||||
fastify.register(require('./routes/user'), { logLevel: 'warn' })
|
||||
fastify.register(require('./routes/events'), { logLevel: 'debug' })
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
Or pass it directly to a route:
|
||||
```js
|
||||
fastify.get('/', { logLevel: 'warn' }, (request, reply) => {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
```
|
||||
*Remember that the custom log level applies only to routes, not to the global
|
||||
Fastify Logger, accessible with `fastify.log`.*
|
||||
|
||||
### Custom Log Serializer
|
||||
<a id="custom-log-serializer"></a>
|
||||
|
||||
In some contexts, logging a large object may waste resources. Define custom
|
||||
[`serializers`](https://github.com/pinojs/pino/blob/master/docs/api.md#serializers-object)
|
||||
and attach them in the appropriate context.
|
||||
|
||||
```js
|
||||
const fastify = require('fastify')({ logger: true })
|
||||
|
||||
fastify.register(require('./routes/user'), {
|
||||
logSerializers: {
|
||||
user: (value) => `My serializer one - ${value.name}`
|
||||
}
|
||||
})
|
||||
fastify.register(require('./routes/events'), {
|
||||
logSerializers: {
|
||||
user: (value) => `My serializer two - ${value.name} ${value.surname}`
|
||||
}
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
Serializers can be inherited by context:
|
||||
|
||||
```js
|
||||
const fastify = Fastify({
|
||||
logger: {
|
||||
level: 'info',
|
||||
serializers: {
|
||||
user (req) {
|
||||
return {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
headers: req.headers,
|
||||
host: req.host,
|
||||
remoteAddress: req.ip,
|
||||
remotePort: req.socket.remotePort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fastify.register(context1, {
|
||||
logSerializers: {
|
||||
user: value => `My serializer father - ${value}`
|
||||
}
|
||||
})
|
||||
|
||||
async function context1 (fastify, opts) {
|
||||
fastify.get('/', (req, reply) => {
|
||||
req.log.info({ user: 'call father serializer', key: 'another key' })
|
||||
// shows: { user: 'My serializer father - call father serializer', key: 'another key' }
|
||||
reply.send({})
|
||||
})
|
||||
}
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
### Config
|
||||
<a id="routes-config"></a>
|
||||
|
||||
Registering a new handler, you can pass a configuration object to it and
|
||||
retrieve it in the handler.
|
||||
|
||||
```js
|
||||
// server.js
|
||||
const fastify = require('fastify')()
|
||||
|
||||
function handler (req, reply) {
|
||||
reply.send(reply.routeOptions.config.output)
|
||||
}
|
||||
|
||||
fastify.get('/en', { config: { output: 'hello world!' } }, handler)
|
||||
fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)
|
||||
|
||||
fastify.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
### Constraints
|
||||
<a id="constraints"></a>
|
||||
|
||||
Fastify supports constraining routes to match certain requests based on
|
||||
properties like the `Host` header or any other value via
|
||||
[`find-my-way`](https://github.com/delvedor/find-my-way) constraints.
|
||||
Constraints are specified in the `constraints` property of the route options.
|
||||
Fastify has two built-in constraints: `version` and `host`. Custom constraint
|
||||
strategies can be added to inspect other parts of a request to decide if a route
|
||||
should be executed.
|
||||
|
||||
#### Version Constraints
|
||||
|
||||
You can provide a `version` key in the `constraints` option to a route.
|
||||
Versioned routes allows multiple handlers to be declared for the same HTTP
|
||||
route path, matched according to the request's `Accept-Version` header.
|
||||
The `Accept-Version` header value should follow the
|
||||
[semver](https://semver.org/) specification, and routes should be declared
|
||||
with exact semver versions for matching.
|
||||
|
||||
Fastify will require a request `Accept-Version` header to be set if the route
|
||||
has a version set, and will prefer a versioned route to a non-versioned route
|
||||
for the same path. Advanced version ranges and pre-releases currently are not
|
||||
supported.
|
||||
|
||||
> **Note:** using this feature can degrade the router’s performance.
|
||||
|
||||
```js
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
constraints: { version: '1.2.0' },
|
||||
handler: function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
}
|
||||
})
|
||||
|
||||
fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
'Accept-Version': '1.x' // it could also be '1.2.0' or '1.2.x'
|
||||
}
|
||||
}, (err, res) => {
|
||||
// { hello: 'world' }
|
||||
})
|
||||
```
|
||||
|
||||
> ⚠ Warning:
|
||||
> Set a
|
||||
> [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary)
|
||||
> header in responses with the value used for versioning
|
||||
> (e.g., `'Accept-Version'`) to prevent cache poisoning attacks.
|
||||
> This can also be configured in a Proxy/CDN.
|
||||
>
|
||||
> ```js
|
||||
> const append = require('vary').append
|
||||
> fastify.addHook('onSend', (req, reply, payload, done) => {
|
||||
> if (req.headers['accept-version']) { // or the custom header being used
|
||||
> let value = reply.getHeader('Vary') || ''
|
||||
> const header = Array.isArray(value) ? value.join(', ') : String(value)
|
||||
> if ((value = append(header, 'Accept-Version'))) { // or the custom header being used
|
||||
> reply.header('Vary', value)
|
||||
> }
|
||||
> }
|
||||
> done()
|
||||
> })
|
||||
> ```
|
||||
|
||||
If multiple versions with the same major or minor are declared, Fastify will
|
||||
always choose the highest compatible with the `Accept-Version` header value.
|
||||
|
||||
If the request lacks an `Accept-Version` header, a 404 error will be returned.
|
||||
|
||||
Custom version matching logic can be defined through the
|
||||
[`constraints`](./Server.md#constraints) configuration when creating a Fastify
|
||||
server instance.
|
||||
|
||||
#### Host Constraints
|
||||
|
||||
Provide a `host` key in the `constraints` route option to limit the route to
|
||||
certain values of the request `Host` header. `host` constraint values can be
|
||||
specified as strings for exact matches or RegExps for arbitrary host matching.
|
||||
|
||||
```js
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
constraints: { host: 'auth.fastify.dev' },
|
||||
handler: function (request, reply) {
|
||||
reply.send('hello world from auth.fastify.dev')
|
||||
}
|
||||
})
|
||||
|
||||
fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
'Host': 'example.com'
|
||||
}
|
||||
}, (err, res) => {
|
||||
// 404 because the host doesn't match the constraint
|
||||
})
|
||||
|
||||
fastify.inject({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
headers: {
|
||||
'Host': 'auth.fastify.dev'
|
||||
}
|
||||
}, (err, res) => {
|
||||
// => 'hello world from auth.fastify.dev'
|
||||
})
|
||||
```
|
||||
|
||||
RegExp `host` constraints can also be specified allowing constraining to hosts
|
||||
matching wildcard subdomains (or any other pattern):
|
||||
|
||||
```js
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev
|
||||
handler: function (request, reply) {
|
||||
reply.send('hello world from ' + request.headers.host)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Asynchronous Custom Constraints
|
||||
|
||||
Custom constraints can be provided, and the `constraint` criteria can be
|
||||
fetched from another source such as a database. Use asynchronous custom
|
||||
constraints as a last resort, as they impact router performance.
|
||||
|
||||
```js
|
||||
function databaseOperation(field, done) {
|
||||
done(null, field)
|
||||
}
|
||||
|
||||
const secret = {
|
||||
// strategy name for referencing in the route handler `constraints` options
|
||||
name: 'secret',
|
||||
// storage factory for storing routes in the find-my-way route tree
|
||||
storage: function () {
|
||||
let handlers = {}
|
||||
return {
|
||||
get: (type) => { return handlers[type] || null },
|
||||
set: (type, store) => { handlers[type] = store }
|
||||
}
|
||||
},
|
||||
// function to get the value of the constraint from each incoming request
|
||||
deriveConstraint: (req, ctx, done) => {
|
||||
databaseOperation(req.headers['secret'], done)
|
||||
},
|
||||
// optional flag marking if handlers without constraints can match requests that have a value for this constraint
|
||||
mustMatchWhenDerived: true
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠ Warning:
|
||||
> When using asynchronous constraints, avoid returning errors inside the
|
||||
> callback. If errors are unavoidable, provide a custom `frameworkErrors`
|
||||
> handler to manage them. Otherwise, route selection may break or expose
|
||||
> sensitive information.
|
||||
>
|
||||
> ```js
|
||||
> const Fastify = require('fastify')
|
||||
>
|
||||
> const fastify = Fastify({
|
||||
> frameworkErrors: function (err, req, res) {
|
||||
> if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {
|
||||
> res.code(400)
|
||||
> return res.send("Invalid header provided")
|
||||
> } else {
|
||||
> res.send(err)
|
||||
> }
|
||||
> }
|
||||
> })
|
||||
> ```
|
||||
2192
node_modules/fastify/docs/Reference/Server.md
generated
vendored
Normal file
2192
node_modules/fastify/docs/Reference/Server.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
256
node_modules/fastify/docs/Reference/Type-Providers.md
generated
vendored
Normal file
256
node_modules/fastify/docs/Reference/Type-Providers.md
generated
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
## Type Providers
|
||||
|
||||
Type Providers are a TypeScript feature that enables Fastify to infer type
|
||||
information from inline JSON Schema. They are an alternative to specifying
|
||||
generic arguments on routes and can reduce the need to keep associated types for
|
||||
each schema in a project.
|
||||
|
||||
### Providers
|
||||
|
||||
Official Type Provider packages follow the
|
||||
`@fastify/type-provider-{provider-name}` naming convention.
|
||||
Several community providers are also available.
|
||||
|
||||
The following inference packages are supported:
|
||||
|
||||
- [`json-schema-to-ts`](https://github.com/ThomasAribart/json-schema-to-ts)
|
||||
- [`typebox`](https://github.com/sinclairzx81/typebox)
|
||||
- [`zod`](https://github.com/colinhacks/zod)
|
||||
|
||||
See also the Type Provider wrapper packages for each of the packages respectively:
|
||||
|
||||
- [`@fastify/type-provider-json-schema-to-ts`](https://github.com/fastify/fastify-type-provider-json-schema-to-ts)
|
||||
- [`@fastify/type-provider-typebox`](https://github.com/fastify/fastify-type-provider-typebox)
|
||||
- [`fastify-type-provider-zod`](https://github.com/turkerdev/fastify-type-provider-zod)
|
||||
(3rd party)
|
||||
|
||||
### Json Schema to Ts
|
||||
|
||||
The following sets up a `json-schema-to-ts` Type Provider:
|
||||
|
||||
```bash
|
||||
$ npm i @fastify/type-provider-json-schema-to-ts
|
||||
```
|
||||
|
||||
```typescript
|
||||
import fastify from 'fastify'
|
||||
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
|
||||
|
||||
const server = fastify().withTypeProvider<JsonSchemaToTsProvider>()
|
||||
|
||||
server.get('/route', {
|
||||
schema: {
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
foo: { type: 'number' },
|
||||
bar: { type: 'string' },
|
||||
},
|
||||
required: ['foo', 'bar']
|
||||
}
|
||||
}
|
||||
}, (request, reply) => {
|
||||
|
||||
// type Query = { foo: number, bar: string }
|
||||
const { foo, bar } = request.query // type safe!
|
||||
})
|
||||
```
|
||||
|
||||
### TypeBox
|
||||
|
||||
The following sets up a TypeBox Type Provider:
|
||||
|
||||
```bash
|
||||
$ npm i @fastify/type-provider-typebox
|
||||
```
|
||||
|
||||
```typescript
|
||||
import fastify from 'fastify'
|
||||
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
const server = fastify().withTypeProvider<TypeBoxTypeProvider>()
|
||||
|
||||
server.get('/route', {
|
||||
schema: {
|
||||
querystring: Type.Object({
|
||||
foo: Type.Number(),
|
||||
bar: Type.String()
|
||||
})
|
||||
}
|
||||
}, (request, reply) => {
|
||||
|
||||
// type Query = { foo: number, bar: string }
|
||||
const { foo, bar } = request.query // type safe!
|
||||
})
|
||||
```
|
||||
|
||||
See the [TypeBox
|
||||
documentation](https://github.com/sinclairzx81/typebox#validation)
|
||||
for setting up AJV to work with TypeBox.
|
||||
|
||||
### Zod
|
||||
|
||||
See [official documentation](https://github.com/turkerdev/fastify-type-provider-zod)
|
||||
for Zod Type Provider instructions.
|
||||
|
||||
|
||||
### Scoped Type-Provider
|
||||
|
||||
The provider types don't propagate globally. In encapsulated usage, one can
|
||||
remap the context to use one or more providers (for example, `typebox` and
|
||||
`json-schema-to-ts` can be used in the same application).
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
import Fastify from 'fastify'
|
||||
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
||||
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
const fastify = Fastify()
|
||||
|
||||
function pluginWithTypebox(fastify: FastifyInstance, _opts, done): void {
|
||||
fastify.withTypeProvider<TypeBoxTypeProvider>()
|
||||
.get('/', {
|
||||
schema: {
|
||||
body: Type.Object({
|
||||
x: Type.String(),
|
||||
y: Type.Number(),
|
||||
z: Type.Boolean()
|
||||
})
|
||||
}
|
||||
}, (req) => {
|
||||
const { x, y, z } = req.body // type safe
|
||||
});
|
||||
done()
|
||||
}
|
||||
|
||||
function pluginWithJsonSchema(fastify: FastifyInstance, _opts, done): void {
|
||||
fastify.withTypeProvider<JsonSchemaToTsProvider>()
|
||||
.get('/', {
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
x: { type: 'string' },
|
||||
y: { type: 'number' },
|
||||
z: { type: 'boolean' }
|
||||
},
|
||||
}
|
||||
}
|
||||
}, (req) => {
|
||||
const { x, y, z } = req.body // type safe
|
||||
});
|
||||
done()
|
||||
}
|
||||
|
||||
fastify.register(pluginWithJsonSchema)
|
||||
fastify.register(pluginWithTypebox)
|
||||
```
|
||||
|
||||
It is important to note that since the types do not propagate globally, it is
|
||||
currently not possible to avoid multiple registrations on routes when dealing
|
||||
with several scopes, as shown below:
|
||||
|
||||
```ts
|
||||
import Fastify from 'fastify'
|
||||
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
|
||||
|
||||
server.register(plugin1) // wrong
|
||||
server.register(plugin2) // correct
|
||||
|
||||
function plugin1(fastify: FastifyInstance, _opts, done): void {
|
||||
fastify.get('/', {
|
||||
schema: {
|
||||
body: Type.Object({
|
||||
x: Type.String(),
|
||||
y: Type.Number(),
|
||||
z: Type.Boolean()
|
||||
})
|
||||
}
|
||||
}, (req) => {
|
||||
// In a new scope, call `withTypeProvider` again to ensure it works
|
||||
const { x, y, z } = req.body
|
||||
});
|
||||
done()
|
||||
}
|
||||
|
||||
function plugin2(fastify: FastifyInstance, _opts, done): void {
|
||||
const server = fastify.withTypeProvider<TypeBoxTypeProvider>()
|
||||
|
||||
server.get('/', {
|
||||
schema: {
|
||||
body: Type.Object({
|
||||
x: Type.String(),
|
||||
y: Type.Number(),
|
||||
z: Type.Boolean()
|
||||
})
|
||||
}
|
||||
}, (req) => {
|
||||
// works
|
||||
const { x, y, z } = req.body
|
||||
});
|
||||
done()
|
||||
}
|
||||
```
|
||||
|
||||
### Type Definition of FastifyInstance + TypeProvider
|
||||
|
||||
When working with modules, use `FastifyInstance` with Type Provider generics.
|
||||
See the example below:
|
||||
|
||||
```ts
|
||||
// index.ts
|
||||
import Fastify from 'fastify'
|
||||
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
||||
import { registerRoutes } from './routes'
|
||||
|
||||
const server = Fastify().withTypeProvider<TypeBoxTypeProvider>()
|
||||
|
||||
registerRoutes(server)
|
||||
|
||||
server.listen({ port: 3000 })
|
||||
```
|
||||
|
||||
```ts
|
||||
// routes.ts
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import {
|
||||
FastifyInstance,
|
||||
FastifyBaseLogger,
|
||||
RawReplyDefaultExpression,
|
||||
RawRequestDefaultExpression,
|
||||
RawServerDefault
|
||||
} from 'fastify'
|
||||
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
||||
|
||||
type FastifyTypebox = FastifyInstance<
|
||||
RawServerDefault,
|
||||
RawRequestDefaultExpression<RawServerDefault>,
|
||||
RawReplyDefaultExpression<RawServerDefault>,
|
||||
FastifyBaseLogger,
|
||||
TypeBoxTypeProvider
|
||||
>;
|
||||
|
||||
export function registerRoutes(fastify: FastifyTypebox): void {
|
||||
fastify.get('/', {
|
||||
schema: {
|
||||
body: Type.Object({
|
||||
x: Type.String(),
|
||||
y: Type.Number(),
|
||||
z: Type.Boolean()
|
||||
})
|
||||
}
|
||||
}, (req) => {
|
||||
// works
|
||||
const { x, y, z } = req.body
|
||||
});
|
||||
}
|
||||
```
|
||||
1588
node_modules/fastify/docs/Reference/TypeScript.md
generated
vendored
Normal file
1588
node_modules/fastify/docs/Reference/TypeScript.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1042
node_modules/fastify/docs/Reference/Validation-and-Serialization.md
generated
vendored
Normal file
1042
node_modules/fastify/docs/Reference/Validation-and-Serialization.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
58
node_modules/fastify/docs/Reference/Warnings.md
generated
vendored
Normal file
58
node_modules/fastify/docs/Reference/Warnings.md
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
**Table of contents**
|
||||
- [Warnings](#warnings)
|
||||
- [Warnings In Fastify](#warnings-in-fastify)
|
||||
- [Fastify Warning Codes](#fastify-warning-codes)
|
||||
- [FSTWRN001](#FSTWRN001)
|
||||
- [FSTWRN002](#FSTWRN002)
|
||||
- [Fastify Deprecation Codes](#fastify-deprecation-codes)
|
||||
- [FSTDEP022](#FSTDEP022)
|
||||
|
||||
## Warnings
|
||||
|
||||
### Warnings In Fastify
|
||||
|
||||
Fastify uses Node.js's [warning event](https://nodejs.org/api/process.html#event-warning)
|
||||
API to notify users of deprecated features and coding mistakes. Fastify's
|
||||
warnings are recognizable by the `FSTWRN` and `FSTDEP` prefixes. When
|
||||
encountering such a warning, it is highly recommended to determine the cause
|
||||
using the [`--trace-warnings`](https://nodejs.org/api/cli.html#--trace-warnings)
|
||||
and [`--trace-deprecation`](https://nodejs.org/api/cli.html#--trace-deprecation)
|
||||
flags. These produce stack traces pointing to where the issue occurs in the
|
||||
application's code. Issues opened about warnings without this information will
|
||||
be closed due to lack of details.
|
||||
|
||||
Warnings can also be disabled, though it is not recommended. If necessary, use
|
||||
one of the following methods:
|
||||
|
||||
- Set the `NODE_NO_WARNINGS` environment variable to `1`
|
||||
- Pass the `--no-warnings` flag to the node process
|
||||
- Set `no-warnings` in the `NODE_OPTIONS` environment variable
|
||||
|
||||
For more information on disabling warnings, see [Node's documentation](https://nodejs.org/api/cli.html).
|
||||
|
||||
Disabling warnings may cause issues when upgrading Fastify versions. Only
|
||||
experienced users should consider disabling warnings.
|
||||
|
||||
### Fastify Warning Codes
|
||||
|
||||
| Code | Description | How to solve | Discussion |
|
||||
| ---- | ----------- | ------------ | ---------- |
|
||||
| <a id="FSTWRN001">FSTWRN001</a> | The specified schema for a route is missing. This may indicate the schema is not well specified. | Check the schema for the route. | [#4647](https://github.com/fastify/fastify/pull/4647) |
|
||||
| <a id="FSTWRN002">FSTWRN002</a> | The %s plugin being registered mixes async and callback styles, which will result in an error in `fastify@5`. | Do not mix async and callback style. | [#5139](https://github.com/fastify/fastify/pull/5139) |
|
||||
|
||||
|
||||
### Fastify Deprecation Codes
|
||||
|
||||
Deprecation codes are supported by the Node.js CLI options:
|
||||
|
||||
- [--no-deprecation](https://nodejs.org/api/cli.html#--no-deprecation)
|
||||
- [--throw-deprecation](https://nodejs.org/api/cli.html#--throw-deprecation)
|
||||
- [--trace-deprecation](https://nodejs.org/api/cli.html#--trace-deprecation)
|
||||
|
||||
|
||||
| Code | Description | How to solve | Discussion |
|
||||
| ---- | ----------- | ------------ | ---------- |
|
||||
| <a id="FSTDEP022">FSTDEP022</a> | You are trying to access the deprecated router options on top option properties. | Use `options.routerOptions`. | [#5985](https://github.com/fastify/fastify/pull/5985)
|
||||
24
node_modules/fastify/docs/index.md
generated
vendored
Normal file
24
node_modules/fastify/docs/index.md
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<h1 align="center">Fastify</h1>
|
||||
|
||||
The documentation for Fastify is split into two categories:
|
||||
|
||||
- [Reference documentation](./Reference/Index.md)
|
||||
- [Guides](./Guides/Index.md)
|
||||
|
||||
The reference documentation utilizes a very formal style in an effort to document
|
||||
Fastify's API and implementation details thoroughly for the developer who needs
|
||||
such. The guides category utilizes an informal educational style as a means to
|
||||
introduce newcomers to core and advanced Fastify concepts.
|
||||
|
||||
## Where To Start
|
||||
|
||||
Complete newcomers to Fastify should first read our [Getting
|
||||
Started](./Guides/Getting-Started.md) guide.
|
||||
|
||||
Developers experienced with Fastify should consult the [reference
|
||||
documentation](./Reference/Index.md) directly to find the topic they are seeking
|
||||
more information about.
|
||||
|
||||
## Additional Documentation
|
||||
|
||||
- Fastify's [Long Term Support (LTS)](./Reference/LTS.md) policy
|
||||
1
node_modules/fastify/docs/resources/encapsulation_context.drawio
generated
vendored
Normal file
1
node_modules/fastify/docs/resources/encapsulation_context.drawio
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<mxfile host="app.diagrams.net" modified="2020-12-06T18:51:58.018Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" etag="vyaguDTT1c9e-NqGeV_7" version="13.10.9" type="device"><diagram id="hZ89Y7exsLGRT07QCK17" name="Page-1">7ZpPk5owGMY/jcd2SCKIx0q3todOO+thjzsRAmQ2Eopx1f30DRKUNLCrncXN6uKMA0/+EN5feJNndICCxWZa4Dz9ySPCBtCJNgP0dQDhaDiS36WwrQQfOJWQFDSqJHAQZvSJKLGutqIRWWoVBedM0FwXQ55lJBSahouCr/VqMWf6XXOcEEOYhZiZ6h2NRKqewnUO+ndCk7S+M3BUyQLXlZWwTHHE1w0J3QxQUHAuqrPFJiCsjF0dl6rdt47S/cAKkoljGjyhO8+Z3U9YQP3bP1PHvc9+fFK9PGK2Ug98Ww4IOgHPBNkINXSxreOxTqkgsxyH5fVaIh+gSSoWTF4BeYqXeUUhphsibzyJKWMBZ7zYNUexW36k/kgKQWWYvzCaZLJM8LKngq+yqGy362wpCv5AGq293VH2KkfX0KtD6mZM6geUtyObhqRiNCV8QUSxlVVU6VDh2uqX6wN8d6y0tAF+L2I14ZJ9zwcm8kRhOQER6kL0m60SmhmE5DzLy9O84CFZytFMXmA2x+FDsov8r5VgNCNK12H8CzKOYRi2QYq8ued6rwMDOq5Gwzdp+C0wUF8shtfMYmwXC/eKWSDfLhbATFJG/MtQ5sc/+35Jx/O6B+fZmABPz9wAmUFBXktU+guLZ0QlSKncgHQvr/rcemGCdk299rX1lAn5POSXp+nbhXzUGfI3TQsRJn7cmha80CfzuBcKdan+XrS8FmfNFf4HIZOQXYjGBiJp7rIo7DF57cN/ZPLqHwrUmdQrbjO1wRYoALp9LbKOnW8OAZFLRm2Qxt4I4X5WmNqtjT7rOyEA3norZFrqq6cEkH2Y4AcmI+MhaB0m03EbYM5tLFCL2zrzLheY5vddO4thByubnAXodnOXsnE9HoOd1gJcvvv7D0SWMTL93/s3FydTsc9cmJ7PilfnFfdDJ0Oyz1zAy7eApyc4+8wFvHwPeHrGO6e5kJeHH9Z3ZY1/J6Cbvw==</diagram></mxfile>
|
||||
3
node_modules/fastify/docs/resources/encapsulation_context.svg
generated
vendored
Normal file
3
node_modules/fastify/docs/resources/encapsulation_context.svg
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 20 KiB |
19
node_modules/fastify/eslint.config.js
generated
vendored
Normal file
19
node_modules/fastify/eslint.config.js
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict'
|
||||
const neostandard = require('neostandard')
|
||||
|
||||
module.exports = [
|
||||
...neostandard({
|
||||
ignores: [
|
||||
'lib/configValidator.js',
|
||||
'lib/error-serializer.js',
|
||||
'test/same-shape.test.js',
|
||||
'test/types/import.js'
|
||||
],
|
||||
ts: true
|
||||
}),
|
||||
{
|
||||
rules: {
|
||||
'comma-dangle': ['error', 'never']
|
||||
}
|
||||
}
|
||||
]
|
||||
38
node_modules/fastify/examples/asyncawait.js
generated
vendored
Normal file
38
node_modules/fastify/examples/asyncawait.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({ logger: true })
|
||||
|
||||
const schema = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function result () {
|
||||
return Promise.resolve({ hello: 'world' })
|
||||
}
|
||||
|
||||
fastify
|
||||
.get('/await', schema, async function (req, reply) {
|
||||
reply.header('Content-Type', 'application/json').code(200)
|
||||
return result()
|
||||
})
|
||||
.get('/', schema, async function (req, reply) {
|
||||
reply.header('Content-Type', 'application/json').code(200)
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
3
node_modules/fastify/examples/benchmark/body.json
generated
vendored
Normal file
3
node_modules/fastify/examples/benchmark/body.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "world"
|
||||
}
|
||||
44
node_modules/fastify/examples/benchmark/hooks-benchmark-async-await.js
generated
vendored
Normal file
44
node_modules/fastify/examples/benchmark/hooks-benchmark-async-await.js
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../../fastify')({ logger: false })
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function promiseFunction (resolve) {
|
||||
setImmediate(resolve)
|
||||
}
|
||||
|
||||
async function asyncHook () {
|
||||
await new Promise(promiseFunction)
|
||||
}
|
||||
|
||||
fastify
|
||||
.addHook('onRequest', asyncHook)
|
||||
.addHook('onRequest', asyncHook)
|
||||
.addHook('preHandler', asyncHook)
|
||||
.addHook('preHandler', asyncHook)
|
||||
.addHook('preHandler', asyncHook)
|
||||
.addHook('onSend', asyncHook)
|
||||
|
||||
fastify.get('/', opts, function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
52
node_modules/fastify/examples/benchmark/hooks-benchmark.js
generated
vendored
Normal file
52
node_modules/fastify/examples/benchmark/hooks-benchmark.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../../fastify')({ logger: false })
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.addHook('onRequest', function (request, reply, done) {
|
||||
done()
|
||||
})
|
||||
.addHook('onRequest', function (request, reply, done) {
|
||||
done()
|
||||
})
|
||||
|
||||
fastify
|
||||
.addHook('preHandler', function (request, reply, done) {
|
||||
done()
|
||||
})
|
||||
.addHook('preHandler', function (request, reply, done) {
|
||||
setImmediate(done)
|
||||
})
|
||||
.addHook('preHandler', function (request, reply, done) {
|
||||
done()
|
||||
})
|
||||
|
||||
fastify
|
||||
.addHook('onSend', function (request, reply, payload, done) {
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.get('/', opts, function (request, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
47
node_modules/fastify/examples/benchmark/parser.js
generated
vendored
Normal file
47
node_modules/fastify/examples/benchmark/parser.js
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../../fastify')({
|
||||
logger: false
|
||||
})
|
||||
|
||||
const jsonParser = require('fast-json-body')
|
||||
const querystring = require('node:querystring')
|
||||
|
||||
// Handled by fastify
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/
|
||||
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/jsoff' http://localhost:3000/
|
||||
fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
|
||||
jsonParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
// curl -X POST -d 'hello=world' -H'Content-type: application/x-www-form-urlencoded' http://localhost:3000/
|
||||
fastify.addContentTypeParser('application/x-www-form-urlencoded', function (request, payload, done) {
|
||||
let body = ''
|
||||
payload.on('data', function (data) {
|
||||
body += data
|
||||
})
|
||||
payload.on('end', function () {
|
||||
try {
|
||||
const parsed = querystring.parse(body)
|
||||
done(null, parsed)
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
})
|
||||
payload.on('error', done)
|
||||
})
|
||||
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/vnd.custom+json' http://localhost:3000/
|
||||
fastify.addContentTypeParser(/^application\/.+\+json$/, { parseAs: 'string' }, fastify.getDefaultJsonParser('error', 'ignore'))
|
||||
|
||||
fastify
|
||||
.post('/', function (req, reply) {
|
||||
reply.send(req.body)
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) throw err
|
||||
})
|
||||
30
node_modules/fastify/examples/benchmark/simple.js
generated
vendored
Normal file
30
node_modules/fastify/examples/benchmark/simple.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../../fastify')({
|
||||
logger: false
|
||||
})
|
||||
|
||||
const schema = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.get('/', schema, function (req, reply) {
|
||||
reply
|
||||
.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) throw err
|
||||
})
|
||||
91
node_modules/fastify/examples/hooks.js
generated
vendored
Normal file
91
node_modules/fastify/examples/hooks.js
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({ logger: true })
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
'2xx': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const optsPost = {
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['hello'],
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
response: opts.response
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.addHook('onRequest', function (request, reply, done) {
|
||||
console.log('onRequest')
|
||||
done()
|
||||
})
|
||||
.addHook('preParsing', function (request, reply, payload, done) {
|
||||
console.log('preParsing')
|
||||
done()
|
||||
})
|
||||
.addHook('preValidation', function (request, reply, done) {
|
||||
console.log('preValidation')
|
||||
done()
|
||||
})
|
||||
.addHook('preHandler', function (request, reply, done) {
|
||||
console.log('preHandler')
|
||||
done()
|
||||
})
|
||||
.addHook('preSerialization', function (request, reply, payload, done) {
|
||||
console.log('preSerialization', payload)
|
||||
done()
|
||||
})
|
||||
.addHook('onError', function (request, reply, error, done) {
|
||||
console.log('onError', error.message)
|
||||
done()
|
||||
})
|
||||
.addHook('onSend', function (request, reply, payload, done) {
|
||||
console.log('onSend', payload)
|
||||
done()
|
||||
})
|
||||
.addHook('onResponse', function (request, reply, done) {
|
||||
console.log('onResponse')
|
||||
done()
|
||||
})
|
||||
.addHook('onRoute', function (routeOptions) {
|
||||
console.log('onRoute')
|
||||
})
|
||||
.addHook('onListen', async function () {
|
||||
console.log('onListen')
|
||||
})
|
||||
.addHook('onClose', function (instance, done) {
|
||||
console.log('onClose')
|
||||
done()
|
||||
})
|
||||
|
||||
fastify.get('/', opts, function (req, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.post('/', optsPost, function (req, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
39
node_modules/fastify/examples/http2.js
generated
vendored
Normal file
39
node_modules/fastify/examples/http2.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const fastify = require('../fastify')({
|
||||
http2: true,
|
||||
https: {
|
||||
key: fs.readFileSync(path.join(__dirname, '../test/https/fastify.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '../test/https/fastify.cert'))
|
||||
},
|
||||
logger: true
|
||||
})
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
'2xx': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.get('/', opts, function (req, reply) {
|
||||
reply.header('Content-Type', 'application/json').code(200)
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
38
node_modules/fastify/examples/https.js
generated
vendored
Normal file
38
node_modules/fastify/examples/https.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const fastify = require('../fastify')({
|
||||
https: {
|
||||
key: fs.readFileSync(path.join(__dirname, '../test/https/fastify.key')),
|
||||
cert: fs.readFileSync(path.join(__dirname, '../test/https/fastify.cert'))
|
||||
},
|
||||
logger: true
|
||||
})
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
'2xx': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.get('/', opts, function (req, reply) {
|
||||
reply.header('Content-Type', 'application/json').code(200)
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
53
node_modules/fastify/examples/parser.js
generated
vendored
Normal file
53
node_modules/fastify/examples/parser.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({ logger: true })
|
||||
const jsonParser = require('fast-json-body')
|
||||
const querystring = require('node:querystring')
|
||||
|
||||
// Handled by fastify
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/
|
||||
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/jsoff' http://localhost:3000/
|
||||
fastify.addContentTypeParser('application/jsoff', function (request, payload, done) {
|
||||
jsonParser(payload, function (err, body) {
|
||||
done(err, body)
|
||||
})
|
||||
})
|
||||
|
||||
// curl -X POST -d 'hello=world' -H'Content-type: application/x-www-form-urlencoded' http://localhost:3000/
|
||||
fastify.addContentTypeParser('application/x-www-form-urlencoded', function (request, payload, done) {
|
||||
let body = ''
|
||||
payload.on('data', function (data) {
|
||||
body += data
|
||||
})
|
||||
payload.on('end', function () {
|
||||
try {
|
||||
const parsed = querystring.parse(body)
|
||||
done(null, parsed)
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
})
|
||||
payload.on('error', done)
|
||||
})
|
||||
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/vnd.custom+json' http://localhost:3000/
|
||||
fastify.addContentTypeParser(/^application\/.+\+json$/, { parseAs: 'string' }, fastify.getDefaultJsonParser('error', 'ignore'))
|
||||
|
||||
// remove default json parser
|
||||
// curl -X POST -d '{"hello":"world"}' -H'Content-type: application/json' http://localhost:3000/ is now no longer handled by fastify
|
||||
fastify.removeContentTypeParser('application/json')
|
||||
|
||||
// This call would remove any content type parser
|
||||
// fastify.removeAllContentTypeParsers()
|
||||
|
||||
fastify
|
||||
.post('/', function (req, reply) {
|
||||
reply.send(req.body)
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
12
node_modules/fastify/examples/plugin.js
generated
vendored
Normal file
12
node_modules/fastify/examples/plugin.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function (fastify, opts, done) {
|
||||
fastify
|
||||
.get('/', opts, function (req, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
.post('/', opts, function (req, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
done()
|
||||
}
|
||||
38
node_modules/fastify/examples/route-prefix.js
generated
vendored
Normal file
38
node_modules/fastify/examples/route-prefix.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({ logger: true })
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
'2xx': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
greet: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify.register(function (instance, options, done) {
|
||||
// the route will be '/english/hello'
|
||||
instance.get('/hello', opts, (req, reply) => {
|
||||
reply.send({ greet: 'hello' })
|
||||
})
|
||||
done()
|
||||
}, { prefix: '/english' })
|
||||
|
||||
fastify.register(function (instance, options, done) {
|
||||
// the route will be '/italian/hello'
|
||||
instance.get('/hello', opts, (req, reply) => {
|
||||
reply.send({ greet: 'ciao' })
|
||||
})
|
||||
done()
|
||||
}, { prefix: '/italian' })
|
||||
|
||||
fastify.listen({ port: 8000 }, function (err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
38
node_modules/fastify/examples/shared-schema.js
generated
vendored
Normal file
38
node_modules/fastify/examples/shared-schema.js
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({ logger: true })
|
||||
|
||||
fastify.addSchema({
|
||||
$id: 'https://foo/common.json',
|
||||
definitions: {
|
||||
response: {
|
||||
$id: '#reply',
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
$id: '#bar',
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
200: { $ref: 'https://foo/common.json#reply' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.get('/', opts, function (req, reply) {
|
||||
reply.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, err => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
20
node_modules/fastify/examples/simple-stream.js
generated
vendored
Normal file
20
node_modules/fastify/examples/simple-stream.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({
|
||||
logger: false
|
||||
})
|
||||
|
||||
const Readable = require('node:stream').Readable
|
||||
|
||||
fastify
|
||||
.get('/', function (req, reply) {
|
||||
const stream = Readable.from(['hello world'])
|
||||
reply.send(stream)
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
fastify.log.info(`server listening on ${address}`)
|
||||
})
|
||||
32
node_modules/fastify/examples/simple.js
generated
vendored
Normal file
32
node_modules/fastify/examples/simple.js
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({
|
||||
logger: false
|
||||
})
|
||||
|
||||
const schema = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fastify
|
||||
.get('/', schema, function (req, reply) {
|
||||
reply
|
||||
.send({ hello: 'world' })
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, (err, address) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
27
node_modules/fastify/examples/simple.mjs
generated
vendored
Normal file
27
node_modules/fastify/examples/simple.mjs
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// works on Node v14.13.0+
|
||||
import { fastify } from '../fastify.js'
|
||||
|
||||
const app = fastify({
|
||||
logger: true
|
||||
})
|
||||
|
||||
const schema = {
|
||||
schema: {
|
||||
response: {
|
||||
200: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/', schema, async function (req, reply) {
|
||||
return { hello: 'world' }
|
||||
})
|
||||
|
||||
app.listen({ port: 3000 }).catch(console.error)
|
||||
79
node_modules/fastify/examples/typescript-server.ts
generated
vendored
Normal file
79
node_modules/fastify/examples/typescript-server.ts
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Most type annotations in this file are not strictly necessary but are
|
||||
* included for this example.
|
||||
*
|
||||
* To run this example execute the following commands to install typescript,
|
||||
* transpile the code, and start the server:
|
||||
*
|
||||
* npm i -g typescript
|
||||
* tsc examples/typescript-server.ts --target es6 --module commonjs
|
||||
* node examples/typescript-server.js
|
||||
*/
|
||||
|
||||
import fastify, { FastifyInstance, RouteShorthandOptions } from '../fastify'
|
||||
import { Server, IncomingMessage, ServerResponse } from 'node:http'
|
||||
|
||||
// Create an http server. We pass the relevant typings for our http version used.
|
||||
// By passing types we get correctly typed access to the underlying http objects in routes.
|
||||
// If using http2 we'd pass <http2.Http2Server, http2.Http2ServerRequest, http2.Http2ServerResponse>
|
||||
const server: FastifyInstance<
|
||||
Server,
|
||||
IncomingMessage,
|
||||
ServerResponse
|
||||
> = fastify({ logger: true })
|
||||
|
||||
// Define interfaces for our request. We can create these automatically
|
||||
// off our JSON Schema files (See TypeScript.md) but for the purpose of this
|
||||
// example we manually define them.
|
||||
interface PingQuerystring {
|
||||
foo?: number;
|
||||
}
|
||||
|
||||
interface PingParams {
|
||||
bar?: string;
|
||||
}
|
||||
|
||||
interface PingHeaders {
|
||||
a?: string;
|
||||
}
|
||||
|
||||
interface PingBody {
|
||||
baz?: string;
|
||||
}
|
||||
|
||||
// Define our route options with schema validation
|
||||
const opts: RouteShorthandOptions = {
|
||||
schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pong: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add our route handler with correct types
|
||||
server.post<{
|
||||
Querystring: PingQuerystring;
|
||||
Params: PingParams;
|
||||
Headers: PingHeaders;
|
||||
Body: PingBody;
|
||||
}>('/ping/:bar', opts, (request, reply) => {
|
||||
console.log(request.query) // this is of type `PingQuerystring`
|
||||
console.log(request.params) // this is of type `PingParams`
|
||||
console.log(request.headers) // this is of type `PingHeaders`
|
||||
console.log(request.body) // this is of type `PingBody`
|
||||
reply.code(200).send({ pong: 'it worked!' })
|
||||
})
|
||||
|
||||
// Start your server
|
||||
server.listen({ port: 8080 }, (err, address) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`server listening on ${address}`)
|
||||
})
|
||||
29
node_modules/fastify/examples/use-plugin.js
generated
vendored
Normal file
29
node_modules/fastify/examples/use-plugin.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
const fastify = require('../fastify')({ logger: true })
|
||||
|
||||
const opts = {
|
||||
schema: {
|
||||
response: {
|
||||
'2xx': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
hello: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fastify.register(require('./plugin'), opts, function (err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
fastify.listen({ port: 3000 }, function (err) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
257
node_modules/fastify/fastify.d.ts
generated
vendored
Normal file
257
node_modules/fastify/fastify.d.ts
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
import * as http from 'node:http'
|
||||
import * as http2 from 'node:http2'
|
||||
import * as https from 'node:https'
|
||||
import { Socket } from 'node:net'
|
||||
|
||||
import { Options as AjvOptions, ValidatorFactory } from '@fastify/ajv-compiler'
|
||||
import { FastifyError } from '@fastify/error'
|
||||
import { Options as FJSOptions, SerializerFactory } from '@fastify/fast-json-stringify-compiler'
|
||||
import { ConstraintStrategy, HTTPVersion } from 'find-my-way'
|
||||
import { InjectOptions, CallbackFunc as LightMyRequestCallback, Chain as LightMyRequestChain, Response as LightMyRequestResponse } from 'light-my-request'
|
||||
|
||||
import { AddContentTypeParser, ConstructorAction, FastifyBodyParser, FastifyContentTypeParser, getDefaultJsonParser, hasContentTypeParser, ProtoAction } from './types/content-type-parser'
|
||||
import { FastifyContextConfig, FastifyReplyContext, FastifyRequestContext } from './types/context'
|
||||
import { FastifyErrorCodes } from './types/errors'
|
||||
import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onListenAsyncHookHandler, onListenHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onRegisterHookHandler, onRequestAbortAsyncHookHandler, onRequestAbortHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, RequestPayload } from './types/hooks'
|
||||
import { FastifyInstance, FastifyListenOptions, PrintRoutesOptions } from './types/instance'
|
||||
import {
|
||||
FastifyBaseLogger,
|
||||
FastifyChildLoggerFactory,
|
||||
FastifyLogFn,
|
||||
FastifyLoggerInstance,
|
||||
FastifyLoggerOptions,
|
||||
LogLevel,
|
||||
PinoLoggerOptions
|
||||
} from './types/logger'
|
||||
import { FastifyPlugin, FastifyPluginAsync, FastifyPluginCallback, FastifyPluginOptions } from './types/plugin'
|
||||
import { FastifyRegister, FastifyRegisterOptions, RegisterOptions } from './types/register'
|
||||
import { FastifyReply } from './types/reply'
|
||||
import { FastifyRequest, RequestGenericInterface } from './types/request'
|
||||
import { RouteGenericInterface, RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler } from './types/route'
|
||||
import { FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, SchemaErrorDataVar, SchemaErrorFormatter } from './types/schema'
|
||||
import { FastifyServerFactory, FastifyServerFactoryHandler } from './types/serverFactory'
|
||||
import { FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike } from './types/type-provider'
|
||||
import { ContextConfigDefault, HTTPMethods, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestBodyDefault, RequestHeadersDefault, RequestParamsDefault, RequestQuerystringDefault } from './types/utils'
|
||||
|
||||
declare module '@fastify/error' {
|
||||
interface FastifyError {
|
||||
validationContext?: SchemaErrorDataVar;
|
||||
validation?: FastifySchemaValidationError[];
|
||||
}
|
||||
}
|
||||
|
||||
type Fastify = typeof fastify
|
||||
|
||||
declare namespace fastify {
|
||||
export const errorCodes: FastifyErrorCodes
|
||||
|
||||
export type FastifyHttp2SecureOptions<
|
||||
Server extends http2.Http2SecureServer,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
||||
> = FastifyServerOptions<Server, Logger> & {
|
||||
http2: true,
|
||||
https: http2.SecureServerOptions,
|
||||
http2SessionTimeout?: number
|
||||
}
|
||||
|
||||
export type FastifyHttp2Options<
|
||||
Server extends http2.Http2Server,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
||||
> = FastifyServerOptions<Server, Logger> & {
|
||||
http2: true,
|
||||
http2SessionTimeout?: number
|
||||
}
|
||||
|
||||
export type FastifyHttpsOptions<
|
||||
Server extends https.Server,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
||||
> = FastifyServerOptions<Server, Logger> & {
|
||||
https: https.ServerOptions | null
|
||||
}
|
||||
|
||||
export type FastifyHttpOptions<
|
||||
Server extends http.Server,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
||||
> = FastifyServerOptions<Server, Logger> & {
|
||||
http?: http.ServerOptions | null
|
||||
}
|
||||
|
||||
type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
|
||||
|
||||
export interface ConnectionError extends Error {
|
||||
code: string,
|
||||
bytesParsed: number,
|
||||
rawPacket: {
|
||||
type: string,
|
||||
data: number[]
|
||||
}
|
||||
}
|
||||
|
||||
type TrustProxyFunction = (address: string, hop: number) => boolean
|
||||
|
||||
export type FastifyRouterOptions<RawServer extends RawServerBase> = {
|
||||
allowUnsafeRegex?: boolean,
|
||||
buildPrettyMeta?: (route: { [k: string]: unknown, store: { [k: string]: unknown } }) => object,
|
||||
caseSensitive?: boolean,
|
||||
constraints?: {
|
||||
[name: string]: ConstraintStrategy<FindMyWayVersion<RawServer>, unknown>,
|
||||
},
|
||||
defaultRoute?: (req: FastifyRequest, res: FastifyReply) => void,
|
||||
ignoreDuplicateSlashes?: boolean,
|
||||
ignoreTrailingSlash?: boolean,
|
||||
maxParamLength?: number,
|
||||
onBadUrl?: (path: string, req: FastifyRequest, res: FastifyReply) => void,
|
||||
querystringParser?: (str: string) => { [key: string]: unknown },
|
||||
useSemicolonDelimiter?: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for a fastify server instance. Utilizes conditional logic on the generic server parameter to enforce certain https and http2
|
||||
*/
|
||||
export type FastifyServerOptions<
|
||||
RawServer extends RawServerBase = RawServerDefault,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger
|
||||
> = {
|
||||
ignoreTrailingSlash?: boolean,
|
||||
ignoreDuplicateSlashes?: boolean,
|
||||
connectionTimeout?: number,
|
||||
keepAliveTimeout?: number,
|
||||
maxRequestsPerSocket?: number,
|
||||
forceCloseConnections?: boolean | 'idle',
|
||||
requestTimeout?: number,
|
||||
pluginTimeout?: number,
|
||||
bodyLimit?: number,
|
||||
maxParamLength?: number,
|
||||
disableRequestLogging?: boolean,
|
||||
exposeHeadRoutes?: boolean,
|
||||
onProtoPoisoning?: ProtoAction,
|
||||
onConstructorPoisoning?: ConstructorAction,
|
||||
logger?: boolean | FastifyLoggerOptions<RawServer> & PinoLoggerOptions,
|
||||
loggerInstance?: Logger
|
||||
serializerOpts?: FJSOptions | Record<string, unknown>,
|
||||
serverFactory?: FastifyServerFactory<RawServer>,
|
||||
caseSensitive?: boolean,
|
||||
allowUnsafeRegex?: boolean,
|
||||
requestIdHeader?: string | false,
|
||||
requestIdLogLabel?: string;
|
||||
useSemicolonDelimiter?: boolean,
|
||||
genReqId?: (req: RawRequestDefaultExpression<RawServer>) => string,
|
||||
trustProxy?: boolean | string | string[] | number | TrustProxyFunction,
|
||||
querystringParser?: (str: string) => { [key: string]: unknown },
|
||||
constraints?: {
|
||||
[name: string]: ConstraintStrategy<FindMyWayVersion<RawServer>, unknown>,
|
||||
},
|
||||
schemaController?: {
|
||||
bucket?: (parentSchemas?: unknown) => {
|
||||
add(schema: unknown): FastifyInstance;
|
||||
getSchema(schemaId: string): unknown;
|
||||
getSchemas(): Record<string, unknown>;
|
||||
};
|
||||
compilersFactory?: {
|
||||
buildValidator?: ValidatorFactory;
|
||||
buildSerializer?: SerializerFactory;
|
||||
};
|
||||
};
|
||||
return503OnClosing?: boolean,
|
||||
ajv?: {
|
||||
customOptions?: AjvOptions,
|
||||
plugins?: (Function | [Function, unknown])[]
|
||||
},
|
||||
frameworkErrors?: <RequestGeneric extends RequestGenericInterface = RequestGenericInterface, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, SchemaCompiler extends FastifySchema = FastifySchema>(
|
||||
error: FastifyError,
|
||||
req: FastifyRequest<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>, FastifySchema, TypeProvider>,
|
||||
res: FastifyReply<RequestGeneric, RawServer, RawRequestDefaultExpression<RawServer>, RawReplyDefaultExpression<RawServer>, FastifyContextConfig, SchemaCompiler, TypeProvider>
|
||||
) => void,
|
||||
rewriteUrl?: (
|
||||
// The RawRequestDefaultExpression, RawReplyDefaultExpression, and FastifyTypeProviderDefault parameters
|
||||
// should be narrowed further but those generic parameters are not passed to this FastifyServerOptions type
|
||||
this: FastifyInstance<RawServer, RawRequestDefaultExpression<RawServer>, RawReplyDefaultExpression<RawServer>, Logger, FastifyTypeProviderDefault>,
|
||||
req: RawRequestDefaultExpression<RawServer>
|
||||
) => string,
|
||||
schemaErrorFormatter?: SchemaErrorFormatter,
|
||||
/**
|
||||
* listener to error events emitted by client connections
|
||||
*/
|
||||
clientErrorHandler?: (error: ConnectionError, socket: Socket) => void,
|
||||
childLoggerFactory?: FastifyChildLoggerFactory,
|
||||
allowErrorHandlerOverride?: boolean
|
||||
routerOptions?: FastifyRouterOptions<RawServer>,
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link FastifySchemaValidationError}
|
||||
*/
|
||||
export type ValidationResult = FastifySchemaValidationError
|
||||
|
||||
/* Export additional types */
|
||||
export type {
|
||||
LightMyRequestChain, InjectOptions, LightMyRequestResponse, LightMyRequestCallback, // 'light-my-request'
|
||||
FastifyRequest, RequestGenericInterface, // './types/request'
|
||||
FastifyReply, // './types/reply'
|
||||
FastifyPluginCallback, FastifyPluginAsync, FastifyPluginOptions, FastifyPlugin, // './types/plugin'
|
||||
FastifyListenOptions, FastifyInstance, PrintRoutesOptions, // './types/instance'
|
||||
FastifyLoggerOptions, FastifyBaseLogger, FastifyLoggerInstance, FastifyLogFn, LogLevel, // './types/logger'
|
||||
FastifyRequestContext, FastifyContextConfig, FastifyReplyContext, // './types/context'
|
||||
RouteHandler, RouteHandlerMethod, RouteOptions, RouteShorthandMethod, RouteShorthandOptions, RouteShorthandOptionsWithHandler, RouteGenericInterface, // './types/route'
|
||||
FastifyRegister, FastifyRegisterOptions, RegisterOptions, // './types/register'
|
||||
FastifyBodyParser, FastifyContentTypeParser, AddContentTypeParser, hasContentTypeParser, getDefaultJsonParser, ProtoAction, ConstructorAction, // './types/content-type-parser'
|
||||
FastifyError, // '@fastify/error'
|
||||
FastifySchema, FastifySchemaValidationError, FastifySchemaCompiler, FastifySerializerCompiler, // './types/schema'
|
||||
HTTPMethods, RawServerBase, RawRequestDefaultExpression, RawReplyDefaultExpression, RawServerDefault, ContextConfigDefault, RequestBodyDefault, RequestQuerystringDefault, RequestParamsDefault, RequestHeadersDefault, // './types/utils'
|
||||
DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, onCloseAsyncHookHandler, onCloseHookHandler, onErrorAsyncHookHandler, onErrorHookHandler, onReadyAsyncHookHandler, onReadyHookHandler, onListenAsyncHookHandler, onListenHookHandler, onRegisterHookHandler, onRequestAsyncHookHandler, onRequestHookHandler, onResponseAsyncHookHandler, onResponseHookHandler, onRouteHookHandler, onSendAsyncHookHandler, onSendHookHandler, onTimeoutAsyncHookHandler, onTimeoutHookHandler, preHandlerAsyncHookHandler, preHandlerHookHandler, preParsingAsyncHookHandler, preParsingHookHandler, preSerializationAsyncHookHandler, preSerializationHookHandler, preValidationAsyncHookHandler, preValidationHookHandler, onRequestAbortHookHandler, onRequestAbortAsyncHookHandler, preCloseAsyncHookHandler, preCloseHookHandler, // './types/hooks'
|
||||
FastifyServerFactory, FastifyServerFactoryHandler, // './types/serverFactory'
|
||||
FastifyTypeProvider, FastifyTypeProviderDefault, SafePromiseLike, // './types/type-provider'
|
||||
FastifyErrorCodes // './types/errors'
|
||||
}
|
||||
// named export
|
||||
// import { plugin } from 'plugin'
|
||||
// const { plugin } = require('plugin')
|
||||
export const fastify: Fastify
|
||||
// default export
|
||||
// import plugin from 'plugin'
|
||||
export { fastify as default }
|
||||
}
|
||||
|
||||
/**
|
||||
* Fastify factory function for the standard fastify http, https, or http2 server instance.
|
||||
*
|
||||
* The default function utilizes http
|
||||
*
|
||||
* @param opts Fastify server options
|
||||
* @returns Fastify server instance
|
||||
*/
|
||||
declare function fastify<
|
||||
Server extends http2.Http2SecureServer,
|
||||
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
|
||||
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
||||
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
||||
> (opts: fastify.FastifyHttp2SecureOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
||||
|
||||
declare function fastify<
|
||||
Server extends http2.Http2Server,
|
||||
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
|
||||
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
||||
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
||||
> (opts: fastify.FastifyHttp2Options<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
||||
|
||||
declare function fastify<
|
||||
Server extends https.Server,
|
||||
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
|
||||
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
||||
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
||||
> (opts: fastify.FastifyHttpsOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
||||
|
||||
declare function fastify<
|
||||
Server extends http.Server,
|
||||
Request extends RawRequestDefaultExpression<Server> = RawRequestDefaultExpression<Server>,
|
||||
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
|
||||
Logger extends FastifyBaseLogger = FastifyBaseLogger,
|
||||
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault
|
||||
> (opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & SafePromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
|
||||
|
||||
// CJS export
|
||||
// const fastify = require('fastify')
|
||||
export = fastify
|
||||
963
node_modules/fastify/fastify.js
generated
vendored
Normal file
963
node_modules/fastify/fastify.js
generated
vendored
Normal file
@@ -0,0 +1,963 @@
|
||||
'use strict'
|
||||
|
||||
const VERSION = '5.6.0'
|
||||
|
||||
const Avvio = require('avvio')
|
||||
const http = require('node:http')
|
||||
const diagnostics = require('node:diagnostics_channel')
|
||||
let lightMyRequest
|
||||
|
||||
const {
|
||||
kAvvioBoot,
|
||||
kChildren,
|
||||
kServerBindings,
|
||||
kBodyLimit,
|
||||
kSupportedHTTPMethods,
|
||||
kRoutePrefix,
|
||||
kLogLevel,
|
||||
kLogSerializers,
|
||||
kHooks,
|
||||
kSchemaController,
|
||||
kRequestAcceptVersion,
|
||||
kReplySerializerDefault,
|
||||
kContentTypeParser,
|
||||
kReply,
|
||||
kRequest,
|
||||
kFourOhFour,
|
||||
kState,
|
||||
kOptions,
|
||||
kPluginNameChain,
|
||||
kSchemaErrorFormatter,
|
||||
kErrorHandler,
|
||||
kKeepAliveConnections,
|
||||
kChildLoggerFactory,
|
||||
kGenReqId,
|
||||
kErrorHandlerAlreadySet
|
||||
} = require('./lib/symbols.js')
|
||||
|
||||
const { createServer } = require('./lib/server')
|
||||
const Reply = require('./lib/reply')
|
||||
const Request = require('./lib/request')
|
||||
const Context = require('./lib/context.js')
|
||||
const decorator = require('./lib/decorate')
|
||||
const ContentTypeParser = require('./lib/contentTypeParser')
|
||||
const SchemaController = require('./lib/schema-controller')
|
||||
const { Hooks, hookRunnerApplication, supportedHooks } = require('./lib/hooks')
|
||||
const { createChildLogger, defaultChildLoggerFactory, createLogger } = require('./lib/logger-factory')
|
||||
const pluginUtils = require('./lib/pluginUtils')
|
||||
const { getGenReqId, reqIdGenFactory } = require('./lib/reqIdGenFactory')
|
||||
const { buildRouting, validateBodyLimitOption, buildRouterOptions } = require('./lib/route')
|
||||
const build404 = require('./lib/fourOhFour')
|
||||
const getSecuredInitialConfig = require('./lib/initialConfigValidation')
|
||||
const override = require('./lib/pluginOverride')
|
||||
const noopSet = require('./lib/noop-set')
|
||||
const {
|
||||
appendStackTrace,
|
||||
AVVIO_ERRORS_MAP,
|
||||
...errorCodes
|
||||
} = require('./lib/errors')
|
||||
const PonyPromise = require('./lib/promise')
|
||||
|
||||
const { defaultInitOptions } = getSecuredInitialConfig
|
||||
|
||||
const {
|
||||
FST_ERR_ASYNC_CONSTRAINT,
|
||||
FST_ERR_BAD_URL,
|
||||
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE,
|
||||
FST_ERR_OPTIONS_NOT_OBJ,
|
||||
FST_ERR_QSP_NOT_FN,
|
||||
FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN,
|
||||
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ,
|
||||
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR,
|
||||
FST_ERR_INSTANCE_ALREADY_LISTENING,
|
||||
FST_ERR_REOPENED_CLOSE_SERVER,
|
||||
FST_ERR_ROUTE_REWRITE_NOT_STR,
|
||||
FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN,
|
||||
FST_ERR_ERROR_HANDLER_NOT_FN,
|
||||
FST_ERR_ERROR_HANDLER_ALREADY_SET,
|
||||
FST_ERR_ROUTE_METHOD_INVALID
|
||||
} = errorCodes
|
||||
|
||||
const { buildErrorHandler } = require('./lib/error-handler.js')
|
||||
const { FSTWRN004 } = require('./lib/warnings.js')
|
||||
|
||||
const initChannel = diagnostics.channel('fastify.initialization')
|
||||
|
||||
function defaultBuildPrettyMeta (route) {
|
||||
// return a shallow copy of route's sanitized context
|
||||
|
||||
const cleanKeys = {}
|
||||
const allowedProps = ['errorHandler', 'logLevel', 'logSerializers']
|
||||
|
||||
allowedProps.concat(supportedHooks).forEach(k => {
|
||||
cleanKeys[k] = route.store[k]
|
||||
})
|
||||
|
||||
return Object.assign({}, cleanKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('./fastify.js').FastifyServerOptions} options
|
||||
*/
|
||||
function fastify (options) {
|
||||
// Options validations
|
||||
if (options && typeof options !== 'object') {
|
||||
throw new FST_ERR_OPTIONS_NOT_OBJ()
|
||||
} else {
|
||||
// Shallow copy options object to prevent mutations outside of this function
|
||||
options = Object.assign({}, options)
|
||||
}
|
||||
|
||||
if (
|
||||
(options.querystringParser && typeof options.querystringParser !== 'function') ||
|
||||
(
|
||||
options.routerOptions?.querystringParser &&
|
||||
typeof options.routerOptions.querystringParser !== 'function'
|
||||
)
|
||||
) {
|
||||
throw new FST_ERR_QSP_NOT_FN(typeof (options.querystringParser ?? options.routerOptions.querystringParser))
|
||||
}
|
||||
|
||||
if (options.schemaController && options.schemaController.bucket && typeof options.schemaController.bucket !== 'function') {
|
||||
throw new FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN(typeof options.schemaController.bucket)
|
||||
}
|
||||
|
||||
validateBodyLimitOption(options.bodyLimit)
|
||||
|
||||
const requestIdHeader = typeof options.requestIdHeader === 'string' && options.requestIdHeader.length !== 0 ? options.requestIdHeader.toLowerCase() : (options.requestIdHeader === true && 'request-id')
|
||||
const genReqId = reqIdGenFactory(requestIdHeader, options.genReqId)
|
||||
const requestIdLogLabel = options.requestIdLogLabel || 'reqId'
|
||||
const bodyLimit = options.bodyLimit || defaultInitOptions.bodyLimit
|
||||
const disableRequestLogging = options.disableRequestLogging || false
|
||||
|
||||
const ajvOptions = Object.assign({
|
||||
customOptions: {},
|
||||
plugins: []
|
||||
}, options.ajv)
|
||||
const frameworkErrors = options.frameworkErrors
|
||||
|
||||
// Ajv options
|
||||
if (!ajvOptions.customOptions || Object.prototype.toString.call(ajvOptions.customOptions) !== '[object Object]') {
|
||||
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ(typeof ajvOptions.customOptions)
|
||||
}
|
||||
if (!ajvOptions.plugins || !Array.isArray(ajvOptions.plugins)) {
|
||||
throw new FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR(typeof ajvOptions.plugins)
|
||||
}
|
||||
|
||||
// Instance Fastify components
|
||||
|
||||
const { logger, hasLogger } = createLogger(options)
|
||||
|
||||
// Update the options with the fixed values
|
||||
options.connectionTimeout = options.connectionTimeout || defaultInitOptions.connectionTimeout
|
||||
options.keepAliveTimeout = options.keepAliveTimeout || defaultInitOptions.keepAliveTimeout
|
||||
options.maxRequestsPerSocket = options.maxRequestsPerSocket || defaultInitOptions.maxRequestsPerSocket
|
||||
options.requestTimeout = options.requestTimeout || defaultInitOptions.requestTimeout
|
||||
options.logger = logger
|
||||
options.requestIdHeader = requestIdHeader
|
||||
options.requestIdLogLabel = requestIdLogLabel
|
||||
options.disableRequestLogging = disableRequestLogging
|
||||
options.ajv = ajvOptions
|
||||
options.clientErrorHandler = options.clientErrorHandler || defaultClientErrorHandler
|
||||
options.allowErrorHandlerOverride = options.allowErrorHandlerOverride ?? defaultInitOptions.allowErrorHandlerOverride
|
||||
|
||||
const initialConfig = getSecuredInitialConfig(options)
|
||||
|
||||
// exposeHeadRoutes have its default set from the validator
|
||||
options.exposeHeadRoutes = initialConfig.exposeHeadRoutes
|
||||
|
||||
options.routerOptions = buildRouterOptions(options, {
|
||||
defaultRoute,
|
||||
onBadUrl,
|
||||
ignoreTrailingSlash: defaultInitOptions.ignoreTrailingSlash,
|
||||
ignoreDuplicateSlashes: defaultInitOptions.ignoreDuplicateSlashes,
|
||||
maxParamLength: defaultInitOptions.maxParamLength,
|
||||
allowUnsafeRegex: defaultInitOptions.allowUnsafeRegex,
|
||||
buildPrettyMeta: defaultBuildPrettyMeta,
|
||||
useSemicolonDelimiter: defaultInitOptions.useSemicolonDelimiter
|
||||
})
|
||||
|
||||
// Default router
|
||||
const router = buildRouting({
|
||||
config: options.routerOptions
|
||||
})
|
||||
|
||||
// 404 router, used for handling encapsulated 404 handlers
|
||||
const fourOhFour = build404(options)
|
||||
|
||||
// HTTP server and its handler
|
||||
const httpHandler = wrapRouting(router, options)
|
||||
|
||||
// we need to set this before calling createServer
|
||||
options.http2SessionTimeout = initialConfig.http2SessionTimeout
|
||||
const { server, listen } = createServer(options, httpHandler)
|
||||
|
||||
const serverHasCloseAllConnections = typeof server.closeAllConnections === 'function'
|
||||
const serverHasCloseIdleConnections = typeof server.closeIdleConnections === 'function'
|
||||
|
||||
let forceCloseConnections = options.forceCloseConnections
|
||||
if (forceCloseConnections === 'idle' && !serverHasCloseIdleConnections) {
|
||||
throw new FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE()
|
||||
} else if (typeof forceCloseConnections !== 'boolean') {
|
||||
/* istanbul ignore next: only one branch can be valid in a given Node.js version */
|
||||
forceCloseConnections = serverHasCloseIdleConnections ? 'idle' : false
|
||||
}
|
||||
|
||||
const keepAliveConnections = !serverHasCloseAllConnections && forceCloseConnections === true ? new Set() : noopSet()
|
||||
|
||||
const setupResponseListeners = Reply.setupResponseListeners
|
||||
const schemaController = SchemaController.buildSchemaController(null, options.schemaController)
|
||||
|
||||
// Public API
|
||||
const fastify = {
|
||||
// Fastify internals
|
||||
[kState]: {
|
||||
listening: false,
|
||||
closing: false,
|
||||
started: false,
|
||||
ready: false,
|
||||
booting: false,
|
||||
aborted: false,
|
||||
readyResolver: null
|
||||
},
|
||||
[kKeepAliveConnections]: keepAliveConnections,
|
||||
[kSupportedHTTPMethods]: {
|
||||
bodyless: new Set([
|
||||
// Standard
|
||||
'GET',
|
||||
'HEAD',
|
||||
'TRACE'
|
||||
]),
|
||||
bodywith: new Set([
|
||||
// Standard
|
||||
'DELETE',
|
||||
'OPTIONS',
|
||||
'PATCH',
|
||||
'PUT',
|
||||
'POST'
|
||||
])
|
||||
},
|
||||
[kOptions]: options,
|
||||
[kChildren]: [],
|
||||
[kServerBindings]: [],
|
||||
[kBodyLimit]: bodyLimit,
|
||||
[kRoutePrefix]: '',
|
||||
[kLogLevel]: '',
|
||||
[kLogSerializers]: null,
|
||||
[kHooks]: new Hooks(),
|
||||
[kSchemaController]: schemaController,
|
||||
[kSchemaErrorFormatter]: null,
|
||||
[kErrorHandler]: buildErrorHandler(),
|
||||
[kErrorHandlerAlreadySet]: false,
|
||||
[kChildLoggerFactory]: defaultChildLoggerFactory,
|
||||
[kReplySerializerDefault]: null,
|
||||
[kContentTypeParser]: new ContentTypeParser(
|
||||
bodyLimit,
|
||||
(options.onProtoPoisoning || defaultInitOptions.onProtoPoisoning),
|
||||
(options.onConstructorPoisoning || defaultInitOptions.onConstructorPoisoning)
|
||||
),
|
||||
[kReply]: Reply.buildReply(Reply),
|
||||
[kRequest]: Request.buildRequest(Request, options.trustProxy),
|
||||
[kFourOhFour]: fourOhFour,
|
||||
[pluginUtils.kRegisteredPlugins]: [],
|
||||
[kPluginNameChain]: ['fastify'],
|
||||
[kAvvioBoot]: null,
|
||||
[kGenReqId]: genReqId,
|
||||
// routing method
|
||||
routing: httpHandler,
|
||||
// routes shorthand methods
|
||||
delete: function _delete (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'DELETE', url, options, handler })
|
||||
},
|
||||
get: function _get (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'GET', url, options, handler })
|
||||
},
|
||||
head: function _head (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'HEAD', url, options, handler })
|
||||
},
|
||||
trace: function _trace (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'TRACE', url, options, handler })
|
||||
},
|
||||
patch: function _patch (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'PATCH', url, options, handler })
|
||||
},
|
||||
post: function _post (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'POST', url, options, handler })
|
||||
},
|
||||
put: function _put (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'PUT', url, options, handler })
|
||||
},
|
||||
options: function _options (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: 'OPTIONS', url, options, handler })
|
||||
},
|
||||
all: function _all (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method: this.supportedMethods, url, options, handler })
|
||||
},
|
||||
// extended route
|
||||
route: function _route (options) {
|
||||
// we need the fastify object that we are producing so we apply a lazy loading of the function,
|
||||
// otherwise we should bind it after the declaration
|
||||
return router.route.call(this, { options })
|
||||
},
|
||||
hasRoute: function _route (options) {
|
||||
return router.hasRoute.call(this, { options })
|
||||
},
|
||||
findRoute: function _findRoute (options) {
|
||||
return router.findRoute(options)
|
||||
},
|
||||
// expose logger instance
|
||||
log: logger,
|
||||
// type provider
|
||||
withTypeProvider,
|
||||
// hooks
|
||||
addHook,
|
||||
// schemas
|
||||
addSchema,
|
||||
getSchema: schemaController.getSchema.bind(schemaController),
|
||||
getSchemas: schemaController.getSchemas.bind(schemaController),
|
||||
setValidatorCompiler,
|
||||
setSerializerCompiler,
|
||||
setSchemaController,
|
||||
setReplySerializer,
|
||||
setSchemaErrorFormatter,
|
||||
// set generated request id
|
||||
setGenReqId,
|
||||
// custom parsers
|
||||
addContentTypeParser: ContentTypeParser.helpers.addContentTypeParser,
|
||||
hasContentTypeParser: ContentTypeParser.helpers.hasContentTypeParser,
|
||||
getDefaultJsonParser: ContentTypeParser.defaultParsers.getDefaultJsonParser,
|
||||
defaultTextParser: ContentTypeParser.defaultParsers.defaultTextParser,
|
||||
removeContentTypeParser: ContentTypeParser.helpers.removeContentTypeParser,
|
||||
removeAllContentTypeParsers: ContentTypeParser.helpers.removeAllContentTypeParsers,
|
||||
// Fastify architecture methods (initialized by Avvio)
|
||||
register: null,
|
||||
after: null,
|
||||
ready: null,
|
||||
onClose: null,
|
||||
close: null,
|
||||
printPlugins: null,
|
||||
hasPlugin: function (name) {
|
||||
return this[pluginUtils.kRegisteredPlugins].includes(name) || this[kPluginNameChain].includes(name)
|
||||
},
|
||||
// http server
|
||||
listen,
|
||||
server,
|
||||
addresses: function () {
|
||||
/* istanbul ignore next */
|
||||
const binded = this[kServerBindings].map(b => b.address())
|
||||
binded.push(this.server.address())
|
||||
return binded.filter(adr => adr)
|
||||
},
|
||||
// extend fastify objects
|
||||
decorate: decorator.add,
|
||||
hasDecorator: decorator.exist,
|
||||
decorateReply: decorator.decorateReply,
|
||||
decorateRequest: decorator.decorateRequest,
|
||||
hasRequestDecorator: decorator.existRequest,
|
||||
hasReplyDecorator: decorator.existReply,
|
||||
getDecorator: decorator.getInstanceDecorator,
|
||||
addHttpMethod,
|
||||
// fake http injection
|
||||
inject,
|
||||
// pretty print of the registered routes
|
||||
printRoutes,
|
||||
// custom error handling
|
||||
setNotFoundHandler,
|
||||
setErrorHandler,
|
||||
// child logger
|
||||
setChildLoggerFactory,
|
||||
// Set fastify initial configuration options read-only object
|
||||
initialConfig,
|
||||
// constraint strategies
|
||||
addConstraintStrategy: router.addConstraintStrategy.bind(router),
|
||||
hasConstraintStrategy: router.hasConstraintStrategy.bind(router)
|
||||
}
|
||||
|
||||
Object.defineProperties(fastify, {
|
||||
listeningOrigin: {
|
||||
get () {
|
||||
const address = this.addresses().slice(-1).pop()
|
||||
/* ignore if windows: unix socket is not testable on Windows platform */
|
||||
/* c8 ignore next 3 */
|
||||
if (typeof address === 'string') {
|
||||
return address
|
||||
}
|
||||
const host = address.family === 'IPv6' ? `[${address.address}]` : address.address
|
||||
return `${this[kOptions].https ? 'https' : 'http'}://${host}:${address.port}`
|
||||
}
|
||||
},
|
||||
pluginName: {
|
||||
configurable: true,
|
||||
get () {
|
||||
if (this[kPluginNameChain].length > 1) {
|
||||
return this[kPluginNameChain].join(' -> ')
|
||||
}
|
||||
return this[kPluginNameChain][0]
|
||||
}
|
||||
},
|
||||
prefix: {
|
||||
configurable: true,
|
||||
get () { return this[kRoutePrefix] }
|
||||
},
|
||||
validatorCompiler: {
|
||||
configurable: true,
|
||||
get () { return this[kSchemaController].getValidatorCompiler() }
|
||||
},
|
||||
serializerCompiler: {
|
||||
configurable: true,
|
||||
get () { return this[kSchemaController].getSerializerCompiler() }
|
||||
},
|
||||
childLoggerFactory: {
|
||||
configurable: true,
|
||||
get () { return this[kChildLoggerFactory] }
|
||||
},
|
||||
version: {
|
||||
configurable: true,
|
||||
get () { return VERSION }
|
||||
},
|
||||
errorHandler: {
|
||||
configurable: true,
|
||||
get () {
|
||||
return this[kErrorHandler].func
|
||||
}
|
||||
},
|
||||
genReqId: {
|
||||
configurable: true,
|
||||
get () { return this[kGenReqId] }
|
||||
},
|
||||
supportedMethods: {
|
||||
configurable: false,
|
||||
get () {
|
||||
return [
|
||||
...this[kSupportedHTTPMethods].bodyless,
|
||||
...this[kSupportedHTTPMethods].bodywith
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (options.schemaErrorFormatter) {
|
||||
validateSchemaErrorFormatter(options.schemaErrorFormatter)
|
||||
fastify[kSchemaErrorFormatter] = options.schemaErrorFormatter.bind(fastify)
|
||||
}
|
||||
|
||||
// Install and configure Avvio
|
||||
// Avvio will update the following Fastify methods:
|
||||
// - register
|
||||
// - after
|
||||
// - ready
|
||||
// - onClose
|
||||
// - close
|
||||
|
||||
const avvioPluginTimeout = Number(options.pluginTimeout)
|
||||
const avvio = Avvio(fastify, {
|
||||
autostart: false,
|
||||
timeout: isNaN(avvioPluginTimeout) === false ? avvioPluginTimeout : defaultInitOptions.pluginTimeout,
|
||||
expose: {
|
||||
use: 'register'
|
||||
}
|
||||
})
|
||||
// Override to allow the plugin encapsulation
|
||||
avvio.override = override
|
||||
avvio.on('start', () => (fastify[kState].started = true))
|
||||
fastify[kAvvioBoot] = fastify.ready // the avvio ready function
|
||||
fastify.ready = ready // overwrite the avvio ready function
|
||||
fastify.printPlugins = avvio.prettyPrint.bind(avvio)
|
||||
|
||||
// cache the closing value, since we are checking it in an hot path
|
||||
avvio.once('preReady', () => {
|
||||
fastify.onClose((instance, done) => {
|
||||
fastify[kState].closing = true
|
||||
router.closeRoutes()
|
||||
|
||||
hookRunnerApplication('preClose', fastify[kAvvioBoot], fastify, function () {
|
||||
if (fastify[kState].listening) {
|
||||
/* istanbul ignore next: Cannot test this without Node.js core support */
|
||||
if (forceCloseConnections === 'idle') {
|
||||
// Not needed in Node 19
|
||||
instance.server.closeIdleConnections()
|
||||
/* istanbul ignore next: Cannot test this without Node.js core support */
|
||||
} else if (serverHasCloseAllConnections && forceCloseConnections) {
|
||||
instance.server.closeAllConnections()
|
||||
} else if (forceCloseConnections === true) {
|
||||
for (const conn of fastify[kKeepAliveConnections]) {
|
||||
// We must invoke the destroy method instead of merely unreffing
|
||||
// the sockets. If we only unref, then the callback passed to
|
||||
// `fastify.close` will never be invoked; nor will any of the
|
||||
// registered `onClose` hooks.
|
||||
conn.destroy()
|
||||
fastify[kKeepAliveConnections].delete(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No new TCP connections are accepted.
|
||||
// We must call close on the server even if we are not listening
|
||||
// otherwise memory will be leaked.
|
||||
// https://github.com/nodejs/node/issues/48604
|
||||
if (!options.serverFactory || fastify[kState].listening) {
|
||||
instance.server.close(function (err) {
|
||||
/* c8 ignore next 6 */
|
||||
if (err && err.code !== 'ERR_SERVER_NOT_RUNNING') {
|
||||
done(null)
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
process.nextTick(done, null)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Create bad URL context
|
||||
const onBadUrlContext = new Context({
|
||||
server: fastify,
|
||||
config: {}
|
||||
})
|
||||
|
||||
// Set the default 404 handler
|
||||
fastify.setNotFoundHandler()
|
||||
fourOhFour.arrange404(fastify)
|
||||
|
||||
router.setup(options, {
|
||||
avvio,
|
||||
fourOhFour,
|
||||
logger,
|
||||
hasLogger,
|
||||
setupResponseListeners,
|
||||
throwIfAlreadyStarted,
|
||||
keepAliveConnections
|
||||
})
|
||||
|
||||
// Delay configuring clientError handler so that it can access fastify state.
|
||||
server.on('clientError', options.clientErrorHandler.bind(fastify))
|
||||
|
||||
if (initChannel.hasSubscribers) {
|
||||
initChannel.publish({ fastify })
|
||||
}
|
||||
|
||||
// Older nodejs versions may not have asyncDispose
|
||||
if ('asyncDispose' in Symbol) {
|
||||
fastify[Symbol.asyncDispose] = function dispose () {
|
||||
return fastify.close()
|
||||
}
|
||||
}
|
||||
|
||||
return fastify
|
||||
|
||||
function throwIfAlreadyStarted (msg) {
|
||||
if (fastify[kState].started) throw new FST_ERR_INSTANCE_ALREADY_LISTENING(msg)
|
||||
}
|
||||
|
||||
// HTTP injection handling
|
||||
// If the server is not ready yet, this
|
||||
// utility will automatically force it.
|
||||
function inject (opts, cb) {
|
||||
// lightMyRequest is dynamically loaded as it seems very expensive
|
||||
// because of Ajv
|
||||
if (lightMyRequest === undefined) {
|
||||
lightMyRequest = require('light-my-request')
|
||||
}
|
||||
|
||||
if (fastify[kState].started) {
|
||||
if (fastify[kState].closing) {
|
||||
// Force to return an error
|
||||
const error = new FST_ERR_REOPENED_CLOSE_SERVER()
|
||||
if (cb) {
|
||||
cb(error)
|
||||
return
|
||||
} else {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
return lightMyRequest(httpHandler, opts, cb)
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
this.ready(err => {
|
||||
if (err) cb(err, null)
|
||||
else lightMyRequest(httpHandler, opts, cb)
|
||||
})
|
||||
} else {
|
||||
return lightMyRequest((req, res) => {
|
||||
this.ready(function (err) {
|
||||
if (err) {
|
||||
res.emit('error', err)
|
||||
return
|
||||
}
|
||||
httpHandler(req, res)
|
||||
})
|
||||
}, opts)
|
||||
}
|
||||
}
|
||||
|
||||
function ready (cb) {
|
||||
if (this[kState].readyResolver !== null) {
|
||||
if (cb != null) {
|
||||
this[kState].readyResolver.promise.then(() => cb(null, fastify), cb)
|
||||
return
|
||||
}
|
||||
|
||||
return this[kState].readyResolver.promise
|
||||
}
|
||||
|
||||
// run the hooks after returning the promise
|
||||
process.nextTick(runHooks)
|
||||
|
||||
// Create a promise no matter what
|
||||
// It will work as a barrier for all the .ready() calls (ensuring single hook execution)
|
||||
// as well as a flow control mechanism to chain cbs and further
|
||||
// promises
|
||||
this[kState].readyResolver = PonyPromise.withResolvers()
|
||||
|
||||
if (!cb) {
|
||||
return this[kState].readyResolver.promise
|
||||
} else {
|
||||
this[kState].readyResolver.promise.then(() => cb(null, fastify), cb)
|
||||
}
|
||||
|
||||
function runHooks () {
|
||||
// start loading
|
||||
fastify[kAvvioBoot]((err, done) => {
|
||||
if (err || fastify[kState].started || fastify[kState].ready || fastify[kState].booting) {
|
||||
manageErr(err)
|
||||
} else {
|
||||
fastify[kState].booting = true
|
||||
hookRunnerApplication('onReady', fastify[kAvvioBoot], fastify, manageErr)
|
||||
}
|
||||
done()
|
||||
})
|
||||
}
|
||||
|
||||
function manageErr (err) {
|
||||
// If the error comes out of Avvio's Error codes
|
||||
// We create a make and preserve the previous error
|
||||
// as cause
|
||||
err = err != null && AVVIO_ERRORS_MAP[err.code] != null
|
||||
? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))
|
||||
: err
|
||||
|
||||
if (err) {
|
||||
return fastify[kState].readyResolver.reject(err)
|
||||
}
|
||||
|
||||
fastify[kState].readyResolver.resolve(fastify)
|
||||
fastify[kState].booting = false
|
||||
fastify[kState].ready = true
|
||||
fastify[kState].readyResolver = null
|
||||
}
|
||||
}
|
||||
|
||||
// Used exclusively in TypeScript contexts to enable auto type inference from JSON schema.
|
||||
function withTypeProvider () {
|
||||
return this
|
||||
}
|
||||
|
||||
// wrapper that we expose to the user for hooks handling
|
||||
function addHook (name, fn) {
|
||||
throwIfAlreadyStarted('Cannot call "addHook"!')
|
||||
|
||||
if (fn == null) {
|
||||
throw new errorCodes.FST_ERR_HOOK_INVALID_HANDLER(name, fn)
|
||||
}
|
||||
|
||||
if (name === 'onSend' || name === 'preSerialization' || name === 'onError' || name === 'preParsing') {
|
||||
if (fn.constructor.name === 'AsyncFunction' && fn.length === 4) {
|
||||
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else if (name === 'onReady' || name === 'onListen') {
|
||||
if (fn.constructor.name === 'AsyncFunction' && fn.length !== 0) {
|
||||
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else if (name === 'onRequestAbort') {
|
||||
if (fn.constructor.name === 'AsyncFunction' && fn.length !== 1) {
|
||||
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else {
|
||||
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
|
||||
throw new errorCodes.FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'onClose') {
|
||||
this.onClose(fn.bind(this))
|
||||
} else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') {
|
||||
this[kHooks].add(name, fn)
|
||||
} else {
|
||||
this.after((err, done) => {
|
||||
try {
|
||||
_addHook.call(this, name, fn)
|
||||
done(err)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return this
|
||||
|
||||
function _addHook (name, fn) {
|
||||
this[kHooks].add(name, fn)
|
||||
this[kChildren].forEach(child => _addHook.call(child, name, fn))
|
||||
}
|
||||
}
|
||||
|
||||
// wrapper that we expose to the user for schemas handling
|
||||
function addSchema (schema) {
|
||||
throwIfAlreadyStarted('Cannot call "addSchema"!')
|
||||
this[kSchemaController].add(schema)
|
||||
this[kChildren].forEach(child => child.addSchema(schema))
|
||||
return this
|
||||
}
|
||||
|
||||
function defaultClientErrorHandler (err, socket) {
|
||||
// In case of a connection reset, the socket has been destroyed and there is nothing that needs to be done.
|
||||
// https://nodejs.org/api/http.html#http_event_clienterror
|
||||
if (err.code === 'ECONNRESET' || socket.destroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
let body, errorCode, errorStatus, errorLabel
|
||||
|
||||
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
|
||||
errorCode = '408'
|
||||
errorStatus = http.STATUS_CODES[errorCode]
|
||||
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
|
||||
errorLabel = 'timeout'
|
||||
} else if (err.code === 'HPE_HEADER_OVERFLOW') {
|
||||
errorCode = '431'
|
||||
errorStatus = http.STATUS_CODES[errorCode]
|
||||
body = `{"error":"${errorStatus}","message":"Exceeded maximum allowed HTTP header size","statusCode":431}`
|
||||
errorLabel = 'header_overflow'
|
||||
} else {
|
||||
errorCode = '400'
|
||||
errorStatus = http.STATUS_CODES[errorCode]
|
||||
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
|
||||
errorLabel = 'error'
|
||||
}
|
||||
|
||||
// Most devs do not know what to do with this error.
|
||||
// In the vast majority of cases, it's a network error and/or some
|
||||
// config issue on the load balancer side.
|
||||
this.log.trace({ err }, `client ${errorLabel}`)
|
||||
// Copying standard node behavior
|
||||
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
|
||||
|
||||
// If the socket is not writable, there is no reason to try to send data.
|
||||
if (socket.writable) {
|
||||
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
|
||||
}
|
||||
socket.destroy(err)
|
||||
}
|
||||
|
||||
// If the router does not match any route, every request will land here
|
||||
// req and res are Node.js core objects
|
||||
function defaultRoute (req, res) {
|
||||
if (req.headers['accept-version'] !== undefined) {
|
||||
// we remove the accept-version header for performance result
|
||||
// because we do not want to go through the constraint checking
|
||||
// the usage of symbol here to prevent any collision on custom header name
|
||||
req.headers[kRequestAcceptVersion] = req.headers['accept-version']
|
||||
req.headers['accept-version'] = undefined
|
||||
}
|
||||
fourOhFour.router.lookup(req, res)
|
||||
}
|
||||
|
||||
function onBadUrl (path, req, res) {
|
||||
if (frameworkErrors) {
|
||||
const id = getGenReqId(onBadUrlContext.server, req)
|
||||
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
||||
|
||||
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
||||
const reply = new Reply(res, request, childLogger)
|
||||
|
||||
if (disableRequestLogging === false) {
|
||||
childLogger.info({ req: request }, 'incoming request')
|
||||
}
|
||||
|
||||
return frameworkErrors(new FST_ERR_BAD_URL(path), request, reply)
|
||||
}
|
||||
const body = `{"error":"Bad Request","code":"FST_ERR_BAD_URL","message":"'${path}' is not a valid url component","statusCode":400}`
|
||||
res.writeHead(400, {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body.length
|
||||
})
|
||||
res.end(body)
|
||||
}
|
||||
|
||||
function buildAsyncConstraintCallback (isAsync, req, res) {
|
||||
if (isAsync === false) return undefined
|
||||
return function onAsyncConstraintError (err) {
|
||||
if (err) {
|
||||
if (frameworkErrors) {
|
||||
const id = getGenReqId(onBadUrlContext.server, req)
|
||||
const childLogger = createChildLogger(onBadUrlContext, logger, req, id)
|
||||
|
||||
const request = new Request(id, null, req, null, childLogger, onBadUrlContext)
|
||||
const reply = new Reply(res, request, childLogger)
|
||||
|
||||
if (disableRequestLogging === false) {
|
||||
childLogger.info({ req: request }, 'incoming request')
|
||||
}
|
||||
|
||||
return frameworkErrors(new FST_ERR_ASYNC_CONSTRAINT(), request, reply)
|
||||
}
|
||||
const body = '{"error":"Internal Server Error","message":"Unexpected error from async constraint","statusCode":500}'
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': body.length
|
||||
})
|
||||
res.end(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setNotFoundHandler (opts, handler) {
|
||||
throwIfAlreadyStarted('Cannot call "setNotFoundHandler"!')
|
||||
|
||||
fourOhFour.setNotFoundHandler.call(this, opts, handler, avvio, router.routeHandler)
|
||||
return this
|
||||
}
|
||||
|
||||
function setValidatorCompiler (validatorCompiler) {
|
||||
throwIfAlreadyStarted('Cannot call "setValidatorCompiler"!')
|
||||
this[kSchemaController].setValidatorCompiler(validatorCompiler)
|
||||
return this
|
||||
}
|
||||
|
||||
function setSchemaErrorFormatter (errorFormatter) {
|
||||
throwIfAlreadyStarted('Cannot call "setSchemaErrorFormatter"!')
|
||||
validateSchemaErrorFormatter(errorFormatter)
|
||||
this[kSchemaErrorFormatter] = errorFormatter.bind(this)
|
||||
return this
|
||||
}
|
||||
|
||||
function setSerializerCompiler (serializerCompiler) {
|
||||
throwIfAlreadyStarted('Cannot call "setSerializerCompiler"!')
|
||||
this[kSchemaController].setSerializerCompiler(serializerCompiler)
|
||||
return this
|
||||
}
|
||||
|
||||
function setSchemaController (schemaControllerOpts) {
|
||||
throwIfAlreadyStarted('Cannot call "setSchemaController"!')
|
||||
const old = this[kSchemaController]
|
||||
const schemaController = SchemaController.buildSchemaController(old, Object.assign({}, old.opts, schemaControllerOpts))
|
||||
this[kSchemaController] = schemaController
|
||||
this.getSchema = schemaController.getSchema.bind(schemaController)
|
||||
this.getSchemas = schemaController.getSchemas.bind(schemaController)
|
||||
return this
|
||||
}
|
||||
|
||||
function setReplySerializer (replySerializer) {
|
||||
throwIfAlreadyStarted('Cannot call "setReplySerializer"!')
|
||||
|
||||
this[kReplySerializerDefault] = replySerializer
|
||||
return this
|
||||
}
|
||||
|
||||
// wrapper that we expose to the user for configure the custom error handler
|
||||
function setErrorHandler (func) {
|
||||
throwIfAlreadyStarted('Cannot call "setErrorHandler"!')
|
||||
|
||||
if (typeof func !== 'function') {
|
||||
throw new FST_ERR_ERROR_HANDLER_NOT_FN()
|
||||
}
|
||||
|
||||
if (!options.allowErrorHandlerOverride && this[kErrorHandlerAlreadySet]) {
|
||||
throw new FST_ERR_ERROR_HANDLER_ALREADY_SET()
|
||||
} else if (this[kErrorHandlerAlreadySet]) {
|
||||
FSTWRN004("To disable this behavior, set 'allowErrorHandlerOverride' to false or ignore this message. For more information, visit: https://fastify.dev/docs/latest/Reference/Server/#allowerrorhandleroverride")
|
||||
}
|
||||
|
||||
this[kErrorHandlerAlreadySet] = true
|
||||
this[kErrorHandler] = buildErrorHandler(this[kErrorHandler], func.bind(this))
|
||||
return this
|
||||
}
|
||||
|
||||
function setChildLoggerFactory (factory) {
|
||||
throwIfAlreadyStarted('Cannot call "setChildLoggerFactory"!')
|
||||
|
||||
this[kChildLoggerFactory] = factory
|
||||
return this
|
||||
}
|
||||
|
||||
function printRoutes (opts = {}) {
|
||||
// includeHooks:true - shortcut to include all supported hooks exported by fastify.Hooks
|
||||
opts.includeMeta = opts.includeHooks ? opts.includeMeta ? supportedHooks.concat(opts.includeMeta) : supportedHooks : opts.includeMeta
|
||||
return router.printRoutes(opts)
|
||||
}
|
||||
|
||||
function wrapRouting (router, { rewriteUrl, logger }) {
|
||||
let isAsync
|
||||
return function preRouting (req, res) {
|
||||
// only call isAsyncConstraint once
|
||||
if (isAsync === undefined) isAsync = router.isAsyncConstraint()
|
||||
if (rewriteUrl) {
|
||||
req.originalUrl = req.url
|
||||
const url = rewriteUrl.call(fastify, req)
|
||||
if (typeof url === 'string') {
|
||||
req.url = url
|
||||
} else {
|
||||
const err = new FST_ERR_ROUTE_REWRITE_NOT_STR(req.url, typeof url)
|
||||
req.destroy(err)
|
||||
}
|
||||
}
|
||||
router.routing(req, res, buildAsyncConstraintCallback(isAsync, req, res))
|
||||
}
|
||||
}
|
||||
|
||||
function setGenReqId (func) {
|
||||
throwIfAlreadyStarted('Cannot call "setGenReqId"!')
|
||||
|
||||
this[kGenReqId] = reqIdGenFactory(this[kOptions].requestIdHeader, func)
|
||||
return this
|
||||
}
|
||||
|
||||
function addHttpMethod (method, { hasBody = false } = {}) {
|
||||
if (typeof method !== 'string' || http.METHODS.indexOf(method) === -1) {
|
||||
throw new FST_ERR_ROUTE_METHOD_INVALID()
|
||||
}
|
||||
|
||||
if (hasBody === true) {
|
||||
this[kSupportedHTTPMethods].bodywith.add(method)
|
||||
this[kSupportedHTTPMethods].bodyless.delete(method)
|
||||
} else {
|
||||
this[kSupportedHTTPMethods].bodywith.delete(method)
|
||||
this[kSupportedHTTPMethods].bodyless.add(method)
|
||||
}
|
||||
|
||||
const _method = method.toLowerCase()
|
||||
if (!this.hasDecorator(_method)) {
|
||||
this.decorate(_method, function (url, options, handler) {
|
||||
return router.prepareRoute.call(this, { method, url, options, handler })
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
function validateSchemaErrorFormatter (schemaErrorFormatter) {
|
||||
if (typeof schemaErrorFormatter !== 'function') {
|
||||
throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN(typeof schemaErrorFormatter)
|
||||
} else if (schemaErrorFormatter.constructor.name === 'AsyncFunction') {
|
||||
throw new FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN('AsyncFunction')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* These export configurations enable JS and TS developers
|
||||
* to consume fastify in whatever way best suits their needs.
|
||||
* Some examples of supported import syntax includes:
|
||||
* - `const fastify = require('fastify')`
|
||||
* - `const { fastify } = require('fastify')`
|
||||
* - `import * as Fastify from 'fastify'`
|
||||
* - `import { fastify, TSC_definition } from 'fastify'`
|
||||
* - `import fastify from 'fastify'`
|
||||
* - `import fastify, { TSC_definition } from 'fastify'`
|
||||
*/
|
||||
module.exports = fastify
|
||||
module.exports.errorCodes = errorCodes
|
||||
module.exports.fastify = fastify
|
||||
module.exports.default = fastify
|
||||
29
node_modules/fastify/integration/server.js
generated
vendored
Normal file
29
node_modules/fastify/integration/server.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
'use strict'
|
||||
|
||||
const Fastify = require('../fastify')
|
||||
|
||||
const fastify = Fastify()
|
||||
|
||||
fastify.listen({
|
||||
host: '::',
|
||||
port: 3000
|
||||
})
|
||||
|
||||
fastify.get('/', async function (request, reply) {
|
||||
reply.code(200).send({ data: 'home page' })
|
||||
})
|
||||
|
||||
fastify.post('/post/:id', async function (request, reply) {
|
||||
const { id } = request.params
|
||||
reply.code(201).send({ data: `${id}` })
|
||||
})
|
||||
|
||||
fastify.put('/put/:id', async function (request, reply) {
|
||||
const { id } = request.params
|
||||
reply.code(200).send({ data: `${id}` })
|
||||
})
|
||||
|
||||
fastify.delete('/delete/:id', async function (request, reply) {
|
||||
const { id } = request.params
|
||||
reply.code(204).send({ data: `${id}` })
|
||||
})
|
||||
23
node_modules/fastify/integration/test.sh
generated
vendored
Normal file
23
node_modules/fastify/integration/test.sh
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
NUMBER=$RANDOM
|
||||
curl -i -X GET -H 'Content-Type: application/json' localhost:3000/ > GET
|
||||
if [[ ! $(cat GET | head -1| cut -f2 -d" ") == "200" || ! $(cat GET | tail -1| cut -f4 -d"\"") == "home page" ]] ; then
|
||||
exit 1
|
||||
fi;
|
||||
curl -i -X POST -H 'Content-Type: application/json' localhost:3000/post/$NUMBER --data {} > POST
|
||||
if [[ ! $(cat POST | head -1| cut -f2 -d" ") == "201" || ! $(cat POST | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
|
||||
exit 1
|
||||
fi;
|
||||
curl -i -X PUT -H 'Content-Type: application/json' localhost:3000/put/$NUMBER --data {} > PUT
|
||||
if [[ ! $(cat PUT | head -1| cut -f2 -d" ") == "200" || ! $(cat PUT | tail -1| cut -f4 -d"\"") == $(echo $NUMBER) ]]; then
|
||||
exit 1
|
||||
fi;
|
||||
curl -i -X DELETE -H 'Content-Type: application/json' localhost:3000/delete/$NUMBER --data {} > DELETE
|
||||
if [[ ! $(cat DELETE | head -1| cut -f2 -d" ") == "204" ]]; then
|
||||
exit 1
|
||||
fi;
|
||||
|
||||
rm -f GET POST PUT DELETE
|
||||
1272
node_modules/fastify/lib/configValidator.js
generated
vendored
Normal file
1272
node_modules/fastify/lib/configValidator.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
402
node_modules/fastify/lib/contentTypeParser.js
generated
vendored
Normal file
402
node_modules/fastify/lib/contentTypeParser.js
generated
vendored
Normal file
@@ -0,0 +1,402 @@
|
||||
'use strict'
|
||||
|
||||
const { AsyncResource } = require('node:async_hooks')
|
||||
const { FifoMap: Fifo } = require('toad-cache')
|
||||
const { parse: secureJsonParse } = require('secure-json-parse')
|
||||
const {
|
||||
kDefaultJsonParse,
|
||||
kContentTypeParser,
|
||||
kBodyLimit,
|
||||
kRequestPayloadStream,
|
||||
kState,
|
||||
kTestInternals,
|
||||
kReplyIsError,
|
||||
kRouteContext
|
||||
} = require('./symbols')
|
||||
|
||||
const {
|
||||
FST_ERR_CTP_INVALID_TYPE,
|
||||
FST_ERR_CTP_EMPTY_TYPE,
|
||||
FST_ERR_CTP_ALREADY_PRESENT,
|
||||
FST_ERR_CTP_INVALID_HANDLER,
|
||||
FST_ERR_CTP_INVALID_PARSE_TYPE,
|
||||
FST_ERR_CTP_BODY_TOO_LARGE,
|
||||
FST_ERR_CTP_INVALID_MEDIA_TYPE,
|
||||
FST_ERR_CTP_INVALID_CONTENT_LENGTH,
|
||||
FST_ERR_CTP_EMPTY_JSON_BODY,
|
||||
FST_ERR_CTP_INSTANCE_ALREADY_STARTED,
|
||||
FST_ERR_CTP_INVALID_JSON_BODY
|
||||
} = require('./errors')
|
||||
const { FSTSEC001 } = require('./warnings')
|
||||
|
||||
function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
|
||||
this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
|
||||
// using a map instead of a plain object to avoid prototype hijack attacks
|
||||
this.customParsers = new Map()
|
||||
this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
|
||||
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
|
||||
this.parserList = ['application/json', 'text/plain']
|
||||
this.parserRegExpList = []
|
||||
this.cache = new Fifo(100)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
||||
const contentTypeIsString = typeof contentType === 'string'
|
||||
|
||||
if (contentTypeIsString) {
|
||||
contentType = contentType.trim().toLowerCase()
|
||||
if (contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE()
|
||||
} else if (!(contentType instanceof RegExp)) {
|
||||
throw new FST_ERR_CTP_INVALID_TYPE()
|
||||
}
|
||||
|
||||
if (typeof parserFn !== 'function') {
|
||||
throw new FST_ERR_CTP_INVALID_HANDLER()
|
||||
}
|
||||
|
||||
if (this.existingParser(contentType)) {
|
||||
throw new FST_ERR_CTP_ALREADY_PRESENT(contentType)
|
||||
}
|
||||
|
||||
if (opts.parseAs !== undefined) {
|
||||
if (opts.parseAs !== 'string' && opts.parseAs !== 'buffer') {
|
||||
throw new FST_ERR_CTP_INVALID_PARSE_TYPE(opts.parseAs)
|
||||
}
|
||||
}
|
||||
|
||||
const parser = new Parser(
|
||||
opts.parseAs === 'string',
|
||||
opts.parseAs === 'buffer',
|
||||
opts.bodyLimit,
|
||||
parserFn
|
||||
)
|
||||
|
||||
if (contentType === '*') {
|
||||
this.customParsers.set('', parser)
|
||||
} else {
|
||||
if (contentTypeIsString) {
|
||||
this.parserList.unshift(contentType)
|
||||
this.customParsers.set(contentType, parser)
|
||||
} else {
|
||||
validateRegExp(contentType)
|
||||
this.parserRegExpList.unshift(contentType)
|
||||
this.customParsers.set(contentType.toString(), parser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.hasParser = function (contentType) {
|
||||
if (typeof contentType === 'string') {
|
||||
contentType = contentType.trim().toLowerCase()
|
||||
} else {
|
||||
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
||||
contentType = contentType.toString()
|
||||
}
|
||||
|
||||
return this.customParsers.has(contentType)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.existingParser = function (contentType) {
|
||||
if (contentType === 'application/json' && this.customParsers.has(contentType)) {
|
||||
return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
|
||||
}
|
||||
if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
|
||||
return this.customParsers.get(contentType).fn !== defaultPlainTextParser
|
||||
}
|
||||
|
||||
return this.hasParser(contentType)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.getParser = function (contentType) {
|
||||
let parser = this.customParsers.get(contentType)
|
||||
if (parser !== undefined) return parser
|
||||
parser = this.cache.get(contentType)
|
||||
if (parser !== undefined) return parser
|
||||
|
||||
const caseInsensitiveContentType = contentType.toLowerCase()
|
||||
for (let i = 0; i !== this.parserList.length; ++i) {
|
||||
const parserListItem = this.parserList[i]
|
||||
if (
|
||||
caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem &&
|
||||
(
|
||||
caseInsensitiveContentType.length === parserListItem.length ||
|
||||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ ||
|
||||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */
|
||||
)
|
||||
) {
|
||||
parser = this.customParsers.get(parserListItem)
|
||||
this.cache.set(contentType, parser)
|
||||
return parser
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j !== this.parserRegExpList.length; ++j) {
|
||||
const parserRegExp = this.parserRegExpList[j]
|
||||
if (parserRegExp.test(contentType)) {
|
||||
parser = this.customParsers.get(parserRegExp.toString())
|
||||
this.cache.set(contentType, parser)
|
||||
return parser
|
||||
}
|
||||
}
|
||||
|
||||
return this.customParsers.get('')
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.removeAll = function () {
|
||||
this.customParsers = new Map()
|
||||
this.parserRegExpList = []
|
||||
this.parserList = []
|
||||
this.cache = new Fifo(100)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.remove = function (contentType) {
|
||||
let parsers
|
||||
|
||||
if (typeof contentType === 'string') {
|
||||
contentType = contentType.trim().toLowerCase()
|
||||
parsers = this.parserList
|
||||
} else {
|
||||
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
||||
contentType = contentType.toString()
|
||||
parsers = this.parserRegExpList
|
||||
}
|
||||
|
||||
const removed = this.customParsers.delete(contentType)
|
||||
const idx = parsers.findIndex(ct => ct.toString() === contentType)
|
||||
|
||||
if (idx > -1) {
|
||||
parsers.splice(idx, 1)
|
||||
}
|
||||
|
||||
return removed || idx > -1
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.run = function (contentType, handler, request, reply) {
|
||||
const parser = this.getParser(contentType)
|
||||
|
||||
if (parser === undefined) {
|
||||
if (request.is404 === true) {
|
||||
handler(request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
|
||||
return
|
||||
}
|
||||
|
||||
const resource = new AsyncResource('content-type-parser:run', request)
|
||||
const done = resource.bind(onDone)
|
||||
|
||||
if (parser.asString === true || parser.asBuffer === true) {
|
||||
rawBody(
|
||||
request,
|
||||
reply,
|
||||
reply[kRouteContext]._parserOptions,
|
||||
parser,
|
||||
done
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const result = parser.fn(request, request[kRequestPayloadStream], done)
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(body => { done(null, body) }, done)
|
||||
}
|
||||
|
||||
function onDone (error, body) {
|
||||
resource.emitDestroy()
|
||||
if (error != null) {
|
||||
// We must close the connection as the client may
|
||||
// send more data
|
||||
reply.header('connection', 'close')
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(error)
|
||||
return
|
||||
}
|
||||
request.body = body
|
||||
handler(request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function rawBody (request, reply, options, parser, done) {
|
||||
const asString = parser.asString === true
|
||||
const limit = options.limit === null ? parser.bodyLimit : options.limit
|
||||
const contentLength = Number(request.headers['content-length'])
|
||||
|
||||
if (contentLength > limit) {
|
||||
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
|
||||
return
|
||||
}
|
||||
|
||||
let receivedLength = 0
|
||||
let body = asString ? '' : []
|
||||
const payload = request[kRequestPayloadStream] || request.raw
|
||||
|
||||
if (asString) {
|
||||
payload.setEncoding('utf8')
|
||||
}
|
||||
|
||||
payload.on('data', onData)
|
||||
payload.on('end', onEnd)
|
||||
payload.on('error', onEnd)
|
||||
payload.resume()
|
||||
|
||||
function onData (chunk) {
|
||||
receivedLength += asString ? Buffer.byteLength(chunk) : chunk.length
|
||||
const { receivedEncodedLength = 0 } = payload
|
||||
// The resulting body length must not exceed bodyLimit (see "zip bomb").
|
||||
// The case when encoded length is larger than received length is rather theoretical,
|
||||
// unless the stream returned by preParsing hook is broken and reports wrong value.
|
||||
if (receivedLength > limit || receivedEncodedLength > limit) {
|
||||
payload.removeListener('data', onData)
|
||||
payload.removeListener('end', onEnd)
|
||||
payload.removeListener('error', onEnd)
|
||||
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (asString) {
|
||||
body += chunk
|
||||
} else {
|
||||
body.push(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
function onEnd (err) {
|
||||
payload.removeListener('data', onData)
|
||||
payload.removeListener('end', onEnd)
|
||||
payload.removeListener('error', onEnd)
|
||||
|
||||
if (err != null) {
|
||||
if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) {
|
||||
err.statusCode = 400
|
||||
}
|
||||
done(err, undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) {
|
||||
done(new FST_ERR_CTP_INVALID_CONTENT_LENGTH(), undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (!asString) {
|
||||
body = Buffer.concat(body)
|
||||
}
|
||||
|
||||
const result = parser.fn(request, body, done)
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(body => { done(null, body) }, done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
|
||||
const parseOptions = { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning }
|
||||
|
||||
return defaultJsonParser
|
||||
|
||||
function defaultJsonParser (req, body, done) {
|
||||
if (body.length === 0) {
|
||||
done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
|
||||
return
|
||||
}
|
||||
try {
|
||||
done(null, secureJsonParse(body, parseOptions))
|
||||
} catch {
|
||||
done(new FST_ERR_CTP_INVALID_JSON_BODY(), undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function defaultPlainTextParser (req, body, done) {
|
||||
done(null, body)
|
||||
}
|
||||
|
||||
function Parser (asString, asBuffer, bodyLimit, fn) {
|
||||
this.asString = asString
|
||||
this.asBuffer = asBuffer
|
||||
this.bodyLimit = bodyLimit
|
||||
this.fn = fn
|
||||
}
|
||||
|
||||
function buildContentTypeParser (c) {
|
||||
const contentTypeParser = new ContentTypeParser()
|
||||
contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse]
|
||||
contentTypeParser.customParsers = new Map(c.customParsers.entries())
|
||||
contentTypeParser.parserList = c.parserList.slice()
|
||||
contentTypeParser.parserRegExpList = c.parserRegExpList.slice()
|
||||
return contentTypeParser
|
||||
}
|
||||
|
||||
function addContentTypeParser (contentType, opts, parser) {
|
||||
if (this[kState].started) {
|
||||
throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('addContentTypeParser')
|
||||
}
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
parser = opts
|
||||
opts = {}
|
||||
}
|
||||
|
||||
if (!opts) opts = {}
|
||||
if (!opts.bodyLimit) opts.bodyLimit = this[kBodyLimit]
|
||||
|
||||
if (Array.isArray(contentType)) {
|
||||
contentType.forEach((type) => this[kContentTypeParser].add(type, opts, parser))
|
||||
} else {
|
||||
this[kContentTypeParser].add(contentType, opts, parser)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
function hasContentTypeParser (contentType) {
|
||||
return this[kContentTypeParser].hasParser(contentType)
|
||||
}
|
||||
|
||||
function removeContentTypeParser (contentType) {
|
||||
if (this[kState].started) {
|
||||
throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('removeContentTypeParser')
|
||||
}
|
||||
|
||||
if (Array.isArray(contentType)) {
|
||||
for (const type of contentType) {
|
||||
this[kContentTypeParser].remove(type)
|
||||
}
|
||||
} else {
|
||||
this[kContentTypeParser].remove(contentType)
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllContentTypeParsers () {
|
||||
if (this[kState].started) {
|
||||
throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('removeAllContentTypeParsers')
|
||||
}
|
||||
|
||||
this[kContentTypeParser].removeAll()
|
||||
}
|
||||
|
||||
function validateRegExp (regexp) {
|
||||
// RegExp should either start with ^ or include ;?
|
||||
// It can ensure the user is properly detect the essence
|
||||
// MIME types.
|
||||
if (regexp.source[0] !== '^' && regexp.source.includes(';?') === false) {
|
||||
FSTSEC001(regexp.source)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContentTypeParser
|
||||
module.exports.helpers = {
|
||||
buildContentTypeParser,
|
||||
addContentTypeParser,
|
||||
hasContentTypeParser,
|
||||
removeContentTypeParser,
|
||||
removeAllContentTypeParsers
|
||||
}
|
||||
module.exports.defaultParsers = {
|
||||
getDefaultJsonParser,
|
||||
defaultTextParser: defaultPlainTextParser
|
||||
}
|
||||
module.exports[kTestInternals] = { rawBody }
|
||||
95
node_modules/fastify/lib/context.js
generated
vendored
Normal file
95
node_modules/fastify/lib/context.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kFourOhFourContext,
|
||||
kReplySerializerDefault,
|
||||
kSchemaErrorFormatter,
|
||||
kErrorHandler,
|
||||
kChildLoggerFactory,
|
||||
kOptions,
|
||||
kReply,
|
||||
kRequest,
|
||||
kBodyLimit,
|
||||
kLogLevel,
|
||||
kContentTypeParser,
|
||||
kRouteByFastify,
|
||||
kRequestCacheValidateFns,
|
||||
kReplyCacheSerializeFns
|
||||
} = require('./symbols.js')
|
||||
|
||||
// Object that holds the context of every request
|
||||
// Every route holds an instance of this object.
|
||||
function Context ({
|
||||
schema,
|
||||
handler,
|
||||
config,
|
||||
requestIdLogLabel,
|
||||
childLoggerFactory,
|
||||
errorHandler,
|
||||
bodyLimit,
|
||||
logLevel,
|
||||
logSerializers,
|
||||
attachValidation,
|
||||
validatorCompiler,
|
||||
serializerCompiler,
|
||||
replySerializer,
|
||||
schemaErrorFormatter,
|
||||
exposeHeadRoute,
|
||||
prefixTrailingSlash,
|
||||
server,
|
||||
isFastify
|
||||
}) {
|
||||
this.schema = schema
|
||||
this.handler = handler
|
||||
this.Reply = server[kReply]
|
||||
this.Request = server[kRequest]
|
||||
this.contentTypeParser = server[kContentTypeParser]
|
||||
this.onRequest = null
|
||||
this.onSend = null
|
||||
this.onError = null
|
||||
this.onTimeout = null
|
||||
this.preHandler = null
|
||||
this.onResponse = null
|
||||
this.preSerialization = null
|
||||
this.onRequestAbort = null
|
||||
this.config = config
|
||||
this.errorHandler = errorHandler || server[kErrorHandler]
|
||||
this.requestIdLogLabel = requestIdLogLabel || server[kOptions].requestIdLogLabel
|
||||
this.childLoggerFactory = childLoggerFactory || server[kChildLoggerFactory]
|
||||
this._middie = null
|
||||
this._parserOptions = {
|
||||
limit: bodyLimit || server[kBodyLimit]
|
||||
}
|
||||
this.exposeHeadRoute = exposeHeadRoute
|
||||
this.prefixTrailingSlash = prefixTrailingSlash
|
||||
this.logLevel = logLevel || server[kLogLevel]
|
||||
this.logSerializers = logSerializers
|
||||
this[kFourOhFourContext] = null
|
||||
this.attachValidation = attachValidation
|
||||
this[kReplySerializerDefault] = replySerializer
|
||||
this.schemaErrorFormatter =
|
||||
schemaErrorFormatter ||
|
||||
server[kSchemaErrorFormatter] ||
|
||||
defaultSchemaErrorFormatter
|
||||
this[kRouteByFastify] = isFastify
|
||||
|
||||
this[kRequestCacheValidateFns] = null
|
||||
this[kReplyCacheSerializeFns] = null
|
||||
this.validatorCompiler = validatorCompiler || null
|
||||
this.serializerCompiler = serializerCompiler || null
|
||||
|
||||
this.server = server
|
||||
}
|
||||
|
||||
function defaultSchemaErrorFormatter (errors, dataVar) {
|
||||
let text = ''
|
||||
const separator = ', '
|
||||
|
||||
for (let i = 0; i !== errors.length; ++i) {
|
||||
const e = errors[i]
|
||||
text += dataVar + (e.instancePath || '') + ' ' + e.message + separator
|
||||
}
|
||||
return new Error(text.slice(0, -separator.length))
|
||||
}
|
||||
|
||||
module.exports = Context
|
||||
152
node_modules/fastify/lib/decorate.js
generated
vendored
Normal file
152
node_modules/fastify/lib/decorate.js
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kReply,
|
||||
kRequest,
|
||||
kState,
|
||||
kHasBeenDecorated
|
||||
} = require('./symbols.js')
|
||||
|
||||
const {
|
||||
FST_ERR_DEC_ALREADY_PRESENT,
|
||||
FST_ERR_DEC_MISSING_DEPENDENCY,
|
||||
FST_ERR_DEC_AFTER_START,
|
||||
FST_ERR_DEC_REFERENCE_TYPE,
|
||||
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE,
|
||||
FST_ERR_DEC_UNDECLARED
|
||||
} = require('./errors')
|
||||
|
||||
function decorate (instance, name, fn, dependencies) {
|
||||
if (Object.hasOwn(instance, name)) {
|
||||
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
||||
}
|
||||
|
||||
checkDependencies(instance, name, dependencies)
|
||||
|
||||
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
||||
Object.defineProperty(instance, name, {
|
||||
get: fn.getter,
|
||||
set: fn.setter
|
||||
})
|
||||
} else {
|
||||
instance[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
function getInstanceDecorator (name) {
|
||||
if (!checkExistence(this, name)) {
|
||||
throw new FST_ERR_DEC_UNDECLARED(name, 'instance')
|
||||
}
|
||||
|
||||
if (typeof this[name] === 'function') {
|
||||
return this[name].bind(this)
|
||||
}
|
||||
|
||||
return this[name]
|
||||
}
|
||||
|
||||
function decorateConstructor (konstructor, name, fn, dependencies) {
|
||||
const instance = konstructor.prototype
|
||||
if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) {
|
||||
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
||||
}
|
||||
|
||||
konstructor[kHasBeenDecorated] = true
|
||||
checkDependencies(konstructor, name, dependencies)
|
||||
|
||||
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
||||
Object.defineProperty(instance, name, {
|
||||
get: fn.getter,
|
||||
set: fn.setter
|
||||
})
|
||||
} else if (typeof fn === 'function') {
|
||||
instance[name] = fn
|
||||
} else {
|
||||
konstructor.props.push({ key: name, value: fn })
|
||||
}
|
||||
}
|
||||
|
||||
function checkReferenceType (name, fn) {
|
||||
if (typeof fn === 'object' && fn && !(typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
||||
throw new FST_ERR_DEC_REFERENCE_TYPE(name, typeof fn)
|
||||
}
|
||||
}
|
||||
|
||||
function decorateFastify (name, fn, dependencies) {
|
||||
assertNotStarted(this, name)
|
||||
decorate(this, name, fn, dependencies)
|
||||
return this
|
||||
}
|
||||
|
||||
function checkExistence (instance, name) {
|
||||
if (name) {
|
||||
return name in instance || (instance.prototype && name in instance.prototype) || hasKey(instance, name)
|
||||
}
|
||||
|
||||
return instance in this
|
||||
}
|
||||
|
||||
function hasKey (fn, name) {
|
||||
if (fn.props) {
|
||||
return fn.props.find(({ key }) => key === name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function checkRequestExistence (name) {
|
||||
if (name && hasKey(this[kRequest], name)) return true
|
||||
return checkExistence(this[kRequest].prototype, name)
|
||||
}
|
||||
|
||||
function checkReplyExistence (name) {
|
||||
if (name && hasKey(this[kReply], name)) return true
|
||||
return checkExistence(this[kReply].prototype, name)
|
||||
}
|
||||
|
||||
function checkDependencies (instance, name, deps) {
|
||||
if (deps === undefined || deps === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!Array.isArray(deps)) {
|
||||
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
|
||||
}
|
||||
|
||||
for (let i = 0; i !== deps.length; ++i) {
|
||||
if (!checkExistence(instance, deps[i])) {
|
||||
throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decorateReply (name, fn, dependencies) {
|
||||
assertNotStarted(this, name)
|
||||
checkReferenceType(name, fn)
|
||||
decorateConstructor(this[kReply], name, fn, dependencies)
|
||||
return this
|
||||
}
|
||||
|
||||
function decorateRequest (name, fn, dependencies) {
|
||||
assertNotStarted(this, name)
|
||||
checkReferenceType(name, fn)
|
||||
decorateConstructor(this[kRequest], name, fn, dependencies)
|
||||
return this
|
||||
}
|
||||
|
||||
function assertNotStarted (instance, name) {
|
||||
if (instance[kState].started) {
|
||||
throw new FST_ERR_DEC_AFTER_START(name)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
add: decorateFastify,
|
||||
exist: checkExistence,
|
||||
existRequest: checkRequestExistence,
|
||||
existReply: checkReplyExistence,
|
||||
dependencies: checkDependencies,
|
||||
decorateReply,
|
||||
decorateRequest,
|
||||
getInstanceDecorator,
|
||||
hasKey
|
||||
}
|
||||
176
node_modules/fastify/lib/error-handler.js
generated
vendored
Normal file
176
node_modules/fastify/lib/error-handler.js
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
'use strict'
|
||||
|
||||
const statusCodes = require('node:http').STATUS_CODES
|
||||
const wrapThenable = require('./wrapThenable')
|
||||
const {
|
||||
kReplyHeaders,
|
||||
kReplyNextErrorHandler,
|
||||
kReplyIsRunningOnErrorHook,
|
||||
kReplyHasStatusCode,
|
||||
kRouteContext,
|
||||
kDisableRequestLogging
|
||||
} = require('./symbols.js')
|
||||
|
||||
const {
|
||||
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
|
||||
FST_ERR_FAILED_ERROR_SERIALIZATION
|
||||
} = require('./errors')
|
||||
|
||||
const { getSchemaSerializer } = require('./schemas')
|
||||
|
||||
const serializeError = require('./error-serializer')
|
||||
|
||||
const rootErrorHandler = {
|
||||
func: defaultErrorHandler,
|
||||
toJSON () {
|
||||
return this.func.name.toString() + '()'
|
||||
}
|
||||
}
|
||||
|
||||
function handleError (reply, error, cb) {
|
||||
reply[kReplyIsRunningOnErrorHook] = false
|
||||
|
||||
const context = reply[kRouteContext]
|
||||
if (reply[kReplyNextErrorHandler] === false) {
|
||||
fallbackErrorHandler(error, reply, function (reply, payload) {
|
||||
try {
|
||||
reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
|
||||
} catch (error) {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
reply.log.warn(
|
||||
{ req: reply.request, res: reply, err: error },
|
||||
error?.message
|
||||
)
|
||||
}
|
||||
reply.raw.writeHead(reply.raw.statusCode)
|
||||
}
|
||||
reply.raw.end(payload)
|
||||
})
|
||||
return
|
||||
}
|
||||
const errorHandler = reply[kReplyNextErrorHandler] || context.errorHandler
|
||||
|
||||
// In case the error handler throws, we set the next errorHandler so we can error again
|
||||
reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler)
|
||||
|
||||
// we need to remove content-type to allow content-type guessing for serialization
|
||||
delete reply[kReplyHeaders]['content-type']
|
||||
delete reply[kReplyHeaders]['content-length']
|
||||
|
||||
const func = errorHandler.func
|
||||
|
||||
if (!func) {
|
||||
reply[kReplyNextErrorHandler] = false
|
||||
fallbackErrorHandler(error, reply, cb)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = func(error, reply.request, reply)
|
||||
if (result !== undefined) {
|
||||
if (result !== null && typeof result.then === 'function') {
|
||||
wrapThenable(result, reply)
|
||||
} else {
|
||||
reply.send(result)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
reply.send(err)
|
||||
}
|
||||
}
|
||||
|
||||
function defaultErrorHandler (error, request, reply) {
|
||||
setErrorHeaders(error, reply)
|
||||
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
|
||||
const statusCode = error.statusCode || error.status
|
||||
reply.code(statusCode >= 400 ? statusCode : 500)
|
||||
}
|
||||
if (reply.statusCode < 500) {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
reply.log.info(
|
||||
{ res: reply, err: error },
|
||||
error?.message
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
reply.log.error(
|
||||
{ req: request, res: reply, err: error },
|
||||
error?.message
|
||||
)
|
||||
}
|
||||
}
|
||||
reply.send(error)
|
||||
}
|
||||
|
||||
function fallbackErrorHandler (error, reply, cb) {
|
||||
const res = reply.raw
|
||||
const statusCode = reply.statusCode
|
||||
reply[kReplyHeaders]['content-type'] = reply[kReplyHeaders]['content-type'] ?? 'application/json; charset=utf-8'
|
||||
let payload
|
||||
try {
|
||||
const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
|
||||
if (serializerFn === false) {
|
||||
payload = serializeError({
|
||||
error: statusCodes[statusCode + ''],
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
statusCode
|
||||
})
|
||||
} else {
|
||||
payload = serializerFn(Object.create(error, {
|
||||
error: { value: statusCodes[statusCode + ''] },
|
||||
message: { value: error.message },
|
||||
statusCode: { value: statusCode }
|
||||
}))
|
||||
}
|
||||
} catch (err) {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
// error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
|
||||
reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
|
||||
}
|
||||
reply.code(500)
|
||||
payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message))
|
||||
}
|
||||
|
||||
if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
|
||||
payload = serializeError(new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload))
|
||||
}
|
||||
|
||||
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
||||
|
||||
cb(reply, payload)
|
||||
}
|
||||
|
||||
function buildErrorHandler (parent = rootErrorHandler, func) {
|
||||
if (!func) {
|
||||
return parent
|
||||
}
|
||||
|
||||
const errorHandler = Object.create(parent)
|
||||
errorHandler.func = func
|
||||
return errorHandler
|
||||
}
|
||||
|
||||
function setErrorHeaders (error, reply) {
|
||||
const res = reply.raw
|
||||
let statusCode = res.statusCode
|
||||
statusCode = (statusCode >= 400) ? statusCode : 500
|
||||
// treat undefined and null as same
|
||||
if (error != null) {
|
||||
if (error.headers !== undefined) {
|
||||
reply.headers(error.headers)
|
||||
}
|
||||
if (error.status >= 400) {
|
||||
statusCode = error.status
|
||||
} else if (error.statusCode >= 400) {
|
||||
statusCode = error.statusCode
|
||||
}
|
||||
}
|
||||
res.statusCode = statusCode
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildErrorHandler,
|
||||
handleError
|
||||
}
|
||||
120
node_modules/fastify/lib/error-serializer.js
generated
vendored
Normal file
120
node_modules/fastify/lib/error-serializer.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// This file is autogenerated by build/build-error-serializer.js, do not edit
|
||||
/* c8 ignore start */
|
||||
|
||||
'use strict'
|
||||
|
||||
const Serializer = require('fast-json-stringify/lib/serializer')
|
||||
const serializerState = {"mode":"standalone"}
|
||||
const serializer = Serializer.restoreFromState(serializerState)
|
||||
|
||||
const validator = null
|
||||
|
||||
|
||||
module.exports = function anonymous(validator,serializer
|
||||
) {
|
||||
|
||||
const JSON_STR_BEGIN_OBJECT = '{'
|
||||
const JSON_STR_END_OBJECT = '}'
|
||||
const JSON_STR_BEGIN_ARRAY = '['
|
||||
const JSON_STR_END_ARRAY = ']'
|
||||
const JSON_STR_COMMA = ','
|
||||
const JSON_STR_COLONS = ':'
|
||||
const JSON_STR_QUOTE = '"'
|
||||
const JSON_STR_EMPTY_OBJECT = JSON_STR_BEGIN_OBJECT + JSON_STR_END_OBJECT
|
||||
const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY
|
||||
const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE
|
||||
const JSON_STR_NULL = 'null'
|
||||
|
||||
|
||||
|
||||
// #
|
||||
function anonymous0 (input) {
|
||||
const obj = (input && typeof input.toJSON === 'function')
|
||||
? input.toJSON()
|
||||
: input
|
||||
|
||||
if (obj === null) return JSON_STR_EMPTY_OBJECT
|
||||
|
||||
let value
|
||||
let json = JSON_STR_BEGIN_OBJECT
|
||||
let addComma = false
|
||||
|
||||
value = obj["statusCode"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"statusCode\":"
|
||||
json += serializer.asNumber(value)
|
||||
}
|
||||
|
||||
value = obj["code"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"code\":"
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
json += JSON_STR_EMPTY_STRING
|
||||
} else if (value instanceof Date) {
|
||||
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
||||
} else if (value instanceof RegExp) {
|
||||
json += serializer.asString(value.source)
|
||||
} else {
|
||||
json += serializer.asString(value.toString())
|
||||
}
|
||||
} else {
|
||||
json += serializer.asString(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
value = obj["error"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"error\":"
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
json += JSON_STR_EMPTY_STRING
|
||||
} else if (value instanceof Date) {
|
||||
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
||||
} else if (value instanceof RegExp) {
|
||||
json += serializer.asString(value.source)
|
||||
} else {
|
||||
json += serializer.asString(value.toString())
|
||||
}
|
||||
} else {
|
||||
json += serializer.asString(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
value = obj["message"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"message\":"
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
json += JSON_STR_EMPTY_STRING
|
||||
} else if (value instanceof Date) {
|
||||
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
||||
} else if (value instanceof RegExp) {
|
||||
json += serializer.asString(value.source)
|
||||
} else {
|
||||
json += serializer.asString(value.toString())
|
||||
}
|
||||
} else {
|
||||
json += serializer.asString(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return json + JSON_STR_END_OBJECT
|
||||
|
||||
}
|
||||
|
||||
const main = anonymous0
|
||||
return main
|
||||
|
||||
}(validator, serializer)
|
||||
/* c8 ignore stop */
|
||||
505
node_modules/fastify/lib/errors.js
generated
vendored
Normal file
505
node_modules/fastify/lib/errors.js
generated
vendored
Normal file
@@ -0,0 +1,505 @@
|
||||
'use strict'
|
||||
|
||||
const createError = require('@fastify/error')
|
||||
|
||||
const codes = {
|
||||
/**
|
||||
* Basic
|
||||
*/
|
||||
FST_ERR_NOT_FOUND: createError(
|
||||
'FST_ERR_NOT_FOUND',
|
||||
'Not Found',
|
||||
404
|
||||
),
|
||||
FST_ERR_OPTIONS_NOT_OBJ: createError(
|
||||
'FST_ERR_OPTIONS_NOT_OBJ',
|
||||
'Options must be an object',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_QSP_NOT_FN: createError(
|
||||
'FST_ERR_QSP_NOT_FN',
|
||||
"querystringParser option should be a function, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN: createError(
|
||||
'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN',
|
||||
"schemaController.bucket option should be a function, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN: createError(
|
||||
'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN',
|
||||
"schemaErrorFormatter option should be a non async function. Instead got '%s'.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ: createError(
|
||||
'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ',
|
||||
"ajv.customOptions option should be an object, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR: createError(
|
||||
'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR',
|
||||
"ajv.plugins option should be an array, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_VALIDATION: createError(
|
||||
'FST_ERR_VALIDATION',
|
||||
'%s',
|
||||
400
|
||||
),
|
||||
FST_ERR_LISTEN_OPTIONS_INVALID: createError(
|
||||
'FST_ERR_LISTEN_OPTIONS_INVALID',
|
||||
"Invalid listen options: '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ERROR_HANDLER_NOT_FN: createError(
|
||||
'FST_ERR_ERROR_HANDLER_NOT_FN',
|
||||
'Error Handler must be a function',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ERROR_HANDLER_ALREADY_SET: createError(
|
||||
'FST_ERR_ERROR_HANDLER_ALREADY_SET',
|
||||
"Error Handler already set in this scope. Set 'allowErrorHandlerOverride: true' to allow overriding.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* ContentTypeParser
|
||||
*/
|
||||
FST_ERR_CTP_ALREADY_PRESENT: createError(
|
||||
'FST_ERR_CTP_ALREADY_PRESENT',
|
||||
"Content type parser '%s' already present."
|
||||
),
|
||||
FST_ERR_CTP_INVALID_TYPE: createError(
|
||||
'FST_ERR_CTP_INVALID_TYPE',
|
||||
'The content type should be a string or a RegExp',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_EMPTY_TYPE: createError(
|
||||
'FST_ERR_CTP_EMPTY_TYPE',
|
||||
'The content type cannot be an empty string',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_INVALID_HANDLER: createError(
|
||||
'FST_ERR_CTP_INVALID_HANDLER',
|
||||
'The content type handler should be a function',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_INVALID_PARSE_TYPE: createError(
|
||||
'FST_ERR_CTP_INVALID_PARSE_TYPE',
|
||||
"The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_BODY_TOO_LARGE: createError(
|
||||
'FST_ERR_CTP_BODY_TOO_LARGE',
|
||||
'Request body is too large',
|
||||
413,
|
||||
RangeError
|
||||
),
|
||||
FST_ERR_CTP_INVALID_MEDIA_TYPE: createError(
|
||||
'FST_ERR_CTP_INVALID_MEDIA_TYPE',
|
||||
'Unsupported Media Type: %s',
|
||||
415
|
||||
),
|
||||
FST_ERR_CTP_INVALID_CONTENT_LENGTH: createError(
|
||||
'FST_ERR_CTP_INVALID_CONTENT_LENGTH',
|
||||
'Request body size did not match Content-Length',
|
||||
400,
|
||||
RangeError
|
||||
),
|
||||
FST_ERR_CTP_EMPTY_JSON_BODY: createError(
|
||||
'FST_ERR_CTP_EMPTY_JSON_BODY',
|
||||
"Body cannot be empty when content-type is set to 'application/json'",
|
||||
400
|
||||
),
|
||||
FST_ERR_CTP_INVALID_JSON_BODY: createError(
|
||||
'FST_ERR_CTP_INVALID_JSON_BODY',
|
||||
"Body is not valid JSON but content-type is set to 'application/json'",
|
||||
400
|
||||
),
|
||||
FST_ERR_CTP_INSTANCE_ALREADY_STARTED: createError(
|
||||
'FST_ERR_CTP_INSTANCE_ALREADY_STARTED',
|
||||
'Cannot call "%s" when fastify instance is already started!',
|
||||
400
|
||||
),
|
||||
|
||||
/**
|
||||
* decorate
|
||||
*/
|
||||
FST_ERR_DEC_ALREADY_PRESENT: createError(
|
||||
'FST_ERR_DEC_ALREADY_PRESENT',
|
||||
"The decorator '%s' has already been added!"
|
||||
),
|
||||
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE: createError(
|
||||
'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE',
|
||||
"The dependencies of decorator '%s' must be of type Array.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_DEC_MISSING_DEPENDENCY: createError(
|
||||
'FST_ERR_DEC_MISSING_DEPENDENCY',
|
||||
"The decorator is missing dependency '%s'."
|
||||
),
|
||||
FST_ERR_DEC_AFTER_START: createError(
|
||||
'FST_ERR_DEC_AFTER_START',
|
||||
"The decorator '%s' has been added after start!"
|
||||
),
|
||||
FST_ERR_DEC_REFERENCE_TYPE: createError(
|
||||
'FST_ERR_DEC_REFERENCE_TYPE',
|
||||
"The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead."
|
||||
),
|
||||
FST_ERR_DEC_UNDECLARED: createError(
|
||||
'FST_ERR_DEC_UNDECLARED',
|
||||
"No decorator '%s' has been declared on %s."
|
||||
),
|
||||
|
||||
/**
|
||||
* hooks
|
||||
*/
|
||||
FST_ERR_HOOK_INVALID_TYPE: createError(
|
||||
'FST_ERR_HOOK_INVALID_TYPE',
|
||||
'The hook name must be a string',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_HOOK_INVALID_HANDLER: createError(
|
||||
'FST_ERR_HOOK_INVALID_HANDLER',
|
||||
'%s hook should be a function, instead got %s',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_HOOK_INVALID_ASYNC_HANDLER: createError(
|
||||
'FST_ERR_HOOK_INVALID_ASYNC_HANDLER',
|
||||
'Async function has too many arguments. Async hooks should not use the \'done\' argument.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_HOOK_NOT_SUPPORTED: createError(
|
||||
'FST_ERR_HOOK_NOT_SUPPORTED',
|
||||
'%s hook not supported!',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
*/
|
||||
FST_ERR_MISSING_MIDDLEWARE: createError(
|
||||
'FST_ERR_MISSING_MIDDLEWARE',
|
||||
'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.',
|
||||
500
|
||||
),
|
||||
|
||||
FST_ERR_HOOK_TIMEOUT: createError(
|
||||
'FST_ERR_HOOK_TIMEOUT',
|
||||
"A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise"
|
||||
),
|
||||
|
||||
/**
|
||||
* logger
|
||||
*/
|
||||
FST_ERR_LOG_INVALID_DESTINATION: createError(
|
||||
'FST_ERR_LOG_INVALID_DESTINATION',
|
||||
'Cannot specify both logger.stream and logger.file options'
|
||||
),
|
||||
|
||||
FST_ERR_LOG_INVALID_LOGGER: createError(
|
||||
'FST_ERR_LOG_INVALID_LOGGER',
|
||||
"Invalid logger object provided. The logger instance should have these functions(s): '%s'.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
FST_ERR_LOG_INVALID_LOGGER_INSTANCE: createError(
|
||||
'FST_ERR_LOG_INVALID_LOGGER_INSTANCE',
|
||||
'loggerInstance only accepts a logger instance.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
FST_ERR_LOG_INVALID_LOGGER_CONFIG: createError(
|
||||
'FST_ERR_LOG_INVALID_LOGGER_CONFIG',
|
||||
'logger options only accepts a configuration object.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED: createError(
|
||||
'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED',
|
||||
'You cannot provide both logger and loggerInstance. Please provide only one.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* reply
|
||||
*/
|
||||
FST_ERR_REP_INVALID_PAYLOAD_TYPE: createError(
|
||||
'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
|
||||
"Attempted to send payload of invalid type '%s'. Expected a string or Buffer.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_REP_RESPONSE_BODY_CONSUMED: createError(
|
||||
'FST_ERR_REP_RESPONSE_BODY_CONSUMED',
|
||||
'Response.body is already consumed.'
|
||||
),
|
||||
FST_ERR_REP_READABLE_STREAM_LOCKED: createError(
|
||||
'FST_ERR_REP_READABLE_STREAM_LOCKED',
|
||||
'ReadableStream was locked. You should call releaseLock() method on reader before sending.'
|
||||
),
|
||||
FST_ERR_REP_ALREADY_SENT: createError(
|
||||
'FST_ERR_REP_ALREADY_SENT',
|
||||
'Reply was already sent, did you forget to "return reply" in "%s" (%s)?'
|
||||
),
|
||||
FST_ERR_REP_SENT_VALUE: createError(
|
||||
'FST_ERR_REP_SENT_VALUE',
|
||||
'The only possible value for reply.sent is true.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_SEND_INSIDE_ONERR: createError(
|
||||
'FST_ERR_SEND_INSIDE_ONERR',
|
||||
'You cannot use `send` inside the `onError` hook'
|
||||
),
|
||||
FST_ERR_SEND_UNDEFINED_ERR: createError(
|
||||
'FST_ERR_SEND_UNDEFINED_ERR',
|
||||
'Undefined error has occurred'
|
||||
),
|
||||
FST_ERR_BAD_STATUS_CODE: createError(
|
||||
'FST_ERR_BAD_STATUS_CODE',
|
||||
'Called reply with an invalid status code: %s'
|
||||
),
|
||||
FST_ERR_BAD_TRAILER_NAME: createError(
|
||||
'FST_ERR_BAD_TRAILER_NAME',
|
||||
'Called reply.trailer with an invalid header name: %s'
|
||||
),
|
||||
FST_ERR_BAD_TRAILER_VALUE: createError(
|
||||
'FST_ERR_BAD_TRAILER_VALUE',
|
||||
"Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function."
|
||||
),
|
||||
FST_ERR_FAILED_ERROR_SERIALIZATION: createError(
|
||||
'FST_ERR_FAILED_ERROR_SERIALIZATION',
|
||||
'Failed to serialize an error. Error: %s. Original error: %s'
|
||||
),
|
||||
FST_ERR_MISSING_SERIALIZATION_FN: createError(
|
||||
'FST_ERR_MISSING_SERIALIZATION_FN',
|
||||
'Missing serialization function. Key "%s"'
|
||||
),
|
||||
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN: createError(
|
||||
'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN',
|
||||
'Missing serialization function. Key "%s:%s"'
|
||||
),
|
||||
FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError(
|
||||
'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION',
|
||||
'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.'
|
||||
),
|
||||
|
||||
/**
|
||||
* schemas
|
||||
*/
|
||||
FST_ERR_SCH_MISSING_ID: createError(
|
||||
'FST_ERR_SCH_MISSING_ID',
|
||||
'Missing schema $id property'
|
||||
),
|
||||
FST_ERR_SCH_ALREADY_PRESENT: createError(
|
||||
'FST_ERR_SCH_ALREADY_PRESENT',
|
||||
"Schema with id '%s' already declared!"
|
||||
),
|
||||
FST_ERR_SCH_CONTENT_MISSING_SCHEMA: createError(
|
||||
'FST_ERR_SCH_CONTENT_MISSING_SCHEMA',
|
||||
"Schema is missing for the content type '%s'"
|
||||
),
|
||||
FST_ERR_SCH_DUPLICATE: createError(
|
||||
'FST_ERR_SCH_DUPLICATE',
|
||||
"Schema with '%s' already present!"
|
||||
),
|
||||
FST_ERR_SCH_VALIDATION_BUILD: createError(
|
||||
'FST_ERR_SCH_VALIDATION_BUILD',
|
||||
'Failed building the validation schema for %s: %s, due to error %s'
|
||||
),
|
||||
FST_ERR_SCH_SERIALIZATION_BUILD: createError(
|
||||
'FST_ERR_SCH_SERIALIZATION_BUILD',
|
||||
'Failed building the serialization schema for %s: %s, due to error %s'
|
||||
),
|
||||
FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX: createError(
|
||||
'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX',
|
||||
'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }'
|
||||
),
|
||||
|
||||
/**
|
||||
* initialConfig
|
||||
*/
|
||||
FST_ERR_INIT_OPTS_INVALID: createError(
|
||||
'FST_ERR_INIT_OPTS_INVALID',
|
||||
"Invalid initialization options: '%s'"
|
||||
),
|
||||
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE: createError(
|
||||
'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE',
|
||||
"Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method"
|
||||
),
|
||||
|
||||
/**
|
||||
* router
|
||||
*/
|
||||
FST_ERR_DUPLICATED_ROUTE: createError(
|
||||
'FST_ERR_DUPLICATED_ROUTE',
|
||||
"Method '%s' already declared for route '%s'"
|
||||
),
|
||||
FST_ERR_BAD_URL: createError(
|
||||
'FST_ERR_BAD_URL',
|
||||
"'%s' is not a valid url component",
|
||||
400,
|
||||
URIError
|
||||
),
|
||||
FST_ERR_ASYNC_CONSTRAINT: createError(
|
||||
'FST_ERR_ASYNC_CONSTRAINT',
|
||||
'Unexpected error from async constraint',
|
||||
500
|
||||
),
|
||||
FST_ERR_INVALID_URL: createError(
|
||||
'FST_ERR_INVALID_URL',
|
||||
"URL must be a string. Received '%s'",
|
||||
400,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_OPTIONS_NOT_OBJ: createError(
|
||||
'FST_ERR_ROUTE_OPTIONS_NOT_OBJ',
|
||||
'Options for "%s:%s" route must be an object',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_DUPLICATED_HANDLER: createError(
|
||||
'FST_ERR_ROUTE_DUPLICATED_HANDLER',
|
||||
'Duplicate handler for "%s:%s" route is not allowed!',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_HANDLER_NOT_FN: createError(
|
||||
'FST_ERR_ROUTE_HANDLER_NOT_FN',
|
||||
'Error Handler for %s:%s route, if defined, must be a function',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_MISSING_HANDLER: createError(
|
||||
'FST_ERR_ROUTE_MISSING_HANDLER',
|
||||
'Missing handler function for "%s:%s" route.',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_METHOD_INVALID: createError(
|
||||
'FST_ERR_ROUTE_METHOD_INVALID',
|
||||
'Provided method is invalid!',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError(
|
||||
'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED',
|
||||
'%s method is not supported.',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED: createError(
|
||||
'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED',
|
||||
'Body validation schema for %s:%s route is not supported!',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT: createError(
|
||||
'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT',
|
||||
"'bodyLimit' option must be an integer > 0. Got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_REWRITE_NOT_STR: createError(
|
||||
'FST_ERR_ROUTE_REWRITE_NOT_STR',
|
||||
'Rewrite url for "%s" needs to be of type "string" but received "%s"',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* again listen when close server
|
||||
*/
|
||||
FST_ERR_REOPENED_CLOSE_SERVER: createError(
|
||||
'FST_ERR_REOPENED_CLOSE_SERVER',
|
||||
'Fastify has already been closed and cannot be reopened'
|
||||
),
|
||||
FST_ERR_REOPENED_SERVER: createError(
|
||||
'FST_ERR_REOPENED_SERVER',
|
||||
'Fastify is already listening'
|
||||
),
|
||||
FST_ERR_INSTANCE_ALREADY_LISTENING: createError(
|
||||
'FST_ERR_INSTANCE_ALREADY_LISTENING',
|
||||
'Fastify instance is already listening. %s'
|
||||
),
|
||||
|
||||
/**
|
||||
* plugin
|
||||
*/
|
||||
FST_ERR_PLUGIN_VERSION_MISMATCH: createError(
|
||||
'FST_ERR_PLUGIN_VERSION_MISMATCH',
|
||||
"fastify-plugin: %s - expected '%s' fastify version, '%s' is installed"
|
||||
),
|
||||
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE: createError(
|
||||
'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE',
|
||||
"The decorator '%s'%s is not present in %s"
|
||||
),
|
||||
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER: createError(
|
||||
'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER',
|
||||
'The %s plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* Avvio Errors
|
||||
*/
|
||||
FST_ERR_PLUGIN_CALLBACK_NOT_FN: createError(
|
||||
'FST_ERR_PLUGIN_CALLBACK_NOT_FN',
|
||||
'fastify-plugin: %s',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_PLUGIN_NOT_VALID: createError(
|
||||
'FST_ERR_PLUGIN_NOT_VALID',
|
||||
'fastify-plugin: %s'
|
||||
),
|
||||
FST_ERR_ROOT_PLG_BOOTED: createError(
|
||||
'FST_ERR_ROOT_PLG_BOOTED',
|
||||
'fastify-plugin: %s'
|
||||
),
|
||||
FST_ERR_PARENT_PLUGIN_BOOTED: createError(
|
||||
'FST_ERR_PARENT_PLUGIN_BOOTED',
|
||||
'fastify-plugin: %s'
|
||||
),
|
||||
FST_ERR_PLUGIN_TIMEOUT: createError(
|
||||
'FST_ERR_PLUGIN_TIMEOUT',
|
||||
'fastify-plugin: %s'
|
||||
)
|
||||
}
|
||||
|
||||
function appendStackTrace (oldErr, newErr) {
|
||||
newErr.cause = oldErr
|
||||
|
||||
return newErr
|
||||
}
|
||||
|
||||
module.exports = codes
|
||||
module.exports.appendStackTrace = appendStackTrace
|
||||
module.exports.AVVIO_ERRORS_MAP = {
|
||||
AVV_ERR_CALLBACK_NOT_FN: codes.FST_ERR_PLUGIN_CALLBACK_NOT_FN,
|
||||
AVV_ERR_PLUGIN_NOT_VALID: codes.FST_ERR_PLUGIN_NOT_VALID,
|
||||
AVV_ERR_ROOT_PLG_BOOTED: codes.FST_ERR_ROOT_PLG_BOOTED,
|
||||
AVV_ERR_PARENT_PLG_LOADED: codes.FST_ERR_PARENT_PLUGIN_BOOTED,
|
||||
AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT,
|
||||
AVV_ERR_PLUGIN_EXEC_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT
|
||||
}
|
||||
187
node_modules/fastify/lib/fourOhFour.js
generated
vendored
Normal file
187
node_modules/fastify/lib/fourOhFour.js
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
'use strict'
|
||||
|
||||
const FindMyWay = require('find-my-way')
|
||||
|
||||
const Reply = require('./reply')
|
||||
const Request = require('./request')
|
||||
const Context = require('./context')
|
||||
const {
|
||||
kRoutePrefix,
|
||||
kCanSetNotFoundHandler,
|
||||
kFourOhFourLevelInstance,
|
||||
kFourOhFourContext,
|
||||
kHooks,
|
||||
kErrorHandler
|
||||
} = require('./symbols.js')
|
||||
const { lifecycleHooks } = require('./hooks')
|
||||
const { buildErrorHandler } = require('./error-handler.js')
|
||||
const {
|
||||
FST_ERR_NOT_FOUND
|
||||
} = require('./errors')
|
||||
const { createChildLogger } = require('./logger-factory')
|
||||
const { getGenReqId } = require('./reqIdGenFactory.js')
|
||||
|
||||
/**
|
||||
* Each fastify instance have a:
|
||||
* kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler set
|
||||
* kCanSetNotFoundHandler: bool to track if the 404 handler has already been set
|
||||
* kFourOhFour: the singleton instance of this 404 module
|
||||
* kFourOhFourContext: the context in the reply object where the handler will be executed
|
||||
*/
|
||||
function fourOhFour (options) {
|
||||
const { logger, disableRequestLogging } = options
|
||||
|
||||
// 404 router, used for handling encapsulated 404 handlers
|
||||
const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack })
|
||||
let _onBadUrlHandler = null
|
||||
|
||||
return { router, setNotFoundHandler, setContext, arrange404 }
|
||||
|
||||
function arrange404 (instance) {
|
||||
// Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler
|
||||
instance[kFourOhFourLevelInstance] = instance
|
||||
instance[kCanSetNotFoundHandler] = true
|
||||
// we need to bind instance for the context
|
||||
router.onBadUrl = router.onBadUrl.bind(instance)
|
||||
router.defaultRoute = router.defaultRoute.bind(instance)
|
||||
}
|
||||
|
||||
function basic404 (request, reply) {
|
||||
const { url, method } = request.raw
|
||||
const message = `Route ${method}:${url} not found`
|
||||
if (!disableRequestLogging) {
|
||||
request.log.info(message)
|
||||
}
|
||||
reply.code(404).send({
|
||||
message,
|
||||
error: 'Not Found',
|
||||
statusCode: 404
|
||||
})
|
||||
}
|
||||
|
||||
function createOnBadUrl () {
|
||||
return function onBadUrl (path, req, res) {
|
||||
const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
|
||||
const id = getGenReqId(fourOhFourContext.server, req)
|
||||
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
||||
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
||||
const reply = new Reply(res, request, childLogger)
|
||||
|
||||
_onBadUrlHandler(request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function setContext (instance, context) {
|
||||
const _404Context = Object.assign({}, instance[kFourOhFourContext])
|
||||
_404Context.onSend = context.onSend
|
||||
context[kFourOhFourContext] = _404Context
|
||||
}
|
||||
|
||||
function setNotFoundHandler (opts, handler, avvio, routeHandler) {
|
||||
// First initialization of the fastify root instance
|
||||
if (this[kCanSetNotFoundHandler] === undefined) {
|
||||
this[kCanSetNotFoundHandler] = true
|
||||
}
|
||||
if (this[kFourOhFourContext] === undefined) {
|
||||
this[kFourOhFourContext] = null
|
||||
}
|
||||
|
||||
const _fastify = this
|
||||
const prefix = this[kRoutePrefix] || '/'
|
||||
|
||||
if (this[kCanSetNotFoundHandler] === false) {
|
||||
throw new Error(`Not found handler already set for Fastify instance with prefix: '${prefix}'`)
|
||||
}
|
||||
|
||||
if (typeof opts === 'object') {
|
||||
if (opts.preHandler) {
|
||||
if (Array.isArray(opts.preHandler)) {
|
||||
opts.preHandler = opts.preHandler.map(hook => hook.bind(_fastify))
|
||||
} else {
|
||||
opts.preHandler = opts.preHandler.bind(_fastify)
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.preValidation) {
|
||||
if (Array.isArray(opts.preValidation)) {
|
||||
opts.preValidation = opts.preValidation.map(hook => hook.bind(_fastify))
|
||||
} else {
|
||||
opts.preValidation = opts.preValidation.bind(_fastify)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
handler = opts
|
||||
opts = undefined
|
||||
}
|
||||
opts = opts || {}
|
||||
|
||||
if (handler) {
|
||||
this[kFourOhFourLevelInstance][kCanSetNotFoundHandler] = false
|
||||
handler = handler.bind(this)
|
||||
// update onBadUrl handler
|
||||
_onBadUrlHandler = handler
|
||||
} else {
|
||||
handler = basic404
|
||||
// update onBadUrl handler
|
||||
_onBadUrlHandler = basic404
|
||||
}
|
||||
|
||||
this.after((notHandledErr, done) => {
|
||||
_setNotFoundHandler.call(this, prefix, opts, handler, avvio, routeHandler)
|
||||
done(notHandledErr)
|
||||
})
|
||||
}
|
||||
|
||||
function _setNotFoundHandler (prefix, opts, handler, avvio, routeHandler) {
|
||||
const context = new Context({
|
||||
schema: opts.schema,
|
||||
handler,
|
||||
config: opts.config || {},
|
||||
server: this
|
||||
})
|
||||
|
||||
avvio.once('preReady', () => {
|
||||
const context = this[kFourOhFourContext]
|
||||
for (const hook of lifecycleHooks) {
|
||||
const toSet = this[kHooks][hook]
|
||||
.concat(opts[hook] || [])
|
||||
.map(h => h.bind(this))
|
||||
context[hook] = toSet.length ? toSet : null
|
||||
}
|
||||
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
|
||||
})
|
||||
|
||||
if (this[kFourOhFourContext] !== null && prefix === '/') {
|
||||
Object.assign(this[kFourOhFourContext], context) // Replace the default 404 handler
|
||||
return
|
||||
}
|
||||
|
||||
this[kFourOhFourLevelInstance][kFourOhFourContext] = context
|
||||
|
||||
router.all(prefix + (prefix.endsWith('/') ? '*' : '/*'), routeHandler, context)
|
||||
router.all(prefix, routeHandler, context)
|
||||
}
|
||||
|
||||
function fourOhFourFallBack (req, res) {
|
||||
// if this happen, we have a very bad bug
|
||||
// we might want to do some hard debugging
|
||||
// here, let's print out as much info as
|
||||
// we can
|
||||
const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
|
||||
const id = getGenReqId(fourOhFourContext.server, req)
|
||||
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
||||
|
||||
childLogger.info({ req }, 'incoming request')
|
||||
|
||||
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
||||
const reply = new Reply(res, request, childLogger)
|
||||
|
||||
request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
|
||||
request.log.warn(router.prettyPrint())
|
||||
reply.code(404).send(new FST_ERR_NOT_FOUND())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fourOhFour
|
||||
182
node_modules/fastify/lib/handleRequest.js
generated
vendored
Normal file
182
node_modules/fastify/lib/handleRequest.js
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
'use strict'
|
||||
|
||||
const diagnostics = require('node:diagnostics_channel')
|
||||
const { validate: validateSchema } = require('./validation')
|
||||
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
|
||||
const wrapThenable = require('./wrapThenable')
|
||||
const {
|
||||
kReplyIsError,
|
||||
kRouteContext,
|
||||
kFourOhFourContext,
|
||||
kSupportedHTTPMethods
|
||||
} = require('./symbols')
|
||||
|
||||
const channels = diagnostics.tracingChannel('fastify.request.handler')
|
||||
|
||||
function handleRequest (err, request, reply) {
|
||||
if (reply.sent === true) return
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
const method = request.method
|
||||
|
||||
if (this[kSupportedHTTPMethods].bodyless.has(method)) {
|
||||
handler(request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if (this[kSupportedHTTPMethods].bodywith.has(method)) {
|
||||
const headers = request.headers
|
||||
const contentType = headers['content-type']
|
||||
|
||||
if (contentType === undefined) {
|
||||
const contentLength = headers['content-length']
|
||||
const transferEncoding = headers['transfer-encoding']
|
||||
const isEmptyBody = transferEncoding === undefined &&
|
||||
(contentLength === undefined || contentLength === '0')
|
||||
|
||||
if (isEmptyBody) {
|
||||
// Request has no body to parse
|
||||
handler(request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
request[kRouteContext].contentTypeParser.run('', handler, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
// Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
|
||||
handler(request, reply)
|
||||
}
|
||||
|
||||
function handler (request, reply) {
|
||||
try {
|
||||
if (request[kRouteContext].preValidation !== null) {
|
||||
preValidationHookRunner(
|
||||
request[kRouteContext].preValidation,
|
||||
request,
|
||||
reply,
|
||||
preValidationCallback
|
||||
)
|
||||
} else {
|
||||
preValidationCallback(null, request, reply)
|
||||
}
|
||||
} catch (err) {
|
||||
preValidationCallback(err, request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function preValidationCallback (err, request, reply) {
|
||||
if (reply.sent === true) return
|
||||
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
const validationErr = validateSchema(reply[kRouteContext], request)
|
||||
const isAsync = (validationErr && typeof validationErr.then === 'function') || false
|
||||
|
||||
if (isAsync) {
|
||||
const cb = validationCompleted.bind(null, request, reply)
|
||||
validationErr.then(cb, cb)
|
||||
} else {
|
||||
validationCompleted(request, reply, validationErr)
|
||||
}
|
||||
}
|
||||
|
||||
function validationCompleted (request, reply, validationErr) {
|
||||
if (validationErr) {
|
||||
if (reply[kRouteContext].attachValidation === false) {
|
||||
reply.send(validationErr)
|
||||
return
|
||||
}
|
||||
|
||||
reply.request.validationError = validationErr
|
||||
}
|
||||
|
||||
// preHandler hook
|
||||
if (request[kRouteContext].preHandler !== null) {
|
||||
preHandlerHookRunner(
|
||||
request[kRouteContext].preHandler,
|
||||
request,
|
||||
reply,
|
||||
preHandlerCallback
|
||||
)
|
||||
} else {
|
||||
preHandlerCallback(null, request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function preHandlerCallback (err, request, reply) {
|
||||
if (reply.sent) return
|
||||
|
||||
const context = request[kRouteContext]
|
||||
|
||||
if (!channels.hasSubscribers || context[kFourOhFourContext] === null) {
|
||||
preHandlerCallbackInner(err, request, reply)
|
||||
} else {
|
||||
const store = {
|
||||
request,
|
||||
reply,
|
||||
async: false,
|
||||
route: {
|
||||
url: context.config.url,
|
||||
method: context.config.method
|
||||
}
|
||||
}
|
||||
channels.start.runStores(store, preHandlerCallbackInner, undefined, err, request, reply, store)
|
||||
}
|
||||
}
|
||||
|
||||
function preHandlerCallbackInner (err, request, reply, store) {
|
||||
const context = request[kRouteContext]
|
||||
|
||||
try {
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
if (store) {
|
||||
store.error = err
|
||||
channels.error.publish(store)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
|
||||
try {
|
||||
result = context.handler(request, reply)
|
||||
} catch (err) {
|
||||
if (store) {
|
||||
store.error = err
|
||||
channels.error.publish(store)
|
||||
}
|
||||
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (result !== undefined) {
|
||||
if (result !== null && typeof result.then === 'function') {
|
||||
wrapThenable(result, reply, store)
|
||||
} else {
|
||||
reply.send(result)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (store) channels.end.publish(store)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = handleRequest
|
||||
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }
|
||||
33
node_modules/fastify/lib/headRoute.js
generated
vendored
Normal file
33
node_modules/fastify/lib/headRoute.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict'
|
||||
function headRouteOnSendHandler (req, reply, payload, done) {
|
||||
// If payload is undefined
|
||||
if (payload === undefined) {
|
||||
reply.header('content-length', '0')
|
||||
done(null, null)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof payload.resume === 'function') {
|
||||
payload.on('error', (err) => {
|
||||
reply.log.error({ err }, 'Error on Stream found for HEAD route')
|
||||
})
|
||||
payload.resume()
|
||||
done(null, null)
|
||||
return
|
||||
}
|
||||
|
||||
const size = '' + Buffer.byteLength(payload)
|
||||
|
||||
reply.header('content-length', size)
|
||||
|
||||
done(null, null)
|
||||
}
|
||||
|
||||
function parseHeadOnSendHandlers (onSendHandlers) {
|
||||
if (onSendHandlers == null) return headRouteOnSendHandler
|
||||
return Array.isArray(onSendHandlers) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseHeadOnSendHandlers
|
||||
}
|
||||
429
node_modules/fastify/lib/hooks.js
generated
vendored
Normal file
429
node_modules/fastify/lib/hooks.js
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
'use strict'
|
||||
|
||||
const applicationHooks = [
|
||||
'onRoute',
|
||||
'onRegister',
|
||||
'onReady',
|
||||
'onListen',
|
||||
'preClose',
|
||||
'onClose'
|
||||
]
|
||||
const lifecycleHooks = [
|
||||
'onTimeout',
|
||||
'onRequest',
|
||||
'preParsing',
|
||||
'preValidation',
|
||||
'preSerialization',
|
||||
'preHandler',
|
||||
'onSend',
|
||||
'onResponse',
|
||||
'onError',
|
||||
'onRequestAbort'
|
||||
]
|
||||
const supportedHooks = lifecycleHooks.concat(applicationHooks)
|
||||
const {
|
||||
FST_ERR_HOOK_INVALID_TYPE,
|
||||
FST_ERR_HOOK_INVALID_HANDLER,
|
||||
FST_ERR_SEND_UNDEFINED_ERR,
|
||||
FST_ERR_HOOK_TIMEOUT,
|
||||
FST_ERR_HOOK_NOT_SUPPORTED,
|
||||
AVVIO_ERRORS_MAP,
|
||||
appendStackTrace
|
||||
} = require('./errors')
|
||||
|
||||
const {
|
||||
kChildren,
|
||||
kHooks,
|
||||
kRequestPayloadStream
|
||||
} = require('./symbols')
|
||||
|
||||
function Hooks () {
|
||||
this.onRequest = []
|
||||
this.preParsing = []
|
||||
this.preValidation = []
|
||||
this.preSerialization = []
|
||||
this.preHandler = []
|
||||
this.onResponse = []
|
||||
this.onSend = []
|
||||
this.onError = []
|
||||
this.onRoute = []
|
||||
this.onRegister = []
|
||||
this.onReady = []
|
||||
this.onListen = []
|
||||
this.onTimeout = []
|
||||
this.onRequestAbort = []
|
||||
this.preClose = []
|
||||
}
|
||||
|
||||
Hooks.prototype = Object.create(null)
|
||||
|
||||
Hooks.prototype.validate = function (hook, fn) {
|
||||
if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE()
|
||||
if (Array.isArray(this[hook]) === false) {
|
||||
throw new FST_ERR_HOOK_NOT_SUPPORTED(hook)
|
||||
}
|
||||
if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn))
|
||||
}
|
||||
|
||||
Hooks.prototype.add = function (hook, fn) {
|
||||
this.validate(hook, fn)
|
||||
this[hook].push(fn)
|
||||
}
|
||||
|
||||
function buildHooks (h) {
|
||||
const hooks = new Hooks()
|
||||
hooks.onRequest = h.onRequest.slice()
|
||||
hooks.preParsing = h.preParsing.slice()
|
||||
hooks.preValidation = h.preValidation.slice()
|
||||
hooks.preSerialization = h.preSerialization.slice()
|
||||
hooks.preHandler = h.preHandler.slice()
|
||||
hooks.onSend = h.onSend.slice()
|
||||
hooks.onResponse = h.onResponse.slice()
|
||||
hooks.onError = h.onError.slice()
|
||||
hooks.onRoute = h.onRoute.slice()
|
||||
hooks.onRegister = h.onRegister.slice()
|
||||
hooks.onTimeout = h.onTimeout.slice()
|
||||
hooks.onRequestAbort = h.onRequestAbort.slice()
|
||||
hooks.onReady = []
|
||||
hooks.onListen = []
|
||||
hooks.preClose = []
|
||||
return hooks
|
||||
}
|
||||
|
||||
function hookRunnerApplication (hookName, boot, server, cb) {
|
||||
const hooks = server[kHooks][hookName]
|
||||
let i = 0
|
||||
let c = 0
|
||||
|
||||
next()
|
||||
|
||||
function exit (err) {
|
||||
const hookFnName = hooks[i - 1]?.name
|
||||
const hookFnFragment = hookFnName ? ` "${hookFnName}"` : ''
|
||||
|
||||
if (err) {
|
||||
if (err.code === 'AVV_ERR_READY_TIMEOUT') {
|
||||
err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName, hookFnFragment))
|
||||
} else {
|
||||
err = AVVIO_ERRORS_MAP[err.code] != null
|
||||
? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))
|
||||
: err
|
||||
}
|
||||
|
||||
cb(err)
|
||||
return
|
||||
}
|
||||
cb()
|
||||
}
|
||||
|
||||
function next (err) {
|
||||
if (err) {
|
||||
exit(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (i === hooks.length && c === server[kChildren].length) {
|
||||
if (i === 0 && c === 0) { // speed up start
|
||||
exit()
|
||||
} else {
|
||||
// This is the last function executed for every fastify instance
|
||||
boot(function manageTimeout (err, done) {
|
||||
// this callback is needed by fastify to provide an hook interface without the error
|
||||
// as first parameter and managing it on behalf the user
|
||||
exit(err)
|
||||
|
||||
// this callback is needed by avvio to continue the loading of the next `register` plugins
|
||||
done(err)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (i === hooks.length && c < server[kChildren].length) {
|
||||
const child = server[kChildren][c++]
|
||||
hookRunnerApplication(hookName, boot, child, next)
|
||||
return
|
||||
}
|
||||
|
||||
boot(wrap(hooks[i++], server))
|
||||
next()
|
||||
}
|
||||
|
||||
function wrap (fn, server) {
|
||||
return function (err, done) {
|
||||
if (err) {
|
||||
done(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (fn.length === 1) {
|
||||
try {
|
||||
fn.call(server, done)
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = fn.call(server)
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
ret.then(done, done)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
err = error
|
||||
}
|
||||
|
||||
done(err) // auto done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onListenHookRunner (server) {
|
||||
const hooks = server[kHooks].onListen
|
||||
const hooksLen = hooks.length
|
||||
|
||||
let i = 0
|
||||
let c = 0
|
||||
|
||||
next()
|
||||
|
||||
function next (err) {
|
||||
err && server.log.error(err)
|
||||
|
||||
if (
|
||||
i === hooksLen
|
||||
) {
|
||||
while (c < server[kChildren].length) {
|
||||
const child = server[kChildren][c++]
|
||||
onListenHookRunner(child)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
wrap(hooks[i++], server, next)
|
||||
}
|
||||
|
||||
async function wrap (fn, server, done) {
|
||||
if (fn.length === 1) {
|
||||
try {
|
||||
fn.call(server, done)
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
try {
|
||||
const ret = fn.call(server)
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
ret.then(done, done)
|
||||
return
|
||||
}
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hookRunnerGenerator (iterator) {
|
||||
return function hookRunner (functions, request, reply, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err) {
|
||||
if (err || i === functions.length) {
|
||||
cb(err, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = iterator(functions[i++], request, reply, next)
|
||||
} catch (error) {
|
||||
cb(error, request, reply)
|
||||
return
|
||||
}
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve () {
|
||||
next()
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request, reply)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
function onResponseHookIterator (fn, request, reply, next) {
|
||||
return fn(request, reply, next)
|
||||
}
|
||||
|
||||
const onResponseHookRunner = hookRunnerGenerator(onResponseHookIterator)
|
||||
const preValidationHookRunner = hookRunnerGenerator(hookIterator)
|
||||
const preHandlerHookRunner = hookRunnerGenerator(hookIterator)
|
||||
const onTimeoutHookRunner = hookRunnerGenerator(hookIterator)
|
||||
const onRequestHookRunner = hookRunnerGenerator(hookIterator)
|
||||
|
||||
function onSendHookRunner (functions, request, reply, payload, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err, newPayload) {
|
||||
if (err) {
|
||||
cb(err, request, reply, payload)
|
||||
return
|
||||
}
|
||||
|
||||
if (newPayload !== undefined) {
|
||||
payload = newPayload
|
||||
}
|
||||
|
||||
if (i === functions.length) {
|
||||
cb(null, request, reply, payload)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = functions[i++](request, reply, payload, next)
|
||||
} catch (error) {
|
||||
cb(error, request, reply)
|
||||
return
|
||||
}
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve (newPayload) {
|
||||
next(null, newPayload)
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request, reply, payload)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
const preSerializationHookRunner = onSendHookRunner
|
||||
|
||||
function preParsingHookRunner (functions, request, reply, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err, newPayload) {
|
||||
if (reply.sent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (newPayload !== undefined) {
|
||||
request[kRequestPayloadStream] = newPayload
|
||||
}
|
||||
|
||||
if (err || i === functions.length) {
|
||||
cb(err, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = functions[i++](request, reply, request[kRequestPayloadStream], next)
|
||||
} catch (error) {
|
||||
cb(error, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve (newPayload) {
|
||||
next(null, newPayload)
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request, reply)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
function onRequestAbortHookRunner (functions, request, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err) {
|
||||
if (err || i === functions.length) {
|
||||
cb(err, request)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = functions[i++](request, next)
|
||||
} catch (error) {
|
||||
cb(error, request)
|
||||
return
|
||||
}
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve () {
|
||||
next()
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
function hookIterator (fn, request, reply, next) {
|
||||
if (reply.sent === true) return undefined
|
||||
return fn(request, reply, next)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Hooks,
|
||||
buildHooks,
|
||||
hookRunnerGenerator,
|
||||
preParsingHookRunner,
|
||||
onResponseHookRunner,
|
||||
onSendHookRunner,
|
||||
preSerializationHookRunner,
|
||||
onRequestAbortHookRunner,
|
||||
hookIterator,
|
||||
hookRunnerApplication,
|
||||
onListenHookRunner,
|
||||
preHandlerHookRunner,
|
||||
preValidationHookRunner,
|
||||
onRequestHookRunner,
|
||||
onTimeoutHookRunner,
|
||||
lifecycleHooks,
|
||||
supportedHooks
|
||||
}
|
||||
37
node_modules/fastify/lib/initialConfigValidation.js
generated
vendored
Normal file
37
node_modules/fastify/lib/initialConfigValidation.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const validate = require('./configValidator')
|
||||
const deepClone = require('rfdc')({ circles: true, proto: false })
|
||||
const { FST_ERR_INIT_OPTS_INVALID } = require('./errors')
|
||||
|
||||
function validateInitialConfig (options) {
|
||||
const opts = deepClone(options)
|
||||
|
||||
if (!validate(opts)) {
|
||||
const error = new FST_ERR_INIT_OPTS_INVALID(JSON.stringify(validate.errors.map(e => e.message)))
|
||||
error.errors = validate.errors
|
||||
throw error
|
||||
}
|
||||
|
||||
return deepFreezeObject(opts)
|
||||
}
|
||||
|
||||
function deepFreezeObject (object) {
|
||||
const properties = Object.getOwnPropertyNames(object)
|
||||
|
||||
for (const name of properties) {
|
||||
const value = object[name]
|
||||
|
||||
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
||||
continue
|
||||
}
|
||||
|
||||
object[name] = value && typeof value === 'object' ? deepFreezeObject(value) : value
|
||||
}
|
||||
|
||||
return Object.freeze(object)
|
||||
}
|
||||
|
||||
module.exports = validateInitialConfig
|
||||
module.exports.defaultInitOptions = validate.defaultInitOptions
|
||||
module.exports.utils = { deepFreezeObject }
|
||||
136
node_modules/fastify/lib/logger-factory.js
generated
vendored
Normal file
136
node_modules/fastify/lib/logger-factory.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
|
||||
FST_ERR_LOG_INVALID_LOGGER_CONFIG,
|
||||
FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
|
||||
FST_ERR_LOG_INVALID_LOGGER
|
||||
} = require('./errors')
|
||||
|
||||
/**
|
||||
* Utility for creating a child logger with the appropriate bindings, logger factory
|
||||
* and validation.
|
||||
* @param {object} context
|
||||
* @param {import('../fastify').FastifyBaseLogger} logger
|
||||
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
|
||||
* @param {string} reqId
|
||||
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
|
||||
*
|
||||
* @returns {object} New logger instance, inheriting all parent bindings,
|
||||
* with child bindings added.
|
||||
*/
|
||||
function createChildLogger (context, logger, req, reqId, loggerOpts) {
|
||||
const loggerBindings = {
|
||||
[context.requestIdLogLabel]: reqId
|
||||
}
|
||||
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
|
||||
|
||||
// Optimization: bypass validation if the factory is our own default factory
|
||||
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
|
||||
validateLogger(child, true) // throw if the child is not a valid logger
|
||||
}
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
/** Default factory to create child logger instance
|
||||
*
|
||||
* @param {import('../fastify.js').FastifyBaseLogger} logger
|
||||
* @param {import('../types/logger.js').Bindings} bindings
|
||||
* @param {import('../types/logger.js').ChildLoggerOptions} opts
|
||||
*
|
||||
* @returns {import('../types/logger.js').FastifyBaseLogger}
|
||||
*/
|
||||
function defaultChildLoggerFactory (logger, bindings, opts) {
|
||||
return logger.child(bindings, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a provided logger object meets the requirements
|
||||
* of a Fastify compatible logger.
|
||||
*
|
||||
* @param {object} logger Object to validate.
|
||||
* @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing)
|
||||
*
|
||||
* @returns {boolean} `true` when the logger meets the requirements.
|
||||
*
|
||||
* @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
|
||||
* missing required methods.
|
||||
*/
|
||||
function validateLogger (logger, strict) {
|
||||
const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
|
||||
const missingMethods = logger
|
||||
? methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
|
||||
: methods
|
||||
|
||||
if (!missingMethods.length) {
|
||||
return true
|
||||
} else if ((missingMethods.length === methods.length) && !strict) {
|
||||
return false
|
||||
} else {
|
||||
throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
|
||||
}
|
||||
}
|
||||
|
||||
function createLogger (options) {
|
||||
if (options.logger && options.loggerInstance) {
|
||||
throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED()
|
||||
}
|
||||
|
||||
if (!options.loggerInstance && !options.logger) {
|
||||
const nullLogger = require('abstract-logging')
|
||||
const logger = nullLogger
|
||||
logger.child = () => logger
|
||||
return { logger, hasLogger: false }
|
||||
}
|
||||
|
||||
const { createPinoLogger, serializers } = require('./logger-pino.js')
|
||||
|
||||
// check if the logger instance has all required properties
|
||||
if (validateLogger(options.loggerInstance)) {
|
||||
const logger = createPinoLogger({
|
||||
logger: options.loggerInstance,
|
||||
serializers: Object.assign({}, serializers, options.loggerInstance.serializers)
|
||||
})
|
||||
return { logger, hasLogger: true }
|
||||
}
|
||||
|
||||
// if a logger instance is passed to logger, throw an exception
|
||||
if (validateLogger(options.logger)) {
|
||||
throw FST_ERR_LOG_INVALID_LOGGER_CONFIG()
|
||||
}
|
||||
|
||||
if (options.loggerInstance) {
|
||||
throw FST_ERR_LOG_INVALID_LOGGER_INSTANCE()
|
||||
}
|
||||
|
||||
const localLoggerOptions = {}
|
||||
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
|
||||
Reflect.ownKeys(options.logger).forEach(prop => {
|
||||
Object.defineProperty(localLoggerOptions, prop, {
|
||||
value: options.logger[prop],
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
})
|
||||
}
|
||||
localLoggerOptions.level = localLoggerOptions.level || 'info'
|
||||
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
|
||||
options.logger = localLoggerOptions
|
||||
const logger = createPinoLogger(options.logger)
|
||||
return { logger, hasLogger: true }
|
||||
}
|
||||
|
||||
function now () {
|
||||
const ts = process.hrtime()
|
||||
return (ts[0] * 1e3) + (ts[1] / 1e6)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createChildLogger,
|
||||
defaultChildLoggerFactory,
|
||||
createLogger,
|
||||
validateLogger,
|
||||
now
|
||||
}
|
||||
68
node_modules/fastify/lib/logger-pino.js
generated
vendored
Normal file
68
node_modules/fastify/lib/logger-pino.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Code imported from `pino-http`
|
||||
* Repo: https://github.com/pinojs/pino-http
|
||||
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
|
||||
*/
|
||||
|
||||
const pino = require('pino')
|
||||
const { serializersSym } = pino.symbols
|
||||
const {
|
||||
FST_ERR_LOG_INVALID_DESTINATION
|
||||
} = require('./errors')
|
||||
|
||||
function createPinoLogger (opts) {
|
||||
if (opts.stream && opts.file) {
|
||||
throw new FST_ERR_LOG_INVALID_DESTINATION()
|
||||
} else if (opts.file) {
|
||||
// we do not have stream
|
||||
opts.stream = pino.destination(opts.file)
|
||||
delete opts.file
|
||||
}
|
||||
|
||||
const prevLogger = opts.logger
|
||||
const prevGenReqId = opts.genReqId
|
||||
let logger = null
|
||||
|
||||
if (prevLogger) {
|
||||
opts.logger = undefined
|
||||
opts.genReqId = undefined
|
||||
// we need to tap into pino internals because in v5 it supports
|
||||
// adding serializers in child loggers
|
||||
if (prevLogger[serializersSym]) {
|
||||
opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
|
||||
}
|
||||
logger = prevLogger.child({}, opts)
|
||||
opts.logger = prevLogger
|
||||
opts.genReqId = prevGenReqId
|
||||
} else {
|
||||
logger = pino(opts, opts.stream)
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
const serializers = {
|
||||
req: function asReqValue (req) {
|
||||
return {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
version: req.headers && req.headers['accept-version'],
|
||||
host: req.host,
|
||||
remoteAddress: req.ip,
|
||||
remotePort: req.socket ? req.socket.remotePort : undefined
|
||||
}
|
||||
},
|
||||
err: pino.stdSerializers.err,
|
||||
res: function asResValue (reply) {
|
||||
return {
|
||||
statusCode: reply.statusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
serializers,
|
||||
createPinoLogger
|
||||
}
|
||||
10
node_modules/fastify/lib/noop-set.js
generated
vendored
Normal file
10
node_modules/fastify/lib/noop-set.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function noopSet () {
|
||||
return {
|
||||
[Symbol.iterator]: function * () {},
|
||||
add () {},
|
||||
delete () {},
|
||||
has () { return true }
|
||||
}
|
||||
}
|
||||
90
node_modules/fastify/lib/pluginOverride.js
generated
vendored
Normal file
90
node_modules/fastify/lib/pluginOverride.js
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kAvvioBoot,
|
||||
kChildren,
|
||||
kRoutePrefix,
|
||||
kLogLevel,
|
||||
kLogSerializers,
|
||||
kHooks,
|
||||
kSchemaController,
|
||||
kContentTypeParser,
|
||||
kReply,
|
||||
kRequest,
|
||||
kFourOhFour,
|
||||
kPluginNameChain,
|
||||
kErrorHandlerAlreadySet
|
||||
} = require('./symbols.js')
|
||||
|
||||
const Reply = require('./reply')
|
||||
const Request = require('./request')
|
||||
const SchemaController = require('./schema-controller')
|
||||
const ContentTypeParser = require('./contentTypeParser')
|
||||
const { buildHooks } = require('./hooks')
|
||||
const pluginUtils = require('./pluginUtils')
|
||||
|
||||
// Function that runs the encapsulation magic.
|
||||
// Everything that need to be encapsulated must be handled in this function.
|
||||
module.exports = function override (old, fn, opts) {
|
||||
const shouldSkipOverride = pluginUtils.registerPlugin.call(old, fn)
|
||||
|
||||
const fnName = pluginUtils.getPluginName(fn) || pluginUtils.getFuncPreview(fn)
|
||||
if (shouldSkipOverride) {
|
||||
// after every plugin registration we will enter a new name
|
||||
old[kPluginNameChain].push(fnName)
|
||||
return old
|
||||
}
|
||||
|
||||
const instance = Object.create(old)
|
||||
old[kChildren].push(instance)
|
||||
instance.ready = old[kAvvioBoot].bind(instance)
|
||||
instance[kChildren] = []
|
||||
|
||||
instance[kReply] = Reply.buildReply(instance[kReply])
|
||||
instance[kRequest] = Request.buildRequest(instance[kRequest])
|
||||
|
||||
instance[kContentTypeParser] = ContentTypeParser.helpers.buildContentTypeParser(instance[kContentTypeParser])
|
||||
instance[kHooks] = buildHooks(instance[kHooks])
|
||||
instance[kRoutePrefix] = buildRoutePrefix(instance[kRoutePrefix], opts.prefix)
|
||||
instance[kLogLevel] = opts.logLevel || instance[kLogLevel]
|
||||
instance[kSchemaController] = SchemaController.buildSchemaController(old[kSchemaController])
|
||||
instance.getSchema = instance[kSchemaController].getSchema.bind(instance[kSchemaController])
|
||||
instance.getSchemas = instance[kSchemaController].getSchemas.bind(instance[kSchemaController])
|
||||
|
||||
// Track the registered and loaded plugins since the root instance.
|
||||
// It does not track the current encapsulated plugin.
|
||||
instance[pluginUtils.kRegisteredPlugins] = Object.create(instance[pluginUtils.kRegisteredPlugins])
|
||||
|
||||
// Track the plugin chain since the root instance.
|
||||
// When an non-encapsulated plugin is added, the chain will be updated.
|
||||
instance[kPluginNameChain] = [fnName]
|
||||
instance[kErrorHandlerAlreadySet] = false
|
||||
|
||||
if (instance[kLogSerializers] || opts.logSerializers) {
|
||||
instance[kLogSerializers] = Object.assign(Object.create(instance[kLogSerializers]), opts.logSerializers)
|
||||
}
|
||||
|
||||
if (opts.prefix) {
|
||||
instance[kFourOhFour].arrange404(instance)
|
||||
}
|
||||
|
||||
for (const hook of instance[kHooks].onRegister) hook.call(old, instance, opts)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
function buildRoutePrefix (instancePrefix, pluginPrefix) {
|
||||
if (!pluginPrefix) {
|
||||
return instancePrefix
|
||||
}
|
||||
|
||||
// Ensure that there is a '/' between the prefixes
|
||||
if (instancePrefix.endsWith('/') && pluginPrefix[0] === '/') {
|
||||
// Remove the extra '/' to avoid: '/first//second'
|
||||
pluginPrefix = pluginPrefix.slice(1)
|
||||
} else if (pluginPrefix[0] !== '/') {
|
||||
pluginPrefix = '/' + pluginPrefix
|
||||
}
|
||||
|
||||
return instancePrefix + pluginPrefix
|
||||
}
|
||||
169
node_modules/fastify/lib/pluginUtils.js
generated
vendored
Normal file
169
node_modules/fastify/lib/pluginUtils.js
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
'use strict'
|
||||
|
||||
const semver = require('semver')
|
||||
const assert = require('node:assert')
|
||||
const kRegisteredPlugins = Symbol.for('registered-plugin')
|
||||
const {
|
||||
kTestInternals
|
||||
} = require('./symbols.js')
|
||||
const { exist, existReply, existRequest } = require('./decorate')
|
||||
const {
|
||||
FST_ERR_PLUGIN_VERSION_MISMATCH,
|
||||
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,
|
||||
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
|
||||
} = require('./errors')
|
||||
|
||||
const rcRegex = /-(?:rc|pre|alpha).+$/u
|
||||
|
||||
function getMeta (fn) {
|
||||
return fn[Symbol.for('plugin-meta')]
|
||||
}
|
||||
|
||||
function getPluginName (func) {
|
||||
const display = getDisplayName(func)
|
||||
if (display) {
|
||||
return display
|
||||
}
|
||||
|
||||
// let's see if this is a file, and in that case use that
|
||||
// this is common for plugins
|
||||
const cache = require.cache
|
||||
// cache is undefined inside SEA
|
||||
if (cache) {
|
||||
const keys = Object.keys(cache)
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (cache[key].exports === func) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if not maybe it's a named function, so use that
|
||||
if (func.name) {
|
||||
return func.name
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function getFuncPreview (func) {
|
||||
// takes the first two lines of the function if nothing else works
|
||||
return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ')
|
||||
}
|
||||
|
||||
function getDisplayName (fn) {
|
||||
return fn[Symbol.for('fastify.display-name')]
|
||||
}
|
||||
|
||||
function shouldSkipOverride (fn) {
|
||||
return !!fn[Symbol.for('skip-override')]
|
||||
}
|
||||
|
||||
function checkDependencies (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (!meta) return
|
||||
|
||||
const dependencies = meta.dependencies
|
||||
if (!dependencies) return
|
||||
assert(Array.isArray(dependencies), 'The dependencies should be an array of strings')
|
||||
|
||||
dependencies.forEach(dependency => {
|
||||
assert(
|
||||
this[kRegisteredPlugins].indexOf(dependency) > -1,
|
||||
`The dependency '${dependency}' of plugin '${meta.name}' is not registered`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function checkDecorators (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (!meta) return
|
||||
|
||||
const { decorators, name } = meta
|
||||
if (!decorators) return
|
||||
|
||||
if (decorators.fastify) _checkDecorators(this, 'Fastify', decorators.fastify, name)
|
||||
if (decorators.reply) _checkDecorators(this, 'Reply', decorators.reply, name)
|
||||
if (decorators.request) _checkDecorators(this, 'Request', decorators.request, name)
|
||||
}
|
||||
|
||||
const checks = {
|
||||
Fastify: exist,
|
||||
Request: existRequest,
|
||||
Reply: existReply
|
||||
}
|
||||
|
||||
function _checkDecorators (that, instance, decorators, name) {
|
||||
assert(Array.isArray(decorators), 'The decorators should be an array of strings')
|
||||
|
||||
decorators.forEach(decorator => {
|
||||
const withPluginName = typeof name === 'string' ? ` required by '${name}'` : ''
|
||||
if (!checks[instance].call(that, decorator)) {
|
||||
throw new FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE(decorator, withPluginName, instance)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function checkVersion (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (meta?.fastify == null) return
|
||||
|
||||
const requiredVersion = meta.fastify
|
||||
|
||||
const fastifyRc = rcRegex.test(this.version)
|
||||
if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {
|
||||
// A Fastify release candidate phase is taking place. In order to reduce
|
||||
// the effort needed to test plugins with the RC, we allow plugins targeting
|
||||
// the prior Fastify release to be loaded.
|
||||
return
|
||||
}
|
||||
if (requiredVersion && semver.satisfies(this.version, requiredVersion, { includePrerelease: fastifyRc }) === false) {
|
||||
// We are not in a release candidate phase. Thus, we must honor the semver
|
||||
// ranges defined by the plugin's metadata. Which is to say, if the plugin
|
||||
// expects an older version of Fastify than the _current_ version, we will
|
||||
// throw an error.
|
||||
throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version)
|
||||
}
|
||||
}
|
||||
|
||||
function registerPluginName (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (!meta) return
|
||||
|
||||
const name = meta.name
|
||||
if (!name) return
|
||||
this[kRegisteredPlugins].push(name)
|
||||
return name
|
||||
}
|
||||
|
||||
function checkPluginHealthiness (fn, pluginName) {
|
||||
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
|
||||
throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
function registerPlugin (fn) {
|
||||
const pluginName = registerPluginName.call(this, fn) || getPluginName(fn)
|
||||
checkPluginHealthiness.call(this, fn, pluginName)
|
||||
checkVersion.call(this, fn)
|
||||
checkDecorators.call(this, fn)
|
||||
checkDependencies.call(this, fn)
|
||||
return shouldSkipOverride(fn)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPluginName,
|
||||
getFuncPreview,
|
||||
kRegisteredPlugins,
|
||||
getDisplayName,
|
||||
registerPlugin
|
||||
}
|
||||
|
||||
module.exports[kTestInternals] = {
|
||||
shouldSkipOverride,
|
||||
getMeta,
|
||||
checkDecorators,
|
||||
checkDependencies
|
||||
}
|
||||
23
node_modules/fastify/lib/promise.js
generated
vendored
Normal file
23
node_modules/fastify/lib/promise.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
const { kTestInternals } = require('./symbols')
|
||||
|
||||
function withResolvers () {
|
||||
let res, rej
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
res = resolve
|
||||
rej = reject
|
||||
})
|
||||
return { promise, resolve: res, reject: rej }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// TODO(20.x): remove when node@20 is not supported
|
||||
withResolvers: typeof Promise.withResolvers === 'function'
|
||||
? Promise.withResolvers.bind(Promise) // Promise.withResolvers must bind to itself
|
||||
/* c8 ignore next */
|
||||
: withResolvers, // Tested using the kTestInternals
|
||||
[kTestInternals]: {
|
||||
withResolvers
|
||||
}
|
||||
}
|
||||
942
node_modules/fastify/lib/reply.js
generated
vendored
Normal file
942
node_modules/fastify/lib/reply.js
generated
vendored
Normal file
@@ -0,0 +1,942 @@
|
||||
'use strict'
|
||||
|
||||
const eos = require('node:stream').finished
|
||||
const Readable = require('node:stream').Readable
|
||||
|
||||
const {
|
||||
kFourOhFourContext,
|
||||
kReplyErrorHandlerCalled,
|
||||
kReplyHijacked,
|
||||
kReplyStartTime,
|
||||
kReplyEndTime,
|
||||
kReplySerializer,
|
||||
kReplySerializerDefault,
|
||||
kReplyIsError,
|
||||
kReplyHeaders,
|
||||
kReplyTrailers,
|
||||
kReplyHasStatusCode,
|
||||
kReplyIsRunningOnErrorHook,
|
||||
kReplyNextErrorHandler,
|
||||
kDisableRequestLogging,
|
||||
kSchemaResponse,
|
||||
kReplyCacheSerializeFns,
|
||||
kSchemaController,
|
||||
kOptions,
|
||||
kRouteContext
|
||||
} = require('./symbols.js')
|
||||
const {
|
||||
onSendHookRunner,
|
||||
onResponseHookRunner,
|
||||
preHandlerHookRunner,
|
||||
preSerializationHookRunner
|
||||
} = require('./hooks')
|
||||
|
||||
const internals = require('./handleRequest')[Symbol.for('internals')]
|
||||
const loggerUtils = require('./logger-factory')
|
||||
const now = loggerUtils.now
|
||||
const { handleError } = require('./error-handler')
|
||||
const { getSchemaSerializer } = require('./schemas')
|
||||
|
||||
const CONTENT_TYPE = {
|
||||
JSON: 'application/json; charset=utf-8',
|
||||
PLAIN: 'text/plain; charset=utf-8',
|
||||
OCTET: 'application/octet-stream'
|
||||
}
|
||||
const {
|
||||
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
|
||||
FST_ERR_REP_RESPONSE_BODY_CONSUMED,
|
||||
FST_ERR_REP_READABLE_STREAM_LOCKED,
|
||||
FST_ERR_REP_ALREADY_SENT,
|
||||
FST_ERR_SEND_INSIDE_ONERR,
|
||||
FST_ERR_BAD_STATUS_CODE,
|
||||
FST_ERR_BAD_TRAILER_NAME,
|
||||
FST_ERR_BAD_TRAILER_VALUE,
|
||||
FST_ERR_MISSING_SERIALIZATION_FN,
|
||||
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN,
|
||||
FST_ERR_DEC_UNDECLARED
|
||||
} = require('./errors')
|
||||
const decorators = require('./decorate')
|
||||
|
||||
const toString = Object.prototype.toString
|
||||
|
||||
function Reply (res, request, log) {
|
||||
this.raw = res
|
||||
this[kReplySerializer] = null
|
||||
this[kReplyErrorHandlerCalled] = false
|
||||
this[kReplyIsError] = false
|
||||
this[kReplyIsRunningOnErrorHook] = false
|
||||
this.request = request
|
||||
this[kReplyHeaders] = {}
|
||||
this[kReplyTrailers] = null
|
||||
this[kReplyHasStatusCode] = false
|
||||
this[kReplyStartTime] = undefined
|
||||
this.log = log
|
||||
}
|
||||
Reply.props = []
|
||||
|
||||
Object.defineProperties(Reply.prototype, {
|
||||
[kRouteContext]: {
|
||||
get () {
|
||||
return this.request[kRouteContext]
|
||||
}
|
||||
},
|
||||
elapsedTime: {
|
||||
get () {
|
||||
if (this[kReplyStartTime] === undefined) {
|
||||
return 0
|
||||
}
|
||||
return (this[kReplyEndTime] || now()) - this[kReplyStartTime]
|
||||
}
|
||||
},
|
||||
server: {
|
||||
get () {
|
||||
return this.request[kRouteContext].server
|
||||
}
|
||||
},
|
||||
sent: {
|
||||
enumerable: true,
|
||||
get () {
|
||||
// We are checking whether reply was hijacked or the response has ended.
|
||||
return (this[kReplyHijacked] || this.raw.writableEnded) === true
|
||||
}
|
||||
},
|
||||
statusCode: {
|
||||
get () {
|
||||
return this.raw.statusCode
|
||||
},
|
||||
set (value) {
|
||||
this.code(value)
|
||||
}
|
||||
},
|
||||
routeOptions: {
|
||||
get () {
|
||||
return this.request.routeOptions
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reply.prototype.writeEarlyHints = function (hints, callback) {
|
||||
this.raw.writeEarlyHints(hints, callback)
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.hijack = function () {
|
||||
this[kReplyHijacked] = true
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.send = function (payload) {
|
||||
if (this[kReplyIsRunningOnErrorHook]) {
|
||||
throw new FST_ERR_SEND_INSIDE_ONERR()
|
||||
}
|
||||
|
||||
if (this.sent === true) {
|
||||
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) })
|
||||
return this
|
||||
}
|
||||
|
||||
if (this[kReplyIsError] || payload instanceof Error) {
|
||||
this[kReplyIsError] = false
|
||||
onErrorHook(this, payload, onSendHook)
|
||||
return this
|
||||
}
|
||||
|
||||
if (payload === undefined) {
|
||||
onSendHook(this, payload)
|
||||
return this
|
||||
}
|
||||
|
||||
const contentType = this.getHeader('content-type')
|
||||
const hasContentType = contentType !== undefined
|
||||
|
||||
if (payload !== null) {
|
||||
if (
|
||||
// node:stream
|
||||
typeof payload.pipe === 'function' ||
|
||||
// node:stream/web
|
||||
typeof payload.getReader === 'function' ||
|
||||
// Response
|
||||
toString.call(payload) === '[object Response]'
|
||||
) {
|
||||
onSendHook(this, payload)
|
||||
return this
|
||||
}
|
||||
|
||||
if (payload.buffer instanceof ArrayBuffer) {
|
||||
if (!hasContentType) {
|
||||
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
|
||||
}
|
||||
const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
|
||||
onSendHook(this, payloadToSend)
|
||||
return this
|
||||
}
|
||||
|
||||
if (!hasContentType && typeof payload === 'string') {
|
||||
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.PLAIN
|
||||
onSendHook(this, payload)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
if (this[kReplySerializer] !== null) {
|
||||
if (typeof payload !== 'string') {
|
||||
preSerializationHook(this, payload)
|
||||
return this
|
||||
}
|
||||
payload = this[kReplySerializer](payload)
|
||||
|
||||
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
|
||||
} else if (!hasContentType || contentType.indexOf('json') !== -1) {
|
||||
if (!hasContentType) {
|
||||
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
||||
} else if (contentType.indexOf('charset') === -1) {
|
||||
// If user doesn't set charset, we will set charset to utf-8
|
||||
const customContentType = contentType.trim()
|
||||
if (customContentType.endsWith(';')) {
|
||||
// custom content-type is ended with ';'
|
||||
this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
|
||||
} else {
|
||||
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof payload !== 'string') {
|
||||
preSerializationHook(this, payload)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
onSendHook(this, payload)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.getHeader = function (key) {
|
||||
key = key.toLowerCase()
|
||||
const value = this[kReplyHeaders][key]
|
||||
return value !== undefined ? value : this.raw.getHeader(key)
|
||||
}
|
||||
|
||||
Reply.prototype.getHeaders = function () {
|
||||
return {
|
||||
...this.raw.getHeaders(),
|
||||
...this[kReplyHeaders]
|
||||
}
|
||||
}
|
||||
|
||||
Reply.prototype.hasHeader = function (key) {
|
||||
key = key.toLowerCase()
|
||||
|
||||
return this[kReplyHeaders][key] !== undefined || this.raw.hasHeader(key)
|
||||
}
|
||||
|
||||
Reply.prototype.removeHeader = function (key) {
|
||||
// Node.js does not like headers with keys set to undefined,
|
||||
// so we have to delete the key.
|
||||
delete this[kReplyHeaders][key.toLowerCase()]
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.header = function (key, value = '') {
|
||||
key = key.toLowerCase()
|
||||
|
||||
if (this[kReplyHeaders][key] && key === 'set-cookie') {
|
||||
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.2
|
||||
if (typeof this[kReplyHeaders][key] === 'string') {
|
||||
this[kReplyHeaders][key] = [this[kReplyHeaders][key]]
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
Array.prototype.push.apply(this[kReplyHeaders][key], value)
|
||||
} else {
|
||||
this[kReplyHeaders][key].push(value)
|
||||
}
|
||||
} else {
|
||||
this[kReplyHeaders][key] = value
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.headers = function (headers) {
|
||||
const keys = Object.keys(headers)
|
||||
for (let i = 0; i !== keys.length; ++i) {
|
||||
const key = keys[i]
|
||||
this.header(key, headers[key])
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer#directives
|
||||
// https://datatracker.ietf.org/doc/html/rfc7230.html#chunked.trailer.part
|
||||
const INVALID_TRAILERS = new Set([
|
||||
'transfer-encoding',
|
||||
'content-length',
|
||||
'host',
|
||||
'cache-control',
|
||||
'max-forwards',
|
||||
'te',
|
||||
'authorization',
|
||||
'set-cookie',
|
||||
'content-encoding',
|
||||
'content-type',
|
||||
'content-range',
|
||||
'trailer'
|
||||
])
|
||||
|
||||
Reply.prototype.trailer = function (key, fn) {
|
||||
key = key.toLowerCase()
|
||||
if (INVALID_TRAILERS.has(key)) {
|
||||
throw new FST_ERR_BAD_TRAILER_NAME(key)
|
||||
}
|
||||
if (typeof fn !== 'function') {
|
||||
throw new FST_ERR_BAD_TRAILER_VALUE(key, typeof fn)
|
||||
}
|
||||
if (this[kReplyTrailers] === null) this[kReplyTrailers] = {}
|
||||
this[kReplyTrailers][key] = fn
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.hasTrailer = function (key) {
|
||||
return this[kReplyTrailers]?.[key.toLowerCase()] !== undefined
|
||||
}
|
||||
|
||||
Reply.prototype.removeTrailer = function (key) {
|
||||
if (this[kReplyTrailers] === null) return this
|
||||
this[kReplyTrailers][key.toLowerCase()] = undefined
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.code = function (code) {
|
||||
const statusCode = +code
|
||||
if (!(statusCode >= 100 && statusCode <= 599)) {
|
||||
throw new FST_ERR_BAD_STATUS_CODE(code || String(code))
|
||||
}
|
||||
|
||||
this.raw.statusCode = statusCode
|
||||
this[kReplyHasStatusCode] = true
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.status = Reply.prototype.code
|
||||
|
||||
Reply.prototype.getSerializationFunction = function (schemaOrStatus, contentType) {
|
||||
let serialize
|
||||
|
||||
if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') {
|
||||
if (typeof contentType === 'string') {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]?.[contentType]
|
||||
} else {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]
|
||||
}
|
||||
} else if (typeof schemaOrStatus === 'object') {
|
||||
serialize = this[kRouteContext][kReplyCacheSerializeFns]?.get(schemaOrStatus)
|
||||
}
|
||||
|
||||
return serialize
|
||||
}
|
||||
|
||||
Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null, contentType = null) {
|
||||
const { request } = this
|
||||
const { method, url } = request
|
||||
|
||||
// Check if serialize function already compiled
|
||||
if (this[kRouteContext][kReplyCacheSerializeFns]?.has(schema)) {
|
||||
return this[kRouteContext][kReplyCacheSerializeFns].get(schema)
|
||||
}
|
||||
|
||||
const serializerCompiler = this[kRouteContext].serializerCompiler ||
|
||||
this.server[kSchemaController].serializerCompiler ||
|
||||
(
|
||||
// We compile the schemas if no custom serializerCompiler is provided
|
||||
// nor set
|
||||
this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
|
||||
this.server[kSchemaController].serializerCompiler
|
||||
)
|
||||
|
||||
const serializeFn = serializerCompiler({
|
||||
schema,
|
||||
method,
|
||||
url,
|
||||
httpStatus,
|
||||
contentType
|
||||
})
|
||||
|
||||
// We create a WeakMap to compile the schema only once
|
||||
// Its done lazily to avoid add overhead by creating the WeakMap
|
||||
// if it is not used
|
||||
// TODO: Explore a central cache for all the schemas shared across
|
||||
// encapsulated contexts
|
||||
if (this[kRouteContext][kReplyCacheSerializeFns] == null) {
|
||||
this[kRouteContext][kReplyCacheSerializeFns] = new WeakMap()
|
||||
}
|
||||
|
||||
this[kRouteContext][kReplyCacheSerializeFns].set(schema, serializeFn)
|
||||
|
||||
return serializeFn
|
||||
}
|
||||
|
||||
Reply.prototype.serializeInput = function (input, schema, httpStatus, contentType) {
|
||||
const possibleContentType = httpStatus
|
||||
let serialize
|
||||
httpStatus = typeof schema === 'string' || typeof schema === 'number'
|
||||
? schema
|
||||
: httpStatus
|
||||
|
||||
contentType = httpStatus && possibleContentType !== httpStatus
|
||||
? possibleContentType
|
||||
: contentType
|
||||
|
||||
if (httpStatus != null) {
|
||||
if (contentType != null) {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]?.[contentType]
|
||||
} else {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]
|
||||
}
|
||||
|
||||
if (serialize == null) {
|
||||
if (contentType) throw new FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN(httpStatus, contentType)
|
||||
throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
|
||||
}
|
||||
} else {
|
||||
// Check if serialize function already compiled
|
||||
if (this[kRouteContext][kReplyCacheSerializeFns]?.has(schema)) {
|
||||
serialize = this[kRouteContext][kReplyCacheSerializeFns].get(schema)
|
||||
} else {
|
||||
serialize = this.compileSerializationSchema(schema, httpStatus, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
return serialize(input)
|
||||
}
|
||||
|
||||
Reply.prototype.serialize = function (payload) {
|
||||
if (this[kReplySerializer] !== null) {
|
||||
return this[kReplySerializer](payload)
|
||||
} else {
|
||||
if (this[kRouteContext] && this[kRouteContext][kReplySerializerDefault]) {
|
||||
return this[kRouteContext][kReplySerializerDefault](payload, this.raw.statusCode)
|
||||
} else {
|
||||
return serialize(this[kRouteContext], payload, this.raw.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reply.prototype.serializer = function (fn) {
|
||||
this[kReplySerializer] = fn
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.type = function (type) {
|
||||
this[kReplyHeaders]['content-type'] = type
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.redirect = function (url, code) {
|
||||
if (!code) {
|
||||
code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
|
||||
}
|
||||
|
||||
return this.header('location', url).code(code).send()
|
||||
}
|
||||
|
||||
Reply.prototype.callNotFound = function () {
|
||||
notFound(this)
|
||||
return this
|
||||
}
|
||||
|
||||
// Make reply a thenable, so it could be used with async/await.
|
||||
// See
|
||||
// - https://github.com/fastify/fastify/issues/1864 for the discussions
|
||||
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then for the signature
|
||||
Reply.prototype.then = function (fulfilled, rejected) {
|
||||
if (this.sent) {
|
||||
fulfilled()
|
||||
return
|
||||
}
|
||||
|
||||
eos(this.raw, (err) => {
|
||||
// We must not treat ERR_STREAM_PREMATURE_CLOSE as
|
||||
// an error because it is created by eos, not by the stream.
|
||||
if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||
if (rejected) {
|
||||
rejected(err)
|
||||
} else {
|
||||
this.log && this.log.warn('unhandled rejection on reply.then')
|
||||
}
|
||||
} else {
|
||||
fulfilled()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reply.prototype.getDecorator = function (name) {
|
||||
if (!decorators.hasKey(this, name) && !decorators.exist(this, name)) {
|
||||
throw new FST_ERR_DEC_UNDECLARED(name, 'reply')
|
||||
}
|
||||
|
||||
const decorator = this[name]
|
||||
if (typeof decorator === 'function') {
|
||||
return decorator.bind(this)
|
||||
}
|
||||
|
||||
return decorator
|
||||
}
|
||||
|
||||
function preSerializationHook (reply, payload) {
|
||||
if (reply[kRouteContext].preSerialization !== null) {
|
||||
preSerializationHookRunner(
|
||||
reply[kRouteContext].preSerialization,
|
||||
reply.request,
|
||||
reply,
|
||||
payload,
|
||||
preSerializationHookEnd
|
||||
)
|
||||
} else {
|
||||
preSerializationHookEnd(null, undefined, reply, payload)
|
||||
}
|
||||
}
|
||||
|
||||
function preSerializationHookEnd (err, _request, reply, payload) {
|
||||
if (err != null) {
|
||||
onErrorHook(reply, err)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (reply[kReplySerializer] !== null) {
|
||||
payload = reply[kReplySerializer](payload)
|
||||
} else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) {
|
||||
payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode)
|
||||
} else {
|
||||
payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode, reply[kReplyHeaders]['content-type'])
|
||||
}
|
||||
} catch (e) {
|
||||
wrapSerializationError(e, reply)
|
||||
onErrorHook(reply, e)
|
||||
return
|
||||
}
|
||||
|
||||
onSendHook(reply, payload)
|
||||
}
|
||||
|
||||
function wrapSerializationError (error, reply) {
|
||||
error.serialization = reply[kRouteContext].config
|
||||
}
|
||||
|
||||
function onSendHook (reply, payload) {
|
||||
if (reply[kRouteContext].onSend !== null) {
|
||||
onSendHookRunner(
|
||||
reply[kRouteContext].onSend,
|
||||
reply.request,
|
||||
reply,
|
||||
payload,
|
||||
wrapOnSendEnd
|
||||
)
|
||||
} else {
|
||||
onSendEnd(reply, payload)
|
||||
}
|
||||
}
|
||||
|
||||
function wrapOnSendEnd (err, request, reply, payload) {
|
||||
if (err != null) {
|
||||
onErrorHook(reply, err)
|
||||
} else {
|
||||
onSendEnd(reply, payload)
|
||||
}
|
||||
}
|
||||
|
||||
function safeWriteHead (reply, statusCode) {
|
||||
const res = reply.raw
|
||||
try {
|
||||
res.writeHead(statusCode, reply[kReplyHeaders])
|
||||
} catch (err) {
|
||||
if (err.code === 'ERR_HTTP_HEADERS_SENT') {
|
||||
reply.log.warn(`Reply was already sent, did you forget to "return reply" in the "${reply.request.raw.url}" (${reply.request.raw.method}) route?`)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function onSendEnd (reply, payload) {
|
||||
const res = reply.raw
|
||||
const req = reply.request
|
||||
|
||||
// we check if we need to update the trailers header and set it
|
||||
if (reply[kReplyTrailers] !== null) {
|
||||
const trailerHeaders = Object.keys(reply[kReplyTrailers])
|
||||
let header = ''
|
||||
for (const trailerName of trailerHeaders) {
|
||||
if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
|
||||
header += ' '
|
||||
header += trailerName
|
||||
}
|
||||
// it must be chunked for trailer to work
|
||||
reply.header('Transfer-Encoding', 'chunked')
|
||||
reply.header('Trailer', header.trim())
|
||||
}
|
||||
|
||||
// since Response contain status code, headers and body,
|
||||
// we need to update the status, add the headers and use it's body as payload
|
||||
// before continuing
|
||||
if (toString.call(payload) === '[object Response]') {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/status
|
||||
if (typeof payload.status === 'number') {
|
||||
reply.code(payload.status)
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
|
||||
if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') {
|
||||
for (const [headerName, headerValue] of payload.headers) {
|
||||
reply.header(headerName, headerValue)
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/body
|
||||
if (payload.body !== null) {
|
||||
if (payload.bodyUsed) {
|
||||
throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED()
|
||||
}
|
||||
}
|
||||
// Keep going, body is either null or ReadableStream
|
||||
payload = payload.body
|
||||
}
|
||||
const statusCode = res.statusCode
|
||||
|
||||
if (payload === undefined || payload === null) {
|
||||
// according to https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
|
||||
// we cannot send a content-length for 304 and 204, and all status code
|
||||
// < 200
|
||||
// A sender MUST NOT send a Content-Length header field in any message
|
||||
// that contains a Transfer-Encoding header field.
|
||||
// For HEAD we don't overwrite the `content-length`
|
||||
if (statusCode >= 200 && statusCode !== 204 && statusCode !== 304 && req.method !== 'HEAD' && reply[kReplyTrailers] === null) {
|
||||
reply[kReplyHeaders]['content-length'] = '0'
|
||||
}
|
||||
|
||||
safeWriteHead(reply, statusCode)
|
||||
sendTrailer(payload, res, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if ((statusCode >= 100 && statusCode < 200) || statusCode === 204) {
|
||||
// Responses without a content body must not send content-type
|
||||
// or content-length headers.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6.
|
||||
reply.removeHeader('content-type')
|
||||
reply.removeHeader('content-length')
|
||||
safeWriteHead(reply, statusCode)
|
||||
sendTrailer(undefined, res, reply)
|
||||
if (typeof payload.resume === 'function') {
|
||||
payload.on('error', noop)
|
||||
payload.resume()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// node:stream
|
||||
if (typeof payload.pipe === 'function') {
|
||||
sendStream(payload, res, reply)
|
||||
return
|
||||
}
|
||||
|
||||
// node:stream/web
|
||||
if (typeof payload.getReader === 'function') {
|
||||
sendWebStream(payload, res, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
|
||||
throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
|
||||
}
|
||||
|
||||
if (reply[kReplyTrailers] === null) {
|
||||
const contentLength = reply[kReplyHeaders]['content-length']
|
||||
if (!contentLength ||
|
||||
(req.raw.method !== 'HEAD' &&
|
||||
Number(contentLength) !== Buffer.byteLength(payload)
|
||||
)
|
||||
) {
|
||||
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
||||
}
|
||||
}
|
||||
|
||||
safeWriteHead(reply, statusCode)
|
||||
// write payload first
|
||||
res.write(payload)
|
||||
// then send trailers
|
||||
sendTrailer(payload, res, reply)
|
||||
}
|
||||
|
||||
function logStreamError (logger, err, res) {
|
||||
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||
if (!logger[kDisableRequestLogging]) {
|
||||
logger.info({ res }, 'stream closed prematurely')
|
||||
}
|
||||
} else {
|
||||
logger.warn({ err }, 'response terminated with an error with headers already sent')
|
||||
}
|
||||
}
|
||||
|
||||
function sendWebStream (payload, res, reply) {
|
||||
if (payload.locked) {
|
||||
throw FST_ERR_REP_READABLE_STREAM_LOCKED()
|
||||
}
|
||||
const nodeStream = Readable.fromWeb(payload)
|
||||
sendStream(nodeStream, res, reply)
|
||||
}
|
||||
|
||||
function sendStream (payload, res, reply) {
|
||||
let sourceOpen = true
|
||||
let errorLogged = false
|
||||
|
||||
// set trailer when stream ended
|
||||
sendStreamTrailer(payload, res, reply)
|
||||
|
||||
eos(payload, { readable: true, writable: false }, function (err) {
|
||||
sourceOpen = false
|
||||
if (err != null) {
|
||||
if (res.headersSent || reply.request.raw.aborted === true) {
|
||||
if (!errorLogged) {
|
||||
errorLogged = true
|
||||
logStreamError(reply.log, err, reply)
|
||||
}
|
||||
res.destroy()
|
||||
} else {
|
||||
onErrorHook(reply, err)
|
||||
}
|
||||
}
|
||||
// there is nothing to do if there is not an error
|
||||
})
|
||||
|
||||
eos(res, function (err) {
|
||||
if (sourceOpen) {
|
||||
if (err != null && res.headersSent && !errorLogged) {
|
||||
errorLogged = true
|
||||
logStreamError(reply.log, err, res)
|
||||
}
|
||||
if (typeof payload.destroy === 'function') {
|
||||
payload.destroy()
|
||||
} else if (typeof payload.close === 'function') {
|
||||
payload.close(noop)
|
||||
} else if (typeof payload.abort === 'function') {
|
||||
payload.abort()
|
||||
} else {
|
||||
reply.log.warn('stream payload does not end properly')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// streams will error asynchronously, and we want to handle that error
|
||||
// appropriately, e.g. a 404 for a missing file. So we cannot use
|
||||
// writeHead, and we need to resort to setHeader, which will trigger
|
||||
// a writeHead when there is data to send.
|
||||
if (!res.headersSent) {
|
||||
for (const key in reply[kReplyHeaders]) {
|
||||
res.setHeader(key, reply[kReplyHeaders][key])
|
||||
}
|
||||
} else {
|
||||
reply.log.warn('response will send, but you shouldn\'t use res.writeHead in stream mode')
|
||||
}
|
||||
payload.pipe(res)
|
||||
}
|
||||
|
||||
function sendTrailer (payload, res, reply) {
|
||||
if (reply[kReplyTrailers] === null) {
|
||||
// when no trailer, we close the stream
|
||||
res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
||||
return
|
||||
}
|
||||
const trailerHeaders = Object.keys(reply[kReplyTrailers])
|
||||
const trailers = {}
|
||||
let handled = 0
|
||||
let skipped = true
|
||||
function send () {
|
||||
// add trailers when all handler handled
|
||||
/* istanbul ignore else */
|
||||
if (handled === 0) {
|
||||
res.addTrailers(trailers)
|
||||
// we need to properly close the stream
|
||||
// after trailers sent
|
||||
res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
||||
}
|
||||
}
|
||||
|
||||
for (const trailerName of trailerHeaders) {
|
||||
if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
|
||||
skipped = false
|
||||
handled--
|
||||
|
||||
function cb (err, value) {
|
||||
// TODO: we may protect multiple callback calls
|
||||
// or mixing async-await with callback
|
||||
handled++
|
||||
|
||||
// we can safely ignore error for trailer
|
||||
// since it does affect the client
|
||||
// we log in here only for debug usage
|
||||
if (err) reply.log.debug(err)
|
||||
else trailers[trailerName] = value
|
||||
|
||||
// we push the check to the end of event
|
||||
// loop, so the registration continue to
|
||||
// process.
|
||||
process.nextTick(send)
|
||||
}
|
||||
|
||||
const result = reply[kReplyTrailers][trailerName](reply, payload, cb)
|
||||
if (typeof result === 'object' && typeof result.then === 'function') {
|
||||
result.then((v) => cb(null, v), cb)
|
||||
}
|
||||
}
|
||||
|
||||
// when all trailers are skipped
|
||||
// we need to close the stream
|
||||
if (skipped) res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
||||
}
|
||||
|
||||
function sendStreamTrailer (payload, res, reply) {
|
||||
if (reply[kReplyTrailers] === null) return
|
||||
payload.on('end', () => sendTrailer(null, res, reply))
|
||||
}
|
||||
|
||||
function onErrorHook (reply, error, cb) {
|
||||
if (reply[kRouteContext].onError !== null && !reply[kReplyNextErrorHandler]) {
|
||||
reply[kReplyIsRunningOnErrorHook] = true
|
||||
onSendHookRunner(
|
||||
reply[kRouteContext].onError,
|
||||
reply.request,
|
||||
reply,
|
||||
error,
|
||||
() => handleError(reply, error, cb)
|
||||
)
|
||||
} else {
|
||||
handleError(reply, error, cb)
|
||||
}
|
||||
}
|
||||
|
||||
function setupResponseListeners (reply) {
|
||||
reply[kReplyStartTime] = now()
|
||||
|
||||
const onResFinished = err => {
|
||||
reply[kReplyEndTime] = now()
|
||||
reply.raw.removeListener('finish', onResFinished)
|
||||
reply.raw.removeListener('error', onResFinished)
|
||||
|
||||
const ctx = reply[kRouteContext]
|
||||
|
||||
if (ctx && ctx.onResponse !== null) {
|
||||
onResponseHookRunner(
|
||||
ctx.onResponse,
|
||||
reply.request,
|
||||
reply,
|
||||
onResponseCallback
|
||||
)
|
||||
} else {
|
||||
onResponseCallback(err, reply.request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
reply.raw.on('finish', onResFinished)
|
||||
reply.raw.on('error', onResFinished)
|
||||
}
|
||||
|
||||
function onResponseCallback (err, request, reply) {
|
||||
if (reply.log[kDisableRequestLogging]) {
|
||||
return
|
||||
}
|
||||
|
||||
const responseTime = reply.elapsedTime
|
||||
|
||||
if (err != null) {
|
||||
reply.log.error({
|
||||
res: reply,
|
||||
err,
|
||||
responseTime
|
||||
}, 'request errored')
|
||||
return
|
||||
}
|
||||
|
||||
reply.log.info({
|
||||
res: reply,
|
||||
responseTime
|
||||
}, 'request completed')
|
||||
}
|
||||
|
||||
function buildReply (R) {
|
||||
const props = R.props.slice()
|
||||
|
||||
function _Reply (res, request, log) {
|
||||
this.raw = res
|
||||
this[kReplyIsError] = false
|
||||
this[kReplyErrorHandlerCalled] = false
|
||||
this[kReplyHijacked] = false
|
||||
this[kReplySerializer] = null
|
||||
this.request = request
|
||||
this[kReplyHeaders] = {}
|
||||
this[kReplyTrailers] = null
|
||||
this[kReplyStartTime] = undefined
|
||||
this[kReplyEndTime] = undefined
|
||||
this.log = log
|
||||
|
||||
let prop
|
||||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
prop = props[i]
|
||||
this[prop.key] = prop.value
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(_Reply.prototype, R.prototype)
|
||||
Object.setPrototypeOf(_Reply, R)
|
||||
_Reply.parent = R
|
||||
_Reply.props = props
|
||||
return _Reply
|
||||
}
|
||||
|
||||
function notFound (reply) {
|
||||
if (reply[kRouteContext][kFourOhFourContext] === null) {
|
||||
reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
|
||||
reply.code(404).send('404 Not Found')
|
||||
return
|
||||
}
|
||||
|
||||
reply.request[kRouteContext] = reply[kRouteContext][kFourOhFourContext]
|
||||
|
||||
// preHandler hook
|
||||
if (reply[kRouteContext].preHandler !== null) {
|
||||
preHandlerHookRunner(
|
||||
reply[kRouteContext].preHandler,
|
||||
reply.request,
|
||||
reply,
|
||||
internals.preHandlerCallback
|
||||
)
|
||||
} else {
|
||||
internals.preHandlerCallback(null, reply.request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs when a payload that is not a string|buffer|stream or null
|
||||
* should be serialized to be streamed to the response.
|
||||
* This is the default serializer that can be customized by the user using the replySerializer
|
||||
*
|
||||
* @param {object} context the request context
|
||||
* @param {object} data the JSON payload to serialize
|
||||
* @param {number} statusCode the http status code
|
||||
* @param {string} [contentType] the reply content type
|
||||
* @returns {string} the serialized payload
|
||||
*/
|
||||
function serialize (context, data, statusCode, contentType) {
|
||||
const fnSerialize = getSchemaSerializer(context, statusCode, contentType)
|
||||
if (fnSerialize) {
|
||||
return fnSerialize(data)
|
||||
}
|
||||
return JSON.stringify(data)
|
||||
}
|
||||
|
||||
function noop () { }
|
||||
|
||||
module.exports = Reply
|
||||
module.exports.buildReply = buildReply
|
||||
module.exports.setupResponseListeners = setupResponseListeners
|
||||
52
node_modules/fastify/lib/reqIdGenFactory.js
generated
vendored
Normal file
52
node_modules/fastify/lib/reqIdGenFactory.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @callback GenerateRequestId
|
||||
* @param {Object} req
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} [requestIdHeader]
|
||||
* @param {GenerateRequestId} [optGenReqId]
|
||||
* @returns {GenerateRequestId}
|
||||
*/
|
||||
function reqIdGenFactory (requestIdHeader, optGenReqId) {
|
||||
const genReqId = optGenReqId || buildDefaultGenReqId()
|
||||
|
||||
if (requestIdHeader) {
|
||||
return buildOptionalHeaderReqId(requestIdHeader, genReqId)
|
||||
}
|
||||
|
||||
return genReqId
|
||||
}
|
||||
|
||||
function getGenReqId (contextServer, req) {
|
||||
return contextServer.genReqId(req)
|
||||
}
|
||||
|
||||
function buildDefaultGenReqId () {
|
||||
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
||||
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
||||
// This is very likely to happen in real-world applications, hence the limit is enforced.
|
||||
// Growing beyond this value will make the id generation slower and cause a deopt.
|
||||
// In the worst cases, it will become a float, losing accuracy.
|
||||
const maxInt = 2147483647
|
||||
|
||||
let nextReqId = 0
|
||||
return function defaultGenReqId () {
|
||||
nextReqId = (nextReqId + 1) & maxInt
|
||||
return `req-${nextReqId.toString(36)}`
|
||||
}
|
||||
}
|
||||
|
||||
function buildOptionalHeaderReqId (requestIdHeader, genReqId) {
|
||||
return function (req) {
|
||||
return req.headers[requestIdHeader] || genReqId(req)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGenReqId,
|
||||
reqIdGenFactory
|
||||
}
|
||||
369
node_modules/fastify/lib/request.js
generated
vendored
Normal file
369
node_modules/fastify/lib/request.js
generated
vendored
Normal file
@@ -0,0 +1,369 @@
|
||||
'use strict'
|
||||
|
||||
const proxyAddr = require('@fastify/proxy-addr')
|
||||
const {
|
||||
kHasBeenDecorated,
|
||||
kSchemaBody,
|
||||
kSchemaHeaders,
|
||||
kSchemaParams,
|
||||
kSchemaQuerystring,
|
||||
kSchemaController,
|
||||
kOptions,
|
||||
kRequestCacheValidateFns,
|
||||
kRouteContext,
|
||||
kRequestOriginalUrl
|
||||
} = require('./symbols')
|
||||
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors')
|
||||
const decorators = require('./decorate')
|
||||
|
||||
const HTTP_PART_SYMBOL_MAP = {
|
||||
body: kSchemaBody,
|
||||
headers: kSchemaHeaders,
|
||||
params: kSchemaParams,
|
||||
querystring: kSchemaQuerystring,
|
||||
query: kSchemaQuerystring
|
||||
}
|
||||
|
||||
function Request (id, params, req, query, log, context) {
|
||||
this.id = id
|
||||
this[kRouteContext] = context
|
||||
this.params = params
|
||||
this.raw = req
|
||||
this.query = query
|
||||
this.log = log
|
||||
this.body = undefined
|
||||
}
|
||||
Request.props = []
|
||||
|
||||
function getTrustProxyFn (tp) {
|
||||
if (typeof tp === 'function') {
|
||||
return tp
|
||||
}
|
||||
if (tp === true) {
|
||||
// Support trusting everything
|
||||
return null
|
||||
}
|
||||
if (typeof tp === 'number') {
|
||||
// Support trusting hop count
|
||||
return function (a, i) { return i < tp }
|
||||
}
|
||||
if (typeof tp === 'string') {
|
||||
// Support comma-separated tps
|
||||
const values = tp.split(',').map(it => it.trim())
|
||||
return proxyAddr.compile(values)
|
||||
}
|
||||
return proxyAddr.compile(tp)
|
||||
}
|
||||
|
||||
function buildRequest (R, trustProxy) {
|
||||
if (trustProxy) {
|
||||
return buildRequestWithTrustProxy(R, trustProxy)
|
||||
}
|
||||
|
||||
return buildRegularRequest(R)
|
||||
}
|
||||
|
||||
function buildRegularRequest (R) {
|
||||
const props = R.props.slice()
|
||||
function _Request (id, params, req, query, log, context) {
|
||||
this.id = id
|
||||
this[kRouteContext] = context
|
||||
this.params = params
|
||||
this.raw = req
|
||||
this.query = query
|
||||
this.log = log
|
||||
this.body = undefined
|
||||
|
||||
let prop
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
prop = props[i]
|
||||
this[prop.key] = prop.value
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(_Request.prototype, R.prototype)
|
||||
Object.setPrototypeOf(_Request, R)
|
||||
_Request.props = props
|
||||
_Request.parent = R
|
||||
|
||||
return _Request
|
||||
}
|
||||
|
||||
function getLastEntryInMultiHeaderValue (headerValue) {
|
||||
// we use the last one if the header is set more than once
|
||||
const lastIndex = headerValue.lastIndexOf(',')
|
||||
return lastIndex === -1 ? headerValue.trim() : headerValue.slice(lastIndex + 1).trim()
|
||||
}
|
||||
|
||||
function buildRequestWithTrustProxy (R, trustProxy) {
|
||||
const _Request = buildRegularRequest(R)
|
||||
const proxyFn = getTrustProxyFn(trustProxy)
|
||||
|
||||
// This is a more optimized version of decoration
|
||||
_Request[kHasBeenDecorated] = true
|
||||
|
||||
Object.defineProperties(_Request.prototype, {
|
||||
ip: {
|
||||
get () {
|
||||
const addrs = proxyAddr.all(this.raw, proxyFn)
|
||||
return addrs[addrs.length - 1]
|
||||
}
|
||||
},
|
||||
ips: {
|
||||
get () {
|
||||
return proxyAddr.all(this.raw, proxyFn)
|
||||
}
|
||||
},
|
||||
host: {
|
||||
get () {
|
||||
if (this.ip !== undefined && this.headers['x-forwarded-host']) {
|
||||
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
|
||||
}
|
||||
/**
|
||||
* The last fallback supports the following cases:
|
||||
* 1. http.requireHostHeader === false
|
||||
* 2. HTTP/1.0 without a Host Header
|
||||
* 3. Headers schema that may remove the Host Header
|
||||
*/
|
||||
return this.headers.host ?? this.headers[':authority'] ?? ''
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
get () {
|
||||
if (this.headers['x-forwarded-proto']) {
|
||||
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-proto'])
|
||||
}
|
||||
if (this.socket) {
|
||||
return this.socket.encrypted ? 'https' : 'http'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return _Request
|
||||
}
|
||||
|
||||
function assertsRequestDecoration (request, name) {
|
||||
if (!decorators.hasKey(request, name) && !decorators.exist(request, name)) {
|
||||
throw new FST_ERR_DEC_UNDECLARED(name, 'request')
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(Request.prototype, {
|
||||
server: {
|
||||
get () {
|
||||
return this[kRouteContext].server
|
||||
}
|
||||
},
|
||||
url: {
|
||||
get () {
|
||||
return this.raw.url
|
||||
}
|
||||
},
|
||||
originalUrl: {
|
||||
get () {
|
||||
/* istanbul ignore else */
|
||||
if (!this[kRequestOriginalUrl]) {
|
||||
this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url
|
||||
}
|
||||
return this[kRequestOriginalUrl]
|
||||
}
|
||||
},
|
||||
method: {
|
||||
get () {
|
||||
return this.raw.method
|
||||
}
|
||||
},
|
||||
routeOptions: {
|
||||
get () {
|
||||
const context = this[kRouteContext]
|
||||
const routeLimit = context._parserOptions.limit
|
||||
const serverLimit = context.server.initialConfig.bodyLimit
|
||||
const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined
|
||||
const options = {
|
||||
method: context.config?.method,
|
||||
url: context.config?.url,
|
||||
bodyLimit: (routeLimit || serverLimit),
|
||||
attachValidation: context.attachValidation,
|
||||
logLevel: context.logLevel,
|
||||
exposeHeadRoute: context.exposeHeadRoute,
|
||||
prefixTrailingSlash: context.prefixTrailingSlash,
|
||||
handler: context.handler,
|
||||
config: context.config,
|
||||
schema: context.schema,
|
||||
version
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
},
|
||||
is404: {
|
||||
get () {
|
||||
return this[kRouteContext].config?.url === undefined
|
||||
}
|
||||
},
|
||||
socket: {
|
||||
get () {
|
||||
return this.raw.socket
|
||||
}
|
||||
},
|
||||
ip: {
|
||||
get () {
|
||||
if (this.socket) {
|
||||
return this.socket.remoteAddress
|
||||
}
|
||||
}
|
||||
},
|
||||
host: {
|
||||
get () {
|
||||
/**
|
||||
* The last fallback supports the following cases:
|
||||
* 1. http.requireHostHeader === false
|
||||
* 2. HTTP/1.0 without a Host Header
|
||||
* 3. Headers schema that may remove the Host Header
|
||||
*/
|
||||
return this.raw.headers.host ?? this.raw.headers[':authority'] ?? ''
|
||||
}
|
||||
},
|
||||
hostname: {
|
||||
get () {
|
||||
return this.host.split(':', 1)[0]
|
||||
}
|
||||
},
|
||||
port: {
|
||||
get () {
|
||||
// first try taking port from host
|
||||
const portFromHost = parseInt(this.host.split(':').slice(-1)[0])
|
||||
if (!isNaN(portFromHost)) {
|
||||
return portFromHost
|
||||
}
|
||||
// now fall back to port from host/:authority header
|
||||
const host = (this.headers.host ?? this.headers[':authority'] ?? '')
|
||||
const portFromHeader = parseInt(host.split(':').slice(-1)[0])
|
||||
if (!isNaN(portFromHeader)) {
|
||||
return portFromHeader
|
||||
}
|
||||
// fall back to null
|
||||
return null
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
get () {
|
||||
if (this.socket) {
|
||||
return this.socket.encrypted ? 'https' : 'http'
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
get () {
|
||||
if (this.additionalHeaders) {
|
||||
return Object.assign({}, this.raw.headers, this.additionalHeaders)
|
||||
}
|
||||
return this.raw.headers
|
||||
},
|
||||
set (headers) {
|
||||
this.additionalHeaders = headers
|
||||
}
|
||||
},
|
||||
getValidationFunction: {
|
||||
value: function (httpPartOrSchema) {
|
||||
if (typeof httpPartOrSchema === 'string') {
|
||||
const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema]
|
||||
return this[kRouteContext][symbol]
|
||||
} else if (typeof httpPartOrSchema === 'object') {
|
||||
return this[kRouteContext][kRequestCacheValidateFns]?.get(httpPartOrSchema)
|
||||
}
|
||||
}
|
||||
},
|
||||
compileValidationSchema: {
|
||||
value: function (schema, httpPart = null) {
|
||||
const { method, url } = this
|
||||
|
||||
if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) {
|
||||
return this[kRouteContext][kRequestCacheValidateFns].get(schema)
|
||||
}
|
||||
|
||||
const validatorCompiler = this[kRouteContext].validatorCompiler ||
|
||||
this.server[kSchemaController].validatorCompiler ||
|
||||
(
|
||||
// We compile the schemas if no custom validatorCompiler is provided
|
||||
// nor set
|
||||
this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
|
||||
this.server[kSchemaController].validatorCompiler
|
||||
)
|
||||
|
||||
const validateFn = validatorCompiler({
|
||||
schema,
|
||||
method,
|
||||
url,
|
||||
httpPart
|
||||
})
|
||||
|
||||
// We create a WeakMap to compile the schema only once
|
||||
// Its done lazily to avoid add overhead by creating the WeakMap
|
||||
// if it is not used
|
||||
// TODO: Explore a central cache for all the schemas shared across
|
||||
// encapsulated contexts
|
||||
if (this[kRouteContext][kRequestCacheValidateFns] == null) {
|
||||
this[kRouteContext][kRequestCacheValidateFns] = new WeakMap()
|
||||
}
|
||||
|
||||
this[kRouteContext][kRequestCacheValidateFns].set(schema, validateFn)
|
||||
|
||||
return validateFn
|
||||
}
|
||||
},
|
||||
validateInput: {
|
||||
value: function (input, schema, httpPart) {
|
||||
httpPart = typeof schema === 'string' ? schema : httpPart
|
||||
|
||||
const symbol = (httpPart != null && typeof httpPart === 'string') && HTTP_PART_SYMBOL_MAP[httpPart]
|
||||
let validate
|
||||
|
||||
if (symbol) {
|
||||
// Validate using the HTTP Request Part schema
|
||||
validate = this[kRouteContext][symbol]
|
||||
}
|
||||
|
||||
// We cannot compile if the schema is missed
|
||||
if (validate == null && (schema == null ||
|
||||
typeof schema !== 'object' ||
|
||||
Array.isArray(schema))
|
||||
) {
|
||||
throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart)
|
||||
}
|
||||
|
||||
if (validate == null) {
|
||||
if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) {
|
||||
validate = this[kRouteContext][kRequestCacheValidateFns].get(schema)
|
||||
} else {
|
||||
// We proceed to compile if there's no validate function yet
|
||||
validate = this.compileValidationSchema(schema, httpPart)
|
||||
}
|
||||
}
|
||||
|
||||
return validate(input)
|
||||
}
|
||||
},
|
||||
getDecorator: {
|
||||
value: function (name) {
|
||||
assertsRequestDecoration(this, name)
|
||||
|
||||
const decorator = this[name]
|
||||
if (typeof decorator === 'function') {
|
||||
return decorator.bind(this)
|
||||
}
|
||||
|
||||
return decorator
|
||||
}
|
||||
},
|
||||
setDecorator: {
|
||||
value: function (name, value) {
|
||||
assertsRequestDecoration(this, name)
|
||||
|
||||
this[name] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = Request
|
||||
module.exports.buildRequest = buildRequest
|
||||
620
node_modules/fastify/lib/route.js
generated
vendored
Normal file
620
node_modules/fastify/lib/route.js
generated
vendored
Normal file
@@ -0,0 +1,620 @@
|
||||
'use strict'
|
||||
|
||||
const FindMyWay = require('find-my-way')
|
||||
const Context = require('./context')
|
||||
const handleRequest = require('./handleRequest')
|
||||
const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
|
||||
const { normalizeSchema } = require('./schemas')
|
||||
const { parseHeadOnSendHandlers } = require('./headRoute')
|
||||
|
||||
const {
|
||||
compileSchemasForValidation,
|
||||
compileSchemasForSerialization
|
||||
} = require('./validation')
|
||||
|
||||
const {
|
||||
FST_ERR_SCH_VALIDATION_BUILD,
|
||||
FST_ERR_SCH_SERIALIZATION_BUILD,
|
||||
FST_ERR_DUPLICATED_ROUTE,
|
||||
FST_ERR_INVALID_URL,
|
||||
FST_ERR_HOOK_INVALID_HANDLER,
|
||||
FST_ERR_ROUTE_OPTIONS_NOT_OBJ,
|
||||
FST_ERR_ROUTE_DUPLICATED_HANDLER,
|
||||
FST_ERR_ROUTE_HANDLER_NOT_FN,
|
||||
FST_ERR_ROUTE_MISSING_HANDLER,
|
||||
FST_ERR_ROUTE_METHOD_NOT_SUPPORTED,
|
||||
FST_ERR_ROUTE_METHOD_INVALID,
|
||||
FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED,
|
||||
FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT,
|
||||
FST_ERR_HOOK_INVALID_ASYNC_HANDLER
|
||||
} = require('./errors')
|
||||
|
||||
const { FSTDEP022 } = require('./warnings')
|
||||
|
||||
const {
|
||||
kRoutePrefix,
|
||||
kSupportedHTTPMethods,
|
||||
kLogLevel,
|
||||
kLogSerializers,
|
||||
kHooks,
|
||||
kSchemaController,
|
||||
kOptions,
|
||||
kReplySerializerDefault,
|
||||
kReplyIsError,
|
||||
kRequestPayloadStream,
|
||||
kDisableRequestLogging,
|
||||
kSchemaErrorFormatter,
|
||||
kErrorHandler,
|
||||
kHasBeenDecorated,
|
||||
kRequestAcceptVersion,
|
||||
kRouteByFastify,
|
||||
kRouteContext
|
||||
} = require('./symbols.js')
|
||||
const { buildErrorHandler } = require('./error-handler')
|
||||
const { createChildLogger } = require('./logger-factory.js')
|
||||
const { getGenReqId } = require('./reqIdGenFactory.js')
|
||||
|
||||
const routerKeys = [
|
||||
'allowUnsafeRegex',
|
||||
'buildPrettyMeta',
|
||||
'caseSensitive',
|
||||
'constraints',
|
||||
'defaultRoute',
|
||||
'ignoreDuplicateSlashes',
|
||||
'ignoreTrailingSlash',
|
||||
'maxParamLength',
|
||||
'onBadUrl',
|
||||
'querystringParser',
|
||||
'useSemicolonDelimiter'
|
||||
]
|
||||
|
||||
function buildRouting (options) {
|
||||
const router = FindMyWay(options.config)
|
||||
|
||||
let avvio
|
||||
let fourOhFour
|
||||
let logger
|
||||
let hasLogger
|
||||
let setupResponseListeners
|
||||
let throwIfAlreadyStarted
|
||||
let disableRequestLogging
|
||||
let ignoreTrailingSlash
|
||||
let ignoreDuplicateSlashes
|
||||
let return503OnClosing
|
||||
let globalExposeHeadRoutes
|
||||
let keepAliveConnections
|
||||
|
||||
let closing = false
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {import('../fastify').FastifyServerOptions} options
|
||||
* @param {*} fastifyArgs
|
||||
*/
|
||||
setup (options, fastifyArgs) {
|
||||
avvio = fastifyArgs.avvio
|
||||
fourOhFour = fastifyArgs.fourOhFour
|
||||
logger = fastifyArgs.logger
|
||||
hasLogger = fastifyArgs.hasLogger
|
||||
setupResponseListeners = fastifyArgs.setupResponseListeners
|
||||
throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
|
||||
|
||||
globalExposeHeadRoutes = options.exposeHeadRoutes
|
||||
disableRequestLogging = options.disableRequestLogging
|
||||
ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash
|
||||
ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes
|
||||
return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true
|
||||
keepAliveConnections = fastifyArgs.keepAliveConnections
|
||||
},
|
||||
routing: router.lookup.bind(router), // router func to find the right handler to call
|
||||
route, // configure a route in the fastify instance
|
||||
hasRoute,
|
||||
prepareRoute,
|
||||
routeHandler,
|
||||
closeRoutes: () => { closing = true },
|
||||
printRoutes: router.prettyPrint.bind(router),
|
||||
addConstraintStrategy,
|
||||
hasConstraintStrategy,
|
||||
isAsyncConstraint,
|
||||
findRoute
|
||||
}
|
||||
|
||||
function addConstraintStrategy (strategy) {
|
||||
throwIfAlreadyStarted('Cannot add constraint strategy!')
|
||||
return router.addConstraintStrategy(strategy)
|
||||
}
|
||||
|
||||
function hasConstraintStrategy (strategyName) {
|
||||
return router.hasConstraintStrategy(strategyName)
|
||||
}
|
||||
|
||||
function isAsyncConstraint () {
|
||||
return router.constrainer.asyncStrategiesInUse.size > 0
|
||||
}
|
||||
|
||||
// Convert shorthand to extended route declaration
|
||||
function prepareRoute ({ method, url, options, handler, isFastify }) {
|
||||
if (typeof url !== 'string') {
|
||||
throw new FST_ERR_INVALID_URL(typeof url)
|
||||
}
|
||||
|
||||
if (!handler && typeof options === 'function') {
|
||||
handler = options // for support over direct function calls such as fastify.get() options are reused as the handler
|
||||
options = {}
|
||||
} else if (handler && typeof handler === 'function') {
|
||||
if (Object.prototype.toString.call(options) !== '[object Object]') {
|
||||
throw new FST_ERR_ROUTE_OPTIONS_NOT_OBJ(method, url)
|
||||
} else if (options.handler) {
|
||||
if (typeof options.handler === 'function') {
|
||||
throw new FST_ERR_ROUTE_DUPLICATED_HANDLER(method, url)
|
||||
} else {
|
||||
throw new FST_ERR_ROUTE_HANDLER_NOT_FN(method, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options = Object.assign({}, options, {
|
||||
method,
|
||||
url,
|
||||
path: url,
|
||||
handler: handler || (options && options.handler)
|
||||
})
|
||||
|
||||
return route.call(this, { options, isFastify })
|
||||
}
|
||||
|
||||
function hasRoute ({ options }) {
|
||||
const normalizedMethod = options.method?.toUpperCase() ?? ''
|
||||
return router.hasRoute(
|
||||
normalizedMethod,
|
||||
options.url || '',
|
||||
options.constraints
|
||||
)
|
||||
}
|
||||
|
||||
function findRoute (options) {
|
||||
const route = router.find(
|
||||
options.method,
|
||||
options.url || '',
|
||||
options.constraints
|
||||
)
|
||||
if (route) {
|
||||
// we must reduce the expose surface, otherwise
|
||||
// we provide the ability for the user to modify
|
||||
// all the route and server information in runtime
|
||||
return {
|
||||
handler: route.handler,
|
||||
params: route.params,
|
||||
searchParams: route.searchParams
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route management
|
||||
* @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
|
||||
*/
|
||||
function route ({ options, isFastify }) {
|
||||
throwIfAlreadyStarted('Cannot add route!')
|
||||
|
||||
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
|
||||
const opts = { ...options }
|
||||
|
||||
const path = opts.url || opts.path || ''
|
||||
|
||||
if (!opts.handler) {
|
||||
throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
|
||||
}
|
||||
|
||||
if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
|
||||
throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path)
|
||||
}
|
||||
|
||||
validateBodyLimitOption(opts.bodyLimit)
|
||||
|
||||
const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes
|
||||
|
||||
let isGetRoute = false
|
||||
let isHeadRoute = false
|
||||
|
||||
if (Array.isArray(opts.method)) {
|
||||
for (let i = 0; i < opts.method.length; ++i) {
|
||||
opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i])
|
||||
validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema)
|
||||
|
||||
isGetRoute = opts.method.includes('GET')
|
||||
isHeadRoute = opts.method.includes('HEAD')
|
||||
}
|
||||
} else {
|
||||
opts.method = normalizeAndValidateMethod.call(this, opts.method)
|
||||
validateSchemaBodyOption.call(this, opts.method, path, opts.schema)
|
||||
|
||||
isGetRoute = opts.method === 'GET'
|
||||
isHeadRoute = opts.method === 'HEAD'
|
||||
}
|
||||
|
||||
// we need to clone a set of initial options for HEAD route
|
||||
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
|
||||
|
||||
const prefix = this[kRoutePrefix]
|
||||
|
||||
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
|
||||
switch (opts.prefixTrailingSlash) {
|
||||
case 'slash':
|
||||
addNewRoute.call(this, { path, isFastify })
|
||||
break
|
||||
case 'no-slash':
|
||||
addNewRoute.call(this, { path: '', isFastify })
|
||||
break
|
||||
case 'both':
|
||||
default:
|
||||
addNewRoute.call(this, { path: '', isFastify })
|
||||
// If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
|
||||
if (ignoreTrailingSlash !== true && (ignoreDuplicateSlashes !== true || !prefix.endsWith('/'))) {
|
||||
addNewRoute.call(this, { path, prefixing: true, isFastify })
|
||||
}
|
||||
}
|
||||
} else if (path[0] === '/' && prefix.endsWith('/')) {
|
||||
// Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
|
||||
addNewRoute.call(this, { path: path.slice(1), isFastify })
|
||||
} else {
|
||||
addNewRoute.call(this, { path, isFastify })
|
||||
}
|
||||
|
||||
// chainable api
|
||||
return this
|
||||
|
||||
function addNewRoute ({ path, prefixing = false, isFastify = false }) {
|
||||
const url = prefix + path
|
||||
|
||||
opts.url = url
|
||||
opts.path = url
|
||||
opts.routePath = path
|
||||
opts.prefix = prefix
|
||||
opts.logLevel = opts.logLevel || this[kLogLevel]
|
||||
|
||||
if (this[kLogSerializers] || opts.logSerializers) {
|
||||
opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
|
||||
}
|
||||
|
||||
if (opts.attachValidation == null) {
|
||||
opts.attachValidation = false
|
||||
}
|
||||
|
||||
if (prefixing === false) {
|
||||
// run 'onRoute' hooks
|
||||
for (const hook of this[kHooks].onRoute) {
|
||||
hook.call(this, opts)
|
||||
}
|
||||
}
|
||||
|
||||
for (const hook of lifecycleHooks) {
|
||||
if (opts && hook in opts) {
|
||||
if (Array.isArray(opts[hook])) {
|
||||
for (const func of opts[hook]) {
|
||||
if (typeof func !== 'function') {
|
||||
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(func))
|
||||
}
|
||||
|
||||
if (hook === 'onSend' || hook === 'preSerialization' || hook === 'onError' || hook === 'preParsing') {
|
||||
if (func.constructor.name === 'AsyncFunction' && func.length === 4) {
|
||||
throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else if (hook === 'onRequestAbort') {
|
||||
if (func.constructor.name === 'AsyncFunction' && func.length !== 1) {
|
||||
throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else {
|
||||
if (func.constructor.name === 'AsyncFunction' && func.length === 3) {
|
||||
throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (opts[hook] !== undefined && typeof opts[hook] !== 'function') {
|
||||
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(opts[hook]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const constraints = opts.constraints || {}
|
||||
const config = {
|
||||
...opts.config,
|
||||
url,
|
||||
method: opts.method
|
||||
}
|
||||
|
||||
const context = new Context({
|
||||
schema: opts.schema,
|
||||
handler: opts.handler.bind(this),
|
||||
config,
|
||||
errorHandler: opts.errorHandler,
|
||||
childLoggerFactory: opts.childLoggerFactory,
|
||||
bodyLimit: opts.bodyLimit,
|
||||
logLevel: opts.logLevel,
|
||||
logSerializers: opts.logSerializers,
|
||||
attachValidation: opts.attachValidation,
|
||||
schemaErrorFormatter: opts.schemaErrorFormatter,
|
||||
replySerializer: this[kReplySerializerDefault],
|
||||
validatorCompiler: opts.validatorCompiler,
|
||||
serializerCompiler: opts.serializerCompiler,
|
||||
exposeHeadRoute: shouldExposeHead,
|
||||
prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'),
|
||||
server: this,
|
||||
isFastify
|
||||
})
|
||||
|
||||
const headHandler = router.findRoute('HEAD', opts.url, constraints)
|
||||
const hasHEADHandler = headHandler !== null
|
||||
|
||||
try {
|
||||
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
|
||||
} catch (error) {
|
||||
// any route insertion error created by fastify can be safely ignore
|
||||
// because it only duplicate route for head
|
||||
if (!context[kRouteByFastify]) {
|
||||
const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route`)
|
||||
if (isDuplicatedRoute) {
|
||||
throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
this.after((notHandledErr, done) => {
|
||||
// Send context async
|
||||
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
|
||||
context._parserOptions.limit = opts.bodyLimit || null
|
||||
context.logLevel = opts.logLevel
|
||||
context.logSerializers = opts.logSerializers
|
||||
context.attachValidation = opts.attachValidation
|
||||
context[kReplySerializerDefault] = this[kReplySerializerDefault]
|
||||
context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
|
||||
|
||||
// Run hooks and more
|
||||
avvio.once('preReady', () => {
|
||||
for (const hook of lifecycleHooks) {
|
||||
const toSet = this[kHooks][hook]
|
||||
.concat(opts[hook] || [])
|
||||
.map(h => h.bind(this))
|
||||
context[hook] = toSet.length ? toSet : null
|
||||
}
|
||||
|
||||
// Optimization: avoid encapsulation if no decoration has been done.
|
||||
while (!context.Request[kHasBeenDecorated] && context.Request.parent) {
|
||||
context.Request = context.Request.parent
|
||||
}
|
||||
while (!context.Reply[kHasBeenDecorated] && context.Reply.parent) {
|
||||
context.Reply = context.Reply.parent
|
||||
}
|
||||
|
||||
// Must store the 404 Context in 'preReady' because it is only guaranteed to
|
||||
// be available after all of the plugins and routes have been loaded.
|
||||
fourOhFour.setContext(this, context)
|
||||
|
||||
if (opts.schema) {
|
||||
context.schema = normalizeSchema(context.schema, this.initialConfig)
|
||||
|
||||
const schemaController = this[kSchemaController]
|
||||
if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
|
||||
schemaController.setupValidator(this[kOptions])
|
||||
}
|
||||
try {
|
||||
const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler
|
||||
compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom)
|
||||
} catch (error) {
|
||||
throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
|
||||
}
|
||||
|
||||
if (opts.schema.response && !opts.serializerCompiler) {
|
||||
schemaController.setupSerializer(this[kOptions])
|
||||
}
|
||||
try {
|
||||
compileSchemasForSerialization(context, opts.serializerCompiler || schemaController.serializerCompiler)
|
||||
} catch (error) {
|
||||
throw new FST_ERR_SCH_SERIALIZATION_BUILD(opts.method, url, error.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
done(notHandledErr)
|
||||
})
|
||||
|
||||
// register head route in sync
|
||||
// we must place it after the `this.after`
|
||||
|
||||
if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) {
|
||||
const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend)
|
||||
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request entry point, the routing has already been executed
|
||||
function routeHandler (req, res, params, context, query) {
|
||||
const id = getGenReqId(context.server, req)
|
||||
|
||||
const loggerOpts = {
|
||||
level: context.logLevel
|
||||
}
|
||||
|
||||
if (context.logSerializers) {
|
||||
loggerOpts.serializers = context.logSerializers
|
||||
}
|
||||
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
|
||||
childLogger[kDisableRequestLogging] = disableRequestLogging
|
||||
|
||||
if (closing === true) {
|
||||
/* istanbul ignore next mac, windows */
|
||||
if (req.httpVersionMajor !== 2) {
|
||||
res.setHeader('Connection', 'close')
|
||||
}
|
||||
|
||||
// TODO remove return503OnClosing after Node v18 goes EOL
|
||||
/* istanbul ignore else */
|
||||
if (return503OnClosing) {
|
||||
// On Node v19 we cannot test this behavior as it won't be necessary
|
||||
// anymore. It will close all the idle connections before they reach this
|
||||
// stage.
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': '80'
|
||||
}
|
||||
res.writeHead(503, headers)
|
||||
res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
|
||||
childLogger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// When server.forceCloseConnections is true, we will collect any requests
|
||||
// that have indicated they want persistence so that they can be reaped
|
||||
// on server close. Otherwise, the container is a noop container.
|
||||
const connHeader = String.prototype.toLowerCase.call(req.headers.connection || '')
|
||||
if (connHeader === 'keep-alive') {
|
||||
if (keepAliveConnections.has(req.socket) === false) {
|
||||
keepAliveConnections.add(req.socket)
|
||||
req.socket.on('close', removeTrackedSocket.bind({ keepAliveConnections, socket: req.socket }))
|
||||
}
|
||||
}
|
||||
|
||||
// we revert the changes in defaultRoute
|
||||
if (req.headers[kRequestAcceptVersion] !== undefined) {
|
||||
req.headers['accept-version'] = req.headers[kRequestAcceptVersion]
|
||||
req.headers[kRequestAcceptVersion] = undefined
|
||||
}
|
||||
|
||||
const request = new context.Request(id, params, req, query, childLogger, context)
|
||||
const reply = new context.Reply(res, request, childLogger)
|
||||
if (disableRequestLogging === false) {
|
||||
childLogger.info({ req: request }, 'incoming request')
|
||||
}
|
||||
|
||||
if (hasLogger === true || context.onResponse !== null) {
|
||||
setupResponseListeners(reply)
|
||||
}
|
||||
|
||||
if (context.onRequest !== null) {
|
||||
onRequestHookRunner(
|
||||
context.onRequest,
|
||||
request,
|
||||
reply,
|
||||
runPreParsing
|
||||
)
|
||||
} else {
|
||||
runPreParsing(null, request, reply)
|
||||
}
|
||||
|
||||
if (context.onRequestAbort !== null) {
|
||||
req.on('close', () => {
|
||||
/* istanbul ignore else */
|
||||
if (req.aborted) {
|
||||
onRequestAbortHookRunner(
|
||||
context.onRequestAbort,
|
||||
request,
|
||||
handleOnRequestAbortHooksErrors.bind(null, reply)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (context.onTimeout !== null) {
|
||||
if (!request.raw.socket._meta) {
|
||||
request.raw.socket.on('timeout', handleTimeout)
|
||||
}
|
||||
request.raw.socket._meta = { context, request, reply }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleOnRequestAbortHooksErrors (reply, err) {
|
||||
if (err) {
|
||||
reply.log.error({ err }, 'onRequestAborted hook failed')
|
||||
}
|
||||
}
|
||||
|
||||
function handleTimeout () {
|
||||
const { context, request, reply } = this._meta
|
||||
onTimeoutHookRunner(
|
||||
context.onTimeout,
|
||||
request,
|
||||
reply,
|
||||
noop
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeAndValidateMethod (method) {
|
||||
if (typeof method !== 'string') {
|
||||
throw new FST_ERR_ROUTE_METHOD_INVALID()
|
||||
}
|
||||
method = method.toUpperCase()
|
||||
if (!this[kSupportedHTTPMethods].bodyless.has(method) &&
|
||||
!this[kSupportedHTTPMethods].bodywith.has(method)) {
|
||||
throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method)
|
||||
}
|
||||
|
||||
return method
|
||||
}
|
||||
|
||||
function validateSchemaBodyOption (method, path, schema) {
|
||||
if (this[kSupportedHTTPMethods].bodyless.has(method) && schema?.body) {
|
||||
throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path)
|
||||
}
|
||||
}
|
||||
|
||||
function validateBodyLimitOption (bodyLimit) {
|
||||
if (bodyLimit === undefined) return
|
||||
if (!Number.isInteger(bodyLimit) || bodyLimit <= 0) {
|
||||
throw new FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT(bodyLimit)
|
||||
}
|
||||
}
|
||||
|
||||
function runPreParsing (err, request, reply) {
|
||||
if (reply.sent === true) return
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
request[kRequestPayloadStream] = request.raw
|
||||
|
||||
if (request[kRouteContext].preParsing !== null) {
|
||||
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest.bind(request.server))
|
||||
} else {
|
||||
handleRequest.call(request.server, null, request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function buildRouterOptions (options, defaultOptions) {
|
||||
const routerOptions = options.routerOptions || Object.create(null)
|
||||
|
||||
const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key))
|
||||
|
||||
if (usedDeprecatedOptions.length > 0) {
|
||||
FSTDEP022(usedDeprecatedOptions.join(', '))
|
||||
}
|
||||
|
||||
for (const key of routerKeys) {
|
||||
if (!Object.hasOwn(routerOptions, key)) {
|
||||
routerOptions[key] = options[key] ?? defaultOptions[key]
|
||||
}
|
||||
}
|
||||
|
||||
return routerOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Used within the route handler as a `net.Socket.close` event handler.
|
||||
* The purpose is to remove a socket from the tracked sockets collection when
|
||||
* the socket has naturally timed out.
|
||||
*/
|
||||
function removeTrackedSocket () {
|
||||
this.keepAliveConnections.delete(this.socket)
|
||||
}
|
||||
|
||||
function noop () { }
|
||||
|
||||
module.exports = { buildRouting, validateBodyLimitOption, buildRouterOptions }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user