fumadocs-openapi 4.1.0 → 4.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/{chunk-BYW6XCQ2.js → chunk-55ZG37EE.js} +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +36 -18
- package/dist/{playground-EVJUDTB3.js → playground-TN274MLB.js} +62 -7
- package/dist/{playground-D8pYn13F.d.ts → playground-vSsfCaVw.d.ts} +5 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +2 -2
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
-
import { A as APIPlaygroundProps } from './playground-
|
|
3
|
-
export { P as PrimitiveRequestField, a as ReferenceSchema, R as RequestSchema } from './playground-
|
|
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
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
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:
|
|
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
|
|
631
|
-
if (!
|
|
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(
|
|
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-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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[];
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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.
|
|
3
|
+
"version": "4.2.0",
|
|
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.
|
|
53
|
-
"fumadocs-ui": "13.0.
|
|
52
|
+
"fumadocs-core": "13.0.1",
|
|
53
|
+
"fumadocs-ui": "13.0.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/js-yaml": "^4.0.9",
|