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.
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/globals.d.ts +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/config.d.ts +28 -0
- package/dist/internal/config.d.ts.map +1 -1
- package/dist/internal/config.js +10 -2
- package/dist/internal/config.js.map +1 -1
- package/dist/internal/llms.d.ts +21 -2
- package/dist/internal/llms.d.ts.map +1 -1
- package/dist/internal/llms.js +41 -1
- package/dist/internal/llms.js.map +1 -1
- package/dist/internal/markdown-negotiation.d.ts +36 -0
- package/dist/internal/markdown-negotiation.d.ts.map +1 -0
- package/dist/internal/markdown-negotiation.js +81 -0
- package/dist/internal/markdown-negotiation.js.map +1 -0
- package/dist/internal/markdown.d.ts.map +1 -1
- package/dist/internal/markdown.js +5 -0
- package/dist/internal/markdown.js.map +1 -1
- package/dist/internal/openapi/anchors.d.ts +25 -0
- package/dist/internal/openapi/anchors.d.ts.map +1 -0
- package/dist/internal/openapi/anchors.js +37 -0
- package/dist/internal/openapi/anchors.js.map +1 -0
- package/dist/internal/openapi/app.d.ts +89 -0
- package/dist/internal/openapi/app.d.ts.map +1 -0
- package/dist/internal/openapi/app.js +62 -0
- package/dist/internal/openapi/app.js.map +1 -0
- package/dist/internal/openapi/index.d.ts +8 -0
- package/dist/internal/openapi/index.d.ts.map +1 -0
- package/dist/internal/openapi/index.js +5 -0
- package/dist/internal/openapi/index.js.map +1 -0
- package/dist/internal/openapi/markdown.d.ts +42 -0
- package/dist/internal/openapi/markdown.d.ts.map +1 -0
- package/dist/internal/openapi/markdown.js +235 -0
- package/dist/internal/openapi/markdown.js.map +1 -0
- package/dist/internal/openapi/openapi.d.ts +187 -0
- package/dist/internal/openapi/openapi.d.ts.map +1 -0
- package/dist/internal/openapi/openapi.js +44 -0
- package/dist/internal/openapi/openapi.js.map +1 -0
- package/dist/internal/openapi/openrpc.d.ts +90 -0
- package/dist/internal/openapi/openrpc.d.ts.map +1 -0
- package/dist/internal/openapi/openrpc.js +213 -0
- package/dist/internal/openapi/openrpc.js.map +1 -0
- package/dist/internal/openapi/parser.d.ts +181 -0
- package/dist/internal/openapi/parser.d.ts.map +1 -0
- package/dist/internal/openapi/parser.js +329 -0
- package/dist/internal/openapi/parser.js.map +1 -0
- package/dist/internal/openapi/registry.d.ts +36 -0
- package/dist/internal/openapi/registry.d.ts.map +1 -0
- package/dist/internal/openapi/registry.js +79 -0
- package/dist/internal/openapi/registry.js.map +1 -0
- package/dist/internal/openapi/sample.d.ts +115 -0
- package/dist/internal/openapi/sample.d.ts.map +1 -0
- package/dist/internal/openapi/sample.js +434 -0
- package/dist/internal/openapi/sample.js.map +1 -0
- package/dist/internal/openapi/search.d.ts +19 -0
- package/dist/internal/openapi/search.d.ts.map +1 -0
- package/dist/internal/openapi/search.js +98 -0
- package/dist/internal/openapi/search.js.map +1 -0
- package/dist/internal/openapi/sidebar.d.ts +30 -0
- package/dist/internal/openapi/sidebar.d.ts.map +1 -0
- package/dist/internal/openapi/sidebar.js +67 -0
- package/dist/internal/openapi/sidebar.js.map +1 -0
- package/dist/internal/openapi/union.d.ts +36 -0
- package/dist/internal/openapi/union.d.ts.map +1 -0
- package/dist/internal/openapi/union.js +69 -0
- package/dist/internal/openapi/union.js.map +1 -0
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +20 -0
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/vite-plugins.d.ts +12 -0
- package/dist/internal/vite-plugins.d.ts.map +1 -1
- package/dist/internal/vite-plugins.js +102 -11
- package/dist/internal/vite-plugins.js.map +1 -1
- package/dist/react/Badge.d.ts +2 -3
- package/dist/react/Badge.d.ts.map +1 -1
- package/dist/react/Layout.client.d.ts.map +1 -1
- package/dist/react/Layout.client.js +2 -2
- package/dist/react/Layout.client.js.map +1 -1
- package/dist/react/OpenApi.d.ts +6 -0
- package/dist/react/OpenApi.d.ts.map +1 -0
- package/dist/react/OpenApi.js +6 -0
- package/dist/react/OpenApi.js.map +1 -0
- package/dist/react/internal/CodeToHtml.client.d.ts +53 -2
- package/dist/react/internal/CodeToHtml.client.d.ts.map +1 -1
- package/dist/react/internal/CodeToHtml.client.js +154 -21
- package/dist/react/internal/CodeToHtml.client.js.map +1 -1
- package/dist/react/internal/Sidebar.d.ts.map +1 -1
- package/dist/react/internal/Sidebar.js +99 -2
- package/dist/react/internal/Sidebar.js.map +1 -1
- package/dist/react/internal/openapi/CodeSample.client.d.ts +21 -0
- package/dist/react/internal/openapi/CodeSample.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/CodeSample.client.js +134 -0
- package/dist/react/internal/openapi/CodeSample.client.js.map +1 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts +17 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.js +18 -0
- package/dist/react/internal/openapi/CollapsibleChildren.client.js.map +1 -0
- package/dist/react/internal/openapi/Disclosure.client.d.ts +28 -0
- package/dist/react/internal/openapi/Disclosure.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/Disclosure.client.js +38 -0
- package/dist/react/internal/openapi/Disclosure.client.js.map +1 -0
- package/dist/react/internal/openapi/Endpoints.d.ts +26 -0
- package/dist/react/internal/openapi/Endpoints.d.ts.map +1 -0
- package/dist/react/internal/openapi/Endpoints.js +33 -0
- package/dist/react/internal/openapi/Endpoints.js.map +1 -0
- package/dist/react/internal/openapi/EndpointsView.d.ts +24 -0
- package/dist/react/internal/openapi/EndpointsView.d.ts.map +1 -0
- package/dist/react/internal/openapi/EndpointsView.js +26 -0
- package/dist/react/internal/openapi/EndpointsView.js.map +1 -0
- package/dist/react/internal/openapi/EnumValues.client.d.ts +14 -0
- package/dist/react/internal/openapi/EnumValues.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/EnumValues.client.js +20 -0
- package/dist/react/internal/openapi/EnumValues.client.js.map +1 -0
- package/dist/react/internal/openapi/HeadingAnchor.d.ts +15 -0
- package/dist/react/internal/openapi/HeadingAnchor.d.ts.map +1 -0
- package/dist/react/internal/openapi/HeadingAnchor.js +12 -0
- package/dist/react/internal/openapi/HeadingAnchor.js.map +1 -0
- package/dist/react/internal/openapi/OpenApiPage.d.ts +79 -0
- package/dist/react/internal/openapi/OpenApiPage.d.ts.map +1 -0
- package/dist/react/internal/openapi/OpenApiPage.js +72 -0
- package/dist/react/internal/openapi/OpenApiPage.js.map +1 -0
- package/dist/react/internal/openapi/Operation.d.ts +25 -0
- package/dist/react/internal/openapi/Operation.d.ts.map +1 -0
- package/dist/react/internal/openapi/Operation.js +101 -0
- package/dist/react/internal/openapi/Operation.js.map +1 -0
- package/dist/react/internal/openapi/Playground.client.d.ts +33 -0
- package/dist/react/internal/openapi/Playground.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/Playground.client.js +170 -0
- package/dist/react/internal/openapi/Playground.client.js.map +1 -0
- package/dist/react/internal/openapi/PropertyExample.client.d.ts +17 -0
- package/dist/react/internal/openapi/PropertyExample.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/PropertyExample.client.js +21 -0
- package/dist/react/internal/openapi/PropertyExample.client.js.map +1 -0
- package/dist/react/internal/openapi/Reference.d.ts +55 -0
- package/dist/react/internal/openapi/Reference.d.ts.map +1 -0
- package/dist/react/internal/openapi/Reference.js +42 -0
- package/dist/react/internal/openapi/Reference.js.map +1 -0
- package/dist/react/internal/openapi/Schema.d.ts +110 -0
- package/dist/react/internal/openapi/Schema.d.ts.map +1 -0
- package/dist/react/internal/openapi/Schema.js +239 -0
- package/dist/react/internal/openapi/Schema.js.map +1 -0
- package/dist/react/internal/openapi/SchemaUnion.client.d.ts +25 -0
- package/dist/react/internal/openapi/SchemaUnion.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/SchemaUnion.client.js +48 -0
- package/dist/react/internal/openapi/SchemaUnion.client.js.map +1 -0
- package/dist/react/internal/openapi/anchor-navigation.client.d.ts +47 -0
- package/dist/react/internal/openapi/anchor-navigation.client.d.ts.map +1 -0
- package/dist/react/internal/openapi/anchor-navigation.client.js +120 -0
- package/dist/react/internal/openapi/anchor-navigation.client.js.map +1 -0
- package/dist/react/internal/openapi/auth.d.ts +28 -0
- package/dist/react/internal/openapi/auth.d.ts.map +1 -0
- package/dist/react/internal/openapi/auth.js +75 -0
- package/dist/react/internal/openapi/auth.js.map +1 -0
- package/dist/react/useLayout.d.ts +2 -0
- package/dist/react/useLayout.d.ts.map +1 -1
- package/dist/react/useLayout.js +2 -0
- package/dist/react/useLayout.js.map +1 -1
- package/dist/server/handlers.d.ts +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/handlers.js +26 -5
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/og-assets.d.ts +5 -0
- package/dist/server/og-assets.d.ts.map +1 -0
- package/dist/server/og-assets.js +11 -0
- package/dist/server/og-assets.js.map +1 -0
- package/dist/server/openapi/assets.d.ts +33 -0
- package/dist/server/openapi/assets.d.ts.map +1 -0
- package/dist/server/openapi/assets.generated.d.ts +9 -0
- package/dist/server/openapi/assets.generated.d.ts.map +1 -0
- package/dist/server/openapi/assets.generated.js +1091 -0
- package/dist/server/openapi/assets.generated.js.map +1 -0
- package/dist/server/openapi/assets.js +32 -0
- package/dist/server/openapi/assets.js.map +1 -0
- package/dist/server/openapi/handler.d.ts +103 -0
- package/dist/server/openapi/handler.d.ts.map +1 -0
- package/dist/server/openapi/handler.js +198 -0
- package/dist/server/openapi/handler.js.map +1 -0
- package/dist/server/openapi/handler.test.d.ts +2 -0
- package/dist/server/openapi/handler.test.d.ts.map +1 -0
- package/dist/server/openapi/handler.test.js +203 -0
- package/dist/server/openapi/handler.test.js.map +1 -0
- package/dist/server/openapi/html.d.ts +16 -0
- package/dist/server/openapi/html.d.ts.map +1 -0
- package/dist/server/openapi/html.js +75 -0
- package/dist/server/openapi/html.js.map +1 -0
- package/dist/server/openapi/pages.d.ts +33 -0
- package/dist/server/openapi/pages.d.ts.map +1 -0
- package/dist/server/openapi/pages.js +130 -0
- package/dist/server/openapi/pages.js.map +1 -0
- package/dist/server/openapi/pages.test.d.ts +2 -0
- package/dist/server/openapi/pages.test.d.ts.map +1 -0
- package/dist/server/openapi/pages.test.js +94 -0
- package/dist/server/openapi/pages.test.js.map +1 -0
- package/dist/server/openapi/state.d.ts +42 -0
- package/dist/server/openapi/state.d.ts.map +1 -0
- package/dist/server/openapi/state.js +101 -0
- package/dist/server/openapi/state.js.map +1 -0
- package/dist/styles/index.css +16 -0
- package/dist/styles/markdown.css +9 -7
- package/dist/styles/openapi-playground.css +80 -0
- package/dist/styles/openapi.css +660 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +1 -0
- package/dist/vite.js.map +1 -1
- package/dist/waku/internal/middleware/md-router.d.ts +0 -4
- package/dist/waku/internal/middleware/md-router.d.ts.map +1 -1
- package/dist/waku/internal/middleware/md-router.js +3 -48
- package/dist/waku/internal/middleware/md-router.js.map +1 -1
- package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js +1 -1
- package/dist/waku/internal/patches/adapters/vercel-build-enhancer.js.map +1 -1
- package/dist/waku/internal/patches/router.d.ts.map +1 -1
- package/dist/waku/internal/patches/router.js +114 -1
- package/dist/waku/internal/patches/router.js.map +1 -1
- package/package.json +5 -1
- package/src/config.ts +1 -0
- package/src/globals.d.ts +16 -0
- package/src/index.ts +1 -0
- package/src/internal/config.ts +40 -1
- package/src/internal/llms.ts +51 -1
- package/src/internal/markdown-negotiation.test.ts +42 -0
- package/src/internal/markdown-negotiation.ts +95 -0
- package/src/internal/markdown.ts +5 -0
- package/src/internal/openapi/anchors.ts +44 -0
- package/src/internal/openapi/app.ts +127 -0
- package/src/internal/openapi/index.ts +24 -0
- package/src/internal/openapi/markdown.test.ts +115 -0
- package/src/internal/openapi/markdown.ts +275 -0
- package/src/internal/openapi/openapi.ts +212 -0
- package/src/internal/openapi/openrpc.test.ts +239 -0
- package/src/internal/openapi/openrpc.ts +295 -0
- package/src/internal/openapi/parser.test.ts +203 -0
- package/src/internal/openapi/parser.ts +613 -0
- package/src/internal/openapi/registry.test.ts +89 -0
- package/src/internal/openapi/registry.ts +89 -0
- package/src/internal/openapi/sample.test.ts +283 -0
- package/src/internal/openapi/sample.ts +562 -0
- package/src/internal/openapi/search.test.ts +62 -0
- package/src/internal/openapi/search.ts +108 -0
- package/src/internal/openapi/sidebar.test.ts +131 -0
- package/src/internal/openapi/sidebar.ts +94 -0
- package/src/internal/openapi/union.test.ts +51 -0
- package/src/internal/openapi/union.ts +74 -0
- package/src/internal/search.ts +20 -0
- package/src/internal/test/virtual-config.stub.ts +14 -0
- package/src/internal/vite-plugins.ts +106 -11
- package/src/openapi-app/App.tsx +64 -0
- package/src/openapi-app/blocks.tsx +33 -0
- package/src/openapi-app/client.tsx +25 -0
- package/src/openapi-app/links.test.ts +84 -0
- package/src/openapi-app/links.ts +66 -0
- package/src/openapi-app/payload.ts +20 -0
- package/src/openapi-app/virtual/config.ts +7 -0
- package/src/openapi-app/virtual/group-icons.ts +2 -0
- package/src/openapi-app/virtual/langs.ts +6 -0
- package/src/openapi-app/virtual/openapi.ts +10 -0
- package/src/openapi-app/virtual/search-index.ts +21 -0
- package/src/openapi-app/virtual/slots.ts +4 -0
- package/src/openapi-app/virtual/user-styles.ts +2 -0
- package/src/openapi-app/waku.tsx +154 -0
- package/src/react/Badge.tsx +2 -3
- package/src/react/Layout.client.tsx +17 -4
- package/src/react/OpenApi.tsx +5 -0
- package/src/react/internal/CodeToHtml.client.tsx +283 -22
- package/src/react/internal/Sidebar.tsx +126 -22
- package/src/react/internal/openapi/CodeSample.client.tsx +294 -0
- package/src/react/internal/openapi/CollapsibleChildren.client.tsx +41 -0
- package/src/react/internal/openapi/Disclosure.client.tsx +67 -0
- package/src/react/internal/openapi/Endpoints.tsx +58 -0
- package/src/react/internal/openapi/EndpointsView.tsx +76 -0
- package/src/react/internal/openapi/EnumValues.client.tsx +49 -0
- package/src/react/internal/openapi/HeadingAnchor.tsx +28 -0
- package/src/react/internal/openapi/OpenApiPage.tsx +173 -0
- package/src/react/internal/openapi/Operation.test.tsx +101 -0
- package/src/react/internal/openapi/Operation.tsx +335 -0
- package/src/react/internal/openapi/Playground.client.tsx +234 -0
- package/src/react/internal/openapi/PropertyExample.client.tsx +55 -0
- package/src/react/internal/openapi/Reference.tsx +120 -0
- package/src/react/internal/openapi/Schema.tsx +467 -0
- package/src/react/internal/openapi/SchemaUnion.client.tsx +123 -0
- package/src/react/internal/openapi/anchor-navigation.client.ts +154 -0
- package/src/react/internal/openapi/auth.ts +69 -0
- package/src/react/useLayout.ts +4 -0
- package/src/server/handlers.ts +31 -6
- package/src/server/og-assets.ts +14 -0
- package/src/server/openapi/assets.generated.ts +1093 -0
- package/src/server/openapi/assets.ts +57 -0
- package/src/server/openapi/handler.test.ts +244 -0
- package/src/server/openapi/handler.ts +277 -0
- package/src/server/openapi/html.ts +84 -0
- package/src/server/openapi/pages.test.ts +111 -0
- package/src/server/openapi/pages.ts +153 -0
- package/src/server/openapi/state.ts +136 -0
- package/src/styles/index.css +16 -0
- package/src/styles/markdown.css +9 -7
- package/src/styles/openapi-playground.css +80 -0
- package/src/styles/openapi.css +660 -0
- package/src/vite.ts +1 -0
- package/src/waku/internal/middleware/md-router.ts +8 -52
- package/src/waku/internal/patches/adapters/vercel-build-enhancer.ts +1 -1
- package/src/waku/internal/patches/router.ts +131 -1
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { Config } from '../config.js'
|
|
2
|
+
import type { Ir, IrGroup, IrOperation, IrParameter, IrResponse } from './parser.js'
|
|
3
|
+
import * as Registry from './registry.js'
|
|
4
|
+
import { codeSamples } from './sample.js'
|
|
5
|
+
import { unwrapSingleVariant } from './union.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A generated Markdown page for an OpenAPI route, ready to be served verbatim by
|
|
9
|
+
* the `.md` router / written as an `/assets/md` asset.
|
|
10
|
+
*/
|
|
11
|
+
export type Page = {
|
|
12
|
+
path: string
|
|
13
|
+
title: string
|
|
14
|
+
description?: string | undefined
|
|
15
|
+
/** Final Markdown, used verbatim (no MDX reprocessing). */
|
|
16
|
+
content: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Builds Markdown pages for every configured OpenAPI section.
|
|
21
|
+
*
|
|
22
|
+
* Produces one page per mount (the section overview, listing every category and
|
|
23
|
+
* its endpoints) plus one page per category (`${mount}/${group.id}`), rendering
|
|
24
|
+
* each operation's parameters, request body, responses, and an example request.
|
|
25
|
+
*
|
|
26
|
+
* These mirror the routes created in the Waku router so `/<mount>.md` and
|
|
27
|
+
* `/<mount>/<group>.md` resolve to a readable, agent-friendly version of the
|
|
28
|
+
* same reference shown in the browser.
|
|
29
|
+
*/
|
|
30
|
+
export async function toPages(config: Config): Promise<Page[]> {
|
|
31
|
+
if (!config.openapi?.length) return []
|
|
32
|
+
|
|
33
|
+
const specs = await Registry.build(config)
|
|
34
|
+
return Object.values(specs).flatMap(fromIr)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds the Markdown pages for a single parsed spec: the section overview plus
|
|
39
|
+
* one page per category. Pure (no registry/network), so it's the unit-testable
|
|
40
|
+
* seam under {@link toPages}.
|
|
41
|
+
*/
|
|
42
|
+
export function fromIr(ir: Ir): Page[] {
|
|
43
|
+
return [overviewPage(ir), ...ir.groups.map((group) => groupPage(ir, group))]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Section overview page: API metadata + every category's endpoints. */
|
|
47
|
+
function overviewPage(ir: Ir): Page {
|
|
48
|
+
return {
|
|
49
|
+
path: ir.path,
|
|
50
|
+
title: ir.info.title,
|
|
51
|
+
description: ir.info.description ? oneLine(ir.info.description) : undefined,
|
|
52
|
+
content: renderOverview(ir, normalizeMount(ir.path)),
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Markdown for the section overview: API metadata + every category's endpoints.
|
|
58
|
+
*
|
|
59
|
+
* `linkBase` is prepended to operation anchors so the endpoint links resolve to
|
|
60
|
+
* wherever the section is mounted (the site uses the configured spec path; the
|
|
61
|
+
* standalone handler passes the live host mount).
|
|
62
|
+
*/
|
|
63
|
+
export function renderOverview(ir: Ir, linkBase: string): string {
|
|
64
|
+
const base = normalizeMount(linkBase)
|
|
65
|
+
const lines: string[] = [`# ${ir.info.title}`, '']
|
|
66
|
+
if (ir.info.version) lines.push(`Version: \`${ir.info.version}\``, '')
|
|
67
|
+
if (ir.info.description) lines.push(ir.info.description.trim(), '')
|
|
68
|
+
|
|
69
|
+
if (ir.servers.length > 0) {
|
|
70
|
+
lines.push('## Servers', '')
|
|
71
|
+
for (const server of ir.servers)
|
|
72
|
+
lines.push(
|
|
73
|
+
`- \`${server.url}\`${server.description ? `: ${oneLine(server.description)}` : ''}`,
|
|
74
|
+
)
|
|
75
|
+
lines.push('')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
lines.push('## Endpoints', '')
|
|
79
|
+
for (const group of ir.groups) {
|
|
80
|
+
lines.push(`### ${group.name}`, '')
|
|
81
|
+
if (group.description) lines.push(oneLine(group.description), '')
|
|
82
|
+
for (const operation of group.operations) {
|
|
83
|
+
const href = `${base}/${group.id}#${operation.id}`
|
|
84
|
+
const label = operation.summary ? `: ${oneLine(operation.summary)}` : ''
|
|
85
|
+
lines.push(`- [\`${operation.method} ${operation.path}\`](${href})${label}`)
|
|
86
|
+
}
|
|
87
|
+
lines.push('')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return `${lines.join('\n').trimEnd()}\n`
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Category page: full detail for each operation in the group. */
|
|
94
|
+
function groupPage(ir: Ir, group: IrGroup): Page {
|
|
95
|
+
return {
|
|
96
|
+
path: `${normalizeMount(ir.path)}/${group.id}`,
|
|
97
|
+
title: group.name,
|
|
98
|
+
description: group.description ? oneLine(group.description) : undefined,
|
|
99
|
+
content: renderGroup(ir, group),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Markdown for a single category: full detail for each of its operations. */
|
|
104
|
+
export function renderGroup(ir: Ir, group: IrGroup): string {
|
|
105
|
+
const lines: string[] = [`# ${group.name}`, '']
|
|
106
|
+
if (group.description) lines.push(group.description.trim(), '')
|
|
107
|
+
|
|
108
|
+
const server = ir.servers[0]?.url
|
|
109
|
+
for (const operation of group.operations) lines.push(...operationLines(operation, server))
|
|
110
|
+
|
|
111
|
+
return `${lines.join('\n').trimEnd()}\n`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function operationLines(operation: IrOperation, server?: string): string[] {
|
|
115
|
+
const lines: string[] = []
|
|
116
|
+
const title = operation.summary ?? `${operation.method} ${operation.path}`
|
|
117
|
+
lines.push(`## ${title}`, '')
|
|
118
|
+
lines.push(`\`${operation.method} ${operation.path}\``, '')
|
|
119
|
+
if (operation.deprecated) lines.push('> **Deprecated**', '')
|
|
120
|
+
if (operation.description) lines.push(operation.description.trim(), '')
|
|
121
|
+
|
|
122
|
+
const byLocation = (location: IrParameter['in']) =>
|
|
123
|
+
operation.parameters.filter((parameter) => parameter.in === location && !parameter.deprecated)
|
|
124
|
+
|
|
125
|
+
for (const [location, heading] of [
|
|
126
|
+
['path', 'Path parameters'],
|
|
127
|
+
['query', 'Query parameters'],
|
|
128
|
+
['header', 'Header parameters'],
|
|
129
|
+
['rpc', 'Parameters'],
|
|
130
|
+
] as const) {
|
|
131
|
+
const params = byLocation(location)
|
|
132
|
+
if (params.length === 0) continue
|
|
133
|
+
lines.push(`### ${heading}`, '')
|
|
134
|
+
for (const parameter of params) lines.push(...parameterLines(parameter))
|
|
135
|
+
lines.push('')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (operation.requestBody && !operation.requestBody.hidden) {
|
|
139
|
+
const body = operation.requestBody
|
|
140
|
+
const media = body.content[0]
|
|
141
|
+
const mediaType = media ? ` (\`${media.mediaType}\`)` : ''
|
|
142
|
+
lines.push(`### Request body${body.required ? ' (required)' : ''}${mediaType}`, '')
|
|
143
|
+
if (body.description) lines.push(oneLine(body.description), '')
|
|
144
|
+
if (media?.schema) {
|
|
145
|
+
const props = schemaLines(media.schema, 0)
|
|
146
|
+
if (props.length > 0) lines.push(...props, '')
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (operation.responses.length > 0) {
|
|
151
|
+
lines.push('### Responses', '')
|
|
152
|
+
for (const response of operation.responses) lines.push(...responseLines(response))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const samples = codeSamples(operation, server)
|
|
156
|
+
if (samples.length > 0) {
|
|
157
|
+
lines.push('### Example request', '')
|
|
158
|
+
for (const sample of samples) {
|
|
159
|
+
lines.push(`\`\`\`${sample.lang}`, sample.code.trim(), '```', '')
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return lines
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parameterLines(parameter: IrParameter): string[] {
|
|
167
|
+
// Unwrap a nullable single-variant union so an object param's member type and
|
|
168
|
+
// nested properties surface (e.g. an RPC `Transaction` object's fields).
|
|
169
|
+
const schema = parameter.schema
|
|
170
|
+
? (unwrapSingleVariant(parameter.schema) ?? parameter.schema)
|
|
171
|
+
: parameter.schema
|
|
172
|
+
const required = parameter.required ? ' _(required)_' : ''
|
|
173
|
+
const type = `\`${typeLabel(schema)}\``
|
|
174
|
+
const description = parameter.description ? `: ${oneLine(parameter.description)}` : ''
|
|
175
|
+
return [`- \`${parameter.name}\` ${type}${required}${description}`, ...schemaLines(schema, 1)]
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function responseLines(response: IrResponse): string[] {
|
|
179
|
+
const lines: string[] = []
|
|
180
|
+
const description = response.description ? `: ${oneLine(response.description)}` : ''
|
|
181
|
+
lines.push(`#### \`${response.status}\`${description}`, '')
|
|
182
|
+
|
|
183
|
+
if (response.headers.length > 0) {
|
|
184
|
+
lines.push('Headers:', '')
|
|
185
|
+
for (const header of response.headers)
|
|
186
|
+
lines.push(
|
|
187
|
+
`- \`${header.name}\` \`${typeLabel(header.schema)}\`${
|
|
188
|
+
header.description ? `: ${oneLine(header.description)}` : ''
|
|
189
|
+
}`,
|
|
190
|
+
)
|
|
191
|
+
lines.push('')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const media = response.content[0]
|
|
195
|
+
if (media?.schema) {
|
|
196
|
+
const props = schemaLines(media.schema, 0)
|
|
197
|
+
if (props.length > 0) {
|
|
198
|
+
if (media.mediaType) lines.push(`Body (\`${media.mediaType}\`):`, '')
|
|
199
|
+
lines.push(...props, '')
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return lines
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const maxDepth = 4
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Renders an object schema's properties as an indented Markdown bullet list,
|
|
210
|
+
* recursing into nested objects (and array-of-object items) up to {@link
|
|
211
|
+
* maxDepth}. Returns an empty array for non-object schemas.
|
|
212
|
+
*/
|
|
213
|
+
function schemaLines(rawSchema: SchemaObject | undefined, depth: number): string[] {
|
|
214
|
+
if (!rawSchema || depth > maxDepth) return []
|
|
215
|
+
|
|
216
|
+
// Unwrap a nullable single-variant union (e.g. `oneOf: [null, X]`) so the
|
|
217
|
+
// member's properties are listed instead of nothing.
|
|
218
|
+
const schema = unwrapSingleVariant(rawSchema) ?? rawSchema
|
|
219
|
+
const target = schema['type'] === 'array' ? (schema['items'] as SchemaObject) : schema
|
|
220
|
+
const properties = target?.['properties'] as Record<string, SchemaObject> | undefined
|
|
221
|
+
if (!properties) return []
|
|
222
|
+
|
|
223
|
+
const required = new Set((target?.['required'] as string[] | undefined) ?? [])
|
|
224
|
+
const indent = ' '.repeat(depth)
|
|
225
|
+
const lines: string[] = []
|
|
226
|
+
|
|
227
|
+
for (const [name, rawProperty] of Object.entries(properties)) {
|
|
228
|
+
const property = unwrapSingleVariant(rawProperty) ?? rawProperty
|
|
229
|
+
const requiredFlag = required.has(name) ? ' _(required)_' : ''
|
|
230
|
+
const description = property['description']
|
|
231
|
+
? `: ${oneLine(String(property['description']))}`
|
|
232
|
+
: ''
|
|
233
|
+
lines.push(`${indent}- \`${name}\` \`${typeLabel(property)}\`${requiredFlag}${description}`)
|
|
234
|
+
lines.push(...schemaLines(property, depth + 1))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return lines
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
type SchemaObject = Record<string, unknown>
|
|
241
|
+
|
|
242
|
+
/** Compact, human-readable type label for a JSON Schema (post-dereference). */
|
|
243
|
+
function typeLabel(schema: SchemaObject | undefined): string {
|
|
244
|
+
if (!schema) return 'unknown'
|
|
245
|
+
|
|
246
|
+
if (Array.isArray(schema['type'])) return (schema['type'] as string[]).join(' | ')
|
|
247
|
+
|
|
248
|
+
const composite = (schema['oneOf'] ?? schema['anyOf']) as SchemaObject[] | undefined
|
|
249
|
+
if (composite) return [...new Set(composite.map(typeLabel))].join(' | ')
|
|
250
|
+
|
|
251
|
+
if (Array.isArray(schema['allOf']))
|
|
252
|
+
return (schema['allOf'] as SchemaObject[]).map(typeLabel).join(' & ')
|
|
253
|
+
|
|
254
|
+
if (schema['type'] === 'array') return `${typeLabel(schema['items'] as SchemaObject)}[]`
|
|
255
|
+
|
|
256
|
+
if (typeof schema['type'] === 'string') {
|
|
257
|
+
const format = schema['format'] ? ` <${schema['format']}>` : ''
|
|
258
|
+
return `${schema['type']}${format}`
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (Array.isArray(schema['enum'])) return 'enum'
|
|
262
|
+
if (schema['properties']) return 'object'
|
|
263
|
+
|
|
264
|
+
return 'unknown'
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Collapse Markdown/whitespace to a single readable line (for summaries). */
|
|
268
|
+
function oneLine(value: string): string {
|
|
269
|
+
return value.replace(/\s+/g, ' ').trim()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Mount path without a trailing slash (e.g. `/` -> ``, `/api/` -> `/api`). */
|
|
273
|
+
function normalizeMount(path: string): string {
|
|
274
|
+
return path === '/' ? '' : path.replace(/\/$/, '')
|
|
275
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI integration config.
|
|
3
|
+
*
|
|
4
|
+
* Mounts an isolated, auto-generated API reference section in the docs from an
|
|
5
|
+
* OpenAPI spec. Use {@link from} to define an entry in `vocs.config.ts`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type * as VocsConfig from '../config.js'
|
|
9
|
+
import type { SidebarItem } from '../sidebar.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Spec source.
|
|
13
|
+
*
|
|
14
|
+
* - A file path relative to the project root (e.g. `./openapi.yaml`).
|
|
15
|
+
* - A URL (e.g. `https://example.com/openapi.json`).
|
|
16
|
+
* - Raw JSON/YAML content.
|
|
17
|
+
* - An inline OpenAPI document object.
|
|
18
|
+
*/
|
|
19
|
+
export type Spec = string | Record<string, unknown>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A spec source, an already-started promise resolving to one, or a (lazy)
|
|
23
|
+
* provider that returns one. Promises/providers are awaited once when the
|
|
24
|
+
* reference is first rendered (and the result reused), so specs generated at
|
|
25
|
+
* runtime — e.g. from a server framework like `hono-openapi` — can be passed
|
|
26
|
+
* without resolving them up front. This lets `Handler.openApi` be constructed
|
|
27
|
+
* synchronously (and mounted via `app.route(...)`) even when the spec is
|
|
28
|
+
* produced asynchronously: build it eagerly and pass the promise directly.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* const spec = generateSpecs(app) // Promise<Spec>, started eagerly
|
|
33
|
+
* app.route('/', Handler.openApi({ spec }, { fallback: 'next' }))
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export type SpecInput = Spec | Promise<Spec> | (() => Spec | Promise<Spec>)
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extra sidebar items to add around the auto-generated section.
|
|
40
|
+
*
|
|
41
|
+
* These are typically links to consumer-authored override/guide pages mounted
|
|
42
|
+
* under the same `path` (e.g. `pages/api/auth.mdx`).
|
|
43
|
+
*/
|
|
44
|
+
export type SidebarExtras = {
|
|
45
|
+
/** Items prepended above the generated `Introduction`/categories. */
|
|
46
|
+
top?: SidebarItem[] | undefined
|
|
47
|
+
/** Items appended below the generated categories. */
|
|
48
|
+
bottom?: SidebarItem[] | undefined
|
|
49
|
+
/**
|
|
50
|
+
* Items nested under the generated `Introduction` entry. When provided, the
|
|
51
|
+
* `Introduction` leaf becomes a collapsible group whose first child is an
|
|
52
|
+
* `Overview` link to the landing page, followed by these items (typically
|
|
53
|
+
* links to guide `pages` mounted under the section, e.g. authentication or
|
|
54
|
+
* versioning).
|
|
55
|
+
*/
|
|
56
|
+
intro?: SidebarItem[] | undefined
|
|
57
|
+
/**
|
|
58
|
+
* Collapse the generated category groups by default. The group matching the
|
|
59
|
+
* active page still auto-expands, so deep links stay navigable. The generated
|
|
60
|
+
* `Introduction` entry is unaffected.
|
|
61
|
+
*
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
collapsed?: boolean | undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A consumer-authored `.md`/`.mdx` page mounted into an OpenAPI section.
|
|
69
|
+
*
|
|
70
|
+
* In the Vite/site integration these are discovered automatically by the file
|
|
71
|
+
* router (e.g. `pages/api/auth.mdx`). In the standalone server handler
|
|
72
|
+
* ({@link file://./../../server/handlers.ts `Handler.openApi`}) — which has no
|
|
73
|
+
* filesystem router — they are supplied explicitly so override/guide content can
|
|
74
|
+
* still be rendered.
|
|
75
|
+
*
|
|
76
|
+
* - `path` is the route relative to the section mount (e.g. `/` overrides the
|
|
77
|
+
* landing intro, `/auth` is a guide page, `/<group>` overrides a category
|
|
78
|
+
* header).
|
|
79
|
+
* - `file` is the path to the `.md`/`.mdx` source, resolved against the handler
|
|
80
|
+
* `rootDir` (default `process.cwd()`). Reading a file requires `node:fs`, so
|
|
81
|
+
* on filesystem-less runtimes (e.g. Cloudflare Workers) supply `content`
|
|
82
|
+
* instead.
|
|
83
|
+
* - `content` is the `.md`/`.mdx` source inline, an alternative to `file` that
|
|
84
|
+
* needs no filesystem access. Exactly one of `file`/`content` is required.
|
|
85
|
+
* - `title` overrides the document title otherwise taken from frontmatter or the
|
|
86
|
+
* first `# heading`, and renders as the page's heading on standalone guide
|
|
87
|
+
* pages.
|
|
88
|
+
* - `description` renders as a subtitle below the title on standalone guide
|
|
89
|
+
* pages (Markdown). Also taken from frontmatter `description` when omitted.
|
|
90
|
+
*/
|
|
91
|
+
export type Page = {
|
|
92
|
+
path: string
|
|
93
|
+
file?: string | undefined
|
|
94
|
+
content?: string | undefined
|
|
95
|
+
title?: string | undefined
|
|
96
|
+
description?: string | undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type Config = {
|
|
100
|
+
/**
|
|
101
|
+
* OpenAPI spec source: file path, URL, raw content, inline object, or a (lazy)
|
|
102
|
+
* provider function returning any of those.
|
|
103
|
+
*/
|
|
104
|
+
spec: SpecInput
|
|
105
|
+
/**
|
|
106
|
+
* Mount path for the generated API reference section.
|
|
107
|
+
*
|
|
108
|
+
* The section gets its own isolated sidebar scoped to this path.
|
|
109
|
+
*
|
|
110
|
+
* Required for the Vite/site integration. Optional for the standalone
|
|
111
|
+
* {@link file://./../../server/handlers.ts `Handler.openApi`} handler (the
|
|
112
|
+
* Hono mount provides the location; `path` only seeds generated link bases and
|
|
113
|
+
* defaults to `/`).
|
|
114
|
+
*
|
|
115
|
+
* @example "/api"
|
|
116
|
+
*/
|
|
117
|
+
path?: string | undefined
|
|
118
|
+
/**
|
|
119
|
+
* Extra sidebar items to add around the auto-generated section (e.g. links to
|
|
120
|
+
* consumer-authored guide pages mounted under `path`).
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* sidebar: {
|
|
125
|
+
* top: [{ text: 'Authentication', link: '/api/auth' }],
|
|
126
|
+
* bottom: [{ text: 'Errors', link: '/api/errors' }],
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
sidebar?: SidebarExtras | undefined
|
|
131
|
+
/**
|
|
132
|
+
* Consumer-authored `.md`/`.mdx` override/guide pages.
|
|
133
|
+
*
|
|
134
|
+
* Only used by the standalone {@link file://./../../server/handlers.ts
|
|
135
|
+
* `Handler.openApi`} handler — the Vite/site integration discovers these via
|
|
136
|
+
* the file router instead.
|
|
137
|
+
*/
|
|
138
|
+
pages?: Page[] | undefined
|
|
139
|
+
/**
|
|
140
|
+
* Vocs config passthrough for the standalone {@link
|
|
141
|
+
* file://./../../server/handlers.ts `Handler.openApi`} handler.
|
|
142
|
+
*
|
|
143
|
+
* The handler renders the real Vocs layout, so these options customize the
|
|
144
|
+
* chrome (theme, top navigation, logo, socials, …) exactly like a
|
|
145
|
+
* `vocs.config.ts`.
|
|
146
|
+
*
|
|
147
|
+
* `title` and `description` default to the spec's `info.title`/`info.description`
|
|
148
|
+
* but can be overridden here. `sidebar` is derived from the generated section
|
|
149
|
+
* (plus the `sidebar` config above) and cannot be set here.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* vocs: {
|
|
154
|
+
* title: 'Acme Docs',
|
|
155
|
+
* logoUrl: '/logo.svg',
|
|
156
|
+
* theme: { accentColor: '#7c3aed' },
|
|
157
|
+
* topNav: [{ text: 'Home', link: 'https://acme.com' }],
|
|
158
|
+
* }
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
vocs?: Omit<VocsConfig.define.Options, 'sidebar' | 'openapi'> | undefined
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Config for the Vite/site integration, where `path` is required (it scopes the
|
|
166
|
+
* generated sidebar and routes).
|
|
167
|
+
*/
|
|
168
|
+
export type SiteConfig = Config & {
|
|
169
|
+
path: string
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Defines an OpenAPI integration entry.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* import { OpenApi } from 'vocs/config'
|
|
178
|
+
*
|
|
179
|
+
* export default defineConfig({
|
|
180
|
+
* openapi: [
|
|
181
|
+
* OpenApi.from({ spec: './openapi.yaml', path: '/api' }),
|
|
182
|
+
* ],
|
|
183
|
+
* })
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export function from(config: from.Options): SiteConfig {
|
|
187
|
+
const { spec, path, sidebar, pages } = config
|
|
188
|
+
|
|
189
|
+
if (!spec) throw new Error('[vocs] `openapi` entry is missing a `spec`.')
|
|
190
|
+
if (!path) throw new Error('[vocs] `openapi` entry is missing a `path`.')
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
spec,
|
|
194
|
+
path: normalizePath(path),
|
|
195
|
+
...(sidebar ? { sidebar } : {}),
|
|
196
|
+
...(pages ? { pages } : {}),
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export declare namespace from {
|
|
201
|
+
type Options = SiteConfig
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Normalizes a mount path: ensures a leading slash and strips any trailing
|
|
206
|
+
* slash (except for the root `/`).
|
|
207
|
+
*/
|
|
208
|
+
export function normalizePath(path: string): string {
|
|
209
|
+
let normalized = path.startsWith('/') ? path : `/${path}`
|
|
210
|
+
if (normalized.length > 1 && normalized.endsWith('/')) normalized = normalized.slice(0, -1)
|
|
211
|
+
return normalized
|
|
212
|
+
}
|