vocs 2.0.17 → 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 (306) hide show
  1. package/dist/config.d.ts +1 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +1 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/globals.d.ts +16 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/config.d.ts +28 -0
  11. package/dist/internal/config.d.ts.map +1 -1
  12. package/dist/internal/config.js +10 -2
  13. package/dist/internal/config.js.map +1 -1
  14. package/dist/internal/llms.d.ts +21 -2
  15. package/dist/internal/llms.d.ts.map +1 -1
  16. package/dist/internal/llms.js +41 -1
  17. package/dist/internal/llms.js.map +1 -1
  18. package/dist/internal/markdown-negotiation.d.ts +36 -0
  19. package/dist/internal/markdown-negotiation.d.ts.map +1 -0
  20. package/dist/internal/markdown-negotiation.js +81 -0
  21. package/dist/internal/markdown-negotiation.js.map +1 -0
  22. package/dist/internal/markdown.d.ts.map +1 -1
  23. package/dist/internal/markdown.js +5 -0
  24. package/dist/internal/markdown.js.map +1 -1
  25. package/dist/internal/openapi/anchors.d.ts +25 -0
  26. package/dist/internal/openapi/anchors.d.ts.map +1 -0
  27. package/dist/internal/openapi/anchors.js +37 -0
  28. package/dist/internal/openapi/anchors.js.map +1 -0
  29. package/dist/internal/openapi/app.d.ts +89 -0
  30. package/dist/internal/openapi/app.d.ts.map +1 -0
  31. package/dist/internal/openapi/app.js +62 -0
  32. package/dist/internal/openapi/app.js.map +1 -0
  33. package/dist/internal/openapi/index.d.ts +8 -0
  34. package/dist/internal/openapi/index.d.ts.map +1 -0
  35. package/dist/internal/openapi/index.js +5 -0
  36. package/dist/internal/openapi/index.js.map +1 -0
  37. package/dist/internal/openapi/markdown.d.ts +42 -0
  38. package/dist/internal/openapi/markdown.d.ts.map +1 -0
  39. package/dist/internal/openapi/markdown.js +235 -0
  40. package/dist/internal/openapi/markdown.js.map +1 -0
  41. package/dist/internal/openapi/openapi.d.ts +187 -0
  42. package/dist/internal/openapi/openapi.d.ts.map +1 -0
  43. package/dist/internal/openapi/openapi.js +44 -0
  44. package/dist/internal/openapi/openapi.js.map +1 -0
  45. package/dist/internal/openapi/openrpc.d.ts +90 -0
  46. package/dist/internal/openapi/openrpc.d.ts.map +1 -0
  47. package/dist/internal/openapi/openrpc.js +213 -0
  48. package/dist/internal/openapi/openrpc.js.map +1 -0
  49. package/dist/internal/openapi/parser.d.ts +181 -0
  50. package/dist/internal/openapi/parser.d.ts.map +1 -0
  51. package/dist/internal/openapi/parser.js +329 -0
  52. package/dist/internal/openapi/parser.js.map +1 -0
  53. package/dist/internal/openapi/registry.d.ts +36 -0
  54. package/dist/internal/openapi/registry.d.ts.map +1 -0
  55. package/dist/internal/openapi/registry.js +79 -0
  56. package/dist/internal/openapi/registry.js.map +1 -0
  57. package/dist/internal/openapi/sample.d.ts +115 -0
  58. package/dist/internal/openapi/sample.d.ts.map +1 -0
  59. package/dist/internal/openapi/sample.js +434 -0
  60. package/dist/internal/openapi/sample.js.map +1 -0
  61. package/dist/internal/openapi/search.d.ts +19 -0
  62. package/dist/internal/openapi/search.d.ts.map +1 -0
  63. package/dist/internal/openapi/search.js +98 -0
  64. package/dist/internal/openapi/search.js.map +1 -0
  65. package/dist/internal/openapi/sidebar.d.ts +30 -0
  66. package/dist/internal/openapi/sidebar.d.ts.map +1 -0
  67. package/dist/internal/openapi/sidebar.js +67 -0
  68. package/dist/internal/openapi/sidebar.js.map +1 -0
  69. package/dist/internal/openapi/union.d.ts +36 -0
  70. package/dist/internal/openapi/union.d.ts.map +1 -0
  71. package/dist/internal/openapi/union.js +69 -0
  72. package/dist/internal/openapi/union.js.map +1 -0
  73. package/dist/internal/search.d.ts.map +1 -1
  74. package/dist/internal/search.js +20 -0
  75. package/dist/internal/search.js.map +1 -1
  76. package/dist/internal/vite-plugins.d.ts +12 -0
  77. package/dist/internal/vite-plugins.d.ts.map +1 -1
  78. package/dist/internal/vite-plugins.js +102 -11
  79. package/dist/internal/vite-plugins.js.map +1 -1
  80. package/dist/react/Badge.d.ts +2 -3
  81. package/dist/react/Badge.d.ts.map +1 -1
  82. package/dist/react/Layout.client.d.ts.map +1 -1
  83. package/dist/react/Layout.client.js +2 -2
  84. package/dist/react/Layout.client.js.map +1 -1
  85. package/dist/react/OpenApi.d.ts +6 -0
  86. package/dist/react/OpenApi.d.ts.map +1 -0
  87. package/dist/react/OpenApi.js +6 -0
  88. package/dist/react/OpenApi.js.map +1 -0
  89. package/dist/react/internal/CodeToHtml.client.d.ts +53 -2
  90. package/dist/react/internal/CodeToHtml.client.d.ts.map +1 -1
  91. package/dist/react/internal/CodeToHtml.client.js +154 -21
  92. package/dist/react/internal/CodeToHtml.client.js.map +1 -1
  93. package/dist/react/internal/Sidebar.d.ts.map +1 -1
  94. package/dist/react/internal/Sidebar.js +99 -2
  95. package/dist/react/internal/Sidebar.js.map +1 -1
  96. package/dist/react/internal/openapi/CodeSample.client.d.ts +21 -0
  97. package/dist/react/internal/openapi/CodeSample.client.d.ts.map +1 -0
  98. package/dist/react/internal/openapi/CodeSample.client.js +134 -0
  99. package/dist/react/internal/openapi/CodeSample.client.js.map +1 -0
  100. package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts +17 -0
  101. package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts.map +1 -0
  102. package/dist/react/internal/openapi/CollapsibleChildren.client.js +18 -0
  103. package/dist/react/internal/openapi/CollapsibleChildren.client.js.map +1 -0
  104. package/dist/react/internal/openapi/Disclosure.client.d.ts +28 -0
  105. package/dist/react/internal/openapi/Disclosure.client.d.ts.map +1 -0
  106. package/dist/react/internal/openapi/Disclosure.client.js +38 -0
  107. package/dist/react/internal/openapi/Disclosure.client.js.map +1 -0
  108. package/dist/react/internal/openapi/Endpoints.d.ts +26 -0
  109. package/dist/react/internal/openapi/Endpoints.d.ts.map +1 -0
  110. package/dist/react/internal/openapi/Endpoints.js +33 -0
  111. package/dist/react/internal/openapi/Endpoints.js.map +1 -0
  112. package/dist/react/internal/openapi/EndpointsView.d.ts +24 -0
  113. package/dist/react/internal/openapi/EndpointsView.d.ts.map +1 -0
  114. package/dist/react/internal/openapi/EndpointsView.js +26 -0
  115. package/dist/react/internal/openapi/EndpointsView.js.map +1 -0
  116. package/dist/react/internal/openapi/EnumValues.client.d.ts +14 -0
  117. package/dist/react/internal/openapi/EnumValues.client.d.ts.map +1 -0
  118. package/dist/react/internal/openapi/EnumValues.client.js +20 -0
  119. package/dist/react/internal/openapi/EnumValues.client.js.map +1 -0
  120. package/dist/react/internal/openapi/HeadingAnchor.d.ts +15 -0
  121. package/dist/react/internal/openapi/HeadingAnchor.d.ts.map +1 -0
  122. package/dist/react/internal/openapi/HeadingAnchor.js +12 -0
  123. package/dist/react/internal/openapi/HeadingAnchor.js.map +1 -0
  124. package/dist/react/internal/openapi/OpenApiPage.d.ts +79 -0
  125. package/dist/react/internal/openapi/OpenApiPage.d.ts.map +1 -0
  126. package/dist/react/internal/openapi/OpenApiPage.js +72 -0
  127. package/dist/react/internal/openapi/OpenApiPage.js.map +1 -0
  128. package/dist/react/internal/openapi/Operation.d.ts +25 -0
  129. package/dist/react/internal/openapi/Operation.d.ts.map +1 -0
  130. package/dist/react/internal/openapi/Operation.js +101 -0
  131. package/dist/react/internal/openapi/Operation.js.map +1 -0
  132. package/dist/react/internal/openapi/Playground.client.d.ts +33 -0
  133. package/dist/react/internal/openapi/Playground.client.d.ts.map +1 -0
  134. package/dist/react/internal/openapi/Playground.client.js +170 -0
  135. package/dist/react/internal/openapi/Playground.client.js.map +1 -0
  136. package/dist/react/internal/openapi/PropertyExample.client.d.ts +17 -0
  137. package/dist/react/internal/openapi/PropertyExample.client.d.ts.map +1 -0
  138. package/dist/react/internal/openapi/PropertyExample.client.js +21 -0
  139. package/dist/react/internal/openapi/PropertyExample.client.js.map +1 -0
  140. package/dist/react/internal/openapi/Reference.d.ts +55 -0
  141. package/dist/react/internal/openapi/Reference.d.ts.map +1 -0
  142. package/dist/react/internal/openapi/Reference.js +42 -0
  143. package/dist/react/internal/openapi/Reference.js.map +1 -0
  144. package/dist/react/internal/openapi/Schema.d.ts +110 -0
  145. package/dist/react/internal/openapi/Schema.d.ts.map +1 -0
  146. package/dist/react/internal/openapi/Schema.js +239 -0
  147. package/dist/react/internal/openapi/Schema.js.map +1 -0
  148. package/dist/react/internal/openapi/SchemaUnion.client.d.ts +25 -0
  149. package/dist/react/internal/openapi/SchemaUnion.client.d.ts.map +1 -0
  150. package/dist/react/internal/openapi/SchemaUnion.client.js +48 -0
  151. package/dist/react/internal/openapi/SchemaUnion.client.js.map +1 -0
  152. package/dist/react/internal/openapi/anchor-navigation.client.d.ts +47 -0
  153. package/dist/react/internal/openapi/anchor-navigation.client.d.ts.map +1 -0
  154. package/dist/react/internal/openapi/anchor-navigation.client.js +120 -0
  155. package/dist/react/internal/openapi/anchor-navigation.client.js.map +1 -0
  156. package/dist/react/internal/openapi/auth.d.ts +28 -0
  157. package/dist/react/internal/openapi/auth.d.ts.map +1 -0
  158. package/dist/react/internal/openapi/auth.js +75 -0
  159. package/dist/react/internal/openapi/auth.js.map +1 -0
  160. package/dist/react/useLayout.d.ts +2 -0
  161. package/dist/react/useLayout.d.ts.map +1 -1
  162. package/dist/react/useLayout.js +2 -0
  163. package/dist/react/useLayout.js.map +1 -1
  164. package/dist/server/handlers.d.ts +1 -1
  165. package/dist/server/handlers.d.ts.map +1 -1
  166. package/dist/server/handlers.js +26 -5
  167. package/dist/server/handlers.js.map +1 -1
  168. package/dist/server/og-assets.d.ts +5 -0
  169. package/dist/server/og-assets.d.ts.map +1 -0
  170. package/dist/server/og-assets.js +11 -0
  171. package/dist/server/og-assets.js.map +1 -0
  172. package/dist/server/openapi/assets.d.ts +33 -0
  173. package/dist/server/openapi/assets.d.ts.map +1 -0
  174. package/dist/server/openapi/assets.generated.d.ts +9 -0
  175. package/dist/server/openapi/assets.generated.d.ts.map +1 -0
  176. package/dist/server/openapi/assets.generated.js +1091 -0
  177. package/dist/server/openapi/assets.generated.js.map +1 -0
  178. package/dist/server/openapi/assets.js +32 -0
  179. package/dist/server/openapi/assets.js.map +1 -0
  180. package/dist/server/openapi/handler.d.ts +103 -0
  181. package/dist/server/openapi/handler.d.ts.map +1 -0
  182. package/dist/server/openapi/handler.js +198 -0
  183. package/dist/server/openapi/handler.js.map +1 -0
  184. package/dist/server/openapi/handler.test.d.ts +2 -0
  185. package/dist/server/openapi/handler.test.d.ts.map +1 -0
  186. package/dist/server/openapi/handler.test.js +203 -0
  187. package/dist/server/openapi/handler.test.js.map +1 -0
  188. package/dist/server/openapi/html.d.ts +16 -0
  189. package/dist/server/openapi/html.d.ts.map +1 -0
  190. package/dist/server/openapi/html.js +75 -0
  191. package/dist/server/openapi/html.js.map +1 -0
  192. package/dist/server/openapi/pages.d.ts +33 -0
  193. package/dist/server/openapi/pages.d.ts.map +1 -0
  194. package/dist/server/openapi/pages.js +130 -0
  195. package/dist/server/openapi/pages.js.map +1 -0
  196. package/dist/server/openapi/pages.test.d.ts +2 -0
  197. package/dist/server/openapi/pages.test.d.ts.map +1 -0
  198. package/dist/server/openapi/pages.test.js +94 -0
  199. package/dist/server/openapi/pages.test.js.map +1 -0
  200. package/dist/server/openapi/state.d.ts +42 -0
  201. package/dist/server/openapi/state.d.ts.map +1 -0
  202. package/dist/server/openapi/state.js +101 -0
  203. package/dist/server/openapi/state.js.map +1 -0
  204. package/dist/styles/index.css +16 -0
  205. package/dist/styles/markdown.css +9 -7
  206. package/dist/styles/openapi-playground.css +80 -0
  207. package/dist/styles/openapi.css +660 -0
  208. package/dist/vite.d.ts.map +1 -1
  209. package/dist/vite.js +1 -0
  210. package/dist/vite.js.map +1 -1
  211. package/dist/waku/internal/middleware/md-router.d.ts +0 -4
  212. package/dist/waku/internal/middleware/md-router.d.ts.map +1 -1
  213. package/dist/waku/internal/middleware/md-router.js +3 -48
  214. package/dist/waku/internal/middleware/md-router.js.map +1 -1
  215. package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js +1 -1
  216. package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js.map +1 -1
  217. package/dist/waku/internal/patches/router.d.ts.map +1 -1
  218. package/dist/waku/internal/patches/router.js +114 -1
  219. package/dist/waku/internal/patches/router.js.map +1 -1
  220. package/package.json +5 -1
  221. package/src/config.ts +1 -0
  222. package/src/globals.d.ts +16 -0
  223. package/src/index.ts +1 -0
  224. package/src/internal/config.ts +40 -1
  225. package/src/internal/llms.ts +51 -1
  226. package/src/internal/markdown-negotiation.test.ts +42 -0
  227. package/src/internal/markdown-negotiation.ts +95 -0
  228. package/src/internal/markdown.ts +5 -0
  229. package/src/internal/openapi/anchors.ts +44 -0
  230. package/src/internal/openapi/app.ts +127 -0
  231. package/src/internal/openapi/index.ts +24 -0
  232. package/src/internal/openapi/markdown.test.ts +115 -0
  233. package/src/internal/openapi/markdown.ts +275 -0
  234. package/src/internal/openapi/openapi.ts +212 -0
  235. package/src/internal/openapi/openrpc.test.ts +239 -0
  236. package/src/internal/openapi/openrpc.ts +295 -0
  237. package/src/internal/openapi/parser.test.ts +203 -0
  238. package/src/internal/openapi/parser.ts +613 -0
  239. package/src/internal/openapi/registry.test.ts +89 -0
  240. package/src/internal/openapi/registry.ts +89 -0
  241. package/src/internal/openapi/sample.test.ts +283 -0
  242. package/src/internal/openapi/sample.ts +562 -0
  243. package/src/internal/openapi/search.test.ts +62 -0
  244. package/src/internal/openapi/search.ts +108 -0
  245. package/src/internal/openapi/sidebar.test.ts +131 -0
  246. package/src/internal/openapi/sidebar.ts +94 -0
  247. package/src/internal/openapi/union.test.ts +51 -0
  248. package/src/internal/openapi/union.ts +74 -0
  249. package/src/internal/search.ts +20 -0
  250. package/src/internal/test/virtual-config.stub.ts +14 -0
  251. package/src/internal/vite-plugins.ts +106 -11
  252. package/src/openapi-app/App.tsx +64 -0
  253. package/src/openapi-app/blocks.tsx +33 -0
  254. package/src/openapi-app/client.tsx +25 -0
  255. package/src/openapi-app/links.test.ts +84 -0
  256. package/src/openapi-app/links.ts +66 -0
  257. package/src/openapi-app/payload.ts +20 -0
  258. package/src/openapi-app/virtual/config.ts +7 -0
  259. package/src/openapi-app/virtual/group-icons.ts +2 -0
  260. package/src/openapi-app/virtual/langs.ts +6 -0
  261. package/src/openapi-app/virtual/openapi.ts +10 -0
  262. package/src/openapi-app/virtual/search-index.ts +21 -0
  263. package/src/openapi-app/virtual/slots.ts +4 -0
  264. package/src/openapi-app/virtual/user-styles.ts +2 -0
  265. package/src/openapi-app/waku.tsx +154 -0
  266. package/src/react/Badge.tsx +2 -3
  267. package/src/react/Layout.client.tsx +17 -4
  268. package/src/react/OpenApi.tsx +5 -0
  269. package/src/react/internal/CodeToHtml.client.tsx +283 -22
  270. package/src/react/internal/Sidebar.tsx +126 -22
  271. package/src/react/internal/openapi/CodeSample.client.tsx +294 -0
  272. package/src/react/internal/openapi/CollapsibleChildren.client.tsx +41 -0
  273. package/src/react/internal/openapi/Disclosure.client.tsx +67 -0
  274. package/src/react/internal/openapi/Endpoints.tsx +58 -0
  275. package/src/react/internal/openapi/EndpointsView.tsx +76 -0
  276. package/src/react/internal/openapi/EnumValues.client.tsx +49 -0
  277. package/src/react/internal/openapi/HeadingAnchor.tsx +28 -0
  278. package/src/react/internal/openapi/OpenApiPage.tsx +173 -0
  279. package/src/react/internal/openapi/Operation.test.tsx +101 -0
  280. package/src/react/internal/openapi/Operation.tsx +335 -0
  281. package/src/react/internal/openapi/Playground.client.tsx +234 -0
  282. package/src/react/internal/openapi/PropertyExample.client.tsx +55 -0
  283. package/src/react/internal/openapi/Reference.tsx +120 -0
  284. package/src/react/internal/openapi/Schema.tsx +467 -0
  285. package/src/react/internal/openapi/SchemaUnion.client.tsx +123 -0
  286. package/src/react/internal/openapi/anchor-navigation.client.ts +154 -0
  287. package/src/react/internal/openapi/auth.ts +69 -0
  288. package/src/react/useLayout.ts +4 -0
  289. package/src/server/handlers.ts +31 -6
  290. package/src/server/og-assets.ts +14 -0
  291. package/src/server/openapi/assets.generated.ts +1093 -0
  292. package/src/server/openapi/assets.ts +57 -0
  293. package/src/server/openapi/handler.test.ts +244 -0
  294. package/src/server/openapi/handler.ts +277 -0
  295. package/src/server/openapi/html.ts +84 -0
  296. package/src/server/openapi/pages.test.ts +111 -0
  297. package/src/server/openapi/pages.ts +153 -0
  298. package/src/server/openapi/state.ts +136 -0
  299. package/src/styles/index.css +16 -0
  300. package/src/styles/markdown.css +9 -7
  301. package/src/styles/openapi-playground.css +80 -0
  302. package/src/styles/openapi.css +660 -0
  303. package/src/vite.ts +1 -0
  304. package/src/waku/internal/middleware/md-router.ts +8 -52
  305. package/src/waku/internal/patches/adapters/vercel-build-enhancer.ts +1 -1
  306. package/src/waku/internal/patches/router.ts +131 -1
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared user-agent lists and content negotiation used to decide whether a
3
+ * request for a documentation route should be served as Markdown instead of
4
+ * HTML. Consumed by the Waku `.md` router middleware and the standalone
5
+ * `Handler.openApi` server so both honor the same rules.
6
+ */
7
+
8
+ /** AI crawlers/agents that should receive Markdown. */
9
+ export const aiUserAgents = [
10
+ 'GPTBot',
11
+ 'OAI-SearchBot',
12
+ 'ChatGPT-User',
13
+ 'ChatGPT-User/2.0',
14
+ 'anthropic-ai',
15
+ 'ClaudeBot',
16
+ 'claude-web',
17
+ 'PerplexityBot',
18
+ 'Perplexity-User',
19
+ 'Google-Extended',
20
+ 'FacebookBot',
21
+ 'meta-externalagent',
22
+ 'Bytespider',
23
+ 'cohere-ai',
24
+ 'AI2Bot',
25
+ 'CCBot',
26
+ 'Diffbot',
27
+ 'omgili',
28
+ 'Timpibot',
29
+ 'MistralAI-User',
30
+ 'GoogleAgent-Mariner',
31
+ ]
32
+
33
+ /** Search engine crawlers that should always receive HTML. */
34
+ export const searchEngineUserAgents = [
35
+ 'Googlebot',
36
+ 'Bingbot',
37
+ 'Amazonbot',
38
+ 'Applebot',
39
+ 'Applebot-Extended',
40
+ 'DuckAssistBot',
41
+ 'YouBot',
42
+ ]
43
+
44
+ /** Terminal HTTP clients that should receive Markdown. */
45
+ export const terminalUserAgents = ['curl/', 'Wget/', 'HTTPie/', 'httpie-go/', 'xh/']
46
+
47
+ /** Link-preview/Open Graph bots that must always receive HTML (for meta tags). */
48
+ export const ogBotUserAgents = [
49
+ 'Discordbot',
50
+ 'Embedly',
51
+ 'Facebot',
52
+ 'Iframely',
53
+ 'LinkedInBot',
54
+ 'Pinterestbot',
55
+ 'Slackbot',
56
+ 'Slurp',
57
+ 'TelegramBot',
58
+ 'Twitterbot',
59
+ 'WhatsApp',
60
+ 'facebookexternalhit',
61
+ ]
62
+
63
+ /**
64
+ * Decides whether a route request should be served as Markdown.
65
+ *
66
+ * Resolution order (matches the `.md` router middleware):
67
+ * 1. Open Graph bots never get Markdown (they need HTML meta tags).
68
+ * 2. An explicit `.md` suffix always serves Markdown.
69
+ * 3. Search engines otherwise always get HTML.
70
+ * 4. AI agents, terminal clients, or `Accept: text/markdown` get Markdown.
71
+ * 5. Everything else gets HTML.
72
+ */
73
+ export function prefersMarkdown(options: prefersMarkdown.Options): boolean {
74
+ const { pathname, userAgent = '', accept = '' } = options
75
+
76
+ if (ogBotUserAgents.some((agent) => userAgent.includes(agent))) return false
77
+ if (pathname.endsWith('.md')) return true
78
+ if (searchEngineUserAgents.some((agent) => userAgent.includes(agent))) return false
79
+
80
+ const isAiAgent = aiUserAgents.some((agent) => userAgent.includes(agent))
81
+ const isTerminal = terminalUserAgents.some((agent) => userAgent.includes(agent))
82
+ const acceptsMarkdown = accept.includes('text/markdown')
83
+ return isAiAgent || isTerminal || acceptsMarkdown
84
+ }
85
+
86
+ export declare namespace prefersMarkdown {
87
+ type Options = {
88
+ /** Request pathname (used to detect an explicit `.md` suffix). */
89
+ pathname: string
90
+ /** Request `User-Agent` header. */
91
+ userAgent?: string | undefined
92
+ /** Request `Accept` header. */
93
+ accept?: string | undefined
94
+ }
95
+ }
@@ -1,5 +1,6 @@
1
1
  import type * as Hast from 'hast'
