blume 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
  12. package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
  13. package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
  14. package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
  15. package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
  16. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  17. package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
  18. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
  19. package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
  20. package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
  21. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  22. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  23. package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
  24. package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
  25. package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
  26. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
  27. package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
  28. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  29. package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
  30. package/dist/cli/chunk-cm89scdv.js +443 -0
  31. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  32. package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
  33. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  34. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  35. package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
  36. package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
  37. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  38. package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
  39. package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
  40. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  41. package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
  42. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
  43. package/dist/cli/chunk-kwrg63tm.js +31 -0
  44. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  45. package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
  46. package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
  47. package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
  48. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
  49. package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
  50. package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
  51. package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
  52. package/dist/cli/chunk-q3wxxq39.js +126 -0
  53. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  54. package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
  55. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  56. package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
  57. package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
  58. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  59. package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
  60. package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
  61. package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
  62. package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
  63. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
  64. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  65. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  66. package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
  67. package/dist/cli/chunk-zf748avv.js.map +37 -0
  68. package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
  69. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
  70. package/dist/cli/index.js +26 -20
  71. package/dist/cli/index.js.map +5 -5
  72. package/dist/types/ai/agent-surface.d.ts +8 -7
  73. package/dist/types/ai/ai-catalog.d.ts +7 -0
  74. package/dist/types/ai/ask-context.d.ts +22 -0
  75. package/dist/types/ai/ask.d.ts +271 -58
  76. package/dist/types/ai/index.d.ts +2 -2
  77. package/dist/types/ai/site-skill.d.ts +26 -0
  78. package/dist/types/ai/skills.d.ts +16 -0
  79. package/dist/types/astro/integration.d.ts +8 -0
  80. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  81. package/dist/types/captcha/index.d.ts +28 -0
  82. package/dist/types/captcha/schema.d.ts +29 -0
  83. package/dist/types/captcha/turnstile.d.ts +28 -0
  84. package/dist/types/components/content/api-field.d.ts +59 -0
  85. package/dist/types/components/content/api-page.d.ts +95 -0
  86. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  87. package/dist/types/components/openapi/helpers.d.ts +137 -0
  88. package/dist/types/components/openapi/security.d.ts +85 -0
  89. package/dist/types/consent/ethyca.d.ts +48 -0
  90. package/dist/types/consent/index.d.ts +37 -0
  91. package/dist/types/consent/native.d.ts +28 -0
  92. package/dist/types/consent/osano.d.ts +42 -0
  93. package/dist/types/consent/schema.d.ts +110 -0
  94. package/dist/types/core/config-input.d.ts +187 -20
  95. package/dist/types/core/data.d.ts +49 -4
  96. package/dist/types/core/footer.d.ts +8 -0
  97. package/dist/types/core/i18n-ui.d.ts +86 -0
  98. package/dist/types/core/includes.d.ts +8 -2
  99. package/dist/types/core/localizable.d.ts +7 -0
  100. package/dist/types/core/page-modes.d.ts +29 -0
  101. package/dist/types/core/redirect-patterns.d.ts +144 -0
  102. package/dist/types/core/related.d.ts +40 -0
  103. package/dist/types/core/schema.d.ts +551 -56
  104. package/dist/types/core/sources/read.d.ts +5 -0
  105. package/dist/types/core/types.d.ts +1 -1
  106. package/dist/types/core/variables.d.ts +42 -0
  107. package/dist/types/deploy/node-headers.d.ts +13 -2
  108. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  109. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  110. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  111. package/dist/types/deploy/redirects.d.ts +39 -11
  112. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  113. package/dist/types/markdown/code-title.d.ts +17 -0
  114. package/dist/types/markdown/features.d.ts +3 -2
  115. package/dist/types/narration/build.d.ts +70 -0
  116. package/dist/types/narration/provider.d.ts +26 -0
  117. package/dist/types/narration/script.d.ts +154 -0
  118. package/dist/types/openapi/model.d.ts +12 -6
  119. package/dist/types/openapi/overlay.d.ts +54 -0
  120. package/dist/types/openapi/parse.d.ts +13 -0
  121. package/dist/types/openapi/references.d.ts +14 -4
  122. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  123. package/dist/types/ratelimit/index.d.ts +27 -0
  124. package/dist/types/ratelimit/memory.d.ts +36 -0
  125. package/dist/types/ratelimit/schema.d.ts +111 -0
  126. package/dist/types/ratelimit/upstash.d.ts +21 -0
  127. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  128. package/dist/types/reference/asyncapi.d.ts +10 -10
  129. package/dist/types/reference/graphql.d.ts +13 -10
  130. package/dist/types/reference/openapi.d.ts +33 -12
  131. package/dist/types/reference/options.d.ts +45 -5
  132. package/dist/types/reference/scalar.d.ts +13 -0
  133. package/dist/types/reference/schema.d.ts +60 -36
  134. package/dist/types/search/documents.d.ts +12 -1
  135. package/dist/types/search/orama-index.d.ts +4 -0
  136. package/dist/types/seo/metatags.d.ts +20 -0
  137. package/docs/02-deployment.mdx +41 -5
  138. package/docs/03-upgrading.mdx +4 -4
  139. package/docs/04-migrating.mdx +1 -1
  140. package/docs/advanced/changelog.mdx +2 -2
  141. package/docs/advanced/skills.mdx +14 -0
  142. package/docs/cli/audit.mdx +21 -1
  143. package/docs/cli/index.mdx +1 -0
  144. package/docs/configuration/analytics.mdx +3 -1
  145. package/docs/configuration/assistant.mdx +188 -36
  146. package/docs/configuration/consent.mdx +100 -0
  147. package/docs/configuration/customization.mdx +4 -4
  148. package/docs/configuration/index.mdx +63 -5
  149. package/docs/configuration/meta.ts +3 -0
  150. package/docs/configuration/narration.mdx +119 -0
  151. package/docs/configuration/rate-limiting.mdx +69 -0
  152. package/docs/configuration/search.mdx +29 -1
  153. package/docs/configuration/theming.mdx +3 -1
  154. package/docs/content/components.mdx +106 -0
  155. package/docs/content/frontmatter.mdx +76 -0
  156. package/docs/content/i18n.mdx +38 -3
  157. package/docs/content/includes.mdx +16 -0
  158. package/docs/content/meta.mdx +1 -0
  159. package/docs/content/meta.ts +1 -0
  160. package/docs/content/navigation.mdx +28 -6
  161. package/docs/content/sources/contentful.mdx +32 -0
  162. package/docs/content/sources/custom.mdx +39 -0
  163. package/docs/content/sources/github-releases.mdx +31 -0
  164. package/docs/content/sources/index.mdx +91 -0
  165. package/docs/content/sources/meta.ts +16 -0
  166. package/docs/content/sources/notion.mdx +32 -0
  167. package/docs/content/sources/obsidian.mdx +37 -0
  168. package/docs/content/sources/payload.mdx +30 -0
  169. package/docs/content/sources/remote-mdx.mdx +40 -0
  170. package/docs/content/sources/sanity.mdx +29 -0
  171. package/docs/content/sources/strapi.mdx +31 -0
  172. package/docs/content/syntax.mdx +67 -2
  173. package/docs/content/variables.mdx +41 -0
  174. package/docs/discoverability/agent-discovery.mdx +29 -2
  175. package/docs/discoverability/index.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/metadata.mdx +17 -0
  178. package/docs/references/api-pages.mdx +74 -0
  179. package/docs/references/asyncapi.mdx +1 -1
  180. package/docs/references/graphql.mdx +2 -2
  181. package/docs/references/meta.ts +1 -1
  182. package/docs/references/openapi.mdx +102 -3
  183. package/docs/references/scalar.mdx +3 -1
  184. package/package.json +36 -1
  185. package/skills/blume/SKILL.md +3 -0
  186. package/skills/blume-migrate/SKILL.md +5 -5
  187. package/skills/blume-migrate/references/docusaurus.md +3 -3
  188. package/skills/blume-migrate/references/mintlify.md +37 -19
  189. package/skills/blume-migrate/references/nextra.md +2 -2
  190. package/skills/blume-migrate/references/starlight.md +2 -1
  191. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  192. package/skills/blume-write-skill/SKILL.md +82 -0
  193. package/src/ai/agent-readability.ts +2 -1
  194. package/src/ai/agent-surface.ts +9 -7
  195. package/src/ai/ai-catalog.ts +6 -3
  196. package/src/ai/ask-context.ts +73 -20
  197. package/src/ai/ask-data.ts +6 -0
  198. package/src/ai/ask-tools.ts +142 -0
  199. package/src/ai/ask.ts +354 -76
  200. package/src/ai/component-markdown.ts +75 -0
  201. package/src/ai/cors.ts +2 -0
  202. package/src/ai/index.ts +12 -0
  203. package/src/ai/llms.ts +6 -2
  204. package/src/ai/markdown.ts +2 -1
  205. package/src/ai/mcp/data.ts +6 -0
  206. package/src/ai/openapi-components.ts +9 -3
  207. package/src/ai/site-skill.ts +274 -0
  208. package/src/ai/skills.ts +35 -2
  209. package/src/ai/write-skill.ts +97 -0
  210. package/src/astro/generate.ts +132 -18
  211. package/src/astro/index.ts +1 -0
  212. package/src/astro/integration.ts +35 -0
  213. package/src/astro/module-types.ts +6 -0
  214. package/src/astro/templates.ts +283 -30
  215. package/src/astro/variables.ts +31 -0
  216. package/src/audit/catalog.ts +19 -0
  217. package/src/audit/checks/contrast.ts +183 -0
  218. package/src/audit/checks/i18n.ts +2 -3
  219. package/src/audit/checks/indexability.ts +2 -3
  220. package/src/audit/checks/links.ts +5 -12
  221. package/src/audit/checks/sitemap.ts +2 -3
  222. package/src/audit/redirects.ts +46 -15
  223. package/src/audit/run.ts +2 -0
  224. package/src/audit/types.ts +2 -1
  225. package/src/blume-modules.d.ts +7 -0
  226. package/src/captcha/client.ts +152 -0
  227. package/src/captcha/hcaptcha.ts +36 -0
  228. package/src/captcha/index.ts +28 -0
  229. package/src/captcha/schema.ts +32 -0
  230. package/src/captcha/turnstile.ts +38 -0
  231. package/src/captcha/verify.ts +71 -0
  232. package/src/cli/command-meta.ts +5 -0
  233. package/src/cli/commands/doctor.ts +22 -16
  234. package/src/cli/commands/skill.ts +96 -0
  235. package/src/cli/index.ts +5 -0
  236. package/src/cli/required-secrets.ts +71 -29
  237. package/src/components/BlumePage.astro +2 -0
  238. package/src/components/content/ApiEndpoint.astro +18 -0
  239. package/src/components/content/ApiField.astro +79 -0
  240. package/src/components/content/ApiPlayground.astro +62 -0
  241. package/src/components/content/ApiRail.astro +19 -0
  242. package/src/components/content/Color.astro +1 -0
  243. package/src/components/content/FileTree.astro +1 -0
  244. package/src/components/content/GithubInfo.astro +1 -0
  245. package/src/components/content/Panel.astro +1 -0
  246. package/src/components/content/ParamField.astro +25 -0
  247. package/src/components/content/RequestExample.astro +17 -0
  248. package/src/components/content/ResponseExample.astro +17 -0
  249. package/src/components/content/ResponseField.astro +32 -0
  250. package/src/components/content/Step.astro +1 -0
  251. package/src/components/content/Tile.astro +1 -0
  252. package/src/components/content/Tooltip.astro +1 -0
  253. package/src/components/content/Tree.astro +1 -0
  254. package/src/components/content/TypeTable.astro +4 -1
  255. package/src/components/content/View.astro +21 -0
  256. package/src/components/content/ViewSwitcher.astro +168 -0
  257. package/src/components/content/api-field.ts +100 -0
  258. package/src/components/content/api-page.ts +224 -0
  259. package/src/components/content/view-switcher.ts +71 -0
  260. package/src/components/index.ts +2 -0
  261. package/src/components/islands/Assistant.astro +10 -1
  262. package/src/components/islands/assistant.tsx +104 -14
  263. package/src/components/islands/code-question.ts +62 -0
  264. package/src/components/islands/hooks.ts +110 -21
  265. package/src/components/islands/support-link.ts +68 -0
  266. package/src/components/layout/Analytics.astro +11 -2
  267. package/src/components/layout/Banner.astro +14 -10
  268. package/src/components/layout/Breadcrumbs.astro +6 -2
  269. package/src/components/layout/ConsentBanner.astro +62 -0
  270. package/src/components/layout/ConsentHead.astro +38 -0
  271. package/src/components/layout/Directory.astro +94 -0
  272. package/src/components/layout/DrawerSwitchers.astro +51 -0
  273. package/src/components/layout/Empty.astro +2 -2
  274. package/src/components/layout/Header.astro +122 -80
  275. package/src/components/layout/LanguageSwitcher.astro +48 -15
  276. package/src/components/layout/NarrationPlayer.astro +1085 -0
  277. package/src/components/layout/NavSelector.astro +33 -7
  278. package/src/components/layout/PageFeedback.astro +54 -49
  279. package/src/components/layout/PageLayout.astro +71 -3
  280. package/src/components/layout/ReferenceLayout.astro +17 -6
  281. package/src/components/layout/RelatedPages.astro +42 -0
  282. package/src/components/layout/RootLayout.astro +308 -45
  283. package/src/components/layout/Search.astro +31 -17
  284. package/src/components/layout/SiteFooter.astro +124 -0
  285. package/src/components/layout/SiteMetatags.astro +16 -0
  286. package/src/components/layout/consent/native.ts +79 -0
  287. package/src/components/layout/consent/types.ts +37 -0
  288. package/src/components/layout/footer-links.ts +53 -0
  289. package/src/components/layout/footer-socials.ts +87 -0
  290. package/src/components/layout/locale-redirect.ts +107 -0
  291. package/src/components/layout/nav-utils.ts +83 -0
  292. package/src/components/layout/page-feedback.ts +96 -0
  293. package/src/components/layout/search/analytics.ts +98 -0
  294. package/src/components/layout/search/flexsearch.ts +17 -5
  295. package/src/components/layout/search/orama-cloud.ts +26 -14
  296. package/src/components/layout/search/types.ts +20 -0
  297. package/src/components/layout/search/typesense.ts +5 -1
  298. package/src/components/openapi/Operation.astro +161 -13
  299. package/src/components/openapi/RequestBody.astro +6 -4
  300. package/src/components/openapi/RequestPanel.astro +59 -7
  301. package/src/components/openapi/Responses.astro +6 -4
  302. package/src/components/openapi/async-snippets.ts +4 -4
  303. package/src/components/openapi/code-samples.ts +97 -0
  304. package/src/components/openapi/security.ts +6 -3
  305. package/src/components/openapi/snippets.ts +869 -8
  306. package/src/components/props.ts +6 -0
  307. package/src/consent/client.ts +95 -0
  308. package/src/consent/clients.ts +19 -0
  309. package/src/consent/ethyca.ts +71 -0
  310. package/src/consent/head.ts +24 -0
  311. package/src/consent/index.ts +37 -0
  312. package/src/consent/init.ts +29 -0
  313. package/src/consent/native.ts +36 -0
  314. package/src/consent/osano.ts +59 -0
  315. package/src/consent/schema.ts +30 -0
  316. package/src/core/builtin-tags.ts +5 -0
  317. package/src/core/client-address.ts +19 -0
  318. package/src/core/config-input.ts +194 -20
  319. package/src/core/data.ts +44 -4
  320. package/src/core/diagnostics.ts +3 -2
  321. package/src/core/footer.ts +26 -0
  322. package/src/core/graph.ts +39 -36
  323. package/src/core/i18n-ui.ts +82 -2
  324. package/src/core/includes.ts +20 -3
  325. package/src/core/links.ts +16 -2
  326. package/src/core/localizable.ts +26 -0
  327. package/src/core/navigation.ts +43 -12
  328. package/src/core/page-modes.ts +56 -0
  329. package/src/core/project-graph.ts +111 -5
  330. package/src/core/redirect-patterns.ts +491 -0
  331. package/src/core/related.ts +131 -0
  332. package/src/core/schema.ts +318 -64
  333. package/src/core/sources/normalize.ts +2 -1
  334. package/src/core/sources/read.ts +8 -3
  335. package/src/core/types.ts +1 -1
  336. package/src/core/ui-packs/ar.ts +43 -0
  337. package/src/core/ui-packs/bg.ts +45 -0
  338. package/src/core/ui-packs/bn.ts +43 -0
  339. package/src/core/ui-packs/ca.ts +46 -0
  340. package/src/core/ui-packs/cs.ts +44 -0
  341. package/src/core/ui-packs/da.ts +45 -0
  342. package/src/core/ui-packs/de.ts +46 -0
  343. package/src/core/ui-packs/el.ts +45 -0
  344. package/src/core/ui-packs/es.ts +46 -0
  345. package/src/core/ui-packs/fa.ts +45 -0
  346. package/src/core/ui-packs/fi.ts +45 -0
  347. package/src/core/ui-packs/fr.ts +46 -0
  348. package/src/core/ui-packs/he.ts +43 -0
  349. package/src/core/ui-packs/hi.ts +44 -0
  350. package/src/core/ui-packs/hr.ts +45 -0
  351. package/src/core/ui-packs/hu.ts +44 -0
  352. package/src/core/ui-packs/id.ts +45 -0
  353. package/src/core/ui-packs/it.ts +45 -0
  354. package/src/core/ui-packs/ja.ts +45 -0
  355. package/src/core/ui-packs/ko.ts +44 -0
  356. package/src/core/ui-packs/nl.ts +46 -0
  357. package/src/core/ui-packs/no.ts +44 -0
  358. package/src/core/ui-packs/pl.ts +45 -0
  359. package/src/core/ui-packs/pt-br.ts +46 -0
  360. package/src/core/ui-packs/pt.ts +45 -0
  361. package/src/core/ui-packs/ro.ts +45 -0
  362. package/src/core/ui-packs/ru.ts +45 -0
  363. package/src/core/ui-packs/sk.ts +44 -0
  364. package/src/core/ui-packs/sr.ts +45 -0
  365. package/src/core/ui-packs/sv.ts +44 -0
  366. package/src/core/ui-packs/th.ts +43 -0
  367. package/src/core/ui-packs/tr.ts +45 -0
  368. package/src/core/ui-packs/uk.ts +45 -0
  369. package/src/core/ui-packs/vi.ts +45 -0
  370. package/src/core/ui-packs/zh-tw.ts +43 -0
  371. package/src/core/ui-packs/zh.ts +43 -0
  372. package/src/core/variables.ts +103 -0
  373. package/src/deploy/artifacts.ts +77 -8
  374. package/src/deploy/cloudflare-negotiation.ts +35 -5
  375. package/src/deploy/headers.ts +2 -1
  376. package/src/deploy/node-headers.ts +63 -9
  377. package/src/deploy/platforms/cloudflare.ts +32 -0
  378. package/src/deploy/platforms/netlify.ts +33 -1
  379. package/src/deploy/platforms/vercel.ts +37 -2
  380. package/src/deploy/redirects.ts +91 -24
  381. package/src/deploy/vercel-negotiation.ts +36 -0
  382. package/src/markdown/api-rail.ts +259 -0
  383. package/src/markdown/code-title.ts +35 -9
  384. package/src/markdown/features.ts +3 -2
  385. package/src/markdown/fence-meta.ts +2 -0
  386. package/src/markdown/include.ts +11 -1
  387. package/src/markdown/index.ts +28 -5
  388. package/src/markdown/math.ts +4 -4
  389. package/src/markdown/variables.ts +56 -0
  390. package/src/markdown/views.ts +82 -0
  391. package/src/narration/build.ts +412 -0
  392. package/src/narration/player.ts +249 -0
  393. package/src/narration/provider.ts +29 -0
  394. package/src/narration/script.ts +592 -0
  395. package/src/og/card.ts +7 -6
  396. package/src/openapi/model.ts +57 -24
  397. package/src/openapi/overlay.ts +256 -0
  398. package/src/openapi/parse.ts +55 -2
  399. package/src/openapi/references.ts +21 -5
  400. package/src/openapi/render-mdx.ts +5 -1
  401. package/src/openapi/scalar.ts +40 -6
  402. package/src/openapi/source.ts +1 -1
  403. package/src/ratelimit/cloudflare.ts +54 -0
  404. package/src/ratelimit/index.ts +30 -0
  405. package/src/ratelimit/memory.ts +45 -0
  406. package/src/ratelimit/runtime.ts +229 -0
  407. package/src/ratelimit/schema.ts +28 -0
  408. package/src/ratelimit/upstash.ts +30 -0
  409. package/src/ratelimit/wrangler.ts +70 -0
  410. package/src/reference/asyncapi.ts +2 -2
  411. package/src/reference/graphql.ts +5 -2
  412. package/src/reference/openapi.ts +21 -7
  413. package/src/reference/options.ts +51 -7
  414. package/src/reference/scalar.ts +33 -8
  415. package/src/registry/eject.ts +20 -4
  416. package/src/registry/registry.ts +7 -0
  417. package/src/search/documents.ts +26 -2
  418. package/src/search/orama-index.ts +28 -3
  419. package/src/search/pagefind-ranking.ts +39 -0
  420. package/src/search/sync/algolia.ts +15 -4
  421. package/src/search/sync/orama-cloud.ts +3 -0
  422. package/src/search/sync/typesense.ts +5 -0
  423. package/src/seo/metatags.ts +67 -0
  424. package/src/theme/chrome-icons.ts +1 -0
  425. package/src/theme/color.ts +500 -0
  426. package/src/theme/entry.ts +56 -3
  427. package/src/theme/palette.ts +121 -45
  428. package/src/translate/prompts.ts +1 -0
  429. package/src/translate/validate.ts +8 -2
  430. package/src/upgrade/upgrade.ts +4 -171
  431. package/dist/cli/chunk-364znk6q.js +0 -75
  432. package/dist/cli/chunk-364znk6q.js.map +0 -11
  433. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  434. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  435. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  436. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  437. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  438. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  439. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  440. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  441. package/docs/content/sources.mdx +0 -334
  442. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
  443. /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
  444. /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
  445. /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
  446. /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
  447. /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
  448. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
  449. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
  450. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
  451. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
  452. /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
  453. /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
  454. /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
  455. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+
