@gitbook/react-openapi 1.0.4 → 1.1.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/InteractiveSection.jsx +10 -9
  3. package/dist/OpenAPICodeSample.jsx +3 -3
  4. package/dist/OpenAPIDisclosure.d.ts +5 -9
  5. package/dist/OpenAPIDisclosure.jsx +25 -27
  6. package/dist/OpenAPIDisclosureGroup.d.ts +2 -2
  7. package/dist/OpenAPIDisclosureGroup.jsx +6 -5
  8. package/dist/OpenAPIPath.jsx +5 -1
  9. package/dist/OpenAPIResponseExample.jsx +8 -8
  10. package/dist/OpenAPIResponses.jsx +3 -3
  11. package/dist/OpenAPISchema.d.ts +3 -26
  12. package/dist/OpenAPISchema.jsx +80 -131
  13. package/dist/OpenAPISpec.jsx +3 -4
  14. package/dist/OpenAPITabs.jsx +51 -47
  15. package/dist/ScalarApiButton.d.ts +3 -2
  16. package/dist/ScalarApiButton.jsx +22 -18
  17. package/dist/StaticSection.d.ts +10 -0
  18. package/dist/StaticSection.jsx +23 -0
  19. package/dist/dereference.d.ts +5 -0
  20. package/dist/dereference.js +68 -0
  21. package/dist/index.d.ts +3 -2
  22. package/dist/index.js +2 -1
  23. package/dist/models/OpenAPIModels.d.ts +9 -0
  24. package/dist/models/OpenAPIModels.jsx +62 -0
  25. package/dist/models/index.d.ts +2 -0
  26. package/dist/models/index.js +2 -0
  27. package/dist/models/resolveOpenAPIModels.d.ts +7 -0
  28. package/dist/models/resolveOpenAPIModels.js +73 -0
  29. package/dist/resolveOpenAPIOperation.d.ts +2 -2
  30. package/dist/resolveOpenAPIOperation.js +3 -34
  31. package/dist/tsconfig.build.tsbuildinfo +1 -1
  32. package/dist/types.d.ts +8 -0
  33. package/dist/useSyncedTabsGlobalState.d.ts +10 -1
  34. package/dist/useSyncedTabsGlobalState.js +19 -15
  35. package/dist/utils.js +42 -3
  36. package/package.json +3 -3
  37. package/src/InteractiveSection.tsx +10 -18
  38. package/src/OpenAPICodeSample.tsx +3 -3
  39. package/src/OpenAPIDisclosure.tsx +35 -42
  40. package/src/OpenAPIDisclosureGroup.tsx +13 -11
  41. package/src/OpenAPIPath.tsx +7 -1
  42. package/src/OpenAPIResponseExample.tsx +8 -15
  43. package/src/OpenAPIResponses.tsx +3 -3
  44. package/src/OpenAPISchema.test.ts +26 -35
  45. package/src/OpenAPISchema.tsx +138 -227
  46. package/src/OpenAPISpec.tsx +3 -5
  47. package/src/OpenAPITabs.tsx +52 -63
  48. package/src/ScalarApiButton.tsx +26 -28
  49. package/src/StaticSection.tsx +59 -0
  50. package/src/dereference.ts +29 -0
  51. package/src/index.ts +3 -2
  52. package/src/models/OpenAPIModels.tsx +89 -0
  53. package/src/models/index.ts +2 -0
  54. package/src/models/resolveOpenAPIModels.ts +35 -0
  55. package/src/resolveOpenAPIOperation.ts +8 -36
  56. package/src/types.ts +10 -0
  57. package/src/useSyncedTabsGlobalState.ts +33 -21
  58. package/src/utils.ts +51 -3
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
 
3
- import { createContext, useContext, useEffect, useMemo, useState } from 'react';
3
+ import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { type Key, Tab, TabList, TabPanel, Tabs, type TabsProps } from 'react-aria-components';
5
- import { useIntersectionObserver } from 'usehooks-ts';
5
+ import { useEventCallback } from 'usehooks-ts';
6
6
  import { Markdown } from './Markdown';
