@kubb/adapter-oas 5.0.0-alpha.1 → 5.0.0-alpha.11

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,7 +1,7 @@
1
1
  import "./chunk--u3MIqq1.js";
2
2
  import path from "node:path";
3
3
  import { collect, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, narrowSchema, schemaTypes, transform } from "@kubb/ast";
4
- import { defineAdapter } from "@kubb/core";
4
+ import { createAdapter } from "@kubb/core";
5
5
  import { bundle, loadConfig } from "@redocly/openapi-core";
6
6
  import yaml from "@stoplight/yaml";
7
7
  import { isRef } from "oas/types";
@@ -11,6 +11,110 @@ import swagger2openapi from "swagger2openapi";
11
11
  import jsonpointer from "jsonpointer";
12
12
  import BaseOas from "oas";
13
13
  import { matchesMimeType } from "oas/utils";
14
+ //#region src/constants.ts
15
+ /**
16
+ * Default values for all `Options` fields.
17
+ */
18
+ const DEFAULT_PARSER_OPTIONS = {
19
+ dateType: "string",
20
+ integerType: "number",
21
+ unknownType: "any",
22
+ emptySchemaType: "any",
23
+ enumSuffix: "enum"
24
+ };
25
+ /**
26
+ * OpenAPI version string written into merged document stubs.
27
+ */
28
+ const MERGE_OPENAPI_VERSION = "3.0.0";
29
+ /**
30
+ * Fallback `info.title` used when merging multiple API documents.
31
+ */
32
+ const MERGE_DEFAULT_TITLE = "Merged API";
33
+ /**
34
+ * Fallback `info.version` used when merging multiple API documents.
35
+ */
36
+ const MERGE_DEFAULT_VERSION = "1.0.0";
37
+ /**
38
+ * JSON Schema keywords that indicate structural composition.
39
+ * A schema fragment containing any of these keys must not be inlined into its
40
+ * parent during `allOf` flattening — it carries semantic meaning of its own.
41
+ */
42
+ const structuralKeys = new Set([
43
+ "properties",
44
+ "items",
45
+ "additionalProperties",
46
+ "oneOf",
47
+ "anyOf",
48
+ "allOf",
49
+ "not"
50
+ ]);
51
+ /**
52
+ * Maps OAS/JSON Schema `format` strings to their Kubb `SchemaType` equivalents.
53
+ *
54
+ * Only formats that need a type different from the raw OAS `type` are listed.
55
+ * `int64`, `date-time`, `date`, and `time` are handled separately because their
56
+ * mapping depends on runtime parser options.
57
+ *
58
+ * Note: `ipv4`, `ipv6`, and `hostname` map to `'url'` — the closest supported
59
+ * scalar type in the Kubb AST, even though these are not strictly URLs.
60
+ */
61
+ const formatMap = {
62
+ uuid: "uuid",
63
+ email: "email",
64
+ "idn-email": "email",
65
+ uri: "url",
66
+ "uri-reference": "url",
67
+ url: "url",
68
+ ipv4: "url",
69
+ ipv6: "url",
70
+ hostname: "url",
71
+ "idn-hostname": "url",
72
+ binary: "blob",
73
+ byte: "blob",
74
+ int32: "integer",
75
+ float: "number",
76
+ double: "number"
77
+ };
78
+ /**
79
+ * Exhaustive list of media types that Kubb recognizes.
80
+ */
81
+ const knownMediaTypes = new Set([
82
+ "application/json",
83
+ "application/xml",
84
+ "application/x-www-form-urlencoded",
85
+ "application/octet-stream",
86
+ "application/pdf",
87
+ "application/zip",
88
+ "application/graphql",
89
+ "multipart/form-data",
90
+ "text/plain",
91
+ "text/html",
92
+ "text/csv",
93
+ "text/xml",
94
+ "image/png",
95
+ "image/jpeg",
96
+ "image/gif",
97
+ "image/webp",
98
+ "image/svg+xml",
99
+ "audio/mpeg",
100
+ "video/mp4"
101
+ ]);
102
+ /**
103
+ * Vendor extension keys used to attach human-readable labels to enum values.
104
+ * Checked in priority order: the first key found wins.
105
+ */
106
+ const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
107
+ /**
108
+ * Scalar primitive schema types used for union member simplification.
109
+ */
110
+ const SCALAR_PRIMITIVE_TYPES = new Set([
111
+ "string",
112
+ "number",
113
+ "integer",
114
+ "bigint",
115
+ "boolean"
116
+ ]);
117
+ //#endregion
14
118
  //#region src/oas/resolveServerUrl.ts
15
119
  /**
16
120
  * Resolves an OpenAPI server URL by substituting `{variable}` placeholders.
@@ -89,6 +193,27 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
89
193
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
90
194
  }
91
195
  //#endregion
196
+ //#region ../../internals/utils/src/names.ts
197
+ /**
198
+ * Returns a unique name by appending an incrementing numeric suffix when the name has already been used.
199
+ * Mutates `data` in-place as a usage counter so subsequent calls remain consistent.
200
+ *
201
+ * @example
202
+ * const seen: Record<string, number> = {}
203
+ * getUniqueName('Foo', seen) // 'Foo'
204
+ * getUniqueName('Foo', seen) // 'Foo2'
205
+ * getUniqueName('Foo', seen) // 'Foo3'
206
+ */
207
+ function getUniqueName(originalName, data) {
208
+ let used = data[originalName] || 0;
209
+ if (used) {
210
+ data[originalName] = ++used;
211
+ originalName += used;
212
+ }
213
+ data[originalName] = 1;
214
+ return originalName;
215
+ }
216
+ //#endregion
92
217
  //#region ../../internals/utils/src/reserved.ts
