testing local-first datastores
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

burrito.space 568a93c9

+6046
.README.md.swp

This is a binary file and will not be displayed.

+17
.claude/settings.local.json
··· 1 + { 2 + "permissions": { 3 + "allow": [ 4 + "WebSearch", 5 + "WebFetch(domain:github.com)", 6 + "Bash(npm run charts:*)", 7 + "Bash(open:*)", 8 + "Bash(npm install:*)", 9 + "Bash(npm uninstall:*)", 10 + "Bash(npm run bench:*)", 11 + "Bash(npx tsx:*)", 12 + "Bash(du -sh:*)" 13 + ], 14 + "deny": [], 15 + "ask": [] 16 + } 17 + }
+5
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + test-data/ 4 + results/ 5 + *.log
+1
.npmrc
··· 1 + legacy-peer-deps=true
+33
CLAUDE.md
··· 1 + # CLAUDE.md 2 + 3 + This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 + 5 + ## Project Overview 6 + 7 + Node.js/TypeScript benchmark suite comparing local datastore performance (TinyBase, LevelGraph, SQLite) with ~1GB test data. Each benchmark runs 10 iterations, discards high/low scores, and reports the median. 8 + 9 + ## Commands 10 + 11 + ```bash 12 + npm install # Install dependencies 13 + npm run generate # Generate test data (~1GB) to test-data/ 14 + npm run bench # Run benchmarks on all stores (10 iterations each) 15 + npm run bench:store tinybase # Benchmark single store 16 + npm run bench:store levelgraph 17 + npm run bench:store sqlite 18 + npm run charts # Regenerate charts from latest results 19 + ``` 20 + 21 + ## Architecture 22 + 23 + - `src/generator/` - Test data generation (URLs, images, documents, metadata) 24 + - `src/harness/` - Benchmark runner, timing, and result reporting 25 + - `src/stores/` - Datastore adapters implementing `DatastoreAdapter` interface 26 + - `src/runner.ts` - Main CLI entry point 27 + 28 + ## Adding a New Datastore 29 + 30 + 1. Create `src/stores/yourstore.ts` implementing `DatastoreAdapter` from `src/harness/types.ts` 31 + 2. Register in `src/stores/index.ts` 32 + 33 + The adapter interface requires: `init()`, `cleanup()`, `addUrls()`, `addImage()`, `addDocument()`, `addMetadata()`, `getRecentUrls()`, `getImages()`, `getDocuments()`, `getDiskUsage()`
+77
README.md
··· 1 + # localstress 2 + 3 + Benchmark suite for comparing local datastore performance in Node.js. 4 + 5 + ## Quick Start 6 + 7 + ```bash 8 + npm install 9 + npm run generate # Generate ~1GB test data (takes a few minutes) 10 + npm run bench # Run benchmarks on all stores 11 + ``` 12 + 13 + ## Datastores Tested 14 + 15 + - **TinyBase** - Reactive data store for local-first apps 16 + - **LevelGraph** - Graph database built on LevelDB 17 + - **SQLite** - Embedded SQL database (via better-sqlite3) 18 + 19 + ## Methodology 20 + 21 + Each benchmark is run **10 times** per datastore. The highest and lowest scores are discarded, and the **median** of the remaining 8 runs is reported. This reduces the impact of outliers from system variability. 22 + 23 + ## Test Dataset 24 + 25 + Target ~1GB total: 26 + 27 + - 10,000 URLs with metadata 28 + - 1,000 PNG images (~900MB) 29 + - 1,000 text documents (~50MB) 30 + - 100,000 metadata rows 31 + 32 + ## Benchmarks 33 + 34 + **Init** 35 + - Store initialization time 36 + 37 + **Writes** 38 + - Add all URLs 39 + - Add all metadata rows 40 + - Add all images 41 + - Add all documents 42 + 43 + **Reads** 44 + - Get 100 most recent URLs 45 + - Get 10 random images 46 + - Get 1000 random documents 47 + 48 + **Disk** 49 + - Total storage space used 50 + 51 + ## Commands 52 + 53 + ```bash 54 + npm run generate # Generate test data to test-data/ 55 + npm run bench # Benchmark all stores (10 iterations each) 56 + npm run bench:store tinybase # Benchmark single store 57 + npm run bench:store levelgraph 58 + npm run bench:store sqlite 59 + npm run charts # Regenerate charts from latest results 60 + ``` 61 + 62 + ## Adding a New Datastore 63 + 64 + 1. Create `src/stores/yourstore.ts` implementing `DatastoreAdapter` from `src/harness/types.ts` 65 + 2. Register in `src/stores/index.ts` 66 + 67 + ## Project Structure 68 + 69 + ``` 70 + src/ 71 + generator/ # Test data generation 72 + harness/ # Benchmark runner and reporting 73 + stores/ # Datastore adapters 74 + runner.ts # CLI entry point 75 + test-data/ # Generated test data (gitignored) 76 + results/ # Benchmark results JSON (gitignored) 77 + ```
+3662
package-lock.json
··· 1 + { 2 + "name": "localstress", 3 + "version": "1.0.0", 4 + "lockfileVersion": 3, 5 + "requires": true, 6 + "packages": { 7 + "": { 8 + "name": "localstress", 9 + "version": "1.0.0", 10 + "dependencies": { 11 + "better-sqlite3": "^11.0.0", 12 + "canvas": "^2.11.0", 13 + "chart.js": "^4.4.0", 14 + "level": "^8.0.0", 15 + "levelgraph": "^4.0.0", 16 + "sharp": "^0.33.0", 17 + "tinybase": "^5.0.0", 18 + "vega": "^6.2.0", 19 + "vega-lite": "^6.4.1" 20 + }, 21 + "devDependencies": { 22 + "@types/node": "^20.10.0", 23 + "tsx": "^4.6.0", 24 + "typescript": "^5.3.0" 25 + } 26 + }, 27 + "node_modules/@emnapi/runtime": { 28 + "version": "1.7.1", 29 + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", 30 + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", 31 + "license": "MIT", 32 + "optional": true, 33 + "dependencies": { 34 + "tslib": "^2.4.0" 35 + } 36 + }, 37 + "node_modules/@esbuild/aix-ppc64": { 38 + "version": "0.27.1", 39 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", 40 + "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", 41 + "cpu": [ 42 + "ppc64" 43 + ], 44 + "dev": true, 45 + "license": "MIT", 46 + "optional": true, 47 + "os": [ 48 + "aix" 49 + ], 50 + "engines": { 51 + "node": ">=18" 52 + } 53 + }, 54 + "node_modules/@esbuild/android-arm": { 55 + "version": "0.27.1", 56 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", 57 + "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", 58 + "cpu": [ 59 + "arm" 60 + ], 61 + "dev": true, 62 + "license": "MIT", 63 + "optional": true, 64 + "os": [ 65 + "android" 66 + ], 67 + "engines": { 68 + "node": ">=18" 69 + } 70 + }, 71 + "node_modules/@esbuild/android-arm64": { 72 + "version": "0.27.1", 73 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", 74 + "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", 75 + "cpu": [ 76 + "arm64" 77 + ], 78 + "dev": true, 79 + "license": "MIT", 80 + "optional": true, 81 + "os": [ 82 + "android" 83 + ], 84 + "engines": { 85 + "node": ">=18" 86 + } 87 + }, 88 + "node_modules/@esbuild/android-x64": { 89 + "version": "0.27.1", 90 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", 91 + "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", 92 + "cpu": [ 93 + "x64" 94 + ], 95 + "dev": true, 96 + "license": "MIT", 97 + "optional": true, 98 + "os": [ 99 + "android" 100 + ], 101 + "engines": { 102 + "node": ">=18" 103 + } 104 + }, 105 + "node_modules/@esbuild/darwin-arm64": { 106 + "version": "0.27.1", 107 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", 108 + "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", 109 + "cpu": [ 110 + "arm64" 111 + ], 112 + "dev": true, 113 + "license": "MIT", 114 + "optional": true, 115 + "os": [ 116 + "darwin" 117 + ], 118 + "engines": { 119 + "node": ">=18" 120 + } 121 + }, 122 + "node_modules/@esbuild/darwin-x64": { 123 + "version": "0.27.1", 124 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", 125 + "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", 126 + "cpu": [ 127 + "x64" 128 + ], 129 + "dev": true, 130 + "license": "MIT", 131 + "optional": true, 132 + "os": [ 133 + "darwin" 134 + ], 135 + "engines": { 136 + "node": ">=18" 137 + } 138 + }, 139 + "node_modules/@esbuild/freebsd-arm64": { 140 + "version": "0.27.1", 141 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", 142 + "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", 143 + "cpu": [ 144 + "arm64" 145 + ], 146 + "dev": true, 147 + "license": "MIT", 148 + "optional": true, 149 + "os": [ 150 + "freebsd" 151 + ], 152 + "engines": { 153 + "node": ">=18" 154 + } 155 + }, 156 + "node_modules/@esbuild/freebsd-x64": { 157 + "version": "0.27.1", 158 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", 159 + "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", 160 + "cpu": [ 161 + "x64" 162 + ], 163 + "dev": true, 164 + "license": "MIT", 165 + "optional": true, 166 + "os": [ 167 + "freebsd" 168 + ], 169 + "engines": { 170 + "node": ">=18" 171 + } 172 + }, 173 + "node_modules/@esbuild/linux-arm": { 174 + "version": "0.27.1", 175 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", 176 + "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", 177 + "cpu": [ 178 + "arm" 179 + ], 180 + "dev": true, 181 + "license": "MIT", 182 + "optional": true, 183 + "os": [ 184 + "linux" 185 + ], 186 + "engines": { 187 + "node": ">=18" 188 + } 189 + }, 190 + "node_modules/@esbuild/linux-arm64": { 191 + "version": "0.27.1", 192 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", 193 + "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", 194 + "cpu": [ 195 + "arm64" 196 + ], 197 + "dev": true, 198 + "license": "MIT", 199 + "optional": true, 200 + "os": [ 201 + "linux" 202 + ], 203 + "engines": { 204 + "node": ">=18" 205 + } 206 + }, 207 + "node_modules/@esbuild/linux-ia32": { 208 + "version": "0.27.1", 209 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", 210 + "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", 211 + "cpu": [ 212 + "ia32" 213 + ], 214 + "dev": true, 215 + "license": "MIT", 216 + "optional": true, 217 + "os": [ 218 + "linux" 219 + ], 220 + "engines": { 221 + "node": ">=18" 222 + } 223 + }, 224 + "node_modules/@esbuild/linux-loong64": { 225 + "version": "0.27.1", 226 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", 227 + "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", 228 + "cpu": [ 229 + "loong64" 230 + ], 231 + "dev": true, 232 + "license": "MIT", 233 + "optional": true, 234 + "os": [ 235 + "linux" 236 + ], 237 + "engines": { 238 + "node": ">=18" 239 + } 240 + }, 241 + "node_modules/@esbuild/linux-mips64el": { 242 + "version": "0.27.1", 243 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", 244 + "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", 245 + "cpu": [ 246 + "mips64el" 247 + ], 248 + "dev": true, 249 + "license": "MIT", 250 + "optional": true, 251 + "os": [ 252 + "linux" 253 + ], 254 + "engines": { 255 + "node": ">=18" 256 + } 257 + }, 258 + "node_modules/@esbuild/linux-ppc64": { 259 + "version": "0.27.1", 260 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", 261 + "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", 262 + "cpu": [ 263 + "ppc64" 264 + ], 265 + "dev": true, 266 + "license": "MIT", 267 + "optional": true, 268 + "os": [ 269 + "linux" 270 + ], 271 + "engines": { 272 + "node": ">=18" 273 + } 274 + }, 275 + "node_modules/@esbuild/linux-riscv64": { 276 + "version": "0.27.1", 277 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", 278 + "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", 279 + "cpu": [ 280 + "riscv64" 281 + ], 282 + "dev": true, 283 + "license": "MIT", 284 + "optional": true, 285 + "os": [ 286 + "linux" 287 + ], 288 + "engines": { 289 + "node": ">=18" 290 + } 291 + }, 292 + "node_modules/@esbuild/linux-s390x": { 293 + "version": "0.27.1", 294 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", 295 + "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", 296 + "cpu": [ 297 + "s390x" 298 + ], 299 + "dev": true, 300 + "license": "MIT", 301 + "optional": true, 302 + "os": [ 303 + "linux" 304 + ], 305 + "engines": { 306 + "node": ">=18" 307 + } 308 + }, 309 + "node_modules/@esbuild/linux-x64": { 310 + "version": "0.27.1", 311 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", 312 + "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", 313 + "cpu": [ 314 + "x64" 315 + ], 316 + "dev": true, 317 + "license": "MIT", 318 + "optional": true, 319 + "os": [ 320 + "linux" 321 + ], 322 + "engines": { 323 + "node": ">=18" 324 + } 325 + }, 326 + "node_modules/@esbuild/netbsd-arm64": { 327 + "version": "0.27.1", 328 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", 329 + "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", 330 + "cpu": [ 331 + "arm64" 332 + ], 333 + "dev": true, 334 + "license": "MIT", 335 + "optional": true, 336 + "os": [ 337 + "netbsd" 338 + ], 339 + "engines": { 340 + "node": ">=18" 341 + } 342 + }, 343 + "node_modules/@esbuild/netbsd-x64": { 344 + "version": "0.27.1", 345 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", 346 + "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", 347 + "cpu": [ 348 + "x64" 349 + ], 350 + "dev": true, 351 + "license": "MIT", 352 + "optional": true, 353 + "os": [ 354 + "netbsd" 355 + ], 356 + "engines": { 357 + "node": ">=18" 358 + } 359 + }, 360 + "node_modules/@esbuild/openbsd-arm64": { 361 + "version": "0.27.1", 362 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", 363 + "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", 364 + "cpu": [ 365 + "arm64" 366 + ], 367 + "dev": true, 368 + "license": "MIT", 369 + "optional": true, 370 + "os": [ 371 + "openbsd" 372 + ], 373 + "engines": { 374 + "node": ">=18" 375 + } 376 + }, 377 + "node_modules/@esbuild/openbsd-x64": { 378 + "version": "0.27.1", 379 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", 380 + "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", 381 + "cpu": [ 382 + "x64" 383 + ], 384 + "dev": true, 385 + "license": "MIT", 386 + "optional": true, 387 + "os": [ 388 + "openbsd" 389 + ], 390 + "engines": { 391 + "node": ">=18" 392 + } 393 + }, 394 + "node_modules/@esbuild/openharmony-arm64": { 395 + "version": "0.27.1", 396 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", 397 + "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", 398 + "cpu": [ 399 + "arm64" 400 + ], 401 + "dev": true, 402 + "license": "MIT", 403 + "optional": true, 404 + "os": [ 405 + "openharmony" 406 + ], 407 + "engines": { 408 + "node": ">=18" 409 + } 410 + }, 411 + "node_modules/@esbuild/sunos-x64": { 412 + "version": "0.27.1", 413 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", 414 + "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", 415 + "cpu": [ 416 + "x64" 417 + ], 418 + "dev": true, 419 + "license": "MIT", 420 + "optional": true, 421 + "os": [ 422 + "sunos" 423 + ], 424 + "engines": { 425 + "node": ">=18" 426 + } 427 + }, 428 + "node_modules/@esbuild/win32-arm64": { 429 + "version": "0.27.1", 430 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", 431 + "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", 432 + "cpu": [ 433 + "arm64" 434 + ], 435 + "dev": true, 436 + "license": "MIT", 437 + "optional": true, 438 + "os": [ 439 + "win32" 440 + ], 441 + "engines": { 442 + "node": ">=18" 443 + } 444 + }, 445 + "node_modules/@esbuild/win32-ia32": { 446 + "version": "0.27.1", 447 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", 448 + "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", 449 + "cpu": [ 450 + "ia32" 451 + ], 452 + "dev": true, 453 + "license": "MIT", 454 + "optional": true, 455 + "os": [ 456 + "win32" 457 + ], 458 + "engines": { 459 + "node": ">=18" 460 + } 461 + }, 462 + "node_modules/@esbuild/win32-x64": { 463 + "version": "0.27.1", 464 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", 465 + "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", 466 + "cpu": [ 467 + "x64" 468 + ], 469 + "dev": true, 470 + "license": "MIT", 471 + "optional": true, 472 + "os": [ 473 + "win32" 474 + ], 475 + "engines": { 476 + "node": ">=18" 477 + } 478 + }, 479 + "node_modules/@img/sharp-darwin-arm64": { 480 + "version": "0.33.5", 481 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", 482 + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", 483 + "cpu": [ 484 + "arm64" 485 + ], 486 + "license": "Apache-2.0", 487 + "optional": true, 488 + "os": [ 489 + "darwin" 490 + ], 491 + "engines": { 492 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 493 + }, 494 + "funding": { 495 + "url": "https://opencollective.com/libvips" 496 + }, 497 + "optionalDependencies": { 498 + "@img/sharp-libvips-darwin-arm64": "1.0.4" 499 + } 500 + }, 501 + "node_modules/@img/sharp-darwin-x64": { 502 + "version": "0.33.5", 503 + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", 504 + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", 505 + "cpu": [ 506 + "x64" 507 + ], 508 + "license": "Apache-2.0", 509 + "optional": true, 510 + "os": [ 511 + "darwin" 512 + ], 513 + "engines": { 514 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 515 + }, 516 + "funding": { 517 + "url": "https://opencollective.com/libvips" 518 + }, 519 + "optionalDependencies": { 520 + "@img/sharp-libvips-darwin-x64": "1.0.4" 521 + } 522 + }, 523 + "node_modules/@img/sharp-libvips-darwin-arm64": { 524 + "version": "1.0.4", 525 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", 526 + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", 527 + "cpu": [ 528 + "arm64" 529 + ], 530 + "license": "LGPL-3.0-or-later", 531 + "optional": true, 532 + "os": [ 533 + "darwin" 534 + ], 535 + "funding": { 536 + "url": "https://opencollective.com/libvips" 537 + } 538 + }, 539 + "node_modules/@img/sharp-libvips-darwin-x64": { 540 + "version": "1.0.4", 541 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", 542 + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", 543 + "cpu": [ 544 + "x64" 545 + ], 546 + "license": "LGPL-3.0-or-later", 547 + "optional": true, 548 + "os": [ 549 + "darwin" 550 + ], 551 + "funding": { 552 + "url": "https://opencollective.com/libvips" 553 + } 554 + }, 555 + "node_modules/@img/sharp-libvips-linux-arm": { 556 + "version": "1.0.5", 557 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", 558 + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", 559 + "cpu": [ 560 + "arm" 561 + ], 562 + "license": "LGPL-3.0-or-later", 563 + "optional": true, 564 + "os": [ 565 + "linux" 566 + ], 567 + "funding": { 568 + "url": "https://opencollective.com/libvips" 569 + } 570 + }, 571 + "node_modules/@img/sharp-libvips-linux-arm64": { 572 + "version": "1.0.4", 573 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", 574 + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", 575 + "cpu": [ 576 + "arm64" 577 + ], 578 + "license": "LGPL-3.0-or-later", 579 + "optional": true, 580 + "os": [ 581 + "linux" 582 + ], 583 + "funding": { 584 + "url": "https://opencollective.com/libvips" 585 + } 586 + }, 587 + "node_modules/@img/sharp-libvips-linux-s390x": { 588 + "version": "1.0.4", 589 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", 590 + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", 591 + "cpu": [ 592 + "s390x" 593 + ], 594 + "license": "LGPL-3.0-or-later", 595 + "optional": true, 596 + "os": [ 597 + "linux" 598 + ], 599 + "funding": { 600 + "url": "https://opencollective.com/libvips" 601 + } 602 + }, 603 + "node_modules/@img/sharp-libvips-linux-x64": { 604 + "version": "1.0.4", 605 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", 606 + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", 607 + "cpu": [ 608 + "x64" 609 + ], 610 + "license": "LGPL-3.0-or-later", 611 + "optional": true, 612 + "os": [ 613 + "linux" 614 + ], 615 + "funding": { 616 + "url": "https://opencollective.com/libvips" 617 + } 618 + }, 619 + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { 620 + "version": "1.0.4", 621 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", 622 + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", 623 + "cpu": [ 624 + "arm64" 625 + ], 626 + "license": "LGPL-3.0-or-later", 627 + "optional": true, 628 + "os": [ 629 + "linux" 630 + ], 631 + "funding": { 632 + "url": "https://opencollective.com/libvips" 633 + } 634 + }, 635 + "node_modules/@img/sharp-libvips-linuxmusl-x64": { 636 + "version": "1.0.4", 637 + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", 638 + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", 639 + "cpu": [ 640 + "x64" 641 + ], 642 + "license": "LGPL-3.0-or-later", 643 + "optional": true, 644 + "os": [ 645 + "linux" 646 + ], 647 + "funding": { 648 + "url": "https://opencollective.com/libvips" 649 + } 650 + }, 651 + "node_modules/@img/sharp-linux-arm": { 652 + "version": "0.33.5", 653 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", 654 + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", 655 + "cpu": [ 656 + "arm" 657 + ], 658 + "license": "Apache-2.0", 659 + "optional": true, 660 + "os": [ 661 + "linux" 662 + ], 663 + "engines": { 664 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 665 + }, 666 + "funding": { 667 + "url": "https://opencollective.com/libvips" 668 + }, 669 + "optionalDependencies": { 670 + "@img/sharp-libvips-linux-arm": "1.0.5" 671 + } 672 + }, 673 + "node_modules/@img/sharp-linux-arm64": { 674 + "version": "0.33.5", 675 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", 676 + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", 677 + "cpu": [ 678 + "arm64" 679 + ], 680 + "license": "Apache-2.0", 681 + "optional": true, 682 + "os": [ 683 + "linux" 684 + ], 685 + "engines": { 686 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 687 + }, 688 + "funding": { 689 + "url": "https://opencollective.com/libvips" 690 + }, 691 + "optionalDependencies": { 692 + "@img/sharp-libvips-linux-arm64": "1.0.4" 693 + } 694 + }, 695 + "node_modules/@img/sharp-linux-s390x": { 696 + "version": "0.33.5", 697 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", 698 + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", 699 + "cpu": [ 700 + "s390x" 701 + ], 702 + "license": "Apache-2.0", 703 + "optional": true, 704 + "os": [ 705 + "linux" 706 + ], 707 + "engines": { 708 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 709 + }, 710 + "funding": { 711 + "url": "https://opencollective.com/libvips" 712 + }, 713 + "optionalDependencies": { 714 + "@img/sharp-libvips-linux-s390x": "1.0.4" 715 + } 716 + }, 717 + "node_modules/@img/sharp-linux-x64": { 718 + "version": "0.33.5", 719 + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", 720 + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", 721 + "cpu": [ 722 + "x64" 723 + ], 724 + "license": "Apache-2.0", 725 + "optional": true, 726 + "os": [ 727 + "linux" 728 + ], 729 + "engines": { 730 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 731 + }, 732 + "funding": { 733 + "url": "https://opencollective.com/libvips" 734 + }, 735 + "optionalDependencies": { 736 + "@img/sharp-libvips-linux-x64": "1.0.4" 737 + } 738 + }, 739 + "node_modules/@img/sharp-linuxmusl-arm64": { 740 + "version": "0.33.5", 741 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", 742 + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", 743 + "cpu": [ 744 + "arm64" 745 + ], 746 + "license": "Apache-2.0", 747 + "optional": true, 748 + "os": [ 749 + "linux" 750 + ], 751 + "engines": { 752 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 753 + }, 754 + "funding": { 755 + "url": "https://opencollective.com/libvips" 756 + }, 757 + "optionalDependencies": { 758 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" 759 + } 760 + }, 761 + "node_modules/@img/sharp-linuxmusl-x64": { 762 + "version": "0.33.5", 763 + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", 764 + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", 765 + "cpu": [ 766 + "x64" 767 + ], 768 + "license": "Apache-2.0", 769 + "optional": true, 770 + "os": [ 771 + "linux" 772 + ], 773 + "engines": { 774 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 775 + }, 776 + "funding": { 777 + "url": "https://opencollective.com/libvips" 778 + }, 779 + "optionalDependencies": { 780 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" 781 + } 782 + }, 783 + "node_modules/@img/sharp-wasm32": { 784 + "version": "0.33.5", 785 + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", 786 + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", 787 + "cpu": [ 788 + "wasm32" 789 + ], 790 + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", 791 + "optional": true, 792 + "dependencies": { 793 + "@emnapi/runtime": "^1.2.0" 794 + }, 795 + "engines": { 796 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 797 + }, 798 + "funding": { 799 + "url": "https://opencollective.com/libvips" 800 + } 801 + }, 802 + "node_modules/@img/sharp-win32-ia32": { 803 + "version": "0.33.5", 804 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", 805 + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", 806 + "cpu": [ 807 + "ia32" 808 + ], 809 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 810 + "optional": true, 811 + "os": [ 812 + "win32" 813 + ], 814 + "engines": { 815 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 816 + }, 817 + "funding": { 818 + "url": "https://opencollective.com/libvips" 819 + } 820 + }, 821 + "node_modules/@img/sharp-win32-x64": { 822 + "version": "0.33.5", 823 + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", 824 + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", 825 + "cpu": [ 826 + "x64" 827 + ], 828 + "license": "Apache-2.0 AND LGPL-3.0-or-later", 829 + "optional": true, 830 + "os": [ 831 + "win32" 832 + ], 833 + "engines": { 834 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 835 + }, 836 + "funding": { 837 + "url": "https://opencollective.com/libvips" 838 + } 839 + }, 840 + "node_modules/@kurkle/color": { 841 + "version": "0.3.4", 842 + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", 843 + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", 844 + "license": "MIT" 845 + }, 846 + "node_modules/@mapbox/node-pre-gyp": { 847 + "version": "1.0.11", 848 + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", 849 + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", 850 + "license": "BSD-3-Clause", 851 + "dependencies": { 852 + "detect-libc": "^2.0.0", 853 + "https-proxy-agent": "^5.0.0", 854 + "make-dir": "^3.1.0", 855 + "node-fetch": "^2.6.7", 856 + "nopt": "^5.0.0", 857 + "npmlog": "^5.0.1", 858 + "rimraf": "^3.0.2", 859 + "semver": "^7.3.5", 860 + "tar": "^6.1.11" 861 + }, 862 + "bin": { 863 + "node-pre-gyp": "bin/node-pre-gyp" 864 + } 865 + }, 866 + "node_modules/@types/estree": { 867 + "version": "1.0.8", 868 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", 869 + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", 870 + "license": "MIT" 871 + }, 872 + "node_modules/@types/geojson": { 873 + "version": "7946.0.16", 874 + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", 875 + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", 876 + "license": "MIT" 877 + }, 878 + "node_modules/@types/node": { 879 + "version": "20.19.25", 880 + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", 881 + "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", 882 + "dev": true, 883 + "license": "MIT", 884 + "dependencies": { 885 + "undici-types": "~6.21.0" 886 + } 887 + }, 888 + "node_modules/abbrev": { 889 + "version": "1.1.1", 890 + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 891 + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 892 + "license": "ISC" 893 + }, 894 + "node_modules/abort-controller": { 895 + "version": "3.0.0", 896 + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 897 + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 898 + "license": "MIT", 899 + "dependencies": { 900 + "event-target-shim": "^5.0.0" 901 + }, 902 + "engines": { 903 + "node": ">=6.5" 904 + } 905 + }, 906 + "node_modules/abstract-level": { 907 + "version": "1.0.4", 908 + "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.4.tgz", 909 + "integrity": "sha512-eUP/6pbXBkMbXFdx4IH2fVgvB7M0JvR7/lIL33zcs0IBcwjdzSSl31TOJsaCzmKSSDF9h8QYSOJux4Nd4YJqFg==", 910 + "license": "MIT", 911 + "dependencies": { 912 + "buffer": "^6.0.3", 913 + "catering": "^2.1.0", 914 + "is-buffer": "^2.0.5", 915 + "level-supports": "^4.0.0", 916 + "level-transcoder": "^1.0.1", 917 + "module-error": "^1.0.1", 918 + "queue-microtask": "^1.2.3" 919 + }, 920 + "engines": { 921 + "node": ">=12" 922 + } 923 + }, 924 + "node_modules/agent-base": { 925 + "version": "6.0.2", 926 + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", 927 + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", 928 + "license": "MIT", 929 + "dependencies": { 930 + "debug": "4" 931 + }, 932 + "engines": { 933 + "node": ">= 6.0.0" 934 + } 935 + }, 936 + "node_modules/ansi-regex": { 937 + "version": "5.0.1", 938 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 939 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 940 + "license": "MIT", 941 + "engines": { 942 + "node": ">=8" 943 + } 944 + }, 945 + "node_modules/ansi-styles": { 946 + "version": "6.2.3", 947 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", 948 + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", 949 + "license": "MIT", 950 + "engines": { 951 + "node": ">=12" 952 + }, 953 + "funding": { 954 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 955 + } 956 + }, 957 + "node_modules/aproba": { 958 + "version": "2.1.0", 959 + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", 960 + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", 961 + "license": "ISC" 962 + }, 963 + "node_modules/are-we-there-yet": { 964 + "version": "2.0.0", 965 + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", 966 + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", 967 + "deprecated": "This package is no longer supported.", 968 + "license": "ISC", 969 + "dependencies": { 970 + "delegates": "^1.0.0", 971 + "readable-stream": "^3.6.0" 972 + }, 973 + "engines": { 974 + "node": ">=10" 975 + } 976 + }, 977 + "node_modules/balanced-match": { 978 + "version": "1.0.2", 979 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 980 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 981 + "license": "MIT" 982 + }, 983 + "node_modules/base64-js": { 984 + "version": "1.5.1", 985 + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 986 + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 987 + "funding": [ 988 + { 989 + "type": "github", 990 + "url": "https://github.com/sponsors/feross" 991 + }, 992 + { 993 + "type": "patreon", 994 + "url": "https://www.patreon.com/feross" 995 + }, 996 + { 997 + "type": "consulting", 998 + "url": "https://feross.org/support" 999 + } 1000 + ], 1001 + "license": "MIT" 1002 + }, 1003 + "node_modules/better-sqlite3": { 1004 + "version": "11.10.0", 1005 + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", 1006 + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", 1007 + "hasInstallScript": true, 1008 + "license": "MIT", 1009 + "dependencies": { 1010 + "bindings": "^1.5.0", 1011 + "prebuild-install": "^7.1.1" 1012 + } 1013 + }, 1014 + "node_modules/bindings": { 1015 + "version": "1.5.0", 1016 + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 1017 + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 1018 + "license": "MIT", 1019 + "dependencies": { 1020 + "file-uri-to-path": "1.0.0" 1021 + } 1022 + }, 1023 + "node_modules/bl": { 1024 + "version": "4.1.0", 1025 + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 1026 + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 1027 + "license": "MIT", 1028 + "dependencies": { 1029 + "buffer": "^5.5.0", 1030 + "inherits": "^2.0.4", 1031 + "readable-stream": "^3.4.0" 1032 + } 1033 + }, 1034 + "node_modules/bl/node_modules/buffer": { 1035 + "version": "5.7.1", 1036 + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 1037 + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 1038 + "funding": [ 1039 + { 1040 + "type": "github", 1041 + "url": "https://github.com/sponsors/feross" 1042 + }, 1043 + { 1044 + "type": "patreon", 1045 + "url": "https://www.patreon.com/feross" 1046 + }, 1047 + { 1048 + "type": "consulting", 1049 + "url": "https://feross.org/support" 1050 + } 1051 + ], 1052 + "license": "MIT", 1053 + "dependencies": { 1054 + "base64-js": "^1.3.1", 1055 + "ieee754": "^1.1.13" 1056 + } 1057 + }, 1058 + "node_modules/brace-expansion": { 1059 + "version": "1.1.12", 1060 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 1061 + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 1062 + "license": "MIT", 1063 + "dependencies": { 1064 + "balanced-match": "^1.0.0", 1065 + "concat-map": "0.0.1" 1066 + } 1067 + }, 1068 + "node_modules/browser-level": { 1069 + "version": "1.0.1", 1070 + "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", 1071 + "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", 1072 + "license": "MIT", 1073 + "dependencies": { 1074 + "abstract-level": "^1.0.2", 1075 + "catering": "^2.1.1", 1076 + "module-error": "^1.0.2", 1077 + "run-parallel-limit": "^1.1.0" 1078 + } 1079 + }, 1080 + "node_modules/buffer": { 1081 + "version": "6.0.3", 1082 + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", 1083 + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 1084 + "funding": [ 1085 + { 1086 + "type": "github", 1087 + "url": "https://github.com/sponsors/feross" 1088 + }, 1089 + { 1090 + "type": "patreon", 1091 + "url": "https://www.patreon.com/feross" 1092 + }, 1093 + { 1094 + "type": "consulting", 1095 + "url": "https://feross.org/support" 1096 + } 1097 + ], 1098 + "license": "MIT", 1099 + "dependencies": { 1100 + "base64-js": "^1.3.1", 1101 + "ieee754": "^1.2.1" 1102 + } 1103 + }, 1104 + "node_modules/callback-stream": { 1105 + "version": "1.1.0", 1106 + "resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz", 1107 + "integrity": "sha512-sAZ9kODla+mGACBZ1IpTCAisKoGnv6PykW7fPk1LrM+mMepE18Yz0515yoVcrZy7dQsTUp3uZLQ/9Sx1RnLoHw==", 1108 + "license": "MIT", 1109 + "dependencies": { 1110 + "inherits": "^2.0.1", 1111 + "readable-stream": "> 1.0.0 < 3.0.0" 1112 + } 1113 + }, 1114 + "node_modules/callback-stream/node_modules/readable-stream": { 1115 + "version": "2.3.8", 1116 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 1117 + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 1118 + "license": "MIT", 1119 + "dependencies": { 1120 + "core-util-is": "~1.0.0", 1121 + "inherits": "~2.0.3", 1122 + "isarray": "~1.0.0", 1123 + "process-nextick-args": "~2.0.0", 1124 + "safe-buffer": "~5.1.1", 1125 + "string_decoder": "~1.1.1", 1126 + "util-deprecate": "~1.0.1" 1127 + } 1128 + }, 1129 + "node_modules/callback-stream/node_modules/safe-buffer": { 1130 + "version": "5.1.2", 1131 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1132 + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1133 + "license": "MIT" 1134 + }, 1135 + "node_modules/callback-stream/node_modules/string_decoder": { 1136 + "version": "1.1.1", 1137 + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1138 + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1139 + "license": "MIT", 1140 + "dependencies": { 1141 + "safe-buffer": "~5.1.0" 1142 + } 1143 + }, 1144 + "node_modules/canvas": { 1145 + "version": "2.11.2", 1146 + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", 1147 + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", 1148 + "hasInstallScript": true, 1149 + "license": "MIT", 1150 + "dependencies": { 1151 + "@mapbox/node-pre-gyp": "^1.0.0", 1152 + "nan": "^2.17.0", 1153 + "simple-get": "^3.0.3" 1154 + }, 1155 + "engines": { 1156 + "node": ">=6" 1157 + } 1158 + }, 1159 + "node_modules/catering": { 1160 + "version": "2.1.1", 1161 + "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", 1162 + "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", 1163 + "license": "MIT", 1164 + "engines": { 1165 + "node": ">=6" 1166 + } 1167 + }, 1168 + "node_modules/chart.js": { 1169 + "version": "4.5.1", 1170 + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", 1171 + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", 1172 + "license": "MIT", 1173 + "dependencies": { 1174 + "@kurkle/color": "^0.3.0" 1175 + }, 1176 + "engines": { 1177 + "pnpm": ">=8" 1178 + } 1179 + }, 1180 + "node_modules/chownr": { 1181 + "version": "2.0.0", 1182 + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 1183 + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", 1184 + "license": "ISC", 1185 + "engines": { 1186 + "node": ">=10" 1187 + } 1188 + }, 1189 + "node_modules/classic-level": { 1190 + "version": "1.4.1", 1191 + "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.4.1.tgz", 1192 + "integrity": "sha512-qGx/KJl3bvtOHrGau2WklEZuXhS3zme+jf+fsu6Ej7W7IP/C49v7KNlWIsT1jZu0YnfzSIYDGcEWpCa1wKGWXQ==", 1193 + "hasInstallScript": true, 1194 + "license": "MIT", 1195 + "dependencies": { 1196 + "abstract-level": "^1.0.2", 1197 + "catering": "^2.1.0", 1198 + "module-error": "^1.0.1", 1199 + "napi-macros": "^2.2.2", 1200 + "node-gyp-build": "^4.3.0" 1201 + }, 1202 + "engines": { 1203 + "node": ">=12" 1204 + } 1205 + }, 1206 + "node_modules/cliui": { 1207 + "version": "9.0.1", 1208 + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", 1209 + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", 1210 + "license": "ISC", 1211 + "dependencies": { 1212 + "string-width": "^7.2.0", 1213 + "strip-ansi": "^7.1.0", 1214 + "wrap-ansi": "^9.0.0" 1215 + }, 1216 + "engines": { 1217 + "node": ">=20" 1218 + } 1219 + }, 1220 + "node_modules/cliui/node_modules/ansi-regex": { 1221 + "version": "6.2.2", 1222 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 1223 + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 1224 + "license": "MIT", 1225 + "engines": { 1226 + "node": ">=12" 1227 + }, 1228 + "funding": { 1229 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 1230 + } 1231 + }, 1232 + "node_modules/cliui/node_modules/emoji-regex": { 1233 + "version": "10.6.0", 1234 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", 1235 + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", 1236 + "license": "MIT" 1237 + }, 1238 + "node_modules/cliui/node_modules/string-width": { 1239 + "version": "7.2.0", 1240 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 1241 + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 1242 + "license": "MIT", 1243 + "dependencies": { 1244 + "emoji-regex": "^10.3.0", 1245 + "get-east-asian-width": "^1.0.0", 1246 + "strip-ansi": "^7.1.0" 1247 + }, 1248 + "engines": { 1249 + "node": ">=18" 1250 + }, 1251 + "funding": { 1252 + "url": "https://github.com/sponsors/sindresorhus" 1253 + } 1254 + }, 1255 + "node_modules/cliui/node_modules/strip-ansi": { 1256 + "version": "7.1.2", 1257 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 1258 + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 1259 + "license": "MIT", 1260 + "dependencies": { 1261 + "ansi-regex": "^6.0.1" 1262 + }, 1263 + "engines": { 1264 + "node": ">=12" 1265 + }, 1266 + "funding": { 1267 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 1268 + } 1269 + }, 1270 + "node_modules/color": { 1271 + "version": "4.2.3", 1272 + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", 1273 + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 1274 + "license": "MIT", 1275 + "dependencies": { 1276 + "color-convert": "^2.0.1", 1277 + "color-string": "^1.9.0" 1278 + }, 1279 + "engines": { 1280 + "node": ">=12.5.0" 1281 + } 1282 + }, 1283 + "node_modules/color-convert": { 1284 + "version": "2.0.1", 1285 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1286 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1287 + "license": "MIT", 1288 + "dependencies": { 1289 + "color-name": "~1.1.4" 1290 + }, 1291 + "engines": { 1292 + "node": ">=7.0.0" 1293 + } 1294 + }, 1295 + "node_modules/color-name": { 1296 + "version": "1.1.4", 1297 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1298 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1299 + "license": "MIT" 1300 + }, 1301 + "node_modules/color-string": { 1302 + "version": "1.9.1", 1303 + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", 1304 + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 1305 + "license": "MIT", 1306 + "dependencies": { 1307 + "color-name": "^1.0.0", 1308 + "simple-swizzle": "^0.2.2" 1309 + } 1310 + }, 1311 + "node_modules/color-support": { 1312 + "version": "1.1.3", 1313 + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", 1314 + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", 1315 + "license": "ISC", 1316 + "bin": { 1317 + "color-support": "bin.js" 1318 + } 1319 + }, 1320 + "node_modules/commander": { 1321 + "version": "7.2.0", 1322 + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", 1323 + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", 1324 + "license": "MIT", 1325 + "engines": { 1326 + "node": ">= 10" 1327 + } 1328 + }, 1329 + "node_modules/concat-map": { 1330 + "version": "0.0.1", 1331 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1332 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 1333 + "license": "MIT" 1334 + }, 1335 + "node_modules/console-control-strings": { 1336 + "version": "1.1.0", 1337 + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 1338 + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", 1339 + "license": "ISC" 1340 + }, 1341 + "node_modules/core-util-is": { 1342 + "version": "1.0.3", 1343 + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 1344 + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 1345 + "license": "MIT" 1346 + }, 1347 + "node_modules/d3-array": { 1348 + "version": "3.2.4", 1349 + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", 1350 + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", 1351 + "license": "ISC", 1352 + "dependencies": { 1353 + "internmap": "1 - 2" 1354 + }, 1355 + "engines": { 1356 + "node": ">=12" 1357 + } 1358 + }, 1359 + "node_modules/d3-color": { 1360 + "version": "3.1.0", 1361 + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", 1362 + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", 1363 + "license": "ISC", 1364 + "engines": { 1365 + "node": ">=12" 1366 + } 1367 + }, 1368 + "node_modules/d3-delaunay": { 1369 + "version": "6.0.4", 1370 + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", 1371 + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", 1372 + "license": "ISC", 1373 + "dependencies": { 1374 + "delaunator": "5" 1375 + }, 1376 + "engines": { 1377 + "node": ">=12" 1378 + } 1379 + }, 1380 + "node_modules/d3-dispatch": { 1381 + "version": "3.0.1", 1382 + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", 1383 + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", 1384 + "license": "ISC", 1385 + "engines": { 1386 + "node": ">=12" 1387 + } 1388 + }, 1389 + "node_modules/d3-dsv": { 1390 + "version": "3.0.1", 1391 + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", 1392 + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", 1393 + "license": "ISC", 1394 + "dependencies": { 1395 + "commander": "7", 1396 + "iconv-lite": "0.6", 1397 + "rw": "1" 1398 + }, 1399 + "bin": { 1400 + "csv2json": "bin/dsv2json.js", 1401 + "csv2tsv": "bin/dsv2dsv.js", 1402 + "dsv2dsv": "bin/dsv2dsv.js", 1403 + "dsv2json": "bin/dsv2json.js", 1404 + "json2csv": "bin/json2dsv.js", 1405 + "json2dsv": "bin/json2dsv.js", 1406 + "json2tsv": "bin/json2dsv.js", 1407 + "tsv2csv": "bin/dsv2dsv.js", 1408 + "tsv2json": "bin/dsv2json.js" 1409 + }, 1410 + "engines": { 1411 + "node": ">=12" 1412 + } 1413 + }, 1414 + "node_modules/d3-force": { 1415 + "version": "3.0.0", 1416 + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", 1417 + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", 1418 + "license": "ISC", 1419 + "dependencies": { 1420 + "d3-dispatch": "1 - 3", 1421 + "d3-quadtree": "1 - 3", 1422 + "d3-timer": "1 - 3" 1423 + }, 1424 + "engines": { 1425 + "node": ">=12" 1426 + } 1427 + }, 1428 + "node_modules/d3-format": { 1429 + "version": "3.1.0", 1430 + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", 1431 + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", 1432 + "license": "ISC", 1433 + "engines": { 1434 + "node": ">=12" 1435 + } 1436 + }, 1437 + "node_modules/d3-geo": { 1438 + "version": "3.1.1", 1439 + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", 1440 + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", 1441 + "license": "ISC", 1442 + "dependencies": { 1443 + "d3-array": "2.5.0 - 3" 1444 + }, 1445 + "engines": { 1446 + "node": ">=12" 1447 + } 1448 + }, 1449 + "node_modules/d3-geo-projection": { 1450 + "version": "4.0.0", 1451 + "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz", 1452 + "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==", 1453 + "license": "ISC", 1454 + "dependencies": { 1455 + "commander": "7", 1456 + "d3-array": "1 - 3", 1457 + "d3-geo": "1.12.0 - 3" 1458 + }, 1459 + "bin": { 1460 + "geo2svg": "bin/geo2svg.js", 1461 + "geograticule": "bin/geograticule.js", 1462 + "geoproject": "bin/geoproject.js", 1463 + "geoquantize": "bin/geoquantize.js", 1464 + "geostitch": "bin/geostitch.js" 1465 + }, 1466 + "engines": { 1467 + "node": ">=12" 1468 + } 1469 + }, 1470 + "node_modules/d3-hierarchy": { 1471 + "version": "3.1.2", 1472 + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", 1473 + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", 1474 + "license": "ISC", 1475 + "engines": { 1476 + "node": ">=12" 1477 + } 1478 + }, 1479 + "node_modules/d3-interpolate": { 1480 + "version": "3.0.1", 1481 + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", 1482 + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", 1483 + "license": "ISC", 1484 + "dependencies": { 1485 + "d3-color": "1 - 3" 1486 + }, 1487 + "engines": { 1488 + "node": ">=12" 1489 + } 1490 + }, 1491 + "node_modules/d3-path": { 1492 + "version": "3.1.0", 1493 + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", 1494 + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", 1495 + "license": "ISC", 1496 + "engines": { 1497 + "node": ">=12" 1498 + } 1499 + }, 1500 + "node_modules/d3-quadtree": { 1501 + "version": "3.0.1", 1502 + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", 1503 + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", 1504 + "license": "ISC", 1505 + "engines": { 1506 + "node": ">=12" 1507 + } 1508 + }, 1509 + "node_modules/d3-scale": { 1510 + "version": "4.0.2", 1511 + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", 1512 + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", 1513 + "license": "ISC", 1514 + "dependencies": { 1515 + "d3-array": "2.10.0 - 3", 1516 + "d3-format": "1 - 3", 1517 + "d3-interpolate": "1.2.0 - 3", 1518 + "d3-time": "2.1.1 - 3", 1519 + "d3-time-format": "2 - 4" 1520 + }, 1521 + "engines": { 1522 + "node": ">=12" 1523 + } 1524 + }, 1525 + "node_modules/d3-scale-chromatic": { 1526 + "version": "3.1.0", 1527 + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", 1528 + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", 1529 + "license": "ISC", 1530 + "dependencies": { 1531 + "d3-color": "1 - 3", 1532 + "d3-interpolate": "1 - 3" 1533 + }, 1534 + "engines": { 1535 + "node": ">=12" 1536 + } 1537 + }, 1538 + "node_modules/d3-shape": { 1539 + "version": "3.2.0", 1540 + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", 1541 + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", 1542 + "license": "ISC", 1543 + "dependencies": { 1544 + "d3-path": "^3.1.0" 1545 + }, 1546 + "engines": { 1547 + "node": ">=12" 1548 + } 1549 + }, 1550 + "node_modules/d3-time": { 1551 + "version": "3.1.0", 1552 + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", 1553 + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", 1554 + "license": "ISC", 1555 + "dependencies": { 1556 + "d3-array": "2 - 3" 1557 + }, 1558 + "engines": { 1559 + "node": ">=12" 1560 + } 1561 + }, 1562 + "node_modules/d3-time-format": { 1563 + "version": "4.1.0", 1564 + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", 1565 + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", 1566 + "license": "ISC", 1567 + "dependencies": { 1568 + "d3-time": "1 - 3" 1569 + }, 1570 + "engines": { 1571 + "node": ">=12" 1572 + } 1573 + }, 1574 + "node_modules/d3-timer": { 1575 + "version": "3.0.1", 1576 + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", 1577 + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", 1578 + "license": "ISC", 1579 + "engines": { 1580 + "node": ">=12" 1581 + } 1582 + }, 1583 + "node_modules/debug": { 1584 + "version": "4.4.3", 1585 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", 1586 + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", 1587 + "license": "MIT", 1588 + "dependencies": { 1589 + "ms": "^2.1.3" 1590 + }, 1591 + "engines": { 1592 + "node": ">=6.0" 1593 + }, 1594 + "peerDependenciesMeta": { 1595 + "supports-color": { 1596 + "optional": true 1597 + } 1598 + } 1599 + }, 1600 + "node_modules/decompress-response": { 1601 + "version": "4.2.1", 1602 + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", 1603 + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", 1604 + "license": "MIT", 1605 + "dependencies": { 1606 + "mimic-response": "^2.0.0" 1607 + }, 1608 + "engines": { 1609 + "node": ">=8" 1610 + } 1611 + }, 1612 + "node_modules/deep-extend": { 1613 + "version": "0.6.0", 1614 + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 1615 + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 1616 + "license": "MIT", 1617 + "engines": { 1618 + "node": ">=4.0.0" 1619 + } 1620 + }, 1621 + "node_modules/delaunator": { 1622 + "version": "5.0.1", 1623 + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", 1624 + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", 1625 + "license": "ISC", 1626 + "dependencies": { 1627 + "robust-predicates": "^3.0.2" 1628 + } 1629 + }, 1630 + "node_modules/delegates": { 1631 + "version": "1.0.0", 1632 + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 1633 + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", 1634 + "license": "MIT" 1635 + }, 1636 + "node_modules/detect-libc": { 1637 + "version": "2.1.2", 1638 + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", 1639 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", 1640 + "license": "Apache-2.0", 1641 + "engines": { 1642 + "node": ">=8" 1643 + } 1644 + }, 1645 + "node_modules/emoji-regex": { 1646 + "version": "8.0.0", 1647 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1648 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1649 + "license": "MIT" 1650 + }, 1651 + "node_modules/end-of-stream": { 1652 + "version": "1.4.5", 1653 + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", 1654 + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", 1655 + "license": "MIT", 1656 + "dependencies": { 1657 + "once": "^1.4.0" 1658 + } 1659 + }, 1660 + "node_modules/esbuild": { 1661 + "version": "0.27.1", 1662 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", 1663 + "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", 1664 + "dev": true, 1665 + "hasInstallScript": true, 1666 + "license": "MIT", 1667 + "bin": { 1668 + "esbuild": "bin/esbuild" 1669 + }, 1670 + "engines": { 1671 + "node": ">=18" 1672 + }, 1673 + "optionalDependencies": { 1674 + "@esbuild/aix-ppc64": "0.27.1", 1675 + "@esbuild/android-arm": "0.27.1", 1676 + "@esbuild/android-arm64": "0.27.1", 1677 + "@esbuild/android-x64": "0.27.1", 1678 + "@esbuild/darwin-arm64": "0.27.1", 1679 + "@esbuild/darwin-x64": "0.27.1", 1680 + "@esbuild/freebsd-arm64": "0.27.1", 1681 + "@esbuild/freebsd-x64": "0.27.1", 1682 + "@esbuild/linux-arm": "0.27.1", 1683 + "@esbuild/linux-arm64": "0.27.1", 1684 + "@esbuild/linux-ia32": "0.27.1", 1685 + "@esbuild/linux-loong64": "0.27.1", 1686 + "@esbuild/linux-mips64el": "0.27.1", 1687 + "@esbuild/linux-ppc64": "0.27.1", 1688 + "@esbuild/linux-riscv64": "0.27.1", 1689 + "@esbuild/linux-s390x": "0.27.1", 1690 + "@esbuild/linux-x64": "0.27.1", 1691 + "@esbuild/netbsd-arm64": "0.27.1", 1692 + "@esbuild/netbsd-x64": "0.27.1", 1693 + "@esbuild/openbsd-arm64": "0.27.1", 1694 + "@esbuild/openbsd-x64": "0.27.1", 1695 + "@esbuild/openharmony-arm64": "0.27.1", 1696 + "@esbuild/sunos-x64": "0.27.1", 1697 + "@esbuild/win32-arm64": "0.27.1", 1698 + "@esbuild/win32-ia32": "0.27.1", 1699 + "@esbuild/win32-x64": "0.27.1" 1700 + } 1701 + }, 1702 + "node_modules/escalade": { 1703 + "version": "3.2.0", 1704 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 1705 + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 1706 + "license": "MIT", 1707 + "engines": { 1708 + "node": ">=6" 1709 + } 1710 + }, 1711 + "node_modules/event-target-shim": { 1712 + "version": "5.0.1", 1713 + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 1714 + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 1715 + "license": "MIT", 1716 + "engines": { 1717 + "node": ">=6" 1718 + } 1719 + }, 1720 + "node_modules/events": { 1721 + "version": "3.3.0", 1722 + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 1723 + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 1724 + "license": "MIT", 1725 + "engines": { 1726 + "node": ">=0.8.x" 1727 + } 1728 + }, 1729 + "node_modules/expand-template": { 1730 + "version": "2.0.3", 1731 + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", 1732 + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", 1733 + "license": "(MIT OR WTFPL)", 1734 + "engines": { 1735 + "node": ">=6" 1736 + } 1737 + }, 1738 + "node_modules/fastfall": { 1739 + "version": "1.5.1", 1740 + "resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz", 1741 + "integrity": "sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==", 1742 + "license": "MIT", 1743 + "dependencies": { 1744 + "reusify": "^1.0.0" 1745 + }, 1746 + "engines": { 1747 + "node": ">=0.10.0" 1748 + } 1749 + }, 1750 + "node_modules/fastparallel": { 1751 + "version": "2.4.1", 1752 + "resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz", 1753 + "integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==", 1754 + "license": "ISC", 1755 + "dependencies": { 1756 + "reusify": "^1.0.4", 1757 + "xtend": "^4.0.2" 1758 + } 1759 + }, 1760 + "node_modules/fastq": { 1761 + "version": "1.19.1", 1762 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 1763 + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 1764 + "license": "ISC", 1765 + "dependencies": { 1766 + "reusify": "^1.0.4" 1767 + } 1768 + }, 1769 + "node_modules/fastseries": { 1770 + "version": "1.7.2", 1771 + "resolved": "https://registry.npmjs.org/fastseries/-/fastseries-1.7.2.tgz", 1772 + "integrity": "sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==", 1773 + "license": "ISC", 1774 + "dependencies": { 1775 + "reusify": "^1.0.0", 1776 + "xtend": "^4.0.0" 1777 + } 1778 + }, 1779 + "node_modules/file-uri-to-path": { 1780 + "version": "1.0.0", 1781 + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 1782 + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 1783 + "license": "MIT" 1784 + }, 1785 + "node_modules/fs-constants": { 1786 + "version": "1.0.0", 1787 + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 1788 + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", 1789 + "license": "MIT" 1790 + }, 1791 + "node_modules/fs-minipass": { 1792 + "version": "2.1.0", 1793 + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 1794 + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 1795 + "license": "ISC", 1796 + "dependencies": { 1797 + "minipass": "^3.0.0" 1798 + }, 1799 + "engines": { 1800 + "node": ">= 8" 1801 + } 1802 + }, 1803 + "node_modules/fs-minipass/node_modules/minipass": { 1804 + "version": "3.3.6", 1805 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 1806 + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 1807 + "license": "ISC", 1808 + "dependencies": { 1809 + "yallist": "^4.0.0" 1810 + }, 1811 + "engines": { 1812 + "node": ">=8" 1813 + } 1814 + }, 1815 + "node_modules/fs.realpath": { 1816 + "version": "1.0.0", 1817 + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 1818 + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 1819 + "license": "ISC" 1820 + }, 1821 + "node_modules/fsevents": { 1822 + "version": "2.3.3", 1823 + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1824 + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1825 + "dev": true, 1826 + "hasInstallScript": true, 1827 + "license": "MIT", 1828 + "optional": true, 1829 + "os": [ 1830 + "darwin" 1831 + ], 1832 + "engines": { 1833 + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1834 + } 1835 + }, 1836 + "node_modules/gauge": { 1837 + "version": "3.0.2", 1838 + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", 1839 + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", 1840 + "deprecated": "This package is no longer supported.", 1841 + "license": "ISC", 1842 + "dependencies": { 1843 + "aproba": "^1.0.3 || ^2.0.0", 1844 + "color-support": "^1.1.2", 1845 + "console-control-strings": "^1.0.0", 1846 + "has-unicode": "^2.0.1", 1847 + "object-assign": "^4.1.1", 1848 + "signal-exit": "^3.0.0", 1849 + "string-width": "^4.2.3", 1850 + "strip-ansi": "^6.0.1", 1851 + "wide-align": "^1.1.2" 1852 + }, 1853 + "engines": { 1854 + "node": ">=10" 1855 + } 1856 + }, 1857 + "node_modules/get-caller-file": { 1858 + "version": "2.0.5", 1859 + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 1860 + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 1861 + "license": "ISC", 1862 + "engines": { 1863 + "node": "6.* || 8.* || >= 10.*" 1864 + } 1865 + }, 1866 + "node_modules/get-east-asian-width": { 1867 + "version": "1.4.0", 1868 + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", 1869 + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", 1870 + "license": "MIT", 1871 + "engines": { 1872 + "node": ">=18" 1873 + }, 1874 + "funding": { 1875 + "url": "https://github.com/sponsors/sindresorhus" 1876 + } 1877 + }, 1878 + "node_modules/get-tsconfig": { 1879 + "version": "4.13.0", 1880 + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", 1881 + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", 1882 + "dev": true, 1883 + "license": "MIT", 1884 + "dependencies": { 1885 + "resolve-pkg-maps": "^1.0.0" 1886 + }, 1887 + "funding": { 1888 + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 1889 + } 1890 + }, 1891 + "node_modules/github-from-package": { 1892 + "version": "0.0.0", 1893 + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", 1894 + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", 1895 + "license": "MIT" 1896 + }, 1897 + "node_modules/glob": { 1898 + "version": "7.2.3", 1899 + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 1900 + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 1901 + "deprecated": "Glob versions prior to v9 are no longer supported", 1902 + "license": "ISC", 1903 + "dependencies": { 1904 + "fs.realpath": "^1.0.0", 1905 + "inflight": "^1.0.4", 1906 + "inherits": "2", 1907 + "minimatch": "^3.1.1", 1908 + "once": "^1.3.0", 1909 + "path-is-absolute": "^1.0.0" 1910 + }, 1911 + "engines": { 1912 + "node": "*" 1913 + }, 1914 + "funding": { 1915 + "url": "https://github.com/sponsors/isaacs" 1916 + } 1917 + }, 1918 + "node_modules/has-unicode": { 1919 + "version": "2.0.1", 1920 + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 1921 + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", 1922 + "license": "ISC" 1923 + }, 1924 + "node_modules/https-proxy-agent": { 1925 + "version": "5.0.1", 1926 + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", 1927 + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", 1928 + "license": "MIT", 1929 + "dependencies": { 1930 + "agent-base": "6", 1931 + "debug": "4" 1932 + }, 1933 + "engines": { 1934 + "node": ">= 6" 1935 + } 1936 + }, 1937 + "node_modules/iconv-lite": { 1938 + "version": "0.6.3", 1939 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1940 + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1941 + "license": "MIT", 1942 + "dependencies": { 1943 + "safer-buffer": ">= 2.1.2 < 3.0.0" 1944 + }, 1945 + "engines": { 1946 + "node": ">=0.10.0" 1947 + } 1948 + }, 1949 + "node_modules/ieee754": { 1950 + "version": "1.2.1", 1951 + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 1952 + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 1953 + "funding": [ 1954 + { 1955 + "type": "github", 1956 + "url": "https://github.com/sponsors/feross" 1957 + }, 1958 + { 1959 + "type": "patreon", 1960 + "url": "https://www.patreon.com/feross" 1961 + }, 1962 + { 1963 + "type": "consulting", 1964 + "url": "https://feross.org/support" 1965 + } 1966 + ], 1967 + "license": "BSD-3-Clause" 1968 + }, 1969 + "node_modules/inflight": { 1970 + "version": "1.0.6", 1971 + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 1972 + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 1973 + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 1974 + "license": "ISC", 1975 + "dependencies": { 1976 + "once": "^1.3.0", 1977 + "wrappy": "1" 1978 + } 1979 + }, 1980 + "node_modules/inherits": { 1981 + "version": "2.0.4", 1982 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1983 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1984 + "license": "ISC" 1985 + }, 1986 + "node_modules/ini": { 1987 + "version": "1.3.8", 1988 + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 1989 + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", 1990 + "license": "ISC" 1991 + }, 1992 + "node_modules/internmap": { 1993 + "version": "2.0.3", 1994 + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", 1995 + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", 1996 + "license": "ISC", 1997 + "engines": { 1998 + "node": ">=12" 1999 + } 2000 + }, 2001 + "node_modules/is-arrayish": { 2002 + "version": "0.3.4", 2003 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", 2004 + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", 2005 + "license": "MIT" 2006 + }, 2007 + "node_modules/is-buffer": { 2008 + "version": "2.0.5", 2009 + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", 2010 + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", 2011 + "funding": [ 2012 + { 2013 + "type": "github", 2014 + "url": "https://github.com/sponsors/feross" 2015 + }, 2016 + { 2017 + "type": "patreon", 2018 + "url": "https://www.patreon.com/feross" 2019 + }, 2020 + { 2021 + "type": "consulting", 2022 + "url": "https://feross.org/support" 2023 + } 2024 + ], 2025 + "license": "MIT", 2026 + "engines": { 2027 + "node": ">=4" 2028 + } 2029 + }, 2030 + "node_modules/is-fullwidth-code-point": { 2031 + "version": "3.0.0", 2032 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2033 + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2034 + "license": "MIT", 2035 + "engines": { 2036 + "node": ">=8" 2037 + } 2038 + }, 2039 + "node_modules/isarray": { 2040 + "version": "1.0.0", 2041 + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 2042 + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 2043 + "license": "MIT" 2044 + }, 2045 + "node_modules/json-stringify-pretty-compact": { 2046 + "version": "4.0.0", 2047 + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", 2048 + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==", 2049 + "license": "MIT" 2050 + }, 2051 + "node_modules/level": { 2052 + "version": "8.0.1", 2053 + "resolved": "https://registry.npmjs.org/level/-/level-8.0.1.tgz", 2054 + "integrity": "sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==", 2055 + "license": "MIT", 2056 + "dependencies": { 2057 + "abstract-level": "^1.0.4", 2058 + "browser-level": "^1.0.1", 2059 + "classic-level": "^1.2.0" 2060 + }, 2061 + "engines": { 2062 + "node": ">=12" 2063 + }, 2064 + "funding": { 2065 + "type": "opencollective", 2066 + "url": "https://opencollective.com/level" 2067 + } 2068 + }, 2069 + "node_modules/level-read-stream": { 2070 + "version": "1.1.1", 2071 + "resolved": "https://registry.npmjs.org/level-read-stream/-/level-read-stream-1.1.1.tgz", 2072 + "integrity": "sha512-Rglcw1ltqLjkWb9CZL/HqeKipY+6RQ/ZSH6cEVsLR4BU4o1Pt08bXpe1h9YD5ifEjYlIIDdk10z2KX/WLbCKhA==", 2073 + "license": "MIT", 2074 + "dependencies": { 2075 + "readable-stream": "^3.4.0" 2076 + }, 2077 + "engines": { 2078 + "node": ">=12" 2079 + }, 2080 + "peerDependencies": { 2081 + "abstract-level": ">=1.0.0" 2082 + }, 2083 + "peerDependenciesMeta": { 2084 + "abstract-level": { 2085 + "optional": true 2086 + } 2087 + } 2088 + }, 2089 + "node_modules/level-supports": { 2090 + "version": "4.0.1", 2091 + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", 2092 + "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", 2093 + "license": "MIT", 2094 + "engines": { 2095 + "node": ">=12" 2096 + } 2097 + }, 2098 + "node_modules/level-transcoder": { 2099 + "version": "1.0.1", 2100 + "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", 2101 + "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", 2102 + "license": "MIT", 2103 + "dependencies": { 2104 + "buffer": "^6.0.3", 2105 + "module-error": "^1.0.1" 2106 + }, 2107 + "engines": { 2108 + "node": ">=12" 2109 + } 2110 + }, 2111 + "node_modules/level-ws": { 2112 + "version": "4.0.0", 2113 + "resolved": "https://registry.npmjs.org/level-ws/-/level-ws-4.0.0.tgz", 2114 + "integrity": "sha512-agBO0OUZb8i7c0nN8czMduKfLaUcQzqbnCv1Dmm0I6RB09aVUtB/YqbO1oYSKO1ROazAA4Is6BEYpG4IVDgFIg==", 2115 + "license": "MIT", 2116 + "dependencies": { 2117 + "readable-stream": "^4.0.0" 2118 + }, 2119 + "engines": { 2120 + "node": ">=12" 2121 + } 2122 + }, 2123 + "node_modules/level-ws/node_modules/readable-stream": { 2124 + "version": "4.7.0", 2125 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", 2126 + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", 2127 + "license": "MIT", 2128 + "dependencies": { 2129 + "abort-controller": "^3.0.0", 2130 + "buffer": "^6.0.3", 2131 + "events": "^3.3.0", 2132 + "process": "^0.11.10", 2133 + "string_decoder": "^1.3.0" 2134 + }, 2135 + "engines": { 2136 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 2137 + } 2138 + }, 2139 + "node_modules/levelgraph": { 2140 + "version": "4.0.0", 2141 + "resolved": "https://registry.npmjs.org/levelgraph/-/levelgraph-4.0.0.tgz", 2142 + "integrity": "sha512-mHIawnZ/w5E655ujYbnO+TXGyyMl1nA+yLeTCgDwOwTi+CwbbC4AXqepeQKNBJhkF09m6h3a9vzIWDRvi+f6jg==", 2143 + "license": "MIT", 2144 + "dependencies": { 2145 + "callback-stream": "^1.1.0", 2146 + "inherits": "^2.0.4", 2147 + "level-read-stream": "^1.1.0", 2148 + "level-ws": "^4.0.0", 2149 + "lodash.keys": "^4.2.0", 2150 + "pump": "^3.0.0", 2151 + "readable-stream": "^4.0.0", 2152 + "steed": "^1.1.3", 2153 + "xtend": "^4.0.2" 2154 + } 2155 + }, 2156 + "node_modules/levelgraph/node_modules/readable-stream": { 2157 + "version": "4.7.0", 2158 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", 2159 + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", 2160 + "license": "MIT", 2161 + "dependencies": { 2162 + "abort-controller": "^3.0.0", 2163 + "buffer": "^6.0.3", 2164 + "events": "^3.3.0", 2165 + "process": "^0.11.10", 2166 + "string_decoder": "^1.3.0" 2167 + }, 2168 + "engines": { 2169 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 2170 + } 2171 + }, 2172 + "node_modules/lodash.keys": { 2173 + "version": "4.2.0", 2174 + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz", 2175 + "integrity": "sha512-J79MkJcp7Df5mizHiVNpjoHXLi4HLjh9VLS/M7lQSGoQ+0oQ+lWEigREkqKyizPB1IawvQLLKY8mzEcm1tkyxQ==", 2176 + "license": "MIT" 2177 + }, 2178 + "node_modules/make-dir": { 2179 + "version": "3.1.0", 2180 + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", 2181 + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", 2182 + "license": "MIT", 2183 + "dependencies": { 2184 + "semver": "^6.0.0" 2185 + }, 2186 + "engines": { 2187 + "node": ">=8" 2188 + }, 2189 + "funding": { 2190 + "url": "https://github.com/sponsors/sindresorhus" 2191 + } 2192 + }, 2193 + "node_modules/make-dir/node_modules/semver": { 2194 + "version": "6.3.1", 2195 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 2196 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 2197 + "license": "ISC", 2198 + "bin": { 2199 + "semver": "bin/semver.js" 2200 + } 2201 + }, 2202 + "node_modules/mimic-response": { 2203 + "version": "2.1.0", 2204 + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", 2205 + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", 2206 + "license": "MIT", 2207 + "engines": { 2208 + "node": ">=8" 2209 + }, 2210 + "funding": { 2211 + "url": "https://github.com/sponsors/sindresorhus" 2212 + } 2213 + }, 2214 + "node_modules/minimatch": { 2215 + "version": "3.1.2", 2216 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 2217 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 2218 + "license": "ISC", 2219 + "dependencies": { 2220 + "brace-expansion": "^1.1.7" 2221 + }, 2222 + "engines": { 2223 + "node": "*" 2224 + } 2225 + }, 2226 + "node_modules/minimist": { 2227 + "version": "1.2.8", 2228 + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 2229 + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 2230 + "license": "MIT", 2231 + "funding": { 2232 + "url": "https://github.com/sponsors/ljharb" 2233 + } 2234 + }, 2235 + "node_modules/minipass": { 2236 + "version": "5.0.0", 2237 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", 2238 + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", 2239 + "license": "ISC", 2240 + "engines": { 2241 + "node": ">=8" 2242 + } 2243 + }, 2244 + "node_modules/minizlib": { 2245 + "version": "2.1.2", 2246 + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 2247 + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 2248 + "license": "MIT", 2249 + "dependencies": { 2250 + "minipass": "^3.0.0", 2251 + "yallist": "^4.0.0" 2252 + }, 2253 + "engines": { 2254 + "node": ">= 8" 2255 + } 2256 + }, 2257 + "node_modules/minizlib/node_modules/minipass": { 2258 + "version": "3.3.6", 2259 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", 2260 + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", 2261 + "license": "ISC", 2262 + "dependencies": { 2263 + "yallist": "^4.0.0" 2264 + }, 2265 + "engines": { 2266 + "node": ">=8" 2267 + } 2268 + }, 2269 + "node_modules/mkdirp": { 2270 + "version": "1.0.4", 2271 + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 2272 + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 2273 + "license": "MIT", 2274 + "bin": { 2275 + "mkdirp": "bin/cmd.js" 2276 + }, 2277 + "engines": { 2278 + "node": ">=10" 2279 + } 2280 + }, 2281 + "node_modules/mkdirp-classic": { 2282 + "version": "0.5.3", 2283 + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 2284 + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", 2285 + "license": "MIT" 2286 + }, 2287 + "node_modules/module-error": { 2288 + "version": "1.0.2", 2289 + "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", 2290 + "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", 2291 + "license": "MIT", 2292 + "engines": { 2293 + "node": ">=10" 2294 + } 2295 + }, 2296 + "node_modules/ms": { 2297 + "version": "2.1.3", 2298 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2299 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2300 + "license": "MIT" 2301 + }, 2302 + "node_modules/nan": { 2303 + "version": "2.24.0", 2304 + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", 2305 + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", 2306 + "license": "MIT" 2307 + }, 2308 + "node_modules/napi-build-utils": { 2309 + "version": "2.0.0", 2310 + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", 2311 + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", 2312 + "license": "MIT" 2313 + }, 2314 + "node_modules/napi-macros": { 2315 + "version": "2.2.2", 2316 + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", 2317 + "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", 2318 + "license": "MIT" 2319 + }, 2320 + "node_modules/node-abi": { 2321 + "version": "3.85.0", 2322 + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", 2323 + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", 2324 + "license": "MIT", 2325 + "dependencies": { 2326 + "semver": "^7.3.5" 2327 + }, 2328 + "engines": { 2329 + "node": ">=10" 2330 + } 2331 + }, 2332 + "node_modules/node-fetch": { 2333 + "version": "2.7.0", 2334 + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 2335 + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 2336 + "license": "MIT", 2337 + "dependencies": { 2338 + "whatwg-url": "^5.0.0" 2339 + }, 2340 + "engines": { 2341 + "node": "4.x || >=6.0.0" 2342 + }, 2343 + "peerDependencies": { 2344 + "encoding": "^0.1.0" 2345 + }, 2346 + "peerDependenciesMeta": { 2347 + "encoding": { 2348 + "optional": true 2349 + } 2350 + } 2351 + }, 2352 + "node_modules/node-gyp-build": { 2353 + "version": "4.8.4", 2354 + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", 2355 + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", 2356 + "license": "MIT", 2357 + "bin": { 2358 + "node-gyp-build": "bin.js", 2359 + "node-gyp-build-optional": "optional.js", 2360 + "node-gyp-build-test": "build-test.js" 2361 + } 2362 + }, 2363 + "node_modules/nopt": { 2364 + "version": "5.0.0", 2365 + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 2366 + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 2367 + "license": "ISC", 2368 + "dependencies": { 2369 + "abbrev": "1" 2370 + }, 2371 + "bin": { 2372 + "nopt": "bin/nopt.js" 2373 + }, 2374 + "engines": { 2375 + "node": ">=6" 2376 + } 2377 + }, 2378 + "node_modules/npmlog": { 2379 + "version": "5.0.1", 2380 + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", 2381 + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", 2382 + "deprecated": "This package is no longer supported.", 2383 + "license": "ISC", 2384 + "dependencies": { 2385 + "are-we-there-yet": "^2.0.0", 2386 + "console-control-strings": "^1.1.0", 2387 + "gauge": "^3.0.0", 2388 + "set-blocking": "^2.0.0" 2389 + } 2390 + }, 2391 + "node_modules/object-assign": { 2392 + "version": "4.1.1", 2393 + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 2394 + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 2395 + "license": "MIT", 2396 + "engines": { 2397 + "node": ">=0.10.0" 2398 + } 2399 + }, 2400 + "node_modules/once": { 2401 + "version": "1.4.0", 2402 + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 2403 + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 2404 + "license": "ISC", 2405 + "dependencies": { 2406 + "wrappy": "1" 2407 + } 2408 + }, 2409 + "node_modules/path-is-absolute": { 2410 + "version": "1.0.1", 2411 + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 2412 + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 2413 + "license": "MIT", 2414 + "engines": { 2415 + "node": ">=0.10.0" 2416 + } 2417 + }, 2418 + "node_modules/prebuild-install": { 2419 + "version": "7.1.3", 2420 + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", 2421 + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", 2422 + "license": "MIT", 2423 + "dependencies": { 2424 + "detect-libc": "^2.0.0", 2425 + "expand-template": "^2.0.3", 2426 + "github-from-package": "0.0.0", 2427 + "minimist": "^1.2.3", 2428 + "mkdirp-classic": "^0.5.3", 2429 + "napi-build-utils": "^2.0.0", 2430 + "node-abi": "^3.3.0", 2431 + "pump": "^3.0.0", 2432 + "rc": "^1.2.7", 2433 + "simple-get": "^4.0.0", 2434 + "tar-fs": "^2.0.0", 2435 + "tunnel-agent": "^0.6.0" 2436 + }, 2437 + "bin": { 2438 + "prebuild-install": "bin.js" 2439 + }, 2440 + "engines": { 2441 + "node": ">=10" 2442 + } 2443 + }, 2444 + "node_modules/prebuild-install/node_modules/decompress-response": { 2445 + "version": "6.0.0", 2446 + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 2447 + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 2448 + "license": "MIT", 2449 + "dependencies": { 2450 + "mimic-response": "^3.1.0" 2451 + }, 2452 + "engines": { 2453 + "node": ">=10" 2454 + }, 2455 + "funding": { 2456 + "url": "https://github.com/sponsors/sindresorhus" 2457 + } 2458 + }, 2459 + "node_modules/prebuild-install/node_modules/mimic-response": { 2460 + "version": "3.1.0", 2461 + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 2462 + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", 2463 + "license": "MIT", 2464 + "engines": { 2465 + "node": ">=10" 2466 + }, 2467 + "funding": { 2468 + "url": "https://github.com/sponsors/sindresorhus" 2469 + } 2470 + }, 2471 + "node_modules/prebuild-install/node_modules/simple-get": { 2472 + "version": "4.0.1", 2473 + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", 2474 + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", 2475 + "funding": [ 2476 + { 2477 + "type": "github", 2478 + "url": "https://github.com/sponsors/feross" 2479 + }, 2480 + { 2481 + "type": "patreon", 2482 + "url": "https://www.patreon.com/feross" 2483 + }, 2484 + { 2485 + "type": "consulting", 2486 + "url": "https://feross.org/support" 2487 + } 2488 + ], 2489 + "license": "MIT", 2490 + "dependencies": { 2491 + "decompress-response": "^6.0.0", 2492 + "once": "^1.3.1", 2493 + "simple-concat": "^1.0.0" 2494 + } 2495 + }, 2496 + "node_modules/process": { 2497 + "version": "0.11.10", 2498 + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 2499 + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 2500 + "license": "MIT", 2501 + "engines": { 2502 + "node": ">= 0.6.0" 2503 + } 2504 + }, 2505 + "node_modules/process-nextick-args": { 2506 + "version": "2.0.1", 2507 + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 2508 + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 2509 + "license": "MIT" 2510 + }, 2511 + "node_modules/pump": { 2512 + "version": "3.0.3", 2513 + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", 2514 + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", 2515 + "license": "MIT", 2516 + "dependencies": { 2517 + "end-of-stream": "^1.1.0", 2518 + "once": "^1.3.1" 2519 + } 2520 + }, 2521 + "node_modules/queue-microtask": { 2522 + "version": "1.2.3", 2523 + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 2524 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 2525 + "funding": [ 2526 + { 2527 + "type": "github", 2528 + "url": "https://github.com/sponsors/feross" 2529 + }, 2530 + { 2531 + "type": "patreon", 2532 + "url": "https://www.patreon.com/feross" 2533 + }, 2534 + { 2535 + "type": "consulting", 2536 + "url": "https://feross.org/support" 2537 + } 2538 + ], 2539 + "license": "MIT" 2540 + }, 2541 + "node_modules/rc": { 2542 + "version": "1.2.8", 2543 + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 2544 + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 2545 + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", 2546 + "dependencies": { 2547 + "deep-extend": "^0.6.0", 2548 + "ini": "~1.3.0", 2549 + "minimist": "^1.2.0", 2550 + "strip-json-comments": "~2.0.1" 2551 + }, 2552 + "bin": { 2553 + "rc": "cli.js" 2554 + } 2555 + }, 2556 + "node_modules/readable-stream": { 2557 + "version": "3.6.2", 2558 + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 2559 + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 2560 + "license": "MIT", 2561 + "dependencies": { 2562 + "inherits": "^2.0.3", 2563 + "string_decoder": "^1.1.1", 2564 + "util-deprecate": "^1.0.1" 2565 + }, 2566 + "engines": { 2567 + "node": ">= 6" 2568 + } 2569 + }, 2570 + "node_modules/resolve-pkg-maps": { 2571 + "version": "1.0.0", 2572 + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 2573 + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 2574 + "dev": true, 2575 + "license": "MIT", 2576 + "funding": { 2577 + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 2578 + } 2579 + }, 2580 + "node_modules/reusify": { 2581 + "version": "1.1.0", 2582 + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 2583 + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 2584 + "license": "MIT", 2585 + "engines": { 2586 + "iojs": ">=1.0.0", 2587 + "node": ">=0.10.0" 2588 + } 2589 + }, 2590 + "node_modules/rimraf": { 2591 + "version": "3.0.2", 2592 + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 2593 + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 2594 + "deprecated": "Rimraf versions prior to v4 are no longer supported", 2595 + "license": "ISC", 2596 + "dependencies": { 2597 + "glob": "^7.1.3" 2598 + }, 2599 + "bin": { 2600 + "rimraf": "bin.js" 2601 + }, 2602 + "funding": { 2603 + "url": "https://github.com/sponsors/isaacs" 2604 + } 2605 + }, 2606 + "node_modules/robust-predicates": { 2607 + "version": "3.0.2", 2608 + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", 2609 + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", 2610 + "license": "Unlicense" 2611 + }, 2612 + "node_modules/run-parallel-limit": { 2613 + "version": "1.1.0", 2614 + "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", 2615 + "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", 2616 + "funding": [ 2617 + { 2618 + "type": "github", 2619 + "url": "https://github.com/sponsors/feross" 2620 + }, 2621 + { 2622 + "type": "patreon", 2623 + "url": "https://www.patreon.com/feross" 2624 + }, 2625 + { 2626 + "type": "consulting", 2627 + "url": "https://feross.org/support" 2628 + } 2629 + ], 2630 + "license": "MIT", 2631 + "dependencies": { 2632 + "queue-microtask": "^1.2.2" 2633 + } 2634 + }, 2635 + "node_modules/rw": { 2636 + "version": "1.3.3", 2637 + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", 2638 + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", 2639 + "license": "BSD-3-Clause" 2640 + }, 2641 + "node_modules/safe-buffer": { 2642 + "version": "5.2.1", 2643 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2644 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 2645 + "funding": [ 2646 + { 2647 + "type": "github", 2648 + "url": "https://github.com/sponsors/feross" 2649 + }, 2650 + { 2651 + "type": "patreon", 2652 + "url": "https://www.patreon.com/feross" 2653 + }, 2654 + { 2655 + "type": "consulting", 2656 + "url": "https://feross.org/support" 2657 + } 2658 + ], 2659 + "license": "MIT" 2660 + }, 2661 + "node_modules/safer-buffer": { 2662 + "version": "2.1.2", 2663 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2664 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 2665 + "license": "MIT" 2666 + }, 2667 + "node_modules/semver": { 2668 + "version": "7.7.3", 2669 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", 2670 + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", 2671 + "license": "ISC", 2672 + "bin": { 2673 + "semver": "bin/semver.js" 2674 + }, 2675 + "engines": { 2676 + "node": ">=10" 2677 + } 2678 + }, 2679 + "node_modules/set-blocking": { 2680 + "version": "2.0.0", 2681 + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 2682 + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", 2683 + "license": "ISC" 2684 + }, 2685 + "node_modules/sharp": { 2686 + "version": "0.33.5", 2687 + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", 2688 + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", 2689 + "hasInstallScript": true, 2690 + "license": "Apache-2.0", 2691 + "dependencies": { 2692 + "color": "^4.2.3", 2693 + "detect-libc": "^2.0.3", 2694 + "semver": "^7.6.3" 2695 + }, 2696 + "engines": { 2697 + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" 2698 + }, 2699 + "funding": { 2700 + "url": "https://opencollective.com/libvips" 2701 + }, 2702 + "optionalDependencies": { 2703 + "@img/sharp-darwin-arm64": "0.33.5", 2704 + "@img/sharp-darwin-x64": "0.33.5", 2705 + "@img/sharp-libvips-darwin-arm64": "1.0.4", 2706 + "@img/sharp-libvips-darwin-x64": "1.0.4", 2707 + "@img/sharp-libvips-linux-arm": "1.0.5", 2708 + "@img/sharp-libvips-linux-arm64": "1.0.4", 2709 + "@img/sharp-libvips-linux-s390x": "1.0.4", 2710 + "@img/sharp-libvips-linux-x64": "1.0.4", 2711 + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", 2712 + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", 2713 + "@img/sharp-linux-arm": "0.33.5", 2714 + "@img/sharp-linux-arm64": "0.33.5", 2715 + "@img/sharp-linux-s390x": "0.33.5", 2716 + "@img/sharp-linux-x64": "0.33.5", 2717 + "@img/sharp-linuxmusl-arm64": "0.33.5", 2718 + "@img/sharp-linuxmusl-x64": "0.33.5", 2719 + "@img/sharp-wasm32": "0.33.5", 2720 + "@img/sharp-win32-ia32": "0.33.5", 2721 + "@img/sharp-win32-x64": "0.33.5" 2722 + } 2723 + }, 2724 + "node_modules/signal-exit": { 2725 + "version": "3.0.7", 2726 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", 2727 + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", 2728 + "license": "ISC" 2729 + }, 2730 + "node_modules/simple-concat": { 2731 + "version": "1.0.1", 2732 + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 2733 + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", 2734 + "funding": [ 2735 + { 2736 + "type": "github", 2737 + "url": "https://github.com/sponsors/feross" 2738 + }, 2739 + { 2740 + "type": "patreon", 2741 + "url": "https://www.patreon.com/feross" 2742 + }, 2743 + { 2744 + "type": "consulting", 2745 + "url": "https://feross.org/support" 2746 + } 2747 + ], 2748 + "license": "MIT" 2749 + }, 2750 + "node_modules/simple-get": { 2751 + "version": "3.1.1", 2752 + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", 2753 + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", 2754 + "license": "MIT", 2755 + "dependencies": { 2756 + "decompress-response": "^4.2.0", 2757 + "once": "^1.3.1", 2758 + "simple-concat": "^1.0.0" 2759 + } 2760 + }, 2761 + "node_modules/simple-swizzle": { 2762 + "version": "0.2.4", 2763 + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", 2764 + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", 2765 + "license": "MIT", 2766 + "dependencies": { 2767 + "is-arrayish": "^0.3.1" 2768 + } 2769 + }, 2770 + "node_modules/steed": { 2771 + "version": "1.1.3", 2772 + "resolved": "https://registry.npmjs.org/steed/-/steed-1.1.3.tgz", 2773 + "integrity": "sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==", 2774 + "license": "MIT", 2775 + "dependencies": { 2776 + "fastfall": "^1.5.0", 2777 + "fastparallel": "^2.2.0", 2778 + "fastq": "^1.3.0", 2779 + "fastseries": "^1.7.0", 2780 + "reusify": "^1.0.0" 2781 + } 2782 + }, 2783 + "node_modules/string_decoder": { 2784 + "version": "1.3.0", 2785 + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 2786 + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 2787 + "license": "MIT", 2788 + "dependencies": { 2789 + "safe-buffer": "~5.2.0" 2790 + } 2791 + }, 2792 + "node_modules/string-width": { 2793 + "version": "4.2.3", 2794 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2795 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2796 + "license": "MIT", 2797 + "dependencies": { 2798 + "emoji-regex": "^8.0.0", 2799 + "is-fullwidth-code-point": "^3.0.0", 2800 + "strip-ansi": "^6.0.1" 2801 + }, 2802 + "engines": { 2803 + "node": ">=8" 2804 + } 2805 + }, 2806 + "node_modules/strip-ansi": { 2807 + "version": "6.0.1", 2808 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2809 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2810 + "license": "MIT", 2811 + "dependencies": { 2812 + "ansi-regex": "^5.0.1" 2813 + }, 2814 + "engines": { 2815 + "node": ">=8" 2816 + } 2817 + }, 2818 + "node_modules/strip-json-comments": { 2819 + "version": "2.0.1", 2820 + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 2821 + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", 2822 + "license": "MIT", 2823 + "engines": { 2824 + "node": ">=0.10.0" 2825 + } 2826 + }, 2827 + "node_modules/tar": { 2828 + "version": "6.2.1", 2829 + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", 2830 + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", 2831 + "license": "ISC", 2832 + "dependencies": { 2833 + "chownr": "^2.0.0", 2834 + "fs-minipass": "^2.0.0", 2835 + "minipass": "^5.0.0", 2836 + "minizlib": "^2.1.1", 2837 + "mkdirp": "^1.0.3", 2838 + "yallist": "^4.0.0" 2839 + }, 2840 + "engines": { 2841 + "node": ">=10" 2842 + } 2843 + }, 2844 + "node_modules/tar-fs": { 2845 + "version": "2.1.4", 2846 + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", 2847 + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", 2848 + "license": "MIT", 2849 + "dependencies": { 2850 + "chownr": "^1.1.1", 2851 + "mkdirp-classic": "^0.5.2", 2852 + "pump": "^3.0.0", 2853 + "tar-stream": "^2.1.4" 2854 + } 2855 + }, 2856 + "node_modules/tar-fs/node_modules/chownr": { 2857 + "version": "1.1.4", 2858 + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 2859 + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", 2860 + "license": "ISC" 2861 + }, 2862 + "node_modules/tar-stream": { 2863 + "version": "2.2.0", 2864 + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 2865 + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 2866 + "license": "MIT", 2867 + "dependencies": { 2868 + "bl": "^4.0.3", 2869 + "end-of-stream": "^1.4.1", 2870 + "fs-constants": "^1.0.0", 2871 + "inherits": "^2.0.3", 2872 + "readable-stream": "^3.1.1" 2873 + }, 2874 + "engines": { 2875 + "node": ">=6" 2876 + } 2877 + }, 2878 + "node_modules/tinybase": { 2879 + "version": "5.4.9", 2880 + "resolved": "https://registry.npmjs.org/tinybase/-/tinybase-5.4.9.tgz", 2881 + "integrity": "sha512-sRXRqe/m6TEWrNZRDF/9JEbQoBTqDYsYQjz3uLQWwpQmhxFKydq2tb+yofzeMLFlj+b/m1TpUSbk8XBv8s7Fnw==", 2882 + "license": "MIT", 2883 + "bin": { 2884 + "tinybase": "cli/index.js" 2885 + }, 2886 + "peerDependencies": { 2887 + "@automerge/automerge-repo": "^1.2.1", 2888 + "@cloudflare/workers-types": "^4.20250312.0", 2889 + "@electric-sql/pglite": "^0.2.17", 2890 + "@libsql/client": "^0.14.0", 2891 + "@powersync/common": "^1.25.0", 2892 + "@sqlite.org/sqlite-wasm": "^3.49.1-build2", 2893 + "@vlcn.io/crsqlite-wasm": "^0.16.0", 2894 + "electric-sql": "^0.12.1", 2895 + "expo": "^52.0.4", 2896 + "expo-sqlite": "^15.1.2", 2897 + "partykit": "^0.0.111", 2898 + "partysocket": "^1.0.2", 2899 + "postgres": "^3.4.5", 2900 + "prettier": "^3.5.3", 2901 + "react": "^18.2.0", 2902 + "react-dom": "^18.2.0", 2903 + "sqlite3": "^5.1.7", 2904 + "ws": "^8.18.1", 2905 + "yjs": "^13.6.24" 2906 + }, 2907 + "peerDependenciesMeta": { 2908 + "@automerge/automerge-repo": { 2909 + "optional": true 2910 + }, 2911 + "@cloudflare/workers-types": { 2912 + "optional": true 2913 + }, 2914 + "@electric-sql/pglite": { 2915 + "optional": true 2916 + }, 2917 + "@libsql/client": { 2918 + "optional": true 2919 + }, 2920 + "@powersync/common": { 2921 + "optional": true 2922 + }, 2923 + "@sqlite.org/sqlite-wasm": { 2924 + "optional": true 2925 + }, 2926 + "@vlcn.io/crsqlite-wasm": { 2927 + "optional": true 2928 + }, 2929 + "electric-sql": { 2930 + "optional": true 2931 + }, 2932 + "expo": { 2933 + "optional": true 2934 + }, 2935 + "expo-sqlite": { 2936 + "optional": true 2937 + }, 2938 + "partykit": { 2939 + "optional": true 2940 + }, 2941 + "partysocket": { 2942 + "optional": true 2943 + }, 2944 + "postgres": { 2945 + "optional": true 2946 + }, 2947 + "prettier": { 2948 + "optional": true 2949 + }, 2950 + "react": { 2951 + "optional": true 2952 + }, 2953 + "react-dom": { 2954 + "optional": true 2955 + }, 2956 + "sqlite3": { 2957 + "optional": true 2958 + }, 2959 + "ws": { 2960 + "optional": true 2961 + }, 2962 + "yjs": { 2963 + "optional": true 2964 + } 2965 + } 2966 + }, 2967 + "node_modules/topojson-client": { 2968 + "version": "3.1.0", 2969 + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", 2970 + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", 2971 + "license": "ISC", 2972 + "dependencies": { 2973 + "commander": "2" 2974 + }, 2975 + "bin": { 2976 + "topo2geo": "bin/topo2geo", 2977 + "topomerge": "bin/topomerge", 2978 + "topoquantize": "bin/topoquantize" 2979 + } 2980 + }, 2981 + "node_modules/topojson-client/node_modules/commander": { 2982 + "version": "2.20.3", 2983 + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 2984 + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 2985 + "license": "MIT" 2986 + }, 2987 + "node_modules/tr46": { 2988 + "version": "0.0.3", 2989 + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 2990 + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 2991 + "license": "MIT" 2992 + }, 2993 + "node_modules/tslib": { 2994 + "version": "2.8.1", 2995 + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 2996 + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 2997 + "license": "0BSD" 2998 + }, 2999 + "node_modules/tsx": { 3000 + "version": "4.21.0", 3001 + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", 3002 + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", 3003 + "dev": true, 3004 + "license": "MIT", 3005 + "dependencies": { 3006 + "esbuild": "~0.27.0", 3007 + "get-tsconfig": "^4.7.5" 3008 + }, 3009 + "bin": { 3010 + "tsx": "dist/cli.mjs" 3011 + }, 3012 + "engines": { 3013 + "node": ">=18.0.0" 3014 + }, 3015 + "optionalDependencies": { 3016 + "fsevents": "~2.3.3" 3017 + } 3018 + }, 3019 + "node_modules/tunnel-agent": { 3020 + "version": "0.6.0", 3021 + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 3022 + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 3023 + "license": "Apache-2.0", 3024 + "dependencies": { 3025 + "safe-buffer": "^5.0.1" 3026 + }, 3027 + "engines": { 3028 + "node": "*" 3029 + } 3030 + }, 3031 + "node_modules/typescript": { 3032 + "version": "5.9.3", 3033 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 3034 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 3035 + "dev": true, 3036 + "license": "Apache-2.0", 3037 + "bin": { 3038 + "tsc": "bin/tsc", 3039 + "tsserver": "bin/tsserver" 3040 + }, 3041 + "engines": { 3042 + "node": ">=14.17" 3043 + } 3044 + }, 3045 + "node_modules/undici-types": { 3046 + "version": "6.21.0", 3047 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 3048 + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", 3049 + "dev": true, 3050 + "license": "MIT" 3051 + }, 3052 + "node_modules/util-deprecate": { 3053 + "version": "1.0.2", 3054 + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 3055 + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 3056 + "license": "MIT" 3057 + }, 3058 + "node_modules/vega": { 3059 + "version": "6.2.0", 3060 + "resolved": "https://registry.npmjs.org/vega/-/vega-6.2.0.tgz", 3061 + "integrity": "sha512-BIwalIcEGysJdQDjeVUmMWB3e50jPDNAMfLJscjEvpunU9bSt7X1OYnQxkg3uBwuRRI4nWfFZO9uIW910nLeGw==", 3062 + "license": "BSD-3-Clause", 3063 + "dependencies": { 3064 + "vega-crossfilter": "~5.1.0", 3065 + "vega-dataflow": "~6.1.0", 3066 + "vega-encode": "~5.1.0", 3067 + "vega-event-selector": "~4.0.0", 3068 + "vega-expression": "~6.1.0", 3069 + "vega-force": "~5.1.0", 3070 + "vega-format": "~2.1.0", 3071 + "vega-functions": "~6.1.0", 3072 + "vega-geo": "~5.1.0", 3073 + "vega-hierarchy": "~5.1.0", 3074 + "vega-label": "~2.1.0", 3075 + "vega-loader": "~5.1.0", 3076 + "vega-parser": "~7.1.0", 3077 + "vega-projection": "~2.1.0", 3078 + "vega-regression": "~2.1.0", 3079 + "vega-runtime": "~7.1.0", 3080 + "vega-scale": "~8.1.0", 3081 + "vega-scenegraph": "~5.1.0", 3082 + "vega-statistics": "~2.0.0", 3083 + "vega-time": "~3.1.0", 3084 + "vega-transforms": "~5.1.0", 3085 + "vega-typings": "~2.1.0", 3086 + "vega-util": "~2.1.0", 3087 + "vega-view": "~6.1.0", 3088 + "vega-view-transforms": "~5.1.0", 3089 + "vega-voronoi": "~5.1.0", 3090 + "vega-wordcloud": "~5.1.0" 3091 + }, 3092 + "funding": { 3093 + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" 3094 + } 3095 + }, 3096 + "node_modules/vega-canvas": { 3097 + "version": "2.0.0", 3098 + "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-2.0.0.tgz", 3099 + "integrity": "sha512-9x+4TTw/USYST5nx4yN272sy9WcqSRjAR0tkQYZJ4cQIeon7uVsnohvoPQK1JZu7K1QXGUqzj08z0u/UegBVMA==", 3100 + "license": "BSD-3-Clause" 3101 + }, 3102 + "node_modules/vega-crossfilter": { 3103 + "version": "5.1.0", 3104 + "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-5.1.0.tgz", 3105 + "integrity": "sha512-EmVhfP3p6AM7o/lPan/QAoqjblI19BxWUlvl2TSs0xjQd8KbaYYbS4Ixt3cmEvl0QjRdBMF6CdJJ/cy9DTS4Fw==", 3106 + "license": "BSD-3-Clause", 3107 + "dependencies": { 3108 + "d3-array": "^3.2.4", 3109 + "vega-dataflow": "^6.1.0", 3110 + "vega-util": "^2.1.0" 3111 + } 3112 + }, 3113 + "node_modules/vega-dataflow": { 3114 + "version": "6.1.0", 3115 + "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-6.1.0.tgz", 3116 + "integrity": "sha512-JxumGlODtFbzoQ4c/jQK8Tb/68ih0lrexlCozcMfTAwQ12XhTqCvlafh7MAKKTMBizjOfaQTHm4Jkyb1H5CfyQ==", 3117 + "license": "BSD-3-Clause", 3118 + "dependencies": { 3119 + "vega-format": "^2.1.0", 3120 + "vega-loader": "^5.1.0", 3121 + "vega-util": "^2.1.0" 3122 + } 3123 + }, 3124 + "node_modules/vega-encode": { 3125 + "version": "5.1.0", 3126 + "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-5.1.0.tgz", 3127 + "integrity": "sha512-q26oI7B+MBQYcTQcr5/c1AMsX3FvjZLQOBi7yI0vV+GEn93fElDgvhQiYrgeYSD4Exi/jBPeUXuN6p4bLz16kA==", 3128 + "license": "BSD-3-Clause", 3129 + "dependencies": { 3130 + "d3-array": "^3.2.4", 3131 + "d3-interpolate": "^3.0.1", 3132 + "vega-dataflow": "^6.1.0", 3133 + "vega-scale": "^8.1.0", 3134 + "vega-util": "^2.1.0" 3135 + } 3136 + }, 3137 + "node_modules/vega-event-selector": { 3138 + "version": "4.0.0", 3139 + "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-4.0.0.tgz", 3140 + "integrity": "sha512-CcWF4m4KL/al1Oa5qSzZ5R776q8lRxCj3IafCHs5xipoEHrkgu1BWa7F/IH5HrDNXeIDnqOpSV1pFsAWRak4gQ==", 3141 + "license": "BSD-3-Clause" 3142 + }, 3143 + "node_modules/vega-expression": { 3144 + "version": "6.1.0", 3145 + "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-6.1.0.tgz", 3146 + "integrity": "sha512-hHgNx/fQ1Vn1u6vHSamH7lRMsOa/yQeHGGcWVmh8fZafLdwdhCM91kZD9p7+AleNpgwiwzfGogtpATFaMmDFYg==", 3147 + "license": "BSD-3-Clause", 3148 + "dependencies": { 3149 + "@types/estree": "^1.0.8", 3150 + "vega-util": "^2.1.0" 3151 + } 3152 + }, 3153 + "node_modules/vega-force": { 3154 + "version": "5.1.0", 3155 + "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-5.1.0.tgz", 3156 + "integrity": "sha512-wdnchOSeXpF9Xx8Yp0s6Do9F7YkFeOn/E/nENtsI7NOcyHpICJ5+UkgjUo9QaQ/Yu+dIDU+sP/4NXsUtq6SMaQ==", 3157 + "license": "BSD-3-Clause", 3158 + "dependencies": { 3159 + "d3-force": "^3.0.0", 3160 + "vega-dataflow": "^6.1.0", 3161 + "vega-util": "^2.1.0" 3162 + } 3163 + }, 3164 + "node_modules/vega-format": { 3165 + "version": "2.1.0", 3166 + "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-2.1.0.tgz", 3167 + "integrity": "sha512-i9Ht33IgqG36+S1gFDpAiKvXCPz+q+1vDhDGKK8YsgMxGOG4PzinKakI66xd7SdV4q97FgpR7odAXqtDN2wKqw==", 3168 + "license": "BSD-3-Clause", 3169 + "dependencies": { 3170 + "d3-array": "^3.2.4", 3171 + "d3-format": "^3.1.0", 3172 + "d3-time-format": "^4.1.0", 3173 + "vega-time": "^3.1.0", 3174 + "vega-util": "^2.1.0" 3175 + } 3176 + }, 3177 + "node_modules/vega-functions": { 3178 + "version": "6.1.1", 3179 + "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-6.1.1.tgz", 3180 + "integrity": "sha512-Due6jP0y0FfsGMTrHnzUGnEwXPu7VwE+9relfo+LjL/tRPYnnKqwWvzt7n9JkeBuZqjkgYjMzm/WucNn6Hkw5A==", 3181 + "license": "BSD-3-Clause", 3182 + "dependencies": { 3183 + "d3-array": "^3.2.4", 3184 + "d3-color": "^3.1.0", 3185 + "d3-geo": "^3.1.1", 3186 + "vega-dataflow": "^6.1.0", 3187 + "vega-expression": "^6.1.0", 3188 + "vega-scale": "^8.1.0", 3189 + "vega-scenegraph": "^5.1.0", 3190 + "vega-selections": "^6.1.0", 3191 + "vega-statistics": "^2.0.0", 3192 + "vega-time": "^3.1.0", 3193 + "vega-util": "^2.1.0" 3194 + } 3195 + }, 3196 + "node_modules/vega-geo": { 3197 + "version": "5.1.0", 3198 + "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-5.1.0.tgz", 3199 + "integrity": "sha512-H8aBBHfthc3rzDbz/Th18+Nvp00J73q3uXGAPDQqizioDm/CoXCK8cX4pMePydBY9S6ikBiGJrLKFDa80wI20g==", 3200 + "license": "BSD-3-Clause", 3201 + "dependencies": { 3202 + "d3-array": "^3.2.4", 3203 + "d3-color": "^3.1.0", 3204 + "d3-geo": "^3.1.1", 3205 + "vega-canvas": "^2.0.0", 3206 + "vega-dataflow": "^6.1.0", 3207 + "vega-projection": "^2.1.0", 3208 + "vega-statistics": "^2.0.0", 3209 + "vega-util": "^2.1.0" 3210 + } 3211 + }, 3212 + "node_modules/vega-hierarchy": { 3213 + "version": "5.1.0", 3214 + "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-5.1.0.tgz", 3215 + "integrity": "sha512-rZlU8QJNETlB6o73lGCPybZtw2fBBsRIRuFE77aCLFHdGsh6wIifhplVarqE9icBqjUHRRUOmcEYfzwVIPr65g==", 3216 + "license": "BSD-3-Clause", 3217 + "dependencies": { 3218 + "d3-hierarchy": "^3.1.2", 3219 + "vega-dataflow": "^6.1.0", 3220 + "vega-util": "^2.1.0" 3221 + } 3222 + }, 3223 + "node_modules/vega-label": { 3224 + "version": "2.1.0", 3225 + "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-2.1.0.tgz", 3226 + "integrity": "sha512-/hgf+zoA3FViDBehrQT42Lta3t8In6YwtMnwjYlh72zNn1p3c7E3YUBwqmAqTM1x+tudgzMRGLYig+bX1ewZxQ==", 3227 + "license": "BSD-3-Clause", 3228 + "dependencies": { 3229 + "vega-canvas": "^2.0.0", 3230 + "vega-dataflow": "^6.1.0", 3231 + "vega-scenegraph": "^5.1.0", 3232 + "vega-util": "^2.1.0" 3233 + } 3234 + }, 3235 + "node_modules/vega-lite": { 3236 + "version": "6.4.1", 3237 + "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-6.4.1.tgz", 3238 + "integrity": "sha512-KO3ybHNouRK4A0al/+2fN9UqgTEfxrd/ntGLY933Hg5UOYotDVQdshR3zn7OfXwQ7uj0W96Vfa5R+QxO8am3IQ==", 3239 + "license": "BSD-3-Clause", 3240 + "dependencies": { 3241 + "json-stringify-pretty-compact": "~4.0.0", 3242 + "tslib": "~2.8.1", 3243 + "vega-event-selector": "~4.0.0", 3244 + "vega-expression": "~6.1.0", 3245 + "vega-util": "~2.1.0", 3246 + "yargs": "~18.0.0" 3247 + }, 3248 + "bin": { 3249 + "vl2pdf": "bin/vl2pdf", 3250 + "vl2png": "bin/vl2png", 3251 + "vl2svg": "bin/vl2svg", 3252 + "vl2vg": "bin/vl2vg" 3253 + }, 3254 + "engines": { 3255 + "node": ">=18" 3256 + }, 3257 + "funding": { 3258 + "url": "https://app.hubspot.com/payments/GyPC972GD9Rt" 3259 + }, 3260 + "peerDependencies": { 3261 + "vega": "^6.0.0" 3262 + } 3263 + }, 3264 + "node_modules/vega-loader": { 3265 + "version": "5.1.0", 3266 + "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-5.1.0.tgz", 3267 + "integrity": "sha512-GaY3BdSPbPNdtrBz8SYUBNmNd8mdPc3mtdZfdkFazQ0RD9m+Toz5oR8fKnTamNSk9fRTJX0Lp3uEqxrAlQVreg==", 3268 + "license": "BSD-3-Clause", 3269 + "dependencies": { 3270 + "d3-dsv": "^3.0.1", 3271 + "topojson-client": "^3.1.0", 3272 + "vega-format": "^2.1.0", 3273 + "vega-util": "^2.1.0" 3274 + } 3275 + }, 3276 + "node_modules/vega-parser": { 3277 + "version": "7.1.0", 3278 + "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-7.1.0.tgz", 3279 + "integrity": "sha512-g0lrYxtmYVW8G6yXpIS4J3Uxt9OUSkc0bLu5afoYDo4rZmoOOdll3x3ebActp5LHPW+usZIE+p5nukRS2vEc7Q==", 3280 + "license": "BSD-3-Clause", 3281 + "dependencies": { 3282 + "vega-dataflow": "^6.1.0", 3283 + "vega-event-selector": "^4.0.0", 3284 + "vega-functions": "^6.1.0", 3285 + "vega-scale": "^8.1.0", 3286 + "vega-util": "^2.1.0" 3287 + } 3288 + }, 3289 + "node_modules/vega-projection": { 3290 + "version": "2.1.0", 3291 + "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-2.1.0.tgz", 3292 + "integrity": "sha512-EjRjVSoMR5ibrU7q8LaOQKP327NcOAM1+eZ+NO4ANvvAutwmbNVTmfA1VpPH+AD0AlBYc39ND/wnRk7SieDiXA==", 3293 + "license": "BSD-3-Clause", 3294 + "dependencies": { 3295 + "d3-geo": "^3.1.1", 3296 + "d3-geo-projection": "^4.0.0", 3297 + "vega-scale": "^8.1.0" 3298 + } 3299 + }, 3300 + "node_modules/vega-regression": { 3301 + "version": "2.1.0", 3302 + "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-2.1.0.tgz", 3303 + "integrity": "sha512-HzC7MuoEwG1rIxRaNTqgcaYF03z/ZxYkQR2D5BN0N45kLnHY1HJXiEcZkcffTsqXdspLjn47yLi44UoCwF5fxQ==", 3304 + "license": "BSD-3-Clause", 3305 + "dependencies": { 3306 + "d3-array": "^3.2.4", 3307 + "vega-dataflow": "^6.1.0", 3308 + "vega-statistics": "^2.0.0", 3309 + "vega-util": "^2.1.0" 3310 + } 3311 + }, 3312 + "node_modules/vega-runtime": { 3313 + "version": "7.1.0", 3314 + "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-7.1.0.tgz", 3315 + "integrity": "sha512-mItI+WHimyEcZlZrQ/zYR3LwHVeyHCWwp7MKaBjkU8EwkSxEEGVceyGUY9X2YuJLiOgkLz/6juYDbMv60pfwYA==", 3316 + "license": "BSD-3-Clause", 3317 + "dependencies": { 3318 + "vega-dataflow": "^6.1.0", 3319 + "vega-util": "^2.1.0" 3320 + } 3321 + }, 3322 + "node_modules/vega-scale": { 3323 + "version": "8.1.0", 3324 + "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-8.1.0.tgz", 3325 + "integrity": "sha512-VEgDuEcOec8+C8+FzLcnAmcXrv2gAJKqQifCdQhkgnsLa978vYUgVfCut/mBSMMHbH8wlUV1D0fKZTjRukA1+A==", 3326 + "license": "BSD-3-Clause", 3327 + "dependencies": { 3328 + "d3-array": "^3.2.4", 3329 + "d3-interpolate": "^3.0.1", 3330 + "d3-scale": "^4.0.2", 3331 + "d3-scale-chromatic": "^3.1.0", 3332 + "vega-time": "^3.1.0", 3333 + "vega-util": "^2.1.0" 3334 + } 3335 + }, 3336 + "node_modules/vega-scenegraph": { 3337 + "version": "5.1.0", 3338 + "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-5.1.0.tgz", 3339 + "integrity": "sha512-4gA89CFIxkZX+4Nvl8SZF2MBOqnlj9J5zgdPh/HPx+JOwtzSlUqIhxFpFj7GWYfwzr/PyZnguBLPihPw1Og/cA==", 3340 + "license": "BSD-3-Clause", 3341 + "dependencies": { 3342 + "d3-path": "^3.1.0", 3343 + "d3-shape": "^3.2.0", 3344 + "vega-canvas": "^2.0.0", 3345 + "vega-loader": "^5.1.0", 3346 + "vega-scale": "^8.1.0", 3347 + "vega-util": "^2.1.0" 3348 + } 3349 + }, 3350 + "node_modules/vega-selections": { 3351 + "version": "6.1.2", 3352 + "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-6.1.2.tgz", 3353 + "integrity": "sha512-xJ+V4qdd46nk2RBdwIRrQm2iSTMHdlu/omhLz1pqRL3jZDrkqNBXimrisci2kIKpH2WBpA1YVagwuZEKBmF2Qw==", 3354 + "license": "BSD-3-Clause", 3355 + "dependencies": { 3356 + "d3-array": "3.2.4", 3357 + "vega-expression": "^6.1.0", 3358 + "vega-util": "^2.1.0" 3359 + } 3360 + }, 3361 + "node_modules/vega-statistics": { 3362 + "version": "2.0.0", 3363 + "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-2.0.0.tgz", 3364 + "integrity": "sha512-dGPfDXnBlgXbZF3oxtkb8JfeRXd5TYHx25Z/tIoaa9jWua4Vf/AoW2wwh8J1qmMy8J03/29aowkp1yk4DOPazQ==", 3365 + "license": "BSD-3-Clause", 3366 + "dependencies": { 3367 + "d3-array": "^3.2.4" 3368 + } 3369 + }, 3370 + "node_modules/vega-time": { 3371 + "version": "3.1.0", 3372 + "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-3.1.0.tgz", 3373 + "integrity": "sha512-G93mWzPwNa6UYQRkr8Ujur9uqxbBDjDT/WpXjbDY0yygdSkRT+zXF+Sb4gjhW0nPaqdiwkn0R6kZcSPMj1bMNA==", 3374 + "license": "BSD-3-Clause", 3375 + "dependencies": { 3376 + "d3-array": "^3.2.4", 3377 + "d3-time": "^3.1.0", 3378 + "vega-util": "^2.1.0" 3379 + } 3380 + }, 3381 + "node_modules/vega-transforms": { 3382 + "version": "5.1.0", 3383 + "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-5.1.0.tgz", 3384 + "integrity": "sha512-mj/sO2tSuzzpiXX8JSl4DDlhEmVwM/46MTAzTNQUQzJPMI/n4ChCjr/SdEbfEyzlD4DPm1bjohZGjLc010yuMg==", 3385 + "license": "BSD-3-Clause", 3386 + "dependencies": { 3387 + "d3-array": "^3.2.4", 3388 + "vega-dataflow": "^6.1.0", 3389 + "vega-statistics": "^2.0.0", 3390 + "vega-time": "^3.1.0", 3391 + "vega-util": "^2.1.0" 3392 + } 3393 + }, 3394 + "node_modules/vega-typings": { 3395 + "version": "2.1.0", 3396 + "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-2.1.0.tgz", 3397 + "integrity": "sha512-zdis4Fg4gv37yEvTTSZEVMNhp8hwyEl7GZ4X4HHddRVRKxWFsbyKvZx/YW5Z9Ox4sjxVA2qHzEbod4Fdx+SEJA==", 3398 + "license": "BSD-3-Clause", 3399 + "dependencies": { 3400 + "@types/geojson": "7946.0.16", 3401 + "vega-event-selector": "^4.0.0", 3402 + "vega-expression": "^6.1.0", 3403 + "vega-util": "^2.1.0" 3404 + } 3405 + }, 3406 + "node_modules/vega-util": { 3407 + "version": "2.1.0", 3408 + "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-2.1.0.tgz", 3409 + "integrity": "sha512-PGfp0m0QCufDmcxKJCWQy4Ov23FoF8DSXmoJwSezi3itQaa2hbxK0+xwsTMP2vy4PR16Pu25HMzgMwXVW1+33w==", 3410 + "license": "BSD-3-Clause" 3411 + }, 3412 + "node_modules/vega-view": { 3413 + "version": "6.1.0", 3414 + "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-6.1.0.tgz", 3415 + "integrity": "sha512-hmHDm/zC65lb23mb9Tr9Gx0wkxP0TMS31LpMPYxIZpvInxvUn7TYitkOtz1elr63k2YZrgmF7ztdGyQ4iCQ5fQ==", 3416 + "license": "BSD-3-Clause", 3417 + "dependencies": { 3418 + "d3-array": "^3.2.4", 3419 + "d3-timer": "^3.0.1", 3420 + "vega-dataflow": "^6.1.0", 3421 + "vega-format": "^2.1.0", 3422 + "vega-functions": "^6.1.0", 3423 + "vega-runtime": "^7.1.0", 3424 + "vega-scenegraph": "^5.1.0", 3425 + "vega-util": "^2.1.0" 3426 + } 3427 + }, 3428 + "node_modules/vega-view-transforms": { 3429 + "version": "5.1.0", 3430 + "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-5.1.0.tgz", 3431 + "integrity": "sha512-fpigh/xn/32t+An1ShoY3MLeGzNdlbAp2+HvFKzPpmpMTZqJEWkk/J/wHU7Swyc28Ta7W1z3fO+8dZkOYO5TWQ==", 3432 + "license": "BSD-3-Clause", 3433 + "dependencies": { 3434 + "vega-dataflow": "^6.1.0", 3435 + "vega-scenegraph": "^5.1.0", 3436 + "vega-util": "^2.1.0" 3437 + } 3438 + }, 3439 + "node_modules/vega-voronoi": { 3440 + "version": "5.1.0", 3441 + "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-5.1.0.tgz", 3442 + "integrity": "sha512-uKdsoR9x60mz7eYtVG+NhlkdQXeVdMr6jHNAHxs+W+i6kawkUp5S9jp1xf1FmW/uZvtO1eqinHQNwATcDRsiUg==", 3443 + "license": "BSD-3-Clause", 3444 + "dependencies": { 3445 + "d3-delaunay": "^6.0.4", 3446 + "vega-dataflow": "^6.1.0", 3447 + "vega-util": "^2.1.0" 3448 + } 3449 + }, 3450 + "node_modules/vega-wordcloud": { 3451 + "version": "5.1.0", 3452 + "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-5.1.0.tgz", 3453 + "integrity": "sha512-sSdNmT8y2D7xXhM2h76dKyaYn3PA4eV49WUUkfYfqHz/vpcu10GSAoFxLhQQTkbZXR+q5ZB63tFUow9W2IFo6g==", 3454 + "license": "BSD-3-Clause", 3455 + "dependencies": { 3456 + "vega-canvas": "^2.0.0", 3457 + "vega-dataflow": "^6.1.0", 3458 + "vega-scale": "^8.1.0", 3459 + "vega-statistics": "^2.0.0", 3460 + "vega-util": "^2.1.0" 3461 + } 3462 + }, 3463 + "node_modules/webidl-conversions": { 3464 + "version": "3.0.1", 3465 + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 3466 + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 3467 + "license": "BSD-2-Clause" 3468 + }, 3469 + "node_modules/whatwg-url": { 3470 + "version": "5.0.0", 3471 + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 3472 + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 3473 + "license": "MIT", 3474 + "dependencies": { 3475 + "tr46": "~0.0.3", 3476 + "webidl-conversions": "^3.0.0" 3477 + } 3478 + }, 3479 + "node_modules/wide-align": { 3480 + "version": "1.1.5", 3481 + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", 3482 + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", 3483 + "license": "ISC", 3484 + "dependencies": { 3485 + "string-width": "^1.0.2 || 2 || 3 || 4" 3486 + } 3487 + }, 3488 + "node_modules/wrap-ansi": { 3489 + "version": "9.0.2", 3490 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", 3491 + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", 3492 + "license": "MIT", 3493 + "dependencies": { 3494 + "ansi-styles": "^6.2.1", 3495 + "string-width": "^7.0.0", 3496 + "strip-ansi": "^7.1.0" 3497 + }, 3498 + "engines": { 3499 + "node": ">=18" 3500 + }, 3501 + "funding": { 3502 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 3503 + } 3504 + }, 3505 + "node_modules/wrap-ansi/node_modules/ansi-regex": { 3506 + "version": "6.2.2", 3507 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 3508 + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 3509 + "license": "MIT", 3510 + "engines": { 3511 + "node": ">=12" 3512 + }, 3513 + "funding": { 3514 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 3515 + } 3516 + }, 3517 + "node_modules/wrap-ansi/node_modules/emoji-regex": { 3518 + "version": "10.6.0", 3519 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", 3520 + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", 3521 + "license": "MIT" 3522 + }, 3523 + "node_modules/wrap-ansi/node_modules/string-width": { 3524 + "version": "7.2.0", 3525 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 3526 + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 3527 + "license": "MIT", 3528 + "dependencies": { 3529 + "emoji-regex": "^10.3.0", 3530 + "get-east-asian-width": "^1.0.0", 3531 + "strip-ansi": "^7.1.0" 3532 + }, 3533 + "engines": { 3534 + "node": ">=18" 3535 + }, 3536 + "funding": { 3537 + "url": "https://github.com/sponsors/sindresorhus" 3538 + } 3539 + }, 3540 + "node_modules/wrap-ansi/node_modules/strip-ansi": { 3541 + "version": "7.1.2", 3542 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 3543 + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 3544 + "license": "MIT", 3545 + "dependencies": { 3546 + "ansi-regex": "^6.0.1" 3547 + }, 3548 + "engines": { 3549 + "node": ">=12" 3550 + }, 3551 + "funding": { 3552 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 3553 + } 3554 + }, 3555 + "node_modules/wrappy": { 3556 + "version": "1.0.2", 3557 + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 3558 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 3559 + "license": "ISC" 3560 + }, 3561 + "node_modules/xtend": { 3562 + "version": "4.0.2", 3563 + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 3564 + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 3565 + "license": "MIT", 3566 + "engines": { 3567 + "node": ">=0.4" 3568 + } 3569 + }, 3570 + "node_modules/y18n": { 3571 + "version": "5.0.8", 3572 + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 3573 + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 3574 + "license": "ISC", 3575 + "engines": { 3576 + "node": ">=10" 3577 + } 3578 + }, 3579 + "node_modules/yallist": { 3580 + "version": "4.0.0", 3581 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 3582 + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 3583 + "license": "ISC" 3584 + }, 3585 + "node_modules/yargs": { 3586 + "version": "18.0.0", 3587 + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", 3588 + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", 3589 + "license": "MIT", 3590 + "dependencies": { 3591 + "cliui": "^9.0.1", 3592 + "escalade": "^3.1.1", 3593 + "get-caller-file": "^2.0.5", 3594 + "string-width": "^7.2.0", 3595 + "y18n": "^5.0.5", 3596 + "yargs-parser": "^22.0.0" 3597 + }, 3598 + "engines": { 3599 + "node": "^20.19.0 || ^22.12.0 || >=23" 3600 + } 3601 + }, 3602 + "node_modules/yargs-parser": { 3603 + "version": "22.0.0", 3604 + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", 3605 + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", 3606 + "license": "ISC", 3607 + "engines": { 3608 + "node": "^20.19.0 || ^22.12.0 || >=23" 3609 + } 3610 + }, 3611 + "node_modules/yargs/node_modules/ansi-regex": { 3612 + "version": "6.2.2", 3613 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 3614 + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 3615 + "license": "MIT", 3616 + "engines": { 3617 + "node": ">=12" 3618 + }, 3619 + "funding": { 3620 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 3621 + } 3622 + }, 3623 + "node_modules/yargs/node_modules/emoji-regex": { 3624 + "version": "10.6.0", 3625 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", 3626 + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", 3627 + "license": "MIT" 3628 + }, 3629 + "node_modules/yargs/node_modules/string-width": { 3630 + "version": "7.2.0", 3631 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 3632 + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 3633 + "license": "MIT", 3634 + "dependencies": { 3635 + "emoji-regex": "^10.3.0", 3636 + "get-east-asian-width": "^1.0.0", 3637 + "strip-ansi": "^7.1.0" 3638 + }, 3639 + "engines": { 3640 + "node": ">=18" 3641 + }, 3642 + "funding": { 3643 + "url": "https://github.com/sponsors/sindresorhus" 3644 + } 3645 + }, 3646 + "node_modules/yargs/node_modules/strip-ansi": { 3647 + "version": "7.1.2", 3648 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 3649 + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 3650 + "license": "MIT", 3651 + "dependencies": { 3652 + "ansi-regex": "^6.0.1" 3653 + }, 3654 + "engines": { 3655 + "node": ">=12" 3656 + }, 3657 + "funding": { 3658 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 3659 + } 3660 + } 3661 + } 3662 + }
+28
package.json
··· 1 + { 2 + "name": "localstress", 3 + "version": "1.0.0", 4 + "description": "Benchmark suite for comparing local datastore performance", 5 + "type": "module", 6 + "scripts": { 7 + "generate": "tsx src/generator/index.ts", 8 + "bench": "tsx src/runner.ts", 9 + "bench:store": "tsx src/runner.ts --store", 10 + "charts": "tsx src/regenerate-charts.ts" 11 + }, 12 + "devDependencies": { 13 + "@types/node": "^20.10.0", 14 + "tsx": "^4.6.0", 15 + "typescript": "^5.3.0" 16 + }, 17 + "dependencies": { 18 + "better-sqlite3": "^11.0.0", 19 + "canvas": "^2.11.0", 20 + "chart.js": "^4.4.0", 21 + "level": "^8.0.0", 22 + "levelgraph": "^4.0.0", 23 + "sharp": "^0.33.0", 24 + "tinybase": "^5.0.0", 25 + "vega": "^6.2.0", 26 + "vega-lite": "^6.4.1" 27 + } 28 + }
+101
src/generator/documents.ts
··· 1 + import { mkdir, writeFile } from 'fs/promises'; 2 + import { join } from 'path'; 3 + 4 + export interface DocumentInfo { 5 + id: string; 6 + filename: string; 7 + size: number; 8 + wordCount: number; 9 + } 10 + 11 + // Lorem ipsum style word generation 12 + const WORDS = [ 13 + 'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 14 + 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 15 + 'magna', 'aliqua', 'enim', 'ad', 'minim', 'veniam', 'quis', 'nostrud', 16 + 'exercitation', 'ullamco', 'laboris', 'nisi', 'aliquip', 'ex', 'ea', 'commodo', 17 + 'consequat', 'duis', 'aute', 'irure', 'in', 'reprehenderit', 'voluptate', 18 + 'velit', 'esse', 'cillum', 'fugiat', 'nulla', 'pariatur', 'excepteur', 'sint', 19 + 'occaecat', 'cupidatat', 'non', 'proident', 'sunt', 'culpa', 'qui', 'officia', 20 + 'deserunt', 'mollit', 'anim', 'id', 'est', 'laborum', 'data', 'system', 21 + 'process', 'function', 'value', 'result', 'query', 'database', 'storage', 22 + 'memory', 'cache', 'index', 'search', 'filter', 'sort', 'aggregate' 23 + ]; 24 + 25 + function randomWord(): string { 26 + return WORDS[Math.floor(Math.random() * WORDS.length)]; 27 + } 28 + 29 + function generateSentence(): string { 30 + const length = Math.floor(Math.random() * 15) + 5; 31 + const words: string[] = []; 32 + for (let i = 0; i < length; i++) { 33 + words.push(randomWord()); 34 + } 35 + words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1); 36 + return words.join(' ') + '.'; 37 + } 38 + 39 + function generateParagraph(): string { 40 + const sentences = Math.floor(Math.random() * 6) + 3; 41 + const parts: string[] = []; 42 + for (let i = 0; i < sentences; i++) { 43 + parts.push(generateSentence()); 44 + } 45 + return parts.join(' '); 46 + } 47 + 48 + function generateDocument(targetSize: number): { content: string; wordCount: number } { 49 + const paragraphs: string[] = []; 50 + let currentSize = 0; 51 + let wordCount = 0; 52 + 53 + while (currentSize < targetSize) { 54 + const para = generateParagraph(); 55 + paragraphs.push(para); 56 + currentSize += para.length; 57 + wordCount += para.split(' ').length; 58 + } 59 + 60 + return { 61 + content: paragraphs.join('\n\n'), 62 + wordCount 63 + }; 64 + } 65 + 66 + export async function generateDocuments( 67 + count: number, 68 + outputDir: string, 69 + onProgress?: (current: number, total: number) => void 70 + ): Promise<DocumentInfo[]> { 71 + const docsDir = join(outputDir, 'documents'); 72 + await mkdir(docsDir, { recursive: true }); 73 + 74 + const documents: DocumentInfo[] = []; 75 + // Target ~50KB average per document, with variation from 20KB to 80KB 76 + const baseSize = 50000; 77 + 78 + for (let i = 0; i < count; i++) { 79 + const id = String(i).padStart(4, '0'); 80 + const filename = `doc-${id}.txt`; 81 + 82 + // Vary document size 83 + const targetSize = baseSize + Math.floor((Math.random() - 0.5) * 60000); 84 + const { content, wordCount } = generateDocument(Math.max(targetSize, 20000)); 85 + 86 + await writeFile(join(docsDir, filename), content); 87 + 88 + documents.push({ 89 + id: `doc-${id}`, 90 + filename, 91 + size: content.length, 92 + wordCount 93 + }); 94 + 95 + if (onProgress) { 96 + onProgress(i + 1, count); 97 + } 98 + } 99 + 100 + return documents; 101 + }
+77
src/generator/images.ts
··· 1 + import sharp from 'sharp'; 2 + import { mkdir, writeFile } from 'fs/promises'; 3 + import { join } from 'path'; 4 + 5 + export interface ImageInfo { 6 + id: string; 7 + filename: string; 8 + width: number; 9 + height: number; 10 + size: number; 11 + } 12 + 13 + // Generate random colored noise image 14 + async function generateRandomImage(width: number, height: number): Promise<Buffer> { 15 + const channels = 3; 16 + const pixels = Buffer.alloc(width * height * channels); 17 + 18 + // Create random colored noise 19 + for (let i = 0; i < pixels.length; i++) { 20 + pixels[i] = Math.floor(Math.random() * 256); 21 + } 22 + 23 + return sharp(pixels, { 24 + raw: { 25 + width, 26 + height, 27 + channels 28 + } 29 + }) 30 + .png({ compressionLevel: 6 }) 31 + .toBuffer(); 32 + } 33 + 34 + // Generate images with varying sizes to hit ~900MB total for 1000 images 35 + // Sizes range from ~500KB to ~1.5MB per image 36 + function getImageDimensions(index: number): { width: number; height: number } { 37 + // Vary dimensions to get different file sizes 38 + const baseSize = 800; 39 + const variation = Math.sin(index * 0.1) * 400 + 400; 40 + const width = Math.floor(baseSize + variation); 41 + const height = Math.floor(baseSize + variation * 0.8); 42 + return { width, height }; 43 + } 44 + 45 + export async function generateImages( 46 + count: number, 47 + outputDir: string, 48 + onProgress?: (current: number, total: number) => void 49 + ): Promise<ImageInfo[]> { 50 + const imagesDir = join(outputDir, 'images'); 51 + await mkdir(imagesDir, { recursive: true }); 52 + 53 + const images: ImageInfo[] = []; 54 + 55 + for (let i = 0; i < count; i++) { 56 + const id = String(i).padStart(4, '0'); 57 + const filename = `img-${id}.png`; 58 + const { width, height } = getImageDimensions(i); 59 + 60 + const buffer = await generateRandomImage(width, height); 61 + await writeFile(join(imagesDir, filename), buffer); 62 + 63 + images.push({ 64 + id: `img-${id}`, 65 + filename, 66 + width, 67 + height, 68 + size: buffer.length 69 + }); 70 + 71 + if (onProgress) { 72 + onProgress(i + 1, count); 73 + } 74 + } 75 + 76 + return images; 77 + }
+76
src/generator/index.ts
··· 1 + import { mkdir, writeFile } from 'fs/promises'; 2 + import { join } from 'path'; 3 + import { generateUrls } from './urls.js'; 4 + import { generateImages } from './images.js'; 5 + import { generateDocuments } from './documents.js'; 6 + import { generateMetadata } from './metadata.js'; 7 + 8 + const OUTPUT_DIR = 'test-data'; 9 + 10 + const COUNTS = { 11 + urls: 10000, 12 + images: 1000, 13 + documents: 1000, 14 + metadata: 100000 15 + }; 16 + 17 + function formatBytes(bytes: number): string { 18 + if (bytes < 1024) return `${bytes} B`; 19 + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; 20 + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; 21 + } 22 + 23 + function progressBar(current: number, total: number, label: string): void { 24 + const width = 30; 25 + const percent = current / total; 26 + const filled = Math.floor(width * percent); 27 + const bar = '█'.repeat(filled) + '░'.repeat(width - filled); 28 + process.stdout.write(`\r${label}: [${bar}] ${current}/${total}`); 29 + if (current === total) console.log(); 30 + } 31 + 32 + async function main() { 33 + console.log('Generating test data...\n'); 34 + await mkdir(OUTPUT_DIR, { recursive: true }); 35 + 36 + // Generate URLs 37 + console.log(`Generating ${COUNTS.urls} URLs...`); 38 + const urls = generateUrls(COUNTS.urls); 39 + const urlsJson = JSON.stringify(urls, null, 2); 40 + await writeFile(join(OUTPUT_DIR, 'urls.json'), urlsJson); 41 + console.log(` URLs: ${formatBytes(urlsJson.length)}\n`); 42 + 43 + // Generate metadata 44 + console.log(`Generating ${COUNTS.metadata} metadata rows...`); 45 + const metadata = generateMetadata(COUNTS.metadata); 46 + const metadataJson = JSON.stringify(metadata, null, 2); 47 + await writeFile(join(OUTPUT_DIR, 'metadata.json'), metadataJson); 48 + console.log(` Metadata: ${formatBytes(metadataJson.length)}\n`); 49 + 50 + // Generate images (slow, show progress) 51 + console.log(`Generating ${COUNTS.images} images...`); 52 + const images = await generateImages(COUNTS.images, OUTPUT_DIR, (current, total) => { 53 + progressBar(current, total, 'Images'); 54 + }); 55 + const totalImageSize = images.reduce((sum, img) => sum + img.size, 0); 56 + await writeFile(join(OUTPUT_DIR, 'images.json'), JSON.stringify(images, null, 2)); 57 + console.log(` Total image size: ${formatBytes(totalImageSize)}\n`); 58 + 59 + // Generate documents (show progress) 60 + console.log(`Generating ${COUNTS.documents} documents...`); 61 + const documents = await generateDocuments(COUNTS.documents, OUTPUT_DIR, (current, total) => { 62 + progressBar(current, total, 'Documents'); 63 + }); 64 + const totalDocSize = documents.reduce((sum, doc) => sum + doc.size, 0); 65 + await writeFile(join(OUTPUT_DIR, 'documents.json'), JSON.stringify(documents, null, 2)); 66 + console.log(` Total document size: ${formatBytes(totalDocSize)}\n`); 67 + 68 + // Summary 69 + const totalSize = urlsJson.length + metadataJson.length + totalImageSize + totalDocSize; 70 + console.log('='.repeat(50)); 71 + console.log('Generation complete!'); 72 + console.log(`Total data size: ${formatBytes(totalSize)}`); 73 + console.log(`Output directory: ${OUTPUT_DIR}/`); 74 + } 75 + 76 + main().catch(console.error);
+57
src/generator/metadata.ts
··· 1 + export interface Metadata { 2 + id: string; 3 + key: string; 4 + value: string | number | boolean; 5 + category: string; 6 + timestamp: number; 7 + } 8 + 9 + const CATEGORIES = [ 10 + 'analytics', 'user', 'system', 'config', 'cache', 'session', 11 + 'event', 'metric', 'log', 'audit', 'preference', 'state' 12 + ]; 13 + 14 + const KEY_PREFIXES = [ 15 + 'page_view', 'click', 'session', 'user_action', 'api_call', 16 + 'error', 'warning', 'info', 'debug', 'performance', 'conversion' 17 + ]; 18 + 19 + function randomCategory(): string { 20 + return CATEGORIES[Math.floor(Math.random() * CATEGORIES.length)]; 21 + } 22 + 23 + function randomKey(index: number): string { 24 + const prefix = KEY_PREFIXES[Math.floor(Math.random() * KEY_PREFIXES.length)]; 25 + return `${prefix}_${index % 1000}`; 26 + } 27 + 28 + function randomValue(): string | number | boolean { 29 + const type = Math.floor(Math.random() * 3); 30 + switch (type) { 31 + case 0: 32 + return Math.floor(Math.random() * 10000); 33 + case 1: 34 + return Math.random() > 0.5; 35 + default: 36 + return `value_${Math.floor(Math.random() * 1000)}`; 37 + } 38 + } 39 + 40 + export function generateMetadata(count: number): Metadata[] { 41 + const metadata: Metadata[] = []; 42 + const baseTime = Date.now(); 43 + 44 + for (let i = 0; i < count; i++) { 45 + const id = String(i).padStart(6, '0'); 46 + 47 + metadata.push({ 48 + id: `meta-${id}`, 49 + key: randomKey(i), 50 + value: randomValue(), 51 + category: randomCategory(), 52 + timestamp: baseTime - (count - i) * 100 53 + }); 54 + } 55 + 56 + return metadata; 57 + }
+57
src/generator/urls.ts
··· 1 + export interface Url { 2 + id: string; 3 + url: string; 4 + title: string; 5 + createdAt: number; 6 + tags: string[]; 7 + } 8 + 9 + const DOMAINS = [ 10 + 'example.com', 'test.org', 'demo.net', 'sample.io', 'docs.dev', 11 + 'api.example.com', 'cdn.test.org', 'static.demo.net', 'app.sample.io' 12 + ]; 13 + 14 + const PATHS = [ 15 + '/articles', '/posts', '/docs', '/api', '/users', '/products', 16 + '/images', '/videos', '/files', '/data', '/reports', '/analytics' 17 + ]; 18 + 19 + const TAGS = [ 20 + 'tech', 'news', 'tutorial', 'reference', 'api', 'documentation', 21 + 'blog', 'product', 'marketing', 'support', 'faq', 'guide' 22 + ]; 23 + 24 + function randomChoice<T>(arr: T[]): T { 25 + return arr[Math.floor(Math.random() * arr.length)]; 26 + } 27 + 28 + function randomTags(): string[] { 29 + const count = Math.floor(Math.random() * 4) + 1; 30 + const tags: string[] = []; 31 + for (let i = 0; i < count; i++) { 32 + const tag = randomChoice(TAGS); 33 + if (!tags.includes(tag)) tags.push(tag); 34 + } 35 + return tags; 36 + } 37 + 38 + export function generateUrls(count: number): Url[] { 39 + const urls: Url[] = []; 40 + const baseTime = Date.now(); 41 + 42 + for (let i = 0; i < count; i++) { 43 + const domain = randomChoice(DOMAINS); 44 + const path = randomChoice(PATHS); 45 + const id = String(i).padStart(5, '0'); 46 + 47 + urls.push({ 48 + id: `url-${id}`, 49 + url: `https://${domain}${path}/${id}`, 50 + title: `Page ${id} - ${domain}`, 51 + createdAt: baseTime - (count - i) * 1000, 52 + tags: randomTags() 53 + }); 54 + } 55 + 56 + return urls; 57 + }
+198
src/harness/benchmark.ts
··· 1 + import { performance } from 'perf_hooks'; 2 + import type { DatastoreAdapter, BenchmarkResult, StoreBenchmarkResults, TestData } from './types.js'; 3 + 4 + async function time<T>(fn: () => Promise<T>): Promise<{ result: T; durationMs: number }> { 5 + const start = performance.now(); 6 + const result = await fn(); 7 + const durationMs = performance.now() - start; 8 + return { result, durationMs }; 9 + } 10 + 11 + async function timeWithCatch<T>( 12 + fn: () => Promise<T>, 13 + defaultResult: T 14 + ): Promise<{ result: T; durationMs: number; failed: boolean; error?: string }> { 15 + const start = performance.now(); 16 + try { 17 + const result = await fn(); 18 + return { result, durationMs: performance.now() - start, failed: false }; 19 + } catch (err) { 20 + return { 21 + result: defaultResult, 22 + durationMs: performance.now() - start, 23 + failed: true, 24 + error: err instanceof Error ? err.message : String(err) 25 + }; 26 + } 27 + } 28 + 29 + function randomSample<T>(arr: T[], count: number): T[] { 30 + const shuffled = [...arr].sort(() => Math.random() - 0.5); 31 + return shuffled.slice(0, count); 32 + } 33 + 34 + function formatDuration(ms: number): string { 35 + if (ms < 1000) return `${ms.toFixed(0)}ms`; 36 + if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`; 37 + return `${(ms / 60000).toFixed(2)}m`; 38 + } 39 + 40 + export async function runBenchmarks( 41 + store: DatastoreAdapter, 42 + data: TestData, 43 + onProgress?: (phase: string, durationMs?: number, failed?: boolean, error?: string) => void 44 + ): Promise<StoreBenchmarkResults> { 45 + const results: StoreBenchmarkResults = { 46 + storeName: store.name, 47 + init: {} as BenchmarkResult, 48 + writes: {} as StoreBenchmarkResults['writes'], 49 + reads: {} as StoreBenchmarkResults['reads'], 50 + disk: { totalBytes: 0 } 51 + }; 52 + 53 + // Initialize store 54 + onProgress?.('Initializing store...'); 55 + const initResult = await time(() => store.init()); 56 + results.init = { 57 + name: 'Store initialization', 58 + durationMs: initResult.durationMs 59 + }; 60 + onProgress?.('Initialized store', initResult.durationMs); 61 + 62 + // === WRITE BENCHMARKS === 63 + 64 + // Write all URLs 65 + onProgress?.('Writing URLs...'); 66 + const urlWrite = await timeWithCatch(() => store.addUrls(data.urls), undefined); 67 + results.writes.allUrls = { 68 + name: 'Write all URLs', 69 + durationMs: urlWrite.durationMs, 70 + itemCount: data.urls.length, 71 + failed: urlWrite.failed, 72 + error: urlWrite.error 73 + }; 74 + onProgress?.('Wrote URLs', urlWrite.durationMs, urlWrite.failed, urlWrite.error); 75 + 76 + // Write all metadata 77 + onProgress?.('Writing metadata...'); 78 + const metaWrite = await timeWithCatch(() => store.addMetadata(data.metadata), undefined); 79 + results.writes.allMetadata = { 80 + name: 'Write all metadata', 81 + durationMs: metaWrite.durationMs, 82 + itemCount: data.metadata.length, 83 + failed: metaWrite.failed, 84 + error: metaWrite.error 85 + }; 86 + onProgress?.('Wrote metadata', metaWrite.durationMs, metaWrite.failed, metaWrite.error); 87 + 88 + // Write all images 89 + onProgress?.('Writing images...'); 90 + let totalImageBytes = 0; 91 + let imagesFailed = false; 92 + let imagesError: string | undefined; 93 + const imageWriteStart = performance.now(); 94 + try { 95 + for (const img of data.images) { 96 + await store.addImage(img.id, img.data); 97 + totalImageBytes += img.data.length; 98 + } 99 + } catch (err) { 100 + imagesFailed = true; 101 + imagesError = err instanceof Error ? err.message : String(err); 102 + } 103 + const imageWriteDuration = performance.now() - imageWriteStart; 104 + results.writes.allImages = { 105 + name: 'Write all images', 106 + durationMs: imageWriteDuration, 107 + itemCount: data.images.length, 108 + bytesProcessed: totalImageBytes, 109 + failed: imagesFailed, 110 + error: imagesError 111 + }; 112 + onProgress?.('Wrote images', imageWriteDuration, imagesFailed, imagesError); 113 + 114 + // Write all documents 115 + onProgress?.('Writing documents...'); 116 + let totalDocBytes = 0; 117 + let docsFailed = false; 118 + let docsError: string | undefined; 119 + const docWriteStart = performance.now(); 120 + try { 121 + for (const doc of data.documents) { 122 + await store.addDocument(doc.id, doc.content); 123 + totalDocBytes += doc.content.length; 124 + } 125 + } catch (err) { 126 + docsFailed = true; 127 + docsError = err instanceof Error ? err.message : String(err); 128 + } 129 + const docWriteDuration = performance.now() - docWriteStart; 130 + results.writes.allDocuments = { 131 + name: 'Write all documents', 132 + durationMs: docWriteDuration, 133 + itemCount: data.documents.length, 134 + bytesProcessed: totalDocBytes, 135 + failed: docsFailed, 136 + error: docsError 137 + }; 138 + onProgress?.('Wrote documents', docWriteDuration, docsFailed, docsError); 139 + 140 + // === READ BENCHMARKS === 141 + 142 + // Read recent 100 URLs 143 + onProgress?.('Reading recent URLs...'); 144 + const urlRead = await timeWithCatch(() => store.getRecentUrls(100), []); 145 + results.reads.recentUrls = { 146 + name: 'Read recent 100 URLs', 147 + durationMs: urlRead.durationMs, 148 + itemCount: urlRead.result.length, 149 + failed: urlRead.failed, 150 + error: urlRead.error 151 + }; 152 + onProgress?.('Read URLs', urlRead.durationMs, urlRead.failed, urlRead.error); 153 + 154 + // Read 10 random images 155 + onProgress?.('Reading random images...'); 156 + const randomImageIds = randomSample(data.images, 10).map(img => img.id); 157 + const imageRead = await timeWithCatch(() => store.getImages(randomImageIds), new Map()); 158 + let readImageBytes = 0; 159 + imageRead.result.forEach(buf => { readImageBytes += buf.length; }); 160 + results.reads.randomImages = { 161 + name: 'Read 10 random images', 162 + durationMs: imageRead.durationMs, 163 + itemCount: imageRead.result.size, 164 + bytesProcessed: readImageBytes, 165 + failed: imageRead.failed, 166 + error: imageRead.error 167 + }; 168 + onProgress?.('Read images', imageRead.durationMs, imageRead.failed, imageRead.error); 169 + 170 + // Read 1000 random documents 171 + onProgress?.('Reading random documents...'); 172 + const randomDocIds = randomSample(data.documents, 1000).map(doc => doc.id); 173 + const docRead = await timeWithCatch(() => store.getDocuments(randomDocIds), new Map()); 174 + let readDocBytes = 0; 175 + docRead.result.forEach(content => { readDocBytes += content.length; }); 176 + results.reads.randomDocuments = { 177 + name: 'Read 1000 random documents', 178 + durationMs: docRead.durationMs, 179 + itemCount: docRead.result.size, 180 + bytesProcessed: readDocBytes, 181 + failed: docRead.failed, 182 + error: docRead.error 183 + }; 184 + onProgress?.('Read documents', docRead.durationMs, docRead.failed, docRead.error); 185 + 186 + // === DISK USAGE === 187 + onProgress?.('Measuring disk usage...'); 188 + const diskResult = await time(() => store.getDiskUsage()); 189 + results.disk.totalBytes = diskResult.result; 190 + onProgress?.('Measured disk usage', diskResult.durationMs); 191 + 192 + // Cleanup 193 + onProgress?.('Cleaning up...'); 194 + const cleanupResult = await time(() => store.cleanup()); 195 + onProgress?.('Cleaned up', cleanupResult.durationMs); 196 + 197 + return results; 198 + }
+571
src/harness/chart.ts
··· 1 + import * as vega from 'vega'; 2 + import * as vegaLite from 'vega-lite'; 3 + import sharp from 'sharp'; 4 + import { writeFile, mkdir } from 'fs/promises'; 5 + import { join } from 'path'; 6 + import type { StoreBenchmarkResults, BenchmarkResult } from './types.js'; 7 + 8 + const WIDTH = 700; 9 + const HEIGHT = 400; 10 + 11 + const COLORS = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6']; 12 + 13 + function formatBytes(bytes: number): string { 14 + if (bytes < 1024) return `${bytes} B`; 15 + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; 16 + if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; 17 + return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; 18 + } 19 + 20 + function formatResultForHtml(b: BenchmarkResult, formatter: (ms: number) => string): string { 21 + if (b.failed) return `<span class="failed">FAILED</span>`; 22 + return formatter(b.durationMs); 23 + } 24 + 25 + async function renderVegaLiteChart(spec: any): Promise<Buffer> { 26 + const vegaSpec = vegaLite.compile(spec).spec; 27 + const view = new vega.View(vega.parse(vegaSpec), { renderer: 'none' }); 28 + const svg = await view.toSVG(); 29 + // Convert SVG to PNG using sharp 30 + return await sharp(Buffer.from(svg)).png().toBuffer(); 31 + } 32 + 33 + export async function generateCharts( 34 + results: StoreBenchmarkResults[], 35 + outputDir: string 36 + ): Promise<string[]> { 37 + await mkdir(outputDir, { recursive: true }); 38 + 39 + const generatedFiles: string[] = []; 40 + 41 + const storeNames = results.map(r => r.storeName); 42 + 43 + // Time-based metrics data 44 + const timeData: any[] = []; 45 + const timeMetrics = [ 46 + { label: 'Init', get: (r: StoreBenchmarkResults) => r.init }, 47 + { label: 'Write URLs', get: (r: StoreBenchmarkResults) => r.writes.allUrls }, 48 + { label: 'Write Metadata', get: (r: StoreBenchmarkResults) => r.writes.allMetadata }, 49 + { label: 'Write Images', get: (r: StoreBenchmarkResults) => r.writes.allImages }, 50 + { label: 'Write Docs', get: (r: StoreBenchmarkResults) => r.writes.allDocuments }, 51 + { label: 'Read URLs', get: (r: StoreBenchmarkResults) => r.reads.recentUrls }, 52 + { label: 'Read Images', get: (r: StoreBenchmarkResults) => r.reads.randomImages }, 53 + { label: 'Read Docs', get: (r: StoreBenchmarkResults) => r.reads.randomDocuments }, 54 + ]; 55 + 56 + for (const metric of timeMetrics) { 57 + for (const r of results) { 58 + const b = metric.get(r); 59 + if (!b.failed) { 60 + timeData.push({ 61 + metric: metric.label, 62 + store: r.storeName, 63 + value: b.durationMs 64 + }); 65 + } 66 + } 67 + } 68 + 69 + // Generate timing comparison chart 70 + const timeSpec = { 71 + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', 72 + width: WIDTH, 73 + height: HEIGHT, 74 + title: { text: 'Benchmark Timing Comparison', fontSize: 18 }, 75 + data: { values: timeData }, 76 + mark: 'bar', 77 + encoding: { 78 + x: { 79 + field: 'metric', 80 + type: 'nominal', 81 + title: null, 82 + axis: { labelAngle: -45, labelFontSize: 11 }, 83 + sort: timeMetrics.map(m => m.label) 84 + }, 85 + y: { 86 + field: 'value', 87 + type: 'quantitative', 88 + title: 'Duration (ms)', 89 + axis: { titleFontSize: 12, labelFontSize: 11 } 90 + }, 91 + xOffset: { field: 'store', type: 'nominal' }, 92 + color: { 93 + field: 'store', 94 + type: 'nominal', 95 + title: 'Store', 96 + scale: { range: COLORS.slice(0, storeNames.length) }, 97 + legend: { titleFontSize: 12, labelFontSize: 11 } 98 + } 99 + }, 100 + config: { 101 + background: 'white', 102 + view: { stroke: null } 103 + } 104 + }; 105 + 106 + const timeChartBuffer = await renderVegaLiteChart(timeSpec); 107 + const timeChartPath = join(outputDir, 'timing-comparison.png'); 108 + await writeFile(timeChartPath, timeChartBuffer); 109 + generatedFiles.push(timeChartPath); 110 + 111 + // Generate disk usage comparison chart 112 + const diskData = results.map(r => ({ 113 + store: r.storeName, 114 + value: r.disk.totalBytes / (1024 * 1024) 115 + })); 116 + 117 + const diskSpec = { 118 + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', 119 + width: WIDTH, 120 + height: HEIGHT, 121 + title: { text: 'Disk Usage Comparison', fontSize: 18 }, 122 + data: { values: diskData }, 123 + mark: 'bar', 124 + encoding: { 125 + x: { 126 + field: 'store', 127 + type: 'nominal', 128 + title: null, 129 + axis: { labelFontSize: 12 } 130 + }, 131 + y: { 132 + field: 'value', 133 + type: 'quantitative', 134 + title: 'Size (MB)', 135 + axis: { titleFontSize: 12, labelFontSize: 11 } 136 + }, 137 + color: { 138 + field: 'store', 139 + type: 'nominal', 140 + scale: { range: COLORS.slice(0, storeNames.length) }, 141 + legend: null 142 + } 143 + }, 144 + config: { 145 + background: 'white', 146 + view: { stroke: null } 147 + } 148 + }; 149 + 150 + const diskChartBuffer = await renderVegaLiteChart(diskSpec); 151 + const diskChartPath = join(outputDir, 'disk-comparison.png'); 152 + await writeFile(diskChartPath, diskChartBuffer); 153 + generatedFiles.push(diskChartPath); 154 + 155 + // Generate write performance chart 156 + const writeData: any[] = []; 157 + const writeMetrics = [ 158 + { label: 'URLs', get: (r: StoreBenchmarkResults) => r.writes.allUrls }, 159 + { label: 'Metadata', get: (r: StoreBenchmarkResults) => r.writes.allMetadata }, 160 + { label: 'Images', get: (r: StoreBenchmarkResults) => r.writes.allImages }, 161 + { label: 'Documents', get: (r: StoreBenchmarkResults) => r.writes.allDocuments }, 162 + ]; 163 + 164 + for (const metric of writeMetrics) { 165 + for (const r of results) { 166 + const b = metric.get(r); 167 + if (!b.failed) { 168 + writeData.push({ 169 + metric: metric.label, 170 + store: r.storeName, 171 + value: b.durationMs / 1000 172 + }); 173 + } 174 + } 175 + } 176 + 177 + const writeSpec = { 178 + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', 179 + width: WIDTH, 180 + height: HEIGHT, 181 + title: { text: 'Write Performance Comparison', fontSize: 18 }, 182 + data: { values: writeData }, 183 + mark: 'bar', 184 + encoding: { 185 + x: { 186 + field: 'metric', 187 + type: 'nominal', 188 + title: null, 189 + axis: { labelFontSize: 12 }, 190 + sort: writeMetrics.map(m => m.label) 191 + }, 192 + y: { 193 + field: 'value', 194 + type: 'quantitative', 195 + title: 'Duration (seconds)', 196 + axis: { titleFontSize: 12, labelFontSize: 11 } 197 + }, 198 + xOffset: { field: 'store', type: 'nominal' }, 199 + color: { 200 + field: 'store', 201 + type: 'nominal', 202 + title: 'Store', 203 + scale: { range: COLORS.slice(0, storeNames.length) }, 204 + legend: { titleFontSize: 12, labelFontSize: 11 } 205 + } 206 + }, 207 + config: { 208 + background: 'white', 209 + view: { stroke: null } 210 + } 211 + }; 212 + 213 + const writeChartBuffer = await renderVegaLiteChart(writeSpec); 214 + const writeChartPath = join(outputDir, 'write-comparison.png'); 215 + await writeFile(writeChartPath, writeChartBuffer); 216 + generatedFiles.push(writeChartPath); 217 + 218 + // Generate read performance chart 219 + const readData: any[] = []; 220 + const readMetrics = [ 221 + { label: 'URLs (100)', get: (r: StoreBenchmarkResults) => r.reads.recentUrls }, 222 + { label: 'Images (10)', get: (r: StoreBenchmarkResults) => r.reads.randomImages }, 223 + { label: 'Docs (1000)', get: (r: StoreBenchmarkResults) => r.reads.randomDocuments }, 224 + ]; 225 + 226 + for (const metric of readMetrics) { 227 + for (const r of results) { 228 + const b = metric.get(r); 229 + if (!b.failed) { 230 + readData.push({ 231 + metric: metric.label, 232 + store: r.storeName, 233 + value: b.durationMs 234 + }); 235 + } 236 + } 237 + } 238 + 239 + const readSpec = { 240 + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', 241 + width: WIDTH, 242 + height: HEIGHT, 243 + title: { text: 'Read Performance Comparison', fontSize: 18 }, 244 + data: { values: readData }, 245 + mark: 'bar', 246 + encoding: { 247 + x: { 248 + field: 'metric', 249 + type: 'nominal', 250 + title: null, 251 + axis: { labelFontSize: 12 }, 252 + sort: readMetrics.map(m => m.label) 253 + }, 254 + y: { 255 + field: 'value', 256 + type: 'quantitative', 257 + title: 'Duration (ms)', 258 + axis: { titleFontSize: 12, labelFontSize: 11 } 259 + }, 260 + xOffset: { field: 'store', type: 'nominal' }, 261 + color: { 262 + field: 'store', 263 + type: 'nominal', 264 + title: 'Store', 265 + scale: { range: COLORS.slice(0, storeNames.length) }, 266 + legend: { titleFontSize: 12, labelFontSize: 11 } 267 + } 268 + }, 269 + config: { 270 + background: 'white', 271 + view: { stroke: null } 272 + } 273 + }; 274 + 275 + const readChartBuffer = await renderVegaLiteChart(readSpec); 276 + const readChartPath = join(outputDir, 'read-comparison.png'); 277 + await writeFile(readChartPath, readChartBuffer); 278 + generatedFiles.push(readChartPath); 279 + 280 + // Generate HTML report 281 + const htmlPath = join(outputDir, 'report.html'); 282 + const html = generateHtmlReport(results, generatedFiles); 283 + await writeFile(htmlPath, html); 284 + generatedFiles.push(htmlPath); 285 + 286 + return generatedFiles; 287 + } 288 + 289 + // Get metric value for comparison (returns null for failed) 290 + function getMetricValue(b: BenchmarkResult): number | null { 291 + return b.failed ? null : b.durationMs; 292 + } 293 + 294 + // Find best/worst indices for a metric (lower is better) 295 + function findBestWorst(values: (number | null)[]): { best: number; worst: number } { 296 + let best = -1, worst = -1; 297 + let bestVal = Infinity, worstVal = -Infinity; 298 + 299 + for (let i = 0; i < values.length; i++) { 300 + const v = values[i]; 301 + if (v === null) continue; 302 + if (v < bestVal) { bestVal = v; best = i; } 303 + if (v > worstVal) { worstVal = v; worst = i; } 304 + } 305 + return { best, worst }; 306 + } 307 + 308 + function generateHtmlReport( 309 + results: StoreBenchmarkResults[], 310 + chartFiles: string[] 311 + ): string { 312 + const storeNames = results.map(r => r.storeName).join(' vs '); 313 + const chartImages = chartFiles 314 + .filter(f => f.endsWith('.png')) 315 + .map(f => f.split('/').pop()) 316 + .map(filename => ` 317 + <div class="chart"> 318 + <img src="${filename}" alt="${filename}"> 319 + </div> 320 + `) 321 + .join('\n'); 322 + 323 + // Define metrics for the table 324 + const metrics = [ 325 + { label: 'Init', get: (r: StoreBenchmarkResults) => r.init, format: (ms: number) => `${ms.toFixed(0)}ms` }, 326 + { label: 'Write URLs', get: (r: StoreBenchmarkResults) => r.writes.allUrls, format: (ms: number) => `${(ms / 1000).toFixed(2)}s` }, 327 + { label: 'Write Meta', get: (r: StoreBenchmarkResults) => r.writes.allMetadata, format: (ms: number) => `${(ms / 1000).toFixed(2)}s` }, 328 + { label: 'Write Imgs', get: (r: StoreBenchmarkResults) => r.writes.allImages, format: (ms: number) => `${(ms / 1000).toFixed(2)}s` }, 329 + { label: 'Write Docs', get: (r: StoreBenchmarkResults) => r.writes.allDocuments, format: (ms: number) => `${(ms / 1000).toFixed(2)}s` }, 330 + { label: 'Read URLs', get: (r: StoreBenchmarkResults) => r.reads.recentUrls, format: (ms: number) => `${ms.toFixed(0)}ms` }, 331 + { label: 'Read Imgs', get: (r: StoreBenchmarkResults) => r.reads.randomImages, format: (ms: number) => `${ms.toFixed(0)}ms` }, 332 + { label: 'Read Docs', get: (r: StoreBenchmarkResults) => r.reads.randomDocuments, format: (ms: number) => `${ms.toFixed(0)}ms` }, 333 + ]; 334 + 335 + // Find best/worst for each metric 336 + const rankings = metrics.map(m => { 337 + const values = results.map(r => getMetricValue(m.get(r))); 338 + return findBestWorst(values); 339 + }); 340 + 341 + // Find best/worst for disk (separate since it's bytes not BenchmarkResult) 342 + const diskValues = results.map(r => r.disk.totalBytes); 343 + const diskRanking = findBestWorst(diskValues); 344 + 345 + // Build table rows with color coding 346 + const tableRows = results.map((r, storeIdx) => { 347 + const cells = metrics.map((m, metricIdx) => { 348 + const b = m.get(r); 349 + const rank = rankings[metricIdx]; 350 + let cssClass = ''; 351 + if (storeIdx === rank.best) cssClass = 'best'; 352 + else if (storeIdx === rank.worst && rank.best !== rank.worst) cssClass = 'worst'; 353 + 354 + if (b.failed) return `<td class="failed">FAILED</td>`; 355 + return `<td class="${cssClass}">${m.format(b.durationMs)}</td>`; 356 + }); 357 + 358 + // Disk cell 359 + let diskClass = ''; 360 + if (storeIdx === diskRanking.best) diskClass = 'best'; 361 + else if (storeIdx === diskRanking.worst && diskRanking.best !== diskRanking.worst) diskClass = 'worst'; 362 + 363 + return ` 364 + <tr> 365 + <td><strong>${r.storeName}</strong></td> 366 + ${cells.join('\n ')} 367 + <td class="${diskClass}">${formatBytes(r.disk.totalBytes)}</td> 368 + </tr>`; 369 + }).join('\n'); 370 + 371 + return `<!DOCTYPE html> 372 + <html lang="en"> 373 + <head> 374 + <meta charset="UTF-8"> 375 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 376 + <title>Benchmark Results - ${storeNames}</title> 377 + <style> 378 + * { 379 + box-sizing: border-box; 380 + } 381 + body { 382 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; 383 + max-width: 1400px; 384 + margin: 0 auto; 385 + padding: 20px; 386 + background: #f5f5f5; 387 + color: #333; 388 + } 389 + h1 { 390 + text-align: center; 391 + color: #2c3e50; 392 + margin-bottom: 10px; 393 + } 394 + .timestamp { 395 + text-align: center; 396 + color: #7f8c8d; 397 + margin-bottom: 30px; 398 + } 399 + .summary { 400 + background: white; 401 + border-radius: 8px; 402 + padding: 20px; 403 + margin-bottom: 30px; 404 + box-shadow: 0 2px 4px rgba(0,0,0,0.1); 405 + overflow-x: auto; 406 + } 407 + table { 408 + width: 100%; 409 + border-collapse: collapse; 410 + font-size: 14px; 411 + } 412 + th, td { 413 + padding: 12px 8px; 414 + text-align: right; 415 + border-bottom: 1px solid #eee; 416 + } 417 + th { 418 + background: #f8f9fa; 419 + font-weight: 600; 420 + color: #2c3e50; 421 + } 422 + td:first-child, th:first-child { 423 + text-align: left; 424 + } 425 + tr:hover { 426 + background: #f8f9fa; 427 + } 428 + .failed { 429 + color: #e74c3c; 430 + font-weight: 600; 431 + } 432 + .best { 433 + background: #d5f4e6; 434 + color: #1e7e34; 435 + font-weight: 600; 436 + } 437 + .worst { 438 + background: #fde2e2; 439 + color: #c0392b; 440 + } 441 + .dataset-info { 442 + background: white; 443 + border-radius: 8px; 444 + padding: 20px; 445 + margin-bottom: 30px; 446 + box-shadow: 0 2px 4px rgba(0,0,0,0.1); 447 + } 448 + .dataset-info h2 { 449 + margin-top: 0; 450 + } 451 + .dataset-info ul { 452 + margin: 0; 453 + padding-left: 20px; 454 + } 455 + .dataset-info li { 456 + margin-bottom: 8px; 457 + } 458 + .test-description { 459 + display: grid; 460 + grid-template-columns: 1fr 1fr; 461 + gap: 20px; 462 + margin-top: 15px; 463 + } 464 + .test-description h3 { 465 + margin: 0 0 10px 0; 466 + color: #2c3e50; 467 + font-size: 14px; 468 + } 469 + .test-description ul { 470 + margin: 0; 471 + font-size: 13px; 472 + } 473 + @media (max-width: 600px) { 474 + .test-description { 475 + grid-template-columns: 1fr; 476 + } 477 + } 478 + .charts { 479 + display: grid; 480 + grid-template-columns: 1fr; 481 + gap: 20px; 482 + } 483 + .chart { 484 + background: white; 485 + border-radius: 8px; 486 + padding: 20px; 487 + box-shadow: 0 2px 4px rgba(0,0,0,0.1); 488 + text-align: center; 489 + } 490 + .chart img { 491 + max-width: 100%; 492 + height: auto; 493 + } 494 + @media (min-width: 1200px) { 495 + .charts { 496 + grid-template-columns: 1fr 1fr; 497 + } 498 + } 499 + </style> 500 + </head> 501 + <body> 502 + <h1>Datastore Benchmark Results</h1> 503 + <p class="timestamp">${storeNames} - ${new Date().toLocaleString()}</p> 504 + 505 + <div class="dataset-info"> 506 + <h2>About This Benchmark</h2> 507 + <p>This benchmark compares local datastore performance using a ~1GB test dataset. Each test is run <strong>10 times</strong> per store, with the highest and lowest scores discarded. The <strong>median</strong> of the remaining 8 runs is reported.</p> 508 + <h3 style="margin-top: 20px; margin-bottom: 10px;">Test Dataset</h3> 509 + <ul> 510 + <li><strong>10,000 URLs</strong> - Bookmarks with title, URL, tags, and timestamp</li> 511 + <li><strong>100,000 metadata rows</strong> - Key-value pairs with categories</li> 512 + <li><strong>1,000 images</strong> - Random PNG images (~1MB each)</li> 513 + <li><strong>1,000 documents</strong> - Text documents (~10KB each)</li> 514 + </ul> 515 + <div class="test-description"> 516 + <div> 517 + <h3>Write Tests</h3> 518 + <ul> 519 + <li><strong>Init</strong> - Initialize/create the datastore</li> 520 + <li><strong>Write URLs</strong> - Insert all 10,000 URL records</li> 521 + <li><strong>Write Metadata</strong> - Insert all 100,000 metadata rows</li> 522 + <li><strong>Write Images</strong> - Store all 1,000 image blobs</li> 523 + <li><strong>Write Documents</strong> - Store all 1,000 text documents</li> 524 + </ul> 525 + </div> 526 + <div> 527 + <h3>Read Tests</h3> 528 + <ul> 529 + <li><strong>Read URLs</strong> - Fetch 100 most recent URLs (sorted by date)</li> 530 + <li><strong>Read Images</strong> - Retrieve 10 random images by ID</li> 531 + <li><strong>Read Documents</strong> - Retrieve 1,000 random documents by ID</li> 532 + <li><strong>Disk Usage</strong> - Total storage size after all writes</li> 533 + </ul> 534 + </div> 535 + </div> 536 + <p style="margin-top: 15px; font-size: 13px; color: #7f8c8d;"> 537 + <strong>Note:</strong> Lower values are better for all metrics. 538 + <span class="best" style="padding: 2px 6px; border-radius: 3px;">Green</span> = best, 539 + <span class="worst" style="padding: 2px 6px; border-radius: 3px;">Red</span> = worst. 540 + </p> 541 + </div> 542 + 543 + <div class="summary"> 544 + <h2>Summary</h2> 545 + <table> 546 + <thead> 547 + <tr> 548 + <th>Store</th> 549 + <th>Init</th> 550 + <th>Write URLs</th> 551 + <th>Write Meta</th> 552 + <th>Write Imgs</th> 553 + <th>Write Docs</th> 554 + <th>Read URLs</th> 555 + <th>Read Imgs</th> 556 + <th>Read Docs</th> 557 + <th>Disk</th> 558 + </tr> 559 + </thead> 560 + <tbody> 561 + ${tableRows} 562 + </tbody> 563 + </table> 564 + </div> 565 + 566 + <div class="charts"> 567 + ${chartImages} 568 + </div> 569 + </body> 570 + </html>`; 571 + }
+129
src/harness/reporter.ts
··· 1 + import { writeFile, mkdir } from 'fs/promises'; 2 + import { join } from 'path'; 3 + import type { StoreBenchmarkResults, BenchmarkResult } from './types.js'; 4 + 5 + function formatDuration(ms: number): string { 6 + if (ms < 1000) return `${ms.toFixed(0)}ms`; 7 + if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`; 8 + return `${(ms / 60000).toFixed(2)}m`; 9 + } 10 + 11 + function formatBytes(bytes: number): string { 12 + if (bytes < 1024) return `${bytes} B`; 13 + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; 14 + if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; 15 + return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`; 16 + } 17 + 18 + function formatThroughput(bytes: number, ms: number): string { 19 + const bytesPerSec = (bytes / ms) * 1000; 20 + return `${formatBytes(bytesPerSec)}/s`; 21 + } 22 + 23 + function padRight(str: string, len: number): string { 24 + return str.length >= len ? str : str + ' '.repeat(len - str.length); 25 + } 26 + 27 + function padLeft(str: string, len: number): string { 28 + return str.length >= len ? str : ' '.repeat(len - str.length) + str; 29 + } 30 + 31 + function printBenchmark(b: BenchmarkResult, indent = ''): void { 32 + if (b.failed) { 33 + console.log(`${indent}${padRight(b.name, 30)} ${padLeft('FAILED', 10)} ${b.error || 'Unknown error'}`); 34 + return; 35 + } 36 + let line = `${indent}${padRight(b.name, 30)} ${padLeft(formatDuration(b.durationMs), 10)}`; 37 + if (b.itemCount !== undefined) { 38 + line += ` (${b.itemCount} items)`; 39 + } 40 + if (b.bytesProcessed !== undefined) { 41 + line += ` ${formatBytes(b.bytesProcessed)}`; 42 + line += ` @ ${formatThroughput(b.bytesProcessed, b.durationMs)}`; 43 + } 44 + console.log(line); 45 + } 46 + 47 + function formatResultOrFailed(b: BenchmarkResult, formatter: (b: BenchmarkResult) => string): string { 48 + if (b.failed) return 'FAILED'; 49 + return formatter(b); 50 + } 51 + 52 + export function printResults(results: StoreBenchmarkResults[]): void { 53 + console.log('\n' + '='.repeat(80)); 54 + console.log('BENCHMARK RESULTS'); 55 + console.log('='.repeat(80)); 56 + 57 + for (const r of results) { 58 + console.log(`\n${'─'.repeat(80)}`); 59 + console.log(`Store: ${r.storeName}`); 60 + console.log('─'.repeat(80)); 61 + 62 + console.log('\nINIT:'); 63 + printBenchmark(r.init, ' '); 64 + 65 + console.log('\nWRITES:'); 66 + printBenchmark(r.writes.allUrls, ' '); 67 + printBenchmark(r.writes.allMetadata, ' '); 68 + printBenchmark(r.writes.allImages, ' '); 69 + printBenchmark(r.writes.allDocuments, ' '); 70 + 71 + console.log('\nREADS:'); 72 + printBenchmark(r.reads.recentUrls, ' '); 73 + printBenchmark(r.reads.randomImages, ' '); 74 + printBenchmark(r.reads.randomDocuments, ' '); 75 + 76 + console.log('\nDISK:'); 77 + console.log(` Total storage: ${formatBytes(r.disk.totalBytes)}`); 78 + } 79 + 80 + // Comparison table if multiple stores 81 + if (results.length > 1) { 82 + console.log('\n' + '='.repeat(80)); 83 + console.log('COMPARISON'); 84 + console.log('='.repeat(80)); 85 + 86 + const headers = ['Metric', ...results.map(r => r.storeName)]; 87 + const colWidth = 15; 88 + 89 + console.log('\n' + headers.map(h => padRight(h, colWidth)).join(' │ ')); 90 + console.log('─'.repeat(colWidth * headers.length + (headers.length - 1) * 3)); 91 + 92 + const metrics = [ 93 + { name: 'Init', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.init, b => formatDuration(b.durationMs)) }, 94 + { name: 'Write URLs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allUrls, b => formatDuration(b.durationMs)) }, 95 + { name: 'Write metadata', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allMetadata, b => formatDuration(b.durationMs)) }, 96 + { name: 'Write images', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allImages, b => formatDuration(b.durationMs)) }, 97 + { name: 'Write docs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.writes.allDocuments, b => formatDuration(b.durationMs)) }, 98 + { name: 'Read URLs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.reads.recentUrls, b => formatDuration(b.durationMs)) }, 99 + { name: 'Read images', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.reads.randomImages, b => formatDuration(b.durationMs)) }, 100 + { name: 'Read docs', get: (r: StoreBenchmarkResults) => formatResultOrFailed(r.reads.randomDocuments, b => formatDuration(b.durationMs)) }, 101 + { name: 'Disk usage', get: (r: StoreBenchmarkResults) => formatBytes(r.disk.totalBytes) }, 102 + ]; 103 + 104 + for (const metric of metrics) { 105 + const row = [padRight(metric.name, colWidth), ...results.map(r => padRight(metric.get(r), colWidth))]; 106 + console.log(row.join(' │ ')); 107 + } 108 + } 109 + 110 + console.log('\n'); 111 + } 112 + 113 + export async function saveResults(results: StoreBenchmarkResults[], baseDir = 'results'): Promise<string> { 114 + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); 115 + const runDir = join(baseDir, `benchmark-${timestamp}`); 116 + 117 + await mkdir(runDir, { recursive: true }); 118 + 119 + await writeFile( 120 + join(runDir, 'results.json'), 121 + JSON.stringify({ 122 + timestamp: new Date().toISOString(), 123 + results 124 + }, null, 2) 125 + ); 126 + 127 + console.log(`Results saved to ${runDir}/results.json`); 128 + return runDir; 129 + }
+59
src/harness/types.ts
··· 1 + import type { Url } from '../generator/urls.js'; 2 + import type { Metadata } from '../generator/metadata.js'; 3 + 4 + export interface DatastoreAdapter { 5 + name: string; 6 + 7 + // Lifecycle 8 + init(): Promise<void>; 9 + cleanup(): Promise<void>; 10 + 11 + // Writes 12 + addUrls(urls: Url[]): Promise<void>; 13 + addImage(id: string, data: Buffer): Promise<void>; 14 + addDocument(id: string, content: string): Promise<void>; 15 + addMetadata(rows: Metadata[]): Promise<void>; 16 + 17 + // Reads 18 + getRecentUrls(count: number): Promise<Url[]>; 19 + getImages(ids: string[]): Promise<Map<string, Buffer>>; 20 + getDocuments(ids: string[]): Promise<Map<string, string>>; 21 + 22 + // Disk 23 + getDiskUsage(): Promise<number>; 24 + } 25 + 26 + export interface BenchmarkResult { 27 + name: string; 28 + durationMs: number; 29 + itemCount?: number; 30 + bytesProcessed?: number; 31 + failed?: boolean; 32 + error?: string; 33 + } 34 + 35 + export interface StoreBenchmarkResults { 36 + storeName: string; 37 + init: BenchmarkResult; 38 + writes: { 39 + allUrls: BenchmarkResult; 40 + allImages: BenchmarkResult; 41 + allDocuments: BenchmarkResult; 42 + allMetadata: BenchmarkResult; 43 + }; 44 + reads: { 45 + recentUrls: BenchmarkResult; 46 + randomImages: BenchmarkResult; 47 + randomDocuments: BenchmarkResult; 48 + }; 49 + disk: { 50 + totalBytes: number; 51 + }; 52 + } 53 + 54 + export interface TestData { 55 + urls: Url[]; 56 + metadata: Metadata[]; 57 + images: Array<{ id: string; filename: string; data: Buffer }>; 58 + documents: Array<{ id: string; filename: string; content: string }>; 59 + }
+54
src/regenerate-charts.ts
··· 1 + import { readFile, readdir, stat } from 'fs/promises'; 2 + import { join } from 'path'; 3 + import { generateCharts } from './harness/chart.js'; 4 + import type { StoreBenchmarkResults } from './harness/types.js'; 5 + 6 + const RESULTS_DIR = 'results'; 7 + 8 + async function main() { 9 + const args = process.argv.slice(2); 10 + let runDir: string; 11 + 12 + if (args[0]) { 13 + runDir = args[0]; 14 + } else { 15 + // Find most recent benchmark directory 16 + const entries = await readdir(RESULTS_DIR); 17 + const dirs: string[] = []; 18 + 19 + for (const entry of entries) { 20 + const path = join(RESULTS_DIR, entry); 21 + const s = await stat(path); 22 + if (s.isDirectory() && entry.startsWith('benchmark-')) { 23 + dirs.push(entry); 24 + } 25 + } 26 + 27 + dirs.sort().reverse(); 28 + 29 + if (dirs.length === 0) { 30 + console.error('No benchmark results found in results/'); 31 + process.exit(1); 32 + } 33 + 34 + runDir = join(RESULTS_DIR, dirs[0]); 35 + console.log(`Using most recent results: ${runDir}`); 36 + } 37 + 38 + // Load results 39 + const jsonFile = join(runDir, 'results.json'); 40 + const data = JSON.parse(await readFile(jsonFile, 'utf-8')); 41 + const results: StoreBenchmarkResults[] = data.results; 42 + 43 + console.log(`Loaded ${results.length} store results: ${results.map(r => r.storeName).join(', ')}`); 44 + 45 + // Generate charts in the same directory 46 + console.log('Generating charts...'); 47 + const chartFiles = await generateCharts(results, runDir); 48 + console.log(`Generated ${chartFiles.length} files in ${runDir}/`); 49 + } 50 + 51 + main().catch((err) => { 52 + console.error('Failed:', err); 53 + process.exit(1); 54 + });
+232
src/runner.ts
··· 1 + import { readFile, access, rm } from 'fs/promises'; 2 + import { join } from 'path'; 3 + import { runBenchmarks } from './harness/benchmark.js'; 4 + import { printResults, saveResults } from './harness/reporter.js'; 5 + import { generateCharts } from './harness/chart.js'; 6 + import type { TestData, StoreBenchmarkResults, BenchmarkResult } from './harness/types.js'; 7 + import type { Url } from './generator/urls.js'; 8 + import type { Metadata } from './generator/metadata.js'; 9 + import type { ImageInfo } from './generator/images.js'; 10 + import type { DocumentInfo } from './generator/documents.js'; 11 + import { getStore, getStoreNames } from './stores/index.js'; 12 + 13 + const ITERATIONS = 10; 14 + 15 + const TEST_DATA_DIR = 'test-data'; 16 + 17 + async function fileExists(path: string): Promise<boolean> { 18 + try { 19 + await access(path); 20 + return true; 21 + } catch { 22 + return false; 23 + } 24 + } 25 + 26 + async function loadTestData(): Promise<TestData> { 27 + console.log('Loading test data...'); 28 + 29 + // Check if test data exists 30 + if (!await fileExists(join(TEST_DATA_DIR, 'urls.json'))) { 31 + console.error('Test data not found. Run "npm run generate" first.'); 32 + process.exit(1); 33 + } 34 + 35 + // Load URLs 36 + const urls: Url[] = JSON.parse( 37 + await readFile(join(TEST_DATA_DIR, 'urls.json'), 'utf-8') 38 + ); 39 + console.log(` Loaded ${urls.length} URLs`); 40 + 41 + // Load metadata 42 + const metadata: Metadata[] = JSON.parse( 43 + await readFile(join(TEST_DATA_DIR, 'metadata.json'), 'utf-8') 44 + ); 45 + console.log(` Loaded ${metadata.length} metadata rows`); 46 + 47 + // Load image index and data 48 + const imageIndex: ImageInfo[] = JSON.parse( 49 + await readFile(join(TEST_DATA_DIR, 'images.json'), 'utf-8') 50 + ); 51 + const images: TestData['images'] = []; 52 + for (const img of imageIndex) { 53 + const data = await readFile(join(TEST_DATA_DIR, 'images', img.filename)); 54 + images.push({ id: img.id, filename: img.filename, data }); 55 + } 56 + console.log(` Loaded ${images.length} images`); 57 + 58 + // Load document index and content 59 + const documentIndex: DocumentInfo[] = JSON.parse( 60 + await readFile(join(TEST_DATA_DIR, 'documents.json'), 'utf-8') 61 + ); 62 + const documents: TestData['documents'] = []; 63 + for (const doc of documentIndex) { 64 + const content = await readFile(join(TEST_DATA_DIR, 'documents', doc.filename), 'utf-8'); 65 + documents.push({ id: doc.id, filename: doc.filename, content }); 66 + } 67 + console.log(` Loaded ${documents.length} documents`); 68 + 69 + return { urls, metadata, images, documents }; 70 + } 71 + 72 + function parseArgs(): { stores: string[] } { 73 + const args = process.argv.slice(2); 74 + const storeIndex = args.indexOf('--store'); 75 + 76 + if (storeIndex !== -1 && args[storeIndex + 1]) { 77 + return { stores: [args[storeIndex + 1]] }; 78 + } 79 + 80 + return { stores: getStoreNames() }; 81 + } 82 + 83 + function formatDuration(ms: number): string { 84 + if (ms < 1000) return `${ms.toFixed(0)}ms`; 85 + if (ms < 60000) return `${(ms / 1000).toFixed(2)}s`; 86 + return `${(ms / 60000).toFixed(2)}m`; 87 + } 88 + 89 + // Calculate median, excluding highest and lowest values 90 + function medianExcludingExtremes(values: number[]): number { 91 + if (values.length <= 2) return values[0] ?? 0; 92 + 93 + const sorted = [...values].sort((a, b) => a - b); 94 + // Remove lowest and highest 95 + const trimmed = sorted.slice(1, -1); 96 + 97 + const mid = Math.floor(trimmed.length / 2); 98 + if (trimmed.length % 2 === 0) { 99 + return (trimmed[mid - 1] + trimmed[mid]) / 2; 100 + } 101 + return trimmed[mid]; 102 + } 103 + 104 + // Aggregate multiple benchmark results into a single median result 105 + function aggregateBenchmarkResult(results: BenchmarkResult[]): BenchmarkResult { 106 + // If any run succeeded, use successful runs for median 107 + const successful = results.filter(r => !r.failed); 108 + 109 + if (successful.length === 0) { 110 + // All failed - return first failure 111 + return results[0]; 112 + } 113 + 114 + const durations = successful.map(r => r.durationMs); 115 + const medianDuration = medianExcludingExtremes(durations); 116 + 117 + // Use metadata from first successful result 118 + const first = successful[0]; 119 + return { 120 + name: first.name, 121 + durationMs: medianDuration, 122 + itemCount: first.itemCount, 123 + bytesProcessed: first.bytesProcessed, 124 + failed: false 125 + }; 126 + } 127 + 128 + // Aggregate multiple store benchmark results into median results 129 + function aggregateStoreResults(runs: StoreBenchmarkResults[]): StoreBenchmarkResults { 130 + return { 131 + storeName: runs[0].storeName, 132 + init: aggregateBenchmarkResult(runs.map(r => r.init)), 133 + writes: { 134 + allUrls: aggregateBenchmarkResult(runs.map(r => r.writes.allUrls)), 135 + allImages: aggregateBenchmarkResult(runs.map(r => r.writes.allImages)), 136 + allDocuments: aggregateBenchmarkResult(runs.map(r => r.writes.allDocuments)), 137 + allMetadata: aggregateBenchmarkResult(runs.map(r => r.writes.allMetadata)), 138 + }, 139 + reads: { 140 + recentUrls: aggregateBenchmarkResult(runs.map(r => r.reads.recentUrls)), 141 + randomImages: aggregateBenchmarkResult(runs.map(r => r.reads.randomImages)), 142 + randomDocuments: aggregateBenchmarkResult(runs.map(r => r.reads.randomDocuments)), 143 + }, 144 + disk: { 145 + // Use median disk usage 146 + totalBytes: medianExcludingExtremes(runs.map(r => r.disk.totalBytes)) 147 + } 148 + }; 149 + } 150 + 151 + async function main() { 152 + console.log('='.repeat(60)); 153 + console.log('DATASTORE BENCHMARK SUITE'); 154 + console.log('='.repeat(60)); 155 + console.log(); 156 + 157 + const { stores: storeNames } = parseArgs(); 158 + const availableStores = getStoreNames(); 159 + 160 + // Validate store names 161 + for (const name of storeNames) { 162 + if (!availableStores.includes(name.toLowerCase())) { 163 + console.error(`Unknown store: ${name}`); 164 + console.error(`Available stores: ${availableStores.join(', ')}`); 165 + process.exit(1); 166 + } 167 + } 168 + 169 + console.log(`Stores to benchmark: ${storeNames.join(', ')}`); 170 + console.log(`Iterations per store: ${ITERATIONS} (excluding high/low, using median)\n`); 171 + 172 + // Load test data 173 + const testData = await loadTestData(); 174 + console.log(); 175 + 176 + // Run benchmarks 177 + const results: StoreBenchmarkResults[] = []; 178 + 179 + for (const storeName of storeNames) { 180 + console.log(`\n${'─'.repeat(50)}`); 181 + console.log(`Benchmarking ${storeName} (${ITERATIONS} iterations)`); 182 + console.log('─'.repeat(50)); 183 + 184 + const store = getStore(storeName); 185 + if (!store) continue; 186 + 187 + const storeRuns: StoreBenchmarkResults[] = []; 188 + 189 + for (let i = 0; i < ITERATIONS; i++) { 190 + console.log(`\n [Run ${i + 1}/${ITERATIONS}]`); 191 + 192 + const result = await runBenchmarks(store, testData, (phase, durationMs, failed, error) => { 193 + if (durationMs !== undefined) { 194 + if (failed) { 195 + console.log(` ✗ ${phase} FAILED (${formatDuration(durationMs)}) - ${error}`); 196 + } else { 197 + console.log(` ✓ ${phase} (${formatDuration(durationMs)})`); 198 + } 199 + } else { 200 + console.log(` → ${phase}`); 201 + } 202 + }); 203 + 204 + storeRuns.push(result); 205 + 206 + // Clean up store data after each iteration 207 + await rm('store-data', { recursive: true, force: true }); 208 + } 209 + 210 + // Aggregate results using median (excluding high/low) 211 + const aggregated = aggregateStoreResults(storeRuns); 212 + results.push(aggregated); 213 + 214 + console.log(`\n Median results for ${storeName} computed from ${ITERATIONS} runs`); 215 + } 216 + 217 + // Print and save results 218 + printResults(results); 219 + const runDir = await saveResults(results); 220 + 221 + // Generate comparison charts if multiple stores were tested 222 + if (results.length > 1) { 223 + console.log('Generating comparison charts...'); 224 + const chartFiles = await generateCharts(results, runDir); 225 + console.log(`Generated ${chartFiles.length} files in ${runDir}/`); 226 + } 227 + } 228 + 229 + main().catch((err) => { 230 + console.error('Benchmark failed:', err); 231 + process.exit(1); 232 + });
+20
src/stores/index.ts
··· 1 + import type { DatastoreAdapter } from '../harness/types.js'; 2 + import { TinyBaseAdapter } from './tinybase.js'; 3 + import { LevelGraphAdapter } from './levelgraph.js'; 4 + import { SQLiteAdapter } from './sqlite.js'; 5 + 6 + // Registry of all available datastores 7 + export const stores: Record<string, () => DatastoreAdapter> = { 8 + tinybase: () => new TinyBaseAdapter(), 9 + levelgraph: () => new LevelGraphAdapter(), 10 + sqlite: () => new SQLiteAdapter() 11 + }; 12 + 13 + export function getStore(name: string): DatastoreAdapter | null { 14 + const factory = stores[name.toLowerCase()]; 15 + return factory ? factory() : null; 16 + } 17 + 18 + export function getStoreNames(): string[] { 19 + return Object.keys(stores); 20 + }
+220
src/stores/levelgraph.ts
··· 1 + import { mkdir, writeFile, readFile, rm, stat, readdir } from 'fs/promises'; 2 + import { join } from 'path'; 3 + import type { DatastoreAdapter } from '../harness/types.js'; 4 + import type { Url } from '../generator/urls.js'; 5 + import type { Metadata } from '../generator/metadata.js'; 6 + 7 + const DATA_DIR = 'store-data/levelgraph'; 8 + 9 + export class LevelGraphAdapter implements DatastoreAdapter { 10 + name = 'LevelGraph'; 11 + private db: any = null; 12 + private blobDir: string; 13 + 14 + constructor() { 15 + this.blobDir = join(DATA_DIR, 'blobs'); 16 + } 17 + 18 + async init(): Promise<void> { 19 + await rm(DATA_DIR, { recursive: true, force: true }); 20 + await mkdir(this.blobDir, { recursive: true }); 21 + 22 + // Dynamic imports 23 + const { Level } = await import('level'); 24 + const levelgraph = (await import('levelgraph')).default; 25 + 26 + const level = new Level(join(DATA_DIR, 'db')); 27 + this.db = levelgraph(level); 28 + } 29 + 30 + async cleanup(): Promise<void> { 31 + if (this.db && this.db.db && typeof this.db.db.close === 'function') { 32 + await this.db.db.close(); 33 + } 34 + this.db = null; 35 + } 36 + 37 + async addUrls(urls: Url[]): Promise<void> { 38 + if (!this.db) throw new Error('DB not initialized'); 39 + 40 + // Store URLs as triples: subject=url_id, predicate=property, object=value 41 + const triples: any[] = []; 42 + for (const url of urls) { 43 + triples.push( 44 + { subject: url.id, predicate: 'type', object: 'url' }, 45 + { subject: url.id, predicate: 'url', object: url.url }, 46 + { subject: url.id, predicate: 'title', object: url.title }, 47 + { subject: url.id, predicate: 'createdAt', object: String(url.createdAt) }, 48 + { subject: url.id, predicate: 'tags', object: JSON.stringify(url.tags) } 49 + ); 50 + } 51 + 52 + await this.putBatch(triples); 53 + } 54 + 55 + async addImage(id: string, data: Buffer): Promise<void> { 56 + if (!this.db) throw new Error('DB not initialized'); 57 + 58 + const filename = `${id}.bin`; 59 + await writeFile(join(this.blobDir, filename), data); 60 + 61 + await this.putBatch([ 62 + { subject: id, predicate: 'type', object: 'image' }, 63 + { subject: id, predicate: 'filename', object: filename }, 64 + { subject: id, predicate: 'size', object: String(data.length) } 65 + ]); 66 + } 67 + 68 + async addDocument(id: string, content: string): Promise<void> { 69 + if (!this.db) throw new Error('DB not initialized'); 70 + 71 + // Store document content as a file (too large for triple object) 72 + const filename = `${id}.txt`; 73 + await writeFile(join(this.blobDir, filename), content); 74 + 75 + await this.putBatch([ 76 + { subject: id, predicate: 'type', object: 'document' }, 77 + { subject: id, predicate: 'filename', object: filename }, 78 + { subject: id, predicate: 'size', object: String(content.length) } 79 + ]); 80 + } 81 + 82 + async addMetadata(rows: Metadata[]): Promise<void> { 83 + if (!this.db) throw new Error('DB not initialized'); 84 + 85 + // Process in smaller batches with overall timeout 86 + const BATCH_SIZE = 1000; 87 + const TIMEOUT_MS = 60000; 88 + const startTime = Date.now(); 89 + 90 + for (let i = 0; i < rows.length; i += BATCH_SIZE) { 91 + // Check timeout before each batch 92 + if (Date.now() - startTime > TIMEOUT_MS) { 93 + throw new Error(`addMetadata timed out after ${TIMEOUT_MS}ms (processed ${i}/${rows.length} rows)`); 94 + } 95 + 96 + const batch = rows.slice(i, i + BATCH_SIZE); 97 + const triples: any[] = []; 98 + 99 + for (const row of batch) { 100 + triples.push( 101 + { subject: row.id, predicate: 'type', object: 'metadata' }, 102 + { subject: row.id, predicate: 'key', object: row.key }, 103 + { subject: row.id, predicate: 'value', object: JSON.stringify(row.value) }, 104 + { subject: row.id, predicate: 'category', object: row.category }, 105 + { subject: row.id, predicate: 'timestamp', object: String(row.timestamp) } 106 + ); 107 + } 108 + 109 + await this.putBatch(triples, 30000); // 30s per batch 110 + } 111 + } 112 + 113 + async getRecentUrls(count: number): Promise<Url[]> { 114 + if (!this.db) throw new Error('DB not initialized'); 115 + 116 + // Get all URL type entities 117 + const urlIds = await this.get({ predicate: 'type', object: 'url' }); 118 + 119 + // For each URL, get all properties 120 + const urls: Url[] = []; 121 + for (const triple of urlIds) { 122 + const props = await this.get({ subject: triple.subject }); 123 + const propMap = new Map<string, string>(); 124 + for (const p of props) { 125 + propMap.set(p.predicate, p.object); 126 + } 127 + 128 + urls.push({ 129 + id: triple.subject, 130 + url: propMap.get('url') || '', 131 + title: propMap.get('title') || '', 132 + createdAt: parseInt(propMap.get('createdAt') || '0', 10), 133 + tags: JSON.parse(propMap.get('tags') || '[]') 134 + }); 135 + } 136 + 137 + // Sort by createdAt descending and take first N 138 + return urls 139 + .sort((a, b) => b.createdAt - a.createdAt) 140 + .slice(0, count); 141 + } 142 + 143 + async getImages(ids: string[]): Promise<Map<string, Buffer>> { 144 + if (!this.db) throw new Error('DB not initialized'); 145 + 146 + const result = new Map<string, Buffer>(); 147 + for (const id of ids) { 148 + const props = await this.get({ subject: id, predicate: 'filename' }); 149 + if (props.length > 0) { 150 + const filename = props[0].object; 151 + const data = await readFile(join(this.blobDir, filename)); 152 + result.set(id, data); 153 + } 154 + } 155 + return result; 156 + } 157 + 158 + async getDocuments(ids: string[]): Promise<Map<string, string>> { 159 + if (!this.db) throw new Error('DB not initialized'); 160 + 161 + const result = new Map<string, string>(); 162 + for (const id of ids) { 163 + const props = await this.get({ subject: id, predicate: 'filename' }); 164 + if (props.length > 0) { 165 + const filename = props[0].object; 166 + const content = await readFile(join(this.blobDir, filename), 'utf-8'); 167 + result.set(id, content); 168 + } 169 + } 170 + return result; 171 + } 172 + 173 + async getDiskUsage(): Promise<number> { 174 + return this.calculateDirSize(DATA_DIR); 175 + } 176 + 177 + private putBatch(triples: any[], timeoutMs = 60000): Promise<void> { 178 + return new Promise((resolve, reject) => { 179 + const timeout = setTimeout(() => { 180 + reject(new Error(`putBatch timed out after ${timeoutMs}ms`)); 181 + }, timeoutMs); 182 + 183 + this.db.put(triples, (err: any) => { 184 + clearTimeout(timeout); 185 + if (err) reject(err); 186 + else resolve(); 187 + }); 188 + }); 189 + } 190 + 191 + private get(pattern: any): Promise<any[]> { 192 + return new Promise((resolve, reject) => { 193 + this.db.get(pattern, (err: any, list: any[]) => { 194 + if (err) reject(err); 195 + else resolve(list); 196 + }); 197 + }); 198 + } 199 + 200 + private async calculateDirSize(dir: string): Promise<number> { 201 + let total = 0; 202 + 203 + try { 204 + const entries = await readdir(dir, { withFileTypes: true }); 205 + for (const entry of entries) { 206 + const path = join(dir, entry.name); 207 + if (entry.isDirectory()) { 208 + total += await this.calculateDirSize(path); 209 + } else { 210 + const s = await stat(path); 211 + total += s.size; 212 + } 213 + } 214 + } catch { 215 + // Directory doesn't exist yet 216 + } 217 + 218 + return total; 219 + } 220 + }
+205
src/stores/sqlite.ts
··· 1 + import Database from 'better-sqlite3'; 2 + import { mkdir, rm, stat, readdir } from 'fs/promises'; 3 + import { join } from 'path'; 4 + import type { DatastoreAdapter } from '../harness/types.js'; 5 + import type { Url } from '../generator/urls.js'; 6 + import type { Metadata } from '../generator/metadata.js'; 7 + 8 + const DATA_DIR = 'store-data/sqlite'; 9 + const DB_PATH = join(DATA_DIR, 'store.db'); 10 + 11 + export class SQLiteAdapter implements DatastoreAdapter { 12 + name = 'SQLite'; 13 + private db: Database.Database | null = null; 14 + 15 + async init(): Promise<void> { 16 + await rm(DATA_DIR, { recursive: true, force: true }); 17 + await mkdir(DATA_DIR, { recursive: true }); 18 + 19 + this.db = new Database(DB_PATH); 20 + 21 + // Enable WAL mode for better write performance 22 + this.db.pragma('journal_mode = WAL'); 23 + 24 + // Create tables 25 + this.db.exec(` 26 + CREATE TABLE urls ( 27 + id TEXT PRIMARY KEY, 28 + url TEXT NOT NULL, 29 + title TEXT NOT NULL, 30 + createdAt INTEGER NOT NULL, 31 + tags TEXT NOT NULL 32 + ); 33 + 34 + CREATE TABLE images ( 35 + id TEXT PRIMARY KEY, 36 + data BLOB NOT NULL, 37 + size INTEGER NOT NULL 38 + ); 39 + 40 + CREATE TABLE documents ( 41 + id TEXT PRIMARY KEY, 42 + content TEXT NOT NULL, 43 + size INTEGER NOT NULL 44 + ); 45 + 46 + CREATE TABLE metadata ( 47 + id TEXT PRIMARY KEY, 48 + key TEXT NOT NULL, 49 + value TEXT NOT NULL, 50 + category TEXT NOT NULL, 51 + timestamp INTEGER NOT NULL 52 + ); 53 + 54 + CREATE INDEX idx_urls_createdAt ON urls(createdAt DESC); 55 + CREATE INDEX idx_metadata_timestamp ON metadata(timestamp DESC); 56 + `); 57 + } 58 + 59 + async cleanup(): Promise<void> { 60 + if (this.db) { 61 + this.db.close(); 62 + this.db = null; 63 + } 64 + } 65 + 66 + async addUrls(urls: Url[]): Promise<void> { 67 + if (!this.db) throw new Error('DB not initialized'); 68 + 69 + const insert = this.db.prepare(` 70 + INSERT INTO urls (id, url, title, createdAt, tags) 71 + VALUES (@id, @url, @title, @createdAt, @tags) 72 + `); 73 + 74 + const insertMany = this.db.transaction((urls: Url[]) => { 75 + for (const url of urls) { 76 + insert.run({ 77 + id: url.id, 78 + url: url.url, 79 + title: url.title, 80 + createdAt: url.createdAt, 81 + tags: JSON.stringify(url.tags) 82 + }); 83 + } 84 + }); 85 + 86 + insertMany(urls); 87 + } 88 + 89 + async addImage(id: string, data: Buffer): Promise<void> { 90 + if (!this.db) throw new Error('DB not initialized'); 91 + 92 + this.db.prepare(` 93 + INSERT INTO images (id, data, size) 94 + VALUES (?, ?, ?) 95 + `).run(id, data, data.length); 96 + } 97 + 98 + async addDocument(id: string, content: string): Promise<void> { 99 + if (!this.db) throw new Error('DB not initialized'); 100 + 101 + this.db.prepare(` 102 + INSERT INTO documents (id, content, size) 103 + VALUES (?, ?, ?) 104 + `).run(id, content, content.length); 105 + } 106 + 107 + async addMetadata(rows: Metadata[]): Promise<void> { 108 + if (!this.db) throw new Error('DB not initialized'); 109 + 110 + const insert = this.db.prepare(` 111 + INSERT INTO metadata (id, key, value, category, timestamp) 112 + VALUES (@id, @key, @value, @category, @timestamp) 113 + `); 114 + 115 + const insertMany = this.db.transaction((rows: Metadata[]) => { 116 + for (const row of rows) { 117 + insert.run({ 118 + id: row.id, 119 + key: row.key, 120 + value: JSON.stringify(row.value), 121 + category: row.category, 122 + timestamp: row.timestamp 123 + }); 124 + } 125 + }); 126 + 127 + insertMany(rows); 128 + } 129 + 130 + async getRecentUrls(count: number): Promise<Url[]> { 131 + if (!this.db) throw new Error('DB not initialized'); 132 + 133 + const rows = this.db.prepare(` 134 + SELECT id, url, title, createdAt, tags 135 + FROM urls 136 + ORDER BY createdAt DESC 137 + LIMIT ? 138 + `).all(count) as any[]; 139 + 140 + return rows.map(row => ({ 141 + id: row.id, 142 + url: row.url, 143 + title: row.title, 144 + createdAt: row.createdAt, 145 + tags: JSON.parse(row.tags) 146 + })); 147 + } 148 + 149 + async getImages(ids: string[]): Promise<Map<string, Buffer>> { 150 + if (!this.db) throw new Error('DB not initialized'); 151 + 152 + const result = new Map<string, Buffer>(); 153 + const stmt = this.db.prepare('SELECT id, data FROM images WHERE id = ?'); 154 + 155 + for (const id of ids) { 156 + const row = stmt.get(id) as { id: string; data: Buffer } | undefined; 157 + if (row) { 158 + result.set(row.id, row.data); 159 + } 160 + } 161 + 162 + return result; 163 + } 164 + 165 + async getDocuments(ids: string[]): Promise<Map<string, string>> { 166 + if (!this.db) throw new Error('DB not initialized'); 167 + 168 + const result = new Map<string, string>(); 169 + const stmt = this.db.prepare('SELECT id, content FROM documents WHERE id = ?'); 170 + 171 + for (const id of ids) { 172 + const row = stmt.get(id) as { id: string; content: string } | undefined; 173 + if (row) { 174 + result.set(row.id, row.content); 175 + } 176 + } 177 + 178 + return result; 179 + } 180 + 181 + async getDiskUsage(): Promise<number> { 182 + return this.calculateDirSize(DATA_DIR); 183 + } 184 + 185 + private async calculateDirSize(dir: string): Promise<number> { 186 + let total = 0; 187 + 188 + try { 189 + const entries = await readdir(dir, { withFileTypes: true }); 190 + for (const entry of entries) { 191 + const path = join(dir, entry.name); 192 + if (entry.isDirectory()) { 193 + total += await this.calculateDirSize(path); 194 + } else { 195 + const s = await stat(path); 196 + total += s.size; 197 + } 198 + } 199 + } catch { 200 + // Directory doesn't exist yet 201 + } 202 + 203 + return total; 204 + } 205 + }
+153
src/stores/tinybase.ts
··· 1 + import { createStore, type Store } from 'tinybase'; 2 + import { mkdir, writeFile, readFile, rm, stat } from 'fs/promises'; 3 + import { join } from 'path'; 4 + import type { DatastoreAdapter } from '../harness/types.js'; 5 + import type { Url } from '../generator/urls.js'; 6 + import type { Metadata } from '../generator/metadata.js'; 7 + 8 + const DATA_DIR = 'store-data/tinybase'; 9 + 10 + export class TinyBaseAdapter implements DatastoreAdapter { 11 + name = 'TinyBase'; 12 + private store: Store | null = null; 13 + private blobDir: string; 14 + 15 + constructor() { 16 + this.blobDir = join(DATA_DIR, 'blobs'); 17 + } 18 + 19 + async init(): Promise<void> { 20 + await rm(DATA_DIR, { recursive: true, force: true }); 21 + await mkdir(this.blobDir, { recursive: true }); 22 + this.store = createStore(); 23 + } 24 + 25 + async cleanup(): Promise<void> { 26 + // Save store state to disk for disk usage measurement 27 + if (this.store) { 28 + const json = JSON.stringify(this.store.getJson()); 29 + await writeFile(join(DATA_DIR, 'store.json'), json); 30 + } 31 + this.store = null; 32 + } 33 + 34 + async addUrls(urls: Url[]): Promise<void> { 35 + if (!this.store) throw new Error('Store not initialized'); 36 + 37 + for (const url of urls) { 38 + this.store.setRow('urls', url.id, { 39 + url: url.url, 40 + title: url.title, 41 + createdAt: url.createdAt, 42 + tags: JSON.stringify(url.tags) 43 + }); 44 + } 45 + } 46 + 47 + async addImage(id: string, data: Buffer): Promise<void> { 48 + if (!this.store) throw new Error('Store not initialized'); 49 + 50 + // Store metadata in TinyBase, blob on disk 51 + const filename = `${id}.bin`; 52 + await writeFile(join(this.blobDir, filename), data); 53 + 54 + this.store.setRow('images', id, { 55 + filename, 56 + size: data.length 57 + }); 58 + } 59 + 60 + async addDocument(id: string, content: string): Promise<void> { 61 + if (!this.store) throw new Error('Store not initialized'); 62 + 63 + // Store documents directly in TinyBase (they're text) 64 + this.store.setRow('documents', id, { 65 + content, 66 + size: content.length 67 + }); 68 + } 69 + 70 + async addMetadata(rows: Metadata[]): Promise<void> { 71 + if (!this.store) throw new Error('Store not initialized'); 72 + 73 + for (const row of rows) { 74 + this.store.setRow('metadata', row.id, { 75 + key: row.key, 76 + value: JSON.stringify(row.value), 77 + category: row.category, 78 + timestamp: row.timestamp 79 + }); 80 + } 81 + } 82 + 83 + async getRecentUrls(count: number): Promise<Url[]> { 84 + if (!this.store) throw new Error('Store not initialized'); 85 + 86 + const table = this.store.getTable('urls'); 87 + const rows = Object.entries(table) 88 + .map(([id, row]) => ({ 89 + id, 90 + url: row.url as string, 91 + title: row.title as string, 92 + createdAt: row.createdAt as number, 93 + tags: JSON.parse(row.tags as string) as string[] 94 + })) 95 + .sort((a, b) => b.createdAt - a.createdAt) 96 + .slice(0, count); 97 + 98 + return rows; 99 + } 100 + 101 + async getImages(ids: string[]): Promise<Map<string, Buffer>> { 102 + if (!this.store) throw new Error('Store not initialized'); 103 + 104 + const result = new Map<string, Buffer>(); 105 + for (const id of ids) { 106 + const row = this.store.getRow('images', id); 107 + if (row && row.filename) { 108 + const data = await readFile(join(this.blobDir, row.filename as string)); 109 + result.set(id, data); 110 + } 111 + } 112 + return result; 113 + } 114 + 115 + async getDocuments(ids: string[]): Promise<Map<string, string>> { 116 + if (!this.store) throw new Error('Store not initialized'); 117 + 118 + const result = new Map<string, string>(); 119 + for (const id of ids) { 120 + const row = this.store.getRow('documents', id); 121 + if (row && row.content) { 122 + result.set(id, row.content as string); 123 + } 124 + } 125 + return result; 126 + } 127 + 128 + async getDiskUsage(): Promise<number> { 129 + return this.calculateDirSize(DATA_DIR); 130 + } 131 + 132 + private async calculateDirSize(dir: string): Promise<number> { 133 + const { readdir } = await import('fs/promises'); 134 + let total = 0; 135 + 136 + try { 137 + const entries = await readdir(dir, { withFileTypes: true }); 138 + for (const entry of entries) { 139 + const path = join(dir, entry.name); 140 + if (entry.isDirectory()) { 141 + total += await this.calculateDirSize(path); 142 + } else { 143 + const s = await stat(path); 144 + total += s.size; 145 + } 146 + } 147 + } catch { 148 + // Directory doesn't exist yet 149 + } 150 + 151 + return total; 152 + } 153 + }
+14
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "bundler", 6 + "esModuleInterop": true, 7 + "strict": true, 8 + "skipLibCheck": true, 9 + "outDir": "dist", 10 + "rootDir": "src", 11 + "resolveJsonModule": true 12 + }, 13 + "include": ["src/**/*"] 14 + }