@kubb/adapter-oas 5.5.6 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,9 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { ast, findCircularSchemasFromGraph, narrowSchema, resolveRefName } from "@kubb/ast";
3
- import { Diagnostics, createAdapter } from "@kubb/core";
2
+ import { Diagnostics, ast, childName, createAdapter, enumPropName, extractRefName, macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion, mergeAdjacentObjectsLazy } from "@kubb/kit";
4
3
  import { access, readFile } from "node:fs/promises";
5
- import path from "node:path";
6
4
  import { parse } from "yaml";
7
5
  import { upgrade } from "@scalar/openapi-upgrader";
8
6
  import { bundle } from "api-ref-bundler";
9
- import { childName, enumPropName, extractRefName, macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion, mergeAdjacentObjectsLazy } from "@kubb/kit";
10
7
  //#region src/constants.ts
11
8
  /**
12
9
  * Default parser options applied when no explicit options are provided.
@@ -507,7 +504,7 @@ async function parseDocument(pathOrApi) {
507
504
  /**
508
505
  * Creates a `Document` from an `AdapterSource`.
509
506
  *
510
- * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
507
+ * - `{ type: 'path' }` bundles an absolute file path (core resolves `input`) or remote URL.
511
508
  * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
512
509
  *
513
510
  * @example
@@ -518,10 +515,8 @@ async function parseDocument(pathOrApi) {
518
515
  */
519
516
  async function parseFromConfig(source) {
520
517
  if (source.type === "data") return parseDocument(typeof source.data === "string" ? parse(source.data) : structuredClone(source.data));
521
- if (urlRegExp.test(source.path)) return parseDocument(source.path);
522
- const resolved = path.resolve(path.dirname(source.path), source.path);
523
- await assertInputExists(resolved);
524
- return parseDocument(resolved);
518
+ await assertInputExists(source.path);
519
+ return parseDocument(source.path);
525
520
  }
526
521
  /**
527
522
  * Asserts the parsed input is an OpenAPI or Swagger document.
@@ -650,21 +645,6 @@ const jsonMimeFragments = [
650
645
  function isJsonMimeType(mimeType) {
651
646
  return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
652
647
  }
653
- /**
654
- * Picks a media-type entry from a `content` map: the first JSON-like media type, falling back to
655
- * the first declared one. Returns `false` when `content` has no entries.
656
- *
657
- * @example
658
- * ```ts
659
- * pickContentEntry({ 'application/xml': xmlEntry, 'application/json': jsonEntry })
660
- * // ['application/json', jsonEntry]
661
- * ```
662
- */
663
- function pickContentEntry(content) {
664
- const mediaTypes = Object.keys(content);
665
- const available = mediaTypes.find(isJsonMimeType) ?? mediaTypes[0];
666
- return available ? [available, content[available]] : false;
667
- }
668
648
  //#endregion
669
649
  //#region src/model/components.ts
670
650
  /**
@@ -870,10 +850,6 @@ function resolveServerUrl(server, overrides) {
870
850
  }
871
851
  //#endregion
872
852
  //#region src/operation.ts
873
- /**
874
- * Slugifies a path for the `operationId` fallback: non-alphanumerics collapse to single dashes,
875
- * with no leading or trailing dash.
876
- */
877
853
  function slugify(value) {
878
854
  return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
879
855
  }
@@ -894,12 +870,12 @@ function getResponseStatusCodes({ schema }) {
894
870
  return Object.keys(responses).filter((key) => !key.startsWith("x-") && !!responses[key] && typeof responses[key] === "object");
895
871
  }
896
872
  /**
897
- * Returns the response object for a status code, resolving a `$ref` through `refs`. `false` when absent.
873
+ * Returns the response object for a status code, resolving a `$ref` through `refs`. `null` when absent.
898
874
  */
899
875
  function getResponseByStatusCode({ operation, refs, statusCode }) {
900
876
  const responses = operation.schema.responses;
901
- if (!responses || isReference(responses)) return false;
902
- return refs.deref(responses[statusCode]) ?? false;
877
+ if (!responses || isReference(responses)) return null;
878
+ return refs.deref(responses[statusCode]);
903
879
  }
904
880
  /**
905
881
  * Resolves the operation's request body, dereferencing a `$ref` through `refs`. Returns `null`
@@ -909,51 +885,37 @@ function getRequestBody({ operation, refs }) {
909
885
  return refs.deref(operation.schema.requestBody);
910
886
  }
911
887
  /**
912
- * Resolves the request body (a `$ref` through `refs`) and returns its content map, or
913
- * `undefined` when the operation has no request body.
914
- */
915
- function getRequestBodyContent({ operation, refs }) {
916
- return getRequestBody({
917
- operation,
918
- refs
919
- })?.content;
920
- }
921
- /**
922
- * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
923
- * Otherwise picks the first JSON-like media type, then the first declared one, as a
924
- * `[mediaType, object]` tuple.
925
- */
926
- function getRequestContent({ operation, refs, mediaType }) {
927
- const content = getRequestBodyContent({
928
- operation,
929
- refs
930
- });
931
- if (!content) return false;
932
- if (mediaType) return mediaType in content ? content[mediaType] : false;
933
- return pickContentEntry(content);
934
- }
935
- /**
936
888
  * Returns the primary request content type. Prefers a JSON-like media type (the last one wins
937
889
  * when several are declared), then the first declared one, defaulting to `'application/json'`.
938
890
  */
939
891
  function getRequestContentType({ operation, refs }) {
940
- const content = getRequestBodyContent({
892
+ const content = getRequestBody({
941
893
  operation,
942
894
  refs
943
- });
895
+ })?.content;
944
896
  const mediaTypes = content ? Object.keys(content) : [];
945
897
  return mediaTypes.findLast(isJsonMimeType) ?? mediaTypes[0] ?? "application/json";
946
898
  }
899
+ /** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
900
+ function getParameters({ operation, refs }) {
901
+ const resolveParams = (params) => params.map((p) => refs.derefKeepingRef(p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
902
+ const operationParams = resolveParams(operation.schema?.parameters || []);
903
+ const pathLevelParams = resolveParams(operation.pathItem.parameters ?? []);
904
+ const paramMap = /* @__PURE__ */ new Map();
905
+ for (const p of [...pathLevelParams, ...operationParams]) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
906
+ return Array.from(paramMap.values());
907
+ }
908
+ /** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
909
+ function getBodySchema({ content, contentType, refs }) {
910
+ const entry = contentType ? content?.[contentType] : void 0;
911
+ if (!entry) return null;
912
+ const binary = getBinaryFallbackSchema(contentType, entry.schema);
913
+ if (binary) return binary;
914
+ return entry.schema ? refs.derefKeepingRef(entry.schema) : null;
915
+ }
947
916
  /**
948
917
  * Builds an `Operation` for every supported HTTP method on every path, in document order.
949
918
  * `x-` path keys and unresolvable path-item `$ref`s are skipped.
950
- *
951
- * @example
952
- * ```ts
953
- * for (const operation of getOperations(document, refs)) {
954
- * parseOperation(options, operation)
955
- * }
956
- * ```
957
919
  */
958
920
  function getOperations(document, refs) {
959
921
  const operations = [];
@@ -978,32 +940,144 @@ function getOperations(document, refs) {
978
940
  }
979
941
  return operations;
980
942
  }
