@veluai/velu 0.1.15 → 0.2.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/dist/cli.js +50 -23
- package/package.json +9 -3
- package/runtime/velu-ui/components/ApiClient.jsx +97 -11
- package/runtime/velu-ui/components/ApiReferencePage.jsx +384 -0
- package/runtime/velu-ui/components/ApiSamples.jsx +36 -0
- package/runtime/velu-ui/components/ContextMenu.jsx +272 -0
- package/runtime/velu-ui/components/NotFound.jsx +63 -0
- package/runtime/velu-ui/components/Sidebar.jsx +10 -2
- package/runtime/velu-ui/components/TryItBar.jsx +15 -3
- package/runtime/velu-ui/components/api-page.css +208 -0
- package/runtime/velu-ui/components/api.css +165 -9
- package/runtime/velu-ui/components/context-menu.css +170 -0
- package/runtime/velu-ui/components/docs-layout.css +18 -0
- package/runtime/velu-ui/components/not-found.css +94 -0
- package/runtime/velu-ui/components/powered-by.css +6 -0
- package/runtime/velu-ui/index.js +4 -0
- package/runtime/velu-ui/lib/api-send.js +92 -0
- package/runtime/velu-ui/lib/brand-icons.jsx +102 -0
- package/runtime/velu-ui/styles.css +3 -0
- package/schema/velu.schema.json +121 -0
- package/src/navigation.js +11 -2
- package/src/runtime/App.jsx +95 -6
- package/templates/starter/api-reference/introduction.mdx +29 -14
- package/templates/starter/openapi.json +160 -0
- package/templates/starter/velu.json +10 -2
- package/templates/starter/api-reference/endpoint/create.mdx +0 -24
- package/templates/starter/api-reference/endpoint/get.mdx +0 -27
package/schema/velu.schema.json
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"type": "string",
|
|
41
41
|
"description": "Path to the favicon, relative to the project root."
|
|
42
42
|
},
|
|
43
|
+
"api": { "$ref": "#/$defs/api" },
|
|
43
44
|
"logo": {
|
|
44
45
|
"description": "Site logo shown in the header, replacing the name wordmark. A single path used in both themes, or per-theme light/dark images with an optional click-through href. Paths are relative to the project root.",
|
|
45
46
|
"oneOf": [
|
|
@@ -169,6 +170,78 @@
|
|
|
169
170
|
"navigation": {
|
|
170
171
|
"$ref": "#/$defs/navContainer",
|
|
171
172
|
"description": "Site navigation. Mintlify-compatible: products > versions > languages > tabs > groups > pages. Switchable axes (product/version/language) become URL path prefixes; the default value of each is unprefixed. Dropdowns are intentionally unsupported."
|
|
173
|
+
},
|
|
174
|
+
"url": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"description": "Production site origin (e.g. \"https://docs.example.com\"), no trailing slash. Used by the static build for canonical URLs, og:url, and sitemap.xml, and by llms.txt for absolute links."
|
|
177
|
+
},
|
|
178
|
+
"thumbnails": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"additionalProperties": false,
|
|
181
|
+
"description": "Auto-generated OG (social share) image options. Build-only.",
|
|
182
|
+
"properties": {
|
|
183
|
+
"background": {
|
|
184
|
+
"type": "string",
|
|
185
|
+
"description": "Project-relative path to a background image for the OG cards (e.g. \"/images/og-background.png\"). Defaults to a brand-color glow."
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
"seo": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"additionalProperties": false,
|
|
192
|
+
"description": "Search-engine optimization. Build-only: meta tags and sitemap behaviour applied when the site is built (not in the dev preview).",
|
|
193
|
+
"properties": {
|
|
194
|
+
"metatags": {
|
|
195
|
+
"type": "object",
|
|
196
|
+
"description": "Extra meta tags injected on every page, as a flat key→value map (e.g. \"google-site-verification\", \"og:image\").",
|
|
197
|
+
"additionalProperties": { "type": "string" }
|
|
198
|
+
},
|
|
199
|
+
"indexing": {
|
|
200
|
+
"type": "string",
|
|
201
|
+
"enum": ["navigable", "all"],
|
|
202
|
+
"description": "\"navigable\" (default) indexes only pages in navigation; \"all\" also includes hidden pages."
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"contextual": {
|
|
207
|
+
"type": "object",
|
|
208
|
+
"additionalProperties": false,
|
|
209
|
+
"description": "Per-page context menu — the \"Copy Page\" split-button + dropdown of agent/IDE actions shown at the top of each page. Mintlify-compatible. Unset → copy, view, chatgpt, claude.",
|
|
210
|
+
"required": ["options"],
|
|
211
|
+
"properties": {
|
|
212
|
+
"options": {
|
|
213
|
+
"type": "array",
|
|
214
|
+
"description": "Menu items, in order. Known string options, or custom { title, description, href } entries.",
|
|
215
|
+
"items": {
|
|
216
|
+
"oneOf": [
|
|
217
|
+
{
|
|
218
|
+
"type": "string",
|
|
219
|
+
"enum": [
|
|
220
|
+
"copy", "assistant", "view", "download-pdf", "download-spec",
|
|
221
|
+
"chatgpt", "claude", "perplexity", "grok", "aistudio", "devin", "windsurf",
|
|
222
|
+
"mcp", "add-mcp", "cursor", "vscode", "devin-mcp"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"type": "object",
|
|
227
|
+
"additionalProperties": false,
|
|
228
|
+
"required": ["title", "href"],
|
|
229
|
+
"properties": {
|
|
230
|
+
"title": { "type": "string", "description": "Menu item label." },
|
|
231
|
+
"description": { "type": "string", "description": "Secondary line under the label." },
|
|
232
|
+
"href": { "type": "string", "description": "URL to open." },
|
|
233
|
+
"icon": { "type": "string", "description": "Lucide icon id." }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
"display": {
|
|
240
|
+
"type": "string",
|
|
241
|
+
"enum": ["header", "toc"],
|
|
242
|
+
"description": "Where the menu renders. Default \"header\"."
|
|
243
|
+
}
|
|
244
|
+
}
|
|
172
245
|
}
|
|
173
246
|
},
|
|
174
247
|
"$defs": {
|
|
@@ -205,6 +278,8 @@
|
|
|
205
278
|
"icon": { "type": "string", "description": "lucide icon id." },
|
|
206
279
|
"expanded": { "type": "boolean", "description": "Start expanded." },
|
|
207
280
|
"root": { "type": "string", "description": "Landing page path for the group." },
|
|
281
|
+
"openapi": { "$ref": "#/$defs/openapiRef" },
|
|
282
|
+
"api": { "$ref": "#/$defs/api" },
|
|
208
283
|
"pages": { "$ref": "#/$defs/pages" }
|
|
209
284
|
}
|
|
210
285
|
},
|
|
@@ -216,11 +291,57 @@
|
|
|
216
291
|
"tab": { "type": "string", "description": "Tab label (top nav)." },
|
|
217
292
|
"icon": { "type": "string" },
|
|
218
293
|
"href": { "type": "string", "description": "External/override link instead of in-site pages." },
|
|
294
|
+
"openapi": { "$ref": "#/$defs/openapiRef" },
|
|
295
|
+
"api": { "$ref": "#/$defs/api" },
|
|
219
296
|
"anchors": { "type": "array", "items": { "$ref": "#/$defs/anchor" } },
|
|
220
297
|
"groups": { "type": "array", "items": { "$ref": "#/$defs/group" } },
|
|
221
298
|
"pages": { "$ref": "#/$defs/pages" }
|
|
222
299
|
}
|
|
223
300
|
},
|
|
301
|
+
"api": {
|
|
302
|
+
"type": "object",
|
|
303
|
+
"additionalProperties": false,
|
|
304
|
+
"description": "API reference behaviour for OpenAPI-generated pages. Set at the root, or on a tab/group with `openapi` to override it for just that section.",
|
|
305
|
+
"properties": {
|
|
306
|
+
"server": {
|
|
307
|
+
"type": "string",
|
|
308
|
+
"description": "Base URL override for the playground + code samples (otherwise the spec's first server is used)."
|
|
309
|
+
},
|
|
310
|
+
"playground": {
|
|
311
|
+
"type": "object",
|
|
312
|
+
"additionalProperties": false,
|
|
313
|
+
"properties": {
|
|
314
|
+
"display": {
|
|
315
|
+
"type": "string",
|
|
316
|
+
"enum": ["interactive", "simple", "none"],
|
|
317
|
+
"description": "Playground mode. \"none\" disables the proxy / live send."
|
|
318
|
+
},
|
|
319
|
+
"proxy": {
|
|
320
|
+
"type": "boolean",
|
|
321
|
+
"description": "Send Try-It requests through the dev proxy to avoid CORS (default true)."
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
"examples": {
|
|
326
|
+
"type": "object",
|
|
327
|
+
"additionalProperties": false,
|
|
328
|
+
"properties": {
|
|
329
|
+
"languages": {
|
|
330
|
+
"type": "array",
|
|
331
|
+
"items": { "type": "string", "enum": ["curl", "javascript", "python", "ruby"] },
|
|
332
|
+
"description": "Which request-snippet languages to generate."
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
"openapiRef": {
|
|
339
|
+
"description": "Path or URL to an OpenAPI spec (JSON/YAML), or an array of them. Auto-generates a page per operation, grouped by tag.",
|
|
340
|
+
"oneOf": [
|
|
341
|
+
{ "type": "string" },
|
|
342
|
+
{ "type": "array", "items": { "type": "string" } }
|
|
343
|
+
]
|
|
344
|
+
},
|
|
224
345
|
"anchor": {
|
|
225
346
|
"type": "object",
|
|
226
347
|
"additionalProperties": false,
|
package/src/navigation.js
CHANGED
|
@@ -186,9 +186,13 @@ function normalizeGroup(g) {
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
/** A `pages[]` entry is
|
|
189
|
+
/** A `pages[]` entry is a string (page path), a generated OpenAPI page
|
|
190
|
+
* ({ apiPage, label, method }), or a nested group. */
|
|
190
191
|
function normalizePageEntry(entry) {
|
|
191
192
|
if (typeof entry === 'string') return { kind: 'page', pagePath: entry };
|
|
193
|
+
if (entry && entry.apiPage) {
|
|
194
|
+
return { kind: 'page', pagePath: entry.apiPage, label: entry.label, method: entry.method, api: true };
|
|
195
|
+
}
|
|
192
196
|
return normalizeGroup(entry);
|
|
193
197
|
}
|
|
194
198
|
|
|
@@ -430,5 +434,10 @@ function itemsFor(nodes, pagesMap, ctx) {
|
|
|
430
434
|
function itemFor(pageNode, pagesMap, ctx) {
|
|
431
435
|
const href = urlInCtx(pageNode.pagePath, ctx);
|
|
432
436
|
const fm = pagesMap[href]?.frontmatter;
|
|
433
|
-
|
|
437
|
+
// Generated API pages carry their own label + HTTP method (for the sidebar
|
|
438
|
+
// method badge); normal pages source their label from frontmatter.
|
|
439
|
+
const label = pageNode.label ?? fm?.title ?? titleCase(lastSeg(href));
|
|
440
|
+
const item = { label, href };
|
|
441
|
+
if (pageNode.method) item.method = pageNode.method;
|
|
442
|
+
return item;
|
|
434
443
|
}
|
package/src/runtime/App.jsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
NavSelect,
|
|
15
15
|
Toc,
|
|
16
16
|
TocBar,
|
|
17
|
+
ContextMenu,
|
|
17
18
|
Callout,
|
|
18
19
|
Accordion,
|
|
19
20
|
AccordionGroup,
|
|
@@ -47,6 +48,8 @@ import {
|
|
|
47
48
|
ApiClient,
|
|
48
49
|
ApiField,
|
|
49
50
|
ApiSidebar,
|
|
51
|
+
ApiSamples,
|
|
52
|
+
NotFound,
|
|
50
53
|
VeluMark,
|
|
51
54
|
} from 'velu-ui';
|
|
52
55
|
import { X, ChevronDown, ChevronUp } from 'lucide-react';
|
|
@@ -726,6 +729,9 @@ function DocsPage() {
|
|
|
726
729
|
const frontmatter = entry?.frontmatter ?? {};
|
|
727
730
|
const PageComponent = entry?.Component ?? null;
|
|
728
731
|
const pageToc = entry?.toc ?? [];
|
|
732
|
+
// No renderable page for this route → show the 404 page (a clean centered
|
|
733
|
+
// takeover: header + footer stay, the docs sidebar/TOC are hidden).
|
|
734
|
+
const isNotFound = !PageComponent;
|
|
729
735
|
|
|
730
736
|
// Switcher option sets (only render a switcher when an axis has >1
|
|
731
737
|
// option). Anchors are pinned sidebar links shown in the context zone.
|
|
@@ -762,6 +768,27 @@ function DocsPage() {
|
|
|
762
768
|
/>
|
|
763
769
|
);
|
|
764
770
|
|
|
771
|
+
// Keep document.title / meta description / <html lang> in sync on client-side
|
|
772
|
+
// navigation. The HTML is SSR'd with a per-page (build) or generic (dev) title;
|
|
773
|
+
// without this the tab title would stay frozen on the first page as you
|
|
774
|
+
// navigate the SPA, and JS-executing crawlers would read the stale value.
|
|
775
|
+
React.useEffect(() => {
|
|
776
|
+
const t = frontmatter.title;
|
|
777
|
+
document.title = isNotFound
|
|
778
|
+
? `Page not found - ${site.name}`
|
|
779
|
+
: t && t !== site.name
|
|
780
|
+
? `${t} - ${site.name}`
|
|
781
|
+
: site.name;
|
|
782
|
+
let descTag = document.querySelector('meta[name="description"]');
|
|
783
|
+
if (!descTag) {
|
|
784
|
+
descTag = document.createElement('meta');
|
|
785
|
+
descTag.setAttribute('name', 'description');
|
|
786
|
+
document.head.appendChild(descTag);
|
|
787
|
+
}
|
|
788
|
+
descTag.setAttribute('content', frontmatter.description || '');
|
|
789
|
+
document.documentElement.lang = nav?.activeLanguageCode || 'en';
|
|
790
|
+
}, [pathname, isNotFound, frontmatter.title, frontmatter.description, nav?.activeLanguageCode]);
|
|
791
|
+
|
|
765
792
|
// Frontmatter title needs an id so scroll-spy + click-to-scroll work
|
|
766
793
|
// against it like any other heading.
|
|
767
794
|
const pageId = React.useMemo(() => {
|
|
@@ -1057,6 +1084,8 @@ function DocsPage() {
|
|
|
1057
1084
|
data-chat-open={chatOpen ? 'true' : 'false'}
|
|
1058
1085
|
data-sidebar-open={sidebarOpen ? 'true' : 'false'}
|
|
1059
1086
|
data-drawer-open={drawerOpen ? 'true' : 'false'}
|
|
1087
|
+
data-api={entry?.api ? 'true' : 'false'}
|
|
1088
|
+
data-not-found={isNotFound ? 'true' : undefined}
|
|
1060
1089
|
>
|
|
1061
1090
|
{/* Scrim — visible at mobile while the drawer OR the chatbot
|
|
1062
1091
|
sheet is open. Sits between the article (z-0) and the
|
|
@@ -1263,11 +1292,27 @@ function DocsPage() {
|
|
|
1263
1292
|
scrollPaddingBlockEnd: 'var(--s1)',
|
|
1264
1293
|
}}
|
|
1265
1294
|
>
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1295
|
+
{entry?.api ? (
|
|
1296
|
+
<ApiSidebar
|
|
1297
|
+
sections={(nav?.sidebarSections ?? []).map((s) => ({
|
|
1298
|
+
title: s.title,
|
|
1299
|
+
icon: s.icon,
|
|
1300
|
+
endpoints: (s.items ?? []).map((it) => ({
|
|
1301
|
+
method: it.method,
|
|
1302
|
+
label: it.label,
|
|
1303
|
+
href: it.href,
|
|
1304
|
+
})),
|
|
1305
|
+
}))}
|
|
1306
|
+
activeHref={pathname}
|
|
1307
|
+
linkComponent={RouterLink}
|
|
1308
|
+
/>
|
|
1309
|
+
) : (
|
|
1310
|
+
<Sidebar
|
|
1311
|
+
sections={nav?.sidebarSections ?? []}
|
|
1312
|
+
activeHref={pathname}
|
|
1313
|
+
linkComponent={RouterLink}
|
|
1314
|
+
/>
|
|
1315
|
+
)}
|
|
1271
1316
|
</div>
|
|
1272
1317
|
<button
|
|
1273
1318
|
type="button"
|
|
@@ -1317,7 +1362,16 @@ function DocsPage() {
|
|
|
1317
1362
|
scrollPaddingBlockEnd: `calc(${footerOverlap}px + 2rem)`,
|
|
1318
1363
|
}}
|
|
1319
1364
|
>
|
|
1320
|
-
|
|
1365
|
+
{/* API reference pages put their code samples in the right rail
|
|
1366
|
+
(where the TOC sits for normal pages). */}
|
|
1367
|
+
{entry?.api && entry.operation ? (
|
|
1368
|
+
<ApiSamples
|
|
1369
|
+
samples={entry.samples}
|
|
1370
|
+
responses={entry.operation.responses}
|
|
1371
|
+
/>
|
|
1372
|
+
) : (
|
|
1373
|
+
<Toc items={toc} activeId={activeId} onSelect={scrollTo} />
|
|
1374
|
+
)}
|
|
1321
1375
|
</aside>
|
|
1322
1376
|
)}
|
|
1323
1377
|
|
|
@@ -1326,6 +1380,21 @@ function DocsPage() {
|
|
|
1326
1380
|
and the right margin collapses to 0 while the chatbot is
|
|
1327
1381
|
open via [data-chat-open="true"]). */}
|
|
1328
1382
|
<div className="velu-docs-layout__center">
|
|
1383
|
+
{isNotFound ? (
|
|
1384
|
+
<main className="velu-404-main">
|
|
1385
|
+
<NotFound
|
|
1386
|
+
homeHref="/"
|
|
1387
|
+
linkComponent={RouterLink}
|
|
1388
|
+
onSearch={() =>
|
|
1389
|
+
window.dispatchEvent(
|
|
1390
|
+
new KeyboardEvent('keydown', { key: 'k', ctrlKey: true, metaKey: true }),
|
|
1391
|
+
)
|
|
1392
|
+
}
|
|
1393
|
+
onAskAI={IS_DEV_PREVIEW ? undefined : () => askAI('')}
|
|
1394
|
+
/>
|
|
1395
|
+
</main>
|
|
1396
|
+
) : (
|
|
1397
|
+
<>
|
|
1329
1398
|
{/* Narrow-layout TOC bar — always in DOM, only visible at
|
|
1330
1399
|
< 1024px (toggled by @container in toc-bar.css). Shares
|
|
1331
1400
|
its `items` + `activeId` + `onSelect` API with the
|
|
@@ -1369,6 +1438,24 @@ function DocsPage() {
|
|
|
1369
1438
|
</span>
|
|
1370
1439
|
</button>
|
|
1371
1440
|
<div className="velu-docs-layout__article">
|
|
1441
|
+
{/* Per-page agent/IDE action bar: the section eyebrow + a
|
|
1442
|
+
"Copy Page" split-button whose dropdown is driven by the
|
|
1443
|
+
Mintlify-compatible `contextual` config. Renders nothing
|
|
1444
|
+
when there's neither an eyebrow nor any enabled options. */}
|
|
1445
|
+
<ContextMenu
|
|
1446
|
+
eyebrow={
|
|
1447
|
+
nav?.breadcrumb && nav.breadcrumb.length > 1
|
|
1448
|
+
? nav.breadcrumb[nav.breadcrumb.length - 2].label
|
|
1449
|
+
: undefined
|
|
1450
|
+
}
|
|
1451
|
+
pageUrl={pathname}
|
|
1452
|
+
title={frontmatter.title}
|
|
1453
|
+
isApi={entry?.api === true}
|
|
1454
|
+
siteName={site.name}
|
|
1455
|
+
options={site.contextual?.options ?? []}
|
|
1456
|
+
onAssistant={IS_DEV_PREVIEW ? undefined : () => askAI('')}
|
|
1457
|
+
/>
|
|
1458
|
+
|
|
1372
1459
|
{/* Page hero from MDX frontmatter. `.velu-hero` rules
|
|
1373
1460
|
(in base.css) keep the title and description tightly
|
|
1374
1461
|
grouped and create a clear break before the prose body. */}
|
|
@@ -1467,6 +1554,8 @@ function DocsPage() {
|
|
|
1467
1554
|
)}
|
|
1468
1555
|
</div>
|
|
1469
1556
|
</main>
|
|
1557
|
+
</>
|
|
1558
|
+
)}
|
|
1470
1559
|
</div>
|
|
1471
1560
|
|
|
1472
1561
|
{/* Full site footer — only when the config provides link columns. Spans
|
|
@@ -1,28 +1,43 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Introduction
|
|
3
|
-
description: How
|
|
3
|
+
description: How this API reference is generated.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
The endpoints in this tab are generated automatically from an OpenAPI
|
|
7
|
+
spec (`openapi.json`). Each operation becomes its own page — with
|
|
8
|
+
parameters, request body, and responses — and an interactive **Try It**
|
|
9
|
+
playground you can use to send real requests.
|
|
9
10
|
|
|
10
11
|
<Callout type="note">
|
|
11
12
|
This is a second tab ("API Reference"). Tabs let you keep guides and
|
|
12
|
-
reference docs in separate top-level sections of the same site.
|
|
13
|
+
reference docs in separate top-level sections of the same site. Point
|
|
14
|
+
the tab's `openapi` field at your own spec to replace this example.
|
|
13
15
|
</Callout>
|
|
14
16
|
|
|
15
|
-
##
|
|
17
|
+
## How it works
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
Add an `openapi` field to a tab (or group) in `velu.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"tab": "API Reference",
|
|
24
|
+
"openapi": "/openapi.json"
|
|
25
|
+
}
|
|
19
26
|
```
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
Velu reads the spec, groups the operations by their tag, and renders a
|
|
29
|
+
page per endpoint. No hand-written endpoint pages required.
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
## The example API
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
This starter points at a live demo API — a small **product catalog**
|
|
34
|
+
backed by [dummyjson.com](https://dummyjson.com). Open any endpoint in
|
|
35
|
+
the sidebar, hit **Try It**, and send a real request:
|
|
36
|
+
|
|
37
|
+
- **Get a product** — try `id` = `1`
|
|
38
|
+
- **Search products** — try `q` = `phone`
|
|
39
|
+
- **Add a product** — the body is pre-filled; Send returns a created product
|
|
40
|
+
|
|
41
|
+
Requests are routed through the dev server's proxy so they aren't blocked
|
|
42
|
+
by CORS. Set `api.playground.proxy` to `false` to send straight from the
|
|
43
|
+
browser.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Product Catalog API",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "A friendly, fully-hosted example API — a product catalog backed by the live https://dummyjson.com service. Every operation hits the real API: list, search, fetch, create, update and delete products and see real responses."
|
|
7
|
+
},
|
|
8
|
+
"servers": [{ "url": "https://dummyjson.com" }],
|
|
9
|
+
"tags": [{ "name": "Products", "description": "Browse and manage the product catalog." }],
|
|
10
|
+
"paths": {
|
|
11
|
+
"/products": {
|
|
12
|
+
"get": {
|
|
13
|
+
"operationId": "listProducts",
|
|
14
|
+
"summary": "List products",
|
|
15
|
+
"description": "List products, with optional pagination.",
|
|
16
|
+
"tags": ["Products"],
|
|
17
|
+
"parameters": [
|
|
18
|
+
{ "name": "limit", "in": "query", "schema": { "type": "integer" }, "description": "How many products to return (0 returns all)." },
|
|
19
|
+
{ "name": "skip", "in": "query", "schema": { "type": "integer" }, "description": "How many products to skip (for pagination)." }
|
|
20
|
+
],
|
|
21
|
+
"responses": {
|
|
22
|
+
"200": {
|
|
23
|
+
"description": "A page of products.",
|
|
24
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductList" } } }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"/products/search": {
|
|
30
|
+
"get": {
|
|
31
|
+
"operationId": "searchProducts",
|
|
32
|
+
"summary": "Search products",
|
|
33
|
+
"description": "Search the catalog by a free-text query.",
|
|
34
|
+
"tags": ["Products"],
|
|
35
|
+
"parameters": [
|
|
36
|
+
{ "name": "q", "in": "query", "required": true, "schema": { "type": "string" }, "description": "The search query (try `phone`)." }
|
|
37
|
+
],
|
|
38
|
+
"responses": {
|
|
39
|
+
"200": {
|
|
40
|
+
"description": "Matching products.",
|
|
41
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductList" } } }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"/products/{id}": {
|
|
47
|
+
"get": {
|
|
48
|
+
"operationId": "getProduct",
|
|
49
|
+
"summary": "Get a product",
|
|
50
|
+
"description": "Retrieve a single product by its id.",
|
|
51
|
+
"tags": ["Products"],
|
|
52
|
+
"parameters": [
|
|
53
|
+
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The product id (try `1`)." }
|
|
54
|
+
],
|
|
55
|
+
"responses": {
|
|
56
|
+
"200": {
|
|
57
|
+
"description": "The product.",
|
|
58
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"put": {
|
|
63
|
+
"operationId": "updateProduct",
|
|
64
|
+
"summary": "Update a product",
|
|
65
|
+
"description": "Update fields on an existing product. (Simulated by the upstream API — the response reflects the change but nothing is persisted.)",
|
|
66
|
+
"tags": ["Products"],
|
|
67
|
+
"parameters": [
|
|
68
|
+
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The product id." }
|
|
69
|
+
],
|
|
70
|
+
"requestBody": {
|
|
71
|
+
"required": true,
|
|
72
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductInput" } } }
|
|
73
|
+
},
|
|
74
|
+
"responses": {
|
|
75
|
+
"200": {
|
|
76
|
+
"description": "The updated product.",
|
|
77
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"delete": {
|
|
82
|
+
"operationId": "deleteProduct",
|
|
83
|
+
"summary": "Delete a product",
|
|
84
|
+
"description": "Delete a product by its id. (Simulated — the response includes `isDeleted` and `deletedOn`.)",
|
|
85
|
+
"tags": ["Products"],
|
|
86
|
+
"parameters": [
|
|
87
|
+
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "The product id." }
|
|
88
|
+
],
|
|
89
|
+
"responses": {
|
|
90
|
+
"200": {
|
|
91
|
+
"description": "The deleted product.",
|
|
92
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"/products/add": {
|
|
98
|
+
"post": {
|
|
99
|
+
"operationId": "addProduct",
|
|
100
|
+
"summary": "Add a product",
|
|
101
|
+
"description": "Create a new product. (Simulated — the response echoes the body with a fresh `id`.)",
|
|
102
|
+
"tags": ["Products"],
|
|
103
|
+
"requestBody": {
|
|
104
|
+
"required": true,
|
|
105
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProductInput" } } }
|
|
106
|
+
},
|
|
107
|
+
"responses": {
|
|
108
|
+
"201": {
|
|
109
|
+
"description": "The created product.",
|
|
110
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Product" } } }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"components": {
|
|
117
|
+
"schemas": {
|
|
118
|
+
"Product": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"properties": {
|
|
121
|
+
"id": { "type": "integer", "description": "Unique identifier." },
|
|
122
|
+
"title": { "type": "string", "description": "Product name." },
|
|
123
|
+
"description": { "type": "string", "description": "Product description." },
|
|
124
|
+
"category": { "type": "string", "description": "Category slug." },
|
|
125
|
+
"price": { "type": "number", "description": "Price in USD." },
|
|
126
|
+
"discountPercentage": { "type": "number", "description": "Discount, as a percentage." },
|
|
127
|
+
"rating": { "type": "number", "description": "Average customer rating (0–5)." },
|
|
128
|
+
"stock": { "type": "integer", "description": "Units in stock." },
|
|
129
|
+
"brand": { "type": "string", "description": "Brand name." },
|
|
130
|
+
"thumbnail": { "type": "string", "description": "Thumbnail image URL." }
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
"ProductList": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"products": { "type": "array", "description": "The products on this page.", "items": { "$ref": "#/components/schemas/Product" } },
|
|
137
|
+
"total": { "type": "integer", "description": "Total number of matching products." },
|
|
138
|
+
"skip": { "type": "integer", "description": "How many were skipped." },
|
|
139
|
+
"limit": { "type": "integer", "description": "How many were returned." }
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"ProductInput": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"required": ["title"],
|
|
145
|
+
"properties": {
|
|
146
|
+
"title": { "type": "string", "description": "Product name." },
|
|
147
|
+
"description": { "type": "string", "description": "Product description." },
|
|
148
|
+
"category": { "type": "string", "description": "Category slug." },
|
|
149
|
+
"price": { "type": "number", "description": "Price in USD." }
|
|
150
|
+
},
|
|
151
|
+
"example": {
|
|
152
|
+
"title": "Velu Notebook",
|
|
153
|
+
"description": "A dotted-grid notebook for docs notes.",
|
|
154
|
+
"category": "stationery",
|
|
155
|
+
"price": 19.99
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://veludocs.com/velu.schema.json",
|
|
3
3
|
"name": "Starter",
|
|
4
|
+
"url": "https://veludocs.com",
|
|
4
5
|
"colors": {
|
|
5
6
|
"primary": "#dc143c"
|
|
6
7
|
},
|
|
7
8
|
"favicon": "/favicon.svg",
|
|
9
|
+
"contextual": {
|
|
10
|
+
"options": ["copy", "view", "chatgpt", "claude", "mcp", "cursor", "vscode"]
|
|
11
|
+
},
|
|
8
12
|
"navigation": {
|
|
9
13
|
"anchors": [
|
|
10
14
|
{ "anchor": "Documentation", "icon": "book-open", "href": "https://veludocs.com" },
|
|
@@ -23,11 +27,15 @@
|
|
|
23
27
|
{
|
|
24
28
|
"tab": "API Reference",
|
|
25
29
|
"icon": "terminal",
|
|
30
|
+
"openapi": "/openapi.json",
|
|
26
31
|
"groups": [
|
|
27
|
-
{ "group": "API Documentation", "pages": ["api-reference/introduction"] }
|
|
28
|
-
{ "group": "Endpoint Examples", "pages": ["api-reference/endpoint/get", "api-reference/endpoint/create"] }
|
|
32
|
+
{ "group": "API Documentation", "pages": ["api-reference/introduction"] }
|
|
29
33
|
]
|
|
30
34
|
}
|
|
31
35
|
]
|
|
36
|
+
},
|
|
37
|
+
"api": {
|
|
38
|
+
"playground": { "proxy": true },
|
|
39
|
+
"examples": { "languages": ["curl", "javascript", "python", "ruby"] }
|
|
32
40
|
}
|
|
33
41
|
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Create item
|
|
3
|
-
description: Create a new item.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
<MethodBadge method="POST" /> `/items`
|
|
7
|
-
|
|
8
|
-
Creates an item and returns it.
|
|
9
|
-
|
|
10
|
-
## Body
|
|
11
|
-
|
|
12
|
-
<ApiField name="name" type="string" required>
|
|
13
|
-
Display name for the item.
|
|
14
|
-
</ApiField>
|
|
15
|
-
|
|
16
|
-
## Response
|
|
17
|
-
|
|
18
|
-
```json
|
|
19
|
-
{
|
|
20
|
-
"id": "itm_2",
|
|
21
|
-
"name": "New item",
|
|
22
|
-
"created_at": "2024-01-01T00:00:00Z"
|
|
23
|
-
}
|
|
24
|
-
```
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Get items
|
|
3
|
-
description: Retrieve a list of items.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
<MethodBadge method="GET" /> `/items`
|
|
7
|
-
|
|
8
|
-
Returns a paginated list of items.
|
|
9
|
-
|
|
10
|
-
## Query parameters
|
|
11
|
-
|
|
12
|
-
<ApiField name="limit" type="integer" default="20">
|
|
13
|
-
Maximum number of items to return.
|
|
14
|
-
</ApiField>
|
|
15
|
-
|
|
16
|
-
<ApiField name="cursor" type="string">
|
|
17
|
-
Pagination cursor from a previous response.
|
|
18
|
-
</ApiField>
|
|
19
|
-
|
|
20
|
-
## Response
|
|
21
|
-
|
|
22
|
-
```json
|
|
23
|
-
{
|
|
24
|
-
"data": [{ "id": "itm_1", "name": "Example" }],
|
|
25
|
-
"next_cursor": null
|
|
26
|
-
}
|
|
27
|
-
```
|