@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.cjs CHANGED
@@ -23,6 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  let _kubb_core = require("@kubb/core");
24
24
  let _kubb_kit = require("@kubb/kit");
25
25
  let node_path = require("node:path");
26
+ let node_url = require("node:url");
26
27
  let node_fs_promises = require("node:fs/promises");
27
28
  let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
28
29
  let api_ref_bundler = require("api-ref-bundler");
@@ -35,7 +36,7 @@ var compatibility = {
35
36
  "node": ">=22.22"
36
37
  };
37
38
  // package.json
38
- var version = "5.17.1";
39
+ var version = "5.18.0";
39
40
  // src/constants.ts
40
41
  /**
41
42
  * Default parser options applied when no explicit options are provided.
@@ -417,6 +418,21 @@ function isOpenApi32(document) {
417
418
  const [major = 0, minor = 0] = String(document.openapi).split(".").map(Number);
418
419
  return major > 3 || major === 3 && minor >= 2;
419
420
  }
421
+ function pickStrings(source, keys) {
422
+ if (!source || typeof source !== "object") return void 0;
423
+ const entries = keys.flatMap((key) => {
424
+ const value = source[key];
425
+ return typeof value === "string" ? [[key, value]] : [];
426
+ });
427
+ return entries.length ? Object.fromEntries(entries) : void 0;
428
+ }
429
+ function getExternalDocs(source) {
430
+ const docs = pickStrings(source, ["url", "description"]);
431
+ return docs?.url ? {
432
+ url: docs.url,
433
+ ...docs.description !== void 0 && { description: docs.description }
434
+ } : void 0;
435
+ }
420
436
  /**
421
437
  * Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
422
438
  */
@@ -431,12 +447,12 @@ function isReference(obj) {
431
447
  return !!obj && typeof obj === "object" && "$ref" in obj;
432
448
  }
433
449
  /**
434
- * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
435
- * excluding the Swagger 2 string form.
450
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object that names a
451
+ * `propertyName`, excluding the Swagger 2 string form and a 3.2 discriminator that only sets `defaultMapping`.
436
452
  */
437
453
  function isDiscriminator(obj) {
438
- const record = obj;
439
- return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
454
+ const discriminator = obj?.["discriminator"];
455
+ return !!discriminator && typeof discriminator === "object" && typeof discriminator.propertyName === "string";
440
456
  }
441
457
  /**
442
458
  * Returns `true` when a schema is a raw binary payload.
@@ -609,6 +625,65 @@ function createRefs(document) {
609
625
  derefKeepingRef
610
626
  };
611
627
  }
628
+ // src/load/schemaIds.ts
629
+ const DATA_KEYS = /* @__PURE__ */ new Set([
630
+ "example",
631
+ "examples",
632
+ "const",
633
+ "enum",
634
+ "default"
635
+ ]);
636
+ const NAMED_ENTRY_KEYS = /* @__PURE__ */ new Set([
637
+ "properties",
638
+ "patternProperties",
639
+ "$defs",
640
+ "definitions",
641
+ "schemas",
642
+ "dependentSchemas"
643
+ ]);
644
+ function walk(node, pointer, visit, parentKey) {
645
+ if (!node || typeof node !== "object") return;
646
+ if (!Array.isArray(node)) visit(node, pointer);
647
+ const named = parentKey !== void 0 && NAMED_ENTRY_KEYS.has(parentKey);
648
+ for (const [key, value] of Object.entries(node)) {
649
+ if (!named && (DATA_KEYS.has(key) || key.startsWith("x-"))) continue;
650
+ walk(value, `${pointer}/${escapePointerToken(key)}`, visit, Array.isArray(node) ? parentKey : key);
651
+ }
652
+ }
653
+ /**
654
+ * The declaration in the nearest enclosing schema, else the only one, else `undefined`.
655
+ */
656
+ function pick(declarations, pointer) {
657
+ if (!declarations) return void 0;
658
+ const enclosing = declarations.filter((target) => pointer === target || pointer.startsWith(`${target}/`));
659
+ if (enclosing.length) return enclosing.reduce((a, b) => b.length > a.length ? b : a);
660
+ return declarations.length === 1 ? declarations[0] : void 0;
661
+ }
662
+ /**
663
+ * Rewrites, in place, each `$ref` to an `$id`, `$anchor` or `$dynamicAnchor`, and each `$dynamicRef`, into a
664
+ * `#/...` pointer, so it is not fetched. Instance data and `x-` extensions are left as written.
665
+ *
666
+ * shortcut: `$id` matches the `$ref` string exactly and `$dynamicRef` resolves statically to the nearest
667
+ * enclosing anchor, so a relative `$id` base or a recursively extended schema is not followed, upgrade when a spec needs that.
668
+ */
669
+ function resolveSchemaIds(document) {
670
+ const targets = /* @__PURE__ */ new Map();
671
+ const declare = (key, pointer) => targets.set(key, [...targets.get(key) ?? [], pointer]);
672
+ walk(document, "#", (node, pointer) => {
673
+ if (typeof node["$id"] === "string") declare(node["$id"], pointer);
674
+ for (const key of ["$anchor", "$dynamicAnchor"]) if (typeof node[key] === "string") declare(`#${node[key]}`, pointer);
675
+ });
676
+ if (!targets.size) return;
677
+ walk(document, "#", (node, pointer) => {
678
+ const ref = typeof node["$ref"] === "string" ? pick(targets.get(node["$ref"]), pointer) : void 0;
679
+ if (ref) node["$ref"] = ref;
680
+ const dynamicRef = typeof node["$dynamicRef"] === "string" ? pick(targets.get(node["$dynamicRef"]), pointer) : void 0;
681
+ if (dynamicRef && node["$ref"] === void 0) {
682
+ node["$ref"] = dynamicRef;
683
+ delete node["$dynamicRef"];
684
+ }
685
+ });
686
+ }
612
687
  // src/load/source.ts
