@kubb/adapter-oas 5.17.1 → 5.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { defineExtension } from "@kubb/core";
2
2
  import { Diagnostics, ast, childName, enumPrimitive, enumPropName, extractRefName, macroDedupeUnion, macroDiscriminatorEnum, macroEnumName, mergeAdjacentObjectsLazy } from "@kubb/kit";
3
- import { join } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { access, readFile } from "node:fs/promises";
5
6
  import { upgrade } from "@scalar/openapi-upgrader";
6
7
  import { bundle } from "api-ref-bundler";
@@ -19,7 +20,7 @@ var compatibility = {
19
20
  "node": ">=22.22"
20
21
  };
21
22
  // package.json
22
- var version = "5.17.1";
23
+ var version = "5.18.0";
23
24
  // src/constants.ts
24
25
  /**
25
26
  * Default parser options applied when no explicit options are provided.
@@ -401,6 +402,21 @@ function isOpenApi32(document) {
401
402
  const [major = 0, minor = 0] = String(document.openapi).split(".").map(Number);
402
403
  return major > 3 || major === 3 && minor >= 2;
403
404
  }
405
+ function pickStrings(source, keys) {
406
+ if (!source || typeof source !== "object") return void 0;
407
+ const entries = keys.flatMap((key) => {
408
+ const value = source[key];
409
+ return typeof value === "string" ? [[key, value]] : [];
410
+ });
411
+ return entries.length ? Object.fromEntries(entries) : void 0;
412
+ }
413
+ function getExternalDocs(source) {
414
+ const docs = pickStrings(source, ["url", "description"]);
415
+ return docs?.url ? {
416
+ url: docs.url,
417
+ ...docs.description !== void 0 && { description: docs.description }
418
+ } : void 0;
419
+ }
404
420
  /**
405
421
  * Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
406
422
  */
@@ -415,12 +431,12 @@ function isReference(obj) {
415
431
  return !!obj && typeof obj === "object" && "$ref" in obj;
416
432
  }
417
433
  /**
418
- * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
419
- * excluding the Swagger 2 string form.
434
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object that names a
435
+ * `propertyName`, excluding the Swagger 2 string form and a 3.2 discriminator that only sets `defaultMapping`.
420
436
  */
421
437
  function isDiscriminator(obj) {
422
- const record = obj;
423
- return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
438
+ const discriminator = obj?.["discriminator"];
439
+ return !!discriminator && typeof discriminator === "object" && typeof discriminator.propertyName === "string";
424
440
  }
425
441
  /**
426
442
  * Returns `true` when a schema is a raw binary payload.
@@ -593,6 +609,65 @@ function createRefs(document) {
593
609
  derefKeepingRef
594
610
  };
595
611
  }
612
+ // src/load/schemaIds.ts
613
+ const DATA_KEYS = /* @__PURE__ */ new Set([
614
+ "example",
615
+ "examples",
616
+ "const",
617
+ "enum",
618
+ "default"
619
+ ]);
620
+ const NAMED_ENTRY_KEYS = /* @__PURE__ */ new Set([
621
+ "properties",
622
+ "patternProperties",
623
+ "$defs",
624
+ "definitions",
625
+ "schemas",
626
+ "dependentSchemas"
627
+ ]);
628
+ function walk(node, pointer, visit, parentKey) {
629
+ if (!node || typeof node !== "object") return;
630
+ if (!Array.isArray(node)) visit(node, pointer);
631
+ const named = parentKey !== void 0 && NAMED_ENTRY_KEYS.has(parentKey);
632
+ for (const [key, value] of Object.entries(node)) {
633
+ if (!named && (DATA_KEYS.has(key) || key.startsWith("x-"))) continue;
634
+ walk(value, `${pointer}/${escapePointerToken(key)}`, visit, Array.isArray(node) ? parentKey : key);
635
+ }
636
+ }
637
+ /**
638
+ * The declaration in the nearest enclosing schema, else the only one, else `undefined`.
639
+ */
640
+ function pick(declarations, pointer) {
641
+ if (!declarations) return void 0;
642
+ const enclosing = declarations.filter((target) => pointer === target || pointer.startsWith(`${target}/`));
643
+ if (enclosing.length) return enclosing.reduce((a, b) => b.length > a.length ? b : a);
644
+ return declarations.length === 1 ? declarations[0] : void 0;
645
+ }
646
+ /**
647
+ * Rewrites, in place, each `$ref` to an `$id`, `$anchor` or `$dynamicAnchor`, and each `$dynamicRef`, into a
648
+ * `#/...` pointer, so it is not fetched. Instance data and `x-` extensions are left as written.
649
+ *
650
+ * shortcut: `$id` matches the `$ref` string exactly and `$dynamicRef` resolves statically to the nearest
651
+ * enclosing anchor, so a relative `$id` base or a recursively extended schema is not followed, upgrade when a spec needs that.
652
+ */
653
+ function resolveSchemaIds(document) {
654
+ const targets = /* @__PURE__ */ new Map();
655
+ const declare = (key, pointer) => targets.set(key, [...targets.get(key) ?? [], pointer]);
656
+ walk(document, "#", (node, pointer) => {
657
+ if (typeof node["$id"] === "string") declare(node["$id"], pointer);
658
+ for (const key of ["$anchor", "$dynamicAnchor"]) if (typeof node[key] === "string") declare(`#${node[key]}`, pointer);
659
+ });
660
+ if (!targets.size) return;
661
+ walk(document, "#", (node, pointer) => {
662
+ const ref = typeof node["$ref"] === "string" ? pick(targets.get(node["$ref"]), pointer) : void 0;
663
+ if (ref) node["$ref"] = ref;
664
+ const dynamicRef = typeof node["$dynamicRef"] === "string" ? pick(targets.get(node["$dynamicRef"]), pointer) : void 0;
665
+ if (dynamicRef && node["$ref"] === void 0) {
666
+ node["$ref"] = dynamicRef;
667
+ delete node["$dynamicRef"];
668
+ }
669
+ });
670
+ }
596
671
  // src/load/source.ts
