blume 2.1.0 → 2.1.2

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 (450) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  3. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  4. package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
  5. package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
  6. package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
  7. package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
  8. package/dist/cli/chunk-124fvmzq.js.map +220 -0
  9. package/dist/cli/chunk-150jk7km.js +52 -0
  10. package/dist/cli/chunk-150jk7km.js.map +10 -0
  11. package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
  12. package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
  13. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
  14. package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
  15. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
  16. package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
  17. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
  18. package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
  19. package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
  20. package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
  21. package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
  22. package/dist/cli/chunk-60hz265r.js.map +10 -0
  23. package/dist/cli/chunk-74wfhv79.js +65 -0
  24. package/dist/cli/chunk-74wfhv79.js.map +10 -0
  25. package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
  26. package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
  27. package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
  28. package/dist/cli/chunk-ave1wcbr.js.map +14 -0
  29. package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
  30. package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
  31. package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
  32. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
  33. package/dist/cli/chunk-ckrcm08h.js +324 -0
  34. package/dist/cli/chunk-ckrcm08h.js.map +14 -0
  35. package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
  36. package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
  37. package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
  38. package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
  39. package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
  40. package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
  41. package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
  42. package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
  43. package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
  44. package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
  45. package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
  46. package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
  47. package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
  48. package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
  49. package/dist/cli/chunk-j1x92mx9.js.map +10 -0
  50. package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
  51. package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
  52. package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
  53. package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
  54. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
  55. package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
  56. package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
  57. package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
  58. package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
  59. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
  60. package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
  61. package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
  62. package/dist/cli/chunk-twy7psdz.js.map +10 -0
  63. package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
  64. package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
  65. package/dist/cli/index.js +24 -19
  66. package/dist/cli/index.js.map +3 -3
  67. package/dist/types/ai/ask-context.d.ts +6 -2
  68. package/dist/types/ai/ask.d.ts +24 -5
  69. package/dist/types/ai/component-markdown.d.ts +2 -0
  70. package/dist/types/ai/openapi-components.d.ts +4 -1
  71. package/dist/types/analytics/clics.d.ts +37 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +18 -0
  74. package/dist/types/astro/integration.d.ts +11 -4
  75. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  76. package/dist/types/astro/pages.d.ts +10 -0
  77. package/dist/types/cli/init/scaffold.d.ts +1 -1
  78. package/dist/types/components/content/api-field.d.ts +4 -1
  79. package/dist/types/components/content/api-page.d.ts +6 -1
  80. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  81. package/dist/types/components/openapi/helpers.d.ts +22 -1
  82. package/dist/types/components/openapi/security.d.ts +10 -0
  83. package/dist/types/components/openapi/snippets.d.ts +60 -0
  84. package/dist/types/core/changelog-index.d.ts +8 -0
  85. package/dist/types/core/config-input.d.ts +37 -6
  86. package/dist/types/core/data.d.ts +4 -0
  87. package/dist/types/core/i18n-ui.d.ts +2 -0
  88. package/dist/types/core/links.d.ts +8 -0
  89. package/dist/types/core/powered-by.d.ts +12 -0
  90. package/dist/types/core/probe.d.ts +2 -0
  91. package/dist/types/core/project-graph.d.ts +6 -0
  92. package/dist/types/core/redirect-patterns.d.ts +11 -3
  93. package/dist/types/core/schema.d.ts +228 -3
  94. package/dist/types/core/sources/cache.d.ts +3 -0
  95. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  96. package/dist/types/core/sources/contentful.d.ts +17 -1
  97. package/dist/types/core/sources/lexical.d.ts +5 -0
  98. package/dist/types/core/sources/lower.d.ts +10 -2
  99. package/dist/types/core/sources/normalize.d.ts +20 -1
  100. package/dist/types/core/sources/notion.d.ts +8 -0
  101. package/dist/types/core/sources/remote.d.ts +37 -10
  102. package/dist/types/core/sources/sanity.d.ts +1 -1
  103. package/dist/types/core/types.d.ts +17 -0
  104. package/dist/types/deploy/adapter-output.d.ts +2 -1
  105. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  106. package/dist/types/deploy/adapters/node.d.ts +58 -23
  107. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  108. package/dist/types/deploy/adapters/types.d.ts +3 -2
  109. package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
  110. package/dist/types/deploy/node-headers.d.ts +12 -4
  111. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  112. package/dist/types/deploy/platforms/node.d.ts +3 -1
  113. package/dist/types/deploy/platforms/types.d.ts +18 -7
  114. package/dist/types/deploy/platforms/vercel.d.ts +21 -2
  115. package/dist/types/deploy/redirects.d.ts +17 -1
  116. package/dist/types/deploy/sitemap.d.ts +2 -1
  117. package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
  118. package/dist/types/narration/build.d.ts +34 -12
  119. package/dist/types/narration/provider.d.ts +96 -8
  120. package/dist/types/openapi/checks.d.ts +27 -0
  121. package/dist/types/openapi/graphql.d.ts +6 -0
  122. package/dist/types/openapi/model.d.ts +15 -4
  123. package/dist/types/openapi/parse.d.ts +3 -0
  124. package/dist/types/openapi/references.d.ts +16 -0
  125. package/dist/types/openapi/sentence.d.ts +11 -1
  126. package/dist/types/ratelimit/index.d.ts +1 -1
  127. package/dist/types/ratelimit/schema.d.ts +8 -0
  128. package/dist/types/ratelimit/upstash.d.ts +39 -14
  129. package/dist/types/reference/graphql.d.ts +76 -1
  130. package/dist/types/reference/index.d.ts +1 -1
  131. package/dist/types/reference/openapi.d.ts +2 -2
  132. package/dist/types/reference/options.d.ts +37 -1
  133. package/dist/types/reference/schema.d.ts +84 -0
  134. package/dist/types/search/adapters/registry.d.ts +3 -0
  135. package/dist/types/search/adapters/typesense.d.ts +14 -4
  136. package/dist/types/search/build.d.ts +3 -1
  137. package/dist/types/search/orama-index.d.ts +9 -9
  138. package/dist/types/search/sync/algolia.d.ts +5 -2
  139. package/dist/types/search/sync/index.d.ts +3 -1
  140. package/dist/types/search/sync/typesense.d.ts +11 -7
  141. package/dist/types/seo/jsonld.d.ts +11 -6
  142. package/dist/types/sources/contentful.d.ts +16 -0
  143. package/dist/types/sources/github-releases.d.ts +8 -1
  144. package/dist/types/sources/notion.d.ts +8 -0
  145. package/dist/types/sources/payload.d.ts +2 -0
  146. package/dist/types/sources/registry.d.ts +63 -0
  147. package/dist/types/sources/shared.d.ts +3 -0
  148. package/dist/types/sources/strapi.d.ts +2 -0
  149. package/docs/01-quickstart.mdx +4 -2
  150. package/docs/02-deployment.mdx +59 -13
  151. package/docs/03-upgrading.mdx +2 -1
  152. package/docs/04-migrating.mdx +9 -9
  153. package/docs/08-faq.mdx +12 -12
  154. package/docs/advanced/changelog.mdx +30 -2
  155. package/docs/advanced/custom-pages.mdx +1 -1
  156. package/docs/advanced/skills.mdx +1 -1
  157. package/docs/cli/audit.mdx +30 -7
  158. package/docs/cli/doctor.mdx +6 -2
  159. package/docs/cli/evals.mdx +4 -4
  160. package/docs/cli/translate.mdx +1 -0
  161. package/docs/cli/validate.mdx +4 -3
  162. package/docs/configuration/analytics.mdx +19 -1
  163. package/docs/configuration/assistant.mdx +6 -2
  164. package/docs/configuration/customization.mdx +4 -2
  165. package/docs/configuration/export.mdx +5 -3
  166. package/docs/configuration/index.mdx +4 -2
  167. package/docs/configuration/narration.mdx +43 -6
  168. package/docs/configuration/rate-limiting.mdx +23 -2
  169. package/docs/configuration/search.mdx +37 -14
  170. package/docs/content/components.mdx +2 -2
  171. package/docs/content/frontmatter.mdx +5 -3
  172. package/docs/content/i18n.mdx +2 -2
  173. package/docs/content/includes.mdx +1 -1
  174. package/docs/content/navigation.mdx +8 -6
  175. package/docs/content/sources/contentful.mdx +9 -4
  176. package/docs/content/sources/custom.mdx +7 -3
  177. package/docs/content/sources/github-releases.mdx +12 -2
  178. package/docs/content/sources/index.mdx +3 -3
  179. package/docs/content/sources/notion.mdx +17 -2
  180. package/docs/content/sources/obsidian.mdx +3 -1
  181. package/docs/content/sources/payload.mdx +5 -2
  182. package/docs/content/sources/remote-mdx.mdx +6 -0
  183. package/docs/content/sources/sanity.mdx +4 -2
  184. package/docs/content/sources/strapi.mdx +5 -2
  185. package/docs/content/syntax.mdx +2 -2
  186. package/docs/discoverability/agent-discovery.mdx +1 -1
  187. package/docs/discoverability/index.mdx +1 -1
  188. package/docs/discoverability/json-api.mdx +2 -0
  189. package/docs/discoverability/llms-txt.mdx +1 -1
  190. package/docs/discoverability/markdown.mdx +3 -1
  191. package/docs/discoverability/mcp.mdx +6 -0
  192. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  193. package/docs/discoverability/structured-data.mdx +8 -6
  194. package/docs/references/api-pages.mdx +1 -1
  195. package/docs/references/asyncapi.mdx +5 -1
  196. package/docs/references/graphql.mdx +15 -1
  197. package/docs/references/openapi.mdx +64 -8
  198. package/package.json +9 -9
  199. package/skills/blume/SKILL.md +1 -1
  200. package/skills/blume-migrate/SKILL.md +2 -2
  201. package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
  202. package/skills/blume-migrate/references/docusaurus.md +4 -4
  203. package/skills/blume-migrate/references/fumadocs.md +17 -13
  204. package/skills/blume-migrate/references/monorepo.md +5 -5
  205. package/skills/blume-migrate/references/nextra.md +8 -5
  206. package/skills/blume-migrate/references/starlight.md +9 -7
  207. package/src/ai/ask-context.ts +33 -3
  208. package/src/ai/ask-limits.ts +13 -0
  209. package/src/ai/ask.ts +24 -6
  210. package/src/ai/changelog-markdown.ts +11 -6
  211. package/src/ai/component-markdown.ts +1 -1
  212. package/src/ai/cors.ts +12 -3
  213. package/src/ai/mcp/query.ts +14 -6
  214. package/src/ai/openapi-components.ts +28 -4
  215. package/src/ai/relative-links.ts +4 -38
  216. package/src/analytics/clics.ts +58 -0
  217. package/src/analytics/head.ts +5 -0
  218. package/src/analytics/index.ts +2 -0
  219. package/src/analytics/schema.ts +2 -0
  220. package/src/astro/generate.ts +91 -56
  221. package/src/astro/integration.ts +72 -22
  222. package/src/astro/markdown-negotiation.ts +3 -0
  223. package/src/astro/module-types.ts +3 -1
  224. package/src/astro/pages.ts +23 -0
  225. package/src/astro/render-deps.ts +10 -5
  226. package/src/astro/runtime-deps.ts +23 -2
  227. package/src/astro/templates.ts +129 -81
  228. package/src/audit/catalog.ts +19 -3
  229. package/src/audit/checks/links.ts +14 -1
  230. package/src/audit/checks/network.ts +110 -6
  231. package/src/audit/checks/redirects.ts +6 -4
  232. package/src/audit/checks/robots.ts +58 -2
  233. package/src/audit/run.ts +3 -0
  234. package/src/audit/types.ts +2 -0
  235. package/src/audit/url.ts +14 -5
  236. package/src/blume-modules.d.ts +5 -1
  237. package/src/cli/args.ts +51 -0
  238. package/src/cli/build-failure.ts +23 -0
  239. package/src/cli/commands/audit.ts +10 -1
  240. package/src/cli/commands/dev.ts +18 -2
  241. package/src/cli/commands/doctor.ts +45 -12
  242. package/src/cli/commands/eval.ts +36 -4
  243. package/src/cli/commands/init.ts +6 -4
  244. package/src/cli/commands/validate.ts +10 -1
  245. package/src/cli/init/scaffold.ts +9 -6
  246. package/src/cli/prepare.ts +6 -3
  247. package/src/cli/required-secrets.ts +3 -2
  248. package/src/components/content/ApiPlayground.astro +6 -2
  249. package/src/components/content/api-field.ts +4 -1
  250. package/src/components/content/api-page.ts +31 -7
  251. package/src/components/content/mermaid-element.ts +15 -1
  252. package/src/components/content/view-switcher.ts +7 -4
  253. package/src/components/islands/hooks.ts +27 -1
  254. package/src/components/layout/NavTree.astro +49 -5
  255. package/src/components/layout/NavTreeScript.astro +18 -0
  256. package/src/components/layout/PageActions.astro +139 -24
  257. package/src/components/layout/PageLayout.astro +13 -3
  258. package/src/components/layout/RootLayout.astro +27 -14
  259. package/src/components/layout/Search.astro +17 -3
  260. package/src/components/layout/TableOfContents.astro +24 -17
  261. package/src/components/layout/analytics-client.ts +8 -8
  262. package/src/components/layout/head-scripts.ts +6 -1
  263. package/src/components/layout/search/algolia.ts +10 -1
  264. package/src/components/layout/search/analytics.ts +42 -14
  265. package/src/components/layout/search/endpoint.ts +48 -5
  266. package/src/components/layout/search/pagefind.ts +114 -5
  267. package/src/components/layout/search/types.ts +12 -0
  268. package/src/components/layout/search/typesense.ts +18 -6
  269. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  270. package/src/components/openapi/Authorization.astro +10 -0
  271. package/src/components/openapi/GraphqlOperation.astro +5 -0
  272. package/src/components/openapi/Operation.astro +24 -1
  273. package/src/components/openapi/ParametersTable.astro +8 -4
  274. package/src/components/openapi/Playground.astro +7 -1
  275. package/src/components/openapi/RequestBody.astro +19 -2
  276. package/src/components/openapi/Responses.astro +1 -0
  277. package/src/components/openapi/SchemaTable.astro +15 -3
  278. package/src/components/openapi/async-snippets.ts +61 -5
  279. package/src/components/openapi/async.ts +123 -29
  280. package/src/components/openapi/graphql-helpers.ts +35 -12
  281. package/src/components/openapi/helpers.ts +36 -2
  282. package/src/components/openapi/message-model.ts +54 -11
  283. package/src/components/openapi/message.ts +14 -4
  284. package/src/components/openapi/operation-model.ts +16 -7
  285. package/src/components/openapi/playground-client.ts +77 -58
  286. package/src/components/openapi/playground-schema.ts +1 -1
  287. package/src/components/openapi/schema-tree.ts +44 -14
  288. package/src/components/openapi/security.ts +10 -0
  289. package/src/components/openapi/snippets.ts +5 -3
  290. package/src/components/openapi/validate-json.ts +4 -3
  291. package/src/components/powered-by-middleware.ts +33 -0
  292. package/src/core/changelog-index.ts +31 -1
  293. package/src/core/component-diagnostics.ts +53 -0
  294. package/src/core/component-overrides.ts +32 -0
  295. package/src/core/config-input.ts +42 -6
  296. package/src/core/data.ts +2 -0
  297. package/src/core/diagnostics.ts +5 -0
  298. package/src/core/i18n-ui.ts +2 -0
  299. package/src/core/includes.ts +1 -1
  300. package/src/core/links.ts +47 -17
  301. package/src/core/powered-by.ts +12 -0
  302. package/src/core/probe.ts +26 -6
  303. package/src/core/project-graph.ts +59 -18
  304. package/src/core/redirect-patterns.ts +36 -7
  305. package/src/core/schema.ts +26 -6
  306. package/src/core/sources/cache.ts +6 -0
  307. package/src/core/sources/contentful-rich-text.ts +29 -6
  308. package/src/core/sources/contentful.ts +135 -25
  309. package/src/core/sources/filesystem.ts +26 -4
  310. package/src/core/sources/github-releases.ts +12 -1
  311. package/src/core/sources/lexical.ts +15 -10
  312. package/src/core/sources/lower.ts +19 -3
  313. package/src/core/sources/mdx-remote.ts +62 -10
  314. package/src/core/sources/normalize.ts +227 -41
  315. package/src/core/sources/notion.ts +34 -10
  316. package/src/core/sources/obsidian.ts +16 -3
  317. package/src/core/sources/payload.ts +38 -15
  318. package/src/core/sources/remote.ts +72 -15
  319. package/src/core/sources/resolve.ts +11 -1
  320. package/src/core/sources/sanity.ts +43 -25
  321. package/src/core/sources/strapi.ts +2 -2
  322. package/src/core/types.ts +18 -0
  323. package/src/core/ui-packs/ar.ts +1 -0
  324. package/src/core/ui-packs/bg.ts +1 -0
  325. package/src/core/ui-packs/bn.ts +1 -0
  326. package/src/core/ui-packs/ca.ts +1 -0
  327. package/src/core/ui-packs/cs.ts +1 -0
  328. package/src/core/ui-packs/da.ts +1 -0
  329. package/src/core/ui-packs/de.ts +1 -0
  330. package/src/core/ui-packs/el.ts +1 -0
  331. package/src/core/ui-packs/es.ts +1 -0
  332. package/src/core/ui-packs/fa.ts +1 -0
  333. package/src/core/ui-packs/fi.ts +1 -0
  334. package/src/core/ui-packs/fr.ts +1 -0
  335. package/src/core/ui-packs/he.ts +1 -0
  336. package/src/core/ui-packs/hi.ts +1 -0
  337. package/src/core/ui-packs/hr.ts +1 -0
  338. package/src/core/ui-packs/hu.ts +1 -0
  339. package/src/core/ui-packs/id.ts +1 -0
  340. package/src/core/ui-packs/it.ts +1 -0
  341. package/src/core/ui-packs/ja.ts +1 -0
  342. package/src/core/ui-packs/ko.ts +1 -0
  343. package/src/core/ui-packs/nl.ts +1 -0
  344. package/src/core/ui-packs/no.ts +1 -0
  345. package/src/core/ui-packs/pl.ts +1 -0
  346. package/src/core/ui-packs/pt-br.ts +1 -0
  347. package/src/core/ui-packs/pt.ts +1 -0
  348. package/src/core/ui-packs/ro.ts +1 -0
  349. package/src/core/ui-packs/ru.ts +1 -0
  350. package/src/core/ui-packs/sk.ts +1 -0
  351. package/src/core/ui-packs/sr.ts +1 -0
  352. package/src/core/ui-packs/sv.ts +1 -0
  353. package/src/core/ui-packs/th.ts +1 -0
  354. package/src/core/ui-packs/tr.ts +1 -0
  355. package/src/core/ui-packs/uk.ts +1 -0
  356. package/src/core/ui-packs/vi.ts +1 -0
  357. package/src/core/ui-packs/zh-tw.ts +1 -0
  358. package/src/core/ui-packs/zh.ts +1 -0
  359. package/src/deploy/adapter-output.ts +7 -2
  360. package/src/deploy/adapters/cloudflare.ts +7 -5
  361. package/src/deploy/adapters/node.ts +47 -9
  362. package/src/deploy/adapters/types.ts +7 -3
  363. package/src/deploy/artifacts.ts +2 -1
  364. package/src/deploy/cloudflare-negotiation.ts +36 -7
  365. package/src/deploy/headers.ts +15 -1
  366. package/src/deploy/node-headers.ts +32 -20
  367. package/src/deploy/platforms/cloudflare.ts +14 -9
  368. package/src/deploy/platforms/netlify.ts +2 -1
  369. package/src/deploy/platforms/node.ts +12 -2
  370. package/src/deploy/platforms/types.ts +21 -7
  371. package/src/deploy/platforms/vercel.ts +115 -17
  372. package/src/deploy/redirects.ts +66 -6
  373. package/src/deploy/rss.ts +3 -1
  374. package/src/deploy/sitemap.ts +39 -5
  375. package/src/deploy/vercel-negotiation.ts +325 -103
  376. package/src/eval/agents.ts +18 -1
  377. package/src/eval/prompts.ts +6 -3
  378. package/src/eval/report.ts +34 -13
  379. package/src/eval/run.ts +5 -0
  380. package/src/markdown/base-links.ts +15 -14
  381. package/src/markdown/package-commands.ts +9 -8
  382. package/src/markdown/relative-links.ts +3 -2
  383. package/src/narration/build.ts +218 -40
  384. package/src/narration/provider.ts +89 -9
  385. package/src/openapi/checks.ts +273 -0
  386. package/src/openapi/graphql-build.ts +21 -0
  387. package/src/openapi/graphql.ts +11 -0
  388. package/src/openapi/model.ts +15 -3
  389. package/src/openapi/parse.ts +127 -2
  390. package/src/openapi/references.ts +43 -3
  391. package/src/openapi/render-mdx.ts +68 -8
  392. package/src/openapi/sentence.ts +29 -2
  393. package/src/openapi/source.ts +39 -14
  394. package/src/ratelimit/index.ts +1 -1
  395. package/src/ratelimit/runtime.ts +37 -19
  396. package/src/ratelimit/upstash.ts +40 -10
  397. package/src/reference/graphql.ts +15 -1
  398. package/src/reference/index.ts +1 -0
  399. package/src/reference/openapi.ts +2 -2
  400. package/src/reference/options.ts +29 -1
  401. package/src/registry/eject.ts +8 -2
  402. package/src/search/adapters/locale-scope.ts +30 -0
  403. package/src/search/adapters/typesense.ts +14 -4
  404. package/src/search/build.ts +21 -3
  405. package/src/search/orama-index.ts +163 -31
  406. package/src/search/source-pages.ts +29 -0
  407. package/src/search/sync/algolia.ts +99 -9
  408. package/src/search/sync/index.ts +52 -13
  409. package/src/search/sync/typesense.ts +72 -27
  410. package/src/seo/jsonld.ts +38 -20
  411. package/src/sources/contentful.ts +12 -0
  412. package/src/sources/github-releases.ts +7 -1
  413. package/src/sources/notion.ts +7 -0
  414. package/src/sources/shared.ts +3 -0
  415. package/src/theme/color.ts +9 -10
  416. package/src/theme/entry.ts +43 -6
  417. package/src/translate/validate.ts +23 -1
  418. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  419. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  420. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  421. package/dist/cli/chunk-4ejxbzee.js +0 -39
  422. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  423. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  424. package/dist/cli/chunk-713mngdq.js.map +0 -14
  425. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  426. package/dist/cli/chunk-amrb6hgh.js +0 -145
  427. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  428. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  429. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  430. package/dist/cli/chunk-ta27feev.js.map +0 -10
  431. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  432. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  433. package/dist/cli/chunk-zf748avv.js.map +0 -37
  434. /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
  435. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
  436. /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
  437. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
  438. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
  439. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
  440. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
  441. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
  442. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
  443. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
  444. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
  445. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
  446. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
  447. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
  448. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
  449. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
  450. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