613
688
  const urlRegExp = /^https?:\/+/i;
614
689
  /**
@@ -778,6 +853,21 @@ function isExtensionPath(path) {
778
853
  return path.some((token, index) => String(token).startsWith("x-") && !namedEntryKeys.has(String(path[index - 1])));
779
854
  }
780
855
  /**
856
+ * Returns the base for relative `$ref`s: the 3.2 `$self` resolved against `pathOrUrl`, else `pathOrUrl`.
857
+ */
858
+ function getBaseUri(pathOrUrl, root) {
859
+ const self = root && typeof root === "object" ? root.$self : void 0;
860
+ if (typeof self !== "string" || !self) return pathOrUrl;
861
+ try {
862
+ if (urlRegExp.test(self)) return new URL(self).href;
863
+ if (/^file:/i.test(self)) return (0, node_url.fileURLToPath)(self);
864
+ if (/^[a-z][a-z\d+.-]*:/i.test(self)) return pathOrUrl;
865
+ return urlRegExp.test(pathOrUrl) ? new URL(self, pathOrUrl).href : (0, node_path.resolve)((0, node_path.dirname)(pathOrUrl), self);
866
+ } catch {
867
+ return pathOrUrl;
868
+ }
869
+ }
870
+ /**
781
871
  * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
782
872
  *
783
873
  * External file schemas are hoisted into named `components.schemas` entries, so a property
@@ -790,8 +880,9 @@ function isExtensionPath(path) {
790
880
  * all-internal document it is a no-op that still walks the whole tree to confirm that, which
791
881
  * costs real time on a large spec.
792
882
  *
793
- * `document` stands in for the file at `pathOrUrl`, such as inline input. Every other file or URL
794
- * read, including one that fails, lands in `sources`. A `$ref` that cannot be read reports
883
+ * `document` stands in for the file at `pathOrUrl`, such as inline input. Relative `$ref`s resolve
884
+ * against the OpenAPI 3.2 `$self` when the document sets one. Every other file or URL read,
885
+ * including one that fails, lands in `sources`. A `$ref` that cannot be read reports
795
886
  * `KUBB_REF_NOT_FOUND` at the key that holds it, as a warning when it sits inside an `x-` extension.
796
887
  *
797
888
  * @example Local file
@@ -803,8 +894,10 @@ function isExtensionPath(path) {
803
894
  async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
804
895
  const cache = /* @__PURE__ */ new Map();
805
896
  const toKey = (sourcePath) => urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
897
+ let entry = pathOrUrl;
898
+ let entryDocument = document;
806
899
  const resolver = (sourcePath) => {
807
- if (document && sourcePath === pathOrUrl) return Promise.resolve(document);
900
+ if (entryDocument !== void 0 && toKey(sourcePath) === toKey(entry)) return Promise.resolve(entryDocument);
808
901
  const key = toKey(sourcePath);
809
902
  const cached = cache.get(key);
810
903
  if (cached) return cached;
@@ -814,9 +907,12 @@ async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
814
907
  return result;
815
908
  };
816
909
  const root = await resolver(pathOrUrl);
910
+ resolveSchemaIds(root);
817
911
  if (typeof root === "object" && root !== null && !hasExternalRef(root)) return root;