6
+ /** The Worker binding Blume declares for the rate limiter. */
7
+ export const RATE_LIMIT_BINDING = "BLUME_RATE_LIMIT";
8
+
9
+ /** Options for {@link cloudflare}. */
10
+ export interface CloudflareRateLimitOptions {
11
+ /**
12
+ * The rate limiting namespace, a positive integer unique in your
13
+ * Cloudflare account. Defaults to one derived from the site.
14
+ */
15
+ namespaceId?: string;
16
+ /** Requests each reader may make per window, per route. Defaults to 10. */
17
+ requests?: number;
18
+ /** The window, in seconds: Cloudflare allows 10 or 60. Defaults to 60. */
19
+ window?: 10 | 60;
20
+ }
21
+
22
+ export const cloudflareRateLimitOptionsSchema = z.strictObject({
23
+ namespaceId: z
24
+ .string()
25
+ .regex(/^[1-9]\d*$/u, "namespaceId is a positive integer, as a string.")
26
+ .optional(),
27
+ requests: z.number().int().positive().optional(),
28
+ window: z.union([z.literal(10), z.literal(60)]).optional(),
29
+ });
30
+
31
+ export type CloudflareRateLimitAdapter = AdapterDescriptor<
32
+ "cloudflare",
33
+ CloudflareRateLimitOptions
34
+ >;
35
+
36
+ export const cloudflareRateLimitAdapterSchema = adapterDescriptorSchema(
37
+ "cloudflare",
38
+ cloudflareRateLimitOptionsSchema
39
+ );
40
+
41
+ /**
42
+ * Counts with Cloudflare's Workers rate limiting, for a site deployed with
43
+ * `deployment: cloudflare()`: Blume declares the binding in the Worker's
44
+ * config, so there is nothing to set up. Cloudflare counts per location and
45
+ * allows a window of 10 or 60 seconds.
46
+ */
47
+ export const cloudflare = (
48
+ options: CloudflareRateLimitOptions = {}
49
+ ): CloudflareRateLimitAdapter => ({
50
+ kind: "cloudflare",
51
+ options,
52
+ requiredSecrets: [],
53
+ runtimeDeps: [],
54
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Rate limiters for `blume.config.ts`. The server routes a reader can call —
3
+ * the assistant, the API playground proxy, and server-side search — limit
4
+ * each reader (by IP address) to a number of requests per window, answering
5
+ * `429 Too Many Requests` past it:
6
+ *
7
+ * ```ts
8
+ * import { defineConfig } from "blume";
9
+ * import { upstash } from "blume/ratelimit";
10
+ *
11
+ * export default defineConfig({
12
+ * rateLimit: upstash({ requests: 30, window: 600 }),
13
+ * });
14
+ * ```
15
+ *
16
+ * `memory()` is on by default; `rateLimit: false` turns limiting off. Each
17
+ * factory returns a plain descriptor (see `core/adapter.ts`), and the
18
+ * generated routes count with `runtime.ts`.
19
+ */
20
+ export type { AdapterDescriptor, JsonValue } from "../core/adapter.ts";
21
+ export { cloudflare } from "./cloudflare.ts";
22
+ export type {
23
+ CloudflareRateLimitAdapter,
24
+ CloudflareRateLimitOptions,
25
+ } from "./cloudflare.ts";
26
+ export { memory } from "./memory.ts";
27
+ export type { MemoryAdapter, RateLimitOptions } from "./memory.ts";
28
+ export type { RateLimitAdapter } from "./schema.ts";
29
+ export { upstash } from "./upstash.ts";
30
+ export type { UpstashAdapter } from "./upstash.ts";
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+
6
+ /** How many requests a reader gets per window, unless set. */
7
+ export const DEFAULT_REQUESTS = 30;
8
+
9
+ /** The window those requests are counted over, in seconds, unless set. */
10
+ export const DEFAULT_WINDOW = 600;
11
+
12
+ /** The limit every rate limiter takes: a count of requests per window. */
13
+ export interface RateLimitOptions {
14
+ /** Requests each reader may make per window, per route. */
15
+ requests?: number;
16
+ /** The window, in seconds. */
17
+ window?: number;
18
+ }
19
+
20
+ export const rateLimitOptionsSchema = z.strictObject({
21
+ requests: z.number().int().positive().optional(),
22
+ window: z.number().int().positive().optional(),
23
+ });
24
+
25
+ export type MemoryAdapter = AdapterDescriptor<"memory", RateLimitOptions>;
26
+
27
+ export const memoryAdapterSchema = adapterDescriptorSchema(
28
+ "memory",
29
+ rateLimitOptionsSchema
30
+ );
31
+
32
+ /**
33
+ * Counts each reader's requests in the server's own memory, the default. On
34
+ * a server that runs as one process (`node()`) the count is exact; on a
35
+ * platform that runs many short-lived instances (Vercel, Netlify,
36
+ * Cloudflare) each instance keeps its own, so it stops a burst from one
37
+ * reader rather than enforcing the limit exactly. For that, share the count
38
+ * with `upstash()` or `cloudflare()`.
39
+ */
40
+ export const memory = (options: RateLimitOptions = {}): MemoryAdapter => ({
41
+ kind: "memory",
42
+ options,
43
+ requiredSecrets: [],
44
+ runtimeDeps: [],
45
+ });
@@ -0,0 +1,229 @@
1
+ /**
2
+ * The rate limit the generated server routes check before any work: the
3
+ * assistant (`/api/ask`), the API playground proxy (`/_api-proxy`), and
4
+ * server-side search. Each route builds one limiter at module scope from the
5
+ * configured adapter (`rateLimit` in `blume.config.ts`) and asks it about
6
+ * every request, keyed by the reader's IP address and the route, so each
7
+ * route has its own budget.
8
+ *
9
+ * A limiter never stands between a reader and the docs by accident: a
10
+ * request whose address the host can't tell is let through, and so is one
11
+ * the shared store fails to count (logged, not blocked). A shared store
12
+ * without its secrets or binding falls back to counting in memory.
13
+ */
14
+ import { clientAddressOf } from "../core/client-address.ts";
15
+ import type { ClientContext } from "../core/client-address.ts";
16
+ import { RATE_LIMIT_BINDING } from "./cloudflare.ts";
17
+ import { DEFAULT_REQUESTS, DEFAULT_WINDOW } from "./memory.ts";
18
+ import type { RateLimitAdapter } from "./schema.ts";
19
+ import { UPSTASH_SECRETS } from "./upstash.ts";
20
+
21
+ /** What a limiter says about one request. */
22
+ export interface RateLimitResult {
23
+ allowed: boolean;
24
+ /** Seconds until the reader's window resets. */
25
+ retryAfter: number;
26
+ }
27
+
28
+ /** Count one request against `key`. */
29
+ export type Limiter = (key: string) => Promise<RateLimitResult>;
30
+
31
+ /** A Workers rate limiting binding (`ratelimits` in the Worker's config). */
32
+ export interface RateLimitBinding {
33
+ limit: (options: { key: string }) => Promise<{ success: boolean }>;
34
+ }
35
+
36
+ /** What a generated route hands the limiter from its runtime. */
37
+ export interface LimiterRuntime {
38
+ /** The Worker's rate limiting binding, under `cloudflare()`. */
39
+ binding?: RateLimitBinding;
40
+ /** For tests; defaults to the global `fetch`. */
41
+ fetch?: typeof fetch;
42
+ /** For tests; defaults to `Date.now`. */
43
+ now?: () => number;
44
+ /** Reads a secret (`getSecret` from `astro:env/server`). */
45
+ secret?: (name: string) => string | undefined;
46
+ }
47
+
48
+ /** Past this many tracked readers, a memory limiter drops expired ones. */
49
+ const MEMORY_SWEEP_AT = 10_000;
50
+
51
+ /** One reader's count in the current window. */
52
+ interface MemoryEntry {
53
+ count: number;
54
+ resetAt: number;
55
+ }
56
+
57
+ /**
58
+ * Count in this server's memory, in fixed windows: each reader's first
59
+ * request opens a window, and the count resets when it ends.
60
+ */
61
+ export const memoryLimiter = (
62
+ requests: number,
63
+ window: number,
64
+ now: () => number = Date.now
65
+ ): Limiter => {
66
+ const entries = new Map<string, MemoryEntry>();
67
+ return (key) => {
68
+ const time = now();
69
+ if (entries.size >= MEMORY_SWEEP_AT) {
70
+ for (const [tracked, entry] of entries) {
71
+ if (entry.resetAt <= time) {
72
+ entries.delete(tracked);
73
+ }
74
+ }
75
+ }
76
+ let entry = entries.get(key);
77
+ if (!entry || entry.resetAt <= time) {
78
+ entry = { count: 0, resetAt: time + window * 1000 };
79
+ entries.set(key, entry);
80
+ }
81
+ entry.count += 1;
82
+ return Promise.resolve({
83
+ allowed: entry.count <= requests,
84
+ retryAfter: Math.ceil((entry.resetAt - time) / 1000),
85
+ });
86
+ };
87
+ };
88
+
89
+ /** One command's reply from Upstash's REST pipeline. */
90
+ interface UpstashReply {
91
+ error?: string;
92
+ result?: number | string | null;
93
+ }
94
+
95
+ /**
96
+ * Count in Upstash Redis over its REST API: one pipeline opens the window
97
+ * (`SET … EX … NX`), counts the request (`INCR`), and reads what's left of
98
+ * the window (`TTL`).
99
+ */
100
+ export const upstashLimiter = (
101
+ requests: number,
102
+ window: number,
103
+ endpoint: string,
104
+ token: string,
105
+ fetchImpl: typeof fetch = fetch
106
+ ): Limiter => {
107
+ const url = `${endpoint.replace(/\/+$/u, "")}/pipeline`;
108
+ return async (key) => {
109
+ const response = await fetchImpl(url, {
110
+ body: JSON.stringify([
111
+ ["SET", key, "0", "EX", String(window), "NX"],
112
+ ["INCR", key],
113
+ ["TTL", key],
114
+ ]),
115
+ headers: {
116
+ authorization: `Bearer ${token}`,
117
+ "content-type": "application/json",
118
+ },
119
+ method: "POST",
120
+ });
121
+ if (!response.ok) {
122
+ throw new Error(`Upstash answered ${response.status}.`);
123
+ }
124
+ // SAFETY: Upstash's pipeline endpoint answers with one reply object per
125
+ // command, in order; a missing or failed reply reads as `NaN` below.
126
+ const replies = (await response.json()) as UpstashReply[];
127
+ const count = Number(replies[1]?.result ?? Number.NaN);
128
+ const ttl = Number(replies[2]?.result);
129
+ if (Number.isNaN(count)) {
130
+ throw new TypeError("Upstash sent no count.");
131
+ }
132
+ return { allowed: count <= requests, retryAfter: ttl > 0 ? ttl : window };
133
+ };
134
+ };
135
+
136
+ /** Count with a Workers rate limiting binding. */
137
+ export const bindingLimiter =
138
+ (binding: RateLimitBinding, window: number): Limiter =>
139
+ async (key) => {
140
+ const { success } = await binding.limit({ key });
141
+ return { allowed: success, retryAfter: window };
142
+ };
143
+
144
+ /** The limit an adapter sets, with Cloudflare's own defaults for its binding. */
145
+ const limitOf = (adapter: RateLimitAdapter) =>
146
+ adapter.kind === "cloudflare"
147
+ ? {
148
+ requests: adapter.options.requests ?? 10,
149
+ window: adapter.options.window ?? 60,
150
+ }
151
+ : {
152
+ requests: adapter.options.requests ?? DEFAULT_REQUESTS,
153
+ window: adapter.options.window ?? DEFAULT_WINDOW,
154
+ };
155
+
156
+ /**
157
+ * The limiter for the configured adapter, or `null` when rate limiting is
158
+ * off. A shared store missing its secrets or binding counts in memory and
159
+ * says so once.
160
+ */
161
+ export const createLimiter = (
162
+ adapter: RateLimitAdapter | null,
163
+ runtime: LimiterRuntime = {}
164
+ ): Limiter | null => {
165
+ if (!adapter) {
166
+ return null;
167
+ }
168
+ const { requests, window } = limitOf(adapter);
169
+ if (adapter.kind === "cloudflare") {
170
+ if (runtime.binding) {
171
+ return bindingLimiter(runtime.binding, window);
172
+ }
173
+ console.warn(
174
+ `Rate limiting counts in memory: the Worker has no ${RATE_LIMIT_BINDING} binding.`
175
+ );
176
+ }
177
+ if (adapter.kind === "upstash") {
178
+ const [endpoint, token] = UPSTASH_SECRETS.map((name) =>
179
+ runtime.secret?.(name)
180
+ );
181
+ if (endpoint && token) {
182
+ return upstashLimiter(requests, window, endpoint, token, runtime.fetch);
183
+ }
184
+ console.warn(
185
+ `Rate limiting counts in memory: set ${UPSTASH_SECRETS.join(" and ")} to share the count through Upstash.`
186
+ );
187
+ }
188
+ return memoryLimiter(requests, window, runtime.now);
189
+ };
190
+
191
+ /** The slice of Astro's `APIContext` the check reads. */
192
+ export type RateLimitContext = ClientContext;
193
+
194
+ /**
195
+ * Count this request against the reader's budget for `scope` (the route).
196
+ * `null` to go ahead, or the `429 Too Many Requests` to return.
197
+ */
198
+ export const rateLimited = async (
199
+ limiter: Limiter | null,
200
+ context: RateLimitContext,
201
+ scope: string
202
+ ): Promise<Response | null> => {
203
+ const address = limiter ? clientAddressOf(context) : undefined;
204
+ if (!(limiter && address)) {
205
+ return null;
206
+ }
207
+ // The host keeps two sites on one shared store apart.
208
+ const key = `blume:${new URL(context.request.url).host}:${scope}:${address}`;
209
+ let result: RateLimitResult;
210
+ try {
211
+ result = await limiter(key);
212
+ } catch (error) {
213
+ console.error("Rate limiting failed; letting the request through:", error);
214
+ return null;
215
+ }
216
+ if (result.allowed) {
217
+ return null;
218
+ }
219
+ return new Response(
220
+ `Too many requests: try again in ${result.retryAfter} seconds.`,
221
+ {
222
+ headers: {
223
+ "content-type": "text/plain; charset=utf-8",
224
+ "retry-after": String(result.retryAfter),
225
+ },
226
+ status: 429,
227
+ }
228
+ );
229
+ };
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+
3
+ import { cloudflareRateLimitAdapterSchema } from "./cloudflare.ts";
4
+ import { memory, memoryAdapterSchema } from "./memory.ts";
5
+ import { upstashAdapterSchema } from "./upstash.ts";
6
+
7
+ const ADAPTER_HINT =
8
+ 'rateLimit takes one adapter — e.g. `rateLimit: upstash()` or `rateLimit: memory({ requests: 60 })`, imported from "blume/ratelimit" — or `false` to turn it off.';
9
+
10
+ /** The configured rate limiter, as its factory returned it. */
11
+ export const rateLimitAdapterSchema = z.discriminatedUnion("kind", [
12
+ cloudflareRateLimitAdapterSchema,
13
+ memoryAdapterSchema,
14
+ upstashAdapterSchema,
15
+ ]);
16
+
17
+ export type RateLimitAdapter = z.output<typeof rateLimitAdapterSchema>;
18
+
19
+ /**
20
+ * `blume.config.rateLimit`: how the server routes a reader can call (the
21
+ * assistant, the API playground proxy, server-side search) limit requests
22
+ * per reader. On by default with `memory()`; `false` turns it off (`null`
23
+ * once resolved).
24
+ */
25
+ export const rateLimitConfigSchema = z
26
+ .union([z.literal(false), rateLimitAdapterSchema], { error: ADAPTER_HINT })
27
+ .optional()
28
+ .transform((adapter) => (adapter === false ? null : (adapter ?? memory())));
@@ -0,0 +1,30 @@
1
+ import type { AdapterDescriptor } from "../core/adapter.ts";
2
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
3
+ import { rateLimitOptionsSchema } from "./memory.ts";
4
+ import type { RateLimitOptions } from "./memory.ts";
5
+
6
+ /** The env vars holding the Upstash Redis REST endpoint and its token. */
7
+ export const UPSTASH_SECRETS = [
8
+ "UPSTASH_REDIS_REST_URL",
9
+ "UPSTASH_REDIS_REST_TOKEN",
10
+ ] as const;
11
+
12
+ export type UpstashAdapter = AdapterDescriptor<"upstash", RateLimitOptions>;
13
+
14
+ export const upstashAdapterSchema = adapterDescriptorSchema(
15
+ "upstash",
16
+ rateLimitOptionsSchema
17
+ );
18
+
19
+ /**
20
+ * Keeps the count in Upstash Redis, which every instance on every host shares,
21
+ * so the limit holds exactly. It talks to Upstash's REST API, so there is
22
+ * nothing to install; the endpoint and token come from
23
+ * `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN`.
24
+ */
25
+ export const upstash = (options: RateLimitOptions = {}): UpstashAdapter => ({
26
+ kind: "upstash",
27
+ options,
28
+ requiredSecrets: [...UPSTASH_SECRETS],
29
+ runtimeDeps: [],
30
+ });
@@ -0,0 +1,70 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import type { JsonValue } from "../core/adapter.ts";
4
+ import { isJsonObject } from "../core/sources/json.ts";
5
+ import { RATE_LIMIT_BINDING } from "./cloudflare.ts";
6
+ import type { CloudflareRateLimitAdapter } from "./cloudflare.ts";
7
+
8
+ /** The Workers rate limiting binding, as the Worker's config declares it. */
9
+ export interface RateLimitBindingConfig {
10
+ name: string;
11
+ namespace_id: string;
12
+ simple: { limit: number; period: 10 | 60 };
13
+ }
14
+
15
+ /**
16
+ * A namespace id for a Worker that sets none: a positive integer derived from
17
+ * its name, so two Blume sites on one account don't share one.
18
+ */
19
+ export const rateLimitNamespace = (workerName: string): string => {
20
+ const digest = createHash("sha256").update(workerName).digest("hex");
21
+ return String((Number.parseInt(digest.slice(0, 8), 16) % 2_000_000_000) + 1);
22
+ };
23
+
24
+ const isText = <Value>(value: Value): value is Value & string =>
25
+ typeof value === "string";
26
+
27
+ /** The binding `cloudflare()` asks for, on the Worker named `workerName`. */
28
+ export const rateLimitBinding = (
29
+ adapter: CloudflareRateLimitAdapter,
30
+ workerName: string
31
+ ): RateLimitBindingConfig => ({
32
+ name: RATE_LIMIT_BINDING,
33
+ namespace_id: adapter.options.namespaceId ?? rateLimitNamespace(workerName),
34
+ simple: {
35
+ limit: adapter.options.requests ?? 10,
36
+ period: adapter.options.window ?? 60,
37
+ },
38
+ });
39
+
40
+ /**
41
+ * The built Worker's config (`dist/server/wrangler.json`) with Blume's rate
42
+ * limiting binding in `ratelimits`, replacing an earlier one of the same name
43
+ * and keeping any others. `null` when the config isn't a JSON object.
44
+ */
45
+ export const withRateLimitBinding = (
46
+ wranglerText: string,
47
+ adapter: CloudflareRateLimitAdapter
48
+ ): string | null => {
49
+ let config: JsonValue;
50
+ try {
51
+ config = JSON.parse(wranglerText);
52
+ } catch {
53
+ return null;
54
+ }
55
+ if (!isJsonObject(config) || Array.isArray(config)) {
56
+ return null;
57
+ }
58
+ const { name, ratelimits } = config;
59
+ const others = Array.isArray(ratelimits)
60
+ ? ratelimits.filter(
61
+ (entry) => !(isJsonObject(entry) && entry.name === RATE_LIMIT_BINDING)
62
+ )
63
+ : [];
64
+ const binding = rateLimitBinding(adapter, isText(name) ? name : "");
65
+ config.ratelimits = [
66
+ ...others,
67
+ { ...binding, simple: { ...binding.simple } },
68
+ ];
69
+ return JSON.stringify(config);
70
+ };
@@ -16,9 +16,9 @@ import type { PlaygroundOptions, ReferenceSourceOptions } from "./options.ts";
16
16
  export interface AsyncApiOptions {
17
17
  /**
18
18
  * Code-sample tools shown per operation (Blume renderer). Defaults to every
19
- * tool appropriate to the operation's protocol binding.
19
+ * tool appropriate to the operation's protocol binding; `false` shows none.
20
20
  */
21
- codeSamples?: string[];
21
+ codeSamples?: string[] | false;
22
22
  /** Start nested schema rows expanded rather than collapsed (Blume renderer). Defaults to `false`. */
23
23
  expandSchemas?: boolean;
24
24
  /**
@@ -13,8 +13,11 @@ import type { GraphqlSourceOptions, PlaygroundOptions } from "./options.ts";
13
13
 
14
14
  /** Options for {@link graphql}. */
15
15
  export interface GraphqlOptions {
16
- /** Code-sample languages shown per operation. Defaults to `["curl", "js", "python"]`. */
17
- codeSamples?: string[];
16
+ /**
17
+ * Code-sample languages shown per operation, in order. Defaults to
18
+ * `["curl", "js", "python"]`; `false` shows none.
19
+ */
20
+ codeSamples?: string[] | false;
18
21
  /**
19
22
  * URL of the live GraphQL endpoint the playground and code samples target —
20
23
  * a schema, unlike an OpenAPI document, names no server. Applies to every
@@ -4,18 +4,24 @@ import type { AdapterDescriptor } from "../core/adapter.ts";
4
4
  import { adapterDescriptorSchema } from "../core/adapter.ts";
5
5
  import {
6
6
  liftSpec,
7
- referenceSourceSchema,
7
+ openapiSourceSchema,
8
+ overlaysHaveSpec,
9
+ overlaysNeedSpecIssue,
8
10
  hasSources,
9
11
  missingSourcesIssue,
10
12
  rendererRemovedHint,
11
13
  sharedOptions,
12
14
  } from "./options.ts";
13
- import type { PlaygroundOptions, ReferenceSourceOptions } from "./options.ts";
15
+ import type { OpenApiSourceOptions, PlaygroundOptions } from "./options.ts";
14
16
 
15
17
  /** Options for {@link openapi}. */
16
18
  export interface OpenApiOptions {
17
- /** Code-sample languages shown per operation (Blume renderer). Defaults to `["curl", "js", "python"]`. */
18
- codeSamples?: string[];
19
+ /**
20
+ * Code-sample languages generated per operation (Blume renderer), in
21
+ * order. Defaults to `["curl", "js", "python"]`; `false` generates none, so
22
+ * only the spec's own `x-codeSamples` show.
23
+ */
24
+ codeSamples?: string[] | false;
19
25
  /** Start nested schema rows expanded rather than collapsed (Blume renderer). Defaults to `false`. */
20
26
  expandSchemas?: boolean;
21
27
  /**
@@ -26,10 +32,15 @@ export interface OpenApiOptions {
26
32
  * `vercel()` in `deployment`).
27
33
  */
28
34
  playground?: PlaygroundOptions;
35
+ /**
36
+ * OpenAPI Overlay documents for `spec`, applied in order before it renders.
37
+ * With `sources`, each source takes its own `overlays`.
38
+ */
39
+ overlays?: string[];
29
40
  /** Where the reference mounts. Defaults to `/reference`. */
30
41
  route?: string;
31
42
  /** One or more specs; each renders on its own route by default. */
32
- sources?: ReferenceSourceOptions[];
43
+ sources?: OpenApiSourceOptions[];
33
44
  /** Shorthand for a single source: `sources: [{ spec }]`. */
34
45
  spec?: string;
35
46
  }
@@ -43,12 +54,15 @@ export const openapiOptionsSchema = z
43
54
  }),
44
55
  /** Start nested schema rows expanded rather than collapsed (Blume renderer). */
45
56
  expandSchemas: z.boolean().default(false),
57
+ /** Overlays for the `spec` shorthand. */
58
+ overlays: z.array(z.string()).optional(),
46
59
  /** One or more specs; each renders on its own route by default. */
47
- sources: z.array(referenceSourceSchema).default([]),
60
+ sources: z.array(openapiSourceSchema).default([]),
48
61
  },
49
62
  rendererRemovedHint
50
63
  )