@@ -9,6 +9,10 @@
9
9
  * result is a `search_select` event with its position, for click-through. The
10
10
  * same query settling twice in a row (a filter toggled back and forth) counts
11
11
  * once.
12
+ *
13
+ * With `search.analytics.queries: false`, providers get each query's length
14
+ * (`queryChars`) instead of its text, and the text rides only the
15
+ * `blume:track` DOM event, the way the assistant reports questions.
12
16
  */
13
17
  import type { TrackProps } from "../analytics-client.ts";
14
18
 
@@ -18,8 +22,15 @@ export const MAX_QUERY_CHARS = 100;
18
22
  /** How long a query sits untouched before it counts as settled. */
19
23
  export const SETTLE_MS = 1000;
20
24
 
21
- /** Sends one analytics event. */
22
- export type SendEvent = (event: string, props: TrackProps) => void;
25
+ /**
26
+ * Sends one analytics event; `local` rides only the `blume:track` DOM event
27
+ * (see `track`).
28
+ */
29
+ export type SendEvent = (
30
+ event: string,
31
+ props: TrackProps,
32
+ local?: TrackProps
33
+ ) => void;
23
34
 
24
35
  /** The timer a tracker waits on; injectable for tests. */
25
36
  export interface SettleTimer {
@@ -27,6 +38,14 @@ export interface SettleTimer {
27
38
  start: (callback: () => void, ms: number) => number;
28
39
  }
29
40
 
41
+ /** How a tracker reports. */
42
+ export interface SearchTrackerOptions {
43
+ /** Whether providers receive each query's text (`search.analytics.queries`). */
44
+ queries?: boolean;
45
+ /** The timer a query waits on to settle; injectable for tests. */
46
+ timer?: SettleTimer;
47
+ }
48
+
30
49
  /** What the dialog tells the tracker. */
31
50
  export interface SearchTracker {
32
51
  /** The dialog closed; record the query it was showing. */
@@ -51,23 +70,33 @@ const browserTimer: SettleTimer = {
51
70
  /** A tracker for one search dialog. */
52
71
  export const createSearchTracker = (
53
72
  send: SendEvent,
54
- timer: SettleTimer = browserTimer
73
+ { queries = true, timer = browserTimer }: SearchTrackerOptions = {}
55
74
  ): SearchTracker => {
56
75
  let pending: PendingSearch | undefined;
57
76
  let handle: number | undefined;
58
77
  let last: string | undefined;
59
78
 
79
+ // Providers get the query's text, or only its length, in which case the
80
+ // text goes to the `blume:track` DOM event alone.
81
+ const report = (event: string, props: TrackProps, query: string): void => {
82
+ if (queries) {
83
+ send(event, { ...props, query });
84
+ } else {
85
+ send(event, { ...props, queryChars: query.length }, { query });
86
+ }
87
+ };
88
+
60
89
  const flush = (): void => {
61
90
  if (handle !== undefined) {
62
91
  timer.cancel(handle);
63
92
  handle = undefined;
64
93
  }
65
94
  if (pending && pending.query !== last) {
66
- send("search", {
67
- path: location.pathname,
68
- query: pending.query,
69
- results: pending.results,
70
- });
95
+ report(
96
+ "search",
97
+ { path: location.pathname, results: pending.results },
98
+ pending.query
99
+ );
71
100
  last = pending.query;
72
101
  }
73
102
  pending = undefined;
@@ -80,12 +109,11 @@ export const createSearchTracker = (
80
109
  },
81
110
  selected: (query, position, url) => {
82
111
  flush();
83
- send("search_select", {
84
- path: location.pathname,
85
- position,
86
- query: query.slice(0, MAX_QUERY_CHARS),
87
- url,
88
- });
112
+ report(
113
+ "search_select",
114
+ { path: location.pathname, position, url },
115
+ query.slice(0, MAX_QUERY_CHARS)
116
+ );
89
117
  },
90
118
  settled: (query, results) => {
91
119
  if (handle !== undefined) {
@@ -1,22 +1,64 @@
1
1
  import { highlight, SEARCH_LIMIT } from "./types.ts";
2
- import type { SearchFn, SearchHit } from "./types.ts";
2
+ import type { SearchClientOptions, SearchFn, SearchHit } from "./types.ts";
3
+
4
+ /**
5
+ * How long a typed query waits for the reader to stop typing before it's
6
+ * sent, in milliseconds. The dialog searches on every keystroke, and each
7
+ * request counts toward the endpoint's `rateLimit`.
8
+ */
9
+ export const SEARCH_DEBOUNCE_MS = 250;
10
+
11
+ /** Resolve after `ms`, or reject with the abort reason once `signal` aborts. */
12
+ const pause = (ms: number, signal: AbortSignal): Promise<void> =>
13
+ // oxlint-disable-next-line promise/avoid-new -- adapt setTimeout and the abort event
14
+ new Promise((resolve, reject) => {
15
+ const timer = setTimeout(resolve, ms);
16
+ signal.addEventListener(
17
+ "abort",
18
+ () => {
19
+ clearTimeout(timer);
20
+ reject(signal.reason);
21
+ },
22
+ { once: true }
23
+ );
24
+ });
3
25
 
4
26
  /**
5
27
  * Server-proxied search (Mixedbread): POST the query to a generated endpoint
6
28
  * that holds the secret key and talks to the service. The returned hits carry
7
29
  * service-derived text and the dialog injects title/excerpt as HTML, so both
8
30
  * are escaped (and query matches marked) here, like every other provider.
31
+ * A failed request rejects, as a hosted provider's does, so the dialog shows
32
+ * its error rather than an empty "no results".
33
+ *
34
+ * With `typing` (the search dialog), a query waits {@link SEARCH_DEBOUNCE_MS}
35
+ * before it's sent, and a newer one supersedes it: the earlier call's wait or
36
+ * request is aborted and it rejects with an `AbortError`. Typing a word then
37
+ * costs one request rather than one per letter, and a slow earlier response
38
+ * can't land after a later one. Without it, every call is sent at once.
9
39
  */
10
- export const createSearch =
11
- (opts: { api: string }): SearchFn =>
12
- async (query) => {
40
+ export const createSearch = (
41
+ opts: { api: string } & SearchClientOptions
42
+ ): SearchFn => {
43
+ let pending: AbortController | undefined;
44
+ return async (query) => {
45
+ let signal: AbortSignal | undefined;
46
+ if (opts.typing) {
47
+ pending?.abort();
48
+ pending = new AbortController();
49
+ ({ signal } = pending);
50
+ await pause(SEARCH_DEBOUNCE_MS, signal);
51
+ }
13
52
  const response = await fetch(opts.api, {
14
53
  body: JSON.stringify({ query }),
15
54
  headers: { "Content-Type": "application/json" },
16
55
  method: "POST",
56
+ signal,
17
57
  });
18
58
  if (!response.ok) {
19
- return { hits: [], sections: [] };
59
+ throw new Error(
60
+ `Search failed: ${opts.api} answered ${response.status}.`
61
+ );
20
62
  }
21
63
  // SAFETY: the endpoint is Blume-generated (`search-endpoint` template) and
22
64
  // responds with the SearchHit list it built; title/excerpt are still
@@ -29,3 +71,4 @@ export const createSearch =
29
71
  }));
30
72
  return { hits, sections: [] };
31
73
  };
74
+ };
@@ -9,14 +9,63 @@ interface PagefindResult {
9
9
  }>;
10
10
  }
11
11
 
12
- interface PagefindModule {
12
+ /** One Pagefind instance: a page language's index, plus any merged into it. */
13
+ interface PagefindInstance {
14
+ mergeIndex: (
15
+ url: string,
16
+ options: { baseUrl: string; language: string }
17
+ ) => Promise<void>;
13
18
  search: (query: string) => Promise<{ results: PagefindResult[] }>;
14
19
  }
15
20
 
21
+ interface PagefindModule {
22
+ createInstance: (options: {
23
+ basePath: string;
24
+ baseUrl: string;
25
+ }) => PagefindInstance;
26
+ }
27
+
28
+ /** `pagefind-entry.json`: the bundle's index for each language. */
29
+ interface PagefindEntry {
30
+ languages: Record<string, { page_count: number }>;
31
+ }
32
+
33
+ // Pagefind names each page after its built file (`quickstart/index.html`), so
34
+ // its URLs end in a slash. Blume serves pages without one (`trailingSlash:
35
+ // "never"`): hosts redirect the slashed URL, and `blume preview` 404s it.
36
+ const TRAILING_SLASH = /(?<=.)\/(?=[#?]|$)/u;
37
+
38
+ /**
39
+ * The language whose index Pagefind loads for a page, picked the way its
40
+ * `findIndex` does: the page's own, else its base language (`pt` for
41
+ * `pt-br`), else the language with the most pages (a locale with no
42
+ * translated pages has no index of its own).
43
+ */
44
+ const loadedLanguage = (
45
+ languages: PagefindEntry["languages"],
46
+ language: string
47
+ ): string | undefined => {
48
+ const [base = language] = language.split("-");
49
+ if (languages[language]) {
50
+ return language;
51
+ }
52
+ if (languages[base]) {
53
+ return base;
54
+ }
55
+ return Object.entries(languages).toSorted(
56
+ ([, a], [, b]) => b.page_count - a.page_count
57
+ )[0]?.[0];
58
+ };
59
+
16
60
  /**
17
61
  * Pagefind: load the index emitted into the built site and query it. The bundle
18
62
  * lives in the output (not `node_modules`), so it is imported at runtime by URL
19
63
  * — which is why this only works in the production build, not `dev`.
64
+ *
65
+ * Pagefind keeps an index per language and searches the one for the page's
66
+ * `<html lang>`, which Blume sets to the locale the dialog scopes to. A
67
+ * search scoped to a locale is that index alone; a search across every
68
+ * language (no `locale`) merges every other language's index into it.
20
69
  */
21
70
  export const createSearch = async (opts: {
22
71
  url: string;
@@ -24,25 +73,85 @@ export const createSearch = async (opts: {
24
73
  // The pagefind bundle lives in the built site (not node_modules) and is
25
74
  // resolved at runtime by URL — it can't be a static, code-splittable path.
26
75
  // SAFETY: the URL points at the `pagefind.js` module our own build emitted,
27
- // whose export contract (`search()`) is fixed by pagefind.
76
+ // whose export contract (`createInstance()`) is fixed by pagefind.
28
77
  // oxlint-disable-next-line react-doctor/no-dynamic-import-path
29
78
  const pagefind = (await import(
30
79
  /* @vite-ignore */
31
80
  opts.url
32
81
  )) as PagefindModule;
82
+ // The bundle's folder, which holds the entry file and every language's index.
83
+ const bundle = new URL("./", new URL(opts.url, document.baseURI));
84
+ // `baseUrl` "/" keeps result URLs base-less routes, like every other
85
+ // provider's: by default Pagefind prefixes the folder the bundle is served
86
+ // under, and the dialog adds the deployment base itself.
87
+ const create = (): PagefindInstance =>
88
+ pagefind.createInstance({ basePath: bundle.pathname, baseUrl: "/" });
89
+
90
+ // Every language's index: the page's own, and every other one merged in.
91
+ const everyLanguage = async (language: string): Promise<PagefindInstance> => {
92
+ const instance = create();
93
+ const response = await fetch(new URL("pagefind-entry.json", bundle));
94
+ // SAFETY: the entry file is the one Pagefind wrote into this bundle.
95
+ const { languages } = (await response.json()) as PagefindEntry;
96
+ const own = loadedLanguage(languages, language);
97
+ // Pagefind skips a merge whose path its own `basePath` starts with, as
98
+ // the same index twice, so the bundle is merged by its full URL.
99
+ await Promise.all(
100
+ Object.keys(languages)
101
+ .filter((code) => code !== own)
102
+ .map((code) =>
103
+ instance.mergeIndex(bundle.href, { baseUrl: "/", language: code })
104
+ )
105
+ );
106
+ return instance;
107
+ };
108
+
109
+ // Pagefind reads the page's language once, when an instance is created. A
110
+ // client-side language switch changes the page's language without a
111
+ // reload, so each language gets its own instance, created on its first
112
+ // search: after the switch, search moves to the new page's index. The
113
+ // every-language instance is one for the whole page load.
114
+ const instances = new Map<string, Promise<PagefindInstance>>();
115
+ const cached = (
116
+ key: string,
117
+ load: () => Promise<PagefindInstance>
118
+ ): Promise<PagefindInstance> => {
119
+ const existing = instances.get(key);
120
+ if (existing) {
121
+ return existing;
122
+ }
123
+ const loading = (async () => {
124
+ try {
125
+ return await load();
126
+ } catch (error) {
127
+ // A failed load (the entry file's fetch, say) is forgotten, so the
128
+ // next search retries it.
129
+ instances.delete(key);
130
+ throw error;
131
+ }
132
+ })();
133
+ instances.set(key, loading);
134
+ return loading;
135
+ };
136
+
33
137
  // Pagefind builds its own marked-up excerpt; we keep its `<mark>` highlights
34
138
  // (dropping any other markup — the excerpt is rendered via innerHTML) and
35
139
  // only highlight the title ourselves. It carries no section/breadcrumb data,
36
140
  // so pills stay hidden and the preview pane falls back to the excerpt.
37
- return async (query) => {
38
- const response = await pagefind.search(query);
141
+ return async (query, options) => {
142
+ // Lowercased like Pagefind's own reading, which names the entry's indexes.
143
+ const language = document.documentElement.lang.toLowerCase() || "unknown";
144
+ const instance = await (options?.locale
145
+ ? cached(language, () => Promise.resolve(create()))
146
+ : cached("*", () => everyLanguage(language)));
147
+ const response = await instance.search(query);
39
148
  const docs = await Promise.all(
40
149
  response.results.slice(0, SEARCH_LIMIT).map((result) => result.data())
41
150
  );
42
151
  const hits = docs.map((doc) => ({
43
152
  excerpt: sanitizeExcerpt(doc.excerpt),
44
153
  title: highlight(doc.meta?.title ?? doc.url, query),
45
- url: doc.url,
154
+ url: doc.url.replace(TRAILING_SLASH, ""),
46
155
  }));
47
156
  return { hits, sections: [] };
48
157
  };
@@ -43,6 +43,18 @@ export type SearchFn = (
43
43
  }
44
44
  ) => Promise<SearchResult>;
45
45
 
46
+ /** What a caller tells `createSearch` (`blume:search-client`) about its queries. */
47
+ export interface SearchClientOptions {
48
+ /**
49
+ * The queries come from a reader typing, as in the search dialog, so each
50
+ * one supersedes the last. The server-proxied client (Mixedbread), whose
51
+ * endpoint is rate-limited, then waits for a pause in typing before it
52
+ * sends a query and aborts the one a newer query supersedes. Other
53
+ * providers ignore it.
54
+ */
55
+ typing?: boolean;
56
+ }
57
+
46
58
  /** A document in the client-loaded `blume-search.json` index. */
47
59
  export interface IndexedDocument {
48
60
  route: string;
@@ -17,10 +17,19 @@ interface TypesenseRecord extends Record<string, unknown> {
17
17
  * key. Documents are imported at build time by the sync step. Every adapter
18
18
  * option Blume doesn't read (`connectionTimeoutSeconds`, `numRetries`, …) is
19
19
  * the site's own client option and goes to the `Client` untouched; the named
20
- * connection options still decide `apiKey` and `nodes`.
20
+ * connection options still decide `apiKey` and `nodes`. `locale` is the
21
+ * sync's: Typesense tokenizes a query for the fields it searches.
21
22
  */
22
23
  export const createSearch = (opts: TypesenseOptions): SearchFn => {
23
- const { apiKey, collection, host, port, protocol, ...clientOptions } = opts;
24
+ const {
25
+ apiKey,
26
+ collection,
27
+ host,
28
+ locale: _locale,
29
+ port,
30
+ protocol,
31
+ ...clientOptions
32
+ } = opts;
24
33
  const client = new Client({
25
34
  ...clientOptions,
26
35
  apiKey,
@@ -35,10 +44,13 @@ export const createSearch = (opts: TypesenseOptions): SearchFn => {
35
44
  per_page: SEARCH_LIMIT,
36
45
  q: query,
37
46
  query_by: "title,keywords,description,content",
38
- // Relevance first, in ten bands, then each page's `search.boost`
39
- // within a band: a boost lifts a page past similar matches without
40
- // floating it over clearly better ones.
41
- sort_by: "_text_match(buckets: 10):desc,boost:desc",
47
+ // Relevance first, then each page's `search.boost` among pages that
48
+ // match equally well: a boost settles a tie without floating a page
49
+ // over a better match. Bucketing the relevance instead
50
+ // (`_text_match(buckets: 10)`) ranks by boost alone inside each
51
+ // bucket, so at equal boosts a bucket falls back to indexing order
52
+ // and the best match can land behind weaker ones.
53
+ sort_by: "_text_match:desc,boost:desc",
42
54
  // The sync marks `locale` and `version` as facets so hosted results
43
55
  // scope to the active language and the viewed docs version (the
44
56
  // current docs upload as "current").
@@ -7,11 +7,12 @@ import { asyncApiOperationObject } from "../../openapi/asyncapi.ts";
7
7
  import { operationOf, specOf } from "../../openapi/model.ts";
8
8
  import { highlightCode } from "../../markdown/index.ts";
9
9
  import {
10
- asyncApiSecurityEntries,
10
+ asyncApiSecurity,
11
11
  bindingGroups,
12
12
  channelParameters,
13
- channelServers,
13
+ kafkaTopic,
14
14
  messageLabel,
15
+ namedChannelServers,
15
16
  operationMessages,
16
17
  payloadSchema,
17
18
  protocolOf,
@@ -33,7 +34,6 @@ import PanelTabs from "./PanelTabs.astro";
33
34
  import ParametersTable from "./ParametersTable.astro";
34
35
  import SchemaTable from "./SchemaTable.astro";
35
36
  import type { SecuritySchemeLike } from "./security.ts";
36
- import { resolveAsyncApiSecurity } from "./security.ts";
37
37
  import OperationPanel from "./OperationPanel.astro";
38
38
 
39
39
  /**
@@ -61,9 +61,11 @@ const schemas = (document.components?.schemas ?? {}) as Record<
61
61
  string,
62
62
  SchemaLike
63
63
  >;
64
- const servers = channelServers(channel, document);
65
- const security = resolveAsyncApiSecurity(
66
- asyncApiSecurityEntries(operation, servers),
64
+ const namedServers = namedChannelServers(channel, document);
65
+ const servers = namedServers.map(({ server }) => server);
66
+ const security = asyncApiSecurity(
67
+ operation,
68
+ namedServers,
67
69
  document.components?.securitySchemes as
68
70
  | Record<string, SecuritySchemeLike>
69
71
  | undefined
@@ -96,10 +98,15 @@ const messagePanels = await Promise.all(
96
98
 
97
99
  // One derived model feeds the static default sample, the composer form, and
98
100
  // (client-side) the live samples + WebSocket frame — they can never drift.
101
+ // A Kafka channel binding's `topic` names the real topic when the address
102
+ // doesn't, so the samples use it.
103
+ const topic =
104
+ protocol === "kafka" ? kafkaTopic(channel, document.components) : undefined;
99
105
  const model = ref
100
106
  ? messageModel({
101
107
  action: ref.method === "send" ? "send" : "receive",
102
- address: ref.path,
108
+ address: topic ?? ref.path,
109
+ components: document.components,
103
110
  messages,
104
111
  parameters,
105
112
  protocol,
@@ -28,6 +28,13 @@ const { security } = Astro.props;
28
28
  Optional — this operation also accepts unauthenticated requests.
29
29
  </p>
30
30
  )}
31
+ {security.unauthenticatedServers &&
32
+ security.unauthenticatedServers.length > 0 && (
33
+ <p class="mb-2 text-muted-foreground text-sm">
34
+ No authorization needed on{" "}
35
+ {security.unauthenticatedServers.join(", ")}.
36
+ </p>
37
+ )}
31
38
  {security.alternatives.map((alternative, index) => (
32
39
  <>
33
40
  {index > 0 && (
@@ -47,6 +54,9 @@ const { security } = Astro.props;
47
54
  <span class="text-muted-foreground text-xs">
48
55
  {schemeLabel(resolved)}
49
56
  {carrier ? ` · ${carrier.in}` : ""}
57
+ {resolved.servers
58
+ ? ` · on ${resolved.servers.join(", ")}`
59
+ : ""}
50
60
  </span>
51
61
  {!security.optional && (
52
62
  <span class="font-medium text-[0.625rem] text-red-600 uppercase tracking-wide dark:text-red-400">
@@ -19,10 +19,12 @@ import {
19
19
  exampleVariables,
20
20
  graphqlPlaygroundModel,
21
21
  graphqlRoutes,
22
+ graphqlSecurity,
22
23
  } from "./graphql-helpers.ts";
23
24
  import { buildRequest, defaultValues } from "./request.ts";
24
25
  import { languageSamplePanels } from "./sample-panels.ts";
25
26
  import { sampleLanguages } from "./snippets.ts";
27
+ import Authorization from "./Authorization.astro";
26
28
  import Description from "./Description.astro";
27
29
  import GraphqlChip from "./GraphqlChip.astro";
28
30
  import GraphqlFieldsTable from "./GraphqlFieldsTable.astro";
@@ -107,6 +109,7 @@ const responsePanels =
107
109
  : [];
108
110
 
109
111
  const returnRoute = field ? routes.get(field.type.name) : undefined;
112
+ const security = spec ? graphqlSecurity(spec) : undefined;
110
113
  ---
111
114
 
112
115
  {
@@ -134,6 +137,7 @@ const returnRoute = field ? routes.get(field.type.name) : undefined;
134
137
  Deprecated: <span set:text={field.deprecationReason} />
135
138
  </p>
136
139
  )}
140
+ {security && <Authorization security={security} />}
137
141
  <GraphqlFieldsTable
138
142
  routes={routes}
139
143
  rows={field.args}
@@ -169,6 +173,7 @@ const returnRoute = field ? routes.get(field.type.name) : undefined;
169
173
  model={model}
170
174
  operation={id}
171
175
  proxy={spec.playground.proxy}
176
+ proxyConfig="graphql"
172
177
  slug={spec.slug}
173
178
  />
174
179
  )}
@@ -131,6 +131,10 @@ const params = mergeParameters(
131
131
  const requestBody = operation?.requestBody
132
132
  ? resolveComponentRef(operation.requestBody, components, "requestBodies")
133
133
  : undefined;
134
+ // A request body table leaves out `readOnly` fields. A webhook's or
135
+ // callback's body is a request the API sends, so it reads like a response
136
+ // (and like its payload example): `readOnly` fields in, `writeOnly` out.
137
+ const bodyDirection = isWebhook ? "response" : "request";
134
138
  const responses = Object.fromEntries(
135
139
  Object.entries(operation?.responses ?? {}).map(([status, response]) => [
136
140
  status,
@@ -178,6 +182,8 @@ interface CallbackEntry {
178
182
  method: string;
179
183
  name: string;
180
184
  operation: FullOperation;
185
+ /** The callback path item's own parameters, which each method inherits. */
186
+ pathParameters?: ParameterLike[];
181
187
  }
182
188
 
183
189
  // Callbacks: for each named callback (resolved through
@@ -202,6 +208,10 @@ const callbacks: CallbackEntry[] = Object.entries(
202
208
  // SAFETY: a path item's method entry is an operation object; the
203
209
  // object check above guards a spec that put a scalar there.
204
210
  operation: entry as FullOperation,
211
+ // SAFETY: the object check above passed, and a path item's
212
+ // `parameters` is a parameter list, which `mergeParameters`
213
+ // reads the way it reads an operation's.
214
+ pathParameters: (item as PathItemLike).parameters,
205
215
  },
206
216
  ]
207
217
  : [];
@@ -209,8 +219,13 @@ const callbacks: CallbackEntry[] = Object.entries(
209
219
  });
210
220
  });
211
221
 
212
- /** A callback's request body and responses, `$ref`s resolved. */
222
+ /** A callback's parameters, request body, and responses, `$ref`s resolved. */
213
223
  const callbackParts = (entry: CallbackEntry) => ({
224
+ parameters: mergeParameters(
225
+ entry.pathParameters,
226
+ entry.operation.parameters,
227
+ components
228
+ ),
214
229
  requestBody: entry.operation.requestBody
215
230
  ? resolveComponentRef(entry.operation.requestBody, components, "requestBodies")
216
231
  : undefined,
@@ -253,6 +268,7 @@ const callbackParts = (entry: CallbackEntry) => ({
253
268
  <ParametersTable parameters={params} schemas={schemas} />
254
269
  {requestBody && (
255
270
  <RequestBody
271
+ direction={bodyDirection}
256
272
  expandAll={spec.expandSchemas}
257
273
  requestBody={requestBody}
258
274
  schemas={schemas}
@@ -298,8 +314,14 @@ const callbackParts = (entry: CallbackEntry) => ({
298
314
  text={entry.operation.description}
299
315
  />
300
316
  )}
317
+ <ParametersTable
318
+ level={4}
319
+ parameters={parts.parameters}
320
+ schemas={schemas}
321
+ />
301
322
  {parts.requestBody && (
302
323
  <RequestBody
324
+ direction="response"
303
325
  expandAll={spec.expandSchemas}
304
326
  level={4}
305
327
  requestBody={parts.requestBody}
@@ -339,6 +361,7 @@ const callbackParts = (entry: CallbackEntry) => ({
339
361
  model={model}
340
362
  operation={id}
341
363
  proxy={spec.playground.proxy}
364
+ proxyConfig="openapi"
342
365
  slug={spec.slug}
343
366
  />
344
367
  )}
@@ -2,6 +2,7 @@
2
2
  import Description from "./Description.astro";
3
3
  import {
4
4
  constraints,
5
+ parameterDescription,
5
6
  resolveSchema,
6
7
  type SchemaLike,
7
8
  typeLabel,
@@ -19,9 +20,11 @@ interface ParamLike {
19
20
  interface Props {
20
21
  parameters: ParamLike[];
21
22
  schemas: Record<string, SchemaLike>;
23
+ /** The headings' level: 2 on an operation, deeper inside a callback. */
24
+ level?: number;
22
25
  }
23
26
 
24
- const { parameters, schemas } = Astro.props;
27
+ const { parameters, schemas, level = 2 } = Astro.props;
25
28
 
26
29
  const SECTIONS: { in: string; title: string }[] = [
27
30
  { in: "path", title: "Path parameters" },
@@ -42,7 +45,7 @@ const groups = SECTIONS.map((section) => ({
42
45
  groups.map((group) => (
43
46
  <section class="mt-6">
44
47
  <div
45
- aria-level="2"
48
+ aria-level={level}
46
49
  class="mb-2 font-semibold text-foreground text-sm"
47
50
  role="heading"
48
51
  >
@@ -54,6 +57,7 @@ const groups = SECTIONS.map((section) => ({
54
57
  const type = param.schema ? typeLabel(param.schema) : "string";
55
58
  const limits = constraints(resolved);
56
59
  const enumValues = Array.isArray(resolved.enum) ? resolved.enum : null;
60
+ const description = parameterDescription(param, schemas);
57
61
  return (
58
62
  <div class="border-border border-t py-4 first:border-t-0">
59
63
  <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
@@ -70,8 +74,8 @@ const groups = SECTIONS.map((section) => ({
70
74
  </span>
71
75
  )}
72
76
  </div>
73
- {param.description && (
74
- <Description class="mt-2 text-muted-foreground text-sm" text={param.description} />
77
+ {description && (
78
+ <Description class="mt-2 text-muted-foreground text-sm" text={description} />
75
79
  )}
76
80
  {limits.length > 0 && (
77
81
  <div class="mt-2 text-muted-foreground text-xs">
@@ -17,12 +17,17 @@ interface Props {
17
17
  model: PlaygroundModel;
18
18
  /** Proxy route for the send, or false to fetch the API directly. */
19
19
  proxy: string | false;
20
+ /**
21
+ * Where this panel's proxy is configured, which the CORS message names:
22
+ * an `openapi()` or `graphql()` reference, or `api` for hand-written pages.
23
+ */
24
+ proxyConfig: "api" | "graphql" | "openapi";
20
25
  /** Spec slug + operation key form the per-operation localStorage key. */
21
26
  slug: string;
22
27
  operation: string;
23
28
  }
24
29
 
25
- const { model, proxy, slug, operation } = Astro.props;
30
+ const { model, proxy, proxyConfig, slug, operation } = Astro.props;
26
31
 
27
32
  // The built-in proxy route carries the site `basePath` but, like every
28
33
  // internal URL in the spec data, not `deployment.base`; `withMountedBase`
@@ -53,6 +58,7 @@ const CHECKBOX_ROW =
53
58
  <blume-playground
54
59
  class="mb-6 block"
55
60
  data-proxy={proxyUrl}
61
+ data-proxy-config={proxyConfig}
56
62
  data-storage-key={`blume-playground:${slug}:${operation}`}
57
63
  >
58
64
  <script