fumadocs-openapi 5.10.6 → 5.11.1

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/index.d.ts CHANGED
@@ -49,14 +49,15 @@ type RequestSchema = PrimitiveSchema | ArraySchema | ObjectSchema | SwitcherSche
49
49
  interface APIPlaygroundProps {
50
50
  route: string;
51
51
  method: string;
52
- bodyType: 'json' | 'form-data';
53
52
  authorization?: PrimitiveRequestField & {
54
53
  authType: string;
55
54
  };
56
55
  path?: PrimitiveRequestField[];
57
56
  query?: PrimitiveRequestField[];
58
57
  header?: PrimitiveRequestField[];
59
- body?: RequestSchema;
58
+ body?: RequestSchema & {
59
+ mediaType: string;
60
+ };
60
61
  schemas: Record<string, RequestSchema>;
61
62
  proxyUrl?: string;
62
63
  }
@@ -51,14 +51,15 @@ type RequestSchema = PrimitiveSchema | ArraySchema | ObjectSchema | SwitcherSche
51
51
  interface APIPlaygroundProps {
52
52
  route: string;
53
53
  method: string;
54
- bodyType: 'json' | 'form-data';
55
54
  authorization?: PrimitiveRequestField & {
56
55
  authType: string;
57
56
  };
58
57
  path?: PrimitiveRequestField[];
59
58
  query?: PrimitiveRequestField[];
60
59
  header?: PrimitiveRequestField[];
61
- body?: RequestSchema;
60
+ body?: RequestSchema & {
61
+ mediaType: string;
62
+ };
62
63
  schemas: Record<string, RequestSchema>;
63
64
  proxyUrl?: string;
64
65
  }
@@ -2,6 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import Slugger from 'github-slugger';
3
3
  import { Fragment as Fragment$1 } from 'react';
4
4
  import { sample } from 'openapi-sampler';
5
+ import { js2xml } from 'xml-js';
5
6
  import { compile } from '@fumari/json-schema-to-typescript';
6
7
  import { remarkGfm, remarkImage, rehypeCode } from 'fumadocs-core/mdx-plugins';
7
8
  import defaultMdxComponents from 'fumadocs-ui/mdx';
@@ -24,9 +25,24 @@ function getPreferredType(body) {
24
25
  return Object.keys(body)[0];
25
26
  }
