@kubb/adapter-oas 5.15.3 → 5.16.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
@@ -35,7 +35,7 @@ var compatibility = {
35
35
  "node": ">=22.22"
36
36
  };
37
37
  // package.json
38
- var version = "5.15.3";
38
+ var version = "5.16.0";
39
39
  // src/constants.ts
40
40
  /**
41
41
  * Default parser options applied when no explicit options are provided.
@@ -75,6 +75,17 @@ const SUPPORTED_METHODS = /* @__PURE__ */ new Set([
75
75
  "query"
76
76
  ]);
77
77
  /**
78
+ * Media types OpenAPI 3.2 reads as a stream of items (its "sequential media types"), each described by `itemSchema`.
79
+ */
80
+ const STREAM_MEDIA_TYPES = /* @__PURE__ */ new Set([
81
+ "application/jsonl",
82
+ "application/x-ndjson",
83
+ "application/json-seq",
84
+ "application/geo+json-seq",
85
+ "text/event-stream",
86
+ "multipart/mixed"
87
+ ]);
88
+ /**
78
89
  * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
79
90
  *
80
91
  * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
@@ -399,6 +410,14 @@ function isNullable(schema) {
399
410
  return false;
400
411
  }
401
412
  /**
413
+ * Returns `true` for an OpenAPI 3.2 or later document. Swagger 2.0 and OpenAPI 3.0 documents are
414
+ * upgraded to 3.1 before parsing, so `openapi` is always set here.
415
+ */
416
+ function isOpenApi32(document) {
417
+ const [major = 0, minor = 0] = String(document.openapi).split(".").map(Number);
418
+ return major > 3 || major === 3 && minor >= 2;
419
+ }
420
+ /**
402
421
  * Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
403
422
  */
