blume 2.0.2 → 2.0.3

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 (293) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/dist/cli/{chunk-6k4ftwze.js → chunk-1d7ve1dm.js} +1 -1
  3. package/dist/cli/{chunk-zp79m0ts.js → chunk-2eytanqx.js} +2 -2
  4. package/dist/cli/{chunk-8g8ytmgx.js → chunk-2hsdwb9n.js} +19 -19
  5. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-2hsdwb9n.js.map} +1 -1
  6. package/dist/cli/{chunk-g698a744.js → chunk-2z928egk.js} +5 -5
  7. package/dist/cli/{chunk-ppzjqwx2.js → chunk-35d4wj9f.js} +17 -18
  8. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-35d4wj9f.js.map} +3 -3
  9. package/dist/cli/{chunk-mqc662a6.js → chunk-364znk6q.js} +2 -2
  10. package/dist/cli/{chunk-gs7r695n.js → chunk-3em5wd2y.js} +21 -8
  11. package/dist/cli/{chunk-gs7r695n.js.map → chunk-3em5wd2y.js.map} +3 -3
  12. package/dist/cli/{chunk-k7pj68a8.js → chunk-5f86nr5m.js} +15 -15
  13. package/dist/cli/{chunk-hr8ne106.js → chunk-5m5nmvyq.js} +68 -43
  14. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  15. package/dist/cli/{chunk-91ws1n6j.js → chunk-5xvm6tfj.js} +14 -14
  16. package/dist/cli/{chunk-w4bxdvsa.js → chunk-6dsbexzp.js} +14 -14
  17. package/dist/cli/{chunk-00gs3wqs.js → chunk-8ktnccpt.js} +1 -1
  18. package/dist/cli/{chunk-j85scx15.js → chunk-9t7a85s3.js} +130 -44
  19. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  20. package/dist/cli/{chunk-bbnwccaz.js → chunk-a58773jm.js} +2 -2
  21. package/dist/cli/{chunk-nfcyttvj.js → chunk-acanzt5p.js} +9 -9
  22. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-acanzt5p.js.map} +1 -1
  23. package/dist/cli/{chunk-wdrt2k2v.js → chunk-akbpwfxc.js} +90 -26
  24. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  25. package/dist/cli/{chunk-sqw4ekg1.js → chunk-b07cmahc.js} +2 -2
  26. package/dist/cli/{chunk-xh43dwgw.js → chunk-c8chx29p.js} +42 -29
  27. package/dist/cli/{chunk-xh43dwgw.js.map → chunk-c8chx29p.js.map} +9 -9
  28. package/dist/cli/{chunk-7mbqtmgb.js → chunk-crgn1q09.js} +21 -12
  29. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  30. package/dist/cli/chunk-e04dxsz1.js +39 -0
  31. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  32. package/dist/cli/{chunk-4e9b9ra6.js → chunk-ey84smr6.js} +3 -3
  33. package/dist/cli/{chunk-d1v5rhy0.js → chunk-g4hq16wv.js} +13 -13
  34. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  35. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  36. package/dist/cli/{chunk-pbg5a4s3.js → chunk-hm3vjy5s.js} +64 -45
  37. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  38. package/dist/cli/{chunk-5r8g91qn.js → chunk-j85vccga.js} +306 -107
  39. package/dist/cli/chunk-j85vccga.js.map +36 -0
  40. package/dist/cli/{chunk-n1yg3tj3.js → chunk-p3v96n38.js} +6 -6
  41. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-p3v96n38.js.map} +3 -3
  42. package/dist/cli/{chunk-7vtckvaw.js → chunk-p73c0m7w.js} +14 -14
  43. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-p73c0m7w.js.map} +1 -1
  44. package/dist/cli/{chunk-qkb5a8sa.js → chunk-pehfxfta.js} +3 -3
  45. package/dist/cli/{chunk-v6ya5kcb.js → chunk-pv29h0wf.js} +1223 -978
  46. package/dist/cli/{chunk-v6ya5kcb.js.map → chunk-pv29h0wf.js.map} +47 -46
  47. package/dist/cli/{chunk-6dtt0zfn.js → chunk-q58y5e6a.js} +10 -10
  48. package/dist/cli/{chunk-6dtt0zfn.js.map → chunk-q58y5e6a.js.map} +3 -3
  49. package/dist/cli/{chunk-fsmrqk8a.js → chunk-r20tn01b.js} +1 -1
  50. package/dist/cli/{chunk-bfwp9vp6.js → chunk-r9rcc4w7.js} +7 -7
  51. package/dist/cli/{chunk-h7k3nq3v.js → chunk-tkacnehg.js} +2 -2
  52. package/dist/cli/{chunk-h2ez8dzb.js → chunk-tzmab476.js} +4 -4
  53. package/dist/cli/{chunk-3yce002v.js → chunk-vg9r4eb9.js} +6 -6
  54. package/dist/cli/{chunk-3yce002v.js.map → chunk-vg9r4eb9.js.map} +4 -4
  55. package/dist/cli/{chunk-hqp2ajnh.js → chunk-wrr3j9w9.js} +16 -17
  56. package/dist/cli/{chunk-hqp2ajnh.js.map → chunk-wrr3j9w9.js.map} +7 -7
  57. package/dist/cli/{chunk-ddndchfr.js → chunk-yfyb25rh.js} +38 -26
  58. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  59. package/dist/cli/index.js +20 -18
  60. package/dist/cli/index.js.map +3 -3
  61. package/dist/types/ai/link-headers.d.ts +8 -1
  62. package/dist/types/ai/openapi-components.d.ts +5 -2
  63. package/dist/types/ai/relative-links.d.ts +9 -7
  64. package/dist/types/ai/skills.d.ts +4 -1
  65. package/dist/types/ai/tar.d.ts +1 -3
  66. package/dist/types/analytics/index.d.ts +2 -0
  67. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  68. package/dist/types/analytics/schema.d.ts +14 -0
  69. package/dist/types/astro/integration.d.ts +3 -2
  70. package/dist/types/core/base-path.d.ts +21 -7
  71. package/dist/types/core/config-input.d.ts +9 -7
  72. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  73. package/dist/types/core/heading-markers.d.ts +5 -7
  74. package/dist/types/core/i18n.d.ts +2 -0
  75. package/dist/types/core/last-modified.d.ts +10 -0
  76. package/dist/types/core/meta.d.ts +8 -0
  77. package/dist/types/core/schema.d.ts +7 -0
  78. package/dist/types/core/sources/lower.d.ts +29 -16
  79. package/dist/types/core/sources/normalize.d.ts +13 -1
  80. package/dist/types/core/sources/watch.d.ts +12 -5
  81. package/dist/types/core/standard-schema.d.ts +5 -0
  82. package/dist/types/deploy/artifacts.d.ts +6 -4
  83. package/dist/types/deploy/headers.d.ts +5 -0
  84. package/dist/types/deploy/platforms/types.d.ts +8 -0
  85. package/dist/types/deploy/redirects.d.ts +24 -13
  86. package/dist/types/markdown/directives.d.ts +62 -0
  87. package/dist/types/markdown/features.d.ts +21 -0
  88. package/dist/types/markdown/mdast.d.ts +63 -0
  89. package/dist/types/openapi/asyncapi.d.ts +4 -2
  90. package/docs/02-deployment.mdx +4 -2
  91. package/docs/08-faq.mdx +1 -1
  92. package/docs/advanced/custom-pages.mdx +3 -3
  93. package/docs/cli/audit.mdx +17 -1
  94. package/docs/cli/evals.mdx +3 -3
  95. package/docs/cli/translate.mdx +3 -3
  96. package/docs/cli/version.mdx +1 -1
  97. package/docs/configuration/analytics.mdx +20 -1
  98. package/docs/configuration/customization.mdx +2 -0
  99. package/docs/configuration/search.mdx +1 -1
  100. package/docs/content/components.mdx +1 -1
  101. package/docs/content/frontmatter.mdx +1 -1
  102. package/docs/content/i18n.mdx +2 -0
  103. package/docs/content/index.mdx +4 -2
  104. package/docs/content/islands.mdx +1 -1
  105. package/docs/content/meta.mdx +3 -1
  106. package/docs/content/sources.mdx +1 -5
  107. package/docs/content/syntax.mdx +14 -0
  108. package/docs/content/versioning.mdx +1 -1
  109. package/docs/discoverability/agent-discovery.mdx +1 -1
  110. package/docs/discoverability/index.mdx +2 -1
  111. package/docs/discoverability/markdown.mdx +2 -2
  112. package/docs/discoverability/open-graph.mdx +5 -3
  113. package/docs/discoverability/rss.mdx +1 -1
  114. package/docs/references/asyncapi.mdx +1 -1
  115. package/docs/references/graphql.mdx +1 -1
  116. package/docs/references/openapi.mdx +5 -3
  117. package/package.json +1 -1
  118. package/skills/blume-migrate/references/fumadocs.md +1 -1
  119. package/skills/blume-migrate/references/nextra.md +1 -1
  120. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +7 -2
  121. package/src/ai/agent-readability.ts +2 -2
  122. package/src/ai/ai-catalog.ts +6 -2
  123. package/src/ai/api/handlers.ts +2 -2
  124. package/src/ai/api/spec.ts +2 -2
  125. package/src/ai/api-catalog.ts +6 -2
  126. package/src/ai/changelog-markdown.ts +2 -2
  127. package/src/ai/link-headers.ts +14 -5
  128. package/src/ai/llms.ts +18 -7
  129. package/src/ai/mcp/discovery.ts +2 -2
  130. package/src/ai/mcp/query.ts +2 -2
  131. package/src/ai/mcp/server.ts +2 -2
  132. package/src/ai/openapi-components.ts +22 -6
  133. package/src/ai/relative-links.ts +42 -26
  134. package/src/ai/serializers.ts +2 -1
  135. package/src/ai/skills.ts +14 -1
  136. package/src/ai/tar.ts +139 -12
  137. package/src/analytics/head.ts +4 -0
  138. package/src/analytics/index.ts +5 -0
  139. package/src/analytics/one-dollar-stats.ts +86 -0
  140. package/src/analytics/schema.ts +2 -0
  141. package/src/astro/generate.ts +1 -1
  142. package/src/astro/include-hmr.ts +31 -12
  143. package/src/astro/include-refresh.ts +19 -8
  144. package/src/astro/integration.ts +108 -14
  145. package/src/astro/runtime-modules.ts +28 -13
  146. package/src/astro/templates.ts +81 -37
  147. package/src/audit/checks/links.ts +8 -1
  148. package/src/audit/graph.ts +3 -1
  149. package/src/audit/run.ts +12 -9
  150. package/src/audit/snapshot.ts +5 -0
  151. package/src/cli/args.ts +32 -0
  152. package/src/cli/commands/audit.ts +4 -1
  153. package/src/cli/commands/doctor.ts +3 -1
  154. package/src/cli/commands/eval.ts +19 -11
  155. package/src/cli/commands/translate.ts +9 -12
  156. package/src/cli/commands/validate.ts +3 -1
  157. package/src/cli/dev-lock.ts +157 -37
  158. package/src/cli/report-format.ts +11 -6
  159. package/src/components/colors.ts +19 -0
  160. package/src/components/content/Badge.astro +5 -3
  161. package/src/components/content/Component.astro +2 -2
  162. package/src/components/content/Tab.astro +0 -1
  163. package/src/components/content/Tabs.astro +4 -0
  164. package/src/components/content/badge-color.ts +5 -2
  165. package/src/components/content/base-href.ts +13 -36
  166. package/src/components/islands/assistant.tsx +21 -4
  167. package/src/components/islands/base-path.ts +47 -10
  168. package/src/components/islands/hooks.ts +35 -19
  169. package/src/components/islands/webmcp.ts +4 -2
  170. package/src/components/layout/Breadcrumbs.astro +5 -2
  171. package/src/components/layout/DiscoveryLinks.astro +9 -5
  172. package/src/components/layout/Header.astro +2 -2
  173. package/src/components/layout/LanguageSwitcher.astro +2 -2
  174. package/src/components/layout/NavSelector.astro +2 -2
  175. package/src/components/layout/NavTabMenu.astro +3 -3
  176. package/src/components/layout/NavTree.astro +16 -6
  177. package/src/components/layout/PageActions.astro +4 -3
  178. package/src/components/layout/PageLayout.astro +7 -6
  179. package/src/components/layout/Pagination.astro +3 -3
  180. package/src/components/layout/RootLayout.astro +5 -5
  181. package/src/components/layout/Search.astro +33 -13
  182. package/src/components/layout/VersionBanner.astro +2 -2
  183. package/src/components/layout/analytics-client.ts +12 -0
  184. package/src/components/layout/toc-active.ts +41 -0
  185. package/src/components/layout/toc-element.ts +8 -14
  186. package/src/components/openapi/ApiTagOperations.astro +2 -2
  187. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  188. package/src/components/openapi/GraphqlChip.astro +2 -2
  189. package/src/components/openapi/Operation.astro +2 -0
  190. package/src/components/openapi/Playground.astro +4 -4
  191. package/src/components/openapi/RequestPanel.astro +5 -2
  192. package/src/components/openapi/SchemaTable.astro +7 -0
  193. package/src/components/openapi/async.ts +38 -6
  194. package/src/components/openapi/helpers.ts +19 -9
  195. package/src/components/openapi/message-composer.ts +6 -1
  196. package/src/components/openapi/message.ts +17 -2
  197. package/src/components/openapi/operation-model.ts +81 -11
  198. package/src/components/openapi/panel.ts +4 -2
  199. package/src/components/openapi/param-style.ts +181 -0
  200. package/src/components/openapi/playground-client.ts +73 -16
  201. package/src/components/openapi/request.ts +190 -26
  202. package/src/components/openapi/schema-tree.ts +30 -24
  203. package/src/components/openapi/snippets.ts +85 -6
  204. package/src/components/openapi/ws-client.ts +18 -2
  205. package/src/core/base-path.ts +62 -16
  206. package/src/core/config-input.ts +9 -7
  207. package/src/core/diagnostics.ts +2 -0
  208. package/src/core/directive-diagnostics.ts +99 -0
  209. package/src/core/frontmatter.ts +21 -18
  210. package/src/core/heading-markers.ts +5 -18
  211. package/src/core/i18n.ts +31 -3
  212. package/src/core/last-modified.ts +25 -3
  213. package/src/core/locale-links.ts +5 -1
  214. package/src/core/meta.ts +33 -19
  215. package/src/core/navigation.ts +28 -8
  216. package/src/core/project-graph.ts +27 -5
  217. package/src/core/schema.ts +24 -4
  218. package/src/core/sources/contentful-rich-text.ts +27 -20
  219. package/src/core/sources/filesystem.ts +20 -2
  220. package/src/core/sources/github-releases.ts +25 -6
  221. package/src/core/sources/lexical.ts +23 -18
  222. package/src/core/sources/lower.ts +201 -34
  223. package/src/core/sources/mdx-remote.ts +51 -16
  224. package/src/core/sources/normalize.ts +325 -90
  225. package/src/core/sources/notion.ts +52 -25
  226. package/src/core/sources/obsidian.ts +23 -6
  227. package/src/core/sources/portable-text.ts +38 -21
  228. package/src/core/sources/strapi-blocks.ts +20 -16
  229. package/src/core/sources/watch.ts +20 -7
  230. package/src/core/standard-schema.ts +10 -6
  231. package/src/core/version-cut.ts +52 -15
  232. package/src/deploy/artifacts.ts +27 -6
  233. package/src/deploy/cloudflare-negotiation.ts +4 -12
  234. package/src/deploy/headers.ts +8 -4
  235. package/src/deploy/node-headers.ts +1 -1
  236. package/src/deploy/platforms/cloudflare.ts +12 -3
  237. package/src/deploy/platforms/netlify.ts +1 -0
  238. package/src/deploy/platforms/node.ts +1 -0
  239. package/src/deploy/platforms/static.ts +1 -0
  240. package/src/deploy/platforms/types.ts +8 -0
  241. package/src/deploy/platforms/vercel.ts +1 -0
  242. package/src/deploy/redirects.ts +54 -18
  243. package/src/deploy/robots.ts +2 -2
  244. package/src/deploy/rss.ts +4 -3
  245. package/src/deploy/sitemap.ts +7 -5
  246. package/src/markdown/base-links.ts +34 -36
  247. package/src/markdown/directives.ts +242 -36
  248. package/src/markdown/features.ts +17 -0
  249. package/src/markdown/index.ts +7 -6
  250. package/src/markdown/mdast.ts +5 -2
  251. package/src/markdown/relative-links.ts +12 -4
  252. package/src/og/card.ts +129 -5
  253. package/src/og/derive.ts +41 -31
  254. package/src/og/index.ts +1 -0
  255. package/src/openapi/asyncapi.ts +4 -2
  256. package/src/openapi/model.ts +22 -14
  257. package/src/openapi/proxy.ts +63 -10
  258. package/src/openapi/render-mdx.ts +41 -2
  259. package/src/registry/eject.ts +181 -24
  260. package/src/search/adapters/version-scope.ts +30 -0
  261. package/src/search/documents.ts +67 -26
  262. package/src/search/popular.ts +2 -1
  263. package/src/seo/jsonld.ts +7 -3
  264. package/src/translate/meta.ts +68 -24
  265. package/src/translate/run.ts +3 -3
  266. package/src/translate/validate.ts +4 -1
  267. package/src/translate/work-list.ts +56 -17
  268. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  269. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  270. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  271. package/dist/cli/chunk-esh98wmb.js +0 -23
  272. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  273. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  274. package/dist/cli/chunk-j85scx15.js.map +0 -10
  275. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  276. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  277. /package/dist/cli/{chunk-6k4ftwze.js.map → chunk-1d7ve1dm.js.map} +0 -0
  278. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-2eytanqx.js.map} +0 -0
  279. /package/dist/cli/{chunk-g698a744.js.map → chunk-2z928egk.js.map} +0 -0
  280. /package/dist/cli/{chunk-mqc662a6.js.map → chunk-364znk6q.js.map} +0 -0
  281. /package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5f86nr5m.js.map} +0 -0
  282. /package/dist/cli/{chunk-91ws1n6j.js.map → chunk-5xvm6tfj.js.map} +0 -0
  283. /package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-6dsbexzp.js.map} +0 -0
  284. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-8ktnccpt.js.map} +0 -0
  285. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-a58773jm.js.map} +0 -0
  286. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-b07cmahc.js.map} +0 -0
  287. /package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-ey84smr6.js.map} +0 -0
  288. /package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-g4hq16wv.js.map} +0 -0
  289. /package/dist/cli/{chunk-qkb5a8sa.js.map → chunk-pehfxfta.js.map} +0 -0
  290. /package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-r20tn01b.js.map} +0 -0
  291. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-r9rcc4w7.js.map} +0 -0
  292. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-tkacnehg.js.map} +0 -0
  293. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-tzmab476.js.map} +0 -0
