@gitbook/react-openapi 1.0.0 → 1.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/InteractiveSection.d.ts +0 -2
  3. package/dist/InteractiveSection.jsx +8 -44
  4. package/dist/OpenAPICodeSample.jsx +12 -12
  5. package/dist/OpenAPIOperation.jsx +8 -5
  6. package/dist/OpenAPIRequestBody.d.ts +2 -2
  7. package/dist/OpenAPIRequestBody.jsx +5 -2
  8. package/dist/OpenAPIResponse.jsx +17 -25
  9. package/dist/OpenAPIResponseExample.jsx +157 -48
  10. package/dist/OpenAPIResponses.jsx +2 -3
  11. package/dist/OpenAPISchema.d.ts +2 -2
  12. package/dist/OpenAPISchema.jsx +28 -29
  13. package/dist/OpenAPISpec.jsx +4 -28
  14. package/dist/OpenAPITabs.d.ts +1 -0
  15. package/dist/OpenAPITabs.jsx +52 -16
  16. package/dist/generateSchemaExample.d.ts +5 -6
  17. package/dist/generateSchemaExample.js +15 -11
  18. package/dist/json2xml.d.ts +4 -0
  19. package/dist/json2xml.js +7 -0
  20. package/dist/resolveOpenAPIOperation.js +4 -4
  21. package/dist/tsconfig.build.tsbuildinfo +1 -1
  22. package/dist/types.d.ts +4 -0
  23. package/dist/useSyncedTabsGlobalState.d.ts +1 -0
  24. package/dist/useSyncedTabsGlobalState.js +16 -0
  25. package/dist/utils.d.ts +26 -3
  26. package/dist/utils.js +72 -9
  27. package/package.json +4 -2
  28. package/src/InteractiveSection.tsx +4 -41
  29. package/src/OpenAPICodeSample.tsx +12 -12
  30. package/src/OpenAPIOperation.tsx +8 -5
  31. package/src/OpenAPIRequestBody.tsx +7 -3
  32. package/src/OpenAPIResponse.tsx +7 -27
  33. package/src/OpenAPIResponseExample.tsx +237 -70
  34. package/src/OpenAPIResponses.tsx +2 -7
  35. package/src/OpenAPISchema.tsx +34 -33
  36. package/src/OpenAPISpec.tsx +4 -22
  37. package/src/OpenAPITabs.tsx +63 -23
  38. package/src/__snapshots__/json2xml.test.ts.snap +18 -0
  39. package/src/generateSchemaExample.ts +11 -10
  40. package/src/json2xml.test.ts +46 -0
  41. package/src/json2xml.ts +8 -0
  42. package/src/resolveOpenAPIOperation.test.ts +1 -1
  43. package/src/resolveOpenAPIOperation.ts +8 -7
  44. package/src/types.ts +1 -0
  45. package/src/useSyncedTabsGlobalState.ts +23 -0
  46. package/src/utils.ts +81 -13
  47. package/dist/fetchOpenAPIOperation.d.ts +0 -27
  48. package/dist/fetchOpenAPIOperation.js +0 -195
  49. package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/types.d.ts CHANGED
@@ -4,6 +4,10 @@ export interface OpenAPIContextProps extends OpenAPIClientContext {
4
4
  code: string;
5
5
  syntax: string;
6
6
  }>;
7
+ renderHeading: (props: {
8
+ deprecated: boolean;
9
+ title: string;
10
+ }) => React.ReactNode;
7
11
  /** Spec url for the Scalar Api Client */
8
12
  specUrl: string;
9
13
  }
@@ -0,0 +1 @@
1
+ export declare function useSyncedTabsGlobalState<T>(): readonly [Map<string, T>, (updater: (tabs: Map<string, T>) => Map<string, T>) => void];
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { create } from 'zustand';
3
+ var useSyncedTabsStore = create()(function (set) { return ({
4
+ tabs: new Map(),
5
+ setTabs: function (updater) {
6
+ return set(function (state) { return ({
7
+ tabs: updater(new Map(state.tabs)), // Ensure a new Map is created for reactivity
8
+ }); });
9
+ },
10
+ }); });
11
+ // Selector for better performance - only re-renders when tabs change
12
+ export function useSyncedTabsGlobalState() {
13
+ var tabs = useSyncedTabsStore(function (state) { return state.tabs; });
14
+ var setTabs = useSyncedTabsStore(function (state) { return state.setTabs; });
15
+ return [tabs, setTabs];
16
+ }
package/dist/utils.d.ts CHANGED
@@ -1,8 +1,31 @@
1
- import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
2
- export declare function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T;
3
- export declare function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject;
1
+ import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
2
+ export declare function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject;
4
3
  export declare function createStateKey(key: string, scope?: string): string;
