From a8253e7de5bfca62a5a6942c147c0eb9cb660e0c Mon Sep 17 00:00:00 2001 From: expressgy Date: Wed, 19 Mar 2025 15:54:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 9 +- .prettier.config.cjs | 30 + .prettierignore | 11 + README.md | 7 +- config/index.js | 42 +- doc/fastify-docs/README.md | 58 +- doc/fastify-docs/docs/Benchmarking.md | 9 + doc/fastify-docs/docs/ContentTypeParser.md | 209 +- doc/fastify-docs/docs/Contributing.md | 10 +- doc/fastify-docs/docs/Decorators.md | 181 +- doc/fastify-docs/docs/Encapsulation.md | 130 +- doc/fastify-docs/docs/Errors.md | 35 +- doc/fastify-docs/docs/Fluent-Schema.md | 115 +- doc/fastify-docs/docs/Getting-Started.md | 294 +- doc/fastify-docs/docs/HTTP2.md | 67 +- doc/fastify-docs/docs/Hooks.md | 346 +- doc/fastify-docs/docs/LTS.md | 24 +- doc/fastify-docs/docs/Lifecycle.md | 8 +- doc/fastify-docs/docs/Logging.md | 132 +- doc/fastify-docs/docs/Middleware.md | 20 +- doc/fastify-docs/docs/Migration-Guide-V3.md | 70 +- doc/fastify-docs/docs/Plugins-Guide.md | 374 +- doc/fastify-docs/docs/Plugins.md | 142 +- doc/fastify-docs/docs/Recommendations.md | 37 +- doc/fastify-docs/docs/Reply.md | 307 +- doc/fastify-docs/docs/Request.md | 44 +- doc/fastify-docs/docs/Routes.md | 466 +- doc/fastify-docs/docs/Server.md | 901 ++- doc/fastify-docs/docs/Serverless.md | 87 +- doc/fastify-docs/docs/Testing.md | 266 +- doc/fastify-docs/docs/TypeScript.md | 674 +- .../docs/Validation-and-Serialization.md | 748 +- doc/fastify-docs/docs/Write-Plugin.md | 21 +- drizzle.config.js | 22 + eslint.config.js | 35 + migrations/0000_wandering_marvel_boy.sql | 7 + migrations/meta/0000_snapshot.json | 59 + migrations/meta/_journal.json | 13 + package-lock.json | 6665 +++++++++++++---- package.json | 85 +- src/application.js | 26 +- src/entities/schema.js | 17 + src/plugins/database/index.js | 28 + src/plugins/index.js | 21 +- src/routes/index.js | 16 +- src/services/user.service.js | 35 + src/utils/logger.js | 18 +- src/utils/start.js | 12 +- 48 files changed, 8998 insertions(+), 3935 deletions(-) create mode 100644 .prettier.config.cjs create mode 100644 .prettierignore create mode 100644 drizzle.config.js create mode 100644 eslint.config.js create mode 100644 migrations/0000_wandering_marvel_boy.sql create mode 100644 migrations/meta/0000_snapshot.json create mode 100644 migrations/meta/_journal.json create mode 100644 src/entities/schema.js create mode 100644 src/plugins/database/index.js create mode 100644 src/services/user.service.js diff --git a/.env b/.env index 9106155..e30475a 100644 --- a/.env +++ b/.env @@ -9,4 +9,11 @@ LOG_FILE=./logs/app.log # 插件配置 PLUGIN_NAME=myapp -API_PREFIX=/api/v1 \ No newline at end of file +API_PREFIX=/api/v1 + +# 数据库 +DB_HOST=172.16.1.10 +DB_USER=nie +DB_PASSWORD=Hxl1314521 +DB_NAME=yuheng +DB_PORT=3306 diff --git a/.prettier.config.cjs b/.prettier.config.cjs new file mode 100644 index 0000000..b91f626 --- /dev/null +++ b/.prettier.config.cjs @@ -0,0 +1,30 @@ +// 转换为 CommonJS 格式(重要!) +module.exports = { + printWidth: 100, // 最大行宽 + semi: true, // 强制分号 + singleQuote: true, // 使用单引号 + trailingComma: 'all', // 自动尾逗号 + tabWidth: 4, // 缩进空格数 + + // 新增常用配置 + arrowParens: 'avoid', // 箭头函数单参数省略括号 + bracketSpacing: true, // 对象花括号空格 { key: value } + endOfLine: 'auto', // 自动识别换行符 + jsxBracketSameLine: false, // JSX 闭合标签换行(React 项目适用) + + // 文件类型覆盖规则 + overrides: [ + { + files: '*.md', + options: { + proseWrap: 'always' // Markdown 自动换行 + } + }, + { + files: '*.json', + options: { + tabWidth: 4 // JSON 使用 2 空格缩进 + } + } + ] +}; // 移除结尾分号保持风格统一 \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..2f7901d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +# 忽略环境文件 +.env +*.env + +# 忽略模块类型冲突文件 +*.cjs +*.mjs + +# 忽略的目录 +node_modules/* +doc/* \ No newline at end of file diff --git a/README.md b/README.md index 297be58..3c1a8ef 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ 基于 Fastify 的 Node.js 角色访问控制(RBAC)系统,提供灵活的权限管理能力。 ## 主要特性 + - 🛡️ 基于 JWT 的身份认证 - 🔑 角色-权限层级管理 - 🚦 请求权限验证中间件 @@ -12,12 +13,14 @@ ## 快速开始 ### 前置要求 + - Node.js v18+ - 数据库(PostgreSQL/MySQL) - Redis(用于会话管理) ### 安装步骤 -```bash + +````bash # 克隆仓库 git clone https://github.com/your-repo.git cd rbac-system @@ -55,4 +58,4 @@ npm run dev ├── config/ # 配置文件 │ ├── default.js # 通用配置 ├── package.json # 依赖管理 -``` \ No newline at end of file +```` diff --git a/config/index.js b/config/index.js index 020612a..56c7ae8 100644 --- a/config/index.js +++ b/config/index.js @@ -1,10 +1,9 @@ -import { config } from 'dotenv' -import path from 'path' +import { config } from 'dotenv'; +import path from 'path'; // 加载环境变量 -config({ path: path.resolve(process.cwd(), '.env') }) -console.log("[NODE_ENV]:", process.env.NODE_ENV); - +config({ path: path.resolve(process.cwd(), '.env') }); +console.log('[NODE_ENV]:', process.env.NODE_ENV); // 基础配置 const baseConfig = { @@ -12,7 +11,7 @@ const baseConfig = { server: { port: process.env.PORT || 9000, host: process.env.HOST || 'localhost', - backlog: process.env.BACKLOG || 511 + backlog: process.env.BACKLOG || 511, }, logger: { level: process.env.LOG_LEVEL || 'trace', @@ -21,9 +20,17 @@ const baseConfig = { }, pluginOptions: { name: process.env.PLUGIN_NAME || 'default', - prefix: process.env.API_PREFIX || '/api' - } -} + prefix: process.env.API_PREFIX || '/api', + }, + db: { + host: process.env.DB_HOST || '127.0.0.1', + port: process.env.DB_PORT || 3306, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME || 'yuheng', + ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: true } : null, + }, +}; // 环境特定配置 const envConfig = { @@ -33,27 +40,24 @@ const envConfig = { }, logger: { level: 'warn', - } + }, }, development: { logger: { level: 'trace', console: true, - } - } -} + }, + }, +}; // 使用深度合并代替展开操作符 function deepMerge(target, source) { for (const key of Object.keys(source)) { if (source[key] instanceof Object && key in target) { - Object.assign(source[key], deepMerge(target[key], source[key])) + Object.assign(source[key], deepMerge(target[key], source[key])); } } - return Object.assign({}, target, source) + return Object.assign({}, target, source); } -export default deepMerge( - baseConfig, - envConfig[process.env.NODE_ENV] || envConfig.development -) \ No newline at end of file +export default deepMerge(baseConfig, envConfig[process.env.NODE_ENV] || envConfig.development); diff --git a/doc/fastify-docs/README.md b/doc/fastify-docs/README.md index 4c5ca5e..7cbc35b 100644 --- a/doc/fastify-docs/README.md +++ b/doc/fastify-docs/README.md @@ -1,33 +1,34 @@ # Fastify 文档中文翻译 ### 文档目录 -* [入门](docs/Getting-Started.md) -* [服务器方法](docs/Server.md) -* [路由](docs/Routes.md) -* [日志](docs/Logging.md) -* [中间件](docs/Middleware.md) -* [钩子方法](docs/Hooks.md) -* [装饰器](docs/Decorators.md) -* [验证和序列化](docs/Validation-and-Serialization.md) -* [生命周期](docs/Lifecycle.md) -* [回复](docs/Reply.md) -* [请求](docs/Request.md) -* [错误](docs/Errors.md) -* [Content-Type 解析](docs/ContentTypeParser.md) -* [插件](docs/Plugins.md) -* [测试](docs/Testing.md) -* [基准测试](docs/Benchmarking.md) -* [如何写一个好的插件](docs/Write-Plugin.md) -* [插件指南](docs/Plugins-Guide.md) -* [HTTP2](docs/HTTP2.md) -* [长期支持计划](docs/LTS.md) -* [TypeScript 与类型支持](docs/TypeScript.md) -* [Serverless](docs/Serverless.md) -* [推荐方案](docs/Recommendations.md) + +- [入门](docs/Getting-Started.md) +- [服务器方法](docs/Server.md) +- [路由](docs/Routes.md) +- [日志](docs/Logging.md) +- [中间件](docs/Middleware.md) +- [钩子方法](docs/Hooks.md) +- [装饰器](docs/Decorators.md) +- [验证和序列化](docs/Validation-and-Serialization.md) +- [生命周期](docs/Lifecycle.md) +- [回复](docs/Reply.md) +- [请求](docs/Request.md) +- [错误](docs/Errors.md) +- [Content-Type 解析](docs/ContentTypeParser.md) +- [插件](docs/Plugins.md) +- [测试](docs/Testing.md) +- [基准测试](docs/Benchmarking.md) +- [如何写一个好的插件](docs/Write-Plugin.md) +- [插件指南](docs/Plugins-Guide.md) +- [HTTP2](docs/HTTP2.md) +- [长期支持计划](docs/LTS.md) +- [TypeScript 与类型支持](docs/TypeScript.md) +- [Serverless](docs/Serverless.md) +- [推荐方案](docs/Recommendations.md) ### 其他版本 -* [v2.x](https://github.com/fastify/docs-chinese/tree/2.x) +- [v2.x](https://github.com/fastify/docs-chinese/tree/2.x) ### 翻译指南 @@ -35,7 +36,8 @@ 为了提供一致的翻译体验,请在开始翻译前参阅[翻译指南](docs/Contributing.md)。感谢! ### 贡献者 -* [@fralonra](https://github.com/fralonra) -* [@poppinlp](https://github.com/poppinlp) -* [@vincent178](https://github.com/vincent178) -* [@xtx1130](https://github.com/xtx1130) + +- [@fralonra](https://github.com/fralonra) +- [@poppinlp](https://github.com/poppinlp) +- [@vincent178](https://github.com/vincent178) +- [@xtx1130](https://github.com/xtx1130) diff --git a/doc/fastify-docs/docs/Benchmarking.md b/doc/fastify-docs/docs/Benchmarking.md index a97c762..0ccb39e 100644 --- a/doc/fastify-docs/docs/Benchmarking.md +++ b/doc/fastify-docs/docs/Benchmarking.md @@ -1,9 +1,11 @@

Fastify

## 基准测试 + 基准测试对于衡量改动可能引起的性能变化很重要. 从用户和贡献者的角度, 我们提供了简便的方法测试你的应用. 这套配置可以自动化你的基准测试, 从不同的分支和不同的 Node.js 的版本. 我们将使用以下模块: + - [Autocannon](https://github.com/mcollina/autocannon): 用 Node 写的 HTTP/1.1 基准测试工具. - [Branch-comparer](https://github.com/StarpTech/branch-comparer): 切换不同的 git 分支, 执行脚本并记录结果. - [Concurrently](https://github.com/kimmobrunfeldt/concurrently): 并行运行命令. @@ -12,11 +14,13 @@ ## 基本用法 ### 在当前分支上运行测试 + ```sh npm run benchmark ``` ### 在不同的 Node.js 版本中运行测试 ✨ + ```sh npx -p node@6 -- npm run benchmark ``` @@ -24,20 +28,25 @@ npx -p node@6 -- npm run benchmark ## 进阶用法 ### 在不同的分支上运行测试 + ```sh branchcmp --rounds 2 --script "npm run benchmark" ``` ### 在不同的分支和不同的 Node.js 版本中运行测试 ✨ + ```sh branchcmp --rounds 2 --script "npm run benchmark" ``` ### 比较当前的分支和主分支 (Gitflow) + ```sh branchcmp --rounds 2 --gitflow --script "npm run benchmark" ``` + or + ```sh npm run bench ``` diff --git a/doc/fastify-docs/docs/ContentTypeParser.md b/doc/fastify-docs/docs/ContentTypeParser.md index 66cdddf..892ae7e 100644 --- a/doc/fastify-docs/docs/ContentTypeParser.md +++ b/doc/fastify-docs/docs/ContentTypeParser.md @@ -1,57 +1,84 @@

Fastify

## `Content-Type` 解析 -Fastify 原生只支持 `'application/json'` 和 `'text/plain'` content type。默认的字符集是 `utf-8`。如果你需要支持其他的 content type,你需要使用 `addContentTypeParser` API。*默认的 JSON 或者纯文本解析器也可以被更改或删除。* -*注:假如你决定用 `Content-Type` 自定义 content type,UTF-8 便不再是默认的字符集了。请确保如下包含该字符集:`text/html; charset=utf-8`。* +Fastify 原生只支持 `'application/json'` 和 `'text/plain'` content type。默认的字符集是 `utf-8`。如果你需要支持其他的 content type,你需要使用 `addContentTypeParser` API。_默认的 JSON 或者纯文本解析器也可以被更改或删除。_ + +_注:假如你决定用 `Content-Type` 自定义 content type,UTF-8 便不再是默认的字符集了。请确保如下包含该字符集:`text/html; charset=utf-8`。_ 和其他的 API 一样,`addContentTypeParser` 被封装在定义它的作用域中了。这就意味着如果你定义在了根作用域中,那么就是全局可用,如果你定义在一个插件中,那么它只能在那个作用域和子作用域中可用。 Fastify 自动将解析好的 payload 添加到 [Fastify request](Request.md) 对象,你能通过 `request.body` 访问。 ### 用法 + ```js -fastify.addContentTypeParser('application/jsoff', function (request, payload, done) { - jsoffParser(payload, function (err, body) { - done(err, body) - }) -}) +fastify.addContentTypeParser( + "application/jsoff", + function (request, payload, done) { + jsoffParser(payload, function (err, body) { + done(err, body); + }); + }, +); // 以相同方式处理多种 content type -fastify.addContentTypeParser(['text/xml', 'application/xml'], function (request, payload, done) { - xmlParser(payload, function (err, body) { - done(err, body) - }) -}) +fastify.addContentTypeParser( + ["text/xml", "application/xml"], + function (request, payload, done) { + xmlParser(payload, function (err, body) { + done(err, body); + }); + }, +); // Node 版本 >= 8.0.0 时也支持 async -fastify.addContentTypeParser('application/jsoff', async function (request, payload) { - var res = await jsoffParserAsync(payload) +fastify.addContentTypeParser( + "application/jsoff", + async function (request, payload) { + var res = await jsoffParserAsync(payload); - return res -}) + return res; + }, +); -// 处理所有匹配的 content type +// 处理所有匹配的 content type fastify.addContentTypeParser(/^image\/.*/, function (request, payload, done) { imageParser(payload, function (err, body) { - done(err, body) - }) -}) + done(err, body); + }); +}); // 可以为不同的 content type 使用默认的 JSON/Text 解析器 -fastify.addContentTypeParser('text/json', { parseAs: 'string' }, fastify.getDefaultJsonParser('ignore', 'ignore')) +fastify.addContentTypeParser( + "text/json", + { parseAs: "string" }, + fastify.getDefaultJsonParser("ignore", "ignore"), +); ``` 在使用正则匹配 content-type 解析器之前,Fastify 首先会查找解析出 `string` 类型值的解析器。假如提供了重复的类型,那么 Fastify 会按照提供的顺序反向查找。因此,你可以先指定一般的 content type,之后再指定更为特殊的类型,正如下面的例子一样。 ```js // 只会调用第二个解析器,因为它也匹配了第一个解析器的类型 -fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} ) -fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => {} ) +fastify.addContentTypeParser( + "application/vnd.custom+xml", + (request, body, done) => {}, +); +fastify.addContentTypeParser( + "application/vnd.custom", + (request, body, done) => {}, +); // 这才是我们期望的行为。因为 Fastify 会首先尝试匹配 `application/vnd.custom+xml` content type 解析器 -fastify.addContentTypeParser('application/vnd.custom', (request, body, done) => {} ) -fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) => {} ) +fastify.addContentTypeParser( + "application/vnd.custom", + (request, body, done) => {}, +); +fastify.addContentTypeParser( + "application/vnd.custom+xml", + (request, body, done) => {}, +); ``` 在 `hasContentTypeParser` 之外,还有其他 API 可供使用,它们是:`hasContentTypeParser`、`removeContentTypeParser` 与 `removeAllContentTypeParsers`。 @@ -61,12 +88,15 @@ fastify.addContentTypeParser('application/vnd.custom+xml', (request, body, done) 使用 `hasContentTypeParser` API 来查询是否存在特定的 content type 解析器。 ```js -if (!fastify.hasContentTypeParser('application/jsoff')){ - fastify.addContentTypeParser('application/jsoff', function (request, payload, done) { - jsoffParser(payload, function (err, body) { - done(err, body) - }) - }) +if (!fastify.hasContentTypeParser("application/jsoff")) { + fastify.addContentTypeParser( + "application/jsoff", + function (request, payload, done) { + jsoffParser(payload, function (err, body) { + done(err, body); + }); + }, + ); } ``` @@ -76,14 +106,14 @@ if (!fastify.hasContentTypeParser('application/jsoff')){ `RegExp` 来匹配。 ```js -fastify.addContentTypeParser('text/xml', function (request, payload, done) { +fastify.addContentTypeParser("text/xml", function (request, payload, done) { xmlParser(payload, function (err, body) { - done(err, body) - }) -}) + done(err, body); + }); +}); // 移除内建的 content type 解析器。这时只有上文添加的 text/html 解析器可用。 -Fastiy.removeContentTypeParser(['application/json', 'text/plain']) +Fastiy.removeContentTypeParser(["application/json", "text/plain"]); ``` #### removeAllContentTypeParsers @@ -91,51 +121,58 @@ Fastiy.removeContentTypeParser(['application/json', 'text/plain']) 在上文的例子中,你需要明确指定所有你想移除的 content type。但你也可以使用 `removeAllContentTypeParsers`直接移除所有现存的 content type 解析器。在下面的例子里,我们实现了一样的效果,但不再需要手动指定 content type 了。和 `removeContentTypeParser` 一样,该 API 也支持封装。当你想注册一个[能捕获所有 content type 的解析器](#Catch-All),且忽略内建的解析器时,这个 API 特别有用。 ```js -Fastiy.removeAllContentTypeParsers() +Fastiy.removeAllContentTypeParsers(); -fastify.addContentTypeParser('text/xml', function (request, payload, done) { +fastify.addContentTypeParser("text/xml", function (request, payload, done) { xmlParser(payload, function (err, body) { - done(err, body) - }) -}) + done(err, body); + }); +}); ``` **注意**:早先的写法 `function(req, done)` 与 `async function(req)` 仍被支持,但不推荐使用。 #### Body Parser -你可以用两种方式解析消息主体。第一种方法在上面演示过了: 你可以添加定制的 content type 解析器来处理请求。第二种方法你可以在 `addContentTypeParser` API 传递 `parseAs` 参数。它可以是 `'string'` 或者 `'buffer'`。如果你使用 `parseAs` 选项 Fastify 会处理 stream 并且进行一些检查,比如消息主体的 [最大尺寸](Factory.md#factory-body-limit) 和消息主体的长度。如果达到了某些限制,自定义的解析器就不会被调用。 +你可以用两种方式解析消息主体。第一种方法在上面演示过了: 你可以添加定制的 content type 解析器来处理请求。第二种方法你可以在 `addContentTypeParser` API 传递 `parseAs` 参数。它可以是 `'string'` 或者 `'buffer'`。如果你使用 `parseAs` 选项 Fastify 会处理 stream 并且进行一些检查,比如消息主体的 [最大尺寸](Factory.md#factory-body-limit) 和消息主体的长度。如果达到了某些限制,自定义的解析器就不会被调用。 ```js -fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) { - try { - var json = JSON.parse(body) - done(null, json) - } catch (err) { - err.statusCode = 400 - done(err, undefined) - } -}) +fastify.addContentTypeParser( + "application/json", + { parseAs: "string" }, + function (req, body, done) { + try { + var json = JSON.parse(body); + done(null, json); + } catch (err) { + err.statusCode = 400; + done(err, undefined); + } + }, +); ``` 查看例子 [`example/parser.js`](https://github.com/fastify/fastify/blob/main/examples/parser.js)。 ##### 自定义解析器的选项 -+ `parseAs` (string): `'string'` 或者 `'buffer'` 定义了如何收集进来的数据。默认是 `'buffer'`。 -+ `bodyLimit` (number): 自定义解析器能够接收的最大的数据长度,比特为单位。默认是全局的消息主体的长度限制[`Fastify 工厂方法`](Factory.md#bodylimit)。 + +- `parseAs` (string): `'string'` 或者 `'buffer'` 定义了如何收集进来的数据。默认是 `'buffer'`。 +- `bodyLimit` (number): 自定义解析器能够接收的最大的数据长度,比特为单位。默认是全局的消息主体的长度限制[`Fastify 工厂方法`](Factory.md#bodylimit)。 #### 捕获所有 有些情况下你需要捕获所有的 content type。通过 Fastify,你只需添加`'*'` content type。 ```js -fastify.addContentTypeParser('*', function (request, payload, done) { - var data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) -}) +fastify.addContentTypeParser("*", function (request, payload, done) { + var data = ""; + payload.on("data", (chunk) => { + data += chunk; + }); + payload.on("end", () => { + done(null, data); + }); +}); ``` 在这种情况下,所有的没有特定 content type 解析器的请求都会被这个方法处理。 @@ -143,36 +180,36 @@ fastify.addContentTypeParser('*', function (request, payload, done) { 对请求流 (stream) 执行管道输送 (pipe) 操作也是有用的。你可以如下定义一个 content 解析器: ```js -fastify.addContentTypeParser('*', function (request, payload, done) { - done() -}) +fastify.addContentTypeParser("*", function (request, payload, done) { + done(); +}); ``` 之后通过核心 HTTP request 对象将请求流直接输送到任意位置: ```js -app.post('/hello', (request, reply) => { - reply.send(request.raw) -}) +app.post("/hello", (request, reply) => { + reply.send(request.raw); +}); ``` 这里有一个将来访的 [json line](https://jsonlines.org/) 对象完整输出到日志的例子: ```js -const split2 = require('split2') -const pump = require('pump') +const split2 = require("split2"); +const pump = require("pump"); - fastify.addContentTypeParser('*', (request, payload, done) => { - done(null, pump(payload, split2(JSON.parse))) -}) +fastify.addContentTypeParser("*", (request, payload, done) => { + done(null, pump(payload, split2(JSON.parse))); +}); fastify.route({ - method: 'POST', - url: '/api/log/jsons', + method: "POST", + url: "/api/log/jsons", handler: (req, res) => { - req.body.on('data', d => console.log(d)) // 记录每个来访的对象 - } -}) + req.body.on("data", (d) => console.log(d)); // 记录每个来访的对象 + }, +}); ``` 关于处理上传的文件,请看[该插件](https://github.com/fastify/fastify-multipart)。 @@ -181,13 +218,15 @@ fastify.route({ ```js // 没有下面这行的话,content type 为 application/json 的 body 将被内建的 json 解析器处理。 -fastify.removeAllContentTypeParsers() +fastify.removeAllContentTypeParsers(); -fastify.addContentTypeParser('*', function (request, payload, done) { - var data = '' - payload.on('data', chunk => { data += chunk }) - payload.on('end', () => { - done(null, data) - }) -}) -``` \ No newline at end of file +fastify.addContentTypeParser("*", function (request, payload, done) { + var data = ""; + payload.on("data", (chunk) => { + data += chunk; + }); + payload.on("end", () => { + done(null, data); + }); +}); +``` diff --git a/doc/fastify-docs/docs/Contributing.md b/doc/fastify-docs/docs/Contributing.md index 74f6e49..97c6b12 100644 --- a/doc/fastify-docs/docs/Contributing.md +++ b/doc/fastify-docs/docs/Contributing.md @@ -2,10 +2,10 @@ 如果你发现了翻译的错误,或文档的滞后,请创建一个 issue 来告诉我们,或是按如下方法直接参与翻译。感谢! -* Fork https://github.com/fastify/docs-chinese -* 创建 Git Branch -* 开始翻译 -* 创建 PR,尽量保证一篇文档一个 commit 一个 PR +- Fork https://github.com/fastify/docs-chinese +- 创建 Git Branch +- 开始翻译 +- 创建 PR,尽量保证一篇文档一个 commit 一个 PR ### 风格规范 @@ -18,7 +18,7 @@ - 中文与英文之间需要加一个空格,例如 `你好 hello 你好` - 中文与数字之间需要加一个空格,例如 `2018 年 12 月 20 日` - 正文中的英文标点需要转换为对应的中文标点,例如 `,` => `,` - - 例外:英文括号修改为半角括号加一个空格的形式,例如 `()` => ` () ` + - 例外:英文括号修改为半角括号加一个空格的形式,例如 `()` => `()` - 注意:英文逗号可能需要转换为中文顿号,而非中文逗号 - 代码片段中的注释需要翻译 - 不做翻译的内容: diff --git a/doc/fastify-docs/docs/Decorators.md b/doc/fastify-docs/docs/Decorators.md index 29fa45e..08db681 100644 --- a/doc/fastify-docs/docs/Decorators.md +++ b/doc/fastify-docs/docs/Decorators.md @@ -4,7 +4,7 @@ 装饰器 API 允许你自定义服务器实例或请求周期中的请求/回复等对象。任意类型的属性都能通过装饰器添加到这些对象上,包括函数、普通对象 (plain object) 以及原生类型。 -装饰器 API 是 *同步* 的。如果异步地添加装饰器,可能会导致在装饰器完成初始化之前, Fastify 实例就已经引导完毕了。因此,必须将 `register` 方法与 `fastify-plugin` 结合使用。详见 [Plugins](Plugins.md)。 +装饰器 API 是 _同步_ 的。如果异步地添加装饰器,可能会导致在装饰器完成初始化之前, Fastify 实例就已经引导完毕了。因此,必须将 `register` 方法与 `fastify-plugin` 结合使用。详见 [Plugins](Plugins.md)。 通过装饰器 API 来自定义对象,底层的 Javascript 引擎便能对其进行优化。这是因为引擎能在所有的对象实例被初始化与使用前,定义好它们的形状 (shape)。下文的例子则是不推荐的做法,因为它在对象的生命周期中修改了它们的形状: @@ -13,32 +13,32 @@ // 在调用请求处理函数之前 // 将 user 属性添加到请求上。 -fastify.addHook('preHandler', function (req, reply, done) { - req.user = 'Bob Dylan' - done() -}) +fastify.addHook("preHandler", function (req, reply, done) { + req.user = "Bob Dylan"; + done(); +}); // 在处理函数中使用 user 属性。 -fastify.get('/', function (req, reply) { - reply.send(`Hello, ${req.user}`) -}) +fastify.get("/", function (req, reply) { + reply.send(`Hello, ${req.user}`); +}); ``` 由于上述例子在请求对象初始化完成后,还改动了它的形状,因此 JavaScript 引擎必须对该对象去优化。使用装饰器 API 能避开去优化问题: ```js // 使用装饰器为请求对象添加 'user' 属性。 -fastify.decorateRequest('user', '') +fastify.decorateRequest("user", ""); // 更新属性。 -fastify.addHook('preHandler', (req, reply, done) => { - req.user = 'Bob Dylan' - done() -}) +fastify.addHook("preHandler", (req, reply, done) => { + req.user = "Bob Dylan"; + done(); +}); // 最后访问它。 -fastify.get('/', (req, reply) => { - reply.send(`Hello, ${req.user}!`) -}) +fastify.get("/", (req, reply) => { + reply.send(`Hello, ${req.user}!`); +}); ``` 装饰器的初始值应该尽可能地与其未来将被设置的值接近。例如,字符串类型的装饰器的初始值为 `''`,对象或函数类型的初始值为 `null`。 @@ -48,9 +48,11 @@ fastify.get('/', (req, reply) => { 更多此话题的内容,请见 [JavaScript engine fundamentals: Shapes and Inline Caches](https://mathiasbynens.be/notes/shapes-ics)。 ### 使用方法 + #### `decorate(name, value, [dependencies])` + 该方法用于自定义 Fastify [server](Server.md) 实例。 @@ -58,42 +60,42 @@ fastify.get('/', (req, reply) => { 例如,为其添加一个新方法: ```js -fastify.decorate('utility', function () { +fastify.decorate("utility", function () { // 新功能的代码 -}) +}); ``` 正如上文所述,还可以传递非函数的值: ```js -fastify.decorate('conf', { - db: 'some.db', - port: 3000 -}) +fastify.decorate("conf", { + db: "some.db", + port: 3000, +}); ``` 通过装饰属性的名称便可访问值: ```js -fastify.utility() +fastify.utility(); -console.log(fastify.conf.db) +console.log(fastify.conf.db); ``` [路由](Routes.md)函数的 `this` 指向 [Fastify server](Server.md): ```js -fastify.decorate('db', new DbConnection()) +fastify.decorate("db", new DbConnection()); -fastify.get('/', async function (request, reply) { - reply({hello: await this.db.query('world')}) -}) +fastify.get("/", async function (request, reply) { + reply({ hello: await this.db.query("world") }); +}); ``` 可选的 `dependencies` 参数用于指定当前装饰器所依赖的其他装饰器列表。这个列表包含了其他装饰器的名称字符串。在下面的例子里,装饰器 "utility" 依赖于 "greet" 和 "log": ```js -fastify.decorate('utility', fn, ['greet', 'log']) +fastify.decorate("utility", fn, ["greet", "log"]); ``` 注:使用箭头函数会破坏 `FastifyInstance` 的 `this` 绑定。 @@ -101,14 +103,15 @@ fastify.decorate('utility', fn, ['greet', 'log']) 一旦有依赖项不满足,`decorate` 方法便会抛出异常。依赖项检查是在服务器实例启动前进行的,因此,在运行时不会发生异常。 #### `decorateReply(name, value, [dependencies])` + 顾名思义,`decorateReply` 向 `Reply` 核心对象添加新的方法或属性: ```js -fastify.decorateReply('utility', function () { +fastify.decorateReply("utility", function () { // 新功能的代码 -}) +}); ``` 注:使用箭头函数会破坏 `this` 和 Fastify `Reply` 实例的绑定。 @@ -117,34 +120,36 @@ fastify.decorateReply('utility', function () { ```js // 反面教材 -fastify.decorateReply('foo', { bar: 'fizz'}) +fastify.decorateReply("foo", { bar: "fizz" }); ``` + 在这个例子里,该对象的引用存在于所有请求中,导致**任何更改都会影响到所有请求,并可能触发安全漏洞或内存泄露**。要合适地封装请求对象,请在 [`'onRequest'` 钩子](Hooks.md#onrequest)里设置新的值。示例如下: ```js -const fp = require('fastify-plugin') +const fp = require("fastify-plugin"); -async function myPlugin (app) { - app.decorateRequest('foo', null) - app.addHook('onRequest', async (req, reply) => { - req.foo = { bar: 42 } - }) +async function myPlugin(app) { + app.decorateRequest("foo", null); + app.addHook("onRequest", async (req, reply) => { + req.foo = { bar: 42 }; + }); } -module.exports = fp(myPlugin) +module.exports = fp(myPlugin); ``` 关于 `dependencies` 参数,请见 [`decorate`](#decorate)。 #### `decorateRequest(name, value, [dependencies])` + 同理,`decorateRequest` 向 `Request` 核心对象添加新的方法或属性: ```js -fastify.decorateRequest('utility', function () { +fastify.decorateRequest("utility", function () { // 新功能的代码 -}) +}); ``` 注:使用箭头函数会破坏 `this` 和 Fastify `Request` 实例的绑定。 @@ -153,110 +158,120 @@ fastify.decorateRequest('utility', function () { ```js // 反面教材 -fastify.decorateRequest('foo', { bar: 'fizz'}) +fastify.decorateRequest("foo", { bar: "fizz" }); ``` + 在这个例子里,该对象的引用存在于所有请求中,导致**任何更改都会影响到所有请求,并可能触发安全漏洞或内存泄露**。要合适地封装请求对象,请在 [`'onRequest'` 钩子](Hooks.md#onrequest)里设置新的值。示例如下: ```js -const fp = require('fastify-plugin') +const fp = require("fastify-plugin"); -async function myPlugin (app) { - app.decorateRequest('foo', null) - app.addHook('onRequest', async (req, reply) => { - req.foo = { bar: 42 } - }) +async function myPlugin(app) { + app.decorateRequest("foo", null); + app.addHook("onRequest", async (req, reply) => { + req.foo = { bar: 42 }; + }); } -module.exports = fp(myPlugin) +module.exports = fp(myPlugin); ``` 关于 `dependencies` 参数,请见 [`decorate`](#decorate)。 #### `hasDecorator(name)` + 用于检查服务器实例上是否存在某个装饰器: ```js -fastify.hasDecorator('utility') +fastify.hasDecorator("utility"); ``` #### hasRequestDecorator + 用于检查 Request 实例上是否存在某个装饰器: ```js -fastify.hasRequestDecorator('utility') +fastify.hasRequestDecorator("utility"); ``` #### hasReplyDecorator + 用于检查 Reply 实例上是否存在某个装饰器: ```js -fastify.hasReplyDecorator('utility') +fastify.hasReplyDecorator("utility"); ``` ### 装饰器与封装 + 在 **封装** 的同一个上下文中,如果通过 `decorate`、`decorateRequest` 以及 `decorateReply` 多次定义了一个同名的的装饰器,将会抛出一个异常。 下面的示例会抛出异常: - ```js -const server = require('fastify')() -server.decorateReply('view', function (template, args) { + +```js +const server = require("fastify")(); +server.decorateReply("view", function (template, args) { // 页面渲染引擎的代码。 -}) -server.get('/', (req, reply) => { - reply.view('/index.html', { hello: 'world' }) -}) +}); +server.get("/", (req, reply) => { + reply.view("/index.html", { hello: "world" }); +}); // 当在其他地方定义 // view 装饰器时,抛出异常。 -server.decorateReply('view', function (template, args) { +server.decorateReply("view", function (template, args) { // 另一个渲染引擎。 -}) -server.listen(3000) +}); +server.listen(3000); ``` 但下面这个例子不会抛异常: ```js -const server = require('fastify')() - server.decorateReply('view', function (template, args) { +const server = require("fastify")(); +server.decorateReply("view", function (template, args) { // 页面渲染引擎的代码。 -}) -server.register(async function (server, opts) { - // 我们在当前封装的插件内添加了一个 view 装饰器。 - // 这么做不会抛出异常。 - // 因为插件外部和内部的 view 装饰器是不一样的。 - server.decorateReply('view', function (template, args) { - // another rendering engine - }) - server.get('/', (req, reply) => { - reply.view('/index.page', { hello: 'world' }) - }) -}, { prefix: '/bar' }) -server.listen(3000) +}); +server.register( + async function (server, opts) { + // 我们在当前封装的插件内添加了一个 view 装饰器。 + // 这么做不会抛出异常。 + // 因为插件外部和内部的 view 装饰器是不一样的。 + server.decorateReply("view", function (template, args) { + // another rendering engine + }); + server.get("/", (req, reply) => { + reply.view("/index.page", { hello: "world" }); + }); + }, + { prefix: "/bar" }, +); +server.listen(3000); ``` ### Getter 和 Setter + 装饰器接受特别的 "getter/setter" 对象。这些对象拥有着名为 `getter` 与 `setter` 的函数 (尽管 `setter` 是可选的)。这么做便可以通过装饰器来定义属性。例如: ```js -fastify.decorate('foo', { - getter () { - return 'a getter' - } -}) +fastify.decorate("foo", { + getter() { + return "a getter"; + }, +}); ``` 上例会在 Fastify 实例中定义一个 `foo` 属性: ```js -console.log(fastify.foo) // 'a getter' +console.log(fastify.foo); // 'a getter' ``` diff --git a/doc/fastify-docs/docs/Encapsulation.md b/doc/fastify-docs/docs/Encapsulation.md index e5db822..1c583bb 100644 --- a/doc/fastify-docs/docs/Encapsulation.md +++ b/doc/fastify-docs/docs/Encapsulation.md @@ -1,6 +1,7 @@

Fastify

+ ## 封装 “封装上下文”是 Fastify 的一个基础特性,负责控制[路由](./Routes.md)能访问的[装饰器](./Decorators.md)、[钩子](./Hooks.md)以及[插件](./Plugins.md)。下图是封装上下文的抽象表现: @@ -12,73 +13,73 @@ 1. _顶层上下文 (root context)_ 2. 三个 _顶层插件 (root plugin)_ 3. 两个 _子上下文 (child context)_,每个 _子上下文_ 拥有 - * 两个 _子插件 (child plugin)_ - * 一个 _孙子上下文 (grandchild context)_,其又拥有 - - 三个 _子插件 (child plugin)_ + - 两个 _子插件 (child plugin)_ + - 一个 _孙子上下文 (grandchild context)_,其又拥有 + - 三个 _子插件 (child plugin)_ -任意 _子上下文_ 或 _孙子上下文_ 都有权访问 _顶层插件_。_孙子上下文_ 有权访问它上级的 _子上下文_ 中注册的 _子插件_,但 _子上下文_ *无权* 访问它下级的 _孙子上下文中_ 注册的 _子插件_。 +任意 _子上下文_ 或 _孙子上下文_ 都有权访问 _顶层插件_。_孙子上下文_ 有权访问它上级的 _子上下文_ 中注册的 _子插件_,但 _子上下文_ _无权_ 访问它下级的 _孙子上下文中_ 注册的 _子插件_。 在 Fastify 中,除了 _顶层上下文_,一切皆为[插件](./Plugins.md)。下文的例子也不例外,所有的“上下文”和“插件”都是包含装饰器、钩子、插件及路由的插件。该例子为有三个路由的 REST API 服务器,第一个路由 (`/one`) 需要鉴权 (使用 [fastify-bearer-auth][bearer]),第二个路由 (`/two`) 无需鉴权,第三个路由 (`/three`) 有权访问第二个路由的上下文: ```js -'use strict' +"use strict"; -const fastify = require('fastify')() +const fastify = require("fastify")(); -fastify.decorateRequest('answer', 42) +fastify.decorateRequest("answer", 42); -fastify.register(async function authenticatedContext (childServer) { - childServer.register(require('fastify-bearer-auth'), { keys: ['abc123'] }) +fastify.register(async function authenticatedContext(childServer) { + childServer.register(require("fastify-bearer-auth"), { keys: ["abc123"] }); childServer.route({ - path: '/one', - method: 'GET', - handler (request, response) { + path: "/one", + method: "GET", + handler(request, response) { response.send({ answer: request.answer, // request.foo 会是 undefined,因为该值是在 publicContext 中定义的 foo: request.foo, // request.bar 会是 undefined,因为该值是在 grandchildContext 中定义的 - bar: request.bar - }) - } - }) -}) + bar: request.bar, + }); + }, + }); +}); -fastify.register(async function publicContext (childServer) { - childServer.decorateRequest('foo', 'foo') +fastify.register(async function publicContext(childServer) { + childServer.decorateRequest("foo", "foo"); childServer.route({ - path: '/two', - method: 'GET', - handler (request, response) { + path: "/two", + method: "GET", + handler(request, response) { response.send({ answer: request.answer, foo: request.foo, // request.bar 会是 undefined,因为该值是在 grandchildContext 中定义的 - bar: request.bar - }) - } - }) + bar: request.bar, + }); + }, + }); - childServer.register(async function grandchildContext (grandchildServer) { - grandchildServer.decorateRequest('bar', 'bar') + childServer.register(async function grandchildContext(grandchildServer) { + grandchildServer.decorateRequest("bar", "bar"); grandchildServer.route({ - path: '/three', - method: 'GET', - handler (request, response) { + path: "/three", + method: "GET", + handler(request, response) { response.send({ answer: request.answer, foo: request.foo, - bar: request.bar - }) - } - }) - }) -}) + bar: request.bar, + }); + }, + }); + }); +}); -fastify.listen(8000) +fastify.listen(8000); ``` 上面的例子展示了所有封装相关的概念: @@ -102,6 +103,7 @@ fastify.listen(8000) [bearer]: https://github.com/fastify/fastify-bearer-auth + ## 在上下文间共享 请注意,在上文例子中,每个上下文都 _仅_ 从父级上下文进行继承,而父级上下文无权访问后代上下文中定义的实体。在某些情况下,我们并不想要这一默认行为。使用 [fastify-plugin][fastify-plugin] ,便能允许父级上下文访问到后代上下文中定义的实体。 @@ -109,50 +111,50 @@ fastify.listen(8000) 假设上例的 `publicContext` 需要获取 `grandchildContext` 中定义的 `bar` 装饰器,我们可以重写代码如下: ```js -'use strict' +"use strict"; -const fastify = require('fastify')() -const fastifyPlugin = require('fastify-plugin') +const fastify = require("fastify")(); +const fastifyPlugin = require("fastify-plugin"); -fastify.decorateRequest('answer', 42) +fastify.decorateRequest("answer", 42); // 为了代码清晰,这里省略了 `authenticatedContext` -fastify.register(async function publicContext (childServer) { - childServer.decorateRequest('foo', 'foo') +fastify.register(async function publicContext(childServer) { + childServer.decorateRequest("foo", "foo"); childServer.route({ - path: '/two', - method: 'GET', - handler (request, response) { + path: "/two", + method: "GET", + handler(request, response) { response.send({ answer: request.answer, foo: request.foo, - bar: request.bar - }) - } - }) + bar: request.bar, + }); + }, + }); - childServer.register(fastifyPlugin(grandchildContext)) + childServer.register(fastifyPlugin(grandchildContext)); - async function grandchildContext (grandchildServer) { - grandchildServer.decorateRequest('bar', 'bar') + async function grandchildContext(grandchildServer) { + grandchildServer.decorateRequest("bar", "bar"); grandchildServer.route({ - path: '/three', - method: 'GET', - handler (request, response) { + path: "/three", + method: "GET", + handler(request, response) { response.send({ answer: request.answer, foo: request.foo, - bar: request.bar - }) - } - }) + bar: request.bar, + }); + }, + }); } -}) +}); -fastify.listen(8000) +fastify.listen(8000); ``` 重启服务,访问 `/two` 和 `/three` 路由: @@ -164,4 +166,4 @@ fastify.listen(8000) {"answer":42,"foo":"foo","bar":"bar"} ``` -[fastify-plugin]: https://github.com/fastify/fastify-plugin \ No newline at end of file +[fastify-plugin]: https://github.com/fastify/fastify-plugin diff --git a/doc/fastify-docs/docs/Errors.md b/doc/fastify-docs/docs/Errors.md index 5907546..2f30f2a 100644 --- a/doc/fastify-docs/docs/Errors.md +++ b/doc/fastify-docs/docs/Errors.md @@ -1,28 +1,35 @@

Fastify

+ ## 错误 + ### Node.js 错误处理 #### 未捕获的错误 + 在 Node.js 里,未捕获的错误容易引起内存泄漏、文件描述符泄漏等生产环境主要的问题。[Domain 模块](https://nodejs.org/en/docs/guides/domain-postmortem/)被设计用来解决这一问题,然而效果不佳。 由于不可能合理地处理所有未捕获的错误,目前最好的处理方案就是[使程序崩溃](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly)。 #### 在 Promise 里捕获错误 + 未处理的 promise rejection (即未被 `.catch()` 处理) 在 Node.js 中也可能引起内存或文件描述符泄漏。`unhandledRejection` 不被推荐,未处理的 rejection 也不会抛出,因此还是可能会泄露。你应当使用如 [`make-promises-safe`](https://github.com/mcollina/make-promises-safe) 的模块来确保未处理的 rejection _总能_ 被抛出。 假如你使用 promise,你应当同时给它们加上 `.catch()`。 ### Fastify 的错误 + Fastify 遵循不全则无的原则,旨在精而优。因此,确保正确处理错误是开发者需要考虑的问题。 #### 输入数据的错误 + 由于大部分的错误源于预期外的输入,因此我们推荐[通过 JSON.schema 来验证输入数据](Validation-and-Serialization.md)。 #### 在 Fastify 中捕捉未捕获的错误 + 在不影响性能的前提下,Fastify 尽可能多地捕捉未捕获的错误。这些错误包括: 1. 同步路由中的错误。如 `app.get('/', () => { throw new Error('kaboom') })` @@ -35,6 +42,7 @@ Fastify 遵循不全则无的原则,旨在精而优。因此,确保正确处 ### Fastify 生命周期钩子的错误,及自定义错误控制函数 在[钩子](Hooks.md#manage-errors-from-a-hook)的文档中提到: + > 假如在钩子执行过程中发生错误,只需把它传递给 `done()`,Fastify 便会自动地关闭请求,并向用户发送合适的错误代码。 如果通过 `setErrorHandler` 自定义了一个错误函数,那么错误会被引导到那里,否则被引导到 Fastify 默认的错误函数中去。 @@ -42,6 +50,7 @@ Fastify 遵循不全则无的原则,旨在精而优。因此,确保正确处 自定义错误函数应该考虑以下几点: - 你可以调用 `reply.send(data)`,正如在[常规路由](Reply.md#senddata)中那样 + - object 会被序列化,并触发 `preSerialization` 钩子 (假如有定义的话) - string、buffer 及 stream 会被直接发送至客户端 (不会序列化),并附带上合适的 header。 @@ -50,19 +59,23 @@ Fastify 遵循不全则无的原则,旨在精而优。因此,确保正确处 - 在同一个钩子内,一个错误不会被触发两次。Fastify 会内在地监控错误的触发,以此避免在回复阶段无限循环地抛错 (在路由函数执行后)。 + ### Fastify 错误代码 + #### FST_ERR_BAD_URL 无效的 url。 + #### FST_ERR_CTP_ALREADY_PRESENT 该 content type 的解析器已经被注册。 + #### FST_ERR_CTP_BODY_TOO_LARGE 请求 body 大小超过限制。 @@ -70,101 +83,121 @@ Fastify 遵循不全则无的原则,旨在精而优。因此,确保正确处 可通过 Fastify 实例的 [`bodyLimit`](Server.md#bodyLimit) 属性改变大小限制。 + #### FST_ERR_CTP_EMPTY_TYPE content type 不能是一个空字符串。 + #### FST_ERR_CTP_INVALID_CONTENT_LENGTH 请求 body 大小与 Content-Length 不一致。 + #### FST_ERR_CTP_INVALID_HANDLER 该 content type 接收的处理函数无效。 + #### FST_ERR_CTP_INVALID_MEDIA_TYPE 收到的 media type 不支持 (例如,不存在合适的 `Content-Type` 解析器)。 + #### FST_ERR_CTP_INVALID_PARSE_TYPE 提供的待解析类型不支持。只支持 `string` 和 `buffer`。 + #### FST_ERR_CTP_INVALID_TYPE `Content-Type` 应为一个字符串。 + #### FST_ERR_DEC_ALREADY_PRESENT 已存在同名的装饰器。 + #### FST_ERR_DEC_MISSING_DEPENDENCY 缺失依赖导致装饰器无法注册。 + #### FST_ERR_HOOK_INVALID_HANDLER 钩子的回调必须为函数。 + #### FST_ERR_HOOK_INVALID_TYPE 钩子名称必须为字符串。 + #### FST_ERR_LOG_INVALID_DESTINATION 日志工具目标地址无效。仅接受 `'stream'` 或 `'file'` 作为目标地址。 + #### FST_ERR_PROMISE_NOT_FULLFILLED 状态码不为 204 时,Promise 的 payload 不能为 'undefined'。 + #### FST_ERR_REP_ALREADY_SENT 响应已发送。 + #### FST_ERR_REP_INVALID_PAYLOAD_TYPE 响应 payload 类型无效。只允许 `string` 或 `Buffer`。 + #### FST_ERR_SCH_ALREADY_PRESENT 同 `$id` 的 schema 已经存在。 + #### FST_ERR_SCH_MISSING_ID 提供的 schema 没有 `$id` 属性。 + #### FST_ERR_SCH_SERIALIZATION_BUILD 用于序列化响应的 JSON schema 不合法。 + #### FST_ERR_SCH_VALIDATION_BUILD 用于校验路由的 JSON schema 不合法。 + #### FST_ERR_SEND_INSIDE_ONERR 不能在 `onError` 钩子中调用 `send`。 + #### FST_ERR_SEND_UNDEFINED_ERR -发生了未定义的错误。 \ No newline at end of file +发生了未定义的错误。 diff --git a/doc/fastify-docs/docs/Fluent-Schema.md b/doc/fastify-docs/docs/Fluent-Schema.md index b9de126..cceb54d 100644 --- a/doc/fastify-docs/docs/Fluent-Schema.md +++ b/doc/fastify-docs/docs/Fluent-Schema.md @@ -9,43 +9,45 @@ ### 基本设置 ```js -const S = require('fluent-json-schema') +const S = require("fluent-json-schema"); // 你可以使用如下的一个对象,或查询数据库来获取数据 const MY_KEYS = { - KEY1: 'ONE', - KEY2: 'TWO' -} + 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())) + .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()) + .prop("name", S.string()) + .prop("excitement", S.integer()); const paramsJsonSchema = S.object() - .prop('par1', S.string()) - .prop('par2', S.integer()) + .prop("par1", S.string()) + .prop("par2", S.integer()); -const headersJsonSchema = S.object() - .prop('x-foo', S.string().required()) +const headersJsonSchema = S.object().prop("x-foo", S.string().required()); const schema = { body: bodyJsonSchema, querystring: queryStringJsonSchema, // (或) query: queryStringJsonSchema params: paramsJsonSchema, - headers: headersJsonSchema -} + headers: headersJsonSchema, +}; -fastify.post('/the/url', { schema }, handler) +fastify.post("/the/url", { schema }, handler); ``` ### 复用 @@ -59,57 +61,62 @@ fastify.post('/the/url', { schema }, handler) ```js const addressSchema = S.object() - .id('#address') - .prop('line1').required() - .prop('line2') - .prop('country').required() - .prop('city').required() - .prop('zipcode').required() + .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) // 你可以任意添加需要的 schema + .id("https://fastify/demo") + .definition("addressSchema", addressSchema) + .definition("otherSchema", otherSchema); // 你可以任意添加需要的 schema -fastify.addSchema(commonSchemas) +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() + .prop("residence", S.ref("https://fastify/demo#address")) + .required() + .prop("office", S.ref("https://fastify/demo#/definitions/addressSchema")) + .required(); -const schema = { body: bodyJsonSchema } +const schema = { body: bodyJsonSchema }; -fastify.post('/the/url', { schema }, handler) +fastify.post("/the/url", { schema }, handler); ``` - **`替换方式`**:在验证阶段之前,使用共用 schema 替换某些字段。 ```js const sharedAddressSchema = { - $id: 'sharedAddress', - type: 'object', - required: ['line1', 'country', 'city', 'zipcode'], + $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) + line1: { type: "string" }, + line2: { type: "string" }, + country: { type: "string" }, + city: { type: "string" }, + zipcode: { type: "string" }, + }, +}; +fastify.addSchema(sharedAddressSchema); const bodyJsonSchema = { - type: 'object', + type: "object", properties: { - vacation: 'sharedAddress#' - } -} + vacation: "sharedAddress#", + }, +}; -const schema = { body: bodyJsonSchema } +const schema = { body: bodyJsonSchema }; -fastify.post('/the/url', { schema }, handler) +fastify.post("/the/url", { schema }, handler); ``` -特别注意:你可以在 `fastify.addSchema` 方法里混用 `$ref` 和 `替换方式`。 \ No newline at end of file +特别注意:你可以在 `fastify.addSchema` 方法里混用 `$ref` 和 `替换方式`。 diff --git a/doc/fastify-docs/docs/Getting-Started.md b/doc/fastify-docs/docs/Getting-Started.md index 9b01661..d9d9b07 100644 --- a/doc/fastify-docs/docs/Getting-Started.md +++ b/doc/fastify-docs/docs/Getting-Started.md @@ -1,78 +1,88 @@

Fastify

## 起步 + Hello!感谢你来到 Fastify 的世界!
这篇文档将向你介绍 Fastify 框架及其特性,也包含了一些示例和指向其他文档的链接。
那,这就开始吧! + ### 安装 + 使用 npm 安装: + ``` npm i fastify --save ``` + 使用 yarn 安装: + ``` yarn add fastify ``` + ### 第一个服务器 + 让我们开始编写第一个服务器吧: + ```js // 加载框架并新建实例 // ESM -import Fastify from 'fastify' +import Fastify from "fastify"; const fastify = Fastify({ - logger: true -}) + logger: true, +}); // CommonJs -const fastify = require('fastify')({ - logger: true -}) +const fastify = require("fastify")({ + logger: true, +}); // 声明路由 -fastify.get('/', function (request, reply) { - reply.send({ hello: 'world' }) -}) +fastify.get("/", function (request, reply) { + reply.send({ hello: "world" }); +}); // 启动服务! fastify.listen(3000, function (err, address) { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } // 服务器监听地址:${address} -}) +}); ``` 更喜欢使用 `async/await`?Fastify 对其提供了开箱即用的支持。
-*(我们还建议使用 [make-promises-safe](https://github.com/mcollina/make-promises-safe) 来避免文件描述符 (file descriptor) 及内存的泄露)* +_(我们还建议使用 [make-promises-safe](https://github.com/mcollina/make-promises-safe) 来避免文件描述符 (file descriptor) 及内存的泄露)_ + ```js // ESM -import Fastify from 'fastify' +import Fastify from "fastify"; const fastify = Fastify({ - logger: true -}) + logger: true, +}); // CommonJs -const fastify = require('fastify')({ - logger: true -}) +const fastify = require("fastify")({ + logger: true, +}); -fastify.get('/', async (request, reply) => { - return { hello: 'world' } -}) +fastify.get("/", async (request, reply) => { + return { hello: "world" }; +}); const start = async () => { try { - await fastify.listen(3000) + await fastify.listen(3000); } catch (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -} -start() +}; +start(); ``` 如此简单,棒极了!
@@ -80,16 +90,17 @@ start() 幸运的是,Fastify 提供了一个易于使用的平台,它能帮助你解决不限于上述的诸多问题! > ## 注 +> > 本文档中的示例,默认情况下只监听本地 `127.0.0.1` 端口。要监听所有有效的 IPv4 端口,需要将代码修改为监听 `0.0.0.0`,如下所示: > > ```js -> fastify.listen(3000, '0.0.0.0', function (err, address) { +> fastify.listen(3000, "0.0.0.0", function (err, address) { > if (err) { -> fastify.log.error(err) -> process.exit(1) +> fastify.log.error(err); +> process.exit(1); > } -> fastify.log.info(`server listening on ${address}`) -> }) +> fastify.log.info(`server listening on ${address}`); +> }); > ``` > > 类似地,`::1` 表示只允许本地的 IPv6 连接。而 `::` 表示允许所有 IPv6 地址的接入,当操作系统支持时,所有的 IPv4 地址也会被允许。 @@ -97,57 +108,61 @@ start() > 当使用 Docker 或其他容器部署时,使用 `0.0.0.0` 或 `::` 会是最简单的暴露应用的方式。 + ### 第一个插件 + 就如同在 JavaScript 中一切皆为对象,在 Fastify 中,一切都是插件 (plugin)。
在深入之前,先来看看插件系统是如何工作的吧!
让我们新建一个基本的服务器,但这回我们把路由 (route) 的声明从入口文件转移到一个外部文件。(参阅[路由声明](Routes.md))。 + ```js // ESM -import Fastify from 'fastify' -import firstRoute from './our-first-route' +import Fastify from "fastify"; +import firstRoute from "./our-first-route"; const fastify = Fastify({ - logger: true -}) + logger: true, +}); -fastify.register(firstRoute) +fastify.register(firstRoute); fastify.listen(3000, function (err, address) { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } // 服务器监听地址:${address} -}) +}); ``` ```js // CommonJs -const fastify = require('fastify')({ - logger: true -}) +const fastify = require("fastify")({ + logger: true, +}); -fastify.register(require('./our-first-route')) +fastify.register(require("./our-first-route")); fastify.listen(3000, function (err, address) { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } // 服务器监听地址:${address} -}) +}); ``` ```js // our-first-route.js -async function routes (fastify, options) { - fastify.get('/', async (request, reply) => { - return { hello: 'world' } - }) +async function routes(fastify, options) { + fastify.get("/", async (request, reply) => { + return { hello: "world" }; + }); } -module.exports = routes +module.exports = routes; ``` + 这个例子调用了 `register` API,它是 Fastify 框架的核心,也是添加路由、插件等的唯一方法。 在本文的开头,我们提到 Fastify 提供了帮助应用异步引导的基础功能。为什么这一功能十分重要呢? @@ -164,110 +179,110 @@ npm i --save fastify-plugin fastify-mongodb ``` **server.js** + ```js // ESM -import Fastify from 'fastify' -import dbConnector from './our-db-connector' -import firstRoute from './our-first-route' +import Fastify from "fastify"; +import dbConnector from "./our-db-connector"; +import firstRoute from "./our-first-route"; const fastify = Fastify({ - logger: true -}) -fastify.register(dbConnector) -fastify.register(firstRoute) + logger: true, +}); +fastify.register(dbConnector); +fastify.register(firstRoute); fastify.listen(3000, function (err, address) { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } // 服务器监听地址:${address} -}) +}); ``` ```js // CommonJs -const fastify = require('fastify')({ - logger: true -}) +const fastify = require("fastify")({ + logger: true, +}); -fastify.register(require('./our-db-connector')) -fastify.register(require('./our-first-route')) +fastify.register(require("./our-db-connector")); +fastify.register(require("./our-first-route")); fastify.listen(3000, function (err, address) { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } // 服务器监听地址:${address} -}) - +}); ``` **our-db-connector.js** + ```js // ESM -import fastifyPlugin from 'fastify-plugin' -import fastifyMongo from 'fastify-mongodb' +import fastifyPlugin from "fastify-plugin"; +import fastifyMongo from "fastify-mongodb"; -async function dbConnector (fastify, options) { +async function dbConnector(fastify, options) { fastify.register(fastifyMongo, { - url: 'mongodb://localhost:27017/test_database' - }) + url: "mongodb://localhost:27017/test_database", + }); } // 用 fastify-plugin 包装插件,以使插件中声明的装饰器、钩子函数暴露在根作用域里。 -module.exports = fastifyPlugin(dbConnector) - +module.exports = fastifyPlugin(dbConnector); ``` ```js // CommonJs -const fastifyPlugin = require('fastify-plugin') +const fastifyPlugin = require("fastify-plugin"); -async function dbConnector (fastify, options) { - fastify.register(require('fastify-mongodb'), { - url: 'mongodb://localhost:27017/test_database' - }) +async function dbConnector(fastify, options) { + fastify.register(require("fastify-mongodb"), { + url: "mongodb://localhost:27017/test_database", + }); } // 用 fastify-plugin 包装插件,以使插件中声明的装饰器、钩子函数暴露在根作用域里。 -module.exports = fastifyPlugin(dbConnector) - +module.exports = fastifyPlugin(dbConnector); ``` **our-first-route.js** + ```js -async function routes (fastify, options) { - const collection = fastify.mongo.db.collection('test_collection') +async function routes(fastify, options) { + const collection = fastify.mongo.db.collection("test_collection"); - fastify.get('/', async (request, reply) => { - return { hello: 'world' } - }) + fastify.get("/", async (request, reply) => { + return { hello: "world" }; + }); - fastify.get('/animals', async (request, reply) => { - const result = await collection.find().toArray() + fastify.get("/animals", async (request, reply) => { + const result = await collection.find().toArray(); if (result.length === 0) { - throw new Error('No documents found') + throw new Error("No documents found"); } - return result - }) + return result; + }); - fastify.get('/animals/:animal', async (request, reply) => { - const result = await collection.findOne({ animal: request.params.animal }) + fastify.get("/animals/:animal", async (request, reply) => { + const result = await collection.findOne({ animal: request.params.animal }); if (!result) { - throw new Error('Invalid value') + throw new Error("Invalid value"); } - return result - }) + return result; + }); } -module.exports = routes +module.exports = routes; ``` 哇,真是快啊!
介绍了一些新概念后,让我们回顾一下迄今为止都做了些什么吧。
如你所见,我们可以使用 `register` 来注册数据库连接器或者路由。 -这是 Fastify 最棒的特性之一了!它使得插件按声明的顺序来加载,唯有当前插件加载完毕后,才会加载下一个插件。如此,我们便可以在第一个插件中注册数据库连接器,并在第二个插件中使用它。*(参见 [这里](Plugins.md#handle-the-scope) 了解如何处理插件的作用域)*。 +这是 Fastify 最棒的特性之一了!它使得插件按声明的顺序来加载,唯有当前插件加载完毕后,才会加载下一个插件。如此,我们便可以在第一个插件中注册数据库连接器,并在第二个插件中使用它。_(参见 [这里](Plugins.md#handle-the-scope) 了解如何处理插件的作用域)_。 当调用函数 `fastify.listen()`、`fastify.inject()` 或 `fastify.ready()` 时,插件便开始加载了。 MongoDB 的插件使用了 `decorate` API,以便在 Fastify 的命名空间下添加自定义对象,如此一来,你就可以在所有地方直接使用这些对象了。我们鼓励运用这一 API,因为它有助于提高代码复用率,减少重复的代码或逻辑。 @@ -275,8 +290,11 @@ MongoDB 的插件使用了 `decorate` API,以便在 Fastify 的命名空间下 更深入的内容,例如插件如何运作、如何新建,以及使用 Fastify 全部的 API 去处理复杂的异步引导的细节,请看[插件指南](Plugins-Guide.md)。 + ### 插件加载顺序 + 为了保证应用的行为一致且可预测,我们强烈建议你采用以下的顺序来组织代码: + ``` └── 来自 Fastify 生态的插件 └── 你自己的插件 @@ -284,8 +302,10 @@ MongoDB 的插件使用了 `decorate` API,以便在 Fastify 的命名空间下 └── 钩子函数 └── 你的服务应用 ``` + 这确保了你总能访问当前作用域下声明的所有属性。
如前文所述,Fastify 提供了一个可靠的封装模型,它能帮助你的应用成为单一且独立的服务。假如你要为某些路由单独地注册插件,只需复写上述的结构就足够了。 + ``` └── 来自 Fastify 生态的插件 └── 你自己的插件 @@ -309,68 +329,82 @@ MongoDB 的插件使用了 `decorate` API,以便在 Fastify 的命名空间下 ``` + ### 验证数据 + 数据的验证在我们的框架中是极为重要的一环,也是核心的概念。
Fastify 使用 [JSON Schema](https://json-schema.org/) 验证来访的请求。(也支持宽松的 JTD schema,但首先得禁用 `jsonShorthand`)。 让我们来看一个验证路由的例子: + ```js const opts = { schema: { body: { - type: 'object', + type: "object", properties: { - someKey: { type: 'string' }, - someOtherKey: { type: 'number' } - } - } - } -} + someKey: { type: "string" }, + someOtherKey: { type: "number" }, + }, + }, + }, +}; -fastify.post('/', opts, async (request, reply) => { - return { hello: 'world' } -}) +fastify.post("/", opts, async (request, reply) => { + return { hello: "world" }; +}); ``` + 这个例子展示了如何向路由传递配置选项。选项中包含了一个名为 `schema` 的对象,它便是我们验证路由所用的模式 (schema)。借由 schema,我们可以验证 `body`、`querystring`、`params` 以及 `header`。
请参阅[验证与序列化](Validation-and-Serialization.md)获取更多信息。 + ### 序列化数据 + Fastify 对 JSON 提供了优异的支持,极大地优化了解析 JSON body 与序列化 JSON 输出的过程。
在 schema 的选项中设置 `response` 的值,能够加快 JSON 的序列化 (没错,这很慢!),就像这样: + ```js const opts = { schema: { response: { 200: { - type: 'object', + type: "object", properties: { - hello: { type: 'string' } - } - } - } - } -} + hello: { type: "string" }, + }, + }, + }, + }, +}; -fastify.get('/', opts, async (request, reply) => { - return { hello: 'world' } -}) +fastify.get("/", opts, async (request, reply) => { + return { hello: "world" }; +}); ``` + 一旦指明了 schema,序列化的速度就能达到原先的 2-3 倍。这么做同时也保护了潜在的敏感数据不被泄露,因为 Fastify 仅对 schema 里出现的数据进行序列化。 请参阅 [验证与序列化](Validation-and-Serialization.md)获取更多信息。 + ### 扩展服务器 + Fastify 生来十分精简,也具有高可扩展性。我们相信,一个小巧的框架足以实现一个优秀的应用。
换句话说,Fastify 并非一个面面俱到的框架,它依赖于自己惊人的[生态系统](https://github.com/fastify/fastify/blob/main/docs/Ecosystem.md)! + ### 测试服务器 + Fastify 并没有提供测试框架,但是我们推荐你在测试中使用 Fastify 的特性及结构。
更多内容请看[测试](Testing.md)! + ### 从命令行启动服务器 + 感谢 [fastify-cli](https://github.com/fastify/fastify-cli),它让 Fastify 集成到了命令行之中。 首先,你得安装 `fastify-cli`: @@ -382,6 +416,7 @@ npm i fastify-cli 你还可以加入 `-g` 选项来全局安装它。 接下来,在 `package.json` 中添加如下行: + ```json { "scripts": { @@ -391,25 +426,30 @@ npm i fastify-cli ``` 然后,创建你的服务器文件: + ```js // server.js -'use strict' +"use strict"; module.exports = async function (fastify, opts) { - fastify.get('/', async (request, reply) => { - return { hello: 'world' } - }) -} + fastify.get("/", async (request, reply) => { + return { hello: "world" }; + }); +}; ``` 最后,启动你的服务器: + ```bash npm start ``` + ### 幻灯片与视频 (英文资源) + - 幻灯片 + - [为你的 HTTP 服务器提速](https://mcollina.github.io/take-your-http-server-to-ludicrous-speed) by [@mcollina](https://github.com/mcollina) - [如果我告诉你 HTTP 可以更快](https://delvedor.github.io/What-if-I-told-you-that-HTTP-can-be-fast) by [@delvedor](https://github.com/delvedor) diff --git a/doc/fastify-docs/docs/HTTP2.md b/doc/fastify-docs/docs/HTTP2.md index 746f5ec..7129690 100644 --- a/doc/fastify-docs/docs/HTTP2.md +++ b/doc/fastify-docs/docs/HTTP2.md @@ -8,26 +8,26 @@ _Fastify_ 提供了从 Node 8.8.0 开始的对 HTTP2 **实验性支持**,_Fast ### 安全 (HTTPS) -所有的现代浏览器都__只能通过安全的连接__ 支持 HTTP2: +所有的现代浏览器都**只能通过安全的连接** 支持 HTTP2: ```js -'use strict' +"use strict"; -const fs = require('fs') -const path = require('path') -const fastify = require('fastify')({ +const fs = require("fs"); +const path = require("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')) - } -}) + 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.get("/", function (request, reply) { + reply.code(200).send({ hello: "world" }); +}); -fastify.listen(3000) +fastify.listen(3000); ``` ALPN 协商允许在同一个 socket 上支持 HTTPS 和 HTTP/2。 @@ -36,25 +36,25 @@ Node 核心 `req` 和 `res` 对象可以是 [HTTP/1](https://nodejs.org/api/http _Fastify_ 自带支持开箱即用: ```js -'use strict' +"use strict"; -const fs = require('fs') -const path = require('path') -const fastify = require('fastify')({ +const fs = require("fs"); +const path = require("path"); +const fastify = require("fastify")({ http2: true, https: { allowHTTP1: true, // 向后支持 HTTP1 - key: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.key')), - cert: fs.readFileSync(path.join(__dirname, '..', 'https', 'fastify.cert')) - } -}) + key: fs.readFileSync(path.join(__dirname, "..", "https", "fastify.key")), + cert: fs.readFileSync(path.join(__dirname, "..", "https", "fastify.cert")), + }, +}); // 该路由从 HTTPS 与 HTTP2 均可访问 -fastify.get('/', function (request, reply) { - reply.code(200).send({ hello: 'world' }) -}) +fastify.get("/", function (request, reply) { + reply.code(200).send({ hello: "world" }); +}); -fastify.listen(3000) +fastify.listen(3000); ``` 你可以像这样测试你的新服务器: @@ -68,17 +68,17 @@ $ npx h2url https://localhost:3000 如果你搭建微服务,你可以纯文本连接 HTTP2,但是浏览器不支持这样做。 ```js -'use strict' +"use strict"; -const fastify = require('fastify')({ - http2: true -}) +const fastify = require("fastify")({ + http2: true, +}); -fastify.get('/', function (request, reply) { - reply.code(200).send({ hello: 'world' }) -}) +fastify.get("/", function (request, reply) { + reply.code(200).send({ hello: "world" }); +}); -fastify.listen(3000) +fastify.listen(3000); ``` 你可以像这样测试你的新服务器: @@ -86,4 +86,3 @@ fastify.listen(3000) ``` $ npx h2url http://localhost:3000 ``` - diff --git a/doc/fastify-docs/docs/Hooks.md b/doc/fastify-docs/docs/Hooks.md index 24f5bf5..7cb07a3 100644 --- a/doc/fastify-docs/docs/Hooks.md +++ b/doc/fastify-docs/docs/Hooks.md @@ -36,21 +36,24 @@ [生命周期](Lifecycle.md)一文清晰地展示了各个钩子执行的位置。
钩子可被封装,因此可以运用在特定的路由上。更多信息请看[作用域](#scope)一节。 -在请求/响应中,有八个可用的钩子 *(按执行顺序排序)*: +在请求/响应中,有八个可用的钩子 _(按执行顺序排序)_: ### onRequest + ```js -fastify.addHook('onRequest', (request, reply, done) => { +fastify.addHook("onRequest", (request, reply, done) => { // 其他代码 - done() -}) + done(); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('onRequest', async (request, reply) => { +fastify.addHook("onRequest", async (request, reply) => { // 其他代码 - await asyncMethod() -}) + await asyncMethod(); +}); ``` **注意**:在 [onRequest](#onrequest) 钩子中,`request.body` 的值总是 `null`,这是因为 body 的解析发生在 [preValidation](#prevalidation) 钩子之前。 @@ -64,18 +67,20 @@ fastify.addHook('onRequest', async (request, reply) => { 例如,你可以解压请求的 body: ```js -fastify.addHook('preParsing', (request, reply, payload, done) => { +fastify.addHook("preParsing", (request, reply, payload, done) => { // 其他代码 - done(null, newPayload) -}) + done(null, newPayload); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('preParsing', async (request, reply, payload) => { +fastify.addHook("preParsing", async (request, reply, payload) => { // 其他代码 - await asyncMethod() - return newPayload -}) + await asyncMethod(); + return newPayload; +}); ``` **注意**:在 [preParsing](#preparsing) 钩子中,`request.body` 的值总是 `null`,这是因为 body 的解析发生在 [preValidation](#prevalidation) 钩子之前。 @@ -89,119 +94,135 @@ fastify.addHook('preParsing', async (request, reply, payload) => { 使用 `preValidation` 钩子时,你可以在校验前修改 payload。示例如下: ```js -fastify.addHook('preValidation', (request, reply, done) => { - req.body = { ...req.body, importantKey: 'randomString' } - done() -}) +fastify.addHook("preValidation", (request, reply, done) => { + req.body = { ...req.body, importantKey: "randomString" }; + done(); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('preValidation', async (request, reply) => { - const importantKey = await generateRandomString() - req.body = { ...req.body, importantKey } -}) +fastify.addHook("preValidation", async (request, reply) => { + const importantKey = await generateRandomString(); + req.body = { ...req.body, importantKey }; +}); ``` ### preHandler + ```js -fastify.addHook('preHandler', (request, reply, done) => { +fastify.addHook("preHandler", (request, reply, done) => { // 其他代码 - done() -}) + done(); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('preHandler', async (request, reply) => { +fastify.addHook("preHandler", async (request, reply) => { // 其他代码 - await asyncMethod() -}) + await asyncMethod(); +}); ``` + ### preSerialization `preSerialization` 钩子让你可以在 payload 被序列化之前改动 (或替换) 它。举个例子: ```js -fastify.addHook('preSerialization', (request, reply, payload, done) => { - const err = null - const newPayload = { wrapped: payload } - done(err, newPayload) -}) +fastify.addHook("preSerialization", (request, reply, payload, done) => { + const err = null; + const newPayload = { wrapped: payload }; + done(err, newPayload); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('preSerialization', async (request, reply, payload) => { - return { wrapped: payload } -}) +fastify.addHook("preSerialization", async (request, reply, payload) => { + return { wrapped: payload }; +}); ``` 注:payload 为 `string`、`Buffer`、`stream` 或 `null` 时,该钩子不会被调用。 ### onError + ```js -fastify.addHook('onError', (request, reply, error, done) => { +fastify.addHook("onError", (request, reply, error, done) => { // 其他代码 - done() -}) + done(); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('onError', async (request, reply, error) => { +fastify.addHook("onError", async (request, reply, error) => { // 当自定义错误日志时有用处 // 你不应该使用这个钩子去更新错误 -}) +}); ``` + `onError` 钩子可用于自定义错误日志,或当发生错误时添加特定的 header。
该钩子并不是为了变更错误而设计的,且调用 `reply.send` 会抛出一个异常。
它只会在 `customErrorHandler` 向用户发送错误之后被执行 (要注意的是,默认的 `customErrorHandler` 总是会发送错误)。 **注意**:与其他钩子不同,`onError` 不支持向 `done` 函数传递错误。 ### onSend + 使用 `onSend` 钩子可以改变 payload。例如: ```js -fastify.addHook('onSend', (request, reply, payload, done) => { +fastify.addHook("onSend", (request, reply, payload, done) => { const err = null; - const newPayload = payload.replace('some-text', 'some-new-text') - done(err, newPayload) -}) + const newPayload = payload.replace("some-text", "some-new-text"); + done(err, newPayload); +}); ``` + 或使用 `async/await`: + ```js -fastify.addHook('onSend', async (request, reply, payload) => { - const newPayload = payload.replace('some-text', 'some-new-text') - return newPayload -}) +fastify.addHook("onSend", async (request, reply, payload) => { + const newPayload = payload.replace("some-text", "some-new-text"); + return newPayload; +}); ``` 你也可以通过将 payload 置为 `null`,发送一个空消息主体的响应: ```js -fastify.addHook('onSend', (request, reply, payload, done) => { - reply.code(304) - const newPayload = null - done(null, newPayload) -}) +fastify.addHook("onSend", (request, reply, payload, done) => { + reply.code(304); + const newPayload = null; + done(null, newPayload); +}); ``` > 将 payload 设为空字符串 `''` 也可以发送空的消息主体。但要小心的是,这么做会造成 `Content-Length` header 的值为 `0`。而 payload 为 `null` 则不会设置 `Content-Length` header。 注:你只能将 payload 修改为 `string`、`Buffer`、`stream` 或 `null`。 - ### onResponse -```js -fastify.addHook('onResponse', (request, reply, done) => { - // 其他代码 - done() -}) -``` -或使用 `async/await`: ```js -fastify.addHook('onResponse', async (request, reply) => { +fastify.addHook("onResponse", (request, reply, done) => { // 其他代码 - await asyncMethod() -}) + done(); +}); +``` + +或使用 `async/await`: + +```js +fastify.addHook("onResponse", async (request, reply) => { + // 其他代码 + await asyncMethod(); +}); ``` `onResponse` 钩子在响应发出后被执行,因此在该钩子中你无法再向客户端发送数据了。但是你可以在此向外部服务发送数据,比如收集数据。 @@ -209,88 +230,97 @@ fastify.addHook('onResponse', async (request, reply) => { ### onTimeout ```js -fastify.addHook('onTimeout', (request, reply, done) => { +fastify.addHook("onTimeout", (request, reply, done) => { // 其他代码 - done() -}) + done(); +}); ``` + Or `async/await`: + ```js -fastify.addHook('onTimeout', async (request, reply) => { +fastify.addHook("onTimeout", async (request, reply) => { // 其他代码 - await asyncMethod() -}) + await asyncMethod(); +}); ``` + `onTimeout` 用于监测请求超时,需要在 Fastify 实例上设置 `connectionTimeout` 属性。当请求超时,socket 挂起 (hang up) 时,该钩子执行。因此,在这个钩子里不能再向客户端发送数据了。 ### 在钩子中管理错误 + 在钩子的执行过程中如果发生了错误,只需将错误传递给 `done()`,Fastify 就会自动关闭请求,并发送一个相应的错误码给用户。 ```js -fastify.addHook('onRequest', (request, reply, done) => { - done(new Error('Some error')) -}) +fastify.addHook("onRequest", (request, reply, done) => { + done(new Error("Some error")); +}); ``` 如果你想自定义发送给用户的错误码,使用 `reply.code()` 即可: + ```js -fastify.addHook('preHandler', (request, reply, done) => { - reply.code(400) - done(new Error('Some error')) -}) +fastify.addHook("preHandler", (request, reply, done) => { + reply.code(400); + done(new Error("Some error")); +}); ``` -*错误最终会在 [`Reply`](Reply.md#errors) 中得到处理。* + +_错误最终会在 [`Reply`](Reply.md#errors) 中得到处理。_ 或者在 `async/await` 函数中抛出错误: + ```js -fastify.addHook('onResponse', async (request, reply) => { - throw new Error('Some error') -}) +fastify.addHook("onResponse", async (request, reply) => { + throw new Error("Some error"); +}); ``` ### 在钩子中响应请求 -需要的话,你可以在路由函数执行前响应一个请求,例如进行身份验证。在钩子中响应暗示着钩子的调用链被 __终止__,剩余的钩子将不会执行。假如钩子使用回调的方式,意即不是 `async` 函数,也没有返回 `Promise`,那么只需要调用 `reply.send()`,并且避免触发回调便可。假如钩子是 `async` 函数,那么 `reply.send()` __必须__ 发生在函数返回或 promise resolve 之前,否则请求将会继续下去。当 `reply.send()` 在 promise 调用链之外被调用时,需要 `return reply`,不然请求将被执行两次。 +需要的话,你可以在路由函数执行前响应一个请求,例如进行身份验证。在钩子中响应暗示着钩子的调用链被 **终止**,剩余的钩子将不会执行。假如钩子使用回调的方式,意即不是 `async` 函数,也没有返回 `Promise`,那么只需要调用 `reply.send()`,并且避免触发回调便可。假如钩子是 `async` 函数,那么 `reply.send()` **必须** 发生在函数返回或 promise resolve 之前,否则请求将会继续下去。当 `reply.send()` 在 promise 调用链之外被调用时,需要 `return reply`,不然请求将被执行两次。 -__不应当混用回调与 `async`/`Promise`__,否则钩子的调用链会被执行两次。 +**不应当混用回调与 `async`/`Promise`**,否则钩子的调用链会被执行两次。 如果你在 `onRequest` 或 `preHandler` 中发出响应,请使用 `reply.send`。 ```js -fastify.addHook('onRequest', (request, reply, done) => { - reply.send('Early response') -}) +fastify.addHook("onRequest", (request, reply, done) => { + reply.send("Early response"); +}); // 也可使用 async 函数 -fastify.addHook('preHandler', async (request, reply) => { - await something() - reply.send({ hello: 'world' }) - return reply // 在这里是可选的,但这是好的实践 -}) +fastify.addHook("preHandler", async (request, reply) => { + await something(); + reply.send({ hello: "world" }); + return reply; // 在这里是可选的,但这是好的实践 +}); ``` 如果你想要使用流 (stream) 来响应请求,你应该避免使用 `async` 函数。必须使用 `async` 函数的话,请参考 [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) -}) +fastify.addHook("onRequest", (request, reply, done) => { + const stream = fs.createReadStream("some-file", "utf8"); + reply.send(stream); +}); ``` 如果发出响应但没有 `await` 关键字,请确保总是 `return reply`: ```js -fastify.addHook('preHandler', async (request, reply) => { - setImmediate(() => { reply.send('hello') }) +fastify.addHook("preHandler", async (request, reply) => { + setImmediate(() => { + reply.send("hello"); + }); // 让处理函数等待 promise 链之外发出的响应 - return reply -}) -fastify.addHook('preHandler', async (request, reply) => { + return reply; +}); +fastify.addHook("preHandler", async (request, reply) => { // fastify-static 插件异步地发送文件,因此需要 return reply - reply.sendFile('myfile') - return reply -}) + reply.sendFile("myfile"); + return reply; +}); ``` ## 应用钩子 @@ -303,71 +333,84 @@ fastify.addHook('preHandler', async (request, reply) => { - [onRegister](#onregister) ### onReady + 在服务器开始监听请求之前,或调用 `.ready()` 方法时触发。在此你无法更改路由,或添加新的钩子。注册的 `onReady` 钩子函数串行执行,只有全部执行完毕时,服务器才会开始监听请求。钩子接受一个回调函数作为参数:`done`,在钩子函数完成后调用。钩子的 `this` 为 Fastify 实例。 ```js // 回调写法 -fastify.addHook('onReady', function (done) { +fastify.addHook("onReady", function (done) { // 其他代码 const err = null; - done(err) -}) + done(err); +}); // 或 async/await -fastify.addHook('onReady', async function () { +fastify.addHook("onReady", async function () { // 异步代码 - await loadCacheFromDatabase() -}) + await loadCacheFromDatabase(); +}); ``` + ### onClose + 使用 `fastify.close()` 停止服务器时被触发。当[插件](Plugins.md)需要一个 "shutdown" 事件时有用,例如关闭一个数据库连接。
该钩子的第一个参数是 Fastify 实例,第二个为 `done` 回调函数。 + ```js -fastify.addHook('onClose', (instance, done) => { +fastify.addHook("onClose", (instance, done) => { // 其他代码 - done() -}) + done(); +}); ``` + ### onRoute + 当注册一个新的路由时被触发。它的监听函数拥有一个唯一的参数:`routeOptions` 对象。该函数是同步的,其本身并不接受回调作为参数。 + ```js -fastify.addHook('onRoute', (routeOptions) => { +fastify.addHook("onRoute", (routeOptions) => { // 其他代码 - routeOptions.method - routeOptions.schema - routeOptions.url // 路由的完整 URL,包括前缀 - routeOptions.path // `url` 的别名 - routeOptions.routePath // 无前缀的 URL - routeOptions.bodyLimit - routeOptions.logLevel - routeOptions.logSerializers - routeOptions.prefix -}) + routeOptions.method; + routeOptions.schema; + routeOptions.url; // 路由的完整 URL,包括前缀 + routeOptions.path; // `url` 的别名 + routeOptions.routePath; // 无前缀的 URL + routeOptions.bodyLimit; + routeOptions.logLevel; + routeOptions.logSerializers; + routeOptions.prefix; +}); ``` 如果在编写插件时,需要自定义程序的路由,比如修改选项或添加新的路由层钩子,你可以在这里添加。 ```js -fastify.addHook('onRoute', (routeOptions) => { +fastify.addHook("onRoute", (routeOptions) => { function onPreSerialization(request, reply, payload, done) { // 其他代码 - done(null, payload) + done(null, payload); } - + // preSerialization 可以是数组或 undefined - routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization] -}) + routeOptions.preSerialization = [ + ...(routeOptions.preSerialization || []), + onPreSerialization, + ]; +}); ``` + ### onRegister + 当注册一个新的插件,或创建了新的封装好的上下文后被触发。该钩子在注册的代码**之前**被执行。
当你的插件需要知晓上下文何时创建完毕,并操作它们时,可以使用这一钩子。
**注意**:被 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 所封装的插件不会触发该钩子。 + ```js fastify.decorate('data', []) @@ -398,40 +441,42 @@ fastify.addHook('onRegister', (instance, opts) => { ``` + ## 作用域 + 除了[应用钩子](#application-hooks),所有的钩子都是封装好的。这意味着你可以通过 `register` 来决定在何处运行它们,正如[插件指南](Plugins-Guide.md)所述。如果你传递一个函数,那么该函数会获得 Fastify 的上下文,如此你便能使用 Fastify 的 API 了。 ```js -fastify.addHook('onRequest', function (request, reply, done) { - const self = this // Fastify 上下文 - done() -}) +fastify.addHook("onRequest", function (request, reply, done) { + const self = this; // Fastify 上下文 + done(); +}); ``` 要注意的是,每个钩子内的 Fastify 上下文都和注册路由时的插件一样,举例如下: ```js -fastify.addHook('onRequest', async function (req, reply) { - if (req.raw.url === '/nested') { - assert.strictEqual(this.foo, 'bar') +fastify.addHook("onRequest", async function (req, reply) { + if (req.raw.url === "/nested") { + assert.strictEqual(this.foo, "bar"); } else { - assert.strictEqual(this.foo, undefined) + assert.strictEqual(this.foo, undefined); } -}) +}); -fastify.get('/', async function (req, reply) { - assert.strictEqual(this.foo, undefined) - return { hello: 'world' } -}) +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.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' } - }) -}) + fastify.get("/nested", async function (req, reply) { + assert.strictEqual(this.foo, "bar"); + return { hello: "world" }; + }); +}); ``` 提醒:使用[箭头函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)的话,`this` 将不会是 Fastify,而是当前的作用域。 @@ -439,6 +484,7 @@ fastify.register(async function plugin (fastify, opts) { ## 路由层钩子 + 你可以为**单个**路由声明一个或多个自定义的生命周期钩子 ([onRequest](#onrequest)、[onResponse](#onresponse)、[preParsing](#preparsing)、[preValidation](#prevalidation)、[preHandler](#prehandler)、[preSerialization](#preserialization)、[onSend](#onsend)、[onTimeout](#ontimeout) 与 [onError](#onerror))。 如果你这么做,这些钩子总是会作为同一类钩子中的最后一个被执行。
当你需要进行认证时,这会很有用,而 [preParsing](#preparsing) 与 [preValidation](#prevalidation) 钩子正是为此而生。 @@ -576,4 +622,4 @@ channel.subscribe(function ({ fastify }) { done() }) }) -``` \ No newline at end of file +``` diff --git a/doc/fastify-docs/docs/LTS.md b/doc/fastify-docs/docs/LTS.md index 529dd7e..adb2a4d 100644 --- a/doc/fastify-docs/docs/LTS.md +++ b/doc/fastify-docs/docs/LTS.md @@ -32,11 +32,11 @@ Fastify 长期支持计划 (LTS) 以本文档为准: ### 计划 -| 版本 | 发布日期 | 长期支持结束 | Node.js 版本 | -| :------ | :----------- | :-------------- | :------------------- | -| 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 | 待定 | 10, 12, 14, 16 | +| 版本 | 发布日期 | 长期支持结束 | Node.js 版本 | +| :---- | :--------- | :----------- | :--------------- | +| 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 | 待定 | 10, 12, 14, 16 | @@ -44,11 +44,11 @@ Fastify 长期支持计划 (LTS) 以本文档为准: Fastify 使用 GitHub Actions 来进行 CI 测试,请参阅 [GitHub 相关文档](https://docs.github.com/cn/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources)来获取下表 YAML 工作流标签所对应的最新虚拟机环境。 -| 系统 | YAML 工作流标签 | 包管理器 | Node.js | -|---------|------------------------|---------------------------|--------------| -| Linux | `ubuntu-latest` | npm | 10,12,14,16 | -| Linux | `ubuntu-18.04` | yarn,pnpm | 10,12 | -| Windows | `windows-latest` | npm | 10,12,14,16 | -| MacOS | `macos-latest` | npm | 10,12,14,16 | +| 系统 | YAML 工作流标签 | 包管理器 | Node.js | +| ------- | ---------------- | --------- | ----------- | +| Linux | `ubuntu-latest` | npm | 10,12,14,16 | +| Linux | `ubuntu-18.04` | yarn,pnpm | 10,12 | +| Windows | `windows-latest` | npm | 10,12,14,16 | +| MacOS | `macos-latest` | npm | 10,12,14,16 | -使用 [yarn](https://yarnpkg.com/) 命令需添加 `--ignore-engines`。 \ No newline at end of file +使用 [yarn](https://yarnpkg.com/) 命令需添加 `--ignore-engines`。 diff --git a/doc/fastify-docs/docs/Lifecycle.md b/doc/fastify-docs/docs/Lifecycle.md index 850ca6a..1011b3e 100644 --- a/doc/fastify-docs/docs/Lifecycle.md +++ b/doc/fastify-docs/docs/Lifecycle.md @@ -1,8 +1,9 @@

Fastify

## 生命周期 + 下图展示了 Fastify 的内部生命周期。
-每个节点右边的分支为生命周期的下一阶段,左边的则是上一个生命周期抛出错误时产生的错误码 *(请注意 Fastify 会自动处理所有的错误)*。 +每个节点右边的分支为生命周期的下一阶段,左边的则是上一个生命周期抛出错误时产生的错误码 _(请注意 Fastify 会自动处理所有的错误)_。 ``` Incoming Request @@ -37,10 +38,11 @@ Incoming Request ``` 在`用户编写的处理函数`执行前或执行时,你可以调用 `reply.hijack()` 以使得 Fastify: + - 终止运行所有钩子及用户的处理函数 - 不再自动发送响应 -特别注意 (*):假如使用了 `reply.raw` 来发送响应,则 `onResponse` 依旧会执行。 +特别注意 (\*):假如使用了 `reply.raw` 来发送响应,则 `onResponse` 依旧会执行。 ## 响应生命周期 @@ -75,4 +77,4 @@ Incoming Request - 通过[响应序列化方法](Server.md#setreplyserializer) (如有设置) - 或当为返回的 HTTP 状态码设置了 JSON schema 时,通过[序列化函数生成器](Server.md#setserializercompiler) -- 或通过默认的 `JSON.stringify` 函数 \ No newline at end of file +- 或通过默认的 `JSON.stringify` 函数 diff --git a/doc/fastify-docs/docs/Logging.md b/doc/fastify-docs/docs/Logging.md index 839a669..e4046a4 100644 --- a/doc/fastify-docs/docs/Logging.md +++ b/doc/fastify-docs/docs/Logging.md @@ -10,107 +10,111 @@ Fastify 专注于性能,因此使用了 [pino](https://github.com/pinojs/pino) 开启日志相当简单: ```js -const fastify = require('fastify')({ - logger: true -}) +const fastify = require("fastify")({ + logger: true, +}); -fastify.get('/', options, function (request, reply) { - request.log.info('Some info about the current request') - reply.send({ hello: 'world' }) -}) +fastify.get("/", options, function (request, reply) { + request.log.info("Some info about the current request"); + reply.send({ hello: "world" }); +}); ``` 在路由函数之外,你可以通过 Fastify 实例上挂载的 Pino 实例来记录日志: + ```js -fastify.log.info('Something important happened!'); +fastify.log.info("Something important happened!"); ``` 如果你想为日志配置选项,直接将选项传递给 Fastify 实例就可以了。 你可以在 [Pino 的文档](https://github.com/pinojs/pino/blob/master/docs/api.md#pinooptions-stream)中找到全部选项。如果你想指定文件地址,可以: ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ logger: { - level: 'info', - file: '/path/to/file' // 将调用 pino.destination() - } -}) -fastify.get('/', options, function (request, reply) { - request.log.info('Some info about the current request') - reply.send({ hello: 'world' }) -}) + level: "info", + file: "/path/to/file", // 将调用 pino.destination() + }, +}); +fastify.get("/", options, function (request, reply) { + request.log.info("Some info about the current request"); + reply.send({ hello: "world" }); +}); ``` 如果需要向 Pino 传送自定义流 (stream),仅需在 `logger` 对象中添加 `stream` 一项即可。 ```js -const split = require('split2') -const stream = split(JSON.parse) +const split = require("split2"); +const stream = split(JSON.parse); -const fastify = require('fastify')({ +const fastify = require("fastify")({ logger: { - level: 'info', - stream: stream - } -}) + level: "info", + stream: stream, + }, +}); ``` 默认情况下,Fastify 给每个请求分配了一个 ID 以便跟踪。如果头部存在 "request-id" 即使用该值,否则会生成一个新的增量 ID。你可以通过 Fastify 工厂函数的 [`requestIdHeader`](Server.md#factory-request-id-header) 与 [`genReqId`](Server.md#genreqid) 来进行自定义。 默认的日志工具使用标准的序列化工具,生成包括 `req`、`res` 与 `err` 属性在内的序列化对象。`req` 对象是 Fastify [`Request`](./Request.md) 对象,而 `res` 则是 Fastify [`Reply`](./Reply.md) 对象。可以借由指定自定义的序列化工具来改变这一行为。 + ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ logger: { serializers: { - req (request) { - return { url: request.url } - } - } - } -}) + req(request) { + return { url: request.url }; + }, + }, + }, +}); ``` + 响应的 payload 与 header 可以按如下方式记录日志 (即便这是*不推荐*的做法): ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ logger: { prettyPrint: true, serializers: { - res (reply) { + res(reply) { // 默认 return { - statusCode: reply.statusCode - } + statusCode: reply.statusCode, + }; }, - req (request) { + req(request) { return { method: request.method, url: request.url, path: request.path, parameters: request.parameters, // 记录 header 可能会触犯隐私法律,例如 GDPR (译注:General Data Protection Regulation)。你应该用 "redact" 选项来移除敏感的字段。此外,验证数据也可能在日志中泄露。 - headers: request.headers + headers: request.headers, }; - } - } - } + }, + }, + }, }); ``` + **注**:在 `req` 方法中,body 无法被序列化。因为请求是在创建子日志时就序列化了,而此时 body 尚未被解析。 以下是记录 `req.body` 的一个方法 ```js -app.addHook('preHandler', function (req, reply, done) { +app.addHook("preHandler", function (req, reply, done) { if (req.body) { - req.log.info({ body: req.body }, 'parsed body') + req.log.info({ body: req.body }, "parsed body"); } - done() -}) + done(); +}); ``` -*Pino 之外的日志工具会忽略该选项。* +_Pino 之外的日志工具会忽略该选项。_ 你还可以提供自定义的日志实例。将实例传入,取代配置选项即可。提供的示例必须实现 Pino 的接口,换句话说,便是拥有下列方法: `info`、`error`、`debug`、`fatal`、`warn`、`trace`、`child`。 @@ -118,21 +122,23 @@ app.addHook('preHandler', function (req, reply, done) { 示例: ```js -const log = require('pino')({ level: 'info' }) -const fastify = require('fastify')({ logger: log }) +const log = require("pino")({ level: "info" }); +const fastify = require("fastify")({ logger: log }); -log.info('does not have request information') +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' }) -}) +fastify.get("/", function (request, reply) { + request.log.info( + "includes request information, but is the same logger instance as `log`", + ); + reply.send({ hello: "world" }); +}); ``` -*当前请求的日志实例在[生命周期](Lifecycle.md)的各部分均可使用。* +_当前请求的日志实例在[生命周期](Lifecycle.md)的各部分均可使用。_ ## 日志修订 - + [Pino](https://getpino.io) 支持低开销的日志修订,以隐藏特定内容。 举例来说,出于安全方面的考虑,我们也许想在 HTTP header 的日志中隐藏 `Authorization` 这一个 header: @@ -140,22 +146,22 @@ fastify.get('/', function (request, reply) { const fastify = Fastify({ logger: { stream: stream, - redact: ['req.headers.authorization'], - level: 'info', + redact: ["req.headers.authorization"], + level: "info", serializers: { - req (request) { + req(request) { return { method: request.method, url: request.url, headers: request.headers, hostname: request.hostname, remoteAddress: request.ip, - remotePort: request.socket.remotePort - } - } - } - } -}) + remotePort: request.socket.remotePort, + }; + }, + }, + }, +}); ``` 更多信息请看 https://getpino.io/#/docs/redaction。 diff --git a/doc/fastify-docs/docs/Middleware.md b/doc/fastify-docs/docs/Middleware.md index dbe0eb0..3e48f33 100644 --- a/doc/fastify-docs/docs/Middleware.md +++ b/doc/fastify-docs/docs/Middleware.md @@ -7,22 +7,22 @@ 以下是通过 [`fastify-express`](https://github.com/fastify/fastify-express) 插件,来使用 express 中间件的示例: ```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')()) +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")()); ``` 或者通过 [`middie`](https://github.com/fastify/middie),它提供了对简单的 express 风格的中间件的支持,但性能更佳: ```js -await fastify.register(require('middie')) -fastify.use(require('cors')()) +await fastify.register(require("middie")); +fastify.use(require("cors")()); ``` ### 替代 -Fastify 提供了最常用中间件的替代品,例如:[`fastify-helmet`](https://github.com/fastify/fastify-helmet) 之于 [`helmet`](https://github.com/helmetjs/helmet),[`fastify-cors`](https://github.com/fastify/fastify-cors) 之于 [`cors`](https://github.com/expressjs/cors),以及 [`fastify-static`](https://github.com/fastify/fastify-static) 之于 [`serve-static`](https://github.com/expressjs/serve-static)。 \ No newline at end of file +Fastify 提供了最常用中间件的替代品,例如:[`fastify-helmet`](https://github.com/fastify/fastify-helmet) 之于 [`helmet`](https://github.com/helmetjs/helmet),[`fastify-cors`](https://github.com/fastify/fastify-cors) 之于 [`cors`](https://github.com/expressjs/cors),以及 [`fastify-static`](https://github.com/fastify/fastify-static) 之于 [`serve-static`](https://github.com/expressjs/serve-static)。 diff --git a/doc/fastify-docs/docs/Migration-Guide-V3.md b/doc/fastify-docs/docs/Migration-Guide-V3.md index d08e8ee..3a5a28f 100644 --- a/doc/fastify-docs/docs/Migration-Guide-V3.md +++ b/doc/fastify-docs/docs/Migration-Guide-V3.md @@ -16,15 +16,15 @@ ```js // 在 Fastify v2 中使用 Express 的 `cors` 中间件。 -fastify.use(require('cors')()); +fastify.use(require("cors")()); ``` **v3:** ```js // 在 Fastify v3 中使用 Express 的 `cors` 中间件。 -await fastify.register(require('fastify-express')); -fastify.use(require('cors')()); +await fastify.register(require("fastify-express")); +fastify.use(require("cors")()); ``` ### 日志序列化 ([#2017](https://github.com/fastify/fastify/pull/2017)) @@ -36,34 +36,34 @@ fastify.use(require('cors')()); **v2:** ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ logger: { serializers: { res(res) { return { statusCode: res.statusCode, - customProp: res.customProp + customProp: res.customProp, }; - } - } - } + }, + }, + }, }); ``` **v3:** ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ logger: { serializers: { res(reply) { return { statusCode: reply.statusCode, // 无需更改 - customProp: reply.raw.customProp // 从 res 对象 (译注:即 Node.js 原生的响应对象,此处为 raw) 中记录属性 + customProp: reply.raw.customProp, // 从 res 对象 (译注:即 Node.js 原生的响应对象,此处为 raw) 中记录属性 }; - } - } - } + }, + }, + }, }); ``` @@ -75,7 +75,7 @@ const fastify = require('fastify')({ ```js const schema = { - body: 'schemaId#' + body: "schemaId#", }; fastify.route({ method, url, schema, handler }); ``` @@ -85,8 +85,8 @@ fastify.route({ method, url, schema, handler }); ```js const schema = { body: { - $ref: 'schemaId#' - } + $ref: "schemaId#", + }, }; fastify.route({ method, url, schema, handler }); ``` @@ -103,8 +103,8 @@ const ajv = new AJV(); ajv.addSchema(schemaA); ajv.addSchema(schemaB); -fastify.setSchemaCompiler(schema => ajv.compile(schema)); -fastify.setSchemaResolver(ref => ajv.getSchema(ref).schema); +fastify.setSchemaCompiler((schema) => ajv.compile(schema)); +fastify.setSchemaResolver((ref) => ajv.getSchema(ref).schema); ``` **v3:** @@ -116,7 +116,7 @@ ajv.addSchema(schemaA); ajv.addSchema(schemaB); fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => - ajv.compile(schema) + ajv.compile(schema), ); ``` @@ -173,14 +173,14 @@ interface PingBody { } server.get( - '/ping/:bar', + "/ping/:bar", opts, (request, reply) => { console.log(request.query); // 类型为 `PingQuerystring` console.log(request.params); // 类型为 `PingParams` console.log(request.headers); // 类型为 `PingHeaders` console.log(request.body); // 类型为 `PingBody` - } + }, ); ``` @@ -192,7 +192,7 @@ server.get<{ Params: PingParams; Headers: PingHeaders; Body: PingBody; -}>('/ping/:bar', opts, async (request, reply) => { +}>("/ping/:bar", opts, async (request, reply) => { console.log(request.query); // 类型为 `PingQuerystring` console.log(request.params); // 类型为 `PingParams` console.log(request.headers); // 类型为 `PingHeaders` @@ -209,12 +209,12 @@ server.get<{ ```js fastify.setErrorHandler((error, request, reply) => { // 不会调用 - reply.send(error) -}) -fastify.get('/', (request, reply) => { - const maybeAnArray = request.body.something ? [] : 'I am a string' - maybeAnArray.substr() // 抛错:[].substr is not a function,同时服务器崩溃 -}) + reply.send(error); +}); +fastify.get("/", (request, reply) => { + const maybeAnArray = request.body.something ? [] : "I am a string"; + maybeAnArray.substr(); // 抛错:[].substr is not a function,同时服务器崩溃 +}); ``` **v3:** @@ -222,12 +222,12 @@ fastify.get('/', (request, reply) => { ```js fastify.setErrorHandler((error, request, reply) => { // 会调用 - reply.send(error) -}) -fastify.get('/', (request, reply) => { - const maybeAnArray = request.body.something ? [] : 'I am a string' - maybeAnArray.substr() // 抛错:[].substr is not a function,但错误得到控制 -}) + reply.send(error); +}); +fastify.get("/", (request, reply) => { + const maybeAnArray = request.body.something ? [] : "I am a string"; + maybeAnArray.substr(); // 抛错:[].substr is not a function,但错误得到控制 +}); ``` ## 更多特性与改善 @@ -238,4 +238,4 @@ fastify.get('/', (request, reply) => { - 添加 [`connectionTimeout`](Server.md#factory-connection-timeout) 选项 ([#2086](https://github.com/fastify/fastify/pull/2086)) - 添加 [`keepAliveTimeout`](Server.md#factory-keep-alive-timeout) 选项 ([#2086](https://github.com/fastify/fastify/pull/2086)) - [插件](Plugins.md#async-await)支持 async-await ([#2093](https://github.com/fastify/fastify/pull/2093)) -- 支持将对象作为错误抛出 ([#2134](https://github.com/fastify/fastify/pull/2134)) \ No newline at end of file +- 支持将对象作为错误抛出 ([#2134](https://github.com/fastify/fastify/pull/2134)) diff --git a/doc/fastify-docs/docs/Plugins-Guide.md b/doc/fastify-docs/docs/Plugins-Guide.md index 87a7c7b..d8a6a2f 100644 --- a/doc/fastify-docs/docs/Plugins-Guide.md +++ b/doc/fastify-docs/docs/Plugins-Guide.md @@ -1,11 +1,13 @@

Fastify

# 插件漫游指南 + 首先, `不要恐慌`! Fastify 从一开始就搭建成非常模块化的系统. 我们搭建了非常强健的 API 来允许你创建命名空间, 来添加工具方法. Fastify 创建的封装模型可以让你在任何时候将你的应用分割成不同的微服务, 而无需重构整个应用. **内容清单** + - [注册器](#register) - [装饰器](#decorators) - [钩子方法](#hooks) @@ -17,310 +19,351 @@ Fastify 从一开始就搭建成非常模块化的系统. 我们搭建了非常 - [开始!](#start) + ## 注册器 + 就像在 JavaScript 万物都是对象, 在 Fastify 万物都是插件.
你的路由, 你的工具方法等等都是插件. 无论添加什么功能的插件, 你都可以使用 Fastify 优秀又独一无二的 API: [`register`](Plugins.md). + ```js -fastify.register( - require('./my-plugin'), - { options } -) +fastify.register(require("./my-plugin"), { options }); ``` + `register` 创建一个新的 Fastify 上下文, 这意味着如果你对 Fastify 的实例做任何改动, 这些改动不会反映到上下文的父级上. 换句话说, 封装! -*为什么封装这么重要?*
+_为什么封装这么重要?_
那么, 假设你创建了一个具有开创性的初创公司, 你会怎么做? 你创建了一个包含所有东西的 API 服务器, 所有东西都在同一个地方, 一个庞然大物!
现在, 你增长得非常迅速, 想要改变架构去尝试微服务. 通常这意味着非常多的工作, 因为交叉依赖和缺少关注点的分离.
Fastify 在这个层面上可以帮助你很多, 多亏了封装模型, 它完全避免了交叉依赖, 并且帮助你将组织成高聚合的代码块. -*让我们回到如何正确地使用 `register`.*
+_让我们回到如何正确地使用 `register`._
插件必须输出一个有以下参数的方法 + ```js -module.exports = function (fastify, options, done) {} +module.exports = function (fastify, options, done) {}; ``` + `fastify` 就是封装的 Fastify 实例, `options` 就是选项对象, 而 `done` 是一个在插件准备好了之后**必须**要调用的方法. -Fastify 的插件模型是完全可重入的和基于图(数据结构)的, 它能够处理任何异步代码并且保证插件的加载顺序, 甚至是关闭顺序! *如何做到的?* 很高兴你发问了, 查看下 [`avvio`](https://github.com/mcollina/avvio)! Fastify 在 `.listen()`, `.inject()` 或者 `.ready()` 被调用了之后开始加载插件. +Fastify 的插件模型是完全可重入的和基于图(数据结构)的, 它能够处理任何异步代码并且保证插件的加载顺序, 甚至是关闭顺序! _如何做到的?_ 很高兴你发问了, 查看下 [`avvio`](https://github.com/mcollina/avvio)! Fastify 在 `.listen()`, `.inject()` 或者 `.ready()` 被调用了之后开始加载插件. 在插件里面你可以做任何想要做的事情, 注册路由, 工具方法 (我们马上会看到这个) 和进行嵌套的注册, 只要记住当所有都设置好了后调用 `done`! + ```js module.exports = function (fastify, options, done) { - fastify.get('/plugin', (request, reply) => { - reply.send({ hello: 'world' }) - }) + fastify.get("/plugin", (request, reply) => { + reply.send({ hello: "world" }); + }); - done() -} + done(); +}; ``` 那么现在你已经知道了如何使用 `register` API 并且知道它是怎么工作的, 但我们如何给 Fastify 添加新的功能, 并且分享给其他的开发者? + ## 装饰器 + 好了, 假设你写了一个非常好的工具方法, 因此你决定在你所有的代码里都能够用这个方法. 你改怎么做? 可能是像以下代码一样: + ```js // your-awesome-utility.js module.exports = function (a, b) { - return a + b -} + return a + b; +}; ``` + ```js -const util = require('./your-awesome-utility') -console.log(util('that is ', 'awesome')) +const util = require("./your-awesome-utility"); +console.log(util("that is ", "awesome")); ``` + 现在你需要在所有需要这个方法的文件中引入它. (别忘了你可能在测试中也需要它). -Fastify 提供了一个更优雅的方法, *装饰器*. +Fastify 提供了一个更优雅的方法, _装饰器_. 创建一个装饰器非常简单, 只要使用 [`decorate`](Decorators.md) API: + ```js -fastify.decorate('util', (a, b) => a + b) +fastify.decorate("util", (a, b) => a + b); ``` + 现在你可以在任意地方通过 `fastify.util` 调用你的方法, 甚至在你的测试中.
这里神奇的是: 你还记得之前我们讨论的封装? 同时使用 `register` 和 `decorate` 可以实现, 让我用例子来阐明这个事情: + ```js fastify.register((instance, opts, done) => { - instance.decorate('util', (a, b) => a + b) - console.log(instance.util('that is ', 'awesome')) + instance.decorate("util", (a, b) => a + b); + console.log(instance.util("that is ", "awesome")); - done() -}) + done(); +}); fastify.register((instance, opts, done) => { - console.log(instance.util('that is ', 'awesome')) // 这里会抛错 + console.log(instance.util("that is ", "awesome")); // 这里会抛错 - done() -}) + done(); +}); ``` + 在第二个注册器中调用 `instance.util` 会抛错, 因为 `util` 只存在第一个注册器的上下文中.
让我们更深入地看一下: 当使用 `register` API 每次都会创建一个新的上下文而且这避免了上文提到的这个状况. 但是注意, 封装只会在父级和同级中有效, 不会在子级中有效. + ```js fastify.register((instance, opts, done) => { - instance.decorate('util', (a, b) => a + b) - console.log(instance.util('that is ', 'awesome')) + 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')) // 这里不会抛错 - done() - }) + console.log(instance.util("that is ", "awesome")); // 这里不会抛错 + done(); + }); - done() -}) + done(); +}); fastify.register((instance, opts, done) => { - console.log(instance.util('that is ', 'awesome')) // 这里会抛错 + console.log(instance.util("that is ", "awesome")); // 这里会抛错 - done() -}) + done(); +}); ``` -*PS: 如果你需要全局的工具方法, 请注意要声明在应用根作用域上. 或者你可以使用 `fastify-plugin` 工具, [参考](#distribution).* + +_PS: 如果你需要全局的工具方法, 请注意要声明在应用根作用域上. 或者你可以使用 `fastify-plugin` 工具, [参考](#distribution)._ `decorate` 不是唯一可以用来扩展服务器的功能的 API, 你还可以使用 `decorateRequest` 和 `decorateReply`. -*`decorateRequest` 和 `decorateReply`? 为什么我们已经有了 `decorate` 还需要它们?*
+_`decorateRequest` 和 `decorateReply`? 为什么我们已经有了 `decorate` 还需要它们?_
好问题, 是为了让开发者更方便地使用 Fastify. 让我们看看这个例子: -```js -fastify.decorate('html', payload => { - return generateHtml(payload) -}) -fastify.get('/html', (request, reply) => { - reply - .type('text/html') - .send(fastify.html({ hello: 'world' })) -}) +```js +fastify.decorate("html", (payload) => { + return generateHtml(payload); +}); + +fastify.get("/html", (request, reply) => { + reply.type("text/html").send(fastify.html({ hello: "world" })); +}); ``` -这个可行, 但可以变得更好! -```js -fastify.decorateReply('html', function (payload) { - this.type('text/html') // this 是 'Reply' 对象 - this.send(generateHtml(payload)) -}) -fastify.get('/html', (request, reply) => { - reply.html({ hello: 'world' }) -}) +这个可行, 但可以变得更好! + +```js +fastify.decorateReply("html", function (payload) { + this.type("text/html"); // this 是 'Reply' 对象 + this.send(generateHtml(payload)); +}); + +fastify.get("/html", (request, reply) => { + reply.html({ hello: "world" }); +}); ``` 你可以对 `request` 对象做同样的事: + ```js -fastify.decorate('getHeader', (req, header) => { - return req.headers[header] -}) +fastify.decorate("getHeader", (req, header) => { + return req.headers[header]; +}); -fastify.addHook('preHandler', (request, reply, done) => { - request.isHappy = fastify.getHeader(request.raw, 'happy') - done() -}) +fastify.addHook("preHandler", (request, reply, done) => { + request.isHappy = fastify.getHeader(request.raw, "happy"); + done(); +}); -fastify.get('/happiness', (request, reply) => { - reply.send({ happy: request.isHappy }) -}) +fastify.get("/happiness", (request, reply) => { + reply.send({ happy: request.isHappy }); +}); ``` + 这个也可行, 但可以变得更好! + ```js -fastify.decorateRequest('setHeader', function (header) { - this.isHappy = this.headers[header] -}) +fastify.decorateRequest("setHeader", function (header) { + this.isHappy = this.headers[header]; +}); -fastify.decorateRequest('isHappy', false) // 这会添加到 Request 对象的原型中, 好快! +fastify.decorateRequest("isHappy", false); // 这会添加到 Request 对象的原型中, 好快! -fastify.addHook('preHandler', (request, reply, done) => { - request.setHeader('happy') - done() -}) +fastify.addHook("preHandler", (request, reply, done) => { + request.setHeader("happy"); + done(); +}); -fastify.get('/happiness', (request, reply) => { - reply.send({ happy: request.isHappy }) -}) +fastify.get("/happiness", (request, reply) => { + reply.send({ happy: request.isHappy }); +}); ``` 我们见识了如何扩展服务器的功能并且如何处理封装系统, 但是假如你需要加一个方法, 每次在服务器 "[emits](Lifecycle.md)" 事件的时候执行这个方法, 该怎么做? + ## 钩子方法 + 你刚刚构建了工具方法, 现在你需要在每个请求的时候都执行这个方法, 你大概会这样做: + ```js -fastify.decorate('util', (request, key, value) => { request[key] = value }) +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("/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) -}) +fastify.get("/plugin2", (request, reply) => { + fastify.util(request, "timestamp", new Date()); + reply.send(request); +}); ``` + 我想大家都同意这个代码是很糟的. 代码重复, 可读性差并且不能扩展. 那么你该怎么消除这个问题呢? 是的, 使用[钩子方法](Hooks.md)!
+ ```js -fastify.decorate('util', (request, key, value) => { request[key] = value }) +fastify.decorate("util", (request, key, value) => { + request[key] = value; +}); -fastify.addHook('preHandler', (request, reply, done) => { - fastify.util(request, 'timestamp', new Date()) - done() -}) +fastify.addHook("preHandler", (request, reply, done) => { + fastify.util(request, "timestamp", new Date()); + done(); +}); -fastify.get('/plugin1', (request, reply) => { - reply.send(request) -}) +fastify.get("/plugin1", (request, reply) => { + reply.send(request); +}); -fastify.get('/plugin2', (request, reply) => { - reply.send(request) -}) +fastify.get("/plugin2", (request, reply) => { + reply.send(request); +}); ``` + 现在每个请求都会运行工具方法, 很显然你可以注册任意多的需要的钩子方法.
-有时, 你希望只在一个路由子集中执行钩子方法, 这个怎么做到? 对了, 封装! +有时, 你希望只在一个路由子集中执行钩子方法, 这个怎么做到? 对了, 封装! ```js fastify.register((instance, opts, done) => { - instance.decorate('util', (request, key, value) => { request[key] = value }) + instance.decorate("util", (request, key, value) => { + request[key] = value; + }); - instance.addHook('preHandler', (request, reply, done) => { - instance.util(request, 'timestamp', new Date()) - done() - }) + instance.addHook("preHandler", (request, reply, done) => { + instance.util(request, "timestamp", new Date()); + done(); + }); - instance.get('/plugin1', (request, reply) => { - reply.send(request) - }) + instance.get("/plugin1", (request, reply) => { + reply.send(request); + }); - done() -}) + done(); +}); -fastify.get('/plugin2', (request, reply) => { - reply.send(request) -}) +fastify.get("/plugin2", (request, reply) => { + reply.send(request); +}); ``` + 现在你的钩子方法只会在第一个路由中运行! -你可能已经注意到, `request` and `reply` 不是标准的 Nodejs *request* 和 *response* 对象, 而是 Fastify 对象.
+你可能已经注意到, `request` and `reply` 不是标准的 Nodejs _request_ 和 _response_ 对象, 而是 Fastify 对象.
+ ## 如何处理封装与分发 + 完美, 现在你知道了(几乎)所有的扩展 Fastify 的工具. 但可能你遇到了一个大问题: 如何分发你的代码? -我们推荐将所有代码包裹在一个`注册器`中分发, 这样你的插件可以支持异步启动 *(`decorate` 是一个同步 API)*, 例如建立数据库链接. +我们推荐将所有代码包裹在一个`注册器`中分发, 这样你的插件可以支持异步启动 _(`decorate` 是一个同步 API)_, 例如建立数据库链接. -*等等? 你不是告诉我 `register` 会创建封装的上下文, 那么我创建的不是就外层不可见了?*
+_等等? 你不是告诉我 `register` 会创建封装的上下文, 那么我创建的不是就外层不可见了?_
是的, 我是说过. 但我没告诉你的是, 你可以通过 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 模块告诉 Fastify 不要进行封装. -```js -const fp = require('fastify-plugin') -const dbClient = require('db-client') -function dbPlugin (fastify, opts, done) { +```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() - }) + fastify.decorate("db", conn); + done(); + }); } -module.exports = fp(dbPlugin) +module.exports = fp(dbPlugin); ``` + 你还可以告诉 `fastify-plugin` 去检查安装的 Fastify 版本, 万一你需要特定的 API. -正如前面所述,Fastify 在 `.listen()`、`.inject()` 以及 `.ready()` 被调用,也即插件被声明 __之后__ 才开始加载插件。这么一来,即使插件通过 [`decorate`](Decorators.md) 向外部的 Fastify 实例注入了变量,在调用 `.listen()`、`.inject()` 和 `.ready()` 之前,这些变量是获取不到的。 +正如前面所述,Fastify 在 `.listen()`、`.inject()` 以及 `.ready()` 被调用,也即插件被声明 **之后** 才开始加载插件。这么一来,即使插件通过 [`decorate`](Decorators.md) 向外部的 Fastify 实例注入了变量,在调用 `.listen()`、`.inject()` 和 `.ready()` 之前,这些变量是获取不到的。 当你需要在 `register` 方法的 `options` 参数里使用另一个插件注入的变量时,你可以向 `options` 传递一个函数参数,而不是对象: -```js -const fastify = require('fastify')() -const fp = require('fastify-plugin') -const dbClient = require('db-client') -function dbPlugin (fastify, opts, done) { +```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.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' } -}) +fastify.register(fp(dbPlugin), { url: "https://example.com" }); +fastify.register(require("your-plugin"), (parent) => { + return { connection: parent.db, otherOption: "foo-bar" }; +}); ``` + 在上面的例子中,`register` 方法的第二个参数的 `parent` 变量是注册了插件的**外部 Fastify 实例**的一份拷贝。这就意味着我们可以获取到之前声明的插件所注入的变量了。 + ## ESM 的支持 自 [Node.js `v13.3.0`](https://nodejs.org/api/esm.html) 开始, ESM 也被支持了!写插件时,你只需要将其作为 ESM 模块导出即可! ```js // plugin.mjs -async function plugin (fastify, opts) { - fastify.get('/', async (req, reply) => { - return { hello: 'world' } - }) +async function plugin(fastify, opts) { + fastify.get("/", async (req, reply) => { + return { hello: "world" }; + }); } -export default plugin +export default plugin; ``` -__注意__:Fastify 不支持具名导入 ESM 模块,但支持 `default` 导入。 +**注意**:Fastify 不支持具名导入 ESM 模块,但支持 `default` 导入。 ```js // server.mjs -import Fastify from 'fastify' +import Fastify from "fastify"; -const fastify = Fastify() +const fastify = Fastify(); ///... fastify.listen(3000, (err, address) => { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -}) +}); ``` + ## 错误处理 + 你的插件也可能在启动的时候失败. 或许你预料到这个并且在这种情况下有特定的处理逻辑. 你该怎么实现呢? `after` API 就是你需要的. `after` 注册一个回调, 在注册之后就会调用这个回调, 它可以有三个参数.
回调会基于不同的参数而变化: @@ -331,46 +374,51 @@ fastify.listen(3000, (err, address) => { 1. 如果有三个参数, 第一个是错误对象, 第二个是顶级上下文(除非你同时指定了服务器和复写, 在这个情况下将会是那个复写的返回), 第三个是完成回调. 让我们看看如何使用它: + ```js -fastify - .register(require('./database-connector')) - .after(err => { - if (err) throw err - }) +fastify.register(require("./database-connector")).after((err) => { + if (err) throw err; +}); ``` + ## 自定义错误 + 假如你的插件需要暴露自定义的错误,[`fastify-error`](https://github.com/fastify/fastify-error) 能帮助你轻松地在代码或插件中生成一致的错误对象。 ```js -const createError = require('fastify-error') -const CustomError = createError('ERROR_CODE', 'message') -console.log(new CustomError()) +const createError = require("fastify-error"); +const CustomError = createError("ERROR_CODE", "message"); +console.log(new CustomError()); ``` + ## 发布提醒 + 假如你要提示用户某个 API 不被推荐,或某个特殊场景需要注意,你可以使用 [`fastify-warning`](https://github.com/fastify/fastify-warning)。 ```js -const warning = require('fastify-warning')() -warning.create('FastifyDeprecation', 'FST_ERROR_CODE', 'message') -warning.emit('FST_ERROR_CODE') +const warning = require("fastify-warning")(); +warning.create("FastifyDeprecation", "FST_ERROR_CODE", "message"); +warning.emit("FST_ERROR_CODE"); ``` + ## 开始! -太棒了, 现在你已经知道了所有创建插件需要的关于 Fastify 和它的插件系统的知识, 如果你写了插件请告诉我们! 我们会将它加入到 [*生态*](https://github.com/fastify/fastify#ecosystem) 章节中! + +太棒了, 现在你已经知道了所有创建插件需要的关于 Fastify 和它的插件系统的知识, 如果你写了插件请告诉我们! 我们会将它加入到 [_生态_](https://github.com/fastify/fastify#ecosystem) 章节中! 如果你想要看看真正的插件例子, 查看: + - [`point-of-view`](https://github.com/fastify/point-of-view) -给 Fastify 提供模版 (*ejs, pug, handlebars, marko*) 支持. + 给 Fastify 提供模版 (_ejs, pug, handlebars, marko_) 支持. - [`fastify-mongodb`](https://github.com/fastify/fastify-mongodb) -Fastify MongoDB 连接插件, 可以在全局使用同一 MongoDb 连接池. + Fastify MongoDB 连接插件, 可以在全局使用同一 MongoDb 连接池. - [`fastify-multipart`](https://github.com/fastify/fastify-multipart) -Multipart 支持 + Multipart 支持 - [`fastify-helmet`](https://github.com/fastify/fastify-helmet) 重要的安全头部支持 - -*如果感觉还差什么? 告诉我们! :)* +_如果感觉还差什么? 告诉我们! :)_ diff --git a/doc/fastify-docs/docs/Plugins.md b/doc/fastify-docs/docs/Plugins.md index 10b8927..14ff937 100644 --- a/doc/fastify-docs/docs/Plugins.md +++ b/doc/fastify-docs/docs/Plugins.md @@ -1,61 +1,67 @@

Fastify

## 插件 + Fastify 允许用户通过插件的方式扩展自身的功能。 一个插件可以是一组路由,一个服务器[装饰器](Decorators.md)或者其他任意的东西。 在使用一个或者许多插件时,只需要一个 API `register`。
-默认, `register` 会创建一个 *新的作用域( Scope )*, 这意味着你能够改变 Fastify 实例(通过`decorate`), 这个改变不会反映到当前作用域, 只会影响到子作用域。 这样可以做到插件的*封装*和*继承*, 我们创建了一个*无回路有向图*(DAG), 因此不会有交叉依赖的问题。 +默认, `register` 会创建一个 _新的作用域( Scope )_, 这意味着你能够改变 Fastify 实例(通过`decorate`), 这个改变不会反映到当前作用域, 只会影响到子作用域。 这样可以做到插件的*封装*和*继承*, 我们创建了一个*无回路有向图*(DAG), 因此不会有交叉依赖的问题。 你已经在[起步](Getting-Started.md#register)部分很直观的看到了怎么使用这个 API。 + ``` fastify.register(plugin, [options]) ``` + ### 插件选项 + `fastify.register` 可选参数列表支持一组预定义的 Fastify 可用的参数, 除了当插件使用了 [fastify-plugin](https://github.com/fastify/fastify-plugin)。 选项对象会在插件被调用传递进去, 无论这个插件是否用了 fastify-plugin。 当前支持的选项有: -+ [`日志级别`](Routes.md#custom-log-level) -+ [`日志序列化器`](Routes.md#custom-log-serializer) -+ [`前缀`](Plugins.md#route-prefixing-options) +- [`日志级别`](Routes.md#custom-log-level) +- [`日志序列化器`](Routes.md#custom-log-serializer) +- [`前缀`](Plugins.md#route-prefixing-options) **注意:当使用 fastify-plugin 时,这些选项会被忽略** Fastify 有可能在将来会直接支持其他的选项。 因此为了避免冲突, 插件应该考虑给选项加入命名空间。 举个例子, 插件 `foo` 可以像以下代码一样注册: ```js -fastify.register(require('fastify-foo'), { - prefix: '/foo', +fastify.register(require("fastify-foo"), { + prefix: "/foo", foo: { - fooOption1: 'value', - fooOption2: 'value' - } -}) + fooOption1: "value", + fooOption2: "value", + }, +}); ``` 如果不考虑冲突, 插件可以简化成直接接收对象参数: ```js -fastify.register(require('fastify-foo'), { - prefix: '/foo', - fooOption1: 'value', - fooOption2: 'value' -}) +fastify.register(require("fastify-foo"), { + prefix: "/foo", + fooOption1: "value", + fooOption2: "value", +}); ``` `options` 参数还可以是一个在插件注册时确定的 `函数`,这个函数的第一位参数是 Fastify 实例: ```js -const fp = require('fastify-plugin') +const fp = require("fastify-plugin"); -fastify.register(fp((fastify, opts, done) => { - fastify.decorate('foo_bar', { hello: 'world' }) +fastify.register( + fp((fastify, opts, done) => { + fastify.decorate("foo_bar", { hello: "world" }); - done() -})) + done(); + }), +); // fastify-foo 的 options 参数会是 { hello: 'world' } -fastify.register(require('fastify-foo'), parent => parent.foo_bar) +fastify.register(require("fastify-foo"), (parent) => parent.foo_bar); ``` 传给函数的 Fastify 实例是插件声明时**外部 Fastify 实例**的最新状态,允许你访问**注册顺序**在前的插件通过 [`decorate`](Decorators.md) 注入的变量。这在需要依赖前置插件对于 Fastify 实例的改动时派得上用场,比如,使用已存在的数据库连接来包装你的插件。 @@ -63,124 +69,142 @@ fastify.register(require('fastify-foo'), parent => parent.foo_bar) 请记住,传给函数的 Fastify 实例和传给插件的实例是一样的,不是外部 Fastify 实例的引用,而是拷贝。任何对函数的实例参数的操作结果,都会和在插件函数中操作的结果一致。也就是说,如果调用了 `decorate`,被注入的变量在插件函数中也是可用的,除非你使用 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 包装了这个插件。 + #### 路由前缀选项 + 如果你传入以 `prefix`为 key , `string` 为值的选项, Fastify 会自动为这个插件下所有的路由添加这个前缀, 更多信息可以查询 [这里](Routes.md#route-prefixing).
注意如果使用了 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 这个选项不会起作用。 + #### 错误处理 + 错误处理是由 [avvio](https://github.com/mcollina/avvio#error-handling) 解决的。
一个通用的原则, 我们建议在下一个 `after` 或 `ready` 代码块中处理错误, 否则错误将出现在 `listen` 回调里。 ```js -fastify.register(require('my-plugin')) +fastify.register(require("my-plugin")); // `after` 将在上一个 `register` 结束后执行 -fastify.after(err => console.log(err)) +fastify.after((err) => console.log(err)); // `ready` 将在所有 `register` 结束后执行 -fastify.ready(err => console.log(err)) +fastify.ready((err) => console.log(err)); // `listen` 是一个特殊的 `ready`, // 因此它的执行时机与 `ready` 一致 fastify.listen(3000, (err, address) => { - if (err) console.log(err) -}) + if (err) console.log(err); +}); ``` + ### async/await -`after`、`ready` 与 `listen` 支持 *async/await*,同时 `fastify` 也是一个 [Thenable](https://promisesaplus.com/) 对象。 +`after`、`ready` 与 `listen` 支持 _async/await_,同时 `fastify` 也是一个 [Thenable](https://promisesaplus.com/) 对象。 ```js -await fastify.register(require('my-plugin')) +await fastify.register(require("my-plugin")); -await fastify.after() +await fastify.after(); -await fastify.ready() +await fastify.ready(); -await fastify.listen(3000) +await fastify.listen(3000); ``` + #### ESM 的支持 自 [Node.js `v13.3.0`](https://nodejs.org/api/esm.html) 开始, ESM 也被支持了! ```js // main.mjs -import Fastify from 'fastify' -const fastify = Fastify() +import Fastify from "fastify"; +const fastify = Fastify(); -fastify.register(import('./plugin.mjs')) +fastify.register(import("./plugin.mjs")); -fastify.listen(3000, console.log) +fastify.listen(3000, console.log); // plugin.mjs -async function plugin (fastify, opts) { - fastify.get('/', async (req, reply) => { - return { hello: 'world' } - }) +async function plugin(fastify, opts) { + fastify.get("/", async (req, reply) => { + return { hello: "world" }; + }); } -export default plugin +export default plugin; ``` + ### 创建插件 + 创建插件非常简单, 你只需要创建一个方法, 这个方法接收三个参数: `fastify` 实例、`options` 选项和 `done` 回调。
例子: + ```js module.exports = function (fastify, opts, done) { - fastify.decorate('utility', () => {}) + fastify.decorate("utility", () => {}); - fastify.get('/', handler) + fastify.get("/", handler); - done() -} + done(); +}; ``` + 你也可以在一个 `register` 内部添加其他 `register`: + ```js module.exports = function (fastify, opts, done) { - fastify.decorate('utility', () => {}) + fastify.decorate("utility", () => {}); - fastify.get('/', handler) + fastify.get("/", handler); - fastify.register(require('./other-plugin')) + fastify.register(require("./other-plugin")); - done() -} + done(); +}; ``` + 有时候, 你需要知道这个服务器何时即将关闭, 例如在你必须关闭数据库连接的时候。 要知道什么时候发生这种情况, 你可以用 [`'onClose'`](Hooks.md#on-close) 钩子。 别忘了 `register` 会创建一个新的 Fastify 作用域, 如果你不需要, 阅读下面的章节。 + ### 处理作用域 + 如果你使用 `register` 仅仅是为了通过[`decorate`](Decorators.md)扩展服务器的功能, 你需要告诉 Fastify 不要创建新的上下文, 不然你的改动不会影响其他作用域中的用户。 你有两种方式告诉 Fastify 避免创建新的上下文: -- 使用 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 模块 + +- 使用 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 模块 - 使用 `'skip-override'` 隐藏属性 我们建议使用 `fastify-plugin` 模块, 因为它是专门用来为你解决这个问题, 并且你可以传一个能够支持的 Fastify 版本范围的参数。 + ```js -const fp = require('fastify-plugin') +const fp = require("fastify-plugin"); module.exports = fp(function (fastify, opts, done) { - fastify.decorate('utility', () => {}) - done() -}, '0.x') + fastify.decorate("utility", () => {}); + done(); +}, "0.x"); ``` + 参考 [`fastify-plugin`](https://github.com/fastify/fastify-plugin) 文档了解更多这个模块。 如果你不用 `fastify-plugin` 模块, 可以使用 `'skip-override'` 隐藏属性, 但我们不推荐这么做。 如果将来 Fastify API 改变了, 你需要去更新你的模块, 如果使用 `fastify-plugin`, 你可以对向后兼容放心。 + ```js -function yourPlugin (fastify, opts, done) { - fastify.decorate('utility', () => {}) - done() +function yourPlugin(fastify, opts, done) { + fastify.decorate("utility", () => {}); + done(); } -yourPlugin[Symbol.for('skip-override')] = true -module.exports = yourPlugin +yourPlugin[Symbol.for("skip-override")] = true; +module.exports = yourPlugin; ``` diff --git a/doc/fastify-docs/docs/Recommendations.md b/doc/fastify-docs/docs/Recommendations.md index e2cf102..3f95f5a 100644 --- a/doc/fastify-docs/docs/Recommendations.md +++ b/doc/fastify-docs/docs/Recommendations.md @@ -4,10 +4,11 @@ 本文涵盖了使用 Fastify 的推荐方案及最佳实践。 -* [使用反向代理](#reverseproxy) -* [Kubernetes](#kubernetes) +- [使用反向代理](#reverseproxy) +- [Kubernetes](#kubernetes) ## 使用反向代理 + Node.js 作为各框架中的先行者,在标准库中提供了易用的 web 服务器。在它现世前,PHP、Python 等语言的使用者,要么需要一个支持该语言的 web 服务器,要么需要一个搭配该语言的 [CGI 网关](cgi)。而 Node.js 能让用户专注于 _直接_ 处理 HTTP 请求的应用本身,这样一来,新的诱惑点变成了处理多个域名的请求、监听多端口 (如 HTTP _和_ HTTPS),接着将应用直接暴露于 Internet 来处理请求。 @@ -150,7 +151,7 @@ server { # 默认服务器 listen 80 default_server; listen [::]:80 default_server; - + # 指定端口 # listen 80; # listen [::]:80; @@ -165,7 +166,7 @@ server { # 默认服务器 listen 443 ssl http2 default_server; listen [::]:443 ssl http2 default_server; - + # 指定端口 # listen 443 ssl http2; # listen [::]:443 ssl http2; @@ -180,21 +181,21 @@ server { ssl_session_timeout 1d; ssl_session_cache shared:FastifyApp:10m; ssl_session_tickets off; - + # 现代化配置 ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; - + # HTTP 严格传输安全 (HSTS) (需要 ngx_http_headers_module 模块) (63072000 秒) add_header Strict-Transport-Security "max-age=63072000" always; - + # 在线证书状态协议缓存 (OCSP stapling) ssl_stapling on; ssl_stapling_verify on; # 自定义域名解析器 (resolver) # resolver 127.0.0.1; - + location / { # 更多信息请见:http://nginx.org/en/docs/http/ngx_http_proxy_module.html proxy_http_version 1.1; @@ -205,7 +206,7 @@ server { 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; - + proxy_pass http://fastify_app:3000; } } @@ -214,17 +215,19 @@ server { [nginx]: https://nginx.org/ ## Kubernetes + `readinessProbe` ([默认情况下](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes)) 使用 pod 的 IP 作为主机名。而 Fastify 默认监听的是 `127.0.0.1`。在这种情况下,探针 (probe) 无法探测到应用。这时,应用必须监听 `0.0.0.0`,或在 `readinessProbe.httpGet` 中如下指定一个主机名,才能正常工作: ```yaml readinessProbe: - httpGet: - path: /health - port: 4000 - initialDelaySeconds: 30 - periodSeconds: 30 - timeoutSeconds: 3 - successThreshold: 1 - failureThreshold: 5 \ No newline at end of file + httpGet: + path: /health + port: 4000 + initialDelaySeconds: 30 + periodSeconds: 30 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 5 +``` diff --git a/doc/fastify-docs/docs/Reply.md b/doc/fastify-docs/docs/Reply.md index bc760b3..267ed8d 100644 --- a/doc/fastify-docs/docs/Reply.md +++ b/doc/fastify-docs/docs/Reply.md @@ -1,6 +1,7 @@

Fastify

## 回复 + - [回复](#reply) - [简介](#introduction) - [.code(statusCode)](#codestatuscode) @@ -29,7 +30,9 @@ - [.then](#then) + ### 简介 + 处理函数的第二个参数为 `Reply`。 Reply 是 Fastify 的一个核心对象。它暴露了以下函数及属性: @@ -49,170 +52,205 @@ Reply 是 Fastify 的一个核心对象。它暴露了以下函数及属性: - `.send(payload)` - 向用户发送 payload。类型可以是纯文本、buffer、JSON、stream,或一个 Error 对象。 - `.sent` - 一个 boolean,检查 `send` 是否已被调用。 - `.raw` - Node 原生的 [`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) 对象。 -- `.res` *(不推荐,请使用 `.raw`)* - Node 原生的 [`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) 对象。 +- `.res` _(不推荐,请使用 `.raw`)_ - Node 原生的 [`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) 对象。 - `.log` - 请求的日志实例。 - `.request` - 请求。 - `.context` - [请求的 context](Request.md#Request) 属性。 ```js -fastify.get('/', options, function (request, reply) { +fastify.get("/", options, function (request, reply) { // 你的代码 reply .code(200) - .header('Content-Type', 'application/json; charset=utf-8') - .send({ hello: 'world' }) -}) + .header("Content-Type", "application/json; charset=utf-8") + .send({ hello: "world" }); +}); ``` 另外,`Reply` 能够访问请求的上下文: ```js -fastify.get('/', {config: {foo: 'bar'}}, function (request, reply) { - reply.send('handler config.foo = ' + reply.context.config.foo) -}) +fastify.get("/", { config: { foo: "bar" } }, function (request, reply) { + reply.send("handler config.foo = " + reply.context.config.foo); +}); ``` + ### .code(statusCode) + 如果没有设置 `reply.code`,`statusCode` 会是 `200`。 + ### .statusCode + 获取或设置 HTTP 状态码。作为 setter 使用时,是 `reply.code()` 的别名。 + ```js if (reply.statusCode >= 299) { - reply.statusCode = 500 + reply.statusCode = 500; } ``` + ### .server + Fastify 服务器的实例,以当前的[封装上下文](Encapsulation.md)为作用域。 ```js -fastify.decorate('util', function util () { - return 'foo' -}) -fastify.get('/', async function (req, rep) { - return rep.server.util() // foo -}) +fastify.decorate("util", function util() { + return "foo"; +}); +fastify.get("/", async function (req, rep) { + return rep.server.util(); // foo +}); ``` + ### .header(key, value) + 设置响应 header。如果值被省略或为 undefined,将被强制设成 `''`。 更多信息,请看 [`http.ServerResponse#setHeader`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_response_setheader_name_value)。 + ### .getHeader(key) + 获取已设置的 header 的值。 + ```js -reply.header('x-foo', 'foo') // 设置 x-foo header 的值为 foo -reply.getHeader('x-foo') // 'foo' +reply.header("x-foo", "foo"); // 设置 x-foo header 的值为 foo +reply.getHeader("x-foo"); // 'foo' ``` + ### .removeHeader(key) 清除已设置的 header 的值。 + ```js -reply.header('x-foo', 'foo') -reply.removeHeader('x-foo') -reply.getHeader('x-foo') // undefined +reply.header("x-foo", "foo"); +reply.removeHeader("x-foo"); +reply.getHeader("x-foo"); // undefined ``` + ### .hasHeader(key) + 返回一个 boolean,用于检查是否设置了某个 header。 + ### .redirect([code ,] dest) + 重定向请求至指定的 URL,状态码可选,当未通过 `code` 方法设置时,默认为 `302`。 示例 (不调用 `reply.code()`):状态码 `302`,重定向至 `/home` + ```js -reply.redirect('/home') +reply.redirect("/home"); ``` 示例 (不调用 `reply.code()`):状态码 `303`,重定向至 `/home` + ```js -reply.redirect(303, '/home') +reply.redirect(303, "/home"); ``` 示例 (调用 `reply.code()`):状态码 `303`,重定向至 `/home` + ```js -reply.code(303).redirect('/home') +reply.code(303).redirect("/home"); ``` 示例 (调用 `reply.code()`):状态码 `302`,重定向至 `/home` + ```js -reply.code(303).redirect(302, '/home') +reply.code(303).redirect(302, "/home"); ``` + ### .callNotFound() + 调用自定义的 not found 处理函数。注意,只有在 [`setNotFoundHandler`](Server.md#set-not-found-handler) 中指明的 `preHandler` 钩子会被调用。 ```js -reply.callNotFound() +reply.callNotFound(); ``` + ### .getResponseTime() + 调用自定义响应时间获取函数,来计算自收到请求起的时间。 ```js -const milliseconds = reply.getResponseTime() +const milliseconds = reply.getResponseTime(); ``` + ### .type(contentType, type) + 设置响应的 content type。 这是 `reply.header('Content-Type', 'the/type')` 的简写。 ```js -reply.type('text/html') +reply.type("text/html"); ``` + 如果 `Content-Type` 为 JSON 子类型,并且未设置 charset 参数,则使用 `utf-8` 作为 charset 的默认参数。 + ### .serializer(func) + `.send()` 方法会默认将 `Buffer`、`stream`、`string`、`undefined`、`Error` 之外类型的值 JSON-序列化。假如你需要在特定的请求上使用自定义的序列化工具,你可以通过 `.serializer()` 来实现。要注意的是,如果使用了自定义的序列化工具,你必须同时设置 `'Content-Type'` header。 ```js reply - .header('Content-Type', 'application/x-protobuf') - .serializer(protoBuf.serialize) + .header("Content-Type", "application/x-protobuf") + .serializer(protoBuf.serialize); ``` 注意,你并不需要在一个 `handler` 内部使用这一工具,因为 Buffers、streams 以及字符串 (除非已经设置了序列化工具) 被认为是已序列化过的。 ```js reply - .header('Content-Type', 'application/x-protobuf') - .send(protoBuf.serialize(data)) + .header("Content-Type", "application/x-protobuf") + .send(protoBuf.serialize(data)); ``` 请看 [`.send()`](#send) 了解更多关于发送不同类型值的信息。 + ### .raw + Node 核心的 [`http.ServerResponse`](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_class_http_serverresponse) 对象。使用 `Reply.raw` 上的方法会跳过 Fastify 对 HTTP 响应的处理逻辑,所以请谨慎使用。以下是一个例子: ```js -app.get('/cookie-2', (req, reply) => { - reply.setCookie('session', 'value', { secure: false }) // 这行不会应用 +app.get("/cookie-2", (req, reply) => { + reply.setCookie("session", "value", { secure: false }); // 这行不会应用 // 在这个例子里我们只使用了 nodejs 的 http 响应对象 - reply.raw.writeHead(200, { 'Content-Type': 'text/plain' }) - reply.raw.write('ok') - reply.raw.end() -}) + reply.raw.writeHead(200, { "Content-Type": "text/plain" }); + reply.raw.write("ok"); + reply.raw.end(); +}); ``` + 在《[回复](Reply.md#getheaders)》里有另一个误用 `Reply.raw` 的例子。 + ### .sent 如你所见,`.sent` 属性表明是否已通过 `reply.send()` 发送了一个响应。 @@ -223,91 +261,109 @@ true`,程序能完全掌控底层的请求,且相关钩子不会被触发。 请看范例: ```js -app.get('/', (req, reply) => { - reply.sent = true - reply.raw.end('hello world') +app.get("/", (req, reply) => { + reply.sent = true; + reply.raw.end("hello world"); - return Promise.resolve('this will be skipped') // 译注:该处会被跳过 -}) + return Promise.resolve("this will be skipped"); // 译注:该处会被跳过 +}); ``` 如果处理函数 reject,将会记录一个错误。 + ### .hijack() + 有时你需要终止请求生命周期的执行,并手动发送响应。 Fastify 提供了 `reply.hijack()` 方法来完成此任务。在 `reply.send()` 之前的任意节点调用该方法,能阻止 Fastify 自动发送响应,并不再执行之后的生命周期函数 (包括用户编写的处理函数)。 -特别注意 (*):假如使用了 `reply.raw` 来发送响应,则 `onResponse` 依旧会执行。 +特别注意 (\*):假如使用了 `reply.raw` 来发送响应,则 `onResponse` 依旧会执行。 + ### .send(data) + 顾名思义,`.send()` 是向用户发送 payload 的函数。 + #### 对象 + 如上文所述,如果你发送 JSON 对象时,设置了输出的 schema,那么 `send` 会使用 [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) 来序列化对象。否则,将使用 `JSON.stringify()`。 + ```js -fastify.get('/json', options, function (request, reply) { - reply.send({ hello: 'world' }) -}) +fastify.get("/json", options, function (request, reply) { + reply.send({ hello: "world" }); +}); ``` + #### 字符串 + 在未设置 `Content-Type` 的时候,字符串会以 `text/plain; charset=utf-8` 类型发送。如果设置了 `Content-Type`,且使用自定义序列化工具,那么 `send` 发出的字符串会被序列化。否则,字符串不会有任何改动 (除非 `Content-Type` 的值为 `application/json; charset=utf-8`,这时,字符串会像对象一样被 JSON-序列化,正如上一节所述)。 + ```js -fastify.get('/json', options, function (request, reply) { - reply.send('plain string') -}) +fastify.get("/json", options, function (request, reply) { + reply.send("plain string"); +}); ``` + #### Streams -*send* 开箱即用地支持 stream。如果在未设置 `'Content-Type'` header 的情况下发送 stream,它会被设定为 `'application/octet-stream'`。 + +_send_ 开箱即用地支持 stream。如果在未设置 `'Content-Type'` header 的情况下发送 stream,它会被设定为 `'application/octet-stream'`。 + ```js -fastify.get('/streams', function (request, reply) { - const fs = require('fs') - const stream = fs.createReadStream('some-file', 'utf8') - reply.send(stream) -}) +fastify.get("/streams", function (request, reply) { + const fs = require("fs"); + const stream = fs.createReadStream("some-file", "utf8"); + reply.send(stream); +}); ``` + #### Buffers -未设置 `'Content-Type'` header 的情况下发送 buffer,*send* 会将其设置为 `'application/octet-stream'`。 + +未设置 `'Content-Type'` header 的情况下发送 buffer,_send_ 会将其设置为 `'application/octet-stream'`。 + ```js -const fs = require('fs') -fastify.get('/streams', function (request, reply) { - fs.readFile('some-file', (err, fileBuffer) => { - reply.send(err || fileBuffer) - }) -}) +const fs = require("fs"); +fastify.get("/streams", function (request, reply) { + fs.readFile("some-file", (err, fileBuffer) => { + reply.send(err || fileBuffer); + }); +}); ``` + #### Errors -若使用 *send* 发送一个 *Error* 的实例,Fastify 会自动创建一个如下的错误结构: + +若使用 _send_ 发送一个 _Error_ 的实例,Fastify 会自动创建一个如下的错误结构: ```js { - error: String // HTTP 错误信息 - code: String // Fastify 的错误代码 - message: String // 用户错误信息 - statusCode: Number // HTTP 状态码 + error: String; // HTTP 错误信息 + code: String; // Fastify 的错误代码 + message: String; // 用户错误信息 + statusCode: Number; // HTTP 状态码 } ``` 你可以向 Error 对象添加自定义属性,例如 `headers`,这可以用来增强 HTTP 响应。
-*注意:如果 `send` 一个错误,但状态码小于 400,Fastify 会自动将其设为 500。* +_注意:如果 `send` 一个错误,但状态码小于 400,Fastify 会自动将其设为 500。_ 贴士:你可以通过 [`http-errors`](https://npm.im/http-errors) 或 [`fastify-sensible`](https://github.com/fastify/fastify-sensible) 来简化生成的错误: ```js -fastify.get('/', function (request, reply) { - reply.send(httpErrors.Gone()) -}) +fastify.get("/", function (request, reply) { + reply.send(httpErrors.Gone()); +}); ``` 你可以通过如下方式自定义 JSON 错误的输出: @@ -318,42 +374,46 @@ fastify.get('/', function (request, reply) { 请注意,如果返回的状态码不在响应 schema 列表里,那么默认行为将被应用。 ```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) -}) +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); + }, +); ``` 如果你想自定义错误处理,请看 [`setErrorHandler`](Server.md#seterrorhandler) API。
-*注:当自定义错误处理时,你需要自行记录日志* +_注:当自定义错误处理时,你需要自行记录日志_ API: - ```js +```js fastify.setErrorHandler(function (error, request, reply) { - request.log.warn(error) - var statusCode = error.statusCode >= 400 ? error.statusCode : 500 + request.log.warn(error); + var statusCode = error.statusCode >= 400 ? error.statusCode : 500; reply .code(statusCode) - .type('text/plain') - .send(statusCode >= 500 ? 'Internal server error' : error.message) -}) + .type("text/plain") + .send(statusCode >= 500 ? "Internal server error" : error.message); +}); ``` 路由生成的 not found 错误会使用 [`setNotFoundHandler`](Server.md#setnotfoundhandler)。 @@ -361,15 +421,14 @@ API: ```js fastify.setNotFoundHandler(function (request, reply) { - reply - .code(404) - .type('text/plain') - .send('a custom not found') -}) + reply.code(404).type("text/plain").send("a custom not found"); +}); ``` + #### 最终 payload 的类型 + 发送的 payload (序列化之后、经过任意的 [`onSend` 钩子](Hooks.md#the-onsend-hook)) 必须为下列类型之一,否则将会抛出一个错误: - `string` @@ -379,41 +438,47 @@ fastify.setNotFoundHandler(function (request, reply) { - `null` + #### Async-Await 与 Promise + Fastify 原生地处理 promise 并支持 async-await。
-*请注意,在下面的例子中我们没有使用 reply.send。* +_请注意,在下面的例子中我们没有使用 reply.send。_ + ```js -const delay = promisify(setTimeout) +const delay = promisify(setTimeout); -fastify.get('/promises', options, function (request, reply) { - return delay(200).then(() => { return { hello: 'world' }}) -}) +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' } -}) +fastify.get("/async-await", options, async function (request, reply) { + await delay(200); + return { hello: "world" }; +}); ``` 被 reject 的 promise 默认发送 `500` 状态码。要修改回复,可以 reject 一个 promise,或在 `async 函数` 中进行 `throw` 操作,同时附带一个有 `statusCode` (或 `status`) 与 `message` 属性的对象。 ```js -fastify.get('/teapot', async function (request, reply) { - const err = new Error() - err.statusCode = 418 - err.message = 'short and stout' - throw err -}) +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' } +fastify.get("/botnet", async function (request, reply) { + throw { statusCode: 418, message: "short and stout" }; // 这一 json 对象将被发送给客户端 -}) +}); ``` 想要了解更多?请看 [Routes#async-await](Routes.md#async-await)。 + ### .then(fulfilled, rejected) 顾名思义,`Reply` 对象能被等待。换句话说,`await reply` 将会等待,直到回复被发送。 diff --git a/doc/fastify-docs/docs/Request.md b/doc/fastify-docs/docs/Request.md index 48cd74b..ba39339 100644 --- a/doc/fastify-docs/docs/Request.md +++ b/doc/fastify-docs/docs/Request.md @@ -1,14 +1,16 @@

Fastify

## Request + 处理函数的第一个参数是 `Request`.
Request 是 Fastify 的核心对象,包含了以下字段: + - `query` - 解析后的 querystring,其格式由 [`querystringParser`](Server.md#querystringParser) 指定。 - `body` - 消息主体 - `params` - URL 参数 - [`headers`](#headers) - header 的 getter 与 setter - `raw` - Node 原生的 HTTP 请求 -- `req` *(不推荐,请使用 `.raw`)* - Node 原生的 HTTP 请求 +- `req` _(不推荐,请使用 `.raw`)_ - Node 原生的 HTTP 请求 - `server` - Fastify 服务器的实例,以当前的[封装上下文](Encapsulation.md)为作用域。 - `id` - 请求 ID - `log` - 请求的日志实例 @@ -32,29 +34,29 @@ Request 是 Fastify 的核心对象,包含了以下字段: ```js request.headers = { - 'foo': 'bar', - 'baz': 'qux' -} + foo: "bar", + baz: "qux", +}; ``` 该操作能向请求 header 添加新的值,且该值能通过 `request.headers.bar` 读取。此外,`request.raw.headers` 能让你访问标准的请求 header。 ```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.hostname) - console.log(request.protocol) - console.log(request.url) - console.log(request.routerMethod) - console.log(request.routerPath) - request.log.info('some info') -}) +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.hostname); + console.log(request.protocol); + console.log(request.url); + console.log(request.routerMethod); + console.log(request.routerPath); + request.log.info("some info"); +}); ``` diff --git a/doc/fastify-docs/docs/Routes.md b/doc/fastify-docs/docs/Routes.md index 0a53ef4..1790e28 100644 --- a/doc/fastify-docs/docs/Routes.md +++ b/doc/fastify-docs/docs/Routes.md @@ -19,47 +19,51 @@ - [路由约束](#constraints) + ### 完整定义 ```js -fastify.route(options) +fastify.route(options); ``` + ### 路由选项 -* `method`:支持的 HTTP 请求方法。目前支持 `'DELETE'`、`'GET'`、`'HEAD'`、`'PATCH'`、`'POST'`、`'PUT'` 以及 `'OPTIONS'`。它还可以是一个 HTTP 方法的数组。 -* `url`:路由匹配的 URL 路径 (别名:`path`)。 -* `schema`:用于验证请求与回复的 schema 对象。 -必须符合 [JSON Schema](https://json-schema.org/) 格式。请看[这里](Validation-and-Serialization.md)了解更多信息。 +- `method`:支持的 HTTP 请求方法。目前支持 `'DELETE'`、`'GET'`、`'HEAD'`、`'PATCH'`、`'POST'`、`'PUT'` 以及 `'OPTIONS'`。它还可以是一个 HTTP 方法的数组。 +- `url`:路由匹配的 URL 路径 (别名:`path`)。 +- `schema`:用于验证请求与回复的 schema 对象。 + 必须符合 [JSON Schema](https://json-schema.org/) 格式。请看[这里](Validation-and-Serialization.md)了解更多信息。 - * `body`:当为 POST 或 PUT 方法时,校验请求主体。 - * `querystring` 或 `query`:校验 querystring。可以是一个完整的 JSON Schema 对象,它包括了值为 `object` 的 `type` 属性以及包含参数的 `properties` 对象,也可以仅仅是 `properties` 对象中的值 (见下文示例)。 - * `params`:校验 url 参数。 - * `response`:过滤并生成用于响应的 schema,能帮助提升 10-20% 的吞吐量。 -* `exposeHeadRoute`:为任意 `GET` 路由创建一个对应的 `HEAD` 路由。默认值为服务器实例上的 [`exposeHeadRoutes`](Server.md#exposeHeadRoutes) 选项的值。如果你不想禁用该选项,又希望自定义 `HEAD` 处理函数,请在 `GET` 路由前定义该处理函数。 -* `attachValidation`:当 schema 校验出错时,将一个 `validationError` 对象添加到请求中,否则错误将被发送给错误处理函数。 -* `onRequest(request, reply, done)`:每当接收到一个请求时触发的[函数](Hooks.md#onrequest)。可以是一个函数数组。 -* `preParsing(request, reply, done)`:解析请求前调用的[函数](Hooks.md#preparsing)。可以是一个函数数组。 -* `preValidation(request, reply, done)`:在共享的 `preValidation` 钩子之后执行的[函数](Hooks.md#prevalidation),在路由层进行认证等场景中会有用处。可以是一个函数数组。 -* `preHandler(request, reply, done)`:处理请求之前调用的[函数](Hooks.md#prehandler)。可以是一个函数数组。 -* `preSerialization(request, reply, payload, done)`:序列化之前调用的[函数](Hooks.md#preserialization)。可以是一个函数数组。 -* `onSend(request, reply, payload, done)`:响应即将发送前调用的[函数](Hooks.md#route-hooks)。可以是一个函数数组。 -* `onResponse(request, reply, done)`:当响应发送后调用的[函数](Hooks.md#onresponse)。因此,在这个函数内部,不允许再向客户端发送数据。可以是一个函数数组。 -* `handler(request, reply)`:处理请求的函数。函数被调用时,[Fastify server](Server.md) 将会与 `this` 进行绑定。注意,使用箭头函数会破坏这一绑定。 -* `errorHandler(error, request, reply)`:在请求作用域内使用的自定义错误控制函数。覆盖默认的全局错误函数,以及由 [`setErrorHandler`](Server.md#setErrorHandler) 设置的请求错误函数。你可以通过 `instance.errorHandler` 访问默认的错误函数,在没有插件覆盖的情况下,其指向 Fastify 默认的 `errorHandler`。 -* `validatorCompiler({ schema, method, url, httpPart })`:生成校验请求的 schema 的函数。详见[验证与序列化](Validation-and-Serialization.md#schema-validator)。 -* `serializerCompiler({ { schema, method, url, httpStatus } })`:生成序列化响应的 schema 的函数。详见[验证与序列化](Validation-and-Serialization.md#schema-serializer)。 -* `schemaErrorFormatter(errors, dataVar)`:生成一个函数,用于格式化来自 schema 校验函数的错误。详见[验证与序列化](Validation-and-Serialization.md#schema-validator)。在当前路由上会覆盖全局的 schema 错误格式化函数,以及 `setSchemaErrorFormatter` 设置的值。 -* `bodyLimit`:一个以字节为单位的整形数,默认值为 `1048576` (1 MiB),防止默认的 JSON 解析器解析超过此大小的请求主体。你也可以通过 `fastify(options)`,在首次创建 Fastify 实例时全局设置该值。 -* `logLevel`:设置日志级别。详见下文。 -* `logSerializers`:设置当前路由的日志序列化器。 -* `config`:存放自定义配置的对象。 -* `version`:一个符合[语义化版本控制规范 (semver)](https://semver.org/) 的字符串。[示例](Routes.md#version)。 -`prefixTrailingSlash`:一个字符串,决定如何处理带前缀的 `/` 路由。 - * `both` (默认值):同时注册 `/prefix` 与 `/prefix/`。 - * `slash`:只会注册 `/prefix/`。 - * `no-slash`:只会注册 `/prefix`。 + - `body`:当为 POST 或 PUT 方法时,校验请求主体。 + - `querystring` 或 `query`:校验 querystring。可以是一个完整的 JSON Schema 对象,它包括了值为 `object` 的 `type` 属性以及包含参数的 `properties` 对象,也可以仅仅是 `properties` 对象中的值 (见下文示例)。 + - `params`:校验 url 参数。 + - `response`:过滤并生成用于响应的 schema,能帮助提升 10-20% 的吞吐量。 + +- `exposeHeadRoute`:为任意 `GET` 路由创建一个对应的 `HEAD` 路由。默认值为服务器实例上的 [`exposeHeadRoutes`](Server.md#exposeHeadRoutes) 选项的值。如果你不想禁用该选项,又希望自定义 `HEAD` 处理函数,请在 `GET` 路由前定义该处理函数。 +- `attachValidation`:当 schema 校验出错时,将一个 `validationError` 对象添加到请求中,否则错误将被发送给错误处理函数。 +- `onRequest(request, reply, done)`:每当接收到一个请求时触发的[函数](Hooks.md#onrequest)。可以是一个函数数组。 +- `preParsing(request, reply, done)`:解析请求前调用的[函数](Hooks.md#preparsing)。可以是一个函数数组。 +- `preValidation(request, reply, done)`:在共享的 `preValidation` 钩子之后执行的[函数](Hooks.md#prevalidation),在路由层进行认证等场景中会有用处。可以是一个函数数组。 +- `preHandler(request, reply, done)`:处理请求之前调用的[函数](Hooks.md#prehandler)。可以是一个函数数组。 +- `preSerialization(request, reply, payload, done)`:序列化之前调用的[函数](Hooks.md#preserialization)。可以是一个函数数组。 +- `onSend(request, reply, payload, done)`:响应即将发送前调用的[函数](Hooks.md#route-hooks)。可以是一个函数数组。 +- `onResponse(request, reply, done)`:当响应发送后调用的[函数](Hooks.md#onresponse)。因此,在这个函数内部,不允许再向客户端发送数据。可以是一个函数数组。 +- `handler(request, reply)`:处理请求的函数。函数被调用时,[Fastify server](Server.md) 将会与 `this` 进行绑定。注意,使用箭头函数会破坏这一绑定。 +- `errorHandler(error, request, reply)`:在请求作用域内使用的自定义错误控制函数。覆盖默认的全局错误函数,以及由 [`setErrorHandler`](Server.md#setErrorHandler) 设置的请求错误函数。你可以通过 `instance.errorHandler` 访问默认的错误函数,在没有插件覆盖的情况下,其指向 Fastify 默认的 `errorHandler`。 +- `validatorCompiler({ schema, method, url, httpPart })`:生成校验请求的 schema 的函数。详见[验证与序列化](Validation-and-Serialization.md#schema-validator)。 +- `serializerCompiler({ { schema, method, url, httpStatus } })`:生成序列化响应的 schema 的函数。详见[验证与序列化](Validation-and-Serialization.md#schema-serializer)。 +- `schemaErrorFormatter(errors, dataVar)`:生成一个函数,用于格式化来自 schema 校验函数的错误。详见[验证与序列化](Validation-and-Serialization.md#schema-validator)。在当前路由上会覆盖全局的 schema 错误格式化函数,以及 `setSchemaErrorFormatter` 设置的值。 +- `bodyLimit`:一个以字节为单位的整形数,默认值为 `1048576` (1 MiB),防止默认的 JSON 解析器解析超过此大小的请求主体。你也可以通过 `fastify(options)`,在首次创建 Fastify 实例时全局设置该值。 +- `logLevel`:设置日志级别。详见下文。 +- `logSerializers`:设置当前路由的日志序列化器。 +- `config`:存放自定义配置的对象。 +- `version`:一个符合[语义化版本控制规范 (semver)](https://semver.org/) 的字符串。[示例](Routes.md#version)。 + `prefixTrailingSlash`:一个字符串,决定如何处理带前缀的 `/` 路由。 + + - `both` (默认值):同时注册 `/prefix` 与 `/prefix/`。 + - `slash`:只会注册 `/prefix/`。 + - `no-slash`:只会注册 `/prefix`。 `request` 的相关内容请看[请求](Request.md)一文。 @@ -68,33 +72,36 @@ fastify.route(options) **注意:** 在[钩子](Hooks.md)一文中有 `onRequest`、`preParsing`、`preValidation`、`preHandler`、`preSerialization`、`onSend` 以及 `onResponse` 更详尽的说明。此外,要在 `handler` 之前就发送响应,请参阅[在钩子中响应请求](Hooks.md#respond-to-a-request-from-a-hook)。 示例: + ```js fastify.route({ - method: 'GET', - url: '/', + method: "GET", + url: "/", schema: { querystring: { - name: { type: 'string' }, - excitement: { type: 'integer' } + name: { type: "string" }, + excitement: { type: "integer" }, }, response: { 200: { - type: 'object', + type: "object", properties: { - hello: { type: 'string' } - } - } - } + hello: { type: "string" }, + }, + }, + }, }, handler: function (request, reply) { - reply.send({ hello: 'world' }) - } -}) + reply.send({ hello: "world" }); + }, +}); ``` + ### 简写定义 -上文的路由定义带有 *Hapi* 的风格。要是偏好 *Express/Restify* 的写法,Fastify 也是支持的:
+ +上文的路由定义带有 _Hapi_ 的风格。要是偏好 _Express/Restify_ 的写法,Fastify 也是支持的:
`fastify.get(path, [options], handler)`
`fastify.head(path, [options], handler)`
`fastify.post(path, [options], handler)`
@@ -104,186 +111,209 @@ fastify.route({ `fastify.patch(path, [options], handler)` 示例: + ```js const opts = { schema: { response: { 200: { - type: 'object', + type: "object", properties: { - hello: { type: 'string' } - } - } - } - } -} -fastify.get('/', opts, (request, reply) => { - reply.send({ hello: 'world' }) -}) + hello: { type: "string" }, + }, + }, + }, + }, +}; +fastify.get("/", opts, (request, reply) => { + reply.send({ hello: "world" }); +}); ``` `fastify.all(path, [options], handler)` 会给所有支持的 HTTP 方法添加相同的处理函数。 处理函数还可以写到 `options` 对象里: + ```js const opts = { schema: { response: { 200: { - type: 'object', + type: "object", properties: { - hello: { type: 'string' } - } - } - } + hello: { type: "string" }, + }, + }, + }, }, handler: function (request, reply) { - reply.send({ hello: 'world' }) - } -} -fastify.get('/', opts) + reply.send({ hello: "world" }); + }, +}; +fastify.get("/", opts); ``` > 注:假如同时在 `options` 和简写方法的第三个参数里指明了处理函数,将会抛出重复的 `handler` 错误。 + ### Url 构建 + Fastify 同时支持静态与动态的 URL
要注册一个**参数命名**的路径,请在参数名前加上*冒号*。*星号*表示**通配符**。 -*注意,静态路由总是在参数路由和通配符之前进行匹配。* +_注意,静态路由总是在参数路由和通配符之前进行匹配。_ ```js // 参数路由 -fastify.get('/example/:userId', (request, reply) => {}) -fastify.get('/example/:userId/:secretToken', (request, reply) => {}) +fastify.get("/example/:userId", (request, reply) => {}); +fastify.get("/example/:userId/:secretToken", (request, reply) => {}); // 通配符 -fastify.get('/example/*', (request, reply) => {}) +fastify.get("/example/*", (request, reply) => {}); ``` 正则表达式路由亦被支持。但要注意,正则表达式会严重拖累性能! + ```js // 正则表达的参数路由 -fastify.get('/example/:file(^\\d+).png', (request, reply) => {}) +fastify.get("/example/:file(^\\d+).png", (request, reply) => {}); ``` 你还可以在同一组斜杠 ("/") 里定义多个参数。就像这样: + ```js -fastify.get('/example/near/:lat-:lng/radius/:r', (request, reply) => {}) +fastify.get("/example/near/:lat-:lng/radius/:r", (request, reply) => {}); ``` -*使用短横线 ("-") 来分隔参数。* + +_使用短横线 ("-") 来分隔参数。_ 最后,同时使用多参数和正则表达式也是允许的。 + ```js -fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', (request, reply) => {}) +fastify.get( + "/example/at/:hour(^\\d{2})h:minute(^\\d{2})m", + (request, reply) => {}, +); ``` + 在这个例子里,任何未被正则匹配的符号均可作为参数的分隔符。 多参数的路由会影响性能,所以应该尽量使用单参数,对于高频访问的路由来说更是如此。 如果你对路由的底层感兴趣,可以查看[find-my-way](https://github.com/delvedor/find-my-way)。 双冒号表示字面意义上的一个冒号,这样就不必通过参数来实现带冒号的路由了。举例如下: + ```js -fastify.post('/name::verb') // 将被解释为 /name:verb +fastify.post("/name::verb"); // 将被解释为 /name:verb ``` + ### Async Await + 你是 `async/await` 的使用者吗?我们为你考虑了一切! + ```js -fastify.get('/', options, async function (request, reply) { - var data = await getData() - var processed = await processData(data) - return processed -}) +fastify.get("/", options, async function (request, reply) { + var data = await getData(); + var processed = await processData(data); + return processed; +}); ``` 如你所见,我们不再使用 `reply.send` 向用户发送数据,只需返回消息主体就可以了! 当然,需要的话你还是可以使用 `reply.send` 发送数据。 + ```js -fastify.get('/', options, async function (request, reply) { - var data = await getData() - var processed = await processData(data) - reply.send(processed) -}) +fastify.get("/", options, async function (request, reply) { + var data = await getData(); + var processed = await processData(data); + reply.send(processed); +}); ``` 假如在路由中,`reply.send()` 脱离了 promise 链,在一个基于回调的 API 中被调用,你可以使用 `await reply`: ```js -fastify.get('/', options, async function (request, reply) { +fastify.get("/", options, async function (request, reply) { setImmediate(() => { - reply.send({ hello: 'world' }) - }) - await reply -}) + reply.send({ hello: "world" }); + }); + await reply; +}); ``` 返回回复也是可行的: ```js -fastify.get('/', options, async function (request, reply) { +fastify.get("/", options, async function (request, reply) { setImmediate(() => { - reply.send({ hello: 'world' }) - }) - return reply -}) + reply.send({ hello: "world" }); + }); + return reply; +}); ``` **警告:** -* 如果你同时使用 `return value` 与 `reply.send(value)`,那么只会发送第一次,同时还会触发警告日志,因为你试图发送两次响应。 -* 不能返回 `undefined`。更多细节请看 [promise 取舍](#promise-resolution)。 + +- 如果你同时使用 `return value` 与 `reply.send(value)`,那么只会发送第一次,同时还会触发警告日志,因为你试图发送两次响应。 +- 不能返回 `undefined`。更多细节请看 [promise 取舍](#promise-resolution)。 + ### Promise 取舍 假如你的处理函数是一个 `async` 函数,或返回了一个 promise,请注意一种必须支持回调函数和 promise 控制流的特殊情况:如果 promise 被 resolve 为 `undefined`,请求会被挂起,并触发一个*错误*日志。 1. 如果你想使用 `async/await` 或 promise,但通过 `reply.send` 返回值: - - **别** `return` 任何值。 - - **别**忘了 `reply.send`。 + - **别** `return` 任何值。 + - **别**忘了 `reply.send`。 2. 如果你想使用 `async/await` 或 promise: - - **别**使用 `reply.send`。 - - **别**返回 `undefined`。 + - **别**使用 `reply.send`。 + - **别**返回 `undefined`。 通过这一方法,我们便可以最小代价同时支持 `回调函数风格` 以及 `async-await`。尽管这么做十分自由,我们还是强烈建议仅使用其中的一种,因为应用的错误处理方式应当保持一致。 **注意**:每个 async 函数各自返回一个 promise 对象。 + ### 路由前缀 + 有时你需要维护同一 API 的多个不同版本。一般的做法是在所有的路由之前加上版本号,例如 `/v1/user`。 Fastify 提供了一个快捷且智能的方法来解决上述问题,无需手动更改全部路由。这就是*路由前缀*。让我们来看下吧: ```js // server.js -const fastify = require('fastify')() +const fastify = require("fastify")(); -fastify.register(require('./routes/v1/users'), { prefix: '/v1' }) -fastify.register(require('./routes/v2/users'), { prefix: '/v2' }) +fastify.register(require("./routes/v1/users"), { prefix: "/v1" }); +fastify.register(require("./routes/v2/users"), { prefix: "/v2" }); -fastify.listen(3000) +fastify.listen(3000); ``` ```js // routes/v1/users.js module.exports = function (fastify, opts, done) { - fastify.get('/user', handler_v1) - 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.get("/user", handler_v2); + done(); +}; ``` -在编译时 Fastify 自动处理了前缀,因此两个不同路由使用相同的路径名并不会产生问题。*(这也意味着性能一点儿也不受影响!)*。 + +在编译时 Fastify 自动处理了前缀,因此两个不同路由使用相同的路径名并不会产生问题。_(这也意味着性能一点儿也不受影响!)_。 现在,你的客户端就可以访问下列路由了: + - `/v1/user` - `/v2/user` @@ -297,7 +327,9 @@ module.exports = function (fastify, opts, done) { 要改变这一行为,请见上文 `prefixTrailingSlash` 选项。 + ### 自定义日志级别 + 在 Fastify 中为路由里设置不同的日志级别是十分容易的。
你只需在插件或路由的选项里设置 `logLevel` 为相应的[值](https://github.com/pinojs/pino/blob/master/docs/api.md#level-string)即可。 @@ -305,96 +337,102 @@ module.exports = function (fastify, opts, done) { ```js // server.js -const fastify = require('fastify')({ logger: true }) +const fastify = require("fastify")({ logger: true }); -fastify.register(require('./routes/user'), { logLevel: 'warn' }) -fastify.register(require('./routes/events'), { logLevel: 'debug' }) +fastify.register(require("./routes/user"), { logLevel: "warn" }); +fastify.register(require("./routes/events"), { logLevel: "debug" }); -fastify.listen(3000) +fastify.listen(3000); ``` 你也可以直接将其传给路由: + ```js -fastify.get('/', { logLevel: 'warn' }, (request, reply) => { - reply.send({ hello: 'world' }) -}) +fastify.get("/", { logLevel: "warn" }, (request, reply) => { + reply.send({ hello: "world" }); +}); ``` -*自定义的日志级别仅对路由生效,通过 `fastify.log` 访问的全局日志并不会受到影响。* + +_自定义的日志级别仅对路由生效,通过 `fastify.log` 访问的全局日志并不会受到影响。_ + ### 自定义日志序列化器 在某些上下文里,你也许需要记录一个大型对象,但这在其他路由中是个负担。这时,你可以定义一些[`序列化器 (serializer)`](https://github.com/pinojs/pino/blob/master/docs/api.md#bindingsserializers-object),并将它们设置在正确的上下文之上! ```js -const fastify = require('fastify')({ logger: true }) -fastify.register(require('./routes/user'), { +const fastify = require("fastify")({ logger: true }); +fastify.register(require("./routes/user"), { logSerializers: { - user: (value) => `My serializer one - ${value.name}` - } -}) -fastify.register(require('./routes/events'), { + user: (value) => `My serializer one - ${value.name}`, + }, +}); +fastify.register(require("./routes/events"), { logSerializers: { - user: (value) => `My serializer two - ${value.name} ${value.surname}` - } -}) -fastify.listen(3000) + user: (value) => `My serializer two - ${value.name} ${value.surname}`, + }, +}); +fastify.listen(3000); ``` 你可以通过上下文来继承序列化器: ```js -const fastify = Fastify({ +const fastify = Fastify({ logger: { - level: 'info', + level: "info", serializers: { - user (req) { + user(req) { return { method: req.method, url: req.url, headers: req.headers, hostname: req.hostname, remoteAddress: req.ip, - remotePort: req.socket.remotePort - } - } - } - } -}) -fastify.register(context1, { + 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' }) + 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" }); // 打印结果: { user: 'My serializer father - call father serializer', key: 'another key' } - reply.send({}) - }) + reply.send({}); + }); } -fastify.listen(3000) +fastify.listen(3000); ``` + ### 配置 + 注册一个新的处理函数,你可以向其传递一个配置对象,并在其中使用它。 ```js // server.js -const fastify = require('fastify')() +const fastify = require("fastify")(); -function handler (req, reply) { - reply.send(reply.context.config.output) +function handler(req, reply) { + reply.send(reply.context.config.output); } -fastify.get('/en', { config: { output: 'hello world!' } }, handler) -fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler) +fastify.get("/en", { config: { output: "hello world!" } }, handler); +fastify.get("/it", { config: { output: "ciao mondo!" } }, handler); -fastify.listen(3000) +fastify.listen(3000); ``` + ### 约束 Fastify 允许你基于请求的某些属性,例如 `Host` header 或 [`find-my-way`](https://github.com/delvedor/find-my-way) 指定的其他值,来限制路由仅匹配特定的请求。路由选项里的 `constraints` 属性便是用于这一特性。Fastify 有两个内建的约束属性:`version` 及 `host`。你可以自定义约束策略,来判断某路由是否处理一个请求。 @@ -403,45 +441,51 @@ Fastify 允许你基于请求的某些属性,例如 `Host` header 或 [`find-m 你可以在路由的 `constraints` 选项中提供一个 `version` 键。路由版本化允许你为相同路径的路由设置多个处理函数,并根据请求的 `Accept-Version` header 来做匹配。 `Accept-Version` header 的值请遵循 [semver](https://semver.org/) 规范,路由也应当附带对应的 semver 版本声明以便成功匹配。
对于版本化的路由,Fastify 需要请求附带上 `Accept-Version` header。此外,相同路径的请求会优先匹配带有版本的控制函数。当前尚不支持 semver 规范中的 advanced ranges 与 pre-releases 语法
-*请注意,这一特性会降低路由的性能。* +_请注意,这一特性会降低路由的性能。_ ```js fastify.route({ - method: 'GET', - url: '/', - constraints: { version: '1.2.0' }, + method: "GET", + url: "/", + constraints: { version: "1.2.0" }, handler: function (request, reply) { - reply.send({ hello: 'world' }) - } -}) + reply.send({ hello: "world" }); + }, +}); -fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'Accept-Version': '1.x' // 也可以是 '1.2.0' 或 '1.2.x' - } -}, (err, res) => { - // { hello: 'world' } -}) +fastify.inject( + { + method: "GET", + url: "/", + headers: { + "Accept-Version": "1.x", // 也可以是 '1.2.0' 或 '1.2.x' + }, + }, + (err, res) => { + // { hello: 'world' } + }, +); ``` -> ## ⚠ 安全提示 +> ## ⚠ 安全提示 +> > 记得设置 [`Vary`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) 响应头 > 为用于区分版本的值 (如 `'Accept-Version'`), > 来避免缓存污染攻击 (cache poisoning attacks)。你也可以在代理或 CDN 层设置该值。 > > ```js -> const append = require('vary').append -> fastify.addHook('onSend', async (req, reply) => { -> if (req.headers['accept-version']) { // 或其他自定义 header -> let value = reply.getHeader('Vary') || '' -> const header = Array.isArray(value) ? value.join(', ') : String(value) -> if ((value = append(header, 'Accept-Version'))) { // 或其他自定义 header -> reply.header('Vary', value) +> const append = require("vary").append; +> fastify.addHook("onSend", async (req, reply) => { +> if (req.headers["accept-version"]) { +> // 或其他自定义 header +> let value = reply.getHeader("Vary") || ""; +> const header = Array.isArray(value) ? value.join(", ") : String(value); +> if ((value = append(header, "Accept-Version"))) { +> // 或其他自定义 header +> reply.header("Vary", value); > } > } -> }) +> }); > ``` 如果你声明了多个拥有相同主版本或次版本号的版本,Fastify 总是会根据 `Accept-Version` header 的值选择最兼容的版本。
@@ -455,44 +499,50 @@ fastify.inject({ ```js fastify.route({ - method: 'GET', - url: '/', - constraints: { host: 'auth.fastify.io' }, + method: "GET", + url: "/", + constraints: { host: "auth.fastify.io" }, handler: function (request, reply) { - reply.send('hello world from auth.fastify.io') - } -}) + reply.send("hello world from auth.fastify.io"); + }, +}); -fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'Host': 'example.com' - } -}, (err, res) => { - // 返回 404,因为 host 不匹配 -}) +fastify.inject( + { + method: "GET", + url: "/", + headers: { + Host: "example.com", + }, + }, + (err, res) => { + // 返回 404,因为 host 不匹配 + }, +); -fastify.inject({ - method: 'GET', - url: '/', - headers: { - 'Host': 'auth.fastify.io' - } -}, (err, res) => { - // => 'hello world from auth.fastify.io' -}) +fastify.inject( + { + method: "GET", + url: "/", + headers: { + Host: "auth.fastify.io", + }, + }, + (err, res) => { + // => 'hello world from auth.fastify.io' + }, +); ``` 正则形式的 `host` 约束也可用于匹配任意的子域 (或其他模式): ```js fastify.route({ - method: 'GET', - url: '/', + method: "GET", + url: "/", constraints: { host: /.*\.fastify\.io/ }, // 匹配 fastify.io 的任意子域 handler: function (request, reply) { - reply.send('hello world from ' + request.headers.host) - } -}) -``` \ No newline at end of file + reply.send("hello world from " + request.headers.host); + }, +}); +``` diff --git a/doc/fastify-docs/docs/Server.md b/doc/fastify-docs/docs/Server.md index 895fa24..ebb54da 100644 --- a/doc/fastify-docs/docs/Server.md +++ b/doc/fastify-docs/docs/Server.md @@ -1,6 +1,7 @@

Fastify

+ ## 工厂函数 Fastify 模块导出了一个工厂函数,可以用于创建新的 Fastify server 实例。这个工厂函数的参数是一个配置对象,用于自定义最终生成的实例。本文描述了这一对象中可用的属性。 @@ -37,13 +38,15 @@ Fastify 模块导出了一个工厂函数,可以用于创建新的 Fa - [initialConfig](./Server.md#initialConfig) + ### `http2` 设置为 `true`,则会使用 Node.js 原生的 [HTTP/2](https://nodejs.org/dist/latest-v14.x/docs/api/http2.html) 模块来绑定 socket。 -+ 默认值:`false` +- 默认值:`false` + ### `https` 用于配置服务器的 TLS socket 的对象。其选项与 Node.js 原生的 [`createServer` 方法](https://nodejs.org/dist/latest-v14.x/docs/api/https.html#https_https_createserver_options_requestlistener)一致。 @@ -53,60 +56,67 @@ Fastify 模块导出了一个工厂函数,可以用于创建新的 Fa http2 选项设置时,`https` 选项也会被应用。 -+ 默认值:`null` +- 默认值:`null` + ### `connectionTimeout` 定义服务器超时,单位为毫秒。作用请见 [`server.timeout` 属性](https://nodejs.org/api/http.html#http_server_timeout)的文档。当指定了 `serverFactory` 时,该选项被忽略。 -+ 默认值:`0` (无超时) +- 默认值:`0` (无超时) + ### `keepAliveTimeout` 定义服务器 keep-alive 超时,单位为毫秒。作用请见 [`server.keepAliveTimeout` 属性](https://nodejs.org/api/http.html#http_server_timeout)的文档。仅当使用 HTTP/1 时有效。当指定了 `serverFactory` 时,该选项被忽略。 -+ 默认值:`5000` (5 秒) +- 默认值:`5000` (5 秒) + ### `ignoreTrailingSlash` Fastify 使用 [find-my-way](https://github.com/delvedor/find-my-way) 处理路由。该选项为 `true` 时,尾斜杠将被省略。 这一选项应用于 server 实例上注册的*所有*路由。 -+ 默认值:`false` +- 默认值:`false` ```js -const fastify = require('fastify')({ - ignoreTrailingSlash: true -}) +const fastify = require("fastify")({ + ignoreTrailingSlash: true, +}); // 同时注册 "/foo" 与 "/foo/" -fastify.get('/foo/', function (req, reply) { - reply.send('foo') -}) +fastify.get("/foo/", function (req, reply) { + reply.send("foo"); +}); // 同时注册 "/bar" 与 "/bar/" -fastify.get('/bar', function (req, reply) { - reply.send('bar') -}) +fastify.get("/bar", function (req, reply) { + reply.send("bar"); +}); ``` + ### `maxParamLength` + 你可以为通过 `maxParamLength` 选项为带参路由 (无论是标准的、正则匹配的,还是复数的) 设置最大参数长度。选项的默认值为 100 字符。
当使用正则匹配的路由时,这非常有用,可以帮你抵御 [DoS 攻击](https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS)。
-*当达到长度限制时,将触发 not found 路由。* +_当达到长度限制时,将触发 not found 路由。_ + ### `bodyLimit` 定义服务器可接受的最大 payload,以字节为单位。 -+ 默认值:`1048576` (1MiB) +- 默认值:`1048576` (1MiB) + ### `onProtoPoisoning` 由 [secure-json-parse](https://github.com/fastify/secure-json-parse) 提供的功能,指定解析带有 `__proto__` 键的 JSON 对象时框架的行为。 @@ -114,9 +124,10 @@ fastify.get('/bar', function (req, reply) { 允许的值为 `'error'`、`'remove'` 与 `'ignore'`。 -+ 默认值:`'error'` +- 默认值:`'error'` + ### `onConstructorPoisoning` 由 [secure-json-parse](https://github.com/fastify/secure-json-parse) 提供的功能,指定解析带有 `constructor` 的 JSON 对象时框架的行为。 @@ -124,33 +135,35 @@ fastify.get('/bar', function (req, reply) { 允许的值为 `'error'`、`'remove'` 与 `'ignore'`。 -+ 默认值:`'error'` +- 默认值:`'error'` + ### `logger` Fastify 依托 [Pino](https://getpino.io/) 内建了一个日志工具。该属性用于配置日志实例。 属性可用的值为: -+ 默认: `false`。禁用日志。所有记录日志的方法将会指向一个空日志工具 [abstract-logging](https://npm.im/abstract-logging) 的实例。 +- 默认: `false`。禁用日志。所有记录日志的方法将会指向一个空日志工具 [abstract-logging](https://npm.im/abstract-logging) 的实例。 -+ `pinoInstance`: 一个已被实例化的 Pino 实例。内建的日志工具将指向这个实例。 +- `pinoInstance`: 一个已被实例化的 Pino 实例。内建的日志工具将指向这个实例。 -+ `object`: 标准的 Pino [选项对象](https://github.com/pinojs/pino/blob/c77d8ec5ce/docs/API.md#constructor)。 -它会被直接传递进 Pino 的构造函数。如果下列属性未在该对象中定义,它们将被相应地添加: - * `level`: 最低的日志级别。若未被设置,则默认为 `'info'`。 - * `serializers`: 序列化函数的哈希。默认情况下,序列化函数应用在 `req` (来访的请求对象)、`res` (发送的响应对象) 以及 `err` (标准的 `Error` 对象) 之上。当一个日志方法接收到含有上述任意属性的对象时,对应的序列化器将会作用于该属性。举例如下: - ```js +- `object`: 标准的 Pino [选项对象](https://github.com/pinojs/pino/blob/c77d8ec5ce/docs/API.md#constructor)。 + 它会被直接传递进 Pino 的构造函数。如果下列属性未在该对象中定义,它们将被相应地添加: + _ `level`: 最低的日志级别。若未被设置,则默认为 `'info'`。 + _ `serializers`: 序列化函数的哈希。默认情况下,序列化函数应用在 `req` (来访的请求对象)、`res` (发送的响应对象) 以及 `err` (标准的 `Error` 对象) 之上。当一个日志方法接收到含有上述任意属性的对象时,对应的序列化器将会作用于该属性。举例如下: + `js fastify.get('/foo', function (req, res) { req.log.info({req}) // 日志输出经过序列化的请求对象 res.send('foo') }) - ``` - 用户提供的序列化函数将会覆盖对应属性默认的序列化函数。 -+ `loggerInstance`:自定义日志工具实例。日志工具必须实现 Pino 的接口,即拥有如下方法:`info`, `error`, `debug`, `fatal`, `warn`, `trace`, `child`。例如: + ` + 用户提供的序列化函数将会覆盖对应属性默认的序列化函数。 +- `loggerInstance`:自定义日志工具实例。日志工具必须实现 Pino 的接口,即拥有如下方法:`info`, `error`, `debug`, `fatal`, `warn`, `trace`, `child`。例如: + ```js -const pino = require('pino')(); +const pino = require("pino")(); const customLogger = { info: function (o, ...n) {}, @@ -159,93 +172,105 @@ const customLogger = { fatal: function (o, ...n) {}, trace: function (o, ...n) {}, debug: function (o, ...n) {}, - child: function() { + child: function () { const child = Object.create(this); child.pino = pino.child(...arguments); return child; }, }; -const fastify = require('fastify')({logger: customLogger}); +const fastify = require("fastify")({ logger: customLogger }); ``` + ### `disableRequestLogging` + 默认情况下当开启日志时,Fastify 会在收到请求与发送该请求的响应时记录 `info` 级别的日志。你可以设置该选项为 `true` 来禁用该功能。这时,通过自定义 `onRequest` 和 `onResponse` 钩子,你能更灵活地记录一个请求的开始与结束。 -+ 默认值:`false` +- 默认值:`false` ```js // 例子:通过钩子再造被禁用的请求日志功能。 -fastify.addHook('onRequest', (req, reply, done) => { - req.log.info({ url: req.raw.url, id: req.id }, 'received request') - done() -}) +fastify.addHook("onRequest", (req, reply, done) => { + req.log.info({ url: req.raw.url, id: req.id }, "received request"); + done(); +}); -fastify.addHook('onResponse', (req, reply, done) => { - req.log.info({ url: req.raw.originalUrl, statusCode: reply.raw.statusCode }, 'request completed') - done() -}) +fastify.addHook("onResponse", (req, reply, done) => { + req.log.info( + { url: req.raw.originalUrl, statusCode: reply.raw.statusCode }, + "request completed", + ); + done(); +}); ``` 请注意,该选项同时也会禁止默认的 `onResponse` 钩子在响应的回调函数出错时记录错误日志。 + ### `serverFactory` + 通过 `serverFactory` 选项,你可以向 Fastify 传递一个自定义的 HTTP server。
`serverFactory` 函数的参数为 `handler` 函数及一个选项对象。`handler` 函数的参数为 `request` 和 `response` 对象,选项对象则与你传递给 Fastify 的一致。 ```js const serverFactory = (handler, opts) => { const server = http.createServer((req, res) => { - handler(req, res) - }) + handler(req, res); + }); - return server -} + return server; +}; -const fastify = Fastify({ serverFactory }) +const fastify = Fastify({ serverFactory }); -fastify.get('/', (req, reply) => { - reply.send({ hello: 'world' }) -}) +fastify.get("/", (req, reply) => { + reply.send({ hello: "world" }); +}); -fastify.listen(3000) +fastify.listen(3000); ``` + Fastify 内在地使用 Node 原生 HTTP server 的 API。因此,如果你使用一个自定义的 server,你必须保证暴露了相同的 API。不这么做的话,你可以在 `serverFactory` 函数内部 `return` 语句之前,向 server 实例添加新的属性。
+ ### `jsonShorthand` -+ 默认值:`true` +- 默认值:`true` 当未发现 JSON Schema 规范中合法的根属性时,Fastify 会默认地自动推断该根属性。如果你想实现自定义的 schema 校验编译器,例如使用 JTD (JSON Type Definition) 代替 JSON schema,你应当设置该选项为 `false` 来确保 schema 不被修改且不会被当成 JSON Schema 处理。 ```js -const AjvJTD = require('ajv/dist/jtd'/* 只在 AJV v7 以上版本生效 */) +const AjvJTD = require("ajv/dist/jtd" /* 只在 AJV v7 以上版本生效 */); const ajv = new AjvJTD({ // 当遇到非法 JTD schema 对象时抛出错误。 - allErrors: process.env.NODE_ENV === 'development' -}) -const fastify = Fastify({ jsonShorthand: false }) + allErrors: process.env.NODE_ENV === "development", +}); +const fastify = Fastify({ jsonShorthand: false }); fastify.setValidatorCompiler(({ schema }) => { - return ajv.compile(schema) -}) -fastify.post('/', { + return ajv.compile(schema); +}); +fastify.post("/", { schema: { body: { properties: { - foo: { type: 'uint8' } - } - } + foo: { type: "uint8" }, + }, + }, }, - handler (req, reply) { reply.send({ ok: 1 }) } -}) + handler(req, reply) { + reply.send({ ok: 1 }); + }, +}); ``` **注:目前 Fastify 并不会在发现非法 schema 时抛错。因此,假如你在已有项目中关闭了该选项,请确保现存所有的 schema 不会因此变得非法,因为它们会被当作笼统的一类进行处理。** + ### `caseSensitive` 默认值为 `true`,此时路由对大小写敏感。这就意味着 `/foo` 与 `/Foo` 是两个不同的路由。当该选项为 `false` 时,路由大小写不敏感,`/foo`、`/Foo` 以及 `/FOO` 都是一样的。 @@ -253,10 +278,10 @@ fastify.post('/', { 将 `caseSensitive` 设置为 `false`,会导致所有路径变为小写,除了路由参数与通配符。 ```js -fastify.get('/user/:username', (request, reply) => { +fastify.get("/user/:username", (request, reply) => { // 原 URL: /USER/NodeJS - console.log(request.params.username) // -> 'NodeJS' -}) + console.log(request.params.username); // -> 'NodeJS' +}); ``` 要注意的是,将该选项设为 `false` 与 [RFC3986](https://tools.ietf.org/html/rfc3986#section-6.2.2.1) 相悖。 @@ -264,109 +289,121 @@ fastify.get('/user/:username', (request, reply) => { 此外,该选项不影响 query string 的解析。要让 query string 忽略大小写,请看 [`querystringParser`](./Server.md#querystringParser)。 + ### `requestIdHeader` 用来获知请求 ID 的 header 名。请看[请求 ID](Logging.md#logging-request-id) 一节。 -+ 默认值:`'request-id'` +- 默认值:`'request-id'` + ### `requestIdLogLabel` 定义日志中请求 ID 的标签。 -+ 默认值:`'reqId'` +- 默认值:`'reqId'` + ### `genReqId` + 用于生成请求 ID 的函数。参数为来访的请求对象。 -+ 默认值:`'request-id' header 的值 (当存在时) 或单调递增的整数` -在分布式系统中,你可能会特别想覆盖如下默认的 ID 生成行为。要生成 `UUID`,请看[hyperid](https://github.com/mcollina/hyperid)。 - ```js -let i = 0 -const fastify = require('fastify')({ - genReqId: function (req) { return i++ } -}) +- 默认值:`'request-id' header 的值 (当存在时) 或单调递增的整数` + 在分布式系统中,你可能会特别想覆盖如下默认的 ID 生成行为。要生成 `UUID`,请看[hyperid](https://github.com/mcollina/hyperid)。 + +```js +let i = 0; +const fastify = require("fastify")({ + genReqId: function (req) { + return i++; + }, +}); ``` **注意:当设置了 [requestIdHeader](#requestidheader) 中定义的 header (默认为 'request-id') 时,genReqId _不会_ 被调用。** + ### `trustProxy` 通过开启 `trustProxy` 选项,Fastify 会认为使用了代理服务,且 `X-Forwarded-*` header 是可信的,否则该值被认为是极具欺骗性的。 ```js -const fastify = Fastify({ trustProxy: true }) +const fastify = Fastify({ trustProxy: true }); ``` -+ 默认值:`false` -+ `true/false`: 信任所有代理 (`true`) 或不信任任意的代理 (`false`)。 -+ `string`: 只信任给定的 IP/CIDR (例如 `'127.0.0.1'`)。可以是一组用英文逗号分隔的地址 (例如 `'127.0.0.1,192.168.1.1/24'`)。 -+ `Array`: 只信任给定的 IP/CIDR 列表 (例如 `['127.0.0.1']`)。 -+ `number`: 信任来自前置代理服务器的第n跳 (hop) 地址作为客户端。 -+ `Function`: 自定义的信任函数,第一个参数为 `address` - ```js - function myTrustFn(address, hop) { - return address === '1.2.3.4' || hop === 1 - } - ``` +- 默认值:`false` +- `true/false`: 信任所有代理 (`true`) 或不信任任意的代理 (`false`)。 +- `string`: 只信任给定的 IP/CIDR (例如 `'127.0.0.1'`)。可以是一组用英文逗号分隔的地址 (例如 `'127.0.0.1,192.168.1.1/24'`)。 +- `Array`: 只信任给定的 IP/CIDR 列表 (例如 `['127.0.0.1']`)。 +- `number`: 信任来自前置代理服务器的第n跳 (hop) 地址作为客户端。 +- `Function`: 自定义的信任函数,第一个参数为 `address` + ```js + function myTrustFn(address, hop) { + return address === "1.2.3.4" || hop === 1; + } + ``` 更多示例详见 [`proxy-addr`](https://www.npmjs.com/package/proxy-addr)。 你还可以通过 [`request`](Request.md) 对象获取 `ip`、`ips`、`hostname` 与 `protocol` 的值。 ```js -fastify.get('/', (request, reply) => { - console.log(request.ip) - console.log(request.ips) - console.log(request.hostname) - console.log(request.protocol) -}) +fastify.get("/", (request, reply) => { + console.log(request.ip); + console.log(request.ips); + console.log(request.hostname); + console.log(request.protocol); +}); ``` **注:如果请求存在多个 x-forwarded-hostx-forwarded-proto header,只会根据最后一个产生 request.hostnamerequest.protocol** + ### `pluginTimeout` 单个插件允许加载的最长时间,以毫秒计。如果某个插件加载超时,则 [`ready`](Server.md#ready) 会抛出一个含有 `'ERR_AVVIO_PLUGIN_TIMEOUT'` 代码的 `Error` 对象。 -+ 默认值:`10000` +- 默认值:`10000` + + + +### `querystringParser` - - ### `querystringParser` - Fastify 默认使用 Node.js 核心的 `querystring` 模块作为 query string 解析器。
你可以通过 `querystringParser` 选项来使用自定义的解析器,例如 [`qs`](https://www.npmjs.com/package/qs)。 ```js -const qs = require('qs') -const fastify = require('fastify')({ - querystringParser: str => qs.parse(str) -}) +const qs = require("qs"); +const fastify = require("fastify")({ + querystringParser: (str) => qs.parse(str), +}); ``` 你也可以改变默认解析器的行为,例如忽略 query string 的大小写: ```js -const querystring = require('querystring') -const fastify = require('fastify')({ - querystringParser: str => querystring.parse(str.toLowerCase()) -}) +const querystring = require("querystring"); +const fastify = require("fastify")({ + querystringParser: (str) => querystring.parse(str.toLowerCase()), +}); ``` 若你只想忽略键的大小写,我们推荐你使用自定义解析器。 + ### `exposeHeadRoutes` 自动为每个 `GET` 路由添加对应的 `HEAD` 路由。如果你不想禁用该选项,又希望自定义 `HEAD` 处理函数,请在 `GET` 路由前定义该处理函数。 -+ 默认值:`false` +- 默认值:`false` + ### `constraints` Fastify 内建的路由约束由 `find-my-way` 支持,允许使用 `version` 或 `host` 来约束路由。通过为 `find-my-way` 提供 `constraints` 对象,你可以添加新的约束策略,或覆盖原有策略。更多内容可见于 [find-my-way](https://github.com/delvedor/find-my-way) 的文档。 @@ -374,40 +411,50 @@ Fastify 内建的路由约束由 `find-my-way` 支持,允许使用 `version` ```js const customVersionStrategy = { storage: function () { - let versions = {} + let versions = {}; return { - get: (version) => { return versions[version] || null }, - set: (version, store) => { versions[version] = store }, - del: (version) => { delete versions[version] }, - empty: () => { versions = {} } - } + get: (version) => { + return versions[version] || null; + }, + set: (version, store) => { + versions[version] = store; + }, + del: (version) => { + delete versions[version]; + }, + empty: () => { + versions = {}; + }, + }; }, deriveVersion: (req, ctx) => { - return req.headers['accept'] - } -} + return req.headers["accept"]; + }, +}; -const fastify = require('fastify')({ +const fastify = require("fastify")({ constraints: { - version: customVersionStrategy - } -}) + version: customVersionStrategy, + }, +}); ``` + ### `return503OnClosing` 调用 `close` 方法后返回 503 状态码。 如果为 `false`,服务器会正常处理请求。 -+ 默认值:`true` +- 默认值:`true` + ### `ajv` 配置 Fastify 使用的 Ajv 6 实例。这使得你无需提供一个自定义的实例。 -+ 默认值: +- 默认值: ```js { @@ -423,35 +470,37 @@ const fastify = require('fastify')({ ``` ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ ajv: { customOptions: { - nullable: false // 参见 [ajv 的配置选项](https://ajv.js.org/#options) + nullable: false, // 参见 [ajv 的配置选项](https://ajv.js.org/#options) }, plugins: [ - require('ajv-merge-patch'), - [require('ajv-keywords'), 'instanceof'] + require("ajv-merge-patch"), + [require("ajv-keywords"), "instanceof"], // 用法: [plugin, pluginOptions] - 插件与选项 // 用法: plugin - 仅插件 - ] - } -}) + ], + }, +}); ``` + ### `serializerOpts` 自定义用于序列化响应 payload 的 [`fast-json-stringify`](https://github.com/fastify/fast-json-stringify#options) 实例的配置: ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ serializerOpts: { - rounding: 'ceil' - } -}) + rounding: "ceil", + }, +}); ``` + ### `http2SessionTimeout` 为每个 HTTP/2 会话设置默认[超时时间](https://nodejs.org/api/http2.html#http2_http2session_settimeout_msecs_callback)。超时后,会话将关闭。默认值:`5000` 毫秒。 @@ -459,74 +508,82 @@ const fastify = require('fastify')({ 要注意的是,使用 HTTP/2 时需要提供一个优雅的“close”体验。一个低的默认值有助于减轻拒绝服务型攻击 (Denial-of-Service Attacks) 的影响。但当你的服务器使用负载均衡策略,或能自动扩容时,则可以延长超时时间。Node 的默认值为 `0`,即无超时。 + ### `frameworkErrors` -+ 默认值:`null` +- 默认值:`null` 对于最常见的场景,Fastify 已经提供了默认的错误处理方法。这个选项允许你重写这些处理方法。 -*注:目前只实现了 `FST_ERR_BAD_URL` 这个错误。* +_注:目前只实现了 `FST_ERR_BAD_URL` 这个错误。_ ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ frameworkErrors: function (error, req, res) { if (error instanceof FST_ERR_BAD_URL) { - res.code(400) - return res.send("Provided url is not valid") + res.code(400); + return res.send("Provided url is not valid"); } else { - res.send(err) + res.send(err); } - } -}) + }, +}); ``` + ### `clientErrorHandler` 设置 [clientErrorHandler](https://nodejs.org/api/http.html#http_event_clienterror) 来监听客户端连接造成的 `error` 事件,并响应 `400` 状态码。 设置了该选项,会覆盖默认的 `clientErrorHandler`。 -+ 默认值: +- 默认值: + ```js -function defaultClientErrorHandler (err, socket) { - if (err.code === 'ECONNRESET') { - return +function defaultClientErrorHandler(err, socket) { + if (err.code === "ECONNRESET") { + return; } const body = JSON.stringify({ - error: http.STATUS_CODES['400'], - message: 'Client Error', - statusCode: 400 - }) - this.log.trace({ err }, 'client error') + error: http.STATUS_CODES["400"], + message: "Client Error", + statusCode: 400, + }); + this.log.trace({ err }, "client error"); if (socket.writable) { - socket.end(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) + socket.end( + `HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, + ); } } ``` -*注:`clientErrorHandler` 使用底层的 socket,故处理函数需要返回格式正确的 HTTP 响应信息,包括状态行、HTTP header 以及 body。在写入之前,为了避免 socket 已被销毁,你还应该检查 socket 是否依然可写。* +_注:`clientErrorHandler` 使用底层的 socket,故处理函数需要返回格式正确的 HTTP 响应信息,包括状态行、HTTP header 以及 body。在写入之前,为了避免 socket 已被销毁,你还应该检查 socket 是否依然可写。_ ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ clientErrorHandler: function (err, socket) { const body = JSON.stringify({ error: { - message: 'Client error', - code: '400' - } - }) + message: "Client error", + code: "400", + }, + }); // `this` 为 fastify 实例 - this.log.trace({ err }, 'client error') + this.log.trace({ err }, "client error"); // 处理函数应当发送正确的 HTTP 响应信息。 - socket.end(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`) - } -}) + socket.end( + `HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`, + ); + }, +}); ``` + ### `rewriteUrl` 设置一个异步函数,返回一个字符串,用于重写 URL。 @@ -534,8 +591,9 @@ const fastify = require('fastify')({ > 重写 URL 会修改 `req` 对象的 `url` 属性 ```js -function rewriteUrl (req) { // req 是 Node.js 的 HTTP 请求对象 - return req.url === '/hi' ? '/hello' : req.url; +function rewriteUrl(req) { + // req 是 Node.js 的 HTTP 请求对象 + return req.url === "/hi" ? "/hello" : req.url; } ``` @@ -546,305 +604,378 @@ function rewriteUrl (req) { // req 是 Node.js 的 HTTP 请求对象 ### 服务器方法 + #### 服务器 + `fastify.server`:由 [**`Fastify 的工厂函数`**](Server.md) 生成的 Node 原生 [server](https://nodejs.org/api/http.html#http_class_http_server) 对象。 + #### after + 当前插件及在其中注册的所有插件加载完毕后调用。总在 `fastify.ready` 之前执行。 ```js fastify .register((instance, opts, done) => { - console.log('当前插件') - done() + console.log("当前插件"); + done(); }) - .after(err => { - console.log('当前插件之后') + .after((err) => { + console.log("当前插件之后"); }) .register((instance, opts, done) => { - console.log('下一个插件') - done() - }) - .ready(err => { - console.log('万事俱备') + console.log("下一个插件"); + done(); }) + .ready((err) => { + console.log("万事俱备"); + }); ``` 当 `after()` 没有回调参数时,它返回一个 `Promise`: ```js fastify.register(async (instance, opts) => { - console.log('Current plugin') -}) + console.log("Current plugin"); +}); -await fastify.after() -console.log('After current plugin') +await fastify.after(); +console.log("After current plugin"); fastify.register(async (instance, opts) => { - console.log('Next plugin') -}) + console.log("Next plugin"); +}); -await fastify.ready() +await fastify.ready(); -console.log('Everything has been loaded') +console.log("Everything has been loaded"); ``` + #### ready + 当所有插件的加载都完成时调用。如有错误发生,它会传递一个 `error` 参数。 + ```js -fastify.ready(err => { - if (err) throw err -}) +fastify.ready((err) => { + if (err) throw err; +}); ``` + 调用时不加参数,它会返回一个 `Promise` 对象: ```js -fastify.ready().then(() => { - console.log('successfully booted!') -}, (err) => { - console.log('an error happened', err) -}) +fastify.ready().then( + () => { + console.log("successfully booted!"); + }, + (err) => { + console.log("an error happened", err); + }, +); ``` + #### listen + 所有的插件加载完毕、`ready` 事件触发后,在指定的端口启动服务器。它的回调函数与 Node 原生方法的回调相同。默认情况下,服务器监听 `localhost` 所决定的地址 (`127.0.0.1` 或 `::1`,取决于操作系统)。将地址设置为 `0.0.0.0` 可监听所有的 IPV4 地址。设置为 `::` 则可监听所有的 IPV6 地址,在某些系统中,这么做亦可同时监听所有 IPV4 地址。监听所有的接口要格外谨慎,因为这种方式存在着固有的[安全风险](https://web.archive.org/web/20170831174611/https://snyk.io/blog/mongodb-hack-and-secure-defaults/)。 ```js fastify.listen(3000, (err, address) => { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -}) +}); ``` 指定监听的地址: ```js -fastify.listen(3000, '127.0.0.1', (err, address) => { +fastify.listen(3000, "127.0.0.1", (err, address) => { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -}) +}); ``` 指定积压队列 (backlog queue size) 的大小: ```js -fastify.listen(3000, '127.0.0.1', 511, (err, address) => { +fastify.listen(3000, "127.0.0.1", 511, (err, address) => { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -}) +}); ``` 没有提供回调函数时,它会返回一个 Promise 对象: ```js -fastify.listen(3000) +fastify + .listen(3000) .then((address) => console.log(`server listening on ${address}`)) - .catch(err => { - console.log('Error starting server:', err) - process.exit(1) - }) + .catch((err) => { + console.log("Error starting server:", err); + process.exit(1); + }); ``` 你还可以在使用 Promise 的同时指定地址: ```js -fastify.listen(3000, '127.0.0.1') +fastify + .listen(3000, "127.0.0.1") .then((address) => console.log(`server listening on ${address}`)) - .catch(err => { - console.log('Error starting server:', err) - process.exit(1) - }) + .catch((err) => { + console.log("Error starting server:", err); + process.exit(1); + }); ``` 当部署在 Docker 或其它容器上时,明智的做法是监听 `0.0.0.0`。因为默认情况下,这些容器并未将映射的端口暴露在 `127.0.0.1`: ```js -fastify.listen(3000, '0.0.0.0', (err, address) => { +fastify.listen(3000, "0.0.0.0", (err, address) => { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -}) +}); ``` 假如未设置 `port` (或设为 0),则会自动选择一个随机可用的端口 (之后可通过 `fastify.server.address().port` 获知)。 + #### getDefaultRoute + 获取服务器 `defaultRoute` 属性的方法: ```js -const defaultRoute = fastify.getDefaultRoute() +const defaultRoute = fastify.getDefaultRoute(); ``` + #### setDefaultRoute + 设置服务器 `defaultRoute` 属性的方法: ```js const defaultRoute = function (req, res) { - res.end('hello world') -} + res.end("hello world"); +}; -fastify.setDefaultRoute(defaultRoute) +fastify.setDefaultRoute(defaultRoute); ``` + #### routing + 访问内部路由库的 `lookup` 方法,该方法将请求匹配到合适的处理函数: ```js -fastify.routing(req, res) +fastify.routing(req, res); ``` + #### route + 将路由添加到服务器的方法,支持简写。请看[这里](Routes.md)。 + #### close + `fastify.close(callback)`:调用这个函数来关闭服务器实例,并触发 [`'onClose'`](Hooks.md#on-close) 钩子。
服务器会向所有新的请求发送 `503` 错误,并销毁它们。 要改变这一行为,请见 [`return503OnClosing`](Server.md#factory-return-503-on-closing)。 如果无参调用,它会返回一个 Promise: - ```js -fastify.close().then(() => { - console.log('successfully closed!') -}, (err) => { - console.log('an error happened', err) -}) +```js +fastify.close().then( + () => { + console.log("successfully closed!"); + }, + (err) => { + console.log("an error happened", err); + }, +); ``` -#### decorate* + +#### decorate\* + 向 Fastify 实例、响应或请求添加装饰器函数。参阅[这里](Decorators.md)了解更多。 + #### register + Fastify 允许用户通过插件扩展功能。插件可以是一组路由、装饰器或其他。请看[这里](Plugins.md)。 + #### addHook + 向 Fastify 添加特定的生命周期钩子函数,请看[这里](Hooks.md)。 + #### prefix + 添加在路由前的完整路径。 示例: ```js -fastify.register(function (instance, opts, done) { - instance.get('/foo', function (request, reply) { - // 输出:"prefix: /v1" - request.log.info('prefix: %s', instance.prefix) - reply.send({prefix: instance.prefix}) - }) +fastify.register( + function (instance, opts, done) { + instance.get("/foo", function (request, reply) { + // 输出:"prefix: /v1" + request.log.info("prefix: %s", instance.prefix); + reply.send({ prefix: instance.prefix }); + }); - instance.register(function (instance, opts, done) { - instance.get('/bar', function (request, reply) { - // 输出:"prefix: /v1/v2" - request.log.info('prefix: %s', instance.prefix) - reply.send({prefix: instance.prefix}) - }) + instance.register( + function (instance, opts, done) { + instance.get("/bar", function (request, reply) { + // 输出:"prefix: /v1/v2" + request.log.info("prefix: %s", instance.prefix); + reply.send({ prefix: instance.prefix }); + }); - done() - }, { prefix: '/v2' }) + done(); + }, + { prefix: "/v2" }, + ); - done() -}, { prefix: '/v1' }) + done(); + }, + { prefix: "/v1" }, +); ``` + #### pluginName + 当前插件的名称。有三种定义插件名称的方式(按顺序)。 1. 如果插件使用 [fastify-plugin](https://github.com/fastify/fastify-plugin),那么名称为元数据 (metadata) 中的 `name`。 2. 如果插件通过 `module.exports` 导出,使用文件名。 3. 如果插件通过常规的 [函数定义](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Defining_functions),则使用函数名。 -*回退方案*:插件函数的头两行将作为插件名,并使用 `--` 替代换行符。这有助于在处理涉及许多插件的问题时,找到根源。 +_回退方案_:插件函数的头两行将作为插件名,并使用 `--` 替代换行符。这有助于在处理涉及许多插件的问题时,找到根源。 重点:如果你要处理一些通过 [fastify-plugin](https://github.com/fastify/fastify-plugin) 包装的嵌套的异名插件,由于没有生成新的定义域,因此不会去覆盖上下文数据,而是将各插件名加入一个数组。在这种情况下,会按涉及到的插件的启动顺序,以 `plugin-A -> plugin-B` 的格式来展示插件名称。 + #### log + 日志的实例,详见[这里](Logging.md)。 + #### version + Fastify 实例的版本。可在插件中使用。详见[插件](Plugins.md#handle-the-scope)一文。 + #### inject + 伪造 HTTP 注入 (作为测试之用) 。请看[更多内容](Testing.md#inject)。 + #### addSchema + `fastify.addSchema(schemaObj)`,向 Fastify 实例添加 JSON schema。你可以通过 `$ref` 关键字在应用的任意位置使用它。
更多内容,请看[验证和序列化](Validation-and-Serialization.md)。 + #### getSchemas + `fastify.getSchemas()`,返回一个对象,包含所有通过 `addSchema` 添加的 schema,对象的键是 JSON schema 的 `$id`。 + #### getSchema + `fastify.getSchema(id)`,返回通过 `addSchema` 添加的拥有匹配 `id` 的 schema,未找到则返回 `undefined`。 + #### setReplySerializer + 作用于未设置 [Reply.serializer(func)](Reply.md#serializerfunc) 的所有路由的默认序列化方法。这个处理函数是完全封装的,因此,不同的插件允许有不同的错误处理函数。 注:仅当状态码为 `2xx` 时才被调用。关于错误处理,请看 [`setErrorHandler`](Server.md#seterrorhandler)。 - ```js -fastify.setReplySerializer(function (payload, statusCode){ +```js +fastify.setReplySerializer(function (payload, statusCode) { // 使用同步函数序列化 payload - return `my serialized ${statusCode} content: ${payload}` -}) + return `my serialized ${statusCode} content: ${payload}`; +}); ``` + #### setValidatorCompiler + 为所有的路由设置 schema 校验编译器 (validator compiler)。详见 [#schema-validator](Validation-and-Serialization.md#schema-validator)。 + #### setSchemaErrorFormatter + 为所有的路由设置 schema 错误格式化器 (schema error formatter)。详见 [#error-handling](Validation-and-Serialization.md#schemaerrorformatter)。 + #### setSerializerCompiler + 为所有的路由设置 schema 序列化编译器 (serializer compiler)。详见 [#schema-serializer](Validation-and-Serialization.md#schema-serializer)。 **注:** [`setReplySerializer`](#set-reply-serializer) 有更高的优先级! + #### validatorCompiler + 该属性用于获取 schema 校验器。未设置校验器时,在服务器启动前,该值是 `null`,之后是一个签名为 `function ({ schema, method, url, httpPart })` 的函数。该函数将 `schema` 参数编译为一个校验数据的函数,并返回生成的函数。 `schema` 参数能访问到所有通过 [`.addSchema`](#add-schema) 添加的共用 schema。 + #### serializerCompiler + 该属性用于获取 schema 序列化器。未设置序列化器时,在服务器启动前,该值是 `null`,之后是一个签名为 `function ({ schema, method, url, httpPart })` 的函数。该函数将 `schema` 参数编译为一个校验数据的函数,并返回生成的函数。 `schema` 参数能访问到所有通过 [`.addSchema`](#add-schema) 添加的共用 schema。 + #### schemaErrorFormatter + 该属性设置一个函数用于格式化 `validationCompiler` 在校验 schema 时发生的错误。详见 [#error-handling](Validation-and-Serialization.md#schemaerrorformatter)。 + #### schemaController + 该属性用于管理: + - `bucket`:应用的 schema 的存放位置 - `compilersFactory`:必须编译 JSON schema 的模块 @@ -861,27 +992,27 @@ const fastify = Fastify({ * @param {object} parentSchemas 会由 `bucket` 对象的 `getSchemas()` 方法返回。 */ - bucket: function factory (parentSchemas) { + bucket: function factory(parentSchemas) { return { - addSchema (inputSchema) { + addSchema(inputSchema) { // 该函数保存用户添加的 schema。 // 调用 `fastify.addSchema()` 时被执行。 }, - getSchema (schema$id) { + getSchema(schema$id) { // 该函数返回通过 `schema$id` 检索得到的原始 schema。 // 调用 `fastify.getSchema()` 时被执行。 - return aSchema + return aSchema; }, - getSchemas () { + getSchemas() { // 返回路由 schema 中通过 $ref 引用的所有 schema。 // 返回对象以 schema 的 `$id` 为键,以原始内容为值。 const allTheSchemaStored = { - 'schema$id1': schema1, - 'schema$id2': schema2 - } - return allTheSchemaStored - } - } + schema$id1: schema1, + schema$id2: schema2, + }; + return allTheSchemaStored; + }, + }; }, /** @@ -895,13 +1026,13 @@ const fastify = Fastify({ * @param {object} externalSchemas 这些 schema 将被 `bucket.getSchemas()` 返回。需要处理外部引用 $ref。 * @param {object} ajvServerOption 服务器的 `ajv` 选项。 */ - buildValidator: function factory (externalSchemas, ajvServerOption) { + buildValidator: function factory(externalSchemas, ajvServerOption) { // 该 factory 函数必须返回一个 schema 校验编译器。 // 详见 [#schema-validator](Validation-and-Serialization.md#schema-validator)。 - const yourAjvInstance = new Ajv(ajvServerOption.customOptions) - return function validatorCompiler ({ schema, method, url, httpPart }) { - return yourAjvInstance.compile(schema) - } + const yourAjvInstance = new Ajv(ajvServerOption.customOptions); + return function validatorCompiler({ schema, method, url, httpPart }) { + return yourAjvInstance.compile(schema); + }; }, /** @@ -911,15 +1042,23 @@ const fastify = Fastify({ * @param {object} externalSchemas 这些 schema 将被 `bucket.getSchemas()` 返回。需要处理外部引用 $ref。 * @param {object} serializerOptsServerOption 服务器的 `serializerOpts` 选项。 */ - buildSerializer: function factory (externalSchemas, serializerOptsServerOption) { + buildSerializer: function factory( + externalSchemas, + serializerOptsServerOption, + ) { // 该 factory 函数必须返回一个 schema 序列化编译器。 // 详见 [#schema-serializer](Validation-and-Serialization.md#schema-serializer)。 - return function serializerCompiler ({ schema, method, url, httpStatus }) { - return data => JSON.stringify(data) - } - } - } - } + return function serializerCompiler({ + schema, + method, + url, + httpStatus, + }) { + return (data) => JSON.stringify(data); + }; + }, + }, + }, }); ``` @@ -930,30 +1069,31 @@ Ajv 8 是 Ajv 6 的后继版本,拥有许多改进与新特性。Ajv 8 新特 下列代码可以将 Ajv 8 作为默认的 schema 验证器使用: ```js -const AjvCompiler = require('@fastify/ajv-compiler') // 必须是 v2.x.x 版本 +const AjvCompiler = require("@fastify/ajv-compiler"); // 必须是 v2.x.x 版本 // 请注意,默认情况下 Ajv 8 不支持 schema 的关键词 `format`, // 因此需要手动添加 -const ajvFormats = require('ajv-formats') +const ajvFormats = require("ajv-formats"); const app = fastify({ ajv: { customOptions: { - validateFormats: true + validateFormats: true, }, - plugins: [ajvFormats] + plugins: [ajvFormats], }, schemaController: { compilersFactory: { - buildValidator: AjvCompiler() - } - } -}) + buildValidator: AjvCompiler(), + }, + }, +}); // 大功告成!现在你可以在 schema 中使用 Ajv 8 的选项与关键词了! ``` + #### setNotFoundHandler `fastify.setNotFoundHandler(handler(request, reply))`:为 404 状态 (not found) 设置处理函数 (handler)。向 `fastify.register()` 传递不同的 [`prefix` 选项](Plugins.md#route-prefixing-option),就可以为不同的插件设置不同的处理函数。这些处理函数被视为常规的路由处理函数,因此它们的请求会经历一个完整的 [Fastify 生命周期](Lifecycle.md#lifecycle)。 @@ -963,71 +1103,79 @@ const app = fastify({ _注:通过此方法注册的 `preValidation` 钩子会在遇到未知路由时触发,但手动调用 [`reply.callNotFound`](Reply.md#call-not-found) 方法时则**不会**_。此时只有 preHandler 会执行。 ```js -fastify.setNotFoundHandler({ - preValidation: (req, reply, done) => { - // 你的代码 - done() - } , - preHandler: (req, reply, done) => { - // 你的代码 - done() - } -}, function (request, reply) { +fastify.setNotFoundHandler( + { + preValidation: (req, reply, done) => { + // 你的代码 + done(); + }, + preHandler: (req, reply, done) => { + // 你的代码 + done(); + }, + }, + function (request, reply) { // 设置了 preValidation 与 preHandler 钩子的默认 not found 处理函数 -}) + }, +); -fastify.register(function (instance, options, done) { - instance.setNotFoundHandler(function (request, reply) { - // '/v1' 开头的 URL 的 not found 处理函数, - // 未设置 preValidation 与 preHandler 钩子 - }) - done() -}, { prefix: '/v1' }) +fastify.register( + function (instance, options, done) { + instance.setNotFoundHandler(function (request, reply) { + // '/v1' 开头的 URL 的 not found 处理函数, + // 未设置 preValidation 与 preHandler 钩子 + }); + done(); + }, + { prefix: "/v1" }, +); ``` Fastify 启动时,会在插件注册之前就调用 `setNotFoundHandler` 方法添加默认的 404 处理函数。假如你想拓展默认 404 处理函数的行为,例如与插件一同使用,你可以在插件上下文内,不传参数地调用 `fastify.setNotFoundHandler()`。 + #### setErrorHandler -`fastify.setErrorHandler(handler(error, request, reply))`:设置任意时刻的错误处理函数。错误处理函数绑定在 Fastify 实例之上,是完全封装 (fully encapsulated) 的,因此不同插件的处理函数可以不同。支持 *async-await* 语法。
-*注:假如错误的 `statusCode` 小于 400,在处理错误前 Fastify 将会自动将其设为 500。* +`fastify.setErrorHandler(handler(error, request, reply))`:设置任意时刻的错误处理函数。错误处理函数绑定在 Fastify 实例之上,是完全封装 (fully encapsulated) 的,因此不同插件的处理函数可以不同。支持 _async-await_ 语法。
+_注:假如错误的 `statusCode` 小于 400,在处理错误前 Fastify 将会自动将其设为 500。_ ```js fastify.setErrorHandler(function (error, request, reply) { // 记录错误 - this.log.error(error) + this.log.error(error); // 发送错误响应 - reply.status(409).send({ ok: false }) -}) + reply.status(409).send({ ok: false }); +}); ``` 当没有设置错误处理函数时,Fastify 会调用一个默认函数。你能通过 `fastify.errorHandler` 访问该函数。它根据 `statusCode` 相应地记录日志。 ```js -var statusCode = error.statusCode +var statusCode = error.statusCode; if (statusCode >= 500) { - log.error(error) + log.error(error); } else if (statusCode >= 400) { - log.info(error) + log.info(error); } else { - log.error(error) + log.error(error); } ``` + #### printRoutes `fastify.printRoutes()`:打印路由的基数树 (radix tree),可作调试之用。可以用 `fastify.printRoutes({ commonPrefix: false })` 来打印扁平化后的路由
-*记得在 `ready` 函数的内部或之后调用它。* +_记得在 `ready` 函数的内部或之后调用它。_ ```js -fastify.get('/test', () => {}) -fastify.get('/test/hello', () => {}) -fastify.get('/hello/world', () => {}) +fastify.get("/test", () => {}); +fastify.get("/test/hello", () => {}); +fastify.get("/hello/world", () => {}); fastify.ready(() => { - console.log(fastify.printRoutes()) + console.log(fastify.printRoutes()); // └── / // ├── test (GET) // │ └── /hello (GET) @@ -1035,63 +1183,67 @@ fastify.ready(() => { // ├── lo/world (GET) // └── licopter (GET) - console.log(fastify.printRoutes({ commonPrefix: false })) + console.log(fastify.printRoutes({ commonPrefix: false })); // └── / (-) // ├── test (GET) // │ └── /hello (GET) // ├── hello/world (GET) // └── helicopter (GET) -}) +}); ``` `fastify.printRoutes({ includeMeta: (true | []) })` 会打印出路由的 `route.store` 对象上的属性。`includeMeta` 的值可以是属性名的数组 (例如:`['onRequest', Symbol('key')]`),也可以只是一个 `true`,表示显示所有属性。简写 `fastify.printRoutes({ includeHooks: true })` 将包含所有的[钩子](Hooks.md)。 ```js - console.log(fastify.printRoutes({ includeHooks: true, includeMeta: ['metaProperty'] })) - // └── / - // ├── test (GET) - // │ • (onRequest) ["anonymous()","namedFunction()"] - // │ • (metaProperty) "value" - // │ └── /hello (GET) - // └── hel - // ├── lo/world (GET) - // │ • (onTimeout) ["anonymous()"] - // └── licopter (GET) - - console.log(fastify.printRoutes({ includeHooks: true })) - // └── / - // ├── test (GET) - // │ • (onRequest) ["anonymous()","namedFunction()"] - // │ └── /hello (GET) - // └── hel - // ├── lo/world (GET) - // │ • (onTimeout) ["anonymous()"] - // └── licopter (GET) +console.log( + fastify.printRoutes({ includeHooks: true, includeMeta: ["metaProperty"] }), +); +// └── / +// ├── test (GET) +// │ • (onRequest) ["anonymous()","namedFunction()"] +// │ • (metaProperty) "value" +// │ └── /hello (GET) +// └── hel +// ├── lo/world (GET) +// │ • (onTimeout) ["anonymous()"] +// └── licopter (GET) + +console.log(fastify.printRoutes({ includeHooks: true })); +// └── / +// ├── test (GET) +// │ • (onRequest) ["anonymous()","namedFunction()"] +// │ └── /hello (GET) +// └── hel +// ├── lo/world (GET) +// │ • (onTimeout) ["anonymous()"] +// └── licopter (GET) ``` + #### printPlugins `fastify.printPlugins()`:打印 avvio 内部的插件树,可用于调试插件注册顺序相关的问题。
-*请在 `ready` 事件的回调中或事件触发之后调用该方法。* +_请在 `ready` 事件的回调中或事件触发之后调用该方法。_ ```js -fastify.register(async function foo (instance) { - instance.register(async function bar () {}) -}) -fastify.register(async function baz () {}) +fastify.register(async function foo(instance) { + instance.register(async function bar() {}); +}); +fastify.register(async function baz() {}); fastify.ready(() => { - console.error(fastify.printPlugins()) + console.error(fastify.printPlugins()); // 输出: // └── root // ├── foo // │ └── bar // └── baz -}) +}); ``` + #### addContentTypeParser `fastify.addContentTypeParser(content-type, options, parser)` 用于给指定 content type 自定义解析器,当你使用自定义的 content types 时会很有帮助。例如 `text/json, application/vnd.oasis.opendocument.text`。`content-type` 是一个字符串、字符串数组或正则表达式。 @@ -1099,47 +1251,64 @@ fastify.ready(() => { ```js // 传递给 getDefaultJsonParser 的两个参数用于配置原型污染以及构造函数污染,允许的值为 'ignore'、'remove' 和 'error'。设置为 ignore 会跳过校验,和直接调用 JSON.parse() 效果相同。详见 `secure-json-parse` 的文档。 -fastify.addContentTypeParser('text/json', { asString: true }, fastify.getDefaultJsonParser('ignore', 'ignore')) +fastify.addContentTypeParser( + "text/json", + { asString: true }, + fastify.getDefaultJsonParser("ignore", "ignore"), +); ``` + #### getDefaultJsonParser `fastify.getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)` 接受两个参数。第一个参数是原型污染的配置,第二个则是构造函数污染的配置。详见 `secure-json-parse` 的文档 + #### defaultTextParser `fastify.defaultTextParser()` 可用于将 content 解析为纯文本。 ```js -fastify.addContentTypeParser('text/json', { asString: true }, fastify.defaultTextParser()) +fastify.addContentTypeParser( + "text/json", + { asString: true }, + fastify.defaultTextParser(), +); ``` + #### errorHandler `fastify.errorHandler` 使用 Fastify 默认的错误处理函数来处理错误。 ```js -fastify.get('/', { - errorHandler: (error, request, reply) => { - if (error.code === 'SOMETHING_SPECIFIC') { - reply.send({ custom: 'response' }) - return - } +fastify.get( + "/", + { + errorHandler: (error, request, reply) => { + if (error.code === "SOMETHING_SPECIFIC") { + reply.send({ custom: "response" }); + return; + } - fastify.errorHandler(error, request, response) - } -}, handler) + fastify.errorHandler(error, request, response); + }, + }, + handler, +); ``` + #### initialConfig `fastify.initialConfig`:暴露一个记录了 Fastify 初始选项的只读对象。 当前暴露的属性有: + - connectionTimeout - keepAliveTimeout - bodyLimit @@ -1157,23 +1326,23 @@ fastify.get('/', { - http2SessionTimeout ```js -const { readFileSync } = require('fs') -const Fastify = require('fastify') +const { readFileSync } = require("fs"); +const Fastify = require("fastify"); const fastify = Fastify({ https: { allowHTTP1: true, - key: readFileSync('./fastify.key'), - cert: readFileSync('./fastify.cert') + key: readFileSync("./fastify.key"), + cert: readFileSync("./fastify.cert"), }, - logger: { level: 'trace'}, + logger: { level: "trace" }, ignoreTrailingSlash: true, maxParamLength: 200, caseSensitive: true, - trustProxy: '127.0.0.1,192.168.1.1/24', -}) + trustProxy: "127.0.0.1,192.168.1.1/24", +}); -console.log(fastify.initialConfig) +console.log(fastify.initialConfig); /* 输出: { @@ -1185,8 +1354,8 @@ console.log(fastify.initialConfig) */ fastify.register(async (instance, opts) => { - instance.get('/', async (request, reply) => { - return instance.initialConfig + instance.get("/", async (request, reply) => { + return instance.initialConfig; /* 返回: { @@ -1196,22 +1365,22 @@ fastify.register(async (instance, opts) => { maxParamLength: 200 } */ - }) + }); - instance.get('/error', async (request, reply) => { + instance.get("/error", async (request, reply) => { // 会抛出错误 // 因为 initialConfig 是只读的,不可修改 - instance.initialConfig.https.allowHTTP1 = false + instance.initialConfig.https.allowHTTP1 = false; - return instance.initialConfig - }) -}) + return instance.initialConfig; + }); +}); // 开始监听 fastify.listen(3000, (err) => { if (err) { - fastify.log.error(err) - process.exit(1) + fastify.log.error(err); + process.exit(1); } -}) +}); ``` diff --git a/doc/fastify-docs/docs/Serverless.md b/doc/fastify-docs/docs/Serverless.md index 094d152..f71e917 100644 --- a/doc/fastify-docs/docs/Serverless.md +++ b/doc/fastify-docs/docs/Serverless.md @@ -21,16 +21,16 @@ Fastify 无法直接运行在无服务器平台上,需要做一点修改。本 以下是使用 Fastify 在 AWS Lambda 和 Amazon API Gateway 架构上构建无服务器 web 应用/服务的示例。 -*注:使用 [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify) 仅是一种可行方案。* +_注:使用 [aws-lambda-fastify](https://github.com/fastify/aws-lambda-fastify) 仅是一种可行方案。_ ### app.js ```js -const fastify = require('fastify'); +const fastify = require("fastify"); function init() { const app = fastify(); - app.get('/', (request, reply) => reply.send({ hello: 'world' })); + app.get("/", (request, reply) => reply.send({ hello: "world" })); return app; } @@ -38,7 +38,7 @@ if (require.main === module) { // 直接调用,即执行 "node app" init().listen(3000, (err) => { if (err) console.error(err); - console.log('server listening on 3000'); + console.log("server listening on 3000"); }); } else { // 作为模块引入 => 用于 aws lambda @@ -52,16 +52,16 @@ if (require.main === module) { 在 [`lambda.js`](https://www.fastify.io/docs/latest/Serverless/#lambda-js) 里,我们会用到它。 当像往常一样运行 Fastify 应用, -比如执行 `node app.js` 时 *(可以用 `require.main === module` 来判断)*, +比如执行 `node app.js` 时 _(可以用 `require.main === module` 来判断)_, 你可以监听某个端口,如此便能本地运行应用了。 ### lambda.js ```js -const awsLambdaFastify = require('aws-lambda-fastify') -const init = require('./app'); +const awsLambdaFastify = require("aws-lambda-fastify"); +const init = require("./app"); -const proxy = awsLambdaFastify(init()) +const proxy = awsLambdaFastify(init()); // 或 // const proxy = awsLambdaFastify(init(), { binaryMimeTypes: ['application/octet-stream'] }) @@ -82,7 +82,6 @@ exports.handler = proxy; 你可以在[这里](https://github.com/claudiajs/example-projects/tree/master/fastify-app-lambda)找到使用 [claudia.js](https://claudiajs.com/tutorials/serverless-express.html) 的可部署的例子。 - ### 注意事项 - 你没法操作 [stream](https://www.fastify.io/docs/latest/Reply/#streams),因为 API Gateway 还不支持它。 @@ -92,7 +91,7 @@ exports.handler = proxy; 与 AWS Lambda 和 Google Cloud Functions 不同,Google Cloud Run 是一个无服务器**容器**环境。它的首要目的是提供一个能运行任意容器的底层抽象 (infrastucture-abstracted) 的环境。因此,你能将 Fastify 部署在 Google Cloud Run 上,而且相比正常的写法,只需要改动极少的代码。 -*参照以下步骤部署 Google Cloud Run。如果你对 gcloud 还不熟悉,请看其[入门文档](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)*。 +_参照以下步骤部署 Google Cloud Run。如果你对 gcloud 还不熟悉,请看其[入门文档](https://cloud.google.com/run/docs/quickstarts/build-and-deploy)_。 ### 调整 Fastify 服务器 @@ -100,35 +99,35 @@ exports.handler = proxy; ```js function build() { - const fastify = Fastify({ trustProxy: true }) - return fastify + const fastify = Fastify({ trustProxy: true }); + return fastify; } async function start() { // Google Cloud Run 会设置这一环境变量, // 因此,你可以使用它判断程序是否运行在 Cloud Run 之中 - const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined + const IS_GOOGLE_CLOUD_RUN = process.env.K_SERVICE !== undefined; // 监听 Cloud Run 提供的端口 - const port = process.env.PORT || 3000 + const port = process.env.PORT || 3000; // 监听 Cloud Run 中所有的 IPV4 地址 - const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined + const address = IS_GOOGLE_CLOUD_RUN ? "0.0.0.0" : undefined; try { - const server = build() - const address = await server.listen(port, address) - console.log(`Listening on ${address}`) + const server = build(); + const address = await server.listen(port, address); + console.log(`Listening on ${address}`); } catch (err) { - console.error(err) - process.exit(1) + console.error(err); + process.exit(1); } } -module.exports = build +module.exports = build; if (require.main === module) { - start() + start(); } ``` @@ -197,7 +196,7 @@ gcloud beta run deploy --image gcr.io/PROJECT-ID/APP-NAME --platform managed ### functions/server.js ```js -export { handler } from '../lambda.js'; // 记得将路径修改为你的应用中对应的 `lambda.js` 的路径 +export { handler } from "../lambda.js"; // 记得将路径修改为你的应用中对应的 `lambda.js` 的路径 ``` ### netlify.toml @@ -218,12 +217,12 @@ export { handler } from '../lambda.js'; // 记得将路径修改为你的应用 **别忘记添加这个文件,否则会有不少问题** ```js -const nodeExternals = require('webpack-node-externals'); -const dotenv = require('dotenv-safe'); -const webpack = require('webpack'); +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'; +const env = process.env.NODE_ENV || "production"; +const dev = env === "development"; if (dev) { dotenv.config({ allowEmptyValues: true }); @@ -231,32 +230,32 @@ if (dev) { module.exports = { mode: env, - devtool: dev ? 'eval-source-map' : 'none', + devtool: dev ? "eval-source-map" : "none", externals: [nodeExternals()], devServer: { proxy: { - '/.netlify': { - target: 'http://localhost:9000', - pathRewrite: { '^/.netlify/functions': '' } - } - } + "/.netlify": { + target: "http://localhost:9000", + pathRewrite: { "^/.netlify/functions": "" }, + }, + }, }, module: { - rules: [] + rules: [], }, plugins: [ new webpack.DefinePlugin({ - 'process.env.APP_ROOT_PATH': JSON.stringify('/'), - 'process.env.NETLIFY_ENV': true, - 'process.env.CONTEXT': env - }) - ] + "process.env.APP_ROOT_PATH": JSON.stringify("/"), + "process.env.NETLIFY_ENV": true, + "process.env.CONTEXT": env, + }), + ], }; ``` ### Scripts -在 `package.json` 的 *scripts* 里加上这一命令 +在 `package.json` 的 _scripts_ 里加上这一命令 ```json "scripts": { @@ -305,6 +304,6 @@ app.register(import("../src/app")); export default async (req, res) => { await app.ready(); - app.server.emit('request', req, res); -} -``` \ No newline at end of file + app.server.emit("request", req, res); +}; +``` diff --git a/doc/fastify-docs/docs/Testing.md b/doc/fastify-docs/docs/Testing.md index a2fc20a..e7997a6 100644 --- a/doc/fastify-docs/docs/Testing.md +++ b/doc/fastify-docs/docs/Testing.md @@ -15,40 +15,40 @@ **app.js**: ```js -'use strict' +"use strict"; -const fastify = require('fastify') +const fastify = require("fastify"); -function build(opts={}) { - const app = fastify(opts) - app.get('/', async function (request, reply) { - return { hello: 'world' } - }) +function build(opts = {}) { + const app = fastify(opts); + app.get("/", async function (request, reply) { + return { hello: "world" }; + }); - return app + return app; } -module.exports = build +module.exports = build; ``` **server.js**: ```js -'use strict' +"use strict"; -const server = require('./app')({ +const server = require("./app")({ logger: { - level: 'info', - prettyPrint: true - } -}) + level: "info", + prettyPrint: true, + }, +}); server.listen(3000, (err, address) => { if (err) { - console.log(err) - process.exit(1) + console.log(err); + process.exit(1); } -}) +}); ``` ### 使用 fastify.inject() 的好处 @@ -60,22 +60,22 @@ server.listen(3000, (err, address) => { **app.test.js**: ```js -'use strict' +"use strict"; -const build = require('./app') +const build = require("./app"); const test = async () => { - const app = build() + const app = build(); const response = await app.inject({ - method: 'GET', - url: '/' - }) + method: "GET", + url: "/", + }); - console.log('status code: ', response.statusCode) - console.log('body: ', response.body) -} -test() + console.log("status code: ", response.statusCode); + console.log("body: ", response.body); +}; +test(); ``` 我们的代码运行在异步函数里,因此可以使用 async/await。 @@ -100,36 +100,40 @@ body: {"hello":"world"} **app.test.js**: ```js -'use strict' +"use strict"; -const { test } = require('tap') -const build = require('./app') +const { test } = require("tap"); +const build = require("./app"); -test('requests the "/" route', async t => { - const app = build() +test('requests the "/" route', async (t) => { + const app = build(); const response = await app.inject({ - method: 'GET', - url: '/' - }) - t.equal(response.statusCode, 200, 'returns a status code of 200') -}) + method: "GET", + url: "/", + }); + t.equal(response.statusCode, 200, "returns a status code of 200"); +}); ``` 执行 `npm test`,查看结果! `inject` 方法能完成的不只有简单的 GET 请求: + ```js -fastify.inject({ - method: String, - url: String, - query: Object, - payload: Object, - headers: Object, - cookies: Object -}, (error, response) => { - // 你的测试 -}) +fastify.inject( + { + method: String, + url: String, + query: Object, + payload: Object, + headers: Object, + cookies: Object, + }, + (error, response) => { + // 你的测试 + }, +); ``` 忽略回调函数,可以链式调用 `.inject` 提供的方法: @@ -137,12 +141,13 @@ fastify.inject({ ```js fastify .inject() - .get('/') - .headers({ foo: 'bar' }) - .query({ foo: 'bar' }) - .end((err, res) => { // 调用 .end 触发请求 - console.log(res.payload) - }) + .get("/") + .headers({ foo: "bar" }) + .query({ foo: "bar" }) + .end((err, res) => { + // 调用 .end 触发请求 + console.log(res.payload); + }); ``` 或是用 promise 的版本 @@ -155,20 +160,26 @@ fastify query: Object, payload: Object, headers: Object, - cookies: Object + cookies: Object, }) - .then(response => { + .then((response) => { // 你的测试 }) - .catch(err => { + .catch((err) => { // 处理错误 - }) + }); ``` Async await 也是支持的! + ```js try { - const res = await fastify.inject({ method: String, url: String, payload: Object, headers: Object }) + const res = await fastify.inject({ + method: String, + url: String, + payload: Object, + headers: Object, + }); // 你的测试 } catch (err) { // 处理错误 @@ -178,49 +189,58 @@ try { #### 另一个例子: **app.js** + ```js -const Fastify = require('fastify') +const Fastify = require("fastify"); -function buildFastify () { - const fastify = Fastify() +function buildFastify() { + const fastify = Fastify(); - fastify.get('/', function (request, reply) { - reply.send({ hello: 'world' }) - }) - - return fastify + fastify.get("/", function (request, reply) { + reply.send({ hello: "world" }); + }); + + return fastify; } -module.exports = buildFastify +module.exports = buildFastify; ``` **test.js** + ```js -const tap = require('tap') -const buildFastify = require('./app') +const tap = require("tap"); +const buildFastify = require("./app"); -tap.test('GET `/` route', t => { - t.plan(4) +tap.test("GET `/` route", (t) => { + t.plan(4); - const fastify = buildFastify() + const fastify = buildFastify(); // 在测试的最后,我们强烈建议你调用 `.close()` // 方法来确保所有与外部服务的连接被关闭。 - t.teardown(() => fastify.close()) + t.teardown(() => fastify.close()); - fastify.inject({ - method: 'GET', - url: '/' - }, (err, response) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(response.json(), { hello: 'world' }) - }) -}) + fastify.inject( + { + method: "GET", + url: "/", + }, + (err, response) => { + t.error(err); + t.equal(response.statusCode, 200); + t.equal( + response.headers["content-type"], + "application/json; charset=utf-8", + ); + t.same(response.json(), { hello: "world" }); + }, + ); +}); ``` ### 测试正在运行的服务器 + 你还可以在 fastify.listen() 启动服务器之后,或是 fastify.ready() 初始化路由与插件之后,进行 Fastify 的测试。 #### 举例: @@ -228,67 +248,81 @@ tap.test('GET `/` route', t => { 使用之前例子的 **app.js**。 **test-listen.js** (用 [`Request`](https://www.npmjs.com/package/request) 测试) + ```js -const tap = require('tap') -const request = require('request') -const buildFastify = require('./app') +const tap = require("tap"); +const request = require("request"); +const buildFastify = require("./app"); -tap.test('GET `/` route', t => { - t.plan(5) +tap.test("GET `/` route", (t) => { + t.plan(5); - const fastify = buildFastify() + const fastify = buildFastify(); - t.teardown(() => fastify.close()) + t.teardown(() => fastify.close()); fastify.listen(0, (err) => { - t.error(err) - - request({ - method: 'GET', - url: 'http://localhost:' + fastify.server.address().port - }, (err, response, body) => { - t.error(err) - t.equal(response.statusCode, 200) - t.equal(response.headers['content-type'], 'application/json; charset=utf-8') - t.same(JSON.parse(body), { hello: 'world' }) - }) - }) -}) + t.error(err); + + request( + { + method: "GET", + url: "http://localhost:" + fastify.server.address().port, + }, + (err, response, body) => { + t.error(err); + t.equal(response.statusCode, 200); + t.equal( + response.headers["content-type"], + "application/json; charset=utf-8", + ); + t.same(JSON.parse(body), { hello: "world" }); + }, + ); + }); +}); ``` **test-ready.js** (用 [`SuperTest`](https://www.npmjs.com/package/supertest) 测试) + ```js -const tap = require('tap') -const supertest = require('supertest') -const buildFastify = require('./app') +const tap = require("tap"); +const supertest = require("supertest"); +const buildFastify = require("./app"); -tap.test('GET `/` route', async (t) => { - const fastify = buildFastify() +tap.test("GET `/` route", async (t) => { + const fastify = buildFastify(); - t.teardown(() => fastify.close()) + t.teardown(() => fastify.close()); - await fastify.ready() + await fastify.ready(); const response = await supertest(fastify.server) - .get('/') + .get("/") .expect(200) - .expect('Content-Type', 'application/json; charset=utf-8') - t.same(response.body, { hello: 'world' }) -}) + .expect("Content-Type", "application/json; charset=utf-8"); + t.same(response.body, { hello: "world" }); +}); ``` ### 如何检测 tap 的测试 + 1. 设置 `{only: true}` 选项,将需要检测的测试与其他测试分离 + ```javascript test('should ...', {only: true}, t => ...) ``` + 2. 通过 `npx` 运行 `tap` + ```bash > npx tap -O -T --node-arg=--inspect-brk test/ ``` + - `-O` 表示开启 `only` 选项,只运行设置了 `{only: true}` 的测试 - `-T` 表示不设置超时 - `--node-arg=--inspect-brk` 会启动 node 调试工具 + 3. 在 VS Code 中创建并运行一个 `Node.js: Attach` 调试配置,不需要额外修改。 -现在你便可以在编辑器中检测你的测试文件 (以及 `Fastify` 的其他部分) 了。 \ No newline at end of file +现在你便可以在编辑器中检测你的测试文件 (以及 `Fastify` 的其他部分) 了。 diff --git a/doc/fastify-docs/docs/TypeScript.md b/doc/fastify-docs/docs/TypeScript.md index 274eb2f..06c3ebb 100644 --- a/doc/fastify-docs/docs/TypeScript.md +++ b/doc/fastify-docs/docs/TypeScript.md @@ -21,44 +21,50 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 这个例子展示了如何使用 Fastify 和 TypeScript 构建一个最简单的 http 服务器。 1. 创建一个 npm 项目,安装 Fastify、typescript 和 node.js 的类型文件: - ```bash - npm init -y - npm i fastify - npm i -D typescript @types/node - ``` + +```bash +npm init -y +npm i fastify +npm i -D typescript @types/node +``` + 2. 在 `package.json` 的 `"scripts"` 里添加以下内容: - ```json - { - "scripts": { - "build": "tsc -p tsconfig.json", - "start": "node index.js" - } + +```json +{ + "scripts": { + "build": "tsc -p tsconfig.json", + "start": "node index.js" } - ``` +} +``` + 3. 初始化 TypeScript 配置文件: - ```bash - npx tsc --init - ``` - 或使用一个[推荐的配置文件](https://github.com/tsconfig/bases#node-10-tsconfigjson)。 -4. 创建 `index.ts` ,在此编写服务器的代码。 -5. 将下列代码添加到该文件中: - ```typescript - import fastify from 'fastify' - const server = fastify() +```bash +npx tsc --init +``` - server.get('/ping', async (request, reply) => { - return 'pong\n' - }) +或使用一个[推荐的配置文件](https://github.com/tsconfig/bases#node-10-tsconfigjson)。4. 创建 `index.ts` ,在此编写服务器的代码。5. 将下列代码添加到该文件中: + +```typescript +import fastify from "fastify"; + +const server = fastify(); + +server.get("/ping", async (request, reply) => { + return "pong\n"; +}); + +server.listen(8080, (err, address) => { + if (err) { + console.error(err); + process.exit(1); + } + console.log(`Server listening at ${address}`); +}); +``` - server.listen(8080, (err, address) => { - if (err) { - console.error(err) - process.exit(1) - } - console.log(`Server listening at ${address}`) - }) - ``` 6. 执行 `npm run build`。这么做会将 `index.ts` 编译为能被 Node.js 运行的 `index.js`。如果遇到了错误,请在 [fastify/help](https://github.com/fastify/help/) 发布 issue。 7. 执行 `npm run start` 来启动 Fastify 服务器。 8. 你将看到控制台输出: `Server listening at http://127.0.0.1:8080`。 @@ -74,6 +80,7 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 1. 照着上面例子的 1-4 步来初始化项目。 2. 在 `index.ts` 中定义两个接口 (interface),`IQuerystring` 和 `IHeaders`: + ```typescript interface IQuerystring { username: string; @@ -81,21 +88,23 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 } interface IHeaders { - 'h-Custom': string; + "h-Custom": string; } ``` + 3. 使用这两个接口,定义一个新的 API 路由,并将它们用作泛型。路由方法的简写形式 (如 `.get`) 接受一个泛型对象 `RouteGenericInterface`,它包含了五个具名属性:`Body`、`Querystring`、`Params`、`Headers` 以及 `Reply`。`Body`、`Querystring`、`Params` 和 `Headers` 四个接口会随着路由方法向下传递,到达路由处理函数中的 `request` 实例,`Reply` 接口则会到达 `reply` 实例。 + ```typescript server.get<{ - Querystring: IQuerystring, - Headers: IHeaders - }>('/auth', async (request, reply) => { - const { username, password } = request.query - const customerHeader = request.headers['h-Custom'] + Querystring: IQuerystring; + Headers: IHeaders; + }>("/auth", async (request, reply) => { + const { username, password } = request.query; + const customerHeader = request.headers["h-Custom"]; // 处理请求数据 - return `logged in!` - }) + return `logged in!`; + }); ``` 4. 执行 `npm run build` 和 `npm run start` 来构建并运行项目。 @@ -107,23 +116,27 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 6. 此外,泛型接口还可以用在路由层钩子方法中。在上面的路由内加上一个 `preValidation` 钩子: ```typescript server.get<{ - Querystring: IQuerystring, - Headers: IHeaders - }>('/auth', { - preValidation: (request, reply, done) => { - const { username, password } = request.query - done(username !== 'admin' ? new Error('Must be admin') : undefined) - } - // 或使用 async - // preValidation: async (request, reply) => { - // const { username, password } = request.query - // return username !== "admin" ? new Error("Must be admin") : undefined; - // } - }, async (request, reply) => { - const customerHeader = request.headers['h-Custom'] - // 处理请求数据 - return `logged in!` - }) + Querystring: IQuerystring; + Headers: IHeaders; + }>( + "/auth", + { + preValidation: (request, reply, done) => { + const { username, password } = request.query; + done(username !== "admin" ? new Error("Must be admin") : undefined); + }, + // 或使用 async + // preValidation: async (request, reply) => { + // const { username, password } = request.query + // return username !== "admin" ? new Error("Must be admin") : undefined; + // } + }, + async (request, reply) => { + const customerHeader = request.headers["h-Custom"]; + // 处理请求数据 + return `logged in!`; + }, + ); ``` 7. 构建运行之后,使用任何值不为 `admin` 的 `username` 查询字符串访问服务。你将收到一个 500 错误:`{"statusCode":500,"error":"Internal Server Error","message":"Must be admin"}` @@ -145,50 +158,50 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 1. 安装 `typebox`。 - ```bash - npm i @sinclair/typebox - ``` + ```bash + npm i @sinclair/typebox + ``` 2. 使用 `Type` 定义 schema,并通过 `Static` 创建相应的类型。 - ```typescript - import { Static, Type } from '@sinclair/typebox' + ```typescript + import { Static, Type } from "@sinclair/typebox"; - const User = Type.Object({ - name: Type.String(), - mail: Type.Optional(Type.String({ format: "email" })), - }); - type UserType = Static; - ``` + const User = Type.Object({ + name: Type.String(), + mail: Type.Optional(Type.String({ format: "email" })), + }); + type UserType = Static; + ``` 3. 在路由中使用定义好的类型与 schema。 - ```typescript - const app = fastify(); + ```typescript + const app = fastify(); - app.post<{ Body: UserType; Reply: UserType }>( - "/", - { - schema: { - body: User, - response: { - 200: User, - }, - }, - }, - (req, rep) => { - const { body: user } = req; - /* user 的类型如下: + app.post<{ Body: UserType; Reply: UserType }>( + "/", + { + schema: { + body: User, + response: { + 200: User, + }, + }, + }, + (req, rep) => { + const { body: user } = req; + /* user 的类型如下: * const user: StaticProperties<{ * name: TString; * mail: TOptional; * }> */ - //... - rep.status(200).send(user); - } - ); - ``` + //... + rep.status(200).send(user); + }, + ); + ``` #### Schemas in JSON Files @@ -239,70 +252,76 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 ``` `json2ts` 是囊括在 `json-schema-to-typescript` 中的命令行工具。`schemas` 是输入路径,`types` 则是输出路径。 + 5. 执行 `npm run compile-schemas`,在 `types` 文件夹下生成两个新文件。 6. 更新 `index.ts`: ```typescript - import fastify from 'fastify' + import fastify from "fastify"; // 导入 json schema - import QuerystringSchema from './schemas/querystring.json' - import HeadersSchema from './schemas/headers.json' + import QuerystringSchema from "./schemas/querystring.json"; + import HeadersSchema from "./schemas/headers.json"; // 导入生成的接口 - import { QuerystringSchema as QuerystringSchemaInterface } from './types/querystring' - import { HeadersSchema as HeadersSchemaInterface } from './types/headers' + import { QuerystringSchema as QuerystringSchemaInterface } from "./types/querystring"; + import { HeadersSchema as HeadersSchemaInterface } from "./types/headers"; - const server = fastify() + const server = fastify(); server.get<{ - Querystring: QuerystringSchemaInterface, - Headers: HeadersSchemaInterface - }>('/auth', { - schema: { - querystring: QuerystringSchema, - headers: HeadersSchema + Querystring: QuerystringSchemaInterface; + Headers: HeadersSchemaInterface; + }>( + "/auth", + { + schema: { + querystring: QuerystringSchema, + headers: HeadersSchema, + }, + preValidation: (request, reply, done) => { + const { username, password } = request.query; + done(username !== "admin" ? new Error("Must be admin") : undefined); + }, }, - preValidation: (request, reply, done) => { - const { username, password } = request.query - done(username !== 'admin' ? new Error('Must be admin') : undefined) - } - }, async (request, reply) => { - const customerHeader = request.headers['h-Custom'] - // 处理请求数据 - return `logged in!` - }) + async (request, reply) => { + const customerHeader = request.headers["h-Custom"]; + // 处理请求数据 + return `logged in!`; + }, + ); server.route<{ - Querystring: QuerystringSchemaInterface, - Headers: HeadersSchemaInterface + Querystring: QuerystringSchemaInterface; + Headers: HeadersSchemaInterface; }>({ - method: 'GET', - url: '/auth2', + method: "GET", + url: "/auth2", schema: { querystring: QuerystringSchema, - headers: HeadersSchema + headers: HeadersSchema, }, preHandler: (request, reply, done) => { - const { username, password } = request.query - const customerHeader = request.headers['h-Custom'] - done() + const { username, password } = request.query; + const customerHeader = request.headers["h-Custom"]; + done(); }, handler: (request, reply) => { - const { username, password } = request.query - const customerHeader = request.headers['h-Custom'] - reply.status(200).send({username}); - } - }) + const { username, password } = request.query; + const customerHeader = request.headers["h-Custom"]; + reply.status(200).send({ username }); + }, + }); server.listen(8080, (err, address) => { if (err) { - console.error(err) - process.exit(0) + console.error(err); + process.exit(0); } - console.log(`Server listening at ${address}`) - }) + console.log(`Server listening at ${address}`); + }); ``` + 要特别关注文件顶部的导入。虽然看上去有些多余,但你必须同时导入 schema 与生成的接口。 真棒!现在你就能同时运用 JSON Schema 与 TypeScript 的定义了。 @@ -317,17 +336,17 @@ Fastify 是用普通的 JavaScript 编写的,因此,类型定义的维护并 npm install -D json-schema-to-ts ``` -你可以像定义正常的对象一样定义 schema。但得注意要用 *const* 来定义,原因见该模块的文档。 +你可以像定义正常的对象一样定义 schema。但得注意要用 _const_ 来定义,原因见该模块的文档。 ```typescript const todo = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - description: { type: 'string' }, - done: { type: 'boolean' }, + name: { type: "string" }, + description: { type: "string" }, + done: { type: "boolean" }, }, - required: ['name'], + required: ["name"], } as const; ``` @@ -336,19 +355,18 @@ const todo = { ```typescript import { FromSchema } from "json-schema-to-ts"; fastify.post<{ Body: FromSchema }>( - '/todo', + "/todo", { schema: { body: todo, response: { 201: { - type: 'string', + type: "string", }, }, - } + }, }, async (request, reply): Promise => { - /* request.body 的类型如下: { @@ -359,9 +377,9 @@ fastify.post<{ Body: FromSchema }>( } */ - request.body.name // 不会抛出类型错误 - request.body.notthere // 会抛出类型错误 - + request.body.name; // 不会抛出类型错误 + request.body.notthere; // 会抛出类型错误 + reply.status(201).send(); }, ); @@ -402,44 +420,53 @@ fastify.post<{ Body: FromSchema }>( ``` 4. 新建 `index.ts` 文件,在这里编写插件代码。 5. 在 `index.ts` 中写入以下代码。 + ```typescript - import { FastifyPluginCallback, FastifyPluginAsync } from 'fastify' - import fp from 'fastify-plugin' + import { FastifyPluginCallback, FastifyPluginAsync } from "fastify"; + import fp from "fastify-plugin"; // 利用声明合并,将插件的属性加入合适的 fastify 接口。 - declare module 'fastify' { + declare module "fastify" { interface FastifyRequest { - myPluginProp: string + myPluginProp: string; } interface FastifyReply { - myPluginProp: number + myPluginProp: number; } } // 定义选项 export interface MyPluginOptions { - myPluginOption: string + myPluginOption: string; } // 使用回调函数定义插件 - const myPluginCallback: FastifyPluginCallback = (fastify, options, done) => { - fastify.decorateRequest('myPluginProp', 'super_secret_value') - fastify.decorateReply('myPluginProp', options.myPluginOption) + const myPluginCallback: FastifyPluginCallback = ( + fastify, + options, + done, + ) => { + fastify.decorateRequest("myPluginProp", "super_secret_value"); + fastify.decorateReply("myPluginProp", options.myPluginOption); - done() - } + done(); + }; // 使用 promise 定义插件 - const myPluginAsync: FastifyPluginAsync = async (fastify, options) => { - fastify.decorateRequest('myPluginProp', 'super_secret_value') - fastify.decorateReply('myPluginProp', options.myPluginOption) - } + const myPluginAsync: FastifyPluginAsync = async ( + fastify, + options, + ) => { + fastify.decorateRequest("myPluginProp", "super_secret_value"); + fastify.decorateReply("myPluginProp", options.myPluginOption); + }; // 使用 fastify-plugin 导出插件 - export default fp(myPluginCallback, '3.x') + export default fp(myPluginCallback, "3.x"); // 或者 // export default fp(myPluginAsync, '3.x') ``` + 6. 运行 `npm run build` 编译,生成 JavaScript 源文件以及类型定义文件。 7. 如此一来,插件便完工了。你可以[发布到 npm] 或直接本地使用。 > 并非将插件发布到 npm _才能_ 使用。你可以将其放在 Fastify 项目内,并像引用任意代码一样引用它!请确保声明文件在项目编译的范围内,以便能被 TypeScript 处理器使用。 @@ -462,28 +489,30 @@ fastify.post<{ Body: FromSchema }>( } ``` 4. 在 `index.js` 中加入以下代码: + ```javascript // 极力推荐使用 fastify-plugin 包装插件 - const fp = require('fastify-plugin') - - function myPlugin (instance, options, done) { + const fp = require("fastify-plugin"); + function myPlugin(instance, options, done) { // 用自定义函数 myPluginFunc 装饰 fastify 实例 - instance.decorate('myPluginFunc', (input) => { - return input.toUpperCase() - }) + instance.decorate("myPluginFunc", (input) => { + return input.toUpperCase(); + }); - done() + done(); } - + module.exports = fp(myPlugin, { - fastify: '3.x', - name: 'my-plugin' // 被 fastify-plugin 用来获取属性名 - }) + fastify: "3.x", + name: "my-plugin", // 被 fastify-plugin 用来获取属性名 + }); ``` + 5. 在 `index.d.ts` 中加入以下代码: + ```typescript - import { FastifyPlugin } from 'fastify' + import { FastifyPlugin } from "fastify"; interface PluginOptions { //... @@ -491,24 +520,24 @@ fastify.post<{ Body: FromSchema }>( // 你可以导出任意内容 // 在此,我们导出之前添加的装饰器 export interface myPluginFunc { - (input: string): string + (input: string): string; } // 利用声明合并将自定义属性加入 Fastify 的类型系统 - declare module 'fastify' { + declare module "fastify" { interface FastifyInstance { - myPluginFunc: myPluginFunc + myPluginFunc: myPluginFunc; } } // fastify-plugin 会自动添加具名导出,因此请确保加上该类型。 // 如果缺少 `module.exports.myPlugin`,变量名会通过 `options.name` 属性获取。 - export const myPlugin: FastifyPlugin + export const myPlugin: FastifyPlugin; // fastify-plugin 会自动在导出的插件上添加 `.default` 属性。详见下文。 - export default myPlugin + export default myPlugin; ``` -__注意__:v2.3.0 及以上版本的 [fastify-plugin](https://github.com/fastify/fastify-plugin) 会自动给导出的插件添加 `default` 属性以及具名导出。为了更好的开发体验,请确保在类型文件中加上了 `export default` 与 `export const myPlugin`。完整的例子可以查看 [fastify-swagger](https://github.com/fastify/fastify-swagger/blob/main/index.d.ts)。 +**注意**:v2.3.0 及以上版本的 [fastify-plugin](https://github.com/fastify/fastify-plugin) 会自动给导出的插件添加 `default` 属性以及具名导出。为了更好的开发体验,请确保在类型文件中加上了 `export default` 与 `export const myPlugin`。完整的例子可以查看 [fastify-swagger](https://github.com/fastify/fastify-swagger/blob/main/index.d.ts)。 这样一来,该插件便能被任意 TypeScript 项目使用了! @@ -521,6 +550,7 @@ Fastify 的插件系统允许开发者装饰 Fastify 以及 request/reply 的实 Fastify 插件使用声明合并来修改已有的 Fastify 类型接口 (详见上一个例子)。声明合并没有那么 _聪明_,只要插件的类型定义在 TypeScript 解释器的范围内,那么**不管**插件本身是否被使用,这些定义都会被包括。这是 TypeScript 的限制,目前无法规避。 尽管如此,还是有一些建议能帮助改善这种状况: + - 确保 [ESLint](https://eslint.org/docs/rules/no-unused-vars) 开启了 `no-unused-vars`,并且所有导入的插件都得到了加载。 - 通过诸如 [depcheck](https://www.npmjs.com/package/depcheck) 或 [npm-check](https://www.npmjs.com/package/npm-check) 的工具来验证所有插件都在项目中得到了使用。 @@ -533,8 +563,8 @@ Fastify 插件使用声明合并来修改已有的 Fastify 类型接口 (详见 ```js /** @type {import('fastify').FastifyPluginAsync<{ optionA: boolean, optionB: string }>} */ module.exports = async function (fastify, { optionA, optionB }) { - fastify.get('/look', () => 'at me'); -} + fastify.get("/look", () => "at me"); +}; ``` ## API 类型系统文档 @@ -544,6 +574,7 @@ module.exports = async function (fastify, { optionA, optionB }) { 所有 `http`、`https` 以及 `http2` 的类型来自 `@types/node`。 [泛型](#generics)的文档包括了其默认值以及约束值。更多关于 TypeScript 泛型的信息请阅读以下文章。 + - [泛型参数默认值](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-3.html#generic-parameter-defaults) - [泛型约束](https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints) @@ -552,53 +583,79 @@ module.exports = async function (fastify, { optionA, optionB }) { Fastify 的 API 都首先来自于 `fastify()` 方法。在 JavaScript 中,通过 `const fastify = require('fastify')` 来导入。在 TypeScript 中,建议的做法是使用 `import/from` 语法,这样类型能得到处理。有如下几种导入的方法。 1. `import fastify from 'fastify'` + - 类型得到了处理,但无法通过点标记 (dot notation) 访问 - 例子: - ```typescript - import fastify from 'fastify' - const f = fastify() - f.listen(8080, () => { console.log('running') }) + ```typescript + import fastify from "fastify"; + + const f = fastify(); + f.listen(8080, () => { + console.log("running"); + }); ``` + - 通过解构赋值访问类型 - ```typescript - import fastify, { FastifyInstance } from 'fastify' - const f: FastifyInstance = fastify() - f.listen(8080, () => { console.log('running') }) + ```typescript + import fastify, { FastifyInstance } from "fastify"; + + const f: FastifyInstance = fastify(); + f.listen(8080, () => { + console.log("running"); + }); ``` + - 主 API 方法也可以使用解构赋值 - ```typescript - import { fastify, FastifyInstance } from 'fastify' - const f: FastifyInstance = fastify() - f.listen(8080, () => { console.log('running') }) + ```typescript + import { fastify, FastifyInstance } from "fastify"; + + const f: FastifyInstance = fastify(); + f.listen(8080, () => { + console.log("running"); + }); ``` + 2. `import * as Fastify from 'fastify'` + - 类型得到了处理,并可通过点标记访问 - 主 API 方法要用稍微不同的语法调用 (见例子) - 例子: - ```typescript - import * as Fastify from 'fastify' - const f: Fastify.FastifyInstance = Fastify.fastify() - f.listen(8080, () => { console.log('running') }) + ```typescript + import * as Fastify from "fastify"; + + const f: Fastify.FastifyInstance = Fastify.fastify(); + f.listen(8080, () => { + console.log("running"); + }); ``` + 3. `const fastify = require('fastify')` + - 语法有效,也能正确地导入。然而并**不**支持类型 - 例子: - ```typescript - const fastify = require('fastify') - const f = fastify() - f.listen(8080, () => { console.log('running') }) + ```typescript + const fastify = require("fastify"); + + const f = fastify(); + f.listen(8080, () => { + console.log("running"); + }); ``` - - 支持解构,且能处理类型 - ```typescript - const { fastify } = require('fastify') - const f = fastify() - f.listen(8080, () => { console.log('running') }) + - 支持解构,且能处理类型 + + ```typescript + const { fastify } = require("fastify"); + + const f = fastify(); + f.listen(8080, () => { + console.log("running"); + }); ``` #### 泛型 @@ -608,6 +665,7 @@ Fastify 的 API 都首先来自于 `fastify()` 方法。在 JavaScript 中,通 多数定义依赖于 `@node/types` 中的 `http`、`https` 与 `http2` 模块。 ##### RawServer + 底层 Node.js server 的类型。 默认值:`http.Server` @@ -617,6 +675,7 @@ Fastify 的 API 都首先来自于 `fastify()` 方法。在 JavaScript 中,通 必要的泛型参数 (Enforces generic parameters):[`RawRequest`][RawRequestGeneric]、[`RawReply`][RawReplyGeneric] ##### RawRequest + 底层 Node.js request 的类型。 默认值:[`RawRequestDefaultExpression`][RawRequestDefaultExpression] @@ -626,22 +685,25 @@ Fastify 的 API 都首先来自于 `fastify()` 方法。在 JavaScript 中,通 被 [`RawServer`][RawServerGeneric] 约束。 ##### RawReply + 底层 Node.js response 的类型。 默认值:[`RawReplyDefaultExpression`][RawReplyDefaultExpression] 约束值:`http.ServerResponse`、`http2.Http2ServerResponse` -被 [`RawServer`][RawServerGeneric] 约束。 +被 [`RawServer`][RawServerGeneric] 约束。 ##### Logger + Fastify 日志工具。 默认值:[`FastifyLoggerOptions`][FastifyLoggerOptions] -被 [`RawServer`][RawServerGeneric] 约束。 +被 [`RawServer`][RawServerGeneric] 约束。 ##### RawBody + 为 content-type-parser 方法提供的泛型参数。 约束值:`string | Buffer` @@ -651,6 +713,7 @@ Fastify 日志工具。 #### Fastify ##### fastify<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [Logger][LoggerGeneric]>(opts?: [FastifyServerOptions][FastifyServerOptions]): [FastifyInstance][FastifyInstance] + [源码](https://github.com/fastify/fastify/blob/main/fastify.d.ts#L19) Fastify 首要的 API 方法。默认情况下创建一个 HTTP 服务器。通过可辨识联合 (discriminant unions) 及重载的方法 (overload methods),类型系统能自动地根据传递给该方法的选项 (详见下文例子),推断出服务器的类型 (http、https 或 http2)。同时,可拓展的泛型类型系统允许用户拓展底层的 Node.js Server、Request 和 Reply 对象。此外,自定义日志类型则可以运用 `Logger` 泛型。详见下文的例子和泛型分类说明。 @@ -658,43 +721,47 @@ Fastify 首要的 API 方法。默认情况下创建一个 HTTP 服务器。通 ###### 例子 1:标准的 HTTP 服务器 无需指明 `Server` 的具体类型,因为默认值就是 HTTP 服务器。 -```typescript -import fastify from 'fastify' -const server = fastify() +```typescript +import fastify from "fastify"; + +const server = fastify(); ``` + 回顾“从例子中学习”的[起步](#getting-started)一节的示例来获取更详细的内容。 ###### 例子 2:HTTPS 服务器 1. 从 `@types/node` 与 `fastify` 导入模块。 ```typescript - import fs from 'fs' - import path from 'path' - import fastify from 'fastify' + import fs from "fs"; + import path from "path"; + import fastify from "fastify"; ``` 2. 按照官方 [Node.js https 服务器指南](https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTPS-server/)的步骤,创建 `key.pem` 与 `cert.pem` 文件。 3. 实例化一个 Fastify https 服务器,并添加一个路由: + ```typescript const server = fastify({ https: { - key: fs.readFileSync(path.join(__dirname, 'key.pem')), - cert: fs.readFileSync(path.join(__dirname, 'cert.pem')) - } - }) + key: fs.readFileSync(path.join(__dirname, "key.pem")), + cert: fs.readFileSync(path.join(__dirname, "cert.pem")), + }, + }); - server.get('/', async function (request, reply) { - return { hello: 'world' } - }) + server.get("/", async function (request, reply) { + return { hello: "world" }; + }); server.listen(8080, (err, address) => { if (err) { - console.error(err) - process.exit(0) + console.error(err); + process.exit(0); } - console.log(`Server listening at ${address}`) - }) + console.log(`Server listening at ${address}`); + }); ``` + 4. 构建并运行!执行 `curl -k https://localhost:8080` 来测试服务。 ###### 例子 3:HTTP2 服务器 @@ -702,11 +769,11 @@ const server = fastify() HTTP2 服务器有两种类型,非安全与安全。两种类型都需要在 `options` 对象中设置 `http2` 属性的值为 `true`。设置 `https` 属性会创建一个安全的 http2 服务器;忽略该属性则创建非安全的服务器。 ```typescript -const insecureServer = fastify({ http2: true }) +const insecureServer = fastify({ http2: true }); const secureServer = fastify({ http2: true, - https: {} // 使用 https 服务的 `key.pem` 和 `cert.pem` 文件 -}) + https: {}, // 使用 https 服务的 `key.pem` 和 `cert.pem` 文件 +}); ``` 更多细节详见 Fastify 的 [HTTP2](HTTP2.md) 文档。 @@ -714,20 +781,21 @@ const secureServer = fastify({ ###### 例子 4:拓展 HTTP 服务器 你不仅可以指定服务器的类型,还可以指定请求与响应的类型,即指定特殊的属性、方法等!在服务器实例化之时指定类型,则之后的实例都可应用自定义的类型。 + ```typescript -import fastify from 'fastify' -import http from 'http' +import fastify from "fastify"; +import http from "http"; interface customRequest extends http.IncomingMessage { - mySpecialProp: string + mySpecialProp: string; } -const server = fastify() +const server = fastify(); -server.get('/', async (request, reply) => { - const someValue = request.raw.mySpecialProp // 由于 `customRequest` 接口的存在,TypeScript 能知道这是一个字符串 - return someValue.toUpperCase() -}) +server.get("/", async (request, reply) => { + const someValue = request.raw.mySpecialProp; // 由于 `customRequest` 接口的存在,TypeScript 能知道这是一个字符串 + return someValue.toUpperCase(); +}); ``` ###### 例子 5:指定日志类型 @@ -737,31 +805,33 @@ Fastify 使用 [Pino](https://getpino.io/#/) 作为日志工具。其中一些 要使用 Pino 的外部实例,请将 `@types/pino` 添加到 devDependencies 中,并把实例传给 `logger` 字段: ```typescript -import fastify from 'fastify' -import pino from 'pino' +import fastify from "fastify"; +import pino from "pino"; const server = fastify({ logger: pino({ - level: 'info', - redact: ['x-userinfo'], - messageKey: 'message' - }) -}) + level: "info", + redact: ["x-userinfo"], + messageKey: "message", + }), +}); -server.get('/', async (request, reply) => { - server.log.info('log message') - return 'another message' -}) +server.get("/", async (request, reply) => { + server.log.info("log message"); + return "another message"; +}); ``` --- ##### fastify.HTTPMethods + [源码](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L8) `'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT' | 'OPTIONS'` 的联合类型 (Union type) ##### fastify.RawServerBase + [源码](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L13) 依赖于 `@types/node` 的模块 `http`、`https`、`http2` @@ -769,6 +839,7 @@ server.get('/', async (request, reply) => { `http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer` 的联合类型 ##### fastify.RawServerDefault + [源码](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L18) 依赖于 `@types/node` 的模块 `http` @@ -800,6 +871,7 @@ Fastify 服务器实例化时,调用 [`fastify()`][Fastify] 方法使用到的 #### Request ##### fastify.FastifyRequest<[RequestGeneric][FastifyRequestGenericInterface], [RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric]> + [源码](https://github.com/fastify/fastify/blob/main/types/request.d.ts#L15) 该接口包含了 Fastify 请求对象的属性。这些属性无视请求类型 (http 或 http2),也无关路由层级。因此在 GET 请求中访问 `request.body` 并不会抛错 (假如 GET 有 body 😉)。 @@ -809,60 +881,67 @@ Fastify 服务器实例化时,调用 [`fastify()`][Fastify] 方法使用到的 在 [`FastifyRequest`][FastifyRequest] 里有基本的范例。更详细的例子请见“从例子中学习”的[插件](#plugins)一节。 ###### 例子 + ```typescript -import fastify from 'fastify' +import fastify from "fastify"; -const server = fastify() +const server = fastify(); -server.decorateRequest('someProp', 'hello!') +server.decorateRequest("someProp", "hello!"); -server.get('/', async (request, reply) => { - const { someProp } = request // 需要通过声明合并将该属性添加到 request 接口上 - return someProp -}) +server.get("/", async (request, reply) => { + const { someProp } = request; // 需要通过声明合并将该属性添加到 request 接口上 + return someProp; +}); // 以下声明必须在 typescript 解释器的作用域内 -declare module 'fastify' { - interface FastifyRequest { // 引用的是接口而非类型 - someProp: string +declare module "fastify" { + interface FastifyRequest { + // 引用的是接口而非类型 + someProp: string; } } // 你也可以如此定义 request 的类型 type CustomRequest = FastifyRequest<{ Body: { test: boolean }; -}> +}>; -server.get('/typedRequest', async (request: CustomRequest, reply: FastifyReply) => { - return request.body.test -}) +server.get( + "/typedRequest", + async (request: CustomRequest, reply: FastifyReply) => { + return request.body.test; + }, +); ``` ##### fastify.RequestGenericInterface + [源码](https://github.com/fastify/fastify/blob/main/types/request.d.ts#L4) Fastify 的请求对象有四个动态属性:`body`、`params`、`query` 以及 `headers`,它们对应的类型可以通过该接口设定。这是具名属性接口,允许开发者忽略他们不想指定的类型。所有忽略的属性默认为 `unknown`。四个属性名为:`Body`、`Querystring`、`Params` 和 `Headers`。 ```typescript -import fastify, { RequestGenericInterface } from 'fastify' +import fastify, { RequestGenericInterface } from "fastify"; -const server = fastify() +const server = fastify(); interface requestGeneric extends RequestGenericInterface { Querystring: { - name: string - } + name: string; + }; } -server.get('/', async (request, reply) => { - const { name } = request.query // 此时 query 属性上有了 name - return name.toUpperCase() -}) +server.get("/", async (request, reply) => { + const { name } = request.query; // 此时 query 属性上有了 name + return name.toUpperCase(); +}); ``` 在“从例子中学习”的 [JSON Schema](#jsonschema) 一节中,你能找到更具体的范例。 ##### fastify.RawRequestDefaultExpression\<[RawServer][RawServerGeneric]\> + [源码](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L23) 依赖于 `@types/node` 的模块 `http`、`https`、`http2` @@ -872,12 +951,12 @@ server.get('/', async (request, reply) => { 如果 `RawServer` 的类型为 `http.Server` 或 `https.Server`,那么该表达式返回 `http.IncomingMessage`,否则返回 `http2.Http2ServerRequest`。 ```typescript -import http from 'http' -import http2 from 'http2' -import { RawRequestDefaultExpression } from 'fastify' +import http from "http"; +import http2 from "http2"; +import { RawRequestDefaultExpression } from "fastify"; -RawRequestDefaultExpression // -> http.IncomingMessage -RawRequestDefaultExpression // -> http2.Http2ServerRequest +RawRequestDefaultExpression; // -> http.IncomingMessage +RawRequestDefaultExpression; // -> http2.Http2ServerRequest ``` --- @@ -885,6 +964,7 @@ RawRequestDefaultExpression // -> http2.Http2ServerRequest #### Reply ##### fastify.FastifyReply<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]> + [源码](https://github.com/fastify/fastify/blob/main/types/reply.d.ts#L32) 该接口包含了 Fastify 添加到 Node.js 标准的 reply 对象上的属性。这些属性和 reply 对象的类型 (http 或 http2) 无关。 @@ -894,27 +974,30 @@ RawRequestDefaultExpression // -> http2.Http2ServerRequest 在 [`FastifyReply`][FastifyReply] 里有基本的范例。更详细的例子请见“从例子中学习”的[插件](#plugins)一节。 ###### 例子 + ```typescript -import fastify from 'fastify' +import fastify from "fastify"; -const server = fastify() +const server = fastify(); -server.decorateReply('someProp', 'world') +server.decorateReply("someProp", "world"); -server.get('/', async (request, reply) => { - const { someProp } = reply //需要通过声明合并将该属性添加到 reply 接口上 - return someProp -}) +server.get("/", async (request, reply) => { + const { someProp } = reply; //需要通过声明合并将该属性添加到 reply 接口上 + return someProp; +}); // 以下声明必须在 typescript 解释器的作用域内 -declare module 'fastify' { - interface FastifyReply { // 引用的是接口而非类型 - someProp: string +declare module "fastify" { + interface FastifyReply { + // 引用的是接口而非类型 + someProp: string; } } ``` ##### fastify.RawReplyDefaultExpression<[RawServer][RawServerGeneric]> + [源码](https://github.com/fastify/fastify/blob/main/types/utils.d.ts#L27) 依赖于 `@types/node` 的模块 `http`、`https`、`http2` @@ -924,12 +1007,12 @@ declare module 'fastify' { 如果 `RawServer` 的类型为 `http.Server` 或 `https.Server`,那么该表达式返回 `http.ServerResponse`,否则返回 `http2.Http2ServerResponse`。 ```typescript -import http from 'http' -import http2 from 'http2' -import { RawReplyDefaultExpression } from 'fastify' +import http from "http"; +import http2 from "http2"; +import { RawReplyDefaultExpression } from "fastify"; -RawReplyDefaultExpression // -> http.ServerResponse -RawReplyDefaultExpression // -> http2.Http2ServerResponse +RawReplyDefaultExpression; // -> http.ServerResponse +RawReplyDefaultExpression; // -> http2.Http2ServerResponse ``` --- @@ -941,16 +1024,19 @@ RawReplyDefaultExpression // -> http2.Http2ServerResponse 创建插件时,我们推荐使用 `fastify-plugin`。在“从例子中学习”的[插件](#plugins)一节中有使用 TypeScript 创建插件的指南。 ##### fastify.FastifyPluginCallback<[Options][FastifyPluginOptions]> + [源码](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L9) [`fastify.register()`][FastifyRegister] 使用的接口方法定义。 ##### fastify.FastifyPluginAsync<[Options][FastifyPluginOptions]> + [源码](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L20) [`fastify.register()`][FastifyRegister] 使用的接口方法定义。 ##### fastify.FastifyPlugin<[Options][FastifyPluginOptions]> + [源码](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L29) [`fastify.register()`][FastifyRegister] 使用的接口方法定义。 @@ -958,6 +1044,7 @@ RawReplyDefaultExpression // -> http2.Http2ServerResponse 通用的 `FastifyPlugin` 已不推荐使用,取而代之的是上述 `FastifyPluginCallback` 以及 `FastifyPluginAsync`。这是因为 `FastifyPlugin` 无法正确推断出异步函数的类型。 ##### fastify.FastifyPluginOptions + [源码](https://github.com/fastify/fastify/blob/main/types/plugin.d.ts#L31) 一个用于约束 [`fastify.register()`][FastifyRegister] 的 `options` 参数为对象类型的宽松类型对象 (loosely typed object)。在创建插件时,将插件的选项定义为此接口 (`interface MyPluginOptions extends FastifyPluginOptions`),传递给 register 方法。 @@ -967,10 +1054,15 @@ RawReplyDefaultExpression // -> http2.Http2ServerResponse #### Register ##### fastify.FastifyRegister(plugin: [FastifyPluginCallback][FastifyPluginCallback], opts: [FastifyRegisterOptions][FastifyRegisterOptions]) + [源码](https://github.com/fastify/fastify/blob/main/types/register.d.ts#L9) + ##### fastify.FastifyRegister(plugin: [FastifyPluginAsync][FastifyPluginAsync], opts: [FastifyRegisterOptions][FastifyRegisterOptions]) + [源码](https://github.com/fastify/fastify/blob/main/types/register.d.ts#L9) + ##### fastify.FastifyRegister(plugin: [FastifyPlugin][FastifyPlugin], opts: [FastifyRegisterOptions][FastifyRegisterOptions]) + [源码](https://github.com/fastify/fastify/blob/main/types/register.d.ts#L9) 指定 [`fastify.register()`](Server.md#register) 类型的类型接口,返回一个拥有默认值为 [FastifyPluginOptions][FastifyPluginOptions] 的 `Options` 泛型的函数签名。当调用此函数时,根据 FastifyPlugin 参数能推断出该泛型,因此不必特别指定。options 参数是插件选项以及 `prefix: string` 和 `logLevel` ([LogLevel][LogLevel]) 两个属性的交叉类型。 @@ -978,20 +1070,21 @@ RawReplyDefaultExpression // -> http2.Http2ServerResponse 以下例子展示了 options 的推断: ```typescript -const server = fastify() +const server = fastify(); const plugin: FastifyPlugin<{ option1: string; option2: boolean; -}> = function (instance, opts, done) { } +}> = function (instance, opts, done) {}; -fastify().register(plugin, {}) // 错误 - options 对象缺失了必要的属性 -fastify().register(plugin, { option1: '', option2: true }) // OK - options 对象包括了必要的属性 +fastify().register(plugin, {}); // 错误 - options 对象缺失了必要的属性 +fastify().register(plugin, { option1: "", option2: true }); // OK - options 对象包括了必要的属性 ``` 在“从例子中学习”的[插件](#plugins)一节中有使用 TypeScript 创建插件的详细示例。 ##### fastify.FastifyRegisterOptions + [源码](https://github.com/fastify/fastify/blob/main/types/register.d.ts#L16) 该类型是 `Options` 泛型以及包括 `prefix: string` 和 `logLevel` ([LogLevel][LogLevel]) 两个可选属性的未导出接口 `RegisterOptions` 的交叉类型。也可以被指定为返回前述交叉类型的函数。 @@ -1051,6 +1144,7 @@ Fastify 的其中一条核心原则便是强大的路由。本节中多数的类 [源码](https://github.com/fastify/fastify/blob/main/types/route.d.ts#L78) 拓展了 RouteShorthandOptions 的接口,并添加以下三个必填属性: + 1. `method` 单个或一组 [HTTP 方法][HTTPMethods]。 2. `url` 路由路径字符串。 3. `handler` 路由控制函数,详见 [RouteHandlerMethod][]。 @@ -1153,7 +1247,7 @@ FastifyError 是自定义的错误对象,包括了状态码及校验结果。 `preValidation` 是第三个钩子,前一个为 `preParsing`,下一个为 `preHandler`。 -注意:在 `preValidation` 钩子中,request.body 永远为 null,因为此时 body 尚未解析 (解析发生在 `preHandler` 钩子之前)。 +注意:在 `preValidation` 钩子中,request.body 永远为 null,因为此时 body 尚未解析 (解析发生在 `preHandler` 钩子之前)。 ##### fastify.preHandlerHookHandler<[RawServer][RawServerGeneric], [RawRequest][RawRequestGeneric], [RawReply][RawReplyGeneric], [RequestGeneric][FastifyRequestGenericInterface], [ContextConfig][ContextConfigGeneric]>(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], done: (err?: [FastifyError][FastifyError]) => void): Promise\ | void @@ -1167,7 +1261,7 @@ FastifyError 是自定义的错误对象,包括了状态码及校验结果。 `preSerialization` 是第五个钩子,前一个为 `preHandler`,下一个为 `onSend`。 -注:当 payload 为 string、Buffer、stream 或 null 时,该钩子不会执行。 +注:当 payload 为 string、Buffer、stream 或 null 时,该钩子不会执行。 ##### fastify.onSendHookHandler(request: [FastifyRequest][FastifyRequest], reply: [FastifyReply][FastifyReply], payload: OnSendPayload, done: (err: [FastifyError][FastifyError] | null, res?: unknown) => void): Promise\ | void @@ -1247,4 +1341,4 @@ FastifyError 是自定义的错误对象,包括了状态码及校验结果。 [FastifyRegisterOptions]: #fastifyfastifytregisteroptions [LogLevel]: #fastifyloglevel [FastifyError]: #fastifyfastifyerror -[RouteOptions]: #fastifyrouteoptionsrawserver-rawrequest-rawreply-requestgeneric-contextconfig \ No newline at end of file +[RouteOptions]: #fastifyrouteoptionsrawserver-rawrequest-rawreply-requestgeneric-contextconfig diff --git a/doc/fastify-docs/docs/Validation-and-Serialization.md b/doc/fastify-docs/docs/Validation-and-Serialization.md index eb88dd3..025eb28 100644 --- a/doc/fastify-docs/docs/Validation-and-Serialization.md +++ b/doc/fastify-docs/docs/Validation-and-Serialization.md @@ -1,79 +1,85 @@

Fastify

## 验证和序列化 + Fastify 使用基于 schema 的途径,从本质上将 schema 编译成了高性能的函数,来实现路由的验证与输出的序列化。我们推荐使用 [JSON Schema](https://json-schema.org/),虽然这并非必要。 -> ## ⚠ 安全须知 +> ## ⚠ 安全须知 +> > 应当将 schema 的定义写入代码。 > 因为不管是验证还是序列化,都会使用 `new Function()` 来动态生成代码并执行。 > 所以,用户提供的 schema 是不安全的。 > 更多内容,请看 [Ajv](https://npm.im/ajv) 与 [fast-json-stringify](https://npm.im/fast-json-stringify)。 ### 核心观念 + 验证与序列化的任务分别由两个可定制的工具完成: + - [Ajv 6](https://www.npmjs.com/package/ajv/v/6.12.6) 用于验证请求。 - [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) 用于序列化响应的 body。 这些工具相互独立,但共享通过 `.addSchema(schema)` 方法添加到 Fastify 实例上的 JSON schema。 + #### 添加共用 schema (shared schema) + 得益于 `addSchema` API,你能向 Fastify 实例添加多个 schema,并在程序的不同部分复用它们。 像往常一样,该 API 是封装好的。 共用 schema 可以通过 JSON Schema 的 [**`$ref`**](https://tools.ietf.org/html/draft-handrews-json-schema-01#section-8) 关键字复用。 以下是引用方法的 _总结_: -+ `myField: { $ref: '#foo'}` 将在当前 schema 内搜索 `$id: '#foo'` 字段。 -+ `myField: { $ref: '#/definitions/foo'}` 将在当前 schema 内搜索 `definitions.foo` 字段。 -+ `myField: { $ref: 'http://url.com/sh.json#'}` 会搜索含 `$id: 'http://url.com/sh.json'` 的共用 schema。 -+ `myField: { $ref: 'http://url.com/sh.json#/definitions/foo'}` 会搜索含 `$id: 'http://url.com/sh.json'` 的共用 schema,并使用其 `definitions.foo` 字段。 -+ `myField: { $ref: 'http://url.com/sh.json#foo'}` 会搜索含 `$id: 'http://url.com/sh.json'` 的共用 schema,并使用其内部带 `$id: '#foo'` 的对象。 - +- `myField: { $ref: '#foo'}` 将在当前 schema 内搜索 `$id: '#foo'` 字段。 +- `myField: { $ref: '#/definitions/foo'}` 将在当前 schema 内搜索 `definitions.foo` 字段。 +- `myField: { $ref: 'http://url.com/sh.json#'}` 会搜索含 `$id: 'http://url.com/sh.json'` 的共用 schema。 +- `myField: { $ref: 'http://url.com/sh.json#/definitions/foo'}` 会搜索含 `$id: 'http://url.com/sh.json'` 的共用 schema,并使用其 `definitions.foo` 字段。 +- `myField: { $ref: 'http://url.com/sh.json#foo'}` 会搜索含 `$id: 'http://url.com/sh.json'` 的共用 schema,并使用其内部带 `$id: '#foo'` 的对象。 **简单用法:** ```js fastify.addSchema({ - $id: 'http://example.com/', - type: 'object', + $id: "http://example.com/", + type: "object", properties: { - hello: { type: 'string' } - } -}) + hello: { type: "string" }, + }, +}); -fastify.post('/', { - handler () {}, +fastify.post("/", { + handler() {}, schema: { body: { - type: 'array', - items: { $ref: 'http://example.com#/properties/hello' } - } - } -}) + type: "array", + items: { $ref: "http://example.com#/properties/hello" }, + }, + }, +}); ``` **`$ref` 作为根引用 (root reference):** ```js fastify.addSchema({ - $id: 'commonSchema', - type: 'object', + $id: "commonSchema", + type: "object", properties: { - hello: { type: 'string' } - } -}) + hello: { type: "string" }, + }, +}); -fastify.post('/', { - handler () {}, +fastify.post("/", { + handler() {}, schema: { - body: { $ref: 'commonSchema#' }, - headers: { $ref: 'commonSchema#' } - } -}) + body: { $ref: "commonSchema#" }, + headers: { $ref: "commonSchema#" }, + }, +}); ``` + #### 获取共用 schema 当自定义验证器或序列化器的时候,Fastify 不再能控制它们,此时 `.addSchema` 方法失去了作用。 @@ -81,43 +87,51 @@ fastify.post('/', { ```js fastify.addSchema({ - $id: 'schemaId', - type: 'object', + $id: "schemaId", + type: "object", properties: { - hello: { type: 'string' } - } -}) + hello: { type: "string" }, + }, +}); -const mySchemas = fastify.getSchemas() -const mySchema = fastify.getSchema('schemaId') +const mySchemas = fastify.getSchemas(); +const mySchema = fastify.getSchema("schemaId"); ``` `getSchemas` 方法也是封装好的,返回的是指定作用域中可用的共用 schema: ```js -fastify.addSchema({ $id: 'one', my: 'hello' }) +fastify.addSchema({ $id: "one", my: "hello" }); // 只返回 schema `one` -fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) }) +fastify.get("/", (request, reply) => { + reply.send(fastify.getSchemas()); +}); fastify.register((instance, opts, done) => { - instance.addSchema({ $id: 'two', my: 'ciao' }) + instance.addSchema({ $id: "two", my: "ciao" }); // 会返回 schema `one` 与 `two` - instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) }) + instance.get("/sub", (request, reply) => { + reply.send(instance.getSchemas()); + }); instance.register((subinstance, opts, done) => { - subinstance.addSchema({ $id: 'three', my: 'hola' }) + subinstance.addSchema({ $id: "three", my: "hola" }); // 会返回 schema `one`、`two` 和 `three` - subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) }) - done() - }) - done() -}) + subinstance.get("/deep", (request, reply) => { + reply.send(subinstance.getSchemas()); + }); + done(); + }); + done(); +}); ``` ### 验证 + 路由的验证是依赖 [Ajv 6](https://www.npmjs.com/package/ajv/v/6.12.6) 实现的。这是一个高性能的 JSON schema 校验工具。验证输入十分简单,只需将字段加入路由的 schema 中即可! 支持的验证类型如下: + - `body`:当请求方法为 POST、PUT 或 PATCH 时,验证 body。 - `querystring` 或 `query`:验证 querystring。 - `params`:验证路由参数。 @@ -128,71 +142,72 @@ fastify.register((instance, opts, done) => { > ℹ 想要使用最新版 Ajv (Ajv 8) 的话,请查阅 [`schemaController`](Server.md#schema-controller) 一节,里边描述了比自定义校验器更简单的方法。 示例: + ```js const bodyJsonSchema = { - type: 'object', - required: ['requiredKey'], + type: "object", + required: ["requiredKey"], properties: { - someKey: { type: 'string' }, - someOtherKey: { type: 'number' }, + someKey: { type: "string" }, + someOtherKey: { type: "number" }, requiredKey: { - type: 'array', + type: "array", maxItems: 3, - items: { type: 'integer' } + items: { type: "integer" }, }, - nullableKey: { type: ['number', 'null'] }, // 或 { type: 'number', nullable: true } - multipleTypesKey: { type: ['boolean', 'number'] }, + nullableKey: { type: ["number", "null"] }, // 或 { type: 'number', nullable: true } + multipleTypesKey: { type: ["boolean", "number"] }, multipleRestrictedTypesKey: { oneOf: [ - { type: 'string', maxLength: 5 }, - { type: 'number', minimum: 10 } - ] + { type: "string", maxLength: 5 }, + { type: "number", minimum: 10 }, + ], }, enumKey: { - type: 'string', - enum: ['John', 'Foo'] + type: "string", + enum: ["John", "Foo"], }, notTypeKey: { - not: { type: 'array' } - } - } -} + not: { type: "array" }, + }, + }, +}; const queryStringJsonSchema = { - type: 'object', + type: "object", properties: { - name: { type: 'string' }, - excitement: { type: 'integer' } - } -} + name: { type: "string" }, + excitement: { type: "integer" }, + }, +}; const paramsJsonSchema = { - type: 'object', + type: "object", properties: { - par1: { type: 'string' }, - par2: { type: 'number' } - } -} + par1: { type: "string" }, + par2: { type: "number" }, + }, +}; const headersJsonSchema = { - type: 'object', + type: "object", properties: { - 'x-foo': { type: 'string' } + "x-foo": { type: "string" }, }, - required: ['x-foo'] -} + required: ["x-foo"], +}; const schema = { body: bodyJsonSchema, querystring: queryStringJsonSchema, params: paramsJsonSchema, - headers: headersJsonSchema -} + headers: headersJsonSchema, +}; -fastify.post('/the/url', { schema }, handler) +fastify.post("/the/url", { schema }, handler); ``` -*请注意,为了通过校验,并在后续过程中使用正确类型的数据,Ajv 会尝试将数据[隐式转换](https://github.com/epoberezkin/ajv#coercing-data-types)为 schema 中 `type` 属性指明的类型。* +_请注意,为了通过校验,并在后续过程中使用正确类型的数据,Ajv 会尝试将数据[隐式转换](https://github.com/epoberezkin/ajv#coercing-data-types)为 schema 中 `type` 属性指明的类型。_ Fastify 提供给 Ajv 的默认配置并不支持隐式转换 querystring 中的数组参数。但是,Fastify 允许你通过设置 Ajv 实例的 [`customOptions`](Server.md#ajv) 选项为 'array',来将参数转换为数组。举例如下: @@ -200,24 +215,24 @@ Fastify 提供给 Ajv 的默认配置并不支持隐式转换 querystring 中的 const opts = { schema: { querystring: { - type: 'object', + type: "object", properties: { ids: { - type: 'array', - default: [] + type: "array", + default: [], }, }, - } - } -} + }, + }, +}; -fastify.get('/', opts, (request, reply) => { - reply.send({ params: request.query }) -}) +fastify.get("/", opts, (request, reply) => { + reply.send({ params: request.query }); +}); fastify.listen(3000, (err) => { - if (err) throw err -}) + if (err) throw err; +}); ``` 默认情况下,该处的请求将返回 `400`: @@ -234,13 +249,13 @@ curl -X GET "http://localhost:3000/?ids=1 const ajv = new Ajv({ removeAdditional: true, useDefaults: true, - coerceTypes: 'array', // 看这里 - allErrors: true -}) + coerceTypes: "array", // 看这里 + allErrors: true, +}); fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { - return ajv.compile(schema) -}) + return ajv.compile(schema); +}); ``` ```sh @@ -258,40 +273,41 @@ const schemaCompilers = { body: new Ajv({ removeAdditional: false, coerceTypes: false, - allErrors: true + allErrors: true, }), params: new Ajv({ removeAdditional: false, coerceTypes: true, - allErrors: true + allErrors: true, }), querystring: new Ajv({ removeAdditional: false, coerceTypes: true, - allErrors: true + allErrors: true, }), headers: new Ajv({ removeAdditional: false, coerceTypes: true, - allErrors: true - }) -} + allErrors: true, + }), +}; -server.setValidatorCompiler(req => { - if (!req.httpPart) { - throw new Error('Missing httpPart') - } - const compiler = schemaCompilers[req.httpPart] - if (!compiler) { - throw new Error(`Missing compiler for ${req.httpPart}`) - } - return compiler.compile(req.schema) -}) +server.setValidatorCompiler((req) => { + if (!req.httpPart) { + throw new Error("Missing httpPart"); + } + const compiler = schemaCompilers[req.httpPart]; + if (!compiler) { + throw new Error(`Missing compiler for ${req.httpPart}`); + } + return compiler.compile(req.schema); +}); ``` 更多信息请看[这里](https://ajv.js.org/coercion.html)。 + #### Ajv 插件 你可以给默认的 `ajv` 实例提供一组插件。这些插件必须**兼容 Ajv 6**。 @@ -299,62 +315,65 @@ server.setValidatorCompiler(req => { > 插件格式参见 [`ajv 选项`](Server.md#ajv) ```js -const fastify = require('fastify')({ +const fastify = require("fastify")({ ajv: { - plugins: [ - require('ajv-merge-patch') - ] - } -}) + plugins: [require("ajv-merge-patch")], + }, +}); -fastify.post('/', { - handler (req, reply) { reply.send({ ok: 1 }) }, +fastify.post("/", { + handler(req, reply) { + reply.send({ ok: 1 }); + }, schema: { body: { $patch: { source: { - type: 'object', + type: "object", properties: { q: { - type: 'string' - } - } + type: "string", + }, + }, }, with: [ { - op: 'add', - path: '/properties/q', - value: { type: 'number' } - } - ] - } - } - } -}) + op: "add", + path: "/properties/q", + value: { type: "number" }, + }, + ], + }, + }, + }, +}); -fastify.post('/foo', { - handler (req, reply) { reply.send({ ok: 1 }) }, +fastify.post("/foo", { + handler(req, reply) { + reply.send({ ok: 1 }); + }, schema: { body: { $merge: { source: { - type: 'object', + type: "object", properties: { q: { - type: 'string' - } - } + type: "string", + }, + }, }, with: { - required: ['q'] - } - } - } - } -}) + required: ["q"], + }, + }, + }, + }, +}); ``` + #### 验证生成器 `validatorCompiler` 返回一个用于验证 body、URL、路由参数、header 以及 querystring 的函数。默认返回一个实现了 [ajv](https://ajv.js.org/) 验证接口的函数。Fastify 内在地使用该函数以加速验证。 @@ -375,8 +394,8 @@ Fastify 使用的 [ajv 基本配置](https://github.com/epoberezkin/ajv#options- 假如你想改变或增加额外的选项,你需要创建一个自定义的实例,并覆盖已存在的实例: ```js -const fastify = require('fastify')() -const Ajv = require('ajv') +const fastify = require("fastify")(); +const Ajv = require("ajv"); const ajv = new Ajv({ // fastify 使用的默认参数(如果需要) removeAdditional: true, @@ -385,64 +404,78 @@ const ajv = new Ajv({ nullable: true, // 任意其他参数 // ... -}) +}); fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => { - return ajv.compile(schema) -}) + return ajv.compile(schema); +}); ``` _**注意:** 如果你使用自定义校验工具的实例(即使是 Ajv),你应当向该实例而非 Fastify 添加 schema,因为在这种情况下,Fastify 默认的校验工具不再使用,而 `addSchema` 方法也不清楚你在使用什么工具进行校验。_ + ##### 使用其他验证工具 -通过 `setValidatorCompiler` 函数,你可以轻松地将 `ajv` 替换为几乎任意的 Javascript 验证工具 (如 [joi](https://github.com/hapijs/joi/)、[yup](https://github.com/jquense/yup/) 等),或自定义它们。 +通过 `setValidatorCompiler` 函数,你可以轻松地将 `ajv` 替换为几乎任意的 Javascript 验证工具 (如 [joi](https://github.com/hapijs/joi/)、[yup](https://github.com/jquense/yup/) 等),或自定义它们。 ```js -const Joi = require('@hapi/joi') +const Joi = require("@hapi/joi"); -fastify.post('/the/url', { - schema: { - body: Joi.object().keys({ - hello: Joi.string().required() - }).required() +fastify.post( + "/the/url", + { + schema: { + body: Joi.object() + .keys({ + hello: Joi.string().required(), + }) + .required(), + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return (data) => schema.validate(data); + }, }, - validatorCompiler: ({ schema, method, url, httpPart }) => { - return data => schema.validate(data) - } -}, handler) + handler, +); ``` ```js -const yup = require('yup') +const yup = require("yup"); // 等同于前文 ajv 基本配置的 yup 的配置 const yupOptions = { strict: false, abortEarly: false, // 返回所有错误(译注:为 true 时出现首个错误后即返回) stripUnknown: true, // 移除额外属性 - recursive: true -} -fastify.post('/the/url', { - schema: { - body: yup.object({ - age: yup.number().integer().required(), - sub: yup.object().shape({ - name: yup.string().required() - }).required() - }) + recursive: true, +}; +fastify.post( + "/the/url", + { + schema: { + body: yup.object({ + age: yup.number().integer().required(), + sub: yup + .object() + .shape({ + name: yup.string().required(), + }) + .required(), + }), + }, + validatorCompiler: ({ schema, method, url, httpPart }) => { + return function (data) { + // 当设置 strict = false 时, yup 的 `validateSync` 函数在验证成功后会返回经过转换的值,而失败时则会抛错。 + try { + const result = schema.validateSync(data, yupOptions); + return { value: result }; + } catch (e) { + return { error: e }; + } + }; + }, }, - validatorCompiler: ({ schema, method, url, httpPart }) => { - return function (data) { - // 当设置 strict = false 时, yup 的 `validateSync` 函数在验证成功后会返回经过转换的值,而失败时则会抛错。 - try { - const result = schema.validateSync(data, yupOptions) - return { value: result } - } catch (e) { - return { error: e } - } - } - } -}, handler) + handler, +); ``` ##### 其他验证工具的验证信息 @@ -451,145 +484,155 @@ Fastify 的错误验证与其默认的验证引擎 `ajv` 紧密结合,错误 要规避以上问题,主要有两个途径: -1. 确保自定义的 `schemaCompiler` 返回的错误结构与 `ajv` 的一致 (当然,由于各引擎的差异,这是件困难的活儿)。 +1. 确保自定义的 `schemaCompiler` 返回的错误结构与 `ajv` 的一致 (当然,由于各引擎的差异,这是件困难的活儿)。 2. 使用自定义的 `errorHandler` 拦截并格式化验证错误。 Fastify 给所有的验证错误添加了两个属性,来帮助你自定义 `errorHandler`: -* validation:来自 `schemaCompiler` 函数的验证函数所返回的对象上的 `error` 属性的内容。 -* validationContext:验证错误的上下文 (body、params、query、headers)。 +- validation:来自 `schemaCompiler` 函数的验证函数所返回的对象上的 `error` 属性的内容。 +- validationContext:验证错误的上下文 (body、params、query、headers)。 以下是一个自定义 `errorHandler` 来处理验证错误的例子: ```js const errorHandler = (error, request, reply) => { - const statusCode = error.statusCode - let response + const statusCode = error.statusCode; + let response; - const { validation, validationContext } = error + const { validation, validationContext } = error; // 检验是否发生了验证错误 if (validation) { response = { // validationContext 的值可能是 'body'、'params'、'headers' 或 'query' message: `A validation error occured when validating the ${validationContext}...`, - // 验证工具返回的结果 - errors: validation - } + // 验证工具返回的结果 + errors: validation, + }; } else { response = { - message: 'An error occurred...' - } + message: "An error occurred...", + }; } - // 其余代码。例如,记录错误日志。 + // 其余代码。例如,记录错误日志。 // ... - reply.status(statusCode).send(response) -} + reply.status(statusCode).send(response); +}; ``` + ### 序列化 + 通常,你会通过 JSON 格式将数据发送至客户端。鉴于此,Fastify 提供了一个强大的工具——[fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) 来帮助你。当你在路由选项中提供了输出的 schema 时,它能派上用场。 我们推荐你编写一个输出的 schema,因为这能让应用的吞吐量提升 100-400% (根据 payload 的不同而有所变化),也能防止敏感信息的意外泄露。 示例: + ```js const schema = { response: { 200: { - type: 'object', + type: "object", properties: { - value: { type: 'string' }, - otherValue: { type: 'boolean' } - } - } - } -} + value: { type: "string" }, + otherValue: { type: "boolean" }, + }, + }, + }, +}; -fastify.post('/the/url', { schema }, handler) +fastify.post("/the/url", { schema }, handler); ``` 如你所见,响应的 schema 是建立在状态码的基础之上的。当你想对多个状态码使用同一个 schema 时,你可以使用类似 `'2xx'` 的表达方法,例如: + ```js const schema = { response: { - '2xx': { - type: 'object', + "2xx": { + type: "object", properties: { - value: { type: 'string' }, - otherValue: { type: 'boolean' } - } + value: { type: "string" }, + otherValue: { type: "boolean" }, + }, }, 201: { // 对比写法 - value: { type: 'string' } - } - } -} + value: { type: "string" }, + }, + }, +}; -fastify.post('/the/url', { schema }, handler) +fastify.post("/the/url", { schema }, handler); ``` + #### 序列化函数生成器 `serializerCompiler` 返回一个根据输入参数返回字符串的函数。你应该提供一个函数,用于序列化所有定义了 `response` JSON Schema 的路由。 ```js fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { - return data => JSON.stringify(data) -}) + return (data) => JSON.stringify(data); +}); -fastify.get('/user', { - handler (req, reply) { - reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' }) +fastify.get("/user", { + handler(req, reply) { + reply.send({ id: 1, name: "Foo", image: "BIG IMAGE" }); }, schema: { response: { - '2xx': { - id: { type: 'number' }, - name: { type: 'string' } - } - } - } -}) + "2xx": { + id: { type: "number" }, + name: { type: "string" }, + }, + }, + }, +}); ``` -*假如你需要在特定位置使用自定义的序列化工具,你可以使用 [`reply.serializer(...)`](Reply.md#serializerfunc)。* +_假如你需要在特定位置使用自定义的序列化工具,你可以使用 [`reply.serializer(...)`](Reply.md#serializerfunc)。_ ### 错误控制 + 当某个请求 schema 校验失败时,Fastify 会自动返回一个包含校验结果的 400 响应。举例来说,假如你的路由有一个如下的 schema: - ```js + +```js const schema = { body: { - type: 'object', + type: "object", properties: { - name: { type: 'string' } + name: { type: "string" }, }, - required: ['name'] - } -} + required: ["name"], + }, +}; ``` + 当校验失败时,路由会立即返回一个包含以下内容的响应: - ```js + +```js { - "statusCode": 400, - "error": "Bad Request", - "message": "body should have required property 'name'" + "statusCode": 400, + "error": "Bad Request", + "message": "body should have required property 'name'" } ``` 如果你想在路由内部控制错误,可以设置 `attachValidation` 选项。当出现 _验证错误_ 时,请求的 `validationError` 属性将会包含一个 `Error` 对象,在这对象内部有原始的验证结果 `validation`,如下所示: - ```js -const fastify = Fastify() - fastify.post('/', { schema, attachValidation: true }, function (req, reply) { + +```js +const fastify = Fastify(); +fastify.post("/", { schema, attachValidation: true }, function (req, reply) { if (req.validationError) { // `req.validationError.validation` 包含了原始的验证错误信息 - reply.code(400).send(req.validationError) + reply.code(400).send(req.validationError); } -}) +}); ``` #### `schemaErrorFormatter` @@ -603,31 +646,33 @@ const fastify = Fastify() const fastify = Fastify({ schemaErrorFormatter: (errors, dataVar) => { // ... 自定义的格式化逻辑 - return new Error(myErrorMessage) - } -}) + return new Error(myErrorMessage); + }, +}); // 或 fastify.setSchemaErrorFormatter(function (errors, dataVar) { - this.log.error({ err: errors }, 'Validation failed') + this.log.error({ err: errors }, "Validation failed"); // ... 自定义的格式化逻辑 - return new Error(myErrorMessage) -}) + return new Error(myErrorMessage); +}); ``` 你还可以使用 [setErrorHandler](https://www.fastify.io/docs/latest/Server/#seterrorhandler) 方法来自定义一个校验错误响应,如下: - ```js + +```js fastify.setErrorHandler(function (error, request, reply) { if (error.validation) { - // error.validationContext 是 [body, params, querystring, headers] 之中的值 - reply.status(422).send(new Error(`validation failed of the ${error.validationContext}`)) + // error.validationContext 是 [body, params, querystring, headers] 之中的值 + reply + .status(422) + .send(new Error(`validation failed of the ${error.validationContext}`)); } -}) +}); ``` 假如你想轻松愉快地自定义错误响应,请查看 [`ajv-errors`](https://github.com/epoberezkin/ajv-errors)。具体的例子可以移步[这里](https://github.com/fastify/example/blob/HEAD/validation-messages/custom-errors-messages.js)。 - 下面的例子展示了如何通过自定义 AJV,为 schema 的**每个属性添加自定义错误信息**。 其中的注释描述了在不同场景下设置不同信息的方法。 @@ -635,89 +680,87 @@ fastify.setErrorHandler(function (error, request, reply) { const fastify = Fastify({ ajv: { customOptions: { jsonPointers: true }, - plugins: [ - require('ajv-errors') - ] - } -}) + plugins: [require("ajv-errors")], + }, +}); const schema = { body: { - type: 'object', + type: "object", properties: { name: { - type: 'string', + type: "string", errorMessage: { - type: 'Bad name' - } + type: "Bad name", + }, }, age: { - type: 'number', + type: "number", errorMessage: { - type: 'Bad age', // 为除了必填外的所有限制 - min: 'Too young' // 自定义错误信息 - } - } + type: "Bad age", // 为除了必填外的所有限制 + min: "Too young", // 自定义错误信息 + }, + }, }, - required: ['name', 'age'], + required: ["name", "age"], errorMessage: { required: { - name: 'Why no name!', // 为必填设置 - age: 'Why no age!' // 错误信息 - } - } - } -} + name: "Why no name!", // 为必填设置 + age: "Why no age!", // 错误信息 + }, + }, + }, +}; -fastify.post('/', { schema, }, (request, reply) => { +fastify.post("/", { schema }, (request, reply) => { reply.send({ - hello: 'world' - }) -}) + hello: "world", + }); +}); ``` 想要本地化错误信息,请看 [ajv-i18n](https://github.com/epoberezkin/ajv-i18n) ```js -const localize = require('ajv-i18n') +const localize = require("ajv-i18n"); -const fastify = Fastify() +const fastify = Fastify(); const schema = { body: { - type: 'object', + type: "object", properties: { name: { - type: 'string', + type: "string", }, age: { - type: 'number', - } + type: "number", + }, }, - required: ['name', 'age'], - } -} + required: ["name", "age"], + }, +}; fastify.setErrorHandler(function (error, request, reply) { if (error.validation) { - localize.ru(error.validation) - reply.status(400).send(error.validation) - return + localize.ru(error.validation); + reply.status(400).send(error.validation); + return; } - reply.send(error) -}) + reply.send(error); +}); ``` ### JSON Schema 支持 为了能更简单地重用 schema,JSON Schema 提供了一些功能,来结合 Fastify 的共用 schema。 -| 用例 | 验证器 | 序列化器 | -|-----------------------------------|-----------|------------| -| 引用 (`$ref`) `$id` | ✔ | ✔️ | -| 引用 (`$ref`) `/definitions` | ✔️ | ✔️ | -| 引用 (`$ref`) 共用 schema `$id` | ✔ | ✔️ | -| 引用 (`$ref`) 共用 schema `/definitions` | ✔ | ✔️ | +| 用例 | 验证器 | 序列化器 | +| ---------------------------------------- | ------ | -------- | +| 引用 (`$ref`) `$id` | ✔ | ✔️ | +| 引用 (`$ref`) `/definitions` | ✔️ | ✔️ | +| 引用 (`$ref`) 共用 schema `$id` | ✔ | ✔️ | +| 引用 (`$ref`) 共用 schema `/definitions` | ✔ | ✔️ | #### 示例 @@ -725,98 +768,103 @@ fastify.setErrorHandler(function (error, request, reply) { ```js const refToId = { - type: 'object', + type: "object", definitions: { foo: { - $id: '#address', - type: 'object', + $id: "#address", + type: "object", properties: { - city: { type: 'string' } - } - } + city: { type: "string" }, + }, + }, }, properties: { - home: { $ref: '#address' }, - work: { $ref: '#address' } - } -} + home: { $ref: "#address" }, + work: { $ref: "#address" }, + }, +}; ``` ##### 同一个 JSON Schema 中对 `/definitions` 的引用 ($ref) + ```js const refToDefinitions = { - type: 'object', + type: "object", definitions: { foo: { - $id: '#address', - type: 'object', + $id: "#address", + type: "object", properties: { - city: { type: 'string' } - } - } + city: { type: "string" }, + }, + }, }, properties: { - home: { $ref: '#/definitions/foo' }, - work: { $ref: '#/definitions/foo' } - } -} + home: { $ref: "#/definitions/foo" }, + work: { $ref: "#/definitions/foo" }, + }, +}; ``` ##### 对外部共用 schema 的 `$id` 的引用 ($ref) + ```js fastify.addSchema({ - $id: 'http://foo/common.json', - type: 'object', + $id: "http://foo/common.json", + type: "object", definitions: { foo: { - $id: '#address', - type: 'object', + $id: "#address", + type: "object", properties: { - city: { type: 'string' } - } - } - } -}) + city: { type: "string" }, + }, + }, + }, +}); const refToSharedSchemaId = { - type: 'object', + type: "object", properties: { - home: { $ref: 'http://foo/common.json#address' }, - work: { $ref: 'http://foo/common.json#address' } - } -} + home: { $ref: "http://foo/common.json#address" }, + work: { $ref: "http://foo/common.json#address" }, + }, +}; ``` ##### 对外部共用 schema 的 `/definitions` 的引用 ($ref) + ```js fastify.addSchema({ - $id: 'http://foo/shared.json', - type: 'object', + $id: "http://foo/shared.json", + type: "object", definitions: { foo: { - type: 'object', + type: "object", properties: { - city: { type: 'string' } - } - } - } -}) + city: { type: "string" }, + }, + }, + }, +}); const refToSharedSchemaDefinitions = { - type: 'object', + type: "object", properties: { - home: { $ref: 'http://foo/shared.json#/definitions/foo' }, - work: { $ref: 'http://foo/shared.json#/definitions/foo' } - } -} + home: { $ref: "http://foo/shared.json#/definitions/foo" }, + work: { $ref: "http://foo/shared.json#/definitions/foo" }, + }, +}; ``` + ### 资源 + - [JSON Schema](https://json-schema.org/) - [理解 JSON Schema](https://spacetelescope.github.io/understanding-json-schema/) - [fast-json-stringify 文档](https://github.com/fastify/fast-json-stringify) - [Ajv 文档](https://github.com/epoberezkin/ajv/blob/master/README.md) - [Ajv i18n](https://github.com/epoberezkin/ajv-i18n) - [Ajv 自定义错误](https://github.com/epoberezkin/ajv-errors) -- 使用核心方法自定义错误处理,并实现错误文件转储的[例子](https://github.com/fastify/example/tree/main/validation-messages) \ No newline at end of file +- 使用核心方法自定义错误处理,并实现错误文件转储的[例子](https://github.com/fastify/example/tree/main/validation-messages) diff --git a/doc/fastify-docs/docs/Write-Plugin.md b/doc/fastify-docs/docs/Write-Plugin.md index 08483a7..49d9135 100644 --- a/doc/fastify-docs/docs/Write-Plugin.md +++ b/doc/fastify-docs/docs/Write-Plugin.md @@ -1,12 +1,14 @@

Fastify

# 如何写一个好的插件 + 首先,要感谢你决定为 Fastify 编写插件。Fastify 本身是一个极简的框架,插件才是它强大功能的来源,所以,谢谢你。
Fastify 的核心原则是高性能、低成本、提供优秀的用户体验。当编写插件时,这些原则应当被遵循。因此,本文我们将会分析一个优质的插件所具有的特征。 -*需要一些灵感?你可以在 issue 中使用 ["plugin suggestion"](https://github.com/fastify/fastify/issues?q=is%3Aissue+is%3Aopen+label%3A%22plugin+suggestion%22) 标签!* +_需要一些灵感?你可以在 issue 中使用 ["plugin suggestion"](https://github.com/fastify/fastify/issues?q=is%3Aissue+is%3Aopen+label%3A%22plugin+suggestion%22) 标签!_ ## 代码 + Fastify 运用了不同的技术来优化代码,其大部分都被写入了文档。我们强烈建议你阅读 [插件指南](Plugins-Guide.md) 一文,以了解所有可用于构建插件的 API 及其用法。 存有疑虑或寻求建议?我们非常高兴能帮助你!只需在我们的 [求助仓库](https://github.com/fastify/help) 提一个 issue 即可! @@ -14,8 +16,10 @@ Fastify 运用了不同的技术来优化代码,其大部分都被写入了文 一旦你向我们的 [生态列表](https://github.com/fastify/fastify/blob/main/docs/Ecosystem.md) 提交了一个插件,我们将会检查你的代码,需要时也会帮忙改进它。 ## 文档 + 文档相当重要。假如你的插件没有好的文档,我们将拒绝将其加入生态列表。缺乏良好的文档会提升用户使用插件的难度,并有可能导致弃用。
以下列出了一些优秀插件文档的示例: + - [`fastify-caching`](https://github.com/fastify/fastify-caching) - [`fastify-compress`](https://github.com/fastify/fastify-compress) - [`fastify-cookie`](https://github.com/fastify/fastify-cookie) @@ -23,37 +27,44 @@ Fastify 运用了不同的技术来优化代码,其大部分都被写入了文 - [`under-pressure`](https://github.com/fastify/under-pressure) ## 许可证 + 你可以为你的插件使用自己偏好的许可,我们不会强求。
我们推荐 [MIT 许可证](https://choosealicense.com/licenses/mit/),因为我们认为它允许更多人自由地使用代码。其他可替代的许可证参见 [OSI list](https://opensource.org/licenses) 或 GitHub 的 [choosealicense.com](https://choosealicense.com/)。 ## 示例 + 总在你的仓库里添加一个示例文件。这对于用户是相当有帮助的,也提供了一个快速的手段来测试你的插件。使用者们会为此感激的。 ## 测试 + 彻底地测试一个插件,来验证其是否正常执行,是极为重要的。
缺乏测试会影响用户的信任感,也无法保证代码在不同版本的依赖下还能正常工作。 我们不强求使用某一测试工具。我们使用的是 [`tap`](https://www.node-tap.org/),因为它提供了开箱即用的并行测试以及代码覆盖率检测。 ## 代码检查 + 这一项不是强制的,但我们强烈推荐你在插件中使用一个代码检查工具。这可以帮助你保持统一的代码风格,同时避免许多错误。 我们使用 [`standard`](https://standardjs.com/),因为它不需任何配置,并且容易与测试集成。 ## 持续集成 + 这一项也不是强制的,但假如你开源发布你的代码,持续集成能保证其他人的参与不会破坏你的插件,并检查插件是否如预期般工作。[CircleCI](https://circleci.com/) 和 [GitHub Actions](https://github.com/features/actions) 都是对开源项目免费的持续集成系统,且易于安装配置。
此外,你还可以启用 [Dependabot](https://dependabot.com/) 或 [Snyk](https://snyk.io/) 等服务,它可以帮你将依赖保持在最新版本,并检查在 Fastify 的新版本上你的插件是否存在问题。 ## 让我们开始吧! + 棒极了,现在你已经了解了如何为 Fastify 写一个好插件! 当你完成了一个插件(或更多)之后,请让我们知道!我们会将其添加到 [生态](https://github.com/fastify/fastify#ecosystem) 一节中! 想看更多真实的例子?请参阅: + - [`point-of-view`](https://github.com/fastify/point-of-view) -Fastify 的模板渲染 (*ejs, pug, handlebars, marko*) 插件。 + Fastify 的模板渲染 (_ejs, pug, handlebars, marko_) 插件。 - [`fastify-mongodb`](https://github.com/fastify/fastify-mongodb) -Fastify 的 MongoDB 连接插件,通过它你可以在你服务器的每个部分共享同一个 MongoDB 连接池。 + Fastify 的 MongoDB 连接插件,通过它你可以在你服务器的每个部分共享同一个 MongoDB 连接池。 - [`fastify-multipart`](https://github.com/fastify/fastify-multipart) -为 Fastify 提供 mltipart 支持。 + 为 Fastify 提供 mltipart 支持。 - [`fastify-helmet`](https://github.com/fastify/fastify-helmet) -重要的请求头安全插件。 \ No newline at end of file + 重要的请求头安全插件。 diff --git a/drizzle.config.js b/drizzle.config.js new file mode 100644 index 0000000..32729c1 --- /dev/null +++ b/drizzle.config.js @@ -0,0 +1,22 @@ +import dotenv from 'dotenv'; + +dotenv.config({ path: '.env' }); + +export default { + schema: './src/entities/schema.js', + out: './SQL', + dialect: 'mysql', + dbCredentials: { + host: process.env.DB_HOST, + port: Number(process.env.DB_PORT), + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + }, + verbose: true, + strict: true, + introspect: { + // 启用驼峰命名 + casing: 'camel', + }, +}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..3158922 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,35 @@ +import configPrettier from 'eslint-config-prettier'; +import pluginImport from 'eslint-plugin-import'; + +export default [ + { + // 将 settings 提升到顶层 + settings: { + 'import/resolver': { + 'custom-alias': { + extensions: ['.js'], + alias: { + '#config': './config', + '#src': './src', + '#start': './src/utils/start.js', + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + rules: { + 'no-console': 'warn', + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + semi: ['error', 'always'], + 'comma-dangle': ['error', 'always-multiline'], + }, + }, + configPrettier, + { + plugins: { import: pluginImport }, + rules: pluginImport.configs.recommended.rules, + }, +]; diff --git a/migrations/0000_wandering_marvel_boy.sql b/migrations/0000_wandering_marvel_boy.sql new file mode 100644 index 0000000..9bafbba --- /dev/null +++ b/migrations/0000_wandering_marvel_boy.sql @@ -0,0 +1,7 @@ +CREATE TABLE `users` ( + `id` int NOT NULL, + `name` varchar(255), + `email` varchar(255), + CONSTRAINT `users_id` PRIMARY KEY(`id`), + CONSTRAINT `users_email_unique` UNIQUE(`email`) +); diff --git a/migrations/meta/0000_snapshot.json b/migrations/meta/0000_snapshot.json new file mode 100644 index 0000000..d2f208f --- /dev/null +++ b/migrations/meta/0000_snapshot.json @@ -0,0 +1,59 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "08e581e2-3826-4de5-b172-057ee6a72da0", + "prevId": "00000000-0000-0000-0000-000000000000", + "tables": { + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "users_id": { + "name": "users_id", + "columns": ["id"] + } + }, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "columns": ["email"] + } + }, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json new file mode 100644 index 0000000..34e808e --- /dev/null +++ b/migrations/meta/_journal.json @@ -0,0 +1,13 @@ +{ + "version": "7", + "dialect": "mysql", + "entries": [ + { + "idx": 0, + "version": "5", + "when": 1742369871003, + "tag": "0000_wandering_marvel_boy", + "breakpoints": true + } + ] +} diff --git a/package-lock.json b/package-lock.json index 09c34bb..7e2ae33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1330 +1,5369 @@ { - "name": "yuheng", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "yuheng", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "dotenv": "^16.4.7", - "fastify": "^5.2.1", - "fastify-plugin": "^5.0.1", - "pino": "^9.6.0", - "pino-multi-stream": "^6.0.0", - "pino-pretty": "^13.0.0" - }, - "devDependencies": { - "cross-env": "^7.0.3", - "nodemon": "^3.1.9" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@fastify/ajv-compiler": { - "version": "4.0.2", - "resolved": "https://registry.npmmirror.com/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", - "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "name": "yuheng", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "yuheng", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.7", + "drizzle-kit": "^0.30.5", + "drizzle-orm": "^0.40.1", + "fastify": "^5.2.1", + "fastify-plugin": "^5.0.1", + "mysql2": "^3.13.0", + "pino": "^9.6.0", + "pino-multi-stream": "^6.0.0", + "pino-pretty": "^13.0.0" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "eslint": "^9.21.0", + "eslint-config-prettier": "^10.0.2", + "eslint-import-resolver-custom-alias": "^1.3.2", + "eslint-plugin-import": "^2.31.0", + "nodemon": "^3.1.9", + "prettier": "^3.5.3" + }, + "engines": { + "node": ">=22" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0" - } - }, - "node_modules/@fastify/error": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/@fastify/error/-/error-4.0.0.tgz", - "integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==", - "license": "MIT" - }, - "node_modules/@fastify/fast-json-stringify-compiler": { - "version": "5.0.2", - "resolved": "https://registry.npmmirror.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.2.tgz", - "integrity": "sha512-YdR7gqlLg1xZAQa+SX4sMNzQHY5pC54fu9oC5aYSUqBhyn6fkLkrdtKlpVdCNPlwuUuXA1PjFTEmvMF6ZVXVGw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmmirror.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "license": "Apache-2.0" }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "fast-json-stringify": "^6.0.0" - } - }, - "node_modules/@fastify/forwarded": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/@fastify/forwarded/-/forwarded-3.0.0.tgz", - "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", - "license": "MIT" - }, - "node_modules/@fastify/merge-json-schemas": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", - "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@fastify/proxy-addr": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz", - "integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==", - "license": "MIT", - "dependencies": { - "@fastify/forwarded": "^3.0.0", - "ipaddr.js": "^2.1.0" - } - }, - "node_modules/abstract-logging": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/abstract-logging/-/abstract-logging-2.0.1.tgz", - "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", - "license": "MIT" - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/avvio": { - "version": "9.1.0", - "resolved": "https://registry.npmmirror.com/avvio/-/avvio-9.1.0.tgz", - "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", - "license": "MIT", - "dependencies": { - "@fastify/error": "^4.0.0", - "fastq": "^1.17.1" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", - "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/fast-copy": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/fast-copy/-/fast-copy-3.0.2.tgz", - "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", - "license": "MIT" - }, - "node_modules/fast-decode-uri-component": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", - "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-json-stringify": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", - "integrity": "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/merge-json-schemas": "^0.2.0", - "ajv": "^8.12.0", - "ajv-formats": "^3.0.1", - "fast-uri": "^3.0.0", - "json-schema-ref-resolver": "^2.0.0", - "rfdc": "^1.2.0" - } - }, - "node_modules/fast-querystring": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/fast-querystring/-/fast-querystring-1.1.2.tgz", - "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", - "license": "MIT", - "dependencies": { - "fast-decode-uri-component": "^1.0.1" - } - }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastify": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/fastify/-/fastify-5.2.1.tgz", - "integrity": "sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "@fastify/ajv-compiler": "^4.0.0", - "@fastify/error": "^4.0.0", - "@fastify/fast-json-stringify-compiler": "^5.0.0", - "@fastify/proxy-addr": "^5.0.0", - "abstract-logging": "^2.0.1", - "avvio": "^9.0.0", - "fast-json-stringify": "^6.0.0", - "find-my-way": "^9.0.0", - "light-my-request": "^6.0.0", - "pino": "^9.0.0", - "process-warning": "^4.0.0", - "rfdc": "^1.3.1", - "secure-json-parse": "^3.0.1", - "semver": "^7.6.0", - "toad-cache": "^3.7.0" - } - }, - "node_modules/fastify-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/fastify-plugin/-/fastify-plugin-5.0.1.tgz", - "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-my-way": { - "version": "9.2.0", - "resolved": "https://registry.npmmirror.com/find-my-way/-/find-my-way-9.2.0.tgz", - "integrity": "sha512-d3uCir8Hmg7W1Ywp8nKf2lJJYU9Nwinvo+1D39Dn09nz65UKXIxUh7j7K8zeWhxqe1WrkS7FJyON/Q/3lPoc6w==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-querystring": "^1.0.0", - "safe-regex2": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/help-me": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/help-me/-/help-me-5.0.0.tgz", - "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", - "license": "MIT" - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/json-schema-ref-resolver": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", - "integrity": "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/light-my-request": { - "version": "6.6.0", - "resolved": "https://registry.npmmirror.com/light-my-request/-/light-my-request-6.6.0.tgz", - "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "cookie": "^1.0.1", - "process-warning": "^4.0.0", - "set-cookie-parser": "^2.6.0" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/on-exit-leak-free": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", - "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pino": { - "version": "9.6.0", - "resolved": "https://registry.npmmirror.com/pino/-/pino-9.6.0.tgz", - "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pino-std-serializers": "^7.0.0", - "process-warning": "^4.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^4.0.1", - "thread-stream": "^3.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", - "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", - "license": "MIT", - "dependencies": { - "split2": "^4.0.0" - } - }, - "node_modules/pino-multi-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/pino-multi-stream/-/pino-multi-stream-6.0.0.tgz", - "integrity": "sha512-oCuTtaDSUB5xK1S45r9oWE0Dj8RWdHVvaGTft5pO/rmzgIqQRkilf5Ooilz3uRm0IYj8sPRho3lVx48LCmXjvQ==", - "deprecated": "No longer supported. Use the multi-stream support in the latest core Pino", - "license": "MIT", - "dependencies": { - "pino": "^7.0.0" - } - }, - "node_modules/pino-multi-stream/node_modules/on-exit-leak-free": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", - "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==", - "license": "MIT" - }, - "node_modules/pino-multi-stream/node_modules/pino": { - "version": "7.11.0", - "resolved": "https://registry.npmmirror.com/pino/-/pino-7.11.0.tgz", - "integrity": "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.0.0", - "on-exit-leak-free": "^0.2.0", - "pino-abstract-transport": "v0.5.0", - "pino-std-serializers": "^4.0.0", - "process-warning": "^1.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.1.0", - "safe-stable-stringify": "^2.1.0", - "sonic-boom": "^2.2.1", - "thread-stream": "^0.15.1" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-multi-stream/node_modules/pino-abstract-transport": { - "version": "0.5.0", - "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", - "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", - "license": "MIT", - "dependencies": { - "duplexify": "^4.1.2", - "split2": "^4.0.0" - } - }, - "node_modules/pino-multi-stream/node_modules/pino-std-serializers": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", - "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==", - "license": "MIT" - }, - "node_modules/pino-multi-stream/node_modules/process-warning": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz", - "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", - "license": "MIT" - }, - "node_modules/pino-multi-stream/node_modules/real-require": { - "version": "0.1.0", - "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.1.0.tgz", - "integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/pino-multi-stream/node_modules/sonic-boom": { - "version": "2.8.0", - "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-2.8.0.tgz", - "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/pino-multi-stream/node_modules/thread-stream": { - "version": "0.15.2", - "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-0.15.2.tgz", - "integrity": "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==", - "license": "MIT", - "dependencies": { - "real-require": "^0.1.0" - } - }, - "node_modules/pino-pretty": { - "version": "13.0.0", - "resolved": "https://registry.npmmirror.com/pino-pretty/-/pino-pretty-13.0.0.tgz", - "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.7", - "dateformat": "^4.6.3", - "fast-copy": "^3.0.2", - "fast-safe-stringify": "^2.1.1", - "help-me": "^5.0.0", - "joycon": "^3.1.1", - "minimist": "^1.2.6", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "^2.0.0", - "pump": "^3.0.0", - "secure-json-parse": "^2.4.0", - "sonic-boom": "^4.0.1", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "pino-pretty": "bin.js" - } - }, - "node_modules/pino-pretty/node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "license": "BSD-3-Clause" - }, - "node_modules/pino-std-serializers": { - "version": "7.0.0", - "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", - "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", - "license": "MIT" - }, - "node_modules/process-warning": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-4.0.1.tgz", - "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ret": { - "version": "0.5.0", - "resolved": "https://registry.npmmirror.com/ret/-/ret-0.5.0.tgz", - "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex2": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/safe-regex2/-/safe-regex2-4.0.1.tgz", - "integrity": "sha512-goqsB+bSlOmVX+CiFX2PFc1OV88j5jvBqIM+DgqrucHnUguAUNtiNOs+aTadq2NqsLQ+TQ3UEVG3gtSFcdlkCg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT", - "dependencies": { - "ret": "~0.5.0" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/secure-json-parse": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-3.0.2.tgz", - "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmmirror.com/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmmirror.com/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.21.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/@fastify/ajv-compiler/-/ajv-compiler-4.0.2.tgz", + "integrity": "sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@fastify/error/-/error-4.0.0.tgz", + "integrity": "sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.2.tgz", + "integrity": "sha512-YdR7gqlLg1xZAQa+SX4sMNzQHY5pC54fu9oC5aYSUqBhyn6fkLkrdtKlpVdCNPlwuUuXA1PjFTEmvMF6ZVXVGw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@fastify/forwarded/-/forwarded-3.0.0.tgz", + "integrity": "sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==", + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@fastify/proxy-addr/-/proxy-addr-5.0.0.tgz", + "integrity": "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==", + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmmirror.com/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@petamoriken/float16": { + "version": "3.9.2", + "resolved": "https://registry.npmmirror.com/@petamoriken/float16/-/float16-3.9.2.tgz", + "integrity": "sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==", + "license": "MIT" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmmirror.com/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avvio": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/avvio/-/avvio-9.1.0.tgz", + "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmmirror.com/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/drizzle-kit": { + "version": "0.30.5", + "resolved": "https://registry.npmmirror.com/drizzle-kit/-/drizzle-kit-0.30.5.tgz", + "integrity": "sha512-l6dMSE100u7sDaTbLczibrQZjA35jLsHNqIV+jmhNVO3O8jzM6kywMOmV9uOz9ZVSCMPQhAZEFjL/qDPVrqpUA==", + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.19.7", + "esbuild-register": "^3.5.0", + "gel": "^2.0.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.40.1", + "resolved": "https://registry.npmmirror.com/drizzle-orm/-/drizzle-orm-0.40.1.tgz", + "integrity": "sha512-aPNhtiJiPfm3qxz1czrnIDkfvkSdKGXYeZkpG55NPTVI186LmK2fBLMi4dsHpPHlJrZeQ92D322YFPHADBALew==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.21.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-10.0.2.tgz", + "integrity": "sha512-1105/17ZIMjmCOJOPNfVdbXafLCLj3hPmkmB7dLgt7XsQ/zkxSuDerE/xgO3RxoHysR1N1whmquY0lSn2O0VLg==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "build/bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-custom-alias": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/eslint-import-resolver-custom-alias/-/eslint-import-resolver-custom-alias-1.3.2.tgz", + "integrity": "sha512-wBPcZA2k6/IXaT8FsLMyiyVSG6WVEuaYIAbeKLXeGwr523BmeB9lKAAoLJWSqp3txsnU4gpkgD2x1q6K8k0uDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^6.0.2", + "resolve": "^1.22.2" + }, + "peerDependencies": { + "eslint-plugin-import": ">=2.2.0" + } + }, + "node_modules/eslint-import-resolver-custom-alias/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmmirror.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmmirror.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmmirror.com/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", + "integrity": "sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^2.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/fastify/-/fastify-5.2.1.tgz", + "integrity": "sha512-rslrNBF67eg8/Gyn7P2URV8/6pz8kSAscFL4EThZJ8JBMaXacVdVE4hmUcnPNKERl5o/xTiBSLfdowBRhVF1WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.0.0", + "process-warning": "^4.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^3.0.1", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/fastify-plugin/-/fastify-plugin-5.0.1.tgz", + "integrity": "sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-my-way": { + "version": "9.2.0", + "resolved": "https://registry.npmmirror.com/find-my-way/-/find-my-way-9.2.0.tgz", + "integrity": "sha512-d3uCir8Hmg7W1Ywp8nKf2lJJYU9Nwinvo+1D39Dn09nz65UKXIxUh7j7K8zeWhxqe1WrkS7FJyON/Q/3lPoc6w==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gel": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/gel/-/gel-2.0.1.tgz", + "integrity": "sha512-gfem3IGvqKqXwEq7XseBogyaRwGsQGuE7Cw/yQsjLGdgiyqX92G1xENPCE0ltunPGcsJIa6XBOTx/PK169mOqw==", + "license": "Apache-2.0", + "dependencies": { + "@petamoriken/float16": "^3.8.7", + "debug": "^4.3.4", + "env-paths": "^3.0.0", + "semver": "^7.6.2", + "shell-quote": "^1.8.1", + "which": "^4.0.0" + }, + "bin": { + "gel": "dist/cli.mjs" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/gel/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/gel/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.0", + "resolved": "https://registry.npmmirror.com/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-ref-resolver": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", + "integrity": "sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmmirror.com/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mysql2": { + "version": "3.13.0", + "resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.13.0.tgz", + "integrity": "sha512-M6DIQjTqKeqXH5HLbLMxwcK5XfXHw30u5ap6EZmu7QVmcF/gnh2wS/EOiQ4MTbXz/vQeoXrmycPlVRM00WSslg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmmirror.com/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.6.0", + "resolved": "https://registry.npmmirror.com/pino/-/pino-9.6.0.tgz", + "integrity": "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^4.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-multi-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/pino-multi-stream/-/pino-multi-stream-6.0.0.tgz", + "integrity": "sha512-oCuTtaDSUB5xK1S45r9oWE0Dj8RWdHVvaGTft5pO/rmzgIqQRkilf5Ooilz3uRm0IYj8sPRho3lVx48LCmXjvQ==", + "deprecated": "No longer supported. Use the multi-stream support in the latest core Pino", + "license": "MIT", + "dependencies": { + "pino": "^7.0.0" + } + }, + "node_modules/pino-multi-stream/node_modules/on-exit-leak-free": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", + "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==", + "license": "MIT" + }, + "node_modules/pino-multi-stream/node_modules/pino": { + "version": "7.11.0", + "resolved": "https://registry.npmmirror.com/pino/-/pino-7.11.0.tgz", + "integrity": "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.0.0", + "on-exit-leak-free": "^0.2.0", + "pino-abstract-transport": "v0.5.0", + "pino-std-serializers": "^4.0.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.1.0", + "safe-stable-stringify": "^2.1.0", + "sonic-boom": "^2.2.1", + "thread-stream": "^0.15.1" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-multi-stream/node_modules/pino-abstract-transport": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", + "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.2", + "split2": "^4.0.0" + } + }, + "node_modules/pino-multi-stream/node_modules/pino-std-serializers": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", + "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==", + "license": "MIT" + }, + "node_modules/pino-multi-stream/node_modules/process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", + "license": "MIT" + }, + "node_modules/pino-multi-stream/node_modules/real-require": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.1.0.tgz", + "integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/pino-multi-stream/node_modules/sonic-boom": { + "version": "2.8.0", + "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-2.8.0.tgz", + "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/pino-multi-stream/node_modules/thread-stream": { + "version": "0.15.2", + "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-0.15.2.tgz", + "integrity": "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.1.0" + } + }, + "node_modules/pino-pretty": { + "version": "13.0.0", + "resolved": "https://registry.npmmirror.com/pino-pretty/-/pino-pretty-13.0.0.tgz", + "integrity": "sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==", + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex2": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/safe-regex2/-/safe-regex2-4.0.1.tgz", + "integrity": "sha512-goqsB+bSlOmVX+CiFX2PFc1OV88j5jvBqIM+DgqrucHnUguAUNtiNOs+aTadq2NqsLQ+TQ3UEVG3gtSFcdlkCg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/secure-json-parse": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/secure-json-parse/-/secure-json-parse-3.0.2.tgz", + "integrity": "sha512-H6nS2o8bWfpFEV6U38sOSjS7bTbdgbCGU9wEM6W14P5H0QOsz94KCusifV44GpHDTu2nqZbuDNhTzu+mjDSw1w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmmirror.com/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmmirror.com/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } - ], - "license": "BSD-3-Clause" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sonic-boom": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/sonic-boom/-/sonic-boom-4.2.0.tgz", - "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/thread-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/thread-stream/-/thread-stream-3.1.0.tgz", - "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", - "license": "MIT", - "dependencies": { - "real-require": "^0.2.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmmirror.com/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" } - } } diff --git a/package.json b/package.json index 343efed..4147b63 100644 --- a/package.json +++ b/package.json @@ -1,37 +1,52 @@ { - "name": "yuheng", - "version": "1.0.0", - "description": "", - "main": "src/application.js", - "type": "module", - "imports": { - "#/*": "./*", - "#common/*": "./src/common/*", - "#config/*": "./config/*", - "#start": "./src/utils/start.js", - "#src/*": "./src/*" - }, - "engines": { - "node": ">=22" - }, - "scripts": { - "start": "cross-env NODE_ENV=production node src/application.js", - "dev": "cross-env NODE_ENV=development nodemon src/application.js", - "lint": "eslint --ext .js src" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "dotenv": "^16.4.7", - "fastify": "^5.2.1", - "fastify-plugin": "^5.0.1", - "pino": "^9.6.0", - "pino-multi-stream": "^6.0.0", - "pino-pretty": "^13.0.0" - }, - "devDependencies": { - "cross-env": "^7.0.3", - "nodemon": "^3.1.9" - } + "name": "yuheng", + "version": "1.0.0", + "description": "", + "main": "src/application.js", + "type": "module", + "imports": { + "#/*": "./*", + "#common/*": "./src/common/*", + "#config/*": "./config/*.js", + "#start": "./src/utils/start.js", + "#plugins/*": "./src/plugins/*.js", + "#src/*": "./src/*.js" + }, + "engines": { + "node": ">=22" + }, + "scripts": { + "start": "cross-env NODE_ENV=production node src/application.js", + "dev": "cross-env NODE_ENV=development nodemon src/application.js", + "lint": "eslint src --ext .js", + "format": "prettier --write . --config ./.prettier.config.cjs --ignore-path .prettierignore", + "lint:format": "prettier --check . --config ./.prettier.config.cjs --ignore-path .prettierignore", + "makeSQL": "drizzle-kit generate", + "makeEntity": "drizzle-kit introspect", + "syncDB": "drizzle-kit migrate", + "sqlV": "drizzle-kit studio" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.7", + "drizzle-kit": "^0.30.5", + "drizzle-orm": "^0.40.1", + "fastify": "^5.2.1", + "fastify-plugin": "^5.0.1", + "mysql2": "^3.13.0", + "pino": "^9.6.0", + "pino-multi-stream": "^6.0.0", + "pino-pretty": "^13.0.0" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "eslint": "^9.21.0", + "eslint-config-prettier": "^10.0.2", + "eslint-import-resolver-custom-alias": "^1.3.2", + "eslint-plugin-import": "^2.31.0", + "nodemon": "^3.1.9", + "prettier": "^3.5.3" + } } diff --git a/src/application.js b/src/application.js index 4f9647a..6f51996 100644 --- a/src/application.js +++ b/src/application.js @@ -1,23 +1,19 @@ // ESM -import "#start" +import '#start'; import Fastify from 'fastify'; -import config from '#config/index.js' -import plugin from '#src/plugins/index.js'; -import routes from '#src/routes/index.js'; -import logger from '#src/utils/logger.js'; +import config from '#config/index'; +import plugin from '#src/plugins/index'; +import routes from '#src/routes/index'; +import logger from '#src/utils/logger'; async function start() { // 创建Fastify实例 - const fastify = new Fastify({ - logger - }) + const fastify = new Fastify({ logger }); // 加载插件 - await fastify.register(plugin); + await fastify.register(plugin, { config }); // 加载路由 - fastify.register(routes); - - await fastify.listen(config.server); // 使用配置中的服务器参数 + await fastify.register(routes); + // 启动服务器 + await fastify.listen(config.server); // 使用配置中的服务器参数 } - - -start(); \ No newline at end of file +start(); diff --git a/src/entities/schema.js b/src/entities/schema.js new file mode 100644 index 0000000..7c3240e --- /dev/null +++ b/src/entities/schema.js @@ -0,0 +1,17 @@ +/** + * @typedef {import('drizzle-orm/mysql-core').MySqlTable} MySqlTable + * @typedef {import('drizzle-orm/mysql-core').varchar} varchar + * @typedef {import('drizzle-orm/mysql-core').int} int + */ + +import { mysqlTable, varchar, int } from 'drizzle-orm/mysql-core'; + +/** + * 用户模型 + * @type {MySqlTable} + */ +export const users = mysqlTable('users', { + id: int('id').primaryKey(), + name: varchar('name', { length: 255 }), + email: varchar('email', { length: 255 }).unique(), +}); diff --git a/src/plugins/database/index.js b/src/plugins/database/index.js new file mode 100644 index 0000000..367b67d --- /dev/null +++ b/src/plugins/database/index.js @@ -0,0 +1,28 @@ +import { drizzle } from 'drizzle-orm/mysql2'; +import { sql } from 'drizzle-orm'; +import mysql from 'mysql2/promise'; +import fastifyPlugin from 'fastify-plugin'; +async function database(fastify, options) { + fastify.log.warn('Register Database Plugin!'); + const config = fastify.config; + // 配置数据库 + const pool = await mysql.createPool({ + host: config.db.host, + port: config.db.port, + user: config.db.user, + password: config.db.password, + database: config.db.database, + ssl: config.db.ssl, + waitForConnections: true, + connectionLimit: 10, + queueLimit: 0, + }); + // 暴露数据库 + const db = drizzle(pool); + // 新增获取所有表名方法 + const [result] = await db.execute(sql`SHOW TABLES`); + const tableList = result.map(row => Object.values(row)[0]); + db.tableList = tableList; + fastify.decorate('db', db); +} +export default fastifyPlugin(database); diff --git a/src/plugins/index.js b/src/plugins/index.js index 15fc3a2..163d9a5 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,13 +1,14 @@ -export default async function plugin(fastify, opts) { +import fp from 'fastify-plugin'; +import database from '#plugins/database/index'; + +async function plugin(fastify, opts) { fastify.log.warn('Register Global Plugin!'); + // 加载配置 fastify.decorate('config', opts.config); - fastify.decorate('cfg', opts.config); - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 1000); - }); - fastify.log.warn(fastify.config); - fastify.log.warn(fastify.cfg); + // 注册数据库 + await fastify.register(database); + fastify.log.warn(fastify.db.tableList); + // 读取数据库基本信息 fastify.log.warn('Register Global Plugin complete!'); -} \ No newline at end of file +} +export default fp(plugin); diff --git a/src/routes/index.js b/src/routes/index.js index 5f83f71..097c207 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,13 +1,7 @@ export default async function routes(fastify, options) { - // 定义一个GET请求的路由,路径为根路径 - fastify.get('/', async (request, reply) => { - return { hello: 'world' }; - }); - - // 你可以在这里添加更多的路由 - // 例如: - // fastify.get('/another-route', async (request, reply) => { - // return { message: 'This is another route' }; - // }); + // 定义一个GET请求的路由,路径为根路径 + fastify.get('/', async (request, reply) => { + fastify.log.info(fastify.config); + return fastify.config; + }); } - diff --git a/src/services/user.service.js b/src/services/user.service.js new file mode 100644 index 0000000..3d09096 --- /dev/null +++ b/src/services/user.service.js @@ -0,0 +1,35 @@ +import { eq } from 'drizzle-orm'; +import { users } from '#db/schema'; + +/** + * 用户服务模块 + * @param {object} fastify - Fastify实例 + */ +export default async function userService(fastify) { + const { db } = fastify; + + return { + /** + * 根据ID获取用户 + * @param {number} userId - 用户ID + * @returns {Promise} 用户对象 + */ + async getUserById(userId) { + return db + .select() + .from(users) + .where(eq(users.id, userId)) + .limit(1) + .then(res => res[0]); + }, + + /** + * 创建新用户 + * @param {object} userData - 用户数据 + * @returns {Promise} 创建的用户记录 + */ + async createUser(userData) { + return db.insert(users).values(userData).returning(); + }, + }; +} diff --git a/src/utils/logger.js b/src/utils/logger.js index f1666cb..1ec265e 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,5 +1,5 @@ -import config from "#config/index.js" -export default{ +import config from '#config/index'; +export default { // 日志级别顺序(从高到低): // fatal(致命) > error(错误) > warn(警告) > info(信息) > debug(调试) > trace(追踪) // 高级别日志会包含低级别日志(设置warn会包含error和fatal) @@ -27,16 +27,16 @@ export default{ // - req: 请求对象 // - responseTime: 响应时间 // - 支持通配符如 'req*'(忽略所有 req 开头字段) - } + }, }, { target: 'pino/file', options: { - destination: config.logger.filePath, // 文件路径从配置读取 + destination: config.logger.filePath, // 文件路径从配置读取 mkdir: true, ignore: 'pid,hostname', - } - } - ] - } -} \ No newline at end of file + }, + }, + ], + }, +}; diff --git a/src/utils/start.js b/src/utils/start.js index 49b4ccf..8e4afa0 100644 --- a/src/utils/start.js +++ b/src/utils/start.js @@ -1,12 +1,12 @@ const text = '> Si Hi <'; // 需要显示的文本 const terminalWidth = process.stdout.columns || 100; const padding = Math.max(0, Math.floor((terminalWidth - text.length * 1.5) / 2)); // 中文每个字占2字符宽度 - +/* eslint-disable no-console */ console.log( '\x1B[48;5;0m%s\x1B[0m', // 灰色背景 '\x1B[32;5;12m\x1B[1m ' + // 白色加粗 - '-'.repeat(padding) + - text + - '-'.repeat(padding)+ - ' \x1B[0m' // 重置样式 -); \ No newline at end of file + '-'.repeat(padding) + + text + + '-'.repeat(padding) + + ' \x1B[0m', // 重置样式 +);