@veluai/velu 0.2.24 → 0.2.27

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 (114) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +44 -44
  3. package/package.json +64 -64
  4. package/runtime/velu-ui/base.css +320 -320
  5. package/runtime/velu-ui/components/Accordion.jsx +64 -64
  6. package/runtime/velu-ui/components/ApiClient.jsx +207 -207
  7. package/runtime/velu-ui/components/ApiField.jsx +87 -87
  8. package/runtime/velu-ui/components/ApiPath.jsx +63 -63
  9. package/runtime/velu-ui/components/ApiReferencePage.jsx +384 -384
  10. package/runtime/velu-ui/components/ApiSamples.jsx +36 -36
  11. package/runtime/velu-ui/components/ApiSidebar.jsx +125 -125
  12. package/runtime/velu-ui/components/AskBar.jsx +71 -71
  13. package/runtime/velu-ui/components/Callout.jsx +114 -114
  14. package/runtime/velu-ui/components/Card.jsx +131 -131
  15. package/runtime/velu-ui/components/ChangelogFilters.jsx +75 -75
  16. package/runtime/velu-ui/components/Chatbot.jsx +972 -972
  17. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  18. package/runtime/velu-ui/components/Columns.jsx +56 -56
  19. package/runtime/velu-ui/components/ContextMenu.jsx +327 -327
  20. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  21. package/runtime/velu-ui/components/Field.jsx +81 -81
  22. package/runtime/velu-ui/components/Image.jsx +163 -163
  23. package/runtime/velu-ui/components/Logo.jsx +31 -31
  24. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  25. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  26. package/runtime/velu-ui/components/NotFound.jsx +63 -63
  27. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  28. package/runtime/velu-ui/components/PageFooter.jsx +145 -145
  29. package/runtime/velu-ui/components/PageHeader.jsx +422 -422
  30. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  31. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  32. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  33. package/runtime/velu-ui/components/Search.jsx +460 -460
  34. package/runtime/velu-ui/components/Sidebar.jsx +246 -246
  35. package/runtime/velu-ui/components/SocialLinks.jsx +90 -90
  36. package/runtime/velu-ui/components/Steps.jsx +65 -65
  37. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  38. package/runtime/velu-ui/components/Toc.jsx +537 -537
  39. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  40. package/runtime/velu-ui/components/Tree.jsx +87 -87
  41. package/runtime/velu-ui/components/TryItBar.jsx +102 -102
  42. package/runtime/velu-ui/components/Update.jsx +92 -92
  43. package/runtime/velu-ui/components/accordion.css +92 -92
  44. package/runtime/velu-ui/components/api-page.css +208 -208
  45. package/runtime/velu-ui/components/api.css +635 -635
  46. package/runtime/velu-ui/components/ask-bar.css +103 -94
  47. package/runtime/velu-ui/components/card.css +105 -105
  48. package/runtime/velu-ui/components/changelog-filters.css +43 -43
  49. package/runtime/velu-ui/components/chatbot.css +687 -687
  50. package/runtime/velu-ui/components/code-block.css +263 -263
  51. package/runtime/velu-ui/components/context-menu.css +199 -199
  52. package/runtime/velu-ui/components/docs-layout.css +822 -822
  53. package/runtime/velu-ui/components/field.css +82 -82
  54. package/runtime/velu-ui/components/image.css +237 -237
  55. package/runtime/velu-ui/components/nav-select.css +157 -157
  56. package/runtime/velu-ui/components/not-found.css +94 -94
  57. package/runtime/velu-ui/components/page-feedback.css +261 -261
  58. package/runtime/velu-ui/components/page-footer.css +130 -130
  59. package/runtime/velu-ui/components/page-header.css +558 -558
  60. package/runtime/velu-ui/components/page-nav.css +50 -50
  61. package/runtime/velu-ui/components/powered-by.css +92 -92
  62. package/runtime/velu-ui/components/prompt.css +99 -99
  63. package/runtime/velu-ui/components/search.css +307 -307
  64. package/runtime/velu-ui/components/sidebar.css +205 -205
  65. package/runtime/velu-ui/components/steps.css +77 -77
  66. package/runtime/velu-ui/components/theme-toggle.css +102 -102
  67. package/runtime/velu-ui/components/toc-bar.css +234 -234
  68. package/runtime/velu-ui/components/tree.css +49 -49
  69. package/runtime/velu-ui/components/update.css +111 -111
  70. package/runtime/velu-ui/index.js +58 -58
  71. package/runtime/velu-ui/lib/api-send.js +118 -118
  72. package/runtime/velu-ui/lib/brand-icons.jsx +103 -103
  73. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  74. package/runtime/velu-ui/lib/copyText.js +64 -64
  75. package/runtime/velu-ui/lib/docs-assistant.js +273 -273
  76. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  77. package/runtime/velu-ui/lib/page-feedback.js +72 -72
  78. package/runtime/velu-ui/lib/pagefind.js +116 -116
  79. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  80. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  81. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  82. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  83. package/runtime/velu-ui/mdx-components.jsx +107 -107
  84. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  85. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  86. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  87. package/runtime/velu-ui/primitives/stack.css +3 -3
  88. package/runtime/velu-ui/primitives/switcher.css +25 -25
  89. package/runtime/velu-ui/styles.css +48 -48
  90. package/runtime/velu-ui/tokens.css +4 -4
  91. package/schema/velu.schema.json +419 -419
  92. package/src/lib/extract-mdx-error.js +170 -170
  93. package/src/lib/issues.js +159 -159
  94. package/src/lib/known-components.js +35 -35
  95. package/src/navigation.js +459 -459
  96. package/src/runtime/App.jsx +1754 -1754
  97. package/src/runtime/ErrorBoundary.jsx +54 -54
  98. package/src/runtime/client-entry.jsx +27 -27
  99. package/src/runtime/server-entry.jsx +24 -24
  100. package/src/template.html +48 -48
  101. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  102. package/templates/starter/ai-tools/cursor.mdx +17 -17
  103. package/templates/starter/api-reference/introduction.mdx +43 -43
  104. package/templates/starter/development.mdx +19 -19
  105. package/templates/starter/essentials/code.mdx +29 -29
  106. package/templates/starter/essentials/images.mdx +29 -29
  107. package/templates/starter/essentials/markdown.mdx +25 -25
  108. package/templates/starter/essentials/navigation.mdx +39 -39
  109. package/templates/starter/essentials/settings.mdx +30 -30
  110. package/templates/starter/favicon.svg +6 -6
  111. package/templates/starter/index.mdx +31 -31
  112. package/templates/starter/openapi.json +160 -160
  113. package/templates/starter/quickstart.mdx +31 -31
  114. package/templates/starter/velu.json +40 -40
