rapidoc 11.0.1 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -54
- package/dist/rapidoc-flow-min.js +580 -0
- package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
- package/dist/rapidoc-min.js +645 -202
- package/dist/rapidoc-min.js.LICENSE.txt +381 -0
- package/package.json +57 -42
- package/src/README.md +150 -0
- package/src/components/README.md +91 -0
- package/src/components/api-request.ts +912 -0
- package/src/components/api-response.ts +514 -0
- package/src/components/json-tree.ts +171 -0
- package/src/components/schema-base-element.ts +329 -0
- package/src/components/schema-table.ts +475 -0
- package/src/components/schema-tree.ts +393 -0
- package/src/components/tag-input.ts +180 -0
- package/src/flow.js +3 -0
- package/src/index.js +6 -0
- package/src/oauth-receiver.ts +29 -0
- package/src/rapi-flow.ts +1178 -0
- package/src/rapidoc-mini.ts +603 -0
- package/src/rapidoc.ts +1854 -0
- package/src/styles/README.md +96 -0
- package/src/styles/auth-styles.js +318 -0
- package/src/styles/border-styles.js +24 -0
- package/src/styles/css-bundle.js +129 -0
- package/src/styles/custom-styles.js +8 -0
- package/src/styles/dialog-box-styles.js +102 -0
- package/src/styles/endpoint-styles.js +564 -0
- package/src/styles/flex-styles.js +16 -0
- package/src/styles/flow-styles.js +248 -0
- package/src/styles/font-styles.js +432 -0
- package/src/styles/info-styles.js +36 -0
- package/src/styles/input-styles.js +555 -0
- package/src/styles/layout-styles.js +396 -0
- package/src/styles/microlighter-styles.js +99 -0
- package/src/styles/nav-styles.js +633 -0
- package/src/styles/param-list-styles.js +261 -0
- package/src/styles/schema-styles.js +296 -0
- package/src/styles/tab-styles.js +106 -0
- package/src/styles/theme-tokens.js +582 -0
- package/src/templates/README.md +89 -0
- package/src/templates/advanced-search-dialog-template.js +94 -0
- package/src/templates/api-response-template.js +223 -0
- package/src/templates/callback-template.js +86 -0
- package/src/templates/code-samples-template.js +52 -0
- package/src/templates/components-template.js +86 -0
- package/src/templates/downloads-template.js +70 -0
- package/src/templates/endpoint-template.js +377 -0
- package/src/templates/expanded-endpoint-template.js +270 -0
- package/src/templates/focused-endpoint-template.js +146 -0
- package/src/templates/header-template.js +92 -0
- package/src/templates/logo-template.js +21 -0
- package/src/templates/main-body-template.js +102 -0
- package/src/templates/navbar-template.js +498 -0
- package/src/templates/overview-template.js +104 -0
- package/src/templates/request-body-template.js +673 -0
- package/src/templates/request-params-template.js +485 -0
- package/src/templates/security-scheme-template.js +1272 -0
- package/src/templates/server-template.js +153 -0
- package/src/utils/README.md +146 -0
- package/src/utils/arazzo/README.md +119 -0
- package/src/utils/arazzo/components.js +15 -0
- package/src/utils/arazzo/graph.js +269 -0
- package/src/utils/arazzo/index.js +52 -0
- package/src/utils/arazzo/operations.js +106 -0
- package/src/utils/arazzo/parse.js +196 -0
- package/src/utils/arazzo/refs.js +40 -0
- package/src/utils/arazzo/svg.js +707 -0
- package/src/utils/auto-color-scheme.js +99 -0
- package/src/utils/bind-navbar.js +175 -0
- package/src/utils/color-utils.js +59 -0
- package/src/utils/common-utils.js +191 -0
- package/src/utils/curl-utils.js +126 -0
- package/src/utils/highlighter.js +419 -0
- package/src/utils/markdown-utils.js +110 -0
- package/src/utils/mcp/README.md +155 -0
- package/src/utils/mcp/common.js +166 -0
- package/src/utils/mcp/manifest.js +382 -0
- package/src/utils/mcp/project/server.ts +53 -0
- package/src/utils/mcp/project.js +88 -0
- package/src/utils/mcp/runtime.js +289 -0
- package/src/utils/mcp/schema.js +98 -0
- package/src/utils/mcp/workflow-runtime.js +374 -0
- package/src/utils/mcp/workflows.js +240 -0
- package/src/utils/mcp/zip.js +102 -0
- package/src/utils/mcp-generator.js +47 -0
- package/src/utils/mock-interceptor.js +557 -0
- package/src/utils/oauth-utils.js +87 -0
- package/src/utils/request-utils.js +264 -0
- package/src/utils/response-utils.js +203 -0
- package/src/utils/sanitize.js +57 -0
- package/src/utils/schema-utils.js +1954 -0
- package/src/utils/slugger.js +187 -0
- package/src/utils/spec-parser.js +941 -0
- package/src/utils/url-utils.js +246 -0
- package/src/utils/xml-utils.js +61 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the API server selector dropdown and server variable configuration form.
|
|
3
|
+
*/
|
|
4
|
+
import { html } from "lit";
|
|
5
|
+
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
6
|
+
import { sanitizeHTML } from "../utils/sanitize.js";
|
|
7
|
+
import { marked } from "marked";
|
|
8
|
+
|
|
9
|
+
export function setApiServer(serverUrl) {
|
|
10
|
+
const serverObj = this.resolvedSpec?.servers.find((s) => s.url === serverUrl);
|
|
11
|
+
if (!serverObj) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
this.selectedServer = serverObj;
|
|
15
|
+
this.requestUpdate();
|
|
16
|
+
this.dispatchEvent(
|
|
17
|
+
new CustomEvent("api-server-change", {
|
|
18
|
+
bubbles: true,
|
|
19
|
+
composed: true,
|
|
20
|
+
detail: {
|
|
21
|
+
selectedServer: serverObj,
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function onApiServerVarChange(e, serverObj) {
|
|
29
|
+
// The variables render as a list, <ul class="server-vars">
|
|
30
|
+
const inputEls = [...e.currentTarget.closest(".server-vars").querySelectorAll("input, select")];
|
|
31
|
+
let tempUrl = serverObj.url;
|
|
32
|
+
inputEls.forEach((v) => {
|
|
33
|
+
const regex = new RegExp(`{${v.dataset.var}}`, "g");
|
|
34
|
+
tempUrl = tempUrl.replace(regex, v.value);
|
|
35
|
+
});
|
|
36
|
+
serverObj.computedUrl = tempUrl;
|
|
37
|
+
this.requestUpdate();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function serverVarsTemplate() {
|
|
41
|
+
// const selectedServerObj = this.resolvedSpec.servers.find((v) => (v.url === this.selectedServer));
|
|
42
|
+
return this.selectedServer && this.selectedServer.variables
|
|
43
|
+
? html`
|
|
44
|
+
<h3 class="rapidoc-heading label-heading table-title" part="heading heading-3">Server variables</h3>
|
|
45
|
+
<div class="param-list-container">
|
|
46
|
+
<ul class="param-list server-vars" part="param-list" role="list">
|
|
47
|
+
${Object.entries(this.selectedServer.variables).map(
|
|
48
|
+
(kv) => html`
|
|
49
|
+
<li class="param" part="param">
|
|
50
|
+
<div class="param-label" part="param-label">
|
|
51
|
+
<div class="param-name" part="param-name">${kv[0]}</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="param-input" part="param-input">
|
|
54
|
+
${
|
|
55
|
+
kv[1].enum
|
|
56
|
+
? html` <select
|
|
57
|
+
data-var="${kv[0]}"
|
|
58
|
+
@input=${(e) => {
|
|
59
|
+
onApiServerVarChange.call(this, e, this.selectedServer);
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
${Object.entries(kv[1].enum).map((e) =>
|
|
63
|
+
kv[1].default === e[1]
|
|
64
|
+
? html`<option selected label=${e[1]} value=${e[1]}></option>`
|
|
65
|
+
: html`<option label=${e[1]} value=${e[1]}></option>`,
|
|
66
|
+
)}
|
|
67
|
+
</select>`
|
|
68
|
+
: html` <input
|
|
69
|
+
type="text"
|
|
70
|
+
part="textbox textbox-server-var"
|
|
71
|
+
spellcheck="false"
|
|
72
|
+
data-var="${kv[0]}"
|
|
73
|
+
value="${kv[1].default}"
|
|
74
|
+
@input=${(e) => {
|
|
75
|
+
onApiServerVarChange.call(this, e, this.selectedServer);
|
|
76
|
+
}}
|
|
77
|
+
/>`
|
|
78
|
+
}
|
|
79
|
+
</div>
|
|
80
|
+
${
|
|
81
|
+
kv[1].description
|
|
82
|
+
? html`<div class="param-details" part="param-details">
|
|
83
|
+
<div class="m-markdown-small">${unsafeHTML(sanitizeHTML(marked(kv[1].description)))}</div>
|
|
84
|
+
</div>`
|
|
85
|
+
: ""
|
|
86
|
+
}
|
|
87
|
+
</li>
|
|
88
|
+
`,
|
|
89
|
+
)}
|
|
90
|
+
</ul>
|
|
91
|
+
</div>
|
|
92
|
+
`
|
|
93
|
+
: "";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default function serverTemplate() {
|
|
97
|
+
if (!this.resolvedSpec || this.resolvedSpec.specLoadError) {
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
return html`<section
|
|
101
|
+
id="servers"
|
|
102
|
+
part="section-servers"
|
|
103
|
+
style="text-align:left; direction:ltr; margin-top:24px; margin-bottom:24px;"
|
|
104
|
+
class="regular-font observe-me ${"read focused".includes(this.renderStyle) ? "section-gap--read-mode" : "section-gap"}"
|
|
105
|
+
>
|
|
106
|
+
<h2 part="section-servers-title heading heading-2" class="rapidoc-heading sub-title" style="display:flex; align-items:center; gap:8px;">
|
|
107
|
+
API Server
|
|
108
|
+
${
|
|
109
|
+
this.mockServer === "true"
|
|
110
|
+
? html`<span
|
|
111
|
+
style="font-size:var(--font-size-small); font-weight:bold; color:var(--green); border:1px solid var(--green); border-radius:var(--radius); padding:1px 6px; letter-spacing:0.5px;"
|
|
112
|
+
title="In-browser mock server is active"
|
|
113
|
+
>
|
|
114
|
+
MOCK ACTIVE
|
|
115
|
+
</span>`
|
|
116
|
+
: ""
|
|
117
|
+
}
|
|
118
|
+
</h2>
|
|
119
|
+
<div class="mono-font" style="margin: 12px 0; font-size:calc(var(--font-size-small) + 1px);">
|
|
120
|
+
${
|
|
121
|
+
!this.resolvedSpec.servers || this.resolvedSpec.servers?.length === 0
|
|
122
|
+
? ""
|
|
123
|
+
: html`
|
|
124
|
+
${this.resolvedSpec?.servers.map(
|
|
125
|
+
(server, i) => html`
|
|
126
|
+
<div class="server-opt-row">
|
|
127
|
+
<input
|
|
128
|
+
type="radio"
|
|
129
|
+
name="api_server"
|
|
130
|
+
id="srvr-opt-${i}"
|
|
131
|
+
value="${server.url}"
|
|
132
|
+
@change=${() => {
|
|
133
|
+
setApiServer.call(this, server.url);
|
|
134
|
+
}}
|
|
135
|
+
.checked="${this.selectedServer.url === server.url}"
|
|
136
|
+
/>
|
|
137
|
+
<label for="srvr-opt-${i}">
|
|
138
|
+
${server.url} ${server.description ? html`- <span class="regular-font">${unsafeHTML(sanitizeHTML(marked.parseInline(server.description)))} </span>` : ""}
|
|
139
|
+
</label>
|
|
140
|
+
</div>
|
|
141
|
+
`,
|
|
142
|
+
)}
|
|
143
|
+
`
|
|
144
|
+
}
|
|
145
|
+
<div class="table-title primary-text" part="label-selected-server">
|
|
146
|
+
SELECTED: ${this.selectedServer?.computedUrl || "none"}
|
|
147
|
+
${this.mockServer === "true" ? html`<span style="color:var(--green); margin-left:6px;">(Mocked)</span>` : ""}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
<slot name="servers"></slot>
|
|
151
|
+
${serverVarsTemplate.call(this)}
|
|
152
|
+
</section>`;
|
|
153
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# RapiDoc Utilities
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Framework-free helpers behind the `<rapi-doc>` component (`src/rapidoc.ts`), its templates and
|
|
6
|
+
the static renderer (`@rapidoc/prerender`, `packages/rapidoc-prerender`). `utils/` owns the spec pipeline (load,
|
|
7
|
+
dereference, group, anchor), schema rendering and example generation, request/response/mock
|
|
8
|
+
helpers, and UI helpers (highlighting, Markdown, sanitizing, navbar binding, color, OAuth). It
|
|
9
|
+
does not own rendering (`templates/`, `components/`) or styles. The MCP manifest, runtime and
|
|
10
|
+
project writer are documented in [`./mcp/README.md`](./mcp/README.md); Arazzo parsing and workflow diagrams in
|
|
11
|
+
[`./arazzo/README.md`](./arazzo/README.md).
|
|
12
|
+
|
|
13
|
+
Everything except `arazzo/` is bundled into `dist/rapidoc-min.js`; `arazzo/` goes into
|
|
14
|
+
`dist/rapidoc-flow-min.js` and `@rapidoc/prerender`. `@rapidoc/portal` depends on this folder
|
|
15
|
+
indirectly through `@rapidoc/prerender` (`methodPathSlug`, `operationAnchorSource`, `toKebabCase`)
|
|
16
|
+
and the anchors `<rapi-doc>` emits, so anchor generation is public behavior.
|
|
17
|
+
Several modules run in Node (static renderer, unit tests) and must guard `window`/`document`.
|
|
18
|
+
|
|
19
|
+
## Interface
|
|
20
|
+
|
|
21
|
+
Spec pipeline:
|
|
22
|
+
|
|
23
|
+
- `spec-parser.js`: default `ProcessSpec` (spec URL, text or object to the render model of
|
|
24
|
+
tags, tag groups, components, security schemes, servers); `isTagExpanded`, `groupByTagGroups`.
|
|
25
|
+
- `slugger.js`: `OperationSlugger` (`slug`, `slugOp`, `slugTag`, `slugTagGroup`, `reset`),
|
|
26
|
+
`operationAnchorSource`, `methodPathSlug`, `toKebabCase`, `clampWordBoundary`, `createSlugger`.
|
|
27
|
+
- `common-utils.js`: search (`advancedSearch`, `getMatchedPaths`), `debounce`, `sleep`,
|
|
28
|
+
clipboard and download helpers, `rapidocApiKey`.
|
|
29
|
+
|
|
30
|
+
Schema rendering:
|
|
31
|
+
|
|
32
|
+
- `schema-utils.js`: `getTypeInfo`, `schemaToAST` (schema tree for renderers),
|
|
33
|
+
`schemaInObjectNotation` (older tree format), `schemaToSampleObj` and `generateExample`
|
|
34
|
+
(examples), `json2xml`, `getMergedProperties`/`getMergedRequired`.
|
|
35
|
+
|
|
36
|
+
Request, response, mock:
|
|
37
|
+
|
|
38
|
+
- `url-utils.js`: path/query serialization, `constructFetchURL`, `oauthReceiverUrl`.
|
|
39
|
+
- `request-utils.js`: headers, url-encoded and multipart bodies.
|
|
40
|
+
- `response-utils.js`: `processFetchResponse` (decode a `Response`),
|
|
41
|
+
`prepareResponseDefinitions`.
|
|
42
|
+
- `curl-utils.js`: `generateCurlCommand`.
|
|
43
|
+
- `mock-interceptor.js`: `enableMockServer`, `disableMockServer`, `updateMockConfig`,
|
|
44
|
+
`isMockServerActive`, `compileRoutes` (from a `ProcessSpec` result).
|
|
45
|
+
|
|
46
|
+
UI helpers:
|
|
47
|
+
|
|
48
|
+
- `highlighter.js`: `highlightAllCode`, `scheduleHighlight` (CSS Custom Highlight API).
|
|
49
|
+
- `markdown-utils.js`: `configureMarkdown` and token helpers for the global `marked`.
|
|
50
|
+
- `sanitize.js`: `sanitizeHTML`.
|
|
51
|
+
- `bind-navbar.js`: `bindNavBar` ties a static `getNavBarHtml` sidebar to `<rapi-doc>`.
|
|
52
|
+
- `auto-color-scheme.js`: `AutoColorSchemeController`, `resolvePageColorScheme`.
|
|
53
|
+
- `oauth-utils.js`: PKCE (`randomUrlSafe`, `pkceChallenge`), `readOAuthReply`,
|
|
54
|
+
`isExpectedOAuthReply`.
|
|
55
|
+
- `color-utils.js`, `xml-utils.js` (`formatXml`): pure helpers.
|
|
56
|
+
- `mcp-generator.js`: `generateMcpServer(spec, options) -> { manifest, files, budget, hostConfig }`,
|
|
57
|
+
re-exporting `./mcp` (`buildMcpManifest`, `createMcpRuntime`, `writeMcpProject`, `createZip`,
|
|
58
|
+
`parseMcpEnabled`, ...).
|
|
59
|
+
|
|
60
|
+
## Invariants
|
|
61
|
+
|
|
62
|
+
- **`ProcessSpec` never rejects** (`[spec-parser.js:ProcessSpec]`). Loading and post-load
|
|
63
|
+
processing are each wrapped in `try/catch`. Every failure resolves to
|
|
64
|
+
`{ specLoadError: true, isSpecLoading: false, info: { title, description, version }, tags: [],
|
|
65
|
+
tagGroups: [] }`, where `description` is `"<url> ┃ Not Found (404)"`,
|
|
66
|
+
`"Unable to load <url> ┃ <status|message>"`, `"Invalid Spec Definition"`, or
|
|
67
|
+
`"Unable to process the spec ┃ <message>"` for failures after loading. Callers check
|
|
68
|
+
`specLoadError`, not exceptions. It uses an optional `this` (`requestUpdate`,
|
|
69
|
+
`before-render` event), so the component calls it with `.call(element, ...)`.
|
|
70
|
+
- **Webhooks** (`[spec-parser.js:groupByTags]`) are grouped after paths and flagged
|
|
71
|
+
`isWebhook`; they are never written into the spec's `paths`, so a webhook named like a path
|
|
72
|
+
does not replace it.
|
|
73
|
+
- **Stable anchors** (`[slugger.js:operationAnchorSource]`, `[slugger.js:OperationSlugger.slug]`):
|
|
74
|
+
- Source priority: `operationId` (trimmed, 1-45 chars, not a placeholder like `operation12`),
|
|
75
|
+
then `summary` (the parser passes `summary || description`; clamped to 40 chars at a word
|
|
76
|
+
boundary), then `methodPathSlug(method, path)`.
|
|
77
|
+
- Memoized per method + path (and tag slug), so one operation keeps one `elementId` per run.
|
|
78
|
+
- Tag prefix `<tagSlug>--` is applied only when the spec has non-empty `x-tagGroups` or
|
|
79
|
+
`x-tag-groups` (`[spec-parser.js:ProcessSpec]`), and never for the tag `general`.
|
|
80
|
+
- Collisions try `<prefix><method>-<slug>` first, then `github-slugger`'s `-1`, `-2` suffixes,
|
|
81
|
+
which depend on encounter order (paths, then webhooks; methods in `supportedMethods` order).
|
|
82
|
+
- Tag and tag-group ids (`tag--<kebab>`, `tag-group--<kebab>`) are not deduplicated.
|
|
83
|
+
- The portal links to these ids and checks them with `operationAnchorSource`/`methodPathSlug`;
|
|
84
|
+
changing any rule above is a breaking change for published portals.
|
|
85
|
+
- **Circular references** (`[spec-parser.js:breakCircularRefs]`): after dereferencing, the tree
|
|
86
|
+
is cloned without `x-ref` keys and each cycle becomes `{ $ref }` (fallback
|
|
87
|
+
`#/components/schemas/Circular`); shared non-cyclic nodes stay shared. Schema code treats a
|
|
88
|
+
remaining `$ref` as the cycle marker, so renderers must not recurse into it.
|
|
89
|
+
- **Schema depth limits** (`[schema-utils.js]`): `schemaToSampleObj` returns `undefined` and
|
|
90
|
+
`schemaInObjectNotation` returns a `::type` stub when `level > 8`. `schemaToAST` expands to
|
|
91
|
+
`MAX_SCHEMA_DEPTH` (8) and, while the tree exceeds `SCHEMA_NODE_BUDGET` (8000 nodes), retries
|
|
92
|
+
with a smaller depth down to `MIN_SCHEMA_DEPTH` (4); cut containers become nodes with
|
|
93
|
+
`truncated: true`. Its module state makes it synchronous and non-reentrant.
|
|
94
|
+
- **Sanitization** (`[sanitize.js:sanitizeHTML]`): non-strings return `""`. The native
|
|
95
|
+
`Sanitizer` path allows all attributes; the fallback removes only `<script>`, `on*` attributes
|
|
96
|
+
and `javascript:` / `data:text/html` in `href`/`src`. Without `DOMParser` (Node) the input is
|
|
97
|
+
returned unchanged, so server-side output is not sanitized.
|
|
98
|
+
- **Mock server** (`[mock-interceptor.js:enableMockServer]`): one mock per `options.owner` (the
|
|
99
|
+
element; omitted, a page-wide default), so several `<rapi-doc>` / `<rapi-doc-mini>` on a page
|
|
100
|
+
mock their own APIs with their own status codes, delay and cycle positions. Repeated calls for an
|
|
101
|
+
owner refresh its routes and config but never stack wrappers; `disableMockServer(owner)` drops
|
|
102
|
+
that mock and restores the originals once none is left. A request is answered by the most
|
|
103
|
+
specific match across the mocks (server origin and base path, then base path, then bare path;
|
|
104
|
+
ties to the mock enabled first); unmatched requests fall through to the real `fetch`/XHR. Mock
|
|
105
|
+
responses carry `X-Mock-Server: RapiDoc`.
|
|
106
|
+
- **Highlighter** (`[highlighter.js]`): `highlightAllCode` and `scheduleHighlight` are no-ops
|
|
107
|
+
without `window` and `CSS.highlights`; highlighting never mutates the DOM.
|
|
108
|
+
- **Headless boundary**: `spec-parser.js`, `schema-utils.js`, `slugger.js`, `mcp-generator.js`,
|
|
109
|
+
`mcp/`, `arazzo/` and everything they import are shared with the Node builds (`@rapidoc/prerender`,
|
|
110
|
+
`@rapidoc/mcp`). They never import Lit, elements, `components/`, `templates/`, `styles/` or
|
|
111
|
+
`static/`, and use only the third-party packages allowed in `tests/unit/headless-boundary.test.js`.
|
|
112
|
+
DOM calls inside functions are allowed (Node never calls them); DOM access at import time is not.
|
|
113
|
+
|
|
114
|
+
## Side Effects
|
|
115
|
+
|
|
116
|
+
- `ProcessSpec` fetches the spec and external `$ref`s, logs load errors with `console.info`, and
|
|
117
|
+
mutates the spec it processes: `x-ref` during dereference, `paths` replaced by `filterPaths`,
|
|
118
|
+
`callbacks` filtered, relative `servers[*].url` prefixed with `location.origin`,
|
|
119
|
+
`computedUrl` added.
|
|
120
|
+
- `mock-interceptor.js` replaces `window.fetch` and `XMLHttpRequest.prototype` methods while
|
|
121
|
+
active and keeps routes and config in module state.
|
|
122
|
+
- `markdown-utils.js` configures the global `marked` at import time.
|
|
123
|
+
- `highlighter.js` writes the global `CSS.highlights` registry and schedules
|
|
124
|
+
`requestAnimationFrame`.
|
|
125
|
+
- `bind-navbar.js` adds DOM and `window` listeners, writes `history`; some timers and the
|
|
126
|
+
`spec-loaded` listener outlive `unbind()`.
|
|
127
|
+
- `auto-color-scheme.js` observes `<html>`/`<body>` attributes and `prefers-color-scheme`.
|
|
128
|
+
- `response-utils.js` creates object URLs it does not revoke; `common-utils.js` clipboard and
|
|
129
|
+
download helpers create temporary elements.
|
|
130
|
+
|
|
131
|
+
## Verification
|
|
132
|
+
|
|
133
|
+
Run `npm run test:unit` from the repo root before and after changing `spec-parser.js` or
|
|
134
|
+
`schema-utils.js`, then `npm run lint` and `npm run format-fix`. Files in `tests/unit/`:
|
|
135
|
+
|
|
136
|
+
- `spec-parser.js`: `spec-parser-relative-url`, `spec-parser-security`,
|
|
137
|
+
`spec-parser-tag-groups`, `spec-parser-webhooks`, `unused-tags`, `openapi-3-2-bookstore`.
|
|
138
|
+
- `slugger.js`: `slugger`.
|
|
139
|
+
- `schema-utils.js`: `schema-utils`, `schema-ast`, `schema-in-object-notation`,
|
|
140
|
+
`schema-nullable`, `schema-modernization`, `multipart-form-curl`.
|
|
141
|
+
- `url-utils.js`: `query-param-serialization`, `oauth-receiver-url`.
|
|
142
|
+
- `request-utils.js`, `curl-utils.js`: `request-utils`, `multipart-part-types`, `curl-command`.
|
|
143
|
+
- `response-utils.js`: `api-request-response`.
|
|
144
|
+
- `mock-interceptor.js`, `oauth-utils.js`, `markdown-utils.js`, `mcp-generator.js`: the
|
|
145
|
+
test file of the same name.
|
|
146
|
+
- Headless boundary (imports of the modules above, transitively): `headless-boundary`.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Arazzo Workflow Diagrams
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Reads Arazzo 1.x documents and draws one workflow as an SVG string: the workflow's inputs and
|
|
6
|
+
outputs, its steps in order, the values passed between steps, and each step's success and failure
|
|
7
|
+
actions. The inputs box (each input's type, required flag, default and enum values; a string enum's type
|
|
8
|
+
reads `enum`, as `<rapi-doc>` labels it) and the
|
|
9
|
+
outputs table are the same in both views, which differ in the step boxes: `expanded` (the
|
|
10
|
+
default) lists each step's values with a keyword for where they come from (`input`, `step`,
|
|
11
|
+
`fixed`, `expr`), its checks (the success criteria, unnumbered), its actions as "condition →
|
|
12
|
+
what happens" under "On success · first match runs" / "On failure · first match runs", and the
|
|
13
|
+
outputs something reads; `compact` shows each step's name, method, path and number of checks,
|
|
14
|
+
with labelled value lines and no actions. Used by `<rapi-flow>` (`src/rapi-flow.ts`, bundled as `dist/rapidoc-flow-min.js`) and by
|
|
15
|
+
the build-time renderer (`@rapidoc/prerender`: `renderArazzoWorkflow`, `getFlowCss`).
|
|
16
|
+
|
|
17
|
+
Not owned here: running workflows (nothing here makes a request), the element's loading and
|
|
18
|
+
events (`src/rapi-flow.ts`), and the diagram's look (`src/styles/flow-styles.js`). `dist/rapidoc-min.js`
|
|
19
|
+
imports only the YAML-free modules `components.js`, `operations.js` and `refs.js`, through
|
|
20
|
+
`utils/mcp/workflows.js`, which compiles workflows into the MCP manifest; `parse.js` (the `yaml`
|
|
21
|
+
package) stays out of it.
|
|
22
|
+
|
|
23
|
+
## Interface
|
|
24
|
+
|
|
25
|
+
- `parseDocument(input)` [parse.js]: JSON or YAML text to an object; an object passes through.
|
|
26
|
+
Throws on empty input or a non-object.
|
|
27
|
+
- `validateArazzo(doc) -> { level, path, message }[]` [parse.js]: structural checks the diagram
|
|
28
|
+
relies on; an empty list means valid.
|
|
29
|
+
- `resolveComponentRef(doc, obj, kind)` [components.js, re-exported by parse.js]: `{ reference: "$components.<kind>.<name>" }`
|
|
30
|
+
to the component, or null.
|
|
31
|
+
- `collectRefs(value)` [refs.js]: `$inputs`, `$steps` and `$workflows` expressions anywhere in a
|
|
32
|
+
JSON value, distinct, in first-seen order.
|
|
33
|
+
- `indexOperations(openapi)`, `indexResolvedSpec(resolvedSpec)` (the same index from
|
|
34
|
+
`<rapi-doc>`'s processed `tags[].paths[]`, webhooks skipped, the first entry kept for an
|
|
35
|
+
operation with several tags), `resolveOperation(step, sources)` [operations.js]: operation lookup
|
|
36
|
+
by operationId (plain or `$sourceDescriptions.<name>.` qualified) and by `operationPath`.
|
|
37
|
+
- `buildFlowGraph(doc, workflowId?, { sources })` [graph.js]: the graph the SVG draws; null when
|
|
38
|
+
the workflow does not exist.
|
|
39
|
+
- `renderFlowDiagram(graph, { view, idPrefix, interactive }) -> { svg, legend }` [svg.js]:
|
|
40
|
+
`legend` names the kinds of lines drawn (`seq`, `data`, `output`, `success`, `failure`);
|
|
41
|
+
`renderFlowSvg` returns only the SVG. `actionLabel` (short: "✗ retry 3× after 2s (if)"),
|
|
42
|
+
`actionSentence` (with criteria), `describeAction(action, checks, index)` (`{ when, does, name, tag }`, the
|
|
43
|
+
wording of the expanded boxes; an action without criteria reads "checks
|
|
44
|
+
fail" / "check fails" / "request fails" by the step's number of checks, or "otherwise" after
|
|
45
|
+
other actions) and `escapeXml` are exported too.
|
|
46
|
+
- `renderArazzoWorkflow(arazzo, options)` [index.js]: parse, validate, index `options.sources`,
|
|
47
|
+
build and draw in one call; returns `{ svg, legend, graph, problems, workflowIds }`.
|
|
48
|
+
|
|
49
|
+
## Invariants
|
|
50
|
+
|
|
51
|
+
- **No DOM, no network.** Text widths are estimated from character counts, so output is the same
|
|
52
|
+
in Node and the browser and long labels are cut with `…` rather than measured.
|
|
53
|
+
- **OpenAPI is not dereferenced.** Only `paths.<path>.<method>.operationId` and `summary` are read,
|
|
54
|
+
so an operation reached through a path-item `$ref` is not found.
|
|
55
|
+
- **Validation is structural.** It checks required fields, duplicate ids, exactly one of
|
|
56
|
+
`operationId` / `operationPath` / `workflowId` per step, action types (retry only on failure),
|
|
57
|
+
goto and `dependsOn` targets, and `$steps.<id>` references. It does not check expression syntax,
|
|
58
|
+
`inputs` JSON Schema, or criteria.
|
|
59
|
+
- **Edges.** A data edge goes from a step to a later step (or to the outputs box) whenever a
|
|
60
|
+
`$steps.<id>.outputs.<name>` expression appears in the later step's `parameters` or
|
|
61
|
+
`requestBody` (or a workflow output). References to the same or an earlier step are not drawn.
|
|
62
|
+
Edges into the outputs box are drawn gray, the others in the primary color. Compact: edges from
|
|
63
|
+
one step with the same labels share a lane and arrive at the middle of the target step; edges
|
|
64
|
+
into the outputs box carry no label and arrive at the step's group, as in expanded. Expanded: one lane per source step, starting at each of its Outputs rows
|
|
65
|
+
that is read and branching to each line (a step binding, or the step's group heading in the
|
|
66
|
+
outputs box) that reads one; no labels, since the lines name the value.
|
|
67
|
+
- **Step outputs.** Each step's `usedOutputs` are its outputs that a later step or the workflow
|
|
68
|
+
reads. Expanded boxes end with an Outputs section listing them, the source shortened
|
|
69
|
+
(`$response.body#/status` reads `response body /status`, the full expression a tooltip), and
|
|
70
|
+
"+N unused" for the rest.
|
|
71
|
+
- **Outputs box.** A table in both views: the output names in one flat column (the workflow's
|
|
72
|
+
result is a flat object), and on the right, under "From step", the step each group comes from,
|
|
73
|
+
once per group, with a rule between groups. Groups follow the order each step first appears;
|
|
74
|
+
outputs not taken from a step come last, their source kind (`input`, `fixed`, `expr`) in the
|
|
75
|
+
step column. A step output whose name differs from the workflow output's follows as
|
|
76
|
+
`← <name>`. A step's line arrives at its group's first row, beside the step name.
|
|
77
|
+
- **Step names** (box titles, the step in `step` value lines, the From step column) share one
|
|
78
|
+
color, `--primary` mixed toward the text color so it passes 4.5:1 on light cards; variable
|
|
79
|
+
names keep the text color.
|
|
80
|
+
- **Linking.** Step boxes carry `data-step-id`; output rows and value lines carry
|
|
81
|
+
`data-from-step`, so a caller can highlight a step with everything it feeds (`<rapi-flow>` does
|
|
82
|
+
on hover, with the `rf-linked` class). With `interactive`, those rows are also `rf-jump` links
|
|
83
|
+
(the outputs box's step names focusable, `role="link"`) for a caller to scroll to the step.
|
|
84
|
+
- **Actions.** A step's actions are its own `onSuccess` / `onFailure`, then the workflow's
|
|
85
|
+
`successActions` / `failureActions` (marked `inherited`) minus any the step overrides by `name`.
|
|
86
|
+
The graph also lists the workflow's actions once as `workflowActions`. Compact shows no
|
|
87
|
+
actions; expanded lists a step's own actions and the inherited ones in one list per outcome, in merge order, each with its name in parentheses after
|
|
88
|
+
what it does; inherited ones also carry a `workflow` tag on the right. A runner takes the first action whose criteria match, so every action after
|
|
89
|
+
one without criteria is marked `shadowed` (it never runs). Shadowed actions are left out of the
|
|
90
|
+
boxes and draw no goto line; the graph's `warnings` (`{ level, path,
|
|
91
|
+
message }`, one per step and outcome) name them, for author tooling;
|
|
92
|
+
`<rapi-flow>` does not show them. Only `goto` with a `stepId` in the same workflow draws a line, from its action line
|
|
93
|
+
(expanded) or the box's left edge (compact); `end`, `retry` and goto to a workflow draw none.
|
|
94
|
+
- **Details.** The graph carries more than the boxes show, for callers that render their own
|
|
95
|
+
documentation: `inputDetails` (type,
|
|
96
|
+
format, required, default, enum, description, from `inputs` or a `$ref` to
|
|
97
|
+
`#/components/inputs/<name>`), each step's `parameters` (component references resolved, their
|
|
98
|
+
`value` overriding), `requestBody`, `criteria` and `outputExprs`, and each output's `expr`.
|
|
99
|
+
Each step's `bindings` are the values it sends: parameters by name and request body fields as
|
|
100
|
+
`body.<path>` (objects flattened, arrays kept whole), each with a `source` (`input`, `step`,
|
|
101
|
+
`expression` or `value`) and the steps it reads (`fromSteps`). Expanded boxes show at most ten.
|
|
102
|
+
Input descriptions and step descriptions are tooltips (`<title>`) on their lines and boxes.
|
|
103
|
+
- **Next-step arrows.** A step falls through to the next one unless it has a success action of type
|
|
104
|
+
`end` or `goto` without `criteria`; then no arrow is drawn into the next step, which is reached
|
|
105
|
+
only through goto lines. The last step always gets an arrow into the outputs box.
|
|
106
|
+
- **Ids.** Marker ids are `<idPrefix>-<name>`; pages with several diagrams pass distinct prefixes.
|
|
107
|
+
Steps carry `data-step-id`; with `interactive`, steps whose operation or workflow is known get
|
|
108
|
+
`tabindex="0" role="button"` and the `rf-clickable` class.
|
|
109
|
+
|
|
110
|
+
## Side Effects
|
|
111
|
+
|
|
112
|
+
- None. `parseDocument` uses the `yaml` package, which is why `dist/rapidoc-flow-min.js` carries it.
|
|
113
|
+
|
|
114
|
+
## Verification
|
|
115
|
+
|
|
116
|
+
- `npm run test:unit` from the repo root; `tests/unit/arazzo-flow.test.js` covers parsing,
|
|
117
|
+
validation, lookup, the graph and the SVG, using `docs/public/specs/petstore.arazzo.yaml`.
|
|
118
|
+
- The docs page `/examples/arazzo-flow` shows `<rapi-flow>` tied to a `<rapi-doc>`.
|
|
119
|
+
- After editing, `npm run lint` and `npm run format-fix`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arazzo reusable components. Kept apart from parse.js so the MCP manifest builder can compile
|
|
3
|
+
* workflows inside dist/rapidoc-min.js without pulling in the YAML parser.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolves `{ reference: "$components.<kind>.<name>" }` against the document's components.
|
|
8
|
+
*/
|
|
9
|
+
export function resolveComponentRef(doc, obj, kind) {
|
|
10
|
+
if (!obj || typeof obj.reference !== "string") return obj;
|
|
11
|
+
const m = obj.reference.match(/^\$components\.([A-Za-z]+)\.([A-Za-z0-9_.-]+)$/);
|
|
12
|
+
if (!m || m[1] !== kind) return null;
|
|
13
|
+
const target = doc?.components?.[kind]?.[m[2]];
|
|
14
|
+
return target ? { ...target, ...(obj.value !== undefined ? { value: obj.value } : {}) } : null;
|
|
15
|
+
}
|