fumadocs-openapi 4.1.1 → 4.2.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.
@@ -43,6 +43,7 @@ function getDefaultValue(item, references) {
43
43
  resolve(Object.values(item.items)[0], references),
44
44
  references
45
45
  );
46
+ if (item.type === "file") return void 0;
46
47
  return String(item.defaultValue);
47
48
  }
48
49
  function getDefaultValues(field, context) {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OpenAPIV3 } from 'openapi-types';
2
- import { A as APIPlaygroundProps } from './playground-D8pYn13F.js';
3
- export { P as PrimitiveRequestField, a as ReferenceSchema, R as RequestSchema } from './playground-D8pYn13F.js';
2
+ import { A as APIPlaygroundProps } from './playground-vSsfCaVw.js';
3
+ export { P as PrimitiveRequestField, a as ReferenceSchema, R as RequestSchema } from './playground-vSsfCaVw.js';
4
4
 
5
5
  interface ResponsesProps {
6
6
  items: string[];
package/dist/index.js CHANGED
@@ -140,9 +140,12 @@ function noRef(v) {
140
140
  return v;
141
141
  }
142
142
  function getPreferredMedia(body) {
143
- if (Object.keys(body).length === 0) return void 0;
144
- if ("application/json" in body) return body["application/json"];
145
- return Object.values(body)[0];
143
+ const type = getPreferredType(body);
144
+ if (type) return body[type];
145
+ }
146
+ function getPreferredType(body) {
147
+ if ("application/json" in body) return "application/json";
148
+ return Object.keys(body)[0];
146
149
  }
147
150
  function toSampleInput(value) {
148
151
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
@@ -296,22 +299,25 @@ function getScheme(requirement, document) {
296
299
  // src/render/playground.ts
297
300
  function renderPlayground(path, method, ctx) {
298
301
  let currentId = 0;
302
+ const bodyContent = noRef(method.requestBody)?.content;
303
+ const mediaType = bodyContent ? getPreferredType(bodyContent) : void 0;
299
304
  const context = {
305
+ allowFile: mediaType === "multipart/form-data",
300
306
  schema: {},
301
307
  nextId() {
302
308
  return String(currentId++);
303
309
  },
304
310
  registered: /* @__PURE__ */ new WeakMap()
305
311
  };
306
- const body = method.requestBody ? getPreferredMedia(noRef(method.requestBody).content) : void 0;
307
312
  return ctx.renderer.APIPlayground({
308
313
  authorization: getAuthorizationField(method, ctx),
309
314
  method: method.method,
310
315
  route: path,
316
+ bodyType: mediaType === "multipart/form-data" ? "form-data" : "json",
311
317
  path: method.parameters.filter((v) => v.in === "path").map((v) => parameterToField(v, context)),
312
318
  query: method.parameters.filter((v) => v.in === "query").map((v) => parameterToField(v, context)),
313
319
  header: method.parameters.filter((v) => v.in === "header").map((v) => parameterToField(v, context)),
314
- body: body?.schema ? toSchema(noRef(body.schema), true, context) : void 0,
320
+ body: bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(noRef(bodyContent[mediaType].schema), true, context) : void 0,
315
321
  schemas: context.schema
316
322
  });
317
323
  }
@@ -424,6 +430,13 @@ function toSchema(schema, required, ctx) {
424
430
  isRequired: false
425
431
  };
426
432
  }
433
+ if (ctx.allowFile && schema.type === "string" && schema.format === "binary") {
434
+ return {
435
+ type: "file",
436
+ isRequired: required,
437
+ description: schema.description ?? schema.title
438
+ };
439
+ }
427
440
  return {
428
441
  type: schema.type === "integer" ? "number" : schema.type,
429
442
  defaultValue: schema.example ?? "",
@@ -550,7 +563,7 @@ function render(name, schema, ctx) {
550
563
  return renderer.Property(
551
564
  {
552
565
  name,
553
- type: getSchemaType(schema),
566
+ type: getSchemaType(schema, ctx),
554
567
  required: ctx.required,
555
568
  deprecated: schema.deprecated
556
569
  },
@@ -583,22 +596,24 @@ function isComplexType(schema) {
583
596
  if (schema.anyOf ?? schema.oneOf ?? schema.allOf) return true;
584
597
  return isObject(schema) || schema.type === "array";
585
598
  }
586
- function getSchemaType(schema) {
599
+ function getSchemaType(schema, ctx) {
587
600
  if (schema.nullable) {
588
- const type = getSchemaType({ ...schema, nullable: false });
601
+ const type = getSchemaType({ ...schema, nullable: false }, ctx);
589
602
  return type === "unknown" ? "null" : `${type} | null`;
590
603
  }
591
604
  if (schema.title) return schema.title;
592
605
  if (schema.type === "array")
593
- return `array<${getSchemaType(noRef(schema.items))}>`;
606
+ return `array<${getSchemaType(noRef(schema.items), ctx)}>`;
594
607
  if (schema.oneOf)
595
- return schema.oneOf.map((one) => getSchemaType(noRef(one))).join(" | ");
608
+ return schema.oneOf.map((one) => getSchemaType(noRef(one), ctx)).join(" | ");
596
609
  if (schema.allOf)
597
- return schema.allOf.map((one) => getSchemaType(noRef(one))).join(" & ");
598
- if (schema.not) return `not ${getSchemaType(noRef(schema.not))}`;
610
+ return schema.allOf.map((one) => getSchemaType(noRef(one), ctx)).join(" & ");
611
+ if (schema.not) return `not ${getSchemaType(noRef(schema.not), ctx)}`;
599
612
  if (schema.anyOf) {
600
- return `Any properties in ${schema.anyOf.map((one) => getSchemaType(noRef(one))).join(", ")}`;
613
+ return `Any properties in ${schema.anyOf.map((one) => getSchemaType(noRef(one), ctx)).join(", ")}`;
601
614
  }
615
+ if (schema.type === "string" && schema.format === "binary" && ctx.allowFile)
616
+ return "File";
602
617
  if (schema.type) return schema.type;
603
618
  if (isObject(schema)) return "object";
604
619
  return "unknown";
@@ -627,17 +642,19 @@ async function renderOperation(path, method, ctx, noTitle = false) {
627
642
  info.push(getAuthSection(security, ctx));
628
643
  }
629
644
  if (body) {
630
- const bodySchema = getPreferredMedia(body.content)?.schema;
631
- if (!bodySchema) throw new Error();
645
+ const type = getPreferredType(body.content);
646
+ if (!type)
647
+ throw new Error(`No supported media type for body content: ${path}`);
632
648
  info.push(
633
649
  heading(level, `Request Body ${!body.required ? "(Optional)" : ""}`),
634
650
  p(body.description),
635
- schemaElement("body", noRef(bodySchema), {
651
+ schemaElement("body", noRef(body.content[type].schema ?? {}), {
636
652
  parseObject: true,
637
653
  readOnly: method.method === "GET",
638
654
  writeOnly: method.method !== "GET",
639
655
  required: body.required ?? false,
640
- render: ctx
656
+ render: ctx,
657
+ allowFile: type === "multipart/form-data"
641
658
  })
642
659
  );
643
660
  }
@@ -660,7 +677,8 @@ async function renderOperation(path, method, ctx, noTitle = false) {
660
677
  readOnly: method.method === "GET",
661
678
  writeOnly: method.method !== "GET",
662
679
  required: param.required ?? false,
663
- render: ctx
680
+ render: ctx,
681
+ allowFile: false
664
682
  }
665
683
  );
666
684
  const groupName = {
@@ -5,7 +5,7 @@ import {
5
5
  resolve,
6
6
  useApiContext,
7
7
  useSchemaContext
8
- } from "./chunk-BYW6XCQ2.js";
8
+ } from "./chunk-55ZG37EE.js";
9
9
 
10
10
  // src/ui/playground.tsx
11
11
  import {
@@ -108,8 +108,30 @@ FormDescription.displayName = "FormDescription";
108
108
 
109
109
  // src/ui/fetcher.ts
110
110
  import { CircleCheckIcon, CircleXIcon } from "lucide-react";
111
- function createBodyFromValue(value, schema, references, dynamic) {
112
- return convertValue("body", value, schema, references, dynamic);
111
+ function createBodyFromValue(type, value, schema, references, dynamic) {
112
+ const result = convertValue("body", value, schema, references, dynamic);
113
+ if (type === "json") {
114
+ return JSON.stringify(result);
115
+ }
116
+ const formData = new FormData();
117
+ if (typeof result !== "object" || !result) {
118
+ throw new Error(
119
+ `Unsupported body type: ${typeof result}, expected: object`
120
+ );
121
+ }
122
+ for (const key of Object.keys(result)) {
123
+ const prop = result[key];
124
+ if (typeof prop === "object" && prop instanceof File) {
125
+ formData.set(key, prop);
126
+ }
127
+ if (Array.isArray(prop) && prop.every((item) => item instanceof File)) {
128
+ for (const item of prop) {
129
+ formData.append(key, item);
130
+ }
131
+ }
132
+ formData.set(key, JSON.stringify(prop));
133
+ }
134
+ return formData;
113
135
  }
114
136
  function convertValue(fieldName, value, schema, references, dynamic) {
115
137
  const isEmpty = value === "" || value === void 0 || value === null;
@@ -162,6 +184,8 @@ function convertValue(fieldName, value, schema, references, dynamic) {
162
184
  return Number(value);
163
185
  case "boolean":
164
186
  return value === "null" ? void 0 : value === "true";
187
+ case "file":
188
+ return value;
165
189
  default:
166
190
  return String(value);
167
191
  }
@@ -679,6 +703,30 @@ function NormalInput({
679
703
  ...props
680
704
  }) {
681
705
  const { control } = useFormContext2();
706
+ if (field.type === "file") {
707
+ return /* @__PURE__ */ jsx4(
708
+ FormField,
709
+ {
710
+ control,
711
+ name: fieldName,
712
+ render: ({ field: { value: _value, onChange, ...restField } }) => /* @__PURE__ */ jsxs2(FormItem, { ...props, children: [
713
+ header,
714
+ /* @__PURE__ */ jsx4(FormControl, { children: /* @__PURE__ */ jsx4(
715
+ "input",
716
+ {
717
+ type: "file",
718
+ multiple: false,
719
+ onChange: (e) => {
720
+ if (!e.target.files) return;
721
+ onChange(e.target.files.item(0));
722
+ },
723
+ ...restField
724
+ }
725
+ ) })
726
+ ] })
727
+ }
728
+ );
729
+ }
682
730
  if (field.type === "boolean") {
683
731
  return /* @__PURE__ */ jsx4(
684
732
  FormField,
@@ -814,6 +862,7 @@ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
814
862
  function APIPlayground({
815
863
  route,
816
864
  method = "GET",
865
+ bodyType,
817
866
  authorization,
818
867
  path = [],
819
868
  header = [],
@@ -835,7 +884,7 @@ function APIPlayground({
835
884
  }
836
885
  });
837
886
  const testQuery = useSWRImmutable(
838
- input ? [baseUrl, route, method, input] : null,
887
+ input ? [baseUrl, route, method, input, bodyType] : null,
839
888
  async () => {
840
889
  if (!input) return;
841
890
  let pathname = route;
@@ -844,7 +893,7 @@ function APIPlayground({
844
893
  if (typeof paramValue === "string")
845
894
  pathname = pathname.replace(`{${key}}`, paramValue);
846
895
  });
847
- const url = new URL(pathname, baseUrl ?? window.location.origin);
896
+ const url = new URL(`${baseUrl ?? window.location.origin}${pathname}`);
848
897
  Object.keys(input.query).forEach((key) => {
849
898
  const paramValue = input.query[key];
850
899
  if (typeof paramValue === "string")
@@ -860,11 +909,17 @@ function APIPlayground({
860
909
  const paramValue = input.header[key];
861
910
  if (typeof paramValue === "string") headers.append(key, paramValue);
862
911
  });
863
- const bodyValue = body && input.body && Object.keys(input.body).length > 0 ? createBodyFromValue(input.body, body, schemas, dynamicRef.current) : void 0;
912
+ const bodyValue = body && input.body && Object.keys(input.body).length > 0 ? createBodyFromValue(
913
+ bodyType,
914
+ input.body,
915
+ body,
916
+ schemas,
917
+ dynamicRef.current
918
+ ) : void 0;
864
919
  const response = await fetch(url, {
865
920
  method,
866
921
  headers,
867
- body: bodyValue ? JSON.stringify(bodyValue) : void 0
922
+ body: bodyValue
868
923
  });
869
924
  const data = await response.json().catch(() => void 0);
870
925
  return { status: response.status, data };
@@ -22,6 +22,9 @@ interface ArraySchema extends BaseSchema {
22
22
  */
23
23
  items: string | RequestSchema;
24
24
  }
25
+ interface FileSchema extends BaseSchema {
26
+ type: 'file';
27
+ }
25
28
  interface ObjectSchema extends BaseSchema {
26
29
  type: 'object';
27
30
  properties: Record<string, ReferenceSchema>;
@@ -37,10 +40,11 @@ interface SwitcherSchema extends BaseSchema {
37
40
  interface NullSchema extends BaseSchema {
38
41
  type: 'null';
39
42
  }
40
- type RequestSchema = PrimitiveSchema | ArraySchema | ObjectSchema | SwitcherSchema | NullSchema;
43
+ type RequestSchema = PrimitiveSchema | ArraySchema | ObjectSchema | SwitcherSchema | NullSchema | FileSchema;
41
44
  interface APIPlaygroundProps {
42
45
  route: string;
43
46
  method: string;
47
+ bodyType: 'json' | 'form-data';
44
48
  authorization?: PrimitiveRequestField;
45
49
  path?: PrimitiveRequestField[];
46
50
  query?: PrimitiveRequestField[];
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactElement, HTMLAttributes, ReactNode, MutableRefObject } from 'react';
3
3
  import { FieldPath, ControllerRenderProps, ControllerFieldState, UseFormStateReturn } from 'react-hook-form';
4
- import { R as RequestSchema, a as ReferenceSchema, A as APIPlaygroundProps, P as PrimitiveRequestField } from '../playground-D8pYn13F.js';
4
+ import { R as RequestSchema, a as ReferenceSchema, A as APIPlaygroundProps, P as PrimitiveRequestField } from '../playground-vSsfCaVw.js';
5
5
  import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
6
6
 
7
7
  interface FormValues {
package/dist/ui/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  getBadgeColor,
6
6
  useApiContext,
7
7
  useSchemaContext
8
- } from "../chunk-BYW6XCQ2.js";
8
+ } from "../chunk-55ZG37EE.js";
9
9
 
10
10
  // src/ui/index.ts
11
11
  import { Tab, Tabs } from "fumadocs-ui/components/tabs";
@@ -174,7 +174,7 @@ function CopyRouteButton({
174
174
 
175
175
  // src/ui/index.ts
176
176
  var APIPlayground = dynamic(
177
- () => import("../playground-EVJUDTB3.js").then((mod) => mod.APIPlayground)
177
+ () => import("../playground-TN274MLB.js").then((mod) => mod.APIPlayground)
178
178
  );
179
179
  var Responses = Tabs;
180
180
  var Response = Tab;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "4.1.1",
3
+ "version": "4.2.1",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -49,8 +49,8 @@
49
49
  "react-hook-form": "^7.52.1",
50
50
  "shiki": "^1.11.1",
51
51
  "swr": "^2.2.5",
52
- "fumadocs-core": "13.0.1",
53
- "fumadocs-ui": "13.0.1"
52
+ "fumadocs-core": "13.0.2",
53
+ "fumadocs-ui": "13.0.2"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/js-yaml": "^4.0.9",