@scalar/openapi-to-markdown 1.0.2 → 1.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/CHANGELOG.md +25 -0
- package/README.md +20 -4
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +8 -0
- package/dist/create-markdown-from-openapi.d.ts +0 -3
- package/dist/create-markdown-from-openapi.d.ts.map +1 -1
- package/dist/create-markdown-from-openapi.js +1 -18
- package/dist/get-markdown-examples.d.ts +22 -0
- package/dist/get-markdown-examples.d.ts.map +1 -0
- package/dist/get-markdown-examples.js +37 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/load-document.d.ts.map +1 -1
- package/dist/load-document.js +8 -1
- package/dist/render-document.d.ts.map +1 -1
- package/dist/render-document.js +3 -2
- package/dist/render-examples.d.ts +6 -0
- package/dist/render-examples.d.ts.map +1 -0
- package/dist/render-examples.js +37 -0
- package/dist/render-operation-details.d.ts +11 -0
- package/dist/render-operation-details.d.ts.map +1 -0
- package/dist/render-operation-details.js +85 -0
- package/dist/render-operation.d.ts.map +1 -1
- package/dist/render-operation.js +22 -10
- package/dist/render-schema.d.ts +35 -11
- package/dist/render-schema.d.ts.map +1 -1
- package/dist/render-schema.js +109 -59
- package/dist/restore-boolean-schemas.d.ts +7 -0
- package/dist/restore-boolean-schemas.d.ts.map +1 -0
- package/dist/restore-boolean-schemas.js +73 -0
- package/dist/select-document.d.ts +1 -1
- package/dist/select-document.d.ts.map +1 -1
- package/dist/select-document.js +1 -1
- package/package.json +13 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @scalar/openapi-to-markdown
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#10251](https://github.com/scalar/scalar/pull/10251): Preserve chained path-item references with non-enumerable links.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
- [#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.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#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.
|
|
16
|
+
- [#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.
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
22
|
+
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.
|
|
23
|
+
|
|
24
|
+
Read only own data properties during migration so inherited parameter lists, XML metadata, and reference targets cannot modify prototype-owned objects.
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
3
28
|
## 1.0.2
|
|
4
29
|
|
|
5
30
|
## 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
|
-
|
|
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"}
|
package/dist/browser.js
ADDED
|
@@ -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;
|
|
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,22 @@
|
|
|
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
|
+
/** Preserve supplied values; generate a fallback only when examples are not supplied. */
|
|
20
|
+
export declare const getMarkdownExamples: (source: ExampleSource, mediaType: string, mode?: "read" | "write", openapiVersion?: string) => MarkdownExample[];
|
|
21
|
+
export {};
|
|
22
|
+
//# 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,CAAC,CAAA;AAElF,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,aAAa,EACrB,WAAW,MAAM,EACjB,OAAO,MAAM,GAAG,OAAO,EACvB,uBAAwB,KACvB,eAAe,EA2BjB,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
/** Preserve supplied values; generate a fallback only when examples are not supplied. */
|
|
5
|
+
export const getMarkdownExamples = (source, mediaType, mode, openapiVersion = '3.2.0') => {
|
|
6
|
+
if (source.example !== undefined)
|
|
7
|
+
return [{ value: source.example }];
|
|
8
|
+
if (source.examples && Object.keys(source.examples).length) {
|
|
9
|
+
return Object.entries(source.examples).flatMap(([name, reference]) => {
|
|
10
|
+
const example = getResolvedRef(reference);
|
|
11
|
+
if (!isObject(example))
|
|
12
|
+
return [];
|
|
13
|
+
const metadata = {
|
|
14
|
+
name,
|
|
15
|
+
summary: typeof example.summary === 'string' ? example.summary : undefined,
|
|
16
|
+
description: typeof example.description === 'string' ? example.description : undefined,
|
|
17
|
+
};
|
|
18
|
+
if (example.value !== undefined)
|
|
19
|
+
return [{ ...metadata, value: example.value }];
|
|
20
|
+
if (/^3\.2\./.test(openapiVersion) && typeof example.serializedValue === 'string')
|
|
21
|
+
return [{ ...metadata, serializedValue: example.serializedValue }];
|
|
22
|
+
if (typeof example.externalValue === 'string')
|
|
23
|
+
return [{ ...metadata, externalValue: example.externalValue }];
|
|
24
|
+
if (/^3\.2\./.test(openapiVersion) && example.dataValue !== undefined)
|
|
25
|
+
return [{ ...metadata, value: example.dataValue }];
|
|
26
|
+
return [];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const schema = getResolvedRef(source.schema);
|
|
30
|
+
if (!isObject(schema))
|
|
31
|
+
return [];
|
|
32
|
+
const value = getExampleFromSchema(getResolvedRef(source.schema, mergeSiblingReferences), {
|
|
33
|
+
xml: mediaType.includes('xml'),
|
|
34
|
+
mode,
|
|
35
|
+
});
|
|
36
|
+
return value === undefined ? [] : [{ value }];
|
|
37
|
+
};
|
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 {
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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 {
|
|
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;
|
|
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"}
|
package/dist/load-document.js
CHANGED
|
@@ -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
|
|
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;
|
|
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,CAyIxF,CAAA"}
|
package/dist/render-document.js
CHANGED
|
@@ -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';
|
|
@@ -101,9 +102,9 @@ export const createDocumentRenderer = () => {
|
|
|
101
102
|
view.type
|
|
102
103
|
? field('Type', inlineCode(Array.isArray(view.type) ? view.type.join(' | ') : view.type))
|
|
103
104
|
: item(paragraph(strong(text('Type:')))),
|
|
104
|
-
]), ...(await description(view.description)), ...schemas.render(schema));
|
|
105
|
+
]), ...(await description(view.description)), ...schemas.render(schema, 0, [], { hideDescription: true }));
|
|
105
106
|
if (view.type === 'object')
|
|
106
|
-
nodes.push(
|
|
107
|
+
nodes.push(...(await renderExamples({ schema }, description, 'application/json', undefined, document['x-original-oas-version'] ?? document.openapi)));
|
|
107
108
|
flush();
|
|
108
109
|
}
|
|
109
110
|
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,CAiCvB,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
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 (example.summary)
|
|
10
|
+
nodes.push(paragraph(text(example.summary)));
|
|
11
|
+
nodes.push(...(await description(example.description)));
|
|
12
|
+
if ('externalValue' in example) {
|
|
13
|
+
nodes.push(paragraph(strong(text('External value:')), text(' '), link(example.externalValue, example.externalValue)));
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if ('serializedValue' in example) {
|
|
17
|
+
nodes.push({
|
|
18
|
+
type: 'code',
|
|
19
|
+
lang: mediaType.includes('xml') ? 'xml' : mediaType.includes('json') ? 'json' : 'text',
|
|
20
|
+
value: example.serializedValue,
|
|
21
|
+
});
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const xml = mediaType.includes('xml');
|
|
25
|
+
nodes.push({
|
|
26
|
+
type: 'code',
|
|
27
|
+
lang: xml ? 'xml' : 'json',
|
|
28
|
+
// XML strings are already serialized; primitives must not become object keys.
|
|
29
|
+
value: xml
|
|
30
|
+
? example.value !== null && typeof example.value === 'object'
|
|
31
|
+
? json2xml(example.value)
|
|
32
|
+
: String(example.value)
|
|
33
|
+
: (JSON.stringify(example.value, null, 2) ?? ''),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return nodes;
|
|
37
|
+
};
|
|
@@ -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;
|
|
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;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,CA2GvB,CAAA"}
|
package/dist/render-operation.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
2
2
|
import { field, heading, inlineCode, item, list, paragraph, strong, text } from './markdown-nodes.js';
|
|
3
|
+
import { renderExamples } from './render-examples.js';
|
|
4
|
+
import { renderEncoding, renderHeaders, renderResponseLinks } from './render-operation-details.js';
|
|
3
5
|
import { renderSecurity } from './render-security.js';
|
|
4
6
|
/** Render effective operation context without mutating the prepared document. */
|
|
5
7
|
export const renderOperation = async (document, path, method, pathItem, operation, webhook, { description, schemas }) => {
|
|
8
|
+
const openapiVersion = document['x-original-oas-version'] ?? document.openapi;
|
|
6
9
|
const stability = operation['x-scalar-stability'];
|
|
7
10
|
const title = (operation.summary || `${method.toUpperCase()} ${path}`) +
|
|
8
11
|
(stability ? ` (${stability})` : operation.deprecated ? ' ⚠️ Deprecated' : '');
|
|
@@ -10,6 +13,8 @@ export const renderOperation = async (document, path, method, pathItem, operatio
|
|
|
10
13
|
field('Method', inlineCode(method.toUpperCase())),
|
|
11
14
|
field(webhook ? 'Webhook' : 'Path', inlineCode(path)),
|
|
12
15
|
];
|
|
16
|
+
if (operation.operationId)
|
|
17
|
+
metadata.push(field('Operation ID', inlineCode(operation.operationId)));
|
|
13
18
|
if (operation.tags)
|
|
14
19
|
metadata.push(field('Tags', text(operation.tags.join(', '))));
|
|
15
20
|
if (stability)
|
|
@@ -52,23 +57,28 @@ export const renderOperation = async (document, path, method, pathItem, operatio
|
|
|
52
57
|
if ('allowReserved' in parameter && parameter.allowReserved)
|
|
53
58
|
fields.push(field('Allow Reserved', text('true')));
|
|
54
59
|
nodes.push(list(fields), ...(await description(parameter.description)));
|
|
55
|
-
if ('schema' in parameter && parameter.schema)
|
|
60
|
+
if ('schema' in parameter && parameter.schema !== undefined)
|
|
56
61
|
nodes.push(...schemas.render(parameter.schema));
|
|
62
|
+
if ('example' in parameter || 'examples' in parameter)
|
|
63
|
+
nodes.push(...(await renderExamples({ example: parameter.example, examples: parameter.examples }, description, 'application/json', 'write', openapiVersion)));
|
|
57
64
|
for (const [mediaType, content] of Object.entries('content' in parameter ? (parameter.content ?? {}) : {})) {
|
|
58
65
|
nodes.push(heading(6, text(`Content-Type: ${mediaType}`)));
|
|
59
|
-
if (content.schema)
|
|
66
|
+
if (content.schema !== undefined)
|
|
60
67
|
nodes.push(...schemas.render(content.schema));
|
|
68
|
+
nodes.push(...(await renderExamples(content, description, mediaType, 'write', openapiVersion)));
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
const body = getResolvedRef(operation.requestBody, mergeSiblingReferences);
|
|
64
|
-
if (body
|
|
72
|
+
if (body) {
|
|
65
73
|
nodes.push(heading(4, text('Request Body')), ...(await description(body.description)));
|
|
66
|
-
if (body.required)
|
|
67
|
-
nodes.push(paragraph(strong(text('Required:')), text('
|
|
68
|
-
for (const [mediaType, content] of Object.entries(body.content)) {
|
|
74
|
+
if (typeof body.required === 'boolean')
|
|
75
|
+
nodes.push(paragraph(strong(text('Required:')), text(' '), inlineCode(body.required)));
|
|
76
|
+
for (const [mediaType, content] of Object.entries(body.content ?? {})) {
|
|
69
77
|
nodes.push(heading(5, text(`Content-Type: ${mediaType}`)));
|
|
70
|
-
if (content.schema)
|
|
71
|
-
nodes.push(...schemas.render(content.schema)
|
|
78
|
+
if (content.schema !== undefined)
|
|
79
|
+
nodes.push(...schemas.render(content.schema));
|
|
80
|
+
nodes.push(...(await renderExamples(content, description, mediaType, 'write', openapiVersion)));
|
|
81
|
+
nodes.push(...(await renderEncoding(content.encoding, mediaType, description, schemas, openapiVersion)));
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
const responses = Object.entries(operation.responses ?? {}).flatMap(([status, reference]) => {
|
|
@@ -79,10 +89,12 @@ export const renderOperation = async (document, path, method, pathItem, operatio
|
|
|
79
89
|
nodes.push(heading(4, text('Responses')));
|
|
80
90
|
for (const { status, response } of responses) {
|
|
81
91
|
nodes.push(heading(5, text(`Status: ${status}${response.description ? ` ${response.description}` : ''}`)));
|
|
92
|
+
nodes.push(...(await renderHeaders(response.headers, description, schemas, openapiVersion)), ...(await renderResponseLinks(response.links, description)));
|
|
82
93
|
for (const [mediaType, content] of Object.entries(response.content ?? {})) {
|
|
83
94
|
nodes.push(heading(6, text(`Content-Type: ${mediaType}`)));
|
|
84
|
-
if (content.schema)
|
|
85
|
-
nodes.push(...schemas.render(content.schema)
|
|
95
|
+
if (content.schema !== undefined)
|
|
96
|
+
nodes.push(...schemas.render(content.schema));
|
|
97
|
+
nodes.push(...(await renderExamples(content, description, mediaType, 'read', openapiVersion)));
|
|
86
98
|
}
|
|
87
99
|
}
|
|
88
100
|
return nodes;
|
package/dist/render-schema.d.ts
CHANGED
|
@@ -1,29 +1,53 @@
|
|
|
1
1
|
import type { MaybeRefSchemaObject, SchemaObject } from '@scalar/workspace-store/schemas/v3.2/strict/schema';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RootContent } from 'mdast';
|
|
3
|
+
/** Boolean schemas must survive rendering without being coerced into empty objects. */
|
|
4
|
+
type MarkdownSchema = MaybeRefSchemaObject | boolean;
|
|
3
5
|
type SchemaView = {
|
|
4
|
-
schema: SchemaObject;
|
|
6
|
+
schema: SchemaObject | boolean;
|
|
5
7
|
title?: string;
|
|
6
8
|
description?: string;
|
|
7
9
|
type?: string | string[];
|
|
8
10
|
format?: string;
|
|
9
11
|
enum?: unknown[];
|
|
12
|
+
const?: unknown;
|
|
10
13
|
default?: unknown;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
readOnly?: boolean;
|
|
15
|
+
writeOnly?: boolean;
|
|
16
|
+
allOf?: MarkdownSchema[];
|
|
17
|
+
anyOf?: MarkdownSchema[];
|
|
18
|
+
oneOf?: MarkdownSchema[];
|
|
19
|
+
not?: MarkdownSchema;
|
|
20
|
+
properties: [string, MarkdownSchema][];
|
|
16
21
|
required: ReadonlySet<string>;
|
|
17
|
-
items?:
|
|
22
|
+
items?: MarkdownSchema;
|
|
18
23
|
minItems?: number;
|
|
19
24
|
maxItems?: number;
|
|
20
25
|
uniqueItems?: boolean;
|
|
26
|
+
minimum?: number;
|
|
27
|
+
maximum?: number;
|
|
28
|
+
exclusiveMinimum?: number;
|
|
29
|
+
exclusiveMaximum?: number;
|
|
30
|
+
multipleOf?: number;
|
|
31
|
+
minLength?: number;
|
|
32
|
+
maxLength?: number;
|
|
33
|
+
pattern?: string;
|
|
34
|
+
minProperties?: number;
|
|
35
|
+
maxProperties?: number;
|
|
36
|
+
additionalProperties?: MarkdownSchema;
|
|
37
|
+
discriminator?: {
|
|
38
|
+
propertyName: string;
|
|
39
|
+
mapping?: Record<string, string>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type RenderOptions = {
|
|
43
|
+
hideDescription?: boolean;
|
|
44
|
+
hideDetails?: boolean;
|
|
45
|
+
property?: boolean;
|
|
21
46
|
};
|
|
22
47
|
/** Schema normalization is cached separately from ancestry-dependent expansion. */
|
|
23
48
|
export type SchemaRenderer = {
|
|
24
|
-
view: (schema:
|
|
25
|
-
render: (schema:
|
|
26
|
-
example: (schema: MaybeRefSchemaObject, xml?: boolean) => Code;
|
|
49
|
+
view: (schema: MarkdownSchema) => SchemaView;
|
|
50
|
+
render: (schema: MarkdownSchema, depth?: number, ancestors?: readonly unknown[], options?: RenderOptions) => RootContent[];
|
|
27
51
|
};
|
|
28
52
|
/** Keep merged reference siblings and sorted properties stable throughout an export. */
|
|
29
53
|
export declare const createSchemaRenderer: () => SchemaRenderer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-schema.d.ts","sourceRoot":"","sources":["../src/render-schema.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"render-schema.d.ts","sourceRoot":"","sources":["../src/render-schema.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,oDAAoD,CAAA;AAC5G,OAAO,KAAK,EAA6B,WAAW,EAAE,MAAM,OAAO,CAAA;AAInE,uFAAuF;AACvF,KAAK,cAAc,GAAG,oBAAoB,GAAG,OAAO,CAAA;AAEpD,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,YAAY,GAAG,OAAO,CAAA;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE,cAAc,EAAE,CAAA;IACxB,GAAG,CAAC,EAAE,cAAc,CAAA;IACpB,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE,CAAA;IACtC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC7B,KAAK,CAAC,EAAE,cAAc,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oBAAoB,CAAC,EAAE,cAAc,CAAA;IACrC,aAAa,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;CAC3E,CAAA;AAED,KAAK,aAAa,GAAG;IACnB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,UAAU,CAAA;IAC5C,MAAM,EAAE,CACN,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,SAAS,OAAO,EAAE,EAC9B,OAAO,CAAC,EAAE,aAAa,KACpB,WAAW,EAAE,CAAA;CACnB,CAAA;AAiBD,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,QAAO,cA0IvC,CAAA"}
|
package/dist/render-schema.js
CHANGED
|
@@ -1,98 +1,148 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { resolve } from '@scalar/workspace-store/resolve';
|
|
1
|
+
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
|
+
import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
4
3
|
import { inlineCode, item, list, paragraph, strong, text } from './markdown-nodes.js';
|
|
4
|
+
/** Boolean targets still combine with adjacent schema keywords. */
|
|
5
|
+
const resolveMarkdownSchema = (input) => {
|
|
6
|
+
const target = getResolvedRef(input);
|
|
7
|
+
const merged = getResolvedRef(input, mergeSiblingReferences);
|
|
8
|
+
if (typeof target !== 'boolean')
|
|
9
|
+
return merged;
|
|
10
|
+
if (!isObject(merged) ||
|
|
11
|
+
!Object.keys(merged).some((key) => !['$ref', '$ref-value', '$global', '$status'].includes(key)))
|
|
12
|
+
return target;
|
|
13
|
+
if (target)
|
|
14
|
+
return merged;
|
|
15
|
+
// A false target remains impossible, even when siblings describe a type or annotations.
|
|
16
|
+
return { ...merged, allOf: [false, ...(Array.isArray(merged.allOf) ? merged.allOf : [])] };
|
|
17
|
+
};
|
|
5
18
|
/** Keep merged reference siblings and sorted properties stable throughout an export. */
|
|
6
19
|
export const createSchemaRenderer = () => {
|
|
7
20
|
const views = new WeakMap();
|
|
8
21
|
const view = (input) => {
|
|
9
|
-
const cached = views.get(input);
|
|
22
|
+
const cached = typeof input === 'object' ? views.get(input) : undefined;
|
|
10
23
|
if (cached)
|
|
11
24
|
return cached;
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
// Keep the linked document's nested identities and boolean schemas. Coercing a
|
|
26
|
+
// second time here would discard boolean children and copy recursive targets.
|
|
27
|
+
const schema = resolveMarkdownSchema(input);
|
|
28
|
+
const value = (typeof schema === 'object' ? schema : {});
|
|
14
29
|
const required = new Set(value.required ?? []);
|
|
15
30
|
const properties = Object.entries(value.properties ?? {})
|
|
16
|
-
.filter((
|
|
31
|
+
.filter(([, child]) => typeof child === 'boolean' || (child !== null && typeof child === 'object'))
|
|
17
32
|
.sort(([a], [b]) => Number(required.has(b)) - Number(required.has(a)) || a.localeCompare(b));
|
|
18
|
-
const result = {
|
|
19
|
-
|
|
33
|
+
const result = {
|
|
34
|
+
...value,
|
|
35
|
+
type: typeof schema === 'boolean' ? (schema ? 'any' : 'never') : value.type,
|
|
36
|
+
schema: schema,
|
|
37
|
+
required,
|
|
38
|
+
properties,
|
|
39
|
+
};
|
|
40
|
+
if (typeof input === 'object')
|
|
41
|
+
views.set(input, result);
|
|
20
42
|
return result;
|
|
21
43
|
};
|
|
22
|
-
const details = (value, property = false) => {
|
|
44
|
+
const details = (value, property = false, hideDescription = false, showType = true) => {
|
|
45
|
+
if (typeof value.schema === 'boolean')
|
|
46
|
+
return [text(value.schema ? 'any (true schema)' : 'never (false schema)')];
|
|
23
47
|
const type = Array.isArray(value.type) ? value.type.join(' | ') : value.type;
|
|
24
|
-
const nodes = type || property ? [inlineCode(type || 'object')] : [];
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
const nodes = showType && (type || property) ? [inlineCode(type || 'object')] : [];
|
|
49
|
+
const add = (label, entry) => {
|
|
50
|
+
if (entry !== undefined)
|
|
51
|
+
nodes.push(text(`${nodes.length ? ', ' : ''}${label}: `), inlineCode(entry));
|
|
52
|
+
};
|
|
53
|
+
add('format', value.format);
|
|
27
54
|
if (value.enum)
|
|
28
|
-
|
|
55
|
+
add('possible values', value.enum.map((entry) => JSON.stringify(entry)).join(', '));
|
|
56
|
+
if (value.const !== undefined)
|
|
57
|
+
add('const', JSON.stringify(value.const));
|
|
29
58
|
if (value.default !== undefined)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
59
|
+
add('default', JSON.stringify(value.default));
|
|
60
|
+
for (const key of [
|
|
61
|
+
'minimum',
|
|
62
|
+
'maximum',
|
|
63
|
+
'exclusiveMinimum',
|
|
64
|
+
'exclusiveMaximum',
|
|
65
|
+
'multipleOf',
|
|
66
|
+
'minLength',
|
|
67
|
+
'maxLength',
|
|
68
|
+
'pattern',
|
|
69
|
+
'minProperties',
|
|
70
|
+
'maxProperties',
|
|
71
|
+
])
|
|
72
|
+
add(key, value[key]);
|
|
73
|
+
for (const key of ['readOnly', 'writeOnly']) {
|
|
74
|
+
if (value[key])
|
|
75
|
+
nodes.push(text(`${nodes.length ? ', ' : ''}${key}`));
|
|
76
|
+
}
|
|
77
|
+
if (!hideDescription && value.description)
|
|
78
|
+
nodes.push(text(`${nodes.length ? ' — ' : ''}${value.description}`));
|
|
33
79
|
return nodes;
|
|
34
80
|
};
|
|
35
|
-
const render = (input, depth = 0, ancestors = []) => {
|
|
36
|
-
//
|
|
37
|
-
const identity =
|
|
38
|
-
if (
|
|
81
|
+
const render = (input, depth = 0, ancestors = [], options = {}) => {
|
|
82
|
+
// Follow the original target: merging reference siblings creates fresh objects.
|
|
83
|
+
const identity = getResolvedRef(input) ?? input;
|
|
84
|
+
if (typeof identity === 'object' && ancestors.includes(identity)) {
|
|
39
85
|
return [paragraph({ type: 'emphasis', children: [text('[Circular Reference]')] })];
|
|
40
86
|
}
|
|
87
|
+
if (depth >= 64)
|
|
88
|
+
return [paragraph(text('[Maximum schema depth reached]'))];
|
|
41
89
|
const value = view(input);
|
|
90
|
+
if (typeof value.schema === 'boolean')
|
|
91
|
+
return options.hideDetails ? [] : [paragraph(...details(value))];
|
|
42
92
|
const childAncestors = [...ancestors, identity];
|
|
93
|
+
const nodes = [];
|
|
43
94
|
for (const [key, label] of [
|
|
44
95
|
['allOf', 'All of:'],
|
|
45
96
|
['anyOf', 'Any of:'],
|
|
46
97
|
['oneOf', 'One of:'],
|
|
47
98
|
]) {
|
|
48
|
-
if (value[key])
|
|
49
|
-
|
|
50
|
-
paragraph(strong(text(label))),
|
|
51
|
-
...value[key].flatMap((child) => render(child, depth + 1, childAncestors)),
|
|
52
|
-
];
|
|
99
|
+
if (value[key]?.length)
|
|
100
|
+
nodes.push(paragraph(strong(text(label))), ...value[key].flatMap((child) => render(child, depth + 1, childAncestors)));
|
|
53
101
|
}
|
|
54
|
-
if (value.not)
|
|
55
|
-
|
|
56
|
-
|
|
102
|
+
if (value.not !== undefined)
|
|
103
|
+
nodes.push(paragraph(strong(text('Not:'))), ...render(value.not, depth + 1, childAncestors));
|
|
104
|
+
const array = value.type === 'array' || value.items !== undefined;
|
|
105
|
+
if (!options.hideDetails) {
|
|
106
|
+
// Child sections imply a single container type, but never its nullable alternatives.
|
|
107
|
+
const impliedType = (value.type === 'object' && value.properties.length > 0) ||
|
|
108
|
+
(value.type === 'array' && value.items !== undefined);
|
|
109
|
+
const annotations = details(value, false, options.hideDescription, !impliedType);
|
|
110
|
+
if (annotations.length)
|
|
111
|
+
nodes.push(paragraph(...annotations));
|
|
112
|
+
}
|
|
113
|
+
if (value.properties.length) {
|
|
57
114
|
const properties = value.properties.map(([name, schema]) => {
|
|
58
115
|
const child = view(schema);
|
|
59
116
|
const label = [inlineCode(name)];
|
|
60
117
|
if (value.required.has(name))
|
|
61
118
|
label.push(text(' (required)'));
|
|
62
119
|
const blocks = [paragraph(strong(...label)), paragraph(...details(child, true))];
|
|
63
|
-
|
|
64
|
-
blocks.push(...render(schema, depth + 1, childAncestors));
|
|
65
|
-
}
|
|
66
|
-
if (child.type === 'array' && child.items) {
|
|
67
|
-
blocks.push(paragraph(strong(text('Items:'))), ...render(child.items, depth + 1, childAncestors));
|
|
68
|
-
}
|
|
120
|
+
blocks.push(...render(schema, depth + 1, childAncestors, { hideDetails: true, property: true }));
|
|
69
121
|
return item(...blocks);
|
|
70
122
|
});
|
|
71
|
-
|
|
123
|
+
nodes.push(list(properties));
|
|
72
124
|
}
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
const constraints = [];
|
|
76
|
-
if (value.minItems !== undefined)
|
|
77
|
-
constraints.push(item(paragraph(text('Min items: '), inlineCode(value.minItems))));
|
|
78
|
-
if (value.maxItems !== undefined)
|
|
79
|
-
constraints.push(item(paragraph(text('Max items: '), inlineCode(value.maxItems))));
|
|
80
|
-
if (value.uniqueItems)
|
|
81
|
-
constraints.push(item(paragraph(text('Unique items: '), inlineCode(true))));
|
|
82
|
-
if (constraints.length)
|
|
83
|
-
nodes.push(list(constraints));
|
|
84
|
-
return nodes;
|
|
125
|
+
if (array && value.items !== undefined) {
|
|
126
|
+
nodes.push(paragraph(strong(text(options.property ? 'Items:' : 'Array of:'))), ...render(value.items, depth + 1, childAncestors));
|
|
85
127
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
128
|
+
const constraints = [];
|
|
129
|
+
if (value.minItems !== undefined)
|
|
130
|
+
constraints.push(item(paragraph(text('Min items: '), inlineCode(value.minItems))));
|
|
131
|
+
if (value.maxItems !== undefined)
|
|
132
|
+
constraints.push(item(paragraph(text('Max items: '), inlineCode(value.maxItems))));
|
|
133
|
+
if (value.uniqueItems !== undefined)
|
|
134
|
+
constraints.push(item(paragraph(text('Unique items: '), inlineCode(value.uniqueItems))));
|
|
135
|
+
if (constraints.length)
|
|
136
|
+
nodes.push(list(constraints));
|
|
137
|
+
if (value.additionalProperties !== undefined)
|
|
138
|
+
nodes.push(paragraph(strong(text('Additional properties:'))), ...render(value.additionalProperties, depth + 1, childAncestors));
|
|
139
|
+
if (value.discriminator) {
|
|
140
|
+
nodes.push(paragraph(strong(text('Discriminator:')), text(' '), inlineCode(value.discriminator.propertyName)));
|
|
141
|
+
const mappings = Object.entries(value.discriminator.mapping ?? {});
|
|
142
|
+
if (mappings.length)
|
|
143
|
+
nodes.push(list(mappings.map(([name, target]) => item(paragraph(inlineCode(name), text(': '), inlineCode(target))))));
|
|
144
|
+
}
|
|
145
|
+
return nodes;
|
|
96
146
|
};
|
|
97
|
-
return { view, render
|
|
147
|
+
return { view, render };
|
|
98
148
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workspace schema currently casts boolean JSON Schemas into empty objects.
|
|
3
|
+
* Restore them only in schema positions, never in example payloads or metadata.
|
|
4
|
+
* TODO: Remove this bridge when the shared schema accepts boolean JSON Schemas.
|
|
5
|
+
*/
|
|
6
|
+
export declare const restoreBooleanSchemas: (source: unknown, target: unknown) => void;
|
|
7
|
+
//# sourceMappingURL=restore-boolean-schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restore-boolean-schemas.d.ts","sourceRoot":"","sources":["../src/restore-boolean-schemas.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,OAAO,EAAE,QAAQ,OAAO,KAAG,IA0DxE,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
|
+
/**
|
|
3
|
+
* The workspace schema currently casts boolean JSON Schemas into empty objects.
|
|
4
|
+
* Restore them only in schema positions, never in example payloads or metadata.
|
|
5
|
+
* TODO: Remove this bridge when the shared schema accepts boolean JSON Schemas.
|
|
6
|
+
*/
|
|
7
|
+
export const restoreBooleanSchemas = (source, target) => {
|
|
8
|
+
const seen = new WeakSet();
|
|
9
|
+
const schemaMaps = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']);
|
|
10
|
+
const schemaArrays = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
|
|
11
|
+
const schemaFields = new Set([
|
|
12
|
+
'items',
|
|
13
|
+
'not',
|
|
14
|
+
'additionalProperties',
|
|
15
|
+
'additionalItems',
|
|
16
|
+
'contains',
|
|
17
|
+
'propertyNames',
|
|
18
|
+
'if',
|
|
19
|
+
'then',
|
|
20
|
+
'else',
|
|
21
|
+
'unevaluatedProperties',
|
|
22
|
+
'unevaluatedItems',
|
|
23
|
+
'$ref-value',
|
|
24
|
+
]);
|
|
25
|
+
const getChildContext = (context, key) => {
|
|
26
|
+
if (context === 'map')
|
|
27
|
+
return 'schema';
|
|
28
|
+
if (context === 'schema') {
|
|
29
|
+
if (schemaMaps.has(key))
|
|
30
|
+
return 'map';
|
|
31
|
+
if (schemaArrays.has(key) || schemaFields.has(key))
|
|
32
|
+
return 'schema';
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (key === 'schema' || key === 'itemSchema')
|
|
36
|
+
return 'schema';
|
|
37
|
+
if (key === 'schemas')
|
|
38
|
+
return 'map';
|
|
39
|
+
if (['example', 'examples', 'default', 'const', 'enum'].includes(key))
|
|
40
|
+
return undefined;
|
|
41
|
+
return 'document';
|
|
42
|
+
};
|
|
43
|
+
const visit = (original, coerced, context = 'document') => {
|
|
44
|
+
if (context === 'schema' && typeof original === 'boolean')
|
|
45
|
+
return original;
|
|
46
|
+
if (!original || typeof original !== 'object' || !coerced || typeof coerced !== 'object' || seen.has(coerced))
|
|
47
|
+
return coerced;
|
|
48
|
+
seen.add(coerced);
|
|
49
|
+
if (Array.isArray(original) && Array.isArray(coerced)) {
|
|
50
|
+
for (const [index, value] of original.entries()) {
|
|
51
|
+
const property = Object.getOwnPropertyDescriptor(coerced, index);
|
|
52
|
+
if (!property || !('value' in property))
|
|
53
|
+
continue;
|
|
54
|
+
Object.defineProperty(coerced, index, { ...property, value: visit(value, property.value, context) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (isObject(original) && isObject(coerced)) {
|
|
58
|
+
for (const [key, value] of Object.entries(original)) {
|
|
59
|
+
// Never follow inherited values or invoke setters, including __proto__.
|
|
60
|
+
// Defining an own data property still preserves those names in schema maps.
|
|
61
|
+
const property = Object.getOwnPropertyDescriptor(coerced, key);
|
|
62
|
+
if (!property || !('value' in property))
|
|
63
|
+
continue;
|
|
64
|
+
const childContext = getChildContext(context, key);
|
|
65
|
+
if (childContext === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
Object.defineProperty(coerced, key, { ...property, value: visit(value, property.value, childContext) });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return coerced;
|
|
71
|
+
};
|
|
72
|
+
visit(source, target);
|
|
73
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HttpMethod } from '@scalar/helpers/http/http-methods';
|
|
2
2
|
import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
|
|
3
3
|
/** Identify one operation by path and method, operation ID, or JSON pointer. */
|
|
4
4
|
export type OperationSelector = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select-document.d.ts","sourceRoot":"","sources":["../src/select-document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"select-document.d.ts","sourceRoot":"","sources":["../src/select-document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,mCAAmC,CAAA;AAIjF,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,8DAA8D,CAAA;AAEnH,gFAAgF;AAChF,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;CAC3C,GACD;IACE,WAAW,EAAE,MAAM,CAAA;CACpB,GACD;IACE,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AACL,2EAA2E;AAC3E,MAAM,MAAM,oBAAoB,GAC5B;KACG,GAAG,IAAI,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,aAAa,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,aAAa,EAAE,GAAG,CAAC;CACnH,CAAC,MAAM,aAAa,CAAC,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;AAE/C,KAAK,aAAa,GAAG;IACnB,SAAS,EAAE,iBAAiB,CAAA;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAA;KAAE,CAAA;IACrE,YAAY,EAAE,IAAI,CAAA;CACnB,CAAA;AAkKD,sEAAsE;AACtE,eAAO,MAAM,cAAc,GAAI,UAAU,eAAe,EAAE,UAAS,oBAAyB,KAAG,eA+M9F,CAAA"}
|
package/dist/select-document.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { HTTP_METHODS } from '@scalar/helpers/http/http-methods';
|
|
1
2
|
import { isObject } from '@scalar/helpers/object/is-object';
|
|
2
3
|
import { getPathItemOperation, getResolvedPathItem } from '@scalar/workspace-store/helpers/for-each-path-item-operation';
|
|
3
4
|
import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
|
|
4
|
-
const HTTP_METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
|
|
5
5
|
const HTTP_METHOD_SET = new Set(HTTP_METHODS);
|
|
6
6
|
const normalizeHttpMethod = (method) => {
|
|
7
7
|
const normalized = method.toLowerCase();
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"llm",
|
|
17
17
|
"swagger"
|
|
18
18
|
],
|
|
19
|
-
"version": "1.0
|
|
19
|
+
"version": "1.1.0",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"import": "./dist/index.js",
|
|
29
29
|
"types": "./dist/index.d.ts",
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./browser": {
|
|
33
|
+
"import": "./dist/browser.js",
|
|
34
|
+
"types": "./dist/browser.d.ts",
|
|
35
|
+
"default": "./dist/browser.js"
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
38
|
"files": [
|
|
@@ -35,26 +40,24 @@
|
|
|
35
40
|
"CHANGELOG.md"
|
|
36
41
|
],
|
|
37
42
|
"dependencies": {
|
|
38
|
-
"@scalar/code-highlight": "0.4.
|
|
39
|
-
"@scalar/helpers": "0.
|
|
40
|
-
"@scalar/json-magic": "0.
|
|
41
|
-
"@scalar/openapi-upgrader": "0.
|
|
42
|
-
"@scalar/workspace-store": "0.
|
|
43
|
+
"@scalar/code-highlight": "0.4.7",
|
|
44
|
+
"@scalar/helpers": "0.13.0",
|
|
45
|
+
"@scalar/json-magic": "0.15.0",
|
|
46
|
+
"@scalar/openapi-upgrader": "0.3.0",
|
|
47
|
+
"@scalar/workspace-store": "0.65.0",
|
|
43
48
|
"rehype-parse": "^9.0.1",
|
|
44
49
|
"rehype-remark": "^10.0.1",
|
|
45
50
|
"rehype-sanitize": "^6.0.0",
|
|
46
|
-
"rehype-stringify": "^10.0.1",
|
|
47
51
|
"remark-gfm": "^4.0.1",
|
|
48
52
|
"remark-parse": "^11.0.0",
|
|
49
|
-
"remark-rehype": "^11.1.2",
|
|
50
53
|
"remark-stringify": "^11.0.0",
|
|
51
54
|
"unified": "^11.0.5"
|
|
52
55
|
},
|
|
53
56
|
"devDependencies": {
|
|
54
|
-
"@hono/node-server": "^1.
|
|
57
|
+
"@hono/node-server": "^2.1.1",
|
|
55
58
|
"@scalar/galaxy": "0.7.1",
|
|
56
59
|
"@types/mdast": "^4.0.4",
|
|
57
|
-
"hono": "^4.13.
|
|
60
|
+
"hono": "^4.13.8",
|
|
58
61
|
"vitest": "4.1.10"
|
|
59
62
|
},
|
|
60
63
|
"scripts": {
|