fumadocs-openapi 5.0.0 → 5.0.2

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.
@@ -180,7 +180,8 @@ interface RenderContext {
180
180
  generateCodeSamples?: (endpoint: EndpointSample) => Awaitable<CodeSample[]>;
181
181
  }
182
182
 
183
- interface ApiPageProps {
183
+ interface ApiPageProps extends Pick<RenderContext, 'generateCodeSamples' | 'generateTypeScriptSchema'> {
184
+ document: OpenAPIV3.Document;
184
185
  /**
185
186
  * An array of operation
186
187
  */
@@ -189,15 +190,14 @@ interface ApiPageProps {
189
190
  method: OpenAPIV3.HttpMethods;
190
191
  }[];
191
192
  hasHead: boolean;
192
- ctx: RenderContext;
193
+ renderer?: Partial<Renderer>;
193
194
  }
194
195
 
195
- interface OpenAPIOptions extends Pick<RenderContext, 'generateCodeSamples' | 'generateTypeScriptSchema'> {
196
+ interface OpenAPIOptions extends Omit<Partial<ApiPageProps>, 'document'> {
196
197
  documentOrPath: string | OpenAPIV3.Document;
197
- renderer?: Partial<Renderer>;
198
198
  }
199
199
  interface OpenAPIServer {
200
- APIPage: FC<Omit<ApiPageProps, 'ctx'>>;
200
+ APIPage: FC<Omit<ApiPageProps, 'document'>>;
201
201
  }
202
202
  declare function createOpenAPI(options: OpenAPIOptions): OpenAPIServer;
203
203
 
@@ -1,4 +1,4 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import Parser from '@apidevtools/json-schema-ref-parser';
3
3
  import Slugger from 'github-slugger';
4
4
  import { createElement, Fragment, useMemo } from 'react';
@@ -28,7 +28,28 @@ function getPreferredType(body) {
28
28
  return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
29
29
  }
30
30
 
31
- function generateSample(path, method, baseUrl) {
31
+ function getSecurities(requirement, document) {
32
+ const results = [];
33
+ const schemas = document.components?.securitySchemes ?? {};
34
+ for (const [key, scopes] of Object.entries(requirement)){
35
+ if (!(key in schemas)) return [];
36
+ const schema = noRef(schemas[key]);
37
+ results.push({
38
+ ...schema,
39
+ scopes
40
+ });
41
+ }
42
+ return results;
43
+ }
44
+ function getSecurityPrefix(security) {
45
+ if (security.type === 'http') return ({
46
+ basic: 'Basic',
47
+ bearer: 'Bearer'
48
+ })[security.scheme];
49
+ if (security.type === 'oauth2') return 'Bearer';
50
+ }
51
+
52
+ function generateSample(path, method, { baseUrl, document }) {
32
53
  const params = [];
33
54
  const responses = {};
34
55
  for (const param of method.parameters){
@@ -51,6 +72,20 @@ function generateSample(path, method, baseUrl) {
51
72
  });
52
73
  }
53
74
  }
75
+ const requirements = method.security ?? document.security;
76
+ if (requirements && requirements.length > 0) {
77
+ for (const security of getSecurities(requirements[0], document)){
78
+ const prefix = getSecurityPrefix(security);
79
+ params.push({
80
+ name: 'Authorization',
81
+ schema: {
82
+ type: 'string'
83
+ },
84
+ sample: prefix ? `${prefix} <token>` : '<token>',
85
+ in: 'header'
86
+ });
87
+ }
88
+ }
54
89
  let bodyOutput;
55
90
  if (method.requestBody) {
56
91
  const body = noRef(method.requestBody).content;
@@ -199,7 +234,7 @@ func main() {
199
234
  ${Array.from(variables.entries()).map(([k, v])=>` ${k} := ${v}`).join('\n')}
200
235
  ${additional.join('\n ')}
201
236
  req, _ := http.NewRequest("${endpoint.method}", url, ${variables.has('payload') ? 'payload' : 'nil'})
202
- ${Array.from(headers.entries()).map(([key, value])=>`req.Header.Add("${key}", ${value})`).join('\n')}
237
+ ${Array.from(headers.entries()).map(([key, value])=>`req.Header.Add("${key}", ${value})`).join('\n ')}
203
238
  res, _ := http.DefaultClient.Do(req)
204
239
  defer res.Body.Close()
205
240
  body, _ := ioutil.ReadAll(res.Body)
@@ -220,20 +255,6 @@ async function getTypescriptSchema(endpoint, code) {
220
255
  }
221
256
  }
222
257
 
223
- function getScheme(requirement, document) {
224
- const results = [];
225
- const schemas = document.components?.securitySchemes ?? {};
226
- for (const [key, scopes] of Object.entries(requirement)){
227
- if (!(key in schemas)) return [];
228
- const schema = noRef(schemas[key]);
229
- results.push({
230
- ...schema,
231
- scopes
232
- });
233
- }
234
- return results;
235
- }
236
-
237
258
  function Playground({ path, method, ctx }) {
238
259
  let currentId = 0;
239
260
  const bodyContent = noRef(method.requestBody)?.content;
@@ -266,7 +287,7 @@ function getAuthorizationField(method, ctx) {
266
287
  if (security.length === 0) return;
267
288
  const singular = security.find((requirements)=>Object.keys(requirements).length === 1);
268
289
  if (!singular) return;
269
- const scheme = getScheme(singular, ctx.document)[0];
290
+ const scheme = getSecurities(singular, ctx.document)[0];
270
291
  return {
271
292
  type: 'string',
272
293
  name: 'Authorization',
@@ -436,6 +457,8 @@ function isObject(schema) {
436
457
  }
437
458
  function Schema({ name, schema, ctx }) {
438
459
  if (schema.readOnly === true && !ctx.readOnly || schema.writeOnly === true && !ctx.writeOnly) return null;
460
+ const parseObject = ctx.parseObject ?? true;
461
+ const stack = ctx.stack ?? [];
439
462
  const { renderer } = ctx.render;
440
463
  const child = [];
441
464
  function field(key, value) {
@@ -450,7 +473,7 @@ function Schema({ name, schema, ctx }) {
450
473
  }, key));
451
474
  }
452
475
  // object type
453
- if (isObject(schema) && ctx.parseObject) {
476
+ if (isObject(schema) && parseObject) {
454
477
  const { additionalProperties, properties } = schema;
455
478
  if (additionalProperties === true) {
456
479
  child.push(/*#__PURE__*/ jsx(renderer.Property, {
@@ -496,7 +519,7 @@ function Schema({ name, schema, ctx }) {
496
519
  if (schema.enum) {
497
520
  field('Value in', schema.enum.map((value)=>JSON.stringify(value)).join(' | '));
498
521
  }
499
- if (isObject(schema) && !ctx.parseObject) {
522
+ if (isObject(schema) && !parseObject) {
500
523
  child.push(/*#__PURE__*/ jsx(renderer.ObjectCollapsible, {
501
524
  name: "Attributes",
502
525
  children: /*#__PURE__*/ jsx(Schema, {
@@ -531,8 +554,7 @@ function Schema({ name, schema, ctx }) {
531
554
  ...schema.type === 'array' ? [
532
555
  schema.items
533
556
  ] : []
534
- ].map(noRef).filter((s)=>isComplexType(s) && !ctx.stack.includes(s));
535
- ctx.stack.push(schema);
557
+ ].map(noRef).filter((s)=>isComplexType(s) && !stack.includes(s));
536
558
  const renderedMentionedTypes = mentionedObjectTypes.map((s, idx)=>{
537
559
  return /*#__PURE__*/ jsx(renderer.ObjectCollapsible, {
538
560
  name: s.title ?? `Object ${(idx + 1).toString()}`,
@@ -541,6 +563,10 @@ function Schema({ name, schema, ctx }) {
541
563
  schema: noRef(s),
542
564
  ctx: {
543
565
  ...ctx,
566
+ stack: [
567
+ schema,
568
+ ...stack
569
+ ],
544
570
  parseObject: true,
545
571
  required: false
546
572
  }
@@ -548,7 +574,6 @@ function Schema({ name, schema, ctx }) {
548
574
  }, `mentioned:${idx.toString()}`);
549
575
  });
550
576
  child.push(...renderedMentionedTypes);
551
- ctx.stack.pop();
552
577
  }
553
578
  return /*#__PURE__*/ jsx(renderer.Property, {
554
579
  name: name,
@@ -605,7 +630,7 @@ function getSchemaType(schema, ctx) {
605
630
  if (schema.anyOf) {
606
631
  return `Any properties in ${schema.anyOf.map((one)=>getSchemaType(noRef(one), ctx)).join(', ')}`;
607
632
  }
608
- if (schema.type === 'string' && schema.format === 'binary' && ctx.allowFile) return 'File';
633
+ if (schema.type === 'string' && schema.format === 'binary' && ctx.allowFile) return 'file';
609
634
  if (schema.type) return schema.type;
610
635
  if (isObject(schema)) return 'object';
611
636
  return 'unknown';
@@ -661,20 +686,18 @@ function Operation({ path, method, ctx, hasHead }) {
661
686
  name: "body",
662
687
  schema: noRef(body.content[type].schema ?? {}),
663
688
  ctx: {
664
- parseObject: true,
665
689
  readOnly: method.method === 'GET',
666
690
  writeOnly: method.method !== 'GET',
667
691
  required: body.required ?? false,
668
692
  render: ctx,
669
- allowFile: type === 'multipart/form-data',
670
- stack: []
693
+ allowFile: type === 'multipart/form-data'
671
694
  }
672
695
  })
673
696
  ]
674
697
  }, "body"));
675
698
  }
676
699
  const parameterGroups = new Map();
677
- const endpoint = generateSample(path, method, ctx.baseUrl);
700
+ const endpoint = generateSample(path, method, ctx);
678
701
  for (const param of method.parameters){
679
702
  const pInfo = endpoint.parameters.find((item)=>item.name === param.name && item.in === param.in);
680
703
  if (!pInfo) continue;
@@ -698,9 +721,7 @@ function Operation({ path, method, ctx, hasHead }) {
698
721
  readOnly: method.method === 'GET',
699
722
  writeOnly: method.method !== 'GET',
700
723
  required: param.required ?? false,
701
- render: ctx,
702
- allowFile: false,
703
- stack: []
724
+ render: ctx
704
725
  }
705
726
  }, param.name));
706
727
  parameterGroups.set(groupName, group);
@@ -778,15 +799,12 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
778
799
  let id = 0;
779
800
  const info = [];
780
801
  for (const requirement of requirements){
781
- if (info.length > 0) info.push(`---`);
782
- for (const schema of getScheme(requirement, document)){
802
+ for (const schema of getSecurities(requirement, document)){
803
+ const prefix = getSecurityPrefix(schema);
783
804
  if (schema.type === 'http') {
784
805
  info.push(/*#__PURE__*/ jsxs(renderer.Property, {
785
806
  name: "Authorization",
786
- type: {
787
- basic: 'Basic <token>',
788
- bearer: 'Bearer <token>'
789
- }[schema.scheme] ?? '<token>',
807
+ type: prefix ? `${prefix} <token>` : '<token>',
790
808
  required: true,
791
809
  children: [
792
810
  schema.description ? /*#__PURE__*/ jsx(Markdown, {
@@ -806,7 +824,7 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
806
824
  if (schema.type === 'oauth2') {
807
825
  info.push(/*#__PURE__*/ jsxs(renderer.Property, {
808
826
  name: "Authorization",
809
- type: "Bearer <token>",
827
+ type: prefix ? `${prefix} <token>` : '<token>',
810
828
  required: true,
811
829
  children: [
812
830
  schema.description ? /*#__PURE__*/ jsx(Markdown, {
@@ -820,15 +838,14 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
820
838
  })
821
839
  ]
822
840
  }),
823
- /*#__PURE__*/ jsxs("p", {
841
+ schema.scopes.length > 0 ? /*#__PURE__*/ jsxs("p", {
824
842
  children: [
825
- "Scope:",
826
- ' ',
843
+ "Scope: ",
827
844
  /*#__PURE__*/ jsx("code", {
828
- children: schema.scopes.length > 0 ? schema.scopes.join(', ') : 'none'
845
+ children: schema.scopes.join(', ')
829
846
  })
830
847
  ]
831
- })
848
+ }) : null
832
849
  ]
833
850
  }, id++));
834
851
  }
@@ -922,24 +939,6 @@ function createMethod(method, operation) {
922
939
  };
923
940
  }
924
941
 
925
- function APIPage({ operations, ctx, hasHead = true }) {
926
- const schema = ctx.document;
927
- return /*#__PURE__*/ jsx(ctx.renderer.Root, {
928
- baseUrl: ctx.baseUrl,
929
- children: operations.map((item)=>{
930
- const operation = schema.paths[item.path]?.[item.method];
931
- if (!operation) return null;
932
- const method = createMethod(item.method, operation);
933
- return /*#__PURE__*/ jsx(Operation, {
934
- method: method,
935
- path: item.path,
936
- ctx: ctx,
937
- hasHead: hasHead
938
- }, `${item.path}:${item.method}`);
939
- })
940
- });
941
- }
942
-
943
942
  const highlighter = await createHighlighter({
944
943
  themes: Object.values(bundledThemes),
945
944
  langs: Object.values(bundledLanguages)
@@ -1002,17 +1001,23 @@ const defaultRenderer = {
1002
1001
  APIPlayground
1003
1002
  };
1004
1003
 
1005
- function createOpenAPI(options) {
1006
- const document = Parser.dereference(options.documentOrPath);
1007
- return {
1008
- APIPage: async (props)=>{
1009
- const ctx = getContext(await document, options);
1010
- return /*#__PURE__*/ jsx(APIPage, {
1004
+ function APIPage(props) {
1005
+ const { operations, document, hasHead = true } = props;
1006
+ const ctx = getContext(document, props);
1007
+ return /*#__PURE__*/ jsx(ctx.renderer.Root, {
1008
+ baseUrl: ctx.baseUrl,
1009
+ children: operations.map((item)=>{
1010
+ const operation = document.paths[item.path]?.[item.method];
1011
+ if (!operation) return null;
1012
+ const method = createMethod(item.method, operation);
1013
+ return /*#__PURE__*/ jsx(Operation, {
1014
+ method: method,
1015
+ path: item.path,
1011
1016
  ctx: ctx,
1012
- ...props
1013
- });
1014
- }
1015
- };
1017
+ hasHead: hasHead
1018
+ }, `${item.path}:${item.method}`);
1019
+ })
1020
+ });
1016
1021
  }
1017
1022
  function getContext(document, options) {
1018
1023
  return {
@@ -1028,6 +1033,18 @@ function getContext(document, options) {
1028
1033
  };
1029
1034
  }
1030
1035
 
1036
+ function createOpenAPI(options) {
1037
+ const document = Parser.dereference(options.documentOrPath);
1038
+ return {
1039
+ APIPage: async (props)=>{
1040
+ return /*#__PURE__*/ jsx(APIPage, {
1041
+ document: await document,
1042
+ ...props
1043
+ });
1044
+ }
1045
+ };
1046
+ }
1047
+
1031
1048
  cva('rounded border px-1.5 py-1 text-xs font-medium leading-[12px]', {
1032
1049
  variants: {
1033
1050
  color: {
@@ -1063,18 +1080,19 @@ function getBadgeColor(method) {
1063
1080
  const data = file.data.data;
1064
1081
  if ('method' in data && typeof data.method === 'string') {
1065
1082
  const color = getBadgeColor(data.method);
1066
- node.name = [
1067
- node.name,
1068
- ' ',
1069
- // eslint-disable-next-line react/jsx-key -- static
1070
- /*#__PURE__*/ jsx("span", {
1071
- className: badgeVariants({
1072
- className: 'ms-auto text-nowrap',
1073
- color
1074
- }),
1075
- children: data.method
1076
- })
1077
- ];
1083
+ node.name = /*#__PURE__*/ jsxs(Fragment$1, {
1084
+ children: [
1085
+ node.name,
1086
+ ' ',
1087
+ /*#__PURE__*/ jsx("span", {
1088
+ className: badgeVariants({
1089
+ className: 'ms-auto text-nowrap',
1090
+ color
1091
+ }),
1092
+ children: data.method
1093
+ })
1094
+ ]
1095
+ });
1078
1096
  }
1079
1097
  return node;
1080
1098
  };
@@ -3,6 +3,7 @@ import { useContext, createContext, useState, useEffect } from 'react';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
  import { Check, Copy } from 'lucide-react';
5
5
  import { cn, useCopyButton, buttonVariants } from 'fumadocs-ui/components/api';
6
+ import dynamic from 'next/dynamic';
6
7
 
7
8
  const ApiContext = /*#__PURE__*/ createContext({
8
9
  baseUrl: undefined,
@@ -57,6 +58,7 @@ function useSchemaContext() {
57
58
  return ctx;
58
59
  }
59
60
 
61
+ const APIPlayground = dynamic(()=>import('./playground-client-Cn3a3hra.js').then((mod)=>mod.APIPlayground));
60
62
  function Root({ children, baseUrl, className, ...props }) {
61
63
  return /*#__PURE__*/ jsx("div", {
62
64
  className: cn('flex flex-col gap-24 text-sm text-fd-muted-foreground', className),
@@ -88,4 +90,4 @@ function CopyRouteButton({ className, route, ...props }) {
88
90
  });
89
91
  }
90
92
 
91
- export { CopyRouteButton as C, Root as R, SchemaContext as S, useApiContext as a, useSchemaContext as u };
93
+ export { APIPlayground as A, CopyRouteButton as C, Root as R, SchemaContext as S, useApiContext as a, useSchemaContext as u };
@@ -2,23 +2,6 @@ import * as react from 'react';
2
2
  import { ReactNode, ReactElement, MutableRefObject, HTMLAttributes } from 'react';
3
3
  import { FieldPath, ControllerRenderProps, ControllerFieldState, UseFormStateReturn } from 'react-hook-form';
4
4
 
5
- interface APIInfoProps {
6
- method: string;
7
- route: string;
8
- children: ReactNode;
9
- }
10
- interface PropertyProps {
11
- name: string;
12
- type: string;
13
- required?: boolean;
14
- deprecated?: boolean;
15
- children?: ReactNode;
16
- }
17
- interface RootProps {
18
- baseUrl?: string;
19
- children: ReactNode;
20
- }
21
-
22
5
  interface BaseRequestField {
23
6
  name: string;
24
7
  description?: string;
@@ -74,6 +57,23 @@ interface APIPlaygroundProps {
74
57
  schemas: Record<string, RequestSchema>;
75
58
  }
76
59
 
60
+ interface APIInfoProps {
61
+ method: string;
62
+ route: string;
63
+ children: ReactNode;
64
+ }
65
+ interface PropertyProps {
66
+ name: string;
67
+ type: string;
68
+ required?: boolean;
69
+ deprecated?: boolean;
70
+ children?: ReactNode;
71
+ }
72
+ interface RootProps {
73
+ baseUrl?: string;
74
+ children: ReactNode;
75
+ }
76
+
77
77
  interface FormValues {
78
78
  authorization: string;
79
79
  path: Record<string, unknown>;
@@ -106,8 +106,6 @@ type DynamicField = {
106
106
  };
107
107
  declare function useSchemaContext(): SchemaContextType;
108
108
 
109
- declare function Root({ children, baseUrl, className, ...props }: RootProps & HTMLAttributes<HTMLDivElement>): React.ReactElement;
110
-
111
109
  declare const APIPlayground: react.ComponentType<APIPlaygroundProps & {
112
110
  fields?: {
113
111
  auth?: CustomField<"authorization", PrimitiveRequestField>;
@@ -117,6 +115,8 @@ declare const APIPlayground: react.ComponentType<APIPlaygroundProps & {
117
115
  body?: CustomField<"body", RequestSchema>;
118
116
  };
119
117
  } & HTMLAttributes<HTMLFormElement>>;
118
+ declare function Root({ children, baseUrl, className, ...props }: RootProps & HTMLAttributes<HTMLDivElement>): React.ReactElement;
119
+
120
120
  declare function APIInfo({ children, className, route, badgeClassname, method, ...props }: APIInfoProps & HTMLAttributes<HTMLDivElement> & {
121
121
  badgeClassname?: string;
122
122
  }): React.ReactElement;
package/dist/ui/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import dynamic from 'next/dynamic';
3
2
  import { cn } from 'fumadocs-ui/components/api';
4
3
  import { Fragment } from 'react';
5
4
  import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
6
5
  import { cva } from 'class-variance-authority';
7
- import { C as CopyRouteButton } from './client-client-CbQJObP6.js';
8
- export { R as Root, u as useSchemaContext } from './client-client-CbQJObP6.js';
6
+ import { C as CopyRouteButton } from './client-client-CMzXLiVt.js';
7
+ export { A as APIPlayground, R as Root, u as useSchemaContext } from './client-client-CMzXLiVt.js';
9
8
 
10
9
  const badgeVariants = cva('rounded border px-1.5 py-1 text-xs font-medium leading-[12px]', {
11
10
  variants: {
@@ -33,7 +32,6 @@ function getBadgeColor(method) {
33
32
  }
34
33
  }
35
34
 
36
- const APIPlayground = dynamic(()=>import('./playground-client-W7yhm4ZD.js').then((mod)=>mod.APIPlayground));
37
35
  function Route({ route }) {
38
36
  const segments = route.split('/').filter((part)=>part.length > 0);
39
37
  return /*#__PURE__*/ jsx("div", {
@@ -141,4 +139,4 @@ function ObjectCollapsible(props) {
141
139
  });
142
140
  }
143
141
 
144
- export { API, APIExample, APIInfo, APIPlayground, ObjectCollapsible, Property };
142
+ export { API, APIExample, APIInfo, ObjectCollapsible, Property };
@@ -6,7 +6,7 @@ import { FormProvider, Controller, useFormContext, useFieldArray, useForm, useWa
6
6
  import useSWRImmutable from 'swr/immutable';
7
7
  import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
8
8
  import { cn, buttonVariants } from 'fumadocs-ui/components/api';
9
- import { u as useSchemaContext, a as useApiContext, S as SchemaContext } from './client-client-CbQJObP6.js';
9
+ import { u as useSchemaContext, a as useApiContext, S as SchemaContext } from './client-client-CMzXLiVt.js';
10
10
  import { Slot } from '@radix-ui/react-slot';
11
11
  import { cva } from 'class-variance-authority';
12
12
  import { CircleCheckIcon, CircleXIcon, ChevronDown, ChevronUp, Check, Trash2, Plus } from 'lucide-react';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",