fumadocs-openapi 5.12.0 → 6.0.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.
@@ -4,7 +4,6 @@ import { Fragment as Fragment$1 } from 'react';
4
4
  import { sample } from 'openapi-sampler';
5
5
  import { js2xml } from 'xml-js';
6
6
  import { compile } from '@fumari/json-schema-to-typescript';
7
- import { ScalarPlayground, Root, API, APIInfo, APIExample as APIExample$1, Property, ObjectCollapsible, APIPlayground } from '../ui/index.js';
8
7
  import { remarkGfm, remarkImage, rehypeCode } from 'fumadocs-core/mdx-plugins';
9
8
  import defaultMdxComponents from 'fumadocs-ui/mdx';
10
9
  import { remark } from 'remark';
@@ -15,6 +14,7 @@ import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
15
14
  import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
16
15
  import * as Base from 'fumadocs-ui/components/codeblock';
17
16
  import { highlight } from 'fumadocs-core/server';
17
+ import { APIPlayground, Root, API, APIInfo, APIExample as APIExample$1, Property, ObjectCollapsible } from '../ui/index.js';
18
18
  import { load, upgrade, dereference } from '@scalar/openapi-parser';
19
19
  import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls';
20
20
  import { readFiles } from '@scalar/openapi-parser/plugins/read-files';
@@ -328,180 +328,6 @@ async function getTypescriptSchema(endpoint, code, dereferenceMap) {
328
328
  }
329
329
  }
330
330
 
