├── .github ├── FUNDING.yml └── workflows │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── bench ├── body-parser.js ├── file.txt ├── formidable.js ├── index.md ├── milliparsec-multipart.js ├── milliparsec.js ├── package.json └── pnpm-lock.yaml ├── biome.json ├── logo.png ├── package.json ├── pnpm-lock.yaml ├── src └── index.ts ├── test.ts ├── tsconfig.build.json └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: v1rtl 4 | liberapay: v1rtl 5 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [master] 10 | pull_request: 11 | branches: [master] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "test" 16 | test: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | # Use environment for CI 20 | environment: ci 21 | 22 | # Steps represent a sequence of tasks that will be executed as part of the job 23 | steps: 24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 25 | - uses: actions/checkout@v4 26 | - uses: pnpm/action-setup@v4 27 | - uses: actions/setup-node@v4 28 | with: 29 | node-version: 18.13 30 | cache: "pnpm" 31 | - run: pnpm install 32 | - run: pnpm test:coverage 33 | - run: pnpm test:report 34 | - name: Setup Biome 35 | uses: biomejs/setup-biome@v2 36 | with: 37 | version: latest 38 | 39 | - name: Run Biome 40 | run: biome ci . 41 | - name: Coveralls 42 | uses: coverallsapp/github-action@master 43 | with: 44 | github-token: ${{ secrets.GITHUB_TOKEN }} 45 | path-to-lcov: ./coverage.lcov 46 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npm 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | id-token: write 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: pnpm/action-setup@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | cache: "pnpm" 17 | node-version: 20 18 | registry-url: "https://registry.npmjs.org" 19 | - run: pnpm install 20 | - run: pnpm build 21 | - name: Setup Biome 22 | uses: biomejs/setup-biome@v2 23 | with: 24 | version: latest 25 | 26 | - name: Run Biome 27 | run: biome ci . 28 | - run: pnpm publish --no-git-checks 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | 13 | # nyc test coverage 14 | .nyc_output 15 | 16 | # node-waf configuration 17 | .lock-wscript 18 | 19 | # Dependency directories 20 | node_modules/ 21 | jspm_packages/ 22 | 23 | # TypeScript v1 declaration files 24 | typings/ 25 | 26 | # Optional npm cache directory 27 | .npm 28 | 29 | # Optional eslint cache 30 | .eslintcache 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Output of 'npm pack' 36 | *.tgz 37 | 38 | # Yarn Integrity file 39 | .yarn-integrity 40 | 41 | # dotenv environment variables file 42 | .env 43 | 44 | dist 45 | coverage 46 | coverage.lcov -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm.packageManager": "pnpm", 3 | "editor.formatOnSave": true, 4 | "biome.enabled": true, 5 | "eslint.enable": false, 6 | "prettier.enable": false, 7 | "editor.codeActionsOnSave": { 8 | "source.fixAll": "explicit", 9 | "source.organizeImports.biome": "explicit" 10 | }, 11 | "typescript.tsdk": "node_modules/typescript/lib" 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 v 1 r t l 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |


3 | 4 |