5
4
  /**
6
5
  * Resolve the description of an object.
7
6
  */
8
7
  export declare function resolveDescription(object: AnyObject): string | undefined;
8
+ /**
9
+ * Extract descriptions from an object.
10
+ */
11
+ export declare function extractDescriptions(object: AnyObject): {
12
+ description: any;
13
+ "x-gitbook-description-html": any;
14
+ };
15
+ /**
16
+ * Resolve the first example from an object.
17
+ */
18
+ export declare function resolveFirstExample(object: AnyObject): any;
19
+ /**
20
+ * Resolve the schema of a parameter.
21
+ * Extract the description, example and deprecated from parameter.
22
+ */
23
+ export declare function resolveParameterSchema(parameter: OpenAPIV3.ParameterBaseObject): OpenAPIV3.SchemaObject;
24
+ /**
25
+ * Transform a parameter object to a property object.
26
+ */
27
+ export declare function parameterToProperty(parameter: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject | OpenAPIV3_1.ReferenceObject): {
28
+ propertyName: string | undefined;
29
+ schema: OpenAPIV3.SchemaObject;
30
+ required: boolean | undefined;
31
+ };
package/dist/utils.js CHANGED
@@ -1,9 +1,14 @@
1
- export function noReference(input) {
2
- if (checkIsReference(input)) {
3
- throw new Error('Reference found');
4
- }
5
- return input;
6
- }
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
7
12
  export function checkIsReference(input) {
8
13
  return typeof input === 'object' && !!input && '$ref' in input;
9
14
  }