981
- //#endregion
982
- //#region src/emit/schemaShape.ts
983
- /**
984
- * Returns the Kubb `SchemaType` for a given OAS `format` string, or `null` if not found.
985
- * Formats not in `formatMap` (e.g., `int64`, `uint64`, `date-time`) are handled separately by parser options.
986
- */
987
- function getSchemaType(format) {
988
- return formatMap[format] ?? null;
943
+ /** Property names whose schema has a truthy `readOnly` or `writeOnly` flag; `$ref` entries are skipped. */
944
+ function collectPropertyKeysByFlag(schema, flag) {
945
+ if (!schema?.properties) return null;
946
+ const keys = [];
947
+ for (const key in schema.properties) {
948
+ const prop = schema.properties[key];
949
+ if (prop && !isReference(prop) && prop[flag]) keys.push(key);
950
+ }
951
+ return keys.length ? keys : null;
952
+ }
953
+ function parseParameter({ param, parentName, options, parseSchema }) {
954
+ const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
955
+ const schema = param.schema ? parseSchema({
956
+ schema: param.schema,
957
+ name: schemaName
958
+ }) : ast.factory.createSchema({ type: options.unknownType });
959
+ const style = param.style;
960
+ const explode = param.explode;
961
+ return ast.factory.createParameter({
962
+ name: param.name,
963
+ in: param.in,
964
+ schema: {
965
+ ...schema,
966
+ description: param.description ?? schema.description
967
+ },
968
+ required: param.required ?? false,
969
+ ...style !== void 0 ? { style } : {},
970
+ ...explode !== void 0 ? { explode } : {}
971
+ });
989
972
  }
990
973
  /**
991
- * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry, plus the
992
- * `specialCasedFormats` that `convertFormat` handles directly (int64, uint64, date-time, date, time). False means the format falls back to
993
- * the base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
994
- * diagnostic in step with the parser as `formatMap` grows.
974
+ * Converts an OAS `Operation` into an `OperationNode`.
995
975
  */
996
- function isHandledFormat(format) {
997
- return getSchemaType(format) !== null || specialCasedFormats.has(format);
976
+ function parseOperation({ operation, ...ctx }) {
977
+ const { refs, contentType, options, parseSchema } = ctx;
978
+ const operationId = getOperationId(operation);
979
+ const operationName = operationId ? pascalCase(operationId) : void 0;
980
+ const parameters = getParameters({
981
+ operation,
982
+ refs
983
+ }).map((param) => parseParameter({
984
+ options,
985
+ parseSchema,
986
+ param,
987
+ parentName: operationName
988
+ }));
989
+ const body = getRequestBody({
990
+ operation,
991
+ refs
992
+ });
993
+ const bodyRequired = body?.required === true;
994
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
995
+ const content = (contentType ? [contentType] : Object.keys(body?.content ?? {})).flatMap((ct) => {
996
+ const schema = getBodySchema({
997
+ content: body?.content,
998
+ contentType: ct,
999
+ refs
1000
+ });
1001
+ if (!schema) return [];
1002
+ return [ast.factory.createContent({
1003
+ contentType: ct,
1004
+ schema: ast.optionality(parseSchema({
1005
+ schema,
1006
+ name: requestBodyName
1007
+ }), bodyRequired),
1008
+ keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1009
+ })];
1010
+ });
1011
+ const requestBody = content.length > 0 || body?.description ? {
1012
+ description: body?.description,
1013
+ required: bodyRequired || void 0,
1014
+ content: content.length > 0 ? content : void 0
1015
+ } : void 0;
1016
+ const responses = getResponseStatusCodes(operation).map((statusCode) => {
1017
+ const response = getResponseByStatusCode({
1018
+ operation,
1019
+ refs,
1020
+ statusCode
1021
+ });
1022
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
1023
+ const parseEntrySchema = (ct) => {
1024
+ const raw = getBodySchema({
1025
+ content: response?.content,
1026
+ contentType: ct,
1027
+ refs
1028
+ });
1029
+ return {
1030
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
1031
+ schema: raw,
1032
+ name: responseName
1033
+ }) : ast.factory.createSchema({ type: options.emptySchemaType }),
1034
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
1035
+ };
1036
+ };
1037
+ const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => ast.factory.createContent({
1038
+ contentType: ct,
1039
+ ...parseEntrySchema(ct)
1040
+ }));
1041
+ if (responseContent.length === 0) responseContent.push(ast.factory.createContent({
1042
+ contentType: getRequestContentType({
1043
+ operation,
1044
+ refs
1045
+ }),
1046
+ ...parseEntrySchema(contentType)
1047
+ }));
1048
+ return ast.factory.createResponse({
1049
+ statusCode,
1050
+ description: response?.description,
1051
+ content: responseContent
1052
+ });
1053
+ });
1054
+ const pickDoc = (key) => {
1055
+ const own = operation.schema[key];
1056
+ if (typeof own === "string") return own;
1057
+ const fallback = operation.pathItem[key];
1058
+ return typeof fallback === "string" ? fallback : void 0;
1059
+ };
1060
+ return ast.factory.createOperation({
1061
+ operationId,
1062
+ protocol: "http",
1063
+ method: operation.method.toUpperCase(),
1064
+ path: operation.path,
1065
+ tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
1066
+ summary: pickDoc("summary") || void 0,
1067
+ description: pickDoc("description") || void 0,
1068
+ deprecated: operation.schema.deprecated || void 0,
1069
+ parameters,
1070
+ requestBody,
1071
+ responses
1072
+ });
998
1073
  }
1074
+ //#endregion
1075
+ //#region src/emit/schemaShape.ts
999
1076
  /**
1000
- * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
1001
- * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
1077
+ * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
1002
1078
  */
1003
- function getPrimitiveType(type) {
1004
- if (type === "number" || type === "integer" || type === "bigint") return type;
1005
- if (type === "boolean") return "boolean";
1006
- return "string";
1079
+ function isHandledFormat(format) {
1080
+ return formatMap[format] !== void 0 || specialCasedFormats.has(format);
1007
1081
  }
1008
1082
  /**
1009
1083
  * Resolves the `dateType` option down to the value for one format. The scalar form of
@@ -1015,51 +1089,6 @@ function resolveDateTypeValue(dateType, format) {
1015
1089
  return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
1016
1090
  }
1017
1091
  /**
1018
- * Resolves the AST type descriptor for a date/time format, honoring the `dateType` option.
1019
- * Returns `null` when the resolved value is `false`, so the format falls through to `string`.
1020
- */
1021
- function getDateType(options, format) {
1022
- const value = resolveDateTypeValue(options.dateType, format);
1023
- if (!value) return null;
1024
- if (format === "date-time") {
1025
- if (value === "date") return {
1026
- type: "date",
1027
- representation: "date"
1028
- };
1029
- if (value === "stringOffset") return {
1030
- type: "datetime",
1031
- offset: true
1032
- };
1033
- if (value === "stringLocal") return {
1034
- type: "datetime",
1035
- local: true
1036
- };
1037
- return {
1038
- type: "datetime",
1039
- offset: false
1040
- };
1041
- }
1042
- if (format === "date") return {
1043
- type: "date",
1044
- representation: value === "date" ? "date" : "string"
1045
- };
1046
- return {
1047
- type: "time",
1048
- representation: value === "date" ? "date" : "string"
1049
- };
1050
- }
1051
- /**
1052
- * Reads a schema's numeric `exclusiveMinimum`/`exclusiveMaximum` bounds (the OAS 3.1 numeric
1053
- * form). Either key is `undefined` when absent or, for the legacy OAS 3.0 boolean form, not a
1054
- * number.
1055
- */
1056
- function getExclusiveBounds(schema) {
1057
- return {
1058
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1059
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1060
- };
1061
- }
1062
- /**
1063
1092
  * Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
1064
1093
  * labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
1065
1094
  * 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
@@ -1068,12 +1097,6 @@ function extractExamples(schema) {
1068
1097
  if (Array.isArray(schema.examples)) return schema.examples;
1069
1098
  return schema.example !== void 0 ? [schema.example] : void 0;
1070
1099
  }
1071
- /**
1072
- * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
1073
- * structurally significant on its own (see `structuralKeys`).
1074
- *
1075
- * A fragment with a structural keyword can't be safely merged into a parent schema.
1076
- */
1077
1100
  function hasStructuralKeywords(fragment) {
1078
1101
  return Object.keys(fragment).some((key) => structuralKeys.has(key));
1079
1102
  }
@@ -1081,23 +1104,19 @@ function hasStructuralKeywords(fragment) {
1081
1104
  * Flattens a keyword-only `allOf` into its parent schema.
1082
1105
  *
1083
1106
  * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
1084
- * (see `structuralKeys`). Outer schema values take precedence over fragment values.
1085
- * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
1107
+ * (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
1108
+ * original schema unchanged when flattening is unsafe.
1086
1109
  *
1087
1110
  * @example
1088
1111
  * ```ts
1089
1112
  * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
1090
1113
  * // { type: 'object', properties: {}, description: 'A pet' }
1091
- * ```
1092
- *
1093
- * @example
1094
- * ```ts
1095
1114
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
1096
1115
  * // returned unchanged, contains a $ref
1097
1116
  * ```
1098
1117
  */
1099
1118
  function flattenSchema(schema) {
1100
- if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
1119
+ if (!schema.allOf?.length) return schema;
1101
1120
  const allOfFragments = schema.allOf;
1102
1121
  if (allOfFragments.some((item) => isReference(item))) return schema;
1103
1122
  if (allOfFragments.some(hasStructuralKeywords)) return schema;
@@ -1176,7 +1195,7 @@ function findDiscriminators(mapping, ref) {
1176
1195
  * at this union usage site, which is what makes this mode "preserve" (as opposed to `propagate`,
1177
1196
  * which additionally patches the child schema's own definition in a post-pass).
1178
1197
  */
1179
- function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode, parse, rawOptions, name, refs }) {
1198
+ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode, parse, name, refs }) {
1180
1199
  function pickDiscriminatorPropertyNode(node, propertyName) {
1181
1200
  const discriminatorProperty = ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1182
1201
  if (!discriminatorProperty) return null;
@@ -1218,7 +1237,7 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
1218
1237
  const memberNode = parse({
1219
1238
  schema: s,
1220
1239
  name
1221
- }, rawOptions);
1240
+ });
1222
1241
  if (!discriminatorValues.length || !discriminator) return memberNode;
1223
1242
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
1224
1243
  propertyName: discriminator.propertyName,
@@ -1404,9 +1423,9 @@ function resolveUnionSchema(schema, siblingProperties) {
1404
1423
  * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1405
1424
  * Circular refs are detected in `refs.resolveNode` and leave `schema` as `null`.
1406
1425
  */
1407
- function convertRef({ schema, name, nullable, defaultValue, rawOptions, document, parse, refs, renames }) {
1426
+ function convertRef({ schema, name, nullable, defaultValue, document, parse, refs, renames }) {
1408
1427
  const refPath = schema.$ref;
1409
- const resolvedSchema = refPath ? refs.resolveNode(refPath, parse, rawOptions) : null;
1428
+ const resolvedSchema = refPath ? refs.resolveNode(refPath, parse) : null;
1410
1429
  const ctx = {
1411
1430
  schema,
1412
1431
  name,
@@ -1426,13 +1445,13 @@ function convertRef({ schema, name, nullable, defaultValue, rawOptions, document
1426
1445
  /**
1427
1446
  * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1428
1447
  */
1429
- function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse, refs }) {
1448
+ function convertAllOf({ schema, name, nullable, defaultValue, parse, refs }) {
1430
1449
  if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1431
1450
  const [memberSchema] = schema.allOf;
1432
1451
  const memberNode = parse({
1433
1452
  schema: memberSchema,
1434
1453
  name
1435
- }, rawOptions);
1454
+ });
1436
1455
  const { kind: _kind, ...memberNodeProps } = memberNode;
1437
1456
  const mergedNullable = nullable || memberNode.nullable || void 0;
1438
1457
  const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
@@ -1460,7 +1479,7 @@ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse,
1460
1479
  const allOfMembers = discriminatedAllOf.map((s) => isReference(s) ? s : resolveUnionSchema(s, siblingProperties)).map((s) => parse({
1461
1480
  schema: s,
1462
1481
  name
1463
- }, rawOptions));
1482
+ }));
1464
1483
  const syntheticStart = allOfMembers.length;
1465
1484
  if (Array.isArray(schema.required) && schema.required.length) {
1466
1485
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
@@ -1481,7 +1500,7 @@ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse,
1481
1500
  allOfMembers.push(parse({
1482
1501
  schema: memberSchema,
1483
1502
  name
1484
- }, rawOptions));
1503
+ }));
1485
1504
  break;
1486
1505
  }
1487
1506
  }
