fumadocs-openapi 10.6.5 → 10.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/css/generated/shared.css +59 -6
  2. package/dist/generate-file.js +3 -2
  3. package/dist/i18n.d.ts +3 -1
  4. package/dist/i18n.js +3 -1
  5. package/dist/playground/auth.js +92 -0
  6. package/dist/playground/client.d.ts +5 -12
  7. package/dist/playground/client.js +257 -217
  8. package/dist/playground/components/inputs.js +2 -2
  9. package/dist/playground/components/oauth-dialog.js +126 -163
  10. package/dist/playground/components/result-display.d.ts +16 -0
  11. package/dist/playground/components/result-display.js +141 -0
  12. package/dist/playground/components/server-select.js +1 -1
  13. package/dist/playground/components/spinner.js +14 -0
  14. package/dist/playground/fetcher.d.ts +9 -3
  15. package/dist/playground/fetcher.js +7 -18
  16. package/dist/playground/status-info.js +27 -23
  17. package/dist/requests/generators/csharp.js +2 -1
  18. package/dist/requests/generators/curl.js +5 -5
  19. package/dist/requests/generators/go.js +5 -5
  20. package/dist/requests/generators/java.js +4 -4
  21. package/dist/requests/generators/javascript.js +3 -3
  22. package/dist/requests/generators/python.js +5 -4
  23. package/dist/requests/media/adapter.js +7 -7
  24. package/dist/requests/string-utils.js +25 -4
  25. package/dist/requests/types.d.ts +3 -2
  26. package/dist/ui/api-page.js +3 -1
  27. package/dist/ui/base.d.ts +8 -4
  28. package/dist/ui/base.js +6 -1
  29. package/dist/ui/client/boundary.lazy.js +2 -1
  30. package/dist/ui/client/i18n.js +6 -4
  31. package/dist/ui/client/storage-key.js +1 -1
  32. package/dist/ui/components/dialog.js +1 -1
  33. package/dist/ui/contexts/api.d.ts +6 -3
  34. package/dist/ui/contexts/api.js +7 -2
  35. package/dist/ui/create-client.js +5 -0
  36. package/dist/ui/operation/client.js +1 -1
  37. package/dist/ui/operation/index.js +1 -1
  38. package/dist/ui/operation/request-tabs.d.ts +10 -0
  39. package/dist/ui/operation/request-tabs.js +44 -38
  40. package/dist/ui/operation/usage-tabs/client.js +1 -1
  41. package/dist/ui/schema/client.js +2 -2
  42. package/dist/ui/schema/index.js +1 -1
  43. package/dist/utils/pages/to-text.js +5 -4
  44. package/dist/utils/schema/index.d.ts +3 -4
  45. package/dist/utils/schema/index.js +4 -9
  46. package/dist/utils/use-query.js +2 -1
  47. package/package.json +11 -10
@@ -1,24 +1,24 @@
1
- import { escapeString, indent, inputToString } from "../string-utils.js";
1
+ import { doubleQuote, indent, inputToString, singleQuote } from "../string-utils.js";
2
2
  //#region src/requests/generators/curl.ts
