fumadocs-openapi 5.7.5 → 5.8.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.
@@ -1,6 +1,5 @@
1
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import Slugger from 'github-slugger';
3
- import Parser from '@apidevtools/json-schema-ref-parser';
4
3
  import { Fragment as Fragment$1 } from 'react';
5
4
  import { sample } from 'openapi-sampler';
6
5
  import { compile } from '@fumari/json-schema-to-typescript';
@@ -10,16 +9,15 @@ import { remark } from 'remark';
10
9
  import remarkRehype from 'remark-rehype';
11
10
  import { toJsxRuntime } from 'hast-util-to-jsx-runtime';
12
11
  import { Heading } from 'fumadocs-ui/components/heading';
13
- import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
14
- import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
12
+ import { Tabs, Tab } from 'fumadocs-ui/components/tabs';
13
+ import { Accordions, Accordion } from 'fumadocs-ui/components/accordion';
15
14
  import * as Base from 'fumadocs-ui/components/codeblock';
16
15
  import { highlight } from 'fumadocs-core/server';
17
- import { APIPlayground, ObjectCollapsible, Property, APIExample as APIExample$1, APIInfo, API, Root } from '../ui/index.js';
16
+ import { Root, API, APIInfo, APIExample as APIExample$1, Property, ObjectCollapsible, APIPlayground } from '../ui/index.js';
17
+ import Parser from '@apidevtools/json-schema-ref-parser';
18
+ import { upgrade } from '@scalar/openapi-parser';
18
19
  import { cva } from 'class-variance-authority';
19
20
 