@@ -14,9 +19,67 @@ export function createStateKey(key, scope) {
14
19
  * Resolve the description of an object.
15
20
  */
16
21
  export function resolveDescription(object) {
17
- return 'x-description-html' in object && typeof object['x-description-html'] === 'string'
18
- ? object['x-description-html']
22
+ return 'x-gitbook-description-html' in object &&
23
+ typeof object['x-gitbook-description-html'] === 'string'
24
+ ? object['x-gitbook-description-html'].trim()
19
25
  : typeof object.description === 'string'
20
- ? object.description
26
+ ? object.description.trim()
21
27
  : undefined;
22
28
  }
29
+ /**
30
+ * Extract descriptions from an object.
31
+ */
32
+ export function extractDescriptions(object) {
33
+ var _a;
34
+ return _a = {
35
+ description: object.description
36
+ },
37
+ _a['x-gitbook-description-html'] = 'x-gitbook-description-html' in object
38
+ ? object['x-gitbook-description-html']
39
+ : undefined,
40
+ _a;
41
+ }
42
+ /**
43
+ * Resolve the first example from an object.
44
+ */
45
+ export function resolveFirstExample(object) {
46
+ if ('examples' in object && typeof object.examples === 'object' && object.examples) {
47
+ var keys = Object.keys(object.examples);
48
+ var firstKey = keys[0];
49
+ if (firstKey && object.examples[firstKey]) {
50
+ return object.examples[firstKey];
51
+ }
52
+ }
53
+ if ('example' in object && object.example !== undefined) {
54
+ return object.example;
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * Resolve the schema of a parameter.
60
+ * Extract the description, example and deprecated from parameter.
61
+ */
62
+ export function resolveParameterSchema(parameter) {
63
+ var schema = checkIsReference(parameter.schema) ? undefined : parameter.schema;
64
+ return __assign(__assign(__assign({}, extractDescriptions(parameter)), { example: resolveFirstExample(parameter),
65
+ // Deprecated can be defined at the parameter level
66
+ deprecated: parameter.deprecated }), schema);
67
+ }
68
+ /**
69
+ * Transform a parameter object to a property object.
70
+ */
71
+ export function parameterToProperty(parameter) {
72
+ var _a;
73
+ if (checkIsReference(parameter)) {
74
+ return {
75
+ propertyName: (_a = parameter.$ref) !== null && _a !== void 0 ? _a : 'Unknown ref',
76
+ schema: {},
77
+ required: undefined,
78
+ };
79
+ }
80
+ return {
81
+ propertyName: parameter.name,
82
+ schema: resolveParameterSchema(parameter),
83
+ required: parameter.required,
84
+ };
85
+ }
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "default": "./dist/index.js"
9
9
  }
10
10
  },
11
- "version": "1.0.0",
11
+ "version": "1.0.2",
12
12
  "sideEffects": false,
13
13
  "dependencies": {
14
14
  "@gitbook/openapi-parser": "workspace:*",
@@ -16,9 +16,11 @@
16
16
  "@scalar/oas-utils": "^0.2.101",
17
17
  "clsx": "^2.1.1",
18
18
  "flatted": "^3.2.9",
19
+ "json-xml-parse": "^1.3.0",
19
20
  "react-aria-components": "^1.6.0",
20
21
  "react-aria": "^3.37.0",
21
- "usehooks-ts": "^3.1.0"
22
+ "usehooks-ts": "^3.1.0",
23
+ "zustand": "^5.0.3"
22
24
  },
23
25
  "devDependencies": {
24
26
  "bun-types": "^1.1.20",
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import clsx from 'clsx';
4
- import { useCallback, useRef, useState, useSyncExternalStore } from 'react';
4
+ import { useRef, useState } from 'react';
5
5
  import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
6
6
  import { useDisclosureState } from 'react-stately';
7
7
 
@@ -11,30 +11,6 @@ interface InteractiveSectionTab {
11
11
  body: React.ReactNode;
12
12
  }
13
13
 
14
- let globalState: Record<string, string> = {};
15
- const listeners = new Set<() => void>();
16
-
17
- function useSyncedTabsGlobalState() {
18
- const subscribe = useCallback((callback: () => void) => {
19
- listeners.add(callback);
20
- return () => listeners.delete(callback);
21
- }, []);
22
-
23
- const getSnapshot = useCallback(() => globalState, []);
24
-
25
- const setSyncedTabs = useCallback(
26
- (updater: (tabs: Record<string, string>) => Record<string, string>) => {
27
- globalState = updater(globalState);
28
- listeners.forEach((listener) => listener());
29
- },
30
- [],
31
- );
32
-
33
- const tabs = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
34
-
35
- return [tabs, setSyncedTabs] as const;
36
- }
37
-
38
14
  /**
39
15
  * To optimize rendering, most of the components are server-components,
40
16
  * and the interactiveness is mainly handled by a few key components like this one.
@@ -59,8 +35,6 @@ export function InteractiveSection(props: {
59
35
  children?: React.ReactNode;
60
36
  /** Children to display within the container */
61
37
  overlay?: React.ReactNode;
62
- /** An optional key referencing a value in global state */
63
- stateKey?: string;
64
38
  }) {
65
39
  const {
66
40
  id,
@@ -73,16 +47,11 @@ export function InteractiveSection(props: {
73
47
  children,
74
48
  overlay,
75
49
  toggleIcon = '▶',
76
- stateKey,
77
50
  } = props;
78
- const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState();
79
- const tabFromState =
80
- stateKey && stateKey in syncedTabs
81
- ? tabs.find((tab) => tab.key === syncedTabs[stateKey])
82
- : undefined;
83
- const [selectedTabKey, setSelectedTab] = useState(tabFromState?.key ?? defaultTab);
51
+
52
+ const [selectedTabKey, setSelectedTab] = useState(defaultTab);
84
53
  const selectedTab: InteractiveSectionTab | undefined =
85
- tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
54
+ tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
86
55
 
87
56
  const state = useDisclosureState({
88
57
  defaultExpanded: defaultOpened,
@@ -153,12 +122,6 @@ export function InteractiveSection(props: {
153
122
  value={selectedTab?.key ?? ''}
154
123
  onChange={(event) => {
155
124
  setSelectedTab(event.target.value);
156
- if (stateKey) {
157
- setSyncedTabs((state) => ({
158
- ...state,
159
- [stateKey]: event.target.value,
160
- }));
161
- }
162
125
  state.expand();
163
126
  }}
164
127
  >
@@ -3,9 +3,10 @@ import { generateMediaTypeExample, generateSchemaExample } from './generateSchem
3
3
  import { InteractiveSection } from './InteractiveSection';
4
4
  import { getServersURL } from './OpenAPIServerURL';
5
5
  import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
6
- import { noReference } from './utils';
6
+ import { createStateKey } from './utils';
7
7
  import { stringifyOpenAPI } from './stringifyOpenAPI';
8
8
  import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
9
+ import { checkIsReference } from './utils';
9
10
 
10
11
  /**
11
12
  * Display code samples to execute the operation.
@@ -20,24 +21,19 @@ export function OpenAPICodeSample(props: {
20
21
  const searchParams = new URLSearchParams();
21
22
  const headersObject: { [k: string]: string } = {};
22
23
 
23
- data.operation.parameters?.forEach((rawParam) => {
24
- const param = noReference(rawParam);
24
+ data.operation.parameters?.forEach((param) => {
25
25
  if (!param) {
26
26
  return;
27
27
  }
28
28
 
29
29
  if (param.in === 'header' && param.required) {
30
- const example = param.schema
31
- ? generateSchemaExample(noReference(param.schema))
32
- : undefined;
30
+ const example = param.schema ? generateSchemaExample(param.schema) : undefined;
33
31
  if (example !== undefined && param.name) {
34
32
  headersObject[param.name] =
35
33
  typeof example !== 'string' ? stringifyOpenAPI(example) : example;
36
34
  }
37
35
  } else if (param.in === 'query' && param.required) {
38
- const example = param.schema
39
- ? generateSchemaExample(noReference(param.schema))
40
- : undefined;
36
+ const example = param.schema ? generateSchemaExample(param.schema) : undefined;
41
37
  if (example !== undefined && param.name) {
42
38
  searchParams.append(
43
39
  param.name,
@@ -47,7 +43,9 @@ export function OpenAPICodeSample(props: {
47
43
  }
48
44
  });
49
45
 
50
- const requestBody = noReference(data.operation.requestBody);
46
+ const requestBody = !checkIsReference(data.operation.requestBody)
47
+ ? data.operation.requestBody
48
+ : undefined;
51
49
  const requestBodyContentEntries = requestBody?.content
52
50
  ? Object.entries(requestBody.content)
53
51
  : undefined;
@@ -60,7 +58,9 @@ export function OpenAPICodeSample(props: {
60
58
  (searchParams.size ? `?${searchParams.toString()}` : ''),
61
59
  method: data.method,
62
60
  body: requestBodyContent
63
- ? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
61
+ ? generateMediaTypeExample(requestBodyContent[1], {
62
+ omitEmptyAndOptionalProperties: true,
63
+ })
64
64
  : undefined,
65
65
  headers: {
66
66
  ...getSecurityHeaders(data.securities),
@@ -115,7 +115,7 @@ export function OpenAPICodeSample(props: {
115
115
  }
116
116
 
117
117
  return (
118
- <OpenAPITabs items={samples}>
118
+ <OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
119
119
  <InteractiveSection header={<OpenAPITabsList />} className="openapi-codesample">
120
120
  <OpenAPITabsPanels />
121
121
  </InteractiveSection>
@@ -25,14 +25,17 @@ export function OpenAPIOperation(props: {
25
25
  blockKey: context.blockKey,
26
26
  };
27
27
 
28
- const description = resolveDescription(operation)?.trim();
28
+ const description = resolveDescription(operation);
29
29
 
30
30
  return (
31
31
  <div className={clsx('openapi-operation', className)}>
32
- <div className="openapi-summary" id={context.id}>
33
- <h2 className="openapi-summary-title" data-deprecated={operation.deprecated}>
34
- {operation.summary}
35
- </h2>
32
+ <div className="openapi-summary">
33
+ {operation.summary
34
+ ? context.renderHeading({
35
+ deprecated: operation.deprecated ?? false,
36
+ title: operation.summary,
37
+ })
38
+ : null}
36
39
  {operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
37
40
  </div>
38
41
  <div className="openapi-columns">
@@ -1,18 +1,22 @@
1
1
  import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
  import { OpenAPIRootSchema } from './OpenAPISchema';
3
- import { noReference } from './utils';
4
3
  import type { OpenAPIClientContext } from './types';
5
4
  import { InteractiveSection } from './InteractiveSection';
5
+ import { checkIsReference } from './utils';
6
6
 
7
7
  /**
8
8
  * Display an interactive request body.
9
9
  */
10
10
  export function OpenAPIRequestBody(props: {
11
- requestBody: OpenAPIV3.RequestBodyObject;
11
+ requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
12
12
  context: OpenAPIClientContext;
13
13
  }) {
14
14
  const { requestBody, context } = props;
15
15
 
16
+ if (checkIsReference(requestBody)) {
17
+ return null;
18
+ }
19
+
16
20
  return (
17
21
  <InteractiveSection
18
22
  header="Body"
@@ -24,7 +28,7 @@ export function OpenAPIRequestBody(props: {
24
28
  label: contentType,
25
29
  body: (
26
30
  <OpenAPIRootSchema
27
- schema={noReference(mediaTypeObject.schema) ?? {}}
31
+ schema={mediaTypeObject.schema ?? {}}
28
32
  context={context}
29
33
  />
30
34
  ),
@@ -1,6 +1,6 @@
1
1
  import type { OpenAPIV3 } from '@gitbook/openapi-parser';
2
2
  import { OpenAPISchemaProperties } from './OpenAPISchema';
3
- import { checkIsReference, noReference, resolveDescription } from './utils';
3
+ import { parameterToProperty, resolveDescription } from './utils';
4
4
  import type { OpenAPIClientContext } from './types';
5
5
  import { OpenAPIDisclosure } from './OpenAPIDisclosure';
6
6
 
@@ -14,7 +14,7 @@ export function OpenAPIResponse(props: {
14
14
  }) {
15
15
  const { response, context, mediaType } = props;
16
16
  const headers = Object.entries(response.headers ?? {}).map(
17
- ([name, header]) => [name, noReference(header) ?? {}] as const,
17
+ ([name, header]) => [name, header ?? {}] as const,
18
18
  );
19
19
  const content = Object.entries(mediaType.schema ?? {});
20
20
 
@@ -27,13 +27,11 @@ export function OpenAPIResponse(props: {
27
27
  return (
28
28
  <div className="openapi-response-body">
29
29
  {headers.length > 0 ? (
30
- <OpenAPIDisclosure context={context} label={'Headers'}>
30
+ <OpenAPIDisclosure context={context} label="Headers">
31
31
  <OpenAPISchemaProperties
32
- properties={headers.map(([name, header]) => ({
33
- propertyName: name,
34
- schema: noReference(header.schema) ?? {},
35
- required: header.required,
36
- }))}
32
+ properties={headers.map(([name, header]) => {
33
+ return parameterToProperty({ name, ...header });
34
+ })}
37
35
  context={context}
38
36
  />
39
37
  </OpenAPIDisclosure>
@@ -41,28 +39,10 @@ export function OpenAPIResponse(props: {
41
39
  <div className="openapi-responsebody">
42
40
  <OpenAPISchemaProperties
43
41
  id={`response-${context.blockKey}`}
44
- properties={[
45
- {
46
- schema: handleUnresolvedReference(mediaType.schema) ?? {},
47
- },
48
- ]}
42
+ properties={mediaType.schema ? [{ schema: mediaType.schema }] : []}
49
43
  context={context}
50
44
  />
51
45
  </div>
52
46
  </div>
53
47
  );
54
48
  }
55
-
56
- function handleUnresolvedReference(
57
- input: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
58
- ): OpenAPIV3.SchemaObject {
59
- const isReference = checkIsReference(input);
60
-
61
- if (isReference || input === undefined) {
62
- // If we find a reference that wasn't resolved or needed to be resolved externally, do not try to render it.
63
- // Instead we render `any`
64
- return {};
65
- }
66
-
67
- return input;
68
- }