@scalar/openapi-to-markdown 1.4.0 → 1.5.1

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 (41) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +66 -5
  3. package/dist/create-markdown-from-openapi.d.ts.map +1 -1
  4. package/dist/create-markdown-from-openapi.js +3 -2
  5. package/dist/document-anchors.d.ts +8 -0
  6. package/dist/document-anchors.d.ts.map +1 -0
  7. package/dist/document-anchors.js +25 -0
  8. package/dist/document-examples.d.ts +14 -0
  9. package/dist/document-examples.d.ts.map +1 -0
  10. package/dist/document-examples.js +54 -0
  11. package/dist/get-markdown-examples.d.ts +2 -0
  12. package/dist/get-markdown-examples.d.ts.map +1 -1
  13. package/dist/get-markdown-examples.js +11 -12
  14. package/dist/markdown-nodes.d.ts +22 -1
  15. package/dist/markdown-nodes.d.ts.map +1 -1
  16. package/dist/markdown-nodes.js +4 -0
  17. package/dist/parse-description.d.ts +2 -0
  18. package/dist/parse-description.d.ts.map +1 -1
  19. package/dist/parse-description.js +28 -1
  20. package/dist/render-document.d.ts +2 -2
  21. package/dist/render-document.d.ts.map +1 -1
  22. package/dist/render-document.js +217 -84
  23. package/dist/render-examples.d.ts +9 -1
  24. package/dist/render-examples.d.ts.map +1 -1
  25. package/dist/render-examples.js +26 -8
  26. package/dist/render-operation-details.d.ts +3 -2
  27. package/dist/render-operation-details.d.ts.map +1 -1
  28. package/dist/render-operation-details.js +8 -5
  29. package/dist/render-operation.d.ts +16 -1
  30. package/dist/render-operation.d.ts.map +1 -1
  31. package/dist/render-operation.js +168 -42
  32. package/dist/render-schema.d.ts +30 -3
  33. package/dist/render-schema.d.ts.map +1 -1
  34. package/dist/render-schema.js +474 -111
  35. package/dist/render-security.d.ts +7 -3
  36. package/dist/render-security.d.ts.map +1 -1
  37. package/dist/render-security.js +61 -12
  38. package/dist/select-document.d.ts +34 -3
  39. package/dist/select-document.d.ts.map +1 -1
  40. package/dist/select-document.js +58 -30
  41. package/package.json +18 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @scalar/openapi-to-markdown
2
2
 