20
- function noRef(v) {
21
- return v;
22
- }
23
21
  function getPreferredType(body) {
24
22
  if ('application/json' in body) return 'application/json';
25
23
  return Object.keys(body)[0];
@@ -29,13 +27,21 @@ function getPreferredType(body) {
29
27
  */ function toSampleInput(value) {
30
28
  return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
31
29
  }
30
+ function isNullable(schema, includeOneOf = true) {
31
+ if (Array.isArray(schema.type) && schema.type.includes('null')) return true;
32
+ if (includeOneOf && (schema.anyOf || schema.oneOf)) {
33
+ if (schema.anyOf?.some((item)=>isNullable(item))) return true;
34
+ if (schema.oneOf?.some((item)=>isNullable(item))) return true;
35
+ }
36
+ return false;
37
+ }
32
38
 
33
39
  function getSecurities(requirement, document) {
34
40
  const results = [];
35
41
  const schemas = document.components?.securitySchemes ?? {};
36
42
  for (const [key, scopes] of Object.entries(requirement)){
37
43
  if (!(key in schemas)) return [];
38
- const schema = noRef(schemas[key]);
44
+ const schema = schemas[key];
39
45
  results.push({
40
46
  ...schema,
41
47
  scopes
@@ -54,22 +60,22 @@ function getSecurityPrefix(security) {
54
60
  function generateSample(path, method, { baseUrl, document }) {
55
61
  const params = [];
56
62
  const responses = {};
57
- for (const param of method.parameters){
63
+ for (const param of method.parameters ?? []){
58
64
  if (param.schema) {
59
65
  params.push({
60
66
  name: param.name,
61
67
  in: param.in,
62
- schema: noRef(param.schema),
68
+ schema: param.schema,
63
69
  sample: param.example ?? sample(param.schema)
64
70
  });
65
71
  } else if (param.content) {
66
72
  const key = getPreferredType(param.content);
67
73
  const content = key ? param.content[key] : undefined;
68
- if (!key || !content?.schema) throw new Error(`Cannot find parameter schema for ${param.name} in ${path} ${method.method}`);
74
+ if (!key || !content) throw new Error(`Cannot find parameter schema for ${param.name} in ${path} ${method.method}`);
69
75
  params.push({
70
76
  name: param.name,
71
77
  in: param.in,
72
- schema: noRef(content.schema),
78
+ schema: content.schema ?? {},
73
79
  sample: content.example ?? param.example ?? sample(content.schema)
74
80
  });
75
81
  }
@@ -90,22 +96,22 @@ function generateSample(path, method, { baseUrl, document }) {
90
96
  }
91
97
  let bodyOutput;
92
98
  if (method.requestBody) {
93
- const body = noRef(method.requestBody).content;
99
+ const body = method.requestBody.content;
94
100
  const type = getPreferredType(body);
95
- const schema = type ? noRef(body[type].schema) : undefined;
96
- if (!type || !schema) throw new Error(`Cannot find body schema for ${path} ${method.method}`);
101
+ if (!type) throw new Error(`Cannot find body schema for ${path} ${method.method}: missing media type`);
102
+ const schema = (type ? body[type].schema : undefined) ?? {};
97
103
  bodyOutput = {
98
104
  schema,
99
105
  mediaType: type,
100
106
  sample: body[type].example ?? generateBody(method.method, schema)
101
107
  };
102
108
  }
103
- for (const [code, value] of Object.entries(method.responses)){
104
- const content = noRef(value).content;
109
+ for (const [code, response] of Object.entries(method.responses ?? {})){
110
+ const content = response.content;
105
111
  if (!content) continue;
106
112
  const mediaType = getPreferredType(content);
107
113
  if (!mediaType) continue;
108
- const responseSchema = noRef(content[mediaType].schema);
114
+ const responseSchema = content[mediaType].schema;
109
115
  if (!responseSchema) continue;
110
116
  responses[code] = {
111
117
  mediaType,
@@ -272,13 +278,15 @@ response = requests.request("${endpoint.method}", url${variables.size > 0 ? `, $
272
278
  print(response.text)`;
273
279
  }
274
280
 
275
- async function getTypescriptSchema(endpoint, code) {
281
+ async function getTypescriptSchema(endpoint, code, dereferenceMap) {
276
282
  if (code in endpoint.responses) {
277
283
  return compile(// re-running on the same schema results in error
278
284
  // because it uses `defineProperty` to define internal references
279
285
  // we clone the schema to fix this problem
280
- structuredClone(endpoint.responses[code].schema), 'Response', {
286
+ // @ts-expect-error any types
287
+ endpoint.responses[code].schema, 'Response', {
281
288
  $refOptions: false,
289
+ schemaToId: dereferenceMap,
282
290
  bannerComment: '',
283
291
  additionalProperties: false,
284
292
  format: true,
@@ -289,7 +297,7 @@ async function getTypescriptSchema(endpoint, code) {
289
297
 
290
298
  function Playground({ path, method, ctx }) {
291
299
  let currentId = 0;
292
- const bodyContent = noRef(method.requestBody)?.content;
300
+ const bodyContent = method.requestBody?.content;
293
301
  const mediaType = bodyContent ? getPreferredType(bodyContent) : undefined;
294
302
  const context = {
295
303
  allowFile: mediaType === 'multipart/form-data',
@@ -297,17 +305,18 @@ function Playground({ path, method, ctx }) {
297
305
  nextId () {
298
306
  return String(currentId++);
299
307
  },
300
- registered: new WeakMap()
308
+ registered: new WeakMap(),
309
+ render: ctx
301
310
  };
302
311
  const props = {
303
312
  authorization: getAuthorizationField(method, ctx),
304
313
  method: method.method,
305
314
  route: path,
306
315
  bodyType: mediaType === 'multipart/form-data' ? 'form-data' : 'json',
307
- path: method.parameters.filter((v)=>v.in === 'path').map((v)=>parameterToField(v, context)),
308
- query: method.parameters.filter((v)=>v.in === 'query').map((v)=>parameterToField(v, context)),
309
- header: method.parameters.filter((v)=>v.in === 'header').map((v)=>parameterToField(v, context)),
310
- body: bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(noRef(bodyContent[mediaType].schema), true, context) : undefined,
316
+ path: method.parameters?.filter((v)=>v.in === 'path').map((v)=>parameterToField(v, context)),
317
+ query: method.parameters?.filter((v)=>v.in === 'query').map((v)=>parameterToField(v, context)),
318
+ header: method.parameters?.filter((v)=>v.in === 'header').map((v)=>parameterToField(v, context)),
319
+ body: bodyContent && mediaType && bodyContent[mediaType].schema ? toSchema(bodyContent[mediaType].schema, true, context) : undefined,
311
320
  schemas: context.schema
312
321
  };
313
322
  return /*#__PURE__*/ jsx(ctx.renderer.APIPlayground, {
@@ -342,7 +351,7 @@ function getIdFromSchema(schema, required, ctx) {
342
351
  function parameterToField(v, ctx) {
343
352
  return {
344
353
  name: v.name,
345
- ...toSchema(noRef(v.schema) ?? {
354
+ ...toSchema(v.schema ?? {
346
355
  type: 'string'
347
356
  }, v.required ?? false, ctx)
348
357
  };
@@ -360,19 +369,19 @@ function toSchema(schema, required, ctx) {
360
369
  type: 'array',
361
370
  description: schema.description ?? schema.title,
362
371
  isRequired: required,
363
- items: getIdFromSchema(noRef(schema.items), false, ctx)
372
+ items: getIdFromSchema(schema.items, false, ctx)
364
373
  };
365
374
  }
366
375
  if (schema.type === 'object' || schema.properties !== undefined || schema.allOf !== undefined) {
367
376
  const properties = {};
368
377
  Object.entries(schema.properties ?? {}).forEach(([key, prop])=>{
369
- properties[key] = toReference(noRef(prop), schema.required?.includes(key) ?? false, ctx);
378
+ properties[key] = toReference(prop, schema.required?.includes(key) ?? false, ctx);
370
379
  });
371
380
  schema.allOf?.forEach((c)=>{
372
- const field = toSchema(noRef(c), true, ctx);
381
+ const field = toSchema(c, true, ctx);
373
382
  if (field.type === 'object') Object.assign(properties, field.properties);
374
383
  });
375
- const additional = noRef(schema.additionalProperties);
384
+ const additional = schema.additionalProperties;
376
385
  let additionalProperties;
377
386
  if (additional && typeof additional === 'object') {
378
387
  if (!additional.type && !additional.anyOf && !additional.allOf && !additional.oneOf) {
@@ -397,8 +406,7 @@ function toSchema(schema, required, ctx) {
397
406
  return {
398
407
  type: 'switcher',
399
408
  description: schema.description ?? schema.title,
400
- items: Object.fromEntries(combine.map((c, idx)=>{
401
- const item = noRef(c);
409
+ items: Object.fromEntries(combine.map((item, idx)=>{
402
410
  return [
403
411
  item.title ?? item.type ?? `Item ${idx.toString()}`,
404
412
  toReference(item, true, ctx)
@@ -419,6 +427,30 @@ function toSchema(schema, required, ctx) {
419
427
  description: schema.description ?? schema.title
420
428
  };
421
429
  }
430
+ if (Array.isArray(schema.type)) {
431
+ const items = {};
432
+ for (const type of schema.type){
433
+ if (type === 'array') {
434
+ items[type] = {
435
+ type,
436
+ items: 'items' in schema && schema.items ? toSchema(schema.items, false, ctx) : toSchema({}, required, ctx),
437
+ isRequired: required
438
+ };
439
+ } else if (type !== 'null' && type !== 'object') {
440
+ items[type] = {
441
+ type: type === 'integer' ? 'number' : type,
442
+ isRequired: required,
443
+ defaultValue: schema.example ?? schema.default ?? ''
444
+ };
445
+ }
446
+ }
447
+ return {
448
+ type: 'switcher',
449
+ description: schema.description ?? schema.title,
450
+ items,
451
+ isRequired: required
452
+ };
453
+ }
422
454
  return {
423
455
  type: schema.type === 'integer' ? 'number' : schema.type,
424
456
  defaultValue: schema.example ?? '',
@@ -477,35 +509,45 @@ function heading(depth, child, ctx) {
477
509
  };
478
510
  function add(s) {
479
511
  if (s.type) {
480
- result.type = result.type ? 'object' : s.type;
512
+ var _result;
513
+ (_result = result).type ?? (_result.type = []);
514
+ if (!Array.isArray(result.type)) {
515
+ result.type = [
516
+ result.type
517
+ ];
518
+ }
519
+ for (const v of Array.isArray(s.type) ? s.type : [
520
+ s.type
521
+ ]){
522
+ if (!result.type.includes(v)) {
523
+ result.type.push(v);
524
+ }
525
+ }
481
526
  }
482
527
  if (s.properties) {
483
- var _result;
484
- (_result = result).properties ?? (_result.properties = {});
528
+ var _result1;
529
+ (_result1 = result).properties ?? (_result1.properties = {});
485
530
  Object.assign(result.properties, s.properties);
486
531
  }
487
532
  if (s.additionalProperties === true) {
488
533
  result.additionalProperties = true;
489
534
  } else if (s.additionalProperties && typeof result.additionalProperties !== 'boolean') {
490
- var _result1;
491
- (_result1 = result).additionalProperties ?? (_result1.additionalProperties = {});
535
+ var _result2;
536
+ (_result2 = result).additionalProperties ?? (_result2.additionalProperties = {});
492
537
  Object.assign(result.additionalProperties, s.additionalProperties);
493
538
  }
494
539
  if (s.required) {
495
- var _result2;
496
- (_result2 = result).required ?? (_result2.required = []);
540
+ var _result3;
541
+ (_result3 = result).required ?? (_result3.required = []);
497
542
  result.required.push(...s.required);
498
543
  }
499
544
  if (s.enum && s.enum.length > 0) {
500
- var _result3;
501
- (_result3 = result).enum ?? (_result3.enum = []);
545
+ var _result4;
546
+ (_result4 = result).enum ?? (_result4.enum = []);
502
547
  result.enum.push(...s.enum);
503
548
  }
504
- if (s.nullable) {
505
- result.nullable = true;
506
- }
507
549
  if (s.allOf) {
508
- noRef(s.allOf).forEach(add);
550
+ s.allOf.forEach(add);
509
551
  }
510
552
  }
511
553
  schema.forEach(add);
@@ -541,7 +583,7 @@ function Schema({ name, schema, ctx }) {
541
583
  } else if (additionalProperties) {
542
584
  child.push(/*#__PURE__*/ jsx(Schema, {
543
585
  name: "[key: string]",
544
- schema: noRef(additionalProperties),
586
+ schema: additionalProperties,
545
587
  ctx: {
546
588
  ...ctx,
547
589
  required: false,
@@ -553,7 +595,7 @@ function Schema({ name, schema, ctx }) {
553
595
  const rendered = Object.entries(properties).map(([key, value])=>{
554
596
  return /*#__PURE__*/ jsx(Schema, {
555
597
  name: key,
556
- schema: noRef(value),
598
+ schema: value,
557
599
  ctx: {
558
600
  ...ctx,
559
601
  required: schema.required?.includes(key) ?? false,
@@ -568,7 +610,7 @@ function Schema({ name, schema, ctx }) {
568
610
  if (schema.allOf && parseObject) {
569
611
  return /*#__PURE__*/ jsx(Schema, {
570
612
  name: name,
571
- schema: combineSchema(noRef(schema.allOf)),
613
+ schema: combineSchema(schema.allOf),
572
614
  ctx: ctx
573
615
  });
574
616
  }
@@ -602,7 +644,7 @@ function Schema({ name, schema, ctx }) {
602
644
  ]
603
645
  }, field.key))
604
646
  }, "fields"));
605
- if ((isObject(schema) || schema.allOf) && !parseObject) {
647
+ if (isObject(schema) && !parseObject && !stack.includes(schema)) {
606
648
  child.push(/*#__PURE__*/ jsx(renderer.ObjectCollapsible, {
607
649
  name: "Attributes",
608
650
  children: /*#__PURE__*/ jsx(Schema, {
@@ -611,7 +653,11 @@ function Schema({ name, schema, ctx }) {
611
653
  ctx: {
612
654
  ...ctx,
613
655
  parseObject: true,
614
- required: false
656
+ required: false,
657
+ stack: [
658
+ schema,
659
+ ...stack
660
+ ]
615
661
  }
616
662
  })
617
663
  }, "attributes"));
@@ -624,13 +670,13 @@ function Schema({ name, schema, ctx }) {
624
670
  ...schema.type === 'array' ? [
625
671
  schema.items
626
672
  ] : []
627
- ].map(noRef).filter((s)=>isComplexType(s) && !stack.includes(s));
673
+ ].filter((s)=>isComplexType(s) && !stack.includes(s));
628
674
  const renderedMentionedTypes = mentionedObjectTypes.map((s, idx)=>{
629
675
  return /*#__PURE__*/ jsx(renderer.ObjectCollapsible, {
630
676
  name: s.title ?? `Object ${(idx + 1).toString()}`,
631
677
  children: /*#__PURE__*/ jsx(Schema, {
632
678
  name: "element",
633
- schema: noRef(s),
679
+ schema: s,
634
680
  ctx: {
635
681
  ...ctx,
636
682
  stack: [
@@ -659,70 +705,82 @@ function Schema({ name, schema, ctx }) {
659
705
  if (schema.anyOf ?? schema.oneOf ?? schema.allOf) return true;
660
706
  return isObject(schema) || schema.type === 'array';
661
707
  }
662
- function getSchemaType(schema, ctx) {
663
- if (schema.nullable) {
664
- const type = getSchemaType({
665
- ...schema,
666
- nullable: false
667
- }, ctx);
708
+ function getSchemaType(schema, ctx, isRoot = true) {
709
+ if (isNullable(schema) && isRoot) {
710
+ const type = getSchemaType(schema, ctx, false);
668
711
  // null if schema only contains `nullable`
669
712
  return type === 'unknown' ? 'null' : `${type} | null`;
670
713
  }
671
714
  if (schema.title) return schema.title;
672
- if (schema.type === 'array') return `array<${getSchemaType(noRef(schema.items), ctx)}>`;
673
- if (schema.oneOf) return schema.oneOf.map((one)=>getSchemaType(noRef(one), ctx)).join(' | ');
715
+ if (schema.type === 'array') return `array<${getSchemaType(schema.items, ctx)}>`;
716
+ if (schema.oneOf) return schema.oneOf.map((one)=>getSchemaType(one, ctx, false)).filter((v)=>v !== 'unknown').join(' | ');
674
717
  if (schema.allOf) {
675
- const allTypeNames = schema.allOf.map((one)=>getSchemaType(noRef(one), ctx));
676
- const hasNull = allTypeNames.includes('null');
677
- const nonNullTypes = allTypeNames.filter((v)=>v !== 'null');
678
- const nonNullTypeNames = nonNullTypes.join(' & ');
679
- if (!hasNull) return nonNullTypeNames;
680
- if (nonNullTypes.length === 0) return 'null';
681
- else if (nonNullTypes.length === 1 || !hasNull) return `${nonNullTypeNames} | null`;
682
- else return `(${nonNullTypeNames}) | null`;
718
+ return schema.allOf.map((one)=>getSchemaType(one, ctx, false)).filter((v)=>v !== 'unknown').join(' & ');
683
719
  }
684
- if (schema.not) return `not ${getSchemaType(noRef(schema.not), ctx)}`;
720
+ if (schema.not) return `not ${getSchemaType(schema.not, ctx, false)}`;
685
721
  if (schema.anyOf) {
686
- return `Any properties in ${schema.anyOf.map((one)=>getSchemaType(noRef(one), ctx)).join(', ')}`;
722
+ return `Any properties in ${schema.anyOf.map((one)=>getSchemaType(one, ctx, false)).filter((v)=>v !== 'unknown').join(', ')}`;
687
723
  }
688
724
  if (schema.type === 'string' && schema.format === 'binary' && ctx.allowFile) return 'file';
689
- if (schema.type) return schema.type;
725
+ if (schema.type && Array.isArray(schema.type)) {
726
+ const nonNullTypes = schema.type.filter((v)=>v !== 'null');
727
+ if (nonNullTypes.length > 0) return nonNullTypes.join(' | ');
728
+ } else if (schema.type && schema.type !== 'null') {
729
+ return schema.type;
730
+ }
690
731
  if (isObject(schema)) return 'object';
691
732
  return 'unknown';
692
733
  }
693
734
 
694
- function Operation({ baseUrls, path, method, ctx, hasHead }) {
695
- let level = 2;
696
- const body = noRef(method.requestBody);
735
+ /**
736
+ * Summarize method endpoint information
737
+ */ function createMethod(method, path, operation) {
738
+ return {
739
+ description: path.description,
740
+ summary: path.summary,
741
+ ...operation,
742
+ parameters: [
743
+ ...operation.parameters ?? [],
744
+ ...path.parameters ?? []
745
+ ],
746
+ method: method.toUpperCase()
747
+ };
748
+ }
749
+
750
+ const methodKeys = [
751
+ 'get',
752
+ 'post',
753
+ 'patch',
754
+ 'delete',
755
+ 'head',
756
+ 'put'
757
+ ];
758
+
759
+ function Operation({ type = 'operation', path, method, ctx, hasHead, headingLevel = 2 }) {
760
+ const { baseUrls } = ctx;
761
+ const body = method.requestBody;
697
762
  const security = method.security ?? ctx.document.security;
698
- const info = [];
763
+ let headNode = null;
764
+ let bodyNode = null;
765
+ let callbacksNode = null;
699
766
  if (hasHead) {
700
- info.push(heading(level, method.summary ?? (method.operationId ? idToTitle(method.operationId) : path), ctx));
701
- level++;
702
- if (method.description) {
703
- info.push(/*#__PURE__*/ jsx(Markdown, {
704
- text: method.description
705
- }, "description"));
706
- }
707
- }
708
- info.push(/*#__PURE__*/ jsx(Playground, {
709
- path: path,
710
- method: method,
711
- ctx: ctx
712
- }, "playground"));
713
- if (security) {
714
- info.push(heading(level, 'Authorization', ctx));
715
- info.push(/*#__PURE__*/ jsx(AuthSection, {
716
- requirements: security,
717
- ctx: ctx
718
- }, "auth"));
767
+ const title = method.summary ?? (method.operationId ? idToTitle(method.operationId) : path);
768
+ headNode = /*#__PURE__*/ jsxs(Fragment, {
769
+ children: [
770
+ heading(headingLevel, title, ctx),
771
+ method.description ? /*#__PURE__*/ jsx(Markdown, {
772
+ text: method.description
773
+ }, "description") : null
774
+ ]
775
+ });
776
+ headingLevel++;
719
777
  }
720
778
  if (body) {
721
779
  const type = getPreferredType(body.content);
722
780
  if (!type) throw new Error(`No supported media type for body content: ${path}`);
723
- info.push(/*#__PURE__*/ jsxs(Fragment$1, {
781
+ bodyNode = /*#__PURE__*/ jsxs(Fragment, {
724
782
  children: [
725
- heading(level, 'Request Body', ctx),
783
+ heading(headingLevel, 'Request Body', ctx),
726
784
  /*#__PURE__*/ jsxs("div", {
727
785
  className: "mb-8 flex flex-row items-center justify-between gap-2",
728
786
  children: [
@@ -739,7 +797,7 @@ function Operation({ baseUrls, path, method, ctx, hasHead }) {
739
797
  }) : null,
740
798
  /*#__PURE__*/ jsx(Schema, {
741
799
  name: "body",
742
- schema: noRef(body.content[type].schema ?? {}),
800
+ schema: body.content[type].schema ?? {},
743
801
  ctx: {
744
802
  readOnly: method.method === 'GET',
745
803
  writeOnly: method.method !== 'GET',
@@ -749,11 +807,11 @@ function Operation({ baseUrls, path, method, ctx, hasHead }) {
749
807
  }
750
808
  })
751
809
  ]
752
- }, "body"));
810
+ });
753
811
  }
754
812
  const parameterGroups = new Map();
755
813
  const endpoint = generateSample(path, method, ctx);
756
- for (const param of method.parameters){
814
+ for (const param of method.parameters ?? []){
757
815
  const pInfo = endpoint.parameters.find((item)=>item.name === param.name && item.in === param.in);
758
816
  if (!pInfo) continue;
759
817
  const schema = pInfo.schema;
@@ -781,24 +839,81 @@ function Operation({ baseUrls, path, method, ctx, hasHead }) {
781
839
  }, param.name));
782
840
  parameterGroups.set(groupName, group);
783
841
  }
784
- for (const [group, parameters] of Array.from(parameterGroups.entries())){
785
- info.push(heading(level, group, ctx), ...parameters);
842
+ if (method.callbacks) {
843
+ callbacksNode = /*#__PURE__*/ jsxs(Fragment, {
844
+ children: [
845
+ heading(headingLevel, 'Webhooks', ctx),
846
+ Object.entries(method.callbacks).map(([name, callback])=>{
847
+ const nodes = Object.entries(callback).map(([path, pathItem])=>{
848
+ const pathNodes = methodKeys.map((method)=>{
849
+ const operation = pathItem[method];
850
+ if (!operation) return null;
851
+ return /*#__PURE__*/ jsx(Operation, {
852
+ type: "webhook",
853
+ hasHead: true,
854
+ path: path,
855
+ headingLevel: headingLevel + 1,
856
+ method: createMethod(method, pathItem, operation),
857
+ ctx: ctx
858
+ }, method);
859
+ });
860
+ return /*#__PURE__*/ jsx(Fragment$1, {
861
+ children: pathNodes
862
+ }, path);
863
+ });
864
+ return /*#__PURE__*/ jsx(Fragment$1, {
865
+ children: nodes
866
+ }, name);
867
+ })
868
+ ]
869
+ });
786
870
  }
787
- return /*#__PURE__*/ jsxs(ctx.renderer.API, {
871
+ const info = /*#__PURE__*/ jsxs(ctx.renderer.APIInfo, {
872
+ head: headNode,
873
+ method: method.method,
874
+ route: path,
875
+ baseUrls: type === 'operation' ? baseUrls : [],
788
876
  children: [
789
- /*#__PURE__*/ jsx(ctx.renderer.APIInfo, {
790
- method: method.method,
791
- route: path,
792
- baseUrls: baseUrls,
793
- children: info
794
- }),
795
- /*#__PURE__*/ jsx(APIExample, {
877
+ type === 'operation' ? /*#__PURE__*/ jsx(Playground, {
878
+ path: path,
796
879
  method: method,
797
- endpoint: endpoint,
798
880
  ctx: ctx
799
- })
881
+ }) : null,
882
+ security ? /*#__PURE__*/ jsxs(Fragment, {
883
+ children: [
884
+ heading(headingLevel, 'Authorization', ctx),
885
+ /*#__PURE__*/ jsx(AuthSection, {
886
+ requirements: security,
887
+ ctx: ctx
888
+ })
889
+ ]
890
+ }) : null,
891
+ bodyNode,
892
+ Array.from(parameterGroups.entries()).map(([group, params])=>{
893
+ return /*#__PURE__*/ jsxs(Fragment$1, {
894
+ children: [
895
+ heading(headingLevel, group, ctx),
896
+ params
897
+ ]
898
+ }, group);
899
+ }),
900
+ callbacksNode
800
901
  ]
801
902
  });
903
+ if (type === 'operation') {
904
+ return /*#__PURE__*/ jsxs(ctx.renderer.API, {
905
+ children: [
906
+ info,
907
+ /*#__PURE__*/ jsx(APIExample, {
908
+ method: method,
909
+ endpoint: endpoint,
910
+ ctx: ctx
911
+ })
912
+ ]
913
+ });
914
+ } else {
915
+ return info;
916
+ }
802
917
  }
803
918
  const defaultSamples = [
804
919
  {
@@ -875,6 +990,14 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
875
990
  for (const requirement of requirements){
876
991
  for (const schema of getSecurities(requirement, document)){
877
992
  const prefix = getSecurityPrefix(schema);
993
+ const scopeElement = schema.scopes.length > 0 ? /*#__PURE__*/ jsxs("p", {
994
+ children: [
995
+ "Scope: ",
996
+ /*#__PURE__*/ jsx("code", {
997
+ children: schema.scopes.join(', ')
998
+ })
999
+ ]
1000
+ }) : null;
878
1001
  if (schema.type === 'http') {
879
1002
  info.push(/*#__PURE__*/ jsxs(renderer.Property, {
880
1003
  name: "Authorization",
@@ -889,7 +1012,8 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
889
1012
  "In: ",
890
1013
  /*#__PURE__*/ jsx("code", {
891
1014
  children: "header"
892
- })
1015
+ }),
1016
+ scopeElement
893
1017
  ]
894
1018
  })
895
1019
  ]
@@ -912,14 +1036,7 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
912
1036
  })
913
1037
  ]
914
1038
  }),
915
- schema.scopes.length > 0 ? /*#__PURE__*/ jsxs("p", {
916
- children: [
917
- "Scope: ",
918
- /*#__PURE__*/ jsx("code", {
919
- children: schema.scopes.join(', ')
920
- })
921
- ]
922
- }) : null
1039
+ scopeElement
923
1040
  ]
924
1041
  }, id++));
925
1042
  }
@@ -936,32 +1053,37 @@ function AuthSection({ ctx: { document, renderer }, requirements }) {
936
1053
  "In: ",
937
1054
  /*#__PURE__*/ jsx("code", {
938
1055
  children: schema.in
939
- })
1056
+ }),
1057
+ scopeElement
940
1058
  ]
941
1059
  })
942
1060
  ]
943
1061
  }, id++));
944
1062
  }
945
1063
  if (schema.type === 'openIdConnect') {
946
- info.push(/*#__PURE__*/ jsx(renderer.Property, {
1064
+ info.push(/*#__PURE__*/ jsxs(renderer.Property, {
947
1065
  name: "OpenID Connect",
948
1066
  type: "<token>",
949
1067
  required: true,
950
- children: schema.description ? /*#__PURE__*/ jsx(Markdown, {
951
- text: schema.description
952
- }) : null
1068
+ children: [
1069
+ schema.description ? /*#__PURE__*/ jsx(Markdown, {
1070
+ text: schema.description
1071
+ }) : null,
1072
+ scopeElement
1073
+ ]
953
1074
  }, id++));
954
1075
  }
955
1076
  }
956
1077
  }
957
1078
  return info;
958
1079
  }
959
- async function ResponseTabs({ endpoint, operation, ctx: { renderer, generateTypeScriptSchema } }) {
1080
+ async function ResponseTabs({ endpoint, operation, ctx: { renderer, generateTypeScriptSchema, dereferenceMap } }) {
960
1081
  const items = [];
961
1082
  const children = [];
1083
+ if (!operation.responses) return null;
962
1084
  for (const code of Object.keys(operation.responses)){
963
1085
  const types = [];
964
- let description = noRef(operation.responses[code]).description;
1086
+ let description = operation.responses[code].description;
965
1087
  if (!description && code in endpoint.responses) description = endpoint.responses[code].schema.description ?? '';
966
1088
  if (code in endpoint.responses) {
967
1089
  types.push({
@@ -974,7 +1096,7 @@ async function ResponseTabs({ endpoint, operation, ctx: { renderer, generateType
974
1096
  if (generateTypeScriptSchema) {
975
1097
  ts = await generateTypeScriptSchema(endpoint, code);
976
1098
  } else if (generateTypeScriptSchema === undefined) {
977
- ts = await getTypescriptSchema(endpoint, code);
1099
+ ts = await getTypescriptSchema(endpoint, code, dereferenceMap);
978
1100
  }
979
1101
  if (ts) {
980
1102
  types.push({
@@ -1005,21 +1127,6 @@ async function ResponseTabs({ endpoint, operation, ctx: { renderer, generateType
1005
1127
  });
1006
1128
  }
1007
1129
 
1008
- /**
1009
- * Summarize method endpoint information
1010
- */ function createMethod(method, path, operation) {
1011
- return {
1012
- description: path.description,
1013
- summary: path.summary,
1014
- ...operation,
1015
- parameters: [
1016
- ...noRef(operation.parameters ?? []),
1017
- ...noRef(path.parameters ?? [])
1018
- ],
1019
- method: method.toUpperCase()
1020
- };
1021
- }
1022
-
1023
1130
  async function CodeBlock({ code, lang, options, ...rest }) {
1024
1131
  const rendered = await highlight(code, {
1025
1132
  lang,
@@ -1082,38 +1189,79 @@ function createRenders(shikiOptions) {
1082
1189
  }
1083
1190
 
1084
1191
  const cache = new Map();
1085
- async function APIPage(props) {
1086
- const { operations, hasHead = true } = props;
1087
- let document;
1088
- if (typeof props.document === 'string' && !props.disableCache) {
1089
- const cached = cache.get(props.document);
1090
- document = cached ?? await Parser.dereference(props.document);
1091
- cache.set(props.document, document);
1092
- } else {
1093
- document = await Parser.dereference(props.document);
1192
+ /**
1193
+ * process & reference input document to a Fumadocs OpenAPI compatible format
1194
+ */ async function processDocument(document, disableCache = false) {
1195
+ const cached = !disableCache && typeof document === 'string' ? cache.get(document) : null;
1196
+ if (cached) return cached;
1197
+ let bundled = await Parser.bundle(document, {
1198
+ mutateInputSchema: false
1199
+ });
1200
+ bundled = upgrade(bundled).specification;
1201
+ const dereferenceMap = new Map();
1202
+ const dereferenced = await Parser.dereference(bundled, {
1203
+ mutateInputSchema: true,
1204
+ dereference: {
1205
+ onDereference ($ref, schema) {
1206
+ dereferenceMap.set(schema, $ref);
1207
+ }
1208
+ }
1209
+ });
1210
+ const processed = {
1211
+ document: dereferenced,
1212
+ dereferenceMap
1213
+ };
1214
+ if (!disableCache && typeof document === 'string') {
1215
+ cache.set(document, processed);
1094
1216
  }
1095
- const ctx = getContext(document, props);
1096
- return /*#__PURE__*/ jsx(ctx.renderer.Root, {
1217
+ return processed;
1218
+ }
1219
+
1220
+ async function APIPage(props) {
1221
+ const { operations, hasHead = true, webhooks, disableCache = process.env.NODE_ENV === 'development' } = props;
1222
+ const processed = await processDocument(props.document, disableCache);
1223
+ const ctx = await getContext(processed, props);
1224
+ const { document } = processed;
1225
+ return /*#__PURE__*/ jsxs(ctx.renderer.Root, {
1097
1226
  baseUrl: ctx.baseUrl,
1098
- children: operations.map((item)=>{
1099
- const pathItem = document.paths[item.path];
1100
- if (!pathItem) return null;
1101
- const operation = pathItem[item.method];
1102
- if (!operation) return null;
1103
- const method = createMethod(item.method, pathItem, operation);
1104
- return /*#__PURE__*/ jsx(Operation, {
1105
- method: method,
1106
- path: item.path,
1107
- ctx: ctx,
1108
- baseUrls: document.servers ? document.servers.map((s)=>s.url) : [],
1109
- hasHead: hasHead
1110
- }, `${item.path}:${item.method}`);
1111
- })
1227
+ children: [
1228
+ operations?.map((item)=>{
1229
+ const pathItem = document.paths?.[item.path];
1230
+ if (!pathItem) return null;
1231
+ const operation = pathItem[item.method];
1232
+ if (!operation) return null;
1233
+ const method = createMethod(item.method, pathItem, operation);
1234
+ return /*#__PURE__*/ jsx(Operation, {
1235
+ method: method,
1236
+ path: item.path,
1237
+ ctx: ctx,
1238
+ hasHead: hasHead
1239
+ }, `${item.path}:${item.method}`);
1240
+ }),
1241
+ webhooks?.map((item)=>{
1242
+ const webhook = document.webhooks?.[item.name];
1243
+ if (!webhook) return;
1244
+ const hook = webhook[item.method];
1245
+ if (!hook) return;
1246
+ const method = createMethod(item.method, webhook, hook);
1247
+ return /*#__PURE__*/ jsx(Operation, {
1248
+ type: "webhook",
1249
+ method: method,
1250
+ ctx: {
1251
+ ...ctx,
1252
+ baseUrl: 'http://localhost:8080'
1253
+ },
1254
+ path: `/${item.name}`,
1255
+ hasHead: hasHead
1256
+ }, `${item.name}:${item.method}`);
1257
+ })
1258
+ ]
1112
1259
  });
1113
1260
  }
1114
- function getContext(document, options) {
1261
+ async function getContext({ document, dereferenceMap }, options = {}) {
1115
1262
  return {
1116
- document,
1263
+ document: document,
1264
+ dereferenceMap,
1117
1265
  renderer: {
1118
1266
  ...createRenders(options.shikiOptions),
1119
1267
  ...options.renderer
@@ -1122,6 +1270,7 @@ function getContext(document, options) {
1122
1270
  generateTypeScriptSchema: options.generateTypeScriptSchema,
1123
1271
  generateCodeSamples: options.generateCodeSamples,
1124
1272
  baseUrl: document.servers?.[0].url ?? 'https://example.com',
1273
+ baseUrls: document.servers?.map((s)=>s.url) ?? [],
1125
1274
  slugger: new Slugger()
1126
1275
  };
1127
1276
  }