Image CDN for atproto built on cloudflare

unit tests

evan.jarrett.net 86b2b834 4d6c781d

verified
Changed files
+2060 -18
src
test
+1279
package-lock.json
··· 12 }, 13 "devDependencies": { 14 "@cloudflare/vitest-pool-workers": "^0.11.1", 15 "typescript": "^5.5.2", 16 "vitest": "^3.2.4", 17 "wrangler": "^4.21.2" 18 } 19 }, 20 "node_modules/@cloudflare/kv-asset-handler": { ··· 1467 "url": "https://opencollective.com/libvips" 1468 } 1469 }, 1470 "node_modules/@jridgewell/resolve-uri": { 1471 "version": "3.1.2", 1472 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", ··· 1493 "dependencies": { 1494 "@jridgewell/resolve-uri": "^3.0.3", 1495 "@jridgewell/sourcemap-codec": "^1.4.10" 1496 } 1497 }, 1498 "node_modules/@poppinss/colors": { ··· 1877 "dev": true, 1878 "license": "MIT" 1879 }, 1880 "node_modules/@vitest/expect": { 1881 "version": "3.2.4", 1882 "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", ··· 2015 "node": ">=0.4.0" 2016 } 2017 }, 2018 "node_modules/assertion-error": { 2019 "version": "2.0.1", 2020 "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", ··· 2025 "node": ">=12" 2026 } 2027 }, 2028 "node_modules/birpc": { 2029 "version": "0.2.14", 2030 "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.14.tgz", ··· 2042 "dev": true, 2043 "license": "MIT" 2044 }, 2045 "node_modules/cac": { 2046 "version": "6.7.14", 2047 "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", ··· 2051 "engines": { 2052 "node": ">=8" 2053 } 2054 }, 2055 "node_modules/chai": { 2056 "version": "5.3.3", ··· 2131 "simple-swizzle": "^0.2.2" 2132 } 2133 }, 2134 "node_modules/cookie": { 2135 "version": "1.1.1", 2136 "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", ··· 2143 "funding": { 2144 "type": "opencollective", 2145 "url": "https://opencollective.com/express" 2146 } 2147 }, 2148 "node_modules/debug": { ··· 2190 "dev": true, 2191 "license": "MIT" 2192 }, 2193 "node_modules/error-stack-parser-es": { 2194 "version": "1.0.5", 2195 "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", ··· 2249 "@esbuild/win32-x64": "0.27.2" 2250 } 2251 }, 2252 "node_modules/estree-walker": { 2253 "version": "3.0.3", 2254 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", ··· 2300 } 2301 } 2302 }, 2303 "node_modules/fsevents": { 2304 "version": "2.3.3", 2305 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ··· 2315 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2316 } 2317 }, 2318 "node_modules/glob-to-regexp": { 2319 "version": "0.4.1", 2320 "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", ··· 2322 "dev": true, 2323 "license": "BSD-2-Clause" 2324 }, 2325 "node_modules/is-arrayish": { 2326 "version": "0.3.4", 2327 "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", ··· 2329 "dev": true, 2330 "license": "MIT" 2331 }, 2332 "node_modules/js-tokens": { 2333 "version": "9.0.1", 2334 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", ··· 2336 "dev": true, 2337 "license": "MIT" 2338 }, 2339 "node_modules/kleur": { 2340 "version": "4.1.5", 2341 "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", ··· 2353 "dev": true, 2354 "license": "MIT" 2355 }, 2356 "node_modules/magic-string": { 2357 "version": "0.30.21", 2358 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", ··· 2363 "@jridgewell/sourcemap-codec": "^1.5.5" 2364 } 2365 }, 2366 "node_modules/mime": { 2367 "version": "3.0.0", 2368 "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", ··· 2413 "url": "https://github.com/sponsors/colinhacks" 2414 } 2415 }, 2416 "node_modules/ms": { 2417 "version": "2.1.3", 2418 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", ··· 2445 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2446 } 2447 }, 2448 "node_modules/path-to-regexp": { 2449 "version": "6.3.0", 2450 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", ··· 2613 "@img/sharp-win32-x64": "0.33.5" 2614 } 2615 }, 2616 "node_modules/siginfo": { 2617 "version": "2.0.0", 2618 "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", ··· 2620 "dev": true, 2621 "license": "ISC" 2622 }, 2623 "node_modules/simple-swizzle": { 2624 "version": "0.2.4", 2625 "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", ··· 2665 "npm": ">=6" 2666 } 2667 }, 2668 "node_modules/strip-literal": { 2669 "version": "3.1.0", 2670 "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", ··· 2691 "url": "https://github.com/chalk/supports-color?sponsor=1" 2692 } 2693 }, 2694 "node_modules/tinybench": { 2695 "version": "2.9.0", 2696 "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", ··· 2792 "license": "MIT", 2793 "dependencies": { 2794 "pathe": "^2.0.3" 2795 } 2796 }, 2797 "node_modules/vite": { ··· 2963 "jsdom": { 2964 "optional": true 2965 } 2966 } 2967 }, 2968 "node_modules/why-is-node-running": { ··· 3539 "@esbuild/win32-x64": "0.27.0" 3540 } 3541 }, 3542 "node_modules/ws": { 3543 "version": "8.18.0", 3544 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", ··· 3560 "optional": true 3561 } 3562 } 3563 }, 3564 "node_modules/youch": { 3565 "version": "4.1.0-beta.10",
··· 12 }, 13 "devDependencies": { 14 "@cloudflare/vitest-pool-workers": "^0.11.1", 15 + "@vitest/coverage-istanbul": "^3.2.4", 16 + "@vitest/coverage-v8": "^3.2.4", 17 "typescript": "^5.5.2", 18 "vitest": "^3.2.4", 19 "wrangler": "^4.21.2" 20 + } 21 + }, 22 + "node_modules/@ampproject/remapping": { 23 + "version": "2.3.0", 24 + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 25 + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 26 + "dev": true, 27 + "license": "Apache-2.0", 28 + "dependencies": { 29 + "@jridgewell/gen-mapping": "^0.3.5", 30 + "@jridgewell/trace-mapping": "^0.3.24" 31 + }, 32 + "engines": { 33 + "node": ">=6.0.0" 34 + } 35 + }, 36 + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { 37 + "version": "0.3.31", 38 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 39 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 40 + "dev": true, 41 + "license": "MIT", 42 + "dependencies": { 43 + "@jridgewell/resolve-uri": "^3.1.0", 44 + "@jridgewell/sourcemap-codec": "^1.4.14" 45 + } 46 + }, 47 + "node_modules/@babel/code-frame": { 48 + "version": "7.27.1", 49 + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 50 + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 51 + "dev": true, 52 + "license": "MIT", 53 + "dependencies": { 54 + "@babel/helper-validator-identifier": "^7.27.1", 55 + "js-tokens": "^4.0.0", 56 + "picocolors": "^1.1.1" 57 + }, 58 + "engines": { 59 + "node": ">=6.9.0" 60 + } 61 + }, 62 + "node_modules/@babel/code-frame/node_modules/js-tokens": { 63 + "version": "4.0.0", 64 + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 65 + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 66 + "dev": true, 67 + "license": "MIT" 68 + }, 69 + "node_modules/@babel/compat-data": { 70 + "version": "7.28.5", 71 + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", 72 + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", 73 + "dev": true, 74 + "license": "MIT", 75 + "engines": { 76 + "node": ">=6.9.0" 77 + } 78 + }, 79 + "node_modules/@babel/core": { 80 + "version": "7.28.5", 81 + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", 82 + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", 83 + "dev": true, 84 + "license": "MIT", 85 + "dependencies": { 86 + "@babel/code-frame": "^7.27.1", 87 + "@babel/generator": "^7.28.5", 88 + "@babel/helper-compilation-targets": "^7.27.2", 89 + "@babel/helper-module-transforms": "^7.28.3", 90 + "@babel/helpers": "^7.28.4", 91 + "@babel/parser": "^7.28.5", 92 + "@babel/template": "^7.27.2", 93 + "@babel/traverse": "^7.28.5", 94 + "@babel/types": "^7.28.5", 95 + "@jridgewell/remapping": "^2.3.5", 96 + "convert-source-map": "^2.0.0", 97 + "debug": "^4.1.0", 98 + "gensync": "^1.0.0-beta.2", 99 + "json5": "^2.2.3", 100 + "semver": "^6.3.1" 101 + }, 102 + "engines": { 103 + "node": ">=6.9.0" 104 + }, 105 + "funding": { 106 + "type": "opencollective", 107 + "url": "https://opencollective.com/babel" 108 + } 109 + }, 110 + "node_modules/@babel/core/node_modules/semver": { 111 + "version": "6.3.1", 112 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 113 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 114 + "dev": true, 115 + "license": "ISC", 116 + "bin": { 117 + "semver": "bin/semver.js" 118 + } 119 + }, 120 + "node_modules/@babel/generator": { 121 + "version": "7.28.5", 122 + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", 123 + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", 124 + "dev": true, 125 + "license": "MIT", 126 + "dependencies": { 127 + "@babel/parser": "^7.28.5", 128 + "@babel/types": "^7.28.5", 129 + "@jridgewell/gen-mapping": "^0.3.12", 130 + "@jridgewell/trace-mapping": "^0.3.28", 131 + "jsesc": "^3.0.2" 132 + }, 133 + "engines": { 134 + "node": ">=6.9.0" 135 + } 136 + }, 137 + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { 138 + "version": "0.3.31", 139 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 140 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 141 + "dev": true, 142 + "license": "MIT", 143 + "dependencies": { 144 + "@jridgewell/resolve-uri": "^3.1.0", 145 + "@jridgewell/sourcemap-codec": "^1.4.14" 146 + } 147 + }, 148 + "node_modules/@babel/helper-compilation-targets": { 149 + "version": "7.27.2", 150 + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", 151 + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", 152 + "dev": true, 153 + "license": "MIT", 154 + "dependencies": { 155 + "@babel/compat-data": "^7.27.2", 156 + "@babel/helper-validator-option": "^7.27.1", 157 + "browserslist": "^4.24.0", 158 + "lru-cache": "^5.1.1", 159 + "semver": "^6.3.1" 160 + }, 161 + "engines": { 162 + "node": ">=6.9.0" 163 + } 164 + }, 165 + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { 166 + "version": "5.1.1", 167 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 168 + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 169 + "dev": true, 170 + "license": "ISC", 171 + "dependencies": { 172 + "yallist": "^3.0.2" 173 + } 174 + }, 175 + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { 176 + "version": "6.3.1", 177 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 178 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 179 + "dev": true, 180 + "license": "ISC", 181 + "bin": { 182 + "semver": "bin/semver.js" 183 + } 184 + }, 185 + "node_modules/@babel/helper-globals": { 186 + "version": "7.28.0", 187 + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", 188 + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", 189 + "dev": true, 190 + "license": "MIT", 191 + "engines": { 192 + "node": ">=6.9.0" 193 + } 194 + }, 195 + "node_modules/@babel/helper-module-imports": { 196 + "version": "7.27.1", 197 + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", 198 + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", 199 + "dev": true, 200 + "license": "MIT", 201 + "dependencies": { 202 + "@babel/traverse": "^7.27.1", 203 + "@babel/types": "^7.27.1" 204 + }, 205 + "engines": { 206 + "node": ">=6.9.0" 207 + } 208 + }, 209 + "node_modules/@babel/helper-module-transforms": { 210 + "version": "7.28.3", 211 + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", 212 + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", 213 + "dev": true, 214 + "license": "MIT", 215 + "dependencies": { 216 + "@babel/helper-module-imports": "^7.27.1", 217 + "@babel/helper-validator-identifier": "^7.27.1", 218 + "@babel/traverse": "^7.28.3" 219 + }, 220 + "engines": { 221 + "node": ">=6.9.0" 222 + }, 223 + "peerDependencies": { 224 + "@babel/core": "^7.0.0" 225 + } 226 + }, 227 + "node_modules/@babel/helper-string-parser": { 228 + "version": "7.27.1", 229 + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 230 + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 231 + "dev": true, 232 + "license": "MIT", 233 + "engines": { 234 + "node": ">=6.9.0" 235 + } 236 + }, 237 + "node_modules/@babel/helper-validator-identifier": { 238 + "version": "7.28.5", 239 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", 240 + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", 241 + "dev": true, 242 + "license": "MIT", 243 + "engines": { 244 + "node": ">=6.9.0" 245 + } 246 + }, 247 + "node_modules/@babel/helper-validator-option": { 248 + "version": "7.27.1", 249 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 250 + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 251 + "dev": true, 252 + "license": "MIT", 253 + "engines": { 254 + "node": ">=6.9.0" 255 + } 256 + }, 257 + "node_modules/@babel/helpers": { 258 + "version": "7.28.4", 259 + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", 260 + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", 261 + "dev": true, 262 + "license": "MIT", 263 + "dependencies": { 264 + "@babel/template": "^7.27.2", 265 + "@babel/types": "^7.28.4" 266 + }, 267 + "engines": { 268 + "node": ">=6.9.0" 269 + } 270 + }, 271 + "node_modules/@babel/parser": { 272 + "version": "7.28.5", 273 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", 274 + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", 275 + "dev": true, 276 + "license": "MIT", 277 + "dependencies": { 278 + "@babel/types": "^7.28.5" 279 + }, 280 + "bin": { 281 + "parser": "bin/babel-parser.js" 282 + }, 283 + "engines": { 284 + "node": ">=6.0.0" 285 + } 286 + }, 287 + "node_modules/@babel/template": { 288 + "version": "7.27.2", 289 + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", 290 + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", 291 + "dev": true, 292 + "license": "MIT", 293 + "dependencies": { 294 + "@babel/code-frame": "^7.27.1", 295 + "@babel/parser": "^7.27.2", 296 + "@babel/types": "^7.27.1" 297 + }, 298 + "engines": { 299 + "node": ">=6.9.0" 300 + } 301 + }, 302 + "node_modules/@babel/traverse": { 303 + "version": "7.28.5", 304 + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", 305 + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", 306 + "dev": true, 307 + "license": "MIT", 308 + "dependencies": { 309 + "@babel/code-frame": "^7.27.1", 310 + "@babel/generator": "^7.28.5", 311 + "@babel/helper-globals": "^7.28.0", 312 + "@babel/parser": "^7.28.5", 313 + "@babel/template": "^7.27.2", 314 + "@babel/types": "^7.28.5", 315 + "debug": "^4.3.1" 316 + }, 317 + "engines": { 318 + "node": ">=6.9.0" 319 + } 320 + }, 321 + "node_modules/@babel/types": { 322 + "version": "7.28.5", 323 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", 324 + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", 325 + "dev": true, 326 + "license": "MIT", 327 + "dependencies": { 328 + "@babel/helper-string-parser": "^7.27.1", 329 + "@babel/helper-validator-identifier": "^7.28.5" 330 + }, 331 + "engines": { 332 + "node": ">=6.9.0" 333 + } 334 + }, 335 + "node_modules/@bcoe/v8-coverage": { 336 + "version": "1.0.2", 337 + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", 338 + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", 339 + "dev": true, 340 + "license": "MIT", 341 + "engines": { 342 + "node": ">=18" 343 } 344 }, 345 "node_modules/@cloudflare/kv-asset-handler": { ··· 1792 "url": "https://opencollective.com/libvips" 1793 } 1794 }, 1795 + "node_modules/@isaacs/cliui": { 1796 + "version": "8.0.2", 1797 + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", 1798 + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", 1799 + "dev": true, 1800 + "license": "ISC", 1801 + "dependencies": { 1802 + "string-width": "^5.1.2", 1803 + "string-width-cjs": "npm:string-width@^4.2.0", 1804 + "strip-ansi": "^7.0.1", 1805 + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", 1806 + "wrap-ansi": "^8.1.0", 1807 + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" 1808 + }, 1809 + "engines": { 1810 + "node": ">=12" 1811 + } 1812 + }, 1813 + "node_modules/@istanbuljs/schema": { 1814 + "version": "0.1.3", 1815 + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", 1816 + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", 1817 + "dev": true, 1818 + "license": "MIT", 1819 + "engines": { 1820 + "node": ">=8" 1821 + } 1822 + }, 1823 + "node_modules/@jridgewell/gen-mapping": { 1824 + "version": "0.3.13", 1825 + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", 1826 + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", 1827 + "dev": true, 1828 + "license": "MIT", 1829 + "dependencies": { 1830 + "@jridgewell/sourcemap-codec": "^1.5.0", 1831 + "@jridgewell/trace-mapping": "^0.3.24" 1832 + } 1833 + }, 1834 + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { 1835 + "version": "0.3.31", 1836 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 1837 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 1838 + "dev": true, 1839 + "license": "MIT", 1840 + "dependencies": { 1841 + "@jridgewell/resolve-uri": "^3.1.0", 1842 + "@jridgewell/sourcemap-codec": "^1.4.14" 1843 + } 1844 + }, 1845 + "node_modules/@jridgewell/remapping": { 1846 + "version": "2.3.5", 1847 + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", 1848 + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", 1849 + "dev": true, 1850 + "license": "MIT", 1851 + "dependencies": { 1852 + "@jridgewell/gen-mapping": "^0.3.5", 1853 + "@jridgewell/trace-mapping": "^0.3.24" 1854 + } 1855 + }, 1856 + "node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { 1857 + "version": "0.3.31", 1858 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 1859 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 1860 + "dev": true, 1861 + "license": "MIT", 1862 + "dependencies": { 1863 + "@jridgewell/resolve-uri": "^3.1.0", 1864 + "@jridgewell/sourcemap-codec": "^1.4.14" 1865 + } 1866 + }, 1867 "node_modules/@jridgewell/resolve-uri": { 1868 "version": "3.1.2", 1869 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", ··· 1890 "dependencies": { 1891 "@jridgewell/resolve-uri": "^3.0.3", 1892 "@jridgewell/sourcemap-codec": "^1.4.10" 1893 + } 1894 + }, 1895 + "node_modules/@pkgjs/parseargs": { 1896 + "version": "0.11.0", 1897 + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", 1898 + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", 1899 + "dev": true, 1900 + "license": "MIT", 1901 + "optional": true, 1902 + "engines": { 1903 + "node": ">=14" 1904 } 1905 }, 1906 "node_modules/@poppinss/colors": { ··· 2285 "dev": true, 2286 "license": "MIT" 2287 }, 2288 + "node_modules/@vitest/coverage-istanbul": { 2289 + "version": "3.2.4", 2290 + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-3.2.4.tgz", 2291 + "integrity": "sha512-IDlpuFJiWU9rhcKLkpzj8mFu/lpe64gVgnV15ZOrYx1iFzxxrxCzbExiUEKtwwXRvEiEMUS6iZeYgnMxgbqbxQ==", 2292 + "dev": true, 2293 + "license": "MIT", 2294 + "dependencies": { 2295 + "@istanbuljs/schema": "^0.1.3", 2296 + "debug": "^4.4.1", 2297 + "istanbul-lib-coverage": "^3.2.2", 2298 + "istanbul-lib-instrument": "^6.0.3", 2299 + "istanbul-lib-report": "^3.0.1", 2300 + "istanbul-lib-source-maps": "^5.0.6", 2301 + "istanbul-reports": "^3.1.7", 2302 + "magicast": "^0.3.5", 2303 + "test-exclude": "^7.0.1", 2304 + "tinyrainbow": "^2.0.0" 2305 + }, 2306 + "funding": { 2307 + "url": "https://opencollective.com/vitest" 2308 + }, 2309 + "peerDependencies": { 2310 + "vitest": "3.2.4" 2311 + } 2312 + }, 2313 + "node_modules/@vitest/coverage-v8": { 2314 + "version": "3.2.4", 2315 + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", 2316 + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", 2317 + "dev": true, 2318 + "license": "MIT", 2319 + "dependencies": { 2320 + "@ampproject/remapping": "^2.3.0", 2321 + "@bcoe/v8-coverage": "^1.0.2", 2322 + "ast-v8-to-istanbul": "^0.3.3", 2323 + "debug": "^4.4.1", 2324 + "istanbul-lib-coverage": "^3.2.2", 2325 + "istanbul-lib-report": "^3.0.1", 2326 + "istanbul-lib-source-maps": "^5.0.6", 2327 + "istanbul-reports": "^3.1.7", 2328 + "magic-string": "^0.30.17", 2329 + "magicast": "^0.3.5", 2330 + "std-env": "^3.9.0", 2331 + "test-exclude": "^7.0.1", 2332 + "tinyrainbow": "^2.0.0" 2333 + }, 2334 + "funding": { 2335 + "url": "https://opencollective.com/vitest" 2336 + }, 2337 + "peerDependencies": { 2338 + "@vitest/browser": "3.2.4", 2339 + "vitest": "3.2.4" 2340 + }, 2341 + "peerDependenciesMeta": { 2342 + "@vitest/browser": { 2343 + "optional": true 2344 + } 2345 + } 2346 + }, 2347 "node_modules/@vitest/expect": { 2348 "version": "3.2.4", 2349 "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", ··· 2482 "node": ">=0.4.0" 2483 } 2484 }, 2485 + "node_modules/ansi-regex": { 2486 + "version": "6.2.2", 2487 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 2488 + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 2489 + "dev": true, 2490 + "license": "MIT", 2491 + "engines": { 2492 + "node": ">=12" 2493 + }, 2494 + "funding": { 2495 + "url": "https://github.com/chalk/ansi-regex?sponsor=1" 2496 + } 2497 + }, 2498 + "node_modules/ansi-styles": { 2499 + "version": "6.2.3", 2500 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", 2501 + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", 2502 + "dev": true, 2503 + "license": "MIT", 2504 + "engines": { 2505 + "node": ">=12" 2506 + }, 2507 + "funding": { 2508 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 2509 + } 2510 + }, 2511 "node_modules/assertion-error": { 2512 "version": "2.0.1", 2513 "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", ··· 2518 "node": ">=12" 2519 } 2520 }, 2521 + "node_modules/ast-v8-to-istanbul": { 2522 + "version": "0.3.10", 2523 + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", 2524 + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", 2525 + "dev": true, 2526 + "license": "MIT", 2527 + "dependencies": { 2528 + "@jridgewell/trace-mapping": "^0.3.31", 2529 + "estree-walker": "^3.0.3", 2530 + "js-tokens": "^9.0.1" 2531 + } 2532 + }, 2533 + "node_modules/ast-v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { 2534 + "version": "0.3.31", 2535 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 2536 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 2537 + "dev": true, 2538 + "license": "MIT", 2539 + "dependencies": { 2540 + "@jridgewell/resolve-uri": "^3.1.0", 2541 + "@jridgewell/sourcemap-codec": "^1.4.14" 2542 + } 2543 + }, 2544 + "node_modules/balanced-match": { 2545 + "version": "1.0.2", 2546 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 2547 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 2548 + "dev": true, 2549 + "license": "MIT" 2550 + }, 2551 + "node_modules/baseline-browser-mapping": { 2552 + "version": "2.9.11", 2553 + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", 2554 + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", 2555 + "dev": true, 2556 + "license": "Apache-2.0", 2557 + "bin": { 2558 + "baseline-browser-mapping": "dist/cli.js" 2559 + } 2560 + }, 2561 "node_modules/birpc": { 2562 "version": "0.2.14", 2563 "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.14.tgz", ··· 2575 "dev": true, 2576 "license": "MIT" 2577 }, 2578 + "node_modules/brace-expansion": { 2579 + "version": "2.0.2", 2580 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 2581 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 2582 + "dev": true, 2583 + "license": "MIT", 2584 + "dependencies": { 2585 + "balanced-match": "^1.0.0" 2586 + } 2587 + }, 2588 + "node_modules/browserslist": { 2589 + "version": "4.28.1", 2590 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", 2591 + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", 2592 + "dev": true, 2593 + "funding": [ 2594 + { 2595 + "type": "opencollective", 2596 + "url": "https://opencollective.com/browserslist" 2597 + }, 2598 + { 2599 + "type": "tidelift", 2600 + "url": "https://tidelift.com/funding/github/npm/browserslist" 2601 + }, 2602 + { 2603 + "type": "github", 2604 + "url": "https://github.com/sponsors/ai" 2605 + } 2606 + ], 2607 + "license": "MIT", 2608 + "dependencies": { 2609 + "baseline-browser-mapping": "^2.9.0", 2610 + "caniuse-lite": "^1.0.30001759", 2611 + "electron-to-chromium": "^1.5.263", 2612 + "node-releases": "^2.0.27", 2613 + "update-browserslist-db": "^1.2.0" 2614 + }, 2615 + "bin": { 2616 + "browserslist": "cli.js" 2617 + }, 2618 + "engines": { 2619 + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 2620 + } 2621 + }, 2622 "node_modules/cac": { 2623 "version": "6.7.14", 2624 "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", ··· 2628 "engines": { 2629 "node": ">=8" 2630 } 2631 + }, 2632 + "node_modules/caniuse-lite": { 2633 + "version": "1.0.30001761", 2634 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", 2635 + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", 2636 + "dev": true, 2637 + "funding": [ 2638 + { 2639 + "type": "opencollective", 2640 + "url": "https://opencollective.com/browserslist" 2641 + }, 2642 + { 2643 + "type": "tidelift", 2644 + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 2645 + }, 2646 + { 2647 + "type": "github", 2648 + "url": "https://github.com/sponsors/ai" 2649 + } 2650 + ], 2651 + "license": "CC-BY-4.0" 2652 }, 2653 "node_modules/chai": { 2654 "version": "5.3.3", ··· 2729 "simple-swizzle": "^0.2.2" 2730 } 2731 }, 2732 + "node_modules/convert-source-map": { 2733 + "version": "2.0.0", 2734 + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 2735 + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 2736 + "dev": true, 2737 + "license": "MIT" 2738 + }, 2739 "node_modules/cookie": { 2740 "version": "1.1.1", 2741 "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", ··· 2748 "funding": { 2749 "type": "opencollective", 2750 "url": "https://opencollective.com/express" 2751 + } 2752 + }, 2753 + "node_modules/cross-spawn": { 2754 + "version": "7.0.6", 2755 + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 2756 + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 2757 + "dev": true, 2758 + "license": "MIT", 2759 + "dependencies": { 2760 + "path-key": "^3.1.0", 2761 + "shebang-command": "^2.0.0", 2762 + "which": "^2.0.1" 2763 + }, 2764 + "engines": { 2765 + "node": ">= 8" 2766 } 2767 }, 2768 "node_modules/debug": { ··· 2810 "dev": true, 2811 "license": "MIT" 2812 }, 2813 + "node_modules/eastasianwidth": { 2814 + "version": "0.2.0", 2815 + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", 2816 + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", 2817 + "dev": true, 2818 + "license": "MIT" 2819 + }, 2820 + "node_modules/electron-to-chromium": { 2821 + "version": "1.5.267", 2822 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", 2823 + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", 2824 + "dev": true, 2825 + "license": "ISC" 2826 + }, 2827 + "node_modules/emoji-regex": { 2828 + "version": "9.2.2", 2829 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", 2830 + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", 2831 + "dev": true, 2832 + "license": "MIT" 2833 + }, 2834 "node_modules/error-stack-parser-es": { 2835 "version": "1.0.5", 2836 "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", ··· 2890 "@esbuild/win32-x64": "0.27.2" 2891 } 2892 }, 2893 + "node_modules/escalade": { 2894 + "version": "3.2.0", 2895 + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 2896 + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 2897 + "dev": true, 2898 + "license": "MIT", 2899 + "engines": { 2900 + "node": ">=6" 2901 + } 2902 + }, 2903 "node_modules/estree-walker": { 2904 "version": "3.0.3", 2905 "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", ··· 2951 } 2952 } 2953 }, 2954 + "node_modules/foreground-child": { 2955 + "version": "3.3.1", 2956 + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", 2957 + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", 2958 + "dev": true, 2959 + "license": "ISC", 2960 + "dependencies": { 2961 + "cross-spawn": "^7.0.6", 2962 + "signal-exit": "^4.0.1" 2963 + }, 2964 + "engines": { 2965 + "node": ">=14" 2966 + }, 2967 + "funding": { 2968 + "url": "https://github.com/sponsors/isaacs" 2969 + } 2970 + }, 2971 "node_modules/fsevents": { 2972 "version": "2.3.3", 2973 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ··· 2983 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 2984 } 2985 }, 2986 + "node_modules/gensync": { 2987 + "version": "1.0.0-beta.2", 2988 + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 2989 + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 2990 + "dev": true, 2991 + "license": "MIT", 2992 + "engines": { 2993 + "node": ">=6.9.0" 2994 + } 2995 + }, 2996 + "node_modules/glob": { 2997 + "version": "10.5.0", 2998 + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", 2999 + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", 3000 + "dev": true, 3001 + "license": "ISC", 3002 + "dependencies": { 3003 + "foreground-child": "^3.1.0", 3004 + "jackspeak": "^3.1.2", 3005 + "minimatch": "^9.0.4", 3006 + "minipass": "^7.1.2", 3007 + "package-json-from-dist": "^1.0.0", 3008 + "path-scurry": "^1.11.1" 3009 + }, 3010 + "bin": { 3011 + "glob": "dist/esm/bin.mjs" 3012 + }, 3013 + "funding": { 3014 + "url": "https://github.com/sponsors/isaacs" 3015 + } 3016 + }, 3017 "node_modules/glob-to-regexp": { 3018 "version": "0.4.1", 3019 "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", ··· 3021 "dev": true, 3022 "license": "BSD-2-Clause" 3023 }, 3024 + "node_modules/has-flag": { 3025 + "version": "4.0.0", 3026 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 3027 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 3028 + "dev": true, 3029 + "license": "MIT", 3030 + "engines": { 3031 + "node": ">=8" 3032 + } 3033 + }, 3034 + "node_modules/html-escaper": { 3035 + "version": "2.0.2", 3036 + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", 3037 + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", 3038 + "dev": true, 3039 + "license": "MIT" 3040 + }, 3041 "node_modules/is-arrayish": { 3042 "version": "0.3.4", 3043 "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", ··· 3045 "dev": true, 3046 "license": "MIT" 3047 }, 3048 + "node_modules/is-fullwidth-code-point": { 3049 + "version": "3.0.0", 3050 + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 3051 + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 3052 + "dev": true, 3053 + "license": "MIT", 3054 + "engines": { 3055 + "node": ">=8" 3056 + } 3057 + }, 3058 + "node_modules/isexe": { 3059 + "version": "2.0.0", 3060 + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 3061 + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 3062 + "dev": true, 3063 + "license": "ISC" 3064 + }, 3065 + "node_modules/istanbul-lib-coverage": { 3066 + "version": "3.2.2", 3067 + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", 3068 + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", 3069 + "dev": true, 3070 + "license": "BSD-3-Clause", 3071 + "engines": { 3072 + "node": ">=8" 3073 + } 3074 + }, 3075 + "node_modules/istanbul-lib-instrument": { 3076 + "version": "6.0.3", 3077 + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", 3078 + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", 3079 + "dev": true, 3080 + "license": "BSD-3-Clause", 3081 + "dependencies": { 3082 + "@babel/core": "^7.23.9", 3083 + "@babel/parser": "^7.23.9", 3084 + "@istanbuljs/schema": "^0.1.3", 3085 + "istanbul-lib-coverage": "^3.2.0", 3086 + "semver": "^7.5.4" 3087 + }, 3088 + "engines": { 3089 + "node": ">=10" 3090 + } 3091 + }, 3092 + "node_modules/istanbul-lib-report": { 3093 + "version": "3.0.1", 3094 + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", 3095 + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", 3096 + "dev": true, 3097 + "license": "BSD-3-Clause", 3098 + "dependencies": { 3099 + "istanbul-lib-coverage": "^3.0.0", 3100 + "make-dir": "^4.0.0", 3101 + "supports-color": "^7.1.0" 3102 + }, 3103 + "engines": { 3104 + "node": ">=10" 3105 + } 3106 + }, 3107 + "node_modules/istanbul-lib-report/node_modules/supports-color": { 3108 + "version": "7.2.0", 3109 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 3110 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 3111 + "dev": true, 3112 + "license": "MIT", 3113 + "dependencies": { 3114 + "has-flag": "^4.0.0" 3115 + }, 3116 + "engines": { 3117 + "node": ">=8" 3118 + } 3119 + }, 3120 + "node_modules/istanbul-lib-source-maps": { 3121 + "version": "5.0.6", 3122 + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", 3123 + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", 3124 + "dev": true, 3125 + "license": "BSD-3-Clause", 3126 + "dependencies": { 3127 + "@jridgewell/trace-mapping": "^0.3.23", 3128 + "debug": "^4.1.1", 3129 + "istanbul-lib-coverage": "^3.0.0" 3130 + }, 3131 + "engines": { 3132 + "node": ">=10" 3133 + } 3134 + }, 3135 + "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": { 3136 + "version": "0.3.31", 3137 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 3138 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 3139 + "dev": true, 3140 + "license": "MIT", 3141 + "dependencies": { 3142 + "@jridgewell/resolve-uri": "^3.1.0", 3143 + "@jridgewell/sourcemap-codec": "^1.4.14" 3144 + } 3145 + }, 3146 + "node_modules/istanbul-reports": { 3147 + "version": "3.2.0", 3148 + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", 3149 + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", 3150 + "dev": true, 3151 + "license": "BSD-3-Clause", 3152 + "dependencies": { 3153 + "html-escaper": "^2.0.0", 3154 + "istanbul-lib-report": "^3.0.0" 3155 + }, 3156 + "engines": { 3157 + "node": ">=8" 3158 + } 3159 + }, 3160 + "node_modules/jackspeak": { 3161 + "version": "3.4.3", 3162 + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", 3163 + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", 3164 + "dev": true, 3165 + "license": "BlueOak-1.0.0", 3166 + "dependencies": { 3167 + "@isaacs/cliui": "^8.0.2" 3168 + }, 3169 + "funding": { 3170 + "url": "https://github.com/sponsors/isaacs" 3171 + }, 3172 + "optionalDependencies": { 3173 + "@pkgjs/parseargs": "^0.11.0" 3174 + } 3175 + }, 3176 "node_modules/js-tokens": { 3177 "version": "9.0.1", 3178 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", ··· 3180 "dev": true, 3181 "license": "MIT" 3182 }, 3183 + "node_modules/jsesc": { 3184 + "version": "3.1.0", 3185 + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", 3186 + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", 3187 + "dev": true, 3188 + "license": "MIT", 3189 + "bin": { 3190 + "jsesc": "bin/jsesc" 3191 + }, 3192 + "engines": { 3193 + "node": ">=6" 3194 + } 3195 + }, 3196 + "node_modules/json5": { 3197 + "version": "2.2.3", 3198 + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 3199 + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 3200 + "dev": true, 3201 + "license": "MIT", 3202 + "bin": { 3203 + "json5": "lib/cli.js" 3204 + }, 3205 + "engines": { 3206 + "node": ">=6" 3207 + } 3208 + }, 3209 "node_modules/kleur": { 3210 "version": "4.1.5", 3211 "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", ··· 3223 "dev": true, 3224 "license": "MIT" 3225 }, 3226 + "node_modules/lru-cache": { 3227 + "version": "10.4.3", 3228 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", 3229 + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", 3230 + "dev": true, 3231 + "license": "ISC" 3232 + }, 3233 "node_modules/magic-string": { 3234 "version": "0.30.21", 3235 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", ··· 3240 "@jridgewell/sourcemap-codec": "^1.5.5" 3241 } 3242 }, 3243 + "node_modules/magicast": { 3244 + "version": "0.3.5", 3245 + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", 3246 + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", 3247 + "dev": true, 3248 + "license": "MIT", 3249 + "dependencies": { 3250 + "@babel/parser": "^7.25.4", 3251 + "@babel/types": "^7.25.4", 3252 + "source-map-js": "^1.2.0" 3253 + } 3254 + }, 3255 + "node_modules/make-dir": { 3256 + "version": "4.0.0", 3257 + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", 3258 + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", 3259 + "dev": true, 3260 + "license": "MIT", 3261 + "dependencies": { 3262 + "semver": "^7.5.3" 3263 + }, 3264 + "engines": { 3265 + "node": ">=10" 3266 + }, 3267 + "funding": { 3268 + "url": "https://github.com/sponsors/sindresorhus" 3269 + } 3270 + }, 3271 "node_modules/mime": { 3272 "version": "3.0.0", 3273 "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", ··· 3318 "url": "https://github.com/sponsors/colinhacks" 3319 } 3320 }, 3321 + "node_modules/minimatch": { 3322 + "version": "9.0.5", 3323 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 3324 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 3325 + "dev": true, 3326 + "license": "ISC", 3327 + "dependencies": { 3328 + "brace-expansion": "^2.0.1" 3329 + }, 3330 + "engines": { 3331 + "node": ">=16 || 14 >=14.17" 3332 + }, 3333 + "funding": { 3334 + "url": "https://github.com/sponsors/isaacs" 3335 + } 3336 + }, 3337 + "node_modules/minipass": { 3338 + "version": "7.1.2", 3339 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", 3340 + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", 3341 + "dev": true, 3342 + "license": "ISC", 3343 + "engines": { 3344 + "node": ">=16 || 14 >=14.17" 3345 + } 3346 + }, 3347 "node_modules/ms": { 3348 "version": "2.1.3", 3349 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", ··· 3376 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 3377 } 3378 }, 3379 + "node_modules/node-releases": { 3380 + "version": "2.0.27", 3381 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", 3382 + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", 3383 + "dev": true, 3384 + "license": "MIT" 3385 + }, 3386 + "node_modules/package-json-from-dist": { 3387 + "version": "1.0.1", 3388 + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", 3389 + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", 3390 + "dev": true, 3391 + "license": "BlueOak-1.0.0" 3392 + }, 3393 + "node_modules/path-key": { 3394 + "version": "3.1.1", 3395 + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 3396 + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 3397 + "dev": true, 3398 + "license": "MIT", 3399 + "engines": { 3400 + "node": ">=8" 3401 + } 3402 + }, 3403 + "node_modules/path-scurry": { 3404 + "version": "1.11.1", 3405 + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", 3406 + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", 3407 + "dev": true, 3408 + "license": "BlueOak-1.0.0", 3409 + "dependencies": { 3410 + "lru-cache": "^10.2.0", 3411 + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" 3412 + }, 3413 + "engines": { 3414 + "node": ">=16 || 14 >=14.18" 3415 + }, 3416 + "funding": { 3417 + "url": "https://github.com/sponsors/isaacs" 3418 + } 3419 + }, 3420 "node_modules/path-to-regexp": { 3421 "version": "6.3.0", 3422 "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", ··· 3585 "@img/sharp-win32-x64": "0.33.5" 3586 } 3587 }, 3588 + "node_modules/shebang-command": { 3589 + "version": "2.0.0", 3590 + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 3591 + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 3592 + "dev": true, 3593 + "license": "MIT", 3594 + "dependencies": { 3595 + "shebang-regex": "^3.0.0" 3596 + }, 3597 + "engines": { 3598 + "node": ">=8" 3599 + } 3600 + }, 3601 + "node_modules/shebang-regex": { 3602 + "version": "3.0.0", 3603 + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 3604 + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 3605 + "dev": true, 3606 + "license": "MIT", 3607 + "engines": { 3608 + "node": ">=8" 3609 + } 3610 + }, 3611 "node_modules/siginfo": { 3612 "version": "2.0.0", 3613 "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", ··· 3615 "dev": true, 3616 "license": "ISC" 3617 }, 3618 + "node_modules/signal-exit": { 3619 + "version": "4.1.0", 3620 + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 3621 + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 3622 + "dev": true, 3623 + "license": "ISC", 3624 + "engines": { 3625 + "node": ">=14" 3626 + }, 3627 + "funding": { 3628 + "url": "https://github.com/sponsors/isaacs" 3629 + } 3630 + }, 3631 "node_modules/simple-swizzle": { 3632 "version": "0.2.4", 3633 "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", ··· 3673 "npm": ">=6" 3674 } 3675 }, 3676 + "node_modules/string-width": { 3677 + "version": "5.1.2", 3678 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", 3679 + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", 3680 + "dev": true, 3681 + "license": "MIT", 3682 + "dependencies": { 3683 + "eastasianwidth": "^0.2.0", 3684 + "emoji-regex": "^9.2.2", 3685 + "strip-ansi": "^7.0.1" 3686 + }, 3687 + "engines": { 3688 + "node": ">=12" 3689 + }, 3690 + "funding": { 3691 + "url": "https://github.com/sponsors/sindresorhus" 3692 + } 3693 + }, 3694 + "node_modules/string-width-cjs": { 3695 + "name": "string-width", 3696 + "version": "4.2.3", 3697 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 3698 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 3699 + "dev": true, 3700 + "license": "MIT", 3701 + "dependencies": { 3702 + "emoji-regex": "^8.0.0", 3703 + "is-fullwidth-code-point": "^3.0.0", 3704 + "strip-ansi": "^6.0.1" 3705 + }, 3706 + "engines": { 3707 + "node": ">=8" 3708 + } 3709 + }, 3710 + "node_modules/string-width-cjs/node_modules/ansi-regex": { 3711 + "version": "5.0.1", 3712 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 3713 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 3714 + "dev": true, 3715 + "license": "MIT", 3716 + "engines": { 3717 + "node": ">=8" 3718 + } 3719 + }, 3720 + "node_modules/string-width-cjs/node_modules/emoji-regex": { 3721 + "version": "8.0.0", 3722 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 3723 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 3724 + "dev": true, 3725 + "license": "MIT" 3726 + }, 3727 + "node_modules/string-width-cjs/node_modules/strip-ansi": { 3728 + "version": "6.0.1", 3729 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 3730 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 3731 + "dev": true, 3732 + "license": "MIT", 3733 + "dependencies": { 3734 + "ansi-regex": "^5.0.1" 3735 + }, 3736 + "engines": { 3737 + "node": ">=8" 3738 + } 3739 + }, 3740 + "node_modules/strip-ansi": { 3741 + "version": "7.1.2", 3742 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 3743 + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 3744 + "dev": true, 3745 + "license": "MIT", 3746 + "dependencies": { 3747 + "ansi-regex": "^6.0.1" 3748 + }, 3749 + "engines": { 3750 + "node": ">=12" 3751 + }, 3752 + "funding": { 3753 + "url": "https://github.com/chalk/strip-ansi?sponsor=1" 3754 + } 3755 + }, 3756 + "node_modules/strip-ansi-cjs": { 3757 + "name": "strip-ansi", 3758 + "version": "6.0.1", 3759 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 3760 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 3761 + "dev": true, 3762 + "license": "MIT", 3763 + "dependencies": { 3764 + "ansi-regex": "^5.0.1" 3765 + }, 3766 + "engines": { 3767 + "node": ">=8" 3768 + } 3769 + }, 3770 + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { 3771 + "version": "5.0.1", 3772 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 3773 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 3774 + "dev": true, 3775 + "license": "MIT", 3776 + "engines": { 3777 + "node": ">=8" 3778 + } 3779 + }, 3780 "node_modules/strip-literal": { 3781 "version": "3.1.0", 3782 "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", ··· 3803 "url": "https://github.com/chalk/supports-color?sponsor=1" 3804 } 3805 }, 3806 + "node_modules/test-exclude": { 3807 + "version": "7.0.1", 3808 + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", 3809 + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", 3810 + "dev": true, 3811 + "license": "ISC", 3812 + "dependencies": { 3813 + "@istanbuljs/schema": "^0.1.2", 3814 + "glob": "^10.4.1", 3815 + "minimatch": "^9.0.4" 3816 + }, 3817 + "engines": { 3818 + "node": ">=18" 3819 + } 3820 + }, 3821 "node_modules/tinybench": { 3822 "version": "2.9.0", 3823 "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", ··· 3919 "license": "MIT", 3920 "dependencies": { 3921 "pathe": "^2.0.3" 3922 + } 3923 + }, 3924 + "node_modules/update-browserslist-db": { 3925 + "version": "1.2.3", 3926 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", 3927 + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", 3928 + "dev": true, 3929 + "funding": [ 3930 + { 3931 + "type": "opencollective", 3932 + "url": "https://opencollective.com/browserslist" 3933 + }, 3934 + { 3935 + "type": "tidelift", 3936 + "url": "https://tidelift.com/funding/github/npm/browserslist" 3937 + }, 3938 + { 3939 + "type": "github", 3940 + "url": "https://github.com/sponsors/ai" 3941 + } 3942 + ], 3943 + "license": "MIT", 3944 + "dependencies": { 3945 + "escalade": "^3.2.0", 3946 + "picocolors": "^1.1.1" 3947 + }, 3948 + "bin": { 3949 + "update-browserslist-db": "cli.js" 3950 + }, 3951 + "peerDependencies": { 3952 + "browserslist": ">= 4.21.0" 3953 } 3954 }, 3955 "node_modules/vite": { ··· 4121 "jsdom": { 4122 "optional": true 4123 } 4124 + } 4125 + }, 4126 + "node_modules/which": { 4127 + "version": "2.0.2", 4128 + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 4129 + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 4130 + "dev": true, 4131 + "license": "ISC", 4132 + "dependencies": { 4133 + "isexe": "^2.0.0" 4134 + }, 4135 + "bin": { 4136 + "node-which": "bin/node-which" 4137 + }, 4138 + "engines": { 4139 + "node": ">= 8" 4140 } 4141 }, 4142 "node_modules/why-is-node-running": { ··· 4713 "@esbuild/win32-x64": "0.27.0" 4714 } 4715 }, 4716 + "node_modules/wrap-ansi": { 4717 + "version": "8.1.0", 4718 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", 4719 + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", 4720 + "dev": true, 4721 + "license": "MIT", 4722 + "dependencies": { 4723 + "ansi-styles": "^6.1.0", 4724 + "string-width": "^5.0.1", 4725 + "strip-ansi": "^7.0.1" 4726 + }, 4727 + "engines": { 4728 + "node": ">=12" 4729 + }, 4730 + "funding": { 4731 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 4732 + } 4733 + }, 4734 + "node_modules/wrap-ansi-cjs": { 4735 + "name": "wrap-ansi", 4736 + "version": "7.0.0", 4737 + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 4738 + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 4739 + "dev": true, 4740 + "license": "MIT", 4741 + "dependencies": { 4742 + "ansi-styles": "^4.0.0", 4743 + "string-width": "^4.1.0", 4744 + "strip-ansi": "^6.0.0" 4745 + }, 4746 + "engines": { 4747 + "node": ">=10" 4748 + }, 4749 + "funding": { 4750 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 4751 + } 4752 + }, 4753 + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { 4754 + "version": "5.0.1", 4755 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 4756 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 4757 + "dev": true, 4758 + "license": "MIT", 4759 + "engines": { 4760 + "node": ">=8" 4761 + } 4762 + }, 4763 + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { 4764 + "version": "4.3.0", 4765 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 4766 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 4767 + "dev": true, 4768 + "license": "MIT", 4769 + "dependencies": { 4770 + "color-convert": "^2.0.1" 4771 + }, 4772 + "engines": { 4773 + "node": ">=8" 4774 + }, 4775 + "funding": { 4776 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 4777 + } 4778 + }, 4779 + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { 4780 + "version": "8.0.0", 4781 + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 4782 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 4783 + "dev": true, 4784 + "license": "MIT" 4785 + }, 4786 + "node_modules/wrap-ansi-cjs/node_modules/string-width": { 4787 + "version": "4.2.3", 4788 + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 4789 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 4790 + "dev": true, 4791 + "license": "MIT", 4792 + "dependencies": { 4793 + "emoji-regex": "^8.0.0", 4794 + "is-fullwidth-code-point": "^3.0.0", 4795 + "strip-ansi": "^6.0.1" 4796 + }, 4797 + "engines": { 4798 + "node": ">=8" 4799 + } 4800 + }, 4801 + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { 4802 + "version": "6.0.1", 4803 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 4804 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 4805 + "dev": true, 4806 + "license": "MIT", 4807 + "dependencies": { 4808 + "ansi-regex": "^5.0.1" 4809 + }, 4810 + "engines": { 4811 + "node": ">=8" 4812 + } 4813 + }, 4814 "node_modules/ws": { 4815 "version": "8.18.0", 4816 "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", ··· 4832 "optional": true 4833 } 4834 } 4835 + }, 4836 + "node_modules/yallist": { 4837 + "version": "3.1.1", 4838 + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 4839 + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 4840 + "dev": true, 4841 + "license": "ISC" 4842 }, 4843 "node_modules/youch": { 4844 "version": "4.1.0-beta.10",
+2
package.json
··· 11 }, 12 "devDependencies": { 13 "@cloudflare/vitest-pool-workers": "^0.11.1", 14 "typescript": "^5.5.2", 15 "vitest": "^3.2.4", 16 "wrangler": "^4.21.2"
··· 11 }, 12 "devDependencies": { 13 "@cloudflare/vitest-pool-workers": "^0.11.1", 14 + "@vitest/coverage-istanbul": "^3.2.4", 15 + "@vitest/coverage-v8": "^3.2.4", 16 "typescript": "^5.5.2", 17 "vitest": "^3.2.4", 18 "wrangler": "^4.21.2"
+10
src/index.ts
··· 256 } 257 } 258 259 export default { 260 async fetch( 261 request: Request,
··· 256 } 257 } 258 259 + // Export helper functions for testing 260 + export { 261 + base62ToBytes, 262 + detectIdentifierFormat, 263 + resolveHandleToDID, 264 + resolvePDSHost, 265 + fetchBlobCidFromRecord, 266 + downloadBlobUnauthenticated, 267 + }; 268 + 269 export default { 270 async fetch( 271 request: Request,
+769 -18
test/index.spec.ts
··· 1 import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test'; 2 - import { describe, it, expect } from 'vitest'; 3 - import worker from '../src/index'; 4 5 - // For now, you'll need to do something like this to get a correctly-typed 6 - // `Request` to pass to `worker.fetch()`. 7 const IncomingRequest = Request<unknown, IncomingRequestCfProperties>; 8 9 - describe('Hello World worker', () => { 10 - it('responds with Hello World! (unit style)', async () => { 11 - const request = new IncomingRequest('http://example.com'); 12 - // Create an empty context to pass to `worker.fetch()`. 13 - const ctx = createExecutionContext(); 14 - const response = await worker.fetch(request, env, ctx); 15 - // Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions 16 - await waitOnExecutionContext(ctx); 17 - expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); 18 - }); 19 20 - it('responds with Hello World! (integration style)', async () => { 21 - const response = await SELF.fetch('https://example.com'); 22 - expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); 23 - }); 24 });
··· 1 import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test'; 2 + import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; 3 + import { CID } from 'multiformats/cid'; 4 + import worker, { 5 + base62ToBytes, 6 + detectIdentifierFormat, 7 + resolveHandleToDID, 8 + resolvePDSHost, 9 + fetchBlobCidFromRecord, 10 + downloadBlobUnauthenticated, 11 + } from '../src/index'; 12 13 const IncomingRequest = Request<unknown, IncomingRequestCfProperties>; 14 15 + // Test constants 16 + const TEST_DID = 'did:plc:ewvi7nxzyoun6zhxrhs64oiz'; 17 + const TEST_HANDLE = 'bsky.app'; 18 + const TEST_CID = 'bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku'; 19 + const TEST_TID = '3jui7kd5354sr'; 20 + const TEST_PDS = 'https://bsky.social'; 21 + 22 + describe('base62ToBytes', () => { 23 + it('converts empty string to empty Uint8Array', () => { 24 + const result = base62ToBytes(''); 25 + expect(result).toEqual(new Uint8Array([])); 26 + }); 27 + 28 + it('converts single character correctly', () => { 29 + const result = base62ToBytes('1'); 30 + expect(result).toEqual(new Uint8Array([1])); 31 + }); 32 + 33 + it('converts known base62 to correct bytes', () => { 34 + // Test with "10" which is 62 in decimal (1*62 + 0) 35 + const result = base62ToBytes('10'); 36 + expect(result).toEqual(new Uint8Array([62])); 37 + }); 38 + 39 + it('handles larger numbers correctly', () => { 40 + // "100" = 1*62^2 + 0*62 + 0 = 3844 41 + const result = base62ToBytes('100'); 42 + // 3844 = 0x0F04, so bytes are [15, 4] 43 + expect(result).toEqual(new Uint8Array([15, 4])); 44 + }); 45 + 46 + it('round-trips through CID decode', () => { 47 + // Create a valid CID, encode to base62-ish bytes, then decode 48 + const cid = CID.parse(TEST_CID); 49 + const bytes = cid.bytes; 50 + 51 + // Encode bytes to base62 52 + const BASE62_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 53 + let num = 0n; 54 + for (const byte of bytes) { 55 + num = num * 256n + BigInt(byte); 56 + } 57 + let base62 = ''; 58 + while (num > 0n) { 59 + base62 = BASE62_CHARS[Number(num % 62n)] + base62; 60 + num = num / 62n; 61 + } 62 + 63 + // Now decode back using our function 64 + const decoded = base62ToBytes(base62); 65 + const decodedCid = CID.decode(decoded); 66 + expect(decodedCid.toString()).toBe(TEST_CID); 67 + }); 68 + }); 69 + 70 + describe('detectIdentifierFormat', () => { 71 + it('detects base32 CID format (bafkrei prefix)', () => { 72 + expect(detectIdentifierFormat('bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku')).toBe('base32'); 73 + expect(detectIdentifierFormat('bafkreiabc')).toBe('base32'); 74 + }); 75 + 76 + it('detects TID format (13 chars matching pattern)', () => { 77 + expect(detectIdentifierFormat('3jui7kd5354sr')).toBe('tid'); 78 + expect(detectIdentifierFormat('3kfg2b5fyjk2i')).toBe('tid'); 79 + }); 80 + 81 + it('returns base62 for other formats', () => { 82 + expect(detectIdentifierFormat('abc123XYZ')).toBe('base62'); 83 + expect(detectIdentifierFormat('shortCID')).toBe('base62'); 84 + }); 85 + 86 + it('returns base62 for invalid TID length', () => { 87 + // Too short 88 + expect(detectIdentifierFormat('3jui7kd535')).toBe('base62'); 89 + // Too long 90 + expect(detectIdentifierFormat('3jui7kd5354srx')).toBe('base62'); 91 + }); 92 + 93 + it('returns base62 for invalid TID characters', () => { 94 + // Contains invalid first character (0, 1, or other invalid) 95 + expect(detectIdentifierFormat('0jui7kd5354sr')).toBe('base62'); 96 + expect(detectIdentifierFormat('1jui7kd5354sr')).toBe('base62'); 97 + }); 98 + }); 99 + 100 + describe('resolveHandleToDID', () => { 101 + beforeEach(() => { 102 + vi.stubGlobal('fetch', vi.fn()); 103 + }); 104 + 105 + afterEach(() => { 106 + vi.unstubAllGlobals(); 107 + }); 108 + 109 + it('resolves DID via DNS-over-HTTPS', async () => { 110 + const mockFetch = vi.fn().mockResolvedValueOnce({ 111 + ok: true, 112 + json: async () => ({ 113 + Answer: [ 114 + { type: 16, data: `"did=${TEST_DID}"` } 115 + ] 116 + }) 117 + }); 118 + vi.stubGlobal('fetch', mockFetch); 119 + 120 + const did = await resolveHandleToDID(TEST_HANDLE); 121 + expect(did).toBe(TEST_DID); 122 + expect(mockFetch).toHaveBeenCalledWith( 123 + expect.stringContaining(`_atproto.${TEST_HANDLE}`), 124 + expect.any(Object) 125 + ); 126 + }); 127 + 128 + it('falls back to HTTPS well-known when DNS fails', async () => { 129 + const mockFetch = vi.fn() 130 + // First call (DNS) fails 131 + .mockResolvedValueOnce({ 132 + ok: false 133 + }) 134 + // Second call (well-known) succeeds 135 + .mockResolvedValueOnce({ 136 + status: 200, 137 + text: async () => TEST_DID 138 + }); 139 + vi.stubGlobal('fetch', mockFetch); 140 + 141 + const did = await resolveHandleToDID(TEST_HANDLE); 142 + expect(did).toBe(TEST_DID); 143 + expect(mockFetch).toHaveBeenCalledTimes(2); 144 + }); 145 + 146 + it('returns null when all resolution methods fail', async () => { 147 + const mockFetch = vi.fn() 148 + .mockResolvedValueOnce({ ok: false }) 149 + .mockResolvedValueOnce({ status: 404 }); 150 + vi.stubGlobal('fetch', mockFetch); 151 + 152 + const did = await resolveHandleToDID('nonexistent.handle'); 153 + expect(did).toBeNull(); 154 + }); 155 + 156 + it('extracts DID from DNS response with quotes', async () => { 157 + const mockFetch = vi.fn().mockResolvedValueOnce({ 158 + ok: true, 159 + json: async () => ({ 160 + Answer: [ 161 + { type: 16, data: '"did=did:plc:z72i7hdynmk6r22z27h6tvur"' } 162 + ] 163 + }) 164 + }); 165 + vi.stubGlobal('fetch', mockFetch); 166 + 167 + const did = await resolveHandleToDID('example.com'); 168 + expect(did).toBe('did:plc:z72i7hdynmk6r22z27h6tvur'); 169 + }); 170 + 171 + it('resolves DID via native DNS when available', async () => { 172 + const mockResolveDns = vi.fn().mockResolvedValue(['did=did:plc:nativedns123']); 173 + vi.stubGlobal('resolveDns', mockResolveDns); 174 + 175 + const did = await resolveHandleToDID('native.test'); 176 + expect(did).toBe('did:plc:nativedns123'); 177 + expect(mockResolveDns).toHaveBeenCalledWith('_atproto.native.test', 'TXT'); 178 + 179 + vi.unstubAllGlobals(); 180 + }); 181 + 182 + it('handles native DNS throwing exception', async () => { 183 + const mockResolveDns = vi.fn().mockRejectedValue(new Error('DNS error')); 184 + vi.stubGlobal('resolveDns', mockResolveDns); 185 + 186 + // DNS throws, should fall back to DNS-over-HTTPS 187 + const mockFetch = vi.fn().mockResolvedValueOnce({ 188 + ok: true, 189 + json: async () => ({ 190 + Answer: [{ type: 16, data: '"did=did:plc:fallback123"' }] 191 + }) 192 + }); 193 + vi.stubGlobal('fetch', mockFetch); 194 + 195 + const did = await resolveHandleToDID('fallback.test'); 196 + expect(did).toBe('did:plc:fallback123'); 197 + 198 + vi.unstubAllGlobals(); 199 + }); 200 + 201 + it('handles DNS-over-HTTPS fetch exception', async () => { 202 + const mockFetch = vi.fn() 203 + // DNS-over-HTTPS throws 204 + .mockRejectedValueOnce(new Error('Network error')) 205 + // HTTPS well-known succeeds 206 + .mockResolvedValueOnce({ 207 + status: 200, 208 + text: async () => 'did:plc:wellknown123' 209 + }); 210 + vi.stubGlobal('fetch', mockFetch); 211 + 212 + const did = await resolveHandleToDID('wellknown.test'); 213 + expect(did).toBe('did:plc:wellknown123'); 214 + }); 215 + 216 + it('handles HTTPS well-known fetch exception', async () => { 217 + const mockFetch = vi.fn() 218 + // DNS-over-HTTPS fails 219 + .mockResolvedValueOnce({ ok: false }) 220 + // HTTPS well-known throws 221 + .mockRejectedValueOnce(new Error('Connection refused')); 222 + vi.stubGlobal('fetch', mockFetch); 223 + 224 + const did = await resolveHandleToDID('error.test'); 225 + expect(did).toBeNull(); 226 + }); 227 + }); 228 + 229 + describe('resolvePDSHost', () => { 230 + beforeEach(() => { 231 + vi.stubGlobal('fetch', vi.fn()); 232 + }); 233 + 234 + afterEach(() => { 235 + vi.unstubAllGlobals(); 236 + }); 237 + 238 + it('resolves PDS host for did:plc via PLC directory', async () => { 239 + const mockFetch = vi.fn().mockResolvedValueOnce({ 240 + ok: true, 241 + json: async () => ({ 242 + service: [ 243 + { id: '#atproto_pds', type: 'AtprotoPersonalDataServer', serviceEndpoint: TEST_PDS } 244 + ] 245 + }) 246 + }); 247 + vi.stubGlobal('fetch', mockFetch); 248 + 249 + const pdsHost = await resolvePDSHost(TEST_DID); 250 + expect(pdsHost).toBe(TEST_PDS); 251 + expect(mockFetch).toHaveBeenCalled(); 252 + }); 253 + 254 + it('resolves PDS host for did:web via well-known', async () => { 255 + const webDid = 'did:web:example.com'; 256 + const mockFetch = vi.fn().mockResolvedValueOnce({ 257 + ok: true, 258 + json: async () => ({ 259 + service: [ 260 + { id: '#atproto_pds', type: 'AtprotoPersonalDataServer', serviceEndpoint: 'https://pds.example.com' } 261 + ] 262 + }) 263 + }); 264 + vi.stubGlobal('fetch', mockFetch); 265 + 266 + const pdsHost = await resolvePDSHost(webDid); 267 + expect(pdsHost).toBe('https://pds.example.com'); 268 + expect(mockFetch).toHaveBeenCalled(); 269 + }); 270 + 271 + it('returns null when PDS not found', async () => { 272 + const mockFetch = vi.fn().mockResolvedValueOnce({ 273 + ok: false 274 + }); 275 + vi.stubGlobal('fetch', mockFetch); 276 + 277 + const pdsHost = await resolvePDSHost(TEST_DID); 278 + expect(pdsHost).toBeNull(); 279 + }); 280 + 281 + it('returns null when service array is missing', async () => { 282 + const mockFetch = vi.fn().mockResolvedValueOnce({ 283 + ok: true, 284 + json: async () => ({}) 285 + }); 286 + vi.stubGlobal('fetch', mockFetch); 287 + 288 + const pdsHost = await resolvePDSHost(TEST_DID); 289 + expect(pdsHost).toBeNull(); 290 + }); 291 + 292 + it('returns null when fetch throws exception', async () => { 293 + const mockFetch = vi.fn().mockRejectedValueOnce(new Error('Network error')); 294 + vi.stubGlobal('fetch', mockFetch); 295 + 296 + const pdsHost = await resolvePDSHost(TEST_DID); 297 + expect(pdsHost).toBeNull(); 298 + }); 299 + }); 300 + 301 + describe('fetchBlobCidFromRecord', () => { 302 + beforeEach(() => { 303 + vi.stubGlobal('fetch', vi.fn()); 304 + }); 305 + 306 + afterEach(() => { 307 + vi.unstubAllGlobals(); 308 + }); 309 + 310 + it('fetches blob CID from record', async () => { 311 + const mockFetch = vi.fn() 312 + // First call: resolvePDSHost 313 + .mockResolvedValueOnce({ 314 + ok: true, 315 + json: async () => ({ 316 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 317 + }) 318 + }) 319 + // Second call: getRecord 320 + .mockResolvedValueOnce({ 321 + ok: true, 322 + json: async () => ({ 323 + uri: `at://${TEST_DID}/blue.imgs.blup.image/${TEST_TID}`, 324 + cid: 'somecid', 325 + value: { 326 + blob: { 327 + ref: { $link: TEST_CID } 328 + } 329 + } 330 + }) 331 + }); 332 + vi.stubGlobal('fetch', mockFetch); 333 + 334 + const blobCid = await fetchBlobCidFromRecord(TEST_DID, TEST_TID); 335 + expect(blobCid).toBe(TEST_CID); 336 + }); 337 + 338 + it('returns null when PDS resolution fails', async () => { 339 + const mockFetch = vi.fn().mockResolvedValueOnce({ 340 + ok: false 341 + }); 342 + vi.stubGlobal('fetch', mockFetch); 343 + 344 + const blobCid = await fetchBlobCidFromRecord(TEST_DID, TEST_TID); 345 + expect(blobCid).toBeNull(); 346 + }); 347 + 348 + it('returns null when record has no blob', async () => { 349 + const mockFetch = vi.fn() 350 + .mockResolvedValueOnce({ 351 + ok: true, 352 + json: async () => ({ 353 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 354 + }) 355 + }) 356 + .mockResolvedValueOnce({ 357 + ok: true, 358 + json: async () => ({ 359 + uri: `at://${TEST_DID}/blue.imgs.blup.image/${TEST_TID}`, 360 + cid: 'somecid', 361 + value: {} 362 + }) 363 + }); 364 + vi.stubGlobal('fetch', mockFetch); 365 + 366 + const blobCid = await fetchBlobCidFromRecord(TEST_DID, TEST_TID); 367 + expect(blobCid).toBeNull(); 368 + }); 369 + 370 + it('returns null when getRecord returns non-OK response', async () => { 371 + const mockFetch = vi.fn() 372 + .mockResolvedValueOnce({ 373 + ok: true, 374 + json: async () => ({ 375 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 376 + }) 377 + }) 378 + .mockResolvedValueOnce({ 379 + ok: false, 380 + status: 404 381 + }); 382 + vi.stubGlobal('fetch', mockFetch); 383 + 384 + const blobCid = await fetchBlobCidFromRecord(TEST_DID, TEST_TID); 385 + expect(blobCid).toBeNull(); 386 + }); 387 + 388 + it('returns null when getRecord fetch throws exception', async () => { 389 + const mockFetch = vi.fn() 390 + .mockResolvedValueOnce({ 391 + ok: true, 392 + json: async () => ({ 393 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 394 + }) 395 + }) 396 + .mockRejectedValueOnce(new Error('Network error')); 397 + vi.stubGlobal('fetch', mockFetch); 398 + 399 + const blobCid = await fetchBlobCidFromRecord(TEST_DID, TEST_TID); 400 + expect(blobCid).toBeNull(); 401 + }); 402 + }); 403 + 404 + describe('downloadBlobUnauthenticated', () => { 405 + beforeEach(() => { 406 + vi.stubGlobal('fetch', vi.fn()); 407 + }); 408 + 409 + afterEach(() => { 410 + vi.unstubAllGlobals(); 411 + }); 412 + 413 + it('downloads blob with correct headers', async () => { 414 + const mockFetch = vi.fn() 415 + // resolvePDSHost 416 + .mockResolvedValueOnce({ 417 + ok: true, 418 + json: async () => ({ 419 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 420 + }) 421 + }) 422 + // getBlob 423 + .mockResolvedValueOnce({ 424 + status: 200, 425 + body: new ReadableStream(), 426 + headers: new Headers({ 427 + 'Content-Type': 'image/jpeg', 428 + 'Content-Length': '12345' 429 + }) 430 + }); 431 + vi.stubGlobal('fetch', mockFetch); 432 + 433 + const ctx = createExecutionContext(); 434 + const response = await downloadBlobUnauthenticated(TEST_DID, TEST_CID, ctx); 435 + 436 + expect(response.status).toBe(200); 437 + expect(response.headers.get('Content-Type')).toBe('image/jpeg'); 438 + expect(response.headers.get('Cache-Control')).toBe('public, max-age=31536000, immutable'); 439 + expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*'); 440 + }); 441 + 442 + it('returns 400 when PDS resolution fails', async () => { 443 + const mockFetch = vi.fn().mockResolvedValueOnce({ 444 + ok: false 445 + }); 446 + vi.stubGlobal('fetch', mockFetch); 447 + 448 + const ctx = createExecutionContext(); 449 + const response = await downloadBlobUnauthenticated(TEST_DID, TEST_CID, ctx); 450 + 451 + expect(response.status).toBe(400); 452 + expect(await response.text()).toBe('Failed to resolve PDS host'); 453 + }); 454 + 455 + it('returns 404 when blob not found', async () => { 456 + const mockFetch = vi.fn() 457 + .mockResolvedValueOnce({ 458 + ok: true, 459 + json: async () => ({ 460 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 461 + }) 462 + }) 463 + .mockResolvedValueOnce({ 464 + status: 404 465 + }); 466 + vi.stubGlobal('fetch', mockFetch); 467 + 468 + const ctx = createExecutionContext(); 469 + const response = await downloadBlobUnauthenticated(TEST_DID, TEST_CID, ctx); 470 + 471 + expect(response.status).toBe(404); 472 + }); 473 + 474 + it('returns 500 when fetch throws exception', async () => { 475 + const mockFetch = vi.fn() 476 + .mockResolvedValueOnce({ 477 + ok: true, 478 + json: async () => ({ 479 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 480 + }) 481 + }) 482 + .mockRejectedValueOnce(new Error('Network error')); 483 + vi.stubGlobal('fetch', mockFetch); 484 + 485 + const ctx = createExecutionContext(); 486 + const response = await downloadBlobUnauthenticated(TEST_DID, TEST_CID, ctx); 487 + 488 + expect(response.status).toBe(500); 489 + expect(await response.text()).toBe('Failed to download blob'); 490 + }); 491 + }); 492 + 493 + describe('Worker fetch handler', () => { 494 + it('handles CORS preflight requests', async () => { 495 + const request = new IncomingRequest('http://example.com/test/test', { 496 + method: 'OPTIONS' 497 + }); 498 + const ctx = createExecutionContext(); 499 + const response = await worker.fetch(request, env, ctx); 500 + await waitOnExecutionContext(ctx); 501 + 502 + expect(response.status).toBe(200); 503 + expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*'); 504 + expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, OPTIONS'); 505 + }); 506 + 507 + it('returns 400 for invalid paths', async () => { 508 + const request = new IncomingRequest('http://example.com/'); 509 + const ctx = createExecutionContext(); 510 + const response = await worker.fetch(request, env, ctx); 511 + await waitOnExecutionContext(ctx); 512 + 513 + expect(response.status).toBe(400); 514 + expect(await response.text()).toContain('Invalid path'); 515 + }); 516 + 517 + it('returns 400 for paths with only handle', async () => { 518 + const request = new IncomingRequest('http://example.com/bsky.app'); 519 + const ctx = createExecutionContext(); 520 + const response = await worker.fetch(request, env, ctx); 521 + await waitOnExecutionContext(ctx); 522 + 523 + expect(response.status).toBe(400); 524 + }); 525 + 526 + it('strips file extensions from CID', async () => { 527 + // This tests that extensions like .jpg, .png are stripped 528 + // We'll test this by mocking and checking the CID used 529 + const mockFetch = vi.fn() 530 + // DNS resolution 531 + .mockResolvedValueOnce({ 532 + ok: true, 533 + json: async () => ({ 534 + Answer: [{ type: 16, data: `"did=${TEST_DID}"` }] 535 + }) 536 + }) 537 + // PDS resolution 538 + .mockResolvedValueOnce({ 539 + ok: true, 540 + json: async () => ({ 541 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 542 + }) 543 + }) 544 + // Blob download 545 + .mockResolvedValueOnce({ 546 + status: 200, 547 + body: new ReadableStream(), 548 + headers: new Headers({ 'Content-Type': 'image/jpeg' }) 549 + }); 550 + vi.stubGlobal('fetch', mockFetch); 551 + 552 + const request = new IncomingRequest(`http://example.com/${TEST_HANDLE}/${TEST_CID}.jpg`); 553 + const ctx = createExecutionContext(); 554 + const response = await worker.fetch(request, env, ctx); 555 + await waitOnExecutionContext(ctx); 556 + 557 + // Should have made it to blob download (extension stripped correctly) 558 + expect(mockFetch).toHaveBeenCalledTimes(3); 559 + 560 + vi.unstubAllGlobals(); 561 + }); 562 + 563 + it('handles DID directly without resolution', async () => { 564 + const mockFetch = vi.fn() 565 + // PDS resolution 566 + .mockResolvedValueOnce({ 567 + ok: true, 568 + json: async () => ({ 569 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 570 + }) 571 + }) 572 + // Blob download 573 + .mockResolvedValueOnce({ 574 + status: 200, 575 + body: new ReadableStream(), 576 + headers: new Headers({ 'Content-Type': 'image/jpeg' }) 577 + }); 578 + vi.stubGlobal('fetch', mockFetch); 579 + 580 + const request = new IncomingRequest(`http://example.com/${TEST_DID}/${TEST_CID}`); 581 + const ctx = createExecutionContext(); 582 + const response = await worker.fetch(request, env, ctx); 583 + await waitOnExecutionContext(ctx); 584 + 585 + // Should skip DNS resolution since it's already a DID 586 + expect(response.status).toBe(200); 587 + expect(mockFetch).toHaveBeenCalledTimes(2); 588 + 589 + vi.unstubAllGlobals(); 590 + }); 591 + 592 + it('returns 404 when handle cannot be resolved', async () => { 593 + const mockFetch = vi.fn() 594 + // DNS lookup fails 595 + .mockResolvedValueOnce({ ok: false }) 596 + // HTTPS well-known fails 597 + .mockResolvedValueOnce({ status: 404 }); 598 + vi.stubGlobal('fetch', mockFetch); 599 + 600 + const request = new IncomingRequest('http://example.com/nonexistent.handle/somecid'); 601 + const ctx = createExecutionContext(); 602 + const response = await worker.fetch(request, env, ctx); 603 + await waitOnExecutionContext(ctx); 604 + 605 + expect(response.status).toBe(404); 606 + expect(await response.text()).toBe('Handle not found'); 607 + 608 + vi.unstubAllGlobals(); 609 + }); 610 + 611 + it('uses cached DID from KV (plc format)', async () => { 612 + const mockFetch = vi.fn() 613 + // PDS resolution 614 + .mockResolvedValueOnce({ 615 + ok: true, 616 + json: async () => ({ 617 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 618 + }) 619 + }) 620 + // Blob download 621 + .mockResolvedValueOnce({ 622 + status: 200, 623 + body: new ReadableStream(), 624 + headers: new Headers({ 'Content-Type': 'image/jpeg' }) 625 + }); 626 + vi.stubGlobal('fetch', mockFetch); 627 628 + // Create a mock env with KV that returns a cached DID 629 + const mockEnv = { 630 + USER_CACHE: { 631 + get: vi.fn().mockResolvedValue('ewvi7nxzyoun6zhxrhs64oiz'), 632 + put: vi.fn() 633 + } 634 + }; 635 + 636 + const request = new IncomingRequest(`http://example.com/${TEST_HANDLE}/${TEST_CID}`); 637 + const ctx = createExecutionContext(); 638 + const response = await worker.fetch(request, mockEnv as any, ctx); 639 + await waitOnExecutionContext(ctx); 640 + 641 + // Should use cached DID and skip DNS resolution 642 + expect(response.status).toBe(200); 643 + expect(mockEnv.USER_CACHE.get).toHaveBeenCalledWith(TEST_HANDLE); 644 + // Only PDS + blob fetch, no DNS lookup 645 + expect(mockFetch).toHaveBeenCalledTimes(2); 646 + 647 + vi.unstubAllGlobals(); 648 + }); 649 + 650 + it('uses cached DID from KV (web format)', async () => { 651 + const mockFetch = vi.fn() 652 + // PDS resolution for did:web 653 + .mockResolvedValueOnce({ 654 + ok: true, 655 + json: async () => ({ 656 + service: [{ id: '#atproto_pds', serviceEndpoint: 'https://pds.example.com' }] 657 + }) 658 + }) 659 + // Blob download 660 + .mockResolvedValueOnce({ 661 + status: 200, 662 + body: new ReadableStream(), 663 + headers: new Headers({ 'Content-Type': 'image/png' }) 664 + }); 665 + vi.stubGlobal('fetch', mockFetch); 666 + 667 + // Create a mock env with KV that returns a cached web DID 668 + const mockEnv = { 669 + USER_CACHE: { 670 + get: vi.fn().mockResolvedValue('web:example.com'), 671 + put: vi.fn() 672 + } 673 + }; 674 + 675 + const request = new IncomingRequest(`http://example.com/somehandle/${TEST_CID}`); 676 + const ctx = createExecutionContext(); 677 + const response = await worker.fetch(request, mockEnv as any, ctx); 678 + await waitOnExecutionContext(ctx); 679 + 680 + expect(response.status).toBe(200); 681 + expect(mockEnv.USER_CACHE.get).toHaveBeenCalled(); 682 + 683 + vi.unstubAllGlobals(); 684 + }); 685 + 686 + it('resolves TID to blob CID', async () => { 687 + const mockFetch = vi.fn() 688 + // PDS resolution (for fetchBlobCidFromRecord) 689 + .mockResolvedValueOnce({ 690 + ok: true, 691 + json: async () => ({ 692 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 693 + }) 694 + }) 695 + // getRecord 696 + .mockResolvedValueOnce({ 697 + ok: true, 698 + json: async () => ({ 699 + uri: `at://${TEST_DID}/blue.imgs.blup.image/${TEST_TID}`, 700 + cid: 'recordcid', 701 + value: { 702 + blob: { ref: { $link: TEST_CID } } 703 + } 704 + }) 705 + }) 706 + // PDS resolution (for downloadBlobUnauthenticated) 707 + .mockResolvedValueOnce({ 708 + ok: true, 709 + json: async () => ({ 710 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 711 + }) 712 + }) 713 + // Blob download 714 + .mockResolvedValueOnce({ 715 + status: 200, 716 + body: new ReadableStream(), 717 + headers: new Headers({ 'Content-Type': 'image/jpeg' }) 718 + }); 719 + vi.stubGlobal('fetch', mockFetch); 720 + 721 + const request = new IncomingRequest(`http://example.com/${TEST_DID}/${TEST_TID}`); 722 + const ctx = createExecutionContext(); 723 + const response = await worker.fetch(request, env, ctx); 724 + await waitOnExecutionContext(ctx); 725 + 726 + expect(response.status).toBe(200); 727 + 728 + vi.unstubAllGlobals(); 729 + }); 730 + 731 + it('returns 404 when TID record not found', async () => { 732 + const mockFetch = vi.fn() 733 + // PDS resolution 734 + .mockResolvedValueOnce({ 735 + ok: true, 736 + json: async () => ({ 737 + service: [{ id: '#atproto_pds', serviceEndpoint: TEST_PDS }] 738 + }) 739 + }) 740 + // getRecord returns no blob 741 + .mockResolvedValueOnce({ 742 + ok: true, 743 + json: async () => ({ 744 + uri: `at://${TEST_DID}/blue.imgs.blup.image/${TEST_TID}`, 745 + cid: 'recordcid', 746 + value: {} 747 + }) 748 + }); 749 + vi.stubGlobal('fetch', mockFetch); 750 + 751 + const request = new IncomingRequest(`http://example.com/${TEST_DID}/${TEST_TID}`); 752 + const ctx = createExecutionContext(); 753 + const response = await worker.fetch(request, env, ctx); 754 + await waitOnExecutionContext(ctx); 755 + 756 + expect(response.status).toBe(404); 757 + expect(await response.text()).toBe('Record not found'); 758 + 759 + vi.unstubAllGlobals(); 760 + }); 761 + 762 + it('returns 400 for invalid base62 CID encoding', async () => { 763 + // Use a string that's detected as base62 but produces invalid CID bytes 764 + // 'AAAA' is valid base62 but won't decode to a valid CID 765 + const invalidBase62 = 'AAAA'; 766 + 767 + const request = new IncomingRequest(`http://example.com/${TEST_DID}/${invalidBase62}`); 768 + const ctx = createExecutionContext(); 769 + const response = await worker.fetch(request, env, ctx); 770 + await waitOnExecutionContext(ctx); 771 + 772 + expect(response.status).toBe(400); 773 + expect(await response.text()).toBe('Invalid CID encoding'); 774 + }); 775 });