OpenAPI: introduce input API on createOpenAPI()

This commit is contained in:
Fuma Nama
2025-08-17 17:57:58 +08:00
parent bc73f4ed9c
commit 2ef7f6bf9e
26 changed files with 310 additions and 233 deletions
+30
View File
@@ -0,0 +1,30 @@
---
'fumadocs-openapi': minor
---
**Introduce `input` API on `createOpenAPI()`, unify `generateFiles()`**
Migration: Move the server object from `lib/source` to `lib/openapi`
```ts
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: ['./my-schema.json'],
});
```
Use the server object for `generateFiles()`:
```ts
import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
void generateFiles({
input: openapi,
output: './content/docs',
// we recommend to enable it
// make sure your endpoint description doesn't break MDX syntax.
includeDescription: true,
});
```
+1 -1
View File
@@ -1,3 +1,3 @@
import { openapi } from '@/lib/source';
import { openapi } from '@/lib/openapi';
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy();
+2 -1
View File
@@ -25,7 +25,7 @@ import { Callout } from 'fumadocs-ui/components/callout';
import { TypeTable } from 'fumadocs-ui/components/type-table';
import * as Preview from '@/components/preview';
import { createMetadata } from '@/lib/metadata';
import { openapi, source } from '@/lib/source';
import { source } from '@/lib/source';
import { Wrapper } from '@/components/preview/wrapper';
import { Mermaid } from '@/components/mdx/mermaid';
import { Rate } from '@/components/rate';
@@ -46,6 +46,7 @@ import { APIPage } from 'fumadocs-openapi/ui';
import { LLMCopyButton, ViewOptions } from '@/components/ai/page-actions';
import * as path from 'node:path';
import { Banner } from 'fumadocs-ui/components/banner';
import { openapi } from '@/lib/openapi';
function PreviewRenderer({ preview }: { preview: string }): ReactNode {
if (preview && preview in Preview) {
@@ -9,12 +9,8 @@ Pass options to the `generateFiles` function.
### Input
An array of input files.
Allowed:
- File Paths
- External URLs
- Wildcard
- OpenAPI Server object.
- anything supported by [`input`](#input-1).
```ts
import { generateFiles } from 'fumadocs-openapi';
@@ -40,7 +36,10 @@ void generateFiles({
Customise how the page is generated, default to `operation`.
> Operation in OpenAPI schema refers to an API endpoint with specific method like `/api/weather:GET`.
<Callout>
Operation refers to an API endpoint with specific method like
`/api/weather:GET`.
</Callout>
| mode | | output |
| --------- | --------------------------- | ------------------------------------- |
@@ -162,6 +161,35 @@ tags:
The server to render pages.
### Input
- File Paths (including wildcards)
- External URLs
- A function (see below)
```ts tab="Basic"
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: ['./unkey.json'],
});
```
```ts tab="Function"
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
async input() {
return {
// [id]: downloaded OpenAPI Schema
my_schema: await fetch(
'https://registry.scalar.com/@scalar/apis/galaxy/latest?format=json',
).then((res) => res.json()),
};
},
});
```
### Generate Code Samples
Generate custom code samples for each API endpoint. Make sure to install the types package to give you type-safety when customising it:
@@ -235,32 +263,3 @@ export const openapi = createOpenAPI({
},
});
```
## Advanced
### Using API Page
> This is not a public API, use it carefully.
To use the `APIPage` component in your MDX files:
```mdx
---
title: Delete Api
full: true
---
<APIPage
document="./unkey.json"
operations={[{ path: '/v1/apis.deleteApi', method: 'post' }]}
hasHead={false}
/>
```
Unlike using the `generateFiles()` function, this supports revalidation of the OpenAPI schema if given an URL.
| Prop | Description |
| ------------ | ----------------------------------------- |
| `document` | OpenAPI Schema |
| `operations` | Operations (API endpoints) to be rendered |
| `hasHead` | Enable to render the heading of operation |
@@ -25,10 +25,19 @@ Please note that you must have Tailwind CSS v4 configured.
### Configure Pages
Create an OpenAPI instance on the server. Fumadocs OpenAPI renders the pages on server-side.
Create an OpenAPI instance on the server.
```ts title="lib/openapi.ts"
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
// the OpenAPI schema, you can also give it an external URL.
input: ['./unkey.json'],
});
```
```ts title="lib/source.ts"
import { createOpenAPI, attachFile } from 'fumadocs-openapi/server';
import { attachFile } from 'fumadocs-openapi/server';
import { loader } from 'fumadocs-core/source';
export const source = loader({
@@ -37,9 +46,6 @@ export const source = loader({
attachFile,
},
});
// [!code ++]
export const openapi = createOpenAPI();
```
Add `APIPage` to your MDX Components, so that you can use it in MDX files.
@@ -47,7 +53,7 @@ Add `APIPage` to your MDX Components, so that you can use it in MDX files.
```tsx title="mdx-components.tsx"
import defaultComponents from 'fumadocs-ui/mdx';
import { APIPage } from 'fumadocs-openapi/ui';
import { openapi } from '@/lib/source';
import { openapi } from '@/lib/openapi';
import type { MDXComponents } from 'mdx/types';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
@@ -59,7 +65,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
}
```
> It is a React Server Component.
> `APIPage` is a React Server Component.
### Generate Files
@@ -67,12 +73,12 @@ You can generate MDX files directly from your OpenAPI schema.
Create a script:
```js title="scripts/generate-docs.mjs"
```js title="scripts/generate-docs.ts"
import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
void generateFiles({
// the OpenAPI schema, you can also give it an external URL.
input: ['./unkey.json'],
input: openapi,
output: './content/docs',
// we recommend to enable it
// make sure your endpoint description doesn't break MDX syntax.
@@ -85,7 +91,7 @@ void generateFiles({
Generate docs with the script:
```bash
node ./scripts/generate-docs.mjs
bun ./scripts/generate-docs.ts
```
## Features
@@ -19,7 +19,7 @@ We can use it for executing HTTP requests on the OpenAPI playground, when the ta
Create a route handler for proxy server.
```ts title="/api/proxy/route.ts"
import { openapi } from '@/lib/source';
import { openapi } from '@/lib/openapi';
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy({
// optional, we recommend to set a list of allowed origins for proxied requests
+13
View File
@@ -0,0 +1,13 @@
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: ['./scalar.yaml'],
proxyUrl: '/api/proxy',
shikiOptions: {
themes: {
dark: 'vesper',
light: 'vitesse-light',
},
},
});
+1 -11
View File
@@ -2,7 +2,7 @@ import { createMDXSource } from 'fumadocs-mdx';
import type { InferMetaType, InferPageType } from 'fumadocs-core/source';
import { loader } from 'fumadocs-core/source';
import { icons } from 'lucide-react';
import { attachFile, createOpenAPI } from 'fumadocs-openapi/server';
import { attachFile } from 'fumadocs-openapi/server';
import { createElement } from 'react';
import { blog as blogPosts, docs } from '@/.source';
@@ -23,15 +23,5 @@ export const blog = loader({
source: createMDXSource(blogPosts),
});
export const openapi = createOpenAPI({
proxyUrl: '/api/proxy',
shikiOptions: {
themes: {
dark: 'vesper',
light: 'vitesse-light',
},
},
});
export type Page = InferPageType<typeof source>;
export type Meta = InferMetaType<typeof source>;
+2 -3
View File
@@ -4,8 +4,8 @@
"private": true,
"scripts": {
"build": "pnpm build:pre && next build && pnpm build:post",
"build:pre": "tsx ./scripts/pre-build.mts",
"build:post": "tsx ./scripts/post-build.mts",
"build:pre": "bun ./scripts/pre-build.ts",
"build:post": "bun ./scripts/post-build.ts",
"clean": "rimraf .next",
"dev": "next dev --turbo",
"lint": "fumadocs-mdx && tsx ./scripts/lint.mts && eslint .",
@@ -79,7 +79,6 @@
"tinyglobby": "^0.2.14",
"ts-morph": "^26.0.0",
"tsconfig": "workspace:*",
"tsx": "^4.20.4",
"typescript": "^5.9.2"
}
}
@@ -1,13 +1,14 @@
import { buildRegistry } from '@/scripts/build-registry.mjs';
import { buildRegistry } from '@/scripts/build-registry';
import * as OpenAPI from 'fumadocs-openapi';
import { rimraf } from 'rimraf';
import { openapi } from '@/lib/openapi';
export async function generateDocs() {
await rimraf('./content/docs/openapi/(generated)');
await Promise.all([
OpenAPI.generateFiles({
input: ['./scalar.yaml'],
input: openapi,
output: './content/docs/openapi/(generated)',
per: 'operation',
includeDescription: true,
+6
View File
@@ -0,0 +1,6 @@
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
// input files
input: ['./openapi.json'],
});
+1 -3
View File
@@ -1,6 +1,6 @@
import { createMDXSource } from 'fumadocs-mdx';
import { loader } from 'fumadocs-core/source';
import { attachFile, createOpenAPI } from 'fumadocs-openapi/server';
import { attachFile } from 'fumadocs-openapi/server';
import { docs, meta } from '@/.source';
export const source = loader({
@@ -10,5 +10,3 @@ export const source = loader({
attachFile,
},
});
export const openapi = createOpenAPI();
+1 -1
View File
@@ -1,6 +1,6 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
import { openapi } from '@/lib/source';
import { openapi } from '@/lib/openapi';
import { APIPage } from 'fumadocs-openapi/ui';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
+1 -1
View File
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"build": "next build",
"build:docs": "node ./scripts/generate-docs.mjs",
"build:docs": "bun ./scripts/generate-docs.ts",
"dev": "next dev --turbo",
"start": "next start",
"types:check": "fumadocs-mdx && tsc --noEmit"
@@ -1,5 +1,6 @@
import * as OpenAPI from 'fumadocs-openapi';
import { rimraf } from 'rimraf';
import { openapi } from '@/lib/openapi';
const out = './content/docs/(api)';
@@ -12,8 +13,7 @@ async function generate() {
});
await OpenAPI.generateFiles({
// input files
input: ['./openapi.json'],
input: openapi,
output: out,
});
}
+43 -47
View File
@@ -1,6 +1,5 @@
import { mkdir, writeFile } from 'node:fs/promises';
import * as path from 'node:path';
import { resolve } from 'node:path';
import { glob } from 'tinyglobby';
import {
generateAll,
@@ -11,10 +10,10 @@ import {
generateTags,
} from './generate';
import {
type DocumentInput,
processDocument,
type ProcessedDocument,
} from '@/utils/process-document';
import type { OpenAPIServer } from '@/server';
interface GenerateFileOutput {
/**
@@ -102,9 +101,9 @@ interface BaseName {
interface BaseConfig extends GenerateOptions {
/**
* Schema files
* Schema files, or the OpenAPI server object
*/
input: string[] | string;
input: string[] | string | OpenAPIServer;
/**
* Output directory
@@ -120,38 +119,52 @@ interface BaseConfig extends GenerateOptions {
}
export async function generateFiles(options: Config): Promise<void> {
const { input, cwd = process.cwd() } = options;
const urlInputs: string[] = [];
const fileInputs: string[] = [];
const { cwd = process.cwd() } = options;
const input =
typeof options.input === 'string' ? [options.input] : options.input;
let schemas: Record<string, ProcessedDocument> = {};
for (const v of typeof input === 'string' ? [input] : input) {
if (isUrl(v)) {
urlInputs.push(v);
} else {
fileInputs.push(v);
if (Array.isArray(input)) {
const targets: string[] = [];
for (const item of input) {
if (isUrl(item)) {
targets.push(item);
} else {
targets.push(...(await glob(item, { cwd, absolute: true })));
}
}
await Promise.all(
targets.map(async (item) => {
schemas[item] = await processDocument(item);
}),
);
} else {
schemas = await input.getSchemas();
}
const resolvedInputs = [
...(await glob(fileInputs, { cwd, absolute: false })),
...urlInputs,
];
const resolvedSchemas = Object.entries(schemas);
if (resolvedInputs.length === 0) {
throw new Error(
`No input files found. Tried resolving: ${
typeof input === 'string' ? input : input.join(', ')
}`,
);
if (resolvedSchemas.length === 0) {
throw new Error('No input files found.');
}
await Promise.all(
resolvedInputs.map((input) => generateFromDocument(input, options)),
resolvedSchemas.map(([id, document]) =>
generateFromDocument(id, document, options),
),
);
}
async function generateFromDocument(pathOrUrl: string, options: Config) {
async function generateFromDocument(
schemaId: string,
document: ProcessedDocument,
options: Config,
) {
const { output, cwd = process.cwd(), slugify = defaultSlugify } = options;
const outputDir = path.join(cwd, output);
let nameFn: (
output: GeneratePageOutput | GenerateTagOutput | GenerateFileOutput,
document: ProcessedDocument['document'],
@@ -168,9 +181,9 @@ async function generateFromDocument(pathOrUrl: string, options: Config) {
}
if (options.per === 'file') {
return isUrl(pathOrUrl)
return isUrl(schemaId)
? 'index'
: path.basename(pathOrUrl, path.extname(pathOrUrl));
: path.basename(schemaId, path.extname(schemaId));
}
const result = output as GeneratePageOutput;
@@ -201,9 +214,6 @@ async function generateFromDocument(pathOrUrl: string, options: Config) {
nameFn = options.name as typeof nameFn;
}
const document = await dereference(pathOrUrl, options);
const outputDir = path.join(cwd, output);
async function write(file: string, content: string) {
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, content);
@@ -248,10 +258,10 @@ async function generateFromDocument(pathOrUrl: string, options: Config) {
}
if (options.per === 'file') {
const result = await generateAll(pathOrUrl, document, options);
const result = await generateAll(schemaId, document, options);
const filename = nameFn(
{
pathOrUrl,
pathOrUrl: schemaId,
content: result,
},
document.document,
@@ -262,7 +272,7 @@ async function generateFromDocument(pathOrUrl: string, options: Config) {
await write(outPath, result);
console.log(`Generated: ${outPath}`);
} else if (options.per === 'tag') {
const results = await generateTags(pathOrUrl, document, options);
const results = await generateTags(schemaId, document, options);
for (const result of results) {
const filename = nameFn(result, document.document);
@@ -271,7 +281,7 @@ async function generateFromDocument(pathOrUrl: string, options: Config) {
console.log(`Generated: ${outPath}`);
}
} else {
const results = await generatePages(pathOrUrl, document, options);
const results = await generatePages(schemaId, document, options);
const mapping = new Map<string, GeneratePageOutput>();
for (const result of results) {
@@ -323,17 +333,3 @@ function getOutputPathFromRoute(path: string): string {
function defaultSlugify(s: string): string {
return s.replace(/\s+/g, '-').toLowerCase();
}
async function dereference(
pathOrDocument: DocumentInput,
options: GenerateOptions,
) {
return processDocument(
// resolve paths
typeof pathOrDocument === 'string' &&
!pathOrDocument.startsWith('http://') &&
!pathOrDocument.startsWith('https://')
? resolve(options.cwd ?? process.cwd(), pathOrDocument)
: pathOrDocument,
);
}
+9 -11
View File
@@ -5,10 +5,7 @@ import {
} from '@/utils/generate-document';
import { idToTitle } from '@/utils/id-to-title';
import type { OperationItem, WebhookItem } from '@/render/api-page';
import type {
DocumentInput,
ProcessedDocument,
} from '@/utils/process-document';
import type { ProcessedDocument } from '@/utils/process-document';
export interface GenerateOptions {
/**
@@ -54,6 +51,7 @@ export interface GenerateOptions {
/**
* Inline the entire OpenAPI document into the MDX file.
*
* @deprecated Use the new `input` API on `createOpenAPI()` instead.
* @defaultValue false
*/
inlineDocument?: boolean;
@@ -77,7 +75,7 @@ export type GeneratePageOutput =
};
export async function generateAll(
input: DocumentInput,
schemaId: string,
processed: ProcessedDocument,
options: GenerateOptions = {},
): Promise<string> {
@@ -85,7 +83,7 @@ export async function generateAll(
const items = getAPIPageItems(document);
return generateDocument(
input,
schemaId,
processed,
{
operations: items.operations,
@@ -104,7 +102,7 @@ export async function generateAll(
}
export async function generatePages(
input: DocumentInput,
schemaId: string,
processed: ProcessedDocument,
options: GenerateOptions = {},
): Promise<GeneratePageOutput[]> {
@@ -122,7 +120,7 @@ export async function generatePages(
type: 'operation',
item,
content: generateDocument(
input,
schemaId,
processed,
{
operations: [item],
@@ -153,7 +151,7 @@ export async function generatePages(
type: 'webhook',
item,
content: generateDocument(
input,
schemaId,
processed,
{
webhooks: [item],
@@ -175,7 +173,7 @@ export async function generatePages(
}
export async function generateTags(
input: DocumentInput,
schemaId: string,
processed: ProcessedDocument,
options: GenerateOptions = {},
): Promise<GenerateTagOutput[]> {
@@ -199,7 +197,7 @@ export async function generateTags(
return {
tag: tag.name,
content: generateDocument(
input,
schemaId,
processed,
{
operations,
+10 -32
View File
@@ -2,43 +2,25 @@ import Slugger from 'github-slugger';
import { Operation } from '@/render/operation';
import type { RenderContext } from '@/types';
import { createMethod } from '@/server/create-method';
import { createRenders, type Renderer } from '@/render/renderer';
import { createRenders } from '@/render/renderer';
import type { OpenAPIV3_1 } from 'openapi-types';
import {
type DocumentInput,
processDocument,
type ProcessedDocument,
} from '@/utils/process-document';
import { defaultAdapters } from '@/media/adapter';
import type { SharedOpenAPIOptions } from '@/server';
type ApiPageContextProps = Pick<
Partial<RenderContext>,
| 'shikiOptions'
| 'generateTypeScriptSchema'
| 'generateCodeSamples'
| 'proxyUrl'
| 'showResponseSchema'
| 'disablePlayground'
| 'mediaAdapters'
>;
export interface ApiPageProps extends ApiPageContextProps {
document: DocumentInput;
export interface ApiPageProps extends SharedOpenAPIOptions {
document: Promise<ProcessedDocument> | string | ProcessedDocument;
hasHead: boolean;
renderer?: Partial<Renderer>;
/**
* An array of operations
*/
operations?: OperationItem[];
webhooks?: WebhookItem[];
/**
* By default, it is disabled on dev mode
*/
disableCache?: boolean;
}
export interface WebhookItem {
@@ -52,13 +34,11 @@ export interface OperationItem {
}
export async function APIPage(props: ApiPageProps) {
const {
operations,
hasHead = true,
webhooks,
disableCache = process.env.NODE_ENV === 'development',
} = props;
const processed = await processDocument(props.document, disableCache);
const { operations, hasHead = true, webhooks } = props;
const processed =
typeof props.document === 'string'
? await processDocument(props.document)
: await props.document;
const ctx = await getContext(processed, props);
const { document } = processed;
@@ -121,9 +101,7 @@ export async function APIPage(props: ApiPageProps) {
export async function getContext(
schema: ProcessedDocument,
options: ApiPageContextProps & {
renderer?: Partial<Renderer>;
} = {},
options: SharedOpenAPIOptions = {},
): Promise<RenderContext> {
const document = schema.document;
const servers =
+122 -3
View File
@@ -1,21 +1,140 @@
import type { ApiPageProps } from '@/render/api-page';
import { createProxy } from '@/server/proxy';
import type { CodeSample } from '@/render/operation';
import type { Renderer } from '@/render/renderer';
import type { NoReference } from '@/utils/schema';
import type {
BuiltinTheme,
CodeOptionsThemes,
CodeToHastOptionsCommon,
} from 'shiki';
import type { MediaAdapter } from '@/media/adapter';
import type { MethodInformation } from '@/types';
import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
import {
processDocument,
type ProcessedDocument,
} from '@/utils/process-document';
export type OpenAPIOptions = Omit<Partial<ApiPageProps>, 'document'>;
type Awaitable<T> = T | Promise<T>;
/**
* schema id -> downloaded schema object
*/
type SchemaMap = Record<string, OpenAPIV3_1.Document | OpenAPIV3.Document>;
type ProcessedSchemaMap = Record<string, ProcessedDocument>;
export interface SharedOpenAPIOptions {
/**
* The url of proxy to avoid CORS issues
*/
proxyUrl?: string;
renderer?: Partial<Renderer>;
/**
* Disable API Playground
*
* @defaultValue false
*/
disablePlayground?: boolean;
/**
* Generate TypeScript definitions from response schema.
*
* Pass `false` to disable it.
*
* @param method - the operation object
* @param statusCode - status code
*/
generateTypeScriptSchema?:
| ((
method: NoReference<MethodInformation>,
statusCode: string,
) => Awaitable<string>)
| false;
/**
* Generate code samples for endpoint.
*/
generateCodeSamples?: (method: MethodInformation) => Awaitable<CodeSample[]>;
shikiOptions?: Omit<CodeToHastOptionsCommon, 'lang'> &
CodeOptionsThemes<BuiltinTheme>;
/**
* Show full response schema instead of only example response & Typescript definitions
*
* @default true
*/
showResponseSchema?: boolean;
mediaAdapters?: Record<string, MediaAdapter>;
}
export interface OpenAPIOptions extends SharedOpenAPIOptions {
/**
* Schema files, can be:
* - URL
* - file path
* - a function returning records of downloaded schemas.
*/
input?: string[] | (() => Promise<SchemaMap>);
/**
* By default, it is disabled on dev mode
*/
disableCache?: boolean;
}
export interface OpenAPIServer {
getAPIPageProps: (from: ApiPageProps) => ApiPageProps;
createProxy: typeof createProxy;
getSchemas: () => Promise<ProcessedSchemaMap>;
}
export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
const {
input = [],
disableCache = process.env.NODE_ENV === 'development',
...shared
} = options;
let schemas: Promise<ProcessedSchemaMap> | undefined;
async function getSchemas() {
const out: ProcessedSchemaMap = {};
if (Array.isArray(input)) {
await Promise.all(
input.map(async (item) => {
out[item] = await processDocument(item, disableCache);
}),
);
} else {
await Promise.all(
Object.entries(await input()).map(async ([k, v]) => {
out[k] = await processDocument(v, disableCache);
}),
);
}
return out;
}
return {
createProxy,
getAPIPageProps(props) {
async getSchemas() {
return (schemas ??= getSchemas());
},
getAPIPageProps({ document, ...props }) {
return {
...options,
...shared,
...props,
document:
typeof document === 'string'
? this.getSchemas().then((map) => {
return map[document] ?? processDocument(document, disableCache);
})
: document,
};
},
};
+2 -51
View File
@@ -1,15 +1,10 @@
import type { OpenAPIV3_1 as V3_1 } from 'openapi-types';
import type { default as Slugger } from 'github-slugger';
import { type Renderer } from '@/render/renderer';
import type { CodeSample } from '@/render/operation';
import type {
BuiltinTheme,
CodeOptionsThemes,
CodeToHastOptionsCommon,
} from 'shiki';
import type { NoReference } from '@/utils/schema';
import type { ProcessedDocument } from '@/utils/process-document';
import type { MediaAdapter } from '@/media/adapter';
import type { SharedOpenAPIOptions } from '@/server';
export type Document = V3_1.Document;
export type OperationObject = V3_1.OperationObject;
@@ -27,28 +22,14 @@ export type MethodInformation = NoReference<OperationObject> & {
method: string;
};
type Awaitable<T> = T | Promise<T>;
/**
* Dereferenced value and its original `$ref` value
*/
export type DereferenceMap = Map<unknown, string>;
export interface RenderContext {
/**
* The url of proxy to avoid CORS issues
*/
proxyUrl?: string;
export interface RenderContext extends SharedOpenAPIOptions {
renderer: Renderer;
/**
* Disable API Playground
*/
disablePlayground?: boolean;
servers: ServerObject[];
slugger: Slugger;
/**
@@ -56,35 +37,5 @@ export interface RenderContext {
*/
schema: ProcessedDocument;
/**
* Generate TypeScript definitions from response schema.
*
* Pass `false` to disable it.
*
* @param method - the operation object
* @param statusCode - status code
*/
generateTypeScriptSchema?:
| ((
method: NoReference<MethodInformation>,
statusCode: string,
) => Awaitable<string>)
| false;
/**
* Generate code samples for endpoint.
*/
generateCodeSamples?: (method: MethodInformation) => Awaitable<CodeSample[]>;
shikiOptions?: Omit<CodeToHastOptionsCommon, 'lang'> &
CodeOptionsThemes<BuiltinTheme>;
/**
* Show full response schema instead of only example response & Typescript definitions
*
* @default true
*/
showResponseSchema?: boolean;
mediaAdapters: Record<string, MediaAdapter>;
}
@@ -11,10 +11,7 @@ import type { GenerateOptions } from '@/generate';
import { idToTitle } from '@/utils/id-to-title';
import type { Document, TagObject } from '@/types';
import type { NoReference } from '@/utils/schema';
import type {
DocumentInput,
ProcessedDocument,
} from '@/utils/process-document';
import type { ProcessedDocument } from '@/utils/process-document';
interface StaticData {
toc: TableOfContents;
@@ -34,7 +31,7 @@ export type DocumentContext =
};
export function generateDocument(
input: DocumentInput,
schemaId: string,
processed: ProcessedDocument,
pageProps: Omit<ApiPageProps, 'document'>,
options: GenerateOptions & {
@@ -52,7 +49,7 @@ export function generateDocument(
const extend = frontmatter?.(options.title, options.description, context);
const page: ApiPageProps = {
...pageProps,
document: options.inlineDocument ? processed.downloaded : input,
document: schemaId,
};
let meta: object | undefined;
@@ -4,8 +4,6 @@ import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
import { bundle, dereference, upgrade } from '@scalar/openapi-parser';
import { fetchUrls, readFiles } from '@scalar/openapi-parser/plugins';
export type DocumentInput = string | OpenAPIV3_1.Document | OpenAPIV3.Document;
export type ProcessedDocument = {
document: NoReference<Document>;
dereferenceMap: DereferenceMap;
@@ -18,7 +16,7 @@ const cache = new Map<string, ProcessedDocument>();
* process & reference input document to a Fumadocs OpenAPI compatible format
*/
export async function processDocument(
input: DocumentInput,
input: string | OpenAPIV3_1.Document | OpenAPIV3.Document,
disableCache = false,
): Promise<ProcessedDocument> {
const cached =
+4 -4
View File
@@ -106,8 +106,8 @@ describe('Generate documents', () => {
per: 'file',
cwd,
}),
).rejects.toThrow(
'No input files found. Tried resolving: ./fixtures/non-existent-*.yaml',
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: No input files found.]`,
);
await expect(
@@ -123,8 +123,8 @@ describe('Generate documents', () => {
algorithm: 'v1',
},
}),
).rejects.toThrow(
'No input files found. Tried resolving: ./fixtures/non-existent-1.yaml, ./fixtures/non-existent-2.yaml',
).rejects.toThrowErrorMatchingInlineSnapshot(
`[Error: No input files found.]`,
);
});
-3
View File
@@ -252,9 +252,6 @@ importers:
tsconfig:
specifier: workspace:*
version: link:../../packages/tsconfig
tsx:
specifier: ^4.20.4
version: 4.20.4
typescript:
specifier: ^5.9.2
version: 5.9.2