mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
feat(openapi): add storageKey option to isolate localStorage for multiple API instances
Fixes #2554 When using multiple createOpenAPI() instances, server selection state would bleed between APIs due to shared 'apiBaseUrl' localStorage key. This adds an optional storageKey parameter that defaults to 'apiBaseUrl' for backward compatibility.
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
---
|
||||
'fumadocs-openapi': minor
|
||||
---
|
||||
|
||||
Add `storageKey` option to isolate localStorage for multiple API instances
|
||||
|
||||
When using multiple `createOpenAPI()` instances in the same application, the server selection state would bleed between different APIs because they all shared the same hardcoded `'apiBaseUrl'` localStorage key.
|
||||
|
||||
**Changes:**
|
||||
- Added `storageKey` parameter to `OpenAPIOptions` and `CreateAPIPageOptions` interfaces
|
||||
- Updated `ApiProvider` to accept and use custom storage keys
|
||||
- Defaults to `'apiBaseUrl'` for backward compatibility
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
export const dataApi = createOpenAPI({
|
||||
input: ['./openapi/data.json'],
|
||||
storageKey: 'apiBaseUrl-data',
|
||||
});
|
||||
|
||||
export const metricsApi = createOpenAPI({
|
||||
input: ['./openapi/metrics.json'],
|
||||
storageKey: 'apiBaseUrl-metrics',
|
||||
});
|
||||
```
|
||||
|
||||
Each API instance now maintains independent server selection state.
|
||||
|
||||
@@ -30,6 +30,15 @@ export interface OpenAPIOptions {
|
||||
* The url of proxy to avoid CORS issues
|
||||
*/
|
||||
proxyUrl?: string;
|
||||
|
||||
/**
|
||||
* Custom localStorage key for server selection.
|
||||
*
|
||||
* Useful when using multiple OpenAPI instances to prevent state conflicts.
|
||||
*
|
||||
* @defaultValue 'apiBaseUrl'
|
||||
*/
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
export interface OpenAPIServer {
|
||||
|
||||
@@ -106,6 +106,15 @@ export interface CreateAPIPageOptions {
|
||||
};
|
||||
|
||||
client?: APIPageClientOptions;
|
||||
|
||||
/**
|
||||
* Custom localStorage key for server selection.
|
||||
*
|
||||
* Useful when using multiple OpenAPI instances to prevent state conflicts.
|
||||
*
|
||||
* @defaultValue 'apiBaseUrl'
|
||||
*/
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
export interface ApiPageProps {
|
||||
@@ -172,7 +181,7 @@ export function createAPIPage(
|
||||
slugger: new Slugger(),
|
||||
};
|
||||
|
||||
return <APIPage {...props} ctx={ctx} />;
|
||||
return <APIPage {...props} ctx={ctx} storageKey={options.storageKey} />;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -180,8 +189,9 @@ function Root({
|
||||
children,
|
||||
className,
|
||||
ctx,
|
||||
storageKey,
|
||||
...props
|
||||
}: { ctx: RenderContext } & ComponentProps<'div'>) {
|
||||
}: { ctx: RenderContext; storageKey?: string } & ComponentProps<'div'>) {
|
||||
const mediaAdapters: Record<string, MediaAdapter> = {};
|
||||
for (const k in ctx.mediaAdapters) {
|
||||
const adapter = ctx.mediaAdapters[k];
|
||||
@@ -195,6 +205,7 @@ function Root({
|
||||
mediaAdapters={mediaAdapters}
|
||||
servers={ctx.servers}
|
||||
shikiOptions={ctx.shikiOptions}
|
||||
storageKey={storageKey}
|
||||
>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
@@ -207,13 +218,15 @@ async function APIPage({
|
||||
operations,
|
||||
webhooks,
|
||||
ctx,
|
||||
storageKey,
|
||||
}: Omit<ApiPageProps, 'document'> & {
|
||||
ctx: RenderContext;
|
||||
storageKey?: string;
|
||||
}) {
|
||||
const { dereferenced } = ctx.schema;
|
||||
|
||||
return (
|
||||
<Root ctx={ctx}>
|
||||
<Root ctx={ctx} storageKey={storageKey}>
|
||||
{operations?.map((item) => {
|
||||
const pathItem = dereferenced.paths?.[item.path];
|
||||
if (!pathItem)
|
||||
|
||||
@@ -19,6 +19,12 @@ export interface ApiProviderProps
|
||||
defaultBaseUrl?: string;
|
||||
children?: ReactNode;
|
||||
mediaAdapters?: Record<string, MediaAdapter>;
|
||||
/**
|
||||
* Custom localStorage key for server selection.
|
||||
*
|
||||
* @defaultValue 'apiBaseUrl'
|
||||
*/
|
||||
storageKey?: string;
|
||||
}
|
||||
|
||||
export interface SelectedServer {
|
||||
@@ -61,6 +67,7 @@ export function ApiProvider({
|
||||
servers,
|
||||
mediaAdapters,
|
||||
shikiOptions,
|
||||
storageKey = 'apiBaseUrl',
|
||||
}: ApiProviderProps) {
|
||||
const [server, setServer] = useState<SelectedServer | null>(() => {
|
||||
const defaultItem = defaultBaseUrl
|
||||
@@ -76,7 +83,7 @@ export function ApiProvider({
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const cached = localStorage.getItem('apiBaseUrl');
|
||||
const cached = localStorage.getItem(storageKey);
|
||||
if (!cached) return;
|
||||
|
||||
try {
|
||||
@@ -87,7 +94,7 @@ export function ApiProvider({
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, []);
|
||||
}, [storageKey]);
|
||||
|
||||
return (
|
||||
<ApiContext.Provider
|
||||
@@ -112,7 +119,7 @@ export function ApiProvider({
|
||||
if (!prev) return null;
|
||||
|
||||
const updated = { ...prev, variables };
|
||||
localStorage.setItem('apiBaseUrl', JSON.stringify(updated));
|
||||
localStorage.setItem(storageKey, JSON.stringify(updated));
|
||||
return updated;
|
||||
});
|
||||
},
|
||||
@@ -125,11 +132,11 @@ export function ApiProvider({
|
||||
variables: getDefaultValues(obj),
|
||||
};
|
||||
|
||||
localStorage.setItem('apiBaseUrl', JSON.stringify(result));
|
||||
localStorage.setItem(storageKey, JSON.stringify(result));
|
||||
setServer(result);
|
||||
},
|
||||
}),
|
||||
[server, servers],
|
||||
[server, servers, storageKey],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user