rapidoc 12.0.0 → 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 +127 -25
- package/dist/rapidoc-flow-min.js +580 -0
- package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
- package/dist/rapidoc-min.js +541 -1309
- package/dist/rapidoc-min.js.LICENSE.txt +381 -0
- package/package.json +21 -18
- package/src/README.md +150 -0
- package/src/components/README.md +91 -0
- package/src/components/api-request.ts +43 -0
- package/src/components/api-response.ts +9 -4
- package/src/components/schema-base-element.ts +18 -4
- package/src/components/schema-table.ts +58 -13
- package/src/components/schema-tree.ts +7 -0
- package/src/components/tag-input.ts +5 -9
- package/src/flow.js +3 -0
- package/src/rapi-flow.ts +1178 -0
- package/src/rapidoc-mini.ts +27 -6
- package/src/rapidoc.ts +108 -62
- package/src/styles/README.md +96 -0
- package/src/styles/css-bundle.js +2 -2
- package/src/styles/endpoint-styles.js +6 -1
- package/src/styles/flow-styles.js +248 -0
- package/src/styles/nav-styles.js +39 -15
- package/src/styles/schema-styles.js +13 -5
- package/src/styles/theme-tokens.js +2 -2
- package/src/templates/README.md +89 -0
- package/src/templates/api-response-template.js +14 -10
- package/src/templates/callback-template.js +1 -1
- package/src/templates/downloads-template.js +12 -27
- package/src/templates/endpoint-template.js +15 -24
- package/src/templates/expanded-endpoint-template.js +8 -4
- package/src/templates/focused-endpoint-template.js +4 -3
- package/src/templates/navbar-template.js +20 -11
- package/src/templates/request-body-template.js +29 -7
- package/src/templates/request-params-template.js +15 -4
- package/src/templates/security-scheme-template.js +11 -2
- package/src/templates/server-template.js +3 -2
- 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/curl-utils.js +5 -2
- package/src/utils/highlighter.js +3 -0
- package/src/utils/mcp/README.md +155 -0
- package/src/utils/mcp/common.js +45 -346
- 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 +29 -125
- package/src/utils/mock-interceptor.js +134 -85
- package/src/utils/request-utils.js +61 -0
- package/src/utils/schema-utils.js +33 -6
- package/src/utils/slugger.js +5 -7
- package/src/utils/spec-parser.js +190 -164
- package/src/utils/url-utils.js +36 -1
- package/dist/rapidoc-static-min.js +0 -13542
- package/src/static/index.d.ts +0 -149
- package/src/static/index.js +0 -62
- package/src/static/navigation.js +0 -497
- package/src/static/render.js +0 -490
- package/src/static/serializer.js +0 -216
- package/src/utils/mcp/generators/csharp.js +0 -242
- package/src/utils/mcp/generators/go.js +0 -298
- package/src/utils/mcp/generators/java.js +0 -330
- package/src/utils/mcp/generators/python.js +0 -248
- package/src/utils/mcp/generators/rust.js +0 -281
- package/src/utils/mcp/generators/typescript.js +0 -290
|
@@ -171,7 +171,7 @@ export function expandedEndpointBodyTemplate(
|
|
|
171
171
|
schema-hide-write-only="${this.schemaHideWriteOnly === "never" ? "false" : path.isWebhook ? "true" : "false"}"
|
|
172
172
|
fetch-credentials="${this.fetchCredentials}"
|
|
173
173
|
exportparts="section-request-title:section-request-title, heading:heading, heading-4:heading-4, heading-5:heading-5, heading-6:heading-6,
|
|
174
|
-
wrap-request-btn:wrap-request-btn, btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-clear-resp:btn-clear-resp,
|
|
174
|
+
wrap-request-btn:wrap-request-btn, btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-fill-example:btn-fill-example, btn-clear-resp:btn-clear-resp,
|
|
175
175
|
tab-panel:tab-panel, tab-btn:tab-btn, tab-btn-row:tab-btn-row, tab-content:tab-content,
|
|
176
176
|
file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
|
|
177
177
|
anchor:anchor, anchor-param-example:anchor-param-example, anchor-param-constraint:anchor-param-constraint, param-list:param-list, param:param, param-label:param-label, param-name:param-name, param-type:param-type, param-input:param-input, param-constraint:param-constraint, param-constraint-label:param-constraint-label, param-details:param-details, param-example-list:param-example-list, value-list:value-list, value-list-label:value-list-label, value-list-items:value-list-items, schema-description:schema-description, schema-multiline-toggle:schema-multiline-toggle"
|
|
@@ -228,9 +228,13 @@ export default function expandedEndpointTemplate() {
|
|
|
228
228
|
)}
|
|
229
229
|
</div>`)}
|
|
230
230
|
</section>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
${
|
|
232
|
+
tag.paths.length > 0
|
|
233
|
+
? html`<section class="regular-font section-gap--read-mode" part="section-operations-in-tag">
|
|
234
|
+
${tag.paths.map((path, idx) => expandedEndpointBodyTemplate.call(this, path, tag.name, tag.description, idx === 0))}
|
|
235
|
+
</section>`
|
|
236
|
+
: ""
|
|
237
|
+
}
|
|
234
238
|
`;
|
|
235
239
|
|
|
236
240
|
if (this.resolvedSpec.tagGroups && this.resolvedSpec.tagGroups.length > 0) {
|
|
@@ -36,8 +36,9 @@ function defaultContentTemplate() {
|
|
|
36
36
|
if (this.showInfo === "true") {
|
|
37
37
|
return wrapFocusedTemplate(html`${overviewTemplate.call(this)}${downloadsTemplate.call(this)}`);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
const
|
|
39
|
+
// the first tag with operations: a description-only tag (show-unused-tags) has none
|
|
40
|
+
const selectedTagObj = this.resolvedSpec.tags.find((t) => t.paths.length > 0);
|
|
41
|
+
const selectedPathObj = selectedTagObj?.paths[0];
|
|
41
42
|
return selectedTagObj && selectedPathObj
|
|
42
43
|
? wrapFocusedTemplate(
|
|
43
44
|
expandedEndpointBodyTemplate.call(
|
|
@@ -79,7 +80,7 @@ export default function focusedEndpointTemplate() {
|
|
|
79
80
|
if (!this.focusedElementId && this.resolvedSpec) {
|
|
80
81
|
this.focusedElementId =
|
|
81
82
|
this.showInfo === "false" || this.showInfo === false
|
|
82
|
-
? this.resolvedSpec.tags?.
|
|
83
|
+
? this.resolvedSpec.tags?.find((t) => t.paths.length > 0)?.paths[0]?.elementId || ""
|
|
83
84
|
: "overview";
|
|
84
85
|
}
|
|
85
86
|
if (!this.focusedElementId || !this.resolvedSpec) {
|
|
@@ -7,19 +7,13 @@ import { getMatchedPaths, isInternalOrHidden } from "~/utils/common-utils";
|
|
|
7
7
|
|
|
8
8
|
export function expandCollapseNavBarTag(navLinkEl, action = "toggle") {
|
|
9
9
|
const tagAndPathEl = navLinkEl?.closest(".nav-bar-tag-and-paths");
|
|
10
|
-
const pathsUnderTagEl = tagAndPathEl?.querySelector(".nav-bar-paths-under-tag");
|
|
11
10
|
if (tagAndPathEl) {
|
|
12
11
|
const isExpanded = tagAndPathEl.classList.contains("expanded");
|
|
13
12
|
if (isExpanded && (action === "toggle" || action === "collapse")) {
|
|
14
|
-
if (pathsUnderTagEl) pathsUnderTagEl.style.maxHeight = 0;
|
|
15
13
|
tagAndPathEl.classList.replace("expanded", "collapsed");
|
|
16
14
|
tagAndPathEl.querySelector(".nav-bar-tag")?.setAttribute("aria-expanded", "false");
|
|
17
15
|
} else if (!isExpanded && (action === "toggle" || action === "expand")) {
|
|
18
16
|
tagAndPathEl.classList.replace("collapsed", "expanded");
|
|
19
|
-
if (pathsUnderTagEl)
|
|
20
|
-
pathsUnderTagEl.style.maxHeight = pathsUnderTagEl.scrollHeight
|
|
21
|
-
? `${pathsUnderTagEl.scrollHeight}px`
|
|
22
|
-
: null;
|
|
23
17
|
tagAndPathEl.querySelector(".nav-bar-tag")?.setAttribute("aria-expanded", "true");
|
|
24
18
|
}
|
|
25
19
|
}
|
|
@@ -61,6 +55,10 @@ export function isNavTagVisible(tag, searchVal) {
|
|
|
61
55
|
if (isInternalOrHidden(tag)) {
|
|
62
56
|
return false;
|
|
63
57
|
}
|
|
58
|
+
// A description-only tag (show-unused-tags) has no operations to match, so the filter hides it
|
|
59
|
+
if (tag.descriptionOnly) {
|
|
60
|
+
return !searchVal;
|
|
61
|
+
}
|
|
64
62
|
return (tag.paths || []).some((path) => isNavPathVisible(path, searchVal, tag.name));
|
|
65
63
|
}
|
|
66
64
|
|
|
@@ -91,16 +89,11 @@ export function expandCollapseAll(event, action = "expand-all") {
|
|
|
91
89
|
const elList = [...navEl.querySelectorAll(".nav-bar-tag-and-paths")];
|
|
92
90
|
if (action === "expand-all") {
|
|
93
91
|
elList.forEach((el) => {
|
|
94
|
-
const navBarPathsUnderTagEl = el.querySelector(".nav-bar-paths-under-tag");
|
|
95
92
|
el.classList.replace("collapsed", "expanded");
|
|
96
|
-
if (navBarPathsUnderTagEl) {
|
|
97
|
-
navBarPathsUnderTagEl.style.maxHeight = `${navBarPathsUnderTagEl?.scrollHeight}px`;
|
|
98
|
-
}
|
|
99
93
|
});
|
|
100
94
|
} else {
|
|
101
95
|
elList.forEach((el) => {
|
|
102
96
|
el.classList.replace("expanded", "collapsed");
|
|
103
|
-
// el.querySelector('.nav-bar-paths-under-tag').style.maxHeight = 0;
|
|
104
97
|
});
|
|
105
98
|
}
|
|
106
99
|
}
|
|
@@ -338,6 +331,22 @@ export default function navbarTemplate() {
|
|
|
338
331
|
<!-- TAGS AND PATHS (3-tier if tagGroups exist, else 2-tier) -->
|
|
339
332
|
${(() => {
|
|
340
333
|
const renderNavTagItem = (tag) => {
|
|
334
|
+
// A description-only tag is a link to its section: no operations to expand or collapse
|
|
335
|
+
if (tag.descriptionOnly) {
|
|
336
|
+
if (!isNavTagVisible(tag, this.searchVal)) return "";
|
|
337
|
+
return html` <div class="nav-bar-tag-and-paths description-only">
|
|
338
|
+
<div
|
|
339
|
+
class="nav-bar-tag"
|
|
340
|
+
part="section-navbar-item section-navbar-tag"
|
|
341
|
+
id="link-${tag.elementId}"
|
|
342
|
+
data-action="navigate"
|
|
343
|
+
data-content-id="${tag.elementId}"
|
|
344
|
+
tabindex="0"
|
|
345
|
+
>
|
|
346
|
+
<div class="nav-bar-tag-title">${tag.displayName || tag.name}</div>
|
|
347
|
+
</div>
|
|
348
|
+
</div>`;
|
|
349
|
+
}
|
|
341
350
|
const visiblePaths = (tag.paths || []).filter((p) =>
|
|
342
351
|
isNavPathVisible(p, this.searchVal, tag.name),
|
|
343
352
|
);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
EMPTY_ARRAY,
|
|
19
19
|
getMergedProperties,
|
|
20
20
|
getMergedRequired,
|
|
21
|
+
exampleLabel,
|
|
21
22
|
} from "~/utils/schema-utils";
|
|
22
23
|
import "~/components/schema-tree";
|
|
23
24
|
import "~/components/schema-table";
|
|
@@ -28,8 +29,14 @@ import {
|
|
|
28
29
|
examplesBesideConstraints,
|
|
29
30
|
renderExample,
|
|
30
31
|
} from "~/templates/request-params-template";
|
|
32
|
+
import { multipartPartContentType } from "~/utils/request-utils";
|
|
31
33
|
|
|
32
|
-
export function formDataParamAsObjectTemplate(
|
|
34
|
+
export function formDataParamAsObjectTemplate(
|
|
35
|
+
fieldName,
|
|
36
|
+
fieldSchema,
|
|
37
|
+
mimeType,
|
|
38
|
+
partContentType = "",
|
|
39
|
+
) {
|
|
33
40
|
// This template is used when form-data param should be send as a object (application/json, application/xml)
|
|
34
41
|
const formdataPartSchema = schemaToAST(fieldSchema);
|
|
35
42
|
const formdataPartExample = generateExample(
|
|
@@ -95,6 +102,7 @@ export function formDataParamAsObjectTemplate(fieldName, fieldSchema, mimeType)
|
|
|
95
102
|
data-array="false"
|
|
96
103
|
data-ptype="${mimeType.includes("form-urlencode") ? "form-urlencode" : "form-data"}"
|
|
97
104
|
data-pname="${fieldName}"
|
|
105
|
+
data-content-type="${partContentType}"
|
|
98
106
|
data-example="${formdataPartExample[0]?.exampleValue || ""}"
|
|
99
107
|
.value="${live(this.fillRequestFieldsWithExample === "true" ? formdataPartExample[0]?.exampleValue || "" : "")}"
|
|
100
108
|
spellcheck="false"
|
|
@@ -133,7 +141,7 @@ export function formDataParamAsObjectTemplate(fieldName, fieldSchema, mimeType)
|
|
|
133
141
|
`;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
|
-
export function formDataTemplate(schema, mimeType, exampleValue = "") {
|
|
144
|
+
export function formDataTemplate(schema, mimeType, exampleValue = "", encoding = {}) {
|
|
137
145
|
const formFieldItems = [];
|
|
138
146
|
const properties = getMergedProperties(schema);
|
|
139
147
|
const required = getMergedRequired(schema);
|
|
@@ -145,6 +153,10 @@ export function formDataTemplate(schema, mimeType, exampleValue = "") {
|
|
|
145
153
|
}
|
|
146
154
|
const fieldExamples = fieldSchema.examples || fieldSchema.example || "";
|
|
147
155
|
const fieldType = fieldSchema.type;
|
|
156
|
+
// multipart parts are sent with this Content-Type (application/json for objects); empty means text/plain
|
|
157
|
+
const partContentType = mimeType.includes("form-data")
|
|
158
|
+
? multipartPartContentType(fieldSchema, encoding?.[fieldName])
|
|
159
|
+
: "";
|
|
148
160
|
const paramSchema = getTypeInfo(fieldSchema);
|
|
149
161
|
const example = normalizeExamples(
|
|
150
162
|
paramSchema.examples || paramSchema.example,
|
|
@@ -169,7 +181,7 @@ export function formDataTemplate(schema, mimeType, exampleValue = "") {
|
|
|
169
181
|
>
|
|
170
182
|
<div class="param-label" part="param-label">
|
|
171
183
|
<div class="param-name ${fieldSchema.deprecated ? "deprecated" : ""}" part="param-name">
|
|
172
|
-
${fieldName}${required.includes(fieldName)
|
|
184
|
+
${fieldName}${required.includes(fieldName) ? html`<span class="param-required" title="Required">*</span>` : ""}
|
|
173
185
|
</div>
|
|
174
186
|
<div class="param-type" part="param-type">
|
|
175
187
|
${
|
|
@@ -227,6 +239,7 @@ export function formDataTemplate(schema, mimeType, exampleValue = "") {
|
|
|
227
239
|
style="width:100%"
|
|
228
240
|
data-ptype="${mimeType.includes("form-urlencode") ? "form-urlencode" : "form-data"}"
|
|
229
241
|
data-pname="${fieldName}"
|
|
242
|
+
data-content-type="${partContentType}"
|
|
230
243
|
data-example="${Array.isArray(fieldExamples) ? fieldExamples.join("~|~") : fieldExamples}"
|
|
231
244
|
data-array="true"
|
|
232
245
|
placeholder="add-multiple ↩"
|
|
@@ -247,6 +260,7 @@ export function formDataTemplate(schema, mimeType, exampleValue = "") {
|
|
|
247
260
|
fieldName,
|
|
248
261
|
fieldSchema,
|
|
249
262
|
mimeType,
|
|
263
|
+
partContentType,
|
|
250
264
|
)
|
|
251
265
|
: html`
|
|
252
266
|
${
|
|
@@ -259,6 +273,7 @@ export function formDataTemplate(schema, mimeType, exampleValue = "") {
|
|
|
259
273
|
style="width:100%"
|
|
260
274
|
data-ptype="${mimeType.includes("form-urlencode") ? "form-urlencode" : "form-data"}"
|
|
261
275
|
data-pname="${fieldName}"
|
|
276
|
+
data-content-type="${partContentType}"
|
|
262
277
|
data-example="${Array.isArray(fieldExamples) ? fieldExamples[0] : fieldExamples}"
|
|
263
278
|
data-array="false"
|
|
264
279
|
/>`
|
|
@@ -379,6 +394,7 @@ export default function requestBodyTemplate() {
|
|
|
379
394
|
schema: content[mimeType].schema,
|
|
380
395
|
example: content[mimeType].example,
|
|
381
396
|
examples: content[mimeType].examples,
|
|
397
|
+
encoding: content[mimeType].encoding,
|
|
382
398
|
});
|
|
383
399
|
// Fallback guard: If selectedRequestBodyType was cleared (e.g., after focused-mode navigation),
|
|
384
400
|
// automatically latch onto the first available MIME type (e.g., application/json).
|
|
@@ -436,11 +452,16 @@ export default function requestBodyTemplate() {
|
|
|
436
452
|
reqBodyExamples.length === 1
|
|
437
453
|
? ""
|
|
438
454
|
: html`
|
|
439
|
-
<select
|
|
455
|
+
<select
|
|
456
|
+
class="select-sm example-select"
|
|
457
|
+
aria-label="request body examples"
|
|
458
|
+
title="${exampleLabel(reqBodyExamples.find((v) => v.exampleId === this.selectedRequestBodyExample))}"
|
|
459
|
+
@change="${(e) => this.onSelectExample(e)}"
|
|
460
|
+
>
|
|
440
461
|
${reqBodyExamples.map(
|
|
441
462
|
(v) =>
|
|
442
|
-
html`<option value="${v.exampleId}" ?selected=${v.exampleId === this.selectedRequestBodyExample}>
|
|
443
|
-
${v
|
|
463
|
+
html`<option value="${v.exampleId}" title="${exampleLabel(v)}" ?selected=${v.exampleId === this.selectedRequestBodyExample}>
|
|
464
|
+
${exampleLabel(v)}
|
|
444
465
|
</option>`,
|
|
445
466
|
)}
|
|
446
467
|
</select>
|
|
@@ -454,7 +475,7 @@ export default function requestBodyTemplate() {
|
|
|
454
475
|
class="example ${v.exampleId === this.selectedRequestBodyExample ? "example-selected" : ""}"
|
|
455
476
|
data-example="${v.exampleId}"
|
|
456
477
|
>
|
|
457
|
-
${v.exampleSummary && v.exampleSummary.length > 80 ? html`<div style="padding: 4px 0">${v.exampleSummary}</div>` : ""}
|
|
478
|
+
${reqBodyExamples.length === 1 && v.exampleSummary && v.exampleSummary.length > 80 ? html`<div style="padding: 4px 0">${v.exampleSummary}</div>` : ""}
|
|
458
479
|
${
|
|
459
480
|
v.exampleDescription
|
|
460
481
|
? html`<div class="m-markdown-small" style="padding: 4px 0">
|
|
@@ -524,6 +545,7 @@ ${v.exampleFormat === "text" ? v.exampleValue : JSON.stringify(v.exampleValue, n
|
|
|
524
545
|
reqBody.schema,
|
|
525
546
|
reqBody.mimeType,
|
|
526
547
|
ex[0] ? ex[0].exampleValue : "",
|
|
548
|
+
reqBody.encoding,
|
|
527
549
|
);
|
|
528
550
|
}
|
|
529
551
|
}
|
|
@@ -148,6 +148,12 @@ export function exampleListTemplate(paramName, paramType, exampleList = []) {
|
|
|
148
148
|
}`;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
// A singular `example` is the value itself, even an object; only an `examples` map wraps its values in { value }
|
|
152
|
+
const singleExample = (ex) =>
|
|
153
|
+
ex !== null && typeof ex === "object" && !Array.isArray(ex)
|
|
154
|
+
? { Example: { value: ex } }
|
|
155
|
+
: standardizeExample(ex);
|
|
156
|
+
|
|
151
157
|
export function inputParametersTemplate(paramType) {
|
|
152
158
|
const filteredParams = this.parameters
|
|
153
159
|
? this.parameters.filter((param) => param.in === paramType)
|
|
@@ -196,8 +202,8 @@ export function inputParametersTemplate(paramType) {
|
|
|
196
202
|
// openapi 3.1.0 spec based examples (which must be Object(string : { value:any, summary?: string, description?: string})
|
|
197
203
|
const example = normalizeExamples(
|
|
198
204
|
standardizeExample(param.examples) ||
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
singleExample(param.example) ||
|
|
206
|
+
singleExample(mimeTypeElem?.example) ||
|
|
201
207
|
standardizeExample(mimeTypeElem?.examples) ||
|
|
202
208
|
standardizeExample(paramSchema.examples) ||
|
|
203
209
|
standardizeExample(paramSchema.example),
|
|
@@ -263,6 +269,7 @@ export function inputParametersTemplate(paramType) {
|
|
|
263
269
|
style="width:100%"
|
|
264
270
|
data-ptype="${paramType}"
|
|
265
271
|
data-pname="${param.name}"
|
|
272
|
+
data-item-type="${declaredParamSchema.items?.type || ""}"
|
|
266
273
|
data-example="${Array.isArray(example.exampleVal) ? example.exampleVal.join("~|~") : example.exampleVal}"
|
|
267
274
|
data-param-serialize-style="${paramStyle}"
|
|
268
275
|
data-param-serialize-explode="${paramExplode}"
|
|
@@ -337,7 +344,11 @@ export function inputParametersTemplate(paramType) {
|
|
|
337
344
|
part="textarea textarea-param"
|
|
338
345
|
data-ptype="${paramType}-object"
|
|
339
346
|
data-pname="${param.name}"
|
|
340
|
-
data-example="${
|
|
347
|
+
data-example="${
|
|
348
|
+
typeof example.exampleVal === "object"
|
|
349
|
+
? JSON.stringify(example.exampleVal, null, 2)
|
|
350
|
+
: example.exampleVal
|
|
351
|
+
}"
|
|
341
352
|
data-param-serialize-style="${paramStyle}"
|
|
342
353
|
data-param-serialize-explode="${paramExplode}"
|
|
343
354
|
data-param-allow-reserved="${paramAllowReserved}"
|
|
@@ -380,7 +391,7 @@ export function inputParametersTemplate(paramType) {
|
|
|
380
391
|
allow-schema-description-expand-toggle="${this.allowSchemaDescriptionExpandToggle}"
|
|
381
392
|
schema-hide-read-only="${this.schemaHideReadOnly.includes(this.method)}"
|
|
382
393
|
schema-hide-write-only="${this.schemaHideWriteOnly.includes(this.method)}"
|
|
383
|
-
exportparts="wrap-request-btn:wrap-request-btn, btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-clear-resp:btn-clear-resp,
|
|
394
|
+
exportparts="wrap-request-btn:wrap-request-btn, btn:btn, btn-fill:btn-fill, btn-outline:btn-outline, btn-try:btn-try, btn-clear:btn-clear, btn-fill-example:btn-fill-example, btn-clear-resp:btn-clear-resp,
|
|
384
395
|
file-input:file-input, textbox:textbox, textbox-param:textbox-param, textarea:textarea, textarea-param:textarea-param,
|
|
385
396
|
anchor:anchor, anchor-param-example:anchor-param-example"
|
|
386
397
|
>
|
|
@@ -119,6 +119,9 @@ export function onClearAllApiKeys() {
|
|
|
119
119
|
v.finalKeyValue = "";
|
|
120
120
|
v.hostNote = "";
|
|
121
121
|
});
|
|
122
|
+
if (this.persistAuth === "true") {
|
|
123
|
+
setPersistedApiKeys.call(this, {});
|
|
124
|
+
}
|
|
122
125
|
this.requestUpdate();
|
|
123
126
|
}
|
|
124
127
|
|
|
@@ -133,7 +136,7 @@ function setPersistedApiKeys(obj) {
|
|
|
133
136
|
export function recoverPersistedApiKeys() {
|
|
134
137
|
const rapidocLs = getPersistedApiKeys.call(this);
|
|
135
138
|
Object.values(rapidocLs).forEach((p) => {
|
|
136
|
-
applyApiKey.call(this, p.securitySchemeId,
|
|
139
|
+
applyApiKey.call(this, p.securitySchemeId, "", "", p.value);
|
|
137
140
|
});
|
|
138
141
|
}
|
|
139
142
|
|
|
@@ -159,8 +162,14 @@ function onApiKeyChange(securitySchemeId) {
|
|
|
159
162
|
applyApiKey.call(this, securitySchemeId, "", "", apiKeyValue);
|
|
160
163
|
}
|
|
161
164
|
if (this.persistAuth === "true") {
|
|
165
|
+
// Only what recoverPersistedApiKeys restores: the apiKey / bearer value. Basic credentials
|
|
166
|
+
// (and the derived Authorization header) are never written to localStorage.
|
|
162
167
|
const rapidocLs = getPersistedApiKeys.call(this);
|
|
163
|
-
|
|
168
|
+
if (securityObj.value) {
|
|
169
|
+
rapidocLs[securitySchemeId] = { securitySchemeId, value: securityObj.value };
|
|
170
|
+
} else {
|
|
171
|
+
delete rapidocLs[securitySchemeId];
|
|
172
|
+
}
|
|
164
173
|
setPersistedApiKeys.call(this, rapidocLs);
|
|
165
174
|
}
|
|
166
175
|
}
|
|
@@ -26,7 +26,8 @@ export function setApiServer(serverUrl) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function onApiServerVarChange(e, serverObj) {
|
|
29
|
-
|
|
29
|
+
// The variables render as a list, <ul class="server-vars">
|
|
30
|
+
const inputEls = [...e.currentTarget.closest(".server-vars").querySelectorAll("input, select")];
|
|
30
31
|
let tempUrl = serverObj.url;
|
|
31
32
|
inputEls.forEach((v) => {
|
|
32
33
|
const regex = new RegExp(`{${v.dataset.var}}`, "g");
|
|
@@ -134,7 +135,7 @@ export default function serverTemplate() {
|
|
|
134
135
|
.checked="${this.selectedServer.url === server.url}"
|
|
135
136
|
/>
|
|
136
137
|
<label for="srvr-opt-${i}">
|
|
137
|
-
${server.url} ${server.description ? html`- <span class="regular-font">${server.description} </span>` : ""}
|
|
138
|
+
${server.url} ${server.description ? html`- <span class="regular-font">${unsafeHTML(sanitizeHTML(marked.parseInline(server.description)))} </span>` : ""}
|
|
138
139
|
</label>
|
|
139
140
|
</div>
|
|
140
141
|
`,
|
|
@@ -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
|
+
}
|