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.
Files changed (77) hide show
  1. package/README.md +127 -25
  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 +541 -1309
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +21 -18
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +43 -0
  10. package/src/components/api-response.ts +9 -4
  11. package/src/components/schema-base-element.ts +18 -4
  12. package/src/components/schema-table.ts +58 -13
  13. package/src/components/schema-tree.ts +7 -0
  14. package/src/components/tag-input.ts +5 -9
  15. package/src/flow.js +3 -0
  16. package/src/rapi-flow.ts +1178 -0
  17. package/src/rapidoc-mini.ts +27 -6
  18. package/src/rapidoc.ts +108 -62
  19. package/src/styles/README.md +96 -0
  20. package/src/styles/css-bundle.js +2 -2
  21. package/src/styles/endpoint-styles.js +6 -1
  22. package/src/styles/flow-styles.js +248 -0
  23. package/src/styles/nav-styles.js +39 -15
  24. package/src/styles/schema-styles.js +13 -5
  25. package/src/styles/theme-tokens.js +2 -2
  26. package/src/templates/README.md +89 -0
  27. package/src/templates/api-response-template.js +14 -10
  28. package/src/templates/callback-template.js +1 -1
  29. package/src/templates/downloads-template.js +12 -27
  30. package/src/templates/endpoint-template.js +15 -24
  31. package/src/templates/expanded-endpoint-template.js +8 -4
  32. package/src/templates/focused-endpoint-template.js +4 -3
  33. package/src/templates/navbar-template.js +20 -11
  34. package/src/templates/request-body-template.js +29 -7
  35. package/src/templates/request-params-template.js +15 -4
  36. package/src/templates/security-scheme-template.js +11 -2
  37. package/src/templates/server-template.js +3 -2
  38. package/src/utils/README.md +146 -0
  39. package/src/utils/arazzo/README.md +119 -0
  40. package/src/utils/arazzo/components.js +15 -0
  41. package/src/utils/arazzo/graph.js +269 -0
  42. package/src/utils/arazzo/index.js +52 -0
  43. package/src/utils/arazzo/operations.js +106 -0
  44. package/src/utils/arazzo/parse.js +196 -0
  45. package/src/utils/arazzo/refs.js +40 -0
  46. package/src/utils/arazzo/svg.js +707 -0
  47. package/src/utils/curl-utils.js +5 -2
  48. package/src/utils/highlighter.js +3 -0
  49. package/src/utils/mcp/README.md +155 -0
  50. package/src/utils/mcp/common.js +45 -346
  51. package/src/utils/mcp/manifest.js +382 -0
  52. package/src/utils/mcp/project/server.ts +53 -0
  53. package/src/utils/mcp/project.js +88 -0
  54. package/src/utils/mcp/runtime.js +289 -0
  55. package/src/utils/mcp/schema.js +98 -0
  56. package/src/utils/mcp/workflow-runtime.js +374 -0
  57. package/src/utils/mcp/workflows.js +240 -0
  58. package/src/utils/mcp/zip.js +102 -0
  59. package/src/utils/mcp-generator.js +29 -125
  60. package/src/utils/mock-interceptor.js +134 -85
  61. package/src/utils/request-utils.js +61 -0
  62. package/src/utils/schema-utils.js +33 -6
  63. package/src/utils/slugger.js +5 -7
  64. package/src/utils/spec-parser.js +190 -164
  65. package/src/utils/url-utils.js +36 -1
  66. package/dist/rapidoc-static-min.js +0 -13542
  67. package/src/static/index.d.ts +0 -149
  68. package/src/static/index.js +0 -62
  69. package/src/static/navigation.js +0 -497
  70. package/src/static/render.js +0 -490
  71. package/src/static/serializer.js +0 -216
  72. package/src/utils/mcp/generators/csharp.js +0 -242
  73. package/src/utils/mcp/generators/go.js +0 -298
  74. package/src/utils/mcp/generators/java.js +0 -330
  75. package/src/utils/mcp/generators/python.js +0 -248
  76. package/src/utils/mcp/generators/rust.js +0 -281
  77. package/src/utils/mcp/generators/typescript.js +0 -290
@@ -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
- @property({ type: String, attribute: "mock-server-status-code" }) mockServerStatusCode = "200";
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
- if (this.resolvedSpec) {
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
- extractMcpOperations,
44
+ buildMcpManifest,
45
45
  estimateTokenCost,
46
- parseMcpTargets,
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
- @property({ type: String, attribute: "mock-server-status-code" }) mockServerStatusCode = "200";
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
- @property({ type: String, attribute: "mcp" }) mcp = "true";
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
- window.addEventListener(
476
- "hashchange",
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
- // return render(mainBodyTemplate(this), this.shadowRoot, { eventContext: this });
599
- const cssLinkEl = document.querySelector(`link[href*="${this.cssFile}"]`);
600
- // adding custom style for RapiDoc
601
- if (cssLinkEl) {
602
- this.shadowRoot.appendChild(cssLinkEl.cloneNode());
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" ? "overview" : this.resolvedSpec.tags[0]?.paths[0]?.elementId;
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
- // Getter for normalized active MCP targets
1725
- get mcpTargets() {
1726
- return parseMcpTargets(this.mcp);
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
- // Public Method - Generates MCP Server code and configuration in memory
1730
- generateMcpServer(target, options = {}) {
1731
- const spec = this.resolvedSpec || this.openApiSpec;
1732
- if (!spec) return null;
1733
- const defaultTarget = this.mcpTargets[0] || "typescript";
1734
- return mcpServerGenerator(spec, {
1735
- target: target || defaultTarget,
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 - Triggers browser download of generated MCP server file
1742
- downloadMcpServer(target, options = {}) {
1743
- const defaultTarget = this.mcpTargets[0] || "typescript";
1744
- const result = this.generateMcpServer(target || defaultTarget, options);
1745
- if (!result || !result.serverCode) return;
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 = filename;
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
- const extracted = extractMcpOperations(spec, options);
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`.
@@ -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);