5 | 6 | [![Version][v-badge-url]][npm-url] [![Coverage][cov-img]][cov-url] 7 | [![Github actions][gh-actions-img]][github-actions] 8 | [![Downloads][dl-badge-url]][npm-url] 9 | 10 |
11 |
12 | 13 | Tiniest body parser in the universe. Built for modern Node.js. 14 | 15 | Check out [deno-libs/parsec](https://github.com/deno-libs/parsec) for Deno port. 16 | 17 | ## Features 18 | 19 | - 🛠 JSON / raw / urlencoded / multipart support 20 | - 📦 tiny package size (8KB dist size) 21 | - 🔥 no dependencies 22 | - ✨ [tinyhttp](https://github.com/tinyhttp/tinyhttp) and Express support 23 | - ⚡ 40% faster than body-parser and 20x faster than formidable 24 | 25 | ## Install 26 | 27 | ```sh 28 | # pnpm 29 | pnpm i milliparsec 30 | 31 | # bun 32 | bun i milliparsec 33 | ``` 34 | 35 | ## Usage 36 | 37 | ### Basic example 38 | 39 | Use a middleware inside a server: 40 | 41 | ```js 42 | import { createServer } from 'node:http' 43 | import { json } from 'milliparsec' 44 | 45 | const server = createServer(async (req: ReqWithBody, res) => { 46 | await json()(req, res, (err) => void err && res.end(err)) 47 | 48 | res.setHeader('Content-Type', 'application/json') 49 | 50 | res.end(JSON.stringify(req.body)) 51 | }) 52 | ``` 53 | 54 | ### What is "parsec"? 55 | 56 | The parsec is a unit of length used to measure large distances to astronomical 57 | objects outside the Solar System. 58 | 59 | [v-badge-url]: https://img.shields.io/npm/v/milliparsec.svg?style=for-the-badge&color=25608B&logo=npm&label= 60 | [npm-url]: https://www.npmjs.com/package/milliparsec 61 | [dl-badge-url]: https://img.shields.io/npm/dt/milliparsec?style=for-the-badge&color=25608B 62 | [github-actions]: https://github.com/talentlessguy/milliparsec/actions 63 | [gh-actions-img]: https://img.shields.io/github/actions/workflow/status/tinyhttp/milliparsec/main.yml?branch=master&style=for-the-badge&color=25608B&label=&logo=github 64 | [cov-img]: https://img.shields.io/coveralls/github/tinyhttp/milliparsec?style=for-the-badge&color=25608B 65 | [cov-url]: https://coveralls.io/github/tinyhttp/milliparsec 66 | -------------------------------------------------------------------------------- /bench/body-parser.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { createServer } from 'node:http' 4 | import bodyParser from 'body-parser' 5 | 6 | const mw = bodyParser.json() 7 | 8 | const server = createServer((req, res) => { 9 | mw(req, res, () => { 10 | // @ts-expect-error added by body parser 11 | res.end(JSON.stringify(req.body)) 12 | }) 13 | }) 14 | 15 | server.listen(3002) 16 | -------------------------------------------------------------------------------- /bench/file.txt: -------------------------------------------------------------------------------- 1 | this is a file that is being sent in a multipart request -------------------------------------------------------------------------------- /bench/formidable.js: -------------------------------------------------------------------------------- 1 | import { createReadStream } from 'node:fs' 2 | // @ts-check 3 | import { createServer } from 'node:http' 4 | import formidable from 'formidable' 5 | 6 | const form = formidable({}) 7 | 8 | const server = createServer((req, res) => { 9 | form.parse(req, (_, fields, files) => { 10 | // @ts-expect-error this is JS 11 | const file = createReadStream(files.file[0].filepath) 12 | file.pipe(res) 13 | }) 14 | }) 15 | 16 | server.listen(3005) 17 | -------------------------------------------------------------------------------- /bench/index.md: -------------------------------------------------------------------------------- 1 | # Benchmark 2 | 3 | Below are benchmarks of body-parser vs milliparsec and formidable vs 4 | milliparsec. Please take into account that these benchmarks are not entirely 5 | accurate, since they are taken on a regular desktop computer in usual 6 | conditions. 7 | 8 | ## Environment 9 | 10 | - Node.js 22.3.1 11 | - System: macOS Sequoia 15.3.1 / Darwin 24.3.0 arm64 kernel 12 | - CPU: Apple M2 (8) @ 3.50 GHz 13 | - Machine: MacBook Air (M2, 2022) 14 | 15 | ## JSON parsing 16 | 17 | ### Benchmark command: 18 | 19 | ```sh 20 | autocannon -m POST -b '{"a":1}' -H "Content-Type=application/json" localhost:3002 # or 3003 21 | ``` 22 | 23 | ### Results 24 | 25 | body-parser result: 26 | 27 | ``` 28 | ┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐ 29 | │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ 30 | ├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤ 31 | │ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.12 ms │ 22 ms │ 32 | └─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘ 33 | ┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬──────────┬─────────┐ 34 | │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ 35 | ├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼──────────┼─────────┤ 36 | │ Req/Sec │ 54,591 │ 54,591 │ 61,759 │ 63,871 │ 61,436.8 │ 2,478.39 │ 54,589 │ 37 | ├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼──────────┼─────────┤ 38 | │ Bytes/Sec │ 7.05 MB │ 7.05 MB │ 7.97 MB │ 8.24 MB │ 7.93 MB │ 319 kB │ 7.04 MB │ 39 | └───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴──────────┴─────────┘ 40 | 41 | Req/Bytes counts sampled once per second. 42 | # of samples: 10 43 | 44 | 614k requests in 10.01s, 79.3 MB read 45 | ``` 46 | 47 | milliparsec result: 48 | 49 | ``` 50 | ┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐ 51 | │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ 52 | ├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤ 53 | │ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.01 ms │ 0.04 ms │ 11 ms │ 54 | └─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘ 55 | ┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐ 56 | │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ 57 | ├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ 58 | │ Req/Sec │ 79,999 │ 79,999 │ 88,127 │ 88,767 │ 87,095.28 │ 2,370.01 │ 79,966 │ 59 | ├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ 60 | │ Bytes/Sec │ 9.76 MB │ 9.76 MB │ 10.7 MB │ 10.8 MB │ 10.6 MB │ 289 kB │ 9.76 MB │ 61 | └───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘ 62 | 63 | Req/Bytes counts sampled once per second. 64 | # of samples: 11 65 | 66 | 958k requests in 11.01s, 117 MB read 67 | 68 | Req/Bytes counts sampled once per second. 69 | # of samples: 11 70 | 71 | 641k requests in 11.02s, 78.2 MB read 72 | ``` 73 | 74 | ### Verdict 75 | 76 | milliparsec, on average, is ~40% faster. 77 | 78 | ## Multipart with files 79 | 80 | ### Benchmark command: 81 | 82 | ```sh 83 | autocannon -m POST --form '{ "file": { "type": "file", "path": "./file.txt" } }' localhost:3004 84 | ``` 85 | 86 | ### Results 87 | 88 | formidable result: 89 | 90 | ``` 91 | ┌─────────┬──────┬──────┬───────┬───────┬─────────┬─────────┬───────┐ 92 | │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ 93 | ├─────────┼──────┼──────┼───────┼───────┼─────────┼─────────┼───────┤ 94 | │ Latency │ 1 ms │ 5 ms │ 19 ms │ 26 ms │ 6.63 ms │ 5.86 ms │ 54 ms │ 95 | └─────────┴──────┴──────┴───────┴───────┴─────────┴─────────┴───────┘ 96 | ┌───────────┬────────┬────────┬────────┬────────┬────────┬──────────┬────────┐ 97 | │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ 98 | ├───────────┼────────┼────────┼────────┼────────┼────────┼──────────┼────────┤ 99 | │ Req/Sec │ 530 │ 530 │ 775 │ 4,595 │ 1,404 │ 1,179.32 │ 530 │ 100 | ├───────────┼────────┼────────┼────────┼────────┼────────┼──────────┼────────┤ 101 | │ Bytes/Sec │ 105 kB │ 105 kB │ 153 kB │ 910 kB │ 278 kB │ 233 kB │ 105 kB │ 102 | └───────────┴────────┴────────┴────────┴────────┴────────┴──────────┴────────┘ 103 | 104 | Req/Bytes counts sampled once per second. 105 | # of samples: 10 106 | 107 | 14k requests in 10.02s, 2.78 MB read 108 | ``` 109 | 110 | milliparsec result: 111 | 112 | ``` 113 | ┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬───────┐ 114 | │ Stat │ 2.5% │ 50% │ 97.5% │ 99% │ Avg │ Stdev │ Max │ 115 | ├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼───────┤ 116 | │ Latency │ 0 ms │ 0 ms │ 0 ms │ 0 ms │ 0.02 ms │ 0.19 ms │ 20 ms │ 117 | └─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴───────┘ 118 | ┌───────────┬─────────┬─────────┬─────────┬─────────┬───────────┬──────────┬─────────┐ 119 | │ Stat │ 1% │ 2.5% │ 50% │ 97.5% │ Avg │ Stdev │ Min │ 120 | ├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ 121 | │ Req/Sec │ 24,063 │ 24,063 │ 29,727 │ 30,863 │ 29,263.28 │ 1,758.94 │ 24,051 │ 122 | ├───────────┼─────────┼─────────┼─────────┼─────────┼───────────┼──────────┼─────────┤ 123 | │ Bytes/Sec │ 4.76 MB │ 4.76 MB │ 5.89 MB │ 6.11 MB │ 5.79 MB │ 348 kB │ 4.76 MB │ 124 | └───────────┴─────────┴─────────┴─────────┴─────────┴───────────┴──────────┴─────────┘ 125 | 126 | Req/Bytes counts sampled once per second. 127 | # of samples: 11 128 | 129 | 322k requests in 11.01s, 63.7 MB read 130 | ``` 131 | 132 | ### Verdict 133 | 134 | milliparsec, on average, is ~20x faster. 135 | -------------------------------------------------------------------------------- /bench/milliparsec-multipart.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { createServer } from 'node:http' 4 | import * as bodyParser from '../dist/index.js' 5 | const mw = bodyParser.multipart() 6 | 7 | const server = createServer((req, res) => { 8 | mw(req, res, () => { 9 | /** 10 | * @type {File} 11 | */ 12 | // @ts-ignore 13 | const file = req.body.file[0] 14 | const stream = file.stream() 15 | 16 | // Pipe the stream to the response 17 | stream.pipeTo( 18 | new WritableStream({ 19 | write(chunk) { 20 | res.write(chunk) 21 | }, 22 | close() { 23 | res.end() 24 | }, 25 | abort(err) { 26 | console.error('Stream error:', err) 27 | res.writeHead(500) 28 | res.end('Error streaming file') 29 | } 30 | }) 31 | ) 32 | }) 33 | }) 34 | 35 | server.listen(3004) 36 | -------------------------------------------------------------------------------- /bench/milliparsec.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import { createServer } from 'node:http' 4 | import * as bodyParser from '../dist/index.js' 5 | 6 | const mw = bodyParser.json() 7 | 8 | const server = createServer((req, res) => { 9 | mw(req, res, () => { 10 | // @ts-expect-error added by body parser 11 | res.end(JSON.stringify(req.body)) 12 | }) 13 | }) 14 | 15 | server.listen(3003) 16 | -------------------------------------------------------------------------------- /bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bench", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "bench": "autocannon -m POST -b '{\"a\":1}' -H \"Content-Type=application/json\"", 9 | "bench:multipart": "autocannon -m POST --form '{ \"file\": { \"type\": \"file\", \"path\": \"./file.txt\" } }'" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/body-parser": "^1.19.5", 16 | "@types/formidable": "^3.4.5", 17 | "autocannon": "^8.0.0", 18 | "body-parser": "^1.20.3" 19 | }, 20 | "dependencies": { 21 | "formidable": "^3.5.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bench/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | formidable: 12 | specifier: ^3.5.2 13 | version: 3.5.2 14 | devDependencies: 15 | '@types/body-parser': 16 | specifier: ^1.19.5 17 | version: 1.19.5 18 | '@types/formidable': 19 | specifier: ^3.4.5 20 | version: 3.4.5 21 | autocannon: 22 | specifier: ^8.0.0 23 | version: 8.0.0 24 | body-parser: 25 | specifier: ^1.20.3 26 | version: 1.20.3 27 | 28 | packages: 29 | 30 | '@assemblyscript/loader@0.19.23': 31 | resolution: {integrity: sha512-ulkCYfFbYj01ie1MDOyxv2F6SpRN1TOj7fQxbP07D6HmeR+gr2JLSmINKjga2emB+b1L2KGrFKBTc+e00p54nw==} 32 | 33 | '@colors/colors@1.5.0': 34 | resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} 35 | engines: {node: '>=0.1.90'} 36 | 37 | '@minimistjs/subarg@1.0.0': 38 | resolution: {integrity: sha512-Q/ONBiM2zNeYUy0mVSO44mWWKYM3UHuEK43PKIOzJCbvUnPoMH1K+gk3cf1kgnCVJFlWmddahQQCmrmBGlk9jQ==} 39 | 40 | '@types/body-parser@1.19.5': 41 | resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} 42 | 43 | '@types/connect@3.4.38': 44 | resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} 45 | 46 | '@types/formidable@3.4.5': 47 | resolution: {integrity: sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==} 48 | 49 | '@types/node@20.14.9': 50 | resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} 51 | 52 | ansi-regex@5.0.1: 53 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 54 | engines: {node: '>=8'} 55 | 56 | ansi-styles@4.3.0: 57 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 58 | engines: {node: '>=8'} 59 | 60 | asap@2.0.6: 61 | resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} 62 | 63 | asynckit@0.4.0: 64 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 65 | 66 | autocannon@8.0.0: 67 | resolution: {integrity: sha512-fMMcWc2JPFcUaqHeR6+PbmEpTxCrPZyBUM95oG4w3ngJ8NfBNas/ZXA+pTHXLqJ0UlFVTcy05GC25WxKx/M20A==} 68 | hasBin: true 69 | 70 | base64-js@1.5.1: 71 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 72 | 73 | body-parser@1.20.3: 74 | resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} 75 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 76 | 77 | buffer@5.7.1: 78 | resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} 79 | 80 | bytes@3.1.2: 81 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 82 | engines: {node: '>= 0.8'} 83 | 84 | call-bind@1.0.7: 85 | resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} 86 | engines: {node: '>= 0.4'} 87 | 88 | chalk@4.1.2: 89 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 90 | engines: {node: '>=10'} 91 | 92 | char-spinner@1.0.1: 93 | resolution: {integrity: sha512-acv43vqJ0+N0rD+Uw3pDHSxP30FHrywu2NO6/wBaHChJIizpDeBUd6NjqhNhy9LGaEAhZAXn46QzmlAvIWd16g==} 94 | 95 | cli-table3@0.6.5: 96 | resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} 97 | engines: {node: 10.* || >= 12.*} 98 | 99 | color-convert@2.0.1: 100 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 101 | engines: {node: '>=7.0.0'} 102 | 103 | color-name@1.1.4: 104 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 105 | 106 | color-support@1.1.3: 107 | resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} 108 | hasBin: true 109 | 110 | combined-stream@1.0.8: 111 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 112 | engines: {node: '>= 0.8'} 113 | 114 | content-type@1.0.5: 115 | resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} 116 | engines: {node: '>= 0.6'} 117 | 118 | cross-argv@2.0.0: 119 | resolution: {integrity: sha512-YIaY9TR5Nxeb8SMdtrU8asWVM4jqJDNDYlKV21LxtYcfNJhp1kEsgSa6qXwXgzN0WQWGODps0+TlGp2xQSHwOg==} 120 | 121 | debug@2.6.9: 122 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 123 | peerDependencies: 124 | supports-color: '*' 125 | peerDependenciesMeta: 126 | supports-color: 127 | optional: true 128 | 129 | define-data-property@1.1.4: 130 | resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} 131 | engines: {node: '>= 0.4'} 132 | 133 | delayed-stream@1.0.0: 134 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 135 | engines: {node: '>=0.4.0'} 136 | 137 | depd@2.0.0: 138 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 139 | engines: {node: '>= 0.8'} 140 | 141 | destroy@1.2.0: 142 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 143 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 144 | 145 | dezalgo@1.0.4: 146 | resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} 147 | 148 | ee-first@1.1.1: 149 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 150 | 151 | emoji-regex@8.0.0: 152 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 153 | 154 | es-define-property@1.0.0: 155 | resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} 156 | engines: {node: '>= 0.4'} 157 | 158 | es-errors@1.3.0: 159 | resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} 160 | engines: {node: '>= 0.4'} 161 | 162 | form-data@4.0.0: 163 | resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} 164 | engines: {node: '>= 6'} 165 | 166 | formidable@3.5.2: 167 | resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} 168 | 169 | function-bind@1.1.2: 170 | resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 171 | 172 | get-intrinsic@1.2.4: 173 | resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} 174 | engines: {node: '>= 0.4'} 175 | 176 | gopd@1.0.1: 177 | resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 178 | 179 | has-async-hooks@1.0.0: 180 | resolution: {integrity: sha512-YF0VPGjkxr7AyyQQNykX8zK4PvtEDsUJAPqwu06UFz1lb6EvI53sPh5H1kWxg8NXI5LsfRCZ8uX9NkYDZBb/mw==} 181 | 182 | has-flag@4.0.0: 183 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 184 | engines: {node: '>=8'} 185 | 186 | has-property-descriptors@1.0.2: 187 | resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} 188 | 189 | has-proto@1.0.3: 190 | resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} 191 | engines: {node: '>= 0.4'} 192 | 193 | has-symbols@1.0.3: 194 | resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 195 | engines: {node: '>= 0.4'} 196 | 197 | hasown@2.0.2: 198 | resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} 199 | engines: {node: '>= 0.4'} 200 | 201 | hdr-histogram-js@3.0.0: 202 | resolution: {integrity: sha512-/EpvQI2/Z98mNFYEnlqJ8Ogful8OpArLG/6Tf2bPnkutBVLIeMVNHjk1ZDfshF2BUweipzbk+dB1hgSB7SIakw==} 203 | engines: {node: '>=14'} 204 | 205 | hdr-histogram-percentiles-obj@3.0.0: 206 | resolution: {integrity: sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==} 207 | 208 | hexoid@2.0.0: 209 | resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} 210 | engines: {node: '>=8'} 211 | 212 | http-errors@2.0.0: 213 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 214 | engines: {node: '>= 0.8'} 215 | 216 | http-parser-js@0.5.8: 217 | resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} 218 | 219 | hyperid@3.2.0: 220 | resolution: {integrity: sha512-PdTtDo+Rmza9nEhTunaDSUKwbC69TIzLEpZUwiB6f+0oqmY0UPfhyHCPt6K1NQ4WFv5yJBTG5vELztVWP+nEVQ==} 221 | 222 | iconv-lite@0.4.24: 223 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 224 | engines: {node: '>=0.10.0'} 225 | 226 | ieee754@1.2.1: 227 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 228 | 229 | inherits@2.0.4: 230 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 231 | 232 | is-fullwidth-code-point@3.0.0: 233 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 234 | engines: {node: '>=8'} 235 | 236 | lodash.chunk@4.2.0: 237 | resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==} 238 | 239 | lodash.clonedeep@4.5.0: 240 | resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} 241 | 242 | lodash.flatten@4.4.0: 243 | resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} 244 | 245 | manage-path@2.0.0: 246 | resolution: {integrity: sha512-NJhyB+PJYTpxhxZJ3lecIGgh4kwIY2RAh44XvAz9UlqthlQwtPBf62uBVR8XaD8CRuSjQ6TnZH2lNJkbLPZM2A==} 247 | 248 | media-typer@0.3.0: 249 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} 250 | engines: {node: '>= 0.6'} 251 | 252 | mime-db@1.52.0: 253 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 254 | engines: {node: '>= 0.6'} 255 | 256 | mime-types@2.1.35: 257 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 258 | engines: {node: '>= 0.6'} 259 | 260 | minimist@1.2.8: 261 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 262 | 263 | ms@2.0.0: 264 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} 265 | 266 | object-inspect@1.13.2: 267 | resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} 268 | engines: {node: '>= 0.4'} 269 | 270 | on-finished@2.4.1: 271 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 272 | engines: {node: '>= 0.8'} 273 | 274 | on-net-listen@1.1.2: 275 | resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} 276 | engines: {node: '>=9.4.0 || ^8.9.4'} 277 | 278 | once@1.4.0: 279 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 280 | 281 | pako@1.0.11: 282 | resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} 283 | 284 | pretty-bytes@5.6.0: 285 | resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} 286 | engines: {node: '>=6'} 287 | 288 | progress@2.0.3: 289 | resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} 290 | engines: {node: '>=0.4.0'} 291 | 292 | qs@6.13.0: 293 | resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} 294 | engines: {node: '>=0.6'} 295 | 296 | raw-body@2.5.2: 297 | resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} 298 | engines: {node: '>= 0.8'} 299 | 300 | reinterval@1.1.0: 301 | resolution: {integrity: sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ==} 302 | 303 | retimer@3.0.0: 304 | resolution: {integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==} 305 | 306 | safer-buffer@2.1.2: 307 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 308 | 309 | semver@7.6.2: 310 | resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} 311 | engines: {node: '>=10'} 312 | hasBin: true 313 | 314 | set-function-length@1.2.2: 315 | resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} 316 | engines: {node: '>= 0.4'} 317 | 318 | setprototypeof@1.2.0: 319 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 320 | 321 | side-channel@1.0.6: 322 | resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} 323 | engines: {node: '>= 0.4'} 324 | 325 | statuses@2.0.1: 326 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 327 | engines: {node: '>= 0.8'} 328 | 329 | string-width@4.2.3: 330 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 331 | engines: {node: '>=8'} 332 | 333 | strip-ansi@6.0.1: 334 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 335 | engines: {node: '>=8'} 336 | 337 | supports-color@7.2.0: 338 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 339 | engines: {node: '>=8'} 340 | 341 | timestring@6.0.0: 342 | resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==} 343 | engines: {node: '>=8'} 344 | 345 | toidentifier@1.0.1: 346 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 347 | engines: {node: '>=0.6'} 348 | 349 | type-is@1.6.18: 350 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 351 | engines: {node: '>= 0.6'} 352 | 353 | undici-types@5.26.5: 354 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 355 | 356 | unpipe@1.0.0: 357 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 358 | engines: {node: '>= 0.8'} 359 | 360 | uuid-parse@1.1.0: 361 | resolution: {integrity: sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==} 362 | 363 | uuid@8.3.2: 364 | resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 365 | hasBin: true 366 | 367 | wrappy@1.0.2: 368 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 369 | 370 | snapshots: 371 | 372 | '@assemblyscript/loader@0.19.23': {} 373 | 374 | '@colors/colors@1.5.0': 375 | optional: true 376 | 377 | '@minimistjs/subarg@1.0.0': 378 | dependencies: 379 | minimist: 1.2.8 380 | 381 | '@types/body-parser@1.19.5': 382 | dependencies: 383 | '@types/connect': 3.4.38 384 | '@types/node': 20.14.9 385 | 386 | '@types/connect@3.4.38': 387 | dependencies: 388 | '@types/node': 20.14.9 389 | 390 | '@types/formidable@3.4.5': 391 | dependencies: 392 | '@types/node': 20.14.9 393 | 394 | '@types/node@20.14.9': 395 | dependencies: 396 | undici-types: 5.26.5 397 | 398 | ansi-regex@5.0.1: {} 399 | 400 | ansi-styles@4.3.0: 401 | dependencies: 402 | color-convert: 2.0.1 403 | 404 | asap@2.0.6: {} 405 | 406 | asynckit@0.4.0: {} 407 | 408 | autocannon@8.0.0: 409 | dependencies: 410 | '@minimistjs/subarg': 1.0.0 411 | chalk: 4.1.2 412 | char-spinner: 1.0.1 413 | cli-table3: 0.6.5 414 | color-support: 1.1.3 415 | cross-argv: 2.0.0 416 | form-data: 4.0.0 417 | has-async-hooks: 1.0.0 418 | hdr-histogram-js: 3.0.0 419 | hdr-histogram-percentiles-obj: 3.0.0 420 | http-parser-js: 0.5.8 421 | hyperid: 3.2.0 422 | lodash.chunk: 4.2.0 423 | lodash.clonedeep: 4.5.0 424 | lodash.flatten: 4.4.0 425 | manage-path: 2.0.0 426 | on-net-listen: 1.1.2 427 | pretty-bytes: 5.6.0 428 | progress: 2.0.3 429 | reinterval: 1.1.0 430 | retimer: 3.0.0 431 | semver: 7.6.2 432 | timestring: 6.0.0 433 | 434 | base64-js@1.5.1: {} 435 | 436 | body-parser@1.20.3: 437 | dependencies: 438 | bytes: 3.1.2 439 | content-type: 1.0.5 440 | debug: 2.6.9 441 | depd: 2.0.0 442 | destroy: 1.2.0 443 | http-errors: 2.0.0 444 | iconv-lite: 0.4.24 445 | on-finished: 2.4.1 446 | qs: 6.13.0 447 | raw-body: 2.5.2 448 | type-is: 1.6.18 449 | unpipe: 1.0.0 450 | transitivePeerDependencies: 451 | - supports-color 452 | 453 | buffer@5.7.1: 454 | dependencies: 455 | base64-js: 1.5.1 456 | ieee754: 1.2.1 457 | 458 | bytes@3.1.2: {} 459 | 460 | call-bind@1.0.7: 461 | dependencies: 462 | es-define-property: 1.0.0 463 | es-errors: 1.3.0 464 | function-bind: 1.1.2 465 | get-intrinsic: 1.2.4 466 | set-function-length: 1.2.2 467 | 468 | chalk@4.1.2: 469 | dependencies: 470 | ansi-styles: 4.3.0 471 | supports-color: 7.2.0 472 | 473 | char-spinner@1.0.1: {} 474 | 475 | cli-table3@0.6.5: 476 | dependencies: 477 | string-width: 4.2.3 478 | optionalDependencies: 479 | '@colors/colors': 1.5.0 480 | 481 | color-convert@2.0.1: 482 | dependencies: 483 | color-name: 1.1.4 484 | 485 | color-name@1.1.4: {} 486 | 487 | color-support@1.1.3: {} 488 | 489 | combined-stream@1.0.8: 490 | dependencies: 491 | delayed-stream: 1.0.0 492 | 493 | content-type@1.0.5: {} 494 | 495 | cross-argv@2.0.0: {} 496 | 497 | debug@2.6.9: 498 | dependencies: 499 | ms: 2.0.0 500 | 501 | define-data-property@1.1.4: 502 | dependencies: 503 | es-define-property: 1.0.0 504 | es-errors: 1.3.0 505 | gopd: 1.0.1 506 | 507 | delayed-stream@1.0.0: {} 508 | 509 | depd@2.0.0: {} 510 | 511 | destroy@1.2.0: {} 512 | 513 | dezalgo@1.0.4: 514 | dependencies: 515 | asap: 2.0.6 516 | wrappy: 1.0.2 517 | 518 | ee-first@1.1.1: {} 519 | 520 | emoji-regex@8.0.0: {} 521 | 522 | es-define-property@1.0.0: 523 | dependencies: 524 | get-intrinsic: 1.2.4 525 | 526 | es-errors@1.3.0: {} 527 | 528 | form-data@4.0.0: 529 | dependencies: 530 | asynckit: 0.4.0 531 | combined-stream: 1.0.8 532 | mime-types: 2.1.35 533 | 534 | formidable@3.5.2: 535 | dependencies: 536 | dezalgo: 1.0.4 537 | hexoid: 2.0.0 538 | once: 1.4.0 539 | 540 | function-bind@1.1.2: {} 541 | 542 | get-intrinsic@1.2.4: 543 | dependencies: 544 | es-errors: 1.3.0 545 | function-bind: 1.1.2 546 | has-proto: 1.0.3 547 | has-symbols: 1.0.3 548 | hasown: 2.0.2 549 | 550 | gopd@1.0.1: 551 | dependencies: 552 | get-intrinsic: 1.2.4 553 | 554 | has-async-hooks@1.0.0: {} 555 | 556 | has-flag@4.0.0: {} 557 | 558 | has-property-descriptors@1.0.2: 559 | dependencies: 560 | es-define-property: 1.0.0 561 | 562 | has-proto@1.0.3: {} 563 | 564 | has-symbols@1.0.3: {} 565 | 566 | hasown@2.0.2: 567 | dependencies: 568 | function-bind: 1.1.2 569 | 570 | hdr-histogram-js@3.0.0: 571 | dependencies: 572 | '@assemblyscript/loader': 0.19.23 573 | base64-js: 1.5.1 574 | pako: 1.0.11 575 | 576 | hdr-histogram-percentiles-obj@3.0.0: {} 577 | 578 | hexoid@2.0.0: {} 579 | 580 | http-errors@2.0.0: 581 | dependencies: 582 | depd: 2.0.0 583 | inherits: 2.0.4 584 | setprototypeof: 1.2.0 585 | statuses: 2.0.1 586 | toidentifier: 1.0.1 587 | 588 | http-parser-js@0.5.8: {} 589 | 590 | hyperid@3.2.0: 591 | dependencies: 592 | buffer: 5.7.1 593 | uuid: 8.3.2 594 | uuid-parse: 1.1.0 595 | 596 | iconv-lite@0.4.24: 597 | dependencies: 598 | safer-buffer: 2.1.2 599 | 600 | ieee754@1.2.1: {} 601 | 602 | inherits@2.0.4: {} 603 | 604 | is-fullwidth-code-point@3.0.0: {} 605 | 606 | lodash.chunk@4.2.0: {} 607 | 608 | lodash.clonedeep@4.5.0: {} 609 | 610 | lodash.flatten@4.4.0: {} 611 | 612 | manage-path@2.0.0: {} 613 | 614 | media-typer@0.3.0: {} 615 | 616 | mime-db@1.52.0: {} 617 | 618 | mime-types@2.1.35: 619 | dependencies: 620 | mime-db: 1.52.0 621 | 622 | minimist@1.2.8: {} 623 | 624 | ms@2.0.0: {} 625 | 626 | object-inspect@1.13.2: {} 627 | 628 | on-finished@2.4.1: 629 | dependencies: 630 | ee-first: 1.1.1 631 | 632 | on-net-listen@1.1.2: {} 633 | 634 | once@1.4.0: 635 | dependencies: 636 | wrappy: 1.0.2 637 | 638 | pako@1.0.11: {} 639 | 640 | pretty-bytes@5.6.0: {} 641 | 642 | progress@2.0.3: {} 643 | 644 | qs@6.13.0: 645 | dependencies: 646 | side-channel: 1.0.6 647 | 648 | raw-body@2.5.2: 649 | dependencies: 650 | bytes: 3.1.2 651 | http-errors: 2.0.0 652 | iconv-lite: 0.4.24 653 | unpipe: 1.0.0 654 | 655 | reinterval@1.1.0: {} 656 | 657 | retimer@3.0.0: {} 658 | 659 | safer-buffer@2.1.2: {} 660 | 661 | semver@7.6.2: {} 662 | 663 | set-function-length@1.2.2: 664 | dependencies: 665 | define-data-property: 1.1.4 666 | es-errors: 1.3.0 667 | function-bind: 1.1.2 668 | get-intrinsic: 1.2.4 669 | gopd: 1.0.1 670 | has-property-descriptors: 1.0.2 671 | 672 | setprototypeof@1.2.0: {} 673 | 674 | side-channel@1.0.6: 675 | dependencies: 676 | call-bind: 1.0.7 677 | es-errors: 1.3.0 678 | get-intrinsic: 1.2.4 679 | object-inspect: 1.13.2 680 | 681 | statuses@2.0.1: {} 682 | 683 | string-width@4.2.3: 684 | dependencies: 685 | emoji-regex: 8.0.0 686 | is-fullwidth-code-point: 3.0.0 687 | strip-ansi: 6.0.1 688 | 689 | strip-ansi@6.0.1: 690 | dependencies: 691 | ansi-regex: 5.0.1 692 | 693 | supports-color@7.2.0: 694 | dependencies: 695 | has-flag: 4.0.0 696 | 697 | timestring@6.0.0: {} 698 | 699 | toidentifier@1.0.1: {} 700 | 701 | type-is@1.6.18: 702 | dependencies: 703 | media-typer: 0.3.0 704 | mime-types: 2.1.35 705 | 706 | undici-types@5.26.5: {} 707 | 708 | unpipe@1.0.0: {} 709 | 710 | uuid-parse@1.1.0: {} 711 | 712 | uuid@8.3.2: {} 713 | 714 | wrappy@1.0.2: {} 715 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", 3 | "files": { 4 | "ignore": ["node_modules", "dist", "coverage", ".pnpm-store", "package.json"] 5 | }, 6 | "formatter": { 7 | "enabled": true, 8 | "formatWithErrors": false, 9 | "indentStyle": "space", 10 | "indentWidth": 2, 11 | "lineEnding": "lf", 12 | "lineWidth": 120, 13 | "attributePosition": "auto" 14 | }, 15 | "organizeImports": { "enabled": true }, 16 | "linter": { 17 | "enabled": true, 18 | "rules": { 19 | "recommended": true, 20 | "correctness": { 21 | "noVoidTypeReturn": "off" 22 | }, 23 | "style": { 24 | "noParameterAssign": "off", 25 | "noNonNullAssertion": "off" 26 | }, 27 | "suspicious": { 28 | "noAssignInExpressions": "off", 29 | "noExplicitAny": "off" 30 | } 31 | } 32 | }, 33 | "javascript": { 34 | "formatter": { 35 | "jsxQuoteStyle": "double", 36 | "quoteProperties": "asNeeded", 37 | "trailingCommas": "none", 38 | "semicolons": "asNeeded", 39 | "arrowParentheses": "always", 40 | "bracketSpacing": true, 41 | "bracketSameLine": false, 42 | "quoteStyle": "single", 43 | "attributePosition": "auto" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinyhttp/milliparsec/61f919d46556852e8a515de08c8b46c917684a03/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "milliparsec", 3 | "version": "5.0.2", 4 | "description": "tiniest body parser in the universe", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/tinyhttp/milliparsec" 8 | }, 9 | "author": "talentlessguy ", 10 | "license": "MIT", 11 | "types": "./dist/index.d.ts", 12 | "type": "module", 13 | "keywords": [ 14 | "body-parser", 15 | "express", 16 | "http", 17 | "body-parsing" 18 | ], 19 | "engines": { 20 | "node": ">=18.13 || >=19.20 || >=20" 21 | }, 22 | "exports": "./dist/index.js", 23 | "devDependencies": { 24 | "@biomejs/biome": "1.9.3", 25 | "@tinyhttp/app": "^2.4.0", 26 | "@types/node": "^18.19.76", 27 | "c8": "10.1.2", 28 | "supertest-fetch": "^2.0.0", 29 | "tsx": "^4.19.1", 30 | "typescript": "^5.6.2" 31 | }, 32 | "files": [ 33 | "dist" 34 | ], 35 | "scripts": { 36 | "test": "tsx --test test.ts", 37 | "test:coverage": "c8 --include=src pnpm test", 38 | "test:report": "c8 report --reporter=text-lcov > coverage.lcov", 39 | "build": "tsc -p tsconfig.build.json", 40 | "prepublishOnly": "pnpm build && pnpm test", 41 | "check": "biome check --write" 42 | }, 43 | "packageManager": "pnpm@9.4.0", 44 | "publishConfig": { 45 | "provenance": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@biomejs/biome': 12 | specifier: 1.9.3 13 | version: 1.9.3 14 | '@tinyhttp/app': 15 | specifier: ^2.4.0 16 | version: 2.4.0 17 | '@types/node': 18 | specifier: ^18.19.76 19 | version: 18.19.76 20 | c8: 21 | specifier: 10.1.2 22 | version: 10.1.2 23 | supertest-fetch: 24 | specifier: ^2.0.0 25 | version: 2.0.0 26 | tsx: 27 | specifier: ^4.19.1 28 | version: 4.19.1 29 | typescript: 30 | specifier: ^5.6.2 31 | version: 5.6.2 32 | 33 | packages: 34 | 35 | '@bcoe/v8-coverage@0.2.3': 36 | resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} 37 | 38 | '@biomejs/biome@1.9.3': 39 | resolution: {integrity: sha512-POjAPz0APAmX33WOQFGQrwLvlu7WLV4CFJMlB12b6ZSg+2q6fYu9kZwLCOA+x83zXfcPd1RpuWOKJW0GbBwLIQ==} 40 | engines: {node: '>=14.21.3'} 41 | hasBin: true 42 | 43 | '@biomejs/cli-darwin-arm64@1.9.3': 44 | resolution: {integrity: sha512-QZzD2XrjJDUyIZK+aR2i5DDxCJfdwiYbUKu9GzkCUJpL78uSelAHAPy7m0GuPMVtF/Uo+OKv97W3P9nuWZangQ==} 45 | engines: {node: '>=14.21.3'} 46 | cpu: [arm64] 47 | os: [darwin] 48 | 49 | '@biomejs/cli-darwin-x64@1.9.3': 50 | resolution: {integrity: sha512-vSCoIBJE0BN3SWDFuAY/tRavpUtNoqiceJ5PrU3xDfsLcm/U6N93JSM0M9OAiC/X7mPPfejtr6Yc9vSgWlEgVw==} 51 | engines: {node: '>=14.21.3'} 52 | cpu: [x64] 53 | os: [darwin] 54 | 55 | '@biomejs/cli-linux-arm64-musl@1.9.3': 56 | resolution: {integrity: sha512-VBzyhaqqqwP3bAkkBrhVq50i3Uj9+RWuj+pYmXrMDgjS5+SKYGE56BwNw4l8hR3SmYbLSbEo15GcV043CDSk+Q==} 57 | engines: {node: '>=14.21.3'} 58 | cpu: [arm64] 59 | os: [linux] 60 | 61 | '@biomejs/cli-linux-arm64@1.9.3': 62 | resolution: {integrity: sha512-vJkAimD2+sVviNTbaWOGqEBy31cW0ZB52KtpVIbkuma7PlfII3tsLhFa+cwbRAcRBkobBBhqZ06hXoZAN8NODQ==} 63 | engines: {node: '>=14.21.3'} 64 | cpu: [arm64] 65 | os: [linux] 66 | 67 | '@biomejs/cli-linux-x64-musl@1.9.3': 68 | resolution: {integrity: sha512-TJmnOG2+NOGM72mlczEsNki9UT+XAsMFAOo8J0me/N47EJ/vkLXxf481evfHLlxMejTY6IN8SdRSiPVLv6AHlA==} 69 | engines: {node: '>=14.21.3'} 70 | cpu: [x64] 71 | os: [linux] 72 | 73 | '@biomejs/cli-linux-x64@1.9.3': 74 | resolution: {integrity: sha512-x220V4c+romd26Mu1ptU+EudMXVS4xmzKxPVb9mgnfYlN4Yx9vD5NZraSx/onJnd3Gh/y8iPUdU5CDZJKg9COA==} 75 | engines: {node: '>=14.21.3'} 76 | cpu: [x64] 77 | os: [linux] 78 | 79 | '@biomejs/cli-win32-arm64@1.9.3': 80 | resolution: {integrity: sha512-lg/yZis2HdQGsycUvHWSzo9kOvnGgvtrYRgoCEwPBwwAL8/6crOp3+f47tPwI/LI1dZrhSji7PNsGKGHbwyAhw==} 81 | engines: {node: '>=14.21.3'} 82 | cpu: [arm64] 83 | os: [win32] 84 | 85 | '@biomejs/cli-win32-x64@1.9.3': 86 | resolution: {integrity: sha512-cQMy2zanBkVLpmmxXdK6YePzmZx0s5Z7KEnwmrW54rcXK3myCNbQa09SwGZ8i/8sLw0H9F3X7K4rxVNGU8/D4Q==} 87 | engines: {node: '>=14.21.3'} 88 | cpu: [x64] 89 | os: [win32] 90 | 91 | '@esbuild/aix-ppc64@0.23.1': 92 | resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} 93 | engines: {node: '>=18'} 94 | cpu: [ppc64] 95 | os: [aix] 96 | 97 | '@esbuild/android-arm64@0.23.1': 98 | resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} 99 | engines: {node: '>=18'} 100 | cpu: [arm64] 101 | os: [android] 102 | 103 | '@esbuild/android-arm@0.23.1': 104 | resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} 105 | engines: {node: '>=18'} 106 | cpu: [arm] 107 | os: [android] 108 | 109 | '@esbuild/android-x64@0.23.1': 110 | resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} 111 | engines: {node: '>=18'} 112 | cpu: [x64] 113 | os: [android] 114 | 115 | '@esbuild/darwin-arm64@0.23.1': 116 | resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} 117 | engines: {node: '>=18'} 118 | cpu: [arm64] 119 | os: [darwin] 120 | 121 | '@esbuild/darwin-x64@0.23.1': 122 | resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} 123 | engines: {node: '>=18'} 124 | cpu: [x64] 125 | os: [darwin] 126 | 127 | '@esbuild/freebsd-arm64@0.23.1': 128 | resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} 129 | engines: {node: '>=18'} 130 | cpu: [arm64] 131 | os: [freebsd] 132 | 133 | '@esbuild/freebsd-x64@0.23.1': 134 | resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} 135 | engines: {node: '>=18'} 136 | cpu: [x64] 137 | os: [freebsd] 138 | 139 | '@esbuild/linux-arm64@0.23.1': 140 | resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} 141 | engines: {node: '>=18'} 142 | cpu: [arm64] 143 | os: [linux] 144 | 145 | '@esbuild/linux-arm@0.23.1': 146 | resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} 147 | engines: {node: '>=18'} 148 | cpu: [arm] 149 | os: [linux] 150 | 151 | '@esbuild/linux-ia32@0.23.1': 152 | resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} 153 | engines: {node: '>=18'} 154 | cpu: [ia32] 155 | os: [linux] 156 | 157 | '@esbuild/linux-loong64@0.23.1': 158 | resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} 159 | engines: {node: '>=18'} 160 | cpu: [loong64] 161 | os: [linux] 162 | 163 | '@esbuild/linux-mips64el@0.23.1': 164 | resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} 165 | engines: {node: '>=18'} 166 | cpu: [mips64el] 167 | os: [linux] 168 | 169 | '@esbuild/linux-ppc64@0.23.1': 170 | resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} 171 | engines: {node: '>=18'} 172 | cpu: [ppc64] 173 | os: [linux] 174 | 175 | '@esbuild/linux-riscv64@0.23.1': 176 | resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} 177 | engines: {node: '>=18'} 178 | cpu: [riscv64] 179 | os: [linux] 180 | 181 | '@esbuild/linux-s390x@0.23.1': 182 | resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} 183 | engines: {node: '>=18'} 184 | cpu: [s390x] 185 | os: [linux] 186 | 187 | '@esbuild/linux-x64@0.23.1': 188 | resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} 189 | engines: {node: '>=18'} 190 | cpu: [x64] 191 | os: [linux] 192 | 193 | '@esbuild/netbsd-x64@0.23.1': 194 | resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} 195 | engines: {node: '>=18'} 196 | cpu: [x64] 197 | os: [netbsd] 198 | 199 | '@esbuild/openbsd-arm64@0.23.1': 200 | resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} 201 | engines: {node: '>=18'} 202 | cpu: [arm64] 203 | os: [openbsd] 204 | 205 | '@esbuild/openbsd-x64@0.23.1': 206 | resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} 207 | engines: {node: '>=18'} 208 | cpu: [x64] 209 | os: [openbsd] 210 | 211 | '@esbuild/sunos-x64@0.23.1': 212 | resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} 213 | engines: {node: '>=18'} 214 | cpu: [x64] 215 | os: [sunos] 216 | 217 | '@esbuild/win32-arm64@0.23.1': 218 | resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} 219 | engines: {node: '>=18'} 220 | cpu: [arm64] 221 | os: [win32] 222 | 223 | '@esbuild/win32-ia32@0.23.1': 224 | resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} 225 | engines: {node: '>=18'} 226 | cpu: [ia32] 227 | os: [win32] 228 | 229 | '@esbuild/win32-x64@0.23.1': 230 | resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} 231 | engines: {node: '>=18'} 232 | cpu: [x64] 233 | os: [win32] 234 | 235 | '@isaacs/cliui@8.0.2': 236 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 237 | engines: {node: '>=12'} 238 | 239 | '@istanbuljs/schema@0.1.3': 240 | resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} 241 | engines: {node: '>=8'} 242 | 243 | '@jridgewell/resolve-uri@3.1.2': 244 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 245 | engines: {node: '>=6.0.0'} 246 | 247 | '@jridgewell/sourcemap-codec@1.5.0': 248 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 249 | 250 | '@jridgewell/trace-mapping@0.3.25': 251 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 252 | 253 | '@pkgjs/parseargs@0.11.0': 254 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 255 | engines: {node: '>=14'} 256 | 257 | '@tinyhttp/accepts@2.2.3': 258 | resolution: {integrity: sha512-9pQN6pJAJOU3McmdJWTcyq7LLFW8Lj5q+DadyKcvp+sxMkEpktKX5sbfJgJuOvjk6+1xWl7pe0YL1US1vaO/1w==} 259 | engines: {node: '>=12.20.0'} 260 | 261 | '@tinyhttp/app@2.4.0': 262 | resolution: {integrity: sha512-vOPiCemQRJq5twnl06dde6XnWiNbVMdVRFJWW/yC/9G0qgvV2TvzNNTxrdlz6YmyB7vIC7Fg3qS6m6gx8RbBNQ==} 263 | engines: {node: '>=14.21.3'} 264 | 265 | '@tinyhttp/content-disposition@2.2.2': 266 | resolution: {integrity: sha512-crXw1txzrS36huQOyQGYFvhTeLeG0Si1xu+/l6kXUVYpE0TjFjEZRqTbuadQLfKGZ0jaI+jJoRyqaWwxOSHW2g==} 267 | engines: {node: '>=12.20.0'} 268 | 269 | '@tinyhttp/content-type@0.1.4': 270 | resolution: {integrity: sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==} 271 | engines: {node: '>=12.4'} 272 | 273 | '@tinyhttp/cookie-signature@2.1.1': 274 | resolution: {integrity: sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==} 275 | engines: {node: '>=12.20.0'} 276 | 277 | '@tinyhttp/cookie@2.1.1': 278 | resolution: {integrity: sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==} 279 | engines: {node: '>=12.20.0'} 280 | 281 | '@tinyhttp/encode-url@2.1.1': 282 | resolution: {integrity: sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==} 283 | engines: {node: '>=12.20.0'} 284 | 285 | '@tinyhttp/etag@2.1.2': 286 | resolution: {integrity: sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==} 287 | engines: {node: '>=12.20.0'} 288 | 289 | '@tinyhttp/forwarded@2.1.1': 290 | resolution: {integrity: sha512-nO3kq0R1LRl2+CAMlnggm22zE6sT8gfvGbNvSitV6F9eaUSurHP0A8YZFMihSkugHxK+uIegh1TKrqgD8+lyGQ==} 291 | engines: {node: '>=12.20.0'} 292 | 293 | '@tinyhttp/proxy-addr@2.2.0': 294 | resolution: {integrity: sha512-WM/PPL9xNvrs7/8Om5nhKbke5FHrP3EfjOOR+wBnjgESfibqn0K7wdUTnzSLp1lBmemr88os1XvzwymSgaibyA==} 295 | engines: {node: '>=12.20.0'} 296 | 297 | '@tinyhttp/req@2.2.4': 298 | resolution: {integrity: sha512-lQAZIAo0NOeghxFOZS57tQzxpHSPPLs9T68Krq2BncEBImKwqaDKUt7M9Y5Kb+rvC/GwIL3LeErhkg7f5iG4IQ==} 299 | engines: {node: '>=12.20.0'} 300 | 301 | '@tinyhttp/res@2.2.4': 302 | resolution: {integrity: sha512-ETBRShnO19oJyIg2XQHQoofXPWeTXPAuwnIVYkU8WaftvXd/Vz4y5+WFQDHUzKlmdGOw5fAFnrEU7pIVMeFeVA==} 303 | engines: {node: '>=12.20.0'} 304 | 305 | '@tinyhttp/router@2.2.3': 306 | resolution: {integrity: sha512-O0MQqWV3Vpg/uXsMYg19XsIgOhwjyhTYWh51Qng7bxqXixxx2PEvZWnFjP7c84K7kU/nUX41KpkEBTLnznk9/Q==} 307 | engines: {node: '>=12.20.0'} 308 | 309 | '@tinyhttp/send@2.2.3': 310 | resolution: {integrity: sha512-o4cVHHGQ8WjVBS8UT0EE/2WnjoybrfXikHwsRoNlG1pfrC/Sd01u1N4Te8cOd/9aNGLr4mGxWb5qTm2RRtEi7g==} 311 | engines: {node: '>=12.20.0'} 312 | 313 | '@tinyhttp/type-is@2.2.4': 314 | resolution: {integrity: sha512-7F328NheridwjIfefBB2j1PEcKKABpADgv7aCJaE8x8EON77ZFrAkI3Rir7pGjopV7V9MBmW88xUQigBEX2rmQ==} 315 | engines: {node: '>=12.20.0'} 316 | 317 | '@tinyhttp/url@2.1.1': 318 | resolution: {integrity: sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==} 319 | engines: {node: '>=12.20.0'} 320 | 321 | '@tinyhttp/vary@0.1.3': 322 | resolution: {integrity: sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==} 323 | engines: {node: '>=12.20'} 324 | 325 | '@types/istanbul-lib-coverage@2.0.6': 326 | resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} 327 | 328 | '@types/node@18.19.76': 329 | resolution: {integrity: sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==} 330 | 331 | ansi-regex@5.0.1: 332 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 333 | engines: {node: '>=8'} 334 | 335 | ansi-regex@6.1.0: 336 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 337 | engines: {node: '>=12'} 338 | 339 | ansi-styles@4.3.0: 340 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 341 | engines: {node: '>=8'} 342 | 343 | ansi-styles@6.2.1: 344 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 345 | engines: {node: '>=12'} 346 | 347 | balanced-match@1.0.2: 348 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 349 | 350 | brace-expansion@2.0.1: 351 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 352 | 353 | c8@10.1.2: 354 | resolution: {integrity: sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==} 355 | engines: {node: '>=18'} 356 | hasBin: true 357 | peerDependencies: 358 | monocart-coverage-reports: ^2 359 | peerDependenciesMeta: 360 | monocart-coverage-reports: 361 | optional: true 362 | 363 | cliui@8.0.1: 364 | resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 365 | engines: {node: '>=12'} 366 | 367 | color-convert@2.0.1: 368 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 369 | engines: {node: '>=7.0.0'} 370 | 371 | color-name@1.1.4: 372 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 373 | 374 | convert-source-map@2.0.0: 375 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 376 | 377 | cross-spawn@7.0.6: 378 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 379 | engines: {node: '>= 8'} 380 | 381 | eastasianwidth@0.2.0: 382 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 383 | 384 | emoji-regex@8.0.0: 385 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 386 | 387 | emoji-regex@9.2.2: 388 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 389 | 390 | es-escape-html@0.1.1: 391 | resolution: {integrity: sha512-yUx1o+8RsG7UlszmYPtks+dm6Lho2m8lgHMOsLJQsFI0R8XwUJwiMhM1M4E/S8QLeGyf6MkDV/pWgjQ0tdTSyQ==} 392 | engines: {node: '>=12.x'} 393 | 394 | esbuild@0.23.1: 395 | resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} 396 | engines: {node: '>=18'} 397 | hasBin: true 398 | 399 | escalade@3.2.0: 400 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 401 | engines: {node: '>=6'} 402 | 403 | find-up@5.0.0: 404 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 405 | engines: {node: '>=10'} 406 | 407 | foreground-child@3.3.0: 408 | resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} 409 | engines: {node: '>=14'} 410 | 411 | fsevents@2.3.3: 412 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 413 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 414 | os: [darwin] 415 | 416 | get-caller-file@2.0.5: 417 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 418 | engines: {node: 6.* || 8.* || >= 10.*} 419 | 420 | get-tsconfig@4.8.1: 421 | resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} 422 | 423 | glob@10.4.5: 424 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 425 | hasBin: true 426 | 427 | has-flag@4.0.0: 428 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 429 | engines: {node: '>=8'} 430 | 431 | header-range-parser@1.1.3: 432 | resolution: {integrity: sha512-B9zCFt3jH8g09LR1vHL4pcAn8yMEtlSlOUdQemzHMRKMImNIhhszdeosYFfNW0WXKQtXIlWB+O4owHJKvEJYaA==} 433 | engines: {node: '>=12.22.0'} 434 | 435 | html-escaper@2.0.2: 436 | resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 437 | 438 | ipaddr.js@2.2.0: 439 | resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} 440 | engines: {node: '>= 10'} 441 | 442 | is-fullwidth-code-point@3.0.0: 443 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 444 | engines: {node: '>=8'} 445 | 446 | isexe@2.0.0: 447 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 448 | 449 | istanbul-lib-coverage@3.2.2: 450 | resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 451 | engines: {node: '>=8'} 452 | 453 | istanbul-lib-report@3.0.1: 454 | resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} 455 | engines: {node: '>=10'} 456 | 457 | istanbul-reports@3.1.7: 458 | resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} 459 | engines: {node: '>=8'} 460 | 461 | jackspeak@3.4.3: 462 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 463 | 464 | locate-path@6.0.0: 465 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 466 | engines: {node: '>=10'} 467 | 468 | lru-cache@10.4.3: 469 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 470 | 471 | make-dir@4.0.0: 472 | resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 473 | engines: {node: '>=10'} 474 | 475 | mime@4.0.4: 476 | resolution: {integrity: sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==} 477 | engines: {node: '>=16'} 478 | hasBin: true 479 | 480 | minimatch@9.0.5: 481 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 482 | engines: {node: '>=16 || 14 >=14.17'} 483 | 484 | minipass@7.1.2: 485 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 486 | engines: {node: '>=16 || 14 >=14.17'} 487 | 488 | negotiator@0.6.4: 489 | resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} 490 | engines: {node: '>= 0.6'} 491 | 492 | p-limit@3.1.0: 493 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 494 | engines: {node: '>=10'} 495 | 496 | p-locate@5.0.0: 497 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 498 | engines: {node: '>=10'} 499 | 500 | package-json-from-dist@1.0.1: 501 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 502 | 503 | path-exists@4.0.0: 504 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 505 | engines: {node: '>=8'} 506 | 507 | path-key@3.1.1: 508 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 509 | engines: {node: '>=8'} 510 | 511 | path-scurry@1.11.1: 512 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 513 | engines: {node: '>=16 || 14 >=14.18'} 514 | 515 | regexparam@2.0.2: 516 | resolution: {integrity: sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==} 517 | engines: {node: '>=8'} 518 | 519 | require-directory@2.1.1: 520 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 521 | engines: {node: '>=0.10.0'} 522 | 523 | resolve-pkg-maps@1.0.0: 524 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 525 | 526 | semver@7.6.3: 527 | resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 528 | engines: {node: '>=10'} 529 | hasBin: true 530 | 531 | shebang-command@2.0.0: 532 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 533 | engines: {node: '>=8'} 534 | 535 | shebang-regex@3.0.0: 536 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 537 | engines: {node: '>=8'} 538 | 539 | signal-exit@4.1.0: 540 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 541 | engines: {node: '>=14'} 542 | 543 | string-width@4.2.3: 544 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 545 | engines: {node: '>=8'} 546 | 547 | string-width@5.1.2: 548 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 549 | engines: {node: '>=12'} 550 | 551 | strip-ansi@6.0.1: 552 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 553 | engines: {node: '>=8'} 554 | 555 | strip-ansi@7.1.0: 556 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 557 | engines: {node: '>=12'} 558 | 559 | supertest-fetch@2.0.0: 560 | resolution: {integrity: sha512-Mx2ZszLJkrBMFt7fmyML12y+u2yHAN5FF94yOzZXHzrTtsS59fjdbqh9G4OAPDM14jnkDkcLk2AgCVGJZcVoUg==} 561 | engines: {node: '>=18.0.0'} 562 | 563 | supports-color@7.2.0: 564 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 565 | engines: {node: '>=8'} 566 | 567 | test-exclude@7.0.1: 568 | resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} 569 | engines: {node: '>=18'} 570 | 571 | tsx@4.19.1: 572 | resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} 573 | engines: {node: '>=18.0.0'} 574 | hasBin: true 575 | 576 | typescript@5.6.2: 577 | resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} 578 | engines: {node: '>=14.17'} 579 | hasBin: true 580 | 581 | undici-types@5.26.5: 582 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 583 | 584 | v8-to-istanbul@9.3.0: 585 | resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} 586 | engines: {node: '>=10.12.0'} 587 | 588 | which@2.0.2: 589 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 590 | engines: {node: '>= 8'} 591 | hasBin: true 592 | 593 | wrap-ansi@7.0.0: 594 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 595 | engines: {node: '>=10'} 596 | 597 | wrap-ansi@8.1.0: 598 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 599 | engines: {node: '>=12'} 600 | 601 | y18n@5.0.8: 602 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 603 | engines: {node: '>=10'} 604 | 605 | yargs-parser@21.1.1: 606 | resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 607 | engines: {node: '>=12'} 608 | 609 | yargs@17.7.2: 610 | resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 611 | engines: {node: '>=12'} 612 | 613 | yocto-queue@0.1.0: 614 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 615 | engines: {node: '>=10'} 616 | 617 | snapshots: 618 | 619 | '@bcoe/v8-coverage@0.2.3': {} 620 | 621 | '@biomejs/biome@1.9.3': 622 | optionalDependencies: 623 | '@biomejs/cli-darwin-arm64': 1.9.3 624 | '@biomejs/cli-darwin-x64': 1.9.3 625 | '@biomejs/cli-linux-arm64': 1.9.3 626 | '@biomejs/cli-linux-arm64-musl': 1.9.3 627 | '@biomejs/cli-linux-x64': 1.9.3 628 | '@biomejs/cli-linux-x64-musl': 1.9.3 629 | '@biomejs/cli-win32-arm64': 1.9.3 630 | '@biomejs/cli-win32-x64': 1.9.3 631 | 632 | '@biomejs/cli-darwin-arm64@1.9.3': 633 | optional: true 634 | 635 | '@biomejs/cli-darwin-x64@1.9.3': 636 | optional: true 637 | 638 | '@biomejs/cli-linux-arm64-musl@1.9.3': 639 | optional: true 640 | 641 | '@biomejs/cli-linux-arm64@1.9.3': 642 | optional: true 643 | 644 | '@biomejs/cli-linux-x64-musl@1.9.3': 645 | optional: true 646 | 647 | '@biomejs/cli-linux-x64@1.9.3': 648 | optional: true 649 | 650 | '@biomejs/cli-win32-arm64@1.9.3': 651 | optional: true 652 | 653 | '@biomejs/cli-win32-x64@1.9.3': 654 | optional: true 655 | 656 | '@esbuild/aix-ppc64@0.23.1': 657 | optional: true 658 | 659 | '@esbuild/android-arm64@0.23.1': 660 | optional: true 661 | 662 | '@esbuild/android-arm@0.23.1': 663 | optional: true 664 | 665 | '@esbuild/android-x64@0.23.1': 666 | optional: true 667 | 668 | '@esbuild/darwin-arm64@0.23.1': 669 | optional: true 670 | 671 | '@esbuild/darwin-x64@0.23.1': 672 | optional: true 673 | 674 | '@esbuild/freebsd-arm64@0.23.1': 675 | optional: true 676 | 677 | '@esbuild/freebsd-x64@0.23.1': 678 | optional: true 679 | 680 | '@esbuild/linux-arm64@0.23.1': 681 | optional: true 682 | 683 | '@esbuild/linux-arm@0.23.1': 684 | optional: true 685 | 686 | '@esbuild/linux-ia32@0.23.1': 687 | optional: true 688 | 689 | '@esbuild/linux-loong64@0.23.1': 690 | optional: true 691 | 692 | '@esbuild/linux-mips64el@0.23.1': 693 | optional: true 694 | 695 | '@esbuild/linux-ppc64@0.23.1': 696 | optional: true 697 | 698 | '@esbuild/linux-riscv64@0.23.1': 699 | optional: true 700 | 701 | '@esbuild/linux-s390x@0.23.1': 702 | optional: true 703 | 704 | '@esbuild/linux-x64@0.23.1': 705 | optional: true 706 | 707 | '@esbuild/netbsd-x64@0.23.1': 708 | optional: true 709 | 710 | '@esbuild/openbsd-arm64@0.23.1': 711 | optional: true 712 | 713 | '@esbuild/openbsd-x64@0.23.1': 714 | optional: true 715 | 716 | '@esbuild/sunos-x64@0.23.1': 717 | optional: true 718 | 719 | '@esbuild/win32-arm64@0.23.1': 720 | optional: true 721 | 722 | '@esbuild/win32-ia32@0.23.1': 723 | optional: true 724 | 725 | '@esbuild/win32-x64@0.23.1': 726 | optional: true 727 | 728 | '@isaacs/cliui@8.0.2': 729 | dependencies: 730 | string-width: 5.1.2 731 | string-width-cjs: string-width@4.2.3 732 | strip-ansi: 7.1.0 733 | strip-ansi-cjs: strip-ansi@6.0.1 734 | wrap-ansi: 8.1.0 735 | wrap-ansi-cjs: wrap-ansi@7.0.0 736 | 737 | '@istanbuljs/schema@0.1.3': {} 738 | 739 | '@jridgewell/resolve-uri@3.1.2': {} 740 | 741 | '@jridgewell/sourcemap-codec@1.5.0': {} 742 | 743 | '@jridgewell/trace-mapping@0.3.25': 744 | dependencies: 745 | '@jridgewell/resolve-uri': 3.1.2 746 | '@jridgewell/sourcemap-codec': 1.5.0 747 | 748 | '@pkgjs/parseargs@0.11.0': 749 | optional: true 750 | 751 | '@tinyhttp/accepts@2.2.3': 752 | dependencies: 753 | mime: 4.0.4 754 | negotiator: 0.6.4 755 | 756 | '@tinyhttp/app@2.4.0': 757 | dependencies: 758 | '@tinyhttp/cookie': 2.1.1 759 | '@tinyhttp/proxy-addr': 2.2.0 760 | '@tinyhttp/req': 2.2.4 761 | '@tinyhttp/res': 2.2.4 762 | '@tinyhttp/router': 2.2.3 763 | header-range-parser: 1.1.3 764 | regexparam: 2.0.2 765 | 766 | '@tinyhttp/content-disposition@2.2.2': {} 767 | 768 | '@tinyhttp/content-type@0.1.4': {} 769 | 770 | '@tinyhttp/cookie-signature@2.1.1': {} 771 | 772 | '@tinyhttp/cookie@2.1.1': {} 773 | 774 | '@tinyhttp/encode-url@2.1.1': {} 775 | 776 | '@tinyhttp/etag@2.1.2': {} 777 | 778 | '@tinyhttp/forwarded@2.1.1': {} 779 | 780 | '@tinyhttp/proxy-addr@2.2.0': 781 | dependencies: 782 | '@tinyhttp/forwarded': 2.1.1 783 | ipaddr.js: 2.2.0 784 | 785 | '@tinyhttp/req@2.2.4': 786 | dependencies: 787 | '@tinyhttp/accepts': 2.2.3 788 | '@tinyhttp/type-is': 2.2.4 789 | '@tinyhttp/url': 2.1.1 790 | header-range-parser: 1.1.3 791 | 792 | '@tinyhttp/res@2.2.4': 793 | dependencies: 794 | '@tinyhttp/content-disposition': 2.2.2 795 | '@tinyhttp/cookie': 2.1.1 796 | '@tinyhttp/cookie-signature': 2.1.1 797 | '@tinyhttp/encode-url': 2.1.1 798 | '@tinyhttp/req': 2.2.4 799 | '@tinyhttp/send': 2.2.3 800 | '@tinyhttp/vary': 0.1.3 801 | es-escape-html: 0.1.1 802 | mime: 4.0.4 803 | 804 | '@tinyhttp/router@2.2.3': {} 805 | 806 | '@tinyhttp/send@2.2.3': 807 | dependencies: 808 | '@tinyhttp/content-type': 0.1.4 809 | '@tinyhttp/etag': 2.1.2 810 | mime: 4.0.4 811 | 812 | '@tinyhttp/type-is@2.2.4': 813 | dependencies: 814 | '@tinyhttp/content-type': 0.1.4 815 | mime: 4.0.4 816 | 817 | '@tinyhttp/url@2.1.1': {} 818 | 819 | '@tinyhttp/vary@0.1.3': {} 820 | 821 | '@types/istanbul-lib-coverage@2.0.6': {} 822 | 823 | '@types/node@18.19.76': 824 | dependencies: 825 | undici-types: 5.26.5 826 | 827 | ansi-regex@5.0.1: {} 828 | 829 | ansi-regex@6.1.0: {} 830 | 831 | ansi-styles@4.3.0: 832 | dependencies: 833 | color-convert: 2.0.1 834 | 835 | ansi-styles@6.2.1: {} 836 | 837 | balanced-match@1.0.2: {} 838 | 839 | brace-expansion@2.0.1: 840 | dependencies: 841 | balanced-match: 1.0.2 842 | 843 | c8@10.1.2: 844 | dependencies: 845 | '@bcoe/v8-coverage': 0.2.3 846 | '@istanbuljs/schema': 0.1.3 847 | find-up: 5.0.0 848 | foreground-child: 3.3.0 849 | istanbul-lib-coverage: 3.2.2 850 | istanbul-lib-report: 3.0.1 851 | istanbul-reports: 3.1.7 852 | test-exclude: 7.0.1 853 | v8-to-istanbul: 9.3.0 854 | yargs: 17.7.2 855 | yargs-parser: 21.1.1 856 | 857 | cliui@8.0.1: 858 | dependencies: 859 | string-width: 4.2.3 860 | strip-ansi: 6.0.1 861 | wrap-ansi: 7.0.0 862 | 863 | color-convert@2.0.1: 864 | dependencies: 865 | color-name: 1.1.4 866 | 867 | color-name@1.1.4: {} 868 | 869 | convert-source-map@2.0.0: {} 870 | 871 | cross-spawn@7.0.6: 872 | dependencies: 873 | path-key: 3.1.1 874 | shebang-command: 2.0.0 875 | which: 2.0.2 876 | 877 | eastasianwidth@0.2.0: {} 878 | 879 | emoji-regex@8.0.0: {} 880 | 881 | emoji-regex@9.2.2: {} 882 | 883 | es-escape-html@0.1.1: {} 884 | 885 | esbuild@0.23.1: 886 | optionalDependencies: 887 | '@esbuild/aix-ppc64': 0.23.1 888 | '@esbuild/android-arm': 0.23.1 889 | '@esbuild/android-arm64': 0.23.1 890 | '@esbuild/android-x64': 0.23.1 891 | '@esbuild/darwin-arm64': 0.23.1 892 | '@esbuild/darwin-x64': 0.23.1 893 | '@esbuild/freebsd-arm64': 0.23.1 894 | '@esbuild/freebsd-x64': 0.23.1 895 | '@esbuild/linux-arm': 0.23.1 896 | '@esbuild/linux-arm64': 0.23.1 897 | '@esbuild/linux-ia32': 0.23.1 898 | '@esbuild/linux-loong64': 0.23.1 899 | '@esbuild/linux-mips64el': 0.23.1 900 | '@esbuild/linux-ppc64': 0.23.1 901 | '@esbuild/linux-riscv64': 0.23.1 902 | '@esbuild/linux-s390x': 0.23.1 903 | '@esbuild/linux-x64': 0.23.1 904 | '@esbuild/netbsd-x64': 0.23.1 905 | '@esbuild/openbsd-arm64': 0.23.1 906 | '@esbuild/openbsd-x64': 0.23.1 907 | '@esbuild/sunos-x64': 0.23.1 908 | '@esbuild/win32-arm64': 0.23.1 909 | '@esbuild/win32-ia32': 0.23.1 910 | '@esbuild/win32-x64': 0.23.1 911 | 912 | escalade@3.2.0: {} 913 | 914 | find-up@5.0.0: 915 | dependencies: 916 | locate-path: 6.0.0 917 | path-exists: 4.0.0 918 | 919 | foreground-child@3.3.0: 920 | dependencies: 921 | cross-spawn: 7.0.6 922 | signal-exit: 4.1.0 923 | 924 | fsevents@2.3.3: 925 | optional: true 926 | 927 | get-caller-file@2.0.5: {} 928 | 929 | get-tsconfig@4.8.1: 930 | dependencies: 931 | resolve-pkg-maps: 1.0.0 932 | 933 | glob@10.4.5: 934 | dependencies: 935 | foreground-child: 3.3.0 936 | jackspeak: 3.4.3 937 | minimatch: 9.0.5 938 | minipass: 7.1.2 939 | package-json-from-dist: 1.0.1 940 | path-scurry: 1.11.1 941 | 942 | has-flag@4.0.0: {} 943 | 944 | header-range-parser@1.1.3: {} 945 | 946 | html-escaper@2.0.2: {} 947 | 948 | ipaddr.js@2.2.0: {} 949 | 950 | is-fullwidth-code-point@3.0.0: {} 951 | 952 | isexe@2.0.0: {} 953 | 954 | istanbul-lib-coverage@3.2.2: {} 955 | 956 | istanbul-lib-report@3.0.1: 957 | dependencies: 958 | istanbul-lib-coverage: 3.2.2 959 | make-dir: 4.0.0 960 | supports-color: 7.2.0 961 | 962 | istanbul-reports@3.1.7: 963 | dependencies: 964 | html-escaper: 2.0.2 965 | istanbul-lib-report: 3.0.1 966 | 967 | jackspeak@3.4.3: 968 | dependencies: 969 | '@isaacs/cliui': 8.0.2 970 | optionalDependencies: 971 | '@pkgjs/parseargs': 0.11.0 972 | 973 | locate-path@6.0.0: 974 | dependencies: 975 | p-locate: 5.0.0 976 | 977 | lru-cache@10.4.3: {} 978 | 979 | make-dir@4.0.0: 980 | dependencies: 981 | semver: 7.6.3 982 | 983 | mime@4.0.4: {} 984 | 985 | minimatch@9.0.5: 986 | dependencies: 987 | brace-expansion: 2.0.1 988 | 989 | minipass@7.1.2: {} 990 | 991 | negotiator@0.6.4: {} 992 | 993 | p-limit@3.1.0: 994 | dependencies: 995 | yocto-queue: 0.1.0 996 | 997 | p-locate@5.0.0: 998 | dependencies: 999 | p-limit: 3.1.0 1000 | 1001 | package-json-from-dist@1.0.1: {} 1002 | 1003 | path-exists@4.0.0: {} 1004 | 1005 | path-key@3.1.1: {} 1006 | 1007 | path-scurry@1.11.1: 1008 | dependencies: 1009 | lru-cache: 10.4.3 1010 | minipass: 7.1.2 1011 | 1012 | regexparam@2.0.2: {} 1013 | 1014 | require-directory@2.1.1: {} 1015 | 1016 | resolve-pkg-maps@1.0.0: {} 1017 | 1018 | semver@7.6.3: {} 1019 | 1020 | shebang-command@2.0.0: 1021 | dependencies: 1022 | shebang-regex: 3.0.0 1023 | 1024 | shebang-regex@3.0.0: {} 1025 | 1026 | signal-exit@4.1.0: {} 1027 | 1028 | string-width@4.2.3: 1029 | dependencies: 1030 | emoji-regex: 8.0.0 1031 | is-fullwidth-code-point: 3.0.0 1032 | strip-ansi: 6.0.1 1033 | 1034 | string-width@5.1.2: 1035 | dependencies: 1036 | eastasianwidth: 0.2.0 1037 | emoji-regex: 9.2.2 1038 | strip-ansi: 7.1.0 1039 | 1040 | strip-ansi@6.0.1: 1041 | dependencies: 1042 | ansi-regex: 5.0.1 1043 | 1044 | strip-ansi@7.1.0: 1045 | dependencies: 1046 | ansi-regex: 6.1.0 1047 | 1048 | supertest-fetch@2.0.0: {} 1049 | 1050 | supports-color@7.2.0: 1051 | dependencies: 1052 | has-flag: 4.0.0 1053 | 1054 | test-exclude@7.0.1: 1055 | dependencies: 1056 | '@istanbuljs/schema': 0.1.3 1057 | glob: 10.4.5 1058 | minimatch: 9.0.5 1059 | 1060 | tsx@4.19.1: 1061 | dependencies: 1062 | esbuild: 0.23.1 1063 | get-tsconfig: 4.8.1 1064 | optionalDependencies: 1065 | fsevents: 2.3.3 1066 | 1067 | typescript@5.6.2: {} 1068 | 1069 | undici-types@5.26.5: {} 1070 | 1071 | v8-to-istanbul@9.3.0: 1072 | dependencies: 1073 | '@jridgewell/trace-mapping': 0.3.25 1074 | '@types/istanbul-lib-coverage': 2.0.6 1075 | convert-source-map: 2.0.0 1076 | 1077 | which@2.0.2: 1078 | dependencies: 1079 | isexe: 2.0.0 1080 | 1081 | wrap-ansi@7.0.0: 1082 | dependencies: 1083 | ansi-styles: 4.3.0 1084 | string-width: 4.2.3 1085 | strip-ansi: 6.0.1 1086 | 1087 | wrap-ansi@8.1.0: 1088 | dependencies: 1089 | ansi-styles: 6.2.1 1090 | string-width: 5.1.2 1091 | strip-ansi: 7.1.0 1092 | 1093 | y18n@5.0.8: {} 1094 | 1095 | yargs-parser@21.1.1: {} 1096 | 1097 | yargs@17.7.2: 1098 | dependencies: 1099 | cliui: 8.0.1 1100 | escalade: 3.2.0 1101 | get-caller-file: 2.0.5 1102 | require-directory: 2.1.1 1103 | string-width: 4.2.3 1104 | y18n: 5.0.8 1105 | yargs-parser: 21.1.1 1106 | 1107 | yocto-queue@0.1.0: {} 1108 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Buffer, File } from 'node:buffer' 2 | import type { IncomingMessage, ServerResponse as Response } from 'node:http' 3 | 4 | type NextFunction = (err?: any) => void 5 | 6 | /** 7 | * Request extension with a body 8 | */ 9 | export type ReqWithBody = IncomingMessage & { 10 | body?: T 11 | } 12 | 13 | export const hasBody = (method: string) => ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) 14 | 15 | const defaultPayloadLimit = 102400 // 100KB 16 | 17 | export type LimitErrorFn = (limit: number) => Error 18 | 19 | export type ParserOptions = Partial<{ 20 | /** 21 | * Limit payload size (in bytes) 22 | * @default '100KB' 23 | */ 24 | payloadLimit: number 25 | /** 26 | * Custom error function for payload limit 27 | */ 28 | payloadLimitErrorFn: LimitErrorFn 29 | }> 30 | 31 | const defaultErrorFn: LimitErrorFn = (payloadLimit) => new Error(`Payload too large. Limit: ${payloadLimit} bytes`) 32 | 33 | // Main function 34 | export const p = 35 | ( 36 | fn: (body: Buffer) => void, 37 | payloadLimit = defaultPayloadLimit, 38 | payloadLimitErrorFn: LimitErrorFn = defaultErrorFn 39 | ) => 40 | async (req: ReqWithBody, _res: Response, next?: (err?: any) => void) => { 41 | try { 42 | const body: Buffer[] = [] 43 | 44 | for await (const chunk of req) { 45 | const totalSize = body.reduce((total, buffer) => total + buffer.byteLength, 0) 46 | if (totalSize > payloadLimit) throw payloadLimitErrorFn(payloadLimit) 47 | body.push(chunk as Buffer) 48 | } 49 | 50 | return fn(Buffer.concat(body)) 51 | } catch (e) { 52 | next?.(e) 53 | } 54 | } 55 | 56 | /** 57 | * Parse payload with a custom function 58 | * @param fn 59 | */ 60 | const custom = 61 | (fn: (body: Buffer) => any) => 62 | async (req: ReqWithBody, _res: Response, next?: NextFunction) => { 63 | if (hasBody(req.method!)) req.body = await p(fn)(req, _res, next) 64 | next?.() 65 | } 66 | 67 | /** 68 | * Parse JSON payload 69 | * @param options 70 | */ 71 | const json = 72 | ({ payloadLimit, payloadLimitErrorFn }: ParserOptions = {}) => 73 | async (req: ReqWithBody, res: Response, next?: NextFunction) => { 74 | if (hasBody(req.method!)) { 75 | req.body = await p( 76 | (x) => { 77 | const str = td.decode(x) 78 | return str ? JSON.parse(str) : {} 79 | }, 80 | payloadLimit, 81 | payloadLimitErrorFn 82 | )(req, res, next) 83 | } 84 | next?.() 85 | } 86 | 87 | /** 88 | * Parse raw payload 89 | * @param options 90 | */ 91 | const raw = 92 | ({ payloadLimit, payloadLimitErrorFn }: ParserOptions = {}) => 93 | async (req: ReqWithBody, _res: Response, next?: NextFunction) => { 94 | if (hasBody(req.method!)) { 95 | req.body = await p((x) => x, payloadLimit, payloadLimitErrorFn)(req, _res, next) 96 | } 97 | next?.() 98 | } 99 | 100 | const td = new TextDecoder() 101 | /** 102 | * Stringify request payload 103 | * @param param0 104 | * @returns 105 | */ 106 | const text = 107 | ({ payloadLimit, payloadLimitErrorFn }: ParserOptions = {}) => 108 | async (req: ReqWithBody, _res: Response, next?: NextFunction) => { 109 | if (hasBody(req.method!)) { 110 | req.body = await p((x) => td.decode(x), payloadLimit, payloadLimitErrorFn)(req, _res, next) 111 | } 112 | next?.() 113 | } 114 | 115 | /** 116 | * Parse urlencoded payload 117 | * @param options 118 | */ 119 | const urlencoded = 120 | ({ payloadLimit, payloadLimitErrorFn }: ParserOptions = {}) => 121 | async (req: ReqWithBody, _res: Response, next?: NextFunction) => { 122 | if (hasBody(req.method!)) { 123 | req.body = await p( 124 | (x) => Object.fromEntries(new URLSearchParams(x.toString()).entries()), 125 | payloadLimit, 126 | payloadLimitErrorFn 127 | )(req, _res, next) 128 | } 129 | next?.() 130 | } 131 | 132 | const getBoundary = (contentType: string) => { 133 | const match = /boundary=(.+);?/.exec(contentType) 134 | return match ? `--${match[1]}` : null 135 | } 136 | 137 | const defaultFileSizeLimitErrorFn: LimitErrorFn = (limit) => new Error(`File too large. Limit: ${limit} bytes`) 138 | 139 | const defaultFileSizeLimit = 200 * 1024 * 1024 140 | 141 | const parseMultipart = ( 142 | body: string, 143 | boundary: string, 144 | { 145 | fileCountLimit, 146 | fileSizeLimit = defaultFileSizeLimit, 147 | fileSizeLimitErrorFn = defaultFileSizeLimitErrorFn 148 | }: MultipartOptions 149 | ) => { 150 | const parts = body.split(new RegExp(`${boundary}(--)?`)).filter((part) => !!part && /content-disposition/i.test(part)) 151 | const parsedBody: Record = {} 152 | 153 | if (fileCountLimit && parts.length > fileCountLimit) throw new Error(`Too many files. Limit: ${fileCountLimit}`) 154 | 155 | // biome-ignore lint/complexity/noForEach: for...of fails 156 | parts.forEach((part) => { 157 | const [headers, ...lines] = part.split('\r\n').filter((part) => !!part) 158 | const data = lines.join('\r\n').trim() 159 | 160 | if (data.length > fileSizeLimit) throw fileSizeLimitErrorFn(fileSizeLimit) 161 | 162 | // Extract the name and filename from the headers 163 | const name = /name="(.+?)"/.exec(headers)![1] 164 | const filename = /filename="(.+?)"/.exec(headers) 165 | if (filename) { 166 | const contentTypeMatch = /Content-Type: (.+)/i.exec(data)! 167 | const fileContent = data.slice(contentTypeMatch[0].length + 2) 168 | 169 | const file = new File([fileContent], filename[1], { type: contentTypeMatch[1] }) 170 | 171 | parsedBody[name] = parsedBody[name] ? [...parsedBody[name], file] : [file] 172 | return 173 | } 174 | parsedBody[name] = parsedBody[name] ? [...parsedBody[name], data] : [data] 175 | return 176 | }) 177 | 178 | return parsedBody 179 | } 180 | type MultipartOptions = Partial<{ 181 | /** 182 | * Limit number of files 183 | */ 184 | fileCountLimit: number 185 | /** 186 | * Limit file size (in bytes) 187 | */ 188 | fileSizeLimit: number 189 | /** 190 | * Custom error function for file size limit 191 | */ 192 | fileSizeLimitErrorFn: LimitErrorFn 193 | }> 194 | /** 195 | * Parse multipart form data (supports files as well) 196 | * 197 | * Does not restrict total payload size by default. 198 | * @param options 199 | */ 200 | const multipart = 201 | ({ payloadLimit = Number.POSITIVE_INFINITY, payloadLimitErrorFn, ...opts }: MultipartOptions & ParserOptions = {}) => 202 | async (req: ReqWithBody, res: Response, next?: NextFunction) => { 203 | if (hasBody(req.method!)) { 204 | req.body = await p( 205 | (x) => { 206 | const boundary = getBoundary(req.headers['content-type']!) 207 | if (boundary) return parseMultipart(td.decode(x), boundary, opts) 208 | return {} 209 | }, 210 | payloadLimit, 211 | payloadLimitErrorFn 212 | )(req, res, next) 213 | } 214 | next?.() 215 | } 216 | 217 | export { custom, json, raw, text, urlencoded, multipart } 218 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import { Buffer } from 'node:buffer' 3 | import { File } from 'node:buffer' 4 | import { createServer } from 'node:http' 5 | import { describe, it } from 'node:test' 6 | import { App } from '@tinyhttp/app' 7 | import { makeFetch } from 'supertest-fetch' 8 | import { type ReqWithBody, custom, json, multipart, raw, text, urlencoded } from './src/index.js' 9 | 10 | const td = new TextDecoder() 11 | 12 | describe('Basic parsing', () => { 13 | it('should parse JSON body', async () => { 14 | const server = createServer(async (req: ReqWithBody, res) => { 15 | await json()(req, res, (err) => err && console.log(err)) 16 | 17 | res.setHeader('Content-Type', 'application/json') 18 | 19 | res.end(JSON.stringify(req.body)) 20 | }) 21 | 22 | await makeFetch(server)('/', { 23 | body: JSON.stringify({ hello: 'world' }), 24 | method: 'POST', 25 | headers: { 26 | Accept: 'application/json', 27 | 'Content-Type': 'application/json' 28 | } 29 | }).expect(200, { hello: 'world' }) 30 | }) 31 | 32 | it('should ignore JSON empty body', async () => { 33 | const server = createServer(async (req: ReqWithBody, res) => { 34 | await json()(req, res, (err) => err && console.log(err)) 35 | 36 | res.setHeader('Content-Type', 'application/json') 37 | 38 | res.end(JSON.stringify({ ok: true })) 39 | }) 40 | 41 | // Empty string body 42 | await makeFetch(server)('/', { 43 | body: '', 44 | method: 'POST', 45 | headers: { 46 | Accept: 'application/json', 47 | 'Content-Type': 'application/json' 48 | } 49 | }).expect(200, { ok: true }) 50 | 51 | // Unset body 52 | await makeFetch(server)('/', { 53 | method: 'POST', 54 | headers: { 55 | Accept: 'application/json', 56 | 'Content-Type': 'application/json' 57 | } 58 | }).expect(200, { ok: true }) 59 | }) 60 | 61 | it('should parse json body with no content-type headers', async () => { 62 | const server = createServer(async (req: ReqWithBody, res) => { 63 | await json()(req, res, (err) => err && console.log(err)) 64 | 65 | res.setHeader('Content-Type', 'application/json') 66 | 67 | res.end(JSON.stringify(req.body)) 68 | }) 69 | 70 | await makeFetch(server)('/', { 71 | body: JSON.stringify({ hello: 'world' }), 72 | method: 'POST', 73 | headers: { 74 | Accept: 'application/json' 75 | } 76 | }).expect(200, { hello: 'world' }) 77 | }) 78 | 79 | it('json should call next() without a body', async () => { 80 | const server = createServer(async (req: ReqWithBody, res) => { 81 | await json()(req, res, (err) => err && console.log(err)) 82 | 83 | res.setHeader('Content-Type', 'application/json') 84 | 85 | res.end() 86 | }) 87 | 88 | await makeFetch(server)('/', { 89 | method: 'POST', 90 | headers: { 91 | Accept: 'application/json', 92 | 'Content-Type': 'application/json' 93 | } 94 | }).expect(200) 95 | }) 96 | 97 | it('json should ignore GET request', async () => { 98 | const server = createServer(async (req: ReqWithBody, res) => { 99 | await json()(req, res, (err) => err && console.log(err)) 100 | 101 | res.end('GET is ignored') 102 | }) 103 | 104 | await makeFetch(server)('/', { 105 | method: 'GET' 106 | }).expect(200, 'GET is ignored') 107 | }) 108 | 109 | it('should parse urlencoded body', async () => { 110 | const server = createServer(async (req: ReqWithBody, res) => { 111 | await urlencoded()(req, res, (err) => err && console.log(err)) 112 | 113 | res.setHeader('Content-Type', 'application/x-www-form-urlencoded') 114 | 115 | res.end(JSON.stringify(req.body)) 116 | }) 117 | 118 | await makeFetch(server)('/', { 119 | body: 'hello=world', 120 | method: 'POST', 121 | headers: { 122 | Accept: 'application/x-www-form-urlencoded', 123 | 'Content-Type': 'application/x-www-form-urlencoded' 124 | } 125 | }).expect(200, { hello: 'world' }) 126 | }) 127 | 128 | it('urlencoded should ignore GET request', async () => { 129 | const server = createServer(async (req: ReqWithBody, res) => { 130 | await urlencoded()(req, res, (err) => err && console.log(err)) 131 | 132 | res.end('GET is ignored') 133 | }) 134 | 135 | await makeFetch(server)('/', { 136 | method: 'GET' 137 | }).expect(200, 'GET is ignored') 138 | }) 139 | 140 | it('should parse text body', async () => { 141 | const server = createServer(async (req: ReqWithBody, res) => { 142 | await text()(req, res, (err) => err && console.log(err)) 143 | 144 | res.setHeader('Content-Type', 'text/plain') 145 | 146 | res.end(req.body) 147 | }) 148 | 149 | await makeFetch(server)('/', { 150 | body: 'hello world', 151 | method: 'POST', 152 | headers: { 153 | Accept: 'text/plain', 154 | 'Content-Type': 'text/plain' 155 | } 156 | }).expect(200, 'hello world') 157 | }) 158 | 159 | it('text should ignore GET request', async () => { 160 | const server = createServer(async (req: ReqWithBody, res) => { 161 | await text()(req, res, (err) => err && console.log(err)) 162 | 163 | res.setHeader('Content-Type', 'text/plain') 164 | 165 | res.end('GET is ignored') 166 | }) 167 | 168 | await makeFetch(server)('/', { 169 | method: 'GET', 170 | headers: { 171 | Accept: 'text/plain', 172 | 'Content-Type': 'text/plain' 173 | } 174 | }).expect(200, 'GET is ignored') 175 | }) 176 | 177 | it('should parse raw body', async () => { 178 | const server = createServer(async (req: ReqWithBody, res) => { 179 | await raw()(req, res, (err) => err && console.log(err)) 180 | 181 | res.setHeader('Content-Type', 'text/plain') 182 | 183 | res.end(req.body) 184 | }) 185 | 186 | await makeFetch(server)('/', { 187 | body: 'hello world', 188 | method: 'POST', 189 | headers: { 190 | Accept: 'text/plain', 191 | 'Content-Type': 'text/plain' 192 | } 193 | }).expect(200, 'hello world') 194 | }) 195 | 196 | it('raw should ignore GET request', async () => { 197 | const server = createServer(async (req: ReqWithBody, res) => { 198 | await raw()(req, res, (err) => err && console.log(err)) 199 | 200 | res.setHeader('Content-Type', 'text/plain') 201 | 202 | res.end('GET is ignored') 203 | }) 204 | 205 | await makeFetch(server)('/', { 206 | method: 'GET', 207 | headers: { 208 | Accept: 'text/plain', 209 | 'Content-Type': 'text/plain' 210 | } 211 | }).expect(200, 'GET is ignored') 212 | }) 213 | 214 | it('should parse custom body', async () => { 215 | const server = createServer(async (req: ReqWithBody, res) => { 216 | await custom((d) => td.decode(d).toUpperCase())(req, res, (err) => err && console.log(err)) 217 | 218 | res.setHeader('Content-Type', 'text/plain') 219 | 220 | res.end(req.body) 221 | }) 222 | 223 | await makeFetch(server)('/', { 224 | body: 'hello world', 225 | method: 'POST', 226 | headers: { 227 | Accept: 'text/plain', 228 | 'Content-Type': 'text/plain' 229 | } 230 | }).expect(200, 'HELLO WORLD') 231 | }) 232 | 233 | it('custom should ignore GET request', async () => { 234 | const server = createServer(async (req: ReqWithBody, res) => { 235 | await custom((d) => td.decode(d).toUpperCase())(req, res, (err) => err && console.log(err)) 236 | 237 | res.setHeader('Content-Type', 'text/plain') 238 | 239 | res.end('GET is ignored') 240 | }) 241 | 242 | await makeFetch(server)('/', { 243 | method: 'GET', 244 | headers: { 245 | Accept: 'text/plain', 246 | 'Content-Type': 'text/plain' 247 | } 248 | }).expect(200, 'GET is ignored') 249 | }) 250 | }) 251 | 252 | describe('Multipart', () => { 253 | it('should parse multipart body', async () => { 254 | const server = createServer(async (req: ReqWithBody, res) => { 255 | await multipart()(req, res, (err) => err && console.log(err)) 256 | res.end(JSON.stringify(req.body)) 257 | }) 258 | 259 | const fd = new FormData() 260 | 261 | fd.set('textfield', 'textfield data\nwith new lines\nbecause this is valid') 262 | fd.set('someother', 'textfield with text') 263 | 264 | await makeFetch(server)('/', { 265 | // probaly better to use form-data package 266 | body: fd, 267 | method: 'POST' 268 | }).expect(200, { 269 | textfield: ['textfield data\r\nwith new lines\r\nbecause this is valid'], 270 | someother: ['textfield with text'] 271 | }) 272 | }) 273 | 274 | it('should not parse if boundary is not present', async () => { 275 | const server = createServer(async (req: ReqWithBody, res) => { 276 | await multipart()(req, res, (err) => err && res.end(err)) 277 | 278 | res.end(JSON.stringify(req.body)) 279 | }) 280 | 281 | const fd = new FormData() 282 | 283 | fd.set('textfield', 'textfield data\nwith new lines\nbecause this is valid') 284 | 285 | await makeFetch(server)('/', { 286 | body: fd, 287 | method: 'POST', 288 | headers: { 289 | Accept: 'multipart/form-data', 290 | // we override Content-Type so that boundary is not present 291 | 'Content-Type': 'multipart/form-data' 292 | } 293 | }).expect(200, {}) 294 | }) 295 | 296 | it('should parse multipart with boundary', async () => { 297 | const server = createServer(async (req: ReqWithBody, res) => { 298 | await multipart()(req, res, (err) => err && res.end(err)) 299 | 300 | res.end(JSON.stringify(req.body)) 301 | }) 302 | 303 | await makeFetch(server)('/', { 304 | // probaly better to use form-data package 305 | body: '--some-boundary\r\nContent-Disposition: form-data; name="textfield"\r\n\r\ntextfield data\nwith new lines\nbecause this is valid\r\n--some-boundary\r\nContent-Disposition: form-data; name="someother"\r\n\r\ntextfield with text\r\n--some-boundary--\r\n', 306 | method: 'POST', 307 | headers: { 308 | Accept: 'multipart/form-data', 309 | 'Content-Type': 'multipart/form-data; boundary=some-boundary' 310 | } 311 | }).expect(200, { 312 | textfield: ['textfield data\nwith new lines\nbecause this is valid'], 313 | someother: ['textfield with text'] 314 | }) 315 | }) 316 | 317 | it('should parse an array of multipart values', async () => { 318 | const server = createServer(async (req: ReqWithBody, res) => { 319 | await multipart()(req, res, (err) => err && console.log(err)) 320 | 321 | res.end(JSON.stringify(req.body)) 322 | }) 323 | 324 | const fd = new FormData() 325 | 326 | fd.set('textfield', 'textfield data\nwith new lines\nbecause this is valid') 327 | fd.append('textfield', 'textfield with text') 328 | 329 | await makeFetch(server)('/', { 330 | // probaly better to use form-data package 331 | body: fd, 332 | method: 'POST' 333 | }).expect(200, { 334 | textfield: ['textfield data\r\nwith new lines\r\nbecause this is valid', 'textfield with text'] 335 | }) 336 | }) 337 | 338 | it('multipart should ignore GET request', async () => { 339 | const server = createServer(async (req: ReqWithBody, res) => { 340 | await multipart()(req, res, (err) => err && console.log(err)) 341 | 342 | res.end('GET is ignored') 343 | }) 344 | 345 | await makeFetch(server)('/', { 346 | method: 'GET', 347 | headers: { 348 | Accept: 'multipart/form-data', 349 | 'Content-Type': 'multipart/form-data; boundary=some-boundary' 350 | } 351 | }).expect(200, 'GET is ignored') 352 | }) 353 | 354 | it('should parse multipart with files', async () => { 355 | const fd = new FormData() 356 | const file = new File(['hello world'], 'hello.txt', { type: 'text/plain' }) 357 | fd.set('file', file as Blob) 358 | const server = createServer(async (req: ReqWithBody<{ file: [File] }>, res) => { 359 | await multipart()(req, res, (err) => err && console.log(err)) 360 | 361 | const formBuf = new Uint8Array(await file.arrayBuffer()) 362 | const buf = new Uint8Array(await req.body!.file[0].arrayBuffer()) 363 | 364 | assert.equal(Buffer.compare(buf, formBuf), 0) 365 | 366 | res.end(req.body?.file[0].name) 367 | }) 368 | 369 | await makeFetch(server)('/', { 370 | // probaly better to use form-data package 371 | body: fd, 372 | method: 'POST' 373 | }).expect(200, 'hello.txt') 374 | }) 375 | 376 | it('should support multiple files', async () => { 377 | const fd = new FormData() 378 | 379 | const files = [ 380 | new File(['hello world'], 'hello.txt', { type: 'text/plain' }), 381 | new File(['bye world'], 'bye.txt', { type: 'text/plain' }) 382 | ] 383 | 384 | fd.set('file1', files[0] as Blob) 385 | fd.set('file2', files[1] as Blob) 386 | 387 | const server = createServer(async (req: ReqWithBody<{ file1: [File]; file2: [File] }>, res) => { 388 | await multipart()(req, res, (err) => err && console.log(err)) 389 | 390 | const files = Object.values(req.body!) 391 | 392 | for (const file of files) { 393 | const buf = new Uint8Array(await file[0].arrayBuffer()) 394 | const i = files.indexOf(file) 395 | const formBuf = new Uint8Array(await files[i][0].arrayBuffer()) 396 | assert.strictEqual(Buffer.compare(buf, formBuf), 0) 397 | } 398 | res.end('ok') 399 | }) 400 | 401 | await makeFetch(server)('/', { 402 | body: fd, 403 | method: 'POST' 404 | }).expect(200) 405 | }) 406 | it('should support binary files', async () => { 407 | const fd = new FormData() 408 | const file = new File([new Uint8Array([1, 2, 3])], 'blob.bin', { type: 'application/octet-stream' }) 409 | fd.set('file', file as Blob) 410 | 411 | const server = createServer(async (req: ReqWithBody<{ file: [File] }>, res) => { 412 | await multipart()(req, res, (err) => err && console.log(err)) 413 | 414 | const formBuf = new Uint8Array(await file.arrayBuffer()) 415 | const buf = new Uint8Array(await req.body!.file[0].arrayBuffer()) 416 | 417 | assert.equal(Buffer.compare(buf, formBuf), 0) 418 | assert.equal(req.body?.file[0].type, 'application/octet-stream') 419 | 420 | res.end(req.body?.file[0].name) 421 | }) 422 | 423 | await makeFetch(server)('/', { 424 | // probaly better to use form-data package 425 | body: fd, 426 | method: 'POST' 427 | }).expect(200, 'blob.bin') 428 | }) 429 | }) 430 | 431 | describe('Limits', () => { 432 | it('should throw on default payloadLimit', async () => { 433 | const server = createServer(async (req: ReqWithBody, res) => { 434 | await text()(req, res, (err) => { 435 | if (err) res.writeHead(413).end(err.message) 436 | else res.end(req.body) 437 | }) 438 | }) 439 | 440 | await makeFetch(server)('/', { 441 | body: new Uint8Array(Buffer.alloc(200 * 1024 ** 2, 'a').buffer), 442 | method: 'POST', 443 | headers: { 444 | Accept: 'text/plain', 445 | 'Content-Type': 'text/plain' 446 | } 447 | }).expect(413, 'Payload too large. Limit: 102400 bytes') 448 | }) 449 | 450 | it('should throw on custom payloadLimit', async () => { 451 | const server = createServer(async (req: ReqWithBody, res) => { 452 | await text({ payloadLimit: 1024 })(req, res, (err) => { 453 | if (err) res.writeHead(413).end(err.message) 454 | else res.end(req.body) 455 | }) 456 | }) 457 | 458 | await makeFetch(server)('/', { 459 | body: new Uint8Array(Buffer.alloc(1024 ** 2, 'a').buffer), 460 | method: 'POST', 461 | headers: { 462 | Accept: 'text/plain', 463 | 'Content-Type': 'text/plain' 464 | } 465 | }).expect(413, 'Payload too large. Limit: 1024 bytes') 466 | }) 467 | 468 | it('should throw on payloadLimit with custom error message', async () => { 469 | const server = createServer(async (req: ReqWithBody, res) => { 470 | await text({ 471 | payloadLimit: 1024, 472 | payloadLimitErrorFn: (payloadLimit) => new Error(`Payload too large. Limit: ${payloadLimit / 1024}KB`) 473 | })(req, res, (err) => { 474 | if (err) res.writeHead(413).end(err.message) 475 | else res.end(req.body) 476 | }) 477 | }) 478 | 479 | await makeFetch(server)('/', { 480 | body: new Uint8Array(Buffer.alloc(1024 ** 2, 'a').buffer), 481 | method: 'POST', 482 | headers: { 483 | Accept: 'text/plain', 484 | 'Content-Type': 'text/plain' 485 | } 486 | }).expect(413, 'Payload too large. Limit: 1KB') 487 | }) 488 | 489 | it('should throw multipart if amount of files exceeds limit', async () => { 490 | const server = createServer(async (req: ReqWithBody, res) => { 491 | await multipart({ fileCountLimit: 1 })(req, res, (err) => { 492 | if (err) res.writeHead(413).end(err.message) 493 | else res.end(req.body) 494 | }) 495 | }) 496 | 497 | const fd = new FormData() 498 | 499 | fd.set('file1', new File(['hello world'], 'hello.txt', { type: 'text/plain' }) as Blob) 500 | fd.set('file2', new File(['bye world'], 'bye.txt', { type: 'text/plain' }) as Blob) 501 | 502 | await makeFetch(server)('/', { 503 | body: fd, 504 | method: 'POST' 505 | }).expect(413, 'Too many files. Limit: 1') 506 | }) 507 | 508 | it('should throw multipart if exceeds allowed file size', async () => { 509 | const server = createServer(async (req: ReqWithBody, res) => { 510 | await multipart({ fileSizeLimit: 10 })(req, res, (err) => { 511 | if (err) res.writeHead(413).end(err.message) 512 | else res.end(req.body) 513 | }) 514 | }) 515 | 516 | const fd = new FormData() 517 | 518 | fd.set('file', new File(['hello world'], 'hello.txt', { type: 'text/plain' }) as Blob) 519 | 520 | await makeFetch(server)('/', { 521 | body: fd, 522 | method: 'POST' 523 | }).expect(413, 'File too large. Limit: 10 bytes') 524 | }) 525 | 526 | it('should throw multipart if exceeds allowed file size with a custom error', async () => { 527 | const server = createServer(async (req: ReqWithBody, res) => { 528 | await multipart({ 529 | fileSizeLimit: 20, 530 | fileSizeLimitErrorFn: (limit) => new Error(`File too large. Limit: ${limit / 1024}KB`) 531 | })(req, res, (err) => { 532 | if (err) res.writeHead(413).end(err.message) 533 | }) 534 | }) 535 | 536 | const fd = new FormData() 537 | 538 | fd.set('file', new File(['hello world to everyone'], 'hello.txt', { type: 'text/plain' }) as Blob) 539 | 540 | await makeFetch(server)('/', { 541 | body: fd, 542 | method: 'POST' 543 | }).expect(413, 'File too large. Limit: 0.01953125KB') 544 | }) 545 | }) 546 | 547 | describe('Framework integration', { timeout: 500 }, () => { 548 | it('works with tinyhttp', async () => { 549 | const app = new App() 550 | 551 | app 552 | .use('/json', json()) 553 | .use('/url', urlencoded()) 554 | .post((req, res) => { 555 | res.json(req.body) 556 | }) 557 | 558 | const server = app.listen() 559 | 560 | const fetch = makeFetch(server) 561 | 562 | await fetch('/json', { 563 | body: JSON.stringify({ hello: 'world' }), 564 | method: 'POST' 565 | }).expect(200, { hello: 'world' }) 566 | 567 | await fetch('/url', { 568 | body: 'hello=world', 569 | method: 'POST' 570 | }) 571 | .expect(200, { hello: 'world' }) 572 | .then(() => server.close()) 573 | }) 574 | }) 575 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["dist"], 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "module": "NodeNext", 6 | "target": "ES2019", 7 | "moduleResolution": "NodeNext", 8 | "declaration": true, 9 | "outDir": "dist", 10 | "strict": true, 11 | "allowJs": true 12 | } 13 | } 14 | --------------------------------------------------------------------------------