404
423
  function pickExtensions(source) {
@@ -465,7 +484,8 @@ const jsonMimeFragments = [
465
484
  * ```
466
485
  */
467
486
  function isJsonMimeType(mimeType) {
468
- return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
487
+ const lower = mimeType.toLowerCase();
488
+ return jsonMimeFragments.some((fragment) => lower.includes(fragment));
469
489
  }
470
490
  // src/refs.ts
471
491
  /**
@@ -631,8 +651,10 @@ async function readSource(sourcePath) {
631
651
  async function resolveSource(sourcePath) {
632
652
  const data = await readSource(sourcePath);
633
653
  if (sourcePath.toLowerCase().endsWith(".md")) return data;
654
+ const pathname = urlRegExp.test(sourcePath) ? new URL(sourcePath).pathname : sourcePath;
634
655
  return parseYaml(data, {
635
656
  origin: sourcePath,
657
+ json: !/\.ya?ml$/i.test(pathname),
636
658
  help: "Fix the syntax at the reported line, or check that `input` points at the spec and not an HTML page."
637
659
  });
638
660
  }
@@ -640,11 +662,19 @@ async function resolveSource(sourcePath) {
640
662
  * Parses a YAML (or JSON) spec. A syntax error throws `KUBB_INVALID_DOCUMENT` naming `origin`,
641
663
  * with the parser message, which keeps the line and column.
642
664
  *
643
- * JSON is valid YAML, so one parser handles both. `js-yaml` parses about ten times faster than
644
- * `yaml`, and unlike `JSON.parse` it rejects duplicate keys instead of keeping the last one. Its
645
- * core schema keeps timestamps as strings and `<<` as a plain key, as `yaml` does for YAML 1.2.
665
+ * With `json` (the default), a document that starts with `{` or `[` goes through `JSON.parse`
666
+ * first, about ten times faster than `js-yaml` on a JSON spec. A duplicate key then keeps the last
667
+ * value, as every JSON parser does. YAML flow syntax also starts with `{`, so a parse failure falls
668
+ * through to YAML. Pass `json: false` for a source known to be YAML.
669
+ *
670
+ * `js-yaml` parses about ten times faster than `yaml`, and rejects duplicate keys instead of
671
+ * keeping the last one. Its core schema keeps timestamps as strings and `<<` as a plain key, as
672
+ * `yaml` does for YAML 1.2.
646
673
  */
647
- function parseYaml(data, { origin, help }) {
674
+ function parseYaml(data, { origin, help, json = true }) {
675
+ if (json && /^\s*[[{]/.test(data)) try {
676
+ return JSON.parse(data);
677
+ } catch {}
648
678
  try {
649
679
  const document = (0, js_yaml.load)(data, { schema: js_yaml.CORE_SCHEMA });
650
680
  if (document !== void 0) return document;
@@ -879,6 +909,17 @@ function reportInvalidSpec(message) {
879
909
  * ```
880
910
  */
881
911
  async function validateDocument(document, { throwOnError = false } = {}) {
912
+ if (isOpenApi32(document)) {
913
+ const diagnostic = {
914
+ code: _kubb_kit.Diagnostics.code.invalidSpec,
915
+ severity: "warning",
916
+ message: "The document was not validated: `@readme/openapi-parser` does not support OpenAPI 3.2 yet.",
917
+ help: "Set `validate: false` on the adapter to skip this check until the validator supports OpenAPI 3.2."
918
+ };
919
+ if (throwOnError) throw new _kubb_kit.Diagnostics.Error(diagnostic);
920
+ _kubb_kit.Diagnostics.report(diagnostic);
921
+ return;
922
+ }
882
923
  const { compileErrors, validate } = await import("@readme/openapi-parser");
883
924
  let result;
884
925
  try {
@@ -908,6 +949,7 @@ function reportValidationErrors({ errors, additionalErrors }) {
908
949
  * validating in place where worker threads are missing, as in edge runtimes.
909
950
  */
910
951
  async function validateDocumentInWorker(document) {
952
+ if (isOpenApi32(document)) return validateDocument(document);
911
953
  let message;
912
954
  try {
913
955
  const { Worker } = await import("node:worker_threads");
@@ -1178,6 +1220,123 @@ function resolveServerUrl(server, overrides) {
1178
1220
  }
1179
1221
  return url;
1180
1222
  }
1223
+ // src/model/mediaType.ts
1224
+ /**
1225
+ * The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
1226
+ */
1227
+ function baseMediaType(contentType) {
1228
+ return contentType.split(";")[0].trim().toLowerCase();
1229
+ }
1230
+ /**
1231
+ * Returns `true` for a media type read as a stream of items: one that OpenAPI 3.2 lists, or any that declares `itemSchema`.
1232
+ */
1233
+ function isStream(contentType, mediaType) {
1234
+ return STREAM_MEDIA_TYPES.has(baseMediaType(contentType)) || mediaType?.itemSchema !== void 0;
1235
+ }
1236
+ /**
1237
+ * Whether the media type is `text/event-stream`, with or without parameters.
1238
+ */
1239
+ function isEventStream(contentType) {
1240
+ return baseMediaType(contentType) === "text/event-stream";
1241
+ }
1242
+ /**
1243
+ * Replaces a `data` string that holds JSON with its `contentSchema`, so the event reads as parsed.
1244
+ */
1245
+ function withParsedData(schema, refs) {
1246
+ const data = schema.properties?.data;
1247
+ const resolved = data && isReference(data) ? refs.resolve(data.$ref, { report: false }) : data;
1248
+ if (!resolved?.contentSchema || !resolved.contentMediaType || !isJsonMimeType(resolved.contentMediaType)) return schema;
1249
+ return {
1250
+ ...schema,
1251
+ properties: {
1252
+ ...schema.properties,
1253
+ data: resolved.contentSchema
1254
+ }
1255
+ };
1256
+ }
1257
+ /**
1258
+ * Keywords next to `$ref` that only annotate it, so a reference that carries nothing else stays a reference.
1259
+ */
1260
+ const REF_ANNOTATIONS = /* @__PURE__ */ new Set([
1261
+ "$ref",
1262
+ "summary",
1263
+ "description",
1264
+ "$comment"
1265
+ ]);
1266
+ /**
1267
+ * Applies `extra` on top of `base` the way JSON Schema does: `properties` and `required` add up, other keywords of `extra` win.
1268
+ */
1269
+ function mergeSchemas(base, extra) {
1270
+ const required = [.../* @__PURE__ */ new Set([...Array.isArray(base.required) ? base.required : [], ...Array.isArray(extra.required) ? extra.required : []])];
1271
+ return {
1272
+ type: "object",
1273
+ ...base,
1274
+ ...extra,
1275
+ properties: {
1276
+ ...base.properties,
1277
+ ...extra.properties
1278
+ },
1279
+ ...required.length ? { required } : {}
1280
+ };
1281
+ }
1282
+ /**
1283
+ * Normalizes the OpenAPI 3.2 `text/event-stream` item schema into one schema per event.
1284
+ * - Keywords next to a `$ref` apply to the referenced schema.
1285
+ * - Shared `properties` merge into each inline `oneOf`/`anyOf` member.
1286
+ * - A JSON `data` string becomes its `contentSchema`.
1287
+ */
1288
+ function toEventSchema(schema, refs) {
1289
+ if (isReference(schema)) {
1290
+ const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => !REF_ANNOTATIONS.has(key)));
1291
+ const resolved = Object.keys(siblings).length ? refs.resolve(schema.$ref, { report: false }) : null;
1292
+ return resolved ? toEventSchema(mergeSchemas(resolved, siblings), refs) : schema;
1293
+ }
1294
+ const key = schema.oneOf?.length ? "oneOf" : schema.anyOf?.length ? "anyOf" : void 0;
1295
+ if (!key) return withParsedData(schema, refs);
1296
+ const members = schema[key];
1297
+ const { properties, required, ...rest } = schema;
1298
+ const merge = !!properties && !members.some(isReference);
1299
+ const shared = {
1300
+ properties,
1301
+ required
1302
+ };
1303
+ return {
1304
+ ...merge ? rest : schema,
1305
+ [key]: members.map((member) => isReference(member) ? member : withParsedData(merge ? mergeSchemas(shared, member) : member, refs))
1306
+ };
1307
+ }
1308
+ /**
1309
+ * Whether the schema, following `$ref`s, or a composition member of it declares an `event` or `data` property.
1310
+ */
1311
+ function declaresEventFields(schema, refs, seen) {
1312
+ if (isReference(schema)) {
1313
+ if (seen.has(schema.$ref)) return false;
1314
+ seen.add(schema.$ref);
1315
+ const resolved = refs.resolve(schema.$ref, { report: false });
1316
+ return !!resolved && declaresEventFields(resolved, refs, seen);
1317
+ }
1318
+ if (schema.properties && (Object.hasOwn(schema.properties, "event") || Object.hasOwn(schema.properties, "data"))) return true;
1319
+ return [
1320
+ ...schema.oneOf ?? [],
1321
+ ...schema.anyOf ?? [],
1322
+ ...schema.allOf ?? []
1323
+ ].some((member) => declaresEventFields(member, refs, seen));
1324
+ }
1325
+ /**
1326
+ * Returns `'event'` when the item declares `event` or `data`, directly or in a composition member, else `'data'`.
1327
+ */
1328
+ function getItemShape(schema, refs) {
1329
+ return declaresEventFields(schema, refs, /* @__PURE__ */ new Set()) ? "event" : "data";
1330
+ }
1331
+ /**
1332
+ * Returns the raw item schema: `itemSchema`, or `schema` on `text/event-stream` as 3.0 and 3.1 documents write one event.
1333
+ */
1334
+ function getItemSchema({ contentType, mediaType, refs, document }) {
1335
+ if (!mediaType || !isStream(contentType, mediaType)) return null;
1336
+ const raw = mediaType.itemSchema ?? (isEventStream(contentType) && !isOpenApi32(document) ? mediaType.schema : void 0);
1337
+ if (!raw) return null;
1338
+ return isEventStream(contentType) ? toEventSchema(raw, refs) : raw;
1339
+ }
1181
1340
  // src/emit/schemaShape.ts
1182
1341
  /**
1183
1342
  * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
@@ -1290,6 +1449,13 @@ function slugify(value) {
1290
1449
  return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
1291
1450
  }
1292
1451
  /**
1452
+ * Returns the operation's JSON pointer, under `additionalOperations` for an OpenAPI 3.2 method outside the fixed fields.
1453
+ */
1454
+ function getOperationPointer({ path, method }) {
1455
+ const pathPointer = `#/paths/${escapePointerToken(path)}`;
1456
+ return SUPPORTED_METHODS.has(method.toLowerCase()) ? `${pathPointer}/${method.toLowerCase()}` : `${pathPointer}/additionalOperations/${escapePointerToken(method)}`;
1457
+ }
1458
+ /**
1293
1459
  * Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
1294
1460
  */
1295
1461
  function getOperationId({ path, method, schema }) {
@@ -1342,7 +1508,7 @@ function getParameters({ operation, refs }) {
1342
1508
  }] : [];
1343
1509
  });
1344
1510
  const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
1345
- const operationParams = resolveParams(operation.schema?.parameters || [], `${pathPointer}/${operation.method.toLowerCase()}`);
1511
+ const operationParams = resolveParams(operation.schema?.parameters || [], getOperationPointer(operation));
1346
1512
  const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
1347
1513
  const paramMap = /* @__PURE__ */ new Map();
1348
1514
  for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
@@ -1375,17 +1541,59 @@ function getEncoding(encoding) {
1375
1541
  });
1376
1542
  return entries.length ? Object.fromEntries(entries) : void 0;
1377
1543
  }
1378
- /** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
1379
- function getBodySchema({ content, contentType, refs }) {
1380
- const entry = contentType ? content?.[contentType] : void 0;
1381
- if (!entry) return null;
1382
- const binary = getBinaryFallbackSchema(contentType, entry.schema);
1544
+ /**
1545
+ * Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
1546
+ */
1547
+ function getMediaType({ content, contentType, refs }) {
1548
+ return contentType ? refs.deref(content?.[contentType]) : null;
1549
+ }
1550
+ /**
1551
+ * Returns the schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON
1552
+ * entry. For a stream this is the item schema, so `schema: string` next to `itemSchema` types one item.
1553
+ */
1554
+ function getBodySchema({ content, contentType, refs, document }) {
1555
+ const entry = getMediaType({
1556
+ content,
1557
+ contentType,
1558
+ refs
1559
+ });
1560
+ if (!contentType || !entry) return null;
1561
+ const schema = getItemSchema({
1562
+ contentType,
1563
+ mediaType: entry,
1564
+ refs,
1565
+ document
1566
+ }) ?? entry.schema;
1567
+ const binary = entry.itemSchema ? void 0 : getBinaryFallbackSchema(contentType, schema);
1383
1568
  if (binary) return binary;
1384
- return entry.schema ? refs.derefKeepingRef(entry.schema) : null;
1569
+ return schema ? refs.derefKeepingRef(schema) : null;
1570
+ }
1571
+ /** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
1572
+ function getStreamField({ contentType, mediaType, refs, document, node }) {
1573
+ if (!isStream(contentType, mediaType)) return {};
1574
+ const raw = getItemSchema({
1575
+ contentType,
1576
+ mediaType,
1577
+ refs,
1578
+ document
1579
+ });
1580
+ return { stream: {
1581
+ ...raw ? { schema: node } : {},
1582
+ ...raw && isEventStream(contentType) ? { shape: getItemShape(raw, refs) } : {}
1583
+ } };
1584
+ }
1585
+ /** The media type keyword the body schema was read from, for the diagnostic pointer. */
1586
+ function schemaKeyword(mediaType) {
1587
+ return mediaType?.itemSchema ? "itemSchema" : "schema";
1588
+ }
1589
+ /** Pointer of a media type entry, following a `$ref` into `components.mediaTypes` so a diagnostic lands on the source. */
1590
+ function mediaTypePointer(content, contentType, fallback) {
1591
+ const entry = content?.[contentType];
1592
+ return isReference(entry) ? entry.$ref : fallback;
1385
1593
  }
1386
1594
  /**
1387
- * Builds an `Operation` for every supported HTTP method on every path, in document order.
1388
- * `x-` path keys and unresolvable path-item `$ref`s are skipped.
1595
+ * Builds an `Operation` for every supported HTTP method and every OpenAPI 3.2 `additionalOperations` entry on
1596
+ * every path, in document order. `x-` path keys and unresolvable path-item `$ref`s are skipped.
1389
1597
  */
1390
1598
  function getOperations(document, refs) {
1391
1599
  const operations = [];
@@ -1407,6 +1615,12 @@ function getOperations(document, refs) {
1407
1615
  pathItem
1408
1616
  });
1409
1617
  }
1618
+ for (const [method, schema] of Object.entries(pathItem.additionalOperations ?? {})) if (schema && typeof schema === "object" && !SUPPORTED_METHODS.has(method.toLowerCase())) operations.push({
1619
+ path,
1620
+ method,
1621
+ schema,
1622
+ pathItem
1623
+ });
1410
1624
  }
1411
1625
  return operations;
1412
1626
  }