93
218
  /**
94
219
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
@@ -205,91 +330,6 @@ var URLPath = class {
205
330
  }
206
331
  };
207
332
  //#endregion
208
- //#region src/constants.ts
209
- /**
210
- * OpenAPI version string written into merged document stubs.
211
- */
212
- const MERGE_OPENAPI_VERSION = "3.0.0";
213
- /**
214
- * Fallback `info.title` used when merging multiple API documents.
215
- */
216
- const MERGE_DEFAULT_TITLE = "Merged API";
217
- /**
218
- * Fallback `info.version` used when merging multiple API documents.
219
- */
220
- const MERGE_DEFAULT_VERSION = "1.0.0";
221
- /**
222
- * JSON Schema keywords that indicate structural composition.
223
- * A schema fragment containing any of these keys must not be inlined into its
224
- * parent during `allOf` flattening — it carries semantic meaning of its own.
225
- */
226
- const structuralKeys = new Set([
227
- "properties",
228
- "items",
229
- "additionalProperties",
230
- "oneOf",
231
- "anyOf",
232
- "allOf",
233
- "not"
234
- ]);
235
- /**
236
- * Maps OAS/JSON Schema `format` strings to their Kubb `SchemaType` equivalents.
237
- *
238
- * Only formats that need a type different from the raw OAS `type` are listed.
239
- * `int64`, `date-time`, `date`, and `time` are handled separately because their
240
- * mapping depends on runtime parser options.
241
- *
242
- * Note: `ipv4`, `ipv6`, and `hostname` map to `'url'` — the closest supported
243
- * scalar type in the Kubb AST, even though these are not strictly URLs.
244
- */
245
- const formatMap = {
246
- uuid: "uuid",
247
- email: "email",
248
- "idn-email": "email",
249
- uri: "url",
250
- "uri-reference": "url",
251
- url: "url",
252
- ipv4: "url",
253
- ipv6: "url",
254
- hostname: "url",
255
- "idn-hostname": "url",
256
- binary: "blob",
257
- byte: "blob",
258
- int32: "integer",
259
- float: "number",
260
- double: "number"
261
- };
262
- /**
263
- * Exhaustive list of media types that Kubb recognizes.
264
- * Kept as a module-level constant to avoid re-allocating the array on every call.
265
- */
266
- const knownMediaTypes = [
267
- "application/json",
268
- "application/xml",
269
- "application/x-www-form-urlencoded",
270
- "application/octet-stream",
271
- "application/pdf",
272
- "application/zip",
273
- "application/graphql",
274
- "multipart/form-data",
275
- "text/plain",
276
- "text/html",
277
- "text/csv",
278
- "text/xml",
279
- "image/png",
280
- "image/jpeg",
281
- "image/gif",
282
- "image/webp",
283
- "image/svg+xml",
284
- "audio/mpeg",
285
- "video/mp4"
286
- ];
287
- /**
288
- * Vendor extension keys used to attach human-readable labels to enum values.
289
- * Checked in priority order: the first key found wins.
290
- */
291
- const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
292
- //#endregion
293
333
  //#region src/oas/Oas.ts
