├── .github └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── biome.json ├── example └── progress.ts ├── package.json ├── pnpm-lock.yaml ├── src ├── concat.ts └── progress.ts ├── test ├── concat.test.ts └── progress.test.ts └── tsconfig.json /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | ci: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-24.04-arm] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - uses: actions/checkout@v4 13 | 14 | - name: Enable corepack 15 | run: corepack enable pnpm 16 | 17 | - uses: actions/setup-node@v4 18 | with: 19 | cache: 'pnpm' 20 | node-version: 18 21 | 22 | - run: pnpm install --frozen-lockfile 23 | - name: Setup Biome 24 | uses: biomejs/setup-biome@v2 25 | with: 26 | version: latest 27 | - name: Run Biome 28 | run: biome ci . 29 | 30 | - name: Run tests 31 | run: pnpm test:coverage && pnpm test:report 32 | - name: Coveralls 33 | uses: coverallsapp/github-action@master 34 | with: 35 | github-token: ${{ secrets.GITHUB_TOKEN }} 36 | path-to-lcov: ./coverage.lcov -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npm 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | release: 7 | runs-on: ubuntu-24.04-arm 8 | permissions: 9 | contents: read 10 | id-token: write 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Enable corepack 14 | run: corepack enable pnpm 15 | 16 | - uses: actions/setup-node@v4 17 | with: 18 | cache: 'pnpm' 19 | node-version: 18 20 | registry-url: 'https://registry.npmjs.org' 21 | - run: pnpm install 22 | - run: pnpm build 23 | - name: Setup Biome 24 | uses: biomejs/setup-biome@v2 25 | with: 26 | version: latest 27 | 28 | - name: Run Biome 29 | run: biome ci . 30 | - name: Publish 31 | run: pnpm publish --no-git-checks 32 | env: 33 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | NPM_CONFIG_PROVENANCE: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm.packageManager": "bun", 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) 2025 v1rtl 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 | # dunai 4 | 5 | ![Coveralls][cov-badge] ![npm size][npm-size-badge] 6 | 7 |
8 | 9 | A collection of tiny utilities for node streams, rewritten for simplicity and compactness. 10 | 11 | ## Features 12 | 13 | - really smol 14 | - 0 dependencies 15 | - ESM-only and types out of the box 16 | 17 | ## API 18 | 19 | ### `dunai/progress` 20 | 21 | - based on `progress-stream` (210KB) 22 | - ~94.2% smaller install size 23 | 24 | ### `dunai/concat` 25 | 26 | - based on `concat-stream` (221KB) 27 | - ~94.5% smaller install size 28 | 29 | [cov-badge]: https://img.shields.io/coverallsCoverage/github/talentlessguy/dunai?style=for-the-badge&color=457b9d 30 | [npm-badge]: https://img.shields.io/npm/d18m/dunai?style=for-the-badge&color=457b9d 31 | [npm-size-badge]: https://img.shields.io/npm/unpacked-size/dunai?style=for-the-badge&color=457b9d -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", 3 | "files": { 4 | "ignore": ["node_modules", "dist", "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": { 16 | "enabled": true 17 | }, 18 | "linter": { 19 | "enabled": true, 20 | "rules": { 21 | "recommended": true, 22 | "style": { 23 | "noParameterAssign": "off", 24 | "useSingleVarDeclarator": "off", 25 | "noCommaOperator": "off" 26 | }, 27 | "suspicious": { 28 | "noConfusingVoidType": "off", 29 | "noExplicitAny": "off", 30 | "noUnsafeDeclarationMerging": "off", 31 | "noAssignInExpressions": "off" 32 | } 33 | } 34 | }, 35 | "javascript": { 36 | "formatter": { 37 | "quoteProperties": "asNeeded", 38 | "trailingCommas": "none", 39 | "semicolons": "asNeeded", 40 | "arrowParentheses": "always", 41 | "bracketSpacing": true, 42 | "bracketSameLine": false, 43 | "quoteStyle": "single", 44 | "attributePosition": "auto" 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/progress.ts: -------------------------------------------------------------------------------- 1 | import http from 'node:http' 2 | import { createProgressStream } from '../src/progress' 3 | 4 | const str = createProgressStream({ 5 | drain: true, 6 | time: 100, 7 | speed: 20 8 | }) 9 | 10 | str.on('progress', (progress) => { 11 | console.log(`${progress.percentage.toFixed(2)}%`) 12 | }) 13 | 14 | const options = { 15 | method: 'GET', 16 | host: 'cachefly.cachefly.net', 17 | path: '/10mb.test', 18 | headers: { 19 | 'user-agent': 'testy test' 20 | } 21 | } 22 | 23 | http 24 | .request(options, (response) => { 25 | response.pipe(str) 26 | }) 27 | .end() 28 | 29 | console.log('progress-stream using http module - downloading 10 MB file') 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dunai", 3 | "version": "0.0.0-dev.2", 4 | "devDependencies": { 5 | "@biomejs/biome": "^1.9.4", 6 | "@types/node": "^22.10.10", 7 | "c8": "^10.1.3", 8 | "expect": "^29.7.0", 9 | "tsx": "^4.19.2", 10 | "typescript": "^5.7.3" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/talentlessguy/dunai.git" 15 | }, 16 | "exports": { 17 | "./concat": { 18 | "types": "./dist/concat.d.ts", 19 | "default": "./dist/concat.js" 20 | }, 21 | "./progress": { 22 | "types": "./dist/progress.d.ts", 23 | "default": "./dist/progress.js" 24 | } 25 | }, 26 | "engines": { 27 | "node": ">=18" 28 | }, 29 | "sideEffects": false, 30 | "scripts": { 31 | "build": "tsc", 32 | "test": "tsx --test test/*.test.ts", 33 | "check": "biome check --write", 34 | "test:coverage": "c8 tsx --test test/*.test.ts", 35 | "test:report": "c8 report --reporter=text-lcov > coverage.lcov" 36 | }, 37 | "type": "module", 38 | "files": [ 39 | "dist" 40 | ], 41 | "license": "MIT", 42 | "packageManager": "pnpm@9.11.0+sha512.0a203ffaed5a3f63242cd064c8fb5892366c103e328079318f78062f24ea8c9d50bc6a47aa3567cabefd824d170e78fa2745ed1f16b132e16436146b7688f19b" 43 | } 44 | -------------------------------------------------------------------------------- /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.4 13 | version: 1.9.4 14 | '@types/node': 15 | specifier: ^22.10.10 16 | version: 22.10.10 17 | c8: 18 | specifier: ^10.1.3 19 | version: 10.1.3 20 | expect: 21 | specifier: ^29.7.0 22 | version: 29.7.0 23 | tsx: 24 | specifier: ^4.19.2 25 | version: 4.19.2 26 | typescript: 27 | specifier: ^5.7.3 28 | version: 5.7.3 29 | 30 | packages: 31 | 32 | '@babel/code-frame@7.26.2': 33 | resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} 34 | engines: {node: '>=6.9.0'} 35 | 36 | '@babel/helper-validator-identifier@7.25.9': 37 | resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} 38 | engines: {node: '>=6.9.0'} 39 | 40 | '@bcoe/v8-coverage@1.0.2': 41 | resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} 42 | engines: {node: '>=18'} 43 | 44 | '@biomejs/biome@1.9.4': 45 | resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} 46 | engines: {node: '>=14.21.3'} 47 | hasBin: true 48 | 49 | '@biomejs/cli-darwin-arm64@1.9.4': 50 | resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} 51 | engines: {node: '>=14.21.3'} 52 | cpu: [arm64] 53 | os: [darwin] 54 | 55 | '@biomejs/cli-darwin-x64@1.9.4': 56 | resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} 57 | engines: {node: '>=14.21.3'} 58 | cpu: [x64] 59 | os: [darwin] 60 | 61 | '@biomejs/cli-linux-arm64-musl@1.9.4': 62 | resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} 63 | engines: {node: '>=14.21.3'} 64 | cpu: [arm64] 65 | os: [linux] 66 | 67 | '@biomejs/cli-linux-arm64@1.9.4': 68 | resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} 69 | engines: {node: '>=14.21.3'} 70 | cpu: [arm64] 71 | os: [linux] 72 | 73 | '@biomejs/cli-linux-x64-musl@1.9.4': 74 | resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} 75 | engines: {node: '>=14.21.3'} 76 | cpu: [x64] 77 | os: [linux] 78 | 79 | '@biomejs/cli-linux-x64@1.9.4': 80 | resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} 81 | engines: {node: '>=14.21.3'} 82 | cpu: [x64] 83 | os: [linux] 84 | 85 | '@biomejs/cli-win32-arm64@1.9.4': 86 | resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} 87 | engines: {node: '>=14.21.3'} 88 | cpu: [arm64] 89 | os: [win32] 90 | 91 | '@biomejs/cli-win32-x64@1.9.4': 92 | resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} 93 | engines: {node: '>=14.21.3'} 94 | cpu: [x64] 95 | os: [win32] 96 | 97 | '@esbuild/aix-ppc64@0.23.1': 98 | resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} 99 | engines: {node: '>=18'} 100 | cpu: [ppc64] 101 | os: [aix] 102 | 103 | '@esbuild/android-arm64@0.23.1': 104 | resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} 105 | engines: {node: '>=18'} 106 | cpu: [arm64] 107 | os: [android] 108 | 109 | '@esbuild/android-arm@0.23.1': 110 | resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} 111 | engines: {node: '>=18'} 112 | cpu: [arm] 113 | os: [android] 114 | 115 | '@esbuild/android-x64@0.23.1': 116 | resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} 117 | engines: {node: '>=18'} 118 | cpu: [x64] 119 | os: [android] 120 | 121 | '@esbuild/darwin-arm64@0.23.1': 122 | resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} 123 | engines: {node: '>=18'} 124 | cpu: [arm64] 125 | os: [darwin] 126 | 127 | '@esbuild/darwin-x64@0.23.1': 128 | resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} 129 | engines: {node: '>=18'} 130 | cpu: [x64] 131 | os: [darwin] 132 | 133 | '@esbuild/freebsd-arm64@0.23.1': 134 | resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} 135 | engines: {node: '>=18'} 136 | cpu: [arm64] 137 | os: [freebsd] 138 | 139 | '@esbuild/freebsd-x64@0.23.1': 140 | resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} 141 | engines: {node: '>=18'} 142 | cpu: [x64] 143 | os: [freebsd] 144 | 145 | '@esbuild/linux-arm64@0.23.1': 146 | resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} 147 | engines: {node: '>=18'} 148 | cpu: [arm64] 149 | os: [linux] 150 | 151 | '@esbuild/linux-arm@0.23.1': 152 | resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} 153 | engines: {node: '>=18'} 154 | cpu: [arm] 155 | os: [linux] 156 | 157 | '@esbuild/linux-ia32@0.23.1': 158 | resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} 159 | engines: {node: '>=18'} 160 | cpu: [ia32] 161 | os: [linux] 162 | 163 | '@esbuild/linux-loong64@0.23.1': 164 | resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} 165 | engines: {node: '>=18'} 166 | cpu: [loong64] 167 | os: [linux] 168 | 169 | '@esbuild/linux-mips64el@0.23.1': 170 | resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} 171 | engines: {node: '>=18'} 172 | cpu: [mips64el] 173 | os: [linux] 174 | 175 | '@esbuild/linux-ppc64@0.23.1': 176 | resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} 177 | engines: {node: '>=18'} 178 | cpu: [ppc64] 179 | os: [linux] 180 | 181 | '@esbuild/linux-riscv64@0.23.1': 182 | resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} 183 | engines: {node: '>=18'} 184 | cpu: [riscv64] 185 | os: [linux] 186 | 187 | '@esbuild/linux-s390x@0.23.1': 188 | resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} 189 | engines: {node: '>=18'} 190 | cpu: [s390x] 191 | os: [linux] 192 | 193 | '@esbuild/linux-x64@0.23.1': 194 | resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} 195 | engines: {node: '>=18'} 196 | cpu: [x64] 197 | os: [linux] 198 | 199 | '@esbuild/netbsd-x64@0.23.1': 200 | resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} 201 | engines: {node: '>=18'} 202 | cpu: [x64] 203 | os: [netbsd] 204 | 205 | '@esbuild/openbsd-arm64@0.23.1': 206 | resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} 207 | engines: {node: '>=18'} 208 | cpu: [arm64] 209 | os: [openbsd] 210 | 211 | '@esbuild/openbsd-x64@0.23.1': 212 | resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} 213 | engines: {node: '>=18'} 214 | cpu: [x64] 215 | os: [openbsd] 216 | 217 | '@esbuild/sunos-x64@0.23.1': 218 | resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} 219 | engines: {node: '>=18'} 220 | cpu: [x64] 221 | os: [sunos] 222 | 223 | '@esbuild/win32-arm64@0.23.1': 224 | resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} 225 | engines: {node: '>=18'} 226 | cpu: [arm64] 227 | os: [win32] 228 | 229 | '@esbuild/win32-ia32@0.23.1': 230 | resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} 231 | engines: {node: '>=18'} 232 | cpu: [ia32] 233 | os: [win32] 234 | 235 | '@esbuild/win32-x64@0.23.1': 236 | resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} 237 | engines: {node: '>=18'} 238 | cpu: [x64] 239 | os: [win32] 240 | 241 | '@isaacs/cliui@8.0.2': 242 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 243 | engines: {node: '>=12'} 244 | 245 | '@istanbuljs/schema@0.1.3': 246 | resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} 247 | engines: {node: '>=8'} 248 | 249 | '@jest/expect-utils@29.7.0': 250 | resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} 251 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 252 | 253 | '@jest/schemas@29.6.3': 254 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} 255 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 256 | 257 | '@jest/types@29.6.3': 258 | resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} 259 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 260 | 261 | '@jridgewell/resolve-uri@3.1.2': 262 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 263 | engines: {node: '>=6.0.0'} 264 | 265 | '@jridgewell/sourcemap-codec@1.5.0': 266 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 267 | 268 | '@jridgewell/trace-mapping@0.3.25': 269 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 270 | 271 | '@pkgjs/parseargs@0.11.0': 272 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 273 | engines: {node: '>=14'} 274 | 275 | '@sinclair/typebox@0.27.8': 276 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 277 | 278 | '@types/istanbul-lib-coverage@2.0.6': 279 | resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} 280 | 281 | '@types/istanbul-lib-report@3.0.3': 282 | resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} 283 | 284 | '@types/istanbul-reports@3.0.4': 285 | resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} 286 | 287 | '@types/node@22.10.10': 288 | resolution: {integrity: sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==} 289 | 290 | '@types/stack-utils@2.0.3': 291 | resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} 292 | 293 | '@types/yargs-parser@21.0.3': 294 | resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} 295 | 296 | '@types/yargs@17.0.33': 297 | resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} 298 | 299 | ansi-regex@5.0.1: 300 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 301 | engines: {node: '>=8'} 302 | 303 | ansi-regex@6.1.0: 304 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 305 | engines: {node: '>=12'} 306 | 307 | ansi-styles@4.3.0: 308 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 309 | engines: {node: '>=8'} 310 | 311 | ansi-styles@5.2.0: 312 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} 313 | engines: {node: '>=10'} 314 | 315 | ansi-styles@6.2.1: 316 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 317 | engines: {node: '>=12'} 318 | 319 | balanced-match@1.0.2: 320 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 321 | 322 | brace-expansion@2.0.1: 323 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 324 | 325 | braces@3.0.3: 326 | resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 327 | engines: {node: '>=8'} 328 | 329 | c8@10.1.3: 330 | resolution: {integrity: sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==} 331 | engines: {node: '>=18'} 332 | hasBin: true 333 | peerDependencies: 334 | monocart-coverage-reports: ^2 335 | peerDependenciesMeta: 336 | monocart-coverage-reports: 337 | optional: true 338 | 339 | chalk@4.1.2: 340 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 341 | engines: {node: '>=10'} 342 | 343 | ci-info@3.9.0: 344 | resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} 345 | engines: {node: '>=8'} 346 | 347 | cliui@8.0.1: 348 | resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} 349 | engines: {node: '>=12'} 350 | 351 | color-convert@2.0.1: 352 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 353 | engines: {node: '>=7.0.0'} 354 | 355 | color-name@1.1.4: 356 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 357 | 358 | convert-source-map@2.0.0: 359 | resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} 360 | 361 | cross-spawn@7.0.6: 362 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 363 | engines: {node: '>= 8'} 364 | 365 | diff-sequences@29.6.3: 366 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 367 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 368 | 369 | eastasianwidth@0.2.0: 370 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 371 | 372 | emoji-regex@8.0.0: 373 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 374 | 375 | emoji-regex@9.2.2: 376 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 377 | 378 | esbuild@0.23.1: 379 | resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} 380 | engines: {node: '>=18'} 381 | hasBin: true 382 | 383 | escalade@3.2.0: 384 | resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} 385 | engines: {node: '>=6'} 386 | 387 | escape-string-regexp@2.0.0: 388 | resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} 389 | engines: {node: '>=8'} 390 | 391 | expect@29.7.0: 392 | resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} 393 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 394 | 395 | fill-range@7.1.1: 396 | resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 397 | engines: {node: '>=8'} 398 | 399 | find-up@5.0.0: 400 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 401 | engines: {node: '>=10'} 402 | 403 | foreground-child@3.3.0: 404 | resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} 405 | engines: {node: '>=14'} 406 | 407 | fsevents@2.3.3: 408 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 409 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 410 | os: [darwin] 411 | 412 | get-caller-file@2.0.5: 413 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 414 | engines: {node: 6.* || 8.* || >= 10.*} 415 | 416 | get-tsconfig@4.10.0: 417 | resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} 418 | 419 | glob@10.4.5: 420 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 421 | hasBin: true 422 | 423 | graceful-fs@4.2.11: 424 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 425 | 426 | has-flag@4.0.0: 427 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 428 | engines: {node: '>=8'} 429 | 430 | html-escaper@2.0.2: 431 | resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 432 | 433 | is-fullwidth-code-point@3.0.0: 434 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 435 | engines: {node: '>=8'} 436 | 437 | is-number@7.0.0: 438 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 439 | engines: {node: '>=0.12.0'} 440 | 441 | isexe@2.0.0: 442 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 443 | 444 | istanbul-lib-coverage@3.2.2: 445 | resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 446 | engines: {node: '>=8'} 447 | 448 | istanbul-lib-report@3.0.1: 449 | resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} 450 | engines: {node: '>=10'} 451 | 452 | istanbul-reports@3.1.7: 453 | resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} 454 | engines: {node: '>=8'} 455 | 456 | jackspeak@3.4.3: 457 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 458 | 459 | jest-diff@29.7.0: 460 | resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} 461 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 462 | 463 | jest-get-type@29.6.3: 464 | resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} 465 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 466 | 467 | jest-matcher-utils@29.7.0: 468 | resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} 469 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 470 | 471 | jest-message-util@29.7.0: 472 | resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} 473 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 474 | 475 | jest-util@29.7.0: 476 | resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} 477 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 478 | 479 | js-tokens@4.0.0: 480 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 481 | 482 | locate-path@6.0.0: 483 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 484 | engines: {node: '>=10'} 485 | 486 | lru-cache@10.4.3: 487 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 488 | 489 | make-dir@4.0.0: 490 | resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 491 | engines: {node: '>=10'} 492 | 493 | micromatch@4.0.8: 494 | resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 495 | engines: {node: '>=8.6'} 496 | 497 | minimatch@9.0.5: 498 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 499 | engines: {node: '>=16 || 14 >=14.17'} 500 | 501 | minipass@7.1.2: 502 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 503 | engines: {node: '>=16 || 14 >=14.17'} 504 | 505 | p-limit@3.1.0: 506 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 507 | engines: {node: '>=10'} 508 | 509 | p-locate@5.0.0: 510 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 511 | engines: {node: '>=10'} 512 | 513 | package-json-from-dist@1.0.1: 514 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 515 | 516 | path-exists@4.0.0: 517 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 518 | engines: {node: '>=8'} 519 | 520 | path-key@3.1.1: 521 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 522 | engines: {node: '>=8'} 523 | 524 | path-scurry@1.11.1: 525 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 526 | engines: {node: '>=16 || 14 >=14.18'} 527 | 528 | picocolors@1.1.1: 529 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 530 | 531 | picomatch@2.3.1: 532 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 533 | engines: {node: '>=8.6'} 534 | 535 | pretty-format@29.7.0: 536 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} 537 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 538 | 539 | react-is@18.3.1: 540 | resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} 541 | 542 | require-directory@2.1.1: 543 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 544 | engines: {node: '>=0.10.0'} 545 | 546 | resolve-pkg-maps@1.0.0: 547 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 548 | 549 | semver@7.6.3: 550 | resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} 551 | engines: {node: '>=10'} 552 | hasBin: true 553 | 554 | shebang-command@2.0.0: 555 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 556 | engines: {node: '>=8'} 557 | 558 | shebang-regex@3.0.0: 559 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 560 | engines: {node: '>=8'} 561 | 562 | signal-exit@4.1.0: 563 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 564 | engines: {node: '>=14'} 565 | 566 | slash@3.0.0: 567 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 568 | engines: {node: '>=8'} 569 | 570 | stack-utils@2.0.6: 571 | resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} 572 | engines: {node: '>=10'} 573 | 574 | string-width@4.2.3: 575 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 576 | engines: {node: '>=8'} 577 | 578 | string-width@5.1.2: 579 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 580 | engines: {node: '>=12'} 581 | 582 | strip-ansi@6.0.1: 583 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 584 | engines: {node: '>=8'} 585 | 586 | strip-ansi@7.1.0: 587 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 588 | engines: {node: '>=12'} 589 | 590 | supports-color@7.2.0: 591 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 592 | engines: {node: '>=8'} 593 | 594 | test-exclude@7.0.1: 595 | resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} 596 | engines: {node: '>=18'} 597 | 598 | to-regex-range@5.0.1: 599 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 600 | engines: {node: '>=8.0'} 601 | 602 | tsx@4.19.2: 603 | resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} 604 | engines: {node: '>=18.0.0'} 605 | hasBin: true 606 | 607 | typescript@5.7.3: 608 | resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} 609 | engines: {node: '>=14.17'} 610 | hasBin: true 611 | 612 | undici-types@6.20.0: 613 | resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} 614 | 615 | v8-to-istanbul@9.3.0: 616 | resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} 617 | engines: {node: '>=10.12.0'} 618 | 619 | which@2.0.2: 620 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 621 | engines: {node: '>= 8'} 622 | hasBin: true 623 | 624 | wrap-ansi@7.0.0: 625 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 626 | engines: {node: '>=10'} 627 | 628 | wrap-ansi@8.1.0: 629 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 630 | engines: {node: '>=12'} 631 | 632 | y18n@5.0.8: 633 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 634 | engines: {node: '>=10'} 635 | 636 | yargs-parser@21.1.1: 637 | resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} 638 | engines: {node: '>=12'} 639 | 640 | yargs@17.7.2: 641 | resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} 642 | engines: {node: '>=12'} 643 | 644 | yocto-queue@0.1.0: 645 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 646 | engines: {node: '>=10'} 647 | 648 | snapshots: 649 | 650 | '@babel/code-frame@7.26.2': 651 | dependencies: 652 | '@babel/helper-validator-identifier': 7.25.9 653 | js-tokens: 4.0.0 654 | picocolors: 1.1.1 655 | 656 | '@babel/helper-validator-identifier@7.25.9': {} 657 | 658 | '@bcoe/v8-coverage@1.0.2': {} 659 | 660 | '@biomejs/biome@1.9.4': 661 | optionalDependencies: 662 | '@biomejs/cli-darwin-arm64': 1.9.4 663 | '@biomejs/cli-darwin-x64': 1.9.4 664 | '@biomejs/cli-linux-arm64': 1.9.4 665 | '@biomejs/cli-linux-arm64-musl': 1.9.4 666 | '@biomejs/cli-linux-x64': 1.9.4 667 | '@biomejs/cli-linux-x64-musl': 1.9.4 668 | '@biomejs/cli-win32-arm64': 1.9.4 669 | '@biomejs/cli-win32-x64': 1.9.4 670 | 671 | '@biomejs/cli-darwin-arm64@1.9.4': 672 | optional: true 673 | 674 | '@biomejs/cli-darwin-x64@1.9.4': 675 | optional: true 676 | 677 | '@biomejs/cli-linux-arm64-musl@1.9.4': 678 | optional: true 679 | 680 | '@biomejs/cli-linux-arm64@1.9.4': 681 | optional: true 682 | 683 | '@biomejs/cli-linux-x64-musl@1.9.4': 684 | optional: true 685 | 686 | '@biomejs/cli-linux-x64@1.9.4': 687 | optional: true 688 | 689 | '@biomejs/cli-win32-arm64@1.9.4': 690 | optional: true 691 | 692 | '@biomejs/cli-win32-x64@1.9.4': 693 | optional: true 694 | 695 | '@esbuild/aix-ppc64@0.23.1': 696 | optional: true 697 | 698 | '@esbuild/android-arm64@0.23.1': 699 | optional: true 700 | 701 | '@esbuild/android-arm@0.23.1': 702 | optional: true 703 | 704 | '@esbuild/android-x64@0.23.1': 705 | optional: true 706 | 707 | '@esbuild/darwin-arm64@0.23.1': 708 | optional: true 709 | 710 | '@esbuild/darwin-x64@0.23.1': 711 | optional: true 712 | 713 | '@esbuild/freebsd-arm64@0.23.1': 714 | optional: true 715 | 716 | '@esbuild/freebsd-x64@0.23.1': 717 | optional: true 718 | 719 | '@esbuild/linux-arm64@0.23.1': 720 | optional: true 721 | 722 | '@esbuild/linux-arm@0.23.1': 723 | optional: true 724 | 725 | '@esbuild/linux-ia32@0.23.1': 726 | optional: true 727 | 728 | '@esbuild/linux-loong64@0.23.1': 729 | optional: true 730 | 731 | '@esbuild/linux-mips64el@0.23.1': 732 | optional: true 733 | 734 | '@esbuild/linux-ppc64@0.23.1': 735 | optional: true 736 | 737 | '@esbuild/linux-riscv64@0.23.1': 738 | optional: true 739 | 740 | '@esbuild/linux-s390x@0.23.1': 741 | optional: true 742 | 743 | '@esbuild/linux-x64@0.23.1': 744 | optional: true 745 | 746 | '@esbuild/netbsd-x64@0.23.1': 747 | optional: true 748 | 749 | '@esbuild/openbsd-arm64@0.23.1': 750 | optional: true 751 | 752 | '@esbuild/openbsd-x64@0.23.1': 753 | optional: true 754 | 755 | '@esbuild/sunos-x64@0.23.1': 756 | optional: true 757 | 758 | '@esbuild/win32-arm64@0.23.1': 759 | optional: true 760 | 761 | '@esbuild/win32-ia32@0.23.1': 762 | optional: true 763 | 764 | '@esbuild/win32-x64@0.23.1': 765 | optional: true 766 | 767 | '@isaacs/cliui@8.0.2': 768 | dependencies: 769 | string-width: 5.1.2 770 | string-width-cjs: string-width@4.2.3 771 | strip-ansi: 7.1.0 772 | strip-ansi-cjs: strip-ansi@6.0.1 773 | wrap-ansi: 8.1.0 774 | wrap-ansi-cjs: wrap-ansi@7.0.0 775 | 776 | '@istanbuljs/schema@0.1.3': {} 777 | 778 | '@jest/expect-utils@29.7.0': 779 | dependencies: 780 | jest-get-type: 29.6.3 781 | 782 | '@jest/schemas@29.6.3': 783 | dependencies: 784 | '@sinclair/typebox': 0.27.8 785 | 786 | '@jest/types@29.6.3': 787 | dependencies: 788 | '@jest/schemas': 29.6.3 789 | '@types/istanbul-lib-coverage': 2.0.6 790 | '@types/istanbul-reports': 3.0.4 791 | '@types/node': 22.10.10 792 | '@types/yargs': 17.0.33 793 | chalk: 4.1.2 794 | 795 | '@jridgewell/resolve-uri@3.1.2': {} 796 | 797 | '@jridgewell/sourcemap-codec@1.5.0': {} 798 | 799 | '@jridgewell/trace-mapping@0.3.25': 800 | dependencies: 801 | '@jridgewell/resolve-uri': 3.1.2 802 | '@jridgewell/sourcemap-codec': 1.5.0 803 | 804 | '@pkgjs/parseargs@0.11.0': 805 | optional: true 806 | 807 | '@sinclair/typebox@0.27.8': {} 808 | 809 | '@types/istanbul-lib-coverage@2.0.6': {} 810 | 811 | '@types/istanbul-lib-report@3.0.3': 812 | dependencies: 813 | '@types/istanbul-lib-coverage': 2.0.6 814 | 815 | '@types/istanbul-reports@3.0.4': 816 | dependencies: 817 | '@types/istanbul-lib-report': 3.0.3 818 | 819 | '@types/node@22.10.10': 820 | dependencies: 821 | undici-types: 6.20.0 822 | 823 | '@types/stack-utils@2.0.3': {} 824 | 825 | '@types/yargs-parser@21.0.3': {} 826 | 827 | '@types/yargs@17.0.33': 828 | dependencies: 829 | '@types/yargs-parser': 21.0.3 830 | 831 | ansi-regex@5.0.1: {} 832 | 833 | ansi-regex@6.1.0: {} 834 | 835 | ansi-styles@4.3.0: 836 | dependencies: 837 | color-convert: 2.0.1 838 | 839 | ansi-styles@5.2.0: {} 840 | 841 | ansi-styles@6.2.1: {} 842 | 843 | balanced-match@1.0.2: {} 844 | 845 | brace-expansion@2.0.1: 846 | dependencies: 847 | balanced-match: 1.0.2 848 | 849 | braces@3.0.3: 850 | dependencies: 851 | fill-range: 7.1.1 852 | 853 | c8@10.1.3: 854 | dependencies: 855 | '@bcoe/v8-coverage': 1.0.2 856 | '@istanbuljs/schema': 0.1.3 857 | find-up: 5.0.0 858 | foreground-child: 3.3.0 859 | istanbul-lib-coverage: 3.2.2 860 | istanbul-lib-report: 3.0.1 861 | istanbul-reports: 3.1.7 862 | test-exclude: 7.0.1 863 | v8-to-istanbul: 9.3.0 864 | yargs: 17.7.2 865 | yargs-parser: 21.1.1 866 | 867 | chalk@4.1.2: 868 | dependencies: 869 | ansi-styles: 4.3.0 870 | supports-color: 7.2.0 871 | 872 | ci-info@3.9.0: {} 873 | 874 | cliui@8.0.1: 875 | dependencies: 876 | string-width: 4.2.3 877 | strip-ansi: 6.0.1 878 | wrap-ansi: 7.0.0 879 | 880 | color-convert@2.0.1: 881 | dependencies: 882 | color-name: 1.1.4 883 | 884 | color-name@1.1.4: {} 885 | 886 | convert-source-map@2.0.0: {} 887 | 888 | cross-spawn@7.0.6: 889 | dependencies: 890 | path-key: 3.1.1 891 | shebang-command: 2.0.0 892 | which: 2.0.2 893 | 894 | diff-sequences@29.6.3: {} 895 | 896 | eastasianwidth@0.2.0: {} 897 | 898 | emoji-regex@8.0.0: {} 899 | 900 | emoji-regex@9.2.2: {} 901 | 902 | esbuild@0.23.1: 903 | optionalDependencies: 904 | '@esbuild/aix-ppc64': 0.23.1 905 | '@esbuild/android-arm': 0.23.1 906 | '@esbuild/android-arm64': 0.23.1 907 | '@esbuild/android-x64': 0.23.1 908 | '@esbuild/darwin-arm64': 0.23.1 909 | '@esbuild/darwin-x64': 0.23.1 910 | '@esbuild/freebsd-arm64': 0.23.1 911 | '@esbuild/freebsd-x64': 0.23.1 912 | '@esbuild/linux-arm': 0.23.1 913 | '@esbuild/linux-arm64': 0.23.1 914 | '@esbuild/linux-ia32': 0.23.1 915 | '@esbuild/linux-loong64': 0.23.1 916 | '@esbuild/linux-mips64el': 0.23.1 917 | '@esbuild/linux-ppc64': 0.23.1 918 | '@esbuild/linux-riscv64': 0.23.1 919 | '@esbuild/linux-s390x': 0.23.1 920 | '@esbuild/linux-x64': 0.23.1 921 | '@esbuild/netbsd-x64': 0.23.1 922 | '@esbuild/openbsd-arm64': 0.23.1 923 | '@esbuild/openbsd-x64': 0.23.1 924 | '@esbuild/sunos-x64': 0.23.1 925 | '@esbuild/win32-arm64': 0.23.1 926 | '@esbuild/win32-ia32': 0.23.1 927 | '@esbuild/win32-x64': 0.23.1 928 | 929 | escalade@3.2.0: {} 930 | 931 | escape-string-regexp@2.0.0: {} 932 | 933 | expect@29.7.0: 934 | dependencies: 935 | '@jest/expect-utils': 29.7.0 936 | jest-get-type: 29.6.3 937 | jest-matcher-utils: 29.7.0 938 | jest-message-util: 29.7.0 939 | jest-util: 29.7.0 940 | 941 | fill-range@7.1.1: 942 | dependencies: 943 | to-regex-range: 5.0.1 944 | 945 | find-up@5.0.0: 946 | dependencies: 947 | locate-path: 6.0.0 948 | path-exists: 4.0.0 949 | 950 | foreground-child@3.3.0: 951 | dependencies: 952 | cross-spawn: 7.0.6 953 | signal-exit: 4.1.0 954 | 955 | fsevents@2.3.3: 956 | optional: true 957 | 958 | get-caller-file@2.0.5: {} 959 | 960 | get-tsconfig@4.10.0: 961 | dependencies: 962 | resolve-pkg-maps: 1.0.0 963 | 964 | glob@10.4.5: 965 | dependencies: 966 | foreground-child: 3.3.0 967 | jackspeak: 3.4.3 968 | minimatch: 9.0.5 969 | minipass: 7.1.2 970 | package-json-from-dist: 1.0.1 971 | path-scurry: 1.11.1 972 | 973 | graceful-fs@4.2.11: {} 974 | 975 | has-flag@4.0.0: {} 976 | 977 | html-escaper@2.0.2: {} 978 | 979 | is-fullwidth-code-point@3.0.0: {} 980 | 981 | is-number@7.0.0: {} 982 | 983 | isexe@2.0.0: {} 984 | 985 | istanbul-lib-coverage@3.2.2: {} 986 | 987 | istanbul-lib-report@3.0.1: 988 | dependencies: 989 | istanbul-lib-coverage: 3.2.2 990 | make-dir: 4.0.0 991 | supports-color: 7.2.0 992 | 993 | istanbul-reports@3.1.7: 994 | dependencies: 995 | html-escaper: 2.0.2 996 | istanbul-lib-report: 3.0.1 997 | 998 | jackspeak@3.4.3: 999 | dependencies: 1000 | '@isaacs/cliui': 8.0.2 1001 | optionalDependencies: 1002 | '@pkgjs/parseargs': 0.11.0 1003 | 1004 | jest-diff@29.7.0: 1005 | dependencies: 1006 | chalk: 4.1.2 1007 | diff-sequences: 29.6.3 1008 | jest-get-type: 29.6.3 1009 | pretty-format: 29.7.0 1010 | 1011 | jest-get-type@29.6.3: {} 1012 | 1013 | jest-matcher-utils@29.7.0: 1014 | dependencies: 1015 | chalk: 4.1.2 1016 | jest-diff: 29.7.0 1017 | jest-get-type: 29.6.3 1018 | pretty-format: 29.7.0 1019 | 1020 | jest-message-util@29.7.0: 1021 | dependencies: 1022 | '@babel/code-frame': 7.26.2 1023 | '@jest/types': 29.6.3 1024 | '@types/stack-utils': 2.0.3 1025 | chalk: 4.1.2 1026 | graceful-fs: 4.2.11 1027 | micromatch: 4.0.8 1028 | pretty-format: 29.7.0 1029 | slash: 3.0.0 1030 | stack-utils: 2.0.6 1031 | 1032 | jest-util@29.7.0: 1033 | dependencies: 1034 | '@jest/types': 29.6.3 1035 | '@types/node': 22.10.10 1036 | chalk: 4.1.2 1037 | ci-info: 3.9.0 1038 | graceful-fs: 4.2.11 1039 | picomatch: 2.3.1 1040 | 1041 | js-tokens@4.0.0: {} 1042 | 1043 | locate-path@6.0.0: 1044 | dependencies: 1045 | p-locate: 5.0.0 1046 | 1047 | lru-cache@10.4.3: {} 1048 | 1049 | make-dir@4.0.0: 1050 | dependencies: 1051 | semver: 7.6.3 1052 | 1053 | micromatch@4.0.8: 1054 | dependencies: 1055 | braces: 3.0.3 1056 | picomatch: 2.3.1 1057 | 1058 | minimatch@9.0.5: 1059 | dependencies: 1060 | brace-expansion: 2.0.1 1061 | 1062 | minipass@7.1.2: {} 1063 | 1064 | p-limit@3.1.0: 1065 | dependencies: 1066 | yocto-queue: 0.1.0 1067 | 1068 | p-locate@5.0.0: 1069 | dependencies: 1070 | p-limit: 3.1.0 1071 | 1072 | package-json-from-dist@1.0.1: {} 1073 | 1074 | path-exists@4.0.0: {} 1075 | 1076 | path-key@3.1.1: {} 1077 | 1078 | path-scurry@1.11.1: 1079 | dependencies: 1080 | lru-cache: 10.4.3 1081 | minipass: 7.1.2 1082 | 1083 | picocolors@1.1.1: {} 1084 | 1085 | picomatch@2.3.1: {} 1086 | 1087 | pretty-format@29.7.0: 1088 | dependencies: 1089 | '@jest/schemas': 29.6.3 1090 | ansi-styles: 5.2.0 1091 | react-is: 18.3.1 1092 | 1093 | react-is@18.3.1: {} 1094 | 1095 | require-directory@2.1.1: {} 1096 | 1097 | resolve-pkg-maps@1.0.0: {} 1098 | 1099 | semver@7.6.3: {} 1100 | 1101 | shebang-command@2.0.0: 1102 | dependencies: 1103 | shebang-regex: 3.0.0 1104 | 1105 | shebang-regex@3.0.0: {} 1106 | 1107 | signal-exit@4.1.0: {} 1108 | 1109 | slash@3.0.0: {} 1110 | 1111 | stack-utils@2.0.6: 1112 | dependencies: 1113 | escape-string-regexp: 2.0.0 1114 | 1115 | string-width@4.2.3: 1116 | dependencies: 1117 | emoji-regex: 8.0.0 1118 | is-fullwidth-code-point: 3.0.0 1119 | strip-ansi: 6.0.1 1120 | 1121 | string-width@5.1.2: 1122 | dependencies: 1123 | eastasianwidth: 0.2.0 1124 | emoji-regex: 9.2.2 1125 | strip-ansi: 7.1.0 1126 | 1127 | strip-ansi@6.0.1: 1128 | dependencies: 1129 | ansi-regex: 5.0.1 1130 | 1131 | strip-ansi@7.1.0: 1132 | dependencies: 1133 | ansi-regex: 6.1.0 1134 | 1135 | supports-color@7.2.0: 1136 | dependencies: 1137 | has-flag: 4.0.0 1138 | 1139 | test-exclude@7.0.1: 1140 | dependencies: 1141 | '@istanbuljs/schema': 0.1.3 1142 | glob: 10.4.5 1143 | minimatch: 9.0.5 1144 | 1145 | to-regex-range@5.0.1: 1146 | dependencies: 1147 | is-number: 7.0.0 1148 | 1149 | tsx@4.19.2: 1150 | dependencies: 1151 | esbuild: 0.23.1 1152 | get-tsconfig: 4.10.0 1153 | optionalDependencies: 1154 | fsevents: 2.3.3 1155 | 1156 | typescript@5.7.3: {} 1157 | 1158 | undici-types@6.20.0: {} 1159 | 1160 | v8-to-istanbul@9.3.0: 1161 | dependencies: 1162 | '@jridgewell/trace-mapping': 0.3.25 1163 | '@types/istanbul-lib-coverage': 2.0.6 1164 | convert-source-map: 2.0.0 1165 | 1166 | which@2.0.2: 1167 | dependencies: 1168 | isexe: 2.0.0 1169 | 1170 | wrap-ansi@7.0.0: 1171 | dependencies: 1172 | ansi-styles: 4.3.0 1173 | string-width: 4.2.3 1174 | strip-ansi: 6.0.1 1175 | 1176 | wrap-ansi@8.1.0: 1177 | dependencies: 1178 | ansi-styles: 6.2.1 1179 | string-width: 5.1.2 1180 | strip-ansi: 7.1.0 1181 | 1182 | y18n@5.0.8: {} 1183 | 1184 | yargs-parser@21.1.1: {} 1185 | 1186 | yargs@17.7.2: 1187 | dependencies: 1188 | cliui: 8.0.1 1189 | escalade: 3.2.0 1190 | get-caller-file: 2.0.5 1191 | require-directory: 2.1.1 1192 | string-width: 4.2.3 1193 | y18n: 5.0.8 1194 | yargs-parser: 21.1.1 1195 | 1196 | yocto-queue@0.1.0: {} 1197 | -------------------------------------------------------------------------------- /src/concat.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'node:buffer' 2 | import { Writable } from 'node:stream' 3 | 4 | interface ConcatStreamOptions { 5 | encoding?: string | null 6 | } 7 | 8 | const isArrayish = (arr: unknown) => /Array\]$/.test(Object.prototype.toString.call(arr)) 9 | 10 | const isBufferish = (p: unknown) => 11 | typeof p === 'string' || isArrayish(p) || (p && typeof (p as any).subarray === 'function') 12 | 13 | export class ConcatStream extends Writable { 14 | encoding: ConcatStreamOptions['encoding'] | null 15 | shouldInferEncoding: boolean 16 | body: any[] 17 | constructor(opts?: ConcatStreamOptions | ((body: any) => void), cb?: (body: any) => void) { 18 | if (typeof opts === 'function') { 19 | cb = opts 20 | opts = {} 21 | } 22 | if (!opts) opts = {} 23 | 24 | super({ objectMode: true }) 25 | 26 | this.encoding = opts.encoding ? (String(opts.encoding).toLowerCase() as ConcatStreamOptions['encoding']) : null 27 | this.shouldInferEncoding = !this.encoding 28 | this.body = [] 29 | 30 | if (cb) this.on('finish', () => void cb(this.getBody())) 31 | } 32 | 33 | _write(chunk: any, enc: NodeJS.BufferEncoding, next?: () => void) { 34 | this.body.push(chunk) 35 | next?.() 36 | } 37 | 38 | inferEncoding(buff?: unknown) { 39 | const firstBuffer = buff === undefined ? this.body[0] : buff 40 | if (Buffer.isBuffer(firstBuffer)) return 'buffer' 41 | if (typeof Uint8Array !== 'undefined' && firstBuffer instanceof Uint8Array) return 'uint8array' 42 | if (Array.isArray(firstBuffer)) return 'array' 43 | if (typeof firstBuffer === 'string') return 'string' 44 | if (Object.prototype.toString.call(firstBuffer) === '[object Object]') return 'object' 45 | return 'buffer' 46 | } 47 | 48 | getBody() { 49 | if (!this.encoding && this.body.length === 0) return [] 50 | if (this.shouldInferEncoding) this.encoding = this.inferEncoding() 51 | 52 | switch (this.encoding) { 53 | case 'array': 54 | return this.body.flat() 55 | case 'string': 56 | return this.stringConcat(this.body) 57 | case 'buffer': 58 | return this.bufferConcat(this.body) 59 | case 'uint8array': 60 | return this.u8Concat(this.body) 61 | default: 62 | return this.body 63 | } 64 | } 65 | 66 | stringConcat(parts: any[]) { 67 | const strings = [] 68 | for (const p of parts) { 69 | if (typeof p === 'string' || Buffer.isBuffer(p)) strings.push(p) 70 | else strings.push(Buffer.from(isBufferish(p) ? p : String(p))) 71 | } 72 | 73 | return Buffer.isBuffer(parts[0]) ? Buffer.concat(strings as Buffer[]).toString('utf8') : strings.join('') 74 | } 75 | 76 | bufferConcat(parts: any[]): Buffer { 77 | return Buffer.concat(parts.map((p) => (Buffer.isBuffer(p) ? p : Buffer.from(isBufferish(p) ? p : String(p))))) 78 | } 79 | 80 | u8Concat(parts: Uint8Array[]): Uint8Array { 81 | const u8 = new Uint8Array( 82 | parts.reduce((len, part) => len + (typeof part === 'string' ? Buffer.from(part).length : part.length), 0) 83 | ) 84 | parts.reduce((offset, part) => { 85 | const buffer = typeof part === 'string' ? Buffer.from(part) : part 86 | u8.set(buffer, offset) 87 | return offset + buffer.length 88 | }, 0) 89 | return u8 90 | } 91 | } 92 | 93 | export const concat = (opts?: ConcatStreamOptions | ((body: any) => void), cb?: (body: any) => void) => 94 | new ConcatStream(opts, cb) 95 | -------------------------------------------------------------------------------- /src/progress.ts: -------------------------------------------------------------------------------- 1 | import { IncomingMessage } from 'node:http' 2 | import { type Stream, Transform } from 'node:stream' 3 | 4 | let tick = 1, 5 | timer: NodeJS.Timeout 6 | const inc = () => (tick = (tick + 1) & 65535) 7 | 8 | const speedometer = (seconds = 5) => { 9 | timer = timer || ((timer = setInterval(inc, 250)), timer.unref?.(), timer) 10 | const size = 4 * seconds, 11 | buf = [0] 12 | let pointer = 1, 13 | last = (tick - 1) & 65535 14 | 15 | return (delta: number) => { 16 | let dist = Math.min((tick - last) & 65535, size) 17 | last = tick 18 | while (dist--) buf[pointer] = buf[(pointer = (pointer + 1) % size || size - 1)] 19 | if (delta) buf[pointer - 1] += delta 20 | return ((buf[pointer - 1] - (buf.length < 4 ? 0 : buf[pointer % size])) * 4) / buf.length 21 | } 22 | } 23 | 24 | export interface ProgressOptions { 25 | length?: number 26 | time?: number 27 | drain?: boolean 28 | transferred?: number 29 | speed?: number 30 | objectMode?: boolean 31 | } 32 | 33 | export interface ProgressUpdate { 34 | percentage: number 35 | transferred: number 36 | length: number 37 | remaining: number 38 | eta: number 39 | runtime: number 40 | delta?: number 41 | speed?: number 42 | } 43 | 44 | type ProgressCallback = (update: ProgressUpdate) => void 45 | 46 | export interface ProgressStream extends Transform { 47 | on(event: string | symbol, listener: (...args: any[]) => void): this 48 | on(event: 'length', listener: (length: number) => void): this 49 | on(event: 'progress', listener: (update: ProgressUpdate) => void): this 50 | emit(event: string | symbol, ...args: any[]): boolean 51 | emit(event: 'length', length: number): boolean 52 | emit(event: 'progress', update: ProgressUpdate): boolean 53 | } 54 | 55 | export class ProgressStream extends Transform { 56 | #length: number 57 | #time: number 58 | #drain: boolean 59 | #transferred: number 60 | #nextUpdate: number 61 | #delta: number 62 | #speed: (delta: number) => number 63 | #startTime: number 64 | #update: ProgressUpdate 65 | 66 | constructor(options: ProgressOptions | ProgressCallback = {}, onprogress?: ProgressCallback) { 67 | if (typeof options === 'function') { 68 | onprogress = options 69 | options = {} 70 | } 71 | const opts = options as ProgressOptions 72 | super({ 73 | objectMode: opts.objectMode, 74 | highWaterMark: opts.objectMode ? 16 : undefined 75 | }) 76 | 77 | this.#length = opts.length || 0 78 | this.#time = opts.time || 0 79 | this.#drain = opts.drain || false 80 | this.#transferred = opts.transferred || 0 81 | this.#nextUpdate = Date.now() + this.#time 82 | this.#delta = 0 83 | this.#speed = speedometer(opts.speed || 5000) 84 | this.#startTime = Date.now() 85 | 86 | this.#update = { 87 | percentage: 0, 88 | transferred: this.#transferred, 89 | length: this.#length, 90 | remaining: this.#length, 91 | eta: 0, 92 | runtime: 0 93 | } 94 | 95 | if (this.#drain) this.resume() 96 | if (onprogress) this.on('progress', onprogress) 97 | 98 | this.#pipeHandler() 99 | } 100 | 101 | #emitProgress(ended = false) { 102 | this.#update.delta = this.#delta 103 | 104 | this.#update.percentage = 105 | ended && this.#length > 0 ? 100 : this.#length ? (this.#transferred / this.#length) * 100 : 0 106 | 107 | this.#update.speed = this.#speed(this.#delta) 108 | this.#update.eta = Math.round(this.#update.remaining / (this.#update.speed || 1)) 109 | this.#update.runtime = Math.floor((Date.now() - this.#startTime) / 1000) 110 | this.#nextUpdate = Date.now() + this.#time 111 | this.#delta = 0 112 | 113 | this.emit('progress', this.#update) 114 | } 115 | 116 | _transform(chunk: any, _: BufferEncoding, callback: (error?: Error | null, data?: any) => void) { 117 | const len = this.readableObjectMode ? 1 : chunk.length 118 | this.#transferred += len 119 | this.#delta += len 120 | this.#update.transferred = this.#transferred 121 | this.#update.remaining = this.#length >= this.#transferred ? this.#length - this.#transferred : 0 122 | 123 | if (Date.now() >= this.#nextUpdate) this.#emitProgress() 124 | callback(null, chunk) 125 | } 126 | 127 | _flush(callback?: () => void) { 128 | this.#emitProgress(true) 129 | callback?.() 130 | } 131 | 132 | #pipeHandler() { 133 | this.on('pipe', (stream: IncomingMessage | Stream) => { 134 | if (typeof this.#length === 'number' && this.#length > 0) return 135 | 136 | if (stream instanceof IncomingMessage && stream.headers?.['content-length']) 137 | return this.setLength(Number.parseInt(stream.headers['content-length'])) 138 | 139 | if ('length' in stream && typeof stream.length === 'number') return this.setLength(stream.length) 140 | 141 | stream.on('response', (res: IncomingMessage) => { 142 | if (!res.headers || res.headers['content-encoding'] === 'gzip') return 143 | if (res.headers['content-length']) this.setLength(Number.parseInt(res.headers['content-length'])) 144 | }) 145 | }) 146 | } 147 | setLength(newLength: number) { 148 | this.#length = newLength 149 | this.#update.length = newLength 150 | this.#update.remaining = newLength - this.#transferred 151 | this.emit('length', newLength) 152 | } 153 | 154 | progress() { 155 | this.#update.speed = this.#speed(0) 156 | this.#update.eta = Math.round(this.#update.remaining / (this.#update.speed || 1)) 157 | return this.#update 158 | } 159 | } 160 | 161 | export function createProgressStream(options?: ProgressOptions | ProgressCallback, onprogress?: ProgressCallback) { 162 | return new ProgressStream(options, onprogress) 163 | } 164 | -------------------------------------------------------------------------------- /test/concat.test.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from 'node:buffer' 2 | import { exec, spawn } from 'node:child_process' 3 | import { test } from 'node:test' 4 | import { expect } from 'expect' 5 | import { concat } from '../src/concat' 6 | 7 | test('ls command', () => { 8 | const cmd = spawn('ls', [import.meta.dirname]) 9 | cmd.stdout.pipe( 10 | concat((out) => { 11 | exec(`ls ${import.meta.dirname}`, (_, body) => { 12 | expect(out.toString()).toBe(body.toString()) 13 | }) 14 | }) 15 | ) 16 | }) 17 | 18 | test('array stream', () => { 19 | const arrays = concat({ encoding: 'array' }, (out: number[]) => { 20 | expect(out).toEqual([1, 2, 3, 4, 5, 6]) 21 | }) 22 | arrays.write([1, 2, 3]) 23 | arrays.write([4, 5, 6]) 24 | arrays.end() 25 | }) 26 | 27 | test('buffer stream', () => { 28 | const buffers = concat((out: Buffer) => { 29 | expect(Buffer.isBuffer(out)).toBe(true) 30 | expect(out.toString('utf8')).toBe('pizza Array is not a stringy cat') 31 | }) 32 | buffers.write(Buffer.from('pizza Array is not a ', 'utf8')) 33 | buffers.write(Buffer.from('stringy cat')) 34 | buffers.end() 35 | }) 36 | 37 | test('buffer mixed writes', () => { 38 | const buffers = concat((out: Buffer) => { 39 | expect(Buffer.isBuffer(out)).toBe(true) 40 | expect(out.toString('utf8')).toBe('pizza Array is not a stringy cat555') 41 | }) 42 | buffers.write(Buffer.from('pizza')) 43 | buffers.write(' Array is not a ') 44 | buffers.write([115, 116, 114, 105, 110, 103, 121]) 45 | const u8 = new Uint8Array(4) 46 | u8[0] = 32 47 | u8[1] = 99 48 | u8[2] = 97 49 | u8[3] = 116 50 | buffers.write(u8) 51 | buffers.write(555) 52 | buffers.end() 53 | }) 54 | 55 | test('type inference works as expected', () => { 56 | const stream = concat() 57 | expect(stream.inferEncoding(['hello'])).toBe('array') 58 | expect(stream.inferEncoding(Buffer.from('hello'))).toBe('buffer') 59 | expect(stream.inferEncoding(undefined)).toBe('buffer') 60 | expect(stream.inferEncoding(new Uint8Array(1))).toBe('uint8array') 61 | expect(stream.inferEncoding('hello')).toBe('string') 62 | expect(stream.inferEncoding('')).toBe('string') 63 | expect(stream.inferEncoding({ hello: 'world' })).toBe('object') 64 | expect(stream.inferEncoding(1)).toBe('buffer') 65 | }) 66 | 67 | test('no callback stream', () => { 68 | const stream = concat() 69 | stream.write('space') 70 | stream.end(' cats') 71 | }) 72 | 73 | test('no encoding set, no data', () => { 74 | const stream = concat((data: any) => { 75 | expect(data).toEqual([]) 76 | }) 77 | stream.end() 78 | }) 79 | 80 | test('encoding set to string, no data', () => { 81 | const stream = concat({ encoding: 'string' }, (data: string) => { 82 | expect(data).toBe('') 83 | }) 84 | stream.end() 85 | }) 86 | 87 | test('typed array stream', () => { 88 | const a = new Uint8Array(5) 89 | a[0] = 97 90 | a[1] = 98 91 | a[2] = 99 92 | a[3] = 100 93 | a[4] = 101 // 'abcde' 94 | 95 | const b = new Uint8Array(3) 96 | b[0] = 32 97 | b[1] = 102 98 | b[2] = 103 // ' fg' 99 | 100 | const c = new Uint8Array(4) 101 | c[0] = 32 102 | c[1] = 120 103 | c[2] = 121 104 | c[3] = 122 // ' xyz' 105 | 106 | return new Promise((resolve) => { 107 | const arrays = concat({ encoding: 'Uint8Array' }, (out: Uint8Array) => { 108 | expect(typeof out.subarray).toBe('function') 109 | expect(Buffer.from(out).toString('utf8')).toBe('abcde fg xyz') 110 | resolve() 111 | }) 112 | 113 | arrays.write(a) 114 | arrays.write(b) 115 | arrays.end(c) 116 | }) 117 | }) 118 | 119 | test('typed array from strings, buffers, and arrays', () => { 120 | return new Promise((resolve) => { 121 | const arrays = concat({ encoding: 'Uint8Array' }, (out: Uint8Array) => { 122 | expect(typeof out.subarray).toBe('function') 123 | expect(Buffer.from(out).toString('utf8')).toBe('abcde fg xyz') 124 | resolve() 125 | }) 126 | 127 | arrays.write('abcde') 128 | arrays.write(Buffer.from(' fg ')) 129 | arrays.end([120, 121, 122]) // 'xyz' 130 | }) 131 | }) 132 | 133 | test('writing objects', () => { 134 | const stream = concat({ encoding: 'objects' }, (objs: any[]) => { 135 | expect(objs.length).toBe(2) 136 | expect(objs[0]).toEqual({ foo: 'bar' }) 137 | expect(objs[1]).toEqual({ baz: 'taco' }) 138 | }) 139 | stream.write({ foo: 'bar' }) 140 | stream.write({ baz: 'taco' }) 141 | stream.end() 142 | }) 143 | 144 | test('switch to objects encoding if no encoding specified and objects are written', () => { 145 | const stream = concat((objs: any[]) => { 146 | expect(objs.length).toBe(2) 147 | expect(objs[0]).toEqual({ foo: 'bar' }) 148 | expect(objs[1]).toEqual({ baz: 'taco' }) 149 | }) 150 | stream.write({ foo: 'bar' }) 151 | stream.write({ baz: 'taco' }) 152 | stream.end() 153 | }) 154 | 155 | test('string -> buffer stream', () => { 156 | const strings = concat({ encoding: 'buffer' }, (out: Buffer) => { 157 | expect(Buffer.isBuffer(out)).toBe(true) 158 | expect(out.toString('utf8')).toBe('nacho dogs') 159 | }) 160 | strings.write('nacho ') 161 | strings.write('dogs') 162 | strings.end() 163 | }) 164 | 165 | test('string stream', () => { 166 | const strings = concat({ encoding: 'string' }, (out: string) => { 167 | expect(typeof out).toBe('string') 168 | expect(out).toBe('nacho dogs') 169 | }) 170 | strings.write('nacho ') 171 | strings.write('dogs') 172 | strings.end() 173 | }) 174 | 175 | test('end chunk', () => { 176 | const endchunk = concat({ encoding: 'string' }, (out: string) => { 177 | expect(out).toBe('this is the end') 178 | }) 179 | endchunk.write('this ') 180 | endchunk.write('is the ') 181 | endchunk.end('end') 182 | }) 183 | 184 | test('string from mixed write encodings', () => { 185 | const strings = concat({ encoding: 'string' }, (out: string) => { 186 | expect(typeof out).toBe('string') 187 | expect(out).toBe('nacho dogs') 188 | }) 189 | strings.write('na') 190 | strings.write(Buffer.from('cho')) 191 | strings.write([32, 100]) 192 | const u8 = new Uint8Array(3) 193 | u8[0] = 111 194 | u8[1] = 103 195 | u8[2] = 115 196 | strings.end(u8) 197 | }) 198 | 199 | test('string from buffers with multibyte characters', () => { 200 | const strings = concat({ encoding: 'string' }, (out: string) => { 201 | expect(typeof out).toBe('string') 202 | expect(out).toBe('☃☃☃☃☃☃☃☃') 203 | }) 204 | const snowman = Buffer.from('☃') 205 | for (let i = 0; i < 8; i++) { 206 | strings.write(snowman.subarray(0, 1)) 207 | strings.write(snowman.subarray(1)) 208 | } 209 | strings.end() 210 | }) 211 | 212 | test('string infer encoding with empty string chunk', () => { 213 | const strings = concat((out: string) => { 214 | expect(typeof out).toBe('string') 215 | expect(out).toBe('nacho dogs') 216 | }) 217 | strings.write('') 218 | strings.write('nacho ') 219 | strings.write('dogs') 220 | strings.end() 221 | }) 222 | 223 | test('to string numbers', () => { 224 | const write = concat((str: string) => { 225 | expect(str).toBe('a1000') 226 | }) 227 | write.write('a') 228 | write.write(1000) 229 | write.end() 230 | }) 231 | -------------------------------------------------------------------------------- /test/progress.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import http from 'node:http' 3 | import { Readable, Transform } from 'node:stream' 4 | import { pipeline } from 'node:stream/promises' 5 | import { beforeEach, describe, it, mock } from 'node:test' 6 | import { setTimeout } from 'node:timers/promises' 7 | import { createProgressStream } from '../src/progress' 8 | import type { ProgressOptions, ProgressUpdate } from '../src/progress' 9 | 10 | const sampleData = Buffer.alloc(1024 * 10) 11 | 12 | describe('ProgressStream', async () => { 13 | let lastUpdate: ProgressUpdate 14 | 15 | beforeEach(() => { 16 | lastUpdate = { 17 | percentage: 0, 18 | transferred: 0, 19 | length: 0, 20 | remaining: 0, 21 | eta: 0, 22 | runtime: 0 23 | } 24 | }) 25 | 26 | async function runTestStream(options: ProgressOptions = {}, data = sampleData) { 27 | const stream = createProgressStream({ 28 | time: 10, 29 | drain: true, 30 | ...options 31 | }) 32 | 33 | const onProgress = mock.fn((update: ProgressUpdate) => { 34 | lastUpdate = update 35 | }) 36 | 37 | stream.on('progress', onProgress) 38 | 39 | const consumer = new Transform({ 40 | transform(chunk, encoding, callback) { 41 | callback(null, chunk) 42 | } 43 | }) 44 | 45 | await pipeline(Readable.from(data), stream, consumer) 46 | 47 | return { onProgress } 48 | } 49 | 50 | it('should emit progress events with correct data', async () => { 51 | const { onProgress } = await runTestStream({ length: sampleData.length }) 52 | 53 | // Verify final progress values 54 | assert.ok(onProgress.mock.callCount() > 0, 'onProgress should have been called') 55 | assert.strictEqual(lastUpdate.percentage, 100, 'percentage should be 100') 56 | assert.strictEqual(lastUpdate.transferred, sampleData.length, 'transferred should equal sampleData length') 57 | assert.strictEqual(lastUpdate.remaining, 0, 'remaining should be 0') 58 | assert.strictEqual(typeof lastUpdate.eta, 'number', 'eta should be a number') 59 | assert.strictEqual(typeof lastUpdate.runtime, 'number', 'runtime should be a number') 60 | }) 61 | 62 | it('should handle unknown length streams', async () => { 63 | const { onProgress } = await runTestStream() 64 | 65 | assert.ok(onProgress.mock.callCount() > 0, 'onProgress should have been called') 66 | assert.strictEqual(lastUpdate.percentage, 0, 'percentage should be 0') 67 | assert.strictEqual(lastUpdate.transferred, sampleData.length, 'transferred should equal sampleData length') 68 | }) 69 | 70 | it('should handle dynamic length updates via setLength', async () => { 71 | const stream = createProgressStream({ time: 10, drain: true }) 72 | const onProgress = mock.fn((update: ProgressUpdate) => { 73 | lastUpdate = update 74 | }) 75 | 76 | stream.on('progress', onProgress) 77 | 78 | // First chunk - 50% of data 79 | stream.write(Buffer.alloc(5120)) 80 | await setTimeout(10) 81 | 82 | // Update length to total size 83 | stream.setLength(sampleData.length) 84 | 85 | // Pipe remaining data 86 | await pipeline( 87 | Readable.from(sampleData.subarray(5120)), 88 | stream, 89 | new Transform({ 90 | transform(chunk, encoding, callback) { 91 | callback(null, chunk) 92 | } 93 | }) 94 | ) 95 | 96 | assert.strictEqual(lastUpdate.percentage, 100, 'percentage should be 100') 97 | assert.strictEqual(lastUpdate.transferred, sampleData.length, 'transferred should equal sampleData length') 98 | }) 99 | 100 | it('should trigger onprogress event', async () => { 101 | const onProgress = mock.fn() 102 | const stream = createProgressStream(onProgress) 103 | 104 | const consumer = new Transform({ 105 | transform(chunk, encoding, callback) { 106 | callback(null, chunk) 107 | } 108 | }) 109 | 110 | await pipeline(Readable.from(sampleData), stream, consumer) 111 | 112 | assert(onProgress.mock.callCount() > 0, 'no progress happened') 113 | }) 114 | 115 | it('should properly log progress for a 1MB file download', async () => { 116 | const progressStream = createProgressStream({ 117 | drain: true, 118 | time: 100, 119 | speed: 20 120 | }) 121 | 122 | const progressPercentages: number[] = [] 123 | 124 | progressStream.on('progress', (progress) => { 125 | progressPercentages.push(progress.percentage) 126 | }) 127 | 128 | const response = await new Promise((resolve, reject) => { 129 | const req = http.request( 130 | { 131 | method: 'GET', 132 | host: 'cachefly.cachefly.net', 133 | path: '/1mb.test', 134 | headers: { 135 | 'user-agent': 'testy test' 136 | } 137 | }, 138 | (res) => { 139 | resolve(res) 140 | } 141 | ) 142 | 143 | req.on('error', (err) => { 144 | reject(err) 145 | }) 146 | 147 | req.end() 148 | }) 149 | 150 | response.pipe(progressStream) 151 | 152 | return await new Promise((resolve, reject) => { 153 | progressStream.on('end', () => { 154 | assert.ok(progressPercentages.length > 0, 'No progress updates were received') 155 | try { 156 | assert.ok(progressPercentages.length > 0, 'No progress updates were received') 157 | 158 | const finalPercentage = progressPercentages[progressPercentages.length - 1] 159 | assert.strictEqual(finalPercentage, 100, 'Final progress percentage should be 100%') 160 | 161 | resolve() 162 | } catch (err) { 163 | reject(err) 164 | } 165 | }) 166 | 167 | progressStream.on('error', (err) => { 168 | reject(err) 169 | }) 170 | }) 171 | }) 172 | }) 173 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "verbatimModuleSyntax": true, 14 | 15 | // Best practices 16 | "strict": true, 17 | "skipLibCheck": true, 18 | "noFallthroughCasesInSwitch": true, 19 | 20 | // Some stricter flags (disabled by default) 21 | "noUnusedLocals": false, 22 | "noUnusedParameters": false, 23 | "noPropertyAccessFromIndexSignature": false, 24 | 25 | "outDir": "dist", 26 | "declaration": true, 27 | "isolatedModules": true 28 | }, 29 | "include": ["src/*.ts"] 30 | } 31 | --------------------------------------------------------------------------------