mirror of
https://github.com/FxEmbed/FxEmbed.git
synced 2026-10-02 01:54:54 +08:00
Merge branch 'main' into fix/1494-traditional-chinese-lang
This commit is contained in:
Generated
+263
-107
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@astrojs/starlight": "^0.41.7",
|
||||
"astro": "^7.2.2",
|
||||
"astro": "^7.2.4",
|
||||
"starlight-openapi": "^0.26.1"
|
||||
}
|
||||
},
|
||||
@@ -173,61 +173,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/compiler-binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "2.0.0-alpha.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz",
|
||||
"integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "2.0.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/compiler-binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "2.0.0-alpha.3",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz",
|
||||
"integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/compiler-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz",
|
||||
"integrity": "sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/compiler-binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.0.tgz",
|
||||
"integrity": "sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^2.0.0-alpha.3",
|
||||
"@emnapi/runtime": "^2.0.0-alpha.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/compiler-binding-win32-arm64-msvc": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.3.2.tgz",
|
||||
@@ -288,42 +233,200 @@
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/markdown-remark": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.2.tgz",
|
||||
"integrity": "sha512-FGfmK84zSNcrsBd0dl1gXE9JvZYElp8EXQa2jpHVAxG4deGKAp43wspxFupjADJX7MSsMRHwYCnfT6EyVmgeFQ==",
|
||||
"node_modules/@astrojs/markdown-satteri": {
|
||||
"version": "0.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.3.7.tgz",
|
||||
"integrity": "sha512-NHcHbrKW/opbZnTZQ5nH293BdcK2VV0tjuzI88CLnvy2njiEJVwUQ5KFnYd4NoWgHJCY/I1CyjGWCR4Vv3khXQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.10.2",
|
||||
"@astrojs/internal-helpers": "0.10.4",
|
||||
"@astrojs/prism": "4.0.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-from-html": "^2.0.3",
|
||||
"hast-util-to-text": "^4.0.2",
|
||||
"mdast-util-definitions": "^6.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"remark-smartypants": "^3.0.2",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-remove-position": "^5.0.0",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"unist-util-visit-parents": "^6.0.2",
|
||||
"vfile": "^6.0.3"
|
||||
"satteri": "^0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.3.5.tgz",
|
||||
"integrity": "sha512-CvWVEFAbay7YO+i9SaqDJubipA5ckiVB89QWoMJ5XC0m5CtFg8JwZ7Kau6X9sYY7FZURH0w2l03ISH2jOS/RDQ==",
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@astrojs/internal-helpers": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.4.tgz",
|
||||
"integrity": "sha512-nozZSy/mKYLqe4YrqbKtdOszedAfXYCtw3wZ0d+CAjz4GqQ4L9rl1ltIL5BlgwmYVinJg/RZ0MgGuWOdlyRZlA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.10.2",
|
||||
"@astrojs/prism": "4.0.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-from-html": "^2.0.3",
|
||||
"satteri": "^0.9.1"
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"js-yaml": "^4.3.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"retext-smartypants": "^6.2.0",
|
||||
"shiki": "^4.0.2",
|
||||
"smol-toml": "^1.6.0",
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-darwin-arm64": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.10.5.tgz",
|
||||
"integrity": "sha512-27KTVl4TJkVahMy/ohyA7qd4938G5UNneFUz/PsScYfpIhj0IVAS23mpcJXdPF44sa6nva198lmV/cKIb2YPyA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-darwin-x64": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.10.5.tgz",
|
||||
"integrity": "sha512-IjnLe3nKspq6qaeqGgjT7MT8VrTV74yWRlaag7ZdNsI8TDAYZ0iPxMCo+9KQZHUk5EyVB+reBI/PFWL5KuFw9Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-linux-arm64-gnu": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.10.5.tgz",
|
||||
"integrity": "sha512-glkYXZCJywjP13v67eAyAMSJdF+ncvEbYvgi/wOtffL9tQ27lr/zsyzUfgs+ovjJ9d8JNQKiXeiArJcX8PJL9w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-linux-arm64-musl": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.10.5.tgz",
|
||||
"integrity": "sha512-yWdgG1g17Nh2QyGVlFUxGRa3FEFwiMcpZEyMNWkbM3deC94cmVc+/i9OuyFpdKuWo3GkgoCtYVOoxk1uCnCZIA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-linux-x64-gnu": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.10.5.tgz",
|
||||
"integrity": "sha512-FVaLoPT1fBgGl0J+AYebyyXJYBachGl8Oyyrf1lye4RTqCB4S0Gwkj1uM9RJyThUOvx5VUmAT1CnNh1SFHA+kw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-linux-x64-musl": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.10.5.tgz",
|
||||
"integrity": "sha512-EHpVAx2bqW3GINHTKkljtxVfQmVDGWIuwOYOP5YghTj+0PkBa2o8oKPRtQ9Kbsr1Fye8jtUcDjhwj2jMNugZKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-wasm32-wasi": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.10.5.tgz",
|
||||
"integrity": "sha512-ypz8c/Zmipxp4IoeDa228Gstv6TLzVmNs3yC6wKCoNSOjx1iwpgzu87Y3hTkXFdwChVGU85qeUDuOIarGUZQLw==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "1.11.1",
|
||||
"@emnapi/runtime": "1.11.1",
|
||||
"@napi-rs/wasm-runtime": "^1.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-win32-arm64-msvc": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.10.5.tgz",
|
||||
"integrity": "sha512-siTV88nb0LRqNpkL2gXboqCwVdq95sLtzMHS1/3eONV2gLbB3NAK46wmSMvCO/yquBvI2lvaFIfd8P12ecsxBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@bruits/satteri-win32-x64-msvc": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.10.5.tgz",
|
||||
"integrity": "sha512-C3IfPvfvMXmlzBxaMPKFS1XiuV9pu2mC7YqkPk7PSvTgPZ8gbdASIpHpztDLvTTQjqZ0z1Ol8tK5X+V6XXC0wQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/@emnapi/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.2",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/markdown-satteri/node_modules/satteri": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/satteri/-/satteri-0.10.5.tgz",
|
||||
"integrity": "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree-jsx": "^1.0.5",
|
||||
"@types/hast": "^3.0.5",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"@types/unist": "^3.0.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@bruits/satteri-darwin-arm64": "0.10.5",
|
||||
"@bruits/satteri-darwin-x64": "0.10.5",
|
||||
"@bruits/satteri-linux-arm64-gnu": "0.10.5",
|
||||
"@bruits/satteri-linux-arm64-musl": "0.10.5",
|
||||
"@bruits/satteri-linux-x64-gnu": "0.10.5",
|
||||
"@bruits/satteri-linux-x64-musl": "0.10.5",
|
||||
"@bruits/satteri-wasm32-wasi": "0.10.5",
|
||||
"@bruits/satteri-win32-arm64-msvc": "0.10.5",
|
||||
"@bruits/satteri-win32-x64-msvc": "0.10.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/mdx": {
|
||||
@@ -360,6 +463,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/mdx/node_modules/@astrojs/markdown-remark": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.2.tgz",
|
||||
"integrity": "sha512-FGfmK84zSNcrsBd0dl1gXE9JvZYElp8EXQa2jpHVAxG4deGKAp43wspxFupjADJX7MSsMRHwYCnfT6EyVmgeFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/internal-helpers": "0.10.2",
|
||||
"@astrojs/prism": "4.0.2",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-from-html": "^2.0.3",
|
||||
"hast-util-to-text": "^4.0.2",
|
||||
"mdast-util-definitions": "^6.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-parse": "^11.0.0",
|
||||
"remark-rehype": "^11.1.2",
|
||||
"remark-smartypants": "^3.0.2",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-remove-position": "^5.0.0",
|
||||
"unist-util-visit": "^5.1.0",
|
||||
"unist-util-visit-parents": "^6.0.2",
|
||||
"vfile": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@astrojs/prism": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz",
|
||||
@@ -1728,21 +1856,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
|
||||
"integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz",
|
||||
"integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@tybys/wasm-util": "^0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
"@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4",
|
||||
"@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@oslojs/encoding": {
|
||||
@@ -2318,9 +2449,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
|
||||
"integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
@@ -2551,14 +2682,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/astro": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/astro/-/astro-7.2.2.tgz",
|
||||
"integrity": "sha512-OvLWCpXpb43lVYcWzSBJ0sk44qcEYYRZCjadtalLfAwb2RaC3P/zT+blZykBw/o6suw6sQQkn00ugN36akD8Mw==",
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/astro/-/astro-7.2.4.tgz",
|
||||
"integrity": "sha512-+cuLsBns2wwUHI9a10xZMbjrF91m7+QNwqTVeljTx0B8Lf+8h0LgVGjdVIL2FALDKD2I565lczeeS3BFC+KdZg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@astrojs/compiler-rs": "^0.3.2",
|
||||
"@astrojs/internal-helpers": "0.10.2",
|
||||
"@astrojs/markdown-satteri": "0.3.5",
|
||||
"@astrojs/internal-helpers": "0.10.4",
|
||||
"@astrojs/markdown-satteri": "0.3.7",
|
||||
"@astrojs/telemetry": "3.3.3",
|
||||
"@capsizecss/unpack": "^4.0.0",
|
||||
"@clack/prompts": "^1.1.0",
|
||||
@@ -2602,7 +2733,7 @@
|
||||
"tinyexec": "^1.0.4",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"ultrahtml": "^1.6.0",
|
||||
"unifont": "~0.7.4",
|
||||
"unifont": "~0.7.5",
|
||||
"unstorage": "^1.17.5",
|
||||
"vite": "^8.0.13",
|
||||
"vitefu": "^1.1.2",
|
||||
@@ -2626,7 +2757,7 @@
|
||||
"sharp": "^0.34.0 || ^0.35.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@astrojs/markdown-remark": "7.2.2"
|
||||
"@astrojs/markdown-remark": "7.2.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@astrojs/markdown-remark": {
|
||||
@@ -2647,10 +2778,26 @@
|
||||
"astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/astro/node_modules/@astrojs/internal-helpers": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.4.tgz",
|
||||
"integrity": "sha512-nozZSy/mKYLqe4YrqbKtdOszedAfXYCtw3wZ0d+CAjz4GqQ4L9rl1ltIL5BlgwmYVinJg/RZ0MgGuWOdlyRZlA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/mdast": "^4.0.4",
|
||||
"js-yaml": "^4.3.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"retext-smartypants": "^6.2.0",
|
||||
"shiki": "^4.0.2",
|
||||
"smol-toml": "^1.6.0",
|
||||
"unified": "^11.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/astro/node_modules/magic-string": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.0.tgz",
|
||||
"integrity": "sha512-ptco+HFxTLgjafSLim2LojBSwfg5feBjd+SqyiwdGkzC38UPdZy3zgrHMI2CoTf5fJL38tbHMYWVzIH8BxGqJw==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.2.tgz",
|
||||
"integrity": "sha512-veT/+7iXrXzT39XnEN4lOxtNl72dMgJ8Lp+5Bd6YcMSWpb0n0MjBM8Uuooi6jgJr8dhUW2swQgBmoZVMni5SVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
@@ -7157,6 +7304,15 @@
|
||||
"integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz",
|
||||
"integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
@@ -7183,14 +7339,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/unifont": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz",
|
||||
"integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==",
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.5.tgz",
|
||||
"integrity": "sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "^3.1.0",
|
||||
"ofetch": "^1.5.1",
|
||||
"ohash": "^2.0.11"
|
||||
"ohash": "^2.0.11",
|
||||
"undici": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-find-after": {
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/starlight": "^0.41.7",
|
||||
"astro": "^7.2.2",
|
||||
"astro": "^7.2.4",
|
||||
"starlight-openapi": "^0.26.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+166
-166
@@ -14,16 +14,16 @@
|
||||
"dependencies": {
|
||||
"@fxembed/atmosphere": "*",
|
||||
"@hono/sentry": "^1.2.2",
|
||||
"@hono/zod-openapi": "^1.6.0",
|
||||
"@hono/zod-openapi": "^1.6.1",
|
||||
"cheerio": "^1.2.0",
|
||||
"hono": "^4.13.2",
|
||||
"hono": "^4.13.3",
|
||||
"i18next": "^26.3.6",
|
||||
"i18next-icu": "^2.4.4",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1110.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1110.0",
|
||||
"@aws-sdk/client-s3": "^3.1111.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1111.0",
|
||||
"@cloudflare/vitest-pool-workers": "^0.21.3",
|
||||
"@cloudflare/workers-types": "^4.20260702.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -43,7 +43,7 @@
|
||||
"tsx": "^4.23.12",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.67.0",
|
||||
"vitest": "^4.1.10",
|
||||
"vitest": "^4.1.11",
|
||||
"wrangler": "^4.107.1"
|
||||
}
|
||||
},
|
||||
@@ -60,14 +60,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/checksums": {
|
||||
"version": "3.1000.27",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.27.tgz",
|
||||
"integrity": "sha512-insWOqKKNUrbN/dohEG7BJ0U5GkyqhjbMb/NHNaLUtq+7my2M8C4EnZZZoxMmXRqCC+P9dEr+KyJA2JGGzoKLg==",
|
||||
"version": "3.1000.28",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.28.tgz",
|
||||
"integrity": "sha512-VCpnmyHQ1IH49ni3LXnQj7DPr7rmcJmzYeiCkYdCcfgNtkvOj38cdcL9lapBWoItZWFACJPFJlymqC7/gem3Gw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -77,18 +77,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-s3": {
|
||||
"version": "3.1110.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1110.0.tgz",
|
||||
"integrity": "sha512-40xbEcWjdaYKlZ4/NvndIJ3LotAEQAvHVQ7Z4NVy4Z4xGRN7xXJlHI9bMh/4aMJQ++6h5W5sv+wqjfk0rEKOBg==",
|
||||
"version": "3.1111.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1111.0.tgz",
|
||||
"integrity": "sha512-VnLT6aSTN8tWl/NsXUysXNZor7wQBp9CRwufo7kt8cwGXvHLZ0S/cV1K9WFcREGboVYSo3NGQ3ZvU7LRidh2aQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/checksums": "^3.1000.27",
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.79",
|
||||
"@aws-sdk/middleware-sdk-s3": "^3.972.73",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.44",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/checksums": "^3.1000.28",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/credential-provider-node": "^3.972.80",
|
||||
"@aws-sdk/middleware-sdk-s3": "^3.972.74",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/fetch-http-handler": "^5.6.13",
|
||||
"@smithy/node-http-handler": "^4.9.13",
|
||||
@@ -100,14 +100,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/core": {
|
||||
"version": "3.977.7",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.7.tgz",
|
||||
"integrity": "sha512-I88Iov89NVmjSmJLKSv7Cn9M2J+a2942OkA8nZCbz+sl4ZeY4zEOcoLOrbt1GRfQ8zEQKnjAJdXixA3J/p1fDQ==",
|
||||
"version": "3.977.8",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.977.8.tgz",
|
||||
"integrity": "sha512-7+Kcrkvrk9lM/m7jRhHpT4jCdvzGHsuaSRbF8TdzzkY1mRzp/Ogwf9c7H29k4gGhey0BBWhCWr16+t0J61gwmg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/xml-builder": "^3.972.38",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@aws-sdk/xml-builder": "^3.972.39",
|
||||
"@aws/lambda-invoke-store": "^0.3.0",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/signature-v4": "^5.6.12",
|
||||
@@ -120,14 +120,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-env": {
|
||||
"version": "3.972.68",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.68.tgz",
|
||||
"integrity": "sha512-2a20A/IdNOwUvaDq91iqqS7BA0XlNMfW3iLGZGZLJv0EbUqhSxB0PIx4rQQqssvWj1uXImb3/UCCdHz/+1dOiA==",
|
||||
"version": "3.972.69",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.69.tgz",
|
||||
"integrity": "sha512-AreCFzcB4kH2HF9031Ot0jSJr3KXvRg6e8uDeub20JEVdZU3Bv0sTq1plc7VsT3KiqutlzH7l0j50UcCWHUioA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -137,14 +137,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-http": {
|
||||
"version": "3.972.70",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.70.tgz",
|
||||
"integrity": "sha512-0yRem2Fs52r/Nn6UAqIlpjexfaYj8ziEozOe9tamtAVT/5bzFLKx8O2r7MaRqgS3hGKHIa1Jij9nKHSsNnb04A==",
|
||||
"version": "3.972.71",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.71.tgz",
|
||||
"integrity": "sha512-A8ObcqVmDMnk4F9NozZ7JwmUu9Q4xyBJkmyq1C5U+wNM9ht9J7+EuuyabsLWXZnOoTqFaJuYBYTKf5CTipkEjA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/fetch-http-handler": "^5.6.13",
|
||||
"@smithy/node-http-handler": "^4.9.13",
|
||||
@@ -156,21 +156,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.973.13",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.13.tgz",
|
||||
"integrity": "sha512-2M39DE02XpYYaSWYk/4AsImXYUU/1L2xmTMLUpMMWq7DfLv191/vCRy3baKtdr45AkJQyVgSjmuVOLm15SwrRQ==",
|
||||
"version": "3.973.14",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.14.tgz",
|
||||
"integrity": "sha512-7c+Wti2LsERNWMfm7ySz3/6RPopFW3Nmn7s63Xpcq6R/tRuY5hpvkHA2xVgi5ukJbvok9l0IDtVEvqTtg+X7dw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.68",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.70",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.75",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.68",
|
||||
"@aws-sdk/credential-provider-sso": "^3.973.12",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.74",
|
||||
"@aws-sdk/nested-clients": "^3.997.42",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.71",
|
||||
"@aws-sdk/credential-provider-login": "^3.972.76",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-sso": "^3.973.13",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.75",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/credential-provider-imds": "^4.4.16",
|
||||
"@smithy/types": "^4.16.1",
|
||||
@@ -181,15 +181,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-login": {
|
||||
"version": "3.972.75",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.75.tgz",
|
||||
"integrity": "sha512-jaTESuJlQsoUZ44f/i2puyPt8VlF/dMMJ9HM3cStYtk7eKX4N9UWi83OLixUkoOJH3BwWlPLCq9YIK9nfWhVBg==",
|
||||
"version": "3.972.76",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.76.tgz",
|
||||
"integrity": "sha512-LVixwOnEJfrrfKHeZjBA8pIMTZjNDq8ak8VpcoWUuCJDrSnBNU8POJksULMgvN089P0MXtQYH2Zs627/MK1K0g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.42",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -199,19 +199,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.972.79",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.79.tgz",
|
||||
"integrity": "sha512-RIw5dof1EHkWubrZzPC941CDtnFG1iAXsxbFgLkhdYZXHc4icU13c/uxSMI0J5eUx9bxa7LjfpdjfClBB1QsDA==",
|
||||
"version": "3.972.80",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.80.tgz",
|
||||
"integrity": "sha512-bE2qh8ww4iClO1jHsBXdOE8FUgzDbdxbyorNjSCoPSkQd51k3jODItuPZfuwcLHZqDXsH+bI4AMHhqtuyR7mSg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "^3.972.68",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.70",
|
||||
"@aws-sdk/credential-provider-ini": "^3.973.13",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.68",
|
||||
"@aws-sdk/credential-provider-sso": "^3.973.12",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.74",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/credential-provider-env": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-http": "^3.972.71",
|
||||
"@aws-sdk/credential-provider-ini": "^3.973.14",
|
||||
"@aws-sdk/credential-provider-process": "^3.972.69",
|
||||
"@aws-sdk/credential-provider-sso": "^3.973.13",
|
||||
"@aws-sdk/credential-provider-web-identity": "^3.972.75",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/credential-provider-imds": "^4.4.16",
|
||||
"@smithy/types": "^4.16.1",
|
||||
@@ -222,14 +222,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-process": {
|
||||
"version": "3.972.68",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.68.tgz",
|
||||
"integrity": "sha512-nLP3Pda2MQTFJ25hKBMmUuB9Uv+bTZQNlufbeCwklP549Vwnkd8bRLJoCKp5k6xjmdyptrPrOfGOhN0mKuca8A==",
|
||||
"version": "3.972.69",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.69.tgz",
|
||||
"integrity": "sha512-9kpTNdZTrcqXTfhxM7fgl9Z68ek3Fu5oe3Yf+A/pJGibEqpgZxz2tSY7SinmyCIU2PJ+ygY4FPoBBnLpocMtrQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -239,16 +239,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.973.12",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.12.tgz",
|
||||
"integrity": "sha512-EmgyyHn+f9WCcelp3L/vci+LGbX8GigWaVphRArjVo5Pktkr9YnLy/mQ6VDkDyBD72dtfRNTgHmD2ts4rTDXKQ==",
|
||||
"version": "3.973.13",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.13.tgz",
|
||||
"integrity": "sha512-Oc81qauMPzUoTnAS2YKpNwY6sY/LUyQTEeaf6yP197WMxkEBQfcKLR1MFpD7+pNTubXnfkH6gwpji+Gc7iyD2Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.42",
|
||||
"@aws-sdk/token-providers": "3.1108.0",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/token-providers": "3.1111.0",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -258,15 +258,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.972.74",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.74.tgz",
|
||||
"integrity": "sha512-0YfczxGXF3RjGj8z7QG/Ho2HnLGKDHfPSHiTs47UU1U/+mmwISDN+rvGKt2zh+3FX8NdT4xd95LGBGyhQw2dgQ==",
|
||||
"version": "3.972.75",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.75.tgz",
|
||||
"integrity": "sha512-YPN6uoGDgjjjeVFZrcOeCJqmB6zpXoeeNgIjqe+DexJaWqdjVfCCe+VAZwli9Z2h8KhFW8oxkO39emQ1tyz/Mw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.42",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -276,15 +276,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
||||
"version": "3.972.73",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.73.tgz",
|
||||
"integrity": "sha512-oy7sRA5HvHcAvkcKX6F8RI240jcOf3c8y/Gqjs9qemIibdKQqGBIi0uwa+47ZRYqGLpdEO28TQU4G73yUzo06Q==",
|
||||
"version": "3.972.74",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.74.tgz",
|
||||
"integrity": "sha512-2lzoV2z2QO5KJZYGOCnIZ1WVQgzMECvwuzr1xb034a++8QW4U4eGrmC2u4yg1xvNv4TLL/Uv5DLyuAiw0b9z7Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.44",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -294,15 +294,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/nested-clients": {
|
||||
"version": "3.997.42",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.42.tgz",
|
||||
"integrity": "sha512-XWRyon2MTHXD/zMoo0Mbge6Vwf+iE0qQaM/RyGO6NfZ9WukCFiQL27nQVZjYy2JwSIg+iXZxKOX95OBXqlSM4w==",
|
||||
"version": "3.997.43",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.43.tgz",
|
||||
"integrity": "sha512-bit+VpqWNyi3wHxFoTsTliNXimCSL2r2OeDTm7ZrG+YsTZ2D7ofDJ6r/t9PVBn80i6/v0X2h9Tgw6QP2MAKfPw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.44",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/fetch-http-handler": "^5.6.13",
|
||||
"@smithy/node-http-handler": "^4.9.13",
|
||||
@@ -314,15 +314,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/s3-request-presigner": {
|
||||
"version": "3.1110.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1110.0.tgz",
|
||||
"integrity": "sha512-xr7vfMj3Mea1r1+GzdBT/9LpKN2l+xBMSzbGoj3Uia6ERPMjx55BeqVtVGBHWw5+IKSGCx5SVC7Jn+3FxntMLg==",
|
||||
"version": "3.1111.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1111.0.tgz",
|
||||
"integrity": "sha512-ACp/VtDTw6AjFW5Q3M59uAMrBbAHeQS0UBIOIgUROO98Z3uhpiy37k9RmvxbXYVugmTcdNTy4DPVQtLG8sDy6g==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.44",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/signature-v4-multi-region": "^3.996.45",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -332,13 +332,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
||||
"version": "3.996.44",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.44.tgz",
|
||||
"integrity": "sha512-ZSfQ35Qn4MhSY+A0Whyr+KBx+wJKZUyBsOrjB2pSHOafRzbFe47T8XcXM8hZqUAC69qnqIy0C9ArxTuud0CC2w==",
|
||||
"version": "3.996.45",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.45.tgz",
|
||||
"integrity": "sha512-bBuyztukzXq6plzFGHAWiQt0QXo+HL8b8lX5cFTzkez/74PtS1c0qPFCIVuHkyoT+miH2qOjAcm1/yoro2ESPA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/signature-v4": "^5.6.12",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -348,15 +348,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.1108.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1108.0.tgz",
|
||||
"integrity": "sha512-rI80zxDxGJ6904eC/YbjkdjY6JdaZvQ01kOmrMvw7cFQGIHo27fhnIVbMSVDS4T6foQImjxYSRoOu/uSJscXDw==",
|
||||
"version": "3.1111.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1111.0.tgz",
|
||||
"integrity": "sha512-JfljgoVtl+s3Qy21n9a7Z48uCQaOXcN74KJ3TEQfPoB293GrXFSt6HSQJF1sTZ8c/5QedEvd3NjJQMO4u9qa5A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "^3.977.7",
|
||||
"@aws-sdk/nested-clients": "^3.997.42",
|
||||
"@aws-sdk/types": "^3.974.3",
|
||||
"@aws-sdk/core": "^3.977.8",
|
||||
"@aws-sdk/nested-clients": "^3.997.43",
|
||||
"@aws-sdk/types": "^3.974.4",
|
||||
"@smithy/core": "^3.31.1",
|
||||
"@smithy/types": "^4.16.1",
|
||||
"tslib": "^2.6.2"
|
||||
@@ -366,9 +366,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/types": {
|
||||
"version": "3.974.3",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.3.tgz",
|
||||
"integrity": "sha512-ECAqfpNsef+7MO8qtR0h9KcFIBAygaE7Cm6UOiQl+ft+uVap+1G7bNEjs4mdJE2OnA4m6k7i8peH8uGIAsOMGw==",
|
||||
"version": "3.974.4",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.4.tgz",
|
||||
"integrity": "sha512-dSFDNG00MEz0/xl5gxL62giLd1iYyJsTxZ1I1DOj6lC+bbgLB4TRsYClJg3b62dhXT1uATzsTNXPnC+33EJV3A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -380,9 +380,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/xml-builder": {
|
||||
"version": "3.972.38",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.38.tgz",
|
||||
"integrity": "sha512-grf7mzfVxBS5AlsuTvBN7uDpzqohFww9fRPCO+EBSUdvtsYMcPSKdz54h/7XiscqNcUM1Ae1MF7JLHmiYYuzbQ==",
|
||||
"version": "3.972.39",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.39.tgz",
|
||||
"integrity": "sha512-FTti8DS5MMWXNUWiRwXAJeYS+0GHHiMy0+7XOhcwk63ILHmfS2UFy2z/HNpZCSOJJ3P3dnWY6hfYNW3DF0nXUA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -2304,9 +2304,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/zod-openapi": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@hono/zod-openapi/-/zod-openapi-1.6.0.tgz",
|
||||
"integrity": "sha512-42HXUBIaGmQh+hZGLw3Hy5piOreIXgnBnUSs55mtn2gnW/xWtU9nQwEUtRRCk5to8Vm2XbfO0J83eUCwCf5eTg==",
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@hono/zod-openapi/-/zod-openapi-1.6.1.tgz",
|
||||
"integrity": "sha512-z1xS3FZxl4bBkU3kMIsQsLtsqxPTha5XZCsDEPMSZ6+3RRBQI7KED1GeoDpq2WueXCacsNIjcB0MtCnEHY7ahQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asteasolutions/zod-to-openapi": "^9.1.0",
|
||||
@@ -4403,16 +4403,16 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
|
||||
"integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
|
||||
"integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.10",
|
||||
"@vitest/utils": "4.1.10",
|
||||
"@vitest/spy": "4.1.11",
|
||||
"@vitest/utils": "4.1.11",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -4421,13 +4421,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
|
||||
"integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
|
||||
"integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.10",
|
||||
"@vitest/spy": "4.1.11",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -4448,9 +4448,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
|
||||
"integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
|
||||
"integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4461,13 +4461,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
|
||||
"integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
|
||||
"integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.10",
|
||||
"@vitest/utils": "4.1.11",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -4475,14 +4475,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
|
||||
"integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
|
||||
"integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.10",
|
||||
"@vitest/utils": "4.1.10",
|
||||
"@vitest/pretty-format": "4.1.11",
|
||||
"@vitest/utils": "4.1.11",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -4491,9 +4491,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
|
||||
"integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
|
||||
"integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -4501,13 +4501,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
|
||||
"integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
|
||||
"integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.10",
|
||||
"@vitest/pretty-format": "4.1.11",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -5952,9 +5952,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.13.2",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.2.tgz",
|
||||
"integrity": "sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA==",
|
||||
"version": "4.13.3",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.3.tgz",
|
||||
"integrity": "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
@@ -8018,19 +8018,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.10",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
|
||||
"integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
|
||||
"integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.10",
|
||||
"@vitest/mocker": "4.1.10",
|
||||
"@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",
|
||||
"@vitest/expect": "4.1.11",
|
||||
"@vitest/mocker": "4.1.11",
|
||||
"@vitest/pretty-format": "4.1.11",
|
||||
"@vitest/runner": "4.1.11",
|
||||
"@vitest/snapshot": "4.1.11",
|
||||
"@vitest/spy": "4.1.11",
|
||||
"@vitest/utils": "4.1.11",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -8058,12 +8058,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@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",
|
||||
"@vitest/browser-playwright": "4.1.11",
|
||||
"@vitest/browser-preview": "4.1.11",
|
||||
"@vitest/browser-webdriverio": "4.1.11",
|
||||
"@vitest/coverage-istanbul": "4.1.11",
|
||||
"@vitest/coverage-v8": "4.1.11",
|
||||
"@vitest/ui": "4.1.11",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
@@ -8452,13 +8452,13 @@
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@hono/zod-openapi": "^1.6.0",
|
||||
"@hono/zod-openapi": "^1.6.1",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.13.3",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.10"
|
||||
"vitest": "^4.1.11"
|
||||
}
|
||||
},
|
||||
"packages/atmosphere/node_modules/@types/node": {
|
||||
|
||||
+5
-5
@@ -33,8 +33,8 @@
|
||||
"author": "dangered wolf",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.1110.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1110.0",
|
||||
"@aws-sdk/client-s3": "^3.1111.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1111.0",
|
||||
"@cloudflare/vitest-pool-workers": "^0.21.3",
|
||||
"@cloudflare/workers-types": "^4.20260702.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -54,15 +54,15 @@
|
||||
"tsx": "^4.23.12",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.67.0",
|
||||
"vitest": "^4.1.10",
|
||||
"vitest": "^4.1.11",
|
||||
"wrangler": "^4.107.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fxembed/atmosphere": "*",
|
||||
"@hono/sentry": "^1.2.2",
|
||||
"@hono/zod-openapi": "^1.6.0",
|
||||
"@hono/zod-openapi": "^1.6.1",
|
||||
"cheerio": "^1.2.0",
|
||||
"hono": "^4.13.2",
|
||||
"hono": "^4.13.3",
|
||||
"i18next": "^26.3.6",
|
||||
"i18next-icu": "^2.4.4",
|
||||
"zod": "^4.4.3"
|
||||
|
||||
@@ -108,10 +108,10 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.13.3",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.10"
|
||||
"vitest": "^4.1.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hono/zod-openapi": "^1.6.0",
|
||||
"@hono/zod-openapi": "^1.6.1",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +398,67 @@ export const fetchSearchPosts = async (
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* `app.bsky.actor.searchActors` against the public AppView. Unlike `searchPosts`, actor search is
|
||||
* served unauthenticated, so this works on a plain AppView with no proxy credentials.
|
||||
*/
|
||||
export const fetchSearchActors = async (
|
||||
params: {
|
||||
q: string;
|
||||
limit: number;
|
||||
cursor?: string;
|
||||
},
|
||||
opts?: BlueskyFetchOpts
|
||||
): Promise<
|
||||
{ ok: true; data: BlueskySearchActorsResponse } | { ok: false; status: number; body: string }
|
||||
> => {
|
||||
const result = await executeBlueskyXrpc<BlueskySearchActorsResponse>(
|
||||
'app.bsky.actor.searchActors',
|
||||
{
|
||||
q: params.q,
|
||||
limit: params.limit,
|
||||
cursor: params.cursor
|
||||
},
|
||||
opts
|
||||
);
|
||||
if (!result.ok) {
|
||||
console.log('Bluesky searchActors failed', result.status, result.body?.slice?.(0, 200));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* `app.bsky.actor.searchActorsTypeahead` — the prefix-matching sibling of `searchActors`, meant
|
||||
* for autocomplete while the user is still typing. No cursor: it answers one short page by design.
|
||||
*/
|
||||
export const fetchSearchActorsTypeahead = async (
|
||||
params: {
|
||||
q: string;
|
||||
limit: number;
|
||||
},
|
||||
opts?: BlueskyFetchOpts
|
||||
): Promise<
|
||||
| { ok: true; data: BlueskySearchActorsTypeaheadResponse }
|
||||
| { ok: false; status: number; body: string }
|
||||
> => {
|
||||
const result = await executeBlueskyXrpc<BlueskySearchActorsTypeaheadResponse>(
|
||||
'app.bsky.actor.searchActorsTypeahead',
|
||||
{
|
||||
q: params.q,
|
||||
limit: params.limit
|
||||
},
|
||||
opts
|
||||
);
|
||||
if (!result.ok) {
|
||||
console.log(
|
||||
'Bluesky searchActorsTypeahead failed',
|
||||
result.status,
|
||||
result.body?.slice?.(0, 200)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const GET_PROFILES_MAX_ACTORS = 25;
|
||||
|
||||
/** Batch `app.bsky.actor.getProfiles` (max 25 actors per request per lexicon). */
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { APIBlueskyStatus, APISearchResultsBluesky } from '../../types/api-schemas.js';
|
||||
import type {
|
||||
APIBlueskyStatus,
|
||||
APISearchResultsBluesky,
|
||||
APITypeaheadResponse,
|
||||
APIUserListResults
|
||||
} from '../../types/api-schemas.js';
|
||||
import { buildAPIBlueskyPost } from './processor.js';
|
||||
import { fetchSearchPosts } from './client.js';
|
||||
import { fetchSearchActors, fetchSearchActorsTypeahead, fetchSearchPosts } from './client.js';
|
||||
import { blueskyProfileViewToApiUser } from './profileFollowers.js';
|
||||
import { isBlueskyGalleryEmbed } from './gallery.js';
|
||||
import type { BlueskyBuildHost } from './build-host.js';
|
||||
|
||||
@@ -101,3 +107,87 @@ export const blueskySearchAPI = async (
|
||||
cursor: { top: null, bottom: nextCursor }
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* People search via `app.bsky.actor.searchActors`. Returns the same `APIUserListResults` envelope
|
||||
* as the follower and repost lists, so a client renders one profile list whatever produced it.
|
||||
*
|
||||
* `#profileView` carries no counts, so `followers`, `following`, and `statuses` come back 0 — a
|
||||
* client that needs them fetches the full profile. `getFollowers` and `getLikes` behave the same.
|
||||
*/
|
||||
export const blueskySearchUsersAPI = async (
|
||||
options: {
|
||||
q: string;
|
||||
count: number;
|
||||
cursor: string | null;
|
||||
},
|
||||
opts?: { credentialKey?: string }
|
||||
): Promise<APIUserListResults> => {
|
||||
const result = await fetchSearchActors(
|
||||
{
|
||||
q: options.q,
|
||||
limit: options.count,
|
||||
cursor: options.cursor ?? undefined
|
||||
},
|
||||
{ credentialKey: opts?.credentialKey }
|
||||
);
|
||||
|
||||
if (!result.ok) {
|
||||
if (result.status === 400 || result.status === 404) {
|
||||
return { code: 404, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
return { code: 500, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
|
||||
const actors = result.data.actors ?? [];
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
results: actors.map(blueskyProfileViewToApiUser),
|
||||
cursor: { top: null, bottom: result.data.cursor ?? null }
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Autocomplete while the user types, via `app.bsky.actor.searchActorsTypeahead`.
|
||||
*
|
||||
* Same envelope as FxTwitter `/2/typeahead` so one client call site serves both networks, but
|
||||
* Bluesky indexes only accounts — there is no hashtag or event autocomplete behind it, so
|
||||
* `topics` and `events` are always empty rather than absent.
|
||||
*/
|
||||
export const blueskyTypeaheadAPI = async (
|
||||
options: {
|
||||
q: string;
|
||||
count: number;
|
||||
},
|
||||
opts?: { credentialKey?: string }
|
||||
): Promise<APITypeaheadResponse> => {
|
||||
const empty = (code: number): APITypeaheadResponse => ({
|
||||
code,
|
||||
query: options.q,
|
||||
num_results: 0,
|
||||
users: [],
|
||||
topics: [],
|
||||
events: []
|
||||
});
|
||||
|
||||
const result = await fetchSearchActorsTypeahead(
|
||||
{ q: options.q, limit: options.count },
|
||||
{ credentialKey: opts?.credentialKey }
|
||||
);
|
||||
|
||||
if (!result.ok) {
|
||||
return empty(result.status === 400 || result.status === 404 ? 404 : 500);
|
||||
}
|
||||
|
||||
const users = (result.data.actors ?? []).map(blueskyProfileViewToApiUser);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
query: options.q,
|
||||
num_results: users.length,
|
||||
users,
|
||||
topics: [],
|
||||
events: []
|
||||
};
|
||||
};
|
||||
|
||||
@@ -36,15 +36,24 @@ const mastodonFetchInit = (signal: AbortSignal): RequestInit => ({
|
||||
const isRedirectStatus = (s: number): boolean =>
|
||||
s === 301 || s === 302 || s === 303 || s === 307 || s === 308;
|
||||
|
||||
/**
|
||||
* Result of the redirect hop. Tagged explicitly rather than by `instanceof Response`: the two
|
||||
* arms are told apart by a field we set ourselves, so this does not depend on the runtime's
|
||||
* `Response` global being the same class the local `fetch` constructs. It is not, on React
|
||||
* Native — and an `instanceof` test there silently returned the raw `Response` as if it were a
|
||||
* result object, giving callers `ok: true` with `data: undefined`.
|
||||
*/
|
||||
type MastodonRedirectResult = { redirected: true; res: Response } | MastodonFetchErr;
|
||||
|
||||
/** Single same-host hop (e.g. trailing slash / canonical URL) without following cross-origin redirects. */
|
||||
async function resolveMastodonRedirectIfNeeded(
|
||||
initialUrl: string,
|
||||
res: Response,
|
||||
expectedHost: string,
|
||||
signal: AbortSignal
|
||||
): Promise<Response | MastodonFetchErr> {
|
||||
): Promise<MastodonRedirectResult> {
|
||||
if (!isRedirectStatus(res.status)) {
|
||||
return res;
|
||||
return { redirected: true, res };
|
||||
}
|
||||
const loc = res.headers.get('Location');
|
||||
if (!loc) {
|
||||
@@ -63,11 +72,11 @@ async function resolveMastodonRedirectIfNeeded(
|
||||
body: `Mastodon redirect to different host rejected (${resolved.hostname})`
|
||||
};
|
||||
}
|
||||
return fetch(resolved.href, mastodonFetchInit(signal));
|
||||
return { redirected: true, res: await fetch(resolved.href, mastodonFetchInit(signal)) };
|
||||
}
|
||||
|
||||
function isMastodonFetchErr(x: Response | MastodonFetchErr): x is MastodonFetchErr {
|
||||
return !(x instanceof Response);
|
||||
function isMastodonFetchErr(x: MastodonRedirectResult): x is MastodonFetchErr {
|
||||
return !('redirected' in x);
|
||||
}
|
||||
|
||||
async function mastodonFetch<T>(
|
||||
@@ -92,7 +101,7 @@ async function mastodonFetch<T>(
|
||||
clearTimeout(t);
|
||||
return afterRedirect;
|
||||
}
|
||||
res = afterRedirect;
|
||||
res = afterRedirect.res;
|
||||
} catch (e) {
|
||||
clearTimeout(t);
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
@@ -221,6 +230,25 @@ export const fetchAccountFollowing = async (
|
||||
{ limit: params.limit, max_id: params.max_id }
|
||||
);
|
||||
|
||||
/**
|
||||
* `GET /api/v2/search?type=accounts`. Unlike the statuses half of the same endpoint, account
|
||||
* search is served to unauthenticated callers, and unlike `/api/v1/accounts/search` it does not
|
||||
* require a token at all. `resolve` stays off so a query never makes the instance go fetch a
|
||||
* remote account on our behalf.
|
||||
*/
|
||||
export const searchAccounts = async (
|
||||
domain: string,
|
||||
q: string,
|
||||
params: { limit: number; offset?: number }
|
||||
): Promise<MastodonFetchResult<MastodonSearchResponse>> =>
|
||||
mastodonFetch<MastodonSearchResponse>(domain, '/api/v2/search', {
|
||||
q,
|
||||
type: 'accounts',
|
||||
resolve: false,
|
||||
limit: params.limit,
|
||||
offset: params.offset
|
||||
});
|
||||
|
||||
export const searchStatuses = async (
|
||||
domain: string,
|
||||
q: string,
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type { APIMastodonStatus, APISearchResultsMastodon } from '../../types/api-schemas.js';
|
||||
import { assertSafeMastodonDomain, nextMaxIdFromLinkHeader, searchStatuses } from './client.js';
|
||||
import { buildAPIMastodonPost } from './processor.js';
|
||||
import type {
|
||||
APIMastodonStatus,
|
||||
APISearchResultsMastodon,
|
||||
APIUserListResults
|
||||
} from '../../types/api-schemas.js';
|
||||
import {
|
||||
assertSafeMastodonDomain,
|
||||
nextMaxIdFromLinkHeader,
|
||||
searchAccounts,
|
||||
searchStatuses
|
||||
} from './client.js';
|
||||
import { buildAPIMastodonPost, mastodonAccountToApiUser } from './processor.js';
|
||||
import type { MastodonBuildHost } from './build-host.js';
|
||||
|
||||
const decodeCursor = (cursor: string | null): { max_id?: string; offset?: number } | undefined => {
|
||||
@@ -114,3 +123,47 @@ export const mastodonSearchAPI = async (
|
||||
cursor: { top: null, bottom }
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* People search via `GET /api/v2/search?type=accounts`.
|
||||
*
|
||||
* Mastodon has no cross-instance account index, so results are whatever the queried instance has
|
||||
* already federated. A large, well-connected instance therefore finds far more accounts than a
|
||||
* small one — the domain the caller picks is the search corpus, not just the transport.
|
||||
*
|
||||
* Single page by design: Mastodon serves the first page of search to anonymous callers but
|
||||
* answers any `offset` with 401 ("Search queries pagination is not supported without
|
||||
* authentication"), and Atmosphere talks to instances unauthenticated. Emitting a cursor here
|
||||
* would only hand the caller a "load more" that always fails, so `cursor.bottom` stays null.
|
||||
*/
|
||||
export const mastodonSearchUsersAPI = async (
|
||||
domain: string,
|
||||
options: {
|
||||
q: string;
|
||||
count: number;
|
||||
}
|
||||
): Promise<APIUserListResults> => {
|
||||
try {
|
||||
assertSafeMastodonDomain(domain);
|
||||
} catch {
|
||||
return { code: 400, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
|
||||
const result = await searchAccounts(domain, options.q, { limit: options.count });
|
||||
|
||||
if (!result.ok) {
|
||||
if (result.status === 401) {
|
||||
return { code: 401, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
if (result.status === 404 || result.status === 400) {
|
||||
return { code: 404, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
return { code: 500, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
results: (result.data.accounts ?? []).map(a => mastodonAccountToApiUser(a, domain)),
|
||||
cursor: { top: null, bottom: null }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -205,6 +205,11 @@ const ERROR_RULES: ErrorRule[] = [
|
||||
match: ({ json }) => parseSearchTimelineClientError(json) === 'blocklisted',
|
||||
disposition: 'ignore',
|
||||
log: 'SearchTimeline blocklisted query (expected client error)'
|
||||
},
|
||||
{
|
||||
match: ({ json }) => parseSearchTimelineClientError(json) === 'query_too_long',
|
||||
disposition: 'ignore',
|
||||
log: 'SearchTimeline query exceeds max length (expected client error)'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -6,9 +6,17 @@ import { isTombstone } from '../../helpers/tombstone.js';
|
||||
import {
|
||||
isSearchTimelineClientErrorResponse,
|
||||
parseSearchTimelineClientError,
|
||||
searchTimelineClientErrorToApiQueryError
|
||||
searchQueryTooLongError,
|
||||
searchTimelineClientErrorToApiQueryError,
|
||||
TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH
|
||||
} from './searchErrors.js';
|
||||
import type { APISearchResults, APITwitterStatus, ApiQueryError } from '../../types/api-schemas.js';
|
||||
import type {
|
||||
APISearchResults,
|
||||
APITwitterStatus,
|
||||
APIUserListResults,
|
||||
ApiQueryError
|
||||
} from '../../types/api-schemas.js';
|
||||
import { convertToApiUser } from './profile.js';
|
||||
import type { FetchResults } from '../../types/fetch-results.js';
|
||||
import type { TwitterBuildHost } from './build-host.js';
|
||||
|
||||
@@ -436,6 +444,10 @@ export const searchAPI = async (
|
||||
host: TwitterBuildHost,
|
||||
language?: string
|
||||
): Promise<APISearchResults | ApiQueryError> => {
|
||||
if (query.length > TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH) {
|
||||
return searchQueryTooLongError(query.length);
|
||||
}
|
||||
|
||||
const product = feedToProduct(feed);
|
||||
|
||||
let response: TwitterSearchTimelineResponse | null;
|
||||
@@ -500,3 +512,66 @@ export const searchAPI = async (
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* People search — the same `SearchTimeline` query with `product: 'People'`, which returns
|
||||
* `TimelineUser` rows instead of tweets. Reuses the follower/reposter row extractor, so the
|
||||
* envelope matches `/2/profile/{handle}/followers` and a client renders one profile list.
|
||||
*/
|
||||
export const searchUsersAPI = async (
|
||||
query: string,
|
||||
count: number,
|
||||
cursor: string | null,
|
||||
host: TwitterBuildHost,
|
||||
language?: string
|
||||
): Promise<APIUserListResults | ApiQueryError> => {
|
||||
if (query.length > TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH) {
|
||||
return searchQueryTooLongError(query.length);
|
||||
}
|
||||
|
||||
let response: TwitterSearchTimelineResponse | null;
|
||||
|
||||
try {
|
||||
response = (await graphqlRequest(host, {
|
||||
query: SearchTimelineQuery,
|
||||
variables: {
|
||||
rawQuery: query,
|
||||
count,
|
||||
product: 'People',
|
||||
cursor: cursor ?? null
|
||||
},
|
||||
headers: buildLanguageHeaders(language),
|
||||
validator: (_response: unknown) => {
|
||||
if (isSearchTimelineClientErrorResponse(_response)) {
|
||||
return true;
|
||||
}
|
||||
const r = _response as TwitterSearchTimelineResponse;
|
||||
return Array.isArray(r?.data?.search_by_raw_query?.search_timeline?.timeline?.instructions);
|
||||
}
|
||||
})) as TwitterSearchTimelineResponse;
|
||||
} catch (e) {
|
||||
console.error('User search request failed', e);
|
||||
return { code: 500, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
|
||||
const clientError = parseSearchTimelineClientError(response);
|
||||
if (clientError) {
|
||||
return searchTimelineClientErrorToApiQueryError(clientError);
|
||||
}
|
||||
|
||||
const instructions = response?.data?.search_by_raw_query?.search_timeline?.timeline?.instructions;
|
||||
if (!instructions) {
|
||||
return { code: 404, results: [], cursor: { top: null, bottom: null } };
|
||||
}
|
||||
|
||||
const { users, cursors } = processUserRelationshipTimelineInstructionsImpl(instructions);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
results: users.map(user => convertToApiUser(user)),
|
||||
cursor: {
|
||||
top: cursors.find(cur => cur.cursorType === 'Top')?.value ?? null,
|
||||
bottom: cursors.find(cur => cur.cursorType === 'Bottom')?.value ?? null
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import type { ApiQueryError } from '../../types/api-schemas.js';
|
||||
|
||||
export type SearchTimelineClientErrorKind = 'empty_query' | 'blocklisted';
|
||||
/** X SearchTimeline rejects `rawQuery` longer than this; match that cap on FxTwitter `/2/search`. */
|
||||
export const TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH = 512;
|
||||
|
||||
export type SearchTimelineClientErrorKind = 'empty_query' | 'blocklisted' | 'query_too_long';
|
||||
|
||||
const QUERY_TOO_LONG_RE = /Raw query length \d+ exceeds max allowed \d+/i;
|
||||
|
||||
export function formatSearchQueryTooLongMessage(length: number): string {
|
||||
return `Raw query length ${length} exceeds max allowed ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH}`;
|
||||
}
|
||||
|
||||
export function searchQueryTooLongError(length: number): ApiQueryError {
|
||||
return {
|
||||
code: 400,
|
||||
message: formatSearchQueryTooLongMessage(length)
|
||||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
if (value !== null && typeof value === 'object') {
|
||||
@@ -49,6 +65,9 @@ export function parseSearchTimelineClientError(
|
||||
if (message.includes('denylisted in Search Content Control tool')) {
|
||||
return 'blocklisted';
|
||||
}
|
||||
if (QUERY_TOO_LONG_RE.test(message)) {
|
||||
return 'query_too_long';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -70,5 +89,10 @@ export function searchTimelineClientErrorToApiQueryError(
|
||||
code: 400,
|
||||
message: 'Search query is blocked by X content controls'
|
||||
};
|
||||
case 'query_too_long':
|
||||
return {
|
||||
code: 400,
|
||||
message: `Raw query length exceeds max allowed ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -325,6 +325,17 @@ declare type BlueskySearchPostsResponse = {
|
||||
hitsTotal?: number;
|
||||
};
|
||||
|
||||
/** `app.bsky.actor.searchActors` output (subset; `actors` are `#profileView`). */
|
||||
declare type BlueskySearchActorsResponse = {
|
||||
actors?: BlueskyProfileView[];
|
||||
cursor?: string;
|
||||
};
|
||||
|
||||
/** `app.bsky.actor.searchActorsTypeahead` output (subset; no cursor — prefix autocomplete only). */
|
||||
declare type BlueskySearchActorsTypeaheadResponse = {
|
||||
actors?: BlueskyProfileView[];
|
||||
};
|
||||
|
||||
/** `app.bsky.unspecced.getTrendingTopics` (subset; public AppView). */
|
||||
declare type BlueskyTrendingTopicRow = {
|
||||
topic: string;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
mastodonProfileMediaV2Route,
|
||||
mastodonProfileStatusesV2Route,
|
||||
mastodonProfileV2Route,
|
||||
mastodonSearchUsersV2Route,
|
||||
mastodonSearchV2Route,
|
||||
mastodonStatusLikesV2Route,
|
||||
mastodonStatusRepostsV2Route,
|
||||
@@ -33,7 +34,10 @@ import {
|
||||
mastodonProfileMediaAPI,
|
||||
mastodonProfileStatusesAPI
|
||||
} from '@fxembed/atmosphere/providers/mastodon/profileStatuses';
|
||||
import { mastodonSearchAPI } from '@fxembed/atmosphere/providers/mastodon/search';
|
||||
import {
|
||||
mastodonSearchAPI,
|
||||
mastodonSearchUsersAPI
|
||||
} from '@fxembed/atmosphere/providers/mastodon/search';
|
||||
import { mastodonStatusLikesAPI } from '@fxembed/atmosphere/providers/mastodon/statusLikes';
|
||||
import { mastodonStatusRepostsAPI } from '@fxembed/atmosphere/providers/mastodon/statusReposts';
|
||||
import { mastodonBuildHostFromContext } from './build-host-adapter';
|
||||
@@ -163,6 +167,25 @@ export const mastodonSearchAPIRequest: RouteHandler<typeof mastodonSearchV2Route
|
||||
return jsonAfterNormalize<typeof mastodonSearchV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const mastodonSearchUsersAPIRequest: RouteHandler<
|
||||
typeof mastodonSearchUsersV2Route
|
||||
> = async c => {
|
||||
const { domain } = c.req.valid('param');
|
||||
const query = c.req.valid('query');
|
||||
const searchResponse = await mastodonSearchUsersAPI(domain, {
|
||||
q: query.q,
|
||||
count: query.count ?? 30
|
||||
});
|
||||
const { httpStatus, payload } = normalizeApiJsonResponse(
|
||||
searchResponse,
|
||||
[200, 400, 401, 404, 500] as const,
|
||||
'mastodonSearchUsersAPIRequest'
|
||||
);
|
||||
c.status(httpStatus);
|
||||
setApiHeaders(c);
|
||||
return jsonAfterNormalize<typeof mastodonSearchUsersV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const mastodonProfileAPIRequest: RouteHandler<typeof mastodonProfileV2Route> = async c => {
|
||||
const { domain, handle } = c.req.valid('param');
|
||||
const processedResponse = await mastodonUserProfileAPI(handle, domain);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
mastodonProfileMediaAPIRequest,
|
||||
mastodonProfileStatusesAPIRequest,
|
||||
mastodonSearchAPIRequest,
|
||||
mastodonSearchUsersAPIRequest,
|
||||
mastodonStatusAPIRequest,
|
||||
mastodonStatusLikesAPIRequest,
|
||||
mastodonStatusRepostsAPIRequest,
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
mastodonProfileMediaV2Route,
|
||||
mastodonProfileStatusesV2Route,
|
||||
mastodonProfileV2Route,
|
||||
mastodonSearchUsersV2Route,
|
||||
mastodonSearchV2Route,
|
||||
mastodonStatusLikesV2Route,
|
||||
mastodonStatusRepostsV2Route,
|
||||
@@ -33,6 +35,7 @@ export const registerMastodonAtmosphereRoutes = (atmosphere: OpenAPIHono) => {
|
||||
atmosphere.openapi(mastodonThreadV2Route, mastodonThreadAPIRequest);
|
||||
atmosphere.openapi(mastodonConversationV2Route, mastodonConversationAPIRequest);
|
||||
atmosphere.openapi(mastodonSearchV2Route, mastodonSearchAPIRequest);
|
||||
atmosphere.openapi(mastodonSearchUsersV2Route, mastodonSearchUsersAPIRequest);
|
||||
atmosphere.openapi(mastodonProfileV2Route, mastodonProfileAPIRequest);
|
||||
atmosphere.openapi(mastodonProfileFollowersV2Route, mastodonProfileFollowersAPIRequest);
|
||||
atmosphere.openapi(mastodonProfileFollowingV2Route, mastodonProfileFollowingAPIRequest);
|
||||
|
||||
@@ -255,6 +255,43 @@ export const mastodonSearchV2Route = createRoute({
|
||||
}
|
||||
});
|
||||
|
||||
export const mastodonSearchUsersV2Route = createRoute({
|
||||
method: 'get',
|
||||
path: '/2/mastodon/{domain}/search/users',
|
||||
summary: 'Search accounts on an instance',
|
||||
description:
|
||||
'Uses Mastodon `GET /api/v2/search?type=accounts`, which (unlike the statuses half of the same endpoint) is served to unauthenticated callers. Same envelope as `/2/mastodon/{domain}/profile/{handle}/followers` (`code`, `results`, `cursor`). There is no cross-instance account index, so results are whatever `{domain}` has already federated — a large instance is a much larger corpus than a small one. Single page: Mastodon rejects any paginated search from an anonymous caller, so `cursor.bottom` is always null.',
|
||||
request: {
|
||||
params: domainParam,
|
||||
query: z.object({
|
||||
q: mastodonSearchQueryString({ description: 'Search query', example: 'gargron' }),
|
||||
count: z.coerce.number().int().min(1).max(100).optional().openapi({ default: 30 })
|
||||
})
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Matching accounts',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
400: {
|
||||
description: 'Invalid query',
|
||||
content: { 'application/json': { schema: ApiQueryErrorSchema } }
|
||||
},
|
||||
401: {
|
||||
description: 'Upstream requires authentication',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
404: {
|
||||
description: 'No results',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
500: {
|
||||
description: 'Upstream error',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const mastodonProfileV2Route = createRoute({
|
||||
method: 'get',
|
||||
path: '/2/mastodon/{domain}/profile/{handle}',
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
profileFollowingAPIRequest,
|
||||
profileStatusesAPIRequest,
|
||||
searchAPIRequest,
|
||||
searchUsersAPIRequest,
|
||||
statusAPIRequest,
|
||||
statusRepostsAPIRequest,
|
||||
statusQuotesAPIRequest,
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
profileFollowingV2Route,
|
||||
profileStatusesV2Route,
|
||||
profileV2Route,
|
||||
searchUsersV2Route,
|
||||
searchV2Route,
|
||||
statusV2Route,
|
||||
statusRepostsV2Route,
|
||||
@@ -77,6 +79,7 @@ api.openapi(profileFollowersV2Route, profileFollowersAPIRequest);
|
||||
api.openapi(profileFollowingV2Route, profileFollowingAPIRequest);
|
||||
api.openapi(profileV2Route, profileAPIRequest);
|
||||
api.openapi(searchV2Route, searchAPIRequest);
|
||||
api.openapi(searchUsersV2Route, searchUsersAPIRequest);
|
||||
api.openapi(typeaheadV2Route, typeaheadAPIRequest);
|
||||
api.openapi(trendsV2Route, trendsAPIRequest);
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ import {
|
||||
PUBLIC_EXPLORE_TIMELINE_KINDS,
|
||||
type PublicExploreTimelineKind
|
||||
} from '@fxembed/atmosphere/providers/twitter/trends';
|
||||
import {
|
||||
formatSearchQueryTooLongMessage,
|
||||
TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH
|
||||
} from '@fxembed/atmosphere/providers/twitter/searchErrors';
|
||||
import {
|
||||
APIProfileRelationshipListSchema,
|
||||
APIUserListResultsSchema,
|
||||
@@ -22,13 +26,27 @@ import {
|
||||
const twitterSearchQueryHasEffectiveContent = (raw: string): boolean =>
|
||||
raw.replace(/_/g, '').trim().length > 0;
|
||||
|
||||
const twitterSearchQueryString = (openapiMeta: { description: string; example: string }) =>
|
||||
z
|
||||
const twitterSearchQueryString = (openapiMeta: {
|
||||
description: string;
|
||||
example: string;
|
||||
maxLength?: number;
|
||||
}) => {
|
||||
const maxLength = openapiMeta.maxLength;
|
||||
return z
|
||||
.string()
|
||||
.refine(twitterSearchQueryHasEffectiveContent, {
|
||||
message: 'Search query must not be empty'
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
if (maxLength !== undefined && value.length > maxLength) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: formatSearchQueryTooLongMessage(value.length)
|
||||
});
|
||||
}
|
||||
})
|
||||
.openapi(openapiMeta);
|
||||
};
|
||||
|
||||
const aboutAccountQuery = z.object({
|
||||
about_account: z.string().optional().openapi({
|
||||
@@ -560,8 +578,9 @@ export const searchV2Route = createRoute({
|
||||
request: {
|
||||
query: z.object({
|
||||
q: twitterSearchQueryString({
|
||||
description: 'Search query (non-empty)',
|
||||
example: 'puppies'
|
||||
description: `Search query (non-empty, max ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH} characters)`,
|
||||
example: 'puppies',
|
||||
maxLength: TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH
|
||||
}),
|
||||
feed: z.enum(['latest', 'top', 'media']).optional().openapi({
|
||||
description: 'Search tab (default latest)',
|
||||
@@ -581,7 +600,7 @@ export const searchV2Route = createRoute({
|
||||
content: { 'application/json': { schema: APISearchResultsSchema } }
|
||||
},
|
||||
400: {
|
||||
description: 'Invalid `q` parameter',
|
||||
description: `Invalid \`q\` parameter (empty or longer than ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH} characters)`,
|
||||
content: { 'application/json': { schema: ApiQueryErrorSchema } }
|
||||
},
|
||||
404: {
|
||||
@@ -595,6 +614,47 @@ export const searchV2Route = createRoute({
|
||||
}
|
||||
});
|
||||
|
||||
export const searchUsersV2Route = createRoute({
|
||||
method: 'get',
|
||||
path: '/2/search/users',
|
||||
summary: 'Search people',
|
||||
description:
|
||||
'Search accounts using the People tab of X search. Same envelope as `/2/profile/{handle}/followers` (`code`, `results`, `cursor`). Pass `cursor.bottom` back as `cursor` for the next page. `/2/typeahead` is the lighter, unpaginated autocomplete over the same corpus.',
|
||||
request: {
|
||||
query: z.object({
|
||||
q: twitterSearchQueryString({
|
||||
description: `Search query (non-empty, max ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH} characters)`,
|
||||
example: 'jack',
|
||||
maxLength: TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH
|
||||
}),
|
||||
count: z.coerce.number().int().min(1).max(100).optional().openapi({
|
||||
description: 'Page size (default 30)',
|
||||
default: 30
|
||||
}),
|
||||
cursor: z.string().optional(),
|
||||
...langQuery.shape
|
||||
})
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Matching accounts',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
400: {
|
||||
description: `Invalid \`q\` parameter (empty or longer than ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH} characters)`,
|
||||
content: { 'application/json': { schema: ApiQueryErrorSchema } }
|
||||
},
|
||||
404: {
|
||||
description: 'No results or timeline unavailable',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
500: {
|
||||
description: 'Upstream or processing error',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const trendsTypeDescription = `Explore timeline kind. Supported: ${PUBLIC_EXPLORE_TIMELINE_KINDS.join(', ')}`;
|
||||
|
||||
const typeaheadResultTypeDescription =
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
profileAboutAPI
|
||||
} from '@fxembed/atmosphere/providers/twitter/profile';
|
||||
import { attachAboutAccountData } from '@fxembed/atmosphere/providers/twitter/aboutAccount';
|
||||
import { searchAPI } from '@fxembed/atmosphere/providers/twitter/search';
|
||||
import { searchAPI, searchUsersAPI } from '@fxembed/atmosphere/providers/twitter/search';
|
||||
import {
|
||||
profileArticlesAPI,
|
||||
profileFollowersAPI,
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
profileAboutV2Route,
|
||||
profileStatusesV2Route,
|
||||
profileV2Route,
|
||||
searchUsersV2Route,
|
||||
searchV2Route,
|
||||
statusV2Route,
|
||||
statusRepostsV2Route,
|
||||
@@ -392,6 +393,29 @@ export const searchAPIRequest: RouteHandler<typeof searchV2Route> = async c => {
|
||||
return jsonAfterNormalize<typeof searchV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const searchUsersAPIRequest: RouteHandler<typeof searchUsersV2Route> = async c => {
|
||||
const host = twitterBuildHostFromContext(c);
|
||||
const query = c.req.valid('query');
|
||||
|
||||
const searchResponse = await searchUsersAPI(
|
||||
query.q,
|
||||
query.count ?? 30,
|
||||
query.cursor ?? null,
|
||||
host,
|
||||
query.lang
|
||||
);
|
||||
const { httpStatus, payload } = normalizeApiJsonResponse(
|
||||
searchResponse,
|
||||
[200, 400, 404, 500] as const,
|
||||
'searchUsersAPIRequest'
|
||||
);
|
||||
c.status(httpStatus);
|
||||
for (const [header, value] of Object.entries(Constants.API_RESPONSE_HEADERS)) {
|
||||
c.header(header, value);
|
||||
}
|
||||
return jsonAfterNormalize<typeof searchUsersV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const trendsAPIRequest: RouteHandler<typeof trendsV2Route> = async c => {
|
||||
const host = twitterBuildHostFromContext(c);
|
||||
const query = c.req.valid('query');
|
||||
|
||||
@@ -18,7 +18,11 @@ import {
|
||||
blueskyProfileFollowersAPI,
|
||||
blueskyProfileFollowingAPI
|
||||
} from '@fxembed/atmosphere/providers/bluesky/profileFollowers';
|
||||
import { blueskySearchAPI } from '@fxembed/atmosphere/providers/bluesky/search';
|
||||
import {
|
||||
blueskySearchAPI,
|
||||
blueskySearchUsersAPI,
|
||||
blueskyTypeaheadAPI
|
||||
} from '@fxembed/atmosphere/providers/bluesky/search';
|
||||
import { blueskyTrendsAPI } from '@fxembed/atmosphere/providers/bluesky/trends';
|
||||
import { blueskyStatusLikesAPI } from '@fxembed/atmosphere/providers/bluesky/statusLikes';
|
||||
import { blueskyStatusRepostsAPI } from '@fxembed/atmosphere/providers/bluesky/statusReposts';
|
||||
@@ -30,8 +34,10 @@ import {
|
||||
blueskyProfileMediaV2Route,
|
||||
blueskyProfileStatusesV2Route,
|
||||
blueskyProfileV2Route,
|
||||
blueskySearchUsersV2Route,
|
||||
blueskySearchV2Route,
|
||||
blueskyTrendsV2Route,
|
||||
blueskyTypeaheadV2Route,
|
||||
blueskyStatusLikesV2Route,
|
||||
blueskyStatusRepostsV2Route,
|
||||
blueskyStatusV2Route,
|
||||
@@ -213,6 +219,50 @@ export const blueskySearchAPIRequest: RouteHandler<typeof blueskySearchV2Route>
|
||||
return jsonAfterNormalize<typeof blueskySearchV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const blueskySearchUsersAPIRequest: RouteHandler<
|
||||
typeof blueskySearchUsersV2Route
|
||||
> = async c => {
|
||||
const query = c.req.valid('query');
|
||||
const searchResponse = await blueskySearchUsersAPI(
|
||||
{
|
||||
q: query.q,
|
||||
count: query.count ?? 30,
|
||||
cursor: query.cursor ?? null
|
||||
},
|
||||
{ credentialKey: c.env?.CREDENTIAL_KEY }
|
||||
);
|
||||
|
||||
const { httpStatus, payload } = normalizeApiJsonResponse(
|
||||
searchResponse,
|
||||
[200, 400, 404, 500] as const,
|
||||
'blueskySearchUsersAPIRequest'
|
||||
);
|
||||
c.status(httpStatus);
|
||||
for (const [header, value] of Object.entries(Constants.API_RESPONSE_HEADERS)) {
|
||||
c.header(header, value);
|
||||
}
|
||||
return jsonAfterNormalize<typeof blueskySearchUsersV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const blueskyTypeaheadAPIRequest: RouteHandler<typeof blueskyTypeaheadV2Route> = async c => {
|
||||
const query = c.req.valid('query');
|
||||
const response = await blueskyTypeaheadAPI(
|
||||
{ q: query.q, count: query.count ?? 8 },
|
||||
{ credentialKey: c.env?.CREDENTIAL_KEY }
|
||||
);
|
||||
|
||||
const { httpStatus, payload } = normalizeApiJsonResponse(
|
||||
response,
|
||||
[200, 400, 404, 500] as const,
|
||||
'blueskyTypeaheadAPIRequest'
|
||||
);
|
||||
c.status(httpStatus);
|
||||
for (const [header, value] of Object.entries(Constants.API_RESPONSE_HEADERS)) {
|
||||
c.header(header, value);
|
||||
}
|
||||
return jsonAfterNormalize<typeof blueskyTypeaheadV2Route>(c, payload, httpStatus);
|
||||
};
|
||||
|
||||
export const blueskyTrendsAPIRequest: RouteHandler<typeof blueskyTrendsV2Route> = async c => {
|
||||
const query = c.req.valid('query');
|
||||
const type = query.type ?? 'trending';
|
||||
|
||||
@@ -12,6 +12,8 @@ import {
|
||||
blueskyProfileMediaV2Route,
|
||||
blueskyProfileStatusesV2Route,
|
||||
blueskyProfileV2Route,
|
||||
blueskySearchUsersV2Route,
|
||||
blueskyTypeaheadV2Route,
|
||||
blueskySearchV2Route,
|
||||
blueskyTrendsV2Route,
|
||||
blueskyStatusLikesV2Route,
|
||||
@@ -28,6 +30,8 @@ import {
|
||||
blueskyProfileMediaAPIRequest,
|
||||
blueskyProfileStatusesAPIRequest,
|
||||
blueskySearchAPIRequest,
|
||||
blueskySearchUsersAPIRequest,
|
||||
blueskyTypeaheadAPIRequest,
|
||||
blueskyTrendsAPIRequest,
|
||||
blueskyStatusAPIRequest,
|
||||
blueskyStatusLikesAPIRequest,
|
||||
@@ -58,6 +62,8 @@ blueskyApi.openapi(blueskyStatusLikesV2Route, blueskyStatusLikesAPIRequest);
|
||||
blueskyApi.openapi(blueskyThreadV2Route, blueskyThreadAPIRequest);
|
||||
blueskyApi.openapi(blueskyConversationV2Route, blueskyConversationAPIRequest);
|
||||
blueskyApi.openapi(blueskySearchV2Route, blueskySearchAPIRequest);
|
||||
blueskyApi.openapi(blueskySearchUsersV2Route, blueskySearchUsersAPIRequest);
|
||||
blueskyApi.openapi(blueskyTypeaheadV2Route, blueskyTypeaheadAPIRequest);
|
||||
blueskyApi.openapi(blueskyTrendsV2Route, blueskyTrendsAPIRequest);
|
||||
blueskyApi.openapi(blueskyProfileV2Route, blueskyProfileAPIRequest);
|
||||
blueskyApi.openapi(blueskyProfileFollowersV2Route, blueskyProfileFollowersAPIRequest);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRoute, z } from '@hono/zod-openapi';
|
||||
import {
|
||||
APIProfileRelationshipListSchema,
|
||||
APITypeaheadResponseSchema,
|
||||
APIUserListResultsSchema,
|
||||
ApiQueryErrorSchema,
|
||||
APISearchResultsBlueskySchema,
|
||||
@@ -314,6 +315,86 @@ export const blueskySearchV2Route = createRoute({
|
||||
}
|
||||
});
|
||||
|
||||
export const blueskySearchUsersV2Route = createRoute({
|
||||
method: 'get',
|
||||
path: '/2/search/users',
|
||||
summary: 'Search people',
|
||||
description:
|
||||
'Search accounts via Bluesky `app.bsky.actor.searchActors`. Same envelope as `/2/profile/{handle}/followers` (`code`, `results`, `cursor`); pass `cursor.bottom` back as `cursor` for the next page. Rows come from `#profileView`, which carries no counts, so `followers`, `following`, and `statuses` are 0 — fetch `/2/profile/{handle}` for a full profile.',
|
||||
request: {
|
||||
query: z.object({
|
||||
q: blueskySearchQueryString({
|
||||
description: 'Search query (non-empty)',
|
||||
example: 'jay'
|
||||
}),
|
||||
count: z.coerce.number().int().min(1).max(100).optional().openapi({
|
||||
description: 'Page size (default 30)',
|
||||
default: 30
|
||||
}),
|
||||
cursor: z
|
||||
.string()
|
||||
.optional()
|
||||
.openapi({ description: 'Pagination cursor from prior response (`cursor.bottom`)' })
|
||||
})
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Matching accounts',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
400: {
|
||||
description: 'Invalid `q` parameter',
|
||||
content: { 'application/json': { schema: ApiQueryErrorSchema } }
|
||||
},
|
||||
404: {
|
||||
description: 'No results or search unavailable',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
},
|
||||
500: {
|
||||
description: 'Upstream or processing error',
|
||||
content: { 'application/json': { schema: APIUserListResultsSchema } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const blueskyTypeaheadV2Route = createRoute({
|
||||
method: 'get',
|
||||
path: '/2/typeahead',
|
||||
summary: 'Search typeahead suggestions',
|
||||
description:
|
||||
'Account autocomplete via Bluesky `app.bsky.actor.searchActorsTypeahead`, in the same envelope as FxTwitter `GET /2/typeahead`. Bluesky indexes only accounts for autocomplete, so `topics` and `events` are always empty. Unpaginated by design — use `/2/search/users` for a full, cursored people search.',
|
||||
request: {
|
||||
query: z.object({
|
||||
q: blueskySearchQueryString({
|
||||
description: 'Prefix or query string',
|
||||
example: 'jac'
|
||||
}),
|
||||
count: z.coerce.number().int().min(1).max(50).optional().openapi({
|
||||
description: 'Maximum suggestions (default 8)',
|
||||
default: 8
|
||||
})
|
||||
})
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Typeahead payload',
|
||||
content: { 'application/json': { schema: APITypeaheadResponseSchema } }
|
||||
},
|
||||
400: {
|
||||
description: 'Invalid `q` parameter',
|
||||
content: { 'application/json': { schema: ApiQueryErrorSchema } }
|
||||
},
|
||||
404: {
|
||||
description: 'No suggestions or upstream returned an error payload',
|
||||
content: { 'application/json': { schema: APITypeaheadResponseSchema } }
|
||||
},
|
||||
500: {
|
||||
description: 'Upstream or processing error',
|
||||
content: { 'application/json': { schema: APITypeaheadResponseSchema } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const blueskyTrendsTypeDescription = `Trend list. \`trending\` returns Bluesky live topics first, then suggested topic feeds to fill \`count\`. \`suggested\` returns only suggested feeds. Upstream: \`app.bsky.unspecced.getTrendingTopics\` (max 25 rows per request).`;
|
||||
|
||||
export const blueskyTrendsV2Route = createRoute({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import type { APITwitterStatus } from '../src/realms/api/schemas';
|
||||
import { TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH } from '@fxembed/atmosphere/providers/twitter/searchErrors';
|
||||
import { app } from '../src/worker';
|
||||
import { botHeaders, twitterBaseUrl } from './helpers/data';
|
||||
import harness from './helpers/harness';
|
||||
@@ -103,6 +104,38 @@ test('API search accepts count parameter', async () => {
|
||||
expect(response.code).toEqual(200);
|
||||
});
|
||||
|
||||
test('API search rejects q longer than 512 characters with 400', async () => {
|
||||
const tooLong = 'a'.repeat(TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH + 1);
|
||||
const result = await app.request(
|
||||
new Request(`https://api.fxtwitter.com/2/search?q=${tooLong}`, {
|
||||
method: 'GET',
|
||||
headers: botHeaders
|
||||
}),
|
||||
undefined,
|
||||
harness
|
||||
);
|
||||
expect(result.status).toEqual(400);
|
||||
const body = (await result.json()) as { code?: number; message?: string; success?: boolean };
|
||||
expect(body.code).toEqual(400);
|
||||
expect(body.message).toContain(
|
||||
`Raw query length ${tooLong.length} exceeds max allowed ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH}`
|
||||
);
|
||||
expect(body.success).toBeUndefined();
|
||||
});
|
||||
|
||||
test('API search accepts q of exactly 512 characters', async () => {
|
||||
const atLimit = 'a'.repeat(TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH);
|
||||
const result = await app.request(
|
||||
new Request(`https://api.fxtwitter.com/2/search?q=${atLimit}`, {
|
||||
method: 'GET',
|
||||
headers: botHeaders
|
||||
}),
|
||||
undefined,
|
||||
harness
|
||||
);
|
||||
expect(result.status).not.toEqual(400);
|
||||
});
|
||||
|
||||
test('API search returns 400 for upstream empty query error', async () => {
|
||||
const result = await app.request(
|
||||
new Request('https://api.fxtwitter.com/2/search?q=empty_query_error', {
|
||||
@@ -132,3 +165,18 @@ test('API search returns 400 for upstream blocklisted query error', async () =>
|
||||
expect(body.code).toEqual(400);
|
||||
expect(body.message).toContain('blocked by X content controls');
|
||||
});
|
||||
|
||||
test('API search returns 400 for upstream query too long error', async () => {
|
||||
const result = await app.request(
|
||||
new Request('https://api.fxtwitter.com/2/search?q=query_too_long_error', {
|
||||
method: 'GET',
|
||||
headers: botHeaders
|
||||
}),
|
||||
undefined,
|
||||
harness
|
||||
);
|
||||
expect(result.status).toEqual(400);
|
||||
const body = (await result.json()) as { code?: number; message?: string };
|
||||
expect(body.code).toEqual(400);
|
||||
expect(body.message).toContain(`exceeds max allowed ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH}`);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"message": "BadRequest: Raw query length 2268 exceeds max allowed 512",
|
||||
"locations": [{ "line": 4, "column": 7 }],
|
||||
"path": ["search_by_raw_query", "search_timeline", "timeline"],
|
||||
"extensions": {
|
||||
"name": "BadRequestError",
|
||||
"source": "Client",
|
||||
"code": 214,
|
||||
"kind": "Validation",
|
||||
"tracing": { "trace_id": "00000000000000000000000000000000" }
|
||||
},
|
||||
"code": 214,
|
||||
"kind": "Validation",
|
||||
"name": "BadRequestError",
|
||||
"source": "Client"
|
||||
}
|
||||
],
|
||||
"data": {}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import {
|
||||
formatSearchQueryTooLongMessage,
|
||||
isSearchTimelineClientErrorResponse,
|
||||
parseSearchTimelineClientError,
|
||||
searchTimelineClientErrorToApiQueryError
|
||||
searchQueryTooLongError,
|
||||
searchTimelineClientErrorToApiQueryError,
|
||||
TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH
|
||||
} from '@fxembed/atmosphere/providers/twitter/searchErrors';
|
||||
|
||||
const emptyQueryError = {
|
||||
@@ -27,6 +30,17 @@ const blocklistedError = {
|
||||
data: {}
|
||||
};
|
||||
|
||||
const queryTooLongError = {
|
||||
errors: [
|
||||
{
|
||||
message: 'BadRequest: Raw query length 2268 exceeds max allowed 512',
|
||||
path: ['search_by_raw_query', 'search_timeline', 'timeline'],
|
||||
code: 214
|
||||
}
|
||||
],
|
||||
data: {}
|
||||
};
|
||||
|
||||
test('parseSearchTimelineClientError detects empty query', () => {
|
||||
expect(parseSearchTimelineClientError(emptyQueryError)).toBe('empty_query');
|
||||
expect(isSearchTimelineClientErrorResponse(emptyQueryError)).toBe(true);
|
||||
@@ -36,6 +50,11 @@ test('parseSearchTimelineClientError detects blocklisted query', () => {
|
||||
expect(parseSearchTimelineClientError(blocklistedError)).toBe('blocklisted');
|
||||
});
|
||||
|
||||
test('parseSearchTimelineClientError detects query too long', () => {
|
||||
expect(parseSearchTimelineClientError(queryTooLongError)).toBe('query_too_long');
|
||||
expect(isSearchTimelineClientErrorResponse(queryTooLongError)).toBe(true);
|
||||
});
|
||||
|
||||
test('parseSearchTimelineClientError ignores unrelated GraphQL errors', () => {
|
||||
expect(
|
||||
parseSearchTimelineClientError({
|
||||
@@ -58,6 +77,11 @@ test('parseSearchTimelineClientError accepts known messages without path', () =>
|
||||
errors: [{ message: 'BadRequest: Query is denylisted in Search Content Control tool.' }]
|
||||
})
|
||||
).toBe('blocklisted');
|
||||
expect(
|
||||
parseSearchTimelineClientError({
|
||||
errors: [{ message: 'BadRequest: Raw query length 2268 exceeds max allowed 512' }]
|
||||
})
|
||||
).toBe('query_too_long');
|
||||
});
|
||||
|
||||
test('searchTimelineClientErrorToApiQueryError maps to API 400 messages', () => {
|
||||
@@ -69,4 +93,18 @@ test('searchTimelineClientErrorToApiQueryError maps to API 400 messages', () =>
|
||||
code: 400,
|
||||
message: 'Search query is blocked by X content controls'
|
||||
});
|
||||
expect(searchTimelineClientErrorToApiQueryError('query_too_long')).toEqual({
|
||||
code: 400,
|
||||
message: `Raw query length exceeds max allowed ${TWITTER_SEARCH_RAW_QUERY_MAX_LENGTH}`
|
||||
});
|
||||
});
|
||||
|
||||
test('searchQueryTooLongError includes the actual query length', () => {
|
||||
expect(searchQueryTooLongError(2268)).toEqual({
|
||||
code: 400,
|
||||
message: formatSearchQueryTooLongMessage(2268)
|
||||
});
|
||||
expect(formatSearchQueryTooLongMessage(2268)).toBe(
|
||||
'Raw query length 2268 exceeds max allowed 512'
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user