@@ -1489,7 +1508,7 @@ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse,
1489
1508
  }
1490
1509
  if (schema.properties) {
1491
1510
  const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1492
- allOfMembers.push(parse({ schema: schemaWithoutAllOf }, rawOptions));
1511
+ allOfMembers.push(parse({ schema: schemaWithoutAllOf }));
1493
1512
  }
1494
1513
  for (const { propertyName, values } of discriminantValues) allOfMembers.push(createDiscriminantNode({
1495
1514
  propertyName,
@@ -1511,7 +1530,7 @@ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse,
1511
1530
  function convertUnion(context) {
1512
1531
  const annotatedEnum = convertAnnotatedEnum(context);
1513
1532
  if (annotatedEnum) return annotatedEnum;
1514
- const { schema, name, nullable, defaultValue, rawOptions, parse, refs } = context;
1533
+ const { schema, name, nullable, defaultValue, parse, refs } = context;
1515
1534
  const ctx = {
1516
1535
  schema,
1517
1536
  name,
@@ -1526,14 +1545,13 @@ function convertUnion(context) {
1526
1545
  const sharedPropertiesNode = schema.properties ? parse({
1527
1546
  schema: memberBaseSchema,
1528
1547
  name
1529
- }, rawOptions) : void 0;
1548
+ }) : void 0;
1530
1549
  if (sharedPropertiesNode || discriminator) {
1531
1550
  const members = narrowUnionMembers({
1532
1551
  unionMembers,
1533
1552
  discriminator,
1534
1553
  sharedPropertiesNode,
1535
1554
  parse,
1536
- rawOptions,
1537
1555
  name,
1538
1556
  refs
1539
1557
  });
@@ -1552,7 +1570,7 @@ function convertUnion(context) {
1552
1570
  const members = unionMembers.map((s) => parse({
1553
1571
  schema: s,
1554
1572
  name
1555
- }, rawOptions));
1573
+ }));
1556
1574
  const unionNode = createNode(ctx, {
1557
1575
  type: "union",
1558
1576
  strategy,
@@ -1567,7 +1585,7 @@ function convertUnion(context) {
1567
1585
  * remains; a single remaining type (e.g. `['string', 'null']`) is handled as that type instead,
1568
1586
  * with nullability already folded in.
1569
1587
  */
1570
- function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1588
+ function convertMultiType({ schema, name, nullable, defaultValue, parse }) {
1571
1589
  const types = schema.type;
1572
1590
  const nonNullTypes = types.filter((t) => t !== "null");
1573
1591
  return createNode({
@@ -1584,7 +1602,7 @@ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, pa
1584
1602
  type: t
1585
1603
  },
1586
1604
  name
1587
- }, rawOptions);
1605
+ });
1588
1606
  })
1589
1607
  });
1590
1608
  }
@@ -1609,6 +1627,44 @@ function normalizeArrayEnum(schema) {
1609
1627
  items: normalizedItems
1610
1628
  };
1611
1629
  }
1630
+ function getPrimitiveType(type) {
1631
+ if (type === "number" || type === "integer" || type === "bigint") return type;
1632
+ if (type === "boolean") return "boolean";
1633
+ return "string";
1634
+ }
1635
+ /** Shared by number, integer and bigint nodes; the exclusive bounds keep only the OAS 3.1 numeric form. */
1636
+ function getNumericConstraints(schema) {
1637
+ return {
1638
+ min: schema.minimum,
1639
+ max: schema.maximum,
1640
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1641
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1642
+ multipleOf: schema.multipleOf
1643
+ };
1644
+ }
1645
+ function getDateType(options, format) {
1646
+ const value = resolveDateTypeValue(options.dateType, format);
1647
+ if (format !== "date-time") return {
1648
+ type: format,
1649
+ representation: value === "date" ? "date" : "string"
1650
+ };
1651
+ if (value === "date") return {
1652
+ type: "date",
1653
+ representation: "date"
1654
+ };
1655
+ if (value === "stringOffset") return {
1656
+ type: "datetime",
1657
+ offset: true
1658
+ };
1659
+ if (value === "stringLocal") return {
1660
+ type: "datetime",
1661
+ local: true
1662
+ };
1663
+ return {
1664
+ type: "datetime",
1665
+ offset: false
1666
+ };
1667
+ }
1612
1668
  /**
1613
1669
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1614
1670
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
@@ -1628,22 +1684,13 @@ function createNullNode(schema, name, nullable) {
1628
1684
  /**
1629
1685
  * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1630
1686
  */