3
+ ## 1.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#10457](https://github.com/scalar/scalar/pull/10457): Render a selected operation, model, tag or webhook in time proportional to the selection instead of the whole document.
8
+
9
+ ## 1.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#10425](https://github.com/scalar/scalar/pull/10425): Make single-page Markdown easier to read, especially for AI agents reading `llms.txt` exports.
14
+ - An operation, webhook or model page now starts with that item as its `#` title and uses `##` sections, without the API's document header. An empty API version is never printed.
15
+ - Schema property descriptions keep their Markdown: paragraphs, links, inline code and fenced code blocks are no longer flattened into one escaped line. A parameter description is no longer repeated by its schema.
16
+ - Each property is one line with its name, type and annotations. Nullable unions read as `string | null`, single-branch `allOf` wrappers as the wrapped type, unions of plain types as one type, `const` and `enum` schemas without a `type` as their inferred JSON type, and arrays of simple items as `array of …`. Composition branches are listed one per item, with discriminator values beside their branch.
17
+ - Parameters are grouped by location. Responses that return the same schema and media type are grouped into one entry, such as a list of error statuses.
18
+ - In linked mode, references are shown as a link in the property's type. Primitive, enum and `const` schemas and aliases of them are written in place instead of linked; set `schemaReferences.inlinePrimitives: false` to link them. Generated examples are left out without a placeholder, while a model page generates an example of its own schema with linked schemas as empty stubs.
19
+ - The Authentication section is left out when the API description declares no security requirements, and only an explicit `security: []` says that no authentication is required. Security schemes are summarized on one line, for example "API key in header `X-Api-Key`", instead of printed as JSON.
20
+ - Selected pages retain empty server overrides as `/` and include server variable choices and descriptions. Model pages retain authored examples for primitive and array schemas as well as objects.
21
+
22
+ - [#10425](https://github.com/scalar/scalar/pull/10425): Give shared schemas canonical definitions and internal links in whole-document Markdown exports, retaining primitive types inline and constraints beside references.
23
+
24
+ Deduplicate generated examples within each schema and request/response/media-type context, label them as generated, and retain authored examples at their original locations.
25
+
26
+ Explain inherited servers and authentication once, with links from operations, while retaining path and operation overrides, server variables, and explicit anonymous access.
27
+
28
+ Add a compact contents index linking to operations, webhooks, and schemas through unique explicit anchors.
29
+
30
+ ### Patch Changes
31
+
32
+ - [#10425](https://github.com/scalar/scalar/pull/10425): Add a browser playground for previewing and downloading per-page, linked, and whole-document Markdown exports from Galaxy, Stripe, GitHub, and Cloudflare examples.
33
+ - [#10425](https://github.com/scalar/scalar/pull/10425): Shorten the `## Schemas` section for schemas a page already expanded. Such a model is now a single line, for example `` `Customer` — shown above. ``, instead of a heading, type, "shown above" note and generated example. The line keeps the model's title and any description that a reference sibling replaced where the schema was expanded. A selected model, leaf schemas, models the page has not expanded, and object models with authored examples keep their full sections. A Stripe operation page such as `GET /v1/customers/{customer}` drops from about 2.5 MB to about 1.3 MB, with 819 of its 918 model sections reduced to one line. Everything above `## Schemas` is unchanged.
34
+
3
35
  ## 1.4.0
4
36
 
5
37
  ### Minor Changes
package/README.md CHANGED
@@ -130,6 +130,15 @@ It preserves Markdown descriptions, GFM tables and code blocks without rendering
130
130
  or converting the generated document through HTML. Descriptions containing raw HTML or
131
131
  Scalar alerts use a separate sanitization and conversion path. Images remain excluded.
132
132
 
133
+ Each schema property is one line with its name, type and annotations, for example
134
+ `` **`archived_at` (required)**: `string | null`, format: `date-time` ``, followed by
135
+ its description with its paragraphs, links and code blocks intact. Nullable unions,
136
+ single-branch `allOf` wrappers and unions of plain types are labelled as one type.
137
+ Parameters are listed by location. Responses that return the same schema and media
138
+ type share one entry, such as a list of error statuses. Security schemes are
139
+ summarized on one line, and an Authentication section appears only when the API
140
+ description declares requirements.
141
+
133
142
  Schema normalization and description parsing are cached within each renderer. Recursive
134
143
  schema expansion still tracks ancestors and stops at a depth of ten. Output may use tighter
135
144
  list spacing and normalized Markdown escaping compared with earlier versions.
@@ -167,15 +176,35 @@ await createMarkdownFromOpenApi(content, {
167
176
  - **Webhook:** One operation selected by its exact OpenAPI webhook name and method, including parameters, payload and responses. The name is a label, not a delivery URL.
168
177
  - **Introduction:** API title, versions, description, contact, license, terms of service, servers and global authentication requirements. No operations, tags, models or webhooks.
169
178
 
170
- Selected pages retain API title, versions and description. They exclude unrelated reference content. Operation servers override path servers, which override document servers. Operation security overrides document security, including `security: []` for anonymous access. Parameter overrides use the parameter name and location. Required schemas are collected after reference resolution, so dependencies remain available even when their original section is omitted.
179
+ An operation, webhook or model page starts with that item as its `#` title, with its sections (parameters, request body, responses) as `##` headings. It leaves out the API title, versions and description, which belong on the introduction page. Tag and whole-document exports keep the document header. Selected pages exclude unrelated reference content. Operation servers override path servers, which override document servers. Operation security overrides document security, including `security: []` for anonymous access. Parameter overrides use the parameter name and location. Required schemas are collected after reference resolution, so dependencies remain available even when their original section is omitted.
171
180
 
172
181
  Omitting options, or passing `{}`, renders the whole document. OpenAPI 2.0 inputs are migrated before selection: use definition names with `model`. Webhooks require OpenAPI 3.1 or later.
173
182
 
183
+ Whole-document exports include a compact contents index after the introduction, linking
184
+ to operations by method and path, webhooks, and component schemas. Empty groups are omitted.
185
+
186
+ Whole-document exports render each named structured schema once under `## Schemas`.
187
+ Operations and nested properties link to that section, while simple primitive references
188
+ remain inline. Generated explicit anchors keep links stable even when schema titles repeat.
189
+ The export is self-contained; a supplied `schemaReferences.resolveUrl` callback still controls
190
+ reference URLs when linked mode is explicitly requested.
191
+
192
+ Within a whole-document export, repeated generated examples link to their first
193
+ occurrence. Request, response, and media-type contexts remain distinct. Synthesized
194
+ values are labeled **Generated example**, and authored examples remain at every
195
+ usage, including all values in a schema's `examples` array.
196
+
197
+ Global servers and authentication are documented once. Operations link to inherited
198
+ defaults and show their own overrides in full. Path-level servers are explained at
199
+ their first use and linked thereafter, including variable defaults, choices, and
200
+ descriptions. Explicit empty server overrides use `/`; explicit `security: []` remains
201
+ anonymous. Absent security requirements do not imply anonymous access.
202
+
174
203
  ### Errors and limitations
175
204
 
176
205
  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.
177
206
 
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.
207
+ 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. Selected-page exports expand shared dependencies once, where they are first used. Whole-document exports instead link structured dependencies to their full definitions under `## Schemas`. For selected-page exports, the `## Schemas` section lists a component the page already expanded on one line, with its title and any description that a reference sibling replaced, and keeps a full section for the selected model, for leaf schemas, for models the page has not expanded yet, and for object models with authored examples. Authentication lists alternatives separately; schemes within one requirement must be used together.
179
208
 
180
209
  ### Copying Markdown in the browser
181
210
 
@@ -237,11 +266,43 @@ method: 'post' } }`, the one-shot `createMarkdownFromOpenApi`, or the browser en
237
266
  point. The browser entry point still requires a workspace-resolved document.
238
267
  Options and URL callbacks are isolated per render, including concurrent renders.
239
268
 
240
- Linked mode omits schema-generated examples with an explicit note, avoiding
241
- expansion through the example generator. Authored media-type and schema examples
242
- remain available. Their size is not capped. Inline schema content and authored
269
+ References to primitive, enum and `const` schemas, and to aliases of them, are
270
+ written in place, because their whole definition fits on one line. Pass
271
+ `inlinePrimitives: false` to link them too. Object, array and composition schemas
272
+ are always linked.
273
+
274
+ Linked mode uses authored media-type and schema examples and does not generate
275
+ examples on operation pages, which would expand every linked schema. A model page
276
+ generates an example for its own schema, with linked schemas left as empty
277
+ stubs. Authored examples are not capped in size. Inline schema content and authored
243
278
  text also remain proportional to the source; this mode bounds traversal across
244
279
  shared references, not the byte size of arbitrary authored content. Root
245
280
  composition branches that contain references link to those schemas rather than
246
281
  flattening their constraints. Full-document exports still include every model
247
282
  section; use a page selector for individual exports.
283
+
284
+ ## Browser playground
285
+
286
+ From the repository root, run:
287
+
288
+ ```sh
289
+ pnpm --filter @scalar/openapi-to-markdown dev
290
+ ```
291
+
292
+ Open `http://localhost:3000` (or the address printed by Vite). Choose Galaxy,
293
+ Stripe, GitHub, or Cloudflare, then preview a **Per-page**, **Linked page**, or
294
+ **Full export**. Page selection includes operations by method/path, models,
295
+ webhooks, tags, and the introduction. Search the page list to find an item.
296
+
297
+ The **Preview** and **Markdown** views show the rendered document and its exact
298
+ source. Copy or download the Markdown, follow internal links in whole exports,
299
+ or follow linked schema URLs to open that model in the playground. The additional
300
+ **Link shared schemas** control also allows a linked full export.
301
+
302
+ Galaxy is included locally through `@scalar/galaxy`; the other examples fetch
303
+ current documents from their official GitHub repositories and require internet
304
+ access on first load. Loading and exporting happen on the development server.
305
+ It retains one resolved document at a time; switching examples releases the previous
306
+ renderer. Large exports can take longer. The reported duration measures Markdown
307
+ export, excluding document loading and HTML preview conversion. `/llms.txt` still
308
+ serves the complete Galaxy Markdown export.
@@ -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;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"}
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,EAAwC,MAAM,mBAAmB,CAAA;AAEnG,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,CAQvG,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,GACpC,OAAO,WAAW,EAClB,UAAU,oBAAoB,KAC7B,OAAO,CAAC,MAAM,CAGhB,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { loadDocument } from './load-document.js';
2
2
  import { createDocumentRenderer } from './render-document.js';
3
- import { selectDocument } from './select-document.js';
3
+ import { createDocumentLookup, selectDocument } from './select-document.js';
4
4
  /**
5
5
  * Load and resolve an API description once, then render any number of selections.
6
6
  * Each renderer owns its document; create a new renderer to pick up source changes.
@@ -8,7 +8,8 @@ import { selectDocument } from './select-document.js';
8
8
  export const createOpenApiMarkdownRenderer = async (input) => {
9
9
  const content = await loadDocument(input);
10
10
  const renderDocument = createDocumentRenderer();
11
- const render = async (options) => await renderDocument(selectDocument(content, options), options);
11
+ const lookup = createDocumentLookup(content);
12
+ const render = async (options) => await renderDocument(selectDocument(content, options, lookup), options);
12
13
  return { render };
13
14
  };
14
15
  /** Generate Markdown from an API description, optionally scoped to a single page. */
@@ -0,0 +1,8 @@
1
+ import type { Html } from 'mdast';
2
+ /** One export owns its destinations, so repeated titles and concurrent renders cannot collide. */
3
+ export declare const createDocumentAnchors: () => {
4
+ get: (kind: string, identity: string, label?: string) => string;
5
+ };
6
+ /** Explicit destinations do not depend on a Markdown viewer's heading slug rules. */
7
+ export declare const anchor: (id: string) => Html;
8
+ //# sourceMappingURL=document-anchors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-anchors.d.ts","sourceRoot":"","sources":["../src/document-anchors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEjC,kGAAkG;AAClG,eAAO,MAAM,qBAAqB,QAAO;IACvC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAmBhE,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,MAAM,GAAI,IAAI,MAAM,KAAG,IAAuD,CAAA"}
@@ -0,0 +1,25 @@
1
+ import { slugger } from '@scalar/helpers/string/slugger';
2
+ /** One export owns its destinations, so repeated titles and concurrent renders cannot collide. */
3
+ export const createDocumentAnchors = () => {
4
+ const slugs = slugger();
5
+ const ids = new Map();
6
+ const used = new Set();
7
+ return {
8
+ get: (kind, identity, label = identity) => {
9
+ const key = JSON.stringify([kind, identity]);
10
+ const previous = ids.get(key);
11
+ if (previous !== undefined)
12
+ return previous;
13
+ const base = `scalar-${kind}-${label}`;
14
+ let id = slugs.slug(base);
15
+ // A literal suffix in a later name can collide with the slugger's numeric suffix.
16
+ while (used.has(id))
17
+ id = slugs.slug(base);
18
+ used.add(id);
19
+ ids.set(key, id);
20
+ return id;
21
+ },
22
+ };
23
+ };
24
+ /** Explicit destinations do not depend on a Markdown viewer's heading slug rules. */
25
+ export const anchor = (id) => ({ type: 'html', value: `<a id="${id}"></a>` });
@@ -0,0 +1,14 @@
1
+ import type { Code } from 'mdast';
2
+ import type { createDocumentAnchors } from './document-anchors.js';
3
+ import { type ExampleSource, getMarkdownExamples } from './get-markdown-examples.js';
4
+ /** Generation and destinations are shared only within one whole-document export. */
5
+ export type DocumentExamples = {
6
+ get: typeof getMarkdownExamples;
7
+ show: (source: ExampleSource, scope: string, code: Code) => {
8
+ id: string;
9
+ previous: boolean;
10
+ };
11
+ };
12
+ /** Reuse generation by schema and context; deduplicate only after comparing the serialized example. */
13
+ export declare const createDocumentExamples: (anchors: ReturnType<typeof createDocumentAnchors>) => DocumentExamples;
14
+ //# sourceMappingURL=document-examples.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-examples.d.ts","sourceRoot":"","sources":["../src/document-examples.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AAC/D,OAAO,EAAE,KAAK,aAAa,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAEjF,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,OAAO,mBAAmB,CAAA;IAC/B,IAAI,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAA;CAC9F,CAAA;AAaD,uGAAuG;AACvG,eAAO,MAAM,sBAAsB,GAAI,SAAS,UAAU,CAAC,OAAO,qBAAqB,CAAC,KAAG,gBAqC1F,CAAA"}
@@ -0,0 +1,54 @@
1
+ import { isObject } from '@scalar/helpers/object/is-object';
2
+ import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
3
+ import { getMarkdownExamples } from './get-markdown-examples.js';
4
+ const bookkeeping = new Set(['$ref', '$ref-value', '$global', '$status', '__scalar_']);
5
+ /** Sibling constraints make a reference a different example source from its target. */
6
+ const identity = (source) => {
7
+ if (!isObject(source.schema))
8
+ return undefined;
9
+ const schema = source.schema;
10
+ const target = '$ref' in schema && Object.keys(schema).every((key) => bookkeeping.has(key)) ? getResolvedRef(schema) : schema;
11
+ return isObject(target) ? target : schema;
12
+ };
13
+ /** Reuse generation by schema and context; deduplicate only after comparing the serialized example. */
14
+ export const createDocumentExamples = (anchors) => {
15
+ const cache = new Map();
16
+ const shown = new WeakMap();
17
+ let cachedCount = 0;
18
+ let nextId = 0;
19
+ return {
20
+ get: (...args) => {
21
+ const [source, ...settings] = args;
22
+ const schema = identity(source);
23
+ if (!schema || source.example !== undefined || Object.keys(source.examples ?? {}).length)
24
+ return getMarkdownExamples(...args);
25
+ const scope = JSON.stringify(settings);
26
+ const previous = cache.get(schema)?.get(scope);
27
+ if (previous)
28
+ return previous;
29
+ // Large APIs must not retain every generated object until the export finishes.
30
+ if (cachedCount >= 256) {
31
+ cache.clear();
32
+ cachedCount = 0;
33
+ }
34
+ const entries = cache.get(schema) ?? new Map();
35
+ const examples = getMarkdownExamples(...args);
36
+ entries.set(scope, examples);
37
+ cache.set(schema, entries);
38
+ cachedCount++;
39
+ return examples;
40
+ },
41
+ show: (source, scope, code) => {
42
+ const schema = identity(source);
43
+ const entries = schema ? (shown.get(schema) ?? new Map()) : new Map();
44
+ const previous = entries.get(scope);
45
+ if (previous?.value === code.value)
46
+ return { id: previous.id, previous: true };
47
+ const id = anchors.get('example', String(++nextId));
48
+ entries.set(scope, { id, value: code.value });
49
+ if (schema)
50
+ shown.set(schema, entries);
51
+ return { id, previous: false };
52
+ },
53
+ };
54
+ };
@@ -9,6 +9,8 @@ type MarkdownExample = {
9
9
  name?: string;
10
10
  summary?: string;
11
11
  description?: string;
12
+ /** Authored examples stay local; only synthesized fallbacks can be deduplicated. */
13
+ generated?: boolean;
12
14
  } & ({
13
15
  value: unknown;
14
16
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"AAMA,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,CACA;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CACpB,CAAA;AAYD;;;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,EAExB,6BAAqC,EACrC,gBAAc,KACb,eAAe,EA2CjB,CAAA"}
1
+ {"version":3,"file":"get-markdown-examples.d.ts","sourceRoot":"","sources":["../src/get-markdown-examples.ts"],"names":[],"mappings":"AAMA,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;IACpB,oFAAoF;IACpF,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,GAAG,CACA;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAClB;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,eAAe,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CACpB,CAAA;AAYD;;;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,EAExB,6BAAqC,EACrC,gBAAc,KACb,eAAe,EAyCjB,CAAA"}
@@ -95,18 +95,15 @@ schemaOpenapiVersion = openapiVersion, linked = false) => {
95
95
  return [];
96
96
  });
97
97
  }
98
- const schema = linked
99
- ? getResolvedRef(source.schema, mergeSiblingReferences)
100
- : getResolvedRef(source.schema);
98
+ const schema = getResolvedRef(source.schema, mergeSiblingReferences);
101
99
  if (!isObject(schema))
102
100
  return [];
103
- if (linked) {
104
- if (schema.example !== undefined)
105
- return [{ value: schema.example }];
106
- if (Array.isArray(schema.examples) && schema.examples.length)
107
- return schema.examples.map((value) => ({ value }));
108
- return [{ omitted: true }];
109
- }
101
+ if (schema.example !== undefined)
102
+ return [{ value: schema.example }];
103
+ if (Array.isArray(schema.examples) && schema.examples.length)
104
+ return schema.examples.map((value) => ({ value }));
105
+ if (linked)
106
+ return [];
110
107
  if (countGeneratedExampleValues(source.schema) > MAX_GENERATED_EXAMPLE_VALUES)
111
108
  return [{ omitted: true }];
112
109
  if (isXmlMediaType(mediaType)) {
@@ -115,11 +112,13 @@ schemaOpenapiVersion = openapiVersion, linked = false) => {
115
112
  openapiVersion: schemaOpenapiVersion,
116
113
  });