@@ -6,14 +6,21 @@ import {
6
6
  resolveSchema,
7
7
  toJson,
8
8
  } from "./helpers.ts";
9
- import type { ParameterLike, SchemaLike, SpecValue } from "./helpers.ts";
9
+ import type {
10
+ ComponentsLike,
11
+ ParameterLike,
12
+ SchemaLike,
13
+ SpecValue,
14
+ } from "./helpers.ts";
10
15
  import {
11
16
  declaredTypes,
12
17
  inputValue,
13
18
  scalarType,
14
19
  validationSchema,
15
20
  } from "./playground-schema.ts";
21
+ import { bodyEncoding } from "./request.ts";
16
22
  import type {
23
+ ParamSerialization,
17
24
  PlaygroundAuthInput,
18
25
  PlaygroundBody,
19
26
  PlaygroundBodyField,
@@ -34,6 +41,7 @@ interface MediaTypeLike {
34
41
  schema?: SchemaLike;
35
42
  example?: SpecValue;
36
43
  examples?: SpecValue;
44
+ encoding?: SpecValue;
37
45
  }
38
46
 
39
47
  /** A server entry: its URL template and the variables that fill it. */
@@ -70,6 +78,45 @@ const isExampleObject = (
70
78
  ): value is Record<string, SpecValue> =>
71
79
  typeof value === "object" && value !== null && !Array.isArray(value);
72
80
 
81
+ const isString = (value: SpecValue): value is string =>
82
+ typeof value === "string";
83
+
84
+ const isBoolean = (value: SpecValue): value is boolean =>
85
+ typeof value === "boolean";
86
+
87
+ /**
88
+ * The `style`/`explode` a parameter or form-body encoding declares, kept only
89
+ * when declared so the embedded model stays small; the request builder
90
+ * applies the OpenAPI defaults for the rest.
91
+ */
92
+ const serialization = (node: Record<string, SpecValue>): ParamSerialization => {
93
+ const rule: ParamSerialization = {};
94
+ if (isString(node.style)) {
95
+ rule.style = node.style;
96
+ }
97
+ if (isBoolean(node.explode)) {
98
+ rule.explode = node.explode;
99
+ }
100
+ return rule;
101
+ };
102
+
103
+ /** A form body's declared per-field `encoding`, when it names any style. */
104
+ const formEncoding = (
105
+ encoding: SpecValue
106
+ ): PlaygroundBody["encoding"] | undefined => {
107
+ if (!isExampleObject(encoding)) {
108
+ return undefined;
109
+ }
110
+ const fields: NonNullable<PlaygroundBody["encoding"]> = {};
111
+ for (const [name, entry] of Object.entries(encoding)) {
112
+ const rule = isExampleObject(entry) ? serialization(entry) : {};
113
+ if (Object.keys(rule).length > 0) {
114
+ fields[name] = rule;
115
+ }
116
+ }
117
+ return Object.keys(fields).length > 0 ? fields : undefined;
118
+ };
119
+
73
120
  /**
74
121
  * Playground inputs for the operation's parameters. Cookie params are skipped
75
122
  * (not supported in v1 — browsers won't let a page set arbitrary cookies).
@@ -80,7 +127,8 @@ const isExampleObject = (
80
127
  */
81
128
  const modelParams = (
82
129
  parameters: ParameterLike[],
83
- schemas: Record<string, SchemaLike>
130
+ schemas: Record<string, SchemaLike>,
131
+ components: ComponentsLike | undefined
84
132
  ): PlaygroundParam[] => {
85
133
  const params: PlaygroundParam[] = [];
86
134
  for (const param of parameters) {
@@ -102,9 +150,11 @@ const modelParams = (
102
150
  type: scalarType(param.schema, schemas),
103
151
  value: required
104
152
  ? inputValue(
105
- declaredExample(param) ?? exampleValue(param.schema, schemas)
153
+ declaredExample(param, components) ??
154
+ exampleValue(param.schema, schemas)
106
155
  )
107
156
  : "",
157
+ ...serialization(param),
108
158
  });
109
159
  }
110
160
  return params;
@@ -165,10 +215,16 @@ const bodyFields = (
165
215
  return fields;
166
216
  };
167
217
 
168
- /** The playground's body editor state, when the operation takes a request body. */
218
+ /**
219
+ * The playground's body editor state, when the operation takes a request body.
220
+ * JSON, form-urlencoded, and multipart bodies are edited as JSON (typed fields
221
+ * when flat) and serialized for their media type on send; any other media
222
+ * type is raw text, prefilled with a string example as written.
223
+ */
169
224
  const modelBody = (
170
225
  requestBody: { content?: Record<string, MediaTypeLike> } | undefined,
171
- schemas: Record<string, SchemaLike>
226
+ schemas: Record<string, SchemaLike>,
227
+ components: ComponentsLike | undefined
172
228
  ): PlaygroundBody | undefined => {
173
229
  const media = jsonContentType(requestBody?.content);
174
230
  if (!media) {
@@ -176,13 +232,25 @@ const modelBody = (
176
232
  }
177
233
  const [contentType, mediaType] = media;
178
234
  const exampleData =
179
- declaredExample(mediaType) ?? exampleValue(mediaType.schema, schemas);
180
- return {
235
+ declaredExample(mediaType, components) ??
236
+ exampleValue(mediaType.schema, schemas);
237
+ const encoding = bodyEncoding(contentType);
238
+ const raw = encoding === "raw";
239
+ const body: PlaygroundBody = {
181
240
  contentType,
182
- example: toJson(exampleData) ?? "",
183
- fields: bodyFields(mediaType.schema, schemas, exampleData),
241
+ example:
242
+ raw && isString(exampleData) ? exampleData : (toJson(exampleData) ?? ""),
243
+ fields: raw
244
+ ? undefined
245
+ : bodyFields(mediaType.schema, schemas, exampleData),
184
246
  schema: validationSchema(mediaType.schema, schemas),
185
247
  };
248
+ const fieldEncoding =
249
+ encoding === "form" ? formEncoding(mediaType.encoding) : undefined;
250
+ if (fieldEncoding) {
251
+ body.encoding = fieldEncoding;
252
+ }
253
+ return body;
186
254
  };
187
255
 
188
256
  const AUTHORIZATION_HEADER = { in: "header", name: "Authorization" } as const;
@@ -272,6 +340,8 @@ export const operationModel = (args: {
272
340
  /** The operation's effective servers (`effectiveServers` output). */
273
341
  servers: ServerLike[];
274
342
  schemas: Record<string, SchemaLike>;
343
+ /** The document's `components`, which `$ref`'d examples resolve against. */
344
+ components?: ComponentsLike;
275
345
  security: OperationSecurity;
276
346
  }): PlaygroundModel => ({
277
347
  // First alternative only — the spec's preferred way to authorize, matching
@@ -281,9 +351,9 @@ export const operationModel = (args: {
281
351
  return input ? [input] : [];
282
352
  }),
283
353
  authOptional: args.security.optional,
284
- body: modelBody(args.requestBody, args.schemas),
354
+ body: modelBody(args.requestBody, args.schemas, args.components),
285
355
  method: args.method.toUpperCase(),
286
- params: modelParams(args.parameters, args.schemas),
356
+ params: modelParams(args.parameters, args.schemas, args.components),
287
357
  path: args.path,
288
358
  // Variables resolve to their defaults: the samples and Send need a real URL.
289
359
  servers: args.servers.map((server) =>
@@ -40,10 +40,12 @@ class BlumePanelTabs extends HTMLElement {
40
40
  // wrap, Home and End jump to the ends, and focus follows selection.
41
41
  tab.addEventListener("keydown", (event) => {
42
42
  const last = tabs.length - 1;
43
+ // The strip runs right to left under dir="rtl", so the arrows swap.
44
+ const rtl = getComputedStyle(this).direction === "rtl";
43
45
  let next: number | undefined;
44
- if (event.key === "ArrowRight") {
46
+ if (event.key === (rtl ? "ArrowLeft" : "ArrowRight")) {
45
47
  next = index === last ? 0 : index + 1;
46
- } else if (event.key === "ArrowLeft") {
48
+ } else if (event.key === (rtl ? "ArrowRight" : "ArrowLeft")) {
47
49
  next = index === 0 ? last : index - 1;
48
50
  } else if (event.key === "Home") {
49
51
  next = 0;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * OpenAPI 3 parameter serialization — `style` and `explode` — for the
3
+ * playground's one request builder (`request.ts`). An array or object value
4
+ * has a different wire shape per style: `tags: ["dog", "cat"]` is
5
+ * `tags=dog&tags=cat` in a query (form, exploded), `dog,cat` in a path
6
+ * (simple), and `filter: { color: "red" }` is `filter[color]=red` as a
7
+ * deepObject. Ships in the client bundle with `request.ts`, so it stays
8
+ * dependency-free.
9
+ */
10
+
11
+ /** Parsed JSON: everything `JSON.parse` can produce. */
12
+ export type JsonValue =
13
+ | string
14
+ | number
15
+ | boolean
16
+ | null
17
+ | JsonValue[]
18
+ | { [key: string]: JsonValue };
19
+
20
+ /** A value lowered for serialization: a scalar, a list's items, or an object's entries. */
21
+ export type StyledValue =
22
+ | { kind: "scalar"; text: string }
23
+ | { kind: "list"; items: string[] }
24
+ | { kind: "object"; entries: [string, string][] };
25
+
26
+ // `typeof` checks live in named predicates (the form the oxlint anti-slop
27
+ // config sanctions).
28
+ const isString = (value: JsonValue): value is string =>
29
+ typeof value === "string";
30
+
31
+ /** Whether a parsed value is a plain JSON object. */
32
+ export const isJsonObject = (
33
+ value: JsonValue
34
+ ): value is { [key: string]: JsonValue } =>
35
+ typeof value === "object" && value !== null && !Array.isArray(value);
36
+
37
+ /** One member as wire text: a string verbatim, anything else as its JSON. */
38
+ export const memberText = (value: JsonValue): string =>
39
+ isString(value) ? value : JSON.stringify(value);
40
+
41
+ /** A parsed value, lowered: arrays to items, objects to entries, the rest to text. */
42
+ export const styledValue = (value: JsonValue): StyledValue => {
43
+ if (Array.isArray(value)) {
44
+ return { items: value.map(memberText), kind: "list" };
45
+ }
46
+ if (isJsonObject(value)) {
47
+ return {
48
+ entries: Object.entries(value).map(([key, member]) => [
49
+ key,
50
+ memberText(member),
51
+ ]),
52
+ kind: "object",
53
+ };
54
+ }
55
+ return { kind: "scalar", text: memberText(value) };
56
+ };
57
+
58
+ /**
59
+ * A playground input's text as a styled value. Only an array- or object-typed
60
+ * parameter reads its text as JSON — its prefill is the example's JSON
61
+ * (`["dog","cat"]`) — and only when the text parses to that shape; anything
62
+ * else is sent as the one scalar it reads as.
63
+ */
64
+ export const parseStyledValue = (text: string, type: string): StyledValue => {
65
+ if (type === "array" || type === "object") {
66
+ let parsed: JsonValue;
67
+ try {
68
+ parsed = JSON.parse(text);
69
+ } catch {
70
+ return { kind: "scalar", text };
71
+ }
72
+ if (type === "array" ? Array.isArray(parsed) : isJsonObject(parsed)) {
73
+ return styledValue(parsed);
74
+ }
75
+ }
76
+ return { kind: "scalar", text };
77
+ };
78
+
79
+ /** The OpenAPI 3 default style: `form` in a query or cookie, `simple` elsewhere. */
80
+ export const defaultStyle = (location: string): string =>
81
+ location === "query" || location === "cookie" ? "form" : "simple";
82
+
83
+ /** `explode` when the spec leaves it out: true for `form`, false for the rest. */
84
+ export const defaultExplode = (style: string): boolean => style === "form";
85
+
86
+ /**
87
+ * A list or object joined into one value: exploded object entries as
88
+ * `key=value` separated by `separator`, exploded items by `separator`, and
89
+ * the unexploded forms comma-separated (`a,b`, `key,value,key2,value2`).
90
+ */
91
+ const joined = (
92
+ value: StyledValue,
93
+ explode: boolean,
94
+ encode: (text: string) => string,
95
+ separator: string
96
+ ): string => {
97
+ if (value.kind === "scalar") {
98
+ return encode(value.text);
99
+ }
100
+ if (value.kind === "list") {
101
+ return value.items.map(encode).join(explode ? separator : ",");
102
+ }
103
+ return explode
104
+ ? value.entries
105
+ .map(([key, member]) => `${encode(key)}=${encode(member)}`)
106
+ .join(separator)
107
+ : value.entries.flat().map(encode).join(",");
108
+ };
109
+
110
+ /**
111
+ * A query parameter, or an `application/x-www-form-urlencoded` body field, as
112
+ * its percent-encoded `name=value` pairs in order. Exploded lists repeat the
113
+ * name (`tags=dog&tags=cat`) and exploded objects spread their entries
114
+ * (`color=red&size=L`) whatever the style; unexploded ones join with the
115
+ * style's delimiter — `,` for form, `%20` for spaceDelimited, `|` for
116
+ * pipeDelimited. deepObject nests entries under the name (`filter[color]=red`).
117
+ */
118
+ export const queryPairs = (
119
+ name: string,
120
+ value: StyledValue,
121
+ style: string,
122
+ explode: boolean
123
+ ): string[] => {
124
+ const key = encodeURIComponent(name);
125
+ if (value.kind === "object" && style === "deepObject") {
126
+ return value.entries.map(
127
+ ([entry, member]) =>
128
+ `${key}[${encodeURIComponent(entry)}]=${encodeURIComponent(member)}`
129
+ );
130
+ }
131
+ if (value.kind === "list" && explode) {
132
+ return value.items.map((item) => `${key}=${encodeURIComponent(item)}`);
133
+ }
134
+ if (value.kind === "object" && explode) {
135
+ return value.entries.map(
136
+ ([entry, member]) =>
137
+ `${encodeURIComponent(entry)}=${encodeURIComponent(member)}`
138
+ );
139
+ }
140
+ let delimiter = ",";
141
+ if (style === "spaceDelimited") {
142
+ delimiter = "%20";
143
+ } else if (style === "pipeDelimited") {
144
+ delimiter = "|";
145
+ }
146
+ const text =
147
+ value.kind === "scalar"
148
+ ? encodeURIComponent(value.text)
149
+ : (value.kind === "list" ? value.items : value.entries.flat())
150
+ .map(encodeURIComponent)
151
+ .join(delimiter);
152
+ return [`${key}=${text}`];
153
+ };
154
+
155
+ /**
156
+ * A path or header parameter's value: `simple` (`dog,cat`), or one of the
157
+ * path-only `label` (`.dog.cat`) and `matrix` (`;tags=dog;tags=cat`) styles.
158
+ * `encode` percent-encodes a path segment; a header passes values through.
159
+ */
160
+ export const templateValue = (
161
+ name: string,
162
+ value: StyledValue,
163
+ style: string,
164
+ explode: boolean,
165
+ encode: (text: string) => string
166
+ ): string => {
167
+ if (style === "label") {
168
+ return `.${joined(value, explode, encode, ".")}`;
169
+ }
170
+ if (style !== "matrix") {
171
+ return joined(value, explode, encode, ",");
172
+ }
173
+ const key = encode(name);
174
+ if (value.kind === "list" && explode) {
175
+ return value.items.map((item) => `;${key}=${encode(item)}`).join("");
176
+ }
177
+ if (value.kind === "object" && explode) {
178
+ return `;${joined(value, true, encode, ";")}`;
179
+ }
180
+ return `;${key}=${joined(value, false, encode, ",")}`;
181
+ };
@@ -16,8 +16,14 @@ import type {
16
16
  PlaygroundModel,
17
17
  RequestValues,
18
18
  } from "./request.ts";
19
- import { buildRequest, redactAuth } from "./request.ts";
20
- import { sampleLanguages } from "./snippets.ts";
19
+ import {
20
+ bodyEncoding,
21
+ buildRequest,
22
+ PROXY_HEADERS_HEADER,
23
+ redactAuth,
24
+ } from "./request.ts";
25
+ import { fetchRefusesMethod, sampleLanguages } from "./snippets.ts";
26
+ import type { RequestSample } from "./snippets.ts";
21
27
  import { validateJson } from "./validate-json.ts";
22
28
 
23
29
  /**
@@ -42,6 +48,16 @@ const COOKIE_MESSAGE =
42
48
  "send the cookie credential you entered. Copy the sample above and run it " +
43
49
  "from a terminal instead.";
44
50
 
51
+ /**
52
+ * fetch refuses the TRACE method outright, proxied or not: the browser throws
53
+ * before anything is sent, which the send's catch would misdiagnose as the
54
+ * CORS wall or an unreachable host. The cURL and Python samples can send it.
55
+ */
56
+ const TRACE_MESSAGE =
57
+ "Browsers don't allow a page to send a `TRACE` request, so this panel " +
58
+ "can't send it. Run the cURL or Python sample above from a terminal " +
59
+ "instead.";
60
+
45
61
  /**
46
62
  * A fetch that fails before any response when the API couldn't be reached at
47
63
  * all — a mistyped host, a refused connection, no network — rather than
@@ -185,6 +201,50 @@ const removeStored = (key: string): void => {
185
201
  }
186
202
  };
187
203
 
204
+ /**
205
+ * The headers a live send carries. A `Cookie` header can still arrive via a
206
+ * spec-declared header parameter; the browser would silently drop the
207
+ * forbidden name anyway, so it is stripped rather than earn a console
208
+ * warning. Blume's own proxy — always a path on this site — forwards only the
209
+ * headers named in {@link PROXY_HEADERS_HEADER}, never ones the browser or
210
+ * the platform add on their own; a multipart body's Content-Type is fetch's,
211
+ * written with the boundary, so it is named without being set. An external
212
+ * proxy on another origin gets no such header: it would only be one more name
213
+ * for its preflight to allow.
214
+ */
215
+ const sendHeaders = (sample: RequestSample, proxy: string) => {
216
+ const headers = { ...sample.headers };
217
+ delete headers.Cookie;
218
+ if (proxy.startsWith("/") && !proxy.startsWith("//")) {
219
+ const names = Object.keys(headers);
220
+ if (sample.formData) {
221
+ names.push("Content-Type");
222
+ }
223
+ headers[PROXY_HEADERS_HEADER] = names.join(", ");
224
+ }
225
+ return headers;
226
+ };
227
+
228
+ /**
229
+ * The body a live send carries: multipart parts as `FormData`, anything else
230
+ * as its text. fetch throws a synchronous TypeError for a GET/HEAD with a
231
+ * body — which the send's catch would mislabel as CORS — so a spec that
232
+ * declares a GET requestBody keeps its samples, but the live send drops it.
233
+ */
234
+ const sendBody = (sample: RequestSample): string | FormData | undefined => {
235
+ if (sample.method === "GET" || sample.method === "HEAD") {
236
+ return undefined;
237
+ }
238
+ if (!sample.formData) {
239
+ return sample.body;
240
+ }
241
+ const form = new FormData();
242
+ for (const [name, value] of sample.formData) {
243
+ form.append(name, value);
244
+ }
245
+ return form;
246
+ };
247
+
188
248
  /** A one-line text element for the response/error regions. */
189
249
  const line = (className: string, text: string): HTMLElement => {
190
250
  const el = document.createElement("div");
@@ -332,9 +392,11 @@ export const initPlayground = (root: HTMLElement): void => {
332
392
  }
333
393
  // An emptied editor means "no body" (see `bodyFor`), not invalid JSON —
334
394
  // reporting a syntax error there would block a send the request builder is
335
- // perfectly happy to make.
395
+ // perfectly happy to make. A raw media type (`text/plain`, XML) isn't
396
+ // JSON at all, so it isn't checked as JSON either.
397
+ const raw = bodyEncoding(model.body?.contentType ?? "") === "raw";
336
398
  const errors =
337
- bodyArea.value.trim() === ""
399
+ raw || bodyArea.value.trim() === ""
338
400
  ? []
339
401
  : validateJson(bodyArea.value, model.body?.schema);
340
402
  bodyErrors.textContent = "";
@@ -453,6 +515,11 @@ export const initPlayground = (root: HTMLElement): void => {
453
515
  if (!response || sending) {
454
516
  return;
455
517
  }
518
+ if (fetchRefusesMethod(model.method)) {
519
+ response.textContent = "";
520
+ response.append(line(ERROR_TEXT, TRACE_MESSAGE));
521
+ return;
522
+ }
456
523
  if (validateBody().length > 0) {
457
524
  return;
458
525
  }
@@ -494,11 +561,7 @@ export const initPlayground = (root: HTMLElement): void => {
494
561
  sample.url
495
562
  )}`
496
563
  : sample.url;
497
- // A `Cookie` header can still arrive via a spec-declared header parameter;
498
- // the browser would silently drop the forbidden name anyway, so strip it
499
- // rather than earn a console warning.
500
- const headers = { ...sample.headers };
501
- delete headers.Cookie;
564
+ const headers = sendHeaders(sample, proxy);
502
565
  // fetch surfaces an invalid URL or header value as the same TypeError a
503
566
  // CORS rejection produces, and the catch below would misdiagnose it as the
504
567
  // CORS wall. Both are validated here, where the real error can be shown,
@@ -526,13 +589,7 @@ export const initPlayground = (root: HTMLElement): void => {
526
589
  const start = performance.now();
527
590
  try {
528
591
  const res = await fetch(url, {
529
- // fetch throws a synchronous TypeError for a GET/HEAD with a body —
530
- // which the catch below would mislabel as CORS. A spec that declares
531
- // a GET requestBody keeps its samples, but the live send drops it.
532
- body:
533
- sample.method === "GET" || sample.method === "HEAD"
534
- ? undefined
535
- : sample.body,
592
+ body: sendBody(sample),
536
593
  headers,
537
594
  method: sample.method,
538
595
  // A `TimeoutError` DOMException is not a TypeError, so it reads as a