@@ -1420,10 +1634,15 @@ function collectPropertyKeysByFlag(schema, flag) {
1420
1634
  }
1421
1635
  return keys.length ? keys : null;
1422
1636
  }
1423
- function parseParameter({ param, pointer, parentName, options, parseSchema }) {
1637
+ /** Converts one parameter into a `ParameterNode`, naming an inline schema after the operation and the parameter. */
1638
+ function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
1424
1639
  const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
1425
1640
  const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
1426
- const rawSchema = param.schema ?? (contentType ? param.content?.[contentType]?.schema : void 0);
1641
+ const rawSchema = param.schema ?? getMediaType({
1642
+ content: param.content,
1643
+ contentType,
1644
+ refs
1645
+ })?.schema;
1427
1646
  const schema = rawSchema ? parseSchema({
1428
1647
  schema: rawSchema,
1429
1648
  name: schemaName
@@ -1446,16 +1665,17 @@ function parseParameter({ param, pointer, parentName, options, parseSchema }) {
1446
1665
  * Converts an OAS `Operation` into an `OperationNode`.
1447
1666
  */
1448
1667
  function parseOperation({ operation, ...ctx }) {
1449
- const { refs, contentType, options, parseSchema } = ctx;
1668
+ const { document, refs, contentType, options, parseSchema } = ctx;
1450
1669
  const operationId = getOperationId(operation);
1451
1670
  const operationName = operationId ? pascalCase(operationId) : void 0;
1452
- const pointer = `#/paths/${escapePointerToken(operation.path)}/${operation.method.toLowerCase()}`;
1671
+ const pointer = getOperationPointer(operation);
1453
1672
  const parameters = getParameters({
1454
1673
  operation,
1455
1674
  refs
1456
1675
  }).map((entry) => parseParameter({
1457
1676
  options,
1458
1677
  parseSchema,
1678
+ refs,
1459
1679
  ...entry,
1460
1680
  parentName: operationName
1461
1681
  }));
@@ -1469,20 +1689,33 @@ function parseOperation({ operation, ...ctx }) {
1469
1689
  const schema = getBodySchema({
1470
1690
  content: body?.content,
1471
1691
  contentType: ct,
1472
- refs
1692
+ refs,
1693
+ document
1473
1694
  });
1474
1695
  if (!schema) return [];
1475
- const node = parseSchema({
1696
+ const mediaType = getMediaType({
1697
+ content: body?.content,
1698
+ contentType: ct,
1699
+ refs
1700
+ });
1701
+ const node = _kubb_kit.ast.optionality(parseSchema({
1476
1702
  schema,
1477
1703
  name: requestBodyName
1478
- });
1479
- scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}/schema`);
1480
- const encoding = getEncoding(body?.content?.[ct]?.encoding);
1704
+ }), bodyRequired);
1705
+ scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
1706
+ const encoding = getEncoding(mediaType?.encoding);
1481
1707
  return [_kubb_kit.ast.factory.createContent({
1482
1708
  contentType: ct,
1483
- schema: _kubb_kit.ast.optionality(node, bodyRequired),
1709
+ schema: node,
1484
1710
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
1485
- ...encoding && { encoding }
1711
+ ...encoding && { encoding },
1712
+ ...getStreamField({
1713
+ contentType: ct,
1714
+ mediaType,
1715
+ refs,
1716
+ document,
1717
+ node
1718
+ })
1486
1719
  })];
1487
1720
  });
1488
1721
  const requestBody = content.length > 0 || body?.description ? {
@@ -1500,6 +1733,12 @@ function parseOperation({ operation, ...ctx }) {
1500
1733
  const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
1501
1734
  const parseEntrySchema = (ct) => {
1502
1735
  const raw = getBodySchema({
1736
+ content: response?.content,
1737
+ contentType: ct,
1738
+ refs,
1739
+ document
1740
+ });
1741
+ const mediaType = getMediaType({
1503
1742
  content: response?.content,
1504
1743
  contentType: ct,
1505
1744
  refs
@@ -1508,10 +1747,17 @@ function parseOperation({ operation, ...ctx }) {
1508
1747
  schema: raw,
1509
1748
  name: responseName
1510
1749
  }) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType });
1511
- if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}/schema`);
1750
+ if (raw && ct) scanSchema(node, `${mediaTypePointer(response?.content, ct, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
1512
1751
  return {
1513
1752
  schema: node,
1514
- keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
1753
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
1754
+ ...ct ? getStreamField({
1755
+ contentType: ct,
1756
+ mediaType,
1757
+ refs,
1758
+ document,
1759
+ node
1760
+ }) : {}
1515
1761
  };
1516
1762
  };
1517
1763
  const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => _kubb_kit.ast.factory.createContent({
@@ -1542,7 +1788,7 @@ function parseOperation({ operation, ...ctx }) {
1542
1788
  pointer,
1543
1789
  operationId,
1544
1790
  protocol: "http",
1545
- method: operation.method.toUpperCase(),
1791
+ method: SUPPORTED_METHODS.has(operation.method) ? operation.method.toUpperCase() : operation.method,
1546
1792
  path: operation.path,
1547
1793
  tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
1548
1794
  summary: pickDoc("summary") || void 0,
@@ -1637,6 +1883,14 @@ function createDiscriminantNode({ propertyName, values }) {
1637
1883
  });
1638
1884
  }
1639
1885
  /**
1886
+ * Returns the `$ref` of an OpenAPI 3.2 `defaultMapping`, written as a schema name or a `$ref`; `undefined` when none.
1887
+ */
1888
+ function getDefaultMappingRef(discriminator) {
1889
+ const value = discriminator?.defaultMapping;
1890
+ if (!value) return void 0;
1891
+ return value.includes("/") ? value : `${SCHEMA_REF_PREFIX}${value}`;
1892
+ }
1893
+ /**
1640
1894
  * Returns every discriminator key whose mapping value matches `ref`, in mapping order.
1641
1895
  *
1642
1896
  * A mapping may point several keys at the same schema, in which case the member has to be
@@ -1732,13 +1986,15 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
1732
1986
  }
1733
1987
  return constrains(variant) ? null : value;
1734
1988
  }
1989
+ const defaultRef = getDefaultMappingRef(discriminator);
1735
1990
  return unionMembers.map((s) => {
1736
1991
  const ref = isReference(s) ? s.$ref : void 0;
1737
- const mappedValues = findDiscriminators({
1992
+ const isDefault = ref !== void 0 && ref === defaultRef;
1993
+ const mappedValues = isDefault ? [] : findDiscriminators({
1738
1994
  mapping: discriminator?.mapping,
1739
1995
  ref
1740
1996
  });
1741
- const implicitValue = mappedValues.length ? null : implicitDiscriminantValue(s);
1997
+ const implicitValue = mappedValues.length || isDefault ? null : implicitDiscriminantValue(s);
1742
1998
  const propertyType = propertyTypeOf({
1743
1999
  schema: s,
1744
2000
  propertyName: discriminator?.propertyName ?? "",
@@ -1783,7 +2039,8 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
1783
2039
  const parentUnion = deref?.oneOf ?? deref?.anyOf;
1784
2040
  if (!deref || !discriminator || isDiscriminator(deref) && !parentUnion) return true;
1785
2041
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1786
- const values = findDiscriminators({
2042
+ const isDefault = childRef === getDefaultMappingRef(discriminator);
2043
+ const values = isDefault ? [] : findDiscriminators({
1787
2044
  mapping: discriminator.mapping,
1788
2045
  ref: childRef
1789
2046
  });
@@ -1806,7 +2063,7 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
1806
2063
  })
1807
2064
  });
1808
2065
  }
1809
- return !(parentUnion && (values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
2066
+ return !(parentUnion && (isDefault || values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
1810
2067
  }),
1811
2068
  discriminantValues
1812
2069
  };
@@ -2128,10 +2385,12 @@ function convertUnion(context) {
2128
2385
  nullable,
2129
2386
  defaultValue
2130
2387
  };
2131
- const unionMembers = resolveUnionMembers([...schema.oneOf ?? [], ...schema.anyOf ?? []], schema.properties ?? {});
2132
- const strategy = schema.oneOf ? "one" : "any";
2133
- const explicitDiscriminatorPropertyName = isDiscriminator(schema) ? schema.discriminator.propertyName : void 0;
2134
2388
  const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
2389
+ const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
2390
+ const defaultRef = getDefaultMappingRef(discriminator);
2391
+ const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
2392
+ const strategy = schema.oneOf ? "one" : "any";
2393
+ const explicitDiscriminatorPropertyName = discriminator?.propertyName;
2135
2394
  const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
2136
2395
  const sharedPropertiesNode = schema.properties ? parse({
2137
2396
  schema: memberBaseSchema,
@@ -2184,7 +2443,7 @@ function convertUnion(context) {
2184
2443
  members,
2185
2444
  discriminatorPropertyName: explicitDiscriminatorPropertyName ?? _kubb_kit.ast.inferDiscriminatorPropertyName(members)
2186
2445
  });
2187
- return _kubb_kit.ast.applyMacros(unionNode, [_kubb_kit.macroSimplifyUnion], { depth: "shallow" });
2446
+ return _kubb_kit.ast.applyMacros(unionNode, [_kubb_kit.macroDedupeUnion], { depth: "shallow" });
2188
2447
  }
2189
2448
  /**
2190
2449
  * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
@@ -2249,6 +2508,34 @@ function getNumericConstraints(schema) {
2249
2508
  };
2250
2509
  }
2251
2510
  /**
2511
+ * Whether an enum value fits the schema's numeric bounds.
2512
+ */
2513
+ function isWithinBounds({ value, schema }) {
2514
+ if (typeof value !== "number") return true;
2515
+ const { min, max, exclusiveMinimum, exclusiveMaximum, multipleOf } = getNumericConstraints(schema);
2516
+ if (min !== void 0 && value < min) return false;
2517
+ if (max !== void 0 && value > max) return false;
2518
+ if (exclusiveMinimum !== void 0 && value <= exclusiveMinimum) return false;
2519
+ if (exclusiveMaximum !== void 0 && value >= exclusiveMaximum) return false;
2520
+ if (multipleOf !== void 0 && Math.abs(value / multipleOf - Math.round(value / multipleOf)) > 1e-9) return false;
2521
+ return true;
2522
+ }
2523
+ /**
2524
+ * Builds the test for which `enum` entries stay: not `null`, and within the numeric bounds. `enum` and the bounds
2525
+ * apply together, so a value outside them is never valid. Bounds that exclude every value are a spec error, so
2526
+ * they are ignored rather than leaving an empty enum.
2527
+ */
2528
+ function createEnumMemberFilter(schema) {
2529
+ const boundsApply = schema.enum.some((value) => value !== null && isWithinBounds({
2530
+ value,
2531
+ schema
2532
+ }));
2533
+ return (value) => value !== null && (!boundsApply || isWithinBounds({
2534
+ value,
2535
+ schema
2536
+ }));
2537
+ }
2538
+ /**
2252
2539
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
2253
2540
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
2254
2541
  */
@@ -2329,7 +2616,8 @@ function convertEnum({ schema, name, nullable, type, parse }) {
2329
2616
  name
2330
2617
  });
2331
2618
  const nullInEnum = schema.enum.includes(null);
2332
- const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
2619
+ const isMember = createEnumMemberFilter(schema);
2620
+ const filteredValues = schema.enum.filter(isMember);
2333
2621
  if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
2334
2622
  const enumNullable = nullable || nullInEnum || void 0;
2335
2623
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
@@ -2348,8 +2636,9 @@ function convertEnum({ schema, name, nullable, type, parse }) {
2348
2636
  primitive,
2349
2637
  enumValues: uniqueValues
2350
2638
  });
2351
- const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
2352
- const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
2639
+ const memberSlots = (list) => Array.isArray(list) ? list.filter((_, index) => isMember(schema.enum[index])) : void 0;
2640
+ const rawEnumNames = extensionKey ? memberSlots(schema[extensionKey]) : void 0;
2641
+ const rawEnumDescriptions = descriptionKey ? memberSlots(schema[descriptionKey]) : void 0;
2353
2642
  const seenNames = /* @__PURE__ */ new Set();
2354
2643
  return createNode(ctx, {
2355
2644
  type: "enum",
@@ -2367,6 +2656,25 @@ function convertEnum({ schema, name, nullable, type, parse }) {
2367
2656
  });
2368
2657
  }
2369
2658
  /**
2659
+ * Converts an `enum` with a handled `format` into a union of its literal values. A named enum node would rename
2660
+ * the exported type (`Status` becomes `StatusKey`), so the union keeps the name the format type had.
2661
+ */
2662
+ function convertFormattedEnum(context) {
2663
+ const { schema, nullable } = context;
2664
+ const values = [...new Set(schema.enum.filter(createEnumMemberFilter(schema)))];
2665
+ return createNode({
2666
+ ...context,
2667
+ nullable: nullable || schema.enum.includes(null) || void 0
2668
+ }, {
2669
+ type: "union",
2670
+ members: values.map((value) => _kubb_kit.ast.factory.createSchema({
2671
+ type: "enum",
2672
+ primitive: getPrimitiveType(typeof value),
2673
+ enumValues: [value]
2674
+ }))
2675
+ });
2676
+ }
2677
+ /**
2370
2678
  * Converts a `type: 'string'` schema into a `StringSchemaNode`.
2371
2679
  */
2372
2680
  function convertString(context) {
@@ -2543,6 +2851,24 @@ function convertArray({ schema, name, nullable, defaultValue, options, parse })
2543
2851
  }
2544
2852
  // src/emit/parseSchema.ts
2545
2853
  /**
2854
+ * Whether the `format` rule owns the schema: a handled format, and for a date format a `dateType` that is not `false`.
2855
+ */
2856
+ function matchesFormat({ schema, options }) {
2857
+ if (!schema.format) return false;
2858
+ if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") return resolveDateTypeValue(options.dateType, schema.format) !== false;
2859
+ return isHandledFormat(schema.format);
2860
+ }
2861
+ /**
2862
+ * Whether the format prints as a plain `string` or `number`, so narrowing it to literal values cannot break callers.
2863
+ * `Date`, `bigint` and `Blob` outputs keep the format.
2864
+ */
2865
+ function formatPrintsPlainType({ schema, options, type }) {
2866
+ const format = schema.format;
2867
+ if (format === "date-time" || format === "date" || format === "time") return resolveDateTypeValue(options.dateType, format) !== "date";
2868
+ if ((format === "int64" || format === "uint64") && type !== "string") return options.integerType !== "bigint";
2869
+ return formatMap[format] !== "blob";
2870
+ }
2871
+ /**
2546
2872
  * Ordered schema rule table. Order is significant: composition keywords (`not`, `$ref`, `allOf`,
2547
2873
  * `oneOf`/`anyOf`) take precedence over `const` (except a `$ref` with a sibling `const`, where the
2548
2874
  * `const` wins), which takes precedence over a genuine OAS 3.1 multi-type array (more than one
@@ -2580,11 +2906,11 @@ const schemaRules = [
2580
2906
  convert: convertMultiType
2581
2907
  },
2582
2908
  {
2583
- match: ({ schema, options }) => {
2584
- if (!schema.format) return false;
2585
- if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") return resolveDateTypeValue(options.dateType, schema.format) !== false;
2586
- return isHandledFormat(schema.format);
2587
- },
2909
+ match: (ctx) => ctx.type !== "array" && !!ctx.schema.enum?.some((value) => value !== null) && matchesFormat(ctx) && formatPrintsPlainType(ctx),
2910
+ convert: convertFormattedEnum
2911
+ },
2912
+ {
2913
+ match: matchesFormat,
2588
2914
  convert: convertFormat
2589
2915
  },
2590
2916
  {
@@ -2594,6 +2920,10 @@ const schemaRules = [
2594
2920
  primitive: "string"
2595
2921
  })
2596
2922
  },
2923
+ {
2924
+ match: ({ schema, type }) => !type && !schema.enum?.length && (schema.contentMediaType !== void 0 || schema.contentSchema !== void 0),
2925
+ convert: convertString
2926
+ },
2597
2927
  {
2598
2928
  match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
2599
2929
  convert: convertString
@@ -2887,7 +3217,7 @@ const locate = (source, location) => {
2887
3217
  */
2888
3218
  const adapterOasName = "oas";
2889
3219
  /**
2890
- * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
3220
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, 3.1, and 3.2 specifications. Reads the
2891
3221
  * spec from `input` (a file path, URL, inline content, or parsed object), validates
2892
3222
  * it, resolves the base URL, and converts every schema and operation into the
2893
3223
  * universal AST that every downstream plugin consumes.
@@ -2961,6 +3291,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
2961
3291
  const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2962
3292
  const operationNodes = getOperations(document, refs).map((operation) => parseOperation({
2963
3293
  operation,
3294
+ document,
2964
3295
  refs,
2965
3296
  contentType,
2966
3297
  options: parserOptions,