3
3
  const curl = {
4
4
  label: "cURL",
5
5
  lang: "bash",
6
6
  generate(url, data) {
7
7
  const s = [];
8
- s.push(`curl -X ${data.method} "${url}"`);
8
+ s.push(`curl -X ${data.method.toUpperCase()} "${url}"`);
9
9
  for (const header in data.header) {
10
10
  const value = `${header}: ${data.header[header].value}`;
11
11
  s.push(`-H "${value}"`);
12
12
  }
13
13
  for (const k in data.cookie) {
14
14
  const param = data.cookie[k];
15
- s.push(`--cookie ${JSON.stringify(`${k}=${param.value}`)}`);
15
+ s.push(`--cookie ${doubleQuote(`${k}=${param.value}`)}`);
16
16
  }
17
17
  if (data.body && data.bodyMediaType === "multipart/form-data") {
18
18
  if (typeof data.body !== "object") throw new Error("[CURL] request body must be an object.");
19
- for (const [key, value] of Object.entries(data.body)) s.push(`-F ${key}=${JSON.stringify(inputToString(value))}`);
19
+ for (const [key, value] of Object.entries(data.body)) s.push(`-F ${key}=${doubleQuote(inputToString(value))}`);
20
20
  } else if (data.body && data.bodyMediaType) {
21
- const escaped = escapeString(inputToString(data.body, data.bodyMediaType), "'");
21
+ const escaped = singleQuote(inputToString(data.body, data.bodyMediaType));
22
22
  s.push(`-H "Content-Type: ${data.bodyMediaType}"`);
23
23
  s.push(`-d ${escaped}`);
24
24
  }
@@ -1,4 +1,4 @@
1
- import { indent } from "../string-utils.js";
1
+ import { doubleQuote, indent } from "../string-utils.js";
2
2
  import { resolveMediaAdapter } from "../media/resolve-adapter.js";
3
3
  import "../media/adapter.js";
4
4
  //#region src/requests/generators/go.ts
@@ -13,10 +13,10 @@ const go = {
13
13
  ];
14
14
  const headers = /* @__PURE__ */ new Map();
15
15
  const variables = /* @__PURE__ */ new Map();
16
- variables.set("url", JSON.stringify(url));
17
- for (const header in data.header) headers.set(header, JSON.stringify(data.header[header].value));
16
+ variables.set("url", doubleQuote(url));
17
+ for (const header in data.header) headers.set(header, doubleQuote(data.header[header].value));
18
18
  const cookies = Object.entries(data.cookie);
19
- if (cookies.length > 0) headers.set("Cookie", JSON.stringify(cookies.map(([k, param]) => `${k}=${param.value}`).join("; ")));
19
+ if (cookies.length > 0) headers.set("Cookie", doubleQuote(cookies.map(([k, param]) => `${k}=${param.value}`).join("; ")));
20
20
  let body;
21
21
  if (data.body && data.bodyMediaType) {
22
22
  const adapter = resolveMediaAdapter(data.bodyMediaType, mediaAdapters);
@@ -37,7 +37,7 @@ ${indent(imports.map((v) => `"${v}"`).join("\n"))}
37
37
  func main() {
38
38
  ${Array.from(variables.entries()).map(([k, v]) => indent(`${k} := ${v}`)).join("\n")}
39
39
  ${body ? indent(body) : ""}
40
- req, _ := http.NewRequest("${data.method}", url, ${body ? "body" : "nil"})
40
+ req, _ := http.NewRequest("${data.method.toUpperCase()}", url, ${body ? "body" : "nil"})
41
41
  ${indent(Array.from(headers.entries()).map(([key, value]) => `req.Header.Add("${key}", ${value})`).join("\n"))}
42
42
  res, _ := http.DefaultClient.Do(req)
43
43
  defer res.Body.Close()
@@ -1,4 +1,4 @@
1
- import { indent } from "../string-utils.js";
1
+ import { doubleQuote, indent } from "../string-utils.js";
2
2
  import { resolveMediaAdapter } from "../media/resolve-adapter.js";
3
3
  import "../media/adapter.js";
4
4
  //#region src/requests/generators/java.ts
@@ -31,13 +31,13 @@ const java = {
31
31
  s.push(indent(".build();"));
32
32
  s.push("");
33
33
  s.push("HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()");
34
- s.push(indent(`.uri(URI.create(${JSON.stringify(url)}))`));
35
- for (const [key, param] of Object.entries(headers)) s.push(indent(`.header(${JSON.stringify(key)}, ${JSON.stringify(param.value)})`));
34
+ s.push(indent(`.uri(URI.create(${doubleQuote(url)}))`));
35
+ for (const [key, param] of Object.entries(headers)) s.push(indent(`.header(${doubleQuote(key)}, ${doubleQuote(param.value)})`));
36
36
  if (data.bodyMediaType) s.push(indent(`.header("Content-Type", "${data.bodyMediaType}")`));
37
37
  const cookies = Object.entries(data.cookie);
38
38
  if (cookies.length > 0) {
39
39
  const cookieString = cookies.map(([key, param]) => `${key}=${param.value}`).join("; ");
40
- s.push(indent(`.header("Cookie", ${JSON.stringify(cookieString)})`));
40
+ s.push(indent(`.header("Cookie", ${doubleQuote(cookieString)})`));
41
41
  }
42
42
  const arg = body ? "body" : "";
43
43
  s.push(indent(`.${data.method.toUpperCase()}(${arg})`));
@@ -1,4 +1,4 @@
1
- import { indent } from "../string-utils.js";
1
+ import { doubleQuote, indent } from "../string-utils.js";
2
2
  import { resolveMediaAdapter } from "../media/resolve-adapter.js";
3
3
  import "../media/adapter.js";
4
4
  //#region src/requests/generators/javascript.ts
@@ -9,7 +9,7 @@ const javascript = {
9
9
  const s = [];
10
10
  const options = /* @__PURE__ */ new Map();
11
11
  const headers = {};
12
- options.set("method", JSON.stringify(data.method));
12
+ options.set("method", `"${data.method.toUpperCase()}"`);
13
13
  if (data.bodyMediaType) headers["Content-Type"] = data.bodyMediaType;
14
14
  for (const [k, v] of Object.entries(data.header)) headers[k] = v.value;
15
15
  const cookies = Object.entries(data.cookie);
@@ -26,7 +26,7 @@ const javascript = {
26
26
  s.push(body);
27
27
  options.set("body", "body");
28
28
  }
29
- const params = [JSON.stringify(url)];
29
+ const params = [doubleQuote(url)];
30
30
  if (options.size > 0) {
31
31
  const str = Array.from(options.entries()).map(([k, v]) => indent(k === v ? k : `${k}: ${v}`)).join(",\n");
32
32
  params.push(`{\n${str}\n}`);
@@ -1,3 +1,4 @@
1
+ import { doubleQuote } from "../string-utils.js";
1
2
  import { resolveMediaAdapter } from "../media/resolve-adapter.js";
2
3
  import "../media/adapter.js";
3
4
  //#region src/requests/generators/python.ts
@@ -7,7 +8,7 @@ const python = {
7
8
  generate(url, data, { mediaAdapters }) {
8
9
  const headers = {};
9
10
  const imports = /* @__PURE__ */ new Set();
10
- const params = [`"${data.method}"`, "url"];
11
+ const params = [`"${data.method.toUpperCase()}"`, "url"];
11
12
  let body;
12
13
  imports.add("requests");
13
14
  if (data.body && data.bodyMediaType) {
@@ -26,7 +27,7 @@ const python = {
26
27
  }
27
28
  return `${Array.from(imports).map((name) => "import " + name).join("\n")}
28
29
 
29
- url = ${JSON.stringify(url)}
30
+ url = ${doubleQuote(url)}
30
31
  ${body ?? ""}
31
32
  response = requests.request(${params.join(", ")})
32
33
 
@@ -36,13 +37,13 @@ print(response.text)`;
36
37
  function generatePythonObject(v, imports) {
37
38
  if (v === null) return "None";
38
39
  else if (typeof v === "boolean") return v ? "True" : "False";
39
- else if (typeof v === "string") return JSON.stringify(v);
40
+ else if (typeof v === "string") return doubleQuote(v);
40
41
  else if (typeof v === "number") return v.toString();
41
42
  else if (Array.isArray(v)) return `[${v.map((item) => generatePythonObject(item, imports)).join(", ")}]`;
42
43
  else if (v instanceof Date) {
43
44
  imports.add("datetime");
44
45
  return `datetime.datetime(${v.getFullYear()}, ${v.getMonth() + 1}, ${v.getDate()}, ${v.getHours()}, ${v.getMinutes()}, ${v.getSeconds()}, ${v.getMilliseconds()})`;
45
- } else if (typeof v === "object") return `{\n${Object.entries(v).map(([key, value]) => ` ${JSON.stringify(key)}: ${generatePythonObject(value, imports)}`).join(", \n")}\n}`;
46
+ } else if (typeof v === "object") return `{\n${Object.entries(v).map(([key, value]) => ` ${doubleQuote(key)}: ${generatePythonObject(value, imports)}`).join(", \n")}\n}`;
46
47
  else throw new Error(`Unsupported type: ${typeof v}`);
47
48
  }
48
49
  //#endregion
@@ -1,4 +1,4 @@
1
- import { escapeString, inputToString } from "../string-utils.js";
1
+ import { backtickQuote, inputToString, tripleDoubleQuote } from "../string-utils.js";
2
2
  import "./resolve-adapter.js";
3
3
  import js2xml from "xml-js/lib/js2xml.js";
4
4
  //#region src/requests/media/adapter.ts
@@ -80,7 +80,7 @@ const defaultAdapters = {
80
80
  s.push("mp := multipart.NewWriter(payload)");
81
81
  for (const [key, value] of Object.entries(data.body)) {
82
82
  if (!value) continue;
83
- const escaped = escapeString(inputToString(value, "application/json"), "`");
83
+ const escaped = backtickQuote(inputToString(value, "application/json"));
84
84
  s.push(`mp.WriteField("${key}", ${escaped})`);
85
85
  }
86
86
  }
@@ -103,20 +103,20 @@ const defaultAdapters = {
103
103
  function str(init, mediaType, ctx) {
104
104
  if (ctx.lang === "js") {
105
105
  if (mediaType === "application/json") return `const body = JSON.stringify(${JSON.stringify(init, null, 2)})`;
106
- return `const body = ${escapeString(inputToString(init, mediaType), "`")}`;
106
+ return `const body = ${backtickQuote(inputToString(init, mediaType))}`;
107
107
  }
108
- if (ctx.lang === "python") return `body = ${escapeString(inputToString(init, mediaType), "\"\"\"")}`;
108
+ if (ctx.lang === "python") return `body = ${tripleDoubleQuote(inputToString(init, mediaType))}`;
109
109
  if (ctx.lang === "go") {
110
110
  const { addImport } = ctx;
111
111
  addImport("strings");
112
- return `body := strings.NewReader(${escapeString(inputToString(init, mediaType), "`")})`;
112
+ return `body := strings.NewReader(${backtickQuote(inputToString(init, mediaType))})`;
113
113
  }
114
114
  if (ctx.lang === "java") {
115
115
  const { addImport } = ctx;
116
116
  addImport("java.net.http.HttpRequest.BodyPublishers");
117
- return `var body = BodyPublishers.ofString(${escapeString(inputToString(init, mediaType), "\"\"\"")});`;
117
+ return `var body = BodyPublishers.ofString(${tripleDoubleQuote(inputToString(init, mediaType))});`;
118
118
  }
119
- if (ctx.lang === "csharp") return `var body = new StringContent(${escapeString(`\n${inputToString(init, mediaType)}\n`, "\"\"\"")}, Encoding.UTF8, "${mediaType}");`;
119
+ if (ctx.lang === "csharp") return `var body = new StringContent(${tripleDoubleQuote(`\n${inputToString(init, mediaType)}\n`)}, Encoding.UTF8, "${mediaType}");`;
120
120
  }
121
121
  //#endregion
122
122
  export { defaultAdapters };
@@ -18,13 +18,34 @@ function inputToString(value, format = "application/json") {
18
18
  spaces: 2
19
19
  });
20
20
  }
21
- function escapeString(str, delimit) {
22
- if (!delimit) return JSON.stringify(str);
23
- return `${delimit}${str.replaceAll(delimit, `\\${delimit}`)}${delimit}`;
21
+ /**
22
+ * Returns the input string wrapped in single quotes, escaping internal single quotes and backslashes.
23
+ */
24
+ function singleQuote(str) {
25
+ return `'${str.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
26
+ }
27
+ /**
28
+ * Returns the input string wrapped in double quotes, escaping internal double quotes and backslashes.
29
+ */
30
+ function doubleQuote(str) {
31
+ return `"${str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
32
+ }
33
+ /**
34
+ * Returns the input string wrapped in Python triple double-quotes,
35
+ * escaping embedded triple quotes and backslashes.
36
+ */
37
+ function tripleDoubleQuote(str) {
38
+ return `"""${str.replace(/\\/g, "\\\\").replace(/"""/g, "\\\"\\\"\\\"")}"""`;
39
+ }
40
+ /**
41
+ * Returns the input string wrapped in backticks, escaping internal backticks and backslashes.
42
+ */
43
+ function backtickQuote(str) {
44
+ return `\`${str.replace(/\\/g, "\\\\").replace(/`/g, "\\`")}\``;
24
45
  }
25
46
  function indent(code, tab = 1) {
26
47
  const p = " ".repeat(tab);
27
48
  return code.split("\n").map((v) => v.length === 0 ? v : p + v).join("\n");
28
49
  }
29
50
  //#endregion
30
- export { escapeString, indent, inputToString };
51
+ export { backtickQuote, doubleQuote, indent, inputToString, singleQuote, tripleDoubleQuote };
@@ -1,8 +1,9 @@
1
1
  import { EncodedParameter, EncodedParameterMultiple } from "./media/encode.js";
2
+ import { HttpMethods } from "../types.js";
2
3
 
3
4
  //#region src/requests/types.d.ts
4
5
  interface RawRequestData {
5
- method: string;
6
+ method: HttpMethods;
6
7
  path: Record<string, unknown>;
7
8
  query: Record<string, unknown>;
8
9
  header: Record<string, unknown>;
@@ -11,7 +12,7 @@ interface RawRequestData {
11
12
  bodyMediaType?: string;
12
13
  }
13
14
  interface RequestData {
14
- method: string;
15
+ method: HttpMethods;
15
16
  path: Record<string, EncodedParameter>;
16
17
  query: Record<string, EncodedParameterMultiple>;
17
18
  header: Record<string, EncodedParameter>;
@@ -9,7 +9,7 @@ function APIPage({ showTitle: hasHead = false, showDescription, operations, webh
9
9
  className: "flex flex-col gap-24 text-sm @container",
10
10
  children: [slots.operations?.map((op) => op.children), slots.webhooks?.map((op) => op.children)]
11
11
  });
12
- const content = renderPageLayout({
12
+ let content = renderPageLayout({
13
13
  operations: operations?.map((item) => {
14
14
  const pathItem = dereferenced.paths?.[item.path];
15
15
  if (!pathItem) throw new Error(`[Fumadocs OpenAPI] Path not found in OpenAPI schema: ${item.path}`);
@@ -44,7 +44,9 @@ function APIPage({ showTitle: hasHead = false, showDescription, operations, webh
44
44
  };
45
45
  })
46
46
  }, ctx);
47
+ if (ctx.playground?.enabled !== false && ctx.playground?.provider) content = ctx.playground.provider({ children: content });
47
48
  return /* @__PURE__ */ jsx(ctx.clientBoundary.ApiProvider, {
49
+ schemes: dereferenced.components?.securitySchemes ?? {},
48
50
  shikiOptions: ctx.shikiOptions,
49
51
  client: ctx.client ?? {},
50
52
  children: /* @__PURE__ */ jsx(ctx.clientBoundary.ServerProvider, {
package/dist/ui/base.d.ts CHANGED
@@ -6,6 +6,7 @@ import { SchemaUIOptions } from "./schema/index.js";
6
6
  import { OpenAPIServer } from "../server/create.js";
7
7
  import { ApiPageProps, OperationItem, WebhookItem } from "./api-page.js";
8
8
  import { ResponseTab } from "./operation/response-tabs.js";
9
+ import { RequestTabsRenderContext } from "./operation/request-tabs.js";
9
10
  import { ClientCodeBlockProvider } from "./components/codeblock.js";
10
11
  import { Awaitable, MethodInformation, RenderContext } from "../types.js";
11
12
  import { NoReference } from "../utils/schema/index.js";
@@ -74,10 +75,7 @@ interface CreateAPIPageOptions {
74
75
  */
75
76
  content?: {
76
77
  renderResponseTabs?: (tabs: ResponseTab[], ctx: RenderContext) => ReactNode;
77
- renderRequestTabs?: (items: ExampleRequestItem[], ctx: RenderContext & {
78
- route: string;
79
- operation: NoReference<MethodInformation>;
80
- }) => ReactNode;
78
+ renderRequestTabs?: (items: ExampleRequestItem[], ctx: RequestTabsRenderContext) => ReactNode;
81
79
  renderAPIExampleLayout?: (slots: {
82
80
  selector: ReactNode;
83
81
  usageTabs: ReactNode;
@@ -142,6 +140,12 @@ interface CreateAPIPageOptions {
142
140
  * @defaultValue true
143
141
  */
144
142
  enabled?: boolean;
143
+ /**
144
+ * render a page-level provider (useful for handling auth)
145
+ */
146
+ provider?: (props: {
147
+ children: ReactNode;
148
+ }) => ReactNode;
145
149
  /**
146
150
  * replace the server-side renderer
147
151
  */
package/dist/ui/base.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { parseSecurities } from "../utils/schema/index.js";
2
2
  import { defaultAdapters } from "../requests/media/adapter.js";
3
- import { encodeInternalRef } from "../utils/schema/ref.js";
4
3
  import { ClientCodeBlockProvider } from "./components/codeblock.js";
4
+ import { encodeInternalRef } from "../utils/schema/ref.js";
5
5
  import { APIPage } from "./api-page.js";
6
6
  import { pickSchema } from "../utils/schema/pick.js";
7
+ import { PlaygroundAuthProvider } from "./client/boundary.lazy.js";
7
8
  import Slugger from "github-slugger";
8
9
  import * as JsxRuntime from "react/jsx-runtime";
9
10
  import { jsx } from "react/jsx-runtime";
@@ -38,6 +39,9 @@ function createAPIPage(server, options) {
38
39
  ...options.shikiOptions
39
40
  }).use(rehypeReact);
40
41
  }
42
+ function renderPlaygroundProviderDefault({ children }) {
43
+ return /* @__PURE__ */ jsx(PlaygroundAuthProvider, { children });
44
+ }
41
45
  function renderPlaygroundDefault({ path, method, ctx }) {
42
46
  return /* @__PURE__ */ jsx(ctx.clientBoundary.PlaygroundClient, {
43
47
  route: path,
@@ -77,6 +81,7 @@ function createAPIPage(server, options) {
77
81
  },
78
82
  playground: {
79
83
  ...options.playground,
84
+ provider: options.playground?.provider ?? renderPlaygroundProviderDefault,
80
85
  render: options.playground?.render ?? renderPlaygroundDefault
81
86
  },
82
87
  renderHeading(depth, text, props) {
@@ -8,6 +8,7 @@ const UsageTabsSelector = wrapLazy(() => import("../operation/usage-tabs/client.
8
8
  const UsageTab = wrapLazy(() => import("../operation/usage-tabs/client.js").then((mod) => ({ default: mod.UsageTab })));
9
9
  const SchemaUI = wrapLazy(() => import("../schema/client.js").then((mod) => ({ default: mod.SchemaUI })));
10
10
  const PlaygroundClient = wrapLazy(() => import("../../playground/client.js"));
11
+ const PlaygroundAuthProvider = wrapLazy(() => import("../../playground/auth.js").then((mod) => ({ default: mod.AuthProvider })));
11
12
  function wrapLazy(load) {
12
13
  const V = lazy(load);
13
14
  return function wrapper(props) {
@@ -15,4 +16,4 @@ function wrapLazy(load) {
15
16
  };
16
17
  }
17
18
  //#endregion
18
- export { ApiProvider, PlaygroundClient, SchemaUI, ServerProvider, UsageTab, UsageTabsSelector };
19
+ export { ApiProvider, PlaygroundAuthProvider, PlaygroundClient, SchemaUI, ServerProvider, UsageTab, UsageTabsSelector };
@@ -9,9 +9,11 @@ function useTranslations() {
9
9
  * Renders a translated string. Use in server components so the label is resolved on the client from the current locale.
10
10
  */
11
11
  function I18nLabel({ label, replacements = {} }) {
12
- let value = useTranslations()[label];
13
- for (const [k, v] of Object.entries(replacements)) value = value.replaceAll(`{${k}}`, v);
14
- return value;
12
+ return withReplacements(useTranslations()[label], replacements);
13
+ }
14
+ function withReplacements(t, replacements) {
15
+ for (const [k, v] of Object.entries(replacements)) t = t.replaceAll(`{${k}}`, v);
16
+ return t;
15
17
  }
16
18
  //#endregion
17
- export { I18nLabel, useTranslations };
19
+ export { I18nLabel, useTranslations, withReplacements };
@@ -5,7 +5,7 @@ function useStorageKey() {
5
5
  const { storageKeyPrefix } = useApiContext().client;
6
6
  return useMemo(() => ({
7
7
  of: (name) => getStorageKey(storageKeyPrefix, name),
8
- AuthField: (field) => getStorageKey(storageKeyPrefix, `auth-${field.original?.id ?? field.fieldName}`)
8
+ AuthField: (schemeId) => getStorageKey(storageKeyPrefix, `auth-${schemeId}`)
9
9
  }), [storageKeyPrefix]);
10
10
  }
11
11
  function getStorageKey(prefix = "fumadocs-openapi-", name) {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { cn } from "../../utils/cn.js";
3
2
  import { useTranslations } from "../client/i18n.js";
3
+ import { cn } from "../../utils/cn.js";
4
4
  import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { X } from "lucide-react";
@@ -1,5 +1,5 @@
1
1
  import { APIPageClientOptions } from "../client/index.js";
2
- import { RenderContext, ServerObject } from "../../types.js";
2
+ import { RenderContext, SecuritySchemeObject, ServerObject } from "../../types.js";
3
3
  import { ReactNode } from "react";
4
4
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
5
 
@@ -7,11 +7,14 @@ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
7
7
  interface InheritFromContext extends Pick<RenderContext, 'shikiOptions'> {
8
8
  client: APIPageClientOptions;
9
9
  }
10
- type ApiProviderProps = InheritFromContext;
10
+ interface ApiProviderProps extends InheritFromContext {
11
+ schemes: Record<string, SecuritySchemeObject>;
12
+ }
11
13
  declare function ApiProvider({
12
14
  children,
13
15
  shikiOptions,
14
- client
16
+ client,
17
+ schemes
15
18
  }: ApiProviderProps & {
16
19
  children: ReactNode;
17
20
  }): _$react_jsx_runtime0.JSX.Element;
@@ -18,7 +18,7 @@ function useServerContext() {
18
18
  if (!ctx) throw new Error("Component must be used under <ApiProvider />");
19
19
  return ctx;
20
20
  }
21
- function ApiProvider({ children, shikiOptions, client }) {
21
+ function ApiProvider({ children, shikiOptions, client, schemes }) {
22
22
  return /* @__PURE__ */ jsx(ApiContext, {
23
23
  value: useMemo(() => {
24
24
  let codeUsages;
@@ -31,12 +31,17 @@ function ApiProvider({ children, shikiOptions, client }) {
31
31
  shikiOptions,
32
32
  client,
33
33
  codeUsages,
34
+ schemes,
34
35
  mediaAdapters: {
35
36
  ...defaultAdapters,
36
37
  ...client.mediaAdapters
37
38
  }
38
39
  };
39
- }, [client, shikiOptions]),
40
+ }, [
41
+ client,
42
+ schemes,
43
+ shikiOptions
44
+ ]),
40
45
  children
41
46
  });
42
47
  }
@@ -1,6 +1,7 @@
1
1
  import { parseSecurities } from "../utils/schema/index.js";
2
2
  import { defaultAdapters } from "../requests/media/adapter.js";
3
3
  import { ClientCodeBlock, ClientCodeBlockProvider } from "./components/codeblock.js";
4
+ import { AuthProvider } from "../playground/auth.js";
4
5
  import { dereferenceDocument } from "../utils/document/dereference.js";
5
6
  import { APIPage } from "./api-page.js";
6
7
  import { boundary_exports } from "./client/boundary.js";
@@ -66,6 +67,9 @@ function createClientAPIPage({ shiki = defaultShikiFactory, shikiOptions = { the
66
67
  readOnly: false
67
68
  });
68
69
  }
70
+ function renderPlaygroundProviderDefault({ children }) {
71
+ return /* @__PURE__ */ jsx(AuthProvider, { children });
72
+ }
69
73
  return function ClientAPIPage({ payload, ...props }) {
70
74
  const processed = useMemo(() => dereferenceDocument(payload.bundled), [payload.bundled]);
71
75
  const ctx = useMemo(() => ({
@@ -82,6 +86,7 @@ function createClientAPIPage({ shiki = defaultShikiFactory, shikiOptions = { the
82
86
  },
83
87
  playground: {
84
88
  ...options.playground,
89
+ provider: options.playground?.provider ?? renderPlaygroundProviderDefault,
85
90
  render: options.playground?.render ?? renderPlaygroundDefault
86
91
  },
87
92
  renderHeading(depth, text, props) {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { cn } from "../../utils/cn.js";
3
2
  import { useTranslations } from "../client/i18n.js";
3
+ import { cn } from "../../utils/cn.js";
4
4
  import { createContext, use, useMemo, useRef, useState } from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Check, Copy } from "lucide-react";
@@ -2,9 +2,9 @@ import { idToTitle } from "../../utils/id-to-title.js";
2
2
  import { createMethod, methodKeys } from "../../utils/schema/index.js";
3
3
  import { isMediaTypeSupported } from "../../requests/media/resolve-adapter.js";
4
4
  import "../../requests/media/adapter.js";
5
+ import { I18nLabel } from "../client/i18n.js";
5
6
  import { cn } from "../../utils/cn.js";
6
7
  import { Badge, MethodLabel } from "../components/method-label.js";
7
- import { I18nLabel } from "../client/i18n.js";
8
8
  import { CopyTypeScriptPanel, OperationProvider } from "./client.js";
9
9
  import { Schema } from "../schema/index.js";
10
10
  import { AccordionContent, AccordionHeader, AccordionItem, AccordionTrigger, Accordions } from "../components/accordion.js";
@@ -0,0 +1,10 @@
1
+ import { MethodInformation, RenderContext } from "../../types.js";
2
+ import { NoReference } from "../../utils/schema/index.js";
3
+ import { ReactNode } from "react";
4
+ //#region src/ui/operation/request-tabs.d.ts
5
+ interface RequestTabsRenderContext extends RenderContext {
6
+ route: string;
7
+ operation: NoReference<MethodInformation>;
8
+ }
9
+ //#endregion
10
+ export { RequestTabsRenderContext };
@@ -1,6 +1,6 @@
1
1
  import { resolveRequestData } from "../../utils/url.js";
2
- import { MethodLabel } from "../components/method-label.js";
3
2
  import { I18nLabel } from "../client/i18n.js";
3
+ import { MethodLabel } from "../components/method-label.js";
4
4
  import { AccordionContent, AccordionHeader, AccordionItem, AccordionTrigger, Accordions } from "../components/accordion.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "fumadocs-ui/components/tabs";
@@ -15,41 +15,6 @@ function RequestTabs({ path, operation, examples, ctx }) {
15
15
  });
16
16
  }
17
17
  function renderRequestTabsDefault(items, ctx) {
18
- function renderItem(item) {
19
- const requestData = item.data;
20
- const displayNames = {
21
- body: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(I18nLabel, { label: "titleRequestBody" }), /* @__PURE__ */ jsx("code", {
22
- className: "text-xs text-fd-muted-foreground ms-auto",
23
- children: requestData.bodyMediaType
24
- })] }),
25
- cookie: /* @__PURE__ */ jsx(I18nLabel, { label: "cookieParameters" }),
26
- header: /* @__PURE__ */ jsx(I18nLabel, { label: "headerParameters" }),
27
- query: /* @__PURE__ */ jsx(I18nLabel, { label: "queryParameters" }),
28
- path: /* @__PURE__ */ jsx(I18nLabel, { label: "pathParameters" })
29
- };
30
- return /* @__PURE__ */ jsxs(Fragment, { children: [
31
- item.description && ctx.renderMarkdown(item.description),
32
- /* @__PURE__ */ jsxs("div", {
33
- className: "flex flex-row gap-2 items-center justify-between",
34
- children: [/* @__PURE__ */ jsx(MethodLabel, { children: requestData.method }), /* @__PURE__ */ jsx("code", { children: resolveRequestData(ctx.route, item.encoded) })]
35
- }),
36
- /* @__PURE__ */ jsx(Accordions, {
37
- type: "multiple",
38
- className: "mt-2",
39
- children: Object.entries(displayNames).map(([k, v]) => {
40
- const data = requestData[k];
41
- if (!data || Object.keys(data).length === 0) return;
42
- return /* @__PURE__ */ jsxs(AccordionItem, {
43
- value: k,
44
- children: [/* @__PURE__ */ jsx(AccordionHeader, { children: /* @__PURE__ */ jsx(AccordionTrigger, { children: v }) }), /* @__PURE__ */ jsx(AccordionContent, {
45
- className: "prose-no-margin",
46
- children: ctx.renderCodeBlock("json", JSON.stringify(data, null, 2))
47
- })]
48
- }, k);
49
- })
50
- })
51
- ] });
52
- }
53
18
  let children;
54
19
  if (items.length > 1) children = /* @__PURE__ */ jsxs(Tabs, {
55
20
  defaultValue: items[0].id,
@@ -58,10 +23,16 @@ function renderRequestTabsDefault(items, ctx) {
58
23
  children: item.id === "_default" ? /* @__PURE__ */ jsx(I18nLabel, { label: "requestTabNameDefault" }) : item.name
59
24
  }, item.id)) }), items.map((item) => /* @__PURE__ */ jsx(TabsContent, {
60
25
  value: item.id,
61
- children: renderItem(item)
26
+ children: /* @__PURE__ */ jsx(RequestTabsItem, {
27
+ item,
28
+ ctx
29
+ })
62
30
  }, item.id))]
63
31
  });
64
- else if (items.length === 1) children = renderItem(items[0]);
32
+ else if (items.length === 1) children = /* @__PURE__ */ jsx(RequestTabsItem, {
33
+ item: items[0],
34
+ ctx
35
+ });
65
36
  else children = /* @__PURE__ */ jsx("p", {
66
37
  className: "text-fd-muted-foreground text-xs",
67
38
  children: /* @__PURE__ */ jsx(I18nLabel, { label: "empty" })
@@ -74,5 +45,40 @@ function renderRequestTabsDefault(items, ctx) {
74
45
  }), children]
75
46
  });
76
47
  }
48
+ function RequestTabsItem({ item, ctx }) {
49
+ const requestData = item.data;
50
+ const displayNames = {
51
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(I18nLabel, { label: "titleRequestBody" }), /* @__PURE__ */ jsx("code", {
52
+ className: "text-xs text-fd-muted-foreground ms-auto",
53
+ children: requestData.bodyMediaType
54
+ })] }),
55
+ cookie: /* @__PURE__ */ jsx(I18nLabel, { label: "cookieParameters" }),
56
+ header: /* @__PURE__ */ jsx(I18nLabel, { label: "headerParameters" }),
57
+ query: /* @__PURE__ */ jsx(I18nLabel, { label: "queryParameters" }),
58
+ path: /* @__PURE__ */ jsx(I18nLabel, { label: "pathParameters" })
59
+ };
60
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
61
+ item.description && ctx.renderMarkdown(item.description),
62
+ /* @__PURE__ */ jsxs("div", {
63
+ className: "flex flex-row gap-2 items-center justify-between",
64
+ children: [/* @__PURE__ */ jsx(MethodLabel, { children: requestData.method }), /* @__PURE__ */ jsx("code", { children: resolveRequestData(ctx.route, item.encoded) })]
65
+ }),
66
+ /* @__PURE__ */ jsx(Accordions, {
67
+ type: "multiple",
68
+ className: "mt-2",
69
+ children: Object.entries(displayNames).map(([k, v]) => {
70
+ const data = requestData[k];
71
+ if (!data || Object.keys(data).length === 0) return;
72
+ return /* @__PURE__ */ jsxs(AccordionItem, {
73
+ value: k,
74
+ children: [/* @__PURE__ */ jsx(AccordionHeader, { children: /* @__PURE__ */ jsx(AccordionTrigger, { children: v }) }), /* @__PURE__ */ jsx(AccordionContent, {
75
+ className: "prose-no-margin",
76
+ children: ctx.renderCodeBlock("json", JSON.stringify(data, null, 2))
77
+ })]
78
+ }, k);
79
+ })
80
+ })
81
+ ] });
82
+ }
77
83
  //#endregion
78
84
  export { RequestTabs };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { joinURL, resolveRequestData, resolveServerUrl, withBase } from "../../../utils/url.js";
3
3
  import { useApiContext, useServerContext } from "../../contexts/api.js";
4
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/select.js";
5
4
  import { ClientCodeBlock } from "../../components/codeblock.js";
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/select.js";
6
6
  import { useOperationContext } from "../client.js";
7
7
  import { useEffect, useMemo, useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,13 +1,13 @@
1
1
  "use client";
2
+ import { useTranslations } from "../client/i18n.js";
2
3
  import { cn } from "../../utils/cn.js";
3
4
  import { Badge } from "../components/method-label.js";
4
- import { useTranslations } from "../client/i18n.js";
5
5
  import { Fragment, Suspense, createContext, use, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
6
6
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { ChevronDown, FilterIcon } from "lucide-react";
8
+ import { buttonVariants } from "fumadocs-ui/components/ui/button";
8
9
  import { cva } from "class-variance-authority";
9
10
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "fumadocs-ui/components/ui/collapsible";
10
- import { buttonVariants } from "fumadocs-ui/components/ui/button";
11
11
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "fumadocs-ui/components/tabs";
12
12
  import { Popover, PopoverContent, PopoverTrigger } from "fumadocs-ui/components/ui/popover";
13
13
  //#region src/ui/schema/client.tsx
@@ -1,6 +1,6 @@
1
+ import { I18nLabel } from "../client/i18n.js";
1
2
  import { mergeAllOf } from "../../utils/schema/merge.js";
2
3
  import { FormatFlags, schemaToString } from "../../utils/schema/to-string.js";
3
- import { I18nLabel } from "../client/i18n.js";
4
4
  import { useMemo } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/ui/schema/index.tsx