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.
Files changed (96) hide show
  1. package/README.md +171 -54
  2. package/dist/rapidoc-flow-min.js +580 -0
  3. package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
  4. package/dist/rapidoc-min.js +645 -202
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +57 -42
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +912 -0
  10. package/src/components/api-response.ts +514 -0
  11. package/src/components/json-tree.ts +171 -0
  12. package/src/components/schema-base-element.ts +329 -0
  13. package/src/components/schema-table.ts +475 -0
  14. package/src/components/schema-tree.ts +393 -0
  15. package/src/components/tag-input.ts +180 -0
  16. package/src/flow.js +3 -0
  17. package/src/index.js +6 -0
  18. package/src/oauth-receiver.ts +29 -0
  19. package/src/rapi-flow.ts +1178 -0
  20. package/src/rapidoc-mini.ts +603 -0
  21. package/src/rapidoc.ts +1854 -0
  22. package/src/styles/README.md +96 -0
  23. package/src/styles/auth-styles.js +318 -0
  24. package/src/styles/border-styles.js +24 -0
  25. package/src/styles/css-bundle.js +129 -0
  26. package/src/styles/custom-styles.js +8 -0
  27. package/src/styles/dialog-box-styles.js +102 -0
  28. package/src/styles/endpoint-styles.js +564 -0
  29. package/src/styles/flex-styles.js +16 -0
  30. package/src/styles/flow-styles.js +248 -0
  31. package/src/styles/font-styles.js +432 -0
  32. package/src/styles/info-styles.js +36 -0
  33. package/src/styles/input-styles.js +555 -0
  34. package/src/styles/layout-styles.js +396 -0
  35. package/src/styles/microlighter-styles.js +99 -0
  36. package/src/styles/nav-styles.js +633 -0
  37. package/src/styles/param-list-styles.js +261 -0
  38. package/src/styles/schema-styles.js +296 -0
  39. package/src/styles/tab-styles.js +106 -0
  40. package/src/styles/theme-tokens.js +582 -0
  41. package/src/templates/README.md +89 -0
  42. package/src/templates/advanced-search-dialog-template.js +94 -0
  43. package/src/templates/api-response-template.js +223 -0
  44. package/src/templates/callback-template.js +86 -0
  45. package/src/templates/code-samples-template.js +52 -0
  46. package/src/templates/components-template.js +86 -0
  47. package/src/templates/downloads-template.js +70 -0
  48. package/src/templates/endpoint-template.js +377 -0
  49. package/src/templates/expanded-endpoint-template.js +270 -0
  50. package/src/templates/focused-endpoint-template.js +146 -0
  51. package/src/templates/header-template.js +92 -0
  52. package/src/templates/logo-template.js +21 -0
  53. package/src/templates/main-body-template.js +102 -0
  54. package/src/templates/navbar-template.js +498 -0
  55. package/src/templates/overview-template.js +104 -0
  56. package/src/templates/request-body-template.js +673 -0
  57. package/src/templates/request-params-template.js +485 -0
  58. package/src/templates/security-scheme-template.js +1272 -0
  59. package/src/templates/server-template.js +153 -0
  60. package/src/utils/README.md +146 -0
  61. package/src/utils/arazzo/README.md +119 -0
  62. package/src/utils/arazzo/components.js +15 -0
  63. package/src/utils/arazzo/graph.js +269 -0
  64. package/src/utils/arazzo/index.js +52 -0
  65. package/src/utils/arazzo/operations.js +106 -0
  66. package/src/utils/arazzo/parse.js +196 -0
  67. package/src/utils/arazzo/refs.js +40 -0
  68. package/src/utils/arazzo/svg.js +707 -0
  69. package/src/utils/auto-color-scheme.js +99 -0
  70. package/src/utils/bind-navbar.js +175 -0
  71. package/src/utils/color-utils.js +59 -0
  72. package/src/utils/common-utils.js +191 -0
  73. package/src/utils/curl-utils.js +126 -0
  74. package/src/utils/highlighter.js +419 -0
  75. package/src/utils/markdown-utils.js +110 -0
  76. package/src/utils/mcp/README.md +155 -0
  77. package/src/utils/mcp/common.js +166 -0
  78. package/src/utils/mcp/manifest.js +382 -0
  79. package/src/utils/mcp/project/server.ts +53 -0
  80. package/src/utils/mcp/project.js +88 -0
  81. package/src/utils/mcp/runtime.js +289 -0
  82. package/src/utils/mcp/schema.js +98 -0
  83. package/src/utils/mcp/workflow-runtime.js +374 -0
  84. package/src/utils/mcp/workflows.js +240 -0
  85. package/src/utils/mcp/zip.js +102 -0
  86. package/src/utils/mcp-generator.js +47 -0
  87. package/src/utils/mock-interceptor.js +557 -0
  88. package/src/utils/oauth-utils.js +87 -0
  89. package/src/utils/request-utils.js +264 -0
  90. package/src/utils/response-utils.js +203 -0
  91. package/src/utils/sanitize.js +57 -0
  92. package/src/utils/schema-utils.js +1954 -0
  93. package/src/utils/slugger.js +187 -0
  94. package/src/utils/spec-parser.js +941 -0
  95. package/src/utils/url-utils.js +246 -0
  96. 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
+ }