117
114
  return [
118
- result.xml === undefined ? { error: 'Unable to generate an XML example.' } : { serializedValue: result.xml },
115
+ result.xml === undefined
116
+ ? { error: 'Unable to generate an XML example.', generated: true }
117
+ : { serializedValue: result.xml, generated: true },
119
118
  ];
120
119
  }
121
120
  const value = getExampleFromSchema(getResolvedRef(source.schema, mergeSiblingReferences), {
122
121
  mode,
123
122
  });
124
- return value === undefined ? [] : [{ value }];
123
+ return value === undefined ? [] : [{ value, generated: true }];
125
124
  };
@@ -1,4 +1,20 @@
1
- import type { Heading, InlineCode, Link, List, ListItem, Paragraph, PhrasingContent, Strong, Text } from 'mdast';
1
+ import type { Emphasis, Heading, InlineCode, Link, List, ListItem, Node, Paragraph, PhrasingContent, Strong, Text } from 'mdast';
2
+ /**
3
+ * A description that still has to be parsed as Markdown. Schema rendering is synchronous and
4
+ * description parsing is not, so the page renderer replaces these before it serializes a page.
5
+ */
6
+ type DescriptionPlaceholder = Node & {
7
+ type: 'descriptionPlaceholder';
8
+ value: string;
9
+ };
10
+ declare module 'mdast' {
11
+ interface BlockContentMap {
12
+ descriptionPlaceholder: DescriptionPlaceholder;
13
+ }
14
+ interface RootContentMap {
15
+ descriptionPlaceholder: DescriptionPlaceholder;
16
+ }
17
+ }
2
18
  /** Collapse HTML-style inline whitespace; the serializer escapes generated text as Markdown. */
