fumadocs-openapi 3.0.0 → 3.1.0

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.
package/dist/index.d.ts CHANGED
@@ -19,6 +19,11 @@ interface PropertyProps {
19
19
  interface ObjectCollapsibleProps {
20
20
  name: string;
21
21
  }
22
+ interface RequestProps {
23
+ language: string;
24
+ name: string;
25
+ code: string;
26
+ }
22
27
  interface Renderer {
23
28
  Root: (child: string[]) => string;
24
29
  API: (child: string[]) => string;
@@ -26,6 +31,8 @@ interface Renderer {
26
31
  APIExample: (child: string[]) => string;
27
32
  Responses: (props: ResponsesProps, child: string[]) => string;
28
33
  Response: (props: ResponseProps, child: string[]) => string;
34
+ Requests: (items: string[], child: string[]) => string;
35
+ Request: (props: RequestProps) => string;
29
36
  ResponseTypes: (child: string[]) => string;
30
37
  ExampleResponse: (json: string) => string;
31
38
  TypeScriptResponse: (code: string) => string;
@@ -97,9 +104,10 @@ interface MethodInformation extends OpenAPIV3.OperationObject {
97
104
  }
98
105
  interface RenderContext {
99
106
  renderer: Renderer;
107
+ document: OpenAPIV3.Document;
100
108
  baseUrl: string;
101
109
  }
102
110
 
103
111
  declare function createElement(name: string, props: object, ...child: string[]): string;
104
112
 
105
- export { type APIInfoProps, type Config, type GenerateOptions, type GenerateTagOutput, type MethodInformation, type ObjectCollapsibleProps, type PropertyProps, type RenderContext, type Renderer, type ResponseProps, type ResponsesProps, type RouteInformation, createElement, defaultRenderer, generate, generateFiles, generateTags };
113
+ export { type APIInfoProps, type Config, type GenerateOptions, type GenerateTagOutput, type MethodInformation, type ObjectCollapsibleProps, type PropertyProps, type RenderContext, type Renderer, type RequestProps, type ResponseProps, type ResponsesProps, type RouteInformation, createElement, defaultRenderer, generate, generateFiles, generateTags };
package/dist/index.js CHANGED
@@ -89,7 +89,9 @@ var defaultRenderer = {
89
89
  codeblock({ language: "ts" }, code)
90
90
  ),
91
91
  Property: (props, child) => createElement("Property", props, ...child),
92
- ObjectCollapsible: (props, child) => createElement("ObjectCollapsible", props, ...child)
92
+ ObjectCollapsible: (props, child) => createElement("ObjectCollapsible", props, ...child),
93
+ Requests: (items, child) => createElement("Requests", { items }, ...child),
94
+ Request: ({ language, code, name }) => createElement("Request", { value: name }, codeblock({ language }, code))
93
95
  };
94
96
 
95
97
  // src/render/page.ts
@@ -117,10 +119,7 @@ ${finalImports}
117
119
  ${Root(content)}`;
118
120
  }
119
121
 
120
- // src/samples/index.ts
121
- import { sample } from "openapi-sampler";
122
-
123
- // src/utils.ts
122
+ // src/utils/schema.ts
124
123
  function noRef(v) {
125
124
  return v;
126
125
  }
@@ -129,11 +128,20 @@ function getPreferredMedia(body) {
129
128
  if ("application/json" in body) return body["application/json"];
130
129
  return Object.values(body)[0];
131
130
  }
132
- function getValue(value) {
131
+ function toSampleInput(value) {
133
132
  return typeof value === "string" ? value : JSON.stringify(value, null, 2);
134
133
  }
135
134
 
136
- // src/samples/index.ts
135
+ // src/utils/generate-input.ts
136
+ import { sample } from "openapi-sampler";
137
+ function generateInput(method, schema) {
138
+ return sample(schema, {
139
+ skipReadOnly: method !== "GET",
140
+ skipWriteOnly: method === "GET"
141
+ });
142
+ }
143
+
144
+ // src/endpoint.ts
137
145
  function createEndpoint(path, method, baseUrl) {
138
146
  const params = [];
139
147
  const responses = {};
@@ -161,33 +169,28 @@ function createEndpoint(path, method, baseUrl) {
161
169
  let pathWithParameters = path;
162
170
  const queryParams = new URLSearchParams();
163
171
  for (const param of params) {
164
- const value = generateSample(method.method, param.schema);
165
- if (param.in === "query") queryParams.append(param.name, getValue(value));
172
+ const value = generateInput(method.method, param.schema);
173
+ if (param.in === "query")
174
+ queryParams.append(param.name, toSampleInput(value));
166
175
  if (param.in === "path")
167
176
  pathWithParameters = pathWithParameters.replace(
168
177
  `{${param.name}}`,
169
- getValue(value)
178
+ toSampleInput(value)
170
179
  );
171
180
  }
172
181
  return {
173
182
  url: new URL(pathWithParameters, baseUrl).toString(),
174
- body: bodySchema ? generateSample(method.method, bodySchema) : void 0,
183
+ body: bodySchema ? generateInput(method.method, bodySchema) : void 0,
175
184
  responses,
176
185
  method: method.method,
177
186
  parameters: params
178
187
  };
179
188
  }
180
- function generateSample(method, schema) {
181
- return sample(schema, {
182
- skipReadOnly: method !== "GET",
183
- skipWriteOnly: method === "GET"
184
- });
185
- }
186
189
 
187
- // src/samples/response.ts
190
+ // src/utils/generate-response.ts
188
191
  function getExampleResponse(endpoint, code) {
189
192
  if (code in endpoint.responses) {
190
- const value = generateSample(
193
+ const value = generateInput(
191
194
  endpoint.method,
192
195
  endpoint.responses[code].schema
193
196
  );
@@ -195,38 +198,66 @@ function getExampleResponse(endpoint, code) {
195
198
  }
196
199
  }
197
200
 
198
- // src/samples/typescript.ts
199
- import { compile } from "json-schema-to-typescript";
200
- async function getTypescript(endpoint, code) {
201
- if (code in endpoint.responses) {
202
- return compile(endpoint.responses[code].schema, "Response", {
203
- bannerComment: "",
204
- additionalProperties: false,
205
- format: true,
206
- enableConstEnums: false
207
- });
208
- }
209
- }
210
-
211
- // src/samples/curl.ts
201
+ // src/requests/curl.ts
212
202
  function getSampleRequest(endpoint) {
213
203
  const s = [];
214
204
  s.push(`curl -X ${endpoint.method} "${endpoint.url}"`);
215
205
  for (const param of endpoint.parameters) {
216
206
  if (param.in === "header") {
217
- const value = generateSample(endpoint.method, param.schema);
218
- const header = `${param.name}: ${getValue2(value)}`;
207
+ const value = generateInput(endpoint.method, param.schema);
208
+ const header = `${param.name}: ${toSampleInput(value)}`;
219
209
  s.push(`-H "${header}"`);
220
210
  }
221
211
  if (param.in === "formData") {
222
212
  console.log("Request example for form data is not supported");
223
213
  }
224
214
  }
225
- if (endpoint.body) s.push(`-d '${getValue2(endpoint.body)}'`);
215
+ if (endpoint.body) s.push(`-d '${toSampleInput(endpoint.body)}'`);
226
216
  return s.join(" \\\n ");
227
217
  }
228
- function getValue2(value) {
229
- return typeof value === "string" ? value : JSON.stringify(value, null, 2);
218
+
219
+ // src/requests/javascript.ts
220
+ function getSampleRequest2(endpoint) {
221
+ const s = [];
222
+ const options = /* @__PURE__ */ new Map();
223
+ const headers = {};
224
+ const formData = {};
225
+ for (const param of endpoint.parameters) {
226
+ if (param.in === "header") {
227
+ headers[param.name] = generateInput(endpoint.method, param.schema);
228
+ }
229
+ if (param.in === "formData") {
230
+ formData[param.name] = generateInput(endpoint.method, param.schema);
231
+ }
232
+ }
233
+ options.set("method", JSON.stringify(endpoint.method));
234
+ if (Object.keys(headers).length > 0) {
235
+ options.set("headers", JSON.stringify(headers, void 0, 2));
236
+ }
237
+ if (Object.keys(formData).length > 0) {
238
+ s.push(`const formData = new FormData();`);
239
+ for (const [key, value] of Object.entries(formData))
240
+ s.push(`formData.set(${key}, ${JSON.stringify(value)}`);
241
+ options.set("body", "formData");
242
+ }
243
+ const optionsStr = Array.from(options.entries()).map(([k, v]) => ` ${k}: ${v}`).join(",\n");
244
+ s.push(`fetch(${JSON.stringify(endpoint.url)}, {
245
+ ${optionsStr}
246
+ });`);
247
+ return s.join("\n\n");
248
+ }
249
+
250
+ // src/utils/get-typescript-schema.ts
251
+ import { compile } from "json-schema-to-typescript";
252
+ async function getTypescriptSchema(endpoint, code) {
253
+ if (code in endpoint.responses) {
254
+ return compile(endpoint.responses[code].schema, "Response", {
255
+ bannerComment: "",
256
+ additionalProperties: false,
257
+ format: true,
258
+ enableConstEnums: false
259
+ });
260
+ }
230
261
  }
231
262
 
232
263
  // src/render/schema.ts
@@ -345,12 +376,17 @@ function getSchemaType(schema) {
345
376
 
346
377
  // src/render/operation.ts
347
378
  async function renderOperation(path, method, ctx) {
379
+ const body = noRef(method.requestBody);
380
+ const security = method.security ?? ctx.document.security;
348
381
  const info = [];
349
382
  const example = [];
350
383
  const title = method.summary ?? method.operationId;
351
384
  if (title) info.push(`## ${title}`);
352
385
  if (method.description) info.push(p(method.description));
353
- const body = noRef(method.requestBody);
386
+ if (security) {
387
+ info.push("### Authorization");
388
+ info.push(getAuthSection(security, ctx));
389
+ }
354
390
  if (body) {
355
391
  const bodySchema = getPreferredMedia(body.content)?.schema;
356
392
  if (!bodySchema) throw new Error();
@@ -403,7 +439,21 @@ async function renderOperation(path, method, ctx) {
403
439
  }
404
440
  info.push(getResponseTable(method));
405
441
  example.push(
406
- codeblock({ language: "bash", title: "curl" }, getSampleRequest(endpoint))
442
+ ctx.renderer.Requests(
443
+ ["cURL", "JavaScript"],
444
+ [
445
+ ctx.renderer.Request({
446
+ name: "cURL",
447
+ code: getSampleRequest(endpoint),
448
+ language: "bash"
449
+ }),
450
+ ctx.renderer.Request({
451
+ name: "JavaScript",
452
+ code: getSampleRequest2(endpoint),
453
+ language: "js"
454
+ })
455
+ ]
456
+ )
407
457
  );
408
458
  example.push(await getResponseTabs(endpoint, method, ctx));
409
459
  return ctx.renderer.API([
@@ -411,6 +461,73 @@ async function renderOperation(path, method, ctx) {
411
461
  ctx.renderer.APIExample(example)
412
462
  ]);
413
463
  }
464
+ function getAuthSection(requirements, { document, renderer }) {
465
+ const info = [];
466
+ const schemas = document.components?.securitySchemes ?? {};
467
+ for (const requirement of requirements) {
468
+ if (info.length > 0) info.push(`---`);
469
+ for (const [name, scopes] of Object.entries(requirement)) {
470
+ if (!(name in schemas)) continue;
471
+ const schema = noRef(schemas[name]);
472
+ if (schema.type === "http") {
473
+ info.push(
474
+ renderer.Property(
475
+ {
476
+ name: "Authorization",
477
+ type: {
478
+ basic: "Basic <token>",
479
+ bearer: "Bearer <token>"
480
+ }[schema.scheme] ?? "<token>",
481
+ required: true
482
+ },
483
+ [p(schema.description), `In: \`header\``]
484
+ )
485
+ );
486
+ }
487
+ if (schema.type === "oauth2") {
488
+ info.push(
489
+ renderer.Property(
490
+ {
491
+ name: "Authorization",
492
+ type: "Bearer <token>",
493
+ required: true
494
+ },
495
+ [
496
+ p(schema.description),
497
+ `In: \`header\``,
498
+ `Scope: \`${scopes.length > 0 ? scopes.join(", ") : "none"}\``
499
+ ]
500
+ )
501
+ );
502
+ }
503
+ if (schema.type === "apiKey") {
504
+ info.push(
505
+ renderer.Property(
506
+ {
507
+ name: schema.name,
508
+ type: "<token>",
509
+ required: true
510
+ },
511
+ [p(schema.description), `In: \`${schema.in}\``]
512
+ )
513
+ );
514
+ }
515
+ if (schema.type === "openIdConnect") {
516
+ info.push(
517
+ renderer.Property(
518
+ {
519
+ name: "OpenID Connect",
520
+ type: "<token>",
521
+ required: true
522
+ },
523
+ [p(schema.description)]
524
+ )
525
+ );
526
+ }
527
+ }
528
+ }
529
+ return info.join("\n\n");
530
+ }
414
531
  function getResponseTable(operation) {
415
532
  const table = [];
416
533
  table.push(`| Status code | Description |`);
@@ -425,7 +542,7 @@ async function getResponseTabs(endpoint, operation, { renderer }) {
425
542
  const child = [];
426
543
  for (const code of Object.keys(operation.responses)) {
427
544
  const example = getExampleResponse(endpoint, code);
428
- const ts = await getTypescript(endpoint, code);
545
+ const ts = await getTypescriptSchema(endpoint, code);
429
546
  const description = code in endpoint.responses ? endpoint.responses[code].schema.description : void 0;
430
547
  if (example && ts) {
431
548
  items.push(code);
@@ -489,6 +606,7 @@ async function generateTags(pathOrDocument, options = {}) {
489
606
  }
490
607
  function getContext(document, options) {
491
608
  return {
609
+ document,
492
610
  renderer: {
493
611
  ...defaultRenderer,
494
612
  ...options.renderer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fumadocs-openapi",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Generate MDX docs for your OpenAPI spec",
5
5
  "keywords": [
6
6
  "NextJs",