331
- function Playground({ path, method, ctx }) {
332
- if (ctx.useScalar) {
333
- return /*#__PURE__*/ jsx(ScalarPlayground, {
334
- spec: ctx.schema.downloaded,
335
- method: method.method,
336
- path: path
337
- });
338
- }
339
- let currentId = 0;
340
- const bodyContent = method.requestBody?.content;
341
- const mediaType = bodyContent ? getPreferredType(bodyContent) : undefined;
342
- const context = {
343
- allowFile: mediaType === 'multipart/form-data',
344
- references: {},
345
- nextId () {
346
- return String(currentId++);
347
- },
348
- registered: new WeakMap(),
349
- render: ctx
350
- };
351
- const bodySchema = bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(bodyContent[mediaType].schema, true, context) : undefined;
352
- const props = {
353
- authorization: getAuthorizationField(method, ctx),
354
- method: method.method,
355
- route: path,
356
- path: method.parameters?.filter((v)=>v.in === 'path').map((v)=>parameterToField(v, context)),
357
- query: method.parameters?.filter((v)=>v.in === 'query').map((v)=>parameterToField(v, context)),
358
- header: method.parameters?.filter((v)=>v.in === 'header').map((v)=>parameterToField(v, context)),
359
- body: bodySchema && mediaType ? {
360
- ...bodySchema,
361
- mediaType: mediaType
362
- } : undefined,
363
- schemas: context.references,
364
- proxyUrl: ctx.proxyUrl
365
- };
366
- return /*#__PURE__*/ jsx(ctx.renderer.APIPlayground, {
367
- ...props
368
- });
369
- }
370
- function getAuthorizationField(method, { schema: { document } }) {
371
- const security = method.security ?? document.security ?? [];
372
- if (security.length === 0) return;
373
- const singular = security.find((requirements)=>Object.keys(requirements).length === 1);
374
- if (!singular) return;
375
- const scheme = getSecurities(singular, document)[0];
376
- return {
377
- type: 'string',
378
- name: scheme.type === 'apiKey' ? scheme.name : 'Authorization',
379
- authType: scheme.type,
380
- defaultValue: scheme.type === 'oauth2' || scheme.type === 'http' && scheme.scheme === 'bearer' ? 'Bearer' : 'Basic',
381
- isRequired: security.every((requirements)=>Object.keys(requirements).length > 0),
382
- description: 'The Authorization access token'
383
- };
384
- }
385
- function getIdFromSchema(schema, required, ctx) {
386
- const registered = ctx.registered.get(schema);
387
- if (registered === undefined) {
388
- const id = ctx.nextId();
389
- ctx.registered.set(schema, id);
390
- ctx.references[id] = toSchema(schema, required, ctx);
391
- return id;
392
- }
393
- return registered;
394
- }
395
- function parameterToField(v, ctx) {
396
- return {
397
- name: v.name,
398
- ...toSchema(v.schema ?? {
399
- type: 'string'
400
- }, v.required ?? false, ctx)
401
- };
402
- }
403
- function toReference(schema, required, ctx) {
404
- return {
405
- type: 'ref',
406
- isRequired: required,
407
- schema: getIdFromSchema(schema, false, ctx)
408
- };
409
- }
410
- function toSchema(schema, required, ctx) {
411
- if (schema.type === 'array') {
412
- return {
413
- type: 'array',
414
- description: schema.description ?? schema.title,
415
- isRequired: required,
416
- items: getIdFromSchema(schema.items, false, ctx)
417
- };
418
- }
419
- if (schema.type === 'object' || schema.properties !== undefined || schema.allOf !== undefined) {
420
- const properties = {};
421
- Object.entries(schema.properties ?? {}).forEach(([key, prop])=>{
422
- properties[key] = toReference(prop, schema.required?.includes(key) ?? false, ctx);
423
- });
424
- schema.allOf?.forEach((c)=>{
425
- const field = toSchema(c, true, ctx);
426
- if (field.type === 'object') Object.assign(properties, field.properties);
427
- });
428
- const additional = schema.additionalProperties;
429
- let additionalProperties;
430
- if (additional && typeof additional === 'object') {
431
- if ((!additional.type || additional.type.length === 0) && !additional.anyOf && !additional.allOf && !additional.oneOf) {
432
- additionalProperties = true;
433
- } else {
434
- additionalProperties = getIdFromSchema(additional, false, ctx);
435
- }
436
- } else {
437
- additionalProperties = additional;
438
- }
439
- return {
440
- type: 'object',
441
- isRequired: required,
442
- description: schema.description ?? schema.title,
443
- properties,
444
- additionalProperties
445
- };
446
- }
447
- if (schema.type === undefined) {
448
- const combine = schema.anyOf ?? schema.oneOf;
449
- if (combine) {
450
- return {
451
- type: 'switcher',
452
- description: schema.description ?? schema.title,
453
- items: Object.fromEntries(combine.map((item, idx)=>{
454
- return [
455
- item.title ?? item.type ?? `Item ${idx.toString()}`,
456
- toReference(item, true, ctx)
457
- ];
458
- })),
459
- isRequired: required
460
- };
461
- }
462
- return {
463
- type: 'null',
464
- isRequired: false
465
- };
466
- }
467
- if (ctx.allowFile && schema.type === 'string' && schema.format === 'binary') {
468
- return {
469
- type: 'file',
470
- isRequired: required,
471
- description: schema.description ?? schema.title
472
- };
473
- }
474
- if (Array.isArray(schema.type)) {
475
- const items = {};
476
- for (const type of schema.type){
477
- if (type === 'array') {
478
- items[type] = {
479
- type,
480
- items: 'items' in schema && schema.items ? toSchema(schema.items, false, ctx) : toSchema({}, required, ctx),
481
- isRequired: required
482
- };
483
- } else {
484
- items[type] = toSchema({
485
- ...schema,
486
- type
487
- }, true, ctx);
488
- }
489
- }
490
- return {
491
- type: 'switcher',
492
- description: schema.description ?? schema.title,
493
- items,
494
- isRequired: required
495
- };
496
- }
497
- return {
498
- type: schema.type === 'integer' ? 'number' : schema.type,
499
- defaultValue: schema.example ?? '',
500
- isRequired: required,
501
- description: schema.description ?? schema.title
502
- };
503
- }
504
-
505
331
  function idToTitle(id) {
506
332
  let result = [];
507
333
  for (const c of id){
@@ -938,7 +764,7 @@ function Operation({ type = 'operation', path, method, ctx, hasHead, headingLeve
938
764
  method: method.method,
939
765
  route: path,
940
766
  children: [
941
- type === 'operation' ? /*#__PURE__*/ jsx(Playground, {
767
+ type === 'operation' ? /*#__PURE__*/ jsx(ctx.renderer.APIPlayground, {
942
768
  path: path,
943
769
  method: method,
944
770
  ctx: ctx
@@ -1207,7 +1033,166 @@ async function CodeBlock({ code, lang, options, ...rest }) {
1207
1033
  });
1208
1034
  }
1209
1035
 
1210
- function createRenders(shikiOptions, _useScalar) {
1036
+ function Playground({ path, method, ctx }) {
1037
+ let currentId = 0;
1038
+ const bodyContent = method.requestBody?.content;
1039
+ const mediaType = bodyContent ? getPreferredType(bodyContent) : undefined;
1040
+ const context = {
1041
+ allowFile: mediaType === 'multipart/form-data',
1042
+ references: {},
1043
+ nextId () {
1044
+ return String(currentId++);
1045
+ },
1046
+ registered: new WeakMap(),
1047
+ render: ctx
1048
+ };
1049
+ const bodySchema = bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(bodyContent[mediaType].schema, true, context) : undefined;
1050
+ const props = {
1051
+ authorization: getAuthorizationField(method, ctx),
1052
+ method: method.method,
1053
+ route: path,
1054
+ path: method.parameters?.filter((v)=>v.in === 'path').map((v)=>parameterToField(v, context)),
1055
+ query: method.parameters?.filter((v)=>v.in === 'query').map((v)=>parameterToField(v, context)),
1056
+ header: method.parameters?.filter((v)=>v.in === 'header').map((v)=>parameterToField(v, context)),
1057
+ body: bodySchema && mediaType ? {
1058
+ ...bodySchema,
1059
+ mediaType: mediaType
1060
+ } : undefined,
1061
+ schemas: context.references,
1062
+ proxyUrl: ctx.proxyUrl
1063
+ };
1064
+ return /*#__PURE__*/ jsx(APIPlayground, {
1065
+ ...props
1066
+ });
1067
+ }
1068
+ function getAuthorizationField(method, { schema: { document } }) {
1069
+ const security = method.security ?? document.security ?? [];
1070
+ if (security.length === 0) return;
1071
+ const singular = security.find((requirements)=>Object.keys(requirements).length === 1);
1072
+ if (!singular) return;
1073
+ return getSecurities(singular, document)[0];
1074
+ }
1075
+ function getIdFromSchema(schema, required, ctx) {
1076
+ const registered = ctx.registered.get(schema);
1077
+ if (registered === undefined) {
1078
+ const id = ctx.nextId();
1079
+ ctx.registered.set(schema, id);
1080
+ ctx.references[id] = toSchema(schema, required, ctx);
1081
+ return id;
1082
+ }
1083
+ return registered;
1084
+ }
1085
+ function parameterToField(v, ctx) {
1086
+ return {
1087
+ name: v.name,
1088
+ ...toSchema(v.schema ?? {
1089
+ type: 'string'
1090
+ }, v.required ?? false, ctx)
1091
+ };
1092
+ }
1093
+ function toReference(schema, required, ctx) {
1094
+ return {
1095
+ type: 'ref',
1096
+ isRequired: required,
1097
+ schema: getIdFromSchema(schema, false, ctx)
1098
+ };
1099
+ }
1100
+ function toSchema(schema, required, ctx) {
1101
+ if (schema.type === 'array') {
1102
+ return {
1103
+ type: 'array',
1104
+ description: schema.description ?? schema.title,
1105
+ isRequired: required,
1106
+ items: getIdFromSchema(schema.items, false, ctx)
1107
+ };
1108
+ }
1109
+ if (schema.type === 'object' || schema.properties !== undefined || schema.allOf !== undefined) {
1110
+ const properties = {};
1111
+ Object.entries(schema.properties ?? {}).forEach(([key, prop])=>{
1112
+ properties[key] = toReference(prop, schema.required?.includes(key) ?? false, ctx);
1113
+ });
1114
+ schema.allOf?.forEach((c)=>{
1115
+ const field = toSchema(c, true, ctx);
1116
+ if (field.type === 'object') Object.assign(properties, field.properties);
1117
+ });
1118
+ const additional = schema.additionalProperties;
1119
+ let additionalProperties;
1120
+ if (additional && typeof additional === 'object') {
1121
+ if ((!additional.type || additional.type.length === 0) && !additional.anyOf && !additional.allOf && !additional.oneOf) {
1122
+ additionalProperties = true;
1123
+ } else {
1124
+ additionalProperties = getIdFromSchema(additional, false, ctx);
1125
+ }
1126
+ } else {
1127
+ additionalProperties = additional;
1128
+ }
1129
+ return {
1130
+ type: 'object',
1131
+ isRequired: required,
1132
+ description: schema.description ?? schema.title,
1133
+ properties,
1134
+ additionalProperties
1135
+ };
1136
+ }
1137
+ if (schema.type === undefined) {
1138
+ const combine = schema.anyOf ?? schema.oneOf;
1139
+ if (combine) {
1140
+ return {
1141
+ type: 'switcher',
1142
+ description: schema.description ?? schema.title,
1143
+ items: Object.fromEntries(combine.map((item, idx)=>{
1144
+ return [
1145
+ item.title ?? item.type ?? `Item ${idx.toString()}`,
1146
+ toReference(item, true, ctx)
1147
+ ];
1148
+ })),
1149
+ isRequired: required
1150
+ };
1151
+ }
1152
+ return {
1153
+ type: 'null',
1154
+ isRequired: false
1155
+ };
1156
+ }
1157
+ if (ctx.allowFile && schema.type === 'string' && schema.format === 'binary') {
1158
+ return {
1159
+ type: 'file',
1160
+ isRequired: required,
1161
+ description: schema.description ?? schema.title
1162
+ };
1163
+ }
1164
+ if (Array.isArray(schema.type)) {
1165
+ const items = {};
1166
+ for (const type of schema.type){
1167
+ if (type === 'array') {
1168
+ items[type] = {
1169
+ type,
1170
+ items: 'items' in schema && schema.items ? toSchema(schema.items, false, ctx) : toSchema({}, required, ctx),
1171
+ isRequired: required
1172
+ };
1173
+ } else {
1174
+ items[type] = toSchema({
1175
+ ...schema,
1176
+ type
1177
+ }, required, ctx);
1178
+ }
1179
+ }
1180
+ return {
1181
+ type: 'switcher',
1182
+ description: schema.description ?? schema.title,
1183
+ items,
1184
+ isRequired: required
1185
+ };
1186
+ }
1187
+ return {
1188
+ type: schema.type === 'integer' ? 'number' : schema.type,
1189
+ defaultValue: schema.example ?? '',
1190
+ isRequired: required,
1191
+ description: schema.description ?? schema.title
1192
+ };
1193
+ }
1194
+
1195
+ function createRenders(shikiOptions) {
1211
1196
  return {
1212
1197
  Root: (props)=>/*#__PURE__*/ jsx(Root, {
1213
1198
  shikiOptions: shikiOptions,
@@ -1252,7 +1237,7 @@ function createRenders(shikiOptions, _useScalar) {
1252
1237
  options: shikiOptions
1253
1238
  })
1254
1239
  }),
1255
- APIPlayground
1240
+ APIPlayground: Playground
1256
1241
  };
1257
1242
  }
1258
1243
 
@@ -1346,12 +1331,11 @@ async function getContext(schema, options = {}) {
1346
1331
  ];
1347
1332
  const server = servers[0];
1348
1333
  return {
1349
- useScalar: options.useScalar ?? false,
1350
1334
  schema,
1351
1335
  proxyUrl: options.proxyUrl,
1352
1336
  showResponseSchema: options.showResponseSchema,
1353
1337
  renderer: {
1354
- ...createRenders(options.shikiOptions, options.useScalar ?? false),
1338
+ ...createRenders(options.shikiOptions),
1355
1339
  ...options.renderer
1356
1340
  },
1357
1341
  shikiOptions: options.shikiOptions,
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+ import { useContext, createContext, useState, useRef, useEffect, useMemo } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+ import { cn } from 'fumadocs-ui/components/api';
5
+ import dynamic from 'next/dynamic';
6
+
7
+ const ApiContext = /*#__PURE__*/ createContext(undefined);
8
+ const ServerSelectContext = /*#__PURE__*/ createContext(undefined);
9
+ function useApiContext() {
10
+ const ctx = useContext(ApiContext);
11
+ if (!ctx) throw new Error('Component must be used under <ApiProvider />');
12
+ return ctx;
13
+ }
14
+ function useServerSelectContext() {
15
+ const ctx = useContext(ServerSelectContext);
16
+ if (!ctx) throw new Error('Component must be used under <ApiProvider />');
17
+ return ctx;
18
+ }
19
+ function ApiProvider({ defaultBaseUrl, children, ...props }) {
20
+ const [server, setServer] = useState(()=>{
21
+ const defaultItem = defaultBaseUrl ? props.servers.find((item)=>item.url === defaultBaseUrl) : undefined;
22
+ return defaultItem ? {
23
+ url: defaultItem.url,
24
+ variables: getDefaultValues(defaultItem)
25
+ } : null;
26
+ });
27
+ const serverRef = useRef(server);
28
+ serverRef.current = server;
29
+ useEffect(()=>{
30
+ const cached = localStorage.getItem('apiBaseUrl');
31
+ if (!cached) return;
32
+ try {
33
+ const obj = JSON.parse(cached);
34
+ if (!obj || typeof obj !== 'object') return;
35
+ setServer(obj);
36
+ } catch {
37
+ // ignore
38
+ }
39
+ }, []);
40
+ return /*#__PURE__*/ jsx(ApiContext.Provider, {
41
+ value: useMemo(()=>({
42
+ ...props,
43
+ serverRef
44
+ }), [
45
+ props
46
+ ]),
47
+ children: /*#__PURE__*/ jsx(ServerSelectContext.Provider, {
48
+ value: useMemo(()=>({
49
+ server,
50
+ setServerVariables (variables) {
51
+ setServer((prev)=>{
52
+ if (!prev) return null;
53
+ const updated = {
54
+ ...prev,
55
+ variables
56
+ };
57
+ localStorage.setItem('apiBaseUrl', JSON.stringify(updated));
58
+ return updated;
59
+ });
60
+ },
61
+ setServer (value) {
62
+ const obj = props.servers.find((item)=>item.url === value);
63
+ if (!obj) return;
64
+ const result = {
65
+ url: value,
66
+ variables: getDefaultValues(obj)
67
+ };
68
+ localStorage.setItem('apiBaseUrl', JSON.stringify(result));
69
+ setServer(result);
70
+ }
71
+ }), [
72
+ server,
73
+ props.servers
74
+ ]),
75
+ children: children
76
+ })
77
+ });
78
+ }
79
+ function getDefaultValues(server) {
80
+ return Object.fromEntries(Object.entries(server.variables ?? {}).map(([k, v])=>[
81
+ k,
82
+ v.default
83
+ ]));
84
+ }
85
+
86
+ const SchemaContext = /*#__PURE__*/ createContext(undefined);
87
+ function useSchemaContext() {
88
+ const ctx = useContext(SchemaContext);
89
+ if (!ctx) throw new Error('Missing provider');
90
+ return ctx;
91
+ }
92
+
93
+ const APIPlayground = dynamic(()=>import('./index-client-CehDtJk-.js').then(function (n) { return n.i; }).then((mod)=>mod.APIPlayground));
94
+ function Root({ children, baseUrl, className, shikiOptions, servers, ...props }) {
95
+ return /*#__PURE__*/ jsx("div", {
96
+ className: cn('flex flex-col gap-24 text-sm text-fd-muted-foreground', className),
97
+ ...props,
98
+ children: /*#__PURE__*/ jsx(ApiProvider, {
99
+ servers: servers,
100
+ shikiOptions: shikiOptions,
101
+ defaultBaseUrl: baseUrl,
102
+ children: children
103
+ })
104
+ });
105
+ }
106
+
107
+ export { APIPlayground as A, Root as R, SchemaContext as S, useApiContext as a, useServerSelectContext as b, useSchemaContext as u };
@@ -1,4 +1,4 @@
1
- import { r as resolve } from './index-client-XPLtFTfD.js';
1
+ import { r as resolve } from './index-client-CehDtJk-.js';
2
2
 
3
3
  /**
4
4
  * @param bodySchema - schema of body