mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
fix(openapi): improve loader API types
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
improve loader API types
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Awaitable } from '@/types';
|
||||
import type { DynamicLoader } from './dynamic';
|
||||
import type { StructuredData } from '@/mdx-plugins';
|
||||
import z from 'zod';
|
||||
import { pageSchema } from './schema';
|
||||
|
||||
export type SourceUnion<Config extends SourceConfig = SourceConfig> =
|
||||
| StaticSource<Config>
|
||||
@@ -39,10 +41,10 @@ export interface MetaData {
|
||||
|
||||
export interface PageData {
|
||||
icon?: string | undefined;
|
||||
title?: string;
|
||||
title?: string | undefined;
|
||||
description?: string | undefined;
|
||||
|
||||
structuredData?: StructuredData | (() => Awaitable<StructuredData>);
|
||||
structuredData?: StructuredData | (() => Awaitable<StructuredData>) | undefined;
|
||||
}
|
||||
|
||||
export type VirtualFile<Config extends SourceConfig = SourceConfig> =
|
||||
|
||||
@@ -104,7 +104,11 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
_name: Name,
|
||||
base: string,
|
||||
glob: AwaitableGlobEntries<unknown>,
|
||||
) {
|
||||
): Promise<
|
||||
Config[Name] extends DocCollection<infer Schema> | DocsCollection<infer Schema>
|
||||
? DocCollectionEntry<Name, StandardSchemaV1.InferOutput<Schema>, TC>[]
|
||||
: never
|
||||
> {
|
||||
const out = await Promise.all(
|
||||
Object.entries(glob).map(async ([k, v]) => {
|
||||
const data: CompiledMDXProperties = typeof v === 'function' ? await v() : v;
|
||||
@@ -117,18 +121,18 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
}),
|
||||
);
|
||||
|
||||
return out as unknown as Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? DocCollectionEntry<Name, StandardSchemaV1.InferOutput<Schema>, TC>[]
|
||||
: never;
|
||||
return out as never;
|
||||
},
|
||||
async docLazy<Name extends keyof Config & string>(
|
||||
_name: Name,
|
||||
base: string,
|
||||
head: AwaitableGlobEntries<unknown>,
|
||||
body: Record<string, () => Promise<unknown>>,
|
||||
) {
|
||||
): Promise<
|
||||
Config[Name] extends DocCollection<infer Schema> | DocsCollection<infer Schema>
|
||||
? AsyncDocCollectionEntry<Name, StandardSchemaV1.InferOutput<Schema>, TC>[]
|
||||
: never
|
||||
> {
|
||||
const out = await Promise.all(
|
||||
Object.entries(head).map(async ([k, v]) => {
|
||||
const data = typeof v === 'function' ? await v() : v;
|
||||
@@ -147,17 +151,19 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
}),
|
||||
);
|
||||
|
||||
return out as unknown as Config[Name] extends
|
||||
| DocCollection<infer Schema>
|
||||
| DocsCollection<infer Schema>
|
||||
? AsyncDocCollectionEntry<Name, StandardSchemaV1.InferOutput<Schema>, TC>[]
|
||||
: never;
|
||||
return out as never;
|
||||
},
|
||||
async meta<Name extends keyof Config & string>(
|
||||
_name: Name,
|
||||
base: string,
|
||||
glob: AwaitableGlobEntries<unknown>,
|
||||
) {
|
||||
): Promise<
|
||||
Config[Name] extends
|
||||
| MetaCollection<infer Schema>
|
||||
| DocsCollection<StandardSchemaV1, infer Schema>
|
||||
? MetaCollectionEntry<StandardSchemaV1.InferOutput<Schema>>[]
|
||||
: never
|
||||
> {
|
||||
const out = await Promise.all(
|
||||
Object.entries(glob).map(async ([k, v]) => {
|
||||
const data = typeof v === 'function' ? await v() : v;
|
||||
@@ -169,11 +175,7 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
}),
|
||||
);
|
||||
|
||||
return out as unknown as Config[Name] extends
|
||||
| MetaCollection<infer Schema>
|
||||
| DocsCollection<StandardSchemaV1, infer Schema>
|
||||
? MetaCollectionEntry<StandardSchemaV1.InferOutput<Schema>>[]
|
||||
: never;
|
||||
return out as never;
|
||||
},
|
||||
|
||||
async docs<Name extends keyof Config & string>(
|
||||
@@ -181,16 +183,8 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
base: string,
|
||||
metaGlob: AwaitableGlobEntries<unknown>,
|
||||
docGlob: AwaitableGlobEntries<unknown>,
|
||||
) {
|
||||
const entry = {
|
||||
docs: await this.doc(name, base, docGlob),
|
||||
meta: await this.meta(name, base, metaGlob),
|
||||
toFumadocsSource(options) {
|
||||
return toFumadocsSource(this.docs, this.meta, options);
|
||||
},
|
||||
} satisfies DocsCollectionEntry;
|
||||
|
||||
return entry as Config[Name] extends DocsCollection<infer Page, infer Meta>
|
||||
): Promise<
|
||||
Config[Name] extends DocsCollection<infer Page, infer Meta>
|
||||
? StandardSchemaV1.InferOutput<Page> extends PageData
|
||||
? StandardSchemaV1.InferOutput<Meta> extends MetaData
|
||||
? DocsCollectionEntry<
|
||||
@@ -201,7 +195,17 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
>
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
: never
|
||||
> {
|
||||
const entry = {
|
||||
docs: await this.doc(name, base, docGlob),
|
||||
meta: await this.meta(name, base, metaGlob),
|
||||
toFumadocsSource(options) {
|
||||
return toFumadocsSource(this.docs, this.meta, options);
|
||||
},
|
||||
} satisfies DocsCollectionEntry;
|
||||
|
||||
return entry as never;
|
||||
},
|
||||
async docsLazy<Name extends keyof Config & string>(
|
||||
name: Name,
|
||||
@@ -209,16 +213,8 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
metaGlob: AwaitableGlobEntries<unknown>,
|
||||
docHeadGlob: AwaitableGlobEntries<unknown>,
|
||||
docBodyGlob: Record<string, () => Promise<unknown>>,
|
||||
) {
|
||||
const entry = {
|
||||
docs: await this.docLazy(name, base, docHeadGlob, docBodyGlob),
|
||||
meta: await this.meta(name, base, metaGlob),
|
||||
toFumadocsSource(options) {
|
||||
return toFumadocsSource(this.docs, this.meta, options);
|
||||
},
|
||||
} satisfies AsyncDocsCollectionEntry;
|
||||
|
||||
return entry as Config[Name] extends DocsCollection<infer Page, infer Meta>
|
||||
): Promise<
|
||||
Config[Name] extends DocsCollection<infer Page, infer Meta>
|
||||
? StandardSchemaV1.InferOutput<Page> extends PageData
|
||||
? StandardSchemaV1.InferOutput<Meta> extends MetaData
|
||||
? AsyncDocsCollectionEntry<
|
||||
@@ -229,7 +225,17 @@ export function server<Config, TC extends InternalTypeConfig>(options: ServerOpt
|
||||
>
|
||||
: never
|
||||
: never
|
||||
: never;
|
||||
: never
|
||||
> {
|
||||
const entry = {
|
||||
docs: await this.docLazy(name, base, docHeadGlob, docBodyGlob),
|
||||
meta: await this.meta(name, base, metaGlob),
|
||||
toFumadocsSource(options) {
|
||||
return toFumadocsSource(this.docs, this.meta, options);
|
||||
},
|
||||
} satisfies AsyncDocsCollectionEntry;
|
||||
|
||||
return entry as never;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"@fastify/deepmerge": "3.2.1",
|
||||
"@scalar/helpers": "0.8.0",
|
||||
"@scalar/json-magic": "0.12.14",
|
||||
"@scalar/openapi-upgrader": "0.2.7",
|
||||
"@scalar/openapi-upgrader": "0.2.8",
|
||||
"ajv": "8.20.0",
|
||||
"fast-content-type-parse": "3.0.0",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
|
||||
@@ -67,7 +67,7 @@ export interface OpenAPIServer {
|
||||
) => Promise<Source<{ metaData: MetaData; pageData: OpenAPIPageData }>>;
|
||||
|
||||
/**
|
||||
* Generate virtual pages for Fumadocs Source API
|
||||
* Generate virtual pages for Fumadocs Source API (note: please disable cache to allow built-in revalidation)
|
||||
*/
|
||||
dynamicSource: (
|
||||
options?: OpenAPISourceOptions,
|
||||
@@ -88,6 +88,7 @@ export interface OpenAPIPageData extends PageData {
|
||||
getClientAPIPageProps: () => Promise<ClientApiPageProps>;
|
||||
structuredData: StructuredData;
|
||||
toc: TOCItemType[];
|
||||
_openapi?: InternalOpenAPIMeta;
|
||||
}
|
||||
|
||||
export type OpenAPISourceOptions = SchemaToPagesOptions & {
|
||||
@@ -117,15 +118,19 @@ export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
|
||||
async function getVirtualFiles(server: OpenAPIServer, options: OpenAPISourceOptions) {
|
||||
const { baseDir = '', meta = false } = options;
|
||||
const { createAutoPreset } = await import('@/utils/pages/preset-auto');
|
||||
const { fromServer } = await import('@/utils/pages/builder');
|
||||
const { fromSchema } = await import('@/utils/pages/builder');
|
||||
const files: VirtualFile<{
|
||||
pageData: OpenAPIPageData;
|
||||
metaData: MetaData;
|
||||
}>[] = [];
|
||||
|
||||
const entries = await fromServer(server, createAutoPreset(options));
|
||||
for (const [schemaId, list] of Object.entries(entries)) {
|
||||
const processed = await server.getSchema(schemaId);
|
||||
const schemas = await server.getSchemas();
|
||||
const builderOptions = createAutoPreset(options);
|
||||
|
||||
for (const [id, schema] of Object.entries(schemas)) {
|
||||
const list = fromSchema(id, schema, builderOptions);
|
||||
|
||||
onEntries(list);
|
||||
|
||||
function onEntry(entry: PageOutput | OperationOutput | WebhookOutput) {
|
||||
const props = getPageProps(entry);
|
||||
@@ -141,7 +146,7 @@ export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
|
||||
async getClientAPIPageProps() {
|
||||
return {
|
||||
payload: {
|
||||
bundled: processed.bundled,
|
||||
bundled: schema.bundled,
|
||||
proxyUrl: server.options.proxyUrl,
|
||||
},
|
||||
...props,
|
||||
@@ -149,11 +154,11 @@ export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
|
||||
},
|
||||
getSchema() {
|
||||
return {
|
||||
id: schemaId,
|
||||
...processed,
|
||||
id,
|
||||
...schema,
|
||||
};
|
||||
},
|
||||
...toStaticData(props, processed.dereferenced),
|
||||
...toStaticData(props, schema.dereferenced),
|
||||
_openapi: {
|
||||
method:
|
||||
entry.type === 'operation' || entry.type === 'webhook'
|
||||
@@ -211,8 +216,6 @@ export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onEntries(list);
|
||||
}
|
||||
|
||||
return files;
|
||||
@@ -265,15 +268,6 @@ export function createCodeSample<T>(
|
||||
return options;
|
||||
}
|
||||
|
||||
declare module 'fumadocs-core/source' {
|
||||
export interface PageData {
|
||||
/**
|
||||
* Added by Fumadocs OpenAPI
|
||||
*/
|
||||
_openapi?: InternalOpenAPIMeta;
|
||||
}
|
||||
}
|
||||
|
||||
export interface InternalOpenAPIMeta {
|
||||
method?: string;
|
||||
webhook?: boolean;
|
||||
@@ -293,7 +287,7 @@ export function openapiPlugin(): LoaderPlugin {
|
||||
const file = this.storage.read(filePath);
|
||||
if (!file || file.format !== 'page') return node;
|
||||
|
||||
const openApiData = file.data._openapi;
|
||||
const openApiData = (file.data as { _openapi?: InternalOpenAPIMeta })._openapi;
|
||||
if (!openApiData || typeof openApiData !== 'object') return node;
|
||||
|
||||
if (openApiData.deprecated) {
|
||||
|
||||
@@ -98,25 +98,6 @@ interface ExtractedInfo {
|
||||
})[];
|
||||
}
|
||||
|
||||
export async function fromServer(
|
||||
server: OpenAPIServer,
|
||||
config: PagesBuilderConfig,
|
||||
): Promise<Record<string, OutputEntry[]>> {
|
||||
const schemas = await server.getSchemas();
|
||||
const generated: Record<string, OutputEntry[]> = {};
|
||||
|
||||
const entries = Object.entries(schemas);
|
||||
if (entries.length === 0) {
|
||||
throw new Error('No input files found.');
|
||||
}
|
||||
|
||||
for (const [id, schema] of entries) {
|
||||
generated[id] = fromSchema(id, schema, config);
|
||||
}
|
||||
|
||||
return generated;
|
||||
}
|
||||
|
||||
export function fromSchema(
|
||||
schemaId: string,
|
||||
processed: DereferencedDocument,
|
||||
|
||||
Reference in New Issue
Block a user