294
334
  /**
295
335
  * Prefix used to create synthetic `$ref` values for anonymous (inline) discriminator schemas.
@@ -501,8 +541,15 @@ var Oas = class extends BaseOas {
501
541
  if (!schema) return;
502
542
  return this.dereferenceWithRef(schema);
503
543
  }
504
- getParametersSchema(operation, inKey) {
505
- const { contentType = operation.getContentType() } = this.#options;
544
+ /**
545
+ * Returns all resolved parameters for an operation, merging path-level and operation-level
546
+ * parameters and deduplicating by `in:name` (operation-level takes precedence).
547
+ *
548
+ * oas v31+ filters out `$ref` parameters in `getParameters()`, so this method accesses the
549
+ * raw `operation.schema.parameters` and path-item parameters directly and resolves `$ref`
550
+ * pointers via `dereferenceWithRef` to preserve backward compatibility.
551
+ */
552
+ getParameters(operation) {
506
553
  const resolveParams = (params) => params.map((p) => this.dereferenceWithRef(p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
507
554
  const operationParams = resolveParams(operation.schema?.parameters || []);
508
555
  const pathItem = this.api?.paths?.[operation.path];
@@ -510,7 +557,11 @@ var Oas = class extends BaseOas {
510
557
  const paramMap = /* @__PURE__ */ new Map();
511
558
  for (const p of pathLevelParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
512
559
  for (const p of operationParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
513
- const params = Array.from(paramMap.values()).filter((v) => v.in === inKey);
560
+ return Array.from(paramMap.values());
561
+ }
562
+ getParametersSchema(operation, inKey) {
563
+ const { contentType = operation.getContentType() } = this.#options;
564
+ const params = this.getParameters(operation).filter((v) => v.in === inKey);
514
565
  if (!params.length) return null;
515
566
  return params.reduce((schema, pathParameters) => {
516
567
  const property = pathParameters.content?.[contentType]?.schema ?? pathParameters.schema;
@@ -900,17 +951,137 @@ function resolveCollisions(schemasWithMeta) {
900
951
  };
901
952
  }
902
953
  //#endregion
903
- //#region src/parser.ts
954
+ //#region src/utils.ts
904
955
  /**
905
- * Default values for all `Options` fields.
956
+ * Extracts the schema name from a `$ref` string.
957
+ * For `#/components/schemas/Order` this returns `'Order'`.
958
+ * Falls back to the full ref string when no slash is present.
906
959
  */
907
- const DEFAULT_OPTIONS = {
908
- dateType: "string",
909
- integerType: "number",
910
- unknownType: "any",
911
- emptySchemaType: "any",
912
- enumSuffix: "enum"
913
- };
960
+ function extractRefName($ref) {
961
+ return $ref.split("/").at(-1) ?? $ref;
962
+ }
963
+ /**
964
+ * Replaces the discriminator property's schema inside an `ObjectSchemaNode`
965
+ * with an enum of the given `values`.
966
+ *
967
+ * - When `enumName` is provided the enum is named, which lets printers emit a
968
+ * standalone enum declaration + type reference (e.g. `PetTypeEnum`).
969
+ * - When `enumName` is omitted the enum stays anonymous, so printers inline it
970
+ * as a literal union (e.g. `'dog'`).
971
+ *
972
+ * Returns the node unchanged when it is not an object or lacks the target property.
973
+ */
974
+ function applyDiscriminatorEnum({ node, propertyName, values, enumName }) {
975
+ if (node.type !== "object" || !node.properties?.length) return node;
976
+ if (!node.properties.some((prop) => prop.name === propertyName)) return node;
977
+ return createSchema({
978
+ ...node,
979
+ properties: node.properties.map((prop) => {
980
+ if (prop.name !== propertyName) return prop;
981
+ const enumSchema = createSchema({
982
+ type: "enum",
983
+ primitive: "string",
984
+ enumValues: values,
985
+ name: enumName,
986
+ readOnly: prop.schema.readOnly,
987
+ writeOnly: prop.schema.writeOnly
988
+ });
989
+ return createProperty({
990
+ ...prop,
991
+ schema: enumSchema
992
+ });
993
+ })
994
+ });
995
+ }
996
+ /**
997
+ * Merges consecutive anonymous (`name`-less) `ObjectSchemaNode`s in `members` into a
998
+ * single object by combining their `properties` arrays.
999
+ *
1000
+ * Only adjacent pairs are merged — non-object or named nodes act as boundaries.
1001
+ * This collapses patterns like `Address & { streetNumber } & { streetName }` into
1002
+ * `Address & { streetNumber; streetName }`.
1003
+ */
1004
+ function mergeAdjacentAnonymousObjects(members) {
1005
+ return members.reduce((acc, member) => {
1006
+ const obj = narrowSchema(member, "object");
1007
+ if (obj && !obj.name) {
1008
+ const prev = acc[acc.length - 1];
1009
+ const prevObj = prev ? narrowSchema(prev, "object") : null;
1010
+ if (prevObj && !prevObj.name) {
1011
+ acc[acc.length - 1] = createSchema({
1012
+ ...prevObj,
1013
+ properties: [...prevObj.properties ?? [], ...obj.properties ?? []]
1014
+ });
1015
+ return acc;
1016
+ }
1017
+ }
1018
+ acc.push(member);
1019
+ return acc;
1020
+ }, []);
1021
+ }
1022
+ /**
1023
+ * Simplifies a union member list by removing `enum` nodes whose `primitive` type is
1024
+ * already represented by a broader scalar node in the same union.
1025
+ *
1026
+ * For example `['placed', 'approved'] | string` collapses to `string` because
1027
+ * `string` subsumes all string literals. `'' | string` similarly becomes `string`.
1028
+ *
1029
+ * Only scalar primitives (`string`, `number`, `integer`, `bigint`, `boolean`) are
1030
+ * considered — object, array, and ref members are left untouched.
1031
+ *
1032
+ * Const-derived enums (those without an `enumType`, produced from an OpenAPI `const`
1033
+ * keyword) are **never** removed — `'accepted' | string` must stay as-is because the
1034
+ * literal is intentional.
1035
+ */
1036
+ function simplifyUnionMembers(members) {
1037
+ const scalarPrimitives = new Set(members.filter((m) => SCALAR_PRIMITIVE_TYPES.has(m.type)).map((m) => m.type));
1038
+ if (!scalarPrimitives.size) return members;
1039
+ return members.filter((m) => {
1040
+ if (m.type !== "enum") return true;
1041
+ const prim = m.primitive;
1042
+ if (!prim) return true;
1043
+ if (!m.enumType) return true;
1044
+ if (scalarPrimitives.has(prim)) return false;
1045
+ if ((prim === "integer" || prim === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1046
+ return true;
1047
+ });
1048
+ }
1049
+ /**
1050
+ * `nameMapping`, and calls `resolve` to obtain the `{ name, path }` pair for
1051
+ * each import. When `oas` is supplied, only `$ref`s that are resolvable in the
1052
+ * spec are included; omit it to skip the existence check.
1053
+ *
1054
+ * This function is the pure, state-free alternative to `OasParser.getImports`.
1055
+ * Because it receives `nameMapping` explicitly it can be called without holding
1056
+ * a reference to the parser or the OAS instance.
1057
+ *
1058
+ * @example
1059
+ * ```ts
1060
+ * // Use adapter state directly — no parser reference needed
1061
+ * const imports = getImports({
1062
+ * node: schemaNode,
1063
+ * nameMapping: adapter.options.nameMapping,
1064
+ * resolve: (schemaName) => ({
1065
+ * name: schemaManager.getName(schemaName, { type: 'type' }),
1066
+ * path: schemaManager.getFile(schemaName).path,
1067
+ * }),
1068
+ * })
1069
+ * ```
1070
+ */
1071
+ function getImports({ node, nameMapping, resolve }) {
1072
+ return collect(node, { schema(schemaNode) {
1073
+ if (schemaNode.type !== "ref" || !schemaNode.ref) return;
1074
+ const rawName = extractRefName(schemaNode.ref);
1075
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
1076
+ if (!result) return;
1077
+ return {
1078
+ name: [result.name],
1079
+ path: result.path
1080
+ };
1081
+ } });
1082
+ }
1083
+ //#endregion
1084
+ //#region src/parser.ts
914
1085
  /**
915
1086
  * Looks up the Kubb `SchemaType` for a given OAS `format` string.
916
1087
  * Returns `undefined` for formats not in `formatMap` (e.g. `int64`, `date-time`),
@@ -920,14 +1091,6 @@ function formatToSchemaType(format) {
920
1091
  return formatMap[format];
921
1092
  }
922
1093
  /**
923
- * Extracts the final path segment of a JSON Pointer `$ref` string.
924
- * For `#/components/schemas/Order` this returns `'Order'`.
925
- * Falls back to the full ref string when no slash is present.
926
- */
927
- function extractRefName($ref) {
928
- return $ref.split("/").at(-1) ?? $ref;
929
- }
930
- /**
931
1094
  * Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
932
1095
  * Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
933
1096
  * `boolean` maps to `'boolean'`; everything else defaults to `'string'`.
@@ -942,27 +1105,7 @@ function getPrimitiveType(type) {
942
1105
  * Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
943
1106
  */
944
1107
  function toMediaType(contentType) {
945
- return knownMediaTypes.includes(contentType) ? contentType : void 0;
946
- }
947
- /**
948
- * When a discriminator is present, replaces the discriminator property's schema with
949
- * an enum of the mapping keys so downstream code emits a precise literal-union type.
950
- * Returns the original schema unchanged when there is no discriminator or no mapping.
951
- */
952
- function applyDiscriminatorEnum(schema) {
953
- if (!isDiscriminator(schema)) return schema;
954
- const propName = schema.discriminator.propertyName;
955
- if (!schema.properties?.[propName]) return schema;
956
- return {
957
- ...schema,
958
- properties: {
959
- ...schema.properties,
960
- [propName]: {
961
- ...schema.properties[propName],
962
- enum: schema.discriminator.mapping ? Object.keys(schema.discriminator.mapping) : void 0
963
- }
964
- }
965
- };
1108
+ return knownMediaTypes.has(contentType) ? contentType : void 0;
966
1109
  }
967
1110
  /**
968
1111
  * Creates an OAS parser that converts an OpenAPI/Swagger spec into
@@ -988,6 +1131,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
988
1131
  contentType,
989
1132
  collisionDetection
990
1133
  });
1134
+ const usedEnumNames = {};
1135
+ const isLegacyNaming = collisionDetection === false;
991
1136
  /**
992
1137
  * Maps an `'any' | 'unknown' | 'void'` option string to the corresponding `SchemaType` constant.
993
1138
  * Used for both `unknownType` (unannotated schemas) and `emptySchemaType` (empty `{}` schemas).
@@ -1034,7 +1179,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1034
1179
  * Shared metadata fields included in every `createSchema` call.
1035
1180
  * Centralizes the common properties so sub-handlers don't repeat them.
1036
1181
  */
1037
- function buildSchemaBase(schema, name, nullable, defaultValue) {
1182
+ function renderSchemaBase(schema, name, nullable, defaultValue) {
1038
1183
  return {
1039
1184
  name,
1040
1185
  nullable,
@@ -1055,18 +1200,17 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1055
1200
  * reflected in generated JSDoc and type modifiers.
1056
1201
  */
1057
1202
  function convertRef({ schema, nullable, defaultValue }) {
1058
- const schemaObject = schema;
1059
1203
  return createSchema({
1060
1204
  type: "ref",
1061
- name: extractRefName(schemaObject.$ref),
1062
- ref: schemaObject.$ref,
1205
+ name: extractRefName(schema.$ref),
1206
+ ref: schema.$ref,
1063
1207
  nullable,
1064
- description: schemaObject.description,
1065
- deprecated: schemaObject.deprecated,
1066
- readOnly: schemaObject.readOnly,
1067
- writeOnly: schemaObject.writeOnly,
1068
- pattern: schemaObject.type === "string" ? schemaObject.pattern : void 0,
1069
- example: schemaObject.example,
1208
+ description: schema.description,
1209
+ deprecated: schema.deprecated,
1210
+ readOnly: schema.readOnly,
1211
+ writeOnly: schema.writeOnly,
1212
+ pattern: schema.type === "string" ? schema.pattern : void 0,
1213
+ example: schema.example,
1070
1214
  default: defaultValue
1071
1215
  });
1072
1216
  }
@@ -1117,6 +1261,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1117
1261
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1118
1262
  return !inOneOf && !inMapping;
1119
1263
  }).map((s) => convertSchema({ schema: s }, options));
1264
+ const syntheticStart = allOfMembers.length;
1120
1265
  if (Array.isArray(schema.required) && schema.required.length) {
1121
1266
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1122
1267
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
@@ -1141,8 +1286,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1141
1286
  }
1142
1287
  return createSchema({
1143
1288
  type: "intersection",
1144
- members: allOfMembers,
1145
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1289
+ members: [...allOfMembers.slice(0, syntheticStart), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
1290
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1146
1291
  });
1147
1292
  }
1148
1293
  /**
@@ -1156,25 +1301,39 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1156
1301
  function convertUnion({ schema, name, nullable, defaultValue, options }) {
1157
1302
  const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1158
1303
  const unionBase = {
1159
- ...buildSchemaBase(schema, name, nullable, defaultValue),
1304
+ ...renderSchemaBase(schema, name, nullable, defaultValue),
1160
1305
  discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0
1161
1306
  };
1162
1307
  if (schema.properties) {
1163
1308
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1164
- const propertiesNode = convertSchema({ schema: schemaWithoutUnion }, options);
1309
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1310
+ const sharedPropertiesNode = convertSchema({
1311
+ schema: discriminator ? Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== "discriminator")) : schemaWithoutUnion,
1312
+ name: isLegacyNaming ? void 0 : name
1313
+ }, options);
1165
1314
  return createSchema({
1166
1315
  type: "union",
1167
1316
  ...unionBase,
1168
- members: unionMembers.map((s) => createSchema({
1169
- type: "intersection",
1170
- members: [convertSchema({ schema: s }, options), propertiesNode]
1171
- }))
1317
+ members: unionMembers.map((s) => {
1318
+ const ref = isReference(s) ? s.$ref : void 0;
1319
+ const discriminatorValue = discriminator?.mapping && ref ? Object.entries(discriminator.mapping).find(([, v]) => v === ref)?.[0] : void 0;
1320
+ let propertiesNode = sharedPropertiesNode;
1321
+ if (discriminatorValue && discriminator) propertiesNode = applyDiscriminatorEnum({
1322
+ node: propertiesNode,
1323
+ propertyName: discriminator.propertyName,
1324
+ values: [discriminatorValue]
1325
+ });
1326
+ return createSchema({
1327
+ type: "intersection",
1328
+ members: [convertSchema({ schema: s }, options), propertiesNode]
1329
+ });
1330
+ })
1172
1331
  });
1173
1332
  }
1174
1333
  return createSchema({
1175
1334
  type: "union",
1176
1335
  ...unionBase,
1177
- members: unionMembers.map((s) => convertSchema({ schema: s }, options))
1336
+ members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s }, options)))
1178
1337
  });
1179
1338
  }
1180
1339
  /**
@@ -1197,7 +1356,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1197
1356
  type: "enum",
1198
1357
  primitive: getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string"),
1199
1358
  enumValues: [constValue],
1200
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1359
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1201
1360
  });
1202
1361
  }
1203
1362
  /**
@@ -1206,7 +1365,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1206
1365
  * (i.e. `format: 'date-time'` with `dateType: false`).
1207
1366
  */
1208
1367
  function convertFormat({ schema, name, nullable, defaultValue, mergedOptions }) {
1209
- const base = buildSchemaBase(schema, name, nullable, defaultValue);
1368
+ const base = renderSchemaBase(schema, name, nullable, defaultValue);
1210
1369
  if (schema.format === "int64") return createSchema({
1211
1370
  type: mergedOptions.integerType === "bigint" ? "bigint" : "integer",
1212
1371
  primitive: "integer",
@@ -1241,6 +1400,11 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1241
1400
  primitive: specialPrimitive,
1242
1401
  type: specialType
1243
1402
  });
1403
+ if (specialType === "url") return createSchema({
1404
+ ...base,
1405
+ primitive: "string",
1406
+ type: "url"
1407
+ });
1244
1408
  return createSchema({
1245
1409
  ...base,
1246
1410
  primitive: specialPrimitive,
@@ -1259,9 +1423,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1259
1423
  */
1260
1424
  function convertEnum({ schema, name, nullable, type, options }) {
1261
1425
  if (type === "array") {
1262
- const rawSchema = schema;
1263
1426
  const normalizedItems = {
1264
- ...typeof rawSchema.items === "object" && !Array.isArray(rawSchema.items) ? rawSchema.items : {},
1427
+ ...typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : {},
1265
1428
  enum: schema.enum
1266
1429
  };
1267
1430
  const { enum: _enum, ...schemaWithoutEnum } = schema;
@@ -1340,23 +1503,62 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1340
1503
  * - not required + not nullable → `optional: true`
1341
1504
  * - not required + nullable → `nullish: true`
1342
1505
  */
1506
+ /**
1507
+ * Builds the propagation name for a child property during recursive schema conversion.
1508
+ *
1509
+ * - **Legacy naming** (`isLegacyNaming`): only the immediate property key is used
1510
+ * (e.g. `Params` for property `params`), keeping nested names short.
1511
+ * - **Default naming**: the parent name is prepended so the full path is encoded
1512
+ * (e.g. `OrderParams` when parent is `Order`).
1513
+ */
1514
+ function resolveChildName(parentName, propName) {
1515
+ if (isLegacyNaming) return pascalCase(propName);
1516
+ return parentName ? pascalCase([parentName, propName].join(" ")) : void 0;
1517
+ }
1518
+ /**
1519
+ * Derives the final name for an enum property schema node.
1520
+ *
1521
+ * The raw name always includes the enum suffix (e.g. `StatusEnum`).
1522
+ * In legacy mode an additional deduplication step appends a numeric suffix
1523
+ * when the same name has already been used (e.g. `ParamsStatusEnum2`).
1524
+ */
1525
+ function resolveEnumPropName(parentName, propName, enumSuffix) {
1526
+ const raw = pascalCase([
1527
+ parentName,
1528
+ propName,
1529
+ enumSuffix
1530
+ ].filter(Boolean).join(" "));
1531
+ return isLegacyNaming ? getUniqueName(raw, usedEnumNames) : raw;
1532
+ }
1533
+ /**
1534
+ * Given a freshly-converted property schema, returns the node with a correct
1535
+ * `name` attached — or stripped — depending on whether the node is a named
1536
+ * enum, a boolean const-enum (always inlined), or a regular schema.
1537
+ */
1538
+ function applyEnumName(propNode, parentName, propName, enumSuffix) {
1539
+ const enumNode = narrowSchema(propNode, "enum");
1540
+ if (enumNode?.primitive === "boolean") return {
1541
+ ...propNode,
1542
+ name: void 0
1543
+ };
1544
+ if (enumNode) return {
1545
+ ...propNode,
1546
+ name: resolveEnumPropName(parentName, propName, enumSuffix)
1547
+ };
1548
+ return propNode;
1549
+ }
1343
1550
  function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }) {
1344
- const resolvedSchema = applyDiscriminatorEnum(schema);
1345
- const properties = resolvedSchema.properties ? Object.entries(resolvedSchema.properties).map(([propName, propSchema]) => {
1346
- const required = Array.isArray(resolvedSchema.required) ? resolvedSchema.required.includes(propName) : !!resolvedSchema.required;
1551
+ const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1552
+ const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1347
1553
  const resolvedPropSchema = propSchema;
1348
1554
  const propNullable = isNullable(resolvedPropSchema);
1349
1555
  return createProperty({
1350
1556
  name: propName,
1351
1557
  schema: {
1352
- ...convertSchema({
1558
+ ...applyEnumName(convertSchema({
1353
1559
  schema: resolvedPropSchema,
1354
- name: name ? pascalCase([
1355
- name,
1356
- propName,
1357
- mergedOptions.enumSuffix
1358
- ].filter(Boolean).join(" ")) : void 0
1359
- }, options),
1560
+ name: resolveChildName(name, propName)
1561
+ }, options), name, propName, mergedOptions.enumSuffix),
1360
1562
  nullable: propNullable || void 0,
1361
1563
  optional: !required && !propNullable ? true : void 0,
1362
1564
  nullish: !required && propNullable ? true : void 0
@@ -1364,22 +1566,32 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1364
1566
  required
1365
1567
  });
1366
1568
  }) : [];
1367
- const additionalProperties = resolvedSchema.additionalProperties;
1569
+ const additionalProperties = schema.additionalProperties;
1368
1570
  let additionalPropertiesNode;
1369
1571
  if (additionalProperties === true) additionalPropertiesNode = true;
1370
1572
  else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode = convertSchema({ schema: additionalProperties }, options);
1371
1573
  else if (additionalProperties === false) additionalPropertiesNode = void 0;
1372
1574
  else if (additionalProperties) additionalPropertiesNode = createSchema({ type: resolveTypeOption(mergedOptions.unknownType) });
1373
- const rawPatternProperties = "patternProperties" in resolvedSchema ? resolvedSchema.patternProperties : void 0;
1374
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || Object.keys(patternSchema).length === 0 ? createSchema({ type: resolveTypeOption(mergedOptions.unknownType) }) : convertSchema({ schema: patternSchema }, options)])) : void 0;
1375
- return createSchema({
1575
+ const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1576
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? createSchema({ type: resolveTypeOption(mergedOptions.unknownType) }) : convertSchema({ schema: patternSchema }, options)])) : void 0;
1577
+ const objectNode = createSchema({
1376
1578
  type: "object",
1377
1579
  primitive: "object",
1378
1580
  properties,
1379
1581
  additionalProperties: additionalPropertiesNode,
1380
1582
  patternProperties,
1381
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1583
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1382
1584
  });
1585
+ if (isDiscriminator(schema) && schema.discriminator.mapping) {
1586
+ const discPropName = schema.discriminator.propertyName;
1587
+ return applyDiscriminatorEnum({
1588
+ node: objectNode,
1589
+ propertyName: discPropName,
1590
+ values: Object.keys(schema.discriminator.mapping),
1591
+ enumName: name ? resolveEnumPropName(name, discPropName, mergedOptions.enumSuffix) : void 0
1592
+ });
1593
+ }
1594
+ return objectNode;
1383
1595
  }
1384
1596
  /**
1385
1597
  * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
@@ -1388,15 +1600,14 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1388
1600
  * after the prefix items is mapped to the rest parameter of the tuple.
1389
1601
  */
1390
1602
  function convertTuple({ schema, name, nullable, defaultValue, options }) {
1391
- const rawSchema = schema;
1392
1603
  return createSchema({
1393
1604
  type: "tuple",
1394
1605
  primitive: "array",
1395
- items: rawSchema.prefixItems.map((item) => convertSchema({ schema: item }, options)),
1396
- rest: rawSchema.items ? convertSchema({ schema: rawSchema.items }, options) : void 0,
1606
+ items: (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item }, options)),
1607
+ rest: schema.items ? convertSchema({ schema: schema.items }, options) : void 0,
1397
1608
  min: schema.minItems,
1398
1609
  max: schema.maxItems,
1399
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1610
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1400
1611
  });
1401
1612
  }
1402
1613
  /**
@@ -1406,19 +1617,19 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1406
1617
  * `enumSuffix` is forwarded so generators can emit a named enum declaration.
1407
1618
  */
1408
1619
  function convertArray({ schema, name, nullable, defaultValue, options, mergedOptions }) {
1409
- const rawSchema = schema;
1410
- const itemName = rawSchema.items?.enum?.length && name ? pascalCase([name, mergedOptions.enumSuffix].join(" ")) : void 0;
1620
+ const rawItems = schema.items;
1621
+ const itemName = rawItems?.enum?.length && name ? resolveEnumPropName(void 0, name, mergedOptions.enumSuffix) : void 0;
1411
1622
  return createSchema({
1412
1623
  type: "array",
1413
1624
  primitive: "array",
1414
- items: rawSchema.items ? [convertSchema({
1415
- schema: rawSchema.items,
1625
+ items: rawItems ? [convertSchema({
1626
+ schema: rawItems,
1416
1627
  name: itemName
1417
1628
  }, options)] : [],
1418
1629
  min: schema.minItems,
1419
1630
  max: schema.maxItems,
1420
1631
  unique: schema.uniqueItems ?? void 0,
1421
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1632
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1422
1633
  });
1423
1634
  }
1424
1635
  /**
@@ -1431,7 +1642,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1431
1642
  min: schema.minLength,
1432
1643
  max: schema.maxLength,
1433
1644
  pattern: schema.pattern,
1434
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1645
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1435
1646
  });
1436
1647
  }
1437
1648
  /**
@@ -1445,7 +1656,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1445
1656
  max: schema.maximum,
1446
1657
  exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1447
1658
  exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1448
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1659
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1449
1660
  });
1450
1661
  }
1451
1662
  /**
@@ -1455,7 +1666,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1455
1666
  return createSchema({
1456
1667
  type: "boolean",
1457
1668
  primitive: "boolean",
1458
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1669
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1459
1670
  });
1460
1671
  }
1461
1672
  /**
@@ -1490,7 +1701,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1490
1701
  */
1491
1702
  function convertSchema({ schema, name }, options) {
1492
1703
  const mergedOptions = {
1493
- ...DEFAULT_OPTIONS,
1704
+ ...DEFAULT_PARSER_OPTIONS,
1494
1705
  ...options
1495
1706
  };
1496
1707
  const flattenedSchema = flattenSchema(schema);
@@ -1521,18 +1732,21 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1521
1732
  if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return createSchema({
1522
1733
  type: "blob",
1523
1734
  primitive: "string",
1524
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1735
+ ...renderSchemaBase(schema, name, nullable, defaultValue)
1525
1736
  });
1526
1737
  if (Array.isArray(schema.type) && schema.type.length > 1) {
1527
1738
  const nonNullTypes = schema.type.filter((t) => t !== "null");
1528
1739
  const arrayNullable = schema.type.includes("null") || nullable || void 0;
1529
1740
  if (nonNullTypes.length > 1) return createSchema({
1530
1741
  type: "union",
1531
- members: nonNullTypes.map((t) => convertSchema({ schema: {
1532
- ...schema,
1533
- type: t
1534
- } }, options)),
1535
- ...buildSchemaBase(schema, name, arrayNullable, defaultValue)
1742
+ members: nonNullTypes.map((t) => convertSchema({
1743
+ schema: {
1744
+ ...schema,
1745
+ type: t
1746
+ },
1747
+ name
1748
+ }, options)),
1749
+ ...renderSchemaBase(schema, name, arrayNullable, defaultValue)
1536
1750
  });
1537
1751
  }
1538
1752
  if (!type) {
@@ -1560,12 +1774,17 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1560
1774
  * When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
1561
1775
  */
1562
1776
  function parseParameter(options, param) {
1777
+ const required = param["required"] ?? false;
1563
1778
  const schema = param["schema"] && !isReference(param["schema"]) ? convertSchema({ schema: param["schema"] }, options) : createSchema({ type: resolveTypeOption(options.unknownType) });
1564
1779
  return createParameter({
1565
1780
  name: param["name"],
1566
1781
  in: param["in"],
1567
- schema,
1568
- required: param["required"] ?? false
1782
+ schema: {
1783
+ ...schema,
1784
+ description: param["description"] ?? schema.description,
1785
+ optional: !required || !!schema.optional ? true : void 0
1786
+ },
1787
+ required
1569
1788
  });
1570
1789
  }
1571
1790
  /**
@@ -1573,15 +1792,13 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1573
1792
  * request body, and all response codes into their AST node equivalents.
1574
1793
  */
1575
1794
  function parseOperation(options, oas, operation) {
1576
- const parameters = operation.getParameters().map((param) => {
1577
- return parseParameter(options, oas.dereferenceWithRef(param));
1578
- });
1795
+ const parameters = oas.getParameters(operation).map((param) => parseParameter(options, param));
1579
1796
  const requestBodySchema = oas.getRequestSchema(operation);
1580
1797
  const requestBody = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : void 0;
1581
1798
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1582
1799
  const responseObj = operation.getResponseByStatusCode(statusCode);
1583
1800
  const responseSchema = oas.getResponseSchema(operation, statusCode);
1584
- const schema = responseSchema && Object.keys(responseSchema).length > 0 ? convertSchema({ schema: responseSchema }, options) : void 0;
1801
+ const schema = responseSchema && Object.keys(responseSchema).length > 0 ? convertSchema({ schema: responseSchema }, options) : createSchema({ type: resolveTypeOption(options.emptySchemaType) });
1585
1802
  const description = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : void 0;
1586
1803
  const rawContent = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.content : void 0;
1587
1804
  return createResponse({
@@ -1594,7 +1811,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1594
1811
  return createOperation({
1595
1812
  operationId: operation.getOperationId(),
1596
1813
  method: operation.method.toUpperCase(),
1597
- path: operation.path,
1814
+ path: new URLPath(operation.path).URL,
1598
1815
  tags: operation.getTags().map((tag) => tag.name),
1599
1816
  summary: operation.getSummary() || void 0,
1600
1817
  description: operation.getDescription() || void 0,
@@ -1610,7 +1827,7 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1610
1827
  */
1611
1828
  function parse(options) {
1612
1829
  const mergedOptions = {
1613
- ...DEFAULT_OPTIONS,
1830
+ ...DEFAULT_PARSER_OPTIONS,
1614
1831
  ...options
1615
1832
  };
1616
1833
  const schemas = Object.entries(schemaObjects).map(([name, schemaObject]) => convertSchema({
@@ -1651,31 +1868,11 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1651
1868
  }
1652
1869
  } });
1653
1870
  }
1654
- /**
1655
- * Collects all `KubbFile.Import` descriptors needed by a `SchemaNode` tree.
1656
- *
1657
- * Walks the tree looking for `ref` nodes, verifies each `$ref` is resolvable in the spec,
1658
- * applies collision-resolved names from `nameMapping`, and calls `resolve` to obtain the
1659
- * import path and name. Returns an empty array for refs that cannot be resolved.
1660
- */
1661
- function getImports(node, resolve) {
1662
- return collect(node, { schema(schemaNode) {
1663
- if (schemaNode.type !== "ref" || !schemaNode.ref) return;
1664
- if (!oas.get(schemaNode.ref)) return;
1665
- const rawName = extractRefName(schemaNode.ref);
1666
- const result = resolve(nameMapping.get(rawName) ?? rawName);
1667
- if (!result) return;
1668
- return {
1669
- name: [result.name],
1670
- path: result.path
1671
- };
1672
- } });
1673
- }
1674
1871
  return {
1675
1872
  parse,
1676
1873
  convertSchema,
1677
1874
  resolveRefs,
1678
- getImports
1875
+ nameMapping
1679
1876
  };
1680
1877
  }
1681
1878
  //#endregion
@@ -1699,8 +1896,9 @@ const adapterOasName = "oas";
1699
1896
  * })
1700
1897
  * ```
1701
1898
  */
1702
- const adapterOas = defineAdapter((options) => {
1703
- const { validate = true, oasClass, contentType, serverIndex, serverVariables, discriminator = "strict", collisionDetection = false, dateType = "string", integerType = "number", unknownType = "any", emptySchemaType = unknownType } = options;
1899
+ const adapterOas = createAdapter((options) => {
1900
+ const { validate = true, oasClass, contentType, serverIndex, serverVariables, discriminator = "strict", collisionDetection = true, 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;
1901
+ const nameMapping = /* @__PURE__ */ new Map();
1704
1902
  return {
1705
1903
  name: "oas",
1706
1904
  options: {
@@ -1714,7 +1912,16 @@ const adapterOas = defineAdapter((options) => {
1714
1912
  dateType,
1715
1913
  integerType,
1716
1914
  unknownType,
1717
- emptySchemaType
1915
+ emptySchemaType,
1916
+ enumSuffix,
1917
+ nameMapping
1918
+ },
1919
+ getImports(node, resolve) {
1920
+ return getImports({
1921
+ node,
1922
+ nameMapping,
1923
+ resolve
1924
+ });
1718
1925
  },
1719
1926
  async parse(source) {
1720
1927
  const oas = await parseFromConfig(sourceToFakeConfig(source), oasClass);
@@ -1728,18 +1935,23 @@ const adapterOas = defineAdapter((options) => {
1728
1935
  } catch (_err) {}
1729
1936
  const server = serverIndex !== void 0 ? oas.api.servers?.at(serverIndex) : void 0;
1730
1937
  const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1938
+ const parser = createOasParser(oas, {
1939
+ contentType,
1940
+ collisionDetection
1941
+ });
1942
+ nameMapping.clear();
1943
+ for (const [key, value] of parser.nameMapping) nameMapping.set(key, value);
1731
1944
  return createRoot({
1732
- ...createOasParser(oas, {
1733
- contentType,
1734
- collisionDetection
1735
- }).parse({
1945
+ ...parser.parse({
1736
1946
  dateType,
1737
1947
  integerType,
1738
1948
  unknownType,
1739
- emptySchemaType
1949
+ emptySchemaType,
1950
+ enumSuffix
1740
1951
  }),
1741
1952
  meta: {
1742
1953
  title: oas.api.info?.title,
1954
+ description: oas.api.info?.description,
1743
1955
  version: oas.api.info?.version,
1744
1956
  baseURL
1745
1957
  }