@@ -1,111 +1,111 @@
1
- /* Update — changelog entries. Two-column rows: a sticky left rail with the
2
- date/label pill, and the entry body on the right. All values are tokens;
3
- light/dark via [data-theme]. */
4
-
5
- .velu-update {
6
- display: grid;
7
- grid-template-columns: minmax(8rem, 11rem) minmax(0, 1fr);
8
- column-gap: var(--s1);
9
- row-gap: var(--s-1);
10
- align-items: start;
11
- margin-block: var(--s1) var(--s3);
12
- /* Clear the fixed header when an entry is jumped to via its anchor. */
13
- scroll-margin-top: calc(var(--velu-header-height) + var(--s0));
14
- }
15
-
16
- .velu-update__meta {
17
- position: sticky;
18
- /* Stick just below the fixed header instead of sliding behind it. */
19
- top: calc(var(--velu-header-height) + var(--s0));
20
- align-self: start;
21
- display: flex;
22
- flex-direction: column;
23
- gap: var(--s-2);
24
- /* Leave room for the absolute anchor icon at left:0, plus a gap to the pill. */
25
- padding-left: var(--s1);
26
- }
27
-
28
- .velu-update__anchor {
29
- position: absolute;
30
- left: 0;
31
- top: 0.3rem;
32
- display: inline-flex;
33
- /* Hidden until the entry is hovered/focused (like heading anchors). */
34
- color: var(--muted-color);
35
- opacity: 0;
36
- transition: opacity 0.15s ease, color 0.15s ease;
37
- }
38
-
39
- .velu-update:hover .velu-update__anchor,
40
- .velu-update:focus-within .velu-update__anchor {
41
- opacity: 1;
42
- color: var(--accent-color);
43
- }
44
-
45
- .velu-update__label {
46
- width: fit-content;
47
- border-radius: 999px;
48
- padding: 0.2rem 0.6rem;
49
- font-size: var(--f-h6);
50
- font-weight: var(--weight-semibold);
51
- line-height: 1.2;
52
- color: var(--page-bg);
53
- background: var(--accent-color);
54
- text-decoration: none;
55
- }
56
-
57
- .velu-update__label:hover {
58
- background: color-mix(in srgb, var(--accent-color) 85%, black);
59
- }
60
-
61
- .velu-update__description {
62
- color: var(--muted-color);
63
- font-size: var(--f-h6);
64
- line-height: var(--lh-h6);
65
- }
66
-
67
- .velu-update__tags {
68
- display: flex;
69
- flex-wrap: wrap;
70
- gap: var(--s-2);
71
- }
72
-
73
- .velu-update__tag {
74
- font-size: var(--f-small, 0.72rem);
75
- color: var(--muted-color);
76
- border: var(--border-width) solid var(--border-color);
77
- border-radius: 999px;
78
- padding: 0.1rem 0.45rem;
79
- }
80
-
81
- .velu-update__content {
82
- min-width: 0;
83
- }
84
-
85
- .velu-update__content > :first-child {
86
- margin-top: 0;
87
- }
88
-
89
- .velu-update__content > :last-child {
90
- margin-bottom: 0;
91
- }
92
-
93
- /* Hidden by a tag filter (DOM-driven). */
94
- .velu-update[hidden] {
95
- display: none !important;
96
- }
97
-
98
- @media (max-width: 768px) {
99
- .velu-update {
100
- grid-template-columns: minmax(0, 1fr);
101
- }
102
-
103
- .velu-update__meta {
104
- position: static;
105
- padding-left: var(--s2);
106
- }
107
-
108
- .velu-update__anchor {
109
- left: 0;
110
- }
111
- }
1
+ /* Update — changelog entries. Two-column rows: a sticky left rail with the
2
+ date/label pill, and the entry body on the right. All values are tokens;
3
+ light/dark via [data-theme]. */
4
+
5
+ .velu-update {
6
+ display: grid;
7
+ grid-template-columns: minmax(8rem, 11rem) minmax(0, 1fr);
8
+ column-gap: var(--s1);
9
+ row-gap: var(--s-1);
10
+ align-items: start;
11
+ margin-block: var(--s1) var(--s3);
12
+ /* Clear the fixed header when an entry is jumped to via its anchor. */
13
+ scroll-margin-top: calc(var(--velu-header-height) + var(--s0));
14
+ }
15
+
16
+ .velu-update__meta {
17
+ position: sticky;
18
+ /* Stick just below the fixed header instead of sliding behind it. */
19
+ top: calc(var(--velu-header-height) + var(--s0));
20
+ align-self: start;
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: var(--s-2);
24
+ /* Leave room for the absolute anchor icon at left:0, plus a gap to the pill. */
25
+ padding-left: var(--s1);
26
+ }
27
+
28
+ .velu-update__anchor {
29
+ position: absolute;
30
+ left: 0;
31
+ top: 0.3rem;
32
+ display: inline-flex;
33
+ /* Hidden until the entry is hovered/focused (like heading anchors). */
34
+ color: var(--muted-color);
35
+ opacity: 0;
36
+ transition: opacity 0.15s ease, color 0.15s ease;
37
+ }
38
+
39
+ .velu-update:hover .velu-update__anchor,
40
+ .velu-update:focus-within .velu-update__anchor {
41
+ opacity: 1;
42
+ color: var(--accent-color);
43
+ }
44
+
45
+ .velu-update__label {
46
+ width: fit-content;
47
+ border-radius: 999px;
48
+ padding: 0.2rem 0.6rem;
49
+ font-size: var(--f-h6);
50
+ font-weight: var(--weight-semibold);
51
+ line-height: 1.2;
52
+ color: var(--page-bg);
53
+ background: var(--accent-color);
54
+ text-decoration: none;
55
+ }
56
+
57
+ .velu-update__label:hover {
58
+ background: color-mix(in srgb, var(--accent-color) 85%, black);
59
+ }
60
+
61
+ .velu-update__description {
62
+ color: var(--muted-color);
63
+ font-size: var(--f-h6);
64
+ line-height: var(--lh-h6);
65
+ }
66
+
67
+ .velu-update__tags {
68
+ display: flex;
69
+ flex-wrap: wrap;
70
+ gap: var(--s-2);
71
+ }
72
+
73
+ .velu-update__tag {
74
+ font-size: var(--f-small, 0.72rem);
75
+ color: var(--muted-color);
76
+ border: var(--border-width) solid var(--border-color);
77
+ border-radius: 999px;
78
+ padding: 0.1rem 0.45rem;
79
+ }
80
+
81
+ .velu-update__content {
82
+ min-width: 0;
83
+ }
84
+
85
+ .velu-update__content > :first-child {
86
+ margin-top: 0;
87
+ }
88
+
89
+ .velu-update__content > :last-child {
90
+ margin-bottom: 0;
91
+ }
92
+
93
+ /* Hidden by a tag filter (DOM-driven). */
94
+ .velu-update[hidden] {
95
+ display: none !important;
96
+ }
97
+
98
+ @media (max-width: 768px) {
99
+ .velu-update {
100
+ grid-template-columns: minmax(0, 1fr);
101
+ }
102
+
103
+ .velu-update__meta {
104
+ position: static;
105
+ padding-left: var(--s2);
106
+ }
107
+
108
+ .velu-update__anchor {
109
+ left: 0;
110
+ }
111
+ }
@@ -1,58 +1,58 @@
1
- export { default as Stack } from './primitives/Stack.jsx';
2
- export { default as Switcher } from './primitives/Switcher.jsx';
3
- export { default as Cluster } from './primitives/Cluster.jsx';
4
- export { default as ThemeToggle } from './components/ThemeToggle.jsx';
5
- export { default as Sidebar } from './components/Sidebar.jsx';
6
- export { default as NavSelect } from './components/NavSelect.jsx';
7
- export { default as ContextMenu } from './components/ContextMenu.jsx';
8
- export { default as Toc } from './components/Toc.jsx';
9
- export { default as TocBar } from './components/TocBar.jsx';
10
- export { default as Callout } from './components/Callout.jsx';
11
- export { default as ErrorCard } from './components/ErrorCard.jsx';
12
- export { default as Logo } from './components/Logo.jsx';
13
- export { default as SocialLinks } from './components/SocialLinks.jsx';
14
- export {
15
- default as Accordion,
16
- AccordionGroup,
17
- } from './components/Accordion.jsx';
18
- export { default as Card, CardGroup } from './components/Card.jsx';
19
- export { default as Columns } from './components/Columns.jsx';
20
- export { default as Field } from './components/Field.jsx';
21
- export { default as Prompt } from './components/Prompt.jsx';
22
- export { default as Steps, Step } from './components/Steps.jsx';
23
- export { default as Tree, Folder, File } from './components/Tree.jsx';
24
- export { default as Update } from './components/Update.jsx';
25
- export { default as ChangelogFilters } from './components/ChangelogFilters.jsx';
26
- export { default as MethodBadge } from './components/MethodBadge.jsx';
27
- export { default as ApiPath } from './components/ApiPath.jsx';
28
- export { default as TryItBar } from './components/TryItBar.jsx';
29
- export { default as ApiField } from './components/ApiField.jsx';
30
- export { default as ApiClient } from './components/ApiClient.jsx';
31
- export { default as ApiSidebar } from './components/ApiSidebar.jsx';
32
- export { default as ApiReferencePage } from './components/ApiReferencePage.jsx';
33
- export { default as ApiSamples } from './components/ApiSamples.jsx';
34
- export { setApiProxyTransport } from './lib/api-send.js';
35
- export { default as NotFound } from './components/NotFound.jsx';
36
- export { default as AskBar } from './components/AskBar.jsx';
37
- export { default as Chatbot } from './components/Chatbot.jsx';
38
- export { default as PageFeedback } from './components/PageFeedback.jsx';
39
- export { default as PageNav } from './components/PageNav.jsx';
40
- export { default as PageFooter } from './components/PageFooter.jsx';
41
- export { default as PoweredBy } from './components/PoweredBy.jsx';
42
- export { default as resolveIcon } from './lib/resolveIcon.jsx';
43
- export {
44
- default as PageHeader,
45
- VeluMark,
46
- } from './components/PageHeader.jsx';
47
- export { default as Search } from './components/Search.jsx';
48
- export { default as pagefindSearch } from './lib/pagefind.js';
49
- export { createDocsAssistant } from './lib/docs-assistant.js';
50
- export { createPageFeedback } from './lib/page-feedback.js';
51
- export { default as Image } from './components/Image.jsx';
52
- export {
53
- default as CodeBlock,
54
- CodeGroup,
55
- } from './components/CodeBlock.jsx';
56
- export {
57
- default as defaultMdxComponents,
58
- } from './mdx-components.jsx';
1
+ export { default as Stack } from './primitives/Stack.jsx';
2
+ export { default as Switcher } from './primitives/Switcher.jsx';
3
+ export { default as Cluster } from './primitives/Cluster.jsx';
4
+ export { default as ThemeToggle } from './components/ThemeToggle.jsx';
5
+ export { default as Sidebar } from './components/Sidebar.jsx';
6
+ export { default as NavSelect } from './components/NavSelect.jsx';
7
+ export { default as ContextMenu } from './components/ContextMenu.jsx';
8
+ export { default as Toc } from './components/Toc.jsx';
9
+ export { default as TocBar } from './components/TocBar.jsx';
10
+ export { default as Callout } from './components/Callout.jsx';
11
+ export { default as ErrorCard } from './components/ErrorCard.jsx';
12
+ export { default as Logo } from './components/Logo.jsx';
13
+ export { default as SocialLinks } from './components/SocialLinks.jsx';
14
+ export {
15
+ default as Accordion,
16
+ AccordionGroup,
17
+ } from './components/Accordion.jsx';
18
+ export { default as Card, CardGroup } from './components/Card.jsx';
19
+ export { default as Columns } from './components/Columns.jsx';
20
+ export { default as Field } from './components/Field.jsx';
21
+ export { default as Prompt } from './components/Prompt.jsx';
22
+ export { default as Steps, Step } from './components/Steps.jsx';
23
+ export { default as Tree, Folder, File } from './components/Tree.jsx';
24
+ export { default as Update } from './components/Update.jsx';
25
+ export { default as ChangelogFilters } from './components/ChangelogFilters.jsx';
26
+ export { default as MethodBadge } from './components/MethodBadge.jsx';
27
+ export { default as ApiPath } from './components/ApiPath.jsx';
28
+ export { default as TryItBar } from './components/TryItBar.jsx';
29
+ export { default as ApiField } from './components/ApiField.jsx';
30
+ export { default as ApiClient } from './components/ApiClient.jsx';
31
+ export { default as ApiSidebar } from './components/ApiSidebar.jsx';
32
+ export { default as ApiReferencePage } from './components/ApiReferencePage.jsx';
33
+ export { default as ApiSamples } from './components/ApiSamples.jsx';
34
+ export { setApiProxyTransport } from './lib/api-send.js';
35
+ export { default as NotFound } from './components/NotFound.jsx';
36
+ export { default as AskBar } from './components/AskBar.jsx';
37
+ export { default as Chatbot } from './components/Chatbot.jsx';
38
+ export { default as PageFeedback } from './components/PageFeedback.jsx';
39
+ export { default as PageNav } from './components/PageNav.jsx';
40
+ export { default as PageFooter } from './components/PageFooter.jsx';
41
+ export { default as PoweredBy } from './components/PoweredBy.jsx';
42
+ export { default as resolveIcon } from './lib/resolveIcon.jsx';
43
+ export {
44
+ default as PageHeader,
45
+ VeluMark,
46
+ } from './components/PageHeader.jsx';
47
+ export { default as Search } from './components/Search.jsx';
48
+ export { default as pagefindSearch } from './lib/pagefind.js';
49
+ export { createDocsAssistant } from './lib/docs-assistant.js';
50
+ export { createPageFeedback } from './lib/page-feedback.js';
51
+ export { default as Image } from './components/Image.jsx';
52
+ export {
53
+ default as CodeBlock,
54
+ CodeGroup,
55
+ } from './components/CodeBlock.jsx';
56
+ export {
57
+ default as defaultMdxComponents,
58
+ } from './mdx-components.jsx';
@@ -1,118 +1,118 @@
1
- // Build and send a "Try It" request from the playground field values.
2
- //
3
- // By default the request goes through the dev server's proxy
4
- // (/@velu-api-proxy) so it isn't blocked by CORS — the proxy forwards it
5
- // server-side and returns { status, statusText, headers, body }. Set the
6
- // project's api.playground.proxy to false to send straight from the browser.
7
-
8
- const PROXY_URL = '/@velu-api-proxy';
9
- const BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
10
-
11
- // Pluggable proxy transport. The dev server and the built site both proxy via
12
- // the same-origin HTTP endpoint (PROXY_URL). The instant preview, however,
13
- // runs in a sandboxed iframe with no server behind that URL, so it registers a
14
- // transport here that forwards the request to the editor host over postMessage
15
- // (which proxies it server-side and replies). The real site never registers
16
- // one, so it keeps using PROXY_URL. A transport receives the built request
17
- // ({ method, url, headers, body }) and resolves the SAME shape the HTTP proxy
18
- // returns: { status, statusText, headers, body } | { error }.
19
- let proxyTransport = null;
20
- export function setApiProxyTransport(fn) {
21
- proxyTransport = typeof fn === 'function' ? fn : null;
22
- }
23
-
24
- // Compose the concrete request (url + headers + body) from the operation and
25
- // the user-entered values: { auth, path, query, header } maps + bodyText.
26
- function buildRequest({ operation, server, values, bodyText }) {
27
- const v = values || {};
28
- let path = operation.path.replace(/\{([^}]+)\}/g, (m, name) => {
29
- const val = v.path?.[name];
30
- return val ? encodeURIComponent(val) : m; // leave {name} if unfilled
31
- });
32
-
33
- const query = Object.entries(v.query || {}).filter(([, val]) => val !== '' && val != null);
34
- const qs = query.map(([k, val]) => `${encodeURIComponent(k)}=${encodeURIComponent(val)}`).join('&');
35
- const base = (server || '').replace(/\/+$/, '');
36
- const url = `${base}${path}${qs ? `?${qs}` : ''}`;
37
-
38
- const headers = {};
39
- for (const a of operation.auth || []) {
40
- const val = v.auth?.[a.name];
41
- if (val) headers[a.name] = a.prefix ? `${a.prefix} ${val}` : val;
42
- }
43
- for (const [k, val] of Object.entries(v.header || {})) {
44
- if (val !== '' && val != null) headers[k] = val;
45
- }
46
-
47
- let body = null;
48
- if (operation.body && BODY_METHODS.has(operation.method)) {
49
- body = bodyText || '';
50
- if (body && !headers['Content-Type']) headers['Content-Type'] = operation.body.contentType || 'application/json';
51
- }
52
-
53
- return { method: operation.method, url, headers, body };
54
- }
55
-
56
- async function readBody(res) {
57
- const text = await res.text();
58
- const ct = res.headers.get('content-type') || '';
59
- if (/json/i.test(ct)) {
60
- try {
61
- return JSON.parse(text);
62
- } catch {
63
- /* fall through to raw text */
64
- }
65
- }
66
- return text;
67
- }
68
-
69
- export async function sendApiRequest({ operation, server, proxy = true, values, bodyText }) {
70
- const req = buildRequest({ operation, server, values, bodyText });
71
-
72
- // Proxied send. A registered transport (the instant preview's host bridge)
73
- // takes precedence over the same-origin HTTP proxy — same return shape.
74
- if (proxy && proxyTransport) {
75
- const payload = await proxyTransport(req);
76
- if (payload && payload.error) return { error: payload.error };
77
- return {
78
- status: payload?.status,
79
- statusText: payload?.statusText || '',
80
- headers: payload?.headers || {},
81
- body: payload?.body,
82
- };
83
- }
84
-
85
- if (proxy) {
86
- const res = await fetch(PROXY_URL, {
87
- method: 'POST',
88
- headers: { 'Content-Type': 'application/json' },
89
- body: JSON.stringify(req),
90
- });
91
- // The proxy returns the upstream result as JSON.
92
- const payload = await res.json().catch(() => ({}));
93
- if (payload && payload.error) return { error: payload.error };
94
- return {
95
- status: payload.status,
96
- statusText: payload.statusText || '',
97
- headers: payload.headers || {},
98
- body: payload.body,
99
- };
100
- }
101
-
102
- // Direct browser fetch (subject to CORS).
103
- const res = await fetch(req.url, {
104
- method: req.method,
105
- headers: req.headers,
106
- body: req.body || undefined,
107
- });
108
- const headers = {};
109
- res.headers.forEach((val, key) => {
110
- headers[key] = val;
111
- });
112
- return {
113
- status: res.status,
114
- statusText: res.statusText,
115
- headers,
116
- body: await readBody(res),
117
- };
118
- }
1
+ // Build and send a "Try It" request from the playground field values.
2
+ //
3
+ // By default the request goes through the dev server's proxy
4
+ // (/@velu-api-proxy) so it isn't blocked by CORS — the proxy forwards it
5
+ // server-side and returns { status, statusText, headers, body }. Set the
6
+ // project's api.playground.proxy to false to send straight from the browser.
7
+
8
+ const PROXY_URL = '/@velu-api-proxy';
9
+ const BODY_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
10
+
11
+ // Pluggable proxy transport. The dev server and the built site both proxy via
12
+ // the same-origin HTTP endpoint (PROXY_URL). The instant preview, however,
13
+ // runs in a sandboxed iframe with no server behind that URL, so it registers a
14
+ // transport here that forwards the request to the editor host over postMessage
15
+ // (which proxies it server-side and replies). The real site never registers
16
+ // one, so it keeps using PROXY_URL. A transport receives the built request
17
+ // ({ method, url, headers, body }) and resolves the SAME shape the HTTP proxy
18
+ // returns: { status, statusText, headers, body } | { error }.
19
+ let proxyTransport = null;
20
+ export function setApiProxyTransport(fn) {
21
+ proxyTransport = typeof fn === 'function' ? fn : null;
22
+ }
23
+
24
+ // Compose the concrete request (url + headers + body) from the operation and
25
+ // the user-entered values: { auth, path, query, header } maps + bodyText.
26
+ function buildRequest({ operation, server, values, bodyText }) {
27
+ const v = values || {};
28
+ let path = operation.path.replace(/\{([^}]+)\}/g, (m, name) => {
29
+ const val = v.path?.[name];
30
+ return val ? encodeURIComponent(val) : m; // leave {name} if unfilled
31
+ });
32
+
33
+ const query = Object.entries(v.query || {}).filter(([, val]) => val !== '' && val != null);
34
+ const qs = query.map(([k, val]) => `${encodeURIComponent(k)}=${encodeURIComponent(val)}`).join('&');
35
+ const base = (server || '').replace(/\/+$/, '');
36
+ const url = `${base}${path}${qs ? `?${qs}` : ''}`;
37
+
38
+ const headers = {};
39
+ for (const a of operation.auth || []) {
40
+ const val = v.auth?.[a.name];
41
+ if (val) headers[a.name] = a.prefix ? `${a.prefix} ${val}` : val;
42
+ }
43
+ for (const [k, val] of Object.entries(v.header || {})) {
44
+ if (val !== '' && val != null) headers[k] = val;
45
+ }
46
+
47
+ let body = null;
48
+ if (operation.body && BODY_METHODS.has(operation.method)) {
49
+ body = bodyText || '';
50
+ if (body && !headers['Content-Type']) headers['Content-Type'] = operation.body.contentType || 'application/json';
51
+ }
52
+
53
+ return { method: operation.method, url, headers, body };
54
+ }
55
+
56
+ async function readBody(res) {
57
+ const text = await res.text();
58
+ const ct = res.headers.get('content-type') || '';
59
+ if (/json/i.test(ct)) {
60
+ try {
61
+ return JSON.parse(text);
62
+ } catch {
63
+ /* fall through to raw text */
64
+ }
65
+ }
66
+ return text;
67
+ }
68
+
69
+ export async function sendApiRequest({ operation, server, proxy = true, values, bodyText }) {
70
+ const req = buildRequest({ operation, server, values, bodyText });
71
+
72
+ // Proxied send. A registered transport (the instant preview's host bridge)
73
+ // takes precedence over the same-origin HTTP proxy — same return shape.
74
+ if (proxy && proxyTransport) {
75
+ const payload = await proxyTransport(req);
76
+ if (payload && payload.error) return { error: payload.error };
77
+ return {
78
+ status: payload?.status,
79
+ statusText: payload?.statusText || '',
80
+ headers: payload?.headers || {},
81
+ body: payload?.body,
82
+ };
83
+ }
84
+
85
+ if (proxy) {
86
+ const res = await fetch(PROXY_URL, {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify(req),
90
+ });
91
+ // The proxy returns the upstream result as JSON.
92
+ const payload = await res.json().catch(() => ({}));
93
+ if (payload && payload.error) return { error: payload.error };
94
+ return {
95
+ status: payload.status,
96
+ statusText: payload.statusText || '',
97
+ headers: payload.headers || {},
98
+ body: payload.body,
99
+ };
100
+ }
101
+
102
+ // Direct browser fetch (subject to CORS).
103
+ const res = await fetch(req.url, {
104
+ method: req.method,
105
+ headers: req.headers,
106
+ body: req.body || undefined,
107
+ });
108
+ const headers = {};
109
+ res.headers.forEach((val, key) => {
110
+ headers[key] = val;
111
+ });
112
+ return {
113
+ status: res.status,
114
+ statusText: res.statusText,
115
+ headers,
116
+ body: await readBody(res),
117
+ };
118
+ }