597
672
  const urlRegExp = /^https?:\/+/i;
598
673
  /**
@@ -762,6 +837,21 @@ function isExtensionPath(path) {
762
837
  return path.some((token, index) => String(token).startsWith("x-") && !namedEntryKeys.has(String(path[index - 1])));
763
838
  }
764
839
  /**
840
+ * Returns the base for relative `$ref`s: the 3.2 `$self` resolved against `pathOrUrl`, else `pathOrUrl`.
841
+ */
842
+ function getBaseUri(pathOrUrl, root) {
843
+ const self = root && typeof root === "object" ? root.$self : void 0;
844
+ if (typeof self !== "string" || !self) return pathOrUrl;
845
+ try {
846
+ if (urlRegExp.test(self)) return new URL(self).href;
847
+ if (/^file:/i.test(self)) return fileURLToPath(self);
848
+ if (/^[a-z][a-z\d+.-]*:/i.test(self)) return pathOrUrl;
849
+ return urlRegExp.test(pathOrUrl) ? new URL(self, pathOrUrl).href : resolve(dirname(pathOrUrl), self);
850
+ } catch {
851
+ return pathOrUrl;
852
+ }
853
+ }
854
+ /**
765
855
  * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
766
856
  *
767
857
  * External file schemas are hoisted into named `components.schemas` entries, so a property
@@ -774,8 +864,9 @@ function isExtensionPath(path) {
774
864
  * all-internal document it is a no-op that still walks the whole tree to confirm that, which
775
865
  * costs real time on a large spec.
776
866
  *
777
- * `document` stands in for the file at `pathOrUrl`, such as inline input. Every other file or URL
778
- * read, including one that fails, lands in `sources`. A `$ref` that cannot be read reports
867
+ * `document` stands in for the file at `pathOrUrl`, such as inline input. Relative `$ref`s resolve
868
+ * against the OpenAPI 3.2 `$self` when the document sets one. Every other file or URL read,
869
+ * including one that fails, lands in `sources`. A `$ref` that cannot be read reports
779
870
  * `KUBB_REF_NOT_FOUND` at the key that holds it, as a warning when it sits inside an `x-` extension.
780
871
  *
781
872
  * @example Local file
@@ -787,8 +878,10 @@ function isExtensionPath(path) {
787
878
  async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
788
879
  const cache = /* @__PURE__ */ new Map();
789
880
  const toKey = (sourcePath) => urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
881
+ let entry = pathOrUrl;
882
+ let entryDocument = document;
790
883
  const resolver = (sourcePath) => {
791
- if (document && sourcePath === pathOrUrl) return Promise.resolve(document);
884
+ if (entryDocument !== void 0 && toKey(sourcePath) === toKey(entry)) return Promise.resolve(entryDocument);
792
885
  const key = toKey(sourcePath);
793
886
  const cached = cache.get(key);
794
887
  if (cached) return cached;
@@ -798,9 +891,12 @@ async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
798
891
  return result;
799
892
  };
800
893
  const root = await resolver(pathOrUrl);
894
+ resolveSchemaIds(root);
801
895
  if (typeof root === "object" && root !== null && !hasExternalRef(root)) return root;