818
- return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver, { hooks: { onError: (message, { path, state }) => {
819
- const pointer = toKey(state.baseFile) === toKey(pathOrUrl) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
912
+ entry = getBaseUri(pathOrUrl, root);
913
+ entryDocument = root;
914
+ return await (0, api_ref_bundler.bundle)(entry, resolver, { hooks: { onError: (message, { path, state }) => {
915
+ const pointer = toKey(state.baseFile) === toKey(entry) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
820
916
  const diagnostic = {
821
917
  code: _kubb_kit.Diagnostics.code.refNotFound,
822
918
  severity: isExtensionPath(path) ? "warning" : "error",
@@ -855,10 +951,12 @@ async function parseFromConfig(source, sources = []) {
855
951
  origin: "the inline `input` string",
856
952
  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."
857
953
  }) : structuredClone(source.data);
858
- return source.root && hasExternalRef(data) ? bundleDocument((0, node_path.join)(source.root, inlineInputName), {
954
+ if (source.root && hasExternalRef(data)) return bundleDocument((0, node_path.join)(source.root, inlineInputName), {
859
955
  document: data,
860
956
  sources
861
- }) : data;
957
+ });
958
+ resolveSchemaIds(data);
959
+ return data;
862
960
  }
863
961
  await assertInputExists(source.path);
864
962
  return bundleDocument(source.path, { sources });
@@ -1183,6 +1281,80 @@ function getSchemas(document, { contentType }, refs) {
1183
1281
  pointers
1184
1282
  };
1185
1283
  }
1284
+ // src/model/security.ts
1285
+ const SCHEME_POINTER = "#/components/securitySchemes/";
1286
+ const API_KEY_LOCATIONS = /* @__PURE__ */ new Set([
1287
+ "header",
1288
+ "query",
1289
+ "cookie"
1290
+ ]);
1291
+ function getCredential(scheme) {
1292
+ if (scheme.type === "apiKey") return scheme.name && scheme.in && API_KEY_LOCATIONS.has(scheme.in) ? {
1293
+ type: "apiKey",
1294
+ in: scheme.in,
1295
+ name: scheme.name
1296
+ } : void 0;
1297
+ if (scheme.type === "http") return scheme.scheme ? {
1298
+ type: "http",
1299
+ scheme: scheme.scheme.toLowerCase(),
1300
+ ...scheme.bearerFormat && { bearerFormat: scheme.bearerFormat }
1301
+ } : void 0;
1302
+ if (scheme.type === "openIdConnect") return {
1303
+ type: "openIdConnect",
1304
+ ...scheme.openIdConnectUrl && { url: scheme.openIdConnectUrl }
1305
+ };
1306
+ if (scheme.type === "oauth2") {
1307
+ const flows = Object.entries(scheme.flows ?? {}).map(([name, flow]) => {
1308
+ const scopes = flow && typeof flow === "object" && flow.scopes && typeof flow.scopes === "object" ? { scopes: flow.scopes } : {};
1309
+ return [name, {
1310
+ ...pickStrings(flow, [
1311
+ "authorizationUrl",
1312
+ "deviceAuthorizationUrl",
1313
+ "tokenUrl",
1314
+ "refreshUrl"
1315
+ ]),
1316
+ ...scopes
1317
+ }];
1318
+ });
1319
+ return {
1320
+ type: "oauth2",
1321
+ flows: Object.fromEntries(flows),
1322
+ ...scheme.oauth2MetadataUrl && { metadataUrl: scheme.oauth2MetadataUrl }
1323
+ };
1324
+ }
1325
+ return scheme.type === "mutualTLS" ? { type: "mutualTLS" } : void 0;
1326
+ }
1327
+ /**
1328
+ * Maps `components.securitySchemes` to `AuthScheme` entries, leaving out a scheme a client cannot place.
1329
+ */
1330
+ function toAuthSchemes(schemes, refs) {
1331
+ return Object.entries(schemes ?? {}).flatMap(([name, value]) => {
1332
+ const scheme = refs.deref(value);
1333
+ const credential = scheme && getCredential(scheme);
1334
+ if (!scheme || !credential) return [];
1335
+ return [{
1336
+ name,
1337
+ ...scheme.description && { description: scheme.description },
1338
+ ...scheme.deprecated && { deprecated: true },
1339
+ credential
1340
+ }];
1341
+ });
1342
+ }
1343
+ /**
1344
+ * Maps security requirements to `AuthRequirement` entries. A 3.2 URI key becomes the scheme name.
1345
+ */
1346
+ function toAuthRequirements(requirements, document) {
1347
+ if (!Array.isArray(requirements)) return void 0;
1348
+ const schemes = document.components?.securitySchemes ?? {};
1349
+ const toName = (key) => {
1350
+ const index = key.indexOf(SCHEME_POINTER);
1351
+ return Object.hasOwn(schemes, key) || index === -1 ? key : unescapePointerToken(key.slice(index + 29));
1352
+ };
1353
+ return requirements.filter((requirement) => !!requirement && typeof requirement === "object").map((requirement) => Object.entries(requirement).map(([key, scopes]) => ({
1354
+ scheme: toName(key),
1355
+ ...Array.isArray(scopes) && scopes.length && { scopes: scopes.map(String) }
1356
+ })));
1357
+ }
1186
1358
  // src/model/server.ts
1187
1359
  /**
1188
1360
  * Reads the server URL from the document's `servers` array at `server.index`,
@@ -1239,8 +1411,11 @@ function resolveServerUrl(server, overrides) {
1239
1411
  * Maps OpenAPI servers to `InputServer` entries: drops entries without a `url` and fills `{variable}` defaults.
1240
1412
  */
1241
1413
  function toInputServers(servers) {
1242
- return servers.filter((entry) => entry.url).map(({ url, description, variables }) => {
1243
- const declared = variables && Object.keys(variables).length > 0 ? { variables } : {};
1414
+ return servers.filter((entry) => entry.url).map(({ url, name, description, variables }) => {
1415
+ const declared = {
1416
+ ...name !== void 0 && { name },
1417
+ ...variables && Object.keys(variables).length > 0 && { variables }
1418
+ };
1244
1419
  try {
1245
1420
  return {
1246
1421
  url: resolveServerUrl({
@@ -1259,6 +1434,20 @@ function toInputServers(servers) {
1259
1434
  }
1260
1435
  });
1261
1436
  }
1437
+ // src/model/tag.ts
1438
+ function toInputTags(tags) {
1439
+ return tags.filter((tag) => typeof tag.name === "string").map(({ name, summary, description, parent, kind, externalDocs }) => {
1440
+ const docs = getExternalDocs(externalDocs);
1441
+ return {
1442
+ name,
1443
+ ...typeof summary === "string" && { summary },
1444
+ ...typeof description === "string" && { description },
1445
+ ...typeof parent === "string" && { parent },
1446
+ ...typeof kind === "string" && { kind },
1447
+ ...docs && { externalDocs: docs }
1448
+ };
1449
+ });
1450
+ }
1262
1451
  // src/model/mediaType.ts
1263
1452
  /**
1264
1453
  * The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
@@ -1616,7 +1805,8 @@ const PARAMETER_STYLES = /* @__PURE__ */ new Set([
1616
1805
  "simple",
1617
1806
  "spaceDelimited",
1618
1807
  "pipeDelimited",
1619
- "deepObject"
1808
+ "deepObject",
1809
+ "cookie"
1620
1810
  ]);
1621
1811
  function pickStyleFields({ style, explode, allowReserved }) {
1622
1812
  return {
@@ -1625,16 +1815,45 @@ function pickStyleFields({ style, explode, allowReserved }) {
1625
1815
  ...allowReserved !== void 0 ? { allowReserved } : {}
1626
1816
  };
1627
1817
  }
1628
- /** The `encoding` of a media type, reduced to the entries that set a field Kubb carries. A `style` outside the OpenAPI set is dropped. */
1629
- function getEncoding(encoding) {
1630
- const entries = Object.entries(encoding ?? {}).flatMap(([property, { contentType, ...rest }]) => {
1631
- const node = {
1632
- ...contentType !== void 0 ? { contentType } : {},
1633
- ...pickStyleFields(rest)
1634
- };
1818
+ function getEncodingFields({ encoding, prefixEncoding, itemEncoding }) {
1819
+ const entries = Object.entries(encoding ?? {}).flatMap(([property, entry]) => {
1820
+ const node = toEncodingNode(entry);
1635
1821
  return Object.keys(node).length ? [[property, node]] : [];
1636
1822
  });
1637
- return entries.length ? Object.fromEntries(entries) : void 0;
1823
+ const item = itemEncoding && toEncodingNode(itemEncoding);
1824
+ return {
1825
+ ...entries.length && { encoding: Object.fromEntries(entries) },
1826
+ ...prefixEncoding?.length && { prefixEncoding: prefixEncoding.map(toEncodingNode) },
1827
+ ...item && Object.keys(item).length && { itemEncoding: item }
1828
+ };
1829
+ }
1830
+ function toEncodingNode({ contentType, encoding, prefixEncoding, itemEncoding, ...rest }) {
1831
+ return {
1832
+ ...contentType !== void 0 && { contentType },
1833
+ ...pickStyleFields(rest),
1834
+ ...getEncodingFields({
1835
+ encoding,
1836
+ prefixEncoding,
1837
+ itemEncoding
1838
+ })
1839
+ };
1840
+ }
1841
+ function getExamples({ example, examples }, refs) {
1842
+ const named = Object.entries(examples ?? {}).flatMap(([name, entry]) => {
1843
+ const resolved = refs.deref(entry);
1844
+ if (!resolved) return [];
1845
+ const { summary, description, dataValue, value, serializedValue } = resolved;
1846
+ const data = dataValue !== void 0 ? dataValue : value;
1847
+ return [{
1848
+ name,
1849
+ ...typeof summary === "string" && { summary },
1850
+ ...typeof description === "string" && { description },
1851
+ ...data !== void 0 && { value: data },
1852
+ ...typeof serializedValue === "string" && { serializedValue }
1853
+ }];
1854
+ });
1855
+ if (named.length) return { examples: named };
1856
+ return example !== void 0 ? { examples: [{ value: example }] } : {};
1638
1857
  }
1639
1858
  /**
1640
1859
  * Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
@@ -1663,6 +1882,24 @@ function getBodySchema({ content, contentType, refs, document }) {
1663
1882
  if (binary) return binary;
1664
1883
  return schema ? refs.derefKeepingRef(schema) : null;
1665
1884
  }
1885
+ function getLinks(links, refs) {
1886
+ const entries = Object.entries(links ?? {}).flatMap(([name, value]) => {
1887
+ const link = refs.deref(value);
1888
+ if (!link) return [];
1889
+ const server = link.server ? toInputServers([link.server])[0] : void 0;
1890
+ return [[name, {
1891
+ ...pickStrings(link, [
1892
+ "operationId",
1893
+ "operationRef",
1894
+ "description"
1895
+ ]),
1896
+ ...link.parameters && { parameters: link.parameters },
1897
+ ..."requestBody" in link && { requestBody: link.requestBody },
1898
+ ...server && { server }
1899
+ }]];
1900
+ });
1901
+ return entries.length ? Object.fromEntries(entries) : void 0;
1902
+ }
1666
1903
  /** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
1667
1904
  function getStreamField({ contentType, mediaType, refs, document, node }) {
1668
1905
  if (!isStream(contentType, mediaType)) return {};
@@ -1733,11 +1970,12 @@ function collectPropertyKeysByFlag(schema, flag) {
1733
1970
  function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
1734
1971
  const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
1735
1972
  const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
1736
- const rawSchema = param.schema ?? getMediaType({
1973
+ const mediaType = getMediaType({
1737
1974
  content: param.content,
1738
1975
  contentType,
1739
1976
  refs
1740
- })?.schema;
1977
+ });
1978
+ const rawSchema = param.schema ?? mediaType?.schema;
1741
1979
  const schema = rawSchema ? parseSchema({
1742
1980
  schema: rawSchema,
1743
1981
  name: schemaName
@@ -1753,7 +1991,10 @@ function parseParameter({ param, pointer, parentName, options, parseSchema, refs
1753
1991
  },
1754
1992
  required: param.required ?? false,
1755
1993
  ...pickStyleFields(param),
1756
- ...contentType !== void 0 ? { contentType } : {}
1994
+ ...contentType !== void 0 ? { contentType } : {},
1995
+ ...getExamples(mediaType ?? param, refs),
1996
+ ...param.deprecated && { deprecated: true },
1997
+ ...param.allowEmptyValue && { allowEmptyValue: true }
1757
1998
  });
1758
1999
  }
1759
2000
  /**
@@ -1798,12 +2039,12 @@ function parseOperation({ operation, ...ctx }) {
1798
2039
  name: requestBodyName
1799
2040
  }), bodyRequired);
1800
2041
  scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
1801
- const encoding = getEncoding(mediaType?.encoding);
1802
2042
  return [_kubb_kit.ast.factory.createContent({
1803
2043
  contentType: ct,
1804
2044
  schema: node,
1805
2045
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
1806
- ...encoding && { encoding },
2046
+ ...mediaType && getEncodingFields(mediaType),
2047
+ ...mediaType && getExamples(mediaType, refs),
1807
2048
  ...getStreamField({
1808
2049
  contentType: ct,
1809
2050
  mediaType,
@@ -1846,6 +2087,8 @@ function parseOperation({ operation, ...ctx }) {
1846
2087
  return {
1847
2088
  schema: node,
1848
2089
  keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
2090
+ ...mediaType && getEncodingFields(mediaType),
2091
+ ...mediaType && getExamples(mediaType, refs),
1849
2092
  ...ct ? getStreamField({
1850
2093
  contentType: ct,
1851
2094
  mediaType,
@@ -1864,11 +2107,32 @@ function parseOperation({ operation, ...ctx }) {
1864
2107
  contentType: "",
1865
2108
  ...parseEntrySchema(contentType)
1866
2109
  }));
2110
+ const responsePointer = `${pointer}/responses/${statusCode}`;
2111
+ const headers = Object.entries(response?.headers ?? {}).flatMap(([name, value]) => {
2112
+ const header = name.toLowerCase() === "content-type" ? null : refs.deref(value);
2113
+ if (!header) return [];
2114
+ return [parseParameter({
2115
+ param: {
2116
+ ...header,
2117
+ name,
2118
+ in: "header"
2119
+ },
2120
+ pointer: `${responsePointer}/headers/${escapePointerToken(name)}`,
2121
+ parentName: responseName,
2122
+ options,
2123
+ parseSchema,
2124
+ refs
2125
+ })];
2126
+ });
2127
+ const links = getLinks(response?.links, refs);
1867
2128
  return _kubb_kit.ast.factory.createResponse({
1868
- pointer: `${pointer}/responses/${statusCode}`,
2129
+ pointer: responsePointer,
1869
2130
  statusCode,
2131
+ summary: response?.summary,
1870
2132
  description: response?.description,
1871
- content: responseContent
2133
+ content: responseContent,
2134
+ ...headers.length && { headers },
2135
+ ...links && { links }
1872
2136
  });
1873
2137
  });
1874
2138
  const pickDoc = (key) => {
@@ -1878,6 +2142,7 @@ function parseOperation({ operation, ...ctx }) {
1878
2142
  return typeof fallback === "string" ? fallback : void 0;
1879
2143
  };
1880
2144
  const servers = operation.schema.servers ?? operation.pathItem.servers;
2145
+ const auth = toAuthRequirements(operation.schema.security ?? document.security, document);
1881
2146
  return _kubb_kit.ast.factory.createOperation({
1882
2147
  pointer,
1883
2148
  operationId,
@@ -1888,11 +2153,13 @@ function parseOperation({ operation, ...ctx }) {
1888
2153
  summary: pickDoc("summary") || void 0,
1889
2154
  description: pickDoc("description") || void 0,
1890
2155
  deprecated: operation.schema.deprecated || void 0,
2156
+ externalDocs: getExternalDocs(operation.schema.externalDocs),
1891
2157
  parameters,
1892
2158
  requestBody,
1893
2159
  responses,
1894
2160
  extensions: pickExtensions(operation.schema),
1895
- ...servers && { servers: toInputServers(servers) }
2161
+ ...servers && { servers: toInputServers(servers) },
2162
+ ...auth && { auth }
1896
2163
  });
1897
2164
  }
1898
2165
  // src/emit/createNode.ts
@@ -1913,6 +2180,7 @@ function createNode({ schema, name, nullable, defaultValue }, extras) {
1913
2180
  default: defaultValue,
1914
2181
  examples: extractExamples(schema),
1915
2182
  format: schema.format,
2183
+ externalDocs: getExternalDocs(schema.externalDocs),
1916
2184
  ...extras
1917
2185
  });
1918
2186
  }
@@ -2377,13 +2645,15 @@ const annotationKeys = /* @__PURE__ */ new Set([
2377
2645
  ]);
2378
2646
  /**
2379
2647
  * Moves `allOf` members that hold only annotations or `if`/`then`/`else` onto the parent, so they do
2380
- * not become `unknown`. The parent's own values win, and the conditional keywords are not enforced.
2648
+ * not become `unknown`. The parent's own values win. The parser reads their `if`/`then`/`else` into `conditionals`.
2381
2649
  */
2650
+ function isAnnotationMember(member) {
2651
+ return !!member && typeof member === "object" && !isReference(member) && Object.keys(member).every((key) => annotationKeys.has(key));
2652
+ }
2382
2653
  function mergeAnnotations(schema) {
2383
2654
  const members = schema.allOf;
2384
- const isAnnotation = (member) => !isReference(member) && Object.keys(member).every((key) => annotationKeys.has(key));
2385
- const annotations = members.filter(isAnnotation);
2386
- const rest = members.filter((member) => !isAnnotation(member));
2655
+ const annotations = members.filter(isAnnotationMember);
2656
+ const rest = members.filter((member) => !isAnnotationMember(member));
2387
2657
  return annotations.length && rest.length ? {
2388
2658
  ...Object.assign({}, ...annotations),
2389
2659
  ...schema,
@@ -2416,7 +2686,8 @@ function convertAllOf({ schema: original, name, nullable, defaultValue, parse, r
2416
2686
  default: mergedDefault,
2417
2687
  examples: extractExamples(schema) ?? memberNode.examples,
2418
2688
  pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
2419
- format: schema.format ?? memberNode.format
2689
+ format: schema.format ?? memberNode.format,
2690
+ externalDocs: getExternalDocs(schema.externalDocs) ?? memberNode.externalDocs
2420
2691
  });
2421
2692
  }
2422
2693
  const { members: discriminatedAllOf, discriminantValues } = extractDiscriminatedAllOfMembers({
@@ -2491,7 +2762,7 @@ function convertUnion(context) {
2491
2762
  };
2492
2763
  const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
2493
2764
  const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
2494
- const defaultRef = getDefaultMappingRef(discriminator);
2765
+ const defaultRef = getDefaultMappingRef(typeof schema.discriminator === "object" ? schema.discriminator : void 0);
2495
2766
  const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
2496
2767
  const strategy = schema.oneOf ? "one" : "any";
2497
2768
  const explicitDiscriminatorPropertyName = discriminator?.propertyName;
@@ -2603,11 +2874,18 @@ function getPrimitiveType(type) {
2603
2874
  }
2604
2875
  /** Shared by number, integer and bigint nodes; the exclusive bounds keep only the OAS 3.1 numeric form. */
2605
2876
  function getNumericConstraints(schema) {
2877
+ const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0;
2878
+ const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0;
2879
+ const hasExclusive = exclusiveMinimum !== void 0 || exclusiveMaximum !== void 0;
2606
2880
  return {
2607
2881
  min: schema.minimum,
2608
2882
  max: schema.maximum,
2609
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
2610
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
2883
+ ...hasExclusive && { exclusive: {
2884
+ ...exclusiveMinimum !== void 0 && { min: exclusiveMinimum },
2885
+ ...exclusiveMaximum !== void 0 && { max: exclusiveMaximum }
2886
+ } },
2887
+ exclusiveMinimum,
2888
+ exclusiveMaximum,
2611
2889
  multipleOf: schema.multipleOf
2612
2890
  };
2613
2891
  }
@@ -2616,11 +2894,11 @@ function getNumericConstraints(schema) {
2616
2894
  */
2617
2895
  function isWithinBounds({ value, schema }) {
2618
2896
  if (typeof value !== "number") return true;
2619
- const { min, max, exclusiveMinimum, exclusiveMaximum, multipleOf } = getNumericConstraints(schema);
2897
+ const { min, max, exclusive, multipleOf } = getNumericConstraints(schema);
2620
2898
  if (min !== void 0 && value < min) return false;
2621
2899
  if (max !== void 0 && value > max) return false;
2622
- if (exclusiveMinimum !== void 0 && value <= exclusiveMinimum) return false;
2623
- if (exclusiveMaximum !== void 0 && value >= exclusiveMaximum) return false;
2900
+ if (exclusive?.min !== void 0 && value <= exclusive.min) return false;
2901
+ if (exclusive?.max !== void 0 && value >= exclusive.max) return false;
2624
2902
  if (multipleOf !== void 0 && Math.abs(value / multipleOf - Math.round(value / multipleOf)) > 1e-9) return false;
2625
2903
  return true;
2626
2904
  }
@@ -2778,6 +3056,15 @@ function convertFormattedEnum(context) {
2778
3056
  }))
2779
3057
  });
2780
3058
  }
3059
+ function getStringContent({ schema, parse }) {
3060
+ const { contentMediaType: mediaType, contentEncoding: encoding, contentSchema } = schema;
3061
+ if (mediaType === void 0 && encoding === void 0 && !contentSchema) return {};
3062
+ return { content: {
3063
+ ...mediaType !== void 0 && { mediaType },
3064
+ ...encoding !== void 0 && { encoding },
3065
+ ...contentSchema && { schema: parse({ schema: contentSchema }) }
3066
+ } };
3067
+ }
2781
3068
  /**
2782
3069
  * Converts a `type: 'string'` schema into a `StringSchemaNode`.
2783
3070
  */
@@ -2788,7 +3075,8 @@ function convertString(context) {
2788
3075
  primitive: "string",
2789
3076
  min: schema.minLength,
2790
3077
  max: schema.maxLength,
2791
- pattern: schema.pattern
3078
+ pattern: schema.pattern,
3079
+ ...getStringContent(context)
2792
3080
  });
2793
3081
  }
2794
3082
  /**
@@ -2802,6 +3090,15 @@ function convertNumeric(context, type) {
2802
3090
  });
2803
3091
  }
2804
3092
  // src/emit/converters/structural.ts
3093
+ function getContains(schema, parse) {
3094
+ const { contains, minContains, maxContains } = schema;
3095
+ if (!contains || typeof contains !== "object") return {};
3096
+ return { contains: {
3097
+ schema: parse({ schema: contains }),
3098
+ ...typeof minContains === "number" && { min: minContains },
3099
+ ...typeof maxContains === "number" && { max: maxContains }
3100
+ } };
3101
+ }
2805
3102
  /**
2806
3103
  * Resolves a `true` or empty-object map schema (`additionalProperties`/`patternProperties`) to
2807
3104
  * `options.unknownType`, otherwise parses it as a regular schema.
@@ -2854,7 +3151,7 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
2854
3151
  required
2855
3152
  });
2856
3153
  }) : [];
2857
- const additionalProperties = schema.additionalProperties;
3154
+ const additionalProperties = schema.additionalProperties ?? schema.unevaluatedProperties;
2858
3155
  const additionalPropertiesNode = (() => {
2859
3156
  if (additionalProperties === true) return true;
2860
3157
  if (additionalProperties) return resolveMapSchema(additionalProperties, {
@@ -2884,6 +3181,8 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
2884
3181
  additionalProperties: additionalPropertiesNode,
2885
3182
  patternProperties,
2886
3183
  propertyNames,
3184
+ min: schema.minProperties,
3185
+ max: schema.maxProperties,
2887
3186
  minProperties: schema.minProperties,
2888
3187
  maxProperties: schema.maxProperties
2889
3188
  });
@@ -2914,7 +3213,8 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
2914
3213
  */
2915
3214
  function convertTuple({ schema, name, nullable, defaultValue, options, parse }) {
2916
3215
  const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }));
2917
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? _kubb_kit.ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: schema.items });
3216
+ const tail = schema.items ?? schema.unevaluatedItems;
3217
+ const rest = tail === false ? void 0 : !tail || tail === true ? _kubb_kit.ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: tail });
2918
3218
  return createNode({
2919
3219
  schema,
2920
3220
  name,
@@ -2926,7 +3226,8 @@ function convertTuple({ schema, name, nullable, defaultValue, options, parse })
2926
3226
  items: tupleItems,
2927
3227
  rest,
2928
3228
  min: schema.minItems,
2929
- max: schema.maxItems
3229
+ max: schema.maxItems,
3230
+ ...getContains(schema, parse)
2930
3231
  });
2931
3232
  }
2932
3233
  /**
@@ -2950,7 +3251,8 @@ function convertArray({ schema, name, nullable, defaultValue, options, parse })
2950
3251
  items,
2951
3252
  min: schema.minItems,
2952
3253
  max: schema.maxItems,
2953
- unique: schema.uniqueItems ?? void 0
3254
+ unique: schema.uniqueItems ?? void 0,
3255
+ ...getContains(schema, parse)
2954
3256
  });
2955
3257
  }
2956
3258
  // src/emit/parseSchema.ts
@@ -3078,6 +3380,20 @@ const schemaRules = [
3078
3380
  ];
3079
3381
  // src/parser.ts
3080
3382
  /**
3383
+ * `unevaluated*` also sees what composition siblings evaluate, so with such a sibling it is dropped to keep the schema open.
3384
+ */
3385
+ function hasUnevaluatedSiblings(schema) {
3386
+ if (schema.unevaluatedProperties === void 0 && schema.unevaluatedItems === void 0) return false;
3387
+ return [
3388
+ schema.allOf,
3389
+ schema.anyOf,
3390
+ schema.oneOf,
3391
+ schema.if,
3392
+ schema.dependentSchemas,
3393
+ schema.$ref
3394
+ ].some((value) => value !== void 0);
3395
+ }
3396
+ /**
3081
3397
  * Creates the schema converter bound to one OpenAPI document: `parseSchema` dispatches each
3082
3398
  * schema through the ordered `schemaRules` table, with the parser options merged once.
3083
3399
  *
@@ -3095,6 +3411,13 @@ function createSchemaParser(ctx) {
3095
3411
  schema: flattenedSchema,
3096
3412
  name
3097
3413
  });
3414
+ if (hasUnevaluatedSiblings(schema)) {
3415
+ const { unevaluatedProperties: _properties, unevaluatedItems: _items, ...open } = schema;
3416
+ return parseSchema({
3417
+ schema: open,
3418
+ name
3419
+ });
3420
+ }
3098
3421
  const nullable = isNullable(schema) || void 0;
3099
3422
  const context = {
3100
3423
  schema,
@@ -3108,8 +3431,62 @@ function createSchemaParser(ctx) {
3108
3431
  refs,
3109
3432
  renames
3110
3433
  };
3111
- for (const rule of schemaRules) if (rule.match(context)) return rule.convert(context);
3112
- return createNode(context, { type: options.emptySchemaType });
3434
+ const rule = schemaRules.find((entry) => entry.match(context));
3435
+ return withConditionals(rule ? rule.convert(context) : createNode(context, { type: options.emptySchemaType }), schema);
3436
+ }
3437
+ function requireKeys(keys) {
3438
+ const unknown = _kubb_kit.ast.factory.createSchema({ type: _kubb_kit.ast.schemaTypes.unknown });
3439
+ return _kubb_kit.ast.factory.createSchema({
3440
+ type: _kubb_kit.ast.schemaTypes.object,
3441
+ primitive: "object",
3442
+ properties: keys.map((name) => _kubb_kit.ast.factory.createProperty({
3443
+ name,
3444
+ required: true,
3445
+ schema: unknown
3446
+ }))
3447
+ });
3448
+ }
3449
+ /**
3450
+ * Adds `if`/`then`/`else`, `dependentRequired` and `dependentSchemas` to the node as `conditionals`.
3451
+ */
3452
+ function withConditionals(node, schema) {
3453
+ const conditionals = [schema, ...Array.isArray(schema.allOf) ? schema.allOf.filter(isAnnotationMember) : []].flatMap(toConditionals);
3454
+ return conditionals.length ? {
3455
+ ...node,
3456
+ conditionals
3457
+ } : node;
3458
+ }
3459
+ function toConditionals(schema) {
3460
+ const branch = (value) => {
3461
+ if (!value || typeof value !== "object") return void 0;
3462
+ const { required, type, ...rest } = value;
3463
+ if (Array.isArray(required) && required.length && (type === void 0 || type === "object") && !Object.keys(rest).length) return requireKeys(required);
3464
+ return parseSchema({ schema: value });
3465
+ };
3466
+ const entries = (value) => value && typeof value === "object" ? Object.entries(value) : [];
3467
+ const conditionals = [];
3468
+ const condition = branch(schema.if);
3469
+ if (condition) {
3470
+ const thenNode = branch(schema.then);
3471
+ const elseNode = branch(schema.else);
3472
+ conditionals.push({
3473
+ if: condition,
3474
+ ...thenNode && { then: thenNode },
3475
+ ...elseNode && { else: elseNode }
3476
+ });
3477
+ }
3478
+ for (const [key, required] of entries(schema.dependentRequired)) if (Array.isArray(required) && required.length && required.every((name) => typeof name === "string")) conditionals.push({
3479
+ if: requireKeys([key]),
3480
+ then: requireKeys(required)
3481
+ });
3482
+ for (const [key, dependent] of entries(schema.dependentSchemas)) {
3483
+ const thenNode = branch(dependent);
3484
+ if (thenNode) conditionals.push({
3485
+ if: requireKeys([key]),
3486
+ then: thenNode
3487
+ });
3488
+ }
3489
+ return conditionals;
3113
3490
  }
3114
3491
  return parseSchema;
3115
3492
  }
@@ -3448,6 +3825,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3448
3825
  schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
3449
3826
  }
3450
3827
  const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
3828
+ const authSchemes = toAuthSchemes(document.components?.securitySchemes, refs);
3451
3829
  return _kubb_kit.ast.factory.createInput({
3452
3830
  schemas: schemaNodes,
3453
3831
  operations,
@@ -3455,23 +3833,25 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3455
3833
  title: document.info?.title,
3456
3834
  description: document.info?.description,
3457
3835
  version: document.info?.version,
3836
+ summary: document.info?.summary,
3837
+ termsOfService: document.info?.termsOfService,
3838
+ contact: pickStrings(document.info?.contact, [
3839
+ "name",
3840
+ "url",
3841
+ "email"
3842
+ ]),
3843
+ license: typeof document.info?.license?.name === "string" ? {
3844
+ ...pickStrings(document.info.license, ["identifier", "url"]),
3845
+ name: document.info.license.name
3846
+ } : void 0,
3847
+ externalDocs: getExternalDocs(document.externalDocs),
3458
3848
  baseURL: resolveBaseUrl({
3459
3849
  document,
3460
3850
  server
3461
3851
  }),
3462
3852
  servers: toInputServers(document.servers ?? []),
3463
- securitySchemes: Object.entries(document.components?.securitySchemes ?? {}).flatMap(([name, value]) => {
3464
- const scheme = refs.deref(value);
3465
- return scheme?.type ? [{
3466
- name,
3467
- type: scheme.type,
3468
- description: scheme.description,
3469
- scheme: scheme.scheme,
3470
- bearerFormat: scheme.bearerFormat,
3471
- in: scheme.in,
3472
- parameterName: scheme.name
3473
- }] : [];
3474
- }),
3853
+ ...document.tags?.length && { tags: toInputTags(document.tags) },
3854
+ ...authSchemes.length && { auth: authSchemes },
3475
3855
  extensions: pickExtensions(document)
3476
3856
  }
3477
3857
  });