├── .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 |
--------------------------------------------------------------------------------