51
- .transform(liftSpec(referenceSourceSchema))
64
+ .refine(overlaysHaveSpec, overlaysNeedSpecIssue)
65
+ .transform(liftSpec(openapiSourceSchema))
52
66
  .refine(hasSources, missingSourcesIssue("openapi"));
53
67
 
54
68
  /** `openapi()` options with every default applied and `spec` folded into `sources`. */
@@ -65,6 +65,40 @@ export interface ReferenceSourceOptions {
65
65
  /** A spec source with every default applied. */
66
66
  export type ResolvedReferenceSource = z.output<typeof referenceSourceSchema>;
67
67
 
68
+ /**
69
+ * [OpenAPI Overlay](https://spec.openapis.org/overlay/latest.html) documents
70
+ * (local paths or `http(s)` URLs) applied in order to an OpenAPI spec before
71
+ * it renders: changes to a spec you don't edit by hand.
72
+ */
73
+ export const overlaysSchema = z.array(z.string()).default([]);
74
+
75
+ /** Whether adapter-level `overlays` has the `spec` it belongs to. */
76
+ export const overlaysHaveSpec = (options: {
77
+ overlays?: string[];
78
+ spec?: string;
79
+ }): boolean => !options.overlays || options.spec !== undefined;
80
+
81
+ /** The refinement issue for `overlays` set beside `sources` instead of `spec`. */
82
+ export const overlaysNeedSpecIssue = {
83
+ message:
84
+ "`overlays` belongs to the `spec` shorthand; with `sources`, give each source its own `overlays`.",
85
+ path: ["overlays"],
86
+ };
87
+
88
+ /** An OpenAPI spec source: the shared shape plus its `overlays`. */
89
+ export const openapiSourceSchema = referenceSourceSchema.extend({
90
+ overlays: overlaysSchema,
91
+ });
92
+
93
+ /** One OpenAPI spec source, as `openapi()` accepts it. */
94
+ export interface OpenApiSourceOptions extends ReferenceSourceOptions {
95
+ /**
96
+ * OpenAPI Overlay documents (local paths or `http(s)` URLs) applied in
97
+ * order to the spec before it renders.
98
+ */
99
+ overlays?: string[];
100
+ }
101
+
68
102
  /**
69
103
  * A single GraphQL schema: the shared source shape plus `endpoint`, the live
70
104
  * GraphQL API URL the playground and code samples target (a schema, unlike an
@@ -157,8 +191,13 @@ export const sharedOptions = (defaults: {
157
191
  codeSamples: string[];
158
192
  route: string;
159
193
  }) => ({
160
- /** Code-sample languages/tools shown per operation (Blume renderer). */
161
- codeSamples: z.array(z.string()).default(defaults.codeSamples),
194
+ /**
195
+ * Code-sample languages/tools shown per operation (Blume renderer), or
196
+ * `false` for none: only the spec's own `x-codeSamples` then.
197
+ */
198
+ codeSamples: z
199
+ .union([z.literal(false), z.array(z.string())])
200
+ .default(defaults.codeSamples),
162
201
  /** The "Try it" panel; see {@link playgroundSchema}. */
163
202
  playground: playgroundSchema,
164
203
  /** Where the reference mounts. */
@@ -168,8 +207,9 @@ export const sharedOptions = (defaults: {
168
207
  });
169
208
 
170
209
  /**
171
- * Resolve the `spec` shorthand into `sources` so downstream code reads one
172
- * field: the shorthand becomes the first source (with the source defaults
210
+ * Resolve the `spec` shorthand (and, on OpenAPI kinds, the `overlays` beside
211
+ * it) into `sources` so downstream code reads one field: the shorthand becomes
212
+ * the first source (with the source defaults
173
213
  * applied through the source schema itself, so the two can never drift) and
174
214
  * the `spec` key is dropped. An adapter with neither renders nothing, which
175
215
  * is a config mistake rather than a choice — `refine` it away with
@@ -179,15 +219,19 @@ export const liftSpec =
179
219
  <Source extends { spec: string }>(
180
220
  sourceSchema: z.ZodType<Source, { spec: string }>
181
221
  ) =>
182
- <Options extends { sources: Source[]; spec?: string }>({
222
+ <Options extends { sources: Source[]; spec?: string; overlays?: string[] }>({
223
+ overlays,
183
224
  spec,
184
225
  ...options
185
- }: Options): Omit<Options, "spec"> => ({
226
+ }: Options): Omit<Options, "spec" | "overlays"> => ({
186
227
  ...options,
187
228
  sources:
188
229
  spec === undefined
189
230
  ? options.sources
190
- : [sourceSchema.parse({ spec }), ...options.sources],
231
+ : [
232
+ sourceSchema.parse(overlays ? { overlays, spec } : { spec }),
233
+ ...options.sources,
234
+ ],
191
235
  });
192
236
 
193
237
  /** Whether an adapter has anything to render; pairs with {@link missingSourcesIssue}. */