1631
- function convertConst({ schema, name, nullable, defaultValue }) {
1687
+ function convertConst(context) {
1688
+ const { schema, name } = context;
1632
1689
  const constValue = schema.const;
1633
1690
  if (constValue === null) return createNullNode(schema, name);
1634
- const constPrimitive = getPrimitiveType((() => {
1635
- if (typeof constValue === "number") return "number";
1636
- if (typeof constValue === "boolean") return "boolean";
1637
- return "string";
1638
- })());
1639
- return createNode({
1640
- schema,
1641
- name,
1642
- nullable,
1643
- defaultValue
1644
- }, {
1691
+ return createNode(context, {
1645
1692
  type: "enum",
1646
- primitive: constPrimitive,
1693
+ primitive: getPrimitiveType(typeof constValue),
1647
1694
  enumValues: [constValue]
1648
1695
  });
1649
1696
  }
@@ -1653,36 +1700,30 @@ function convertConst({ schema, name, nullable, defaultValue }) {
1653
1700
  * a date-ish format, that `dateType` is not `false`.
1654
1701
  */
1655
1702
  function convertFormat(context) {
1656
- const { schema, name, nullable, defaultValue, options, type } = context;
1657
- const ctx = {
1658
- schema,
1659
- name,
1660
- nullable,
1661
- defaultValue
1662
- };
1703
+ const { schema, options, type } = context;
1663
1704
  if (type === "string" && numericFormats.has(schema.format)) return convertString(context);
1664
- if (schema.format === "int64" || schema.format === "uint64") return createNode(ctx, {
1705
+ if (schema.format === "int64" || schema.format === "uint64") return createNode(context, {
1665
1706
  type: options.integerType === "bigint" ? "bigint" : "integer",
1666
1707
  primitive: "integer",
1667
1708
  ...getNumericConstraints(schema)
1668
1709
  });
1669
1710
  if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1670
1711
  const dateType = getDateType(options, schema.format);
1671
- if (dateType.type === "datetime") return createNode(ctx, {
1712
+ if (dateType.type === "datetime") return createNode(context, {
1672
1713
  primitive: "string",
1673
1714
  type: "datetime",
1674
1715
  offset: dateType.offset,
1675
1716
  local: dateType.local
1676
1717
  });
1677
- return createNode(ctx, {
1718
+ return createNode(context, {
1678
1719
  primitive: "string",
1679
1720
  type: dateType.type,
1680
1721
  representation: dateType.representation
1681
1722
  });
1682
1723
  }
1683
- const specialType = getSchemaType(schema.format);
1684
- const isNumeric = specialType === "number" || specialType === "integer" || specialType === "bigint";
1685
- return createNode(ctx, {
1724
+ const specialType = formatMap[schema.format];
1725
+ const isNumeric = specialType === "number" || specialType === "integer";
1726
+ return createNode(context, {
1686
1727
  primitive: isNumeric ? specialType : "string",
1687
1728
  type: specialType,
1688
1729
  ...specialType === "url" || specialType === "uuid" || specialType === "email" ? {
@@ -1695,11 +1736,11 @@ function convertFormat(context) {
1695
1736
  /**
1696
1737
  * Converts an `enum` schema into an `EnumSchemaNode`.
1697
1738
  */
1698
- function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
1739
+ function convertEnum({ schema, name, nullable, type, parse }) {
1699
1740
  if (type === "array") return parse({
1700
1741
  schema: normalizeArrayEnum(schema),
1701
1742
  name
1702
- }, rawOptions);
1743
+ });
1703
1744
  const nullInEnum = schema.enum.includes(null);
1704
1745
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1705
1746
  if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
@@ -1751,13 +1792,9 @@ function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
1751
1792
  /**
1752
1793
  * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1753
1794
  */
1754
- function convertString({ schema, name, nullable, defaultValue }) {
1755
- return createNode({
1756
- schema,
1757
- name,
1758
- nullable,
1759
- defaultValue
1760
- }, {
1795
+ function convertString(context) {
1796
+ const { schema } = context;
1797
+ return createNode(context, {
1761
1798
  type: "string",
1762
1799
  primitive: "string",
1763
1800
  min: schema.minLength,
@@ -1766,58 +1803,13 @@ function convertString({ schema, name, nullable, defaultValue }) {
1766
1803
  });
1767
1804
  }
1768
1805
  /**
1769
- * Reads the numeric constraints shared by every `number`, `integer` and `bigint` node. A `format`
1770
- * such as `double` or `int32` only narrows the type, so formatted numbers keep these too.
1771
- */
1772
- function getNumericConstraints(schema) {
1773
- return {
1774
- min: schema.minimum,
1775
- max: schema.maximum,
1776
- ...getExclusiveBounds(schema),
1777
- multipleOf: schema.multipleOf
1778
- };
1779
- }
1780
- /**
1781
1806
  * Converts a `type: 'number'` or `type: 'integer'` schema.
1782
1807
  */
1783
- function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1784
- return createNode({
1785
- schema,
1786
- name,
1787
- nullable,
1788
- defaultValue
1789
- }, {
1808
+ function convertNumeric(context, type) {
1809
+ return createNode(context, {
1790
1810
  type,
1791
1811
  primitive: type,
1792
- ...getNumericConstraints(schema)
1793
- });
1794
- }
1795
- /**
1796
- * Converts a `type: 'boolean'` schema.
1797
- */
1798
- function convertBoolean({ schema, name, nullable, defaultValue }) {
1799
- return createNode({
1800
- schema,
1801
- name,
1802
- nullable,
1803
- defaultValue
1804
- }, {
1805
- type: "boolean",
1806
- primitive: "boolean"
1807
- });
1808
- }
1809
- /**
1810
- * Converts a raw binary schema into a `blob` node.
1811
- */
1812
- function convertBinary({ schema, name, nullable, defaultValue }) {
1813
- return createNode({
1814
- schema,
1815
- name,
1816
- nullable,
1817
- defaultValue
1818
- }, {
1819
- type: "blob",
1820
- primitive: "string"
1812
+ ...getNumericConstraints(context.schema)
1821
1813
  });
1822
1814
  }
1823
1815
  //#endregion
@@ -1826,9 +1818,9 @@ function convertBinary({ schema, name, nullable, defaultValue }) {
1826
1818
  * Resolves a `true` or empty-object map schema (`additionalProperties`/`patternProperties`) to
1827
1819
  * `options.unknownType`, otherwise parses it as a regular schema.
1828
1820
  */
1829
- function resolveMapSchema(mapSchema, options, parse, rawOptions) {
1821
+ function resolveMapSchema(mapSchema, { options, parse }) {
1830
1822
  if (mapSchema === true || typeof mapSchema === "object" && Object.keys(mapSchema).length === 0) return ast.factory.createSchema({ type: options.unknownType });
1831
- return parse({ schema: mapSchema }, rawOptions);
1823
+ return parse({ schema: mapSchema });
1832
1824
  }
1833
1825
  /**
1834
1826
  * Names the inline enums on a property's schema, and on each item when the property is a tuple, from
@@ -1850,7 +1842,7 @@ function nameEnums(node, options) {
1850
1842
  /**
1851
1843
  * Converts an object-like schema into an `ObjectSchemaNode`.
1852
1844
  */
1853
- function convertObject({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1845
+ function convertObject({ schema, name, nullable, defaultValue, options, parse }) {
1854
1846
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1855
1847
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1856
1848
  const resolvedPropSchema = propSchema;
@@ -1858,7 +1850,7 @@ function convertObject({ schema, name, nullable, defaultValue, rawOptions, optio
1858
1850
  const schemaNode = nameEnums(parse({
1859
1851
  schema: resolvedPropSchema,
1860
1852
  name: childName(name, propName)
1861
- }, rawOptions), {
1853
+ }), {
1862
1854
  parentName: name,
1863
1855
  propName,
1864
1856
  enumSuffix: options.enumSuffix
@@ -1875,11 +1867,17 @@ function convertObject({ schema, name, nullable, defaultValue, rawOptions, optio
1875
1867
  const additionalProperties = schema.additionalProperties;
1876
1868
  const additionalPropertiesNode = (() => {
1877
1869
  if (additionalProperties === true) return true;
1878
- if (additionalProperties) return resolveMapSchema(additionalProperties, options, parse, rawOptions);
1870
+ if (additionalProperties) return resolveMapSchema(additionalProperties, {
1871
+ options,
1872
+ parse
1873
+ });
1879
1874
  return additionalProperties;
1880
1875
  })();
1881
1876
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1882
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, resolveMapSchema(patternSchema, options, parse, rawOptions)])) : void 0;
1877
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, resolveMapSchema(patternSchema, {
1878
+ options,
1879
+ parse
1880
+ })])) : void 0;
1883
1881
  const objectNode = createNode({
1884
1882
  schema,
1885
1883
  name,
@@ -1909,9 +1907,9 @@ function convertObject({ schema, name, nullable, defaultValue, rawOptions, optio
1909
1907
  /**
1910
1908
  * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1911
1909
  */
1912
- function convertTuple({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1913
- const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }, rawOptions));
1914
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: schema.items }, rawOptions);
1910
+ function convertTuple({ schema, name, nullable, defaultValue, options, parse }) {
1911
+ const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }));
1912
+ const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: schema.items });
1915
1913
  return createNode({
1916
1914
  schema,
1917
1915
  name,
@@ -1929,13 +1927,13 @@ function convertTuple({ schema, name, nullable, defaultValue, rawOptions, option
1929
1927
  /**
1930
1928
  * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1931
1929
  */
1932
- function convertArray({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1930
+ function convertArray({ schema, name, nullable, defaultValue, options, parse }) {
1933
1931
  const rawItems = schema.items;
1934
1932
  const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
1935
1933
  const items = rawItems ? [parse({
1936
1934
  schema: rawItems,
1937
1935
  name: itemName
1938
- }, rawOptions)] : [];
1936
+ })] : [];
1939
1937
  return createNode({
1940
1938
  schema,
1941
1939
  name,
@@ -1994,7 +1992,10 @@ const schemaRules = [
1994
1992
  },
1995
1993
  {
1996
1994
  match: ({ schema }) => isBinary(schema),
1997
- convert: convertBinary
1995
+ convert: (ctx) => createNode(ctx, {
1996
+ type: "blob",
1997
+ primitive: "string"
1998
+ })
1998
1999
  },
1999
2000
  {
2000
2001
  match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
@@ -2034,7 +2035,10 @@ const schemaRules = [
2034
2035
  },
2035
2036
  {
2036
2037
  match: ({ type }) => type === "boolean",
2037
- convert: convertBoolean
2038
+ convert: (ctx) => createNode(ctx, {
2039
+ type: "boolean",
2040
+ primitive: "boolean"
2041
+ })
2038
2042
  },
2039
2043
  {
2040
2044
  match: ({ type }) => type === "null",
@@ -2042,95 +2046,94 @@ const schemaRules = [
2042
2046
  }
2043
2047
  ];
2044
2048
  //#endregion
2045
- //#region src/refs.ts
2046
- const _refCache = /* @__PURE__ */ new WeakMap();
2047
- /**
2048
- * Walks a local `#/...` JSON pointer against `document`, memoized per document. `applicable` is
2049
- * `false` for an empty or non-local ref (the caller should not treat that as a failed lookup).
2050
- * Shared by `resolveRef`'s reporting walk and `createRefs().resolve`'s silent walk, so both use
2051
- * the same trimming and caching instead of two separate implementations.
2052
- */
2053
- function walkPointer(document, $ref) {
2054
- const trimmed = $ref.trim();
2055
- if (trimmed === "" || !trimmed.startsWith("#")) return {
2056
- applicable: false,
2057
- value: null
2049
+ //#region src/parser.ts
2050
+ /**
2051
+ * Creates the schema converter bound to one OpenAPI document: `parseSchema` dispatches each
2052
+ * schema through the ordered `schemaRules` table, with the parser options merged once.
2053
+ *
2054
+ * @internal
2055
+ */
2056
+ function createSchemaParser(ctx) {
2057
+ const { document, refs, renames } = ctx;
2058
+ const options = {
2059
+ ...DEFAULT_PARSER_OPTIONS,
2060
+ ...ctx.options
2058
2061
  };
2059
- const pointer = trimmed.substring(1);
2060
- let docCache = _refCache.get(document);
2061
- if (!docCache) {
2062
- docCache = /* @__PURE__ */ new Map();
2063
- _refCache.set(document, docCache);
2062
+ function parseSchema({ schema, name }) {
2063
+ const flattenedSchema = flattenSchema(schema);
2064
+ if (flattenedSchema !== schema) return parseSchema({
2065
+ schema: flattenedSchema,
2066
+ name
2067
+ });
2068
+ const nullable = isNullable(schema) || void 0;
2069
+ const context = {
2070
+ schema,
2071
+ name,
2072
+ nullable,
2073
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
2074
+ type: Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") ?? schema.type[0] : schema.type,
2075
+ options,
2076
+ parse: parseSchema,
2077
+ document,
2078
+ refs,
2079
+ renames
2080
+ };
2081
+ for (const rule of schemaRules) if (rule.match(context)) return rule.convert(context);
2082
+ return createNode(context, { type: options.emptySchemaType });
2064
2083
  }
2065
- if (docCache.has(pointer)) return {
2066
- applicable: true,
2067
- value: docCache.get(pointer)
2068
- };
2069
- const current = pointer.split("/").filter(Boolean).map((token) => globalThis.decodeURIComponent(token).replaceAll("~1", "/").replaceAll("~0", "~")).reduce((obj, key) => obj?.[key], document);
2070
- if (current) docCache.set(pointer, current);
2071
- return {
2072
- applicable: true,
2073
- value: current ?? null
2074
- };
2084
+ return { parseSchema };
2075
2085
  }
2086
+ //#endregion
2087
+ //#region src/promoteEnums.ts
2076
2088
  /**
2077
- * Resolves a local JSON pointer reference from a document.
2078
- *
2079
- * Accepts `#/...` refs. Returns `null` for an empty or non-local ref. When the pointer cannot be
2080
- * resolved, reports a `refNotFound` diagnostic into the active build and returns `null`. Outside a
2081
- * build there is no sink to collect it, so it throws instead.
2082
- *
2083
- * @example
2084
- * ```ts
2085
- * resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
2086
- * ```
2089
+ * Collects inline enums to lift to the top level, keyed by the name the parser derived for them
2090
+ * (e.g. `PetStatusEnum`). An enum already defined as a top-level component is left as-is, and a
2091
+ * name that recurs maps to the first definition so each name yields one shared type.
2087
2092
  */
2088
- function resolveRef(document, $ref) {
2089
- const { applicable, value } = walkPointer(document, $ref);
2090
- if (!applicable) return null;
2091
- if (value) return value;
2092
- const diagnostic = {
2093
- code: Diagnostics.code.refNotFound,
2094
- severity: "error",
2095
- message: `Could not find a definition for ${$ref}.`,
2096
- help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
2097
- location: {
2098
- kind: "schema",
2099
- pointer: $ref,
2100
- ref: $ref
2093
+ function collectInlineEnums(roots, topLevelNames) {
2094
+ const promoted = /* @__PURE__ */ new Map();
2095
+ for (const root of roots) {
2096
+ const isSchemaRoot = root.kind === "Schema";
2097
+ for (const node of ast.collect(root, { schema: (schemaNode) => schemaNode })) {
2098
+ if (node.type !== "enum" || !node.name) continue;
2099
+ if (isSchemaRoot && node === root) continue;
2100
+ if (topLevelNames.has(node.name)) continue;
2101
+ if (!promoted.has(node.name)) promoted.set(node.name, {
2102
+ ...node,
2103
+ optional: void 0,
2104
+ nullish: void 0
2105
+ });
2101
2106
  }
2102
- };
2103
- if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
2104
- return null;
2107
+ }
2108
+ return promoted;
2105
2109
  }
2106
2110
  /**
2107
- * Resolves a `$ref` object while preserving the original `$ref` field on the result.
2108
- *
2109
- * Useful for parser flows that need both dereferenced fields and pointer
2110
- * identity (for naming/import purposes). Non-reference values are returned as-is.
2111
- *
2112
- * @example
2113
- * ```ts
2114
- * dereferenceWithRef(document, { $ref: '#/components/schemas/Pet' })
2115
- * // { $ref: '#/components/schemas/Pet', type: 'object', properties: { ... } }
2116
- * ```
2111
+ * Replaces every promoted inline enum in `node` with a `ref` to its lifted definition, keeping the
2112
+ * occurrence's usage-slot and documentation fields.
2117
2113
  */
2118
- function dereferenceWithRef(document, schema) {
2119
- if (isReference(schema)) return {
2120
- ...schema,
2121
- ...resolveRef(document, schema.$ref),
2122
- $ref: schema.$ref
2123
- };
2124
- return schema;
2114
+ function refPromotedEnums(node, promoted) {
2115
+ if (promoted.size === 0) return node;
2116
+ return ast.transform(node, { schema(schemaNode) {
2117
+ if (schemaNode.type !== "enum" || !schemaNode.name || !promoted.has(schemaNode.name)) return void 0;
2118
+ return ast.factory.createSchema({
2119
+ type: "ref",
2120
+ name: schemaNode.name,
2121
+ ref: `${SCHEMA_REF_PREFIX}${schemaNode.name}`,
2122
+ optional: schemaNode.optional,
2123
+ nullish: schemaNode.nullish,
2124
+ readOnly: schemaNode.readOnly,
2125
+ writeOnly: schemaNode.writeOnly,
2126
+ deprecated: schemaNode.deprecated,
2127
+ description: schemaNode.description,
2128
+ default: schemaNode.default,
2129
+ examples: schemaNode.examples
2130
+ });
2131
+ } });
2125
2132
  }
2133
+ //#endregion
2134
+ //#region src/refs.ts
2126
2135
  /**
2127
- * Creates the `$ref` resolution service for one document.
2128
- *
2129
- * Replaces what used to be six overlapping resolvers (a reporting walk, a silent walk, an
2130
- * existence check, and a resolve-then-parse-into-a-node step, each with its own cache) with one
2131
- * pointer walk and one explicit `report` contract for a missing ref: `report: true` (the default)
2132
- * reports a `refNotFound` diagnostic (or throws outside a build), `report: false` resolves to
2133
- * `null` silently for a speculative lookup.
2136
+ * Creates the `$ref` resolution service for one document. One pointer walk backs every method.
2134
2137
  *
2135
2138
  * @example
2136
2139
  * ```ts
@@ -2140,12 +2143,31 @@ function dereferenceWithRef(document, schema) {
2140
2143
  * refs.exists('#/components/schemas/Pet')
2141
2144
  * refs.resolveNode('#/components/schemas/Pet', parseSchema)
2142
2145
  * refs.deref<ResponseObject>(operation.schema.responses?.['200'])
2146
+ * refs.derefKeepingRef(parameter)
2143
2147
  * ```
2144
2148
  */
2145
2149
  function createRefs(document) {
2150
+ const pointerCache = /* @__PURE__ */ new Map();
2146
2151
  const resolvedNodeCache = /* @__PURE__ */ new Map();
2147
- const existenceCache = /* @__PURE__ */ new Map();
2148
2152
  const resolvingRefs = /* @__PURE__ */ new Set();
2153
+ function walkPointer($ref) {
2154
+ const trimmed = $ref.trim();
2155
+ if (trimmed === "" || !trimmed.startsWith("#")) return {
2156
+ applicable: false,
2157
+ value: null
2158
+ };
2159
+ const pointer = trimmed.substring(1);
2160
+ if (pointerCache.has(pointer)) return {
2161
+ applicable: true,
2162
+ value: pointerCache.get(pointer)
2163
+ };
2164
+ const current = pointer.split("/").filter(Boolean).map((token) => globalThis.decodeURIComponent(token).replaceAll("~1", "/").replaceAll("~0", "~")).reduce((obj, key) => obj?.[key], document);
2165
+ if (current) pointerCache.set(pointer, current);
2166
+ return {
2167
+ applicable: true,
2168
+ value: current ?? null
2169
+ };
2170
+ }
2149
2171
  /**
2150
2172
  * Resolves a local `#/...` JSON pointer. Returns `null` for an empty or non-local ref.
2151
2173
  * `report: true` (default) reports a `refNotFound` diagnostic into the active build (or throws
@@ -2153,27 +2175,36 @@ function createRefs(document) {
2153
2175
  * for a speculative lookup where a missing ref is not an error.
2154
2176
  */
2155
2177
  function resolve(refPath, options) {
2156
- if (options?.report === false) {
2157
- const { applicable, value } = walkPointer(document, refPath);
2158
- return applicable ? value : null;
2159
- }
2160
- return resolveRef(document, refPath);
2178
+ const { applicable, value } = walkPointer(refPath);
2179
+ if (!applicable || value || options?.report === false) return value;
2180
+ const diagnostic = {
2181
+ code: Diagnostics.code.refNotFound,
2182
+ severity: "error",
2183
+ message: `Could not find a definition for ${refPath}.`,
2184
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
2185
+ location: {
2186
+ kind: "schema",
2187
+ pointer: refPath,
2188
+ ref: refPath
2189
+ }
2190
+ };
2191
+ if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
2192
+ return null;
2161
2193
  }
2162
2194
  /**
2163
2195
  * Returns `true` when a `$ref` path resolves to a component the document actually defines.
2164
2196
  * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
2165
- * goes `false` for a `$ref` that points at a component the spec never declares. Memoized.
2197
+ * goes `false` for a `$ref` that points at a component the spec never declares.
2166
2198
  */
2167
2199
  function exists(refPath) {
2168
- if (!existenceCache.has(refPath)) existenceCache.set(refPath, !!resolve(refPath, { report: false }));
2169
- return existenceCache.get(refPath) ?? false;
2200
+ return !!resolve(refPath, { report: false });
2170
2201
  }
2171
2202
  /**
2172
2203
  * Resolves a `$ref` to its parsed node via `parse`, guarding against cycles and memoizing per
2173
2204
  * instance. Returns `null` when the ref is currently being resolved (a cycle) or cannot be
2174
2205
  * resolved (e.g. a minimal document in a unit test).
2175
2206
  */
2176
- function resolveNode(refPath, parse, rawOptions) {
2207
+ function resolveNode(refPath, parse) {
2177
2208
  if (resolvingRefs.has(refPath)) return null;
2178
2209
  if (!resolvedNodeCache.has(refPath)) {
2179
2210
  let resolved = null;
@@ -2181,7 +2212,7 @@ function createRefs(document) {
2181
2212
  const referenced = resolve(refPath);
2182
2213
  if (referenced) {
2183
2214
  resolvingRefs.add(refPath);
2184
- resolved = parse({ schema: referenced }, rawOptions);
2215
+ resolved = parse({ schema: referenced });
2185
2216
  resolvingRefs.delete(refPath);
2186
2217
  }
2187
2218
  } catch {}
@@ -2194,393 +2225,30 @@ function createRefs(document) {
2194
2225
  * resolved target. Returns `null` when the value is empty, cannot be resolved, or is still a
2195
2226
  * `$ref` after resolving (e.g. a document with no component registry). A non-`$ref` value is
2196
2227
  * returned as-is.
2197
- *
2198
- * @example
2199
- * ```ts
2200
- * refs.deref<ResponseObject>(operation.schema.responses?.['200'])
2201
- * ```
2202
2228
  */
2203
2229
  function deref(value) {
2204
2230
  if (!isReference(value)) return value ? value : null;
2205
2231
  const resolved = resolve(value.$ref);
2206
2232
  return resolved && !isReference(resolved) ? resolved : null;
2207
2233
  }
2234
+ /** Resolves a `$ref` object but keeps the `$ref` field on the result; other values pass through. */
2235
+ function derefKeepingRef(value) {
2236
+ if (isReference(value)) return {
2237
+ ...value,
2238
+ ...resolve(value.$ref),
2239
+ $ref: value.$ref
2240
+ };
2241
+ return value;
2242
+ }
2208
2243
  return {
2209
2244
  resolve,
2210
2245
  exists,
2211
2246
  resolveNode,
2212
- deref
2213
- };
2214
- }
2215
- //#endregion
2216
- //#region src/model/operations.ts
2217
- /**
2218
- * Returns all parameters for an operation, merging path-level and operation-level entries.
2219
- * Operation-level parameters override path-level ones with the same `in:name` key.
2220
- * Each `$ref` parameter is dereferenced via `dereferenceWithRef` before merging.
2221
- *
2222
- * @example
2223
- * ```ts
2224
- * getParameters({ document, operation })
2225
- * // [{ name: 'petId', in: 'path', required: true, schema: { type: 'integer' } }]
2226
- * ```
2227
- */
2228
- function getParameters({ document, operation }) {
2229
- const resolveParams = (params) => params.map((p) => dereferenceWithRef(document, p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
2230
- const operationParams = resolveParams(operation.schema?.parameters || []);
2231
- const pathLevelParams = resolveParams(operation.pathItem.parameters ?? []);
2232
- const paramMap = /* @__PURE__ */ new Map();
2233
- for (const p of pathLevelParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
2234
- for (const p of operationParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
2235
- return Array.from(paramMap.values());
2236
- }
2237
- function getResponseBody(responseBody, contentType) {
2238
- if (!responseBody) return false;
2239
- if (isReference(responseBody)) return false;
2240
- const body = responseBody;
2241
- if (!body.content) return false;
2242
- if (contentType) return contentType in body.content ? [contentType, body.content[contentType]] : false;
2243
- return pickContentEntry(body.content);
2244
- }
2245
- /**
2246
- * Returns the response schema for a given operation and HTTP status code.
2247
- *
2248
- * Returns an empty object `{}` when no response body schema is available.
2249
- *
2250
- * @example
2251
- * ```ts
2252
- * getResponseSchema({ document, operation, refs, statusCode: 200 }) // SchemaObject
2253
- * getResponseSchema({ document, operation, refs, statusCode: '4XX' }) // {}
2254
- * ```
2255
- */
2256
- function getResponseSchema({ document, operation, refs, statusCode, options = {} }) {
2257
- const responseBody = getResponseBody(getResponseByStatusCode({
2258
- operation,
2259
- refs,
2260
- statusCode
2261
- }), options.contentType);
2262
- if (responseBody === false) return {};
2263
- const [mediaType, entry] = responseBody;
2264
- const schema = entry.schema;
2265
- const binary = getBinaryFallbackSchema(mediaType, schema);
2266
- if (binary) return binary;
2267
- if (!schema) return {};
2268
- return dereferenceWithRef(document, schema);
2269
- }
2270
- /**
2271
- * Returns the request body schema for an operation, or `null` when absent.
2272
- *
2273
- * @example
2274
- * ```ts
2275
- * getRequestSchema({ document, operation, refs }) // SchemaObject | null
2276
- * ```
2277
- */
2278
- function getRequestSchema({ document, operation, refs, options = {} }) {
2279
- const requestBody = getRequestContent({
2280
- operation,
2281
- refs,
2282
- mediaType: options.contentType
2283
- });
2284
- if (requestBody === false) return null;
2285
- const mediaType = Array.isArray(requestBody) ? requestBody[0] : options.contentType;
2286
- const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
2287
- const binary = getBinaryFallbackSchema(mediaType, schema);
2288
- if (binary) return binary;
2289
- if (!schema) return null;
2290
- return dereferenceWithRef(document, schema);
2291
- }
2292
- /**
2293
- * Returns all request body content type keys for an operation, resolving a `$ref` requestBody
2294
- * through `refs`.
2295
- *
2296
- * @example
2297
- * ```ts
2298
- * getRequestBodyContentTypes(operation, refs)
2299
- * // ['application/json', 'multipart/form-data']
2300
- * ```
2301
- */
2302
- function getRequestBodyContentTypes(operation, refs) {
2303
- const body = getRequestBody({
2304
- operation,
2305
- refs
2306
- });
2307
- return body?.content ? Object.keys(body.content) : [];
2308
- }
2309
- /**
2310
- * Returns all response content type keys for an operation at a given status code, resolving the
2311
- * response `$ref` through `refs`.
2312
- *
2313
- * @example
2314
- * ```ts
2315
- * getResponseBodyContentTypes(operation, refs, 200)
2316
- * // ['application/json', 'application/xml']
2317
- * ```
2318
- */
2319
- function getResponseBodyContentTypes(operation, refs, statusCode) {
2320
- const responseObj = getResponseByStatusCode({
2321
- operation,
2322
- refs,
2323
- statusCode
2324
- });
2325
- if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
2326
- const body = responseObj;
2327
- return body.content ? Object.keys(body.content) : [];
2328
- }
2329
- //#endregion
2330
- //#region src/parser.ts
2331
- /**
2332
- * Creates the schema and operation converters bound to one OpenAPI document.
2333
- *
2334
- * Takes the `$ref` service for this document (shared with the rest of the pipeline, see
2335
- * `adapter.ts`) and owns the `parseSchema` recursion seam, then dispatches each schema through
2336
- * the ordered `schemaRules` table from `emit/parseSchema.ts`. Every converter is a standalone
2337
- * function that recurses through the `parse` function passed to it, so this file only wires
2338
- * state to the converters.
2339
- *
2340
- * @internal
2341
- */
2342
- function createSchemaParser(ctx) {
2343
- const document = ctx.document;
2344
- const refs = ctx.refs;
2345
- /**
2346
- * Converts an OAS `SchemaObject` into a `SchemaNode`.
2347
- *
2348
- * Builds the per-schema context, then walks the ordered {@link schemaRules} table and returns
2349
- * the first converter that produces a node. When none match, falls back to the configured
2350
- * `emptySchemaType`.
2351
- */
2352
- function parseSchema({ schema, name }, rawOptions) {
2353
- const options = {
2354
- ...DEFAULT_PARSER_OPTIONS,
2355
- ...rawOptions
2356
- };
2357
- const flattenedSchema = flattenSchema(schema);
2358
- if (flattenedSchema && flattenedSchema !== schema) return parseSchema({
2359
- schema: flattenedSchema,
2360
- name
2361
- }, rawOptions);
2362
- const nullable = isNullable(schema) || void 0;
2363
- const context = {
2364
- schema,
2365
- name,
2366
- nullable,
2367
- defaultValue: schema.default === null && nullable ? void 0 : schema.default,
2368
- type: Array.isArray(schema.type) ? schema.type.find((t) => t !== "null") ?? schema.type[0] : schema.type,
2369
- rawOptions,
2370
- options,
2371
- parse: parseSchema,
2372
- document,
2373
- refs,
2374
- renames: ctx.renames
2375
- };
2376
- for (const rule of schemaRules) if (rule.match(context)) return rule.convert(context);
2377
- const emptyType = options.emptySchemaType;
2378
- return createNode(context, { type: emptyType });
2379
- }
2380
- /**
2381
- * Converts a dereferenced OAS parameter object into a `ParameterNode`.
2382
- */
2383
- function parseParameter(options, param, parentName) {
2384
- const required = param["required"] ?? false;
2385
- const paramName = param["name"];
2386
- const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
2387
- const schema = param["schema"] ? parseSchema({
2388
- schema: param["schema"],
2389
- name: schemaName
2390
- }, options) : ast.factory.createSchema({ type: options.unknownType });
2391
- const style = param["style"];
2392
- const explode = param["explode"];
2393
- return ast.factory.createParameter({
2394
- name: paramName,
2395
- in: param["in"],
2396
- schema: {
2397
- ...schema,
2398
- description: param["description"] ?? schema.description
2399
- },
2400
- required,
2401
- ...style !== void 0 ? { style } : {},
2402
- ...explode !== void 0 ? { explode } : {}
2403
- });
2404
- }
2405
- /**
2406
- * Reads the inline `requestBody` metadata (description / required) that OAS exposes
2407
- * outside the schema itself, resolving a `$ref` requestBody through `refs`. Returns an
2408
- * empty object when the request body is missing or cannot be resolved.
2409
- */
2410
- function getRequestBodyMeta(operation) {
2411
- const body = getRequestBody({
2412
- operation,
2413
- refs
2414
- });
2415
- if (!body) return { required: false };
2416
- return {
2417
- description: body.description,
2418
- required: body.required === true
2419
- };
2420
- }
2421
- /**
2422
- * Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
2423
- * `$ref` entries are skipped since their flags live on the dereferenced target.
2424
- */
2425
- function collectPropertyKeysByFlag(schema, flag) {
2426
- if (!schema?.properties) return null;
2427
- const keys = [];
2428
- for (const key in schema.properties) {
2429
- const prop = schema.properties[key];
2430
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
2431
- }
2432
- return keys.length ? keys : null;
2433
- }
2434
- /**
2435
- * Converts an OAS `Operation` into an `OperationNode`.
2436
- */
2437
- function parseOperation(options, operation) {
2438
- const operationId = getOperationId(operation);
2439
- const operationName = operationId ? pascalCase(operationId) : void 0;
2440
- const parameters = getParameters({
2441
- document,
2442
- operation
2443
- }).map((param) => parseParameter(options, param, operationName));
2444
- const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(operation, refs);
2445
- const requestBodyMeta = getRequestBodyMeta(operation);
2446
- const requestBodyName = operationName ? `${operationName}Request` : void 0;
2447
- const content = allContentTypes.flatMap((ct) => {
2448
- const schema = getRequestSchema({
2449
- document,
2450
- operation,
2451
- refs,
2452
- options: { contentType: ct }
2453
- });
2454
- if (!schema) return [];
2455
- return [ast.factory.createContent({
2456
- contentType: ct,
2457
- schema: ast.optionality(parseSchema({
2458
- schema,
2459
- name: requestBodyName
2460
- }, options), requestBodyMeta.required),
2461
- keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
2462
- })];
2463
- });
2464
- const requestBody = content.length > 0 || requestBodyMeta.description ? {
2465
- description: requestBodyMeta.description,
2466
- required: requestBodyMeta.required || void 0,
2467
- content: content.length > 0 ? content : void 0
2468
- } : void 0;
2469
- const responses = getResponseStatusCodes(operation).map((statusCode) => {
2470
- const responseObj = getResponseByStatusCode({
2471
- operation,
2472
- refs,
2473
- statusCode
2474
- });
2475
- const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2476
- const description = typeof responseObj === "object" && responseObj !== null ? responseObj.description : void 0;
2477
- const parseEntrySchema = (contentType) => {
2478
- const raw = getResponseSchema({
2479
- document,
2480
- operation,
2481
- refs,
2482
- statusCode,
2483
- options: { contentType }
2484
- });
2485
- return {
2486
- schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2487
- schema: raw,
2488
- name: responseName
2489
- }, options) : ast.factory.createSchema({ type: options.emptySchemaType }),
2490
- keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2491
- };
2492
- };
2493
- const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(operation, refs, statusCode)).map((contentType) => ast.factory.createContent({
2494
- contentType,
2495
- ...parseEntrySchema(contentType)
2496
- }));
2497
- if (content.length === 0) content.push(ast.factory.createContent({
2498
- contentType: getRequestContentType({
2499
- operation,
2500
- refs
2501
- }) || "application/json",
2502
- ...parseEntrySchema(ctx.contentType)
2503
- }));
2504
- return ast.factory.createResponse({
2505
- statusCode,
2506
- description,
2507
- content
2508
- });
2509
- });
2510
- const pickDoc = (key) => {
2511
- const own = operation.schema[key];
2512
- if (typeof own === "string") return own;
2513
- const fallback = operation.pathItem[key];
2514
- return typeof fallback === "string" ? fallback : void 0;
2515
- };
2516
- return ast.factory.createOperation({
2517
- operationId,
2518
- protocol: "http",
2519
- method: operation.method.toUpperCase(),
2520
- path: operation.path,
2521
- tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
2522
- summary: pickDoc("summary") || void 0,
2523
- description: pickDoc("description") || void 0,
2524
- deprecated: operation.schema.deprecated || void 0,
2525
- parameters,
2526
- requestBody,
2527
- responses
2528
- });
2529
- }
2530
- return {
2531
- parseSchema,
2532
- parseOperation,
2533
- parseParameter
2247
+ deref,
2248
+ derefKeepingRef
2534
2249
  };
2535
2250
  }
2536
2251
  //#endregion
2537
- //#region src/promoteEnums.ts
2538
- /**
2539
- * Collects inline enums to lift to the top level, keyed by the name the parser derived for them
2540
- * (e.g. `PetStatusEnum`). An enum already defined as a top-level component is left as-is, and a
2541
- * name that recurs maps to the first definition so each name yields one shared type.
2542
- */
2543
- function collectInlineEnums(roots, topLevelNames) {
2544
- const promoted = /* @__PURE__ */ new Map();
2545
- for (const root of roots) {
2546
- const isSchemaRoot = root.kind === "Schema";
2547
- for (const node of ast.collect(root, { schema: (schemaNode) => schemaNode })) {
2548
- if (node.type !== "enum" || !node.name) continue;
2549
- if (isSchemaRoot && node === root) continue;
2550
- if (topLevelNames.has(node.name)) continue;
2551
- if (!promoted.has(node.name)) promoted.set(node.name, {
2552
- ...node,
2553
- optional: void 0,
2554
- nullish: void 0
2555
- });
2556
- }
2557
- }
2558
- return promoted;
2559
- }
2560
- /**
2561
- * Replaces every promoted inline enum in `node` with a `ref` to its lifted definition, keeping the
2562
- * occurrence's usage-slot and documentation fields.
2563
- */
2564
- function refPromotedEnums(node, promoted) {
2565
- if (promoted.size === 0) return node;
2566
- return ast.transform(node, { schema(schemaNode) {
2567
- if (schemaNode.type !== "enum" || !schemaNode.name || !promoted.has(schemaNode.name)) return void 0;
2568
- return ast.factory.createSchema({
2569
- type: "ref",
2570
- name: schemaNode.name,
2571
- ref: `${SCHEMA_REF_PREFIX}${schemaNode.name}`,
2572
- optional: schemaNode.optional,
2573
- nullish: schemaNode.nullish,
2574
- readOnly: schemaNode.readOnly,
2575
- writeOnly: schemaNode.writeOnly,
2576
- deprecated: schemaNode.deprecated,
2577
- description: schemaNode.description,
2578
- default: schemaNode.default,
2579
- examples: schemaNode.examples
2580
- });
2581
- } });
2582
- }
2583
- //#endregion
2584
2252
  //#region src/schemaDiagnostics.ts
2585
2253
  /**
2586
2254
  * Scans one freshly converted top-level schema in a single walk, so the post-convert pass never
@@ -2607,7 +2275,7 @@ function escapePointerToken(token) {
2607
2275
  }
2608
2276
  function visit(node, pointer, refs) {
2609
2277
  if (node.type === "ref") {
2610
- const refName = resolveRefName(node);
2278
+ const refName = ast.resolveRefName(node);
2611
2279
  if (refName) refs.add(refName);
2612
2280
  }
2613
2281
  if (node.deprecated) Diagnostics.report({
@@ -2680,17 +2348,24 @@ const adapterOasName = "oas";
2680
2348
  const adapterOas = createAdapter((options) => {
2681
2349
  const { validate = true, contentType, server, discriminator = "preserve", enums = "inline", dateType = DEFAULT_PARSER_OPTIONS.dateType, integerType = DEFAULT_PARSER_OPTIONS.integerType, unknownType = DEFAULT_PARSER_OPTIONS.unknownType, enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix, emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType } = options;
2682
2350
  const parserOptions = {
2683
- ...DEFAULT_PARSER_OPTIONS,
2684
2351
  dateType,
2685
2352
  integerType,
2686
2353
  unknownType,
2687
2354
  emptySchemaType,
2688
2355
  enumSuffix
2689
2356
  };
2357
+ const resolvedOptions = {
2358
+ validate,
2359
+ contentType,
2360
+ server,
2361
+ discriminator,
2362
+ enums,
2363
+ ...parserOptions
2364
+ };
2690
2365
  let parsedDocument = null;
2691
2366
  const inputCache = /* @__PURE__ */ new WeakMap();
2692
2367
  function parseInput({ document, refs, schemas, parser }) {
2693
- const { parseSchema, parseOperation } = parser;
2368
+ const { parseSchema } = parser;
2694
2369
  const parsedByName = /* @__PURE__ */ new Map();
2695
2370
  const refAliasMap = /* @__PURE__ */ new Map();
2696
2371
  const enumNames = [];
@@ -2700,7 +2375,7 @@ const adapterOas = createAdapter((options) => {
2700
2375
  const node = parseSchema({
2701
2376
  schema,
2702
2377
  name
2703
- }, parserOptions);
2378
+ });
2704
2379
  parsedByName.set(name, node);
2705
2380
  const refs = scanSchema({
2706
2381
  node,
@@ -2708,29 +2383,31 @@ const adapterOas = createAdapter((options) => {
2708
2383
  });
2709
2384
  if (node.name) refGraph.set(node.name, refs);
2710
2385
  if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2711
- if (narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
2386
+ if (ast.narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
2712
2387
  if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2713
2388
  }
2714
- const circularNames = [...findCircularSchemasFromGraph(refGraph)];
2389
+ const circularNames = [...ast.findCircularSchemasFromGraph(refGraph)];
2715
2390
  const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2716
- const operationNodes = [];
2717
- for (const operation of getOperations(document, refs)) {
2718
- const operationNode = parseOperation(parserOptions, operation);
2719
- if (operationNode) operationNodes.push(operationNode);
2720
- }
2391
+ const operationNodes = getOperations(document, refs).map((operation) => parseOperation({
2392
+ operation,
2393
+ refs,
2394
+ contentType,
2395
+ options: parserOptions,
2396
+ parseSchema
2397
+ }));
2721
2398
  const promotedEnums = enums === "root" ? collectInlineEnums([...parsedByName.values(), ...operationNodes], new Set(Object.keys(schemas))) : null;
2722
2399
  if (promotedEnums) for (const name of promotedEnums.keys()) enumNames.push(name);
2723
2400
  const schemaNodes = promotedEnums ? [...promotedEnums.values()] : [];
2724
2401
  for (const name of Object.keys(schemas)) {
2725
- const alias = refAliasMap.get(name);
2726
- const node = alias?.name && parsedByName.has(alias.name) ? {
2727
- ...parsedByName.get(alias.name),
2402
+ const aliasTarget = refAliasMap.get(name)?.name;
2403
+ const aliased = aliasTarget ? parsedByName.get(aliasTarget) : void 0;
2404
+ const parsed = parsedByName.get(name);
2405
+ const child = discriminatorChildMap?.get(name);
2406
+ const base = child ? patchDiscriminatorNode(parsed, child) : parsed;
2407
+ const node = aliased ? {
2408
+ ...aliased,
2728
2409
  name
2729
- } : (() => {
2730
- const parsed = parsedByName.get(name);
2731
- const child = discriminatorChildMap?.get(name);
2732
- return child ? patchDiscriminatorNode(parsed, child) : parsed;
2733
- })();
2410
+ } : base;
2734
2411
  schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
2735
2412
  }
2736
2413
  const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
@@ -2752,20 +2429,7 @@ const adapterOas = createAdapter((options) => {
2752
2429
  }
2753
2430
  return {
2754
2431
  name: "oas",
2755
- get options() {
2756
- return {
2757
- validate,
2758
- contentType,
2759
- server,
2760
- discriminator,
2761
- enums,
2762
- dateType,
2763
- integerType,
2764
- unknownType,
2765
- emptySchemaType,
2766
- enumSuffix
2767
- };
2768
- },
2432
+ options: resolvedOptions,
2769
2433
  get document() {
2770
2434
  return parsedDocument;
2771
2435
  },
@@ -2794,8 +2458,8 @@ const adapterOas = createAdapter((options) => {
2794
2458
  parser: createSchemaParser({
2795
2459
  document,
2796
2460
  refs,
2797
- contentType,
2798
- renames
2461
+ renames,
2462
+ options: parserOptions
2799
2463
  })
2800
2464
  });
2801
2465
  })();