802
- return await bundle(pathOrUrl, resolver, { hooks: { onError: (message, { path, state }) => {
803
- const pointer = toKey(state.baseFile) === toKey(pathOrUrl) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
896
+ entry = getBaseUri(pathOrUrl, root);
897
+ entryDocument = root;
898
+ return await bundle(entry, resolver, { hooks: { onError: (message, { path, state }) => {
899
+ const pointer = toKey(state.baseFile) === toKey(entry) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
804
900
  const diagnostic = {
805
901
  code: Diagnostics.code.refNotFound,
806
902
  severity: isExtensionPath(path) ? "warning" : "error",
@@ -839,10 +935,12 @@ async function parseFromConfig(source, sources = []) {
839
935
  origin: "the inline `input` string",
840
936
  help: "Fix the syntax at the reported line. Kubb reads `input` as an inline spec, not a file path, because it starts with `{`, `[`, `openapi:`, or `swagger:`, or spans several lines."
841
937
  }) : structuredClone(source.data);
842
- return source.root && hasExternalRef(data) ? bundleDocument(join(source.root, inlineInputName), {
938
+ if (source.root && hasExternalRef(data)) return bundleDocument(join(source.root, inlineInputName), {
843
939
  document: data,
844
940
  sources
845
- }) : data;
941
+ });
942
+ resolveSchemaIds(data);
943
+ return data;
846
944
  }
847
945
  await assertInputExists(source.path);
848
946
  return bundleDocument(source.path, { sources });
@@ -1167,6 +1265,80 @@ function getSchemas(document, { contentType }, refs) {
1167
1265
  pointers
1168
1266
  };
1169
1267
  }
1268
+ // src/model/security.ts
1269
+ const SCHEME_POINTER = "#/components/securitySchemes/";
1270
+ const API_KEY_LOCATIONS = /* @__PURE__ */ new Set([
1271
+ "header",
1272
+ "query",
1273
+ "cookie"
1274
+ ]);
1275
+ function getCredential(scheme) {
1276
+ if (scheme.type === "apiKey") return scheme.name && scheme.in && API_KEY_LOCATIONS.has(scheme.in) ? {
1277
+ type: "apiKey",
1278
+ in: scheme.in,
1279
+ name: scheme.name
1280
+ } : void 0;
1281
+ if (scheme.type === "http") return scheme.scheme ? {
1282
+ type: "http",
1283
+ scheme: scheme.scheme.toLowerCase(),
1284
+ ...scheme.bearerFormat && { bearerFormat: scheme.bearerFormat }
1285
+ } : void 0;
1286
+ if (scheme.type === "openIdConnect") return {
1287
+ type: "openIdConnect",
1288
+ ...scheme.openIdConnectUrl && { url: scheme.openIdConnectUrl }
1289
+ };
1290
+ if (scheme.type === "oauth2") {
1291
+ const flows = Object.entries(scheme.flows ?? {}).map(([name, flow]) => {
1292
+ const scopes = flow && typeof flow === "object" && flow.scopes && typeof flow.scopes === "object" ? { scopes: flow.scopes } : {};
1293
+ return [name, {
1294
+ ...pickStrings(flow, [
1295
+ "authorizationUrl",
1296
+ "deviceAuthorizationUrl",
1297
+ "tokenUrl",
1298
+ "refreshUrl"
1299
+ ]),
1300
+ ...scopes
1301
+ }];
1302
+ });
1303
+ return {
1304
+ type: "oauth2",
1305
+ flows: Object.fromEntries(flows),
1306
+ ...scheme.oauth2MetadataUrl && { metadataUrl: scheme.oauth2MetadataUrl }
1307
+ };
1308
+ }
1309
+ return scheme.type === "mutualTLS" ? { type: "mutualTLS" } : void 0;
1310
+ }
1311
+ /**
1312
+ * Maps `components.securitySchemes` to `AuthScheme` entries, leaving out a scheme a client cannot place.
1313
+ */
1314
+ function toAuthSchemes(schemes, refs) {
1315
+ return Object.entries(schemes ?? {}).flatMap(([name, value]) => {
1316
+ const scheme = refs.deref(value);
1317
+ const credential = scheme && getCredential(scheme);
1318
+ if (!scheme || !credential) return [];
1319
+ return [{
1320
+ name,
1321
+ ...scheme.description && { description: scheme.description },
1322
+ ...scheme.deprecated && { deprecated: true },
1323
+ credential
1324
+ }];
1325
+ });
1326
+ }
1327
+ /**
1328
+ * Maps security requirements to `AuthRequirement` entries. A 3.2 URI key becomes the scheme name.
1329
+ */
1330
+ function toAuthRequirements(requirements, document) {
1331
+ if (!Array.isArray(requirements)) return void 0;
1332
+ const schemes = document.components?.securitySchemes ?? {};
1333
+ const toName = (key) => {
1334
+ const index = key.indexOf(SCHEME_POINTER);
1335
+ return Object.hasOwn(schemes, key) || index === -1 ? key : unescapePointerToken(key.slice(index + 29));
1336
+ };
1337
+ return requirements.filter((requirement) => !!requirement && typeof requirement === "object").map((requirement) => Object.entries(requirement).map(([key, scopes]) => ({
1338
+ scheme: toName(key),
1339
+ ...Array.isArray(scopes) && scopes.length && { scopes: scopes.map(String) }
1340
+ })));
1341
+ }
1170
1342
  // src/model/server.ts
1171
1343
  /**
1172
1344
  * Reads the server URL from the document's `servers` array at `server.index`,
@@ -1223,8 +1395,11 @@ function resolveServerUrl(server, overrides) {
1223
1395
  * Maps OpenAPI servers to `InputServer` entries: drops entries without a `url` and fills `{variable}` defaults.
1224
1396
  */
1225
1397
  function toInputServers(servers) {
1226
- return servers.filter((entry) => entry.url).map(({ url, description, variables }) => {
1227
- const declared = variables && Object.keys(variables).length > 0 ? { variables } : {};
1398
+ return servers.filter((entry) => entry.url).map(({ url, name, description, variables }) => {
1399
+ const declared = {
1400
+ ...name !== void 0 && { name },
1401
+ ...variables && Object.keys(variables).length > 0 && { variables }
1402
+ };
1228
1403
  try {
1229
1404
  return {
1230
1405
  url: resolveServerUrl({
@@ -1243,6 +1418,20 @@ function toInputServers(servers) {
1243
1418
  }
1244
1419
  });
1245
1420
  }
1421
+ // src/model/tag.ts
1422
+ function toInputTags(tags) {
1423
+ return tags.filter((tag) => typeof tag.name === "string").map(({ name, summary, description, parent, kind, externalDocs }) => {
1424
+ const docs = getExternalDocs(externalDocs);
1425
+ return {
1426
+ name,
1427
+ ...typeof summary === "string" && { summary },
1428
+ ...typeof description === "string" && { description },
1429
+ ...typeof parent === "string" && { parent },
1430
+ ...typeof kind === "string" && { kind },
1431
+ ...docs && { externalDocs: docs }
1432
+ };
1433
+ });
1434
+ }
1246
1435
  // src/model/mediaType.ts
1247
1436
  /**
1248
1437
  * The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
@@ -1600,7 +1789,8 @@ const PARAMETER_STYLES = /* @__PURE__ */ new Set([
1600
1789
  "simple",
1601
1790
  "spaceDelimited",
1602
1791
  "pipeDelimited",
1603
- "deepObject"
1792
+ "deepObject",
1793
+ "cookie"
1604
1794
  ]);
1605
1795
  function pickStyleFields({ style, explode, allowReserved }) {
1606
1796
  return {
@@ -1609,16 +1799,45 @@ function pickStyleFields({ style, explode, allowReserved }) {
1609
1799
  ...allowReserved !== void 0 ? { allowReserved } : {}
1610
1800
  };
1611
1801
  }
1612
- /** The `encoding` of a media type, reduced to the entries that set a field Kubb carries. A `style` outside the OpenAPI set is dropped. */
1613
- function getEncoding(encoding) {
1614
- const entries = Object.entries(encoding ?? {}).flatMap(([property, { contentType, ...rest }]) => {
1615
- const node = {
1616
- ...contentType !== void 0 ? { contentType } : {},
1617
- ...pickStyleFields(rest)
1618
- };
1802
+ function getEncodingFields({ encoding, prefixEncoding, itemEncoding }) {
1803
+ const entries = Object.entries(encoding ?? {}).flatMap(([property, entry]) => {
1804
+ const node = toEncodingNode(entry);
1619
1805
  return Object.keys(node).length ? [[property, node]] : [];
1620
1806
  });
1621
- return entries.length ? Object.fromEntries(entries) : void 0;
1807
+ const item = itemEncoding && toEncodingNode(itemEncoding);
1808
+ return {
1809
+ ...entries.length && { encoding: Object.fromEntries(entries) },
1810
+ ...prefixEncoding?.length && { prefixEncoding: prefixEncoding.map(toEncodingNode) },
1811
+ ...item && Object.keys(item).length && { itemEncoding: item }
1812
+ };
1813
+ }
1814
+ function toEncodingNode({ contentType, encoding, prefixEncoding, itemEncoding, ...rest }) {
1815
+ return {
1816
+ ...contentType !== void 0 && { contentType },
1817
+ ...pickStyleFields(rest),
1818
+ ...getEncodingFields({
1819
+ encoding,
1820
+ prefixEncoding,
1821
+ itemEncoding
1822
+ })
1823
+ };
1824
+ }
1825
+ function getExamples({ example, examples }, refs) {
1826
+ const named = Object.entries(examples ?? {}).flatMap(([name, entry]) => {
1827
+ const resolved = refs.deref(entry);
1828
+ if (!resolved) return [];
1829
+ const { summary, description, dataValue, value, serializedValue } = resolved;
1830
+ const data = dataValue !== void 0 ? dataValue : value;
1831
+ return [{
1832
+ name,
1833
+ ...typeof summary === "string" && { summary },
1834
+ ...typeof description === "string" && { description },
1835
+ ...data !== void 0 && { value: data },
1836
+ ...typeof serializedValue === "string" && { serializedValue }
1837
+ }];
1838
+ });
1839
+ if (named.length) return { examples: named };
1840
+ return example !== void 0 ? { examples: [{ value: example }] } : {};
1622
1841
  }
1623
1842
  /**
1624
1843
  * Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
@@ -1647,6 +1866,24 @@ function getBodySchema({ content, contentType, refs, document }) {
1647
1866
  if (binary) return binary;
1648
1867
  return schema ? refs.derefKeepingRef(schema) : null;
1649
1868
  }
1869
+ function getLinks(links, refs) {
1870
+ const entries = Object.entries(links ?? {}).flatMap(([name, value]) => {
1871
+ const link = refs.deref(value);
1872
+ if (!link) return [];
1873
+ const server = link.server ? toInputServers([link.server])[0] : void 0;
1874
+ return [[name, {
1875
+ ...pickStrings(link, [
1876
+ "operationId",
1877
+ "operationRef",
1878
+ "description"
1879
+ ]),
1880
+ ...link.parameters && { parameters: link.parameters },
1881
+ ..."requestBody" in link && { requestBody: link.requestBody },
1882
+ ...server && { server }
1883
+ }]];
1884
+ });
1885
+ return entries.length ? Object.fromEntries(entries) : void 0;
1886
+ }
1650
1887
  /** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
1651
1888
  function getStreamField({ contentType, mediaType, refs, document, node }) {
1652
1889
  if (!isStream(contentType, mediaType)) return {};
@@ -1717,11 +1954,12 @@ function collectPropertyKeysByFlag(schema, flag) {
1717
1954
  function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
1718
1955
  const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
1719
1956
  const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
1720
- const rawSchema = param.schema ?? getMediaType({
1957
+ const mediaType = getMediaType({
1721
1958
  content: param.content,
1722
1959
  contentType,
1723
1960
  refs
1724
- })?.schema;
1961
+ });
1962
+ const rawSchema = param.schema ?? mediaType?.schema;
1725
1963
  const schema = rawSchema ? parseSchema({
1726
1964
  schema: rawSchema,
1727
1965
  name: schemaName
@@ -1737,7 +1975,10 @@ function parseParameter({ param, pointer, parentName, options, parseSchema, refs
1737
1975
  },
1738
1976
  required: param.required ?? false,
1739
1977
  ...pickStyleFields(param),
1740
- ...contentType !== void 0 ? { contentType } : {}
1978
+ ...contentType !== void 0 ? { contentType } : {},
1979
+ ...getExamples(mediaType ?? param, refs),
1980
+ ...param.deprecated && { deprecated: true },
1981
+ ...param.allowEmptyValue && { allowEmptyValue: true }
1741
1982
  });
1742
1983
  }
1743
1984
  /**
@@ -1782,12 +2023,12 @@ function parseOperation({ operation, ...ctx }) {
1782
2023
  name: requestBodyName
1783
2024
  }), bodyRequired);
1784
2025
  scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
1785
- const encoding = getEncoding(mediaType?.encoding);
1786
2026
  return [ast.factory.createContent({
1787
2027
  contentType: ct,
1788
2028
  schema: node,
1789
2029
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
1790
- ...encoding && { encoding },
2030
+ ...mediaType && getEncodingFields(mediaType),
2031
+ ...mediaType && getExamples(mediaType, refs),
1791
2032
  ...getStreamField({
1792
2033
  contentType: ct,
1793
2034
  mediaType,
@@ -1830,6 +2071,8 @@ function parseOperation({ operation, ...ctx }) {
1830
2071
  return {
1831
2072
  schema: node,
1832
2073
  keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
2074
+ ...mediaType && getEncodingFields(mediaType),
2075
+ ...mediaType && getExamples(mediaType, refs),
1833
2076
  ...ct ? getStreamField({
1834
2077
  contentType: ct,
1835
2078
  mediaType,
@@ -1848,11 +2091,32 @@ function parseOperation({ operation, ...ctx }) {
1848
2091
  contentType: "",
1849
2092
  ...parseEntrySchema(contentType)
1850
2093
  }));
2094
+ const responsePointer = `${pointer}/responses/${statusCode}`;
2095
+ const headers = Object.entries(response?.headers ?? {}).flatMap(([name, value]) => {
2096
+ const header = name.toLowerCase() === "content-type" ? null : refs.deref(value);
2097
+ if (!header) return [];
2098
+ return [parseParameter({
2099
+ param: {
2100
+ ...header,
2101
+ name,
2102
+ in: "header"
2103
+ },
2104
+ pointer: `${responsePointer}/headers/${escapePointerToken(name)}`,
2105
+ parentName: responseName,
2106
+ options,
2107
+ parseSchema,
2108
+ refs
2109
+ })];
2110
+ });
2111
+ const links = getLinks(response?.links, refs);
1851
2112
  return ast.factory.createResponse({
1852
- pointer: `${pointer}/responses/${statusCode}`,
2113
+ pointer: responsePointer,
1853
2114
  statusCode,
2115
+ summary: response?.summary,
1854
2116
  description: response?.description,
1855
- content: responseContent
2117
+ content: responseContent,
2118
+ ...headers.length && { headers },
2119
+ ...links && { links }
1856
2120
  });
1857
2121
  });
1858
2122
  const pickDoc = (key) => {
@@ -1862,6 +2126,7 @@ function parseOperation({ operation, ...ctx }) {
1862
2126
  return typeof fallback === "string" ? fallback : void 0;
1863
2127
  };
1864
2128
  const servers = operation.schema.servers ?? operation.pathItem.servers;
2129
+ const auth = toAuthRequirements(operation.schema.security ?? document.security, document);
1865
2130
  return ast.factory.createOperation({
1866
2131
  pointer,
1867
2132
  operationId,
@@ -1872,11 +2137,13 @@ function parseOperation({ operation, ...ctx }) {
1872
2137
  summary: pickDoc("summary") || void 0,
1873
2138
  description: pickDoc("description") || void 0,
1874
2139
  deprecated: operation.schema.deprecated || void 0,
2140
+ externalDocs: getExternalDocs(operation.schema.externalDocs),
1875
2141
  parameters,
1876
2142
  requestBody,
1877
2143
  responses,
1878
2144
  extensions: pickExtensions(operation.schema),
1879
- ...servers && { servers: toInputServers(servers) }
2145
+ ...servers && { servers: toInputServers(servers) },
2146
+ ...auth && { auth }
1880
2147
  });
1881
2148
  }
1882
2149
  // src/emit/createNode.ts
@@ -1897,6 +2164,7 @@ function createNode({ schema, name, nullable, defaultValue }, extras) {
1897
2164
  default: defaultValue,
1898
2165
  examples: extractExamples(schema),
1899
2166
  format: schema.format,
2167
+ externalDocs: getExternalDocs(schema.externalDocs),
1900
2168
  ...extras
1901
2169
  });
1902
2170
  }
@@ -2361,13 +2629,15 @@ const annotationKeys = /* @__PURE__ */ new Set([
2361
2629
  ]);
2362
2630
  /**
2363
2631
  * Moves `allOf` members that hold only annotations or `if`/`then`/`else` onto the parent, so they do
2364
- * not become `unknown`. The parent's own values win, and the conditional keywords are not enforced.
2632
+ * not become `unknown`. The parent's own values win. The parser reads their `if`/`then`/`else` into `conditionals`.
2365
2633
  */
2634
+ function isAnnotationMember(member) {
2635
+ return !!member && typeof member === "object" && !isReference(member) && Object.keys(member).every((key) => annotationKeys.has(key));
2636
+ }
2366
2637
  function mergeAnnotations(schema) {
2367
2638
  const members = schema.allOf;
2368
- const isAnnotation = (member) => !isReference(member) && Object.keys(member).every((key) => annotationKeys.has(key));
2369
- const annotations = members.filter(isAnnotation);
2370
- const rest = members.filter((member) => !isAnnotation(member));
2639
+ const annotations = members.filter(isAnnotationMember);
2640
+ const rest = members.filter((member) => !isAnnotationMember(member));
2371
2641
  return annotations.length && rest.length ? {
2372
2642
  ...Object.assign({}, ...annotations),
2373
2643
  ...schema,
@@ -2400,7 +2670,8 @@ function convertAllOf({ schema: original, name, nullable, defaultValue, parse, r
2400
2670
  default: mergedDefault,
2401
2671
  examples: extractExamples(schema) ?? memberNode.examples,
2402
2672
  pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
2403
- format: schema.format ?? memberNode.format
2673
+ format: schema.format ?? memberNode.format,
2674
+ externalDocs: getExternalDocs(schema.externalDocs) ?? memberNode.externalDocs
2404
2675
  });
2405
2676
  }
2406
2677
  const { members: discriminatedAllOf, discriminantValues } = extractDiscriminatedAllOfMembers({
@@ -2475,7 +2746,7 @@ function convertUnion(context) {
2475
2746
  };
2476
2747
  const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
2477
2748
  const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
2478
- const defaultRef = getDefaultMappingRef(discriminator);
2749
+ const defaultRef = getDefaultMappingRef(typeof schema.discriminator === "object" ? schema.discriminator : void 0);
2479
2750
  const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
2480
2751
  const strategy = schema.oneOf ? "one" : "any";
2481
2752
  const explicitDiscriminatorPropertyName = discriminator?.propertyName;
@@ -2587,11 +2858,18 @@ function getPrimitiveType(type) {
2587
2858
  }
2588
2859
  /** Shared by number, integer and bigint nodes; the exclusive bounds keep only the OAS 3.1 numeric form. */
2589
2860
  function getNumericConstraints(schema) {
2861
+ const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0;
2862
+ const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0;
2863
+ const hasExclusive = exclusiveMinimum !== void 0 || exclusiveMaximum !== void 0;
2590
2864
  return {
2591
2865
  min: schema.minimum,
2592
2866
  max: schema.maximum,
2593
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
2594
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
2867
+ ...hasExclusive && { exclusive: {
2868
+ ...exclusiveMinimum !== void 0 && { min: exclusiveMinimum },
2869
+ ...exclusiveMaximum !== void 0 && { max: exclusiveMaximum }
2870
+ } },
2871
+ exclusiveMinimum,
2872
+ exclusiveMaximum,
2595
2873
  multipleOf: schema.multipleOf
2596
2874
  };
2597
2875
  }
@@ -2600,11 +2878,11 @@ function getNumericConstraints(schema) {
2600
2878
  */
2601
2879
  function isWithinBounds({ value, schema }) {
2602
2880
  if (typeof value !== "number") return true;
2603
- const { min, max, exclusiveMinimum, exclusiveMaximum, multipleOf } = getNumericConstraints(schema);
2881
+ const { min, max, exclusive, multipleOf } = getNumericConstraints(schema);
2604
2882
  if (min !== void 0 && value < min) return false;
2605
2883
  if (max !== void 0 && value > max) return false;
2606
- if (exclusiveMinimum !== void 0 && value <= exclusiveMinimum) return false;
2607
- if (exclusiveMaximum !== void 0 && value >= exclusiveMaximum) return false;
2884
+ if (exclusive?.min !== void 0 && value <= exclusive.min) return false;
2885
+ if (exclusive?.max !== void 0 && value >= exclusive.max) return false;
2608
2886
  if (multipleOf !== void 0 && Math.abs(value / multipleOf - Math.round(value / multipleOf)) > 1e-9) return false;
2609
2887
  return true;
2610
2888
  }
@@ -2762,6 +3040,15 @@ function convertFormattedEnum(context) {
2762
3040
  }))
2763
3041
  });
2764
3042
  }
3043
+ function getStringContent({ schema, parse }) {
3044
+ const { contentMediaType: mediaType, contentEncoding: encoding, contentSchema } = schema;
3045
+ if (mediaType === void 0 && encoding === void 0 && !contentSchema) return {};
3046
+ return { content: {
3047
+ ...mediaType !== void 0 && { mediaType },
3048
+ ...encoding !== void 0 && { encoding },
3049
+ ...contentSchema && { schema: parse({ schema: contentSchema }) }
3050
+ } };
3051
+ }
2765
3052
  /**
2766
3053
  * Converts a `type: 'string'` schema into a `StringSchemaNode`.
2767
3054
  */
@@ -2772,7 +3059,8 @@ function convertString(context) {
2772
3059
  primitive: "string",
2773
3060
  min: schema.minLength,
2774
3061
  max: schema.maxLength,
2775
- pattern: schema.pattern
3062
+ pattern: schema.pattern,
3063
+ ...getStringContent(context)
2776
3064
  });
2777
3065
  }
2778
3066
  /**
@@ -2786,6 +3074,15 @@ function convertNumeric(context, type) {
2786
3074
  });
2787
3075
  }
2788
3076
  // src/emit/converters/structural.ts
3077
+ function getContains(schema, parse) {
3078
+ const { contains, minContains, maxContains } = schema;
3079
+ if (!contains || typeof contains !== "object") return {};
3080
+ return { contains: {
3081
+ schema: parse({ schema: contains }),
3082
+ ...typeof minContains === "number" && { min: minContains },
3083
+ ...typeof maxContains === "number" && { max: maxContains }
3084
+ } };
3085
+ }
2789
3086
  /**
2790
3087
  * Resolves a `true` or empty-object map schema (`additionalProperties`/`patternProperties`) to
2791
3088
  * `options.unknownType`, otherwise parses it as a regular schema.
@@ -2838,7 +3135,7 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
2838
3135
  required
2839
3136
  });
2840
3137
  }) : [];
2841
- const additionalProperties = schema.additionalProperties;
3138
+ const additionalProperties = schema.additionalProperties ?? schema.unevaluatedProperties;
2842
3139
  const additionalPropertiesNode = (() => {
2843
3140
  if (additionalProperties === true) return true;
2844
3141
  if (additionalProperties) return resolveMapSchema(additionalProperties, {
@@ -2868,6 +3165,8 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
2868
3165
  additionalProperties: additionalPropertiesNode,
2869
3166
  patternProperties,
2870
3167
  propertyNames,
3168
+ min: schema.minProperties,
3169
+ max: schema.maxProperties,
2871
3170
  minProperties: schema.minProperties,
2872
3171
  maxProperties: schema.maxProperties
2873
3172
  });
@@ -2898,7 +3197,8 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
2898
3197
  */
2899
3198
  function convertTuple({ schema, name, nullable, defaultValue, options, parse }) {
2900
3199
  const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }));
2901
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: schema.items });
3200
+ const tail = schema.items ?? schema.unevaluatedItems;
3201
+ const rest = tail === false ? void 0 : !tail || tail === true ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: tail });
2902
3202
  return createNode({
2903
3203
  schema,
2904
3204
  name,
@@ -2910,7 +3210,8 @@ function convertTuple({ schema, name, nullable, defaultValue, options, parse })
2910
3210
  items: tupleItems,
2911
3211
  rest,
2912
3212
  min: schema.minItems,
2913
- max: schema.maxItems
3213
+ max: schema.maxItems,
3214
+ ...getContains(schema, parse)
2914
3215
  });
2915
3216
  }
2916
3217
  /**
@@ -2934,7 +3235,8 @@ function convertArray({ schema, name, nullable, defaultValue, options, parse })
2934
3235
  items,
2935
3236
  min: schema.minItems,
2936
3237
  max: schema.maxItems,
2937
- unique: schema.uniqueItems ?? void 0
3238
+ unique: schema.uniqueItems ?? void 0,
3239
+ ...getContains(schema, parse)
2938
3240
  });
2939
3241
  }
2940
3242
  // src/emit/parseSchema.ts
@@ -3062,6 +3364,20 @@ const schemaRules = [
3062
3364
  ];
3063
3365
  // src/parser.ts
3064
3366
  /**
3367
+ * `unevaluated*` also sees what composition siblings evaluate, so with such a sibling it is dropped to keep the schema open.
3368
+ */
3369
+ function hasUnevaluatedSiblings(schema) {
3370
+ if (schema.unevaluatedProperties === void 0 && schema.unevaluatedItems === void 0) return false;
3371
+ return [
3372
+ schema.allOf,
3373
+ schema.anyOf,
3374
+ schema.oneOf,
3375
+ schema.if,
3376
+ schema.dependentSchemas,
3377
+ schema.$ref
3378
+ ].some((value) => value !== void 0);
3379
+ }
3380
+ /**
3065
3381
  * Creates the schema converter bound to one OpenAPI document: `parseSchema` dispatches each
3066
3382
  * schema through the ordered `schemaRules` table, with the parser options merged once.
3067
3383
  *
@@ -3079,6 +3395,13 @@ function createSchemaParser(ctx) {
3079
3395
  schema: flattenedSchema,
3080
3396
  name
3081
3397
  });
3398
+ if (hasUnevaluatedSiblings(schema)) {
3399
+ const { unevaluatedProperties: _properties, unevaluatedItems: _items, ...open } = schema;
3400
+ return parseSchema({
3401
+ schema: open,
3402
+ name
3403
+ });
3404
+ }
3082
3405
  const nullable = isNullable(schema) || void 0;
3083
3406
  const context = {
3084
3407
  schema,
@@ -3092,8 +3415,62 @@ function createSchemaParser(ctx) {
3092
3415
  refs,
3093
3416
  renames
3094
3417
  };
3095
- for (const rule of schemaRules) if (rule.match(context)) return rule.convert(context);
3096
- return createNode(context, { type: options.emptySchemaType });
3418
+ const rule = schemaRules.find((entry) => entry.match(context));
3419
+ return withConditionals(rule ? rule.convert(context) : createNode(context, { type: options.emptySchemaType }), schema);
3420
+ }
3421
+ function requireKeys(keys) {
3422
+ const unknown = ast.factory.createSchema({ type: ast.schemaTypes.unknown });
3423
+ return ast.factory.createSchema({
3424
+ type: ast.schemaTypes.object,
3425
+ primitive: "object",
3426
+ properties: keys.map((name) => ast.factory.createProperty({
3427
+ name,
3428
+ required: true,
3429
+ schema: unknown
3430
+ }))
3431
+ });
3432
+ }
3433
+ /**
3434
+ * Adds `if`/`then`/`else`, `dependentRequired` and `dependentSchemas` to the node as `conditionals`.
3435
+ */
3436
+ function withConditionals(node, schema) {
3437
+ const conditionals = [schema, ...Array.isArray(schema.allOf) ? schema.allOf.filter(isAnnotationMember) : []].flatMap(toConditionals);
3438
+ return conditionals.length ? {
3439
+ ...node,
3440
+ conditionals
3441
+ } : node;
3442
+ }
3443
+ function toConditionals(schema) {
3444
+ const branch = (value) => {
3445
+ if (!value || typeof value !== "object") return void 0;
3446
+ const { required, type, ...rest } = value;
3447
+ if (Array.isArray(required) && required.length && (type === void 0 || type === "object") && !Object.keys(rest).length) return requireKeys(required);
3448
+ return parseSchema({ schema: value });
3449
+ };
3450
+ const entries = (value) => value && typeof value === "object" ? Object.entries(value) : [];
3451
+ const conditionals = [];
3452
+ const condition = branch(schema.if);
3453
+ if (condition) {
3454
+ const thenNode = branch(schema.then);
3455
+ const elseNode = branch(schema.else);
3456
+ conditionals.push({
3457
+ if: condition,
3458
+ ...thenNode && { then: thenNode },
3459
+ ...elseNode && { else: elseNode }
3460
+ });
3461
+ }
3462
+ for (const [key, required] of entries(schema.dependentRequired)) if (Array.isArray(required) && required.length && required.every((name) => typeof name === "string")) conditionals.push({
3463
+ if: requireKeys([key]),
3464
+ then: requireKeys(required)
3465
+ });
3466
+ for (const [key, dependent] of entries(schema.dependentSchemas)) {
3467
+ const thenNode = branch(dependent);
3468
+ if (thenNode) conditionals.push({
3469
+ if: requireKeys([key]),
3470
+ then: thenNode
3471
+ });
3472
+ }
3473
+ return conditionals;
3097
3474
  }
3098
3475
  return parseSchema;
3099
3476
  }
@@ -3433,6 +3810,7 @@ const adapterOas = defineExtension({
3433
3810
  schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
3434
3811
  }
3435
3812
  const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
3813
+ const authSchemes = toAuthSchemes(document.components?.securitySchemes, refs);
3436
3814
  return ast.factory.createInput({
3437
3815
  schemas: schemaNodes,
3438
3816
  operations,
@@ -3440,23 +3818,25 @@ const adapterOas = defineExtension({
3440
3818
  title: document.info?.title,
3441
3819
  description: document.info?.description,
3442
3820
  version: document.info?.version,
3821
+ summary: document.info?.summary,
3822
+ termsOfService: document.info?.termsOfService,
3823
+ contact: pickStrings(document.info?.contact, [
3824
+ "name",
3825
+ "url",
3826
+ "email"
3827
+ ]),
3828
+ license: typeof document.info?.license?.name === "string" ? {
3829
+ ...pickStrings(document.info.license, ["identifier", "url"]),
3830
+ name: document.info.license.name
3831
+ } : void 0,
3832
+ externalDocs: getExternalDocs(document.externalDocs),
3443
3833
  baseURL: resolveBaseUrl({
3444
3834
  document,
3445
3835
  server
3446
3836
  }),
3447
3837
  servers: toInputServers(document.servers ?? []),
3448
- securitySchemes: Object.entries(document.components?.securitySchemes ?? {}).flatMap(([name, value]) => {
3449
- const scheme = refs.deref(value);
3450
- return scheme?.type ? [{
3451
- name,
3452
- type: scheme.type,
3453
- description: scheme.description,
3454
- scheme: scheme.scheme,
3455
- bearerFormat: scheme.bearerFormat,
3456
- in: scheme.in,
3457
- parameterName: scheme.name
3458
- }] : [];
3459
- }),
3838
+ ...document.tags?.length && { tags: toInputTags(document.tags) },
3839
+ ...authSchemes.length && { auth: authSchemes },
3460
3840
  extensions: pickExtensions(document)
3461
3841
  }
3462
3842
  });