26
27
  /**
27
- * Convert to JSON string if necessary
28
- */ function toSampleInput(value) {
29
- return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
28
+ * Convert input to string (with quotes)
29
+ */ function inputToString(value, mediaType = 'application/json', multiLine = 'none') {
30
+ const getStr = (v)=>{
31
+ if (multiLine === 'none') return JSON.stringify(v);
32
+ const delimit = multiLine === 'backtick' ? `\`` : `'`;
33
+ return `${delimit}${v.replaceAll(delimit, `\\${delimit}`)}${delimit}`;
34
+ };
35
+ if (typeof value === 'string') return getStr(value);
36
+ if (mediaType === 'application/json' || mediaType === 'multipart/form-data') {
37
+ return getStr(JSON.stringify(value, null, 2));
38
+ }
39
+ if (mediaType === 'application/xml') {
40
+ return getStr(js2xml(value, {
41
+ compact: true,
42
+ spaces: 2
43
+ }));
44
+ }
45
+ throw new Error(`Unsupported media type: ${mediaType}`);
30
46
  }
31
47
  function isNullable(schema, includeOneOf = true) {
32
48
  if (Array.isArray(schema.type) && schema.type.includes('null')) return true;
@@ -124,8 +140,8 @@ function generateSample(path, method, { baseUrl, document }) {
124
140
  const queryParams = new URLSearchParams();
125
141
  for (const param of params){
126
142
  const value = generateBody(method.method, param.schema);
127
- if (param.in === 'query') queryParams.append(param.name, toSampleInput(value));
128
- if (param.in === 'path') pathWithParameters = pathWithParameters.replace(`{${param.name}}`, toSampleInput(value));
143
+ if (param.in === 'query') queryParams.append(param.name, String(value));
144
+ if (param.in === 'path') pathWithParameters = pathWithParameters.replace(`{${param.name}}`, String(value));
129
145
  }
130
146
  if (queryParams.size > 0) pathWithParameters = `${pathWithParameters}?${queryParams.toString()}`;
131
147
  return {
@@ -148,11 +164,11 @@ function getSampleRequest$3(endpoint) {
148
164
  s.push(`curl -X ${endpoint.method} "${endpoint.url}"`);
149
165
  for (const param of endpoint.parameters){
150
166
  if (param.in === 'header') {
151
- const header = `${param.name}: ${toSampleInput(param.sample)}`;
167
+ const header = `${param.name}: ${param.sample}`;
152
168
  s.push(`-H "${header}"`);
153
169
  }
154
170
  if (param.in === 'cookie') {
155
- const cookie = JSON.stringify(`${param.name}=${toSampleInput(param.sample)}`);
171
+ const cookie = JSON.stringify(`${param.name}=${param.sample}`);
156
172
  s.push(`--cookie ${cookie}`);
157
173
  }
158
174
  }
@@ -160,12 +176,12 @@ function getSampleRequest$3(endpoint) {
160
176
  const sample = endpoint.body.sample;
161
177
  if (sample && typeof sample === 'object') {
162
178
  for (const [key, value] of Object.entries(sample)){
163
- s.push(`-F ${key}=${JSON.stringify(value, null, 2)}`);
179
+ s.push(`-F ${key}=${inputToString(value)}`);
164
180
  }
165
181
  }
166
182
  } else if (endpoint.body) {
167
- s.push(`-H "Content-Type: application/json"`);
168
- s.push(`-d '${toSampleInput(endpoint.body.sample)}'`);
183
+ s.push(`-H "Content-Type: ${endpoint.body.mediaType}"`);
184
+ s.push(`-d ${inputToString(endpoint.body.sample, endpoint.body.mediaType, 'single-quote')}`);
169
185
  }
170
186
  return s.flatMap((v, i)=>v.split('\n').map((line)=>i > 0 ? ` ${line}` : line).join('\n')).join(' \\\n');
171
187
  }
@@ -184,17 +200,23 @@ function getSampleRequest$2(endpoint) {
184
200
  }
185
201
  }
186
202
  if (cookies.size > 0) {
187
- headers.set('cookie', Array.from(cookies.entries()).map(([key, value])=>`${key}=${toSampleInput(value)}`).join('; '));
203
+ headers.set('cookie', Array.from(cookies.entries()).map(([key, value])=>`${key}=${value}`).join('; '));
188
204
  }
189
205
  if (headers.size > 0) {
190
- options.set('headers', JSON.stringify(Object.fromEntries(headers.entries()), undefined, 2).replaceAll('\n', '\n '));
206
+ options.set('headers', JSON.stringify(Object.fromEntries(headers.entries()), null, 2).replaceAll('\n', '\n '));
191
207
  }
192
208
  if (endpoint.body?.mediaType === 'multipart/form-data' && typeof endpoint.body.sample === 'object' && endpoint.body.sample) {
193
209
  s.push(`const formData = new FormData();`);
194
- for (const [key, value] of Object.entries(endpoint.body.sample))s.push(`formData.set(${key}, ${JSON.stringify(value)})`);
210
+ for (const [key, value] of Object.entries(endpoint.body.sample))s.push(`formData.set(${key}, ${inputToString(value)})`);
195
211
  options.set('body', 'formData');
196
212
  } else if (endpoint.body) {
197
- options.set('body', `JSON.stringify(${JSON.stringify(endpoint.body.sample, null, 2).replaceAll('\n', '\n ')})`);
213
+ let code;
214
+ if (endpoint.body.mediaType === 'application/json') {
215
+ code = typeof endpoint.body.sample === 'string' ? inputToString(endpoint.body.sample, endpoint.body.mediaType, 'backtick') : `JSON.stringify(${JSON.stringify(endpoint.body.sample, null, 2)})`;
216
+ } else {
217
+ code = inputToString(endpoint.body.sample, endpoint.body.mediaType, 'backtick');
218
+ }
219
+ options.set('body', code.replaceAll('\n', '\n '));
198
220
  }
199
221
  const optionsStr = Array.from(options.entries()).map(([k, v])=>` ${k}: ${v}`).join(',\n');
200
222
  s.push(`fetch(${JSON.stringify(endpoint.url)}, {\n${optionsStr}\n});`);
@@ -208,29 +230,27 @@ function getSampleRequest$1(endpoint) {
208
230
  'io/ioutil'
209
231
  ];
210
232
  const headers = new Map();
211
- const cookies = new Map();
212
233
  const variables = new Map();
213
234
  // additional lines before initializing request
214
235
  const additional = [];
215
236
  for (const p of endpoint.parameters){
216
237
  if (p.in === 'header') headers.set(p.name, JSON.stringify(p.sample));
217
- if (p.in === 'cookie') cookies.set(p.name, toSampleInput(p.sample));
218
238
  }
239
+ const cookies = endpoint.parameters.filter((p)=>p.in === 'cookie');
219
240
  variables.set('url', JSON.stringify(endpoint.url));
220
- if (cookies.size > 0) headers.set('Cookie', JSON.stringify(Array.from(cookies.entries()).map(([key, value])=>`${key}=${value}`).join('; ')));
241
+ if (cookies.length > 0) headers.set('Cookie', JSON.stringify(cookies.map((p)=>`${p.name}=${p.sample}`).join('; ')));
221
242
  if (endpoint.body) {
222
243
  headers.set('Content-Type', `"${endpoint.body.mediaType}"`);
223
- if (endpoint.body.mediaType === 'application/json') {
224
- imports.push('strings');
225
- variables.set('payload', `strings.NewReader(\`${JSON.stringify(endpoint.body.sample, null, 2).replaceAll('\n', '\n ')}\`)`);
226
- }
227
244
  if (endpoint.body.mediaType === 'multipart/form-data' && typeof endpoint.body.sample === 'object') {
228
245
  imports.push('mime/multipart', 'bytes');
229
246
  variables.set('payload', `new(bytes.Buffer)`);
230
247
  variables.set('mp', 'multipart.NewWriter(payload)');
231
248
  for (const [key, value] of Object.entries(endpoint.body.sample ?? {})){
232
- additional.push(`mp.WriteField("${key}", ${JSON.stringify(toSampleInput(value))})`);
249
+ additional.push(`mp.WriteField("${key}", ${inputToString(value, undefined, 'backtick')})`);
233
250
  }
251
+ } else {
252
+ imports.push('strings');
253
+ variables.set('payload', `strings.NewReader(${inputToString(endpoint.body.sample, endpoint.body.mediaType, 'backtick').replaceAll('\n', '\n ')})`);
234
254
  }
235
255
  }
236
256
  return `package main
@@ -261,15 +281,26 @@ function getSampleRequest(endpoint) {
261
281
  if (param.in === 'header') headers.set(param.name, param.sample);
262
282
  if (param.in === 'cookie') cookies.set(param.name, param.sample);
263
283
  }
284
+ if (endpoint.body) {
285
+ switch(endpoint.body.mediaType){
286
+ case 'application/json':
287
+ variables.set('json', JSON.stringify(endpoint.body.sample, null, 2));
288
+ break;
289
+ case 'multipart/form-data':
290
+ headers.set('Content-Type', endpoint.body.mediaType);
291
+ variables.set('data', JSON.stringify(endpoint.body.sample, null, 2));
292
+ break;
293
+ default:
294
+ headers.set('Content-Type', endpoint.body.mediaType);
295
+ variables.set('data', inputToString(endpoint.body.sample, endpoint.body.mediaType, 'backtick'));
296
+ }
297
+ }
264
298
  if (headers.size > 0) {
265
299
  variables.set('headers', JSON.stringify(Object.fromEntries(headers.entries()), null, 2));
266
300
  }
267
301
  if (cookies.size > 0) {
268
302
  variables.set('cookies', JSON.stringify(Object.fromEntries(cookies.entries()), null, 2));
269
303
  }
270
- if (endpoint.body) {
271
- variables.set(endpoint.body.mediaType === 'multipart/form-data' ? 'data' : 'json', JSON.stringify(endpoint.body.sample, null, 2));
272
- }
273
304
  return `import requests
274
305
 
275
306
  url = ${JSON.stringify(endpoint.url)}
@@ -314,11 +345,13 @@ function Playground({ path, method, ctx }) {
314
345
  authorization: getAuthorizationField(method, ctx),
315
346
  method: method.method,
316
347
  route: path,
317
- bodyType: mediaType === 'multipart/form-data' ? 'form-data' : 'json',
318
348
  path: method.parameters?.filter((v)=>v.in === 'path').map((v)=>parameterToField(v, context)),
319
349
  query: method.parameters?.filter((v)=>v.in === 'query').map((v)=>parameterToField(v, context)),
320
350
  header: method.parameters?.filter((v)=>v.in === 'header').map((v)=>parameterToField(v, context)),
321
- body: bodySchema,
351
+ body: bodySchema && mediaType ? {
352
+ ...bodySchema,
353
+ mediaType: mediaType
354
+ } : undefined,
322
355
  schemas: context.references,
323
356
  proxyUrl: ctx.proxyUrl
324
357
  };
@@ -253,7 +253,7 @@ function useSchemaContext() {
253
253
  return ctx;
254
254
  }
255
255
 
256
- const APIPlayground = dynamic(()=>import('./index-client-69lE5jtH.js').then(function (n) { return n.i; }).then((mod)=>mod.APIPlayground));
256
+ const APIPlayground = dynamic(()=>import('./index-client-B6op0H4J.js').then(function (n) { return n.i; }).then((mod)=>mod.APIPlayground));
257
257
  function Root({ children, baseUrl, className, shikiOptions, ...props }) {
258
258
  return /*#__PURE__*/ jsx("div", {
259
259
  className: cn('flex flex-col gap-24 text-sm text-fd-muted-foreground', className),
@@ -1,22 +1,22 @@
1
- import { r as resolve } from './index-client-69lE5jtH.js';
1
+ import { r as resolve } from './index-client-B6op0H4J.js';
2
2
 
3
3
  /**
4
4
  * @param bodySchema - schema of body
5
5
  * @param references - defined references of schemas, needed for resolve cyclic references
6
6
  */ function createBrowserFetcher(bodySchema, references) {
7
7
  return {
8
- async fetch (input) {
8
+ async fetch (options) {
9
9
  const headers = new Headers();
10
- if (input.type !== 'form-data') headers.append('Content-Type', 'application/json');
11
- for (const key of Object.keys(input.header)){
12
- const paramValue = input.header[key];
10
+ if (options.body && options.body.mediaType !== 'multipart/form-data') headers.append('Content-Type', options.body.mediaType);
11
+ for (const key of Object.keys(options.header)){
12
+ const paramValue = options.header[key];
13
13
  if (typeof paramValue === 'string' && paramValue.length > 0) headers.append(key, paramValue.toString());
14
14
  }
15
- return fetch(input.url, {
16
- method: input.method,
15
+ return fetch(options.url, {
16
+ method: options.method,
17
17
  cache: 'no-cache',
18
18
  headers,
19
- body: bodySchema ? createBodyFromValue(input.type, input.body, bodySchema, references, input.dynamicFields ?? new Map()) : undefined,
19
+ body: bodySchema && options.body ? await createBodyFromValue(options.body.mediaType, options.body.value, bodySchema, references, options.dynamicFields ?? new Map()) : undefined,
20
20
  signal: AbortSignal.timeout(10 * 1000)
21
21
  }).then(async (res)=>{
22
22
  const contentType = res.headers.get('Content-Type') ?? '';
@@ -47,11 +47,18 @@ import { r as resolve } from './index-client-69lE5jtH.js';
47
47
  }
48
48
  /**
49
49
  * Create request body from value
50
- */ function createBodyFromValue(type, value, schema, references, dynamicFields) {
50
+ */ async function createBodyFromValue(mediaType, value, schema, references, dynamicFields) {
51
51
  const result = convertValue('body', value, schema, references, dynamicFields);
52
- if (type === 'json') {
52
+ if (mediaType === 'application/json') {
53
53
  return JSON.stringify(result);
54
54
  }
55
+ if (mediaType === 'application/xml') {
56
+ const { js2xml } = await import('xml-js');
57
+ return js2xml(result, {
58
+ compact: true,
59
+ spaces: 2
60
+ });
61
+ }
55
62
  const formData = new FormData();
56
63
  if (typeof result !== 'object' || !result) {
57
64
  throw new Error(`Unsupported body type: ${typeof result}, expected: object`);
@@ -88,8 +95,7 @@ import { r as resolve } from './index-client-69lE5jtH.js';
88
95
  return value.map((item, index)=>convertValue(`${fieldName}.${String(index)}`, item, resolve(schema.items, references), references, dynamicFields));
89
96
  }
90
97
  if (schema.type === 'switcher') {
91
- const schema = resolve(getDynamicFieldSchema(fieldName, dynamicFields), references);
92
- return convertValue(fieldName, value, schema, references, dynamicFields);
98
+ return convertValue(fieldName, value, resolve(getDynamicFieldSchema(fieldName, dynamicFields, Object.values(schema.items).at(0)), references), references, dynamicFields);
93
99
  }
94
100
  if (typeof value === 'object' && schema.type === 'object') {
95
101
  const entries = Object.keys(value).map((key)=>{
@@ -127,9 +133,11 @@ import { r as resolve } from './index-client-69lE5jtH.js';
127
133
  return String(value);
128
134
  }
129
135
  }
130
- function getDynamicFieldSchema(name, dynamicFields) {
136
+ function getDynamicFieldSchema(name, dynamicFields, defaultValue) {
131
137
  const field = dynamicFields.get(name);
132
- return field?.type === 'field' ? field.schema : {
138
+ if (field?.type === 'field') return field.schema;
139
+ if (defaultValue) return defaultValue;
140
+ return {
133
141
  type: 'null',
134
142
  isRequired: false
135
143
  };
@@ -5,7 +5,7 @@ import { forwardRef, useId, createContext, useContext, useState, useCallback, us
5
5
  import { FormProvider, Controller, useFormContext, useFieldArray, useForm, useWatch } from 'react-hook-form';
6
6
  import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
7
7
  import { cn, buttonVariants } from 'fumadocs-ui/components/api';
8
- import { C as ChevronDown, a as ChevronUp, b as Check, u as useSchemaContext, T as Trash2, P as Plus, c as useApiContext, d as CircleCheck, e as CircleX, S as SchemaContext } from './client-client-B9w--AKD.js';
8
+ import { C as ChevronDown, a as ChevronUp, b as Check, u as useSchemaContext, T as Trash2, P as Plus, c as useApiContext, d as CircleCheck, e as CircleX, S as SchemaContext } from './client-client-NS2E5Jdn.js';
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
  import { cva } from 'class-variance-authority';
11
11
  import { useOnChange } from 'fumadocs-core/utils/use-on-change';
@@ -735,7 +735,7 @@ function getStatusInfo(status) {
735
735
  };
736
736
  }
737
737
 
738
- function APIPlayground({ route, method = 'GET', bodyType, authorization, path = [], header = [], query = [], body, fields = {}, schemas, proxyUrl }) {
738
+ function APIPlayground({ route, method = 'GET', authorization, path = [], header = [], query = [], body, fields = {}, schemas, proxyUrl }) {
739
739
  const { baseUrl } = useApiContext();
740
740
  const dynamicRef = useRef(new Map());
741
741
  const form = useForm({
@@ -748,7 +748,7 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
748
748
  }
749
749
  });
750
750
  const testQuery = useQuery(async (input)=>{
751
- const fetcher = await import('./fetcher-DBWYlEEA.js').then((mod)=>mod.createBrowserFetcher(body, schemas));
751
+ const fetcher = await import('./fetcher-B7R5rjLv.js').then((mod)=>mod.createBrowserFetcher(body, schemas));
752
752
  const targetUrl = `${baseUrl ?? window.location.origin}${createPathnameFromInput(route, input.path, input.query)}`;
753
753
  let url;
754
754
  if (proxyUrl) {
@@ -764,10 +764,12 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
764
764
  header[authorization.name] = input.authorization;
765
765
  }
766
766
  return fetcher.fetch({
767
- type: bodyType,
768
767
  url: url.toString(),
769
768
  header,
770
- body: input.body,
769
+ body: body ? {
770
+ mediaType: body.mediaType,
771
+ value: input.body
772
+ } : undefined,
771
773
  dynamicFields: dynamicRef.current,
772
774
  method
773
775
  });
@@ -129,14 +129,15 @@ type RequestSchema = PrimitiveSchema | ArraySchema | ObjectSchema | SwitcherSche
129
129
  interface APIPlaygroundProps {
130
130
  route: string;
131
131
  method: string;
132
- bodyType: 'json' | 'form-data';
133
132
  authorization?: PrimitiveRequestField & {
134
133
  authType: string;
135
134
  };
136
135
  path?: PrimitiveRequestField[];
137
136
  query?: PrimitiveRequestField[];
138
137
  header?: PrimitiveRequestField[];
139
- body?: RequestSchema;
138
+ body?: RequestSchema & {
139
+ mediaType: string;
140
+ };
140
141
  schemas: Record<string, RequestSchema>;
141
142
  proxyUrl?: string;
142
143
  }
package/dist/ui/index.js CHANGED
@@ -3,8 +3,8 @@ import { cn } from 'fumadocs-ui/components/api';
3
3
  import { Fragment } from 'react';
4
4
  import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
5
5
  import { cva } from 'class-variance-authority';
6
- import { f as CopyRouteButton, B as BaseUrlSelect } from './client-client-B9w--AKD.js';
7
- export { A as APIPlayground, R as Root, u as useSchemaContext } from './client-client-B9w--AKD.js';
6
+ import { f as CopyRouteButton, B as BaseUrlSelect } from './client-client-NS2E5Jdn.js';
7
+ export { A as APIPlayground, R as Root, u as useSchemaContext } from './client-client-NS2E5Jdn.js';
8
8
 
9
9
  const badgeVariants = cva('rounded-xl border px-1.5 py-1 text-xs font-medium leading-[12px]', {
10
10
  variants: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "5.10.6",
3
+ "version": "5.11.1",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -45,18 +45,19 @@
45
45
  "react-hook-form": "^7.54.2",
46
46
  "remark": "^15.0.1",
47
47
  "remark-rehype": "^11.1.1",
48
- "shiki": "^1.24.3",
49
- "fumadocs-core": "14.6.8",
50
- "fumadocs-ui": "14.6.8"
48
+ "shiki": "^1.26.1",
49
+ "xml-js": "^1.6.11",
50
+ "fumadocs-core": "14.7.1",
51
+ "fumadocs-ui": "14.7.1"
51
52
  },
52
53
  "devDependencies": {
53
54
  "@types/js-yaml": "^4.0.9",
54
- "@types/node": "22.10.2",
55
+ "@types/node": "22.10.5",
55
56
  "@types/openapi-sampler": "^1.0.3",
56
57
  "@types/react": "^19.0.2",
57
- "bunchee": "^6.0.3",
58
+ "bunchee": "^6.2.0",
58
59
  "lucide-react": "^0.469.0",
59
- "next": "15.1.2",
60
+ "next": "15.1.3",
60
61
  "openapi-types": "^12.1.3",
61
62
  "eslint-config-custom": "0.0.0",
62
63
  "tsconfig": "0.0.0"