fumadocs-openapi 5.9.0 → 5.10.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
@@ -58,6 +58,7 @@ interface APIPlaygroundProps {
58
58
  header?: PrimitiveRequestField[];
59
59
  body?: RequestSchema;
60
60
  schemas: Record<string, RequestSchema>;
61
+ proxyUrl?: string;
61
62
  }
62
63
 
63
64
  interface ResponsesProps {
@@ -191,6 +192,10 @@ type Awaitable<T> = T | Promise<T>;
191
192
  */
192
193
  type DereferenceMap = Map<unknown, string>;
193
194
  interface RenderContext {
195
+ /**
196
+ * The url of proxy to avoid CORS issues
197
+ */
198
+ proxyUrl?: string;
194
199
  renderer: Renderer;
195
200
  /**
196
201
  * dereferenced schema
@@ -2,6 +2,7 @@ import { ComponentType, ReactNode, FC } from 'react';
2
2
  import { OpenAPIV3_1, OpenAPIV3 } from 'openapi-types';
3
3
  import Slugger from 'github-slugger';
4
4
  import { CodeToHastOptionsCommon, CodeOptionsThemes, BuiltinTheme } from 'shiki';
5
+ import { NextRequest } from 'next/server';
5
6
  import { BuildPageTreeOptions } from 'fumadocs-core/source';
6
7
 
7
8
  interface BaseRequestField {
@@ -59,6 +60,7 @@ interface APIPlaygroundProps {
59
60
  header?: PrimitiveRequestField[];
60
61
  body?: RequestSchema;
61
62
  schemas: Record<string, RequestSchema>;
63
+ proxyUrl?: string;
62
64
  }
63
65
 
64
66
  interface ResponsesProps {
@@ -177,6 +179,10 @@ type Awaitable<T> = T | Promise<T>;
177
179
  */
178
180
  type DereferenceMap = Map<unknown, string>;
179
181
  interface RenderContext {
182
+ /**
183
+ * The url of proxy to avoid CORS issues
184
+ */
185
+ proxyUrl?: string;
180
186
  renderer: Renderer;
181
187
  /**
182
188
  * dereferenced schema
@@ -204,7 +210,8 @@ interface RenderContext {
204
210
 
205
211
  type DocumentInput = string | OpenAPIV3_1.Document | OpenAPIV3.Document;
206
212
 
207
- interface ApiPageProps extends Pick<RenderContext, 'generateCodeSamples' | 'generateTypeScriptSchema' | 'shikiOptions'> {
213
+ type ApiPageContextProps = Pick<Partial<RenderContext>, 'shikiOptions' | 'generateTypeScriptSchema' | 'generateCodeSamples' | 'proxyUrl'>;
214
+ interface ApiPageProps extends ApiPageContextProps {
208
215
  document: DocumentInput;
209
216
  hasHead: boolean;
210
217
  renderer?: Partial<Renderer>;
@@ -227,6 +234,12 @@ interface OperationItem {
227
234
  method: OpenAPIV3_1.HttpMethods;
228
235
  }
229
236
 
237
+ declare const keys: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"];
238
+ type Proxy = {
239
+ [K in (typeof keys)[number]]: (req: NextRequest) => Promise<Response>;
240
+ };
241
+ declare function createProxy(allowedUrls?: string[]): Proxy;
242
+
230
243
  interface OpenAPIOptions extends Omit<Partial<ApiPageProps>, 'document'> {
231
244
  /**
232
245
  * @deprecated Pass document to `APIPage` instead
@@ -235,6 +248,7 @@ interface OpenAPIOptions extends Omit<Partial<ApiPageProps>, 'document'> {
235
248
  }
236
249
  interface OpenAPIServer {
237
250
  APIPage: FC<ApiPageProps>;
251
+ createProxy: typeof createProxy;
238
252
  }
239
253
  declare function createOpenAPI(options?: OpenAPIOptions): OpenAPIServer;
240
254
 
@@ -302,13 +302,14 @@ function Playground({ path, method, ctx }) {
302
302
  const mediaType = bodyContent ? getPreferredType(bodyContent) : undefined;
303
303
  const context = {
304
304
  allowFile: mediaType === 'multipart/form-data',
305
- schema: {},
305
+ references: {},
306
306
  nextId () {
307
307
  return String(currentId++);
308
308
  },
309
309
  registered: new WeakMap(),
310
310
  render: ctx
311
311
  };
312
+ const bodySchema = bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(bodyContent[mediaType].schema, true, context) : undefined;
312
313
  const props = {
313
314
  authorization: getAuthorizationField(method, ctx),
314
315
  method: method.method,
@@ -317,8 +318,9 @@ function Playground({ path, method, ctx }) {
317
318
  path: method.parameters?.filter((v)=>v.in === 'path').map((v)=>parameterToField(v, context)),
318
319
  query: method.parameters?.filter((v)=>v.in === 'query').map((v)=>parameterToField(v, context)),
319
320
  header: method.parameters?.filter((v)=>v.in === 'header').map((v)=>parameterToField(v, context)),
320
- body: bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(bodyContent[mediaType].schema, true, context) : undefined,
321
- schemas: context.schema
321
+ body: bodySchema,
322
+ schemas: context.references,
323
+ proxyUrl: ctx.proxyUrl
322
324
  };
323
325
  return /*#__PURE__*/ jsx(ctx.renderer.APIPlayground, {
324
326
  ...props
@@ -344,7 +346,7 @@ function getIdFromSchema(schema, required, ctx) {
344
346
  if (registered === undefined) {
345
347
  const id = ctx.nextId();
346
348
  ctx.registered.set(schema, id);
347
- ctx.schema[id] = toSchema(schema, required, ctx);
349
+ ctx.references[id] = toSchema(schema, required, ctx);
348
350
  return id;
349
351
  }
350
352
  return registered;
@@ -553,7 +555,7 @@ function heading(depth, child, ctx) {
553
555
  return result;
554
556
  }
555
557
 
556
- const keys = {
558
+ const keys$1 = {
557
559
  default: 'Default',
558
560
  minimum: 'Minimum',
559
561
  maximum: 'Maximum',
@@ -617,7 +619,7 @@ function Schema({ name, schema, ctx }) {
617
619
  text: schema.description
618
620
  }, "description"));
619
621
  const fields = [];
620
- for (const [key, value] of Object.entries(keys)){
622
+ for (const [key, value] of Object.entries(keys$1)){
621
623
  if (key in schema) {
622
624
  fields.push({
623
625
  key: value,
@@ -1262,6 +1264,7 @@ async function getContext({ document, dereferenceMap }, options = {}) {
1262
1264
  return {
1263
1265
  document: document,
1264
1266
  dereferenceMap,
1267
+ proxyUrl: options.proxyUrl,
1265
1268
  renderer: {
1266
1269
  ...createRenders(options.shikiOptions),
1267
1270
  ...options.renderer
@@ -1275,8 +1278,69 @@ async function getContext({ document, dereferenceMap }, options = {}) {
1275
1278
  };
1276
1279
  }
1277
1280
 
1281
+ const keys = [
1282
+ 'GET',
1283
+ 'POST',
1284
+ 'PUT',
1285
+ 'DELETE',
1286
+ 'PATCH',
1287
+ 'HEAD'
1288
+ ];
1289
+ function createProxy(allowedUrls) {
1290
+ const handlers = {};
1291
+ async function handler(req) {
1292
+ const url = req.nextUrl.searchParams.get('url');
1293
+ if (!url) {
1294
+ return Response.json('A `url` query parameter is required for proxy url', {
1295
+ status: 400
1296
+ });
1297
+ }
1298
+ if (allowedUrls && allowedUrls.every((allowedUrl)=>!allowedUrl.startsWith(url))) {
1299
+ return Response.json('The given `url` query parameter is not allowed', {
1300
+ status: 400
1301
+ });
1302
+ }
1303
+ const clonedReq = new Request(url, {
1304
+ ...req,
1305
+ cache: 'no-cache',
1306
+ mode: 'cors'
1307
+ });
1308
+ clonedReq.headers.forEach((_value, originalKey)=>{
1309
+ const key = originalKey.toLowerCase();
1310
+ const notAllowed = key === 'origin';
1311
+ if (notAllowed) {
1312
+ clonedReq.headers.delete(originalKey);
1313
+ }
1314
+ });
1315
+ const res = await fetch(clonedReq).catch((e)=>new Error(e.toString()));
1316
+ if (res instanceof Error) {
1317
+ return Response.json(`Failed to proxy request: ${res.message}`, {
1318
+ status: 400
1319
+ });
1320
+ }
1321
+ const headers = new Headers(res.headers);
1322
+ headers.forEach((_value, originalKey)=>{
1323
+ const key = originalKey.toLowerCase();
1324
+ const notAllowed = key.startsWith('access-control-') || key === 'content-encoding';
1325
+ if (notAllowed) {
1326
+ headers.delete(originalKey);
1327
+ }
1328
+ });
1329
+ headers.set('X-Forwarded-Host', res.url);
1330
+ return new Response(res.body, {
1331
+ ...res,
1332
+ headers
1333
+ });
1334
+ }
1335
+ for (const key of keys){
1336
+ handlers[key] = handler;
1337
+ }
1338
+ return handlers;
1339
+ }
1340
+
1278
1341
  function createOpenAPI(options = {}) {
1279
1342
  return {
1343
+ createProxy,
1280
1344
  APIPage (props) {
1281
1345
  return /*#__PURE__*/ jsx(APIPage, {
1282
1346
  ...options,
@@ -6,7 +6,7 @@ import dynamic from 'next/dynamic';
6
6
  import { useOnChange } from 'fumadocs-core/utils/use-on-change';
7
7
 
8
8
  /**
9
- * @license lucide-react v0.468.0 - ISC
9
+ * @license lucide-react v0.469.0 - ISC
10
10
  *
11
11
  * This source code is licensed under the ISC license.
12
12
  * See the LICENSE file in the root directory of this source tree.
@@ -16,7 +16,7 @@ const mergeClasses = (...classes)=>classes.filter((className, index, array)=>{
16
16
  }).join(" ").trim();
17
17
 
18
18
  /**
19
- * @license lucide-react v0.468.0 - ISC
19
+ * @license lucide-react v0.469.0 - ISC
20
20
  *
21
21
  * This source code is licensed under the ISC license.
22
22
  * See the LICENSE file in the root directory of this source tree.
@@ -253,7 +253,7 @@ function useSchemaContext() {
253
253
  return ctx;
254
254
  }
255
255
 
256
- const APIPlayground = dynamic(()=>import('./playground-client-HUgWUI95.js').then((mod)=>mod.APIPlayground));
256
+ const APIPlayground = dynamic(()=>import('./index-client-69lE5jtH.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),
@@ -309,4 +309,4 @@ function BaseUrlSelect({ baseUrls }) {
309
309
  });
310
310
  }
311
311
 
312
- export { APIPlayground as A, BaseUrlSelect as B, CircleCheck as C, Plus as P, Root as R, SchemaContext as S, Trash2 as T, CircleX as a, ChevronDown as b, ChevronUp as c, Check as d, useApiContext as e, CopyRouteButton as f, useSchemaContext as u };
312
+ export { APIPlayground as A, BaseUrlSelect as B, ChevronDown as C, Plus as P, Root as R, SchemaContext as S, Trash2 as T, ChevronUp as a, Check as b, useApiContext as c, CircleCheck as d, CircleX as e, CopyRouteButton as f, useSchemaContext as u };
@@ -0,0 +1,138 @@
1
+ import { r as resolve } from './index-client-69lE5jtH.js';
2
+
3
+ /**
4
+ * @param bodySchema - schema of body
5
+ * @param references - defined references of schemas, needed for resolve cyclic references
6
+ */ function createBrowserFetcher(bodySchema, references) {
7
+ return {
8
+ async fetch (input) {
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];
13
+ if (typeof paramValue === 'string' && paramValue.length > 0) headers.append(key, paramValue.toString());
14
+ }
15
+ return fetch(input.url, {
16
+ method: input.method,
17
+ cache: 'no-cache',
18
+ headers,
19
+ body: bodySchema ? createBodyFromValue(input.type, input.body, bodySchema, references, input.dynamicFields ?? new Map()) : undefined,
20
+ signal: AbortSignal.timeout(10 * 1000)
21
+ }).then(async (res)=>{
22
+ const contentType = res.headers.get('Content-Type') ?? '';
23
+ let type;
24
+ let data;
25
+ if (contentType.startsWith('application/json')) {
26
+ type = 'json';
27
+ data = await res.json();
28
+ } else {
29
+ type = contentType.startsWith('text/html') ? 'html' : 'text';
30
+ data = await res.text();
31
+ }
32
+ return {
33
+ status: res.status,
34
+ type,
35
+ data
36
+ };
37
+ }).catch((e)=>{
38
+ const message = e instanceof Error ? `[${e.name}] ${e.message}` : e.toString();
39
+ return {
40
+ status: 400,
41
+ type: 'text',
42
+ data: `Client side error: ${message}`
43
+ };
44
+ });
45
+ }
46
+ };
47
+ }
48
+ /**
49
+ * Create request body from value
50
+ */ function createBodyFromValue(type, value, schema, references, dynamicFields) {
51
+ const result = convertValue('body', value, schema, references, dynamicFields);
52
+ if (type === 'json') {
53
+ return JSON.stringify(result);
54
+ }
55
+ const formData = new FormData();
56
+ if (typeof result !== 'object' || !result) {
57
+ throw new Error(`Unsupported body type: ${typeof result}, expected: object`);
58
+ }
59
+ for (const key of Object.keys(result)){
60
+ const prop = result[key];
61
+ if (typeof prop === 'object' && prop instanceof File) {
62
+ formData.set(key, prop);
63
+ }
64
+ if (Array.isArray(prop) && prop.every((item)=>item instanceof File)) {
65
+ for (const item of prop){
66
+ formData.append(key, item);
67
+ }
68
+ }
69
+ if (prop && !(prop instanceof File)) {
70
+ formData.set(key, JSON.stringify(prop));
71
+ }
72
+ }
73
+ return formData;
74
+ }
75
+ /**
76
+ * Convert a value (object or string) to the corresponding type of schema
77
+ *
78
+ * @param fieldName - field name of value
79
+ * @param value - the original value
80
+ * @param schema - the schema of field
81
+ * @param references - schema references
82
+ * @param dynamicFields - Dynamic references
83
+ */ function convertValue(fieldName, value, schema, references, dynamicFields) {
84
+ const isEmpty = value === '' || value === undefined || value === null;
85
+ if (isEmpty && schema.isRequired) return schema.type === 'boolean' ? false : '';
86
+ else if (isEmpty) return undefined;
87
+ if (Array.isArray(value) && schema.type === 'array') {
88
+ return value.map((item, index)=>convertValue(`${fieldName}.${String(index)}`, item, resolve(schema.items, references), references, dynamicFields));
89
+ }
90
+ if (schema.type === 'switcher') {
91
+ const schema = resolve(getDynamicFieldSchema(fieldName, dynamicFields), references);
92
+ return convertValue(fieldName, value, schema, references, dynamicFields);
93
+ }
94
+ if (typeof value === 'object' && schema.type === 'object') {
95
+ const entries = Object.keys(value).map((key)=>{
96
+ const prop = value[key];
97
+ const propFieldName = `${fieldName}.${key}`;
98
+ if (key in schema.properties) {
99
+ return [
100
+ key,
101
+ convertValue(propFieldName, prop, resolve(schema.properties[key], references), references, dynamicFields)
102
+ ];
103
+ }
104
+ if (schema.additionalProperties) {
105
+ const schema = resolve(getDynamicFieldSchema(propFieldName, dynamicFields), references);
106
+ return [
107
+ key,
108
+ convertValue(propFieldName, prop, schema, references, dynamicFields)
109
+ ];
110
+ }
111
+ console.warn('Could not resolve field', propFieldName, dynamicFields);
112
+ return [
113
+ key,
114
+ prop
115
+ ];
116
+ });
117
+ return Object.fromEntries(entries);
118
+ }
119
+ switch(schema.type){
120
+ case 'number':
121
+ return Number(value);
122
+ case 'boolean':
123
+ return value === 'null' ? undefined : value === 'true';
124
+ case 'file':
125
+ return value; // file
126
+ default:
127
+ return String(value);
128
+ }
129
+ }
130
+ function getDynamicFieldSchema(name, dynamicFields) {
131
+ const field = dynamicFields.get(name);
132
+ return field?.type === 'field' ? field.schema : {
133
+ type: 'null',
134
+ isRequired: false
135
+ };
136
+ }
137
+
138
+ export { createBodyFromValue, createBrowserFetcher };
@@ -5,13 +5,12 @@ 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 CircleCheck, a as CircleX, b as ChevronDown, c as ChevronUp, d as Check, u as useSchemaContext, T as Trash2, P as Plus, e as useApiContext, S as SchemaContext } from './client-client-DopF7PSu.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-B9w--AKD.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';
12
12
  import * as SelectPrimitive from '@radix-ui/react-select';
13
- import * as Base from 'fumadocs-ui/components/codeblock';
14
- import { useShiki } from 'fumadocs-core/utils/use-shiki';
13
+ import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock';
15
14
 
16
15
  const Form = FormProvider;
17
16
  const FormFieldContext = /*#__PURE__*/ createContext({
@@ -102,145 +101,6 @@ FormDescription.displayName = 'FormDescription';
102
101
  };
103
102
  }
104
103
 
105
- /**
106
- * Create request body from value
107
- */ function createBodyFromValue(type, value, schema, references, dynamic) {
108
- const result = convertValue('body', value, schema, references, dynamic);
109
- if (type === 'json') {
110
- return JSON.stringify(result);
111
- }
112
- const formData = new FormData();
113
- if (typeof result !== 'object' || !result) {
114
- throw new Error(`Unsupported body type: ${typeof result}, expected: object`);
115
- }
116
- for (const key of Object.keys(result)){
117
- const prop = result[key];
118
- if (typeof prop === 'object' && prop instanceof File) {
119
- formData.set(key, prop);
120
- }
121
- if (Array.isArray(prop) && prop.every((item)=>item instanceof File)) {
122
- for (const item of prop){
123
- formData.append(key, item);
124
- }
125
- }
126
- if (prop && !(prop instanceof File)) {
127
- formData.set(key, JSON.stringify(prop));
128
- }
129
- }
130
- return formData;
131
- }
132
- /**
133
- * Convert a value (object or string) to the corresponding type of schema
134
- *
135
- * @param fieldName - field name of value
136
- * @param value - the original value
137
- * @param schema - the schema of field
138
- * @param references - schema references
139
- * @param dynamic - Dynamic references
140
- */ function convertValue(fieldName, value, schema, references, dynamic) {
141
- const isEmpty = value === '' || value === undefined || value === null;
142
- if (isEmpty && schema.isRequired) return schema.type === 'boolean' ? false : '';
143
- else if (isEmpty) return undefined;
144
- if (Array.isArray(value) && schema.type === 'array') {
145
- return value.map((item, index)=>convertValue(`${fieldName}.${String(index)}`, item, resolve(schema.items, references), references, dynamic));
146
- }
147
- if (schema.type === 'switcher') {
148
- return convertDynamicValue(fieldName, value, references, dynamic);
149
- }
150
- if (typeof value === 'object' && schema.type === 'object') {
151
- const entries = Object.keys(value).map((key)=>{
152
- const prop = value[key];
153
- const propFieldName = `${fieldName}.${key}`;
154
- if (key in schema.properties) {
155
- return [
156
- key,
157
- convertValue(propFieldName, prop, resolve(schema.properties[key], references), references, dynamic)
158
- ];
159
- }
160
- if (schema.additionalProperties) {
161
- return [
162
- key,
163
- convertDynamicValue(propFieldName, prop, references, dynamic)
164
- ];
165
- }
166
- console.warn('Could not resolve field', propFieldName, dynamic);
167
- return [
168
- key,
169
- prop
170
- ];
171
- });
172
- return Object.fromEntries(entries);
173
- }
174
- switch(schema.type){
175
- case 'number':
176
- return Number(value);
177
- case 'boolean':
178
- return value === 'null' ? undefined : value === 'true';
179
- case 'file':
180
- return value; // file
181
- default:
182
- return String(value);
183
- }
184
- }
185
- function convertDynamicValue(fieldName, value, references, dynamic) {
186
- const fieldDynamic = dynamic.get(fieldName);
187
- return convertValue(fieldName, value, fieldDynamic?.type === 'field' ? resolve(fieldDynamic.schema, references) : {
188
- type: 'null',
189
- isRequired: false
190
- }, references, dynamic);
191
- }
192
- const statusMap = {
193
- 400: {
194
- description: 'Bad Request',
195
- color: 'text-red-500',
196
- icon: CircleX
197
- },
198
- 401: {
199
- description: 'Unauthorized',
200
- color: 'text-red-500',
201
- icon: CircleX
202
- },
203
- 403: {
204
- description: 'Forbidden',
205
- color: 'text-red-500',
206
- icon: CircleX
207
- },
208
- 404: {
209
- description: 'Not Found',
210
- color: 'text-fd-muted-foreground',
211
- icon: CircleX
212
- },
213
- 500: {
214
- description: 'Internal Server Error',
215
- color: 'text-red-500',
216
- icon: CircleX
217
- }
218
- };
219
- function getStatusInfo(status) {
220
- if (status in statusMap) {
221
- return statusMap[status];
222
- }
223
- if (status >= 200 && status < 300) {
224
- return {
225
- description: 'Successful',
226
- color: 'text-green-500',
227
- icon: CircleCheck
228
- };
229
- }
230
- if (status >= 400) {
231
- return {
232
- description: 'Error',
233
- color: 'text-red-500',
234
- icon: CircleX
235
- };
236
- }
237
- return {
238
- description: 'No Description',
239
- color: 'text-fd-muted-foreground',
240
- icon: CircleX
241
- };
242
- }
243
-
244
104
  function getDefaultValue(item, references) {
245
105
  if (item.type === 'object') return Object.fromEntries(Object.entries(item.properties).map(([key, prop])=>[
246
106
  key,
@@ -816,24 +676,66 @@ function ArrayInput({ fieldName, field, ...props }) {
816
676
 
817
677
  function CodeBlock({ code, lang = 'json' }) {
818
678
  const { shikiOptions } = useApiContext();
819
- const rendered = useShiki(code, {
820
- lang,
821
- ...shikiOptions,
822
- components: {
823
- pre: (props)=>/*#__PURE__*/ jsx(Base.Pre, {
824
- className: "max-h-[288px]",
825
- ...props,
826
- children: props.children
827
- })
828
- }
829
- });
830
- return /*#__PURE__*/ jsx(Base.CodeBlock, {
831
- className: "my-0",
832
- children: rendered
679
+ return /*#__PURE__*/ jsx(DynamicCodeBlock, {
680
+ lang: lang,
681
+ code: code,
682
+ options: shikiOptions
833
683
  });
834
684
  }
835
685
 
836
- function APIPlayground({ route, method = 'GET', bodyType, authorization, path = [], header = [], query = [], body, fields = {}, schemas }) {
686
+ const statusMap = {
687
+ 400: {
688
+ description: 'Bad Request',
689
+ color: 'text-red-500',
690
+ icon: CircleX
691
+ },
692
+ 401: {
693
+ description: 'Unauthorized',
694
+ color: 'text-red-500',
695
+ icon: CircleX
696
+ },
697
+ 403: {
698
+ description: 'Forbidden',
699
+ color: 'text-red-500',
700
+ icon: CircleX
701
+ },
702
+ 404: {
703
+ description: 'Not Found',
704
+ color: 'text-fd-muted-foreground',
705
+ icon: CircleX
706
+ },
707
+ 500: {
708
+ description: 'Internal Server Error',
709
+ color: 'text-red-500',
710
+ icon: CircleX
711
+ }
712
+ };
713
+ function getStatusInfo(status) {
714
+ if (status in statusMap) {
715
+ return statusMap[status];
716
+ }
717
+ if (status >= 200 && status < 300) {
718
+ return {
719
+ description: 'Successful',
720
+ color: 'text-green-500',
721
+ icon: CircleCheck
722
+ };
723
+ }
724
+ if (status >= 400) {
725
+ return {
726
+ description: 'Error',
727
+ color: 'text-red-500',
728
+ icon: CircleX
729
+ };
730
+ }
731
+ return {
732
+ description: 'No Description',
733
+ color: 'text-fd-muted-foreground',
734
+ icon: CircleX
735
+ };
736
+ }
737
+
738
+ function APIPlayground({ route, method = 'GET', bodyType, authorization, path = [], header = [], query = [], body, fields = {}, schemas, proxyUrl }) {
837
739
  const { baseUrl } = useApiContext();
838
740
  const dynamicRef = useRef(new Map());
839
741
  const form = useForm({
@@ -846,27 +748,29 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
846
748
  }
847
749
  });
848
750
  const testQuery = useQuery(async (input)=>{
849
- const url = new URL(`${baseUrl ?? window.location.origin}${createUrlFromInput(route, input.path, input.query)}`);
850
- const headers = new Headers();
851
- if (bodyType !== 'form-data') headers.append('Content-Type', 'application/json');
751
+ const fetcher = await import('./fetcher-DBWYlEEA.js').then((mod)=>mod.createBrowserFetcher(body, schemas));
752
+ const targetUrl = `${baseUrl ?? window.location.origin}${createPathnameFromInput(route, input.path, input.query)}`;
753
+ let url;
754
+ if (proxyUrl) {
755
+ url = new URL(proxyUrl, window.location.origin);
756
+ url.searchParams.append('url', targetUrl);
757
+ } else {
758
+ url = new URL(targetUrl);
759
+ }
760
+ const header = {
761
+ ...input.header
762
+ };
852
763
  if (input.authorization && authorization) {
853
- headers.append(authorization.name, input.authorization);
764
+ header[authorization.name] = input.authorization;
854
765
  }
855
- Object.keys(input.header).forEach((key)=>{
856
- const paramValue = input.header[key];
857
- if (typeof paramValue === 'string' && paramValue.length > 0) headers.append(key, paramValue);
858
- });
859
- const bodyValue = body && input.body && Object.keys(input.body).length > 0 ? createBodyFromValue(bodyType, input.body, body, schemas, dynamicRef.current) : undefined;
860
- const response = await fetch(url, {
861
- method,
862
- headers,
863
- body: bodyValue
766
+ return fetcher.fetch({
767
+ type: bodyType,
768
+ url: url.toString(),
769
+ header,
770
+ body: input.body,
771
+ dynamicFields: dynamicRef.current,
772
+ method
864
773
  });
865
- const data = await response.json().catch(()=>undefined);
866
- return {
867
- status: response.status,
868
- data
869
- };
870
774
  });
871
775
  useEffect(()=>{
872
776
  if (!authorization) return;
@@ -902,6 +806,7 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
902
806
  field: info
903
807
  }, key);
904
808
  }
809
+ const isParamEmpty = path.length === 0 && query.length === 0 && header.length === 0 && body === undefined;
905
810
  return /*#__PURE__*/ jsx(Form, {
906
811
  ...form,
907
812
  children: /*#__PURE__*/ jsx(SchemaContext.Provider, {
@@ -932,9 +837,9 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
932
837
  ]
933
838
  }),
934
839
  authorization ? renderCustomField('authorization', authorization, fields.auth) : null,
935
- /*#__PURE__*/ jsxs(Accordions, {
840
+ !isParamEmpty ? /*#__PURE__*/ jsxs(Accordions, {
936
841
  type: "multiple",
937
- className: cn('-m-3 border-0 bg-transparent text-sm', path.length === 0 && query.length === 0 && header.length === 0 && !body && 'hidden'),
842
+ className: "-m-3 border-0 bg-transparent text-sm",
938
843
  children: [
939
844
  path.length > 0 ? /*#__PURE__*/ jsx(Accordion, {
940
845
  title: "Path",
@@ -965,7 +870,7 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
965
870
  }) : renderCustomField('body', body, fields.body)
966
871
  }) : null
967
872
  ]
968
- }),
873
+ }) : null,
969
874
  testQuery.data ? /*#__PURE__*/ jsx(ResultDisplay, {
970
875
  data: testQuery.data
971
876
  }) : null
@@ -974,17 +879,17 @@ function APIPlayground({ route, method = 'GET', bodyType, authorization, path =
974
879
  })
975
880
  });
976
881
  }
977
- function createUrlFromInput(route, path, query) {
882
+ function createPathnameFromInput(route, path, query) {
978
883
  let pathname = route;
979
- Object.keys(path).forEach((key)=>{
884
+ for (const key of Object.keys(path)){
980
885
  const paramValue = path[key];
981
886
  if (typeof paramValue === 'string' && paramValue.length > 0) pathname = pathname.replace(`{${key}}`, paramValue);
982
- });
887
+ }
983
888
  const searchParams = new URLSearchParams();
984
- Object.keys(query).forEach((key)=>{
889
+ for (const key of Object.keys(query)){
985
890
  const paramValue = query[key];
986
891
  if (typeof paramValue === 'string' && paramValue.length > 0) searchParams.append(key, paramValue);
987
- });
892
+ }
988
893
  return searchParams.size > 0 ? `${pathname}?${searchParams.toString()}` : pathname;
989
894
  }
990
895
  function RouteDisplay({ route }) {
@@ -994,7 +899,7 @@ function RouteDisplay({ route }) {
994
899
  'query'
995
900
  ]
996
901
  });
997
- const pathname = useMemo(()=>createUrlFromInput(route, path, query), [
902
+ const pathname = useMemo(()=>createPathnameFromInput(route, path, query), [
998
903
  route,
999
904
  path,
1000
905
  query
@@ -1025,7 +930,8 @@ function ResultDisplay({ data }) {
1025
930
  children: data.status
1026
931
  }),
1027
932
  data.data ? /*#__PURE__*/ jsx(CodeBlock, {
1028
- code: JSON.stringify(data.data, null, 2)
933
+ lang: typeof data.data === 'string' && data.data.length > 50000 ? 'text' : data.type,
934
+ code: typeof data.data === 'string' ? data.data : JSON.stringify(data.data, null, 2)
1029
935
  }) : null
1030
936
  ]
1031
937
  });
@@ -1053,4 +959,9 @@ function useQuery(fn) {
1053
959
  ]);
1054
960
  }
1055
961
 
1056
- export { APIPlayground };
962
+ var index = {
963
+ __proto__: null,
964
+ APIPlayground: APIPlayground
965
+ };
966
+
967
+ export { index as i, resolve as r };
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
- import { ReactNode, ComponentType, ReactElement, MutableRefObject, HTMLAttributes } from 'react';
3
+ import { ReactNode, ComponentType, ReactElement, RefObject, HTMLAttributes } from 'react';
4
4
  import { FieldPath, ControllerRenderProps, ControllerFieldState, UseFormStateReturn } from 'react-hook-form';
5
5
  import { OpenAPIV3_1 } from 'openapi-types';
6
6
  import Slugger from 'github-slugger';
@@ -54,6 +54,10 @@ type Awaitable<T> = T | Promise<T>;
54
54
  */
55
55
  type DereferenceMap = Map<unknown, string>;
56
56
  interface RenderContext {
57
+ /**
58
+ * The url of proxy to avoid CORS issues
59
+ */
60
+ proxyUrl?: string;
57
61
  renderer: Renderer;
58
62
  /**
59
63
  * dereferenced schema
@@ -134,6 +138,7 @@ interface APIPlaygroundProps {
134
138
  header?: PrimitiveRequestField[];
135
139
  body?: RequestSchema;
136
140
  schemas: Record<string, RequestSchema>;
141
+ proxyUrl?: string;
137
142
  }
138
143
 
139
144
  interface ResponsesProps {
@@ -225,7 +230,7 @@ interface CustomField<TName extends FieldPath<FormValues>, Info> {
225
230
 
226
231
  interface SchemaContextType {
227
232
  references: Record<string, RequestSchema>;
228
- dynamic: MutableRefObject<Map<string, DynamicField>>;
233
+ dynamic: RefObject<Map<string, DynamicField>>;
229
234
  }
230
235
  type DynamicField = {
231
236
  type: 'object';
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-DopF7PSu.js';
7
- export { A as APIPlayground, R as Root, u as useSchemaContext } from './client-client-DopF7PSu.js';
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';
8
8
 
9
9
  const badgeVariants = cva('rounded-xl border px-1.5 py-1 text-xs font-medium leading-[12px]', {
10
10
  variants: {
@@ -95,9 +95,9 @@ function APIInfo({ className, route, badgeClassname, baseUrls, method = 'GET', h
95
95
  function API({ children, ...props }) {
96
96
  return /*#__PURE__*/ jsx("div", {
97
97
  ...props,
98
- className: cn('flex flex-col gap-x-6 gap-y-4 max-xl:[--fd-toc-height:46px] max-md:[--fd-toc-height:36px] xl:flex-row xl:items-start', props.className),
98
+ className: cn('flex flex-col gap-x-6 gap-y-4 xl:flex-row xl:items-start', props.className),
99
99
  style: {
100
- '--fd-api-info-top': 'calc(var(--fd-nav-height) + var(--fd-banner-height) + var(--fd-toc-height, 0.5rem))',
100
+ '--fd-api-info-top': 'calc(var(--fd-nav-height) + var(--fd-banner-height) + var(--fd-tocnav-height, 0px))',
101
101
  ...props.style
102
102
  },
103
103
  children: children
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "5.9.0",
3
+ "version": "5.10.1",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",
@@ -32,9 +32,9 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@fumari/json-schema-to-typescript": "^1.1.2",
35
- "@radix-ui/react-select": "^2.1.3",
35
+ "@radix-ui/react-select": "^2.1.4",
36
36
  "@radix-ui/react-slot": "^1.1.1",
37
- "@scalar/openapi-parser": "0.10.0",
37
+ "@scalar/openapi-parser": "0.10.2",
38
38
  "ajv-draft-04": "^1.0.0",
39
39
  "class-variance-authority": "^0.7.1",
40
40
  "fast-glob": "^3.3.1",
@@ -42,21 +42,21 @@
42
42
  "hast-util-to-jsx-runtime": "^2.3.2",
43
43
  "js-yaml": "^4.1.0",
44
44
  "openapi-sampler": "^1.6.1",
45
- "react-hook-form": "^7.54.1",
45
+ "react-hook-form": "^7.54.2",
46
46
  "remark": "^15.0.1",
47
47
  "remark-rehype": "^11.1.1",
48
- "shiki": "^1.24.2",
49
- "fumadocs-ui": "14.6.1",
50
- "fumadocs-core": "14.6.1"
48
+ "shiki": "^1.24.3",
49
+ "fumadocs-core": "14.6.3",
50
+ "fumadocs-ui": "14.6.3"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/js-yaml": "^4.0.9",
54
54
  "@types/node": "22.10.2",
55
55
  "@types/openapi-sampler": "^1.0.3",
56
- "@types/react": "^19.0.1",
56
+ "@types/react": "^19.0.2",
57
57
  "bunchee": "^6.0.3",
58
- "lucide-react": "^0.468.0",
59
- "next": "15.1.0",
58
+ "lucide-react": "^0.469.0",
59
+ "next": "15.1.2",
60
60
  "openapi-types": "^12.1.3",
61
61
  "eslint-config-custom": "0.0.0",
62
62
  "tsconfig": "0.0.0"