rapidoc 11.0.0 → 12.0.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 (80) hide show
  1. package/README.md +46 -31
  2. package/dist/rapidoc-min.js +1424 -213
  3. package/dist/rapidoc-static-min.js +13542 -0
  4. package/package.json +48 -34
  5. package/src/components/api-request.ts +869 -0
  6. package/src/components/api-response.ts +509 -0
  7. package/src/components/json-tree.ts +171 -0
  8. package/src/components/schema-base-element.ts +315 -0
  9. package/src/components/schema-table.ts +430 -0
  10. package/src/components/schema-tree.ts +386 -0
  11. package/src/components/tag-input.ts +184 -0
  12. package/src/index.js +6 -0
  13. package/src/oauth-receiver.ts +29 -0
  14. package/src/rapidoc-mini.ts +582 -0
  15. package/src/rapidoc.ts +1808 -0
  16. package/src/static/index.d.ts +149 -0
  17. package/src/static/index.js +62 -0
  18. package/src/static/navigation.js +497 -0
  19. package/src/static/render.js +490 -0
  20. package/src/static/serializer.js +216 -0
  21. package/src/styles/auth-styles.js +318 -0
  22. package/src/styles/border-styles.js +24 -0
  23. package/src/styles/css-bundle.js +129 -0
  24. package/src/styles/custom-styles.js +8 -0
  25. package/src/styles/dialog-box-styles.js +102 -0
  26. package/src/styles/endpoint-styles.js +559 -0
  27. package/src/styles/flex-styles.js +16 -0
  28. package/src/styles/font-styles.js +432 -0
  29. package/src/styles/info-styles.js +36 -0
  30. package/src/styles/input-styles.js +555 -0
  31. package/src/styles/layout-styles.js +396 -0
  32. package/src/styles/microlighter-styles.js +99 -0
  33. package/src/styles/nav-styles.js +609 -0
  34. package/src/styles/param-list-styles.js +261 -0
  35. package/src/styles/schema-styles.js +288 -0
  36. package/src/styles/tab-styles.js +106 -0
  37. package/src/styles/theme-tokens.js +582 -0
  38. package/src/templates/advanced-search-dialog-template.js +94 -0
  39. package/src/templates/api-response-template.js +219 -0
  40. package/src/templates/callback-template.js +86 -0
  41. package/src/templates/code-samples-template.js +52 -0
  42. package/src/templates/components-template.js +86 -0
  43. package/src/templates/downloads-template.js +85 -0
  44. package/src/templates/endpoint-template.js +386 -0
  45. package/src/templates/expanded-endpoint-template.js +266 -0
  46. package/src/templates/focused-endpoint-template.js +145 -0
  47. package/src/templates/header-template.js +92 -0
  48. package/src/templates/logo-template.js +21 -0
  49. package/src/templates/main-body-template.js +102 -0
  50. package/src/templates/navbar-template.js +489 -0
  51. package/src/templates/overview-template.js +104 -0
  52. package/src/templates/request-body-template.js +651 -0
  53. package/src/templates/request-params-template.js +474 -0
  54. package/src/templates/security-scheme-template.js +1263 -0
  55. package/src/templates/server-template.js +152 -0
  56. package/src/utils/auto-color-scheme.js +99 -0
  57. package/src/utils/bind-navbar.js +175 -0
  58. package/src/utils/color-utils.js +59 -0
  59. package/src/utils/common-utils.js +191 -0
  60. package/src/utils/curl-utils.js +123 -0
  61. package/src/utils/highlighter.js +416 -0
  62. package/src/utils/markdown-utils.js +110 -0
  63. package/src/utils/mcp/common.js +467 -0
  64. package/src/utils/mcp/generators/csharp.js +242 -0
  65. package/src/utils/mcp/generators/go.js +298 -0
  66. package/src/utils/mcp/generators/java.js +330 -0
  67. package/src/utils/mcp/generators/python.js +248 -0
  68. package/src/utils/mcp/generators/rust.js +281 -0
  69. package/src/utils/mcp/generators/typescript.js +290 -0
  70. package/src/utils/mcp-generator.js +143 -0
  71. package/src/utils/mock-interceptor.js +508 -0
  72. package/src/utils/oauth-utils.js +87 -0
  73. package/src/utils/request-utils.js +203 -0
  74. package/src/utils/response-utils.js +203 -0
  75. package/src/utils/sanitize.js +57 -0
  76. package/src/utils/schema-utils.js +1927 -0
  77. package/src/utils/slugger.js +189 -0
  78. package/src/utils/spec-parser.js +915 -0
  79. package/src/utils/url-utils.js +211 -0
  80. package/src/utils/xml-utils.js +61 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Serializes a header parameter value according to OpenAPI 3 rules.
3
+ * Objects can be serialized exploded (key=value,key2=value2) or unexploded (key,value,key2,value2).
4
+ *
5
+ * @param {any} rawValue - Raw header parameter value
6
+ * @param {object} [options]
7
+ * @param {boolean} [options.explode=false] - Whether to explode object properties with '='
8
+ * @param {boolean} [options.isObject=false] - Whether value is an object
9
+ * @returns {string} Serialized header string
10
+ */
11
+ export function serializeHeaderValue(rawValue, options = {}) {
12
+ const { explode = false, isObject = false } = options;
13
+
14
+ if (rawValue === undefined || rawValue === null || rawValue === "") {
15
+ return "";
16
+ }
17
+
18
+ if (isObject || (typeof rawValue === "object" && !Array.isArray(rawValue))) {
19
+ let obj = rawValue;
20
+ if (typeof rawValue === "string") {
21
+ try {
22
+ obj = JSON.parse(rawValue.replace(/\n/g, "").trim());
23
+ } catch {
24
+ return "";
25
+ }
26
+ }
27
+
28
+ if (!obj || typeof obj !== "object") {
29
+ return "";
30
+ }
31
+
32
+ const separator = explode ? "=" : ",";
33
+ return Object.keys(obj)
34
+ .map((key) => {
35
+ const val = obj[key];
36
+ const valStr = typeof val === "object" ? JSON.stringify(val) : String(val);
37
+ return `${key}${separator}${valStr}`;
38
+ })
39
+ .join(",");
40
+ }
41
+
42
+ if (Array.isArray(rawValue)) {
43
+ const filtered = rawValue.filter((v) => v !== "" && v !== undefined && v !== null);
44
+ return filtered.join(",");
45
+ }
46
+
47
+ return String(rawValue);
48
+ }
49
+
50
+ /**
51
+ * Constructs a Headers instance for an API request from structured inputs.
52
+ *
53
+ * @param {object} options
54
+ * @param {string} [options.accept] - Accept header
55
+ * @param {Array<object>} [options.apiKeys] - Applied API keys
56
+ * @param {Array<{ name: string, value: any, explode?: boolean, isObject?: boolean }>} [options.headerParams]
57
+ * @param {string} [options.contentType] - Content-Type to apply (omitted for multipart/form-data)
58
+ * @returns {Headers}
59
+ */
60
+ export function constructFetchHeaders({
61
+ accept = "",
62
+ apiKeys = [],
63
+ headerParams = [],
64
+ contentType = "",
65
+ } = {}) {
66
+ const reqHeaders = new Headers();
67
+
68
+ if (accept) {
69
+ reqHeaders.append("Accept", accept);
70
+ }
71
+
72
+ if (Array.isArray(apiKeys)) {
73
+ apiKeys
74
+ .filter((k) => k && k.in === "header" && k.finalKeyValue)
75
+ .forEach((k) => {
76
+ reqHeaders.append(k.name, k.finalKeyValue);
77
+ });
78
+ }
79
+
80
+ if (Array.isArray(headerParams)) {
81
+ headerParams.forEach((param) => {
82
+ if (
83
+ param &&
84
+ param.name &&
85
+ param.value !== undefined &&
86
+ param.value !== null &&
87
+ param.value !== ""
88
+ ) {
89
+ const headerValue = serializeHeaderValue(param.value, {
90
+ explode: param.explode === true || param.explode === "true",
91
+ isObject: param.isObject === true,
92
+ });
93
+ if (headerValue) {
94
+ reqHeaders.append(param.name, headerValue);
95
+ }
96
+ }
97
+ });
98
+ }
99
+
100
+ if (contentType && !contentType.includes("form-data")) {
101
+ reqHeaders.append("Content-Type", contentType);
102
+ }
103
+
104
+ return reqHeaders;
105
+ }
106
+
107
+ /**
108
+ * Builds a URLSearchParams body from field entries or a dynamic JSON template.
109
+ *
110
+ * @param {Array<{ name: string, value: any, isArray?: boolean }>} fields
111
+ * @param {string} [dynamicJson] - Raw JSON string for dynamic forms
112
+ * @returns {URLSearchParams}
113
+ */
114
+ export function buildUrlEncodedBody(fields = [], dynamicJson = "") {
115
+ const params = new URLSearchParams();
116
+
117
+ if (dynamicJson) {
118
+ try {
119
+ const obj = JSON.parse(dynamicJson);
120
+ if (obj && typeof obj === "object") {
121
+ for (const prop of Object.keys(obj)) {
122
+ params.append(
123
+ prop,
124
+ typeof obj[prop] === "object" ? JSON.stringify(obj[prop]) : String(obj[prop]),
125
+ );
126
+ }
127
+ return params;
128
+ }
129
+ } catch {
130
+ // Fall through to fields
131
+ }
132
+ }
133
+
134
+ if (Array.isArray(fields)) {
135
+ fields.forEach((field) => {
136
+ if (
137
+ !field ||
138
+ !field.name ||
139
+ field.value === undefined ||
140
+ field.value === null ||
141
+ field.value === ""
142
+ ) {
143
+ return;
144
+ }
145
+ if (field.isArray) {
146
+ const arr = Array.isArray(field.value) ? field.value : [field.value];
147
+ const filtered = arr.filter((v) => v !== "" && v !== undefined && v !== null);
148
+ if (filtered.length > 0) {
149
+ params.append(field.name, filtered.join(","));
150
+ }
151
+ } else {
152
+ params.append(field.name, String(field.value));
153
+ }
154
+ });
155
+ }
156
+
157
+ return params;
158
+ }
159
+
160
+ /**
161
+ * Builds a FormData instance from structured field entries.
162
+ *
163
+ * @param {Array<{ name: string, value: any, file?: File, isArray?: boolean }>} fields
164
+ * @returns {FormData}
165
+ */
166
+ export function buildMultipartFormData(fields = []) {
167
+ const formData = new FormData();
168
+
169
+ if (!Array.isArray(fields)) {
170
+ return formData;
171
+ }
172
+
173
+ fields.forEach((field) => {
174
+ if (!field || !field.name) {
175
+ return;
176
+ }
177
+
178
+ if (typeof File !== "undefined" && field.file instanceof File) {
179
+ formData.append(field.name, field.file, field.file.name);
180
+ return;
181
+ }
182
+
183
+ if (field.value === undefined || field.value === null || field.value === "") {
184
+ return;
185
+ }
186
+
187
+ if (field.isArray) {
188
+ const arr = Array.isArray(field.value) ? field.value : [field.value];
189
+ const filtered = arr.filter((v) => v !== "" && v !== undefined && v !== null);
190
+ filtered.forEach((item) => {
191
+ if (typeof File !== "undefined" && item instanceof File) {
192
+ formData.append(field.name, item, item.name);
193
+ } else {
194
+ formData.append(field.name, String(item));
195
+ }
196
+ });
197
+ } else {
198
+ formData.append(field.name, String(field.value));
199
+ }
200
+ });
201
+
202
+ return formData;
203
+ }
@@ -0,0 +1,203 @@
1
+ import { formatXml } from "./xml-utils.js";
2
+ import { schemaToAST, generateExample, standardizeExample } from "./schema-utils.js";
3
+
4
+ /**
5
+ * Parses and processes a standard Fetch API Response into RapiDoc formatted response state.
6
+ * Handles JSON parsing, XML formatting, NDJSON streaming format, charset decoding,
7
+ * and binary MIME sniffing with RFC 5987 Content-Disposition decoding.
8
+ *
9
+ * @param {Response} fetchResponse - Standard Fetch Response object
10
+ * @returns {Promise<{
11
+ * responseText: string,
12
+ * respJson?: any,
13
+ * respBlob?: Blob,
14
+ * respText?: string,
15
+ * responseIsBlob: boolean,
16
+ * responseBlobType: string,
17
+ * responseBlobUrl: string,
18
+ * respContentDisposition: string
19
+ * }>}
20
+ */
21
+ export async function processFetchResponse(fetchResponse) {
22
+ let respBlob;
23
+ let respJson;
24
+ let respText;
25
+ let responseIsBlob = false;
26
+ let responseBlobType = "";
27
+ let responseBlobUrl = "";
28
+ let respContentDisposition = "";
29
+ let responseText = "";
30
+
31
+ let contentType = fetchResponse.headers.get("content-type");
32
+ const respEmpty = (await fetchResponse.clone().text()).length === 0;
33
+ if (respEmpty) {
34
+ responseText = "";
35
+ } else if (contentType) {
36
+ contentType = contentType.split(";")[0].trim();
37
+ if (contentType === "application/x-ndjson") {
38
+ responseText = await fetchResponse.text();
39
+ } else if (contentType.includes("json")) {
40
+ if (/charset=[^"']+/.test(contentType)) {
41
+ const encoding = contentType.split("charset=")[1];
42
+ const buffer = await fetchResponse.arrayBuffer();
43
+ try {
44
+ respText = new TextDecoder(encoding).decode(buffer);
45
+ } catch {
46
+ respText = new TextDecoder("utf-8").decode(buffer);
47
+ }
48
+ try {
49
+ respJson = JSON.parse(respText);
50
+ responseText = JSON.stringify(respJson, null, 2);
51
+ } catch {
52
+ responseText = respText;
53
+ }
54
+ } else {
55
+ respJson = await fetchResponse.json();
56
+ responseText = JSON.stringify(respJson, null, 2);
57
+ }
58
+ } else if (
59
+ /^font\/|tar$|zip$|7z$|rtf$|msword$|excel$|\/pdf$|\/octet-stream$|^application\/vnd\./.test(
60
+ contentType,
61
+ )
62
+ ) {
63
+ responseIsBlob = true;
64
+ responseBlobType = "download";
65
+ } else if (contentType.startsWith("image")) {
66
+ responseIsBlob = true;
67
+ responseBlobType = "image";
68
+ } else if (/^audio|^image|^video/.test(contentType)) {
69
+ responseIsBlob = true;
70
+ responseBlobType = "view";
71
+ } else {
72
+ respText = await fetchResponse.text();
73
+ if (contentType.includes("xml")) {
74
+ responseText = formatXml(respText, { textNodesOnSameLine: true, indentor: " " });
75
+ } else {
76
+ responseText = respText;
77
+ }
78
+ }
79
+ if (responseIsBlob) {
80
+ const contentDisposition = fetchResponse.headers.get("content-disposition") || "";
81
+ let filenameFromContentDisposition = "filename";
82
+ if (contentDisposition) {
83
+ const filenameStarRegexMatch = contentDisposition.match(/filename\*=\s*UTF-8''([^;]+)/);
84
+ if (filenameStarRegexMatch) {
85
+ filenameFromContentDisposition = decodeURIComponent(filenameStarRegexMatch[1]);
86
+ } else {
87
+ const filenameMatch = contentDisposition.match(/filename="?([^"]+)"?/);
88
+ if (filenameMatch) {
89
+ filenameFromContentDisposition = filenameMatch[1];
90
+ }
91
+ }
92
+ }
93
+ respContentDisposition = filenameFromContentDisposition;
94
+ respBlob = await fetchResponse.blob();
95
+ if (typeof URL !== "undefined" && URL.createObjectURL) {
96
+ responseBlobUrl = URL.createObjectURL(respBlob);
97
+ }
98
+ }
99
+ } else {
100
+ respText = await fetchResponse.text();
101
+ responseText = respText;
102
+ }
103
+
104
+ return {
105
+ responseText,
106
+ respJson,
107
+ respBlob,
108
+ respText,
109
+ responseIsBlob,
110
+ responseBlobType,
111
+ responseBlobUrl,
112
+ respContentDisposition,
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Pre-computes schema ASTs, examples, and headers for all response codes and MIME types.
118
+ * Pure function extracted from api-response component to enable caching and unit testing.
119
+ *
120
+ * @param {object} responses - OpenAPI responses map
121
+ * @param {object} [options]
122
+ * @param {boolean|string} [options.callback=false]
123
+ * @param {boolean|string} [options.webhook=false]
124
+ * @returns {{
125
+ * headersForEachRespStatus: object,
126
+ * mimeResponsesForEachStatus: object,
127
+ * defaultStatus: string,
128
+ * defaultMimeType: string
129
+ * }}
130
+ */
131
+ export function prepareResponseDefinitions(responses, options = {}) {
132
+ const { callback = false, webhook = false } = options;
133
+ const isCallbackOrWebhook =
134
+ callback === true || callback === "true" || webhook === true || webhook === "true";
135
+
136
+ const headersForEachRespStatus = {};
137
+ const mimeResponsesForEachStatus = {};
138
+ let defaultStatus = "";
139
+ let defaultMimeType = "";
140
+
141
+ if (!responses || typeof responses !== "object") {
142
+ return {
143
+ headersForEachRespStatus,
144
+ mimeResponsesForEachStatus,
145
+ defaultStatus,
146
+ defaultMimeType,
147
+ };
148
+ }
149
+
150
+ for (const statusCode of Object.keys(responses)) {
151
+ if (statusCode === "$$ref") {
152
+ continue;
153
+ }
154
+ if (!defaultStatus) {
155
+ defaultStatus = statusCode;
156
+ }
157
+ const allMimeResp = {};
158
+ const content = responses[statusCode]?.content;
159
+
160
+ if (content && typeof content === "object") {
161
+ for (const mimeResp of Object.keys(content)) {
162
+ const mimeRespObj = content[mimeResp];
163
+ if (!defaultMimeType) {
164
+ defaultMimeType = mimeResp;
165
+ }
166
+
167
+ const schemaTree = mimeRespObj?.schema ? schemaToAST(mimeRespObj.schema) : null;
168
+ const respExamples = generateExample(
169
+ mimeRespObj?.schema,
170
+ mimeResp,
171
+ standardizeExample(mimeRespObj?.examples),
172
+ standardizeExample(mimeRespObj?.example),
173
+ !isCallbackOrWebhook,
174
+ isCallbackOrWebhook,
175
+ mimeResp.includes("json") ? "json" : "text",
176
+ );
177
+
178
+ allMimeResp[mimeResp] = {
179
+ description: responses[statusCode].description,
180
+ examples: respExamples,
181
+ selectedExample: respExamples[0]?.exampleId || "",
182
+ schemaTree,
183
+ };
184
+ }
185
+ }
186
+
187
+ const tempHeaders = [];
188
+ if (responses[statusCode]?.headers) {
189
+ for (const key of Object.keys(responses[statusCode].headers)) {
190
+ tempHeaders.push({ name: key, ...responses[statusCode].headers[key] });
191
+ }
192
+ }
193
+ headersForEachRespStatus[statusCode] = tempHeaders;
194
+ mimeResponsesForEachStatus[statusCode] = allMimeResp;
195
+ }
196
+
197
+ return {
198
+ headersForEachRespStatus,
199
+ mimeResponsesForEachStatus,
200
+ defaultStatus,
201
+ defaultMimeType,
202
+ };
203
+ }
@@ -0,0 +1,57 @@
1
+ // One instance for every call: constructing it per description is measurable on large schemas
2
+ let nativeSanitizer;
3
+
4
+ export function sanitizeHTML(htmlString) {
5
+ if (typeof htmlString !== "string") {
6
+ return "";
7
+ }
8
+
9
+ // Use native Sanitizer API if supported
10
+ if (typeof window !== "undefined" && window.Sanitizer && Element.prototype.setHTML) {
11
+ try {
12
+ const el = document.createElement("div");
13
+ nativeSanitizer ??= new window.Sanitizer({ allowAttributes: { "*": ["*"] } });
14
+ el.setHTML(htmlString, { sanitizer: nativeSanitizer });
15
+ return el.innerHTML;
16
+ } catch {
17
+ // If Sanitizer with options throws, fall through to fallback shim
18
+ }
19
+ }
20
+
21
+ // Minimal shim for unsupported browsers to prevent basic XSS
22
+ let doc;
23
+ if (typeof DOMParser !== "undefined") {
24
+ doc = new DOMParser().parseFromString(htmlString, "text/html");
25
+ } else {
26
+ // Fallback for SSR/Node if needed, though this is primarily browser-side
27
+ return htmlString;
28
+ }
29
+
30
+ // Remove script tags
31
+ const scripts = doc.querySelectorAll("script");
32
+ for (let i = scripts.length - 1; i >= 0; i--) {
33
+ scripts[i].parentNode.removeChild(scripts[i]);
34
+ }
35
+
36
+ // Remove on* attributes and javascript: URIs
37
+ const allElements = doc.querySelectorAll("*");
38
+ for (let i = 0; i < allElements.length; i++) {
39
+ const el = allElements[i];
40
+ for (let j = el.attributes.length - 1; j >= 0; j--) {
41
+ const attr = el.attributes[j];
42
+ const attrName = attr.name.toLowerCase();
43
+ const attrValue = attr.value.toLowerCase();
44
+
45
+ if (attrName.startsWith("on")) {
46
+ el.removeAttribute(attr.name);
47
+ } else if (
48
+ (attrName === "href" || attrName === "src") &&
49
+ (attrValue.includes("javascript:") || attrValue.includes("data:text/html"))
50
+ ) {
51
+ el.removeAttribute(attr.name);
52
+ }
53
+ }
54
+ }
55
+
56
+ return doc.body.innerHTML;
57
+ }