7
- import { useSyncedTabsGlobalState } from './useSyncedTabsGlobalState';
7
+ import { getOrCreateTabStoreByKey } from './useSyncedTabsGlobalState';
8
8
 
9
9
  export type TabItem = {
10
10
  key: Key;
@@ -15,7 +15,7 @@ export type TabItem = {
15
15
 
16
16
  type OpenAPITabsContextData = {
17
17
  items: TabItem[];
18
- selectedTab: TabItem;
18
+ selectedTab: TabItem | null;
19
19
  };
20
20
 
21
21
  const OpenAPITabsContext = createContext<OpenAPITabsContextData | null>(null);
@@ -35,68 +35,54 @@ export function OpenAPITabs(
35
35
  props: React.PropsWithChildren<TabsProps & { items: TabItem[]; stateKey?: string }>
36
36
  ) {
37
37
  const { children, items, stateKey } = props;
38
- const [ref, isIntersectionVisible] = useIntersectionObserver({
39
- threshold: 0.1,
40
- rootMargin: '200px',
41
- });
42
- const isVisible = stateKey ? isIntersectionVisible : true;
43
- const defaultTab = items[0] as TabItem;
44
- const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState<TabItem>();
45
- const [selectedTabKey, setSelectedTabKey] = useState(() => {
46
- if (isVisible && stateKey && syncedTabs && syncedTabs.has(stateKey)) {
47
- const tabFromState = syncedTabs.get(stateKey);
48
- return tabFromState?.key ?? items[0]?.key;
38
+ const [tabKey, setTabKey] = useState<Key | null>(() => {
39
+ if (stateKey && typeof window !== 'undefined') {
40
+ const store = getOrCreateTabStoreByKey(stateKey);
41
+ const tabKey = store.getState().tabKey;
42
+ if (tabKey) {
43
+ return tabKey;
44
+ }
49
45
  }
50
- return items[0]?.key;
46
+ return items[0]?.key ?? null;
51
47
  });
52
- const [selectedTab, setSelectedTab] = useState<TabItem>(defaultTab);
53
-
54
- const handleSelectionChange = (key: Key) => {
55
- setSelectedTabKey(key);
56
- if (stateKey) {
57
- const tab = items.find((item) => item.key === key);
58
-
59
- if (!tab) {
60
- return;
61
- }
62
-
63
- setSyncedTabs((state) => {
64
- const newState = new Map(state);
65
- newState.set(stateKey, tab);
66
- return newState;
67
- });
48
+ const selectTab = useEventCallback((key: Key | null) => {
49
+ if (!key || key === tabKey) {
50
+ return;
68
51
  }
69
- };
70
-
52
+ const tab = items.find((item) => item.key === key);
53
+ if (!tab) {
54
+ return;
55
+ }
56
+ setTabKey(key);
57
+ });
58
+ const selectedTab = items.find((item) => item.key === tabKey) ?? items[0] ?? null;
59
+ const cancelDeferRef = useRef<(() => void) | null>(null);
71
60
  useEffect(() => {
72
- if (isVisible && stateKey && syncedTabs && syncedTabs.has(stateKey)) {
73
- const tabFromState = syncedTabs.get(stateKey);
74
-
75
- if (!items.some((item) => item.key === tabFromState?.key)) {
76
- return setSelectedTab(defaultTab);
77
- }
78
-
79
- if (tabFromState && tabFromState?.key !== selectedTab?.key) {
80
- const tabFromItems = items.find((item) => item.key === tabFromState.key);
81
-
82
- if (!tabFromItems) {
83
- return;
84
- }
85
-
86
- setSelectedTab(tabFromItems);
87
- }
61
+ if (!stateKey) {
62
+ return undefined;
88
63
  }
89
- }, [isVisible, stateKey, syncedTabs, selectedTabKey]);
90
-
64
+ const store = getOrCreateTabStoreByKey(stateKey);
65
+ return store.subscribe((state) => {
66
+ cancelDeferRef.current?.();
67
+ cancelDeferRef.current = defer(() => selectTab(state.tabKey));
68
+ });
69
+ }, [stateKey, selectTab]);
70
+ useEffect(() => {
71
+ return () => cancelDeferRef.current?.();
72
+ }, []);
91
73
  const contextValue = useMemo(() => ({ items, selectedTab }), [items, selectedTab]);
92
-
93
74
  return (
94
75
  <OpenAPITabsContext.Provider value={contextValue}>
95
76
  <Tabs
96
- ref={ref}
97
77
  className="openapi-tabs"
98
- onSelectionChange={handleSelectionChange}
99
- selectedKey={selectedTab?.key}
78
+ onSelectionChange={(tabKey) => {
79
+ selectTab(tabKey);
80
+ if (stateKey) {
81
+ const store = getOrCreateTabStoreByKey(stateKey);
82
+ store.setState({ tabKey });
83
+ }
84
+ }}
85
+ selectedKey={tabKey}
100
86
  >
101
87
  {children}
102
88
  </Tabs>
@@ -104,6 +90,11 @@ export function OpenAPITabs(
104
90
  );
105
91
  }
106
92
 
93
+ const defer = (fn: () => void) => {
94
+ const id = setTimeout(fn, 0);
95
+ return () => clearTimeout(id);
96
+ };
97
+
107
98
  /**
108
99
  * The OpenAPI Tabs list component.
109
100
  * This component should be used as a child of the OpenAPITabs component.
@@ -116,14 +107,14 @@ export function OpenAPITabsList() {
116
107
  <TabList className="openapi-tabs-list">
117
108
  {items.map((tab) => (
118
109
  <Tab
110
+ key={tab.key}
111
+ id={tab.key}
119
112
  style={({ isFocusVisible }) => ({
120
113
  outline: isFocusVisible
121
114
  ? '2px solid rgb(var(--primary-color-500)/0.4)'
122
115
  : 'none',
123
116
  })}
124
117
  className="openapi-tabs-tab"
125
- key={`Tab-${tab.key}`}
126
- id={tab.key}
127
118
  >
128
119
  {tab.label}
129
120
  </Tab>
@@ -144,12 +135,10 @@ export function OpenAPITabsPanels() {
144
135
  return null;
145
136
  }
146
137
 
138
+ const key = selectedTab.key.toString();
139
+
147
140
  return (
148
- <TabPanel
149
- key={`TabPanel-${selectedTab.key}`}
150
- id={selectedTab.key.toString()}
151
- className="openapi-tabs-panel"
152
- >
141
+ <TabPanel key={key} id={key} className="openapi-tabs-panel">
153
142
  {selectedTab.body}
154
143
  {selectedTab.description ? (
155
144
  <Markdown source={selectedTab.description} className="openapi-tabs-footer" />
@@ -1,24 +1,21 @@
1
1
  'use client';
2
2
 
3
3
  import { ApiClientModalProvider, useApiClientModal } from '@scalar/api-client-react';
4
- import { useEffect, useImperativeHandle, useRef, useState } from 'react';
4
+ import { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
5
  import { createPortal } from 'react-dom';
6
6
 
7
- import { useEventCallback } from 'usehooks-ts';
7
+ import type { OpenAPIV3_1 } from '@gitbook/openapi-parser';
8
8
  import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
9
9
 
10
10
  /**
11
11
  * Button which launches the Scalar API Client
12
12
  */
13
- export function ScalarApiButton({
14
- method,
15
- path,
16
- specUrl,
17
- }: {
18
- method: string;
13
+ export function ScalarApiButton(props: {
14
+ method: OpenAPIV3_1.HttpMethods;
19
15
  path: string;
20
16
  specUrl: string;
21
17
  }) {
18
+ const { method, path, specUrl } = props;
22
19
  const [isOpen, setIsOpen] = useState(false);
23
20
  const controllerRef = useRef<ScalarModalControllerRef>(null);
24
21
  return (
@@ -55,21 +52,18 @@ export function ScalarApiButton({
55
52
  }
56
53
 
57
54
  function ScalarModal(props: {
58
- method: string;
55
+ method: OpenAPIV3_1.HttpMethods;
59
56
  path: string;
60
57
  specUrl: string;
61
58
  controllerRef: React.Ref<ScalarModalControllerRef>;
62
59
  }) {
60
+ const { method, path, specUrl, controllerRef } = props;
63
61
  return (
64
62
  <ApiClientModalProvider
65
- configuration={{ spec: { url: props.specUrl } }}
66
- initialRequest={{ path: props.path, method: props.method }}
63
+ configuration={{ spec: { url: specUrl } }}
64
+ initialRequest={{ method, path }}
67
65
  >
68
- <ScalarModalController
69
- method={props.method}
70
- path={props.path}
71
- controllerRef={props.controllerRef}
72
- />
66
+ <ScalarModalController method={method} path={path} controllerRef={controllerRef} />
73
67
  </ApiClientModalProvider>
74
68
  );
75
69
  }
@@ -79,28 +73,32 @@ type ScalarModalControllerRef = {
79
73
  };
80
74
 
81
75
  function ScalarModalController(props: {
82
- method: string;
76
+ method: OpenAPIV3_1.HttpMethods;
83
77
  path: string;
84
78
  controllerRef: React.Ref<ScalarModalControllerRef>;
85
79
  }) {
80
+ const { method, path, controllerRef } = props;
86
81
  const client = useApiClientModal();
87
- const openClient = client?.open;
82
+ const openScalarClient = client?.open;
83
+ const { onOpenClient: trackClientOpening } = useOpenAPIOperationContext();
84
+ const openClient = useMemo(() => {
85
+ if (openScalarClient) {
86
+ return () => {
87
+ openScalarClient({ method, path, _source: 'gitbook' });
88
+ trackClientOpening({ method, path });
89
+ };
90
+ }
91
+ return null;
92
+ }, [openScalarClient, method, path, trackClientOpening]);
88
93
  useImperativeHandle(
89
- props.controllerRef,
94
+ controllerRef,
90
95
  () => ({ openClient: openClient ? () => openClient() : undefined }),
91
96
  [openClient]
92
97
  );
93
98
 
94
- // Open the client when the component is mounted.
95
- const { onOpenClient } = useOpenAPIOperationContext();
96
- const trackOpening = useEventCallback(() => {
97
- onOpenClient({ method: props.method, path: props.path });
98
- });
99
+ // Open at mount
99
100
  useEffect(() => {
100
- if (openClient) {
101
- openClient();
102
- trackOpening();
103
- }
101
+ openClient?.();
104
102
  }, [openClient]);
105
103
  return null;
106
104
  }
@@ -0,0 +1,59 @@
1
+ import clsx from 'clsx';
2
+ import { type ComponentPropsWithoutRef, forwardRef } from 'react';
3
+
4
+ export function Section(props: ComponentPropsWithoutRef<'div'>) {
5
+ return <div {...props} className={clsx('openapi-section', props.className)} />;
6
+ }
7
+
8
+ export function SectionHeader(props: ComponentPropsWithoutRef<'div'>) {
9
+ return (
10
+ <div
11
+ {...props}
12
+ className={clsx(
13
+ 'openapi-section-header',
14
+ props.className && `${props.className}-header`
15
+ )}
16
+ />
17
+ );
18
+ }
19
+
20
+ export function SectionHeaderContent(props: ComponentPropsWithoutRef<'div'>) {
21
+ return (
22
+ <div
23
+ {...props}
24
+ className={clsx(
25
+ 'openapi-section-header-content',
26
+ props.className && `${props.className}-header-content`
27
+ )}
28
+ />
29
+ );
30
+ }
31
+
32
+ export const SectionBody = forwardRef(function SectionBody(
33
+ props: ComponentPropsWithoutRef<'div'>,
34
+ ref: React.ForwardedRef<HTMLDivElement>
35
+ ) {
36
+ return (
37
+ <div
38
+ ref={ref}
39
+ {...props}
40
+ className={clsx('openapi-section-body', props.className && `${props.className}-body`)}
41
+ />
42
+ );
43
+ });
44
+
45
+ export function StaticSection(props: {
46
+ className: string;
47
+ header: React.ReactNode;
48
+ children: React.ReactNode;
49
+ }) {
50
+ const { className, header, children } = props;
51
+ return (
52
+ <Section className={className}>
53
+ <SectionHeader className={className}>
54
+ <SectionHeaderContent className={className}>{header}</SectionHeaderContent>
55
+ </SectionHeader>
56
+ <SectionBody className={className}>{children}</SectionBody>
57
+ </Section>
58
+ );
59
+ }
@@ -0,0 +1,29 @@
1
+ import { type Filesystem, type OpenAPIV3xDocument, dereference } from '@gitbook/openapi-parser';
2
+
3
+ const dereferenceCache = new WeakMap<Filesystem, Promise<OpenAPIV3xDocument>>();
4
+
5
+ /**
6
+ * Memoized version of `dereferenceSchema`.
7
+ */
8
+ export function dereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
9
+ if (dereferenceCache.has(filesystem)) {
10
+ return dereferenceCache.get(filesystem) as Promise<OpenAPIV3xDocument>;
11
+ }
12
+
13
+ const promise = baseDereferenceFilesystem(filesystem);
14
+ dereferenceCache.set(filesystem, promise);
15
+ return promise;
16
+ }
17
+
18
+ /**
19
+ * Dereference an OpenAPI schema.
20
+ */
21
+ async function baseDereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
22
+ const result = await dereference(filesystem);
23
+
24
+ if (!result.schema) {
25
+ throw new Error('Failed to dereference OpenAPI document');
26
+ }
27
+
28
+ return result.schema as OpenAPIV3xDocument;
29
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
- export * from './resolveOpenAPIOperation';
1
+ export * from './models';
2
2
  export * from './OpenAPIOperation';
3
3
  export * from './OpenAPIOperationContext';
4
- export type { OpenAPIOperationData } from './types';
4
+ export * from './resolveOpenAPIOperation';
5
+ export type { OpenAPIModelsData, OpenAPIOperationData } from './types';
@@ -0,0 +1,89 @@
1
+ import clsx from 'clsx';
2
+ import { OpenAPIDisclosureGroup } from '../OpenAPIDisclosureGroup';
3
+ import { OpenAPIRootSchema } from '../OpenAPISchema';
4
+ import { Section, SectionBody } from '../StaticSection';
5
+ import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPIModelsData } from '../types';
6
+
7
+ /**
8
+ * Display OpenAPI Models.
9
+ */
10
+ export function OpenAPIModels(props: {
11
+ className?: string;
12
+ data: OpenAPIModelsData;
13
+ context: OpenAPIContextProps;
14
+ }) {
15
+ const { className, data, context } = props;
16
+ const { models } = data;
17
+
18
+ const clientContext: OpenAPIClientContext = {
19
+ defaultInteractiveOpened: context.defaultInteractiveOpened,
20
+ icons: context.icons,
21
+ blockKey: context.blockKey,
22
+ };
23
+
24
+ if (!models.length) {
25
+ return null;
26
+ }
27
+
28
+ return (
29
+ <div className={clsx('openapi-models', className)}>
30
+ <OpenAPIRootModelsSchema models={models} context={clientContext} />
31
+ </div>
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Root schema for OpenAPI models.
37
+ * It displays a single model or a disclosure group for multiple models.
38
+ */
39
+ function OpenAPIRootModelsSchema(props: {
40
+ models: OpenAPIModelsData['models'];
41
+ context: OpenAPIClientContext;
42
+ }) {
43
+ const { models, context } = props;
44
+
45
+ // If there is only one model, we show it directly.
46
+ if (models.length === 1) {
47
+ const schema = models?.[0]?.schema;
48
+
49
+ if (!schema) {
50
+ return null;
51
+ }
52
+
53
+ return (
54
+ <Section>
55
+ <SectionBody>
56
+ <OpenAPIRootSchema schema={schema} context={context} />
57
+ </SectionBody>
58
+ </Section>
59
+ );
60
+ }
61
+
62
+ // If there are multiple models, we use a disclosure group to show them all.
63
+ return (
64
+ <OpenAPIDisclosureGroup
65
+ allowsMultipleExpanded
66
+ icon={context.icons.chevronRight}
67
+ groups={models.map(({ name, schema }) => ({
68
+ id: name,
69
+ label: (
70
+ <div className="openapi-response-tab-content" key={`model-${name}`}>
71
+ <span className="openapi-response-statuscode">{name}</span>
72
+ </div>
73
+ ),
74
+ tabs: [
75
+ {
76
+ id: 'model',
77
+ body: (
78
+ <Section className="openapi-section-models">
79
+ <SectionBody>
80
+ <OpenAPIRootSchema schema={schema} context={context} />
81
+ </SectionBody>
82
+ </Section>
83
+ ),
84
+ },
85
+ ],
86
+ }))}
87
+ />
88
+ );
89
+ }
@@ -0,0 +1,2 @@
1
+ export * from './OpenAPIModels';
2
+ export * from './resolveOpenAPIModels';
@@ -0,0 +1,35 @@
1
+ import {
2
+ type Filesystem,
3
+ type OpenAPIV3,
4
+ type OpenAPIV3_1,
5
+ type OpenAPIV3xDocument,
6
+ shouldIgnoreEntity,
7
+ } from '@gitbook/openapi-parser';
8
+ import { dereferenceFilesystem } from '../dereference';
9
+ import type { OpenAPIModel, OpenAPIModelsData } from '../types';
10
+
11
+ //!!TODO: We should return only the models that are used in the block. Still a WIP awaiting future work.
12
+
13
+ /**
14
+ * Resolve an OpenAPI models from a file and compile it to a more usable format.
15
+ * Models are extracted from the OpenAPI components.schemas
16
+ */
17
+ export async function resolveOpenAPIModels(
18
+ filesystem: Filesystem<OpenAPIV3xDocument>
19
+ ): Promise<OpenAPIModelsData | null> {
20
+ const schema = await dereferenceFilesystem(filesystem);
21
+
22
+ const models = getOpenAPIComponents(schema);
23
+
24
+ return { models };
25
+ }
26
+
27
+ /**
28
+ * Get OpenAPI components.schemas that are not ignored.
29
+ */
30
+ function getOpenAPIComponents(schema: OpenAPIV3.Document | OpenAPIV3_1.Document): OpenAPIModel[] {
31
+ const schemas = schema.components?.schemas ?? {};
32
+ return Object.entries(schemas)
33
+ .filter(([, schema]) => !shouldIgnoreEntity(schema))
34
+ .map(([key, schema]) => ({ name: key, schema }));
35
+ }
@@ -1,16 +1,16 @@
1
1
  import { fromJSON, toJSON } from 'flatted';
2
2
 
3
- import {
4
- type Filesystem,
5
- type OpenAPIV3,
6
- type OpenAPIV3_1,
7
- type OpenAPIV3xDocument,
8
- dereference,
3
+ import type {
4
+ Filesystem,
5
+ OpenAPIV3,
6
+ OpenAPIV3_1,
7
+ OpenAPIV3xDocument,
9
8
  } from '@gitbook/openapi-parser';
9
+ import { dereferenceFilesystem } from './dereference';
10
10
  import type { OpenAPIOperationData } from './types';
11
11
  import { checkIsReference } from './utils';
12
12
 
13
- export { toJSON, fromJSON };
13
+ export { fromJSON, toJSON };
14
14
 
15
15
  /**
16
16
  * Resolve an OpenAPI operation in a file and compile it to a more usable format.
@@ -23,7 +23,7 @@ export async function resolveOpenAPIOperation(
23
23
  }
24
24
  ): Promise<OpenAPIOperationData | null> {
25
25
  const { path, method } = operationDescriptor;
26
- const schema = await memoDereferenceFilesystem(filesystem);
26
+ const schema = await dereferenceFilesystem(filesystem);
27
27
  let operation = getOperationByPathAndMethod(schema, path, method);
28
28
 
29
29
  if (!operation) {
@@ -69,34 +69,6 @@ export async function resolveOpenAPIOperation(
69
69
  };
70
70
  }
71
71
 
72
- const dereferenceCache = new WeakMap<Filesystem, Promise<OpenAPIV3xDocument>>();
73
-
74
- /**
75
- * Memoized version of `dereferenceSchema`.
76
- */
77
- function memoDereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
78
- if (dereferenceCache.has(filesystem)) {
79
- return dereferenceCache.get(filesystem) as Promise<OpenAPIV3xDocument>;
80
- }
81
-
82
- const promise = dereferenceFilesystem(filesystem);
83
- dereferenceCache.set(filesystem, promise);
84
- return promise;
85
- }
86
-
87
- /**
88
- * Dereference an OpenAPI schema.
89
- */
90
- async function dereferenceFilesystem(filesystem: Filesystem): Promise<OpenAPIV3xDocument> {
91
- const result = await dereference(filesystem);
92
-
93
- if (!result.schema) {
94
- throw new Error('Failed to dereference OpenAPI document');
95
- }
96
-
97
- return result.schema as OpenAPIV3xDocument;
98
- }
99
-
100
72
  /**
101
73
  * Get a path object from its path.
102
74
  */
package/src/types.ts CHANGED
@@ -55,3 +55,13 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
55
55
  /** Securities that should be used for this operation */
56
56
  securities: [string, OpenAPIV3.SecuritySchemeObject][];
57
57
  }
58
+
59
+ export type OpenAPIModel = {
60
+ name: string;
61
+ schema: OpenAPIV3.SchemaObject;
62
+ };
63
+
64
+ export interface OpenAPIModelsData {
65
+ /** Components schemas to be used for models */
66
+ models: OpenAPIModel[];
67
+ }
@@ -1,23 +1,35 @@
1
1
  'use client';
2
2
 
3
- import { create } from 'zustand';
4
-
5
- interface SyncedTabsState<T> {
6
- tabs: Map<string, T>;
7
- setTabs: (updater: (tabs: Map<string, T>) => Map<string, T>) => void;
8
- }
9
-
10
- const useSyncedTabsStore = create<SyncedTabsState<any>>()((set) => ({
11
- tabs: new Map<string, any>(),
12
- setTabs: (updater) =>
13
- set((state) => ({
14
- tabs: updater(new Map(state.tabs)), // Ensure a new Map is created for reactivity
15
- })),
16
- }));
17
-
18
- // Selector for better performance - only re-renders when tabs change
19
- export function useSyncedTabsGlobalState<T>() {
20
- const tabs = useSyncedTabsStore((state) => state.tabs as Map<string, T>);
21
- const setTabs = useSyncedTabsStore((state) => state.setTabs as SyncedTabsState<T>['setTabs']);
22
- return [tabs, setTabs] as const;
23
- }
3
+ import { createStore } from 'zustand';
4
+
5
+ type Key = string | number;
6
+
7
+ type TabState = {
8
+ tabKey: Key | null;
9
+ };
10
+
11
+ type TabActions = { setTabKey: (tab: Key | null) => void };
12
+
13
+ type TabStore = TabState & TabActions;
14
+
15
+ const createTabStore = (initialTab?: Key) => {
16
+ return createStore<TabStore>()((set) => ({
17
+ tabKey: initialTab ?? null,
18
+ setTabKey: (tabKey) => {
19
+ set(() => ({ tabKey }));
20
+ },
21
+ }));
22
+ };
23
+
24
+ const defaultTabStores = new Map<string, ReturnType<typeof createTabStore>>();
25
+
26
+ const createTabStoreFactory = (stores: typeof defaultTabStores) => {
27
+ return (storeKey: string, initialKey?: Key) => {
28
+ if (!stores.has(storeKey)) {
29
+ stores.set(storeKey, createTabStore(initialKey));
30
+ }
31
+ return stores.get(storeKey)!;
32
+ };
33
+ };
34
+
35
+ export const getOrCreateTabStoreByKey = createTabStoreFactory(defaultTabStores);