3
19
  export declare const text: (value: unknown) => Text;
4
20
  /** Inline code fences are chosen by the serializer. */
@@ -7,6 +23,8 @@ export declare const inlineCode: (value: unknown) => InlineCode;
7
23
  export declare const paragraph: (...children: PhrasingContent[]) => Paragraph;
8
24
  /** Construct an emphasized label. */
9
25
  export declare const strong: (...children: PhrasingContent[]) => Strong;
26
+ /** Construct emphasized text, for notes about the output itself. */
27
+ export declare const emphasis: (...children: PhrasingContent[]) => Emphasis;
10
28
  /** Construct a section heading. */
11
29
  export declare const heading: (depth: Heading["depth"], ...children: PhrasingContent[]) => Heading;
12
30
  /** Construct a list item that may contain nested blocks. */
@@ -22,4 +40,7 @@ export declare const safeUrl: (url: string) => string;
22
40
  export declare const link: (url: string, label: string) => Link;
23
41
  /** Render a metadata label and value with the established nonbreaking separator. */
24
42
  export declare const field: (label: string, value: PhrasingContent) => ListItem;
43
+ /** Defer a description, so it keeps its paragraphs, links and code blocks once parsed. */
44
+ export declare const describe: (value: unknown) => DescriptionPlaceholder[];
45
+ export {};
25
46
  //# sourceMappingURL=markdown-nodes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-nodes.d.ts","sourceRoot":"","sources":["../src/markdown-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEhH,gGAAgG;AAChG,eAAO,MAAM,IAAI,GAAI,OAAO,OAAO,KAAG,IAGpC,CAAA;AACF,uDAAuD;AACvD,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,UAAkE,CAAA;AAC9G,iDAAiD;AACjD,eAAO,MAAM,SAAS,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,SAA8C,CAAA;AACzG,qCAAqC;AACrC,eAAO,MAAM,MAAM,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,MAAwC,CAAA;AAChG,mCAAmC;AACnC,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,UAAU,eAAe,EAAE,KAAG,OAIhF,CAAA;AACF,4DAA4D;AAC5D,eAAO,MAAM,IAAI,GAAI,GAAG,UAAU,QAAQ,CAAC,UAAU,CAAC,KAAG,QAA2D,CAAA;AACpH,mCAAmC;AACnC,eAAO,MAAM,IAAI,GAAI,UAAU,QAAQ,EAAE,KAAG,IAAmE,CAAA;AAC/G;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,MAIrC,CAAA;AACD,8EAA8E;AAC9E,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAAsE,CAAA;AACxH,oFAAoF;AACpF,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,OAAO,eAAe,KAAG,QACK,CAAA"}