2
2
  import type * as Mdast from 'mdast'
3
+ import rehypeSlug from 'rehype-slug'
3
4
  import rehypeStringify from 'rehype-stringify'
4
5
  import remarkGfm from 'remark-gfm'
5
6
  import remarkParse from 'remark-parse'
@@ -160,6 +161,10 @@ const processor = unified()
160
161
  .use(remarkGfm)
161
162
  .use(remarkGitHubCallouts)
162
163
  .use(remarkRehype, { allowDangerousHtml: true })
164
+ // Add `id`s to headings so the outline (which scans `[data-v-content]
165
+ // :is(h2..h6)[id]`) and heading anchors work on guide/trait pages and on
166
+ // rendered spec descriptions, matching the MDX pipeline.
167
+ .use(rehypeSlug)
163
168
  .use(rehypeVocsAttrs)
164
169
  .use(rehypeTableWrapper)
165
170
  .use(rehypeCalloutIcons)
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared id/path helpers that keep the left-hand schema documentation rows and
3
+ * the right-hand response example lines pointing at the same anchor ids. Both
4
+ * sides derive an element id from a schema "path" (the chain of property names
5
+ * leading to a value, e.g. `['data', 'items', 'id']`) plus an `idBase` that
6
+ * scopes the path to a specific operation section (a request body or a single
7
+ * response status).
8
+ */
9
+
10
+ /** A chain of property names identifying a value within a schema. */
11
+ export type SchemaPath = readonly string[]
12
+
13
+ /** Slugifies a path segment / title into a URL-safe id fragment. */
14
+ export function slug(value: string): string {
15
+ const result = value
16
+ .toLowerCase()
17
+ .replace(/[^a-z0-9]+/g, '-')
18
+ .replace(/^-+|-+$/g, '')
19
+ return result || 'value'
20
+ }
21
+
22
+ /** Id base for an operation's request body schema rows. */
23
+ export function requestBodyIdBase(operationId: string): string {
24
+ return `${operationId}-request-body`
25
+ }
26
+
27
+ /** Id base for a single response status's schema rows. */
28
+ export function responseIdBase(operationId: string, status: string): string {
29
+ return `${operationId}-response-${slug(status)}`
30
+ }
31
+
32
+ /**
33
+ * Qualifies an id base by media type when a section exposes more than one media
34
+ * type, so colliding paths across media types resolve to distinct ids. With a
35
+ * single media type the base is returned unchanged (shorter, stable ids).
36
+ */
37
+ export function mediaIdBase(base: string, mediaType: string, mediaCount: number): string {
38
+ return mediaCount > 1 ? `${base}-${slug(mediaType)}` : base
39
+ }
40
+
41
+ /** The element id for a schema property at `path` within `idBase`. */
42
+ export function schemaPropertyId(idBase: string, path: SchemaPath): string {
43
+ return `${idBase}-${slug(path.join('.'))}`
44
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Shared types for the prebuilt OpenAPI app (the `Handler.openApi` server
3
+ * handler and the static browser bundle it serves).
4
+ *
5
+ * This module is intentionally free of Node and DOM dependencies so it can be
6
+ * imported by both the server handler and the client entry.
7
+ */
8
+
9
+ import type { Config } from '../config.js'
10
+ import type { SidebarItem } from '../sidebar.js'
11
+ import { normalizePath } from './openapi.js'
12
+ import type { Ir } from './parser.js'
13
+
14
+ /**
15
+ * A block of rendered page content. Guide/override pages are compiled on the
16
+ * server into an ordered list of blocks so embedded `<OpenApi.Endpoints />`
17
+ * components can be re-hydrated as real React on the client (rather than
18
+ * inlined as static HTML).
19
+ */
20
+ export type PageBlock =
21
+ | { type: 'html'; html: string }
22
+ | { type: 'endpoints'; path?: string | undefined }
23
+
24
+ /**
25
+ * A compiled consumer-authored page, keyed by its section-relative route.
26
+ *
27
+ * - `path === '/'` overrides the landing/intro content.
28
+ * - `path === '/<group>'` overrides a category header.
29
+ * - any other `path` is a standalone guide page.
30
+ */
31
+ export type CompiledPage = {
32
+ /** Section-relative route (normalized, leading slash, no trailing slash). */
33
+ path: string
34
+ /** Document title (frontmatter `title` or first `# heading`). */
35
+ title?: string | undefined
36
+ /** Subtitle Markdown rendered below the title on guide pages. */
37
+ description?: string | undefined
38
+ /**
39
+ * Render `title`/`description` as an on-page header above the content. Set for
40
+ * trait pages (`x-traitTag`), whose title comes from the tag name and subtitle
41
+ * from `x-subtitle` (their Markdown body has no heading). Authored guide pages
42
+ * leave this unset because their MDX owns its own headings.
43
+ */
44
+ header?: boolean | undefined
45
+ /** Ordered content blocks. */
46
+ blocks: PageBlock[]
47
+ /**
48
+ * Raw authored Markdown (frontmatter + ESM import/export lines stripped), used
49
+ * to serve the page's `.md` / agent-facing version. Absent for trait pages
50
+ * compiled from a description that is already the body.
51
+ */
52
+ markdown?: string | undefined
53
+ }
54
+
55
+ /**
56
+ * The JSON payload embedded in the standalone HTML shell and consumed by the
57
+ * browser bundle to render the reference entirely client-side.
58
+ */
59
+ export type Payload = {
60
+ /** Parsed OpenAPI IR. */
61
+ ir: Ir
62
+ /** Document title (spec `info.title`). */
63
+ title: string
64
+ /** Computed sidebar (extras + generated items). */
65
+ sidebar: SidebarItem<true>[]
66
+ /** Compiled override/guide pages. */
67
+ pages: CompiledPage[]
68
+ /**
69
+ * Serialized Vocs config that backs `virtual:vocs/config` in the browser
70
+ * bundle, so the prebuilt app renders the real Vocs layout/chrome. Functions
71
+ * are serialized via {@link file://../config-serializer.ts config-serializer};
72
+ * the client deserializes them on load.
73
+ */
74
+ config: Config
75
+ }
76
+
77
+ /** Joins a section base path with a section-relative sub-path. */
78
+ export function join(base: string, sub: string): string {
79
+ const b = base === '/' ? '' : base
80
+ if (!sub || sub === '/') return b || '/'
81
+ return `${b}${sub.startsWith('/') ? sub : `/${sub}`}`
82
+ }
83
+
84
+ /**
85
+ * The set of known section routes for a payload: the intro (the spec `path`),
86
+ * one per category, and one per compiled page.
87
+ */
88
+ export function knownRoutes(payload: Payload): string[] {
89
+ const base = payload.ir.path || '/'
90
+ const routes = new Set<string>()
91
+ routes.add(base === '/' ? '/' : normalizePath(base))
92
+ for (const group of payload.ir.groups) routes.add(join(base, `/${group.id}`))
93
+ for (const page of payload.pages) routes.add(join(base, page.path))
94
+ return [...routes]
95
+ }
96
+
97
+ /**
98
+ * Infers the host mount prefix from a request pathname: the longest known
99
+ * section route that is a path-suffix of `pathname` is stripped; whatever
100
+ * precedes it is the mount (e.g. `/docs/pets` with route `/pets` → `/docs`).
101
+ * Falls back to treating the whole pathname as the mount (the intro page).
102
+ */
103
+ export function inferMount(pathname: string, routes: string[]): string {
104
+ const path = stripTrailingSlash(pathname)
105
+ let mount = ''
106
+ let best = -1
107
+ for (const route of routes) {
108
+ if (route === '/') continue
109
+ if (path === route || path.endsWith(route)) {
110
+ const prefix = path.slice(0, path.length - route.length)
111
+ if (route.length > best && (prefix === '' || prefix.startsWith('/'))) {
112
+ best = route.length
113
+ mount = prefix
114
+ }
115
+ }
116
+ }
117
+ // No group/page route matched: we're on the intro/landing page, so the whole
118
+ // pathname is the mount. A root mount (`/`) has no prefix, though — return ''
119
+ // so callers build `/group` rather than `//group` (a protocol-relative URL the
120
+ // browser reads as `http://group/`).
121
+ if (best === -1) return path === '/' ? '' : path
122
+ return mount
123
+ }
124
+
125
+ function stripTrailingSlash(value: string): string {
126
+ return value.length > 1 && value.endsWith('/') ? value.slice(0, -1) : value
127
+ }
@@ -0,0 +1,24 @@
1
+ export type { SchemaPath } from './anchors.js'
2
+ export {
3
+ mediaIdBase,
4
+ requestBodyIdBase,
5
+ responseIdBase,
6
+ schemaPropertyId,
7
+ slug,
8
+ } from './anchors.js'
9
+ export * from './openapi.js'
10
+ export type {
11
+ Ir,
12
+ IrBody,
13
+ IrGroup,
14
+ IrMediaType,
15
+ IrOperation,
16
+ IrParameter,
17
+ IrResponse,
18
+ IrSecurityScheme,
19
+ IrServer,
20
+ Method,
21
+ } from './parser.js'
22
+ export { methods, parse } from './parser.js'
23
+ export type { CodeSample, ResponseSample } from './sample.js'
24
+ export { codeSamples, harRequest, responseSamples } from './sample.js'
@@ -0,0 +1,115 @@
1
+ import { describe, expect, test } from 'vitest'
2
+ import { fromIr } from './markdown.js'
3
+ import type { Ir } from './parser.js'
4
+
5
+ const ir: Ir = {
6
+ path: '/api',
7
+ client: { url: 'https://example.com/openapi.json' },
8
+ info: { title: 'Petstore', version: '1.0.0', description: 'The **best** pet API.' },
9
+ servers: [{ url: 'https://example.com', description: 'Production' }],
10
+ securitySchemes: {},
11
+ traits: [],
12
+ groups: [
13
+ {
14
+ id: 'pets',
15
+ name: 'Pets',
16
+ description: 'Manage pets.',
17
+ operations: [
18
+ {
19
+ id: 'listpets',
20
+ method: 'GET',
21
+ path: '/pets',
22
+ summary: 'List pets',
23
+ description: 'Returns all pets.',
24
+ parameters: [
25
+ { name: 'limit', in: 'query', description: 'Max items', schema: { type: 'integer' } },
26
+ { name: 'cursor', in: 'query', deprecated: true },
27
+ ],
28
+ responses: [
29
+ {
30
+ status: '200',
31
+ description: 'A list of pets',
32
+ headers: [
33
+ { name: 'X-Total', schema: { type: 'integer' }, description: 'Total count' },
34
+ ],
35
+ content: [
36
+ {
37
+ mediaType: 'application/json',
38
+ schema: {
39
+ type: 'object',
40
+ required: ['data'],
41
+ properties: {
42
+ data: { type: 'array', items: { type: 'object' }, description: 'Pets.' },
43
+ },
44
+ },
45
+ },
46
+ ],
47
+ },
48
+ ],
49
+ },
50
+ {
51
+ id: 'createpet',
52
+ method: 'POST',
53
+ path: '/pets',
54
+ summary: 'Create pet',
55
+ parameters: [],
56
+ requestBody: {
57
+ required: true,
58
+ content: [
59
+ {
60
+ mediaType: 'application/json',
61
+ schema: {
62
+ type: 'object',
63
+ required: ['name'],
64
+ properties: { name: { type: 'string', description: 'Pet name.' } },
65
+ },
66
+ },
67
+ ],
68
+ },
69
+ responses: [{ status: '201', description: 'Created', content: [], headers: [] }],
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ }
75
+
76
+ describe('fromIr', () => {
77
+ const pages = fromIr(ir)
78
+
79
+ test('emits an overview page and one page per category', () => {
80
+ expect(pages.map((page) => ({ path: page.path, title: page.title }))).toEqual([
81
+ { path: '/api', title: 'Petstore' },
82
+ { path: '/api/pets', title: 'Pets' },
83
+ ])
84
+ })
85
+
86
+ test('overview lists every category with links to its operations', () => {
87
+ const overview = pages.find((page) => page.path === '/api')
88
+ expect(overview?.content).toContain('# Petstore')
89
+ expect(overview?.content).toContain('Version: `1.0.0`')
90
+ expect(overview?.content).toContain('## Servers')
91
+ expect(overview?.content).toContain('## Endpoints')
92
+ expect(overview?.content).toContain('### Pets')
93
+ expect(overview?.content).toContain('- [`GET /pets`](/api/pets#listpets): List pets')
94
+ })
95
+
96
+ test('category page renders params, request body, responses and samples', () => {
97
+ const group = pages.find((page) => page.path === '/api/pets')
98
+ const content = group?.content ?? ''
99
+ expect(content).toContain('## List pets')
100
+ expect(content).toContain('`GET /pets`')
101
+ expect(content).toContain('### Query parameters')
102
+ expect(content).toContain('- `limit` `integer`: Max items')
103
+ // Deprecated parameters are excluded.
104
+ expect(content).not.toContain('cursor')
105
+ expect(content).toContain('### Responses')
106
+ expect(content).toContain('#### `200`: A list of pets')
107
+ expect(content).toContain('- `X-Total` `integer`: Total count')
108
+ expect(content).toContain('- `data` `object[]` _(required)_: Pets.')
109
+ expect(content).toContain('### Request body (required) (`application/json`)')
110
+ expect(content).toContain('- `name` `string` _(required)_: Pet name.')
111
+ expect(content).toContain('### Example request')
112
+ expect(content).toContain('```bash')
113
+ expect(content).toContain('curl')
114
+ })
115
+ })