diff --git a/README.md b/README.md index 9b5d656..b17c51e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,25 @@ https://developers.cloudflare.com/workers/configuration/secrets/#via-the-dashboa | `ALLOWED_BUCKETS` | Set the buckets allowed, separated by `,`. A directory with the bucket name will be created directly under Google Drive. | | `PUBLIC_READ_BUCKETS` | *(Optional)* Buckets that allow unauthenticated GET/HEAD access without signature, separated by `,`. Write operations (PUT/POST/DELETE) still require authentication. Must be a subset of `ALLOWED_BUCKETS`. | +### 4. Enable Multipart Uploads -### 4. CORS Configuration +Large uploads require S3 Multipart Upload because Cloudflare's request-size limit applies before a request reaches the Worker. Multipart support uses one SQLite-backed Durable Object per upload and streams each admitted part directly into one Google Drive resumable session. + +After deploying the `MultipartUploadDO` migration, change `ALLOW_MULTIPART` to `"true"` in `wrangler.jsonc`. Recommended AWS CLI settings: + +```ini +[default] +s3 = + multipart_chunksize = 16MB + max_concurrent_requests = 3 +request_checksum_calculation = when_required +``` + +Multipart parts must use consecutive numbers starting at 1 and are immutable after commit. The completed object's ETag defaults to Google Drive's real MD5 rather than S3's composite multipart ETag; set `ETAG_STYLE` to `"multipart"` only for clients that require the composite form. Existing objects may be re-evaluated once when their old Drive-ID ETag changes to MD5. + +Google Drive's free tier has 15 GB total storage, and Google applies a 750 GB daily upload limit. + + +### 5. CORS Configuration If you need to configure CORS, set up your own domain for Workers and use Cloudflare's Response Header Transform Rules to add the necessary headers. https://developers.cloudflare.com/rules/transform/response-header-modification/ diff --git a/biome.json b/biome.json index 41521c2..653f73c 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.7/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -18,7 +18,7 @@ "linter": { "enabled": true, "rules": { - "recommended": true, + "preset": "recommended", "performance": { "noImgElement": "off" }, diff --git a/package.json b/package.json index 80d7368..a6a9365 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,12 @@ "format": "biome format --write" }, "devDependencies": { - "@aws-sdk/client-s3": "^3.1031.0", - "@aws-sdk/s3-request-presigner": "^3.1000.0", - "@biomejs/biome": "^2.4.12", - "@cloudflare/vitest-pool-workers": "^0.12.1", + "@aws-sdk/client-s3": "^3.1106.0", + "@aws-sdk/s3-request-presigner": "^3.1106.0", + "@biomejs/biome": "^2.5.7", + "@cloudflare/vitest-pool-workers": "^0.20.3", "aws4fetch": "^1.0.20", - "typescript": "^6.0.3", - "vitest": "~4.1.4" + "typescript": "^7.0.2", + "vitest": "~4.1.10" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8ceb73..40370eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,311 +9,204 @@ importers: .: devDependencies: '@aws-sdk/client-s3': - specifier: ^3.1031.0 - version: 3.1031.0 + specifier: ^3.1106.0 + version: 3.1106.0 '@aws-sdk/s3-request-presigner': - specifier: ^3.1000.0 - version: 3.1000.0 + specifier: ^3.1106.0 + version: 3.1106.0 '@biomejs/biome': - specifier: ^2.4.12 - version: 2.4.12 + specifier: ^2.5.7 + version: 2.5.7 '@cloudflare/vitest-pool-workers': - specifier: ^0.12.1 - version: 0.12.1(@vitest/runner@4.1.4)(@vitest/snapshot@4.1.4)(vitest@4.1.4(vite@7.3.1)) + specifier: ^0.20.3 + version: 0.20.3(@vitest/runner@4.1.10)(@vitest/snapshot@4.1.10)(vitest@4.1.10(vite@8.2.1(esbuild@0.28.1))) aws4fetch: specifier: ^1.0.20 version: 1.0.20 typescript: - specifier: ^6.0.3 - version: 6.0.3 + specifier: ^7.0.2 + version: 7.0.2 vitest: - specifier: ~4.1.4 - version: 4.1.4(vite@7.3.1) + specifier: ~4.1.10 + version: 4.1.10(vite@8.2.1(esbuild@0.28.1)) packages: - '@aws-crypto/crc32@5.2.0': - resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/crc32c@5.2.0': - resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} - - '@aws-crypto/sha1-browser@5.2.0': - resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} - - '@aws-crypto/sha256-browser@5.2.0': - resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - - '@aws-crypto/sha256-js@5.2.0': - resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/supports-web-crypto@5.2.0': - resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - - '@aws-crypto/util@5.2.0': - resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - - '@aws-sdk/client-s3@3.1031.0': - resolution: {integrity: sha512-HflwKSpDl2pwPiH116n9dy1pKW+5yEoiGFor6NO1yADgge+QY5LqluBiHscD7k/o9ib+dgxf6Vg4WsctcRT3OA==} + '@aws-sdk/checksums@3.1000.26': + resolution: {integrity: sha512-CGznePoL+1oWCSzqmkvlYMpWQEZohPR3LntjKXXfVH+oh6Kd8d+yHLkjpiAGwPIHAxFe4OAq3aKfRnv/wqWIyA==} engines: {node: '>=20.0.0'} - '@aws-sdk/core@3.974.0': - resolution: {integrity: sha512-8j+dMtyDqNXFmi09CBdz8TY6Ltf2jhfHuP6ZvG4zVjndRc6JF0aeBUbRwQLndbptFCsdctRQgdNWecy4TIfXAw==} + '@aws-sdk/client-s3@3.1106.0': + resolution: {integrity: sha512-hUTlnyRRGlVdfvJLL3hCEnMm7CmunSzc/lxFVRX8g1fjJTMUVbyQCfhfMhp7dZ7JBftLU6OYresu3Hje4nvkJw==} engines: {node: '>=20.0.0'} - '@aws-sdk/crc64-nvme@3.972.7': - resolution: {integrity: sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg==} + '@aws-sdk/core@3.977.6': + resolution: {integrity: sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-env@3.972.26': - resolution: {integrity: sha512-WBHAMxyPdgeJY6ZGLvq9mJwzZ+GaNUROQbfdVshtMsDVBrZTj5ZuFjKclSjSHvKSHJ4Y4O2yvI/aA/hrJbYfng==} + '@aws-sdk/credential-provider-env@3.972.67': + resolution: {integrity: sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-http@3.972.28': - resolution: {integrity: sha512-+1DwCjjpo1WoiZTN08yGitI3nUwZUSQWVWFrW4C46HqZwACjcUQ7C66tnKPBTVxrEYYDOP11A6Afmu1L6ylt3g==} + '@aws-sdk/credential-provider-http@3.972.69': + resolution: {integrity: sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.972.30': - resolution: {integrity: sha512-Fg1oJcoijwOZjTxdbx+ubqbQl8YEQ4Cwhjw6TWzQjuDEvQYNhnCXW2pN7eKtdTrdE4a6+5TVKGSm2I+i2BKIQg==} + '@aws-sdk/credential-provider-ini@3.973.12': + resolution: {integrity: sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.972.30': - resolution: {integrity: sha512-nchIrrI/7dgjG1bW/DEWOJc00K9n+kkl6B8Mk0KO6d4GfWBOXlVr9uHp7CJR9FIrjmov5SGjHXG2q9XAtkRw6Q==} + '@aws-sdk/credential-provider-login@3.972.74': + resolution: {integrity: sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.972.31': - resolution: {integrity: sha512-99OHVQ6eZ5DTxiOWgHdjBMvLqv7xoY4jLK6nZ1NcNSQbAnYZkQNIHi/VqInc9fnmg7of9si/z+waE6YL9OQIlw==} + '@aws-sdk/credential-provider-node@3.972.78': + resolution: {integrity: sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-process@3.972.26': - resolution: {integrity: sha512-jibxNld3m+vbmQwn98hcQ+fLIVrx3cQuhZlSs1/hix48SjDS5/pjMLwpmtLD/lFnd6ve1AL4o1bZg3X1WRa2SQ==} + '@aws-sdk/credential-provider-process@3.972.67': + resolution: {integrity: sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.972.30': - resolution: {integrity: sha512-honYIM17F/+QSWJRE84T4u//ofqEi7rLbnwmIpu7fgFX5PML78wbtdSAy5Xwyve3TLpE9/f9zQx0aBVxSjAOPw==} + '@aws-sdk/credential-provider-sso@3.973.11': + resolution: {integrity: sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.972.30': - resolution: {integrity: sha512-CyL4oWUlONQRN2SsYMVrA9Z3i3QfLWTQctI8tuKbjNGCVVDCnJf/yMbSJCOZgpPFRtxh7dgQwvpqwmJm+iytmw==} + '@aws-sdk/credential-provider-web-identity@3.972.73': + resolution: {integrity: sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-bucket-endpoint@3.972.10': - resolution: {integrity: sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA==} + '@aws-sdk/middleware-sdk-s3@3.972.72': + resolution: {integrity: sha512-lSAoVPvQxX1d8TOM6waKDBQrvvZcm4w6pCldFAsRUffEaXq6lYY0pPyew3KlLu6Xqb74DXI42hGvSsbGBLljlw==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-expect-continue@3.972.10': - resolution: {integrity: sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ==} + '@aws-sdk/nested-clients@3.997.41': + resolution: {integrity: sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.974.8': - resolution: {integrity: sha512-c+bD9J3f56oOPmmseCfT6PhkykiC5vtq0/ZDaK7U1Da/u/b7ZhhidfTHGnqa1pMCro9ZkM4QBcJ70lP7RgnPWg==} + '@aws-sdk/s3-request-presigner@3.1106.0': + resolution: {integrity: sha512-ZI5SCkyz8jB3Qr6NPSJ7R4A/PkniQvbD1DO8APwhMsubFcfwPSJMMUxrkv9k1E20ikRk7skpM9itZh+0wI9K/w==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-host-header@3.972.10': - resolution: {integrity: sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg==} + '@aws-sdk/signature-v4-multi-region@3.996.43': + resolution: {integrity: sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-location-constraint@3.972.10': - resolution: {integrity: sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ==} + '@aws-sdk/token-providers@3.1103.0': + resolution: {integrity: sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-logger@3.972.10': - resolution: {integrity: sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ==} + '@aws-sdk/types@3.974.2': + resolution: {integrity: sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-recursion-detection@3.972.11': - resolution: {integrity: sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ==} + '@aws-sdk/xml-builder@3.972.37': + resolution: {integrity: sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-sdk-s3@3.972.15': - resolution: {integrity: sha512-WDLgssevOU5BFx1s8jA7jj6cE5HuImz28sy9jKOaVtz0AW1lYqSzotzdyiybFaBcQTs5zxXOb2pUfyMxgEKY3Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-sdk-s3@3.972.29': - resolution: {integrity: sha512-ayk68penP1WDZmyDZVeUQzq+HI3iDq5xezohUxIQoKFKE0KdCnDcxLCNnLanhBfgQDaKiGHVXhxZMDWJAEEBsQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-ssec@3.972.10': - resolution: {integrity: sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/middleware-user-agent@3.972.30': - resolution: {integrity: sha512-lCz6JfelhjD6Eco1urXM2rOYRaxROSqeoY6IEKx+soegFJOajmIBCMHTAWuJl25Wf9IAST+i0/yOk9G3rMV26A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/nested-clients@3.996.20': - resolution: {integrity: sha512-bzPdsNQnCh6TvvUmTHLZlL8qgyME6mNiUErcRMyJPywIl1BEu2VZRShel3mUoSh89bOBEXEWtjocDMolFxd/9A==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/region-config-resolver@3.972.12': - resolution: {integrity: sha512-QQI43Mxd53nBij0pm8HXC+t4IOC6gnhhZfzxE0OATQyO6QfPV4e+aTIRRuAJKA6Nig/cR8eLwPryqYTX9ZrjAQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/s3-request-presigner@3.1000.0': - resolution: {integrity: sha512-DP6EbwCD0CKzBwBnT1X6STB5i+bY765CxjMbWCATDhCgOB343Q6AHM9c1S/300Uc5waXWtI/Wdeak9Ru56JOvg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.996.17': - resolution: {integrity: sha512-qDwhXw+SIM5vMAMgflA8LPRa7xP+/wgXYr++llzCOwp7kkM2v7GGGWzoRW8e1CACaO4ljZd/NSQbsRLKm1sMWw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.996.3': - resolution: {integrity: sha512-gQYI/Buwp0CAGQxY7mR5VzkP56rkWq2Y1ROkFuXh5XY94DsSjJw62B3I0N0lysQmtwiL2ht2KHI9NylM/RP4FA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/token-providers@3.1031.0': - resolution: {integrity: sha512-zj/PvnbQK/2KJNln5K2QRI9HSsy+B4emz2gbQyUHkk6l7Lidu83P/9tfmC2cJXkcC3vdmyKH2DP3Iw/FDfKQuQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/types@3.973.4': - resolution: {integrity: sha512-RW60aH26Bsc016Y9B98hC0Plx6fK5P2v/iQYwMzrSjiDh1qRMUCP6KrXHYEHe3uFvKiOC93Z9zk4BJsUi6Tj1Q==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/types@3.973.8': - resolution: {integrity: sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-arn-parser@3.972.2': - resolution: {integrity: sha512-VkykWbqMjlSgBFDyrY3nOSqupMc6ivXuGmvci6Q3NnLq5kC+mKQe2QBZ4nrWRE/jqOxeFP2uYzLtwncYYcvQDg==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-arn-parser@3.972.3': - resolution: {integrity: sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-endpoints@3.996.7': - resolution: {integrity: sha512-ty4LQxN1QC+YhUP28NfEgZDEGXkyqOQy+BDriBozqHsrYO4JMgiPhfizqOGF7P+euBTZ5Ez6SKlLAMCLo8tzmw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-format-url@3.972.6': - resolution: {integrity: sha512-0YNVNgFyziCejXJx0rzxPiD2rkxTWco4c9wiMF6n37Tb9aQvIF8+t7GyEyIFCwQHZ0VMQaAl+nCZHOYz5I5EKw==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-locate-window@3.965.5': - resolution: {integrity: sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==} - engines: {node: '>=20.0.0'} - - '@aws-sdk/util-user-agent-browser@3.972.10': - resolution: {integrity: sha512-FAzqXvfEssGdSIz8ejatan0bOdx1qefBWKF/gWmVBXIP1HkS7v/wjjaqrAGGKvyihrXTXW00/2/1nTJtxpXz7g==} - - '@aws-sdk/util-user-agent-node@3.973.16': - resolution: {integrity: sha512-ccvu0FNCI0C6OqmxI/tWn7BD8qGooWuURssiIM+6vbksFO8opXR4JOGtGYPj8QYzN/vfwNYrcK344PPbYuvzRg==} - engines: {node: '>=20.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/xml-builder@3.972.18': - resolution: {integrity: sha512-BMDNVG1ETXRhl1tnisQiYBef3RShJ1kfZA7x7afivTFMLirfHNTb6U71K569HNXhSXbQZsweHvSDZ6euBw8hPA==} - engines: {node: '>=20.0.0'} - - '@aws/lambda-invoke-store@0.2.4': - resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==} + '@aws/lambda-invoke-store@0.3.0': + resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} - '@biomejs/biome@2.4.12': - resolution: {integrity: sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA==} + '@biomejs/biome@2.5.7': + resolution: {integrity: sha512-zr8K/DcY5tYsQOQwqMJ0AWElo6QgmgNI7idXgXLhevVszlt8RGVpesEJPqx3ThazLaOwjJ5Y8fz3BtH5fGZNsw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.4.12': - resolution: {integrity: sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==} + '@biomejs/cli-darwin-arm64@2.5.7': + resolution: {integrity: sha512-vxo/Ls3/PYdQWyLhYYcgMOCzQypAjcY+iihS8M0wW03l16TCLW4zqZzGo75gm1VdCMj38hTVZ31KBWrZ4G9dJw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.4.12': - resolution: {integrity: sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==} + '@biomejs/cli-darwin-x64@2.5.7': + resolution: {integrity: sha512-Cd3Ga61amT/Yl/0x8elP5hhGYaFy4bw6WuysTgf7oo8TA5tJ5A1k+DkVoJ2BHbTVil51gTX9VPzArnrlLJ3Kyg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.4.12': - resolution: {integrity: sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==} + '@biomejs/cli-linux-arm64-musl@2.5.7': + resolution: {integrity: sha512-xPI5yB6XlpDbNkS+bm1t42olw5c4l3UrlOmLg7KtLJvjvkNF/1V4tnUgfkylGIeb3u/T+BzMGYqgQhzjAoJzuQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.4.12': - resolution: {integrity: sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==} + '@biomejs/cli-linux-arm64@2.5.7': + resolution: {integrity: sha512-rR2QE0yF2GYSuYuKIa7pKvODGJqnOH+2eDREAM8wV+mWKSkMQKdAp4zXEZfTaxY8PMoNONnpgSWcBCyLDPDOKg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.4.12': - resolution: {integrity: sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==} + '@biomejs/cli-linux-x64-musl@2.5.7': + resolution: {integrity: sha512-rE5VZi+qtmPgQH+l7jVxYoZ18b/TiHEhulhMpjmCZH1PltSbjRcxNWywC3HZ9tYottG7ORkeTtoscBilKSBm0g==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.4.12': - resolution: {integrity: sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==} + '@biomejs/cli-linux-x64@2.5.7': + resolution: {integrity: sha512-FQgqJhscrqJUFptGaRSUJWlXAExwWcDwLuK49dvKfkQ1bB5SEEyFssnsxQY83Xm6jR0EbbX3+8+D5bfvYqUG2Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.4.12': - resolution: {integrity: sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==} + '@biomejs/cli-win32-arm64@2.5.7': + resolution: {integrity: sha512-Oq4x0CCwP4jirrcTywXs5kOGZ4v5vuEP+gWrbtjApOA2CL9F3F9GlIdQIci8AKSCa/zURanMRpX/4wQ7Am6hHg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.4.12': - resolution: {integrity: sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA==} + '@biomejs/cli-win32-x64@2.5.7': + resolution: {integrity: sha512-V+0wu/nrj2S+MhP4EQ0uHNolP0IALEsz45pg0WoKkHfDeh0+ItHwP/p7bX5RPoMOl9NkpHYWdYPhIcy2mACHvQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] - '@cloudflare/kv-asset-handler@0.4.1': - resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==} - engines: {node: '>=18.0.0'} + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} - '@cloudflare/unenv-preset@2.8.0': - resolution: {integrity: sha512-oIAu6EdQ4zJuPwwKr9odIEqd8AV96z1aqi3RBEA4iKaJ+Vd3fvuI6m5EDC7/QCv+oaPIhy1SkYBYxmD09N+oZg==} + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} peerDependencies: unenv: 2.0.0-rc.24 - workerd: ^1.20251202.0 + workerd: '>1.20260305.0 <2.0.0-0' peerDependenciesMeta: workerd: optional: true - '@cloudflare/vitest-pool-workers@0.12.1': - resolution: {integrity: sha512-n3zlxG716/o23a7FWT1FSMXcRcmzw01dqyK24JzrTzJ1NLt5LnkA4LOFnRIznrLESjPxW18wKofMnF/z1iUB2A==} + '@cloudflare/vitest-pool-workers@0.20.3': + resolution: {integrity: sha512-aCMvM5zQ3MTz8SorSZB6ZxjVK2Gof6UhIc2Z1z9zbX/obucSYwDUkx8A0MUOod4I7clfB0QLarKBjRdIczK3vg==} peerDependencies: - '@vitest/runner': 2.0.x - 3.2.x - '@vitest/snapshot': 2.0.x - 3.2.x - vitest: 2.0.x - 3.2.x + '@vitest/runner': ^4.1.0 + '@vitest/snapshot': ^4.1.0 + vitest: ^4.1.0 - '@cloudflare/workerd-darwin-64@1.20260107.1': - resolution: {integrity: sha512-Srwe/IukVppkMU2qTndkFaKCmZBI7CnZoq4Y0U0gD/8158VGzMREHTqCii4IcCeHifwrtDqTWu8EcA1VBKI4mg==} + '@cloudflare/workerd-darwin-64@1.20260801.1': + resolution: {integrity: sha512-wuJWbXpKvncJi1P0GKS+iYpN5tHdb7JPJJ/+6ZQe8zzovHHVMkLJPNBsgWpqeUhpM3g9qTwEKd2rglNKejuh5A==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260107.1': - resolution: {integrity: sha512-aAYwU7zXW+UZFh/a4vHP5cs1ulTOcDRLzwU9547yKad06RlZ6ioRm7ovjdYvdqdmbI8mPd99v4LN9gMmecazQw==} + '@cloudflare/workerd-darwin-arm64@1.20260801.1': + resolution: {integrity: sha512-kwoZiTpnhNrF3+APx84Q/oAqvJ3sU9yefGagwm/ASaH/2W19x0vghkW/r4qCoHCK0WW7EPugZ+aXjgPMRtlq1Q==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260107.1': - resolution: {integrity: sha512-Wh7xWtFOkk6WY3CXe3lSqZ1anMkFcwy+qOGIjtmvQ/3nCOaG34vKNwPIE9iwryPupqkSuDmEqkosI1UUnSTh1A==} + '@cloudflare/workerd-linux-64@1.20260801.1': + resolution: {integrity: sha512-r0vAxCZH+Jih9Unm1yoyiByPNWNgawcKciOHDm5Q37ZVGOkKLsT9AtLe3yLSaul76WrKqtf+JP2n0WW32VBLJg==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260107.1': - resolution: {integrity: sha512-NI0/5rdssdZZKYHxNG4umTmMzODByq86vSCEk8u4HQbGhRCQo7rV1eXn84ntSBdyWBzWdYGISCbeZMsgfIjSTg==} + '@cloudflare/workerd-linux-arm64@1.20260801.1': + resolution: {integrity: sha512-zWgpdZtSozvIgzQNmQiDSF8yEOQJUkRAWNsDXXzAAoy+fCn8YUoSibj3mpFSbZRvbUldeBEaW6SCdC2VEMkhNQ==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260107.1': - resolution: {integrity: sha512-gmBMqs606Gd/IhBEBPSL/hJAqy2L8IyPUjKtoqd/Ccy7GQxbSc0rYlRkxbQ9YzmqnuhrTVYvXuLscyWrpmAJkw==} + '@cloudflare/workerd-windows-64@1.20260801.1': + resolution: {integrity: sha512-2oQz+Ksu4ji6e/+ZoYX+tWQEcxAii2p7l+iR8kx48W1llMalaufAsmVxTlk+3/vrM7D3/2c0iK448e0UQTcIMg==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -322,423 +215,308 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - '@esbuild/aix-ppc64@0.27.0': - resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.0': - resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.0': - resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.0': - resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.0': - resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.0': - resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.0': - resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.0': - resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.0': - resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.0': - resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.0': - resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.0': - resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.0': - resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.0': - resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.2': + resolution: {integrity: sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.2': + resolution: {integrity: sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.2': + resolution: {integrity: sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.1': + resolution: {integrity: sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.1': + resolution: {integrity: sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.1': + resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.3.1': + resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.3.1': + resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.27.0': - resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} - engines: {node: '>=18'} + '@img/sharp-libvips-linux-riscv64@1.3.1': + resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} + '@img/sharp-libvips-linux-s390x@1.3.1': + resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.3.1': + resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.35.2': + resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.35.2': + resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.35.2': + resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.35.2': + resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==} + engines: {node: '>=20.9.0'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.27.0': - resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} - engines: {node: '>=18'} + '@img/sharp-linux-s390x@0.35.2': + resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==} + engines: {node: '>=20.9.0'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.0': - resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} - engines: {node: '>=18'} + '@img/sharp-linux-x64@0.35.2': + resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.0': - resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.0': - resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.0': - resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.0': - resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.0': - resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.0': - resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.0': - resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.0': - resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.0': - resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + '@img/sharp-linuxmusl-arm64@0.35.2': + resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==} + engines: {node: '>=20.9.0'} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + '@img/sharp-linuxmusl-x64@0.35.2': + resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] + '@img/sharp-wasm32@0.35.2': + resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==} + engines: {node: '>=20.9.0'} - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-webcontainers-wasm32@0.35.2': + resolution: {integrity: sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==} + engines: {node: '>=20.9.0'} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-arm64@0.35.2': + resolution: {integrity: sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.2': + resolution: {integrity: sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==} + engines: {node: ^20.9.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + '@img/sharp-win32-x64@0.35.2': + resolution: {integrity: sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==} + engines: {node: '>=20.9.0'} cpu: [x64] os: [win32] @@ -752,6 +530,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@oxc-project/types@0.143.0': + resolution: {integrity: sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==} + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -761,437 +542,123 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@rollup/rollup-android-arm-eabi@4.60.1': - resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.1': - resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + '@rolldown/binding-android-arm64@1.2.3': + resolution: {integrity: sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.1': - resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + '@rolldown/binding-darwin-arm64@1.2.3': + resolution: {integrity: sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.1': - resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + '@rolldown/binding-darwin-x64@1.2.3': + resolution: {integrity: sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.1': - resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.1': - resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + '@rolldown/binding-freebsd-x64@1.2.3': + resolution: {integrity: sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': + resolution: {integrity: sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.60.1': - resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + '@rolldown/binding-linux-arm64-gnu@1.2.3': + resolution: {integrity: sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.60.1': - resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + '@rolldown/binding-linux-arm64-musl@1.2.3': + resolution: {integrity: sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.60.1': - resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-loong64-musl@4.60.1': - resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + '@rolldown/binding-linux-ppc64-gnu@1.2.3': + resolution: {integrity: sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-musl@4.60.1': - resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + '@rolldown/binding-linux-s390x-gnu@1.2.3': + resolution: {integrity: sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.60.1': - resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + '@rolldown/binding-linux-x64-gnu@1.2.3': + resolution: {integrity: sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.60.1': - resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + '@rolldown/binding-linux-x64-musl@1.2.3': + resolution: {integrity: sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rollup/rollup-openbsd-x64@4.60.1': - resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.1': - resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + '@rolldown/binding-openharmony-arm64@1.2.3': + resolution: {integrity: sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.1': - resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + '@rolldown/binding-win32-arm64-msvc@1.2.3': + resolution: {integrity: sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.1': - resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.1': - resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + '@rolldown/binding-win32-x64-msvc@1.2.3': + resolution: {integrity: sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.1': - resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} - cpu: [x64] - os: [win32] + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} '@sindresorhus/is@7.2.0': resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} engines: {node: '>=18'} - '@smithy/chunked-blob-reader-native@4.2.3': - resolution: {integrity: sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==} + '@smithy/core@3.31.1': + resolution: {integrity: sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg==} engines: {node: '>=18.0.0'} - '@smithy/chunked-blob-reader@5.2.2': - resolution: {integrity: sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==} + '@smithy/credential-provider-imds@4.4.16': + resolution: {integrity: sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w==} engines: {node: '>=18.0.0'} - '@smithy/config-resolver@4.4.16': - resolution: {integrity: sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg==} + '@smithy/fetch-http-handler@5.6.13': + resolution: {integrity: sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A==} engines: {node: '>=18.0.0'} - '@smithy/core@3.23.15': - resolution: {integrity: sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ==} + '@smithy/node-http-handler@4.9.13': + resolution: {integrity: sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw==} engines: {node: '>=18.0.0'} - '@smithy/core@3.23.6': - resolution: {integrity: sha512-4xE+0L2NrsFKpEVFlFELkIHQddBvMbQ41LRIP74dGCXnY1zQ9DgksrBcRBDJT+iOzGy4VEJIeU3hkUK5mn06kg==} + '@smithy/signature-v4@5.6.12': + resolution: {integrity: sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.2.14': - resolution: {integrity: sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==} + '@smithy/types@4.16.1': + resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@4.2.14': - resolution: {integrity: sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-browser@4.2.14': - resolution: {integrity: sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-config-resolver@4.3.14': - resolution: {integrity: sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-node@4.2.14': - resolution: {integrity: sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==} - engines: {node: '>=18.0.0'} - - '@smithy/eventstream-serde-universal@4.2.14': - resolution: {integrity: sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==} - engines: {node: '>=18.0.0'} - - '@smithy/fetch-http-handler@5.3.17': - resolution: {integrity: sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-blob-browser@4.2.15': - resolution: {integrity: sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-node@4.2.14': - resolution: {integrity: sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==} - engines: {node: '>=18.0.0'} - - '@smithy/hash-stream-node@4.2.14': - resolution: {integrity: sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ==} - engines: {node: '>=18.0.0'} - - '@smithy/invalid-dependency@4.2.14': - resolution: {integrity: sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==} - engines: {node: '>=18.0.0'} - - '@smithy/is-array-buffer@2.2.0': - resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} - engines: {node: '>=14.0.0'} - - '@smithy/is-array-buffer@4.2.1': - resolution: {integrity: sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==} - engines: {node: '>=18.0.0'} - - '@smithy/is-array-buffer@4.2.2': - resolution: {integrity: sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==} - engines: {node: '>=18.0.0'} - - '@smithy/md5-js@4.2.14': - resolution: {integrity: sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-content-length@4.2.14': - resolution: {integrity: sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-endpoint@4.4.20': - resolution: {integrity: sha512-9W6Np4ceBP3XCYAGLoMCmn8t2RRVzuD1ndWPLBbv7H9CrwM9Bprf6Up6BM9ZA/3alodg0b7Kf6ftBK9R1N04vw==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-endpoint@4.4.30': - resolution: {integrity: sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-retry@4.5.3': - resolution: {integrity: sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-serde@4.2.11': - resolution: {integrity: sha512-STQdONGPwbbC7cusL60s7vOa6He6A9w2jWhoapL0mgVjmR19pr26slV+yoSP76SIssMTX/95e5nOZ6UQv6jolg==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-serde@4.2.18': - resolution: {integrity: sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-stack@4.2.10': - resolution: {integrity: sha512-pmts/WovNcE/tlyHa8z/groPeOtqtEpp61q3W0nW1nDJuMq/x+hWa/OVQBtgU0tBqupeXq0VBOLA4UZwE8I0YA==} - engines: {node: '>=18.0.0'} - - '@smithy/middleware-stack@4.2.14': - resolution: {integrity: sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==} - engines: {node: '>=18.0.0'} - - '@smithy/node-config-provider@4.3.10': - resolution: {integrity: sha512-UALRbJtVX34AdP2VECKVlnNgidLHA2A7YgcJzwSBg1hzmnO/bZBHl/LDQQyYifzUwp1UOODnl9JJ3KNawpUJ9w==} - engines: {node: '>=18.0.0'} - - '@smithy/node-config-provider@4.3.14': - resolution: {integrity: sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==} - engines: {node: '>=18.0.0'} - - '@smithy/node-http-handler@4.5.3': - resolution: {integrity: sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw==} - engines: {node: '>=18.0.0'} - - '@smithy/property-provider@4.2.10': - resolution: {integrity: sha512-5jm60P0CU7tom0eNrZ7YrkgBaoLFXzmqB0wVS+4uK8PPGmosSrLNf6rRd50UBvukztawZ7zyA8TxlrKpF5z9jw==} - engines: {node: '>=18.0.0'} - - '@smithy/property-provider@4.2.14': - resolution: {integrity: sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==} - engines: {node: '>=18.0.0'} - - '@smithy/protocol-http@5.3.10': - resolution: {integrity: sha512-2NzVWpYY0tRdfeCJLsgrR89KE3NTWT2wGulhNUxYlRmtRmPwLQwKzhrfVaiNlA9ZpJvbW7cjTVChYKgnkqXj1A==} - engines: {node: '>=18.0.0'} - - '@smithy/protocol-http@5.3.14': - resolution: {integrity: sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-builder@4.2.10': - resolution: {integrity: sha512-HeN7kEvuzO2DmAzLukE9UryiUvejD3tMp9a1D1NJETerIfKobBUCLfviP6QEk500166eD2IATaXM59qgUI+YDA==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-builder@4.2.14': - resolution: {integrity: sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-parser@4.2.10': - resolution: {integrity: sha512-4Mh18J26+ao1oX5wXJfWlTT+Q1OpDR8ssiC9PDOuEgVBGloqg18Fw7h5Ct8DyT9NBYwJgtJ2nLjKKFU6RP1G1Q==} - engines: {node: '>=18.0.0'} - - '@smithy/querystring-parser@4.2.14': - resolution: {integrity: sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==} - engines: {node: '>=18.0.0'} - - '@smithy/service-error-classification@4.2.14': - resolution: {integrity: sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ==} - engines: {node: '>=18.0.0'} - - '@smithy/shared-ini-file-loader@4.4.5': - resolution: {integrity: sha512-pHgASxl50rrtOztgQCPmOXFjRW+mCd7ALr/3uXNzRrRoGV5G2+78GOsQ3HlQuBVHCh9o6xqMNvlIKZjWn4Euug==} - engines: {node: '>=18.0.0'} - - '@smithy/shared-ini-file-loader@4.4.9': - resolution: {integrity: sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.3.10': - resolution: {integrity: sha512-Wab3wW8468WqTKIxI+aZe3JYO52/RYT/8sDOdzkUhjnLakLe9qoQqIcfih/qxcF4qWEFoWBszY0mj5uxffaVXA==} - engines: {node: '>=18.0.0'} - - '@smithy/signature-v4@5.3.14': - resolution: {integrity: sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.12.0': - resolution: {integrity: sha512-R8bQ9K3lCcXyZmBnQqUZJF4ChZmtWT5NLi6x5kgWx5D+/j0KorXcA0YcFg/X5TOgnTCy1tbKc6z2g2y4amFupQ==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.12.11': - resolution: {integrity: sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg==} - engines: {node: '>=18.0.0'} - - '@smithy/types@4.13.0': - resolution: {integrity: sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==} - engines: {node: '>=18.0.0'} - - '@smithy/types@4.14.1': - resolution: {integrity: sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==} - engines: {node: '>=18.0.0'} - - '@smithy/url-parser@4.2.10': - resolution: {integrity: sha512-uypjF7fCDsRk26u3qHmFI/ePL7bxxB9vKkE+2WKEciHhz+4QtbzWiHRVNRJwU3cKhrYDYQE3b0MRFtqfLYdA4A==} - engines: {node: '>=18.0.0'} - - '@smithy/url-parser@4.2.14': - resolution: {integrity: sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-base64@4.3.2': - resolution: {integrity: sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-browser@4.2.2': - resolution: {integrity: sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-body-length-node@4.2.3': - resolution: {integrity: sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==} - engines: {node: '>=18.0.0'} - - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-buffer-from@4.2.1': - resolution: {integrity: sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==} - engines: {node: '>=18.0.0'} - - '@smithy/util-buffer-from@4.2.2': - resolution: {integrity: sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==} - engines: {node: '>=18.0.0'} - - '@smithy/util-config-provider@4.2.1': - resolution: {integrity: sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==} - engines: {node: '>=18.0.0'} - - '@smithy/util-config-provider@4.2.2': - resolution: {integrity: sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-browser@4.3.47': - resolution: {integrity: sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-node@4.2.52': - resolution: {integrity: sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w==} - engines: {node: '>=18.0.0'} - - '@smithy/util-endpoints@3.4.1': - resolution: {integrity: sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-hex-encoding@4.2.1': - resolution: {integrity: sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-hex-encoding@4.2.2': - resolution: {integrity: sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-middleware@4.2.10': - resolution: {integrity: sha512-LxaQIWLp4y0r72eA8mwPNQ9va4h5KeLM0I3M/HV9klmFaY2kN766wf5vsTzmaOpNNb7GgXAd9a25P3h8T49PSA==} - engines: {node: '>=18.0.0'} - - '@smithy/util-middleware@4.2.14': - resolution: {integrity: sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-retry@4.3.2': - resolution: {integrity: sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A==} - engines: {node: '>=18.0.0'} - - '@smithy/util-stream@4.5.15': - resolution: {integrity: sha512-OlOKnaqnkU9X+6wEkd7mN+WB7orPbCVDauXOj22Q7VtiTkvy7ZdSsOg4QiNAZMgI4OkvNf+/VLUC3VXkxuWJZw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-stream@4.5.23': - resolution: {integrity: sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg==} - engines: {node: '>=18.0.0'} - - '@smithy/util-uri-escape@4.2.1': - resolution: {integrity: sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==} - engines: {node: '>=18.0.0'} - - '@smithy/util-uri-escape@4.2.2': - resolution: {integrity: sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@4.2.2': - resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-waiter@4.2.16': - resolution: {integrity: sha512-GtclrKoZ3Lt7jPQ7aTIYKfjY92OgceScftVnkTsG8e1KV8rkvZgN+ny6YSRhd9hxB8rZtwVbmln7NTvE5O3GmQ==} - engines: {node: '>=18.0.0'} - - '@smithy/uuid@1.1.1': - resolution: {integrity: sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==} - engines: {node: '>=18.0.0'} - - '@smithy/uuid@1.1.2': - resolution: {integrity: sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==} - engines: {node: '>=18.0.0'} - - '@speed-highlight/core@1.2.14': - resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==} + '@speed-highlight/core@1.2.24': + resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==} '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1202,14 +669,134 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@vitest/expect@4.1.4': - resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] - '@vitest/mocker@4.1.4': - resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1219,29 +806,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.4': - resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} - '@vitest/runner@4.1.4': - resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} - '@vitest/snapshot@4.1.4': - resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} - '@vitest/spy@4.1.4': - resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} - '@vitest/utils@4.1.4': - resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} - - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} @@ -1250,9 +828,6 @@ packages: aws4fetch@1.0.20: resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} - birpc@0.2.14: - resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==} - blake3-wasm@2.1.5: resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} @@ -1263,22 +838,8 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - cjs-module-lexer@1.4.3: - resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} + cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1291,43 +852,24 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devalue@5.6.1: - resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} - error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} - esbuild@0.27.0: - resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} engines: {node: '>=18'} hasBin: true estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} - - expect-type@1.3.0: - resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} - fast-xml-builder@1.1.5: - resolution: {integrity: sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA==} - - fast-xml-parser@5.5.8: - resolution: {integrity: sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ==} - hasBin: true - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1342,44 +884,95 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - get-port@7.1.0: - resolution: {integrity: sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==} - engines: {node: '>=16'} - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - is-arrayish@0.3.4: - resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true + miniflare@5.20260801.1-alpha: + resolution: {integrity: sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==} + engines: {node: '>=22.0.0'} - miniflare@4.20260107.0: - resolution: {integrity: sha512-X93sXczqbBq9ixoM6jnesmdTqp+4baVC/aM/DuPpRS0LK0XtcqaO75qPzNEvDEzBAHxwMAWRIum/9hg32YB8iA==} - engines: {node: '>=18.0.0'} - hasBin: true - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - obug@2.1.1: - resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - - path-expression-matcher@1.5.0: - resolution: {integrity: sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==} - engines: {node: '>=14.0.0'} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -1390,34 +983,31 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} - postcss@8.5.10: - resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} engines: {node: ^10 || ^12 || >=14} - rollup@4.60.1: - resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} + rolldown@1.2.3: + resolution: {integrity: sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.35.2: + resolution: {integrity: sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==} + engines: {node: '>=20.9.0'} siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - simple-swizzle@0.2.4: - resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1425,15 +1015,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@4.1.0: - resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} - - stoppable@1.1.0: - resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} - engines: {node: '>=4', npm: '>=6'} - - strnum@2.2.3: - resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} @@ -1442,42 +1025,43 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.1.1: - resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} engines: {node: '>=18'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} - tinyrainbow@3.1.0: - resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true - undici@7.14.0: - resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} engines: {node: '>=20.18.1'} unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - vite@7.3.1: - resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + vite@8.2.1: + resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -1488,12 +1072,14 @@ packages: peerDependenciesMeta: '@types/node': optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -1509,20 +1095,20 @@ packages: yaml: optional: true - vitest@4.1.4: - resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.4 - '@vitest/browser-preview': 4.1.4 - '@vitest/browser-webdriverio': 4.1.4 - '@vitest/coverage-istanbul': 4.1.4 - '@vitest/coverage-v8': 4.1.4 - '@vitest/ui': 4.1.4 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 happy-dom: '*' jsdom: '*' vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1555,23 +1141,23 @@ packages: engines: {node: '>=8'} hasBin: true - workerd@1.20260107.1: - resolution: {integrity: sha512-4ylAQJDdJZdMAUl2SbJgTa77YHpa88l6qmhiuCLNactP933+rifs7I0w1DslhUIFgydArUX5dNLAZnZhT7Bh7g==} + workerd@1.20260801.1: + resolution: {integrity: sha512-/g9JGTyqnHtoIscpBHqKD8swE2V4StBs2i69PmLiOhH45OP95jCFICl4F1hKlgN57rqfni5LiCitttoX5OOkVA==} engines: {node: '>=16'} hasBin: true - wrangler@4.58.0: - resolution: {integrity: sha512-Jm6EYtlt8iUcznOCPSMYC54DYkwrMNESzbH0Vh3GFHv/7XVw5gBC13YJAB+nWMRGJ+6B2dMzy/NVQS4ONL51Pw==} - engines: {node: '>=20.0.0'} + wrangler@4.120.0: + resolution: {integrity: sha512-cBmu/MeaB/fPacC0JpATs4duTOCagBxrZo+vBzuTX06tLzwSyAHE1drlHUZ8rP0VqVz1fy3ReGYTiHdKkoHltg==} + engines: {node: '>=22.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260107.1 + '@cloudflare/workers-types': ^5.20260801.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true - ws@8.18.0: - resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1588,825 +1174,449 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: - '@aws-crypto/crc32@5.2.0': + '@aws-sdk/checksums@3.1000.26': dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.8 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-crypto/crc32c@5.2.0': + '@aws-sdk/client-s3@3.1106.0': dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.8 + '@aws-sdk/checksums': 3.1000.26 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/credential-provider-node': 3.972.78 + '@aws-sdk/middleware-sdk-s3': 3.972.72 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-crypto/sha1-browser@5.2.0': + '@aws-sdk/core@3.977.6': dependencies: - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-locate-window': 3.965.5 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-crypto/sha256-browser@5.2.0': - dependencies: - '@aws-crypto/sha256-js': 5.2.0 - '@aws-crypto/supports-web-crypto': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-locate-window': 3.965.5 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-crypto/sha256-js@5.2.0': - dependencies: - '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.973.8 - tslib: 2.8.1 - - '@aws-crypto/supports-web-crypto@5.2.0': - dependencies: - tslib: 2.8.1 - - '@aws-crypto/util@5.2.0': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/util-utf8': 2.3.0 - tslib: 2.8.1 - - '@aws-sdk/client-s3@3.1031.0': - dependencies: - '@aws-crypto/sha1-browser': 5.2.0 - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.0 - '@aws-sdk/credential-provider-node': 3.972.31 - '@aws-sdk/middleware-bucket-endpoint': 3.972.10 - '@aws-sdk/middleware-expect-continue': 3.972.10 - '@aws-sdk/middleware-flexible-checksums': 3.974.8 - '@aws-sdk/middleware-host-header': 3.972.10 - '@aws-sdk/middleware-location-constraint': 3.972.10 - '@aws-sdk/middleware-logger': 3.972.10 - '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-sdk-s3': 3.972.29 - '@aws-sdk/middleware-ssec': 3.972.10 - '@aws-sdk/middleware-user-agent': 3.972.30 - '@aws-sdk/region-config-resolver': 3.972.12 - '@aws-sdk/signature-v4-multi-region': 3.996.17 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 - '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.16 - '@smithy/config-resolver': 4.4.16 - '@smithy/core': 3.23.15 - '@smithy/eventstream-serde-browser': 4.2.14 - '@smithy/eventstream-serde-config-resolver': 4.3.14 - '@smithy/eventstream-serde-node': 4.2.14 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/hash-blob-browser': 4.2.15 - '@smithy/hash-node': 4.2.14 - '@smithy/hash-stream-node': 4.2.14 - '@smithy/invalid-dependency': 4.2.14 - '@smithy/md5-js': 4.2.14 - '@smithy/middleware-content-length': 4.2.14 - '@smithy/middleware-endpoint': 4.4.30 - '@smithy/middleware-retry': 4.5.3 - '@smithy/middleware-serde': 4.2.18 - '@smithy/middleware-stack': 4.2.14 - '@smithy/node-config-provider': 4.3.14 - '@smithy/node-http-handler': 4.5.3 - '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.47 - '@smithy/util-defaults-mode-node': 4.2.52 - '@smithy/util-endpoints': 3.4.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.2 - '@smithy/util-stream': 4.5.23 - '@smithy/util-utf8': 4.2.2 - '@smithy/util-waiter': 4.2.16 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/core@3.974.0': - dependencies: - '@aws-sdk/types': 3.973.8 - '@aws-sdk/xml-builder': 3.972.18 - '@smithy/core': 3.23.15 - '@smithy/node-config-provider': 4.3.14 - '@smithy/property-provider': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/crc64-nvme@3.972.7': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.972.26': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.972.28': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/types': 3.973.8 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.5.3 - '@smithy/property-provider': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/util-stream': 4.5.23 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-ini@3.972.30': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/credential-provider-env': 3.972.26 - '@aws-sdk/credential-provider-http': 3.972.28 - '@aws-sdk/credential-provider-login': 3.972.30 - '@aws-sdk/credential-provider-process': 3.972.26 - '@aws-sdk/credential-provider-sso': 3.972.30 - '@aws-sdk/credential-provider-web-identity': 3.972.30 - '@aws-sdk/nested-clients': 3.996.20 - '@aws-sdk/types': 3.973.8 - '@smithy/credential-provider-imds': 4.2.14 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-login@3.972.30': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/nested-clients': 3.996.20 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-node@3.972.31': - dependencies: - '@aws-sdk/credential-provider-env': 3.972.26 - '@aws-sdk/credential-provider-http': 3.972.28 - '@aws-sdk/credential-provider-ini': 3.972.30 - '@aws-sdk/credential-provider-process': 3.972.26 - '@aws-sdk/credential-provider-sso': 3.972.30 - '@aws-sdk/credential-provider-web-identity': 3.972.30 - '@aws-sdk/types': 3.973.8 - '@smithy/credential-provider-imds': 4.2.14 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-process@3.972.26': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-sso@3.972.30': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/nested-clients': 3.996.20 - '@aws-sdk/token-providers': 3.1031.0 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-web-identity@3.972.30': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/nested-clients': 3.996.20 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/middleware-bucket-endpoint@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-arn-parser': 3.972.3 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-expect-continue@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/middleware-flexible-checksums@3.974.8': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@aws-crypto/crc32c': 5.2.0 - '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.974.0 - '@aws-sdk/crc64-nvme': 3.972.7 - '@aws-sdk/types': 3.973.8 - '@smithy/is-array-buffer': 4.2.2 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.23 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-host-header@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/middleware-location-constraint@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/middleware-logger@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/middleware-recursion-detection@3.972.11': - dependencies: - '@aws-sdk/types': 3.973.8 - '@aws/lambda-invoke-store': 0.2.4 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-s3@3.972.15': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-arn-parser': 3.972.2 - '@smithy/core': 3.23.15 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.10 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.23 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-s3@3.972.29': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-arn-parser': 3.972.3 - '@smithy/core': 3.23.15 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.23 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@aws-sdk/middleware-ssec@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/middleware-user-agent@3.972.30': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 - '@smithy/core': 3.23.15 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-retry': 4.3.2 - tslib: 2.8.1 - - '@aws-sdk/nested-clients@3.996.20': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.974.0 - '@aws-sdk/middleware-host-header': 3.972.10 - '@aws-sdk/middleware-logger': 3.972.10 - '@aws-sdk/middleware-recursion-detection': 3.972.11 - '@aws-sdk/middleware-user-agent': 3.972.30 - '@aws-sdk/region-config-resolver': 3.972.12 - '@aws-sdk/types': 3.973.8 - '@aws-sdk/util-endpoints': 3.996.7 - '@aws-sdk/util-user-agent-browser': 3.972.10 - '@aws-sdk/util-user-agent-node': 3.973.16 - '@smithy/config-resolver': 4.4.16 - '@smithy/core': 3.23.15 - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/hash-node': 4.2.14 - '@smithy/invalid-dependency': 4.2.14 - '@smithy/middleware-content-length': 4.2.14 - '@smithy/middleware-endpoint': 4.4.30 - '@smithy/middleware-retry': 4.5.3 - '@smithy/middleware-serde': 4.2.18 - '@smithy/middleware-stack': 4.2.14 - '@smithy/node-config-provider': 4.3.14 - '@smithy/node-http-handler': 4.5.3 - '@smithy/protocol-http': 5.3.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-body-length-node': 4.2.3 - '@smithy/util-defaults-mode-browser': 4.3.47 - '@smithy/util-defaults-mode-node': 4.2.52 - '@smithy/util-endpoints': 3.4.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/region-config-resolver@3.972.12': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/config-resolver': 4.4.16 - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/s3-request-presigner@3.1000.0': - dependencies: - '@aws-sdk/signature-v4-multi-region': 3.996.3 - '@aws-sdk/types': 3.973.4 - '@aws-sdk/util-format-url': 3.972.6 - '@smithy/middleware-endpoint': 4.4.20 - '@smithy/protocol-http': 5.3.10 - '@smithy/smithy-client': 4.12.0 - '@smithy/types': 4.13.0 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.996.17': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.29 - '@aws-sdk/types': 3.973.8 - '@smithy/protocol-http': 5.3.14 - '@smithy/signature-v4': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.996.3': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.972.15 - '@aws-sdk/types': 3.973.4 - '@smithy/protocol-http': 5.3.10 - '@smithy/signature-v4': 5.3.10 - '@smithy/types': 4.13.0 - tslib: 2.8.1 - - '@aws-sdk/token-providers@3.1031.0': - dependencies: - '@aws-sdk/core': 3.974.0 - '@aws-sdk/nested-clients': 3.996.20 - '@aws-sdk/types': 3.973.8 - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/types@3.973.4': - dependencies: - '@smithy/types': 4.13.0 - tslib: 2.8.1 - - '@aws-sdk/types@3.973.8': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@aws-sdk/util-arn-parser@3.972.2': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-arn-parser@3.972.3': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-endpoints@3.996.7': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-endpoints': 3.4.1 - tslib: 2.8.1 - - '@aws-sdk/util-format-url@3.972.6': - dependencies: - '@aws-sdk/types': 3.973.4 - '@smithy/querystring-builder': 4.2.10 - '@smithy/types': 4.13.0 - tslib: 2.8.1 - - '@aws-sdk/util-locate-window@3.965.5': - dependencies: - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-browser@3.972.10': - dependencies: - '@aws-sdk/types': 3.973.8 - '@smithy/types': 4.14.1 + '@aws-sdk/types': 3.974.2 + '@aws-sdk/xml-builder': 3.972.37 + '@aws/lambda-invoke-store': 0.3.0 + '@smithy/core': 3.31.1 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 bowser: 2.14.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.973.16': + '@aws-sdk/credential-provider-env@3.972.67': dependencies: - '@aws-sdk/middleware-user-agent': 3.972.30 - '@aws-sdk/types': 3.973.8 - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.972.18': + '@aws-sdk/credential-provider-http@3.972.69': dependencies: - '@smithy/types': 4.14.1 - fast-xml-parser: 5.5.8 + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@aws/lambda-invoke-store@0.2.4': {} + '@aws-sdk/credential-provider-ini@3.973.12': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/credential-provider-env': 3.972.67 + '@aws-sdk/credential-provider-http': 3.972.69 + '@aws-sdk/credential-provider-login': 3.972.74 + '@aws-sdk/credential-provider-process': 3.972.67 + '@aws-sdk/credential-provider-sso': 3.973.11 + '@aws-sdk/credential-provider-web-identity': 3.972.73 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 + '@smithy/types': 4.16.1 + tslib: 2.8.1 - '@biomejs/biome@2.4.12': + '@aws-sdk/credential-provider-login@3.972.74': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-node@3.972.78': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.67 + '@aws-sdk/credential-provider-http': 3.972.69 + '@aws-sdk/credential-provider-ini': 3.973.12 + '@aws-sdk/credential-provider-process': 3.972.67 + '@aws-sdk/credential-provider-sso': 3.973.11 + '@aws-sdk/credential-provider-web-identity': 3.972.73 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/credential-provider-imds': 4.4.16 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-process@3.972.67': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.973.11': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/token-providers': 3.1103.0 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-web-identity@3.972.73': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.972.72': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.997.41': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/fetch-http-handler': 5.6.13 + '@smithy/node-http-handler': 4.9.13 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/s3-request-presigner@3.1106.0': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/signature-v4-multi-region': 3.996.43 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.996.43': + dependencies: + '@aws-sdk/types': 3.974.2 + '@smithy/signature-v4': 5.6.12 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.1103.0': + dependencies: + '@aws-sdk/core': 3.977.6 + '@aws-sdk/nested-clients': 3.997.41 + '@aws-sdk/types': 3.974.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/types@3.974.2': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.972.37': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.3.0': {} + + '@biomejs/biome@2.5.7': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.4.12 - '@biomejs/cli-darwin-x64': 2.4.12 - '@biomejs/cli-linux-arm64': 2.4.12 - '@biomejs/cli-linux-arm64-musl': 2.4.12 - '@biomejs/cli-linux-x64': 2.4.12 - '@biomejs/cli-linux-x64-musl': 2.4.12 - '@biomejs/cli-win32-arm64': 2.4.12 - '@biomejs/cli-win32-x64': 2.4.12 + '@biomejs/cli-darwin-arm64': 2.5.7 + '@biomejs/cli-darwin-x64': 2.5.7 + '@biomejs/cli-linux-arm64': 2.5.7 + '@biomejs/cli-linux-arm64-musl': 2.5.7 + '@biomejs/cli-linux-x64': 2.5.7 + '@biomejs/cli-linux-x64-musl': 2.5.7 + '@biomejs/cli-win32-arm64': 2.5.7 + '@biomejs/cli-win32-x64': 2.5.7 - '@biomejs/cli-darwin-arm64@2.4.12': + '@biomejs/cli-darwin-arm64@2.5.7': optional: true - '@biomejs/cli-darwin-x64@2.4.12': + '@biomejs/cli-darwin-x64@2.5.7': optional: true - '@biomejs/cli-linux-arm64-musl@2.4.12': + '@biomejs/cli-linux-arm64-musl@2.5.7': optional: true - '@biomejs/cli-linux-arm64@2.4.12': + '@biomejs/cli-linux-arm64@2.5.7': optional: true - '@biomejs/cli-linux-x64-musl@2.4.12': + '@biomejs/cli-linux-x64-musl@2.5.7': optional: true - '@biomejs/cli-linux-x64@2.4.12': + '@biomejs/cli-linux-x64@2.5.7': optional: true - '@biomejs/cli-win32-arm64@2.4.12': + '@biomejs/cli-win32-arm64@2.5.7': optional: true - '@biomejs/cli-win32-x64@2.4.12': + '@biomejs/cli-win32-x64@2.5.7': optional: true - '@cloudflare/kv-asset-handler@0.4.1': - dependencies: - mime: 3.0.0 + '@cloudflare/kv-asset-handler@0.5.0': {} - '@cloudflare/unenv-preset@2.8.0(unenv@2.0.0-rc.24)(workerd@1.20260107.1)': + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260107.1 + workerd: 1.20260801.1 - '@cloudflare/vitest-pool-workers@0.12.1(@vitest/runner@4.1.4)(@vitest/snapshot@4.1.4)(vitest@4.1.4(vite@7.3.1))': + '@cloudflare/vitest-pool-workers@0.20.3(@vitest/runner@4.1.10)(@vitest/snapshot@4.1.10)(vitest@4.1.10(vite@8.2.1(esbuild@0.28.1)))': dependencies: - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - birpc: 0.2.14 - cjs-module-lexer: 1.4.3 - devalue: 5.6.1 - esbuild: 0.27.0 - get-port: 7.1.0 - miniflare: 4.20260107.0 - semver: 7.7.3 - vitest: 4.1.4(vite@7.3.1) - wrangler: 4.58.0 - zod: 3.25.76 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + cjs-module-lexer: 1.2.3 + esbuild: 0.28.1 + miniflare: 5.20260801.1-alpha + vitest: 4.1.10(vite@8.2.1(esbuild@0.28.1)) + wrangler: 4.120.0 + zod: 4.4.3 transitivePeerDependencies: - '@cloudflare/workers-types' - bufferutil - utf-8-validate - '@cloudflare/workerd-darwin-64@1.20260107.1': + '@cloudflare/workerd-darwin-64@1.20260801.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260107.1': + '@cloudflare/workerd-darwin-arm64@1.20260801.1': optional: true - '@cloudflare/workerd-linux-64@1.20260107.1': + '@cloudflare/workerd-linux-64@1.20260801.1': optional: true - '@cloudflare/workerd-linux-arm64@1.20260107.1': + '@cloudflare/workerd-linux-arm64@1.20260801.1': optional: true - '@cloudflare/workerd-windows-64@1.20260107.1': + '@cloudflare/workerd-windows-64@1.20260801.1': optional: true '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/runtime@1.8.1': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.27.0': + '@esbuild/aix-ppc64@0.28.1': optional: true - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/android-arm64@0.28.1': optional: true - '@esbuild/android-arm64@0.27.0': + '@esbuild/android-arm@0.28.1': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/android-x64@0.28.1': optional: true - '@esbuild/android-arm@0.27.0': + '@esbuild/darwin-arm64@0.28.1': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/darwin-x64@0.28.1': optional: true - '@esbuild/android-x64@0.27.0': + '@esbuild/freebsd-arm64@0.28.1': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/freebsd-x64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.0': + '@esbuild/linux-arm64@0.28.1': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/linux-arm@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.0': + '@esbuild/linux-ia32@0.28.1': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/linux-loong64@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.0': + '@esbuild/linux-mips64el@0.28.1': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/linux-ppc64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.0': + '@esbuild/linux-riscv64@0.28.1': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/linux-s390x@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.0': + '@esbuild/linux-x64@0.28.1': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/netbsd-arm64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.0': + '@esbuild/netbsd-x64@0.28.1': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/openbsd-arm64@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.0': + '@esbuild/openbsd-x64@0.28.1': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/openharmony-arm64@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.0': + '@esbuild/sunos-x64@0.28.1': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/win32-arm64@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.0': + '@esbuild/win32-ia32@0.28.1': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/win32-x64@0.28.1': optional: true - '@esbuild/linux-ppc64@0.27.0': - optional: true + '@img/colour@1.1.0': {} - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.27.0': - optional: true - - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.27.0': - optional: true - - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-x64@0.27.0': - optional: true - - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.27.0': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.27.0': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/openbsd-arm64@0.27.0': - optional: true - - '@esbuild/openbsd-arm64@0.27.7': - optional: true - - '@esbuild/openbsd-x64@0.27.0': - optional: true - - '@esbuild/openbsd-x64@0.27.7': - optional: true - - '@esbuild/openharmony-arm64@0.27.0': - optional: true - - '@esbuild/openharmony-arm64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.27.0': - optional: true - - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.27.0': - optional: true - - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.27.0': - optional: true - - '@esbuild/win32-ia32@0.27.7': - optional: true - - '@esbuild/win32-x64@0.27.0': - optional: true - - '@esbuild/win32-x64@0.27.7': - optional: true - - '@img/sharp-darwin-arm64@0.33.5': + '@img/sharp-darwin-arm64@0.35.2': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-arm64': 1.3.1 optional: true - '@img/sharp-darwin-x64@0.33.5': + '@img/sharp-darwin-x64@0.35.2': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.3.1 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - - '@img/sharp-wasm32@0.33.5': + '@img/sharp-freebsd-wasm32@0.35.2': dependencies: - '@emnapi/runtime': 1.8.1 + '@img/sharp-wasm32': 0.35.2 optional: true - '@img/sharp-win32-ia32@0.33.5': + '@img/sharp-libvips-darwin-arm64@1.3.1': optional: true - '@img/sharp-win32-x64@0.33.5': + '@img/sharp-libvips-darwin-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.1': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.1': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + optional: true + + '@img/sharp-linux-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.1 + optional: true + + '@img/sharp-linux-arm@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.1 + optional: true + + '@img/sharp-linux-ppc64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.1 + optional: true + + '@img/sharp-linux-riscv64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.1 + optional: true + + '@img/sharp-linux-s390x@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.1 + optional: true + + '@img/sharp-linux-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + optional: true + + '@img/sharp-wasm32@0.35.2': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-win32-arm64@0.35.2': + optional: true + + '@img/sharp-win32-ia32@0.35.2': + optional: true + + '@img/sharp-win32-x64@0.35.2': optional: true '@jridgewell/resolve-uri@3.1.2': {} @@ -2418,6 +1628,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@oxc-project/types@0.143.0': {} + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -2430,557 +1642,86 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@rollup/rollup-android-arm-eabi@4.60.1': + '@rolldown/binding-android-arm64@1.2.3': optional: true - '@rollup/rollup-android-arm64@4.60.1': + '@rolldown/binding-darwin-arm64@1.2.3': optional: true - '@rollup/rollup-darwin-arm64@4.60.1': + '@rolldown/binding-darwin-x64@1.2.3': optional: true - '@rollup/rollup-darwin-x64@4.60.1': + '@rolldown/binding-freebsd-x64@1.2.3': optional: true - '@rollup/rollup-freebsd-arm64@4.60.1': + '@rolldown/binding-linux-arm-gnueabihf@1.2.3': optional: true - '@rollup/rollup-freebsd-x64@4.60.1': + '@rolldown/binding-linux-arm64-gnu@1.2.3': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + '@rolldown/binding-linux-arm64-musl@1.2.3': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.1': + '@rolldown/binding-linux-ppc64-gnu@1.2.3': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.1': + '@rolldown/binding-linux-s390x-gnu@1.2.3': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.1': + '@rolldown/binding-linux-x64-gnu@1.2.3': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.1': + '@rolldown/binding-linux-x64-musl@1.2.3': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.1': + '@rolldown/binding-openharmony-arm64@1.2.3': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.1': + '@rolldown/binding-win32-arm64-msvc@1.2.3': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.1': + '@rolldown/binding-win32-x64-msvc@1.2.3': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.1': - optional: true + '@rolldown/pluginutils@1.0.1': {} '@sindresorhus/is@7.2.0': {} - '@smithy/chunked-blob-reader-native@4.2.3': + '@smithy/core@3.31.1': dependencies: - '@smithy/util-base64': 4.3.2 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/chunked-blob-reader@5.2.2': + '@smithy/credential-provider-imds@4.4.16': dependencies: + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/config-resolver@4.4.16': + '@smithy/fetch-http-handler@5.6.13': dependencies: - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-config-provider': 4.2.2 - '@smithy/util-endpoints': 3.4.1 - '@smithy/util-middleware': 4.2.14 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/core@3.23.15': + '@smithy/node-http-handler@4.9.13': dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.23 - '@smithy/util-utf8': 4.2.2 - '@smithy/uuid': 1.1.2 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/core@3.23.6': + '@smithy/signature-v4@5.6.12': dependencies: - '@smithy/middleware-serde': 4.2.18 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-body-length-browser': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-stream': 4.5.23 - '@smithy/util-utf8': 4.2.2 - '@smithy/uuid': 1.1.1 + '@smithy/core': 3.31.1 + '@smithy/types': 4.16.1 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.2.14': + '@smithy/types@4.16.1': dependencies: - '@smithy/node-config-provider': 4.3.14 - '@smithy/property-provider': 4.2.14 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 tslib: 2.8.1 - '@smithy/eventstream-codec@4.2.14': - dependencies: - '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.14.1 - '@smithy/util-hex-encoding': 4.2.2 - tslib: 2.8.1 - - '@smithy/eventstream-serde-browser@4.2.14': - dependencies: - '@smithy/eventstream-serde-universal': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/eventstream-serde-config-resolver@4.3.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/eventstream-serde-node@4.2.14': - dependencies: - '@smithy/eventstream-serde-universal': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/eventstream-serde-universal@4.2.14': - dependencies: - '@smithy/eventstream-codec': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/fetch-http-handler@5.3.17': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/querystring-builder': 4.2.14 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - tslib: 2.8.1 - - '@smithy/hash-blob-browser@4.2.15': - dependencies: - '@smithy/chunked-blob-reader': 5.2.2 - '@smithy/chunked-blob-reader-native': 4.2.3 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/hash-node@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-buffer-from': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/hash-stream-node@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/invalid-dependency@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/is-array-buffer@2.2.0': - dependencies: - tslib: 2.8.1 - - '@smithy/is-array-buffer@4.2.1': - dependencies: - tslib: 2.8.1 - - '@smithy/is-array-buffer@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/md5-js@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/middleware-content-length@4.2.14': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.4.20': - dependencies: - '@smithy/core': 3.23.6 - '@smithy/middleware-serde': 4.2.11 - '@smithy/node-config-provider': 4.3.10 - '@smithy/shared-ini-file-loader': 4.4.5 - '@smithy/types': 4.13.0 - '@smithy/url-parser': 4.2.10 - '@smithy/util-middleware': 4.2.10 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.4.30': - dependencies: - '@smithy/core': 3.23.15 - '@smithy/middleware-serde': 4.2.18 - '@smithy/node-config-provider': 4.3.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - '@smithy/url-parser': 4.2.14 - '@smithy/util-middleware': 4.2.14 - tslib: 2.8.1 - - '@smithy/middleware-retry@4.5.3': - dependencies: - '@smithy/core': 3.23.15 - '@smithy/node-config-provider': 4.3.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/service-error-classification': 4.2.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-retry': 4.3.2 - '@smithy/uuid': 1.1.2 - tslib: 2.8.1 - - '@smithy/middleware-serde@4.2.11': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/middleware-serde@4.2.18': - dependencies: - '@smithy/core': 3.23.15 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/middleware-stack@4.2.10': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/middleware-stack@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/node-config-provider@4.3.10': - dependencies: - '@smithy/property-provider': 4.2.10 - '@smithy/shared-ini-file-loader': 4.4.5 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/node-config-provider@4.3.14': - dependencies: - '@smithy/property-provider': 4.2.14 - '@smithy/shared-ini-file-loader': 4.4.9 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/node-http-handler@4.5.3': - dependencies: - '@smithy/protocol-http': 5.3.14 - '@smithy/querystring-builder': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/property-provider@4.2.10': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/property-provider@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/protocol-http@5.3.10': - dependencies: - '@smithy/types': 4.13.0 - tslib: 2.8.1 - - '@smithy/protocol-http@5.3.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/querystring-builder@4.2.10': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-uri-escape': 4.2.1 - tslib: 2.8.1 - - '@smithy/querystring-builder@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - '@smithy/util-uri-escape': 4.2.2 - tslib: 2.8.1 - - '@smithy/querystring-parser@4.2.10': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/querystring-parser@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/service-error-classification@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - - '@smithy/shared-ini-file-loader@4.4.5': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/shared-ini-file-loader@4.4.9': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/signature-v4@5.3.10': - dependencies: - '@smithy/is-array-buffer': 4.2.1 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-hex-encoding': 4.2.1 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-uri-escape': 4.2.1 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/signature-v4@5.3.14': - dependencies: - '@smithy/is-array-buffer': 4.2.2 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-hex-encoding': 4.2.2 - '@smithy/util-middleware': 4.2.14 - '@smithy/util-uri-escape': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/smithy-client@4.12.0': - dependencies: - '@smithy/core': 3.23.6 - '@smithy/middleware-endpoint': 4.4.20 - '@smithy/middleware-stack': 4.2.10 - '@smithy/protocol-http': 5.3.10 - '@smithy/types': 4.13.0 - '@smithy/util-stream': 4.5.15 - tslib: 2.8.1 - - '@smithy/smithy-client@4.12.11': - dependencies: - '@smithy/core': 3.23.15 - '@smithy/middleware-endpoint': 4.4.30 - '@smithy/middleware-stack': 4.2.14 - '@smithy/protocol-http': 5.3.14 - '@smithy/types': 4.14.1 - '@smithy/util-stream': 4.5.23 - tslib: 2.8.1 - - '@smithy/types@4.13.0': - dependencies: - tslib: 2.8.1 - - '@smithy/types@4.14.1': - dependencies: - tslib: 2.8.1 - - '@smithy/url-parser@4.2.10': - dependencies: - '@smithy/querystring-parser': 4.2.10 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/url-parser@4.2.14': - dependencies: - '@smithy/querystring-parser': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-base64@4.3.2': - dependencies: - '@smithy/util-buffer-from': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-body-length-browser@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-body-length-node@4.2.3': - dependencies: - tslib: 2.8.1 - - '@smithy/util-buffer-from@2.2.0': - dependencies: - '@smithy/is-array-buffer': 2.2.0 - tslib: 2.8.1 - - '@smithy/util-buffer-from@4.2.1': - dependencies: - '@smithy/is-array-buffer': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-buffer-from@4.2.2': - dependencies: - '@smithy/is-array-buffer': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-config-provider@4.2.1': - dependencies: - tslib: 2.8.1 - - '@smithy/util-config-provider@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-defaults-mode-browser@4.3.47': - dependencies: - '@smithy/property-provider': 4.2.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-defaults-mode-node@4.2.52': - dependencies: - '@smithy/config-resolver': 4.4.16 - '@smithy/credential-provider-imds': 4.2.14 - '@smithy/node-config-provider': 4.3.14 - '@smithy/property-provider': 4.2.14 - '@smithy/smithy-client': 4.12.11 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-endpoints@3.4.1': - dependencies: - '@smithy/node-config-provider': 4.3.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-hex-encoding@4.2.1': - dependencies: - tslib: 2.8.1 - - '@smithy/util-hex-encoding@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-middleware@4.2.10': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-middleware@4.2.14': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-retry@4.3.2': - dependencies: - '@smithy/service-error-classification': 4.2.14 - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/util-stream@4.5.15': - dependencies: - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.5.3 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-buffer-from': 4.2.1 - '@smithy/util-hex-encoding': 4.2.1 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-stream@4.5.23': - dependencies: - '@smithy/fetch-http-handler': 5.3.17 - '@smithy/node-http-handler': 4.5.3 - '@smithy/types': 4.14.1 - '@smithy/util-base64': 4.3.2 - '@smithy/util-buffer-from': 4.2.2 - '@smithy/util-hex-encoding': 4.2.2 - '@smithy/util-utf8': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-uri-escape@4.2.1': - dependencies: - tslib: 2.8.1 - - '@smithy/util-uri-escape@4.2.2': - dependencies: - tslib: 2.8.1 - - '@smithy/util-utf8@2.3.0': - dependencies: - '@smithy/util-buffer-from': 2.2.0 - tslib: 2.8.1 - - '@smithy/util-utf8@4.2.2': - dependencies: - '@smithy/util-buffer-from': 4.2.2 - tslib: 2.8.1 - - '@smithy/util-waiter@4.2.16': - dependencies: - '@smithy/types': 4.14.1 - tslib: 2.8.1 - - '@smithy/uuid@1.1.1': - dependencies: - tslib: 2.8.1 - - '@smithy/uuid@1.1.2': - dependencies: - tslib: 2.8.1 - - '@speed-highlight/core@1.2.14': {} + '@speed-highlight/core@1.2.24': {} '@standard-schema/spec@1.1.0': {} @@ -2991,82 +1732,120 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} - '@vitest/expect@4.1.4': + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 chai: 6.2.2 - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/mocker@4.1.4(vite@7.3.1)': + '@vitest/mocker@4.1.10(vite@8.2.1(esbuild@0.28.1))': dependencies: - '@vitest/spy': 4.1.4 + '@vitest/spy': 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1 + vite: 8.2.1(esbuild@0.28.1) - '@vitest/pretty-format@4.1.4': + '@vitest/pretty-format@4.1.10': dependencies: - tinyrainbow: 3.1.0 + tinyrainbow: 3.1.1 - '@vitest/runner@4.1.4': + '@vitest/runner@4.1.10': dependencies: - '@vitest/utils': 4.1.4 + '@vitest/utils': 4.1.10 pathe: 2.0.3 - '@vitest/snapshot@4.1.4': + '@vitest/snapshot@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.4 - '@vitest/utils': 4.1.4 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.4': {} + '@vitest/spy@4.1.10': {} - '@vitest/utils@4.1.4': + '@vitest/utils@4.1.10': dependencies: - '@vitest/pretty-format': 4.1.4 + '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - - acorn-walk@8.3.2: {} - - acorn@8.14.0: {} + tinyrainbow: 3.1.1 assertion-error@2.0.1: {} aws4fetch@1.0.20: {} - birpc@0.2.14: {} - blake3-wasm@2.1.5: {} bowser@2.14.1: {} chai@6.2.2: {} - cjs-module-lexer@1.4.3: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 + cjs-module-lexer@1.2.3: {} convert-source-map@2.0.0: {} @@ -3074,132 +1853,122 @@ snapshots: detect-libc@2.1.2: {} - devalue@5.6.1: {} - error-stack-parser-es@1.0.5: {} - es-module-lexer@2.0.0: {} + es-module-lexer@2.3.1: {} - esbuild@0.27.0: + esbuild@0.28.1: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.0 - '@esbuild/android-arm': 0.27.0 - '@esbuild/android-arm64': 0.27.0 - '@esbuild/android-x64': 0.27.0 - '@esbuild/darwin-arm64': 0.27.0 - '@esbuild/darwin-x64': 0.27.0 - '@esbuild/freebsd-arm64': 0.27.0 - '@esbuild/freebsd-x64': 0.27.0 - '@esbuild/linux-arm': 0.27.0 - '@esbuild/linux-arm64': 0.27.0 - '@esbuild/linux-ia32': 0.27.0 - '@esbuild/linux-loong64': 0.27.0 - '@esbuild/linux-mips64el': 0.27.0 - '@esbuild/linux-ppc64': 0.27.0 - '@esbuild/linux-riscv64': 0.27.0 - '@esbuild/linux-s390x': 0.27.0 - '@esbuild/linux-x64': 0.27.0 - '@esbuild/netbsd-arm64': 0.27.0 - '@esbuild/netbsd-x64': 0.27.0 - '@esbuild/openbsd-arm64': 0.27.0 - '@esbuild/openbsd-x64': 0.27.0 - '@esbuild/openharmony-arm64': 0.27.0 - '@esbuild/sunos-x64': 0.27.0 - '@esbuild/win32-arm64': 0.27.0 - '@esbuild/win32-ia32': 0.27.0 - '@esbuild/win32-x64': 0.27.0 - - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 - exit-hook@2.2.1: {} + expect-type@1.4.0: {} - expect-type@1.3.0: {} - - fast-xml-builder@1.1.5: - dependencies: - path-expression-matcher: 1.5.0 - - fast-xml-parser@5.5.8: - dependencies: - fast-xml-builder: 1.1.5 - path-expression-matcher: 1.5.0 - strnum: 2.2.3 - - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 fsevents@2.3.3: optional: true - get-port@7.1.0: {} - - glob-to-regexp@0.4.1: {} - - is-arrayish@0.3.4: {} - kleur@4.1.5: {} + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - mime@3.0.0: {} - - miniflare@4.20260107.0: + miniflare@5.20260801.1-alpha: dependencies: '@cspotcode/source-map-support': 0.8.1 - acorn: 8.14.0 - acorn-walk: 8.3.2 - exit-hook: 2.2.1 - glob-to-regexp: 0.4.1 - sharp: 0.33.5 - stoppable: 1.1.0 - undici: 7.14.0 - workerd: 1.20260107.1 - ws: 8.18.0 + sharp: 0.35.2 + undici: 7.29.0 + workerd: 1.20260801.1 + ws: 8.21.0 youch: 4.1.0-beta.10 - zod: 3.25.76 transitivePeerDependencies: - bufferutil - utf-8-validate - nanoid@3.3.11: {} + nanoid@3.3.18: {} - obug@2.1.1: {} - - path-expression-matcher@1.5.0: {} + obug@2.1.4: {} path-to-regexp@6.3.0: {} @@ -3207,144 +1976,152 @@ snapshots: picocolors@1.1.1: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} - postcss@8.5.10: + postcss@8.5.26: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.18 picocolors: 1.1.1 source-map-js: 1.2.1 - rollup@4.60.1: + rolldown@1.2.3: dependencies: - '@types/estree': 1.0.8 + '@oxc-project/types': 0.143.0 + '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.1 - '@rollup/rollup-android-arm64': 4.60.1 - '@rollup/rollup-darwin-arm64': 4.60.1 - '@rollup/rollup-darwin-x64': 4.60.1 - '@rollup/rollup-freebsd-arm64': 4.60.1 - '@rollup/rollup-freebsd-x64': 4.60.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 - '@rollup/rollup-linux-arm-musleabihf': 4.60.1 - '@rollup/rollup-linux-arm64-gnu': 4.60.1 - '@rollup/rollup-linux-arm64-musl': 4.60.1 - '@rollup/rollup-linux-loong64-gnu': 4.60.1 - '@rollup/rollup-linux-loong64-musl': 4.60.1 - '@rollup/rollup-linux-ppc64-gnu': 4.60.1 - '@rollup/rollup-linux-ppc64-musl': 4.60.1 - '@rollup/rollup-linux-riscv64-gnu': 4.60.1 - '@rollup/rollup-linux-riscv64-musl': 4.60.1 - '@rollup/rollup-linux-s390x-gnu': 4.60.1 - '@rollup/rollup-linux-x64-gnu': 4.60.1 - '@rollup/rollup-linux-x64-musl': 4.60.1 - '@rollup/rollup-openbsd-x64': 4.60.1 - '@rollup/rollup-openharmony-arm64': 4.60.1 - '@rollup/rollup-win32-arm64-msvc': 4.60.1 - '@rollup/rollup-win32-ia32-msvc': 4.60.1 - '@rollup/rollup-win32-x64-gnu': 4.60.1 - '@rollup/rollup-win32-x64-msvc': 4.60.1 - fsevents: 2.3.3 + '@rolldown/binding-android-arm64': 1.2.3 + '@rolldown/binding-darwin-arm64': 1.2.3 + '@rolldown/binding-darwin-x64': 1.2.3 + '@rolldown/binding-freebsd-x64': 1.2.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.3 + '@rolldown/binding-linux-arm64-gnu': 1.2.3 + '@rolldown/binding-linux-arm64-musl': 1.2.3 + '@rolldown/binding-linux-ppc64-gnu': 1.2.3 + '@rolldown/binding-linux-s390x-gnu': 1.2.3 + '@rolldown/binding-linux-x64-gnu': 1.2.3 + '@rolldown/binding-linux-x64-musl': 1.2.3 + '@rolldown/binding-openharmony-arm64': 1.2.3 + '@rolldown/binding-win32-arm64-msvc': 1.2.3 + '@rolldown/binding-win32-x64-msvc': 1.2.3 - semver@7.7.3: {} + semver@7.8.5: {} - sharp@0.33.5: + sharp@0.35.2: dependencies: - color: 4.2.3 + '@img/colour': 1.1.0 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.8.5 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 + '@img/sharp-darwin-arm64': 0.35.2 + '@img/sharp-darwin-x64': 0.35.2 + '@img/sharp-freebsd-wasm32': 0.35.2 + '@img/sharp-libvips-darwin-arm64': 1.3.1 + '@img/sharp-libvips-darwin-x64': 1.3.1 + '@img/sharp-libvips-linux-arm': 1.3.1 + '@img/sharp-libvips-linux-arm64': 1.3.1 + '@img/sharp-libvips-linux-ppc64': 1.3.1 + '@img/sharp-libvips-linux-riscv64': 1.3.1 + '@img/sharp-libvips-linux-s390x': 1.3.1 + '@img/sharp-libvips-linux-x64': 1.3.1 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + '@img/sharp-linux-arm': 0.35.2 + '@img/sharp-linux-arm64': 0.35.2 + '@img/sharp-linux-ppc64': 0.35.2 + '@img/sharp-linux-riscv64': 0.35.2 + '@img/sharp-linux-s390x': 0.35.2 + '@img/sharp-linux-x64': 0.35.2 + '@img/sharp-linuxmusl-arm64': 0.35.2 + '@img/sharp-linuxmusl-x64': 0.35.2 + '@img/sharp-webcontainers-wasm32': 0.35.2 + '@img/sharp-win32-arm64': 0.35.2 + '@img/sharp-win32-ia32': 0.35.2 + '@img/sharp-win32-x64': 0.35.2 siginfo@2.0.0: {} - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - source-map-js@1.2.1: {} stackback@0.0.2: {} - std-env@4.1.0: {} - - stoppable@1.1.0: {} - - strnum@2.2.3: {} + std-env@4.2.0: {} supports-color@10.2.2: {} tinybench@2.9.0: {} - tinyexec@1.1.1: {} + tinyexec@1.3.0: {} - tinyglobby@0.2.16: + tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 - tinyrainbow@3.1.0: {} + tinyrainbow@3.1.1: {} tslib@2.8.1: {} - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 - undici@7.14.0: {} + undici@7.29.0: {} unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 - vite@7.3.1: + vite@8.2.1(esbuild@0.28.1): dependencies: - esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.10 - rollup: 4.60.1 - tinyglobby: 0.2.16 + lightningcss: 1.33.0 + picomatch: 4.0.5 + postcss: 8.5.26 + rolldown: 1.2.3 + tinyglobby: 0.2.17 optionalDependencies: + esbuild: 0.28.1 fsevents: 2.3.3 - vitest@4.1.4(vite@7.3.1): + vitest@4.1.10(vite@8.2.1(esbuild@0.28.1)): dependencies: - '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@7.3.1) - '@vitest/pretty-format': 4.1.4 - '@vitest/runner': 4.1.4 - '@vitest/snapshot': 4.1.4 - '@vitest/spy': 4.1.4 - '@vitest/utils': 4.1.4 - es-module-lexer: 2.0.0 - expect-type: 1.3.0 + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.2.1(esbuild@0.28.1)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 magic-string: 0.30.21 - obug: 2.1.1 + obug: 2.1.4 pathe: 2.0.3 - picomatch: 4.0.4 - std-env: 4.1.0 + picomatch: 4.0.5 + std-env: 4.2.0 tinybench: 2.9.0 - tinyexec: 1.1.1 - tinyglobby: 0.2.16 - tinyrainbow: 3.1.0 - vite: 7.3.1 + tinyexec: 1.3.0 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.1(esbuild@0.28.1) why-is-node-running: 2.3.0 transitivePeerDependencies: - msw @@ -3354,31 +2131,31 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - workerd@1.20260107.1: + workerd@1.20260801.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260107.1 - '@cloudflare/workerd-darwin-arm64': 1.20260107.1 - '@cloudflare/workerd-linux-64': 1.20260107.1 - '@cloudflare/workerd-linux-arm64': 1.20260107.1 - '@cloudflare/workerd-windows-64': 1.20260107.1 + '@cloudflare/workerd-darwin-64': 1.20260801.1 + '@cloudflare/workerd-darwin-arm64': 1.20260801.1 + '@cloudflare/workerd-linux-64': 1.20260801.1 + '@cloudflare/workerd-linux-arm64': 1.20260801.1 + '@cloudflare/workerd-windows-64': 1.20260801.1 - wrangler@4.58.0: + wrangler@4.120.0: dependencies: - '@cloudflare/kv-asset-handler': 0.4.1 - '@cloudflare/unenv-preset': 2.8.0(unenv@2.0.0-rc.24)(workerd@1.20260107.1) + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260801.1) blake3-wasm: 2.1.5 - esbuild: 0.27.0 - miniflare: 4.20260107.0 + esbuild: 0.28.1 + miniflare: 5.20260801.1-alpha path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260107.1 + workerd: 1.20260801.1 optionalDependencies: fsevents: 2.3.3 transitivePeerDependencies: - bufferutil - utf-8-validate - ws@8.18.0: {} + ws@8.21.0: {} youch-core@0.3.3: dependencies: @@ -3389,8 +2166,8 @@ snapshots: dependencies: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 - '@speed-highlight/core': 1.2.14 + '@speed-highlight/core': 1.2.24 cookie: 1.1.1 youch-core: 0.3.3 - zod@3.25.76: {} + zod@4.4.3: {} diff --git a/src/aws-chunked.ts b/src/aws-chunked.ts new file mode 100644 index 0000000..1b4a69f --- /dev/null +++ b/src/aws-chunked.ts @@ -0,0 +1,158 @@ +const MAX_CONTROL_LINE = 128; + +type DecoderState = "HEADER" | "DATA" | "CRLF" | "TRAILER" | "DONE"; + +function asBytes(value: Uint8Array | ArrayBuffer): Uint8Array { + return value instanceof Uint8Array ? value : new Uint8Array(value); +} + +/** Iterates decoded payload views without scanning or copying payload bytes. */ +export async function* decodedBodyChunks(stream: ReadableStream | null, awsChunked: boolean): AsyncGenerator { + if (!stream) return; + const reader = stream.getReader(); + if (!awsChunked) { + try { + while (true) { + const { done, value } = await reader.read(); + if (done) return; + if (value.byteLength > 0) yield asBytes(value); + } + } finally { + reader.releaseLock(); + } + } + + let state: DecoderState = "HEADER"; + let remaining = 0; + let control: number[] = []; + let emitted = 0; + + const consumeControlByte = (byte: number): string | null => { + control.push(byte); + if (control.length > MAX_CONTROL_LINE) throw new Error("Invalid aws-chunked control line"); + const length = control.length; + if (length < 2 || control[length - 2] !== 13 || control[length - 1] !== 10) return null; + const line = new TextDecoder().decode(new Uint8Array(control.slice(0, -2))); + control = []; + return line; + }; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const chunk = asBytes(value); + let offset = 0; + while (offset < chunk.byteLength) { + if (state === "DATA") { + const length = Math.min(remaining, chunk.byteLength - offset); + if (length > 0) { + emitted += length; + remaining -= length; + yield chunk.subarray(offset, offset + length); + offset += length; + } + if (remaining === 0) state = "CRLF"; + continue; + } + + const line = consumeControlByte(chunk[offset++]); + if (line === null) continue; + + if (state === "HEADER") { + const sizeText = line.split(";", 1)[0]; + if (!/^[0-9a-fA-F]+$/.test(sizeText)) throw new Error("Invalid aws-chunked chunk size"); + remaining = Number.parseInt(sizeText, 16); + if (!Number.isSafeInteger(remaining)) throw new Error("aws-chunked chunk is too large"); + state = remaining === 0 ? "TRAILER" : "DATA"; + } else if (state === "CRLF") { + if (line !== "") throw new Error("Invalid aws-chunked data terminator"); + state = "HEADER"; + } else if (state === "TRAILER" && line === "") { + state = "DONE"; + if (offset !== chunk.byteLength) throw new Error("Unexpected bytes after aws-chunked trailer"); + } else if (state === "DONE") { + throw new Error("Unexpected bytes after aws-chunked trailer"); + } + } + } + } finally { + reader.releaseLock(); + } + + if (state !== "DONE") throw new Error("Truncated aws-chunked body"); + return emitted; +} + +export async function pumpBody( + stream: ReadableStream | null, + writer: WritableStreamDefaultWriter, + options: { awsChunked: boolean; expectedLength?: number; skipBytes?: number; maxBytes?: number; prefix?: Uint8Array } = { awsChunked: false }, +): Promise<{ written: number; tail: Uint8Array }> { + let decoded = 0; + let written = 0; + let skip = options.skipBytes ?? 0; + const tailParts: Uint8Array[] = []; + let tailLength = 0; + + const consume = async (input: Uint8Array): Promise => { + let chunk = input; + if (skip >= chunk.byteLength) { + skip -= chunk.byteLength; + return; + } + if (skip > 0) { + chunk = chunk.subarray(skip); + skip = 0; + } + const remaining = options.maxBytes === undefined ? chunk.byteLength : Math.max(0, options.maxBytes - written); + const send = chunk.subarray(0, remaining); + if (send.byteLength > 0) { + await writer.write(send); + written += send.byteLength; + } + if (send.byteLength < chunk.byteLength) { + const tail = chunk.subarray(send.byteLength); + tailParts.push(tail); + tailLength += tail.byteLength; + } + }; + + try { + if (options.prefix) await consume(options.prefix); + for await (const chunk of decodedBodyChunks(stream, options.awsChunked)) { + decoded += chunk.byteLength; + await consume(chunk); + } + if (skip !== 0) throw new Error("Body is shorter than the committed Drive range"); + if (options.expectedLength !== undefined && decoded !== options.expectedLength) throw new Error(`Decoded body length ${decoded} does not match x-amz-decoded-content-length ${options.expectedLength}`); + await writer.close(); + } catch (error) { + await writer.abort(error).catch(() => undefined); + throw error; + } + + const tail = new Uint8Array(tailLength); + let offset = 0; + for (const part of tailParts) { + tail.set(part, offset); + offset += part.byteLength; + } + return { written, tail }; +} + +export function isAwsChunked(request: Request): boolean { + return ( + request.headers + .get("content-encoding") + ?.split(",") + .some((encoding) => encoding.trim().toLowerCase() === "aws-chunked") ?? false + ); +} + +export function decodedContentLength(request: Request): number | undefined { + const value = request.headers.get("x-amz-decoded-content-length") ?? request.headers.get("content-length"); + if (value === null || !/^\d+$/.test(value)) return undefined; + const length = Number(value); + return Number.isSafeInteger(length) ? length : undefined; +} diff --git a/src/aws-signature.ts b/src/aws-signature.ts new file mode 100644 index 0000000..e52831d --- /dev/null +++ b/src/aws-signature.ts @@ -0,0 +1,126 @@ +import type { Env } from "./types"; + +function encodeRFC3986(str: string): string { + return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); +} + +function bufToHex(buf: ArrayBuffer): string { + return Array.from(new Uint8Array(buf)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +async function hmacSha256(key: string | ArrayBuffer, data: string): Promise { + const keyData = typeof key === "string" ? new TextEncoder().encode(key) : key; + const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); + return await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(data)); +} + +async function sha256(data: string): Promise { + const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data)); + return bufToHex(hash); +} + +async function getSigningKey(secret: string, date: string, region: string, service: string): Promise { + const kDate = await hmacSha256(`AWS4${secret}`, date); + const kRegion = await hmacSha256(kDate, region); + const kService = await hmacSha256(kRegion, service); + return await hmacSha256(kService, "aws4_request"); +} + +async function createCanonicalRequest(request: Request, isQueryAuth: boolean): Promise { + const url = new URL(request.url); + + const method = request.method; + const canonicalUri = url.pathname || "/"; + + const params = Array.from(url.searchParams.entries()) + .filter(([key]) => key !== "X-Amz-Signature") + .sort(([a], [b]) => { + if (a < b) return -1; + if (a > b) return 1; + return 0; + }) + .map(([key, val]) => `${encodeRFC3986(key)}=${encodeRFC3986(val)}`) + .join("&"); + + let signedHeadersList: string[]; + if (isQueryAuth) { + signedHeadersList = (url.searchParams.get("X-Amz-SignedHeaders") ?? "host").split(";"); + } else { + const authHeader = request.headers.get("Authorization") ?? ""; + const match = authHeader.match(/SignedHeaders=([^,\s]+)/); + signedHeadersList = match ? match[1].split(";") : ["host"]; + } + + const canonicalHeaders = signedHeadersList + .map((h) => { + const headerName = h.toLowerCase(); + let headerValue = ""; + + if (headerName === "host") { + headerValue = url.hostname; + const port = url.port; + if (port && !((url.protocol === "https:" && port === "443") || (url.protocol === "http:" && port === "80"))) { + headerValue += `:${port}`; + } + } else { + headerValue = request.headers.get(headerName)?.trim() ?? ""; + } + + return `${headerName}:${headerValue}\n`; + }) + .join(""); + + const signedHeaders = signedHeadersList.join(";"); + const payloadHash = request.headers.get("x-amz-content-sha256") ?? "UNSIGNED-PAYLOAD"; + + return [method, canonicalUri, params, canonicalHeaders, signedHeaders, payloadHash].join("\n"); +} + +/** Verifies an AWS Signature V4 signature carried in either the Authorization header or presigned query params. */ +export async function verifySignature(request: Request, env: Env): Promise { + const url = new URL(request.url); + const headers = request.headers; + + const isQueryAuth = url.searchParams.has("X-Amz-Algorithm"); + + let algorithm: string; + if (isQueryAuth) { + algorithm = url.searchParams.get("X-Amz-Algorithm") ?? ""; + } else { + const authHeader = headers.get("Authorization") ?? ""; + algorithm = authHeader.split(" ")[0]; + } + + if (!algorithm?.includes("AWS4-HMAC-SHA256")) { + return false; + } + + const datetime = (isQueryAuth ? url.searchParams.get("X-Amz-Date") : headers.get("x-amz-date")) ?? ""; + + if (!datetime) return false; + + const date = datetime.substring(0, 8); + + const canonicalRequest = await createCanonicalRequest(request, isQueryAuth); + const hashedCanonicalRequest = await sha256(canonicalRequest); + + const credentialScope = `${date}/${env.REGION}/s3/aws4_request`; + const stringToSign = ["AWS4-HMAC-SHA256", datetime, credentialScope, hashedCanonicalRequest].join("\n"); + + const signingKey = await getSigningKey(env.SECRET_KEY, date, env.REGION, "s3"); + const signature = await hmacSha256(signingKey, stringToSign); + const signatureHex = bufToHex(signature); + + let expectedSignature = ""; + if (isQueryAuth) { + expectedSignature = url.searchParams.get("X-Amz-Signature") ?? ""; + } else { + const authHeader = headers.get("Authorization") ?? ""; + const match = authHeader.match(/Signature=([a-f0-9]+)/); + expectedSignature = match ? match[1] : ""; + } + + return signatureHex === expectedSignature; +} diff --git a/src/bucket-access.ts b/src/bucket-access.ts new file mode 100644 index 0000000..9fc0f98 --- /dev/null +++ b/src/bucket-access.ts @@ -0,0 +1,34 @@ +import type { Env } from "./types"; + +/** + * Checks whether the bucket is present in the ALLOWED_BUCKETS allowlist. + * Access is denied by default when the allowlist is missing or empty. + */ +export function isAllowedBucket(bucket: string, env: Env): boolean { + if (!env.ALLOWED_BUCKETS) { + return false; + } + + const allowedBuckets = env.ALLOWED_BUCKETS.split(",") + .map((b) => b.trim()) + .filter((b) => b); + + if (allowedBuckets.length === 0) { + return false; + } + + return allowedBuckets.includes(bucket); +} + +/** Checks whether the bucket allows unauthenticated read access. */ +export function isPublicReadBucket(bucket: string, env: Env): boolean { + if (!env.PUBLIC_READ_BUCKETS) { + return false; + } + + const publicReadBuckets = env.PUBLIC_READ_BUCKETS.split(",") + .map((b) => b.trim()) + .filter((b) => b); + + return publicReadBuckets.includes(bucket); +} diff --git a/src/drive-resumable.ts b/src/drive-resumable.ts new file mode 100644 index 0000000..b20c271 --- /dev/null +++ b/src/drive-resumable.ts @@ -0,0 +1,76 @@ +import type { DriveUploadResult } from "./types"; + +export const DRIVE_CHUNK_SIZE = 256 * 1024; +const DRIVE_FIELDS = "id,name,size,mimeType,md5Checksum"; + +export function alignedSendLen(driveOffset: number, available: number): number { + if (available <= 1) return 0; + return Math.max(0, Math.floor((driveOffset + available - 1) / DRIVE_CHUNK_SIZE) * DRIVE_CHUNK_SIZE - driveOffset); +} + +export async function createSession(accessToken: string, metadata: { name: string; parents: string[]; mimeType: string; existingFileId?: string }): Promise { + const url = metadata.existingFileId ? `https://www.googleapis.com/upload/drive/v3/files/${metadata.existingFileId}?uploadType=resumable&fields=${encodeURIComponent(DRIVE_FIELDS)}` : `https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&fields=${encodeURIComponent(DRIVE_FIELDS)}`; + const response = await fetch(url, { + method: metadata.existingFileId ? "PATCH" : "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json; charset=UTF-8", + "X-Upload-Content-Type": metadata.mimeType, + }, + body: JSON.stringify(metadata.existingFileId ? { name: metadata.name } : { name: metadata.name, parents: metadata.parents }), + }); + const uploadUrl = response.headers.get("Location"); + if (!response.ok || !uploadUrl) throw new Error(`Failed to create Drive resumable session (${response.status})`); + return uploadUrl; +} + +export function nextDriveOffset(response: Response): number { + const range = response.headers.get("Range"); + if (!range) return 0; + const match = /^bytes=0-(\d+)$/.exec(range); + if (!match) throw new Error("Drive returned an invalid committed range"); + return Number(match[1]) + 1; +} + +export async function queryStatus(uploadUrl: string, accessToken: string): Promise { + const response = await fetch(uploadUrl, { + method: "PUT", + headers: { Authorization: `Bearer ${accessToken}`, "Content-Length": "0", "Content-Range": "bytes */*" }, + }); + if (response.status === 308) return nextDriveOffset(response); + if (response.ok) { + const metadata = await response.json(); + return Number(metadata.size ?? 0); + } + throw new Error(`Drive resumable status query failed (${response.status})`); +} + +export async function putFinalChunk(uploadUrl: string, accessToken: string, startOffset: number, total: number, body: Uint8Array): Promise { + const response = await fetch(uploadUrl, { + method: "PUT", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Length": body.byteLength.toString(), + "Content-Range": `bytes ${startOffset}-${total - 1}/${total}`, + }, + body, + }); + if (!response.ok) throw new Error(`Drive final chunk failed (${response.status}): ${await response.text()}`); + return await response.json(); +} + +export async function createEmptyFile(accessToken: string, metadata: { name: string; parents: string[]; mimeType: string; existingFileId?: string }): Promise { + const url = metadata.existingFileId ? `https://www.googleapis.com/drive/v3/files/${metadata.existingFileId}?fields=${encodeURIComponent(DRIVE_FIELDS)}` : `https://www.googleapis.com/drive/v3/files?fields=${encodeURIComponent(DRIVE_FIELDS)}`; + const response = await fetch(url, { + method: metadata.existingFileId ? "PATCH" : "POST", + headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" }, + body: JSON.stringify(metadata.existingFileId ? { name: metadata.name } : { name: metadata.name, parents: metadata.parents, mimeType: metadata.mimeType }), + }); + if (!response.ok) throw new Error(`Drive empty-file creation failed (${response.status})`); + return await response.json(); +} + +export async function cancelSession(uploadUrl: string, accessToken: string): Promise { + const response = await fetch(uploadUrl, { method: "DELETE", headers: { Authorization: `Bearer ${accessToken}` } }); + if (response.status !== 499 && !response.ok && response.status !== 404) throw new Error(`Drive resumable cancellation failed (${response.status})`); +} diff --git a/src/google-drive.ts b/src/google-drive.ts new file mode 100644 index 0000000..d082c94 --- /dev/null +++ b/src/google-drive.ts @@ -0,0 +1,270 @@ +import { decodedContentLength, isAwsChunked, pumpBody } from "./aws-chunked"; +import type { DriveDownloadResult, DriveFileMetadata, DriveUploadResult, Env, GoogleDriveFile, GoogleDriveSearchResponse } from "./types"; + +interface GoogleTokenResponse { + access_token: string; + expires_in: number; + error_description?: string; +} + +interface GoogleDriveCreateResponse { + id: string; +} + +const DRIVE_FIELDS = "id,name,size,mimeType,md5Checksum"; + +function driveLiteral(value: string): string { + return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); +} + +function driveFilesUrl(q: string, fields: string): string { + const url = new URL("https://www.googleapis.com/drive/v3/files"); + url.searchParams.set("q", q); + url.searchParams.set("fields", fields); + return url.toString(); +} + +/** Fetches an OAuth access token, using the KV-cached one when available. */ +export async function getAccessToken(env: Env): Promise { + const cacheKey = "google_access_token"; + + const cachedToken = await env.AUTH_KV.get(cacheKey); + if (cachedToken) { + return cachedToken; + } + + const response = await fetch("https://oauth2.googleapis.com/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + client_id: env.GOOGLE_CLIENT_ID, + client_secret: env.GOOGLE_CLIENT_SECRET, + refresh_token: env.GOOGLE_REFRESH_TOKEN, + grant_type: "refresh_token", + }), + }); + + const data: GoogleTokenResponse = await response.json(); + if (!response.ok) { + throw new Error(`Token Error: ${data.error_description}`); + } + + await env.AUTH_KV.put(cacheKey, data.access_token, { + expirationTtl: data.expires_in - 60, + }); + + return data.access_token; +} + +/** Finds a folder by name under the given parent, creating it if it doesn't exist yet. */ +async function getOrCreateFolder(accessToken: string, folderName: string, parentId: string | null, env: Env): Promise { + // Include parentId in the cache key so folders with the same name in different parents don't collide. + const cacheKey = parentId ? `${parentId}/${folderName}` : folderName; + const cached = await env.FOLDER_CACHE.get(cacheKey); + if (cached) return cached; + + const parentQuery = parentId ? ` and '${parentId}' in parents` : ""; + const searchRes = await fetch(driveFilesUrl(`name='${driveLiteral(folderName)}' and mimeType='application/vnd.google-apps.folder' and trashed=false${parentQuery}`, "files(id,name)"), { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + const searchData: GoogleDriveSearchResponse = await searchRes.json(); + + if (searchData.files && searchData.files.length > 0) { + const folderId = searchData.files[0].id; + await env.FOLDER_CACHE.put(cacheKey, folderId, { expirationTtl: 3600 }); + return folderId; + } + + const createBody: { name: string; mimeType: string; parents?: string[] } = { + name: folderName, + mimeType: "application/vnd.google-apps.folder", + }; + + if (parentId) { + createBody.parents = [parentId]; + } + + const createRes = await fetch("https://www.googleapis.com/drive/v3/files", { + method: "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(createBody), + }); + + const createData: GoogleDriveCreateResponse = await createRes.json(); + await env.FOLDER_CACHE.put(cacheKey, createData.id, { expirationTtl: 3600 }); + return createData.id; +} + +/** Resolves an S3 object key to its parent folder ID, creating the directory hierarchy as needed. */ +export async function resolvePathToFolderAndFile(accessToken: string, bucket: string, objectKey: string, env: Env): Promise<{ parentFolderId: string; fileName: string }> { + let currentFolderId = await getOrCreateFolder(accessToken, bucket, null, env); + + const parts = objectKey.split("/").filter((p) => p); + + if (parts.length === 0) { + throw new Error("Invalid object key"); + } + + const fileName = parts[parts.length - 1]; + const directories = parts.slice(0, -1); + + for (const dir of directories) { + currentFolderId = await getOrCreateFolder(accessToken, dir, currentFolderId, env); + } + + return { + parentFolderId: currentFolderId, + fileName: fileName, + }; +} + +export async function streamUploadToDrive(accessToken: string, request: Request, bucket: string, objectKey: string, mimeType: string, env: Env): Promise { + const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); + const existing = await findFileInFolder(accessToken, parentFolderId, fileName); + const initUrl = existing ? `https://www.googleapis.com/upload/drive/v3/files/${existing.id}?uploadType=resumable&fields=${encodeURIComponent(DRIVE_FIELDS)}` : `https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&fields=${encodeURIComponent(DRIVE_FIELDS)}`; + const decodedLength = decodedContentLength(request); + + // Initialize a resumable upload session. + const initRes = await fetch(initUrl, { + method: existing ? "PATCH" : "POST", + headers: { + Authorization: `Bearer ${accessToken}`, + "X-Upload-Content-Type": mimeType, + ...(decodedLength === undefined ? {} : { "X-Upload-Content-Length": decodedLength.toString() }), + "Content-Type": "application/json; charset=UTF-8", + }, + body: JSON.stringify(existing ? { name: fileName } : { name: fileName, parents: [parentFolderId] }), + }); + + const uploadUrl = initRes.headers.get("Location"); + if (!uploadUrl) { + console.error(initRes.status); + console.error(await initRes.text()); + throw new Error("Failed to get upload URL"); + } + + let uploadRes: Response; + if (isAwsChunked(request)) { + if (decodedLength === undefined) throw new Error("x-amz-decoded-content-length is required for aws-chunked uploads"); + const decoded = new FixedLengthStream(decodedLength, { highWaterMark: 1 << 20 }); + const uploadPromise = fetch(uploadUrl, { + method: "PUT", + headers: { Authorization: `Bearer ${accessToken}`, "Content-Length": decodedLength.toString() }, + body: decoded.readable, + duplex: "half", + } as RequestInit); + await pumpBody(request.body, decoded.writable.getWriter(), { awsChunked: true, expectedLength: decodedLength }); + uploadRes = await uploadPromise; + } else { + const body = request.body ?? new Uint8Array(); + uploadRes = await fetch(uploadUrl, { + method: "PUT", + headers: { Authorization: `Bearer ${accessToken}`, ...(decodedLength === undefined ? {} : { "Content-Length": decodedLength.toString() }) }, + body, + duplex: "half", + } as RequestInit); + } + + if (!uploadRes.ok) { + const errorText = await uploadRes.text(); + throw new Error(`Upload failed: ${errorText}`); + } + + return await uploadRes.json(); +} + +export async function findFileInFolder(accessToken: string, folderId: string, fileName: string): Promise { + const searchRes = await fetch(driveFilesUrl(`name='${driveLiteral(fileName)}' and '${driveLiteral(folderId)}' in parents and trashed=false`, `files(${DRIVE_FIELDS})`), { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + const data: GoogleDriveSearchResponse = await searchRes.json(); + return data.files && data.files.length > 0 ? data.files[0] : null; +} + +export async function streamDownloadFromDrive(accessToken: string, bucket: string, objectKey: string, env: Env, range?: string): Promise { + const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); + const file = await findFileInFolder(accessToken, parentFolderId, fileName); + + if (!file) { + throw new Error("File not found"); + } + + const controller = new AbortController(); + const timeout = setTimeout(() => { + controller.abort(); + }, 30000); + + const downloadRes = await fetch(`https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`, { + headers: { Authorization: `Bearer ${accessToken}`, ...(range ? { Range: range } : {}) }, + signal: controller.signal, + }); + + clearTimeout(timeout); + + if (!downloadRes.ok) { + console.error(downloadRes.status); + console.error(await downloadRes.text()); + throw new Error("Download failed"); + } + + return { + body: downloadRes.body!, + contentType: file.mimeType || "application/octet-stream", + size: parseInt(file.size || "0", 10), + id: file.id, + md5Checksum: file.md5Checksum, + status: downloadRes.status, + contentRange: downloadRes.headers.get("Content-Range") ?? undefined, + contentLength: downloadRes.headers.get("Content-Length") ?? undefined, + }; +} + +export async function deleteFromDrive(accessToken: string, bucket: string, objectKey: string, env: Env): Promise { + const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); + const file = await findFileInFolder(accessToken, parentFolderId, fileName); + + if (!file) { + throw new Error("File not found"); + } + + const deleteRes = await fetch(`https://www.googleapis.com/drive/v3/files/${file.id}`, { + method: "DELETE", + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + if (!deleteRes.ok) { + throw new Error("Delete failed"); + } +} + +export async function getFileMetadata(accessToken: string, bucket: string, objectKey: string, env: Env): Promise { + const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); + const file = await findFileInFolder(accessToken, parentFolderId, fileName); + + if (!file) { + throw new Error("File not found"); + } + + return { + id: file.id, + mimeType: file.mimeType || "application/octet-stream", + size: parseInt(file.size || "0", 10), + md5Checksum: file.md5Checksum, + }; +} + +export async function listFiles(accessToken: string, bucket: string, env: Env): Promise { + const folderId = await getOrCreateFolder(accessToken, bucket, null, env); + + const listRes = await fetch(driveFilesUrl(`'${driveLiteral(folderId)}' in parents and trashed=false`, "files(id,name,mimeType,size,modifiedTime,md5Checksum)"), { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + const data: GoogleDriveSearchResponse = await listRes.json(); + return data.files || []; +} diff --git a/src/index.ts b/src/index.ts index 41c1130..d7b81b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,581 +1,36 @@ -/** - * S3-Compatible API Server on Cloudflare Workers - * Backend: Google Drive with streaming support and nested directory structure - */ +import { verifySignature } from "./aws-signature"; +import { isAllowedBucket, isPublicReadBucket } from "./bucket-access"; +import { getAccessToken } from "./google-drive"; +import { MultipartUploadDO } from "./multipart-do"; +import { dispatch } from "./router"; +import { S3Exception, s3Error } from "./s3-errors"; +import type { Env } from "./types"; + +export { MultipartUploadDO }; export default { - async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { + async fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise { + if (request.method === "OPTIONS") return new Response(null, { status: 204 }); + + const url = new URL(request.url); + const pathParts = url.pathname.split("/").filter(Boolean); + const bucket = pathParts[0] || ""; + const objectKey = pathParts.slice(1).join("/"); + const resource = url.pathname || "/"; + try { - if (request.method === "OPTIONS") { - return new Response(null, { status: 204 }); + if (!isAllowedBucket(bucket, env)) return s3Error("AccessDenied", 403, undefined, resource, request.method === "HEAD"); + + const isPublicRead = isPublicReadBucket(bucket, env) && (request.method === "GET" || request.method === "HEAD"); + if (!isPublicRead && !(await verifySignature(request, env))) { + return s3Error("SignatureDoesNotMatch", 403, undefined, resource, request.method === "HEAD"); } - const url = new URL(request.url); - const method = request.method; - - // パスからバケット名とオブジェクトキーを抽出 - const pathParts = url.pathname.split("/").filter((p) => p); - const bucket = pathParts[0] || ""; - const objectKey = pathParts.slice(1).join("/"); - - if (!isAllowedBucket(bucket, env)) { - return new Response("Access denied to this bucket", { status: 403 }); - } - - const isPublicRead = isPublicReadBucket(bucket, env); - const isReadMethod = method === "GET" || method === "HEAD"; - - if (!(isPublicRead && isReadMethod)) { - const isValid = await verifySignature(request, env); - if (!isValid) { - return new Response("Invalid Signature", { status: 403 }); - } - } - - const accessToken = await getAccessToken(env); - - if (method === "PUT" || method === "POST") { - // ストリーミングアップロード - if (!objectKey) { - return new Response("Object key required", { status: 400 }); - } - - const contentType = request.headers.get("Content-Type") || "application/octet-stream"; - const result = await streamUploadToDrive(accessToken, request.body, bucket, objectKey, contentType, env); - - return new Response(JSON.stringify(result), { - status: 200, - headers: { - "Content-Type": "application/json", - ETag: `"${result.id}"`, // Google DriveのファイルIDをETagとして使用 - }, - }); - } else if (method === "GET") { - // ストリーミングダウンロード - if (!objectKey) { - // バケット(フォルダ)の一覧を返す - const files = await listFiles(accessToken, bucket, env); - return new Response(generateListBucketResult(files, bucket), { - status: 200, - headers: { "Content-Type": "application/xml" }, - }); - } - - try { - const fileStream = await streamDownloadFromDrive(accessToken, bucket, objectKey, env); - - return new Response(fileStream.body, { - status: 200, - headers: { - "Content-Type": fileStream.contentType, - "Content-Length": fileStream.size.toString(), - "Cache-Control": "s-maxage=300, no-store", - ETag: `"${fileStream.id}"`, - }, - }); - } catch (e) { - const error = e as Error; - if (error.message === "File not found") { - return new Response("NoSuchKey", { status: 404 }); - } - throw e; - } - } else if (method === "DELETE") { - // ファイル削除 - if (!objectKey) { - return new Response("Object key required", { status: 400 }); - } - - try { - await deleteFromDrive(accessToken, bucket, objectKey, env); - return new Response(null, { status: 204 }); - } catch (e) { - const error = e as Error; - if (error.message === "File not found") { - return new Response(null, { status: 404 }); - } - throw e; - } - } else if (method === "HEAD") { - // メタデータ取得 - if (!objectKey) { - return new Response(null, { status: 400 }); - } - - try { - const metadata = await getFileMetadata(accessToken, bucket, objectKey, env); - return new Response(null, { - status: 200, - headers: { - "Content-Type": metadata.mimeType, - "Content-Length": metadata.size.toString(), - ETag: `"${metadata.id}"`, - }, - }); - } catch (e) { - const error = e as Error; - if (error.message === "File not found") { - return new Response(null, { status: 404 }); - } - throw e; - } - } - - return new Response("Method not allowed", { status: 405 }); - } catch (e) { - const error = e as Error; - console.error("Error:", error); - return new Response(error.message, { status: 500 }); + return await dispatch(request, env, await getAccessToken(env), bucket, objectKey); + } catch (error) { + if (error instanceof S3Exception) return s3Error(error.code, error.status, error.message, resource, request.method === "HEAD", error.headers); + console.error(JSON.stringify({ message: "request failed", error: error instanceof Error ? error.message : String(error), method: request.method, path: url.pathname })); + return s3Error("InternalError", 500, undefined, resource, request.method === "HEAD"); } }, } satisfies ExportedHandler; - -interface Env { - ACCESS_KEY: string; - SECRET_KEY: string; - REGION: string; - GOOGLE_CLIENT_ID: string; - GOOGLE_CLIENT_SECRET: string; - GOOGLE_REFRESH_TOKEN: string; - AUTH_KV: KVNamespace; - FOLDER_CACHE: KVNamespace; - ALLOWED_BUCKETS?: string; - PUBLIC_READ_BUCKETS?: string; -} - -interface GoogleDriveFile { - id: string; - name: string; - mimeType: string; - size: string; - modifiedTime?: string; -} - -interface GoogleDriveSearchResponse { - files?: GoogleDriveFile[]; -} - -function isAllowedBucket(bucket: string, env: Env): boolean { - console.log(bucket); - // 許可リストが設定されていない場合はすべて拒否 - if (!env.ALLOWED_BUCKETS) { - return false; - } - - const allowedBuckets = env.ALLOWED_BUCKETS.split(",") - .map((b) => b.trim()) - .filter((b) => b); - - // 空の許可リストの場合もすべて拒否 - if (allowedBuckets.length === 0) { - return false; - } - - // バケット名が許可リストに含まれているかチェック - return allowedBuckets.includes(bucket); -} - -function isPublicReadBucket(bucket: string, env: Env): boolean { - if (!env.PUBLIC_READ_BUCKETS) { - return false; - } - - const publicReadBuckets = env.PUBLIC_READ_BUCKETS.split(",") - .map((b) => b.trim()) - .filter((b) => b); - - return publicReadBuckets.includes(bucket); -} - -// ======================================== -// Google Drive API Functions -// ======================================== - -async function getAccessToken(env: Env): Promise { - const cacheKey = "google_access_token"; - - const cachedToken = await env.AUTH_KV.get(cacheKey); - if (cachedToken) { - return cachedToken; - } - - const response = await fetch("https://oauth2.googleapis.com/token", { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id: env.GOOGLE_CLIENT_ID, - client_secret: env.GOOGLE_CLIENT_SECRET, - refresh_token: env.GOOGLE_REFRESH_TOKEN, - grant_type: "refresh_token", - }), - }); - - const data: any = await response.json(); - if (!response.ok) { - throw new Error(`Token Error: ${data.error_description}`); - } - - await env.AUTH_KV.put(cacheKey, data.access_token, { - expirationTtl: data.expires_in - 60, - }); - - return data.access_token; -} - -async function getOrCreateFolder(accessToken: string, folderName: string, parentId: string | null, env: Env): Promise { - // キャッシュキーにparentIdを含める - const cacheKey = parentId ? `${parentId}/${folderName}` : folderName; - const cached = await env.FOLDER_CACHE.get(cacheKey); - if (cached) return cached; - - // フォルダを検索(親フォルダを指定) - const parentQuery = parentId ? ` and '${parentId}' in parents` : ""; - const searchRes = await fetch(`https://www.googleapis.com/drive/v3/files?q=name='${encodeURIComponent(folderName)}' and mimeType='application/vnd.google-apps.folder' and trashed=false${parentQuery}`, { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - - const searchData: GoogleDriveSearchResponse = await searchRes.json(); - - if (searchData.files && searchData.files.length > 0) { - const folderId = searchData.files[0].id; - await env.FOLDER_CACHE.put(cacheKey, folderId, { expirationTtl: 3600 }); - return folderId; - } - - // フォルダが存在しない場合は作成 - const createBody: any = { - name: folderName, - mimeType: "application/vnd.google-apps.folder", - }; - - if (parentId) { - createBody.parents = [parentId]; - } - - const createRes = await fetch("https://www.googleapis.com/drive/v3/files", { - method: "POST", - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(createBody), - }); - - const createData: any = await createRes.json(); - await env.FOLDER_CACHE.put(cacheKey, createData.id, { expirationTtl: 3600 }); - return createData.id; -} - -async function resolvePathToFolderAndFile(accessToken: string, bucket: string, objectKey: string, env: Env): Promise<{ parentFolderId: string; fileName: string }> { - // バケットのルートフォルダを取得 - let currentFolderId = await getOrCreateFolder(accessToken, bucket, null, env); - - // objectKeyを/で分割 - const parts = objectKey.split("/").filter((p) => p); - - if (parts.length === 0) { - throw new Error("Invalid object key"); - } - - const fileName = parts[parts.length - 1]; - const directories = parts.slice(0, -1); - - for (const dir of directories) { - currentFolderId = await getOrCreateFolder(accessToken, dir, currentFolderId, env); - } - - return { - parentFolderId: currentFolderId, - fileName: fileName, - }; -} - -async function streamUploadToDrive(accessToken: string, stream: ReadableStream | null, bucket: string, objectKey: string, mimeType: string, env: Env): Promise { - if (!stream) { - throw new Error("Request body is required"); - } - - // パスを解析してフォルダ階層を作成 - const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); - - // Resumable uploadの初期化 - const initRes = await fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable", { - method: "POST", - headers: { - Authorization: `Bearer ${accessToken}`, - "X-Upload-Content-Type": mimeType, - "Content-Type": "application/json; charset=UTF-8", - }, - body: JSON.stringify({ - name: fileName, - parents: [parentFolderId], - }), - }); - - const uploadUrl = initRes.headers.get("Location"); - if (!uploadUrl) { - console.error(initRes.status); - console.error(await initRes.text()); - throw new Error("Failed to get upload URL"); - } - - // ストリーミングアップロード - const uploadRes = await fetch(uploadUrl, { - method: "PUT", - headers: { - Authorization: `Bearer ${accessToken}`, - }, - body: stream, - duplex: "half", - } as RequestInit); - - if (!uploadRes.ok) { - const errorText = await uploadRes.text(); - throw new Error(`Upload failed: ${errorText}`); - } - - return await uploadRes.json(); -} - -async function findFileInFolder(accessToken: string, folderId: string, fileName: string): Promise { - const searchRes = await fetch(`https://www.googleapis.com/drive/v3/files?q=name='${encodeURIComponent(fileName)}' and '${folderId}' in parents and trashed=false&fields=files(id,name,mimeType,size)`, { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - - const data: GoogleDriveSearchResponse = await searchRes.json(); - return data.files && data.files.length > 0 ? data.files[0] : null; -} - -async function streamDownloadFromDrive(accessToken: string, bucket: string, objectKey: string, env: Env): Promise<{ body: ReadableStream; contentType: string; size: number; id: string }> { - const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); - const file = await findFileInFolder(accessToken, parentFolderId, fileName); - - if (!file) { - throw new Error("File not found"); - } - - const controller = new AbortController(); - const timeout = setTimeout(() => { - controller.abort(); - }, 5000); - - const downloadRes = await fetch(`https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`, { - headers: { Authorization: `Bearer ${accessToken}` }, - signal: controller.signal, - }); - - clearTimeout(timeout); - - if (!downloadRes.ok) { - console.error(downloadRes.status); - console.error(await downloadRes.text()); - throw new Error("Download failed"); - } - - return { - body: downloadRes.body!, - contentType: file.mimeType || "application/octet-stream", - size: parseInt(file.size || "0"), - id: file.id, - }; -} - -async function deleteFromDrive(accessToken: string, bucket: string, objectKey: string, env: Env): Promise { - const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); - const file = await findFileInFolder(accessToken, parentFolderId, fileName); - - if (!file) { - throw new Error("File not found"); - } - - const deleteRes = await fetch(`https://www.googleapis.com/drive/v3/files/${file.id}`, { - method: "DELETE", - headers: { Authorization: `Bearer ${accessToken}` }, - }); - - if (!deleteRes.ok) { - throw new Error("Delete failed"); - } -} - -async function getFileMetadata(accessToken: string, bucket: string, objectKey: string, env: Env): Promise<{ id: string; mimeType: string; size: number }> { - const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, objectKey, env); - const file = await findFileInFolder(accessToken, parentFolderId, fileName); - - if (!file) { - throw new Error("File not found"); - } - - return { - id: file.id, - mimeType: file.mimeType || "application/octet-stream", - size: parseInt(file.size || "0"), - }; -} - -async function listFiles(accessToken: string, bucket: string, env: Env): Promise { - const folderId = await getOrCreateFolder(accessToken, bucket, null, env); - - const listRes = await fetch(`https://www.googleapis.com/drive/v3/files?q='${folderId}' in parents and trashed=false&fields=files(id,name,mimeType,size,modifiedTime)`, { - headers: { Authorization: `Bearer ${accessToken}` }, - }); - - const data: GoogleDriveSearchResponse = await listRes.json(); - return data.files || []; -} - -function generateListBucketResult(files: any[], bucket: string): string { - const contents = files - .map( - (f) => ` - - ${escapeXml(f.name)} - ${f.modifiedTime || new Date().toISOString()} - "${f.id}" - ${f.size || 0} - STANDARD - `, - ) - .join(""); - - return ` - - ${escapeXml(bucket)} - - 1000 - false - ${contents} -`; -} - -function escapeXml(str: string): string { - return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); -} - -// ======================================== -// AWS Signature V4 Verification -// ======================================== - -async function verifySignature(request: Request, env: Env): Promise { - const url = new URL(request.url); - const headers = request.headers; - - const isQueryAuth = url.searchParams.has("X-Amz-Algorithm"); - - let algorithm: string; - if (isQueryAuth) { - algorithm = url.searchParams.get("X-Amz-Algorithm") ?? ""; - } else { - const authHeader = headers.get("Authorization") ?? ""; - algorithm = authHeader.split(" ")[0]; - } - - if (!algorithm || !algorithm.includes("AWS4-HMAC-SHA256")) { - return false; - } - - const datetime = (isQueryAuth ? url.searchParams.get("X-Amz-Date") : headers.get("x-amz-date")) ?? ""; - - if (!datetime) return false; - - const date = datetime.substring(0, 8); - - const canonicalRequest = await createCanonicalRequest(request, isQueryAuth); - const hashedCanonicalRequest = await sha256(canonicalRequest); - - const credentialScope = `${date}/${env.REGION}/s3/aws4_request`; - const stringToSign = ["AWS4-HMAC-SHA256", datetime, credentialScope, hashedCanonicalRequest].join("\n"); - - const signingKey = await getSigningKey(env.SECRET_KEY, date, env.REGION, "s3"); - const signature = await hmacSha256(signingKey, stringToSign); - const signatureHex = bufToHex(signature); - - let expectedSignature = ""; - if (isQueryAuth) { - expectedSignature = url.searchParams.get("X-Amz-Signature") ?? ""; - } else { - const authHeader = headers.get("Authorization") ?? ""; - const match = authHeader.match(/Signature=([a-f0-9]+)/); - expectedSignature = match ? match[1] : ""; - } - - return signatureHex === expectedSignature; -} - -async function createCanonicalRequest(request: Request, isQueryAuth: boolean): Promise { - const url = new URL(request.url); - - const method = request.method; - const canonicalUri = url.pathname || "/"; - - const params = Array.from(url.searchParams.entries()) - .filter(([key]) => key !== "X-Amz-Signature") - .sort(([a], [b]) => { - if (a < b) return -1; - if (a > b) return 1; - return 0; - }) - .map(([key, val]) => `${encodeRFC3986(key)}=${encodeRFC3986(val)}`) - .join("&"); - - let signedHeadersList: string[]; - if (isQueryAuth) { - signedHeadersList = (url.searchParams.get("X-Amz-SignedHeaders") ?? "host").split(";"); - } else { - const authHeader = request.headers.get("Authorization") ?? ""; - const match = authHeader.match(/SignedHeaders=([^,\s]+)/); - signedHeadersList = match ? match[1].split(";") : ["host"]; - } - - const canonicalHeaders = signedHeadersList - .map((h) => { - const headerName = h.toLowerCase(); - let headerValue = ""; - - if (headerName === "host") { - headerValue = url.hostname; - const port = url.port; - if (port && !((url.protocol === "https:" && port === "443") || (url.protocol === "http:" && port === "80"))) { - headerValue += `:${port}`; - } - } else { - headerValue = request.headers.get(headerName)?.trim() ?? ""; - } - - return `${headerName}:${headerValue}\n`; - }) - .join(""); - - const signedHeaders = signedHeadersList.join(";"); - const payloadHash = request.headers.get("x-amz-content-sha256") ?? (isQueryAuth ? "UNSIGNED-PAYLOAD" : "UNSIGNED-PAYLOAD"); - - return [method, canonicalUri, params, canonicalHeaders, signedHeaders, payloadHash].join("\n"); -} - -function encodeRFC3986(str: string): string { - return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); -} - -async function getSigningKey(secret: string, date: string, region: string, service: string): Promise { - const kDate = await hmacSha256("AWS4" + secret, date); - const kRegion = await hmacSha256(kDate, region); - const kService = await hmacSha256(kRegion, service); - return await hmacSha256(kService, "aws4_request"); -} - -async function hmacSha256(key: string | ArrayBuffer, data: string): Promise { - const keyData = typeof key === "string" ? new TextEncoder().encode(key) : key; - const cryptoKey = await crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]); - return await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(data)); -} - -async function sha256(data: string): Promise { - const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(data)); - return bufToHex(hash); -} - -function bufToHex(buf: ArrayBuffer): string { - return Array.from(new Uint8Array(buf)) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); -} diff --git a/src/multipart-do.ts b/src/multipart-do.ts new file mode 100644 index 0000000..8d0fc4c --- /dev/null +++ b/src/multipart-do.ts @@ -0,0 +1,282 @@ +import { alignedSendLen, cancelSession, createEmptyFile, putFinalChunk, queryStatus } from "./drive-resumable"; +import { getAccessToken } from "./google-drive"; +import type { DriveUploadResult, Env } from "./types"; + +import { DurableObject } from "cloudflare:workers"; + +const WAIT_TIMEOUT_MS = 20_000; +const LEASE_MS = 120_000; +const EXPIRE_AFTER_MS = 24 * 60 * 60 * 1000; + +interface PartRow extends Record { + partNumber: number; + size: number; + etag: string; + endOffset: number; + ts: number; +} + +interface InFlight { + requestId: string; + partNumber: number; + driveOffsetAtStart: number; + carryLen: number; + partLen: number; + sendLen: number; + leaseExpiresAt: number; +} + +export type BeginPartResult = { kind: "admit"; uploadUrl: string; driveOffset: number; carry: Uint8Array; sendLen: number; skipBytes: number } | { kind: "committed"; etag: string } | { kind: "slowdown" } | { kind: "error"; code: "NoSuchUpload" | "InvalidPart" | "InternalError"; message: string }; + +export type CompleteResult = { kind: "complete"; metadata: DriveUploadResult; partEtags: string[] } | { kind: "error"; code: "NoSuchUpload" | "InvalidPart" | "InvalidPartOrder" | "InternalError"; message: string }; + +interface Waiter { + requestId: string; + partNumber: number; + partLen: number; + resolve: (result: BeginPartResult) => void; + timer: ReturnType; +} + +interface StateValueRow extends Record { + v: ArrayBuffer | string | number | null; +} + +export class MultipartUploadDO extends DurableObject { + private readonly waiters = new Map(); + + constructor(ctx: DurableObjectState, env: Env) { + super(ctx, env); + void ctx.blockConcurrencyWhile(async () => { + this.ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS state (k TEXT PRIMARY KEY, v)"); + this.ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS parts (partNumber INTEGER PRIMARY KEY, size INTEGER NOT NULL, etag TEXT NOT NULL, endOffset INTEGER NOT NULL, ts INTEGER NOT NULL)"); + }); + } + + private getValue(key: string): T | undefined { + return this.ctx.storage.sql.exec("SELECT v FROM state WHERE k = ?", key).toArray()[0]?.v as T | undefined; + } + + private setValue(key: string, value: ArrayBuffer | string | number | null): void { + this.ctx.storage.sql.exec("INSERT INTO state (k, v) VALUES (?, ?) ON CONFLICT(k) DO UPDATE SET v = excluded.v", key, value); + } + + private hasUpload(): boolean { + return this.getValue("uploadUrl") !== undefined; + } + + private carry(): Uint8Array { + const value = this.getValue("carry"); + return value ? new Uint8Array(value) : new Uint8Array(); + } + + private inFlight(): InFlight | null { + const value = this.getValue("inFlight"); + return value ? (JSON.parse(value) as InFlight) : null; + } + + private part(partNumber: number): PartRow | undefined { + return this.ctx.storage.sql.exec("SELECT partNumber, size, etag, endOffset, ts FROM parts WHERE partNumber = ?", partNumber).toArray()[0]; + } + + async init(input: { uploadUrl: string; bucket: string; key: string; mimeType: string; parentFolderId: string; fileName: string; existingFileId?: string }): Promise { + if (this.hasUpload()) return false; + this.ctx.storage.sql.exec("DELETE FROM parts"); + for (const [key, value] of Object.entries(input)) { + if (value !== undefined) this.setValue(key, value); + } + this.setValue("driveOffset", 0); + this.setValue("carry", new ArrayBuffer(0)); + this.setValue("nextExpectedPart", 1); + this.setValue("createdAt", Date.now()); + this.setValue("inFlight", null); + await this.ctx.storage.setAlarm(Date.now() + EXPIRE_AFTER_MS); + return true; + } + + private async resyncExpiredLease(inFlight: InFlight, requestId: string, partLen: number): Promise { + if (partLen !== inFlight.partLen) return { kind: "error", code: "InvalidPart", message: "A retried part must have the same decoded length" }; + const accessToken = await getAccessToken(this.env); + const actualOffset = await queryStatus(this.getValue("uploadUrl")!, accessToken); + const partStartFileOffset = inFlight.driveOffsetAtStart + inFlight.carryLen; + let skipBytes = 0; + if (actualOffset === inFlight.driveOffsetAtStart) { + // The persisted carry remains valid. + } else if (actualOffset >= partStartFileOffset) { + skipBytes = actualOffset - partStartFileOffset; + if (skipBytes > inFlight.partLen) return { kind: "error", code: "InternalError", message: "Drive committed beyond the leased part" }; + this.setValue("driveOffset", actualOffset); + this.setValue("carry", new ArrayBuffer(0)); + } else { + return { kind: "error", code: "InternalError", message: "Drive committed only a prefix of the multipart carry" }; + } + this.setValue("inFlight", null); + return this.admit(requestId, inFlight.partNumber, inFlight.partLen, skipBytes); + } + + private admit(requestId: string, partNumber: number, partLen: number, skipBytes = 0): BeginPartResult { + const carry = this.carry(); + const driveOffset = this.getValue("driveOffset") ?? 0; + const available = carry.byteLength + partLen - skipBytes; + const sendLen = alignedSendLen(driveOffset, available); + const inFlight: InFlight = { + requestId, + partNumber, + driveOffsetAtStart: driveOffset, + carryLen: carry.byteLength, + partLen, + sendLen, + leaseExpiresAt: Date.now() + LEASE_MS, + }; + this.setValue("inFlight", JSON.stringify(inFlight)); + return { kind: "admit", uploadUrl: this.getValue("uploadUrl")!, driveOffset, carry, sendLen, skipBytes }; + } + + async beginPart(requestId: string, partNumber: number, partLen: number): Promise { + if (!this.hasUpload()) return { kind: "error", code: "NoSuchUpload", message: "Multipart upload not found" }; + if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > 10_000 || !Number.isSafeInteger(partLen) || partLen < 0) { + return { kind: "error", code: "InvalidPart", message: "Invalid part number or length" }; + } + const committed = this.part(partNumber); + if (committed) return { kind: "committed", etag: committed.etag }; + + const nextExpected = this.getValue("nextExpectedPart") ?? 1; + if (partNumber < nextExpected) return { kind: "error", code: "InvalidPart", message: "Part is no longer available" }; + if (partNumber > nextExpected + 64) return { kind: "slowdown" }; + + const lease = this.inFlight(); + if (partNumber === nextExpected && (!lease || lease.leaseExpiresAt <= Date.now())) { + if (lease) { + try { + const result = await this.resyncExpiredLease(lease, requestId, partLen); + if (result) return result; + } catch (error) { + return { kind: "error", code: "InternalError", message: error instanceof Error ? error.message : String(error) }; + } + } + return this.admit(requestId, partNumber, partLen); + } + + return await new Promise((resolve) => { + const timer = setTimeout(() => { + this.waiters.delete(requestId); + resolve({ kind: "slowdown" }); + }, WAIT_TIMEOUT_MS); + this.waiters.set(requestId, { requestId, partNumber, partLen, resolve, timer }); + }); + } + + async cancelWaiter(requestId: string): Promise { + const waiter = this.waiters.get(requestId); + if (!waiter) return; + clearTimeout(waiter.timer); + this.waiters.delete(requestId); + waiter.resolve({ kind: "slowdown" }); + } + + async failPart(partNumber: number): Promise { + const lease = this.inFlight(); + if (!lease || lease.partNumber !== partNumber) return; + lease.leaseExpiresAt = 0; + this.setValue("inFlight", JSON.stringify(lease)); + await this.wakeWaiters(); + } + + async endPart(partNumber: number, newDriveOffset: number, tail: Uint8Array, etag: string, partLen: number): Promise { + const lease = this.inFlight(); + if (!lease || lease.partNumber !== partNumber) return false; + if (newDriveOffset !== lease.driveOffsetAtStart + lease.sendLen || tail.byteLength > 256 * 1024) return false; + const tailBuffer = new Uint8Array(tail).buffer; + this.ctx.storage.transactionSync(() => { + this.setValue("driveOffset", newDriveOffset); + this.setValue("carry", tailBuffer); + this.setValue("nextExpectedPart", partNumber + 1); + this.setValue("inFlight", null); + this.ctx.storage.sql.exec("INSERT INTO parts (partNumber, size, etag, endOffset, ts) VALUES (?, ?, ?, ?, ?)", partNumber, partLen, etag, newDriveOffset + tail.byteLength, Date.now()); + }); + await this.wakeWaiters(); + return true; + } + + private async wakeWaiters(): Promise { + const nextExpected = this.getValue("nextExpectedPart") ?? 1; + if (this.inFlight()) return; + const waiter = [...this.waiters.values()].find((candidate) => candidate.partNumber === nextExpected); + if (!waiter) return; + clearTimeout(waiter.timer); + this.waiters.delete(waiter.requestId); + waiter.resolve(this.admit(waiter.requestId, waiter.partNumber, waiter.partLen)); + } + + async complete(clientParts: Array<{ partNumber: number; etag: string }>, expectedTotal?: number): Promise { + if (!this.hasUpload()) return { kind: "error", code: "NoSuchUpload", message: "Multipart upload not found" }; + if (this.inFlight()) return { kind: "error", code: "InvalidPart", message: "A part is still being uploaded" }; + const stored = this.ctx.storage.sql.exec("SELECT partNumber, size, etag, endOffset, ts FROM parts ORDER BY partNumber").toArray(); + if (clientParts.length !== stored.length) return { kind: "error", code: "InvalidPart", message: "The completed part list does not match uploaded parts" }; + for (let index = 0; index < clientParts.length; index++) { + const client = clientParts[index]; + const part = stored[index]; + if (client.partNumber !== index + 1) return { kind: "error", code: "InvalidPartOrder", message: "Parts must be consecutive starting at 1" }; + if (part.partNumber !== client.partNumber || part.etag !== client.etag) return { kind: "error", code: "InvalidPart", message: `Part ${client.partNumber} does not match` }; + } + + const driveOffset = this.getValue("driveOffset") ?? 0; + const carry = this.carry(); + const total = driveOffset + carry.byteLength; + if (expectedTotal !== undefined && expectedTotal !== total) return { kind: "error", code: "InvalidPart", message: "x-amz-mp-object-size does not match uploaded parts" }; + + try { + const accessToken = await getAccessToken(this.env); + let metadata: DriveUploadResult; + if (total === 0) { + await cancelSession(this.getValue("uploadUrl")!, accessToken); + metadata = await createEmptyFile(accessToken, { + name: this.getValue("fileName")!, + parents: [this.getValue("parentFolderId")!], + mimeType: this.getValue("mimeType")!, + existingFileId: this.getValue("existingFileId"), + }); + } else { + metadata = await putFinalChunk(this.getValue("uploadUrl")!, accessToken, driveOffset, total, carry); + } + const partEtags = stored.map((part) => part.etag); + await this.ctx.storage.deleteAlarm(); + await this.ctx.storage.deleteAll(); + return { kind: "complete", metadata, partEtags }; + } catch (error) { + return { kind: "error", code: "InternalError", message: error instanceof Error ? error.message : String(error) }; + } + } + + async listParts(marker: number, maxParts: number): Promise<{ parts: PartRow[]; nextMarker: number; truncated: boolean } | null> { + if (!this.hasUpload()) return null; + const rows = this.ctx.storage.sql.exec("SELECT partNumber, size, etag, endOffset, ts FROM parts WHERE partNumber > ? ORDER BY partNumber LIMIT ?", marker, maxParts + 1).toArray(); + const truncated = rows.length > maxParts; + const parts = rows.slice(0, maxParts); + return { parts, nextMarker: parts.at(-1)?.partNumber ?? marker, truncated }; + } + + async abort(): Promise { + if (!this.hasUpload()) return false; + const accessToken = await getAccessToken(this.env); + await cancelSession(this.getValue("uploadUrl")!, accessToken); + await this.ctx.storage.deleteAlarm(); + await this.ctx.storage.deleteAll(); + for (const waiter of this.waiters.values()) { + clearTimeout(waiter.timer); + waiter.resolve({ kind: "error", code: "NoSuchUpload", message: "Multipart upload was aborted" }); + } + this.waiters.clear(); + return true; + } + + async alarm(): Promise { + if (!this.hasUpload()) return; + try { + await this.abort(); + } catch (error) { + console.error(JSON.stringify({ message: "multipart cleanup failed", error: error instanceof Error ? error.message : String(error) })); + await this.ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000); + } + } +} diff --git a/src/router.ts b/src/router.ts new file mode 100644 index 0000000..2ebb3c8 --- /dev/null +++ b/src/router.ts @@ -0,0 +1,237 @@ +import { decodedContentLength, isAwsChunked, pumpBody } from "./aws-chunked"; +import { createSession, nextDriveOffset } from "./drive-resumable"; +import { deleteFromDrive, findFileInFolder, getFileMetadata, listFiles, resolvePathToFolderAndFile, streamDownloadFromDrive, streamUploadToDrive } from "./google-drive"; +import { S3Exception, s3Error } from "./s3-errors"; +import { completeMultipartUploadResult, generateListBucketResult, initiateMultipartUploadResult, listMultipartUploadsResult, listPartsResult, parseCompleteMultipartUpload } from "./s3-xml"; +import type { DriveUploadResult, Env } from "./types"; + +const MAX_COMPLETE_XML = 4 * 1024 * 1024; + +function etag(file: { id: string; md5Checksum?: string }): string { + return file.md5Checksum || file.id; +} + +function xmlResponse(body: string, status = 200): Response { + return new Response(body, { status, headers: { "Content-Type": "application/xml" } }); +} + +function multipartStub(env: Env, uploadId: string) { + return env.MPU.getByName(uploadId); +} + +function encodeUploadId(bucket: string, key: string): string { + const bytes = crypto.getRandomValues(new Uint8Array(16)); + const random = btoa(String.fromCharCode(...bytes)) + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); + return `${random}.${encodeURIComponent(bucket)}.${encodeURIComponent(key)}`; +} + +function uploadIdMatches(uploadId: string, bucket: string, key: string): boolean { + const parts = uploadId.split("."); + if (parts.length < 3) return false; + try { + return decodeURIComponent(parts[1]) === bucket && decodeURIComponent(parts.slice(2).join(".")) === key; + } catch { + return false; + } +} + +function parsePositiveInt(value: string | null, fallback?: number): number | undefined { + if (value === null) return fallback; + if (!/^\d+$/.test(value)) return undefined; + const number = Number(value); + return Number.isSafeInteger(number) ? number : undefined; +} + +async function drainBody(body: ReadableStream | null): Promise { + if (body) await body.pipeTo(new WritableStream()); +} + +async function partEtag(uploadId: string, partNumber: number, partLen: number, fileOffset: number): Promise { + const value = new TextEncoder().encode(`${uploadId}:${partNumber}:${partLen}:${fileOffset}`); + const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", value)).subarray(0, 16); + return Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +async function multipartObjectEtag(metadata: DriveUploadResult, partEtags: string[], style: Env["ETAG_STYLE"]): Promise { + if (style !== "multipart" || partEtags.length === 0) return etag(metadata); + const bytes = new Uint8Array(partEtags.length * 16); + for (let index = 0; index < partEtags.length; index++) { + const value = partEtags[index]; + for (let byte = 0; byte < 16; byte++) bytes[index * 16 + byte] = Number.parseInt(value.slice(byte * 2, byte * 2 + 2), 16); + } + const digest = await crypto.subtle.digest("MD5", bytes); + return `${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}-${partEtags.length}`; +} + +async function createMultipart(request: Request, env: Env, accessToken: string, bucket: string, key: string): Promise { + if (env.ALLOW_MULTIPART !== "true") return s3Error("NotImplemented", 501, undefined, `/${bucket}/${key}`); + const mimeType = request.headers.get("Content-Type") || "application/octet-stream"; + const { parentFolderId, fileName } = await resolvePathToFolderAndFile(accessToken, bucket, key, env); + const existing = await findFileInFolder(accessToken, parentFolderId, fileName); + const uploadUrl = await createSession(accessToken, { name: fileName, parents: [parentFolderId], mimeType, existingFileId: existing?.id }); + const uploadId = encodeUploadId(bucket, key); + const initialized = await multipartStub(env, uploadId).init({ uploadUrl, bucket, key, mimeType, parentFolderId, fileName, existingFileId: existing?.id }); + if (!initialized) throw new Error("Failed to initialize multipart state"); + return xmlResponse(initiateMultipartUploadResult(bucket, key, uploadId)); +} + +async function uploadPart(request: Request, env: Env, accessToken: string, bucket: string, key: string, url: URL): Promise { + const uploadId = url.searchParams.get("uploadId") ?? ""; + const partNumber = parsePositiveInt(url.searchParams.get("partNumber")); + if (!uploadIdMatches(uploadId, bucket, key)) return s3Error("NoSuchUpload", 404, undefined, `/${bucket}/${key}`); + if (!partNumber || partNumber > 10_000) return s3Error("InvalidArgument", 400, "partNumber must be between 1 and 10000", `/${bucket}/${key}`); + if (request.headers.has("x-amz-copy-source")) return s3Error("NotImplemented", 501, undefined, `/${bucket}/${key}`); + const length = decodedContentLength(request); + if (length === undefined) return s3Error("InvalidArgument", 400, "Content-Length or x-amz-decoded-content-length is required", `/${bucket}/${key}`); + + const stub = multipartStub(env, uploadId); + const requestId = crypto.randomUUID(); + const onAbort = () => void stub.cancelWaiter(requestId); + request.signal.addEventListener("abort", onAbort, { once: true }); + const lease = await stub.beginPart(requestId, partNumber, length); + request.signal.removeEventListener("abort", onAbort); + if (lease.kind === "slowdown") return s3Error("SlowDown", 503, undefined, `/${bucket}/${key}`, false, { "Retry-After": "1" }); + if (lease.kind === "error") return s3Error(lease.code, lease.code === "NoSuchUpload" ? 404 : 500, lease.message, `/${bucket}/${key}`); + if (lease.kind === "committed") { + await drainBody(request.body); + return new Response(null, { status: 200, headers: { ETag: `"${lease.etag}"` } }); + } + + try { + const fixed = lease.sendLen === 0 ? null : new FixedLengthStream(lease.sendLen, { highWaterMark: 1 << 20 }); + const drivePromise = fixed + ? fetch(lease.uploadUrl, { + method: "PUT", + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Length": lease.sendLen.toString(), + "Content-Range": `bytes ${lease.driveOffset}-${lease.driveOffset + lease.sendLen - 1}/*`, + }, + body: fixed.readable, + duplex: "half", + } as RequestInit) + : null; + const sink = fixed?.writable ?? new WritableStream(); + const pumped = await pumpBody(request.body, sink.getWriter(), { + awsChunked: isAwsChunked(request), + expectedLength: length, + skipBytes: lease.skipBytes, + maxBytes: lease.sendLen, + prefix: lease.carry, + }); + const driveResponse = drivePromise ? await drivePromise : null; + if (driveResponse && driveResponse.status !== 308) throw new Error(`Drive part upload returned ${driveResponse.status}: ${await driveResponse.text()}`); + const newOffset = driveResponse ? nextDriveOffset(driveResponse) : lease.driveOffset; + const value = await partEtag(uploadId, partNumber, length, lease.driveOffset + lease.carry.byteLength); + if (!(await stub.endPart(partNumber, newOffset, pumped.tail, value, length))) throw new Error("Multipart lease was lost before commit"); + return new Response(null, { status: 200, headers: { ETag: `"${value}"` } }); + } catch (error) { + await stub.failPart(partNumber); + throw error; + } +} + +async function completeMultipart(request: Request, env: Env, bucket: string, key: string, url: URL): Promise { + const uploadId = url.searchParams.get("uploadId") ?? ""; + if (!uploadIdMatches(uploadId, bucket, key)) return s3Error("NoSuchUpload", 404, undefined, `/${bucket}/${key}`); + const contentLength = parsePositiveInt(request.headers.get("Content-Length")); + if (contentLength !== undefined && contentLength > MAX_COMPLETE_XML) return s3Error("EntityTooLarge", 400, "Completion XML exceeds 4 MiB", `/${bucket}/${key}`); + const text = await request.text(); + if (text.length > MAX_COMPLETE_XML) return s3Error("EntityTooLarge", 400, "Completion XML exceeds 4 MiB", `/${bucket}/${key}`); + let parts: Array<{ partNumber: number; etag: string }>; + try { + parts = parseCompleteMultipartUpload(text); + } catch { + return s3Error("MalformedXML", 400, undefined, `/${bucket}/${key}`); + } + const expectedTotal = parsePositiveInt(request.headers.get("x-amz-mp-object-size")); + const result = await multipartStub(env, uploadId).complete(parts, expectedTotal); + if (result.kind === "error") return s3Error(result.code, result.code === "NoSuchUpload" ? 404 : 400, result.message, `/${bucket}/${key}`); + const value = await multipartObjectEtag(result.metadata, result.partEtags, env.ETAG_STYLE); + return xmlResponse(completeMultipartUploadResult(bucket, key, value)); +} + +async function abortMultipart(env: Env, bucket: string, key: string, url: URL): Promise { + const uploadId = url.searchParams.get("uploadId") ?? ""; + if (!uploadIdMatches(uploadId, bucket, key) || !(await multipartStub(env, uploadId).abort())) return s3Error("NoSuchUpload", 404, undefined, `/${bucket}/${key}`); + return new Response(null, { status: 204 }); +} + +async function listParts(env: Env, bucket: string, key: string, url: URL): Promise { + const uploadId = url.searchParams.get("uploadId") ?? ""; + if (!uploadIdMatches(uploadId, bucket, key)) return s3Error("NoSuchUpload", 404, undefined, `/${bucket}/${key}`); + const marker = parsePositiveInt(url.searchParams.get("part-number-marker"), 0); + const maxParts = parsePositiveInt(url.searchParams.get("max-parts"), 1000); + if (marker === undefined || maxParts === undefined || maxParts > 1000) return s3Error("InvalidArgument", 400, undefined, `/${bucket}/${key}`); + const result = await multipartStub(env, uploadId).listParts(marker, maxParts); + if (!result) return s3Error("NoSuchUpload", 404, undefined, `/${bucket}/${key}`); + return xmlResponse(listPartsResult(bucket, key, uploadId, result)); +} + +async function putObject(request: Request, env: Env, accessToken: string, bucket: string, key: string): Promise { + if (request.headers.has("x-amz-copy-source")) return s3Error("NotImplemented", 501, undefined, `/${bucket}/${key}`); + const result = await streamUploadToDrive(accessToken, request, bucket, key, request.headers.get("Content-Type") || "application/octet-stream", env); + return new Response(null, { status: 200, headers: { ETag: `"${etag(result)}"` } }); +} + +export async function dispatch(request: Request, env: Env, accessToken: string, bucket: string, key: string): Promise { + const url = new URL(request.url); + const method = request.method; + const resource = `/${bucket}${key ? `/${key}` : ""}`; + + if (method === "POST" && key && url.searchParams.has("uploads")) return createMultipart(request, env, accessToken, bucket, key); + if (method === "PUT" && key && url.searchParams.has("uploadId") && url.searchParams.has("partNumber")) return uploadPart(request, env, accessToken, bucket, key, url); + if (method === "POST" && key && url.searchParams.has("uploadId")) return completeMultipart(request, env, bucket, key, url); + if (method === "DELETE" && key && url.searchParams.has("uploadId")) return abortMultipart(env, bucket, key, url); + if (method === "GET" && key && url.searchParams.has("uploadId")) return listParts(env, bucket, key, url); + if (method === "GET" && !key && url.searchParams.has("uploads")) return xmlResponse(listMultipartUploadsResult(bucket)); + if (method === "PUT" && key) return putObject(request, env, accessToken, bucket, key); + + if (method === "GET") { + if (!key) return xmlResponse(generateListBucketResult(await listFiles(accessToken, bucket, env), bucket)); + try { + const file = await streamDownloadFromDrive(accessToken, bucket, key, env, request.headers.get("Range") ?? undefined); + const headers = new Headers({ + "Content-Type": file.contentType, + "Content-Length": file.contentLength ?? file.size.toString(), + "Cache-Control": "s-maxage=300, no-store", + "Accept-Ranges": "bytes", + ETag: `"${etag(file)}"`, + }); + if (file.contentRange) headers.set("Content-Range", file.contentRange); + return new Response(file.body, { status: file.status, headers }); + } catch (error) { + if (error instanceof Error && error.message === "File not found") return s3Error("NoSuchKey", 404, undefined, resource); + throw error; + } + } + + if (method === "HEAD") { + if (!key) return new Response(null, { status: 200 }); + try { + const metadata = await getFileMetadata(accessToken, bucket, key, env); + return new Response(null, { + status: 200, + headers: { "Content-Type": metadata.mimeType, "Content-Length": metadata.size.toString(), "Accept-Ranges": "bytes", ETag: `"${etag(metadata)}"` }, + }); + } catch (error) { + if (error instanceof Error && error.message === "File not found") return s3Error("NoSuchKey", 404, undefined, resource, true); + throw error; + } + } + + if (method === "DELETE" && key) { + try { + await deleteFromDrive(accessToken, bucket, key, env); + return new Response(null, { status: 204 }); + } catch (error) { + if (error instanceof Error && error.message === "File not found") return s3Error("NoSuchKey", 404, undefined, resource); + throw error; + } + } + + throw new S3Exception("MethodNotAllowed", 405); +} diff --git a/src/s3-errors.ts b/src/s3-errors.ts new file mode 100644 index 0000000..549eda4 --- /dev/null +++ b/src/s3-errors.ts @@ -0,0 +1,39 @@ +import { escapeXml } from "./s3-xml"; + +export type S3ErrorCode = "AccessDenied" | "SignatureDoesNotMatch" | "NoSuchKey" | "NoSuchUpload" | "InvalidPart" | "InvalidPartOrder" | "EntityTooLarge" | "MalformedXML" | "InvalidArgument" | "MethodNotAllowed" | "NotImplemented" | "SlowDown" | "InternalError"; + +const DEFAULT_MESSAGES: Record = { + AccessDenied: "Access Denied", + SignatureDoesNotMatch: "The request signature we calculated does not match the signature you provided.", + NoSuchKey: "The specified key does not exist.", + NoSuchUpload: "The specified multipart upload does not exist.", + InvalidPart: "One or more of the specified parts could not be found.", + InvalidPartOrder: "The list of parts was not in ascending order.", + EntityTooLarge: "Your proposed upload exceeds the maximum allowed object size.", + MalformedXML: "The XML you provided was not well-formed or did not validate against our published schema.", + InvalidArgument: "Invalid argument.", + MethodNotAllowed: "The specified method is not allowed against this resource.", + NotImplemented: "A header you provided implies functionality that is not implemented.", + SlowDown: "Please reduce your request rate.", + InternalError: "We encountered an internal error. Please try again.", +}; + +export class S3Exception extends Error { + constructor( + readonly code: S3ErrorCode, + readonly status: number, + message = DEFAULT_MESSAGES[code], + readonly headers?: HeadersInit, + ) { + super(message); + } +} + +export function s3Error(code: S3ErrorCode, status: number, message = DEFAULT_MESSAGES[code], resource?: string, head = false, headers?: HeadersInit): Response { + const requestId = crypto.randomUUID(); + const body = head ? null : `\n${code}${escapeXml(message)}${resource ? `${escapeXml(resource)}` : ""}${requestId}`; + const responseHeaders = new Headers(headers); + responseHeaders.set("Content-Type", "application/xml"); + responseHeaders.set("x-amz-request-id", requestId); + return new Response(body, { status, headers: responseHeaders }); +} diff --git a/src/s3-xml.ts b/src/s3-xml.ts new file mode 100644 index 0000000..2535b7c --- /dev/null +++ b/src/s3-xml.ts @@ -0,0 +1,66 @@ +import type { GoogleDriveFile } from "./types"; + +export function escapeXml(str: string): string { + return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +} + +export function generateListBucketResult(files: GoogleDriveFile[], bucket: string): string { + const contents = files + .map( + (f) => ` + + ${escapeXml(f.name)} + ${f.modifiedTime || new Date().toISOString()} + "${f.md5Checksum || f.id}" + ${f.size || 0} + STANDARD + `, + ) + .join(""); + + return ` + + ${escapeXml(bucket)} + + 1000 + false + ${contents} +`; +} + +export function initiateMultipartUploadResult(bucket: string, key: string, uploadId: string): string { + return ` +${escapeXml(bucket)}${escapeXml(key)}${escapeXml(uploadId)}`; +} + +export function completeMultipartUploadResult(bucket: string, key: string, etag: string): string { + return ` +/${escapeXml(bucket)}/${escapeXml(key)}${escapeXml(bucket)}${escapeXml(key)}"${escapeXml(etag)}"`; +} + +export function listPartsResult(bucket: string, key: string, uploadId: string, result: { parts: Array<{ partNumber: number; size: number; etag: string; ts: number }>; nextMarker: number; truncated: boolean }): string { + const parts = result.parts.map((part) => `${part.partNumber}${new Date(part.ts).toISOString()}"${escapeXml(part.etag)}"${part.size}`).join(""); + return ` +${escapeXml(bucket)}${escapeXml(key)}${escapeXml(uploadId)}${result.nextMarker}${result.truncated}${parts}`; +} + +export function listMultipartUploadsResult(bucket: string): string { + return ` +${escapeXml(bucket)}1000false`; +} + +const PART_RE = /]*>([\s\S]*?)<\/Part>/g; +const NUM_RE = /\s*(\d+)\s*<\/PartNumber>/; +const ETAG_RE = /\s*(?:"|")?([^<"&]*)(?:"|")?\s*<\/ETag>/; + +export function parseCompleteMultipartUpload(xml: string): Array<{ partNumber: number; etag: string }> { + const parts: Array<{ partNumber: number; etag: string }> = []; + for (const match of xml.matchAll(PART_RE)) { + const numberMatch = NUM_RE.exec(match[1]); + const etagMatch = ETAG_RE.exec(match[1]); + if (!numberMatch || !etagMatch || parts.length >= 10_000) throw new Error("MalformedXML"); + parts.push({ partNumber: Number(numberMatch[1]), etag: etagMatch[1].trim() }); + } + if (parts.length === 0 && !/]*>\s*<\/CompleteMultipartUpload>/.test(xml)) throw new Error("MalformedXML"); + return parts; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..54ae13f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,54 @@ +export interface Env { + ACCESS_KEY: string; + SECRET_KEY: string; + REGION: string; + GOOGLE_CLIENT_ID: string; + GOOGLE_CLIENT_SECRET: string; + GOOGLE_REFRESH_TOKEN: string; + AUTH_KV: KVNamespace; + FOLDER_CACHE: KVNamespace; + MPU: DurableObjectNamespace; + ALLOWED_BUCKETS?: string; + PUBLIC_READ_BUCKETS?: string; + ALLOW_MULTIPART?: string; + ETAG_STYLE?: "md5" | "multipart"; +} + +export interface GoogleDriveFile { + id: string; + name: string; + mimeType: string; + size: string; + modifiedTime?: string; + md5Checksum?: string; +} + +export interface GoogleDriveSearchResponse { + files?: GoogleDriveFile[]; +} + +export interface DriveUploadResult { + id: string; + name: string; + mimeType?: string; + size?: string; + md5Checksum?: string; +} + +export interface DriveDownloadResult { + body: ReadableStream; + contentType: string; + size: number; + id: string; + md5Checksum?: string; + status: number; + contentRange?: string; + contentLength?: string; +} + +export interface DriveFileMetadata { + id: string; + mimeType: string; + size: number; + md5Checksum?: string; +} diff --git a/test/s3.test.ts b/test/s3.test.ts index 675c0cd..f5bd170 100644 --- a/test/s3.test.ts +++ b/test/s3.test.ts @@ -1,1097 +1,311 @@ -import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; -import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; import { AwsClient } from "aws4fetch"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { decodedBodyChunks } from "../src/aws-chunked"; import worker from "../src/index"; +import type { Env } from "../src/types"; -const ENV = { - ACCESS_KEY: "test-access-key", - SECRET_KEY: "test-secret-key", - REGION: "auto", - GOOGLE_CLIENT_ID: "test-client-id", - GOOGLE_CLIENT_SECRET: "test-client-secret", - GOOGLE_REFRESH_TOKEN: "test-refresh-token", - AUTH_KV: createMockKV(), - FOLDER_CACHE: createMockKV(), - ALLOWED_BUCKETS: "test-bucket,empty-bucket,my-bucket", -}; +import { env } from "cloudflare:test"; -const CTX = { - waitUntil: vi.fn(), - passThroughOnException: vi.fn(), -}; +const ENV = env as unknown as Env; +const ENDPOINT = "https://s3-api.example.com"; +const CTX = { waitUntil: vi.fn(), passThroughOnException: vi.fn() } as unknown as ExecutionContext; -function createMockKV() { - const storage = new Map(); - return { - get: vi.fn(async (key: string) => storage.get(key) || null), - put: vi.fn(async (key: string, value: string, options?: any) => { - storage.set(key, value); - }), - delete: vi.fn(async (key: string) => storage.delete(key)), - list: vi.fn(async () => ({ keys: [] })), - }; +interface StoredFile { + id: string; + name: string; + parent: string; + mimeType: string; + data: Uint8Array; + md5Checksum: string; } -// Google API のモック -global.fetch = vi.fn(async (url: string | URL | Request, init?: any) => { - const urlStr = typeof url === "string" ? url : url instanceof URL ? url.toString() : url.url; +interface UploadSession { + id: string; + fileId?: string; + name: string; + parent: string; + mimeType: string; + committed: Uint8Array; +} - // OAuth トークン取得 - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); +class FakeDrive { + readonly files = new Map(); + readonly sessions = new Map(); + private nextId = 1; + + async handle(input: string | URL | Request, init?: RequestInit): Promise { + const request = input instanceof Request ? input : new Request(input, init); + const url = new URL(request.url); + if (url.hostname === "oauth2.googleapis.com") return Response.json({ access_token: "token", expires_in: 3600 }); + if (url.hostname !== "www.googleapis.com") return new Response("Not Found", { status: 404 }); + + if (url.pathname === "/drive/v3/files" && request.method === "GET") return this.search(url); + if (url.pathname === "/drive/v3/files" && request.method === "POST") return this.createMetadata(await request.json>()); + if (url.pathname.startsWith("/drive/v3/files/") && url.searchParams.get("alt") === "media") return this.download(url, request); + if (url.pathname.startsWith("/drive/v3/files/") && request.method === "DELETE") { + this.files.delete(url.pathname.split("/").at(-1)!); + return new Response(null, { status: 204 }); + } + if (url.pathname.startsWith("/upload/drive/v3/files") && url.searchParams.get("uploadType") === "resumable") return this.initialize(url, request); + if (url.pathname.startsWith("/upload/session/")) return this.upload(url, request); + return new Response("Not Found", { status: 404 }); } - // フォルダ検索 - if (urlStr.includes("drive/v3/files") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - return new Response( - JSON.stringify({ - files: [{ id: "folder-id-123", name: "test-bucket" }], - }), - { status: 200 }, - ); + private search(url: URL): Response { + const q = url.searchParams.get("q") ?? ""; + const name = /name='((?:\\.|[^'])*)'/.exec(q)?.[1]?.replace(/\\'/g, "'").replace(/\\\\/g, "\\"); + const parent = /'([^']+)' in parents/.exec(q)?.[1]; + if (q.includes("application/vnd.google-apps.folder")) { + const id = parent ? `folder:${parent}:${name}` : `folder:${name}`; + return Response.json({ files: [{ id, name, mimeType: "application/vnd.google-apps.folder" }] }); + } + const files = [...this.files.values()].filter((file) => file.name === name && file.parent === parent); + return Response.json({ files: files.map((file) => ({ ...file, size: String(file.data.byteLength), data: undefined })) }); } - // ファイル検索 - if (urlStr.includes("drive/v3/files") && urlStr.includes("in parents")) { - return new Response( - JSON.stringify({ - files: [ - { - id: "file-id-456", - name: "test-file.txt", - mimeType: "text/plain", - size: "11", - }, - ], - }), - { status: 200 }, - ); - } - - // Resumable upload 初期化 - if (urlStr.includes("uploadType=resumable") && init?.method === "POST") { - return new Response(null, { - status: 200, - headers: { Location: "https://www.googleapis.com/upload/drive/v3/files/uploadid123" }, - }); - } - - // Resumable upload 実行 - if (urlStr.includes("upload/drive/v3/files/uploadid")) { - return new Response( - JSON.stringify({ - id: "new-file-id-789", - name: "uploaded-file.txt", - mimeType: "text/plain", - }), - { status: 200 }, - ); - } - - // ファイルダウンロード - if (urlStr.includes("alt=media")) { - return new Response("Hello World", { - status: 200, - headers: { "Content-Type": "text/plain" }, - }); - } - - // ファイル削除 - if (init?.method === "DELETE") { - return new Response(null, { status: 204 }); - } - - return new Response("Not Found", { status: 404 }); -}) as any; - -describe("S3 API Server with Google Drive Backend", () => { - const endpoint = "https://s3-api.example.com"; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - // 1. PUT (Upload) テスト - it("should upload file via PUT with Header Auth", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const requestUrl = `${endpoint}/test-bucket/test-file.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "PUT", - headers: { - "Content-Type": "text/plain", - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - body: "Hello World", - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(200); - const result = await response.json(); - expect(result).toHaveProperty("id"); - }); - - // 2. GET (Download) テスト - it("should download file via GET with Header Auth", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const requestUrl = `${endpoint}/test-bucket/test-file.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "GET", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(200); - expect(await response.text()).toBe("Hello World"); - }); - - // 3. DELETE テスト - it("should delete file via DELETE", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const requestUrl = `${endpoint}/test-bucket/test-file.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "DELETE", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(204); - }); - - // 4. HEAD (Metadata) テスト - it("should get file metadata via HEAD", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const requestUrl = `${endpoint}/test-bucket/test-file.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "HEAD", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(200); - expect(response.headers.get("Content-Type")).toBe("text/plain"); - expect(response.headers.get("Content-Length")).toBe("11"); - }); - - // 5. Presigned URL テスト - it("should verify presigned URLs from @aws-sdk/s3-request-presigner", async () => { - const s3 = new S3Client({ - endpoint: `${endpoint}/my-bucket`, - region: ENV.REGION, - credentials: { - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - }, - }); - - const command = new GetObjectCommand({ - Bucket: "my-bucket", - Key: "test.png", - }); - - const url = await getSignedUrl(s3, command, { expiresIn: 3600 }); - const parsedUrl = new URL(url); - const testUrl = new URL(endpoint); - testUrl.hostname = parsedUrl.hostname; - testUrl.pathname = parsedUrl.pathname; - testUrl.search = parsedUrl.search; - - const request = new Request(testUrl.toString(), { method: "GET" }); - const response = await worker.fetch(request, ENV, CTX); - expect(response.status).toBe(200); - }); - - // 6. 不正な署名のテスト - it("should reject invalid signatures", async () => { - const request = new Request(`${endpoint}/hack`, { - method: "GET", - headers: { - Authorization: "AWS4-HMAC-SHA256 Credential=bad/20260108/auto/s3/aws4_request, SignedHeaders=host, Signature=wrong", - "x-amz-date": "20260108T000000Z", - }, - }); - - const response = await worker.fetch(request, ENV, CTX); - expect(response.status).toBe(403); - }); - - // 7. バケット一覧テスト (LIST) - it("should list files in bucket", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - return new Response( - JSON.stringify({ - files: [{ id: "folder-id-123", name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("in parents")) { - return new Response( - JSON.stringify({ - files: [ - { id: "1", name: "file1.txt", size: "100", mimeType: "text/plain", modifiedTime: "2024-01-01T00:00:00Z" }, - { id: "2", name: "file2.txt", size: "200", mimeType: "text/plain", modifiedTime: "2024-01-02T00:00:00Z" }, - ], - }), - { status: 200 }, - ); - } - - return new Response("Not Found", { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/`; - const signedReq = await aws4.sign(requestUrl, { - method: "GET", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(200); - const xmlText = await response.text(); - expect(xmlText).toContain(" { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - return new Response( - JSON.stringify({ - files: [{ id: "folder-id-123", name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("in parents")) { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - - return new Response("Not Found", { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/non-existent.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "HEAD", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(404); - - global.fetch = originalFetch; - }); - - // 9. 存在しないファイルのGETリクエスト (404) - it("should return 404 for GET on non-existent file", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - return new Response( - JSON.stringify({ - files: [{ id: "folder-id-123", name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("in parents")) { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - - return new Response("Not Found", { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/non-existent.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "GET", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(404); - expect(await response.text()).toBe("NoSuchKey"); - - global.fetch = originalFetch; - }); - - // 10. 存在しないファイルのDELETEリクエスト (404) - it("should return 404 for DELETE on non-existent file", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - return new Response( - JSON.stringify({ - files: [{ id: "folder-id-123", name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("in parents")) { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - - return new Response("Not Found", { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/non-existent.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "DELETE", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(404); - - global.fetch = originalFetch; - }); - - // 11. 空のバケット一覧 - it("should return empty list for empty bucket", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - return new Response( - JSON.stringify({ - files: [{ id: "folder-id-123", name: "empty-bucket" }], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("drive/v3/files") && urlStr.includes("in parents")) { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - - return new Response("Not Found", { status: 404 }); - }); - - const requestUrl = `${endpoint}/empty-bucket/`; - const signedReq = await aws4.sign(requestUrl, { - method: "GET", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(200); - const xmlText = await response.text(); - expect(xmlText).toContain(""); - - global.fetch = originalFetch; - }); - - // 12. ネストされたフォルダーへのアップロード - it("should upload file to nested directory structure", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const folderIds = { - bucket: "bucket-id-123", - dir1: "dir1-id-456", - dir2: "dir2-id-789", + private createMetadata(metadata: Record): Response { + const id = `file-${this.nextId++}`; + const file: StoredFile = { + id, + name: String(metadata.name), + parent: String((metadata.parents as string[] | undefined)?.[0] ?? ""), + mimeType: String(metadata.mimeType ?? "application/octet-stream"), + data: new Uint8Array(), + md5Checksum: "d41d8cd98f00b204e9800998ecf8427e", }; + this.files.set(id, file); + return Response.json({ ...file, size: "0", data: undefined }); + } - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - // OAuth トークン - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - // フォルダ作成 (POST) - if (urlStr === "https://www.googleapis.com/drive/v3/files" && init?.method === "POST") { - const body = JSON.parse(init.body); - if (body.mimeType === "application/vnd.google-apps.folder") { - // フォルダ名に応じてIDを返す - if (body.name === "dir1") { - return new Response(JSON.stringify({ id: folderIds.dir1, name: "dir1" }), { status: 200 }); - } else if (body.name === "dir2") { - return new Response(JSON.stringify({ id: folderIds.dir2, name: "dir2" }), { status: 200 }); - } - } - } - - // フォルダ検索 - バケット (親なし) - if (urlStr.includes("name='test-bucket'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'") && !urlStr.includes("in parents")) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.bucket, name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - // フォルダ検索 - dir1 - if (urlStr.includes("name='dir1'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.bucket}' in parents`)) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.dir1, name: "dir1" }], - }), - { status: 200 }, - ); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - // フォルダ検索 - dir2 - if (urlStr.includes("name='dir2'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.dir1}' in parents`)) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.dir2, name: "dir2" }], - }), - { status: 200 }, - ); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - // Resumable upload 初期化 - if (urlStr.includes("uploadType=resumable") && init?.method === "POST") { - const body = JSON.parse(init.body); - // 親フォルダがdir2であることを確認 - expect(body.parents).toEqual([folderIds.dir2]); - expect(body.name).toBe("file.txt"); - - return new Response(null, { - status: 200, - headers: { Location: "https://www.googleapis.com/upload/drive/v3/files/uploadid999" }, - }); - } - - // Resumable upload 実行 - if (urlStr.includes("upload/drive/v3/files/uploadid999")) { - return new Response( - JSON.stringify({ - id: "nested-file-id-999", - name: "file.txt", - mimeType: "text/plain", - }), - { status: 200 }, - ); - } - - console.error("Unhandled URL:", urlStr); - return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 }); + private async initialize(url: URL, request: Request): Promise { + const metadata = await request.json<{ name: string; parents?: string[] }>(); + const pathId = url.pathname.split("/").at(-1); + const fileId = pathId === "files" ? undefined : pathId; + const id = `session-${this.nextId++}`; + this.sessions.set(id, { + id, + fileId, + name: metadata.name, + parent: metadata.parents?.[0] ?? (fileId ? this.files.get(fileId)?.parent : "") ?? "", + mimeType: request.headers.get("X-Upload-Content-Type") ?? "application/octet-stream", + committed: new Uint8Array(), }); + return new Response(null, { status: 200, headers: { Location: `https://www.googleapis.com/upload/session/${id}` } }); + } - const requestUrl = `${endpoint}/test-bucket/dir1/dir2/file.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "PUT", - headers: { - "Content-Type": "text/plain", - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - body: "Nested content", - }); + private async upload(url: URL, request: Request): Promise { + const id = url.pathname.split("/").at(-1)!; + const session = this.sessions.get(id); + if (!session) return new Response(null, { status: 404 }); + if (request.method === "DELETE") { + this.sessions.delete(id); + return new Response(null, { status: 499 }); + } + const range = request.headers.get("Content-Range"); + if (range === "bytes */*") return new Response(null, { status: 308, headers: this.rangeHeaders(session.committed.byteLength) }); + const body = new Uint8Array(await request.arrayBuffer()); + if (!range) return this.finalize(session, body); + const match = /^bytes (\d+)-(\d+)\/(\*|\d+)$/.exec(range); + if (!match) return new Response("Bad range", { status: 400 }); + const start = Number(match[1]); + const end = Number(match[2]); + const total = match[3] === "*" ? null : Number(match[3]); + expect(start).toBe(session.committed.byteLength); + expect(end - start + 1).toBe(body.byteLength); + if (total === null) expect(body.byteLength % (256 * 1024)).toBe(0); + session.committed = concat(session.committed, body); + if (total === null) return new Response(null, { status: 308, headers: this.rangeHeaders(session.committed.byteLength) }); + expect(session.committed.byteLength).toBe(total); + return this.finalize(session, session.committed); + } - const response = await worker.fetch(signedReq, ENV, CTX); + private rangeHeaders(length: number): HeadersInit { + return length === 0 ? {} : { Range: `bytes=0-${length - 1}` }; + } + + private finalize(session: UploadSession, data: Uint8Array): Response { + const id = session.fileId ?? `file-${this.nextId++}`; + const file: StoredFile = { id, name: session.name, parent: session.parent, mimeType: session.mimeType, data, md5Checksum: fakeMd5(data) }; + this.files.set(id, file); + this.sessions.delete(session.id); + return Response.json({ ...file, size: String(data.byteLength), data: undefined }); + } + + private download(url: URL, request: Request): Response { + const file = this.files.get(url.pathname.split("/").at(-1)!); + if (!file) return new Response(null, { status: 404 }); + const range = request.headers.get("Range"); + if (!range) return new Response(file.data, { headers: { "Content-Length": String(file.data.byteLength) } }); + const match = /^bytes=(\d+)-(\d+)?$/.exec(range); + if (!match) return new Response(null, { status: 416 }); + const start = Number(match[1]); + const end = Math.min(file.data.byteLength - 1, match[2] ? Number(match[2]) : file.data.byteLength - 1); + const data = file.data.slice(start, end + 1); + return new Response(data, { status: 206, headers: { "Content-Length": String(data.byteLength), "Content-Range": `bytes ${start}-${end}/${file.data.byteLength}` } }); + } +} + +function concat(a: Uint8Array, b: Uint8Array): Uint8Array { + const output = new Uint8Array(a.byteLength + b.byteLength); + output.set(a); + output.set(b, a.byteLength); + return output; +} + +function fakeMd5(data: Uint8Array): string { + let state = 0x811c9dc5; + for (const byte of data) state = Math.imul(state ^ byte, 0x01000193); + return (state >>> 0).toString(16).padStart(8, "0").repeat(4); +} + +function bytes(length: number, seed = 17): Uint8Array { + const output = new Uint8Array(length); + let state = seed; + for (let index = 0; index < length; index++) { + state = (Math.imul(state, 1664525) + 1013904223) | 0; + output[index] = state >>> 24; + } + return output; +} + +async function signed(path: string, init: RequestInit): Promise { + const aws = new AwsClient({ accessKeyId: ENV.ACCESS_KEY, secretAccessKey: ENV.SECRET_KEY, region: ENV.REGION, service: "s3" }); + const bodyLength = typeof init.body === "string" ? new TextEncoder().encode(init.body).byteLength : init.body instanceof Uint8Array ? init.body.byteLength : undefined; + return aws.sign(`${ENDPOINT}${path}`, { + ...init, + headers: { "x-amz-content-sha256": "UNSIGNED-PAYLOAD", ...(bodyLength === undefined ? {} : { "x-amz-decoded-content-length": String(bodyLength) }), ...init.headers }, + }); +} + +let drive: FakeDrive; + +beforeEach(async () => { + drive = new FakeDrive(); + vi.stubGlobal( + "fetch", + vi.fn((input, init) => drive.handle(input, init)), + ); + await ENV.AUTH_KV.delete("google_access_token"); +}); + +describe("S3 compatibility", () => { + it("returns an empty PutObject response and overwrites the same Drive file", async () => { + const first = await worker.fetch(await signed("/test-bucket/file.txt", { method: "PUT", body: "first" }), ENV, CTX); + expect(first.status).toBe(200); + expect(await first.text()).toBe(""); + expect(first.headers.get("ETag")).toMatch(/^"[0-9a-f]{32}"$/); + + const second = await worker.fetch(await signed("/test-bucket/file.txt", { method: "PUT", body: "second" }), ENV, CTX); + expect(second.status).toBe(200); + expect([...drive.files.values()].filter((file) => file.name === "file.txt")).toHaveLength(1); + expect(new TextDecoder().decode([...drive.files.values()].find((file) => file.name === "file.txt")!.data)).toBe("second"); + }); + + it("forwards Range and returns a standard XML NoSuchKey", async () => { + await worker.fetch(await signed("/test-bucket/range.bin", { method: "PUT", body: "0123456789" }), ENV, CTX); + const response = await worker.fetch(await signed("/test-bucket/range.bin", { method: "GET", headers: { Range: "bytes=2-5" } }), ENV, CTX); + expect(response.status).toBe(206); + expect(response.headers.get("Content-Range")).toBe("bytes 2-5/10"); + expect(response.headers.get("Accept-Ranges")).toBe("bytes"); + expect(await response.text()).toBe("2345"); + + const missing = await worker.fetch(await signed("/test-bucket/missing", { method: "GET" }), ENV, CTX); + expect(missing.status).toBe(404); + expect(await missing.text()).toContain("NoSuchKey"); + }); + + it("decodes both aws-chunked framing variants across arbitrary boundaries", async () => { + const payload = bytes(70_013); + for (const trailer of [true, false]) { + const framed = encodeAwsChunked(payload, trailer); + for (const split of [1, 7, 127, 8191]) { + const chunks: Uint8Array[] = []; + for (let offset = 0; offset < framed.byteLength; offset += split) chunks.push(framed.slice(offset, offset + split)); + const decoded: Uint8Array[] = []; + for await (const chunk of decodedBodyChunks(streamOf(chunks), true)) decoded.push(chunk); + expect(concatAll(decoded)).toEqual(payload); + } + } + }); + + it("round-trips non-aligned multipart parts byte-exact", async () => { + const source = bytes(1_500_123); + const create = await worker.fetch(await signed("/test-bucket/big.bin?uploads", { method: "POST", headers: { "Content-Type": "application/octet-stream" } }), ENV, CTX); + expect(create.status).toBe(200); + const uploadId = /([^<]+)<\/UploadId>/.exec(await create.text())![1]; + const completed: Array<{ partNumber: number; etag: string }> = []; + for (let index = 0, offset = 0; offset < source.byteLength; index++) { + const end = Math.min(source.byteLength, offset + 500_000); + const part = await worker.fetch(await signed(`/test-bucket/big.bin?partNumber=${index + 1}&uploadId=${encodeURIComponent(uploadId)}`, { method: "PUT", body: source.slice(offset, end) }), ENV, CTX); + expect(part.status).toBe(200); + completed.push({ partNumber: index + 1, etag: part.headers.get("ETag")!.replaceAll('"', "") }); + offset = end; + } + const xml = `${completed.map((part) => `${part.partNumber}"${part.etag}"`).join("")}`; + const result = await worker.fetch(await signed(`/test-bucket/big.bin?uploadId=${encodeURIComponent(uploadId)}`, { method: "POST", body: xml }), ENV, CTX); + expect(result.status).toBe(200); + expect(await result.text()).toMatch(/"[0-9a-f]{32}"<\/ETag>/); + const stored = [...drive.files.values()].find((file) => file.name === "big.bin")!.data; + expect(stored.byteLength).toBe(source.byteLength); + expect(fakeMd5(stored)).toBe(fakeMd5(source)); + }); + + it("accepts aws-chunked PutObject without storing framing", async () => { + const source = bytes(1_000_003); + const framed = encodeAwsChunked(source, true); + const response = await worker.fetch( + await signed("/test-bucket/chunked.bin", { + method: "PUT", + body: framed, + headers: { + "content-encoding": "aws-chunked", + "x-amz-decoded-content-length": String(source.byteLength), + "x-amz-content-sha256": "STREAMING-UNSIGNED-PAYLOAD-TRAILER", + }, + }), + ENV, + CTX, + ); expect(response.status).toBe(200); - const result = await response.json(); - expect(result.id).toBe("nested-file-id-999"); - expect(result.name).toBe("file.txt"); - - global.fetch = originalFetch; + expect([...drive.files.values()].find((file) => file.name === "chunked.bin")!.data).toEqual(source); }); - // 13. ネストされたフォルダーからのダウンロード - it("should download file from nested directory structure", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const folderIds = { - bucket: "bucket-id-123", - images: "images-id-456", - photos: "photos-id-789", - }; - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - // フォルダ作成 (POST) - if (urlStr === "https://www.googleapis.com/drive/v3/files" && init?.method === "POST") { - const body = JSON.parse(init.body); - if (body.mimeType === "application/vnd.google-apps.folder") { - if (body.name === "images") { - return new Response(JSON.stringify({ id: folderIds.images, name: "images" }), { status: 200 }); - } else if (body.name === "photos") { - return new Response(JSON.stringify({ id: folderIds.photos, name: "photos" }), { status: 200 }); - } - } - } - - // バケット検索 - if (urlStr.includes("name='test-bucket'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'") && !urlStr.includes("in parents")) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.bucket, name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - // images フォルダ検索 - if (urlStr.includes("name='images'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.bucket}' in parents`)) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.images, name: "images" }], - }), - { status: 200 }, - ); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - // photos フォルダ検索 - if (urlStr.includes("name='photos'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.images}' in parents`)) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.photos, name: "photos" }], - }), - { status: 200 }, - ); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - // ファイル検索 - photo.jpg - if (urlStr.includes("name='photo.jpg'") && urlStr.includes(`'${folderIds.photos}' in parents`)) { - return new Response( - JSON.stringify({ - files: [ - { - id: "photo-file-id-999", - name: "photo.jpg", - mimeType: "image/jpeg", - size: "12345", - }, - ], - }), - { status: 200 }, - ); - } - - // ファイルダウンロード - if (urlStr.includes("photo-file-id-999") && urlStr.includes("alt=media")) { - return new Response("Binary image data", { - status: 200, - headers: { "Content-Type": "image/jpeg" }, - }); - } - - console.error("Unhandled URL:", urlStr); - return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/images/photos/photo.jpg`; - const signedReq = await aws4.sign(requestUrl, { - method: "GET", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); + it("supports an empty PutObject", async () => { + const response = await worker.fetch(await signed("/test-bucket/empty", { method: "PUT", body: new Uint8Array() }), ENV, CTX); expect(response.status).toBe(200); - expect(response.headers.get("Content-Type")).toBe("image/jpeg"); - expect(await response.text()).toBe("Binary image data"); - - global.fetch = originalFetch; - }); - - // 14. ネストされたフォルダーからの削除 - it("should delete file from nested directory structure", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const folderIds = { - bucket: "bucket-id-123", - docs: "docs-id-456", - archive: "archive-id-789", - }; - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - // フォルダ作成 (POST) - if (urlStr === "https://www.googleapis.com/drive/v3/files" && init?.method === "POST") { - const body = JSON.parse(init.body); - if (body.mimeType === "application/vnd.google-apps.folder") { - if (body.name === "docs") { - return new Response(JSON.stringify({ id: folderIds.docs, name: "docs" }), { status: 200 }); - } else if (body.name === "archive") { - return new Response(JSON.stringify({ id: folderIds.archive, name: "archive" }), { status: 200 }); - } - } - } - - if (urlStr.includes("name='test-bucket'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'") && !urlStr.includes("in parents")) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.bucket, name: "test-bucket" }], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("name='docs'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.bucket}' in parents`)) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.docs, name: "docs" }], - }), - { status: 200 }, - ); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - if (urlStr.includes("name='archive'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.docs}' in parents`)) { - return new Response( - JSON.stringify({ - files: [{ id: folderIds.archive, name: "archive" }], - }), - { status: 200 }, - ); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - if (urlStr.includes("name='old.pdf'") && urlStr.includes(`'${folderIds.archive}' in parents`)) { - return new Response( - JSON.stringify({ - files: [ - { - id: "old-pdf-id-999", - name: "old.pdf", - mimeType: "application/pdf", - size: "54321", - }, - ], - }), - { status: 200 }, - ); - } - - if (urlStr.includes("old-pdf-id-999") && init?.method === "DELETE") { - return new Response(null, { status: 204 }); - } - - console.error("Unhandled URL:", urlStr); - return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/docs/archive/old.pdf`; - const signedReq = await aws4.sign(requestUrl, { - method: "DELETE", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(204); - - global.fetch = originalFetch; - }); - - // 15. 深くネストされたフォルダーのメタデータ取得 - it("should get metadata for file in deeply nested structure", async () => { - const aws4 = new AwsClient({ - accessKeyId: ENV.ACCESS_KEY, - secretAccessKey: ENV.SECRET_KEY, - region: ENV.REGION, - service: "s3", - }); - - const folderIds = { - bucket: "bucket-id-123", - a: "a-id-111", - b: "b-id-222", - c: "c-id-333", - d: "d-id-444", - }; - - const originalFetch = global.fetch; - (global.fetch as any) = vi.fn(async (url: string, init?: any) => { - const urlStr = typeof url === "string" ? url : url.toString(); - - if (urlStr.includes("oauth2.googleapis.com/token")) { - return new Response( - JSON.stringify({ - access_token: "mock-access-token", - expires_in: 3600, - }), - { status: 200 }, - ); - } - - // フォルダ作成 (POST) - if (urlStr === "https://www.googleapis.com/drive/v3/files" && init?.method === "POST") { - const body = JSON.parse(init.body); - if (body.mimeType === "application/vnd.google-apps.folder") { - if (body.name === "a") { - return new Response(JSON.stringify({ id: folderIds.a, name: "a" }), { status: 200 }); - } else if (body.name === "b") { - return new Response(JSON.stringify({ id: folderIds.b, name: "b" }), { status: 200 }); - } else if (body.name === "c") { - return new Response(JSON.stringify({ id: folderIds.c, name: "c" }), { status: 200 }); - } else if (body.name === "d") { - return new Response(JSON.stringify({ id: folderIds.d, name: "d" }), { status: 200 }); - } - } - } - - // フォルダ階層の検索 - if (urlStr.includes("name='test-bucket'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'") && !urlStr.includes("in parents")) { - return new Response(JSON.stringify({ files: [{ id: folderIds.bucket, name: "test-bucket" }] }), { status: 200 }); - } - if (urlStr.includes("name='a'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.bucket}' in parents`)) { - return new Response(JSON.stringify({ files: [{ id: folderIds.a, name: "a" }] }), { status: 200 }); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - if (urlStr.includes("name='b'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.a}' in parents`)) { - return new Response(JSON.stringify({ files: [{ id: folderIds.b, name: "b" }] }), { status: 200 }); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - if (urlStr.includes("name='c'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.b}' in parents`)) { - return new Response(JSON.stringify({ files: [{ id: folderIds.c, name: "c" }] }), { status: 200 }); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - if (urlStr.includes("name='d'") && urlStr.includes("mimeType='application/vnd.google-apps.folder'")) { - if (urlStr.includes(`'${folderIds.c}' in parents`)) { - return new Response(JSON.stringify({ files: [{ id: folderIds.d, name: "d" }] }), { status: 200 }); - } else { - return new Response(JSON.stringify({ files: [] }), { status: 200 }); - } - } - - // ファイル検索 - if (urlStr.includes("name='deep.txt'") && urlStr.includes(`'${folderIds.d}' in parents`)) { - return new Response( - JSON.stringify({ - files: [ - { - id: "deep-file-id-999", - name: "deep.txt", - mimeType: "text/plain", - size: "999", - }, - ], - }), - { status: 200 }, - ); - } - - console.error("Unhandled URL:", urlStr); - return new Response(JSON.stringify({ error: "Not Found" }), { status: 404 }); - }); - - const requestUrl = `${endpoint}/test-bucket/a/b/c/d/deep.txt`; - const signedReq = await aws4.sign(requestUrl, { - method: "HEAD", - headers: { - "x-amz-content-sha256": "UNSIGNED-PAYLOAD", - }, - }); - - const response = await worker.fetch(signedReq, ENV, CTX); - expect(response.status).toBe(200); - expect(response.headers.get("Content-Type")).toBe("text/plain"); - expect(response.headers.get("Content-Length")).toBe("999"); - expect(response.headers.get("ETag")).toBe('"deep-file-id-999"'); - - global.fetch = originalFetch; - }); - - // ======================================== - // Public Read Tests - // ======================================== - - const PUBLIC_READ_ENV = { - ...ENV, - PUBLIC_READ_BUCKETS: "public-bucket", - ALLOWED_BUCKETS: "test-bucket,empty-bucket,my-bucket,public-bucket", - }; - - // 16. Public read bucket - unsigned GET should succeed - it("should allow unsigned GET on public read bucket", async () => { - const request = new Request(`${endpoint}/public-bucket/test-file.txt`, { - method: "GET", - }); - - const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX); - expect(response.status).toBe(200); - expect(await response.text()).toBe("Hello World"); - }); - - // 17. Public read bucket - unsigned HEAD should succeed - it("should allow unsigned HEAD on public read bucket", async () => { - const request = new Request(`${endpoint}/public-bucket/test-file.txt`, { - method: "HEAD", - }); - - const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX); - expect(response.status).toBe(200); - expect(response.headers.get("Content-Type")).toBe("text/plain"); - expect(response.headers.get("Content-Length")).toBe("11"); - }); - - // 18. Public read bucket - unsigned PUT should be rejected - it("should reject unsigned PUT on public read bucket", async () => { - const request = new Request(`${endpoint}/public-bucket/test-file.txt`, { - method: "PUT", - headers: { "Content-Type": "text/plain" }, - body: "Hello World", - }); - - const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX); - expect(response.status).toBe(403); - }); - - // 19. Public read bucket - unsigned DELETE should be rejected - it("should reject unsigned DELETE on public read bucket", async () => { - const request = new Request(`${endpoint}/public-bucket/test-file.txt`, { - method: "DELETE", - }); - - const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX); - expect(response.status).toBe(403); - }); - - // 20. Non-public bucket - unsigned GET should still be rejected - it("should reject unsigned GET on non-public bucket", async () => { - const request = new Request(`${endpoint}/test-bucket/test-file.txt`, { - method: "GET", - }); - - const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX); - expect(response.status).toBe(403); - }); - - // 21. Public read bucket - unsigned list (GET without key) should succeed - it("should allow unsigned list on public read bucket", async () => { - const request = new Request(`${endpoint}/public-bucket/`, { - method: "GET", - }); - - const response = await worker.fetch(request, PUBLIC_READ_ENV, CTX); - expect(response.status).toBe(200); - const xmlText = await response.text(); - expect(xmlText).toContain(" file.name === "empty")!.data.byteLength).toBe(0); }); }); + +function streamOf(chunks: Uint8Array[]): ReadableStream { + return new ReadableStream({ + start(controller) { + for (const chunk of chunks) controller.enqueue(chunk); + controller.close(); + }, + }); +} + +function concatAll(chunks: Uint8Array[]): Uint8Array { + const output = new Uint8Array(chunks.reduce((length, chunk) => length + chunk.byteLength, 0)); + let offset = 0; + for (const chunk of chunks) { + output.set(chunk, offset); + offset += chunk.byteLength; + } + return output; +} + +function encodeAwsChunked(payload: Uint8Array, trailer: boolean): Uint8Array { + const chunks: Uint8Array[] = []; + const encoder = new TextEncoder(); + for (let offset = 0; offset < payload.byteLength; offset += 65_537) { + const data = payload.subarray(offset, Math.min(payload.byteLength, offset + 65_537)); + const extension = trailer ? "" : `;chunk-signature=${"0".repeat(64)}`; + chunks.push(encoder.encode(`${data.byteLength.toString(16)}${extension}\r\n`), data, encoder.encode("\r\n")); + } + const extension = trailer ? "" : `;chunk-signature=${"0".repeat(64)}`; + chunks.push(encoder.encode(`0${extension}\r\n${trailer ? "x-amz-checksum-crc32:AAAAAA==\r\n" : ""}\r\n`)); + return concatAll(chunks); +} diff --git a/test/tsconfig.json b/test/tsconfig.json index e4e113c..c41c133 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "types": ["@cloudflare/vitest-pool-workers"] + "types": ["@cloudflare/vitest-pool-workers/types"] }, "include": ["./**/*.ts", "../worker-configuration.d.ts"], "exclude": [] diff --git a/vitest.config.mts b/vitest.config.mts index a57ae67..bc13d45 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,6 +1,25 @@ +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { configDefaults, defineConfig } from "vitest/config"; export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + miniflare: { + bindings: { + ACCESS_KEY: "test-access-key", + SECRET_KEY: "test-secret-key", + REGION: "auto", + GOOGLE_CLIENT_ID: "test-client-id", + GOOGLE_CLIENT_SECRET: "test-client-secret", + GOOGLE_REFRESH_TOKEN: "test-refresh-token", + ALLOWED_BUCKETS: "test-bucket,empty-bucket,my-bucket", + ALLOW_MULTIPART: "true", + ETAG_STYLE: "md5", + }, + }, + }), + ], test: { exclude: [...configDefaults.exclude, ".pnpm-wrangler/**", ".pnpm-store/**", "node_modules/**"], }, diff --git a/worker-configuration.d.ts b/worker-configuration.d.ts index 3186b0e..8b68580 100644 --- a/worker-configuration.d.ts +++ b/worker-configuration.d.ts @@ -1,13 +1,21 @@ /* eslint-disable */ -// Generated by Wrangler by running `wrangler types` (hash: b739a9c19cff1463949c4db47674ed86) -// Runtime types generated with workerd@1.20260103.0 2025-09-27 +// Generated by Wrangler by running `wrangler types` (hash: f1715efaed926a6241da69b62ea21ad8) +// Runtime types generated with workerd@1.20260804.1 2025-09-27 +interface __BaseEnv_Env { + AUTH_KV: KVNamespace; + FOLDER_CACHE: KVNamespace; + ALLOW_MULTIPART: "false"; + ETAG_STYLE: "md5"; + MPU: DurableObjectNamespace; +} declare namespace Cloudflare { interface GlobalProps { mainModule: typeof import("./src/index"); + durableNamespaces: "MultipartUploadDO"; } - interface Env {} + interface Env extends __BaseEnv_Env {} } -interface Env extends Cloudflare.Env {} +interface Env extends __BaseEnv_Env {} // Begin runtime types /*! ***************************************************************************** @@ -424,28 +432,33 @@ declare const performance: Performance; declare const Cloudflare: Cloudflare; declare const origin: string; declare const navigator: Navigator; -interface TestController {} +type TestController = {}; interface ExecutionContext { waitUntil(promise: Promise): void; passThroughOnException(): void; readonly props: Props; + cache?: CacheContext; + readonly access?: CloudflareAccessContext; + tracing: Tracing; } -type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; -type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; -type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; -type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; -interface ExportedHandler { - fetch?: ExportedHandlerFetchHandler; - tail?: ExportedHandlerTailHandler; - trace?: ExportedHandlerTraceHandler; - tailStream?: ExportedHandlerTailStreamHandler; - scheduled?: ExportedHandlerScheduledHandler; - test?: ExportedHandlerTestHandler; - email?: EmailExportedHandler; - queue?: ExportedHandlerQueueHandler; +type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; +type ExportedHandlerConnectHandler = (socket: Socket, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; +type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; +type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; +interface ExportedHandler { + fetch?: ExportedHandlerFetchHandler; + connect?: ExportedHandlerConnectHandler; + tail?: ExportedHandlerTailHandler; + trace?: ExportedHandlerTraceHandler; + tailStream?: ExportedHandlerTailStreamHandler; + scheduled?: ExportedHandlerScheduledHandler; + test?: ExportedHandlerTestHandler; + email?: EmailExportedHandler; + queue?: ExportedHandlerQueueHandler; } interface StructuredSerializeOptions { transfer?: any[]; @@ -454,24 +467,50 @@ declare abstract class Navigator { sendBeacon(url: string, body?: BodyInit): boolean; readonly userAgent: string; readonly hardwareConcurrency: number; + readonly platform: string; readonly language: string; readonly languages: string[]; } interface AlarmInvocationInfo { readonly isRetry: boolean; readonly retryCount: number; + readonly scheduledTime: number; } interface Cloudflare { readonly compatibilityFlags: Record; } +interface CachePurgeError { + code: number; + message: string; +} +interface CachePurgeResult { + success: boolean; + errors: CachePurgeError[]; +} +interface CachePurgeOptions { + tags?: string[]; + pathPrefixes?: string[]; + purgeEverything?: boolean; +} +interface CacheContext { + purge(options: CachePurgeOptions): Promise; +} +interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} +declare abstract class ColoLocalActorNamespace { + get(actorId: string): Fetcher; +} interface DurableObject { fetch(request: Request): Response | Promise; + connect?(socket: Socket): void | Promise; alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; webSocketError?(ws: WebSocket, error: unknown): void | Promise; } -type DurableObjectStub = Fetcher & { +type DurableObjectStub = Fetcher & { readonly id: DurableObjectId; readonly name?: string; }; @@ -479,6 +518,7 @@ interface DurableObjectId { toString(): string; equals(other: DurableObjectId): boolean; readonly name?: string; + readonly jurisdiction?: string; } declare abstract class DurableObjectNamespace { newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; @@ -488,23 +528,24 @@ declare abstract class DurableObjectNamespace; jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; } -type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; +type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high" | "us"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } -type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; +type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "apac-ne" | "apac-se" | "oc" | "afr" | "me"; type DurableObjectRoutingMode = "primary-only"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; routingMode?: DurableObjectRoutingMode; } -interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> {} +type DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> = {}; interface DurableObjectState { waitUntil(promise: Promise): void; readonly props: Props; readonly id: DurableObjectId; readonly storage: DurableObjectStorage; container?: Container; + facets: DurableObjectFacets; blockConcurrencyWhile(callback: () => Promise): Promise; acceptWebSocket(ws: WebSocket, tags?: string[]): void; getWebSockets(tag?: string): WebSocket[]; @@ -581,6 +622,16 @@ declare class WebSocketRequestResponsePair { get request(): string; get response(): string; } +interface DurableObjectFacets { + get(name: string, getStartupOptions: () => FacetStartupOptions | Promise>): Fetcher; + abort(name: string, reason: any): void; + delete(name: string): void; + clone(src: string, dst: string): void; +} +interface FacetStartupOptions { + id?: DurableObjectId | string; + class: DurableObjectClass; +} interface AnalyticsEngineDataset { writeDataPoint(event?: AnalyticsEngineDataPoint): void; } @@ -873,7 +924,7 @@ interface CustomEventCustomEventInit { * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) */ declare class Blob { - constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); + constructor(bits?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); /** * The **`size`** read-only property of the Blob interface returns the size of the Blob or File in bytes. * @@ -1382,6 +1433,12 @@ declare abstract class PromiseRejectionEvent extends Event { */ declare class FormData { constructor(); + /** + * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) + */ + append(name: string, value: string | Blob): void; /** * The **`append()`** method of the FormData interface appends a new value onto an existing key inside a `FormData` object, or adds the key if it does not already exist. * @@ -1418,6 +1475,12 @@ declare class FormData { * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) */ has(name: string): boolean; + /** + * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) + */ + set(name: string, value: string | Blob): void; /** * The **`set()`** method of the FormData interface sets a new value for an existing key inside a `FormData` object, or adds the key/value if it does not already exist. * @@ -1732,7 +1795,7 @@ interface Request> e * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) */ signal: AbortSignal; - cf: Cf | undefined; + cf?: Cf; /** * The **`integrity`** read-only property of the Request interface contains the subresource integrity value of the request. * @@ -1855,8 +1918,31 @@ interface KVNamespaceGetWithMetadataResult { } type QueueContentType = "text" | "bytes" | "json" | "v8"; interface Queue { - send(message: Body, options?: QueueSendOptions): Promise; - sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; + metrics(): Promise; + send(message: Body, options?: QueueSendOptions): Promise; + sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; +} +interface QueueSendMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface QueueSendMetadata { + metrics: QueueSendMetrics; +} +interface QueueSendResponse { + metadata: QueueSendMetadata; +} +interface QueueSendBatchMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface QueueSendBatchMetadata { + metrics: QueueSendBatchMetrics; +} +interface QueueSendBatchResponse { + metadata: QueueSendBatchMetadata; } interface QueueSendOptions { contentType?: QueueContentType; @@ -1870,6 +1956,19 @@ interface MessageSendRequest { contentType?: QueueContentType; delaySeconds?: number; } +interface QueueMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface MessageBatchMetrics { + backlogCount: number; + backlogBytes: number; + oldestMessageTimestamp?: Date; +} +interface MessageBatchMetadata { + metrics: MessageBatchMetrics; +} interface QueueRetryOptions { delaySeconds?: number; } @@ -1884,12 +1983,14 @@ interface Message { interface QueueEvent extends ExtendableEvent { readonly messages: readonly Message[]; readonly queue: string; + readonly metadata: MessageBatchMetadata; retryAll(options?: QueueRetryOptions): void; ackAll(): void; } interface MessageBatch { readonly messages: readonly Message[]; readonly queue: string; + readonly metadata: MessageBatchMetadata; retryAll(options?: QueueRetryOptions): void; ackAll(): void; } @@ -1908,7 +2009,7 @@ interface R2ListOptions { startAfter?: string; include?: ("httpMetadata" | "customMetadata")[]; } -declare abstract class R2Bucket { +interface R2Bucket { head(key: string): Promise; get( key: string, @@ -2585,6 +2686,11 @@ interface QueuingStrategyInit { */ highWaterMark: number; } +interface TracePreviewInfo { + id: string; + slug: string; + name: string; +} interface ScriptVersion { id?: string; tag?: string; @@ -2595,7 +2701,7 @@ declare abstract class TailEvent extends ExtendableEvent { readonly traces: TraceItem[]; } interface TraceItem { - readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; + readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemConnectEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; readonly eventTimestamp: number | null; readonly logs: TraceLog[]; readonly exceptions: TraceException[]; @@ -2605,6 +2711,8 @@ interface TraceItem { readonly scriptVersion?: ScriptVersion; readonly dispatchNamespace?: string; readonly scriptTags?: string[]; + readonly tailAttributes?: Record; + readonly preview?: TracePreviewInfo; readonly durableObjectId?: string; readonly outcome: string; readonly executionModel: string; @@ -2615,7 +2723,8 @@ interface TraceItem { interface TraceItemAlarmEventInfo { readonly scheduledTime: Date; } -interface TraceItemCustomEventInfo {} +type TraceItemConnectEventInfo = {}; +type TraceItemCustomEventInfo = {}; interface TraceItemScheduledEventInfo { readonly scheduledTime: number; readonly cron: string; @@ -3030,7 +3139,7 @@ declare var WebSocket: { * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) */ interface WebSocket extends EventTarget { - accept(): void; + accept(options?: WebSocketAcceptOptions): void; /** * The **`WebSocket.send()`** method enqueues the specified data to be transmitted to the server over the WebSocket connection, increasing the value of `bufferedAmount` by the number of bytes needed to contain the data. * @@ -3069,6 +3178,22 @@ interface WebSocket extends EventTarget { * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions) */ extensions: string | null; + /** + * The **`WebSocket.binaryType`** property controls the type of binary data being received over the WebSocket connection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/binaryType) + */ + binaryType: "blob" | "arraybuffer"; +} +interface WebSocketAcceptOptions { + /** + * When set to `true`, receiving a server-initiated WebSocket Close frame will not + * automatically send a reciprocal Close frame, leaving the connection in a half-open + * state. This is useful for proxying scenarios where you need to coordinate closing + * both sides independently. Defaults to `false` when the + * `no_web_socket_half_open_by_default` compatibility flag is enabled. + */ + allowHalfOpen?: boolean; } declare const WebSocketPair: { new (): { @@ -3180,6 +3305,28 @@ interface EventSourceEventSourceInit { withCredentials?: boolean; fetcher?: Fetcher; } +interface ExecOutput { + readonly stdout: ArrayBuffer; + readonly stderr: ArrayBuffer; + readonly exitCode: number; +} +interface ContainerExecOptions { + cwd?: string; + env?: Record; + user?: string; + stdin?: ReadableStream | "pipe"; + stdout?: "pipe" | "ignore"; + stderr?: "pipe" | "ignore" | "combined"; +} +interface ExecProcess { + readonly stdin: WritableStream | null; + readonly stdout: ReadableStream | null; + readonly stderr: ReadableStream | null; + readonly pid: number; + readonly exitCode: Promise; + output(): Promise; + kill(signal?: number): void; +} interface Container { get running(): boolean; start(options?: ContainerStartupOptions): void; @@ -3188,12 +3335,42 @@ interface Container { signal(signo: number): void; getTcpPort(port: number): Fetcher; setInactivityTimeout(durationMs: number | bigint): Promise; + interceptOutboundHttp(addr: string, binding: Fetcher): Promise; + interceptAllOutboundHttp(binding: Fetcher): Promise; + snapshotDirectory(options: ContainerDirectorySnapshotOptions): Promise; + snapshotContainer(options: ContainerSnapshotOptions): Promise; + interceptOutboundHttps(addr: string, binding: Fetcher): Promise; + exec(cmd: string[], options?: ContainerExecOptions): Promise; +} +interface ContainerDirectorySnapshot { + id: string; + size: number; + dir: string; + name?: string; +} +interface ContainerDirectorySnapshotOptions { + dir: string; + name?: string; +} +interface ContainerDirectorySnapshotRestoreParams { + snapshot: ContainerDirectorySnapshot; + mountPoint?: string; +} +interface ContainerSnapshot { + id: string; + size: number; + name?: string; +} +interface ContainerSnapshotOptions { + name?: string; } interface ContainerStartupOptions { entrypoint?: string[]; enableInternet: boolean; env?: Record; - hardTimeout?: number | bigint; + labels?: Record; + directorySnapshots?: ContainerDirectorySnapshotRestoreParams[]; + containerSnapshot?: ContainerSnapshot; } /** * The **`MessagePort`** interface of the Channel Messaging API represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. @@ -3258,6 +3435,8 @@ type LoopbackForExport Rpc.EntrypointBranded) : undefined; type LoopbackServiceStub = Fetcher & (T extends CloudflareWorkersModule.WorkerEntrypoint ? (opts: { props?: Props }) => Fetcher : (opts: { props?: any }) => Fetcher); type LoopbackDurableObjectClass = DurableObjectClass & (T extends CloudflareWorkersModule.DurableObject ? (opts: { props?: Props }) => DurableObjectClass : (opts: { props?: any }) => DurableObjectClass); +interface LoopbackDurableObjectNamespace extends DurableObjectNamespace {} +interface LoopbackColoLocalActorNamespace extends ColoLocalActorNamespace {} interface SyncKvStorage { get(key: string): T | undefined; list(options?: SyncKvListOptions): Iterable<[string, T]>; @@ -3274,12 +3453,15 @@ interface SyncKvListOptions { } interface WorkerStub { getEntrypoint(name?: string, options?: WorkerStubEntrypointOptions): Fetcher; + getDurableObjectClass(name?: string, options?: WorkerStubEntrypointOptions): DurableObjectClass; } interface WorkerStubEntrypointOptions { props?: any; + limits?: workerdResourceLimits; } interface WorkerLoader { get(name: string | null, getCode: () => WorkerLoaderWorkerCode | Promise): WorkerStub; + load(code: WorkerLoaderWorkerCode): WorkerStub; } interface WorkerLoaderModule { js?: string; @@ -3294,6 +3476,7 @@ interface WorkerLoaderWorkerCode { compatibilityDate: string; compatibilityFlags?: string[]; allowExperimental?: boolean; + limits?: workerdResourceLimits; mainModule: string; modules: Record; env?: any; @@ -3301,6 +3484,10 @@ interface WorkerLoaderWorkerCode { tails?: Fetcher[]; streamingTails?: Fetcher[]; } +interface workerdResourceLimits { + cpuMs?: number; + subRequests?: number; +} /** * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, * as well as timing of subrequests and other operations. @@ -3312,6 +3499,1045 @@ declare abstract class Performance { get timeOrigin(): number; /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */ now(): number; + /** + * The **`toJSON()`** method of the Performance interface is a Serialization; it returns a JSON representation of the Performance object. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Performance/toJSON) + */ + toJSON(): object; +} +interface Tracing { + enterSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; + startActiveSpan(name: string, callback: (span: Span, ...args: A) => T, ...args: A): T; + Span: typeof Span; +} +declare abstract class Span { + get isTraced(): boolean; + setAttribute(key: string, value?: boolean | number | string): void; + end(): void; +} +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} +// ============================================================================ +// Agent Memory +// +// Public type surface for user Workers binding to an Agent Memory namespace. +// ============================================================================ +/** Memory type — every memory is classified into exactly one. */ +type AgentMemoryMemoryType = "fact" | "event" | "instruction" | "task"; +/** Search intensity for recall. */ +type AgentMemoryThinkingLevel = "low" | "medium" | "high"; +/** Response verbosity for recall. */ +type AgentMemoryResponseLength = "short" | "medium" | "long"; +/** A conversation message passed to ingest(). */ +interface AgentMemoryMessage { + role: "system" | "user" | "assistant"; + content: string; + /** Optional message timestamp. */ + timestamp?: Date; +} +/** Raw memory content passed to remember(). */ +interface AgentMemoryIncomingMemory { + /** Raw memory content. The service classifies and summarizes automatically. */ + content: string; + /** Optional session identifier to associate with this memory. */ + sessionId?: string | null | undefined; +} +/** A stored memory returned from remember(), get(), and delete(). */ +interface AgentMemoryMemory { + /** Memory ID. */ + id: string; + /** Memory type. */ + type: AgentMemoryMemoryType; + /** Text summary. */ + summary: string; + /** Memory text. */ + content: string; + /** Session that created this memory. */ + sessionId: string | null; + /** Memory creation time. */ + createdAt: Date; + /** Memory last-update time. */ + updatedAt: Date; +} +/** Single entry in a list() response. Same shape as Memory minus full content. */ +type AgentMemoryMemoryListEntry = Omit; +/** A scored memory candidate in a recall result. */ +interface AgentMemoryScoredCandidate { + /** Candidate ID. */ + id: string; + /** Text summary. */ + summary: string; + /** Session that created this candidate, when known. */ + sessionId: string | null; + /** Relevance score (higher is better). Comparable only within a single query. */ + score: number; +} +/** Options for the ingest() method. */ +interface AgentMemoryIngestOptions { + /** Session identifier to associate with memories created during ingestion. */ + sessionId?: string | null | undefined; +} +/** Options for the getSummary() method. */ +interface AgentMemoryGetSummaryOptions { + /** Session identifier to retrieve session summary for. */ + sessionId?: string | null | undefined; +} +/** Response from the getSummary() method. */ +interface AgentMemoryGetSummaryResponse { + /** Markdown summary. */ + summary: string; +} +/** + * Options for the recall() method. + * + * `referenceDate` accepts a Date object, an ISO-8601 date string + * (YYYY-MM-DD), or a full ISO-8601 datetime string. When provided, this + * date is used as "today" for resolving relative time references + * ("how many days ago", "last week") instead of the server's wall-clock time. + */ +interface AgentMemoryRecallOptions { + /** Recall intensity: "low" (default), "medium", or "high". */ + thinkingLevel?: AgentMemoryThinkingLevel; + /** Response verbosity: "short", "medium" (default), or "long". */ + responseLength?: AgentMemoryResponseLength; + /** Temporal anchor for date arithmetic. */ + referenceDate?: Date | string; +} +/** Response from the recall() method. */ +interface AgentMemoryRecallResult { + /** Number of memories retrieved. */ + count: number; + /** LLM-generated answer synthesizing the matching memories. */ + answer: string; + /** Matching memories ranked by relevance. */ + candidates: AgentMemoryScoredCandidate[]; +} +/** + * Options for the list() method. + * + * `cursor` is the opaque continuation token returned by the previous page; + * pass it back unchanged to fetch the next page. `sessionId` and `type` + * are exact-match filters; combining them is allowed. + */ +interface AgentMemoryListMemoriesOptions { + /** Maximum number of memories to return. Default 20, max 500. */ + limit?: number; + /** Opaque cursor from a previous page. */ + cursor?: string; + /** Exact-match session filter. */ + sessionId?: string; + /** Exact-match memory-type filter. */ + type?: AgentMemoryMemoryType; +} +/** Response from the list() method. */ +interface AgentMemoryListMemoriesResult { + memories: AgentMemoryMemoryListEntry[]; + /** Continuation cursor; absent when this page exhausted the result set. */ + cursor?: string; +} +/** + * A single Agent Memory profile, scoped to a profile name. + * + * Returned by {@link AgentMemoryNamespace.getProfile}. + */ +declare abstract class AgentMemoryProfile { + /** + * Retrieve a memory by ID. + * + * @param memoryId - ULID of the memory to retrieve. + * @throws if the memory does not exist. + */ + get(memoryId: string): Promise; + /** + * Delete a memory by ID. + * + * Removes the memory and any source messages linked by the memory's + * source message IDs. + * + * @param memoryId - ULID of the memory to delete. + * @throws if the memory does not exist. + */ + delete(memoryId: string): Promise; + /** + * Store a memory in this profile. The content is automatically classified, + * summarized, and indexed. + * + * @param memory - Raw memory content to persist. + */ + remember(memory: AgentMemoryIncomingMemory): Promise; + /** + * Extract memories from a conversation. + * + * @param messages - Conversation messages to extract memories from. + * @param options - Optional ingest options. + */ + ingest(messages: Iterable, options?: AgentMemoryIngestOptions): Promise; + /** + * Get a profile summary. + * + * @param options - Optional getSummary options. + */ + getSummary(options?: AgentMemoryGetSummaryOptions): Promise; + /** + * Recall memories in this profile. + * + * @param query - Recall query matched against memory content and keywords. + * @param options - Optional recall parameters. + * @returns Matching memories with relevance scores and a synthesized answer. + */ + recall(query: string, options?: AgentMemoryRecallOptions): Promise; + /** + * List active memories in this profile. + * + * Returns a paginated, filterable view of stored memories. Superseded + * versions are excluded. Use the returned `cursor` (when present) to + * fetch the next page. + * + * @param options - Optional pagination and filter options. + */ + list(options?: AgentMemoryListMemoriesOptions): Promise; + /** + * Soft-delete every memory and message in this profile that is tagged + * with `sessionId`. + * + * Idempotent: deleting a sessionId that has no rows is a no-op. + * + * @param sessionId - Session to delete. + */ + deleteSession(sessionId: string): Promise; +} +/** + * Namespace-level Agent Memory binding. + * + * Used as the type of an `env.MEMORY`-style binding backed by the Agent + * Memory product. + * + * @example + * ```ts + * export default { + * async fetch(_request: Request, env: Env): Promise { + * const profile = await env.MEMORY.getProfile("wrangler-e2e"); + * const summary = await profile.getSummary(); + * return Response.json(summary); + * }, + * }; + * ``` + */ +declare abstract class AgentMemoryNamespace { + /** + * Get a memory profile by name. Profiles are isolated by namespace and + * addressed by a compound key (namespaceId:profileName). + * + * @param profileName - Profile name (validated against naming rules). + * @returns RPC target for interacting with the profile. + */ + getProfile(profileName: string): Promise; + /** + * Soft-delete a profile and schedule deferred purge. Marks all + * memories and messages as deleted. + * + * @param profileName - Name of the profile to delete. + */ + deleteProfile(profileName: string): Promise; +} +// ============ AI Search Error Interfaces ============ +interface AiSearchInternalError extends Error {} +interface AiSearchNotFoundError extends Error {} +// ============ AI Search Common Types ============ +/** A single message in a conversation-style search or chat request. */ +type AiSearchMessage = { + role: "system" | "developer" | "user" | "assistant" | "tool"; + content: string | null; +}; +/** + * Common shape for `ai_search_options` used by both single-instance and multi-instance requests. + * Contains retrieval, query rewrite, reranking, and cache sub-options. + */ +type AiSearchOptions = { + retrieval?: { + /** Which retrieval backend to use. Defaults to the instance's configured index_method. */ + retrieval_type?: "vector" | "keyword" | "hybrid"; + /** Fusion method for combining vector + keyword results. */ + fusion_method?: "max" | "rrf"; + /** How keyword terms are combined: "and" = all terms must match, "or" = any term matches. */ + keyword_match_mode?: "and" | "or"; + /** Minimum similarity score (0-1) for a result to be included. Default 0.4. */ + match_threshold?: number; + /** Maximum number of results to return (1-50). Default 10. */ + max_num_results?: number; + /** Vectorize metadata filters applied to the search. */ + filters?: VectorizeVectorMetadataFilter; + /** Number of surrounding chunks to include for context (0-3). Default 0. */ + context_expansion?: number; + /** If true, return only item metadata without chunk text. */ + metadata_only?: boolean; + /** If true (default), return empty results on retrieval failure instead of throwing. */ + return_on_failure?: boolean; + /** Boost results by metadata field values. Max 3 entries. */ + boost_by?: Array<{ + field: string; + direction?: "asc" | "desc" | "exists" | "not_exists"; + }>; + [key: string]: unknown; + }; + query_rewrite?: { + enabled?: boolean; + model?: string; + rewrite_prompt?: string; + [key: string]: unknown; + }; + reranking?: { + enabled?: boolean; + model?: string; + /** Match threshold (0-1, default 0.4) */ + match_threshold?: number; + [key: string]: unknown; + }; + cache?: { + enabled?: boolean; + cache_threshold?: "super_strict_match" | "close_enough" | "flexible_friend" | "anything_goes"; + }; + [key: string]: unknown; +}; +// ============ AI Search Request Types ============ +/** + * Request body for single-instance search. + * Exactly one of `query` or `messages` must be provided. + */ +type AiSearchSearchRequest = + | { + /** Simple query string. */ + query: string; + messages?: never; + ai_search_options?: AiSearchOptions; + } + | { + query?: never; + /** Conversation-style input. At least one user message with non-empty content is required. */ + messages: AiSearchMessage[]; + ai_search_options?: AiSearchOptions; + }; +type AiSearchChatCompletionsRequest = { + messages: AiSearchMessage[]; + model?: string; + stream?: boolean; + ai_search_options?: AiSearchOptions; + [key: string]: unknown; +}; +// ============ AI Search Multi-Instance Types (Namespace-Scoped) ============ +/** `ai_search_options` shape for multi-instance requests — requires `instance_ids`. */ +type AiSearchMultiSearchOptions = AiSearchOptions & { + /** Instance IDs to search across (1-10). */ + instance_ids: string[]; +}; +/** + * Request for searching across multiple instances within a namespace. + * `ai_search_options` is required and must include `instance_ids`. + * Exactly one of `query` or `messages` must be provided. + */ +type AiSearchMultiSearchRequest = + | { + /** Simple query string. */ + query: string; + messages?: never; + ai_search_options: AiSearchMultiSearchOptions; + } + | { + query?: never; + /** Conversation-style input. */ + messages: AiSearchMessage[]; + ai_search_options: AiSearchMultiSearchOptions; + }; +/** A search result chunk tagged with the instance it originated from. */ +type AiSearchMultiSearchChunk = AiSearchSearchResponse["chunks"][number] & { + instance_id: string; +}; +/** Describes a per-instance error during a multi-instance operation. */ +type AiSearchMultiSearchError = { + instance_id: string; + message: string; +}; +/** Response from a multi-instance search, with chunks tagged by instance and optional partial-failure errors. */ +type AiSearchMultiSearchResponse = { + search_query: string; + chunks: AiSearchMultiSearchChunk[]; + errors?: AiSearchMultiSearchError[]; +}; +/** Request for chat completions across multiple instances within a namespace. `ai_search_options` is required and must include `instance_ids`. */ +type AiSearchMultiChatCompletionsRequest = Omit & { + ai_search_options: AiSearchMultiSearchOptions; +}; +/** Response from multi-instance chat completions, with chunks tagged by instance and optional partial-failure errors. */ +type AiSearchMultiChatCompletionsResponse = Omit & { + chunks: AiSearchMultiSearchChunk[]; + errors?: AiSearchMultiSearchError[]; +}; +// ============ AI Search Response Types ============ +type AiSearchSearchResponse = { + search_query: string; + chunks: Array<{ + id: string; + type: string; + /** Match score (0-1) */ + score: number; + text: string; + item: { + timestamp?: number; + key: string; + metadata?: Record; + }; + scoring_details?: { + /** Keyword match score (0-1) */ + keyword_score?: number; + /** Vector similarity score (0-1) */ + vector_score?: number; + /** Keyword rank position */ + keyword_rank?: number; + /** Vector rank position */ + vector_rank?: number; + /** Reranking model score */ + reranking_score?: number; + /** Fusion method used to combine results */ + fusion_method?: "rrf" | "max"; + [key: string]: unknown; + }; + }>; +}; +type AiSearchChatCompletionsResponse = { + id?: string; + object?: string; + model?: string; + choices: Array<{ + index?: number; + message: { + role: "system" | "developer" | "user" | "assistant" | "tool"; + content: string | null; + [key: string]: unknown; + }; + [key: string]: unknown; + }>; + chunks: AiSearchSearchResponse["chunks"]; + [key: string]: unknown; +}; +type AiSearchStatsResponse = { + queued?: number; + running?: number; + completed?: number; + error?: number; + skipped?: number; + outdated?: number; + last_activity?: string; + /** Storage engine statistics. */ + engine?: { + vectorize?: { + vectorsCount: number; + dimensions: number; + }; + r2?: { + payloadSizeBytes: number; + metadataSizeBytes: number; + objectCount: number; + }; + }; +}; +// ============ AI Search Instance Info Types ============ +type AiSearchInstanceInfo = { + id: string; + type?: "r2" | "web-crawler" | string; + source?: string; + source_params?: unknown; + paused?: boolean; + status?: string; + namespace?: string; + created_at?: string; + modified_at?: string; + token_id?: string; + ai_gateway_id?: string; + rewrite_query?: boolean; + reranking?: boolean; + embedding_model?: string; + ai_search_model?: string; + rewrite_model?: string; + reranking_model?: string; + /** @deprecated Use index_method instead. */ + hybrid_search_enabled?: boolean; + /** Controls which storage backends are active. */ + index_method?: { + vector?: boolean; + keyword?: boolean; + }; + /** Fusion method for combining vector and keyword results. */ + fusion_method?: "max" | "rrf"; + indexing_options?: { + keyword_tokenizer?: "porter" | "trigram"; + } | null; + retrieval_options?: { + keyword_match_mode?: "and" | "or"; + boost_by?: Array<{ + field: string; + direction?: "asc" | "desc" | "exists" | "not_exists"; + }>; + } | null; + chunk?: boolean; + chunk_size?: number; + chunk_overlap?: number; + score_threshold?: number; + max_num_results?: number; + cache?: boolean; + cache_threshold?: "super_strict_match" | "close_enough" | "flexible_friend" | "anything_goes"; + custom_metadata?: Array<{ + field_name: string; + data_type: "text" | "number" | "boolean" | "datetime"; + }>; + /** Sync interval in seconds. */ + sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; + metadata?: Record; + [key: string]: unknown; +}; +/** Pagination, search, and ordering parameters for listing instances within a namespace. */ +type AiSearchListInstancesParams = { + page?: number; + per_page?: number; + /** Search instances by ID. */ + search?: string; + /** Field to sort by. */ + order_by?: "created_at"; + /** Sort direction. */ + order_by_direction?: "asc" | "desc"; +}; +type AiSearchListResponse = { + result: AiSearchInstanceInfo[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +// ============ AI Search Config Types ============ +type AiSearchConfig = { + /** Instance ID (1-32 chars, pattern: ^[a-z0-9_]+(?:-[a-z0-9_]+)*$) */ + id: string; + /** Instance type. Omit to create with built-in storage. */ + type?: "r2" | "web-crawler" | string; + /** Source URL (required for web-crawler type). */ + source?: string; + source_params?: unknown; + /** Token ID (UUID format) */ + token_id?: string; + ai_gateway_id?: string; + /** Enable query rewriting (default false) */ + rewrite_query?: boolean; + /** Enable reranking (default false) */ + reranking?: boolean; + embedding_model?: string; + ai_search_model?: string; + rewrite_model?: string; + reranking_model?: string; + /** @deprecated Use index_method instead. */ + hybrid_search_enabled?: boolean; + /** Controls which storage backends are used during indexing. Defaults to vector-only. */ + index_method?: { + vector?: boolean; + keyword?: boolean; + }; + /** Fusion method for combining vector and keyword results. "rrf" = reciprocal rank fusion (default), "max" = maximum score. */ + fusion_method?: "max" | "rrf"; + indexing_options?: { + keyword_tokenizer?: "porter" | "trigram"; + } | null; + retrieval_options?: { + keyword_match_mode?: "and" | "or"; + boost_by?: Array<{ + field: string; + direction?: "asc" | "desc" | "exists" | "not_exists"; + }>; + } | null; + chunk?: boolean; + chunk_size?: number; + chunk_overlap?: number; + /** Minimum similarity score (0-1) for a result to be included. */ + score_threshold?: number; + max_num_results?: number; + cache?: boolean; + /** Similarity threshold for cache hits. Stricter = fewer cache hits but higher relevance. */ + cache_threshold?: "super_strict_match" | "close_enough" | "flexible_friend" | "anything_goes"; + custom_metadata?: Array<{ + field_name: string; + data_type: "text" | "number" | "boolean" | "datetime"; + }>; + namespace?: string; + /** Sync interval in seconds. 3600=1h, 7200=2h, 14400=4h, 21600=6h, 43200=12h, 86400=24h. */ + sync_interval?: 3600 | 7200 | 14400 | 21600 | 43200 | 86400; + metadata?: Record; + [key: string]: unknown; +}; +// ============ AI Search Item Types ============ +type AiSearchItemInfo = { + id: string; + key: string; + status: "completed" | "error" | "skipped" | "queued" | "running" | "outdated"; + next_action?: "INDEX" | "DELETE" | null; + error?: string; + checksum?: string; + namespace?: string; + chunks_count?: number | null; + file_size?: number | null; + source_id?: string | null; + last_seen_at?: string; + created_at?: string; + metadata?: Record; + [key: string]: unknown; +}; +type AiSearchItemContentResult = { + body: ReadableStream; + contentType: string; + filename: string; + size: number; +}; +type AiSearchUploadItemOptions = { + metadata?: Record; +}; +type AiSearchListItemsParams = { + page?: number; + per_page?: number; + /** Search items by key name. */ + search?: string; + /** Sort order for results. */ + sort_by?: "status" | "modified_at"; + /** Filter items by processing status. */ + status?: "queued" | "running" | "completed" | "error" | "skipped" | "outdated"; + /** Filter items by source (e.g. "builtin" or "web-crawler:https://example.com"). */ + source?: string; + /** JSON-encoded Vectorize filter for metadata filtering. */ + metadata_filter?: string; +}; +type AiSearchListItemsResponse = { + result: AiSearchItemInfo[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +// ============ AI Search Item Logs Types ============ +type AiSearchItemLogsParams = { + /** Maximum number of log entries to return (1-100, default 50). */ + limit?: number; + /** Opaque cursor for pagination. Pass the `cursor` value from a previous response. */ + cursor?: string; +}; +type AiSearchItemLog = { + timestamp: string; + action: string; + message: string; + fileKey?: string; + chunkCount?: number; + processingTimeMs?: number; + errorType?: string; +}; +/** Paginated response for item processing logs (cursor-based). */ +type AiSearchItemLogsResponse = { + result: AiSearchItemLog[]; + result_info: { + count: number; + per_page: number; + cursor: string | null; + truncated: boolean; + }; +}; +// ============ AI Search Item Chunks Types ============ +type AiSearchItemChunksParams = { + /** Maximum number of chunks to return (1-100, default 20). */ + limit?: number; + /** Offset into the chunks list (default 0). */ + offset?: number; +}; +/** A single indexed chunk belonging to an item, including its text content and byte range. */ +type AiSearchItemChunk = { + id: string; + text: string; + start_byte: number; + end_byte: number; + item?: { + timestamp?: number; + key: string; + metadata?: Record; + }; +}; +/** Paginated response for item chunks (offset-based). */ +type AiSearchItemChunksResponse = { + result: AiSearchItemChunk[]; + result_info: { + count: number; + total: number; + limit: number; + offset: number; + }; +}; +// ============ AI Search Job Types ============ +type AiSearchJobInfo = { + id: string; + source: "user" | "schedule"; + description?: string; + last_seen_at?: string; + started_at?: string; + ended_at?: string; + end_reason?: string; +}; +type AiSearchJobLog = { + id: number; + message: string; + message_type: number; + created_at: number; +}; +type AiSearchCreateJobParams = { + description?: string; +}; +type AiSearchListJobsParams = { + page?: number; + per_page?: number; +}; +type AiSearchListJobsResponse = { + result: AiSearchJobInfo[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +type AiSearchJobLogsParams = { + page?: number; + per_page?: number; +}; +type AiSearchJobLogsResponse = { + result: AiSearchJobLog[]; + result_info?: { + count: number; + page: number; + per_page: number; + total_count: number; + }; +}; +// ============ AI Search Sub-Service Classes ============ +/** + * Single item service for an AI Search instance. + * Provides info, download, sync, logs, and chunks operations on a specific item. + */ +declare abstract class AiSearchItem { + /** Get metadata about this item. */ + info(): Promise; + /** + * Download the item's content. + * @returns Object with body stream, content type, filename, and size. + */ + download(): Promise; + /** + * Trigger re-indexing of this item. + * @returns The updated item info. + */ + sync(): Promise; + /** + * Retrieve processing logs for this item (cursor-based pagination). + * @param params Optional pagination parameters (limit, cursor). + * @returns Paginated log entries for this item. + */ + logs(params?: AiSearchItemLogsParams): Promise; + /** + * List indexed chunks for this item (offset-based pagination). + * @param params Optional pagination parameters (limit, offset). + * @returns Paginated chunk entries for this item. + */ + chunks(params?: AiSearchItemChunksParams): Promise; +} +/** + * Items collection service for an AI Search instance. + * Provides list, upload, and access to individual items. + */ +declare abstract class AiSearchItems { + /** List items in this instance. */ + list(params?: AiSearchListItemsParams): Promise; + /** + * Upload a file as an item. Behaves as an upsert: if an item with the same + * filename already exists, it is overwritten and re-indexed. + * @param name Filename for the uploaded item. + * @param content File content as a ReadableStream, Blob, or string. + * @param options Optional metadata to attach to the item. + * @returns The created item info. + */ + upload(name: string, content: ReadableStream | Blob | string, options?: AiSearchUploadItemOptions): Promise; + /** + * Upload a file and poll until processing completes. + * Behaves as an upsert: if an item with the same filename already exists, + * it is overwritten and re-indexed. + * @param name Filename for the uploaded item. + * @param content File content as a ReadableStream, Blob, or string. + * @param options Optional metadata and polling configuration. + * @returns The item info after processing completes (or timeout). + */ + uploadAndPoll( + name: string, + content: ReadableStream | Blob | string, + options?: AiSearchUploadItemOptions & { + /** Polling interval in milliseconds (default 1000). */ + pollIntervalMs?: number; + /** Maximum time to wait in milliseconds (default 30000). */ + timeoutMs?: number; + }, + ): Promise; + /** + * Get an item by ID. + * @param itemId The item identifier. + * @returns Item service for info, download, sync, logs, and chunks operations. + */ + get(itemId: string): AiSearchItem; + /** + * Delete an item from the instance. + * @param itemId The item identifier. + */ + delete(itemId: string): Promise; +} +/** + * Single job service for an AI Search instance. + * Provides info, logs, and cancel operations for a specific job. + */ +declare abstract class AiSearchJob { + /** Get metadata about this job. */ + info(): Promise; + /** Get logs for this job. */ + logs(params?: AiSearchJobLogsParams): Promise; + /** + * Cancel a running job. + * @returns The updated job info. + * @throws AiSearchNotFoundError if the job does not exist. + */ + cancel(): Promise; +} +/** + * Jobs collection service for an AI Search instance. + * Provides list, create, and access to individual jobs. + */ +declare abstract class AiSearchJobs { + /** List jobs for this instance. */ + list(params?: AiSearchListJobsParams): Promise; + /** + * Create a new indexing job. + * @param params Optional job parameters. + * @returns The created job info. + */ + create(params?: AiSearchCreateJobParams): Promise; + /** + * Get a job by ID. + * @param jobId The job identifier. + * @returns Job service for info, logs, and cancel operations. + */ + get(jobId: string): AiSearchJob; +} +// ============ AI Search Binding Classes ============ +/** + * Instance-level AI Search service. + * + * Used as: + * - The return type of `AiSearchNamespace.get(name)` (namespace binding) + * - The type of `env.BLOG_SEARCH` (single instance binding via `ai_search`) + * + * Provides search, chat, update, stats, items, and jobs operations. + * + * @example + * ```ts + * // Via namespace binding + * const instance = env.AI_SEARCH.get("blog"); + * const results = await instance.search({ + * query: "How does caching work?", + * }); + * + * // Via single instance binding + * const results = await env.BLOG_SEARCH.search({ + * messages: [{ role: "user", content: "How does caching work?" }], + * }); + * ``` + */ +declare abstract class AiSearchInstance { + /** + * Search the AI Search instance for relevant chunks. + * @param params Search request with query or messages and optional AI search options. + * @returns Search response with matching chunks and search query. + */ + search(params: AiSearchSearchRequest): Promise; + /** + * Generate chat completions with AI Search context (streaming). + * @param params Chat completions request with stream: true. + * @returns ReadableStream of server-sent events. + */ + chatCompletions( + params: AiSearchChatCompletionsRequest & { + stream: true; + }, + ): Promise; + /** + * Generate chat completions with AI Search context. + * @param params Chat completions request. + * @returns Chat completion response with choices and RAG chunks. + */ + chatCompletions(params: AiSearchChatCompletionsRequest): Promise; + /** + * Update the instance configuration. + * @param config Partial configuration to update. + * @returns Updated instance info. + */ + update(config: Partial): Promise; + /** Get metadata about this instance. */ + info(): Promise; + /** + * Get instance statistics (item count, indexing status, etc.). + * @returns Statistics with counts per status, last activity time, and engine details. + */ + stats(): Promise; + /** Items collection — list, upload, and manage items in this instance. */ + get items(): AiSearchItems; + /** Jobs collection — list, create, and inspect indexing jobs. */ + get jobs(): AiSearchJobs; +} +/** + * Namespace-level AI Search service. + * + * Used as the type of `env.AI_SEARCH` (namespace binding via `ai_search_namespaces`). + * Scoped to a single namespace. Provides dynamic instance access, creation, deletion, + * and multi-instance search/chat operations. + * + * @example + * ```ts + * // Access an instance within the namespace + * const blog = env.AI_SEARCH.get("blog"); + * const results = await blog.search({ query: "How does caching work?" }); + * + * // List all instances in the namespace + * const instances = await env.AI_SEARCH.list(); + * + * // Create a new instance with built-in storage + * const tenant = await env.AI_SEARCH.create({ id: "tenant-123" }); + * + * // Upload items into the instance + * await tenant.items.upload("doc.pdf", fileContent); + * + * // Search across multiple instances + * const multi = await env.AI_SEARCH.search({ + * query: "caching", + * ai_search_options: { instance_ids: ["blog", "docs"] }, + * }); + * + * // Delete an instance + * await env.AI_SEARCH.delete("tenant-123"); + * ``` + */ +declare abstract class AiSearchNamespace { + /** + * Get an instance by name within the bound namespace. + * @param name Instance name. + * @returns Instance service for search, chat, update, stats, items, and jobs. + */ + get(name: string): AiSearchInstance; + /** + * List instances in the bound namespace. + * @param params Optional pagination, search, and ordering parameters. + * @returns Array of instance metadata with pagination info. + */ + list(params?: AiSearchListInstancesParams): Promise; + /** + * Create a new instance within the bound namespace. + * @param config Instance configuration. Only `id` is required — omit `type` and `source` to create with built-in storage. + * @returns Instance service for the newly created instance. + * + * @example + * ```ts + * // Create with built-in storage (upload items manually) + * const instance = await env.AI_SEARCH.create({ id: "my-search" }); + * + * // Create with web crawler source + * const instance = await env.AI_SEARCH.create({ + * id: "docs-search", + * type: "web-crawler", + * source: "https://developers.cloudflare.com", + * }); + * ``` + */ + create(config: AiSearchConfig): Promise; + /** + * Delete an instance from the bound namespace. + * @param name Instance name to delete. + */ + delete(name: string): Promise; + /** + * Search across multiple instances within the bound namespace. + * Fans out to the specified instance_ids and merges results. + * @param params Search request with required `ai_search_options.instance_ids`. + * @returns Search response with chunks tagged by instance_id and optional partial-failure errors. + */ + search(params: AiSearchMultiSearchRequest): Promise; + /** + * Generate chat completions across multiple instances within the bound namespace (streaming). + * Fans out to the specified instance_ids, merges context, and generates a response. + * @param params Chat completions request with stream: true and required `ai_search_options.instance_ids`. + * @returns ReadableStream of server-sent events. + */ + chatCompletions( + params: AiSearchMultiChatCompletionsRequest & { + stream: true; + }, + ): Promise; + /** + * Generate chat completions across multiple instances within the bound namespace. + * Fans out to the specified instance_ids, merges context, and generates a response. + * @param params Chat completions request with required `ai_search_options.instance_ids`. + * @returns Chat completion response with choices, chunks tagged by instance_id, and optional partial-failure errors. + */ + chatCompletions(params: AiSearchMultiChatCompletionsRequest): Promise; } type AiImageClassificationInput = { image: number[]; @@ -3578,6 +4804,369 @@ declare abstract class BaseAiTranslation { inputs: AiTranslationInput; postProcessedOutputs: AiTranslationOutput; } +/** + * Workers AI support for OpenAI's Chat Completions API + */ +type ChatCompletionContentPartText = { + type: "text"; + text: string; +}; +type ChatCompletionContentPartImage = { + type: "image_url"; + image_url: { + url: string; + detail?: "auto" | "low" | "high"; + }; +}; +type ChatCompletionContentPartInputAudio = { + type: "input_audio"; + input_audio: { + /** Base64 encoded audio data. */ + data: string; + format: "wav" | "mp3"; + }; +}; +type ChatCompletionContentPartFile = { + type: "file"; + file: { + /** Base64 encoded file data. */ + file_data?: string; + /** The ID of an uploaded file. */ + file_id?: string; + filename?: string; + }; +}; +type ChatCompletionContentPartRefusal = { + type: "refusal"; + refusal: string; +}; +type ChatCompletionContentPart = ChatCompletionContentPartText | ChatCompletionContentPartImage | ChatCompletionContentPartInputAudio | ChatCompletionContentPartFile; +type FunctionDefinition = { + name: string; + description?: string; + parameters?: Record; + strict?: boolean | null; +}; +type ChatCompletionFunctionTool = { + type: "function"; + function: FunctionDefinition; +}; +type ChatCompletionCustomToolGrammarFormat = { + type: "grammar"; + grammar: { + definition: string; + syntax: "lark" | "regex"; + }; +}; +type ChatCompletionCustomToolTextFormat = { + type: "text"; +}; +type ChatCompletionCustomToolFormat = ChatCompletionCustomToolTextFormat | ChatCompletionCustomToolGrammarFormat; +type ChatCompletionCustomTool = { + type: "custom"; + custom: { + name: string; + description?: string; + format?: ChatCompletionCustomToolFormat; + }; +}; +type ChatCompletionTool = ChatCompletionFunctionTool | ChatCompletionCustomTool; +type ChatCompletionMessageFunctionToolCall = { + id: string; + type: "function"; + function: { + name: string; + /** JSON-encoded arguments string. */ + arguments: string; + }; +}; +type ChatCompletionMessageCustomToolCall = { + id: string; + type: "custom"; + custom: { + name: string; + input: string; + }; +}; +type ChatCompletionMessageToolCall = ChatCompletionMessageFunctionToolCall | ChatCompletionMessageCustomToolCall; +type ChatCompletionToolChoiceFunction = { + type: "function"; + function: { + name: string; + }; +}; +type ChatCompletionToolChoiceCustom = { + type: "custom"; + custom: { + name: string; + }; +}; +type ChatCompletionToolChoiceAllowedTools = { + type: "allowed_tools"; + allowed_tools: { + mode: "auto" | "required"; + tools: Array>; + }; +}; +type ChatCompletionToolChoiceOption = "none" | "auto" | "required" | ChatCompletionToolChoiceFunction | ChatCompletionToolChoiceCustom | ChatCompletionToolChoiceAllowedTools; +type DeveloperMessage = { + role: "developer"; + content: + | string + | Array<{ + type: "text"; + text: string; + }>; + name?: string; +}; +type SystemMessage = { + role: "system"; + content: + | string + | Array<{ + type: "text"; + text: string; + }>; + name?: string; +}; +/** + * Permissive merged content part used inside UserMessage arrays. + * + * Cabidela has a limitation where anyOf/oneOf with enum-based discrimination + * inside nested array items does not correctly match different branches for + * different array elements, so the schema uses a single merged object. + */ +type UserMessageContentPart = { + type: "text" | "image_url" | "input_audio" | "file"; + text?: string; + image_url?: { + url?: string; + detail?: "auto" | "low" | "high"; + }; + input_audio?: { + data?: string; + format?: "wav" | "mp3"; + }; + file?: { + file_data?: string; + file_id?: string; + filename?: string; + }; +}; +type UserMessage = { + role: "user"; + content: string | Array; + name?: string; +}; +type AssistantMessageContentPart = { + type: "text" | "refusal"; + text?: string; + refusal?: string; +}; +type AssistantMessage = { + role: "assistant"; + content?: string | null | Array; + refusal?: string | null; + name?: string; + audio?: { + id: string; + }; + tool_calls?: Array; + function_call?: { + name: string; + arguments: string; + }; +}; +type ToolMessage = { + role: "tool"; + content: + | string + | Array<{ + type: "text"; + text: string; + }>; + tool_call_id: string; +}; +type FunctionMessage = { + role: "function"; + content: string; + name: string; +}; +type ChatCompletionMessageParam = DeveloperMessage | SystemMessage | UserMessage | AssistantMessage | ToolMessage | FunctionMessage; +type ChatCompletionsResponseFormatText = { + type: "text"; +}; +type ChatCompletionsResponseFormatJSONObject = { + type: "json_object"; +}; +type ResponseFormatJSONSchema = { + type: "json_schema"; + json_schema: { + name: string; + description?: string; + schema?: Record; + strict?: boolean | null; + }; +}; +type ResponseFormat = ChatCompletionsResponseFormatText | ChatCompletionsResponseFormatJSONObject | ResponseFormatJSONSchema; +type ChatCompletionsStreamOptions = { + include_usage?: boolean; + include_obfuscation?: boolean; +}; +type PredictionContent = { + type: "content"; + content: + | string + | Array<{ + type: "text"; + text: string; + }>; +}; +type AudioParams = { + voice: + | string + | { + id: string; + }; + format: "wav" | "aac" | "mp3" | "flac" | "opus" | "pcm16"; +}; +type WebSearchUserLocation = { + type: "approximate"; + approximate: { + city?: string; + country?: string; + region?: string; + timezone?: string; + }; +}; +type WebSearchOptions = { + search_context_size?: "low" | "medium" | "high"; + user_location?: WebSearchUserLocation; +}; +type ChatTemplateKwargs = { + /** Whether to enable reasoning, enabled by default. */ + enable_thinking?: boolean; + /** If false, preserves reasoning context between turns. */ + clear_thinking?: boolean; +}; +/** Shared optional properties used by both Prompt and Messages input branches. */ +type ChatCompletionsCommonOptions = { + model?: string; + audio?: AudioParams; + frequency_penalty?: number | null; + logit_bias?: Record | null; + logprobs?: boolean | null; + top_logprobs?: number | null; + max_tokens?: number | null; + max_completion_tokens?: number | null; + metadata?: Record | null; + modalities?: Array<"text" | "audio"> | null; + n?: number | null; + parallel_tool_calls?: boolean; + prediction?: PredictionContent; + presence_penalty?: number | null; + reasoning_effort?: "low" | "medium" | "high" | null; + chat_template_kwargs?: ChatTemplateKwargs; + response_format?: ResponseFormat; + seed?: number | null; + service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; + stop?: string | Array | null; + store?: boolean | null; + stream?: boolean | null; + stream_options?: ChatCompletionsStreamOptions; + temperature?: number | null; + tool_choice?: ChatCompletionToolChoiceOption; + tools?: Array; + top_p?: number | null; + user?: string; + web_search_options?: WebSearchOptions; + function_call?: + | "none" + | "auto" + | { + name: string; + }; + functions?: Array; +}; +type PromptTokensDetails = { + cached_tokens?: number; + audio_tokens?: number; +}; +type CompletionTokensDetails = { + reasoning_tokens?: number; + audio_tokens?: number; + accepted_prediction_tokens?: number; + rejected_prediction_tokens?: number; +}; +type CompletionUsage = { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + prompt_tokens_details?: PromptTokensDetails; + completion_tokens_details?: CompletionTokensDetails; +}; +type ChatCompletionTopLogprob = { + token: string; + logprob: number; + bytes: Array | null; +}; +type ChatCompletionTokenLogprob = { + token: string; + logprob: number; + bytes: Array | null; + top_logprobs: Array; +}; +type ChatCompletionAudio = { + id: string; + /** Base64 encoded audio bytes. */ + data: string; + expires_at: number; + transcript: string; +}; +type ChatCompletionUrlCitation = { + type: "url_citation"; + url_citation: { + url: string; + title: string; + start_index: number; + end_index: number; + }; +}; +type ChatCompletionResponseMessage = { + role: "assistant"; + content: string | null; + refusal: string | null; + annotations?: Array; + audio?: ChatCompletionAudio; + tool_calls?: Array; + function_call?: { + name: string; + arguments: string; + } | null; +}; +type ChatCompletionLogprobs = { + content: Array | null; + refusal?: Array | null; +}; +type ChatCompletionChoice = { + index: number; + message: ChatCompletionResponseMessage; + finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | "function_call"; + logprobs: ChatCompletionLogprobs | null; +}; +type ChatCompletionsMessagesInput = { + messages: Array; +} & ChatCompletionsCommonOptions; +type ChatCompletionsOutput = { + id: string; + object: string; + created: number; + model: string; + choices: Array; + usage?: CompletionUsage; + system_fingerprint?: string | null; + service_tier?: "auto" | "default" | "flex" | "scale" | "priority" | null; +}; /** * Workers AI support for OpenAI's Responses API * Reference: https://github.com/openai/openai-node/blob/master/src/resources/responses/responses.ts @@ -3968,6 +5557,12 @@ type ReasoningEffort = "minimal" | "low" | "medium" | "high" | null; type StreamOptions = { include_obfuscation?: boolean; }; +/** Marks keys from T that aren't in U as optional never */ +type Without = { + [P in Exclude]?: never; +}; +/** Either T or U, but not both (mutually exclusive) */ +type XOR = (T & Without) | (U & Without); type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = | { text: string | string[]; @@ -4260,10 +5855,12 @@ declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; } interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { - /** - * Base64 encoded value of the audio data. - */ - audio: string; + audio: + | string + | { + body?: object; + contentType?: string; + }; /** * Supported tasks are 'translate' or 'transcribe'. */ @@ -4281,9 +5878,33 @@ interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { */ initial_prompt?: string; /** - * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result. + * The prefix appended to the beginning of the output of the transcription and can guide the transcription result. */ prefix?: string; + /** + * The number of beams to use in beam search decoding. Higher values may improve accuracy at the cost of speed. + */ + beam_size?: number; + /** + * Whether to condition on previous text during transcription. Setting to false may help prevent hallucination loops. + */ + condition_on_previous_text?: boolean; + /** + * Threshold for detecting no-speech segments. Segments with no-speech probability above this value are skipped. + */ + no_speech_threshold?: number; + /** + * Threshold for filtering out segments with high compression ratio, which often indicate repetitive or hallucinated text. + */ + compression_ratio_threshold?: number; + /** + * Threshold for filtering out segments with low average log probability, indicating low confidence. + */ + log_prob_threshold?: number; + /** + * Optional threshold (in seconds) to skip silent periods that may cause hallucinations. + */ + hallucination_silence_threshold?: number; } interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { transcription_info?: { @@ -4426,8 +6047,8 @@ interface Ai_Cf_Baai_Bge_M3_Input_Embedding_1 { */ truncate_inputs?: boolean; } -type Ai_Cf_Baai_Bge_M3_Output = Ai_Cf_Baai_Bge_M3_Ouput_Query | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts | Ai_Cf_Baai_Bge_M3_Ouput_Embedding | Ai_Cf_Baai_Bge_M3_AsyncResponse; -interface Ai_Cf_Baai_Bge_M3_Ouput_Query { +type Ai_Cf_Baai_Bge_M3_Output = Ai_Cf_Baai_Bge_M3_Output_Query | Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts | Ai_Cf_Baai_Bge_M3_Output_Embedding | Ai_Cf_Baai_Bge_M3_AsyncResponse; +interface Ai_Cf_Baai_Bge_M3_Output_Query { response?: { /** * Index of the context in the request @@ -4447,7 +6068,7 @@ interface Ai_Cf_Baai_Bge_M3_Output_EmbeddingFor_Contexts { */ pooling?: "mean" | "cls"; } -interface Ai_Cf_Baai_Bge_M3_Ouput_Embedding { +interface Ai_Cf_Baai_Bge_M3_Output_Embedding { shape?: number[]; /** * Embeddings of the requested text values @@ -4550,7 +6171,7 @@ interface Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Messages { */ role?: string; /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: @@ -4802,10 +6423,18 @@ interface Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - /** - * The content of the message as a string. - */ - content: string; + content: + | string + | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -5457,7 +7086,7 @@ interface Ai_Cf_Qwen_Qwq_32B_Messages { */ role?: string; /** - * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 + * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: @@ -6787,10 +8416,18 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - /** - * The content of the message as a string. - */ - content: string; + content: + | string + | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -6999,10 +8636,18 @@ interface Ai_Cf_Qwen_Qwen3_30B_A3B_Fp8_Messages_1 { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - /** - * The content of the message as a string. - */ - content: string; + content: + | string + | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -7558,12 +9203,12 @@ declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 { postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output; } declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B { - inputs: ResponsesInput; - postProcessedOutputs: ResponsesOutput; + inputs: XOR; + postProcessedOutputs: XOR; } declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B { - inputs: ResponsesInput; - postProcessedOutputs: ResponsesOutput; + inputs: XOR; + postProcessedOutputs: XOR; } interface Ai_Cf_Leonardo_Phoenix_1_0_Input { /** @@ -7796,10 +9441,18 @@ interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - /** - * The content of the message as a string. - */ - content: string; + content: + | string + | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -8008,10 +9661,18 @@ interface Ai_Cf_Aisingapore_Gemma_Sea_Lion_V4_27B_It_Messages_1 { * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; - /** - * The content of the message as a string. - */ - content: string; + content: + | string + | { + /** + * Type of the content (text) + */ + type?: string; + /** + * Text content + */ + text?: string; + }[]; }[]; functions?: { name: string; @@ -8547,6 +10208,74 @@ declare abstract class Base_Ai_Cf_Deepgram_Aura_2_Es { inputs: Ai_Cf_Deepgram_Aura_2_Es_Input; postProcessedOutputs: Ai_Cf_Deepgram_Aura_2_Es_Output; } +interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input { + multipart: { + body?: object; + contentType?: string; + }; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output { + /** + * Generated image as Base64 string. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev { + inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Dev_Output; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input { + multipart: { + body?: object; + contentType?: string; + }; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output { + /** + * Generated image as Base64 string. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B { + inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B_Output; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input { + multipart: { + body?: object; + contentType?: string; + }; +} +interface Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output { + /** + * Generated image as Base64 string. + */ + image?: string; +} +declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B { + inputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Input; + postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B_Output; +} +declare abstract class Base_Ai_Cf_Zai_Org_Glm_4_7_Flash { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_5 { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Moonshotai_Kimi_K2_6 { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} +declare abstract class Base_Ai_Cf_Google_Gemma_4_26B_A4B_IT { + inputs: ChatCompletionsInput; + postProcessedOutputs: ChatCompletionsOutput; +} interface AiModels { "@cf/huggingface/distilbert-sst-2-int8": BaseAiTextClassification; "@cf/stabilityai/stable-diffusion-xl-base-1.0": BaseAiTextToImage; @@ -8565,7 +10294,6 @@ interface AiModels { "@hf/thebloke/zephyr-7b-beta-awq": BaseAiTextGeneration; "@hf/thebloke/openhermes-2.5-mistral-7b-awq": BaseAiTextGeneration; "@hf/thebloke/neural-chat-7b-v3-1-awq": BaseAiTextGeneration; - "@hf/thebloke/llamaguard-7b-awq": BaseAiTextGeneration; "@hf/thebloke/deepseek-coder-6.7b-base-awq": BaseAiTextGeneration; "@hf/thebloke/deepseek-coder-6.7b-instruct-awq": BaseAiTextGeneration; "@cf/deepseek-ai/deepseek-math-7b-instruct": BaseAiTextGeneration; @@ -8632,6 +10360,14 @@ interface AiModels { "@cf/deepgram/flux": Base_Ai_Cf_Deepgram_Flux; "@cf/deepgram/aura-2-en": Base_Ai_Cf_Deepgram_Aura_2_En; "@cf/deepgram/aura-2-es": Base_Ai_Cf_Deepgram_Aura_2_Es; + "@cf/black-forest-labs/flux-2-dev": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Dev; + "@cf/black-forest-labs/flux-2-klein-4b": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_4B; + "@cf/black-forest-labs/flux-2-klein-9b": Base_Ai_Cf_Black_Forest_Labs_Flux_2_Klein_9B; + "@cf/zai-org/glm-4.7-flash": Base_Ai_Cf_Zai_Org_Glm_4_7_Flash; + "@cf/moonshotai/kimi-k2.5": Base_Ai_Cf_Moonshotai_Kimi_K2_5; + "@cf/moonshotai/kimi-k2.6": Base_Ai_Cf_Moonshotai_Kimi_K2_6; + "@cf/nvidia/nemotron-3-120b-a12b": Base_Ai_Cf_Nvidia_Nemotron_3_120B_A12B; + "@cf/google/gemma-4-26b-a4b-it": Base_Ai_Cf_Google_Gemma_4_26B_A4B_IT; } type AiOptions = { /** @@ -8657,6 +10393,7 @@ type AiOptions = { returnRawResponse?: boolean; prefix?: string; extraHeaders?: object; + signal?: AbortSignal; }; type AiModelsSearchParams = { author?: string; @@ -8683,32 +10420,75 @@ type AiModelsSearchObject = { value: string; }[]; }; +type ChatCompletionsBase = ChatCompletionsMessagesInput; +type ChatCompletionsInput = ChatCompletionsMessagesInput; interface InferenceUpstreamError extends Error {} interface AiInternalError extends Error {} type AiModelListType = Record; +type AiAsyncBatchResponse = { + request_id: string; +}; declare abstract class Ai { aiGatewayLogId: string | null; gateway(gatewayId: string): AiGateway; + /** + * @deprecated Use the standalone `ai_search_namespaces` or `ai_search` Workers bindings instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ + aiSearch(): AiSearchNamespace; + /** + * @deprecated AutoRAG has been replaced by AI Search. + * Use the standalone `ai_search_namespaces` or `ai_search` Workers bindings instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + * + * @param autoragId Instance ID + */ autorag(autoragId: string): AutoRAG; - run( + // Batch request + run( model: Name, - inputs: InputOptions, - options?: Options, - ): Promise< - Options extends - | { - returnRawResponse: true; - } - | { - websocket: true; - } - ? Response - : InputOptions extends { - stream: true; - } - ? ReadableStream - : AiModelList[Name]["postProcessedOutputs"] - >; + inputs: { + requests: AiModelList[Name]["inputs"][]; + }, + options: AiOptions & { + queueRequest: true; + }, + ): Promise; + // Raw response + run( + model: Name, + inputs: AiModelList[Name]["inputs"], + options: AiOptions & { + returnRawResponse: true; + }, + ): Promise; + // WebSocket + run( + model: Name, + inputs: AiModelList[Name]["inputs"], + options: AiOptions & { + websocket: true; + }, + ): Promise; + // Streaming + run( + model: Name, + inputs: AiModelList[Name]["inputs"] & { + stream: true; + }, + options?: AiOptions, + ): Promise; + // Normal (default) - known model + run(model: Name, inputs: AiModelList[Name]["inputs"], options?: AiOptions): Promise; + // Unknown model (fallback). + // + // The `Exclude<..., keyof AiModelList>` constraint forces TypeScript to + // route any model name that is a literal key of `AiModelList` to one of + // the known-model overloads above (so input/output mismatches surface as + // type errors rather than silently falling back to `Record`). + // Names that aren't in `AiModelList` — e.g. third-party gateway models + // like `"google/nano-banana"` — still hit this overload. + run(model: Model extends keyof AiModelList ? never : Model, inputs: Record, options?: AiOptions): Promise>; models(params?: AiModelsSearchParams): Promise; toMarkdown(): ToMarkdownService; toMarkdown(files: MarkdownDocument[], options?: ConversionRequestOptions): Promise; @@ -8809,13 +10589,252 @@ declare abstract class AiGateway { options?: { gateway?: UniversalGatewayOptions; extraHeaders?: object; + signal?: AbortSignal; }, ): Promise; getUrl(provider?: AIGatewayProviders | string): Promise; // eslint-disable-line } +// Copyright (c) 2022-2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +/** + * Artifacts — Git-compatible file storage on Cloudflare Workers. + * + * Provides programmatic access to create, manage, and fork repositories, + * and to issue and revoke scoped access tokens. + */ +/** Information about a repository. */ +interface ArtifactsRepoInfo { + /** Unique repository ID. */ + id: string; + /** Repository name. */ + name: string; + /** Repository description, or null if not set. */ + description: string | null; + /** Default branch name (e.g. "main"). */ + defaultBranch: string; + /** ISO 8601 creation timestamp. */ + createdAt: string; + /** ISO 8601 last-updated timestamp. */ + updatedAt: string; + /** ISO 8601 timestamp of the last push, or null if never pushed. */ + lastPushAt: string | null; + /** Fork source (e.g. "github:owner/repo", "artifacts:namespace/repo"), or null if not a fork. */ + source: string | null; + /** Whether the repository is read-only. */ + readOnly: boolean; + /** HTTPS git remote URL. */ + remote: string; +} +/** Result of creating a repository — includes the initial access token. */ +interface ArtifactsCreateRepoResult { + /** Unique repository ID. */ + id: string; + /** Repository name. */ + name: string; + /** Repository description, or null if not set. */ + description: string | null; + /** Default branch name. */ + defaultBranch: string; + /** HTTPS git remote URL. */ + remote: string; + /** Plaintext access token (only returned at creation time). */ + token: string; + /** ISO 8601 token expiry timestamp. */ + tokenExpiresAt: string; +} +/** Paginated list of repositories. */ +interface ArtifactsRepoListResult { + /** Repositories in this page (without the `remote` field). */ + repos: Omit[]; + /** Total number of repositories in the namespace. */ + total: number; + /** Cursor for the next page, if there are more results. */ + cursor?: string; +} +/** Result of creating an access token. */ +interface ArtifactsCreateTokenResult { + /** Unique token ID. */ + id: string; + /** Plaintext token (only returned at creation time). */ + plaintext: string; + /** Token scope: "read" or "write". */ + scope: "read" | "write"; + /** ISO 8601 token expiry timestamp. */ + expiresAt: string; +} +/** Token metadata (no plaintext). */ +interface ArtifactsTokenInfo { + /** Unique token ID. */ + id: string; + /** Token scope: "read" or "write". */ + scope: "read" | "write"; + /** Token state: "active", "expired", or "revoked". */ + state: "active" | "expired" | "revoked"; + /** ISO 8601 creation timestamp. */ + createdAt: string; + /** ISO 8601 expiry timestamp. */ + expiresAt: string; +} +/** Paginated list of tokens for a repository. */ +interface ArtifactsTokenListResult { + /** Tokens in this page. */ + tokens: ArtifactsTokenInfo[]; + /** Total number of tokens for the repository. */ + total: number; +} +/** + * Handle for a single repository. Returned by Artifacts.get(). + * + * Methods may throw `ArtifactsError` with code `INTERNAL_ERROR` if an unexpected service error occurs. + */ +interface ArtifactsRepo extends ArtifactsRepoInfo { + /** + * Create an access token for this repo. + * @param scope Token scope: "write" (default) or "read". + * @param ttl Time-to-live in seconds (default 86400, min 60, max 31536000). + * @throws {ArtifactsError} with code `INVALID_TTL` if ttl is out of range. + */ + createToken(scope?: "write" | "read", ttl?: number): Promise; + /** List tokens for this repo (metadata only, no plaintext). */ + listTokens(): Promise; + /** + * Revoke a token by plaintext or ID. + * @param tokenOrId Plaintext token or token ID. + * @returns true if revoked, false if not found. + * @throws {ArtifactsError} with code `INVALID_INPUT` if tokenOrId is empty. + */ + revokeToken(tokenOrId: string): Promise; + // ── Fork ── + /** + * Fork this repo to a new repo. + * @param name Target repository name. + * @param opts Optional: description, readOnly flag, defaultBranchOnly (default true). + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. + * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the target repo already exists. + * @throws {ArtifactsError} with code `FORK_IN_PROGRESS` if a fork is already running. + */ + fork( + name: string, + opts?: { + description?: string; + readOnly?: boolean; + defaultBranchOnly?: boolean; + }, + ): Promise; +} +// ── Error types ────────────────────────────────────────────────────────────── +/** + * Error codes returned by Artifacts binding operations. + * + * Each code maps to a numeric code available on `ArtifactsError.numericCode`. + */ +type ArtifactsErrorCode = "ALREADY_EXISTS" | "NOT_FOUND" | "IMPORT_IN_PROGRESS" | "FORK_IN_PROGRESS" | "INVALID_INPUT" | "INVALID_REPO_NAME" | "INVALID_TTL" | "INVALID_URL" | "REMOTE_AUTH_REQUIRED" | "UPSTREAM_UNAVAILABLE" | "MEMORY_LIMIT" | "INTERNAL_ERROR"; +/** + * Error thrown by Artifacts binding operations. + * + * Uses a string `.code` discriminator following the Cloudflare platform + * convention (StreamError, ImagesError, etc.). The `.numericCode` matches + * the REST API `errors[].code` values. + */ +interface ArtifactsError extends Error { + readonly name: "ArtifactsError"; + /** String error code for programmatic matching. */ + readonly code: ArtifactsErrorCode; + /** Numeric error code matching the REST API. */ + readonly numericCode: number; +} +// ── Binding ────────────────────────────────────────────────────────────────── +/** + * Artifacts binding — namespace-level operations. + * + * Methods may throw `ArtifactsError` with code `INTERNAL_ERROR` if an unexpected service error occurs. + */ +interface Artifacts { + /** + * Create a new repository with an initial access token. + * @param name Repository name (alphanumeric, dots, hyphens, underscores). + * @param opts Optional: readOnly flag, description, default branch name. + * @returns Repo metadata with initial token. + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. + * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the repo already exists. + */ + create( + name: string, + opts?: { + readOnly?: boolean; + description?: string; + setDefaultBranch?: string; + }, + ): Promise; + /** + * Get a handle to an existing repository. + * @param name Repository name. + * @returns Repo handle. + * @throws {ArtifactsError} with code `NOT_FOUND` if the repo does not exist. + * @throws {ArtifactsError} with code `IMPORT_IN_PROGRESS` if the repo is still importing. + * @throws {ArtifactsError} with code `FORK_IN_PROGRESS` if the repo is still forking. + */ + get(name: string): Promise; + /** + * Import a repository from an external git remote. + * @param params Source URL and optional branch/depth, plus target name and options. + * @returns Repo metadata with initial token. + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if the target name is invalid. + * @throws {ArtifactsError} with code `INVALID_INPUT` if the source URL is not valid HTTPS. + * @throws {ArtifactsError} with code `INVALID_URL` if the source URL does not point to a git repository. + * @throws {ArtifactsError} with code `REMOTE_AUTH_REQUIRED` if the remote requires authentication. + * @throws {ArtifactsError} with code `NOT_FOUND` if the remote repository does not exist. + * @throws {ArtifactsError} with code `UPSTREAM_UNAVAILABLE` if the remote cannot be reached. + * @throws {ArtifactsError} with code `MEMORY_LIMIT` if the import exceeds service memory limits. + * @throws {ArtifactsError} with code `ALREADY_EXISTS` if the target repo already exists. + */ + import(params: { + source: { + url: string; + branch?: string; + depth?: number; + }; + target: { + name: string; + opts?: { + description?: string; + readOnly?: boolean; + }; + }; + }): Promise; + /** + * List repositories with cursor-based pagination. + * @param opts Optional: limit (1–200, default 50), cursor for next page. + */ + list(opts?: { limit?: number; cursor?: string }): Promise; + /** + * Delete a repository and all associated tokens. + * @param name Repository name. + * @returns true if deleted, false if not found. + * @throws {ArtifactsError} with code `INVALID_REPO_NAME` if name is invalid. + */ + delete(name: string): Promise; +} +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ interface AutoRAGInternalError extends Error {} +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ interface AutoRAGNotFoundError extends Error {} +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ interface AutoRAGUnauthorizedError extends Error {} +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ interface AutoRAGNameNotSetError extends Error {} type ComparisonFilter = { key: string; @@ -8826,6 +10845,10 @@ type CompoundFilter = { type: "and" | "or"; filters: ComparisonFilter[]; }; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ type AutoRagSearchRequest = { query: string; filters?: CompoundFilter | ComparisonFilter; @@ -8840,13 +10863,25 @@ type AutoRagSearchRequest = { }; rewrite_query?: boolean; }; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ type AutoRagAiSearchRequest = AutoRagSearchRequest & { stream?: boolean; system_prompt?: string; }; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ type AutoRagAiSearchRequestStreaming = Omit & { stream: true; }; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ type AutoRagSearchResponse = { object: "vector_store.search_results.page"; search_query: string; @@ -8863,6 +10898,10 @@ type AutoRagSearchResponse = { has_more: boolean; next_page: string | null; }; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ type AutoRagListResponse = { id: string; enable: boolean; @@ -8872,87 +10911,496 @@ type AutoRagListResponse = { paused: boolean; status: string; }[]; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ type AutoRagAiSearchResponse = AutoRagSearchResponse & { response: string; }; +/** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ declare abstract class AutoRAG { + /** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ list(): Promise; + /** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ search(params: AutoRagSearchRequest): Promise; + /** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; + /** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ aiSearch(params: AutoRagAiSearchRequest): Promise; + /** + * @deprecated Use the standalone AI Search Workers binding instead. + * See https://developers.cloudflare.com/ai-search/usage/workers-binding/ + */ aiSearch(params: AutoRagAiSearchRequest): Promise; } -interface BasicImageTransformations { - /** - * Maximum width in image pixels. The value must be an integer. +type BrowserRunLifecycleEvent = "load" | "domcontentloaded" | "networkidle0" | "networkidle2"; +type BrowserRunResourceType = "document" | "stylesheet" | "image" | "media" | "font" | "script" | "texttrack" | "xhr" | "fetch" | "prefetch" | "eventsource" | "websocket" | "manifest" | "signedexchange" | "ping" | "cspviolationreport" | "preflight" | "other"; +/** Options fields shared by all quick actions. */ +interface BrowserRunBaseOptions { + /** Adds `