1
+ {"version":3,"file":"markdown-nodes.d.ts","sourceRoot":"","sources":["../src/markdown-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,OAAO,EACP,UAAU,EACV,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,eAAe,EACf,MAAM,EACN,IAAI,EACL,MAAM,OAAO,CAAA;AAEd;;;GAGG;AACH,KAAK,sBAAsB,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtF,OAAO,QAAQ,OAAO,CAAC;IACrB,UAAU,eAAe;QACvB,sBAAsB,EAAE,sBAAsB,CAAA;KAC/C;IACD,UAAU,cAAc;QACtB,sBAAsB,EAAE,sBAAsB,CAAA;KAC/C;CACF;AAED,gGAAgG;AAChG,eAAO,MAAM,IAAI,GAAI,OAAO,OAAO,KAAG,IAGpC,CAAA;AACF,uDAAuD;AACvD,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,UAAkE,CAAA;AAC9G,iDAAiD;AACjD,eAAO,MAAM,SAAS,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,SAA8C,CAAA;AACzG,qCAAqC;AACrC,eAAO,MAAM,MAAM,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,MAAwC,CAAA;AAChG,oEAAoE;AACpE,eAAO,MAAM,QAAQ,GAAI,GAAG,UAAU,eAAe,EAAE,KAAG,QAA4C,CAAA;AACtG,mCAAmC;AACnC,eAAO,MAAM,OAAO,GAAI,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,GAAG,UAAU,eAAe,EAAE,KAAG,OAIhF,CAAA;AACF,4DAA4D;AAC5D,eAAO,MAAM,IAAI,GAAI,GAAG,UAAU,QAAQ,CAAC,UAAU,CAAC,KAAG,QAA2D,CAAA;AACpH,mCAAmC;AACnC,eAAO,MAAM,IAAI,GAAI,UAAU,QAAQ,EAAE,KAAG,IAAmE,CAAA;AAC/G;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,MAIrC,CAAA;AACD,8EAA8E;AAC9E,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAAsE,CAAA;AACxH,oFAAoF;AACpF,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,OAAO,eAAe,KAAG,QACK,CAAA;AACnE,0FAA0F;AAC1F,eAAO,MAAM,QAAQ,GAAI,OAAO,OAAO,KAAG,sBAAsB,EAC8B,CAAA"}
@@ -9,6 +9,8 @@ export const inlineCode = (value) => ({ type: 'inlineCode', value: String(value
9
9
  export const paragraph = (...children) => ({ type: 'paragraph', children });
10
10
  /** Construct an emphasized label. */
11
11
  export const strong = (...children) => ({ type: 'strong', children });
12
+ /** Construct emphasized text, for notes about the output itself. */
13
+ export const emphasis = (...children) => ({ type: 'emphasis', children });
12
14
  /** Construct a section heading. */
13
15
  export const heading = (depth, ...children) => ({
14
16
  type: 'heading',
@@ -32,3 +34,5 @@ export const safeUrl = (url) => {
32
34
  export const link = (url, label) => ({ type: 'link', url: safeUrl(url), children: [text(label)] });
33
35
  /** Render a metadata label and value with the established nonbreaking separator. */
34
36
  export const field = (label, value) => item(paragraph(strong(text(`${label}:`)), text('\u00a0'), value));
37
+ /** Defer a description, so it keeps its paragraphs, links and code blocks once parsed. */
38
+ export const describe = (value) => typeof value === 'string' && value.trim() ? [{ type: 'descriptionPlaceholder', value }] : [];
@@ -3,4 +3,6 @@ import type { RootContent } from 'mdast';
3
3
  export type DescriptionParser = (value?: string) => Promise<RootContent[]>;
4
4
  /** Share parsing across pages while giving every rendered document its own reference namespace. */
5
5
  export declare const createDescriptionParser: () => (() => DescriptionParser);
6
+ /** Replace every deferred description in the given nodes with its parsed Markdown blocks. */
7
+ export declare const expandDescriptions: (nodes: RootContent[], description: DescriptionParser) => Promise<void>;
6
8
  //# sourceMappingURL=parse-description.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-description.d.ts","sourceRoot":"","sources":["../src/parse-description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,OAAO,CAAA;AA2ErD,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;AAE1E,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,QAAO,CAAC,MAAM,iBAAiB,CA0ClE,CAAA"}
1
+ {"version":3,"file":"parse-description.d.ts","sourceRoot":"","sources":["../src/parse-description.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAuB,WAAW,EAAE,MAAM,OAAO,CAAA;AAkF7D,0FAA0F;AAC1F,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAA;AAE1E,mGAAmG;AACnG,eAAO,MAAM,uBAAuB,QAAO,CAAC,MAAM,iBAAiB,CA4ClE,CAAA;AAED,6FAA6F;AAC7F,eAAO,MAAM,kBAAkB,GAAU,OAAO,WAAW,EAAE,EAAE,aAAa,iBAAiB,KAAG,OAAO,CAAC,IAAI,CAe3G,CAAA"}
@@ -68,10 +68,18 @@ const pruneDefinitions = (tree) => {
68
68
  return true;
69
69
  });
70
70
  };
71
+ /**
72
+ * A single line without Markdown punctuation, entities, autolinks or block markers parses to one
73
+ * paragraph containing the same text, so it does not need the parser.
74
+ */
75
+ const isPlainText = (value) => !/[\n\r\t\\`*_[\]<>#|~!&=]|:\/\/|www\.|^\s*(?:[-+]|\d+[.)])|^ {4}/.test(value) && value.trim().length > 0;
71
76
  /** Share parsing across pages while giving every rendered document its own reference namespace. */
72
77
  export const createDescriptionParser = () => {
73
78
  const cache = new Map();
74
79
  const parse = async (value) => {
80
+ // Most schema descriptions are one plain sentence. Parsing them costs more than the page's other text.
81
+ if (isPlainText(value))
82
+ return [{ type: 'paragraph', children: [{ type: 'text', value: value.trim() }] }];
75
83
  const parsed = parser.parse(value);
76
84
  // Raw HTML and GitHub alerts retain Scalar's existing conversion behavior.
77
85
  const tree = containsHtml(parsed) || /^\s*>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION|SUCCESS)\]/im.test(value)
@@ -103,7 +111,7 @@ export const createDescriptionParser = () => {
103
111
  const prefix = `description-${state.nextId++}-`;
104
112
  const cached = cache.get(value) ?? parse(value);
105
113
  if (!cache.has(value)) {
106
- if (cache.size >= 256)
114
+ if (cache.size >= 8192)
107
115
  cache.clear();
108
116
  cache.set(value, cached);
109
117
  }
@@ -114,3 +122,22 @@ export const createDescriptionParser = () => {
114
122
  };
115
123
  };
116
124
  };
125
+ /** Replace every deferred description in the given nodes with its parsed Markdown blocks. */
126
+ export const expandDescriptions = async (nodes, description) => {
127
+ const found = [];
128
+ const collect = (siblings) => {
129
+ for (const node of siblings) {
130
+ if (node.type === 'descriptionPlaceholder')
131
+ found.push({ siblings, node });
132
+ else if ('children' in node)
133
+ collect(node.children);
134
+ }
135
+ };
136
+ collect(nodes);
137
+ const parsed = await Promise.all(found.map(({ node }) => description(node.value)));
138
+ // Replace from the end, so earlier positions in a shared sibling list stay valid.
139
+ for (let index = found.length - 1; index >= 0; index--) {
140
+ const { siblings, node } = found[index];
141
+ siblings.splice(siblings.indexOf(node), 1, ...parsed[index]);
142
+ }
143
+ };
@@ -1,5 +1,5 @@
1
1
  import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
2
- import type { SchemaReferenceOptions } from './select-document.js';
2
+ import type { OpenApiRenderOptions } from './select-document.js';
3
3
  /** Build Markdown directly, retaining caches only for this immutable document snapshot. */
4
- export declare const createDocumentRenderer: () => ((document: OpenApiDocument, options?: SchemaReferenceOptions) => Promise<string>);
4
+ export declare const createDocumentRenderer: () => ((document: OpenApiDocument, options?: OpenApiRenderOptions) => Promise<string>);
5
5
  //# sourceMappingURL=render-document.d.ts.map
@@ -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;AAYpH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAI/D,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAO,CAAC,CACzC,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE,sBAAsB,KAC7B,OAAO,CAAC,MAAM,CAAC,CA+InB,CAAA"}
1
+ {"version":3,"file":"render-document.d.ts","sourceRoot":"","sources":["../src/render-document.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,8DAA8D,CAAA;AAcrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AA+C7D,2FAA2F;AAC3F,eAAO,MAAM,sBAAsB,QAAO,CAAC,CACzC,QAAQ,EAAE,eAAe,EACzB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,MAAM,CAAC,CAsPnB,CAAA"}