@@ -595,6 +654,11 @@ function ApiReferenceSwitcher({
);
}
+function getExistingSidebarBanner(config: DocsConfig): unknown {
+ if (!config.sidebar || config.sidebar === true) return undefined;
+ return config.sidebar.banner;
+}
+
function mergeBanner(existing: unknown, next: ReactNode) {
if (!existing) return next;
@@ -616,12 +680,14 @@ export function withNextApiReferenceBanner(config: DocsConfig): DocsConfig {
const switcher = (
);
+ const existingBanner = getExistingSidebarBanner(config);
+ const banner = mergeBanner(existingBanner, switcher);
if (!config.sidebar || config.sidebar === true) {
return {
...config,
sidebar: {
- banner: switcher,
+ banner,
},
};
}
@@ -630,7 +696,7 @@ export function withNextApiReferenceBanner(config: DocsConfig): DocsConfig {
...config,
sidebar: {
...config.sidebar,
- banner: mergeBanner(config.sidebar.banner, switcher),
+ banner,
},
};
}
@@ -638,7 +704,7 @@ export function withNextApiReferenceBanner(config: DocsConfig): DocsConfig {
export function createNextApiReference(config: DocsConfig) {
const apiReference = resolveApiReferenceConfig(config.apiReference);
- return async () => {
+ return async (request?: Request) => {
if (!apiReference.enabled) {
return new Response("Not Found", {
status: 404,
@@ -648,6 +714,7 @@ export function createNextApiReference(config: DocsConfig) {
const document = await buildApiReferenceOpenApiDocumentAsync(config, {
framework: "next",
rootDir: process.cwd(),
+ baseUrl: getOriginFromRequest(request),
});
return ApiReference({
@@ -676,3 +743,214 @@ export function createNextApiReference(config: DocsConfig) {
})();
};
}
+
+function getOpenApiInfo(document: Record
): {
+ title: string;
+ description?: string;
+} {
+ const info =
+ document.info && typeof document.info === "object" && !Array.isArray(document.info)
+ ? (document.info as Record)
+ : {};
+
+ return {
+ title: typeof info.title === "string" && info.title.trim() ? info.title : "API Reference",
+ description:
+ typeof info.description === "string" && info.description.trim()
+ ? info.description
+ : undefined,
+ };
+}
+
+export function createNextApiReferencePage(config: DocsConfig) {
+ return async function NextApiReferencePage(props?: {
+ params?: Promise<{ slug?: string[] }> | { slug?: string[] };
+ }) {
+ const [{ createAPIPage }, { DocsBody, DocsDescription, DocsPage, DocsTitle }] =
+ await Promise.all([
+ import("fumadocs-openapi/ui"),
+ import("fumadocs-ui/layouts/notebook/page"),
+ ]);
+ const { info, pages, server, source } = await getNextApiReferenceSourceState(config);
+ const resolvedParams = props?.params ? await props.params : undefined;
+ const slug = resolvedParams?.slug ?? [];
+
+ if (pages.length === 0) {
+ return (
+
+ {info.title}
+ {info.description}
+
+
+ No operations were found in the OpenAPI document.
+
+
+
+ );
+ }
+
+ if (slug.length === 0) {
+ redirect(pages[0].url);
+ }
+
+ const page = source.getPage(slug);
+ if (!page || typeof page.data.getAPIPageProps !== "function") {
+ notFound();
+ }
+ const APIPage = createAPIPage(server);
+ const currentPageIndex = pages.findIndex((entry) => entry.url === page.url);
+ const previousPage = currentPageIndex > 0 ? pages[currentPageIndex - 1] : undefined;
+ const nextPage =
+ currentPageIndex >= 0 && currentPageIndex < pages.length - 1
+ ? pages[currentPageIndex + 1]
+ : undefined;
+
+ return (
+
+ {page.data.title ?? info.title}
+
+ {typeof page.data.description === "string" && page.data.description.trim()
+ ? page.data.description
+ : info.description}
+
+
+
+
+ {previousPage || nextPage ? (
+
+ ) : null}
+
+ );
+ };
+}
+
+export function createNextApiReferenceLayout(config: DocsConfig) {
+ return async function NextApiReferenceLayout(props: { children: React.ReactNode }) {
+ const { DocsLayout } = await import("fumadocs-ui/layouts/notebook");
+ const { apiReference, source } = await getNextApiReferenceSourceState(config);
+ const docsUrl = getDocsUrl(config);
+ const apiUrl = `/${apiReference.path}`;
+ const banner = mergeBanner(
+ getExistingSidebarBanner(config),
+ ,
+ );
+
+ return (
+
+
+
+ {props.children}
+
+
+ );
+ };
+}
+
+export async function getNextApiReferenceSourceState(
+ config: DocsConfig,
+): Promise {
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
+ const [{ createOpenAPI, openapiPlugin, openapiSource }, { loader }] = await Promise.all([
+ import("fumadocs-openapi/server"),
+ import("fumadocs-core/source"),
+ ]);
+ const baseUrl = await getOriginFromNextHeaders();
+ const document = await buildApiReferenceOpenApiDocumentAsync(config, {
+ framework: "next",
+ rootDir: process.cwd(),
+ baseUrl,
+ });
+
+ const server = createOpenAPI({
+ input: async () => ({
+ main: document as any,
+ }),
+ });
+ const info = getOpenApiInfo(document);
+ const source = loader(
+ await openapiSource(server, {
+ per: "operation",
+ groupBy: "tag",
+ name(output, dereferenced) {
+ if (output.type !== "operation") {
+ return slugifyApiReferencePageName(output.item.name);
+ }
+
+ const pathItem = dereferenced.paths?.[output.item.path];
+ const operation = pathItem?.[output.item.method];
+ const summary =
+ typeof operation?.summary === "string" && operation.summary.trim()
+ ? operation.summary
+ : typeof operation?.operationId === "string" && operation.operationId.trim()
+ ? operation.operationId
+ : `${output.item.method} ${output.item.path}`;
+
+ return slugifyApiReferencePageName(summary);
+ },
+ }),
+ {
+ baseUrl: `/${apiReference.path}`,
+ plugins: [openapiPlugin()],
+ },
+ );
+
+ return {
+ apiReference,
+ document,
+ info,
+ pages: source.getPages(),
+ server,
+ source,
+ };
+}
+
+export function getNextApiReferenceMode(config: DocsConfig): "fumadocs" | "scalar" {
+ return resolveApiReferenceRenderer(config.apiReference, "next");
+}
diff --git a/packages/next/src/config.test.ts b/packages/next/src/config.test.ts
index 3b3ad494..9b0fbb02 100644
--- a/packages/next/src/config.test.ts
+++ b/packages/next/src/config.test.ts
@@ -16,6 +16,16 @@ const DOCS_CONFIG_WITH_API_REFERENCE = `export default {
};
`;
+const DOCS_CONFIG_WITH_SCALAR_API_REFERENCE = `export default {
+ entry: "docs",
+ apiReference: {
+ enabled: true,
+ path: "api-reference",
+ renderer: "scalar",
+ },
+};
+`;
+
describe("withDocs (app dir: src/app vs app)", () => {
let tmpDir: string;
let originalCwd: string;
@@ -71,14 +81,28 @@ describe("withDocs (app dir: src/app vs app)", () => {
expect(existsSync(join(tmpDir, "app/docs/layout.tsx"))).toBe(false);
});
- it("generates API reference routes when enabled in docs.config", () => {
+ it("generates a fumadocs API reference page when enabled in docs.config", () => {
writeFileSync(join(tmpDir, "docs.config.ts"), DOCS_CONFIG_WITH_API_REFERENCE, "utf-8");
mkdirSync(join(tmpDir, "app"), { recursive: true });
process.chdir(tmpDir);
withDocs({});
+ expect(existsSync(join(tmpDir, "app/api-reference/layout.tsx"))).toBe(true);
+ expect(existsSync(join(tmpDir, "app/api-reference/[[...slug]]/page.tsx"))).toBe(true);
+ expect(existsSync(join(tmpDir, "app/api-reference/[[...slug]]/route.ts"))).toBe(false);
+ });
+
+ it("generates a scalar API reference route when renderer is set explicitly", () => {
+ writeFileSync(join(tmpDir, "docs.config.ts"), DOCS_CONFIG_WITH_SCALAR_API_REFERENCE, "utf-8");
+ mkdirSync(join(tmpDir, "app"), { recursive: true });
+ process.chdir(tmpDir);
+
+ withDocs({});
+
+ expect(existsSync(join(tmpDir, "app/api-reference/layout.tsx"))).toBe(false);
expect(existsSync(join(tmpDir, "app/api-reference/[[...slug]]/route.ts"))).toBe(true);
+ expect(existsSync(join(tmpDir, "app/api-reference/[[...slug]]/page.tsx"))).toBe(false);
});
it("skips API reference route generation for static export", () => {
@@ -88,6 +112,8 @@ describe("withDocs (app dir: src/app vs app)", () => {
withDocs({ output: "export" });
+ expect(existsSync(join(tmpDir, "app/api-reference/layout.tsx"))).toBe(false);
+ expect(existsSync(join(tmpDir, "app/api-reference/[[...slug]]/page.tsx"))).toBe(false);
expect(existsSync(join(tmpDir, "app/api-reference/[[...slug]]/route.ts"))).toBe(false);
});
@@ -110,7 +136,8 @@ describe("withDocs (app dir: src/app vs app)", () => {
withDocs({});
- expect(existsSync(join(tmpDir, "app/custom-api-reference/[[...slug]]/route.ts"))).toBe(true);
+ expect(existsSync(join(tmpDir, "app/custom-api-reference/layout.tsx"))).toBe(true);
+ expect(existsSync(join(tmpDir, "app/custom-api-reference/[[...slug]]/page.tsx"))).toBe(true);
});
it("generates a layout that re-exports the package-owned docs layout", () => {
diff --git a/packages/next/src/config.ts b/packages/next/src/config.ts
index e73898a5..850c93bf 100644
--- a/packages/next/src/config.ts
+++ b/packages/next/src/config.ts
@@ -21,8 +21,9 @@
* export default withDocs({ output: "export" });
*/
-import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
-import { join } from "node:path";
+import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
+import { isAbsolute, join, relative } from "node:path";
+import { fileURLToPath } from "node:url";
/** Resolve Next.js App Router directory: prefer src/app when present, else app. */
function getNextAppDir(root: string): string {
@@ -96,10 +97,93 @@ export const GET = createNextApiReference(docsConfig);
export const revalidate = false;
`;
+const API_REFERENCE_PAGE_TEMPLATE = `\
+${GENERATED_BANNER}
+import "@farming-labs/next/api-reference.css";
+import docsConfig from "@/docs.config";
+import { createNextApiReferencePage } from "@farming-labs/next/api-reference";
+
+export const dynamic = "force-dynamic";
+export const revalidate = 0;
+
+const ApiReferencePage = createNextApiReferencePage(docsConfig);
+
+export default ApiReferencePage;
+`;
+
+const API_REFERENCE_LAYOUT_TEMPLATE = `\
+${GENERATED_BANNER}
+import docsConfig from "@/docs.config";
+import { createNextApiReferenceLayout } from "@farming-labs/next/api-reference";
+
+const ApiReferenceLayout = createNextApiReferenceLayout(docsConfig);
+
+export default ApiReferenceLayout;
+`;
+
// ─── Helpers ────────────────────────────────────────────────────────
const FILE_EXTS = ["tsx", "ts", "jsx", "js"];
const INTERNAL_DOCS_CONFIG_ALIAS = "@farming-labs/next-internal-docs-config";
+const NEXT_PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
+
+function resolvePackageAlias(packageName: string, fallbacks: string[] = []): string | undefined {
+ const candidates = [
+ join(NEXT_PACKAGE_ROOT, "node_modules", packageName),
+ ...fallbacks.map((value) => join(NEXT_PACKAGE_ROOT, value)),
+ ];
+
+ return candidates.find((value) => existsSync(value));
+}
+
+function resolvePackageSubpath(packageDir: string, relativePath: string): string {
+ if (!isAbsolute(packageDir))
+ return `${packageDir}/${relativePath.replace(/^dist\//, "").replace(/\/index\.js$/, "")}`;
+ return join(packageDir, relativePath);
+}
+
+function toTurbopackAliasPath(root: string, value: string): string {
+ if (!isAbsolute(value)) return value;
+ const relativePath = relative(root, value);
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
+}
+
+const FUMADOCS_OPENAPI_PACKAGE_ALIAS =
+ resolvePackageAlias("fumadocs-openapi") ?? "fumadocs-openapi";
+const FUMADOCS_CORE_PACKAGE_ALIAS = resolvePackageAlias("fumadocs-core") ?? "fumadocs-core";
+const FUMADOCS_UI_PACKAGE_ALIAS =
+ resolvePackageAlias("fumadocs-ui", [
+ "node_modules/fumadocs-openapi/node_modules/fumadocs-ui",
+ "../node_modules/fumadocs-ui",
+ ]) ?? "fumadocs-ui";
+const FUMADOCS_OPENAPI_UI_ALIAS = resolvePackageSubpath(
+ FUMADOCS_OPENAPI_PACKAGE_ALIAS,
+ "dist/ui/index.js",
+);
+const FUMADOCS_OPENAPI_SERVER_ALIAS = resolvePackageSubpath(
+ FUMADOCS_OPENAPI_PACKAGE_ALIAS,
+ "dist/server/index.js",
+);
+const FUMADOCS_CORE_FRAMEWORK_ALIAS = resolvePackageSubpath(
+ FUMADOCS_CORE_PACKAGE_ALIAS,
+ "dist/framework/index.js",
+);
+const FUMADOCS_CORE_FRAMEWORK_NEXT_ALIAS = resolvePackageSubpath(
+ FUMADOCS_CORE_PACKAGE_ALIAS,
+ "dist/framework/next.js",
+);
+const FUMADOCS_UI_NOTEBOOK_ALIAS = resolvePackageSubpath(
+ FUMADOCS_UI_PACKAGE_ALIAS,
+ "dist/layouts/notebook/index.js",
+);
+const FUMADOCS_UI_NOTEBOOK_PAGE_ALIAS = resolvePackageSubpath(
+ FUMADOCS_UI_PACKAGE_ALIAS,
+ "dist/layouts/notebook/page/index.js",
+);
+const FUMADOCS_UI_PROVIDER_NEXT_ALIAS = resolvePackageSubpath(
+ FUMADOCS_UI_PACKAGE_ALIAS,
+ "dist/provider/next.js",
+);
function hasFile(root: string, baseName: string): boolean {
return FILE_EXTS.some((ext) => existsSync(join(root, `${baseName}.${ext}`)));
@@ -161,6 +245,7 @@ function readOgEndpoint(root: string): string | undefined {
function readApiReferenceConfig(root: string): {
enabled: boolean;
path: string;
+ renderer: "fumadocs" | "scalar";
routeRoot: string;
} {
for (const ext of FILE_EXTS) {
@@ -171,29 +256,35 @@ function readApiReferenceConfig(root: string): {
const content = readFileSync(configPath, "utf-8");
const directFalse = content.match(/apiReference\s*:\s*false/);
- if (directFalse) return { enabled: false, path: "api-reference", routeRoot: "api" };
+ if (directFalse) {
+ return { enabled: false, path: "api-reference", renderer: "fumadocs", routeRoot: "api" };
+ }
const directTrue = content.match(/apiReference\s*:\s*true/);
- if (directTrue) return { enabled: true, path: "api-reference", routeRoot: "api" };
+ if (directTrue) {
+ return { enabled: true, path: "api-reference", renderer: "fumadocs", routeRoot: "api" };
+ }
const block = extractObjectLiteral(content, "apiReference");
if (!block) continue;
const enabledMatch = block.match(/enabled\s*:\s*(true|false)/);
const pathMatch = block.match(/path\s*:\s*["']([^"']+)["']/);
+ const rendererMatch = block.match(/renderer\s*:\s*["'](fumadocs|scalar)["']/);
const routeRootMatch = block.match(/routeRoot\s*:\s*["']([^"']+)["']/);
return {
enabled: enabledMatch ? enabledMatch[1] !== "false" : true,
path: pathMatch?.[1]?.replace(/^\/+|\/+$/g, "") || "api-reference",
+ renderer: (rendererMatch?.[1] as "fumadocs" | "scalar" | undefined) ?? "fumadocs",
routeRoot: routeRootMatch?.[1]?.replace(/^\/+|\/+$/g, "") || "api",
};
} catch {
- return { enabled: false, path: "api-reference", routeRoot: "api" };
+ return { enabled: false, path: "api-reference", renderer: "fumadocs", routeRoot: "api" };
}
}
- return { enabled: false, path: "api-reference", routeRoot: "api" };
+ return { enabled: false, path: "api-reference", renderer: "fumadocs", routeRoot: "api" };
}
function extractObjectLiteral(content: string, key: string): string | undefined {
@@ -224,6 +315,12 @@ function extractObjectLiteral(content: string, key: string): string | undefined
return undefined;
}
+function removeManagedFile(filePath: string) {
+ if (isManagedGeneratedFile(filePath)) {
+ rmSync(filePath, { force: true });
+ }
+}
+
// ─── withDocs ───────────────────────────────────────────────────────
export function withDocs(nextConfig: Record = {}) {
@@ -263,13 +360,41 @@ export function withDocs(nextConfig: Record = {}) {
writeFileSync(join(docsApiRouteDir, "route.ts"), DOCS_API_ROUTE_TEMPLATE);
}
- // ── 3.1. Auto-generate app/{apiReference.path}/[[...slug]]/route.ts ──
+ // ── 3.1. Auto-generate API reference route/page ───────────────────
const apiReference = readApiReferenceConfig(root);
if (apiReference.enabled && !isStaticExport) {
- const apiReferenceRouteDir = join(root, appDir, ...apiReference.path.split("/"), "[[...slug]]");
- if (!hasFile(apiReferenceRouteDir, "route")) {
- mkdirSync(apiReferenceRouteDir, { recursive: true });
- writeFileSync(join(apiReferenceRouteDir, "route.ts"), API_REFERENCE_ROUTE_TEMPLATE);
+ const apiReferenceBaseDir = join(root, appDir, ...apiReference.path.split("/"));
+ const apiReferencePageDir = join(apiReferenceBaseDir, "[[...slug]]");
+ const apiReferencePagePath = join(apiReferencePageDir, "page.tsx");
+ const apiReferenceLayoutPath = join(apiReferenceBaseDir, "layout.tsx");
+ const apiReferenceRouteDir = join(apiReferenceBaseDir, "[[...slug]]");
+ const apiReferenceRoutePath = join(apiReferenceRouteDir, "route.ts");
+ const legacyApiReferencePagePath = join(apiReferenceBaseDir, "page.tsx");
+
+ if (apiReference.renderer === "fumadocs") {
+ removeManagedFile(apiReferenceRoutePath);
+ removeManagedFile(legacyApiReferencePagePath);
+ if (
+ !hasFile(apiReferenceBaseDir, "layout") ||
+ isManagedGeneratedFile(apiReferenceLayoutPath)
+ ) {
+ mkdirSync(apiReferenceBaseDir, { recursive: true });
+ writeFileSync(apiReferenceLayoutPath, API_REFERENCE_LAYOUT_TEMPLATE);
+ }
+ if (!hasFile(apiReferencePageDir, "page") || isManagedGeneratedFile(apiReferencePagePath)) {
+ mkdirSync(apiReferencePageDir, { recursive: true });
+ writeFileSync(apiReferencePagePath, API_REFERENCE_PAGE_TEMPLATE);
+ }
+ } else {
+ removeManagedFile(apiReferenceLayoutPath);
+ removeManagedFile(apiReferencePagePath);
+ if (
+ !hasFile(apiReferenceRouteDir, "route") ||
+ isManagedGeneratedFile(apiReferenceRoutePath)
+ ) {
+ mkdirSync(apiReferenceRouteDir, { recursive: true });
+ writeFileSync(apiReferenceRoutePath, API_REFERENCE_ROUTE_TEMPLATE);
+ }
}
}
@@ -322,6 +447,22 @@ export function withDocs(nextConfig: Record = {}) {
resolveAlias: {
...existingResolveAlias,
[INTERNAL_DOCS_CONFIG_ALIAS]: docsConfigRelativeAlias,
+ "fumadocs-openapi": toTurbopackAliasPath(root, FUMADOCS_OPENAPI_PACKAGE_ALIAS),
+ "fumadocs-openapi/ui": toTurbopackAliasPath(root, FUMADOCS_OPENAPI_UI_ALIAS),
+ "fumadocs-openapi/server": toTurbopackAliasPath(root, FUMADOCS_OPENAPI_SERVER_ALIAS),
+ "fumadocs-core": toTurbopackAliasPath(root, FUMADOCS_CORE_PACKAGE_ALIAS),
+ "fumadocs-core/framework": toTurbopackAliasPath(root, FUMADOCS_CORE_FRAMEWORK_ALIAS),
+ "fumadocs-core/framework/next": toTurbopackAliasPath(
+ root,
+ FUMADOCS_CORE_FRAMEWORK_NEXT_ALIAS,
+ ),
+ "fumadocs-ui": toTurbopackAliasPath(root, FUMADOCS_UI_PACKAGE_ALIAS),
+ "fumadocs-ui/layouts/notebook": toTurbopackAliasPath(root, FUMADOCS_UI_NOTEBOOK_ALIAS),
+ "fumadocs-ui/layouts/notebook/page": toTurbopackAliasPath(
+ root,
+ FUMADOCS_UI_NOTEBOOK_PAGE_ALIAS,
+ ),
+ "fumadocs-ui/provider/next": toTurbopackAliasPath(root, FUMADOCS_UI_PROVIDER_NEXT_ALIAS),
},
};
@@ -334,6 +475,18 @@ export function withDocs(nextConfig: Record = {}) {
resolvedConfig.resolve ??= {};
resolvedConfig.resolve.alias ??= {};
resolvedConfig.resolve.alias[INTERNAL_DOCS_CONFIG_ALIAS] = docsConfigAbsolutePath;
+ resolvedConfig.resolve.alias["fumadocs-openapi"] = FUMADOCS_OPENAPI_PACKAGE_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-openapi/ui"] = FUMADOCS_OPENAPI_UI_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-openapi/server"] = FUMADOCS_OPENAPI_SERVER_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-core"] = FUMADOCS_CORE_PACKAGE_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-core/framework"] = FUMADOCS_CORE_FRAMEWORK_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-core/framework/next"] =
+ FUMADOCS_CORE_FRAMEWORK_NEXT_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-ui"] = FUMADOCS_UI_PACKAGE_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-ui/layouts/notebook"] = FUMADOCS_UI_NOTEBOOK_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-ui/layouts/notebook/page"] =
+ FUMADOCS_UI_NOTEBOOK_PAGE_ALIAS;
+ resolvedConfig.resolve.alias["fumadocs-ui/provider/next"] = FUMADOCS_UI_PROVIDER_NEXT_ALIAS;
return resolvedConfig;
};
diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts
index a6db7780..a52ed3dc 100644
--- a/packages/next/src/index.ts
+++ b/packages/next/src/index.ts
@@ -2,6 +2,10 @@ export { withDocs } from "./config.js";
export {
buildNextOpenApiDocument,
createNextApiReference,
+ createNextApiReferenceLayout,
+ createNextApiReferencePage,
+ getNextApiReferenceSourceState,
+ getNextApiReferenceMode,
resolveApiReferenceConfig,
withNextApiReferenceBanner,
} from "./api-reference.js";
diff --git a/packages/next/src/react-dom-server.d.ts b/packages/next/src/react-dom-server.d.ts
new file mode 100644
index 00000000..bc5a315b
--- /dev/null
+++ b/packages/next/src/react-dom-server.d.ts
@@ -0,0 +1,3 @@
+declare module "react-dom/server" {
+ export function renderToStaticMarkup(element: React.ReactNode): string;
+}
diff --git a/packages/next/styles/api-reference.css b/packages/next/styles/api-reference.css
new file mode 100644
index 00000000..275f1a1a
--- /dev/null
+++ b/packages/next/styles/api-reference.css
@@ -0,0 +1,146 @@
+@import "tailwindcss";
+@import "fumadocs-ui/css/solar.css";
+@import "fumadocs-ui/css/preset.css";
+@import "fumadocs-openapi/css/preset.css";
+
+.fd-api-reference-route {
+ --fd-layout-width: 97rem;
+ --color-fd-background: hsl(0 0% 7.04%);
+ --color-fd-foreground: hsl(0 0% 92%);
+ --color-fd-muted: hsl(0 0% 12.9%);
+ --color-fd-muted-foreground: hsl(0 0% 70% / 0.8);
+ --color-fd-popover: hsl(0 0% 11.6%);
+ --color-fd-popover-foreground: hsl(0 0% 86.9%);
+ --color-fd-card: hsl(0 0% 9.8%);
+ --color-fd-card-foreground: hsl(0 0% 98%);
+ --color-fd-border: hsl(0 0% 40% / 0.2);
+ --color-fd-secondary: hsl(0 0% 12.9%);
+ --color-fd-secondary-foreground: hsl(0 0% 92%);
+ --color-fd-accent: hsl(0 0% 40.9% / 0.3);
+ --color-fd-accent-foreground: hsl(0 0% 90%);
+ --color-fd-primary: oklch(0.902 0.0461 259.51);
+ --color-fd-primary-foreground: hsl(0 0% 9%);
+ --color-fd-ring: hsl(0 0% 54.9%);
+ --color-fd-article: hsl(0 0% 7.04%);
+ letter-spacing: -0.25px;
+ color-scheme: dark;
+}
+
+.fd-api-reference-route #nd-subnav {
+ display: none;
+}
+
+.fd-api-reference-route #nd-notebook-layout {
+ --fd-header-height: 0px !important;
+ background: var(--color-fd-background);
+}
+
+@media (min-width: 768px) {
+ .fd-api-reference-route #nd-notebook-layout {
+ grid-template:
+ "sidebar header toc"
+ "sidebar main toc" 1fr /
+ minmax(var(--fd-sidebar-col), 1fr)
+ minmax(
+ 0,
+ calc(var(--fd-layout-width, 97rem) - var(--fd-sidebar-width) - var(--fd-toc-width))
+ )
+ minmax(min-content, 1fr) !important;
+ }
+}
+
+.fd-api-reference-route #nd-notebook-layout,
+.fd-api-reference-route #nd-page,
+.fd-api-reference-route [grid-area="main"] {
+ background: var(--color-fd-background);
+}
+
+.fd-api-reference-route #nd-sidebar[data-collapsed="false"] {
+ background: var(--color-fd-background);
+ border-inline-end: 1px solid color-mix(in srgb, var(--color-fd-border) 95%, transparent);
+ box-shadow: inset -1px 0 0 color-mix(in srgb, var(--color-fd-border) 45%, transparent);
+}
+
+.fd-api-reference-route #nd-page.fd-api-reference-page > * {
+ width: 100%;
+ max-width: 1180px;
+}
+
+.fd-api-reference-route #nd-page.fd-api-reference-page > .prose {
+ max-width: 1180px;
+}
+
+.fd-api-reference-pagination {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+ margin-top: 1rem;
+}
+
+.fd-api-reference-pagination-item {
+ display: flex;
+ min-height: 8.5rem;
+ flex-direction: column;
+ gap: 0.45rem;
+ border: 1px solid var(--color-fd-border);
+ border-radius: 1rem;
+ background: var(--color-fd-card);
+ padding: 1.1rem 1.25rem;
+ text-decoration: none;
+ color: inherit;
+ transition:
+ border-color 150ms ease,
+ background-color 150ms ease,
+ transform 150ms ease;
+}
+
+.fd-api-reference-pagination-item:hover {
+ border-color: color-mix(in srgb, var(--color-fd-primary) 30%, var(--color-fd-border));
+ background: color-mix(in srgb, var(--color-fd-accent) 45%, var(--color-fd-card));
+ transform: translateY(-1px);
+}
+
+.fd-api-reference-pagination-item[data-direction="next"] {
+ text-align: right;
+}
+
+.fd-api-reference-pagination-label {
+ font-size: 0.72rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--color-fd-muted-foreground);
+}
+
+.fd-api-reference-pagination-title {
+ font-size: 1.05rem;
+ font-weight: 600;
+ color: var(--color-fd-foreground);
+}
+
+.fd-api-reference-pagination-description {
+ color: var(--color-fd-muted-foreground);
+ font-size: 0.92rem;
+ line-height: 1.5;
+}
+
+.fd-api-reference-pagination-spacer {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .fd-api-reference-route #nd-notebook-layout #nd-page {
+ padding-inline: 1.5rem;
+ padding-top: 2rem;
+ }
+
+ .fd-api-reference-route #nd-notebook-layout #nd-page::before {
+ display: none;
+ }
+}
+
+@media (max-width: 1023px) {
+ .fd-api-reference-pagination {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json
index 8f5deb50..9b278ad2 100644
--- a/packages/next/tsconfig.json
+++ b/packages/next/tsconfig.json
@@ -12,7 +12,9 @@
"skipLibCheck": true,
"types": ["node", "react"],
"paths": {
- "@farming-labs/docs": ["../docs/src/index.ts"]
+ "@farming-labs/docs": ["../docs/src/index.ts"],
+ "@farming-labs/docs/server": ["../docs/src/server.ts"],
+ "@farming-labs/theme/client-hooks": ["../fumadocs/src/docs-client-hooks.tsx"]
},
"noEmit": true
},
diff --git a/packages/next/tsdown.config.ts b/packages/next/tsdown.config.ts
index 0e16502e..9b5970db 100644
--- a/packages/next/tsdown.config.ts
+++ b/packages/next/tsdown.config.ts
@@ -26,6 +26,10 @@ export default defineConfig({
"@mdx-js/loader",
"@mdx-js/react",
"fumadocs-core",
+ "fumadocs-openapi",
+ "fumadocs-openapi/*",
+ "fumadocs-ui",
+ "fumadocs-ui/*",
"remark-gfm",
"remark-frontmatter",
"remark-mdx-frontmatter",
diff --git a/packages/next/vitest.config.ts b/packages/next/vitest.config.ts
index c497b8ec..a1b8e37c 100644
--- a/packages/next/vitest.config.ts
+++ b/packages/next/vitest.config.ts
@@ -1,6 +1,16 @@
+import { dirname, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
+const rootDir = dirname(fileURLToPath(import.meta.url));
+
export default defineConfig({
+ resolve: {
+ alias: {
+ "@farming-labs/docs/server": resolve(rootDir, "../docs/src/server.ts"),
+ "@farming-labs/docs": resolve(rootDir, "../docs/src/index.ts"),
+ },
+ },
test: {
include: ["src/**/*.test.ts"],
globals: true,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f370025d..029726ca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -314,10 +314,10 @@ importers:
dependencies:
fumadocs-core:
specifier: ^16.6.1
- version: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ version: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
fumadocs-ui:
specifier: ^16.6.1
- version: 16.6.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1)
+ version: 16.6.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1)
gray-matter:
specifier: ^4.0.3
version: 4.0.3
@@ -372,7 +372,10 @@ importers:
version: 0.10.3(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
fumadocs-core:
specifier: ^16.6.1
- version: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ version: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ fumadocs-openapi:
+ specifier: 10.3.6
+ version: 10.3.6(yqk7lfb2y7xrvp3mgs2mflp2pe)
next:
specifier: '>=16.0.0'
version: 16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -504,7 +507,7 @@ importers:
version: 0.4.3
fumadocs-core:
specifier: ^16.6.1
- version: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ version: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
gray-matter:
specifier: ^4.0.3
version: 4.0.3
@@ -1221,6 +1224,28 @@ packages:
tailwindcss:
optional: true
+ '@fumari/json-schema-to-typescript@2.0.0':
+ resolution: {integrity: sha512-X0Wm3QJLj1Rtb1nY2exM6QwMXb9LGyIKLf35+n6xyltDDBLMECOC4R/zPaw3RwgFVmvRLSmLCd+ht4sKabgmNw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ '@apidevtools/json-schema-ref-parser': 14.x.x
+ prettier: 3.x.x
+ peerDependenciesMeta:
+ '@apidevtools/json-schema-ref-parser':
+ optional: true
+ prettier:
+ optional: true
+
+ '@fumari/stf@0.0.3':
+ resolution: {integrity: sha512-EDgfqz6oWJLPfmrekl4sCssypPmQ1dV8J7RqWR9Wbzj2mekUIeAqljGkh1EgykZp8Yve9Ehnmn3gjHggFgQU2A==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^19.2.0
+ react-dom: ^19.2.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
engines: {node: '>=18'}
@@ -2565,6 +2590,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-select@2.2.6':
+ resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-slot@1.2.3':
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
peerDependencies:
@@ -2984,10 +3022,26 @@ packages:
resolution: {integrity: sha512-Fp/J5RyajknwZrDG5/nsJRxL07cS2gJ9wvnvIWcFgsR8HWy/7qMQ+PtpYhdBmMYiBAql1JJobKTu/7ZWqnHaFA==}
engines: {node: '>=22'}
+ '@scalar/helpers@0.2.12':
+ resolution: {integrity: sha512-Ig/H1Je8nqcDiY+YwFIpATxF2ko7zKrjIZFWK2gGeNTYK4Np9XnqDHg56jM3Xru439Eh4qHq9P/lX7Se5nnxFA==}
+ engines: {node: '>=20'}
+
+ '@scalar/helpers@0.2.18':
+ resolution: {integrity: sha512-w1d4tpNEVZ293oB2BAgLrS0kVPUtG3eByNmOCJA5eK9vcT4D3cmsGtWjUaaqit0BQCsBFHK51rasGvSWnApYTw==}
+ engines: {node: '>=20'}
+
'@scalar/helpers@0.4.1':
resolution: {integrity: sha512-XfyYqSUA597wfzS8lmMY1xvYv4si2WytuoBWj7JDpx3E/lVq7YEsticXF/Q30ttFVRBfgQErg8MQI6b6IkZr2Q==}
engines: {node: '>=22'}
+ '@scalar/json-magic@0.11.1':
+ resolution: {integrity: sha512-JsugkVpZ9SmKW6fDhamcmkttc9YOPGgb9Azbwc7hXTlZgG6YeYXx8qFvYr5eJE4cfzCqalodS/9w7moZnVG3cw==}
+ engines: {node: '>=20'}
+
+ '@scalar/json-magic@0.11.7':
+ resolution: {integrity: sha512-GVz9E0vXu+ecypkdn0biK1gbQVkK4QTTX1Hq3eMgxlLQC91wwiqWfCqwfhuX0LRu+Z5OmYhLhufDJEEh56rVgA==}
+ engines: {node: '>=20'}
+
'@scalar/nextjs-api-reference@0.10.3':
resolution: {integrity: sha512-Ewar7YWavvMU5W1YUCX2zXisQVURVtNG2b9AhEN6QGqWUtIJ/C+cOqOtyWGF8iVXbYGIsOyFASKN+GOhdoQMJQ==}
engines: {node: '>=22'}
@@ -2995,6 +3049,26 @@ packages:
next: ^15.0.0 || ^16.0.0
react: ^19.0.0
+ '@scalar/openapi-parser@0.24.10':
+ resolution: {integrity: sha512-E9K8OYD7XKHsvTyLTSdILKHbm4Q3n/MA3EGdDTEBLJHSJd1vLOwiJzrp3+h+xiqFxlX7vlecInZvFy/3c1fqPg==}
+ engines: {node: '>=20'}
+
+ '@scalar/openapi-types@0.5.3':
+ resolution: {integrity: sha512-m4n/Su3K01d15dmdWO1LlqecdSPKuNjuokrJLdiQ485kW/hRHbXW1QP6tJL75myhw/XhX5YhYAR+jrwnGjXiMw==}
+ engines: {node: '>=20'}
+
+ '@scalar/openapi-types@0.5.4':
+ resolution: {integrity: sha512-2pEbhprh8lLGDfUI6mNm9EV104pjb3+aJsXrFaqfgOSre7r6NlgM5HcSbsLjzDAnTikjJhJ3IMal1Rz8WVwiOw==}
+ engines: {node: '>=20'}
+
+ '@scalar/openapi-upgrader@0.1.11':
+ resolution: {integrity: sha512-ngJcHGoCHmpWgYtNy08vmzFfLdQEkMpvaCQqNPPMNKq0QEXOv89e/rn+TZJZgPnRlY7fDIoIhn9lNgr+azBW+w==}
+ engines: {node: '>=20'}
+
+ '@scalar/openapi-upgrader@0.1.8':
+ resolution: {integrity: sha512-2xuYLLs0fBadLIk4I1ObjMiCnOyLPEMPf24A1HtHQvhKGDnGlvT63F2rU2Xw8lxCjgHnzveMPnOJEbwIy64RCg==}
+ engines: {node: '>=20'}
+
'@scalar/types@0.7.3':
resolution: {integrity: sha512-9ThydMH28aA5DMm8Q/nTKXKnAKejG7Awlj7IC99oXkq5aOJ60Sn0kS1RWaqQosaWI3ezyPJ7/sUglssAEMA6EA==}
engines: {node: '>=22'}
@@ -3405,6 +3479,9 @@ packages:
'@types/jsesc@2.5.1':
resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==}
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
@@ -3656,9 +3733,28 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
+ ajv-draft-04@1.0.0:
+ resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
+ peerDependencies:
+ ajv: ^8.5.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ajv@8.18.0:
+ resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
+
alien-signals@3.1.2:
resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
@@ -4548,6 +4644,16 @@ packages:
fast-npm-meta@1.2.1:
resolution: {integrity: sha512-vTHOCEbzcbQEfYL0sPzcz+HF5asxoy60tPBVaiYzsCfuyhbXZCSqXL+LgPGV22nuAYimoGMeDpywMQB4aOw8HQ==}
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+
+ fast-xml-builder@1.1.4:
+ resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
+
+ fast-xml-parser@5.5.9:
+ resolution: {integrity: sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==}
+ hasBin: true
+
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -4584,6 +4690,9 @@ packages:
resolution: {integrity: sha512-piJxbLnkD9Xcyi7dWJRnqszEURixe7CrF/efBfbffe2DPyabmuIuqraruY8cXTs19QoM8VJzx47BDRVNXETM7Q==}
engines: {node: '>=20'}
+ foreach@2.0.6:
+ resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==}
+
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@@ -4693,6 +4802,24 @@ packages:
zod:
optional: true
+ fumadocs-openapi@10.3.6:
+ resolution: {integrity: sha512-mihOR2WpTtq8Hy37EjmkRWNApiJNWzB8AJN6cFP0q5nZ+DDKoCrMr3mVcVZJ1NCmCyaC1I4iZAxg8MSR89CcOQ==}
+ peerDependencies:
+ '@scalar/api-client-react': '*'
+ '@types/react': '*'
+ fumadocs-core: ^16.5.0
+ fumadocs-ui: ^16.5.0
+ json-schema-typed: '*'
+ react: ^19.2.0
+ react-dom: ^19.2.0
+ peerDependenciesMeta:
+ '@scalar/api-client-react':
+ optional: true
+ '@types/react':
+ optional: true
+ json-schema-typed:
+ optional: true
+
fumadocs-ui@16.6.1:
resolution: {integrity: sha512-rwms1m/0c5uykFDWA3e29ZyBHG+JXSeCF6QBKZEz5MqOxPfOrFNk0q8yyqaf9lkGldP/PRegR3a8/6+PB5tn0w==}
peerDependencies:
@@ -5089,9 +5216,15 @@ packages:
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+ json-pointer@0.6.2:
+ resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==}
+
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -5100,6 +5233,10 @@ packages:
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
+ jsonpointer@5.0.1:
+ resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
+ engines: {node: '>=0.10.0'}
+
kind-of@6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
@@ -5126,6 +5263,10 @@ packages:
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
engines: {node: '>= 0.6.3'}
+ leven@4.1.0:
+ resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
lightningcss-android-arm64@1.30.2:
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
engines: {node: '>= 12.0.0'}
@@ -5329,6 +5470,11 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ lucide-react@0.570.0:
+ resolution: {integrity: sha512-qGnQ8bEPJLMseKo7kI6jK6GW6Y2Yl4PpqoWbroNsobZ8+tZR4SUuO4EXK3oWCdZr48SZ7PnaulTkvzkKvG/Iqg==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
magic-regexp@0.10.0:
resolution: {integrity: sha512-Uly1Bu4lO1hwHUW0CQeSWuRtzCMNO00CmXtS8N6fyvB3B979GOEEeAkiTUDsmbYLAbvpUS/Kt5c4ibosAzVyVg==}
@@ -5789,6 +5935,9 @@ packages:
resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
engines: {node: '>=18'}
+ openapi-sampler@1.7.2:
+ resolution: {integrity: sha512-OKytvqB5XIaTgA9xtw8W8UTar+uymW2xPVpFN0NihMtuHPdPTGxBEhGnfFnJW5g/gOSIvkP+H0Xh3XhVI9/n7g==}
+
oxc-minify@0.112.0:
resolution: {integrity: sha512-rkVSeeIRSt+RYI9uX6xonBpLUpvZyegxIg0UL87ev7YAfUqp7IIZlRjkgQN5Us1lyXD//TOo0Dcuuro/TYOWoQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -5877,6 +6026,10 @@ packages:
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+ path-expression-matcher@1.2.0:
+ resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==}
+ engines: {node: '>=14.0.0'}
+
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -6228,6 +6381,12 @@ packages:
peerDependencies:
react: ^19.2.4
+ react-hook-form@7.72.0:
+ resolution: {integrity: sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
react-medium-image-zoom@5.4.0:
resolution: {integrity: sha512-BsE+EnFVQzFIlyuuQrZ9iTwyKpKkqdFZV1ImEQN573QPqGrIUuNni7aF+sZwDcxlsuOMayCr6oO/PZR/yJnbRg==}
peerDependencies:
@@ -6380,6 +6539,10 @@ packages:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -6692,6 +6855,9 @@ packages:
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
+ strnum@2.2.2:
+ resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==}
+
structured-clone-es@1.0.0:
resolution: {integrity: sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==}
@@ -6755,6 +6921,9 @@ packages:
tailwind-merge@3.4.0:
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
+ tailwind-merge@3.5.0:
+ resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
+
tailwindcss@4.1.18:
resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
@@ -7478,6 +7647,10 @@ packages:
resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
engines: {node: '>=18'}
+ xml-js@1.6.11:
+ resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
+ hasBin: true
+
xmlbuilder2@4.0.3:
resolution: {integrity: sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==}
engines: {node: '>=20.0'}
@@ -8097,6 +8270,19 @@ snapshots:
optionalDependencies:
tailwindcss: 4.2.1
+ '@fumari/json-schema-to-typescript@2.0.0(prettier@3.8.1)':
+ dependencies:
+ js-yaml: 4.1.1
+ optionalDependencies:
+ prettier: 3.8.1
+
+ '@fumari/stf@0.0.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+
'@img/colour@1.0.0':
optional: true
@@ -9375,6 +9561,35 @@ snapshots:
'@types/react': 19.2.14
'@types/react-dom': 19.2.3(@types/react@19.2.14)
+ '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ aria-hidden: 1.2.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
+
'@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
@@ -9679,14 +9894,58 @@ snapshots:
dependencies:
'@scalar/types': 0.7.3
+ '@scalar/helpers@0.2.12': {}
+
+ '@scalar/helpers@0.2.18': {}
+
'@scalar/helpers@0.4.1': {}
+ '@scalar/json-magic@0.11.1':
+ dependencies:
+ '@scalar/helpers': 0.2.12
+ yaml: 2.8.2
+
+ '@scalar/json-magic@0.11.7':
+ dependencies:
+ '@scalar/helpers': 0.2.18
+ pathe: 2.0.3
+ yaml: 2.8.2
+
'@scalar/nextjs-api-reference@0.10.3(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
dependencies:
'@scalar/core': 0.4.3
next: 16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
+ '@scalar/openapi-parser@0.24.10':
+ dependencies:
+ '@scalar/helpers': 0.2.12
+ '@scalar/json-magic': 0.11.1
+ '@scalar/openapi-types': 0.5.3
+ '@scalar/openapi-upgrader': 0.1.8
+ ajv: 8.18.0
+ ajv-draft-04: 1.0.0(ajv@8.18.0)
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ jsonpointer: 5.0.1
+ leven: 4.1.0
+ yaml: 2.8.2
+
+ '@scalar/openapi-types@0.5.3':
+ dependencies:
+ zod: 4.3.6
+
+ '@scalar/openapi-types@0.5.4':
+ dependencies:
+ zod: 4.3.6
+
+ '@scalar/openapi-upgrader@0.1.11':
+ dependencies:
+ '@scalar/openapi-types': 0.5.4
+
+ '@scalar/openapi-upgrader@0.1.8':
+ dependencies:
+ '@scalar/openapi-types': 0.5.3
+
'@scalar/types@0.7.3':
dependencies:
'@scalar/helpers': 0.4.1
@@ -10177,6 +10436,8 @@ snapshots:
'@types/jsesc@2.5.1': {}
+ '@types/json-schema@7.0.15': {}
+
'@types/mdast@4.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -10512,6 +10773,14 @@ snapshots:
agent-base@7.1.4: {}
+ ajv-draft-04@1.0.0(ajv@8.18.0):
+ optionalDependencies:
+ ajv: 8.18.0
+
+ ajv-formats@3.0.1(ajv@8.18.0):
+ optionalDependencies:
+ ajv: 8.18.0
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -10520,6 +10789,13 @@ snapshots:
uri-js: 4.4.1
optional: true
+ ajv@8.18.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
alien-signals@3.1.2: {}
ansi-align@3.0.1:
@@ -11530,8 +11806,7 @@ snapshots:
dependencies:
pure-rand: 6.1.0
- fast-deep-equal@3.1.3:
- optional: true
+ fast-deep-equal@3.1.3: {}
fast-fifo@1.3.2: {}
@@ -11548,6 +11823,18 @@ snapshots:
fast-npm-meta@1.2.1: {}
+ fast-uri@3.1.0: {}
+
+ fast-xml-builder@1.1.4:
+ dependencies:
+ path-expression-matcher: 1.2.0
+
+ fast-xml-parser@5.5.9:
+ dependencies:
+ fast-xml-builder: 1.1.4
+ path-expression-matcher: 1.2.0
+ strnum: 2.2.2
+
fastq@1.20.1:
dependencies:
reusify: 1.1.0
@@ -11580,6 +11867,8 @@ snapshots:
dependencies:
tiny-inflate: 1.0.3
+ foreach@2.0.6: {}
+
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@@ -11615,7 +11904,7 @@ snapshots:
fsevents@2.3.3:
optional: true
- fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6):
+ fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6):
dependencies:
'@formatjs/intl-localematcher': 0.8.1
'@orama/orama': 3.1.18
@@ -11647,7 +11936,7 @@ snapshots:
'@types/hast': 3.0.4
'@types/mdast': 4.0.4
'@types/react': 19.2.14
- lucide-react: 0.564.0(react@19.2.4)
+ lucide-react: 0.570.0(react@19.2.4)
next: 16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
@@ -11655,7 +11944,43 @@ snapshots:
transitivePeerDependencies:
- supports-color
- fumadocs-ui@16.6.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1):
+ fumadocs-openapi@10.3.6(yqk7lfb2y7xrvp3mgs2mflp2pe):
+ dependencies:
+ '@fumari/json-schema-to-typescript': 2.0.0(prettier@3.8.1)
+ '@fumari/stf': 0.0.3(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4)
+ '@scalar/json-magic': 0.11.7
+ '@scalar/openapi-parser': 0.24.10
+ '@scalar/openapi-upgrader': 0.1.11
+ ajv: 8.18.0
+ class-variance-authority: 0.7.1
+ fumadocs-core: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ fumadocs-ui: 16.6.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1)
+ github-slugger: 2.0.0
+ hast-util-to-jsx-runtime: 2.3.6
+ js-yaml: 4.1.1
+ lucide-react: 0.570.0(react@19.2.4)
+ next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ openapi-sampler: 1.7.2
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-hook-form: 7.72.0(react@19.2.4)
+ remark: 15.0.1
+ remark-rehype: 11.1.2
+ tailwind-merge: 3.5.0
+ xml-js: 1.6.11
+ optionalDependencies:
+ '@types/react': 19.2.14
+ transitivePeerDependencies:
+ - '@apidevtools/json-schema-ref-parser'
+ - '@types/react-dom'
+ - prettier
+ - supports-color
+
+ fumadocs-ui@16.6.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1):
dependencies:
'@fumadocs/tailwind': 0.0.2(tailwindcss@4.2.1)
'@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -11669,7 +11994,7 @@ snapshots:
'@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4)
'@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
class-variance-authority: 0.7.1
- fumadocs-core: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.564.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
+ fumadocs-core: 16.6.1(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.167.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
lucide-react: 0.563.0(react@19.2.4)
motion: 12.34.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -12143,13 +12468,21 @@ snapshots:
json-parse-even-better-errors@2.3.1: {}
+ json-pointer@0.6.2:
+ dependencies:
+ foreach: 2.0.6
+
json-schema-traverse@0.4.1:
optional: true
+ json-schema-traverse@1.0.0: {}
+
json5@2.2.3: {}
jsonc-parser@3.3.1: {}
+ jsonpointer@5.0.1: {}
+
kind-of@6.0.3: {}
kleur@3.0.3: {}
@@ -12169,6 +12502,8 @@ snapshots:
dependencies:
readable-stream: 2.3.8
+ leven@4.1.0: {}
+
lightningcss-android-arm64@1.30.2:
optional: true
@@ -12332,6 +12667,10 @@ snapshots:
dependencies:
react: 19.2.4
+ lucide-react@0.570.0(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
magic-regexp@0.10.0:
dependencies:
estree-walker: 3.0.3
@@ -13253,6 +13592,12 @@ snapshots:
is-inside-container: 1.0.0
wsl-utils: 0.1.0
+ openapi-sampler@1.7.2:
+ dependencies:
+ '@types/json-schema': 7.0.15
+ fast-xml-parser: 5.5.9
+ json-pointer: 0.6.2
+
oxc-minify@0.112.0:
optionalDependencies:
'@oxc-minify/binding-android-arm-eabi': 0.112.0
@@ -13455,6 +13800,8 @@ snapshots:
path-browserify@1.0.1: {}
+ path-expression-matcher@1.2.0: {}
+
path-key@3.1.1: {}
path-key@4.0.0: {}
@@ -13799,6 +14146,10 @@ snapshots:
react: 19.2.4
scheduler: 0.27.0
+ react-hook-form@7.72.0(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
react-medium-image-zoom@5.4.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
react: 19.2.4
@@ -14030,6 +14381,8 @@ snapshots:
require-directory@2.1.1: {}
+ require-from-string@2.0.2: {}
+
resolve-from@4.0.0: {}
resolve-from@5.0.0: {}
@@ -14405,6 +14758,8 @@ snapshots:
dependencies:
js-tokens: 9.0.1
+ strnum@2.2.2: {}
+
structured-clone-es@1.0.0: {}
style-to-js@1.1.21:
@@ -14471,6 +14826,8 @@ snapshots:
tailwind-merge@3.4.0: {}
+ tailwind-merge@3.5.0: {}
+
tailwindcss@4.1.18: {}
tailwindcss@4.2.1: {}
@@ -15190,6 +15547,10 @@ snapshots:
dependencies:
is-wsl: 3.1.1
+ xml-js@1.6.11:
+ dependencies:
+ sax: 1.5.0
+
xmlbuilder2@4.0.3:
dependencies:
'@oozcitak/dom': 2.0.2
diff --git a/website/app/docs/configuration/page.mdx b/website/app/docs/configuration/page.mdx
index a45fd722..0ae1c0cf 100644
--- a/website/app/docs/configuration/page.mdx
+++ b/website/app/docs/configuration/page.mdx
@@ -189,6 +189,7 @@ export default defineDocs({
apiReference: {
enabled: true,
path: "api-reference",
+ renderer: "fumadocs",
routeRoot: "api",
exclude: ["/api/internal/health", "internal/debug"],
},
@@ -205,6 +206,7 @@ export default defineDocs({
apiReference: {
enabled: true,
path: "api-reference",
+ renderer: "fumadocs",
specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
},
theme: fumadocs(),
@@ -215,13 +217,20 @@ export default defineDocs({
| ----------- | ---------- | ----------------- | ----------- |
| `enabled` | `boolean` | `true` inside the object | Enables generated API reference pages |
| `path` | `string` | `"api-reference"` | URL path where the generated reference lives |
-| `specUrl` | `string` | — | Absolute URL to a hosted OpenAPI JSON document. When set, local route scanning is skipped |
+| `renderer` | `"fumadocs" \| "scalar"` | framework-specific | UI renderer. Defaults to `fumadocs` in Next.js and `scalar` in TanStack Start, SvelteKit, Astro, and Nuxt |
+| `specUrl` | `string` | — | URL to a hosted OpenAPI JSON document. Supports absolute URLs and request-relative paths in Next.js. When set, local route scanning is skipped |
| `routeRoot` | `string` | `"api"` | Filesystem route root to scan. Bare values like `"api"` resolve inside `app/` or `src/app/`; full values like `"app/internal-api"` are supported too |
| `exclude` | `string[]` | `[]` | Routes to omit from the generated reference. Accepts URL-style paths like `"/api/hello"` or route-root-relative entries like `"hello"` / `"hello/route.ts"` |
When `specUrl` is set, `routeRoot` and `exclude` are ignored because the reference is rendered
from the remote spec.
+Renderer notes:
+
+- **Next.js** defaults to the Fumadocs OpenAPI UI and auto-generates the page route with `withDocs()`
+- **TanStack Start**, **SvelteKit**, **Astro**, and **Nuxt** default to Scalar because they currently serve the API reference through HTML handlers
+- Set `renderer: "scalar"` in Next.js if you want to keep the old Scalar UI
+
That does not change the framework routing requirements:
- **Next.js** still generates the API reference route automatically with `withDocs()`