@scalar/openapi-to-markdown 1.0.2 → 1.2.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +20 -4
  3. package/dist/browser.d.ts +9 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/browser.js +8 -0
  6. package/dist/create-markdown-from-openapi.d.ts +0 -3
  7. package/dist/create-markdown-from-openapi.d.ts.map +1 -1
  8. package/dist/create-markdown-from-openapi.js +1 -18
  9. package/dist/get-markdown-examples.d.ts +29 -0
  10. package/dist/get-markdown-examples.d.ts.map +1 -0
  11. package/dist/get-markdown-examples.js +105 -0
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/load-document.d.ts.map +1 -1
  16. package/dist/load-document.js +8 -1
  17. package/dist/render-document.d.ts.map +1 -1
  18. package/dist/render-document.js +8 -3
  19. package/dist/render-examples.d.ts +6 -0
  20. package/dist/render-examples.d.ts.map +1 -0
  21. package/dist/render-examples.js +41 -0
  22. package/dist/render-operation-details.d.ts +11 -0
  23. package/dist/render-operation-details.d.ts.map +1 -0
  24. package/dist/render-operation-details.js +85 -0
  25. package/dist/render-operation.d.ts.map +1 -1
  26. package/dist/render-operation.js +26 -15
  27. package/dist/render-schema.d.ts +55 -13
  28. package/dist/render-schema.d.ts.map +1 -1
  29. package/dist/render-schema.js +227 -69
  30. package/dist/restore-boolean-schemas.d.ts +7 -0
  31. package/dist/restore-boolean-schemas.d.ts.map +1 -0
  32. package/dist/restore-boolean-schemas.js +73 -0
  33. package/dist/select-document.d.ts +3 -3
  34. package/dist/select-document.d.ts.map +1 -1
  35. package/dist/select-document.js +57 -36
  36. package/package.json +13 -10
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # @scalar/openapi-to-markdown
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#10186](https://github.com/scalar/scalar/pull/10186): Support document-defined additional operations in Markdown operation, webhook, and tag selections. Preserve custom method spelling and inherited parameters, servers, and security when copying an operation as Markdown.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#10329](https://github.com/scalar/scalar/pull/10329): Render each shared schema once per page. The first reference to a structured schema expands it and labels it with its name (for example `schema: Account`). Later references print "Schema `Account` is shown above." instead of expanding it again. Model sections for schemas already expanded on the page refer back to them too. True cycles still print `[Circular Reference]`. This keeps Markdown output proportional to the schema graph, rather than to the number of paths through it. Before this change, densely linked descriptions such as Stripe's ran out of memory when rendering a single operation.
12
+
13
+ A node budget per operation and model section adds a visible `[Schema output truncated]` marker as a last resort. Generated examples that would exceed 10,000 values are omitted with a note. References past the depth limit now point to the model's own section instead of being cut off.
14
+
15
+ ## 1.1.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#10251](https://github.com/scalar/scalar/pull/10251): Preserve chained path-item references with non-enumerable links.
20
+
21
+ Remove the HTML output APIs `createHtmlFromOpenApi` and `renderer.renderHtml` from `@scalar/openapi-to-markdown`. Use `createMarkdownFromOpenApi` or `renderer.render` and convert the resulting Markdown with an application-provided renderer when HTML is needed.
22
+
23
+ - [#10222](https://github.com/scalar/scalar/pull/10222): Add a Copy as Markdown button to operations and webhooks in both API Reference layouts. Add a browser entry point for converting resolved OpenAPI documents to Markdown.
24
+
25
+ ### Patch Changes
26
+
27
+ - [#10275](https://github.com/scalar/scalar/pull/10275): Preserve operation and schema details, supplied and named examples, response headers and links, and request encoding metadata in Markdown output. Render composition siblings and schema annotations, respect readOnly/writeOnly when generating examples, and distinguish recursive references from deep schemas.
28
+ - [#10211](https://github.com/scalar/scalar/pull/10211): Preserve literal data and tag groups when upgrading to OpenAPI 3.2, migrate XML metadata only in schemas, and remove incompatible legacy XML flags. Make 3.2 upgrades leave the input unchanged, match the complete source version, prevent previously inactive parameter settings from changing serialization, and report path-specific errors for detected compatibility issues that require an author's decision.
29
+
30
+ Tag `kind` values may change: navigation groups are classified from actual operation-tag usage instead of name substrings. Malformed 3.1 versions now report explicit errors, and successful 3.2 upgrades clone the input only once.
31
+
32
+ Expose `UpgradeIncompatibilityError` so Markdown generation can retain OpenAPI 3.1 for descriptions requiring author decisions instead of failing or silently changing semantics. Clone safety and malformed-version errors still propagate.
33
+
34
+ The mock server also retains OpenAPI 3.1 when the strict 3.2 migration reports compatibility diagnostics. Existing inline XML descriptions continue loading without inventing element names.
35
+
36
+ Read only own data properties during migration so inherited parameter lists, XML metadata, and reference targets cannot modify prototype-owned objects.
37
+
38
+ Add `upgrade(input, '3.2', { onIncompatible: 'collect' })` to return a complete document and compatibility diagnostics. Compatible descriptions upgrade to 3.2; incompatible descriptions retain 3.1 without partial transformations. Strict mode remains the default, and malformed-version and clone-safety errors still propagate. The Markdown converter and mock server now use the shared collect mode.
39
+
3
40
  ## 1.0.2
4
41
 
5
42
  ## 1.0.1
package/README.md CHANGED
@@ -134,10 +134,8 @@ Schema normalization and description parsing are cached within each renderer. Re
134
134
  schema expansion still tracks ancestors and stops at a depth of ten. Output may use tighter
135
135
  list spacing and normalized Markdown escaping compared with earlier versions.
136
136
 
137
- ### HTML output
138
-
139
- `createHtmlFromOpenApi` and `renderer.renderHtml` remain available. They convert the
140
- Markdown output to HTML on demand, without loading a Vue renderer.
137
+ The package only generates Markdown. To produce HTML, pass the Markdown output to a
138
+ Markdown renderer in your application.
141
139
 
142
140
  ## Community
143
141
 
@@ -178,3 +176,21 @@ Omitting options, or passing `{}`, renders the whole document. OpenAPI 2.0 input
178
176
  Invalid, combined, or missing selectors reject the returned promise with an error. Duplicate operation IDs are ambiguous and list matching paths and methods; use a path/method selector instead. Duplicate tag declarations are also rejected. Names are case sensitive. Operation JSON pointers must target `/paths/{path}/{method}`, with an optional leading `#` and standard `~0`/`~1` escaping.
179
177
 
180
178
  Selection does not add support for every OpenAPI or JSON Schema keyword. Callbacks are not selectable pages. External references follow the existing workspace loader behavior. Recursive schema expansion stops on a repeated ancestor, with a depth limit of ten as a fallback. Shared dependencies have one component section, but may also appear inline where used. Authentication lists alternatives separately; schemes within one requirement must be used together.
179
+
180
+ ### Copying Markdown in the browser
181
+
182
+ Use the browser entry point with an OpenAPI document already resolved by
183
+ `@scalar/workspace-store`. It supports the same page selectors as the default
184
+ entry point, without file loading or HTML minification.
185
+
186
+ ```ts
187
+ const { createMarkdownFromOpenApi } =
188
+ await import('@scalar/openapi-to-markdown/browser')
189
+
190
+ const markdown = await createMarkdownFromOpenApi(document, {
191
+ operation: { path: '/users/{id}', method: 'get' },
192
+ })
193
+ ```
194
+
195
+ Use the default entry point for raw JSON, YAML, URLs, or file paths that still need
196
+ loading, migration, and reference resolution.
@@ -0,0 +1,9 @@
1
+ import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
2
+ import { type OpenApiRenderOptions } from './select-document.js';
3
+ /**
4
+ * Convert an OpenAPI document from the workspace store to Markdown in the browser.
5
+ * References must already be resolved by the store. This entry point does not load
6
+ * files, fetch URLs, or migrate raw API descriptions.
7
+ */
8
+ export declare const createMarkdownFromOpenApi: (document: OpenApiDocument, options?: OpenApiRenderOptions) => Promise<string>;
9
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AAGnG,OAAO,EAAE,KAAK,oBAAoB,EAAkB,MAAM,mBAAmB,CAAA;AAE7E;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GACpC,UAAU,eAAe,EACzB,UAAU,oBAAoB,KAC7B,OAAO,CAAC,MAAM,CAAsE,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { createDocumentRenderer } from './render-document.js';
2
+ import { selectDocument } from './select-document.js';
3
+ /**
4
+ * Convert an OpenAPI document from the workspace store to Markdown in the browser.
5
+ * References must already be resolved by the store. This entry point does not load
6
+ * files, fetch URLs, or migrate raw API descriptions.
7
+ */
8
+ export const createMarkdownFromOpenApi = async (document, options) => await createDocumentRenderer()(selectDocument(document, options));
@@ -4,7 +4,6 @@ type AnyDocument = OpenApiDocument | Record<string, unknown> | string;
4
4
  /** A resolved API description that can render multiple pages without loading it again. */
5
5
  export type OpenApiMarkdownRenderer = {
6
6
  render: (options?: OpenApiRenderOptions) => Promise<string>;
7
- renderHtml: (options?: OpenApiRenderOptions) => Promise<string>;
8
7
  };
9
8
  /**
10
9
  * Load and resolve an API description once, then render any number of selections.
@@ -13,7 +12,5 @@ export type OpenApiMarkdownRenderer = {
13
12
  export declare const createOpenApiMarkdownRenderer: (input: AnyDocument) => Promise<OpenApiMarkdownRenderer>;
14
13
  /** Generate Markdown from an API description, optionally scoped to a single page. */
15
14
  export declare const createMarkdownFromOpenApi: (input: AnyDocument, options?: OpenApiRenderOptions) => Promise<string>;
16
- /** Generate HTML through the optional Markdown conversion path. */
17
- export declare const createHtmlFromOpenApi: (input: AnyDocument, options?: OpenApiRenderOptions) => Promise<string>;
18
15
  export {};
19
16
  //# sourceMappingURL=create-markdown-from-openapi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-markdown-from-openapi.d.ts","sourceRoot":"","sources":["../src/create-markdown-from-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AAInG,OAAO,EAAE,KAAK,oBAAoB,EAAkB,MAAM,mBAAmB,CAAA;AAE7E,KAAK,WAAW,GAAG,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;AACrE,0FAA0F;AAC1F,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC3D,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAChE,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAU,OAAO,WAAW,KAAG,OAAO,CAAC,uBAAuB,CAoBvG,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,GACpC,OAAO,WAAW,EAClB,UAAU,oBAAoB,KAC7B,OAAO,CAAC,MAAM,CAGhB,CAAA;AAED,mEAAmE;AACnE,eAAO,MAAM,qBAAqB,GAAU,OAAO,WAAW,EAAE,UAAU,oBAAoB,KAAG,OAAO,CAAC,MAAM,CAG9G,CAAA"}
1
+ {"version":3,"file":"create-markdown-from-openapi.d.ts","sourceRoot":"","sources":["../src/create-markdown-from-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AAInG,OAAO,EAAE,KAAK,oBAAoB,EAAkB,MAAM,mBAAmB,CAAA;AAE7E,KAAK,WAAW,GAAG,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;AACrE,0FAA0F;AAC1F,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GAAU,OAAO,WAAW,KAAG,OAAO,CAAC,uBAAuB,CAOvG,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,GACpC,OAAO,WAAW,EAClB,UAAU,oBAAoB,KAC7B,OAAO,CAAC,MAAM,CAGhB,CAAA"}
@@ -9,27 +9,10 @@ export const createOpenApiMarkdownRenderer = async (input) => {
9
9
  const content = await loadDocument(input);
10
10
  const renderDocument = createDocumentRenderer();
11
11
  const render = async (options) => await renderDocument(selectDocument(content, options));
12
- return {
13
- render,
14
- renderHtml: async (options) => {
15
- const [{ unified }, { default: remarkParse }, { default: remarkRehype }, { default: rehypeStringify }] = await Promise.all([
16
- import('unified'),
17
- import('remark-parse'),
18
- import('remark-rehype'),
19
- import('rehype-stringify'),
20
- ]);
21
- const processor = unified().use(remarkParse).use(remarkRehype).use(rehypeStringify);
22
- return processor.processSync(await render(options)).toString();
23
- },
24
- };
12
+ return { render };
25
13
  };
26
14
  /** Generate Markdown from an API description, optionally scoped to a single page. */
27
15
  export const createMarkdownFromOpenApi = async (input, options) => {
28
16
  const renderer = await createOpenApiMarkdownRenderer(input);
29
17
  return renderer.render(options);
30
18
  };
31
- /** Generate HTML through the optional Markdown conversion path. */
32
- export const createHtmlFromOpenApi = async (input, options) => {
33
- const renderer = await createOpenApiMarkdownRenderer(input);
34
- return renderer.renderHtml(options);
35
- };
@@ -0,0 +1,29 @@
1
+ /** Example-bearing media types, parameters, and headers share the same precedence rules. */
2
+ export type ExampleSource = {
3
+ schema?: unknown;
4
+ example?: unknown;
5
+ examples?: Record<string, unknown>;
6
+ };
7
+ /** Named examples can contain a literal value or point to an external value. */
8
+ type MarkdownExample = {
9
+ name?: string;
10
+ summary?: string;
11
+ description?: string;
12
+ } & ({
13
+ value: unknown;
14
+ } | {
15
+ externalValue: string;
16
+ } | {
17
+ serializedValue: string;
18
+ } | {
19
+ omitted: true;
20
+ });
21
+ /**
22
+ * Estimate how many values an example generated from this schema contains, stopping just past the limit.
23
+ * Counts are cached per schema and level, so this is linear in the size of the schema graph.
24
+ */
25
+ export declare const countGeneratedExampleValues: (root: unknown, limit?: number) => number;
26
+ /** Preserve supplied values; generate a fallback only when examples are not supplied. */
27
+ export declare const getMarkdownExamples: (source: ExampleSource, mediaType: string, mode?: "read" | "write", openapiVersion?: string) => MarkdownExample[];
28
+ export {};
29
+ //# sourceMappingURL=get-markdown-examples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"AAKA,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC,CAAA;AAED,gFAAgF;AAChF,KAAK,eAAe,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,CAAC;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAAC,CAAA;AAYtG;;;GAGG;AACH,eAAO,MAAM,2BAA2B,GAAI,MAAM,OAAO,EAAE,cAAoC,KAAG,MA2CjG,CAAA;AAED,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,aAAa,EACrB,WAAW,MAAM,EACjB,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,KACvB,eAAe,EA4BjB,CAAA"}
@@ -0,0 +1,105 @@
1
+ import { isObject } from '@scalar/helpers/object/is-object';
2
+ import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/helpers/get-resolved-ref';
3
+ import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
4
+ /** Mirrors the depth at which `getExampleFromSchema` stops following nested schemas. */
5
+ const EXAMPLE_DEPTH = 10;
6
+ /**
7
+ * Generated examples repeat every shared schema at each place it is used, up to ten levels deep.
8
+ * A densely shared schema graph therefore produces billions of values, so larger examples are skipped.
9
+ * The largest generated examples in the Stripe, GitHub, and Cloudflare descriptions have about 7,600 values.
10
+ */
11
+ const MAX_GENERATED_EXAMPLE_VALUES = 10_000;
12
+ /**
13
+ * Estimate how many values an example generated from this schema contains, stopping just past the limit.
14
+ * Counts are cached per schema and level, so this is linear in the size of the schema graph.
15
+ */
16
+ export const countGeneratedExampleValues = (root, limit = MAX_GENERATED_EXAMPLE_VALUES) => {
17
+ const counts = new WeakMap();
18
+ const count = (input, level) => {
19
+ if (level > EXAMPLE_DEPTH || !isObject(input))
20
+ return 1;
21
+ const cached = counts.get(input)?.get(level);
22
+ if (cached !== undefined)
23
+ return cached;
24
+ const schema = getResolvedRef(input, mergeSiblingReferences);
25
+ if (!isObject(schema))
26
+ return 1;
27
+ // Supplied examples bypass schema expansion in the generator too.
28
+ if (schema.example !== undefined || (Array.isArray(schema.examples) && schema.examples.length > 0))
29
+ return 1;
30
+ let total = 1;
31
+ const add = (child) => {
32
+ if (total <= limit)
33
+ total += count(child, level + 1);
34
+ };
35
+ if (isObject(schema.properties))
36
+ Object.values(schema.properties).forEach(add);
37
+ if (isObject(schema.patternProperties))
38
+ Object.values(schema.patternProperties).forEach(add);
39
+ if (isObject(schema.additionalProperties))
40
+ add(schema.additionalProperties);
41
+ if (schema.items !== undefined)
42
+ add(schema.items);
43
+ if (Array.isArray(schema.prefixItems))
44
+ schema.prefixItems.forEach(add);
45
+ if (Array.isArray(schema.allOf))
46
+ schema.allOf.forEach(add);
47
+ const variants = Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [];
48
+ // Object and array generation can use the first variant, while other unions skip null.
49
+ // A discriminator can choose any variant, so bound the largest candidate in that case.
50
+ const nonNull = variants.find((variant) => {
51
+ const resolved = getResolvedRef(variant, mergeSiblingReferences);
52
+ return isObject(resolved) && resolved.type !== 'null';
53
+ });
54
+ const candidates = isObject(schema.discriminator) && schema.discriminator.defaultMapping !== undefined
55
+ ? variants
56
+ : [variants[0], nonNull];
57
+ let variantCount = 0;
58
+ for (const candidate of candidates) {
59
+ if (candidate !== undefined && total <= limit && variantCount <= limit)
60
+ variantCount = Math.max(variantCount, count(candidate, level + 1));
61
+ }
62
+ total += variantCount;
63
+ const levels = counts.get(input) ?? new Map();
64
+ levels.set(level, total);
65
+ counts.set(input, levels);
66
+ return total;
67
+ };
68
+ return count(root, 0);
69
+ };
70
+ /** Preserve supplied values; generate a fallback only when examples are not supplied. */
71
+ export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3.2.0') => {
72
+ if (source.example !== undefined)
73
+ return [{ value: source.example }];
74
+ if (source.examples && Object.keys(source.examples).length) {
75
+ return Object.entries(source.examples).flatMap(([name, reference]) => {
76
+ const example = getResolvedRef(reference);
77
+ if (!isObject(example))
78
+ return [];
79
+ const metadata = {
80
+ name,
81
+ summary: typeof example.summary === 'string' ? example.summary : undefined,
82
+ description: typeof example.description === 'string' ? example.description : undefined,
83
+ };
84
+ if (example.value !== undefined)
85
+ return [{ ...metadata, value: example.value }];
86
+ if (/^3\.2\./.test(openapiVersion) && typeof example.serializedValue === 'string')
87
+ return [{ ...metadata, serializedValue: example.serializedValue }];
88
+ if (typeof example.externalValue === 'string')
89
+ return [{ ...metadata, externalValue: example.externalValue }];
90
+ if (/^3\.2\./.test(openapiVersion) && example.dataValue !== undefined)
91
+ return [{ ...metadata, value: example.dataValue }];
92
+ return [];
93
+ });
94
+ }
95
+ const schema = getResolvedRef(source.schema);
96
+ if (!isObject(schema))
97
+ return [];
98
+ if (countGeneratedExampleValues(source.schema) > MAX_GENERATED_EXAMPLE_VALUES)
99
+ return [{ omitted: true }];
100
+ const value = getExampleFromSchema(getResolvedRef(source.schema, mergeSiblingReferences), {
101
+ xml: mediaType.includes('xml'),
102
+ mode,
103
+ });
104
+ return value === undefined ? [] : [{ value }];
105
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { HttpMethod } from '@scalar/helpers/http/http-methods';
2
2
  export type { OpenApiMarkdownRenderer } from './create-markdown-from-openapi.js';
3
- export { createHtmlFromOpenApi, createMarkdownFromOpenApi, createOpenApiMarkdownRenderer, } from './create-markdown-from-openapi.js';
3
+ export { createMarkdownFromOpenApi, createOpenApiMarkdownRenderer, } from './create-markdown-from-openapi.js';
4
4
  export type { OpenApiRenderOptions, OperationSelector } from './select-document.js';
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AAC7E,OAAO,EACL,qBAAqB,EACrB,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,gCAAgC,CAAA;AACvC,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAEnE,YAAY,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AAC7E,OAAO,EACL,yBAAyB,EACzB,6BAA6B,GAC9B,MAAM,gCAAgC,CAAA;AACvC,YAAY,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { createHtmlFromOpenApi, createMarkdownFromOpenApi, createOpenApiMarkdownRenderer, } from './create-markdown-from-openapi.js';
1
+ export { createMarkdownFromOpenApi, createOpenApiMarkdownRenderer, } from './create-markdown-from-openapi.js';
@@ -1 +1 @@
1
- {"version":3,"file":"load-document.d.ts","sourceRoot":"","sources":["../src/load-document.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,8DAA8D,CAAA;AAyErE,8EAA8E;AAC9E,eAAO,MAAM,YAAY,GACvB,OAAO,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,KACxD,OAAO,CAAC,eAAe,CAoFzB,CAAA"}
1
+ {"version":3,"file":"load-document.d.ts","sourceRoot":"","sources":["../src/load-document.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,8DAA8D,CAAA;AA2ErE,8EAA8E;AAC9E,eAAO,MAAM,YAAY,GACvB,OAAO,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,KACxD,OAAO,CAAC,eAAe,CA0FzB,CAAA"}
@@ -10,6 +10,7 @@ import { upgrade } from '@scalar/openapi-upgrader';
10
10
  import { deepClone } from '@scalar/workspace-store/helpers/deep-clone';
11
11
  import { coerceValue } from '@scalar/workspace-store/schemas/typebox-coerce';
12
12
  import { OpenAPIDocumentSchema, } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
13
+ import { restoreBooleanSchemas } from './restore-boolean-schemas.js';
13
14
  /**
14
15
  * Link references in a private, bundled document without proxies or expanded copies.
15
16
  * JSON Magic owns `$id` and anchor indexing, which keeps local-reference behavior
@@ -104,7 +105,8 @@ export const loadDocument = async (input) => {
104
105
  throw new Error('Failed to load OpenAPI document');
105
106
  }
106
107
  // Upgrade before indexing so reference resolution sees one consistent dialect.
107
- const upgraded = upgrade(raw, '3.2');
108
+ const declaredOpenapiVersion = typeof raw.openapi === 'string' ? raw.openapi : '2.0';
109
+ const { document: upgraded } = upgrade(raw, '3.2', { onIncompatible: 'collect' });
108
110
  const upgradedSchemas = getSchemas(upgraded);
109
111
  const hasExternalReferences = attachRefValues(upgraded, false, upgradedSchemas);
110
112
  let document = upgraded;
@@ -134,6 +136,11 @@ export const loadDocument = async (input) => {
134
136
  // Restore non-enumerable shared links afterward so rendering never expands the graph.
135
137
  attachRefValues(document, true, schemas);
136
138
  const coerced = coerceValue(OpenAPIDocumentSchema, document);
139
+ // Rendering must use the declared version for features added after OpenAPI 3.1.
140
+ coerced['x-original-oas-version'] = declaredOpenapiVersion;
141
+ // Boolean schemas were introduced in OpenAPI 3.1; older descriptions retain their existing coercion.
142
+ if (/^3\.[12]\./.test(declaredOpenapiVersion))
143
+ restoreBooleanSchemas(document, coerced);
137
144
  // Keep extension resources that local and bundled references can target.
138
145
  for (const [key, value] of Object.entries(document)) {
139
146
  if (key.startsWith('x-') && !(key in coerced)) {
@@ -1 +1 @@
1
- {"version":3,"file":"render-document.d.ts","sourceRoot":"","sources":["../src/render-document.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,8DAA8D,CAAA;AAcpH,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAO,CAAC,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAgIxF,CAAA"}
1
+ {"version":3,"file":"render-document.d.ts","sourceRoot":"","sources":["../src/render-document.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,8DAA8D,CAAA;AAepH,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAO,CAAC,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CA6IxF,CAAA"}
@@ -5,6 +5,7 @@ import remarkStringify from 'remark-stringify';
5
5
  import { unified } from 'unified';
6
6
  import { field, heading, inlineCode, item, link, list, paragraph, strong, text } from './markdown-nodes.js';
7
7
  import { createDescriptionParser } from './parse-description.js';
8
+ import { renderExamples } from './render-examples.js';
8
9
  import { renderOperation } from './render-operation.js';
9
10
  import { createSchemaRenderer } from './render-schema.js';
10
11
  import { renderSecurity } from './render-security.js';
@@ -12,9 +13,11 @@ const serializer = unified().use(remarkGfm).use(remarkStringify, { bullet: '-' }
12
13
  /** Build Markdown directly, retaining caches only for this immutable document snapshot. */
13
14
  export const createDocumentRenderer = () => {
14
15
  const descriptions = createDescriptionParser();
15
- const schemas = createSchemaRenderer();
16
+ const schemaRenderer = createSchemaRenderer();
16
17
  return async (document) => {
17
18
  const description = descriptions();
19
+ // Each page expands a shared schema once, then refers back to it.
20
+ const schemas = schemaRenderer.forDocument(document.components?.schemas);
18
21
  const { info } = document;
19
22
  const metadata = [
20
23
  field('OpenAPI Version', inlineCode(document.openapi)),
@@ -84,6 +87,7 @@ export const createDocumentRenderer = () => {
84
87
  nodes.push(heading(2, text(group.title)));
85
88
  hasOperations = true;
86
89
  }
90
+ schemas.beginSection();
87
91
  nodes.push(...(await renderOperation(document, path, method, pathItem, operation, group.webhook, {
88
92
  description,
89
93
  schemas,
@@ -96,14 +100,15 @@ export const createDocumentRenderer = () => {
96
100
  if (models.length)
97
101
  nodes.push(heading(2, text('Schemas')));
98
102
  for (const [name, schema] of models) {
103
+ schemas.beginSection();
99
104
  const view = schemas.view(schema);
100
105
  nodes.push(heading(3, text(view.title ?? name)), list([
101
106
  view.type
102
107
  ? field('Type', inlineCode(Array.isArray(view.type) ? view.type.join(' | ') : view.type))
103
108
  : item(paragraph(strong(text('Type:')))),
104
- ]), ...(await description(view.description)), ...schemas.render(schema));
109
+ ]), ...(await description(view.description)), ...schemas.render(schema, 0, [], { hideDescription: true, name }));
105
110
  if (view.type === 'object')
106
- nodes.push(paragraph(strong(text('Example:'))), schemas.example(schema));
111
+ nodes.push(...(await renderExamples({ schema }, description, 'application/json', undefined, document['x-original-oas-version'] ?? document.openapi)));
107
112
  flush();
108
113
  }
109
114
  flush();
@@ -0,0 +1,6 @@
1
+ import type { RootContent } from 'mdast';
2
+ import { type ExampleSource } from './get-markdown-examples.js';
3
+ import type { DescriptionParser } from './parse-description.js';
4
+ /** Render supplied examples before considering a schema-generated fallback. */
5
+ export declare const renderExamples: (source: ExampleSource, description: DescriptionParser, mediaType?: string, mode?: "read" | "write", openapiVersion?: string) => Promise<RootContent[]>;
6
+ //# sourceMappingURL=render-examples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-examples.d.ts","sourceRoot":"","sources":["../src/render-examples.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAExC,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,yBAAyB,CAAA;AAEjF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,GACzB,QAAQ,aAAa,EACrB,aAAa,iBAAiB,EAC9B,kBAA8B,EAC9B,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,KACvB,OAAO,CAAC,WAAW,EAAE,CAqCvB,CAAA"}
@@ -0,0 +1,41 @@
1
+ import { json2xml } from '@scalar/helpers/file/json2xml';
2
+ import { getMarkdownExamples } from './get-markdown-examples.js';
3
+ import { link, paragraph, strong, text } from './markdown-nodes.js';
4
+ /** Render supplied examples before considering a schema-generated fallback. */
5
+ export const renderExamples = async (source, description, mediaType = 'application/json', mode, openapiVersion = '3.2.0') => {
6
+ const nodes = [];
7
+ for (const example of getMarkdownExamples(source, mediaType, mode, openapiVersion)) {
8
+ nodes.push(paragraph(strong(text(example.name ? `Example: ${example.name}` : 'Example:'))));
9
+ if ('omitted' in example) {
10
+ nodes.push(paragraph(text('[Generated example omitted because it is too large]')));
11
+ continue;
12
+ }
13
+ if (example.summary)
14
+ nodes.push(paragraph(text(example.summary)));
15
+ nodes.push(...(await description(example.description)));
16
+ if ('externalValue' in example) {
17
+ nodes.push(paragraph(strong(text('External value:')), text(' '), link(example.externalValue, example.externalValue)));
18
+ continue;
19
+ }
20
+ if ('serializedValue' in example) {
21
+ nodes.push({
22
+ type: 'code',
23
+ lang: mediaType.includes('xml') ? 'xml' : mediaType.includes('json') ? 'json' : 'text',
24
+ value: example.serializedValue,
25
+ });
26
+ continue;
27
+ }
28
+ const xml = mediaType.includes('xml');
29
+ nodes.push({
30
+ type: 'code',
31
+ lang: xml ? 'xml' : 'json',
32
+ // XML strings are already serialized; primitives must not become object keys.
33
+ value: xml
34
+ ? example.value !== null && typeof example.value === 'object'
35
+ ? json2xml(example.value)
36
+ : String(example.value)
37
+ : (JSON.stringify(example.value, null, 2) ?? ''),
38
+ });
39
+ }
40
+ return nodes;
41
+ };
@@ -0,0 +1,11 @@
1
+ import type { EncodingObject, ResponseObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
2
+ import type { RootContent } from 'mdast';
3
+ import type { DescriptionParser } from './parse-description.js';
4
+ import type { SchemaRenderer } from './render-schema.js';
5
+ /** Render response or multipart headers, whose names come from their containing map. */
6
+ export declare const renderHeaders: (headers: ResponseObject["headers"], description: DescriptionParser, schemas: SchemaRenderer, openapiVersion: string) => Promise<RootContent[]>;
7
+ /** Preserve explicit encoding settings, including false flags and part headers. */
8
+ export declare const renderEncoding: (encoding: Record<string, EncodingObject> | undefined, mediaType: string, description: DescriptionParser, schemas: SchemaRenderer, openapiVersion: string) => Promise<RootContent[]>;
9
+ /** Render response link expressions as literal values, without evaluating them. */
10
+ export declare const renderResponseLinks: (links: ResponseObject["links"], description: DescriptionParser) => Promise<RootContent[]>;
11
+ //# sourceMappingURL=render-operation-details.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-operation-details.d.ts","sourceRoot":"","sources":["../src/render-operation-details.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,8DAA8D,CAAA;AAClH,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,wFAAwF;AACxF,eAAO,MAAM,aAAa,GACxB,SAAS,cAAc,CAAC,SAAS,CAAC,EAClC,aAAa,iBAAiB,EAC9B,SAAS,cAAc,EACvB,gBAAgB,MAAM,KACrB,OAAO,CAAC,WAAW,EAAE,CAkCvB,CAAA;AAED,mFAAmF;AACnF,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,SAAS,EACpD,WAAW,MAAM,EACjB,aAAa,iBAAiB,EAC9B,SAAS,cAAc,EACvB,gBAAgB,MAAM,KACrB,OAAO,CAAC,WAAW,EAAE,CAuBvB,CAAA;AAID,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,GAC9B,OAAO,cAAc,CAAC,OAAO,CAAC,EAC9B,aAAa,iBAAiB,KAC7B,OAAO,CAAC,WAAW,EAAE,CA2BvB,CAAA"}
@@ -0,0 +1,85 @@
1
+ import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
+ import { inlineCode, item, list, paragraph, strong, text } from './markdown-nodes.js';
3
+ import { renderExamples } from './render-examples.js';
4
+ /** Render response or multipart headers, whose names come from their containing map. */
5
+ export const renderHeaders = async (headers, description, schemas, openapiVersion) => {
6
+ const entries = [];
7
+ for (const [name, reference] of Object.entries(headers ?? {})) {
8
+ // OpenAPI reserves Content-Type for the media type map / encoding field.
9
+ if (name.toLowerCase() === 'content-type')
10
+ continue;
11
+ if (!getResolvedRef(reference))
12
+ continue;
13
+ const header = getResolvedRef(reference, mergeSiblingReferences);
14
+ const blocks = [
15
+ paragraph(strong(inlineCode(name)), text(header.required ? ' (required)' : '')),
16
+ ...(await description(header.description)),
17
+ ];
18
+ if ('schema' in header && header.schema !== undefined)
19
+ blocks.push(...schemas.render(header.schema));
20
+ if ('example' in header || 'examples' in header) {
21
+ blocks.push(...(await renderExamples({ example: header.example, examples: header.examples }, description, 'application/json', undefined, openapiVersion)));
22
+ }
23
+ for (const [mediaType, content] of Object.entries('content' in header ? (header.content ?? {}) : {})) {
24
+ blocks.push(paragraph(strong(text('Content-Type:')), text(` ${mediaType}`)));
25
+ if (content.schema !== undefined)
26
+ blocks.push(...schemas.render(content.schema));
27
+ blocks.push(...(await renderExamples(content, description, mediaType, undefined, openapiVersion)));
28
+ }
29
+ entries.push(item(...blocks));
30
+ }
31
+ return entries.length ? [paragraph(strong(text('Headers:'))), list(entries)] : [];
32
+ };
33
+ /** Preserve explicit encoding settings, including false flags and part headers. */
34
+ export const renderEncoding = async (encoding, mediaType, description, schemas, openapiVersion) => {
35
+ const multipart = mediaType.startsWith('multipart/');
36
+ if (!multipart && mediaType !== 'application/x-www-form-urlencoded')
37
+ return [];
38
+ const entries = [];
39
+ for (const [name, entry] of Object.entries(encoding ?? {})) {
40
+ const fields = [];
41
+ for (const [key, label] of [
42
+ ['contentType', 'Content-Type'],
43
+ ['style', 'Style'],
44
+ ['explode', 'Explode'],
45
+ ['allowReserved', 'Allow reserved'],
46
+ ]) {
47
+ if (entry[key] !== undefined)
48
+ fields.push(item(paragraph(text(`${label}: `), inlineCode(entry[key]))));
49
+ }
50
+ const blocks = [paragraph(strong(inlineCode(name)))];
51
+ if (fields.length)
52
+ blocks.push(list(fields));
53
+ if (multipart)
54
+ blocks.push(...(await renderHeaders(entry.headers, description, schemas, openapiVersion)));
55
+ entries.push(item(...blocks));
56
+ }
57
+ return entries.length ? [paragraph(strong(text('Encoding:'))), list(entries)] : [];
58
+ };
59
+ const formatValue = (value) => (typeof value === 'string' ? value : (JSON.stringify(value) ?? ''));
60
+ /** Render response link expressions as literal values, without evaluating them. */
61
+ export const renderResponseLinks = async (links, description) => {
62
+ const entries = [];
63
+ for (const [name, reference] of Object.entries(links ?? {})) {
64
+ if (!getResolvedRef(reference))
65
+ continue;
66
+ const link = getResolvedRef(reference, mergeSiblingReferences);
67
+ const blocks = [
68
+ paragraph(strong(text(name))),
69
+ ...(await description(link.description)),
70
+ ];
71
+ if (link.operationId)
72
+ blocks.push(paragraph(strong(text('Operation ID:')), text(' '), inlineCode(link.operationId)));
73
+ if (link.operationRef)
74
+ blocks.push(paragraph(strong(text('Operation reference:')), text(' '), inlineCode(link.operationRef)));
75
+ const parameters = Object.entries(link.parameters ?? {});
76
+ if (parameters.length)
77
+ blocks.push(list(parameters.map(([name, value]) => item(paragraph(strong(text(`${name}:`)), text(' '), inlineCode(formatValue(value)))))));
78
+ if (link.requestBody !== undefined)
79
+ blocks.push(paragraph(strong(text('Request body:')), text(' '), inlineCode(formatValue(link.requestBody))));
80
+ if (link.server)
81
+ blocks.push(paragraph(strong(text('Server:')), text(' '), inlineCode(link.server.url)));
82
+ entries.push(item(...blocks));
83
+ }
84
+ return entries.length ? [paragraph(strong(text('Links:'))), list(entries)] : [];
85
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"render-operation.d.ts","sourceRoot":"","sources":["../src/render-operation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAEf,cAAc,EAGf,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,oEAAoE;AACpE,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,iBAAiB,CAAA;IAC9B,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,GAC1B,UAAU,eAAe,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,UAAU,cAAc,EACxB,WAAW,eAAe,EAC1B,SAAS,OAAO,EAChB,0BAA0B,aAAa,KACtC,OAAO,CAAC,WAAW,EAAE,CAgGvB,CAAA"}
1
+ {"version":3,"file":"render-operation.d.ts","sourceRoot":"","sources":["../src/render-operation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EAEf,cAAc,EAGf,MAAM,8DAA8D,CAAA;AACrE,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,OAAO,CAAA;AAGlD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAG5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD,oEAAoE;AACpE,KAAK,aAAa,GAAG;IACnB,WAAW,EAAE,iBAAiB,CAAA;IAC9B,OAAO,EAAE,cAAc,CAAA;CACxB,CAAA;AAED,iFAAiF;AACjF,eAAO,MAAM,eAAe,GAC1B,UAAU,eAAe,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,UAAU,cAAc,EACxB,WAAW,eAAe,EAC1B,SAAS,OAAO,EAChB,0BAA0B,aAAa,KACtC,OAAO,CAAC,WAAW,EAAE,CAyGvB,CAAA"}