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
package/src/rapidoc-mini.ts
CHANGED
|
@@ -89,7 +89,10 @@ export default class RapiDocMini extends LitElement {
|
|
|
89
89
|
|
|
90
90
|
// Mock Server
|
|
91
91
|
@property({ type: String, attribute: "mock-server" }) mockServer = "false";
|
|
92
|
-
|
|
92
|
+
// A string of codes for every operation, or a map by operation (`{ "POST /orders": "503, 200", "*": "200" }`)
|
|
93
|
+
@property({ type: String, attribute: "mock-server-status-code" }) mockServerStatusCode:
|
|
94
|
+
| string
|
|
95
|
+
| Record<string, string> = "200";
|
|
93
96
|
@property({ type: String, attribute: "mock-server-status-strategy" }) mockServerStatusStrategy =
|
|
94
97
|
"example";
|
|
95
98
|
@property({ type: Number, attribute: "mock-server-delay" }) mockServerDelay = 0;
|
|
@@ -120,6 +123,7 @@ export default class RapiDocMini extends LitElement {
|
|
|
120
123
|
@property({ type: Boolean }) loading = false;
|
|
121
124
|
|
|
122
125
|
isMini: boolean;
|
|
126
|
+
_specLoadSeq: number;
|
|
123
127
|
updateRoute: string;
|
|
124
128
|
renderStyle: string;
|
|
125
129
|
showHeader: string;
|
|
@@ -135,6 +139,8 @@ export default class RapiDocMini extends LitElement {
|
|
|
135
139
|
constructor() {
|
|
136
140
|
super();
|
|
137
141
|
this.isMini = true;
|
|
142
|
+
// Spec loads carry a token; only the latest load may apply its result (see loadSpec)
|
|
143
|
+
this._specLoadSeq = 0;
|
|
138
144
|
this.updateRoute = "false";
|
|
139
145
|
this.renderStyle = "view";
|
|
140
146
|
this.showHeader = "false";
|
|
@@ -312,7 +318,7 @@ export default class RapiDocMini extends LitElement {
|
|
|
312
318
|
|
|
313
319
|
disconnectedCallback() {
|
|
314
320
|
if (this.mockServer === "true") {
|
|
315
|
-
disableMockServer();
|
|
321
|
+
disableMockServer(this);
|
|
316
322
|
}
|
|
317
323
|
super.disconnectedCallback();
|
|
318
324
|
}
|
|
@@ -364,13 +370,14 @@ export default class RapiDocMini extends LitElement {
|
|
|
364
370
|
if (changedProperties.has("mockServer")) {
|
|
365
371
|
if (this.mockServer === "true" && this.resolvedSpec) {
|
|
366
372
|
enableMockServer(this.resolvedSpec, {
|
|
373
|
+
owner: this,
|
|
367
374
|
statusCode: this.mockServerStatusCode,
|
|
368
375
|
statusStrategy: this.mockServerStatusStrategy,
|
|
369
376
|
delay: this.mockServerDelay,
|
|
370
377
|
log: this.mockServerLog,
|
|
371
378
|
});
|
|
372
379
|
} else if (this.mockServer !== "true") {
|
|
373
|
-
disableMockServer();
|
|
380
|
+
disableMockServer(this);
|
|
374
381
|
}
|
|
375
382
|
}
|
|
376
383
|
if (
|
|
@@ -380,6 +387,7 @@ export default class RapiDocMini extends LitElement {
|
|
|
380
387
|
changedProperties.has("mockServerLog")
|
|
381
388
|
) {
|
|
382
389
|
updateMockConfig({
|
|
390
|
+
owner: this,
|
|
383
391
|
statusCode: this.mockServerStatusCode,
|
|
384
392
|
statusStrategy: this.mockServerStatusStrategy,
|
|
385
393
|
delay: this.mockServerDelay,
|
|
@@ -438,7 +446,8 @@ export default class RapiDocMini extends LitElement {
|
|
|
438
446
|
}
|
|
439
447
|
|
|
440
448
|
if (updateSelectedApiKey) {
|
|
441
|
-
|
|
449
|
+
// While the spec is loading, resolvedSpec is a placeholder without securitySchemes
|
|
450
|
+
if (this.resolvedSpec?.securitySchemes) {
|
|
442
451
|
const rapiDocApiKey = this.resolvedSpec.securitySchemes.find(
|
|
443
452
|
(v: any) => v.securitySchemeId === rapidocApiKey,
|
|
444
453
|
);
|
|
@@ -474,6 +483,8 @@ export default class RapiDocMini extends LitElement {
|
|
|
474
483
|
if (!specUrl) {
|
|
475
484
|
return;
|
|
476
485
|
}
|
|
486
|
+
// Each load gets a token; a load whose token is no longer current never applies its result
|
|
487
|
+
const token = ++this._specLoadSeq;
|
|
477
488
|
try {
|
|
478
489
|
this.resolvedSpec = {
|
|
479
490
|
specLoadError: false,
|
|
@@ -498,9 +509,15 @@ export default class RapiDocMini extends LitElement {
|
|
|
498
509
|
this.matchType,
|
|
499
510
|
this.removeEndpointsWithBadgeLabelAs,
|
|
500
511
|
);
|
|
512
|
+
if (token !== this._specLoadSeq) {
|
|
513
|
+
return; // a newer load started while this one was parsing
|
|
514
|
+
}
|
|
501
515
|
this.loading = false;
|
|
502
|
-
this.afterSpecParsedAndValidated(spec);
|
|
516
|
+
this.afterSpecParsedAndValidated(spec, token);
|
|
503
517
|
} catch (err: any) {
|
|
518
|
+
if (token !== this._specLoadSeq) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
504
521
|
this.loading = false;
|
|
505
522
|
this.loadFailed = true;
|
|
506
523
|
this.resolvedSpec = null;
|
|
@@ -530,7 +547,7 @@ export default class RapiDocMini extends LitElement {
|
|
|
530
547
|
return setApiServer.call(this, apiServerUrl);
|
|
531
548
|
}
|
|
532
549
|
|
|
533
|
-
async afterSpecParsedAndValidated(spec) {
|
|
550
|
+
async afterSpecParsedAndValidated(spec, token = this._specLoadSeq) {
|
|
534
551
|
this.resolvedSpec = spec;
|
|
535
552
|
this.selectedServer = undefined;
|
|
536
553
|
if (this.defaultApiServerUrl) {
|
|
@@ -553,11 +570,15 @@ export default class RapiDocMini extends LitElement {
|
|
|
553
570
|
this.requestUpdate();
|
|
554
571
|
|
|
555
572
|
while (!(await this.updateComplete));
|
|
573
|
+
if (token !== this._specLoadSeq) {
|
|
574
|
+
return; // a newer load took over while this one was rendering; it fires its own spec-loaded
|
|
575
|
+
}
|
|
556
576
|
const specLoadedEvent = new CustomEvent("spec-loaded", { detail: spec });
|
|
557
577
|
this.dispatchEvent(specLoadedEvent);
|
|
558
578
|
|
|
559
579
|
if (this.mockServer === "true") {
|
|
560
580
|
enableMockServer(this.resolvedSpec, {
|
|
581
|
+
owner: this,
|
|
561
582
|
statusCode: this.mockServerStatusCode,
|
|
562
583
|
statusStrategy: this.mockServerStatusStrategy,
|
|
563
584
|
delay: this.mockServerDelay,
|
package/src/rapidoc.ts
CHANGED
|
@@ -41,9 +41,10 @@ import { applyApiKey, onClearAllApiKeys } from "~/templates/security-scheme-temp
|
|
|
41
41
|
import { setApiServer } from "~/templates/server-template";
|
|
42
42
|
import {
|
|
43
43
|
generateMcpServer as mcpServerGenerator,
|
|
44
|
-
|
|
44
|
+
buildMcpManifest,
|
|
45
45
|
estimateTokenCost,
|
|
46
|
-
|
|
46
|
+
parseMcpEnabled,
|
|
47
|
+
createZip,
|
|
47
48
|
} from "~/utils/mcp-generator";
|
|
48
49
|
import { bindNavBar } from "~/utils/bind-navbar";
|
|
49
50
|
import { methodPathSlug } from "~/utils/slugger";
|
|
@@ -91,6 +92,11 @@ export default class RapiDoc extends LitElement {
|
|
|
91
92
|
// Set on the reader's first pointer, key, wheel or touch input (see getContentScrollBehavior)
|
|
92
93
|
this._readerHasInteracted = false;
|
|
93
94
|
this._onFirstInteraction = null;
|
|
95
|
+
this._onHashChange = null;
|
|
96
|
+
// Spec loads carry a token; only the latest load may apply its result (see loadSpec)
|
|
97
|
+
this._specLoadSeq = 0;
|
|
98
|
+
// The spec-url attribute value whose load has been handled, or that an inlined spec superseded
|
|
99
|
+
this._lastSpecUrlRequested = null;
|
|
94
100
|
this.initIntersectionObserver();
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -104,6 +110,7 @@ export default class RapiDoc extends LitElement {
|
|
|
104
110
|
@property({ type: String, attribute: "sort-tags" }) sortTags = "false";
|
|
105
111
|
@property({ type: String, attribute: "sort-schemas" }) sortSchemas = "false";
|
|
106
112
|
@property({ type: String, attribute: "generate-missing-tags" }) generateMissingTags = "false";
|
|
113
|
+
@property({ type: String, attribute: "show-unused-tags" }) showUnusedTags = "false";
|
|
107
114
|
@property({ type: String, attribute: "sort-endpoints-by" }) sortEndpointsBy = "path";
|
|
108
115
|
@property({ type: String, attribute: false }) specFile: any;
|
|
109
116
|
|
|
@@ -138,7 +145,10 @@ export default class RapiDoc extends LitElement {
|
|
|
138
145
|
|
|
139
146
|
// Mock Server
|
|
140
147
|
@property({ type: String, attribute: "mock-server" }) mockServer = "false";
|
|
141
|
-
|
|
148
|
+
// A string of codes for every operation, or a map by operation (`{ "POST /orders": "503, 200", "*": "200" }`)
|
|
149
|
+
@property({ type: String, attribute: "mock-server-status-code" }) mockServerStatusCode:
|
|
150
|
+
| string
|
|
151
|
+
| Record<string, string> = "200";
|
|
142
152
|
@property({ type: String, attribute: "mock-server-status-strategy" }) mockServerStatusStrategy =
|
|
143
153
|
"example";
|
|
144
154
|
@property({ type: Number, attribute: "mock-server-delay" }) mockServerDelay = 0;
|
|
@@ -160,7 +170,8 @@ export default class RapiDoc extends LitElement {
|
|
|
160
170
|
@property({ type: String, attribute: "allow-server-selection" }) allowServerSelection = "true";
|
|
161
171
|
@property({ type: String, attribute: "allow-schema-description-expand-toggle" })
|
|
162
172
|
allowSchemaDescriptionExpandToggle = "true";
|
|
163
|
-
|
|
173
|
+
// "true" shows the Export MCP Server button; "none" or "false" hides it
|
|
174
|
+
@property({ type: String, attribute: "mcp" }) mcp = "false";
|
|
164
175
|
@property({ type: String, attribute: "mcp-server-name" }) mcpServerName = "";
|
|
165
176
|
@property({ type: String, attribute: "show-components" }) showComponents = "false";
|
|
166
177
|
@property({ type: String, attribute: "page-direction" }) pageDirection = "ltr";
|
|
@@ -388,6 +399,9 @@ export default class RapiDoc extends LitElement {
|
|
|
388
399
|
if (!this.generateMissingTags || !"true, false,".includes(`${this.generateMissingTags},`)) {
|
|
389
400
|
this.generateMissingTags = "false";
|
|
390
401
|
}
|
|
402
|
+
if (!this.showUnusedTags || !"true, false,".includes(`${this.showUnusedTags},`)) {
|
|
403
|
+
this.showUnusedTags = "false";
|
|
404
|
+
}
|
|
391
405
|
if (
|
|
392
406
|
!this.sortEndpointsBy ||
|
|
393
407
|
!"method, path, summary, none,".includes(`${this.sortEndpointsBy},`)
|
|
@@ -472,13 +486,12 @@ export default class RapiDoc extends LitElement {
|
|
|
472
486
|
this.mcp = "none";
|
|
473
487
|
}
|
|
474
488
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
() => {
|
|
489
|
+
if (!this._onHashChange) {
|
|
490
|
+
this._onHashChange = () => {
|
|
478
491
|
this.scrollToPath(this.getElementIDFromURL());
|
|
479
|
-
}
|
|
480
|
-
true
|
|
481
|
-
|
|
492
|
+
};
|
|
493
|
+
window.addEventListener("hashchange", this._onHashChange, true);
|
|
494
|
+
}
|
|
482
495
|
|
|
483
496
|
if (!this._readerHasInteracted && !this._onFirstInteraction) {
|
|
484
497
|
this._onFirstInteraction = () => {
|
|
@@ -490,7 +503,9 @@ export default class RapiDoc extends LitElement {
|
|
|
490
503
|
);
|
|
491
504
|
}
|
|
492
505
|
|
|
493
|
-
// Auto-hydrate from inlined spec or spec-url attribute if not already loaded
|
|
506
|
+
// Auto-hydrate from inlined spec or spec-url attribute if not already loaded. This block owns
|
|
507
|
+
// the initial load; the deferred spec-url path in attributeChangedCallback skips the value
|
|
508
|
+
// recorded in _lastSpecUrlRequested so one <rapi-doc spec-url> loads once.
|
|
494
509
|
if (!this.resolvedSpec) {
|
|
495
510
|
const inlinedSpecEl = this.querySelector(
|
|
496
511
|
'script[type="application/json"][slot="rapidoc-spec"]',
|
|
@@ -498,6 +513,8 @@ export default class RapiDoc extends LitElement {
|
|
|
498
513
|
if (inlinedSpecEl?.textContent) {
|
|
499
514
|
try {
|
|
500
515
|
const parsed = JSON.parse(inlinedSpecEl.textContent);
|
|
516
|
+
// The inlined spec wins over a spec-url attribute present at connect
|
|
517
|
+
this._lastSpecUrlRequested = this.getAttribute("spec-url");
|
|
501
518
|
this.loadSpec(parsed);
|
|
502
519
|
} catch {
|
|
503
520
|
// ignore parse error
|
|
@@ -524,9 +541,13 @@ export default class RapiDoc extends LitElement {
|
|
|
524
541
|
if (this.intersectionObserver) {
|
|
525
542
|
this.intersectionObserver.disconnect();
|
|
526
543
|
}
|
|
544
|
+
if (this._onHashChange) {
|
|
545
|
+
window.removeEventListener("hashchange", this._onHashChange, true);
|
|
546
|
+
this._onHashChange = null;
|
|
547
|
+
}
|
|
527
548
|
this.removeFirstInteractionListeners();
|
|
528
549
|
if (this.mockServer === "true") {
|
|
529
|
-
disableMockServer();
|
|
550
|
+
disableMockServer(this);
|
|
530
551
|
}
|
|
531
552
|
super.disconnectedCallback();
|
|
532
553
|
}
|
|
@@ -595,11 +616,12 @@ export default class RapiDoc extends LitElement {
|
|
|
595
616
|
}
|
|
596
617
|
this._clearDsdIfNeeded();
|
|
597
618
|
|
|
598
|
-
//
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
619
|
+
// adding custom style for RapiDoc: the page's <link> whose href contains the css-file value
|
|
620
|
+
if (this.cssFile) {
|
|
621
|
+
const cssLinkEl = document.querySelector(`link[href*="${CSS.escape(this.cssFile)}"]`);
|
|
622
|
+
if (cssLinkEl) {
|
|
623
|
+
this.shadowRoot.appendChild(cssLinkEl.cloneNode());
|
|
624
|
+
}
|
|
603
625
|
}
|
|
604
626
|
return mainBodyTemplate.call(this);
|
|
605
627
|
}
|
|
@@ -662,13 +684,14 @@ export default class RapiDoc extends LitElement {
|
|
|
662
684
|
if (changedProperties.has("mockServer")) {
|
|
663
685
|
if (this.mockServer === "true" && this.resolvedSpec) {
|
|
664
686
|
enableMockServer(this.resolvedSpec, {
|
|
687
|
+
owner: this,
|
|
665
688
|
statusCode: this.mockServerStatusCode,
|
|
666
689
|
statusStrategy: this.mockServerStatusStrategy,
|
|
667
690
|
delay: this.mockServerDelay,
|
|
668
691
|
log: this.mockServerLog,
|
|
669
692
|
});
|
|
670
693
|
} else if (this.mockServer !== "true") {
|
|
671
|
-
disableMockServer();
|
|
694
|
+
disableMockServer(this);
|
|
672
695
|
}
|
|
673
696
|
}
|
|
674
697
|
if (
|
|
@@ -678,6 +701,7 @@ export default class RapiDoc extends LitElement {
|
|
|
678
701
|
changedProperties.has("mockServerLog")
|
|
679
702
|
) {
|
|
680
703
|
updateMockConfig({
|
|
704
|
+
owner: this,
|
|
681
705
|
statusCode: this.mockServerStatusCode,
|
|
682
706
|
statusStrategy: this.mockServerStatusStrategy,
|
|
683
707
|
delay: this.mockServerDelay,
|
|
@@ -702,6 +726,10 @@ export default class RapiDoc extends LitElement {
|
|
|
702
726
|
if (oldVal !== newVal) {
|
|
703
727
|
// put it at the end of event-loop to load all the attributes
|
|
704
728
|
window.setTimeout(async () => {
|
|
729
|
+
// Already loaded by connectedCallback (or superseded by an inlined spec there)
|
|
730
|
+
if (newVal === this._lastSpecUrlRequested) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
705
733
|
await this.loadSpec(newVal);
|
|
706
734
|
// If goto-path is provided and no location-hash is present then try to scroll there
|
|
707
735
|
if (this.gotoPath && !window.location.hash) {
|
|
@@ -894,6 +922,11 @@ export default class RapiDoc extends LitElement {
|
|
|
894
922
|
if (!specUrl) {
|
|
895
923
|
return;
|
|
896
924
|
}
|
|
925
|
+
if (typeof specUrl === "string") {
|
|
926
|
+
this._lastSpecUrlRequested = specUrl;
|
|
927
|
+
}
|
|
928
|
+
// Each load gets a token; a load whose token is no longer current never applies its result
|
|
929
|
+
const token = ++this._specLoadSeq;
|
|
897
930
|
this.searchVal = "";
|
|
898
931
|
try {
|
|
899
932
|
if (!this._hasDsd) {
|
|
@@ -919,10 +952,17 @@ export default class RapiDoc extends LitElement {
|
|
|
919
952
|
this.matchPaths,
|
|
920
953
|
this.matchType,
|
|
921
954
|
this.removeEndpointsWithBadgeLabelAs,
|
|
955
|
+
this.showUnusedTags === "true",
|
|
922
956
|
);
|
|
957
|
+
if (token !== this._specLoadSeq) {
|
|
958
|
+
return; // a newer load started while this one was parsing
|
|
959
|
+
}
|
|
923
960
|
this.loading = false;
|
|
924
|
-
this.afterSpecParsedAndValidated(spec);
|
|
961
|
+
this.afterSpecParsedAndValidated(spec, token);
|
|
925
962
|
} catch (err) {
|
|
963
|
+
if (token !== this._specLoadSeq) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
926
966
|
this.loading = false;
|
|
927
967
|
this._clearDsdIfNeeded();
|
|
928
968
|
this.loadFailed = true;
|
|
@@ -931,7 +971,7 @@ export default class RapiDoc extends LitElement {
|
|
|
931
971
|
}
|
|
932
972
|
}
|
|
933
973
|
|
|
934
|
-
async afterSpecParsedAndValidated(spec) {
|
|
974
|
+
async afterSpecParsedAndValidated(spec, token = this._specLoadSeq) {
|
|
935
975
|
this._clearDsdIfNeeded();
|
|
936
976
|
this.resolvedSpec = spec;
|
|
937
977
|
this.selectedServer = undefined;
|
|
@@ -954,11 +994,15 @@ export default class RapiDoc extends LitElement {
|
|
|
954
994
|
}
|
|
955
995
|
this.requestUpdate();
|
|
956
996
|
while (!(await this.updateComplete));
|
|
997
|
+
if (token !== this._specLoadSeq) {
|
|
998
|
+
return; // a newer load took over while this one was rendering; it fires its own spec-loaded
|
|
999
|
+
}
|
|
957
1000
|
const specLoadedEvent = new CustomEvent("spec-loaded", { detail: spec });
|
|
958
1001
|
this.dispatchEvent(specLoadedEvent);
|
|
959
1002
|
|
|
960
1003
|
if (this.mockServer === "true") {
|
|
961
1004
|
enableMockServer(this.resolvedSpec, {
|
|
1005
|
+
owner: this,
|
|
962
1006
|
statusCode: this.mockServerStatusCode,
|
|
963
1007
|
statusStrategy: this.mockServerStatusStrategy,
|
|
964
1008
|
delay: this.mockServerDelay,
|
|
@@ -973,6 +1017,9 @@ export default class RapiDoc extends LitElement {
|
|
|
973
1017
|
}
|
|
974
1018
|
if (this.renderStyle === "read") {
|
|
975
1019
|
await sleep(100);
|
|
1020
|
+
if (token !== this._specLoadSeq) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
976
1023
|
this.observeExpandedContent(); // This will auto-highlight the selected nav-item in read-mode
|
|
977
1024
|
}
|
|
978
1025
|
|
|
@@ -993,7 +1040,9 @@ export default class RapiDoc extends LitElement {
|
|
|
993
1040
|
// If goto-path is provided and no location-hash is present then try to scroll to default element
|
|
994
1041
|
if (!this.gotoPath) {
|
|
995
1042
|
const defaultElementId =
|
|
996
|
-
this.showInfo === "true"
|
|
1043
|
+
this.showInfo === "true"
|
|
1044
|
+
? "overview"
|
|
1045
|
+
: this.resolvedSpec.tags.find((t) => t.paths.length > 0)?.paths[0]?.elementId;
|
|
997
1046
|
if (defaultElementId) {
|
|
998
1047
|
this.scrollToPath(defaultElementId);
|
|
999
1048
|
}
|
|
@@ -1721,53 +1770,51 @@ export default class RapiDoc extends LitElement {
|
|
|
1721
1770
|
return setApiServer.call(this, apiServerUrl);
|
|
1722
1771
|
}
|
|
1723
1772
|
|
|
1724
|
-
//
|
|
1725
|
-
get
|
|
1726
|
-
return
|
|
1773
|
+
// True when the `mcp` attribute asks for the Export MCP Server button
|
|
1774
|
+
get mcpEnabled() {
|
|
1775
|
+
return parseMcpEnabled(this.mcp);
|
|
1727
1776
|
}
|
|
1728
1777
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1778
|
+
/**
|
|
1779
|
+
* The <rapi-flow> on the page whose Arazzo document the export takes workflow tools from: the
|
|
1780
|
+
* one whose `for` names this element, else one without `for`. Nothing when rapi-flow is not
|
|
1781
|
+
* loaded.
|
|
1782
|
+
*/
|
|
1783
|
+
private findArazzoDocument() {
|
|
1784
|
+
if (typeof customElements === "undefined" || !customElements.get("rapi-flow")) return undefined;
|
|
1785
|
+
const flows = [...document.querySelectorAll("rapi-flow")] as any[];
|
|
1786
|
+
return (
|
|
1787
|
+
flows.find((f) => this.id && f.getAttribute("for") === this.id && f.arazzoDocument) ||
|
|
1788
|
+
flows.find((f) => !f.getAttribute("for") && f.arazzoDocument)
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
private mcpOptions(options) {
|
|
1793
|
+
const flow = this.findArazzoDocument();
|
|
1794
|
+
return {
|
|
1736
1795
|
serverName: this.mcpServerName,
|
|
1796
|
+
arazzo: flow?.arazzoDocument,
|
|
1797
|
+
// <rapi-flow> carries the workflow runtime; this bundle does not
|
|
1798
|
+
workflowRuntime: flow?.workflowRuntimeSource,
|
|
1737
1799
|
...options,
|
|
1738
|
-
}
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// Public Method - Builds the MCP manifest and the files of the exported project in memory
|
|
1804
|
+
generateMcpServer(options = {}) {
|
|
1805
|
+
const spec = this.resolvedSpec || this.openApiSpec;
|
|
1806
|
+
if (!spec) return null;
|
|
1807
|
+
return mcpServerGenerator(spec, this.mcpOptions(options));
|
|
1739
1808
|
}
|
|
1740
1809
|
|
|
1741
|
-
// Public Method -
|
|
1742
|
-
downloadMcpServer(
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
const t = result.target;
|
|
1747
|
-
let filename = "server.ts";
|
|
1748
|
-
let mime = "text/typescript";
|
|
1749
|
-
|
|
1750
|
-
if (t === "python") {
|
|
1751
|
-
filename = "server.py";
|
|
1752
|
-
mime = "text/x-python";
|
|
1753
|
-
} else if (t === "go") {
|
|
1754
|
-
filename = "main.go";
|
|
1755
|
-
mime = "text/x-go";
|
|
1756
|
-
} else if (t === "csharp" || t === "c#") {
|
|
1757
|
-
filename = "Program.cs";
|
|
1758
|
-
mime = "text/plain";
|
|
1759
|
-
} else if (t === "rust") {
|
|
1760
|
-
filename = "main.rs";
|
|
1761
|
-
mime = "text/x-rust";
|
|
1762
|
-
} else if (t === "java") {
|
|
1763
|
-
filename = "RapiDocMcpServer.java";
|
|
1764
|
-
mime = "text/x-java-source";
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
const blob = new Blob([result.serverCode], { type: mime });
|
|
1810
|
+
// Public Method - Downloads the exported MCP project as a zip
|
|
1811
|
+
downloadMcpServer(options = {}) {
|
|
1812
|
+
const result = this.generateMcpServer(options);
|
|
1813
|
+
if (!result) return;
|
|
1814
|
+
const blob = new Blob([createZip(result.files)], { type: "application/zip" });
|
|
1768
1815
|
const link = document.createElement("a");
|
|
1769
1816
|
link.href = URL.createObjectURL(blob);
|
|
1770
|
-
link.download =
|
|
1817
|
+
link.download = `${result.manifest.server.name.replace(/_/g, "-")}-mcp.zip`;
|
|
1771
1818
|
document.body.appendChild(link);
|
|
1772
1819
|
link.click();
|
|
1773
1820
|
document.body.removeChild(link);
|
|
@@ -1778,8 +1825,7 @@ export default class RapiDoc extends LitElement {
|
|
|
1778
1825
|
getMcpBudget(options = {}) {
|
|
1779
1826
|
const spec = this.resolvedSpec || this.openApiSpec;
|
|
1780
1827
|
if (!spec) return null;
|
|
1781
|
-
|
|
1782
|
-
return estimateTokenCost(extracted.tools, extracted.resources, extracted.workflows);
|
|
1828
|
+
return estimateTokenCost(buildMcpManifest(spec, this.mcpOptions(options)));
|
|
1783
1829
|
}
|
|
1784
1830
|
|
|
1785
1831
|
// Event handler for Advanced Search text-inputs and checkboxes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# `src/styles` — RapiDoc Stylesheets and Theme Tokens
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The CSS of `<rapi-doc>` and `<rapi-doc-mini>`, written as Lit `css` templates (one default export
|
|
6
|
+
per stylesheet file), plus the theming helpers that turn the `theme` attribute into CSS custom
|
|
7
|
+
properties, and string transforms that reuse the same CSS outside a shadow root.
|
|
8
|
+
|
|
9
|
+
- `theme-tokens.js` defines the custom properties (shadcn/ui names such as `--background`,
|
|
10
|
+
`--primary`, `--radius`; `--sidebar-*` for navigation; `--h1-font-size`… and `--heading-*`) and
|
|
11
|
+
switches them on host attributes (`theme`, `color-scheme`, `radius`, `scale`,
|
|
12
|
+
`nav-item-spacing`). The other stylesheets read those tokens and style component class names.
|
|
13
|
+
- `css-bundle.js` composes every stylesheet into `allStyles` and rewrites `:host` selectors for
|
|
14
|
+
Light DOM use.
|
|
15
|
+
|
|
16
|
+
Not owned here: which attributes exist (`src/rapidoc.ts`), the documented token list
|
|
17
|
+
(`docs/src/page-data/styling-data.yaml`), and the portal's own layout CSS.
|
|
18
|
+
|
|
19
|
+
Dependents: `src/rapidoc.ts`, `src/rapidoc-mini.ts` and the shadow-root components in
|
|
20
|
+
`src/components/` list the stylesheets they need in `static get styles()`; `packages/rapidoc-prerender/src/index.js`
|
|
21
|
+
re-exports `getBundledCss`, `getNavCss` and `getThemeTokensCss` as `@rapidoc/prerender`, and
|
|
22
|
+
`packages/rapidoc-prerender/src/render.js` uses `getBundledCss` for Declarative Shadow DOM output; `@rapidoc/portal`
|
|
23
|
+
(`src/pages/api-reference.astro`) uses `getNavCss` and `getThemeTokensCss`.
|
|
24
|
+
|
|
25
|
+
## Interface
|
|
26
|
+
|
|
27
|
+
`theme-tokens.js`:
|
|
28
|
+
|
|
29
|
+
- `DEFAULT_THEME` (`"ember"`), `THEMES` (preset name to `{ dark, light }` declaration strings),
|
|
30
|
+
`THEME_PRESETS` (its keys).
|
|
31
|
+
- `isValidHexColor`, `isValidTheme`, `isCustomColor`, `isLightColor`: theme value checks.
|
|
32
|
+
- `normalizeTheme(theme) -> { theme }`: presets lower-cased, hex kept, anything else becomes
|
|
33
|
+
`DEFAULT_THEME`.
|
|
34
|
+
- `applyCustomTheme(element, themeVal)`: sets inline `--primary` and `--primary-foreground` on
|
|
35
|
+
the host for a valid hex string, and removes them for anything else (presets, non-strings).
|
|
36
|
+
- default export: the token stylesheet (defaults, geometry switches, color schemes, presets).
|
|
37
|
+
|
|
38
|
+
`css-bundle.js`:
|
|
39
|
+
|
|
40
|
+
- `allStyles`: every stylesheet in cascade order, `ThemeTokens` first, `CustomStyles` last.
|
|
41
|
+
- `getBundledCss({ scope })` (also the default export): `allStyles` as one string; with a scope
|
|
42
|
+
other than `:host`, rewrites `:host(X)` to `<scope>X`, `:host` to `<scope>` and `::slotted(X)` to
|
|
43
|
+
`X`. Only `scope` is read, although the JSDoc also lists `theme` and `colorScheme`.
|
|
44
|
+
- `getNavCss(scope)`: `nav-styles.js` with the same rewrites, wrapped in a `<scope> { ... }` block
|
|
45
|
+
that also sets a compact `--sidebar-item-padding`, for the portal sidebar.
|
|
46
|
+
- `getThemeTokensCss(theme, scope = ":root")`: the token stylesheet with `theme` / `color-scheme`
|
|
47
|
+
host rules rewritten to `[data-theme='dark'|'light'|'system']` selectors (other presets keyed by
|
|
48
|
+
`[data-theme-preset='<name>']`).
|
|
49
|
+
|
|
50
|
+
Every other file default-exports one stylesheet named after what it styles (`nav-styles.js`,
|
|
51
|
+
`endpoint-styles.js`, `schema-styles.js`, ...); `custom-styles.js` is an empty slot kept last.
|
|
52
|
+
`flow-styles.js` styles the Arazzo diagram for `<rapi-flow>` and `@rapidoc/prerender` `getFlowCss()`; it
|
|
53
|
+
uses plain class selectors (no `:host`) and is not part of `allStyles`.
|
|
54
|
+
|
|
55
|
+
The custom properties themselves are not listed here: `theme-tokens.js` is the source of truth and
|
|
56
|
+
`docs/src/page-data/styling-data.yaml` is the documented list (`packages/rapidoc/AGENTS.md` §5,
|
|
57
|
+
rule 6).
|
|
58
|
+
|
|
59
|
+
## Invariants
|
|
60
|
+
|
|
61
|
+
- Every custom property RapiDoc reads is defined somewhere in `src`; public tokens are defined
|
|
62
|
+
with a default in `theme-tokens.js`. `tests/unit/css-variables.test.js` enforces this, forbids
|
|
63
|
+
the removed v11 names and requires the shadcn/ui core tokens.
|
|
64
|
+
- Tokens are read as plain `var(--x)`, without a fallback. The one exception is the local
|
|
65
|
+
`--method-color` in `endpoint-styles.js`, which `.m-endpoint` and the verb classes set and which
|
|
66
|
+
is read with a fallback to `--method-default` or `--primary`.
|
|
67
|
+
- Adding, renaming or removing a token also updates `docs/src/page-data/styling-data.yaml` and the
|
|
68
|
+
current migration guide.
|
|
69
|
+
- Dark is the default scheme: the dark block matches `:host`, `[color-scheme='dark']`,
|
|
70
|
+
`[color-scheme='system']` and no `color-scheme`; `system` turns light only under
|
|
71
|
+
`@media (prefers-color-scheme: light)`. `DEFAULT_THEME` also applies with no `theme` attribute.
|
|
72
|
+
- `--primary` and `--primary-foreground` have no `:host` default; they come from a preset rule or
|
|
73
|
+
from `applyCustomTheme`. `--ring` follows `--primary`.
|
|
74
|
+
- The `:host(...)` selector shapes written by `presetCss` and the color-scheme block are exactly
|
|
75
|
+
what `getThemeTokensCss` and `getNavCss` match with regular expressions; change both together.
|
|
76
|
+
`getThemeTokensCss` rewrites only `theme` / `color-scheme` rules, so the `radius`, `scale` and
|
|
77
|
+
`nav-item-spacing` rules come out as unusable `<scope>([...])` selectors.
|
|
78
|
+
- `ThemeTokens` is first in `allStyles` and in both components' `static get styles()`.
|
|
79
|
+
`CustomStyles` is last in `allStyles` and in `rapidoc.ts`; `rapidoc-mini.ts` does not load it
|
|
80
|
+
and ends with its own inline `css` block.
|
|
81
|
+
- Lit templates are not reformatted by oxfmt; keep the CSS text inside `css` templates as written.
|
|
82
|
+
|
|
83
|
+
## Side Effects
|
|
84
|
+
|
|
85
|
+
- Each stylesheet file creates a Lit `CSSResult` at import; `theme-tokens.js` also builds the
|
|
86
|
+
preset CSS from `THEMES` once.
|
|
87
|
+
- `applyCustomTheme` mutates the element's inline style. Everything else is pure; nothing touches
|
|
88
|
+
the network or disk.
|
|
89
|
+
|
|
90
|
+
## Verification
|
|
91
|
+
|
|
92
|
+
- `npm run test:unit` from the repo root. Suites: `tests/unit/css-variables.test.js` (defined
|
|
93
|
+
tokens, no undefined reads), `tests/unit/theme-validation.test.js` (`theme-tokens.js` helpers,
|
|
94
|
+
`nav-styles.js`), `tests/unit/static-renderer.test.js` (`getBundledCss`, `getNavCss`,
|
|
95
|
+
`getThemeTokensCss` output).
|
|
96
|
+
- After editing, `npm run lint`, `npm run format-fix` and `npm run build:rapidoc`.
|
package/src/styles/css-bundle.js
CHANGED
|
@@ -51,7 +51,7 @@ export function getBundledCss(options = {}) {
|
|
|
51
51
|
// If rendering in Light DOM without Shadow DOM, adapt :host selectors to the container
|
|
52
52
|
if (scope && scope !== ":host") {
|
|
53
53
|
combinedCss = combinedCss
|
|
54
|
-
.replace(/:host\(([^)]+)\)/g, `${scope}$1`)
|
|
54
|
+
.replace(/:host\(((?:[^()]|\([^()]*\))+)\)/g, `${scope}$1`)
|
|
55
55
|
.replace(/:host\b/g, scope)
|
|
56
56
|
.replace(/::slotted\(([^)]+)\)/g, `$1`);
|
|
57
57
|
}
|
|
@@ -73,7 +73,7 @@ export function getNavCss(scope = "") {
|
|
|
73
73
|
/:host\(\[color-scheme='([^']+)'\]\)/g,
|
|
74
74
|
`[data-theme='$1'] ${scope}, ${scope}[color-scheme='$1']`,
|
|
75
75
|
)
|
|
76
|
-
.replace(/:host\(([^)]+)\)/g, `${scope}$1`)
|
|
76
|
+
.replace(/:host\(((?:[^()]|\([^()]*\))+)\)/g, `${scope}$1`)
|
|
77
77
|
.replace(/:host\b/g, scope)
|
|
78
78
|
.replace(/::slotted\(([^)]+)\)/g, `$1`)
|
|
79
79
|
.replace(/^\s*\.nav-bar\s*\{/gm, "&.nav-bar, & {");
|
|
@@ -51,14 +51,18 @@ export default css`
|
|
|
51
51
|
gap: 4px;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/* a block, so it fills the callback box less its indent; width: 100% plus the margin overflowed the box */
|
|
54
55
|
.callback-operation-list {
|
|
55
56
|
margin-left: 16px;
|
|
56
|
-
width: 100%;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.callback-operation-item {
|
|
60
60
|
margin-top: 12px;
|
|
61
61
|
}
|
|
62
|
+
/* a callback sits in the request column, too narrow to put its request and response side by side in row layout */
|
|
63
|
+
.callback-operation-item > .expanded-req-resp-container {
|
|
64
|
+
flex-direction: column !important;
|
|
65
|
+
}
|
|
62
66
|
|
|
63
67
|
.callback-operation-header {
|
|
64
68
|
display: flex;
|
|
@@ -257,6 +261,7 @@ export default css`
|
|
|
257
261
|
|
|
258
262
|
.endpoint-head .method {
|
|
259
263
|
padding: 2px 5px;
|
|
264
|
+
box-sizing: border-box;
|
|
260
265
|
vertical-align: middle;
|
|
261
266
|
font-size: var(--font-size-small);
|
|
262
267
|
height: calc(var(--font-size-small) + 16px);
|