@kubb/adapter-oas 5.0.0-alpha.3 → 5.0.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,45 +21,211 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- let node_path = require("node:path");
25
- node_path = __toESM(node_path);
26
24
  let _kubb_ast = require("@kubb/ast");
27
25
  let _kubb_core = require("@kubb/core");
26
+ let node_path = require("node:path");
27
+ node_path = __toESM(node_path);
28
28
  let _redocly_openapi_core = require("@redocly/openapi-core");
29
29
  let _stoplight_yaml = require("@stoplight/yaml");
30
30
  _stoplight_yaml = __toESM(_stoplight_yaml);
31
- let oas_types = require("oas/types");
32
31
  let oas_normalize = require("oas-normalize");
33
32
  oas_normalize = __toESM(oas_normalize);
34
33
  let remeda = require("remeda");
35
34
  let swagger2openapi = require("swagger2openapi");
36
35
  swagger2openapi = __toESM(swagger2openapi);
37
- let jsonpointer = require("jsonpointer");
38
- jsonpointer = __toESM(jsonpointer);
39
36
  let oas = require("oas");
40
37
  oas = __toESM(oas);
38
+ let jsonpointer = require("jsonpointer");
39
+ jsonpointer = __toESM(jsonpointer);
40
+ let oas_types = require("oas/types");
41
41
  let oas_utils = require("oas/utils");
42
- //#region src/oas/resolveServerUrl.ts
42
+ //#region src/constants.ts
43
43
  /**
44
- * Resolves an OpenAPI server URL by substituting `{variable}` placeholders.
44
+ * Default parser options applied when no explicit options are provided.
45
45
  *
46
- * Resolution priority per variable:
47
- * 1. `overrides[key]` — caller-supplied value.
48
- * 2. `variable.default` spec-defined default, coerced to `string`.
49
- * 3. Variable is left unreplaced when neither is available.
46
+ * @example
47
+ * ```ts
48
+ * import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
50
49
  *
51
- * Throws when an `overrides` value is not present in the variable's `enum` list.
50
+ * const parser = createOasParser(oas)
51
+ * const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
52
+ * ```
52
53
  */
53
- function resolveServerUrl(server, overrides) {
54
- if (!server.variables) return server.url;
55
- let url = server.url;
56
- for (const [key, variable] of Object.entries(server.variables)) {
57
- const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
58
- if (value === void 0) continue;
59
- if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(`Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`);
60
- url = url.replaceAll(`{${key}}`, value);
54
+ const DEFAULT_PARSER_OPTIONS = {
55
+ dateType: "string",
56
+ integerType: "number",
57
+ unknownType: "any",
58
+ emptySchemaType: "any",
59
+ enumSuffix: "enum"
60
+ };
61
+ /**
62
+ * OpenAPI version string written into the stub document created during multi-spec merges.
63
+ */
64
+ const MERGE_OPENAPI_VERSION = "3.0.0";
65
+ /**
66
+ * Fallback `info.title` placed in the stub document when merging multiple API files.
67
+ */
68
+ const MERGE_DEFAULT_TITLE = "Merged API";
69
+ /**
70
+ * Fallback `info.version` placed in the stub document when merging multiple API files.
71
+ */
72
+ const MERGE_DEFAULT_VERSION = "1.0.0";
73
+ /**
74
+ * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
75
+ *
76
+ * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
77
+ * intersection member rather than being merged into the parent.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { structuralKeys } from '@kubb/adapter-oas'
82
+ *
83
+ * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
84
+ * // true when fragment has e.g. 'properties' or 'oneOf'
85
+ * ```
86
+ */
87
+ const structuralKeys = new Set([
88
+ "properties",
89
+ "items",
90
+ "additionalProperties",
91
+ "oneOf",
92
+ "anyOf",
93
+ "allOf",
94
+ "not"
95
+ ]);
96
+ /**
97
+ * Static map from OAS `format` strings to Kubb `SchemaType` values.
98
+ *
99
+ * Only formats whose AST type differs from the OAS `type` field appear here.
100
+ * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
101
+ * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
102
+ * `idn-hostname` map to `'url'` as the closest generic string-format type.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { formatMap } from '@kubb/adapter-oas'
107
+ *
108
+ * formatMap['uuid'] // 'uuid'
109
+ * formatMap['binary'] // 'blob'
110
+ * formatMap['float'] // 'number'
111
+ * ```
112
+ */
113
+ const formatMap = {
114
+ uuid: "uuid",
115
+ email: "email",
116
+ "idn-email": "email",
117
+ uri: "url",
118
+ "uri-reference": "url",
119
+ url: "url",
120
+ ipv4: "ipv4",
121
+ ipv6: "ipv6",
122
+ hostname: "url",
123
+ "idn-hostname": "url",
124
+ binary: "blob",
125
+ byte: "blob",
126
+ int32: "integer",
127
+ float: "number",
128
+ double: "number"
129
+ };
130
+ /**
131
+ * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * import { enumExtensionKeys } from '@kubb/adapter-oas'
136
+ *
137
+ * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
138
+ * ```
139
+ */
140
+ const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
141
+ /**
142
+ * Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
143
+ * Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
144
+ */
145
+ const typeOptionMap = new Map([
146
+ ["any", _kubb_ast.schemaTypes.any],
147
+ ["unknown", _kubb_ast.schemaTypes.unknown],
148
+ ["void", _kubb_ast.schemaTypes.void]
149
+ ]);
150
+ //#endregion
151
+ //#region src/discriminator.ts
152
+ /**
153
+ * Injects discriminator enum values into child schemas so they know which value identifies them.
154
+ *
155
+ * Finds every union schema in `root.schemas` that has a `discriminatorPropertyName`, collects the
156
+ * enum value each union member is mapped to, then adds (or replaces) that property on the matching
157
+ * child object schema.
158
+ *
159
+ * Returns a new `RootNode` — the original is never mutated.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const { root } = parseOas(document, options)
164
+ * const next = applyDiscriminatorInheritance(root)
165
+ * ```
166
+ */
167
+ function applyDiscriminatorInheritance(root) {
168
+ const childMap = /* @__PURE__ */ new Map();
169
+ for (const schema of root.schemas) {
170
+ let unionNode = (0, _kubb_ast.narrowSchema)(schema, "union");
171
+ if (!unionNode) {
172
+ const intersectionMembers = (0, _kubb_ast.narrowSchema)(schema, "intersection")?.members;
173
+ if (intersectionMembers) for (const m of intersectionMembers) {
174
+ const u = (0, _kubb_ast.narrowSchema)(m, "union");
175
+ if (u) {
176
+ unionNode = u;
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
182
+ const { discriminatorPropertyName, members } = unionNode;
183
+ for (const member of members) {
184
+ const intersectionNode = (0, _kubb_ast.narrowSchema)(member, "intersection");
185
+ if (!intersectionNode?.members) continue;
186
+ let refNode;
187
+ let objNode;
188
+ for (const m of intersectionNode.members) {
189
+ refNode ??= (0, _kubb_ast.narrowSchema)(m, "ref");
190
+ objNode ??= (0, _kubb_ast.narrowSchema)(m, "object");
191
+ }
192
+ if (!refNode?.name || !objNode) continue;
193
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
194
+ const enumNode = prop ? (0, _kubb_ast.narrowSchema)(prop.schema, "enum") : void 0;
195
+ if (!enumNode?.enumValues?.length) continue;
196
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
197
+ if (!enumValues.length) continue;
198
+ const existing = childMap.get(refNode.name);
199
+ if (existing) existing.enumValues.push(...enumValues);
200
+ else childMap.set(refNode.name, {
201
+ propertyName: discriminatorPropertyName,
202
+ enumValues: [...enumValues]
203
+ });
204
+ }
61
205
  }
62
- return url;
206
+ if (childMap.size === 0) return root;
207
+ return (0, _kubb_ast.transform)(root, { schema(node, { parent }) {
208
+ if (parent?.kind !== "Root" || !node.name) return;
209
+ const entry = childMap.get(node.name);
210
+ if (!entry) return;
211
+ const objectNode = (0, _kubb_ast.narrowSchema)(node, "object");
212
+ if (!objectNode) return;
213
+ const { propertyName, enumValues } = entry;
214
+ const newProp = (0, _kubb_ast.createProperty)({
215
+ name: propertyName,
216
+ required: true,
217
+ schema: (0, _kubb_ast.createSchema)({
218
+ type: "enum",
219
+ enumValues
220
+ })
221
+ });
222
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
223
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
224
+ return {
225
+ ...objectNode,
226
+ properties: newProperties
227
+ };
228
+ } });
63
229
  }
64
230
  //#endregion
65
231
  //#region ../../internals/utils/src/casing.ts
@@ -81,9 +247,12 @@ function toCamelOrPascal(text, pascal) {
81
247
  * Splits `text` on `.` and applies `transformPart` to each segment.
82
248
  * The last segment receives `isLast = true`, all earlier segments receive `false`.
83
249
  * Segments are joined with `/` to form a file path.
250
+ *
251
+ * Only splits on dots followed by a letter so that version numbers
252
+ * embedded in operationIds (e.g. `v2025.0`) are kept intact.
84
253
  */
85
254
  function applyToFileParts(text, transformPart) {
86
- const parts = text.split(".");
255
+ const parts = text.split(/\.(?=[a-zA-Z])/);
87
256
  return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
88
257
  }
89
258
  /**
@@ -120,6 +289,13 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
120
289
  //#region ../../internals/utils/src/reserved.ts
121
290
  /**
122
291
  * Returns `true` when `name` is a syntactically valid JavaScript variable name.
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * isValidVarName('status') // true
296
+ * isValidVarName('class') // false (reserved word)
297
+ * isValidVarName('42foo') // false (starts with digit)
298
+ * ```
123
299
  */
124
300
  function isValidVarName(name) {
125
301
  try {
@@ -140,18 +316,33 @@ function isValidVarName(name) {
140
316
  * p.template // '`/pet/${petId}`'
141
317
  */
142
318
  var URLPath = class {
143
- /** The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`. */
319
+ /**
320
+ * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
321
+ */
144
322
  path;
145
323
  #options;
146
324
  constructor(path, options = {}) {
147
325
  this.path = path;
148
326
  this.#options = options;
149
327
  }
150
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
328
+ /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
329
+ *
330
+ * @example
331
+ * ```ts
332
+ * new URLPath('/pet/{petId}').URL // '/pet/:petId'
333
+ * ```
334
+ */
151
335
  get URL() {
152
336
  return this.toURLPath();
153
337
  }
154
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`). */
338
+ /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
343
+ * new URLPath('/pet/{petId}').isURL // false
344
+ * ```
345
+ */
155
346
  get isURL() {
156
347
  try {
157
348
  return !!new URL(this.path).href;
@@ -169,11 +360,25 @@ var URLPath = class {
169
360
  get template() {
170
361
  return this.toTemplateString();
171
362
  }
172
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set. */
363
+ /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
364
+ *
365
+ * @example
366
+ * ```ts
367
+ * new URLPath('/pet/{petId}').object
368
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
369
+ * ```
370
+ */
173
371
  get object() {
174
372
  return this.toObject();
175
373
  }
176
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters. */
374
+ /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
375
+ *
376
+ * @example
377
+ * ```ts
378
+ * new URLPath('/pet/{petId}').params // { petId: 'petId' }
379
+ * new URLPath('/pet').params // undefined
380
+ * ```
381
+ */
177
382
  get params() {
178
383
  return this.getParams();
179
384
  }
@@ -181,7 +386,9 @@ var URLPath = class {
181
386
  const param = isValidVarName(raw) ? raw : camelCase(raw);
182
387
  return this.#options.casing === "camelcase" ? camelCase(param) : param;
183
388
  }
184
- /** Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name. */
389
+ /**
390
+ * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
391
+ */
185
392
  #eachParam(fn) {
186
393
  for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
187
394
  const raw = match[1];
@@ -218,6 +425,12 @@ var URLPath = class {
218
425
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
219
426
  * An optional `replacer` transforms each parameter name in both key and value positions.
220
427
  * Returns `undefined` when no path parameters are found.
428
+ *
429
+ * @example
430
+ * ```ts
431
+ * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
432
+ * // { petId: 'petId', tagId: 'tagId' }
433
+ * ```
221
434
  */
222
435
  getParams(replacer) {
223
436
  const params = {};
@@ -227,439 +440,28 @@ var URLPath = class {
227
440
  });
228
441
  return Object.keys(params).length > 0 ? params : void 0;
229
442
  }
230
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. */
443
+ /** Converts the OpenAPI path to Express-style colon syntax.
444
+ *
445
+ * @example
446
+ * ```ts
447
+ * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
448
+ * ```
449
+ */
231
450
  toURLPath() {
232
451
  return this.path.replace(/\{([^}]+)\}/g, ":$1");
233
452
  }
234
453
  };
235
454
  //#endregion
236
- //#region src/constants.ts
237
- /**
238
- * OpenAPI version string written into merged document stubs.
239
- */
240
- const MERGE_OPENAPI_VERSION = "3.0.0";
241
- /**
242
- * Fallback `info.title` used when merging multiple API documents.
243
- */
244
- const MERGE_DEFAULT_TITLE = "Merged API";
245
- /**
246
- * Fallback `info.version` used when merging multiple API documents.
247
- */
248
- const MERGE_DEFAULT_VERSION = "1.0.0";
455
+ //#region src/guards.ts
249
456
  /**
250
- * JSON Schema keywords that indicate structural composition.
251
- * A schema fragment containing any of these keys must not be inlined into its
252
- * parent during `allOf` flattening — it carries semantic meaning of its own.
253
- */
254
- const structuralKeys = new Set([
255
- "properties",
256
- "items",
257
- "additionalProperties",
258
- "oneOf",
259
- "anyOf",
260
- "allOf",
261
- "not"
262
- ]);
263
- /**
264
- * Maps OAS/JSON Schema `format` strings to their Kubb `SchemaType` equivalents.
265
- *
266
- * Only formats that need a type different from the raw OAS `type` are listed.
267
- * `int64`, `date-time`, `date`, and `time` are handled separately because their
268
- * mapping depends on runtime parser options.
457
+ * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
269
458
  *
270
- * Note: `ipv4`, `ipv6`, and `hostname` map to `'url'` — the closest supported
271
- * scalar type in the Kubb AST, even though these are not strictly URLs.
272
- */
273
- const formatMap = {
274
- uuid: "uuid",
275
- email: "email",
276
- "idn-email": "email",
277
- uri: "url",
278
- "uri-reference": "url",
279
- url: "url",
280
- ipv4: "url",
281
- ipv6: "url",
282
- hostname: "url",
283
- "idn-hostname": "url",
284
- binary: "blob",
285
- byte: "blob",
286
- int32: "integer",
287
- float: "number",
288
- double: "number"
289
- };
290
- /**
291
- * Exhaustive list of media types that Kubb recognizes.
292
- * Kept as a module-level constant to avoid re-allocating the array on every call.
293
- */
294
- const knownMediaTypes = [
295
- "application/json",
296
- "application/xml",
297
- "application/x-www-form-urlencoded",
298
- "application/octet-stream",
299
- "application/pdf",
300
- "application/zip",
301
- "application/graphql",
302
- "multipart/form-data",
303
- "text/plain",
304
- "text/html",
305
- "text/csv",
306
- "text/xml",
307
- "image/png",
308
- "image/jpeg",
309
- "image/gif",
310
- "image/webp",
311
- "image/svg+xml",
312
- "audio/mpeg",
313
- "video/mp4"
314
- ];
315
- /**
316
- * Vendor extension keys used to attach human-readable labels to enum values.
317
- * Checked in priority order: the first key found wins.
318
- */
319
- const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
320
- //#endregion
321
- //#region src/oas/Oas.ts
322
- /**
323
- * Prefix used to create synthetic `$ref` values for anonymous (inline) discriminator schemas.
324
- * The suffix is the schema index within the discriminator's `oneOf`/`anyOf` array.
325
- * @example `#kubb-inline-0`
326
- */
327
- const KUBB_INLINE_REF_PREFIX = "#kubb-inline-";
328
- var Oas = class extends oas.default {
329
- #options = { discriminator: "strict" };
330
- document;
331
- constructor(document) {
332
- super(document, void 0);
333
- this.document = document;
334
- }
335
- setOptions(options) {
336
- this.#options = {
337
- ...this.#options,
338
- ...options
339
- };
340
- if (this.#options.discriminator === "inherit") this.#applyDiscriminatorInheritance();
341
- }
342
- get options() {
343
- return this.#options;
344
- }
345
- get($ref) {
346
- const origRef = $ref;
347
- $ref = $ref.trim();
348
- if ($ref === "") return null;
349
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
350
- else return null;
351
- const current = jsonpointer.default.get(this.api, $ref);
352
- if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
353
- return current;
354
- }
355
- getKey($ref) {
356
- const key = $ref.split("/").pop();
357
- return key === "" ? void 0 : key;
358
- }
359
- set($ref, value) {
360
- $ref = $ref.trim();
361
- if ($ref === "") return false;
362
- if ($ref.startsWith("#")) {
363
- $ref = globalThis.decodeURIComponent($ref.substring(1));
364
- jsonpointer.default.set(this.api, $ref, value);
365
- }
366
- }
367
- #setDiscriminator(schema) {
368
- const { mapping = {}, propertyName } = schema.discriminator;
369
- if (this.#options.discriminator === "inherit") Object.entries(mapping).forEach(([mappingKey, mappingValue]) => {
370
- if (mappingValue) {
371
- const childSchema = this.get(mappingValue);
372
- if (!childSchema) return;
373
- if (!childSchema.properties) childSchema.properties = {};
374
- const property = childSchema.properties[propertyName];
375
- if (childSchema.properties) {
376
- childSchema.properties[propertyName] = {
377
- ...childSchema.properties ? childSchema.properties[propertyName] : {},
378
- enum: [...property?.enum?.filter((value) => value !== mappingKey) ?? [], mappingKey]
379
- };
380
- childSchema.required = typeof childSchema.required === "boolean" ? childSchema.required : [...new Set([...childSchema.required ?? [], propertyName])];
381
- this.set(mappingValue, childSchema);
382
- }
383
- }
384
- });
385
- }
386
- getDiscriminator(schema) {
387
- if (!isDiscriminator(schema) || !schema) return null;
388
- const { mapping = {}, propertyName } = schema.discriminator;
389
- /**
390
- * Helper to extract discriminator value from a schema.
391
- * Checks in order:
392
- * 1. Extension property matching propertyName (e.g., x-linode-ref-name)
393
- * 2. Property with const value
394
- * 3. Property with single enum value
395
- * 4. Title as fallback
396
- */
397
- const getDiscriminatorValue = (schema) => {
398
- if (!schema) return null;
399
- if (propertyName.startsWith("x-")) {
400
- const extensionValue = schema[propertyName];
401
- if (extensionValue && typeof extensionValue === "string") return extensionValue;
402
- }
403
- const propertySchema = schema.properties?.[propertyName];
404
- if (propertySchema && "const" in propertySchema && propertySchema.const !== void 0) return String(propertySchema.const);
405
- if (propertySchema && propertySchema.enum?.length === 1) return String(propertySchema.enum[0]);
406
- return schema.title || null;
407
- };
408
- /**
409
- * Process oneOf/anyOf items to build mapping.
410
- * Handles both $ref and inline schemas.
411
- */
412
- const processSchemas = (schemas, existingMapping) => {
413
- schemas.forEach((schemaItem, index) => {
414
- if (isReference(schemaItem)) {
415
- const key = this.getKey(schemaItem.$ref);
416
- try {
417
- const discriminatorValue = getDiscriminatorValue(this.get(schemaItem.$ref));
418
- const canAdd = key && !Object.values(existingMapping).includes(schemaItem.$ref);
419
- if (canAdd && discriminatorValue) existingMapping[discriminatorValue] = schemaItem.$ref;
420
- else if (canAdd) existingMapping[key] = schemaItem.$ref;
421
- } catch (_error) {
422
- if (key && !Object.values(existingMapping).includes(schemaItem.$ref)) existingMapping[key] = schemaItem.$ref;
423
- }
424
- } else {
425
- const discriminatorValue = getDiscriminatorValue(schemaItem);
426
- if (discriminatorValue) existingMapping[discriminatorValue] = `${KUBB_INLINE_REF_PREFIX}${index}`;
427
- }
428
- });
429
- };
430
- if (schema.oneOf) processSchemas(schema.oneOf, mapping);
431
- if (schema.anyOf) processSchemas(schema.anyOf, mapping);
432
- return {
433
- ...schema.discriminator,
434
- mapping
435
- };
436
- }
437
- dereferenceWithRef(schema) {
438
- if (isReference(schema)) return {
439
- ...schema,
440
- ...this.get(schema.$ref),
441
- $ref: schema.$ref
442
- };
443
- return schema;
444
- }
445
- #applyDiscriminatorInheritance() {
446
- const components = this.api.components;
447
- if (!components?.schemas) return;
448
- const visited = /* @__PURE__ */ new WeakSet();
449
- const enqueue = (value) => {
450
- if (!value) return;
451
- if (Array.isArray(value)) {
452
- for (const item of value) enqueue(item);
453
- return;
454
- }
455
- if (typeof value === "object") visit(value);
456
- };
457
- const visit = (schema) => {
458
- if (!schema || typeof schema !== "object") return;
459
- if (isReference(schema)) {
460
- visit(this.get(schema.$ref));
461
- return;
462
- }
463
- const schemaObject = schema;
464
- if (visited.has(schemaObject)) return;
465
- visited.add(schemaObject);
466
- if (isDiscriminator(schemaObject)) this.#setDiscriminator(schemaObject);
467
- if ("allOf" in schemaObject) enqueue(schemaObject.allOf);
468
- if ("oneOf" in schemaObject) enqueue(schemaObject.oneOf);
469
- if ("anyOf" in schemaObject) enqueue(schemaObject.anyOf);
470
- if ("not" in schemaObject) enqueue(schemaObject.not);
471
- if ("items" in schemaObject) enqueue(schemaObject.items);
472
- if ("prefixItems" in schemaObject) enqueue(schemaObject.prefixItems);
473
- if (schemaObject.properties) enqueue(Object.values(schemaObject.properties));
474
- if (schemaObject.additionalProperties && typeof schemaObject.additionalProperties === "object") enqueue(schemaObject.additionalProperties);
475
- };
476
- for (const schema of Object.values(components.schemas)) visit(schema);
477
- }
478
- /**
479
- * Oas does not have a getResponseBody(contentType)
480
- */
481
- #getResponseBodyFactory(responseBody) {
482
- function hasResponseBody(res = responseBody) {
483
- return !!res;
484
- }
485
- return (contentType) => {
486
- if (!hasResponseBody(responseBody)) return false;
487
- if (isReference(responseBody)) return false;
488
- if (!responseBody.content) return false;
489
- if (contentType) {
490
- if (!(contentType in responseBody.content)) return false;
491
- return responseBody.content[contentType];
492
- }
493
- let availableContentType;
494
- const contentTypes = Object.keys(responseBody.content);
495
- contentTypes.forEach((mt) => {
496
- if (!availableContentType && oas_utils.matchesMimeType.json(mt)) availableContentType = mt;
497
- });
498
- if (!availableContentType) contentTypes.forEach((mt) => {
499
- if (!availableContentType) availableContentType = mt;
500
- });
501
- if (availableContentType) return [
502
- availableContentType,
503
- responseBody.content[availableContentType],
504
- ...responseBody.description ? [responseBody.description] : []
505
- ];
506
- return false;
507
- };
508
- }
509
- getResponseSchema(operation, statusCode) {
510
- if (operation.schema.responses) Object.keys(operation.schema.responses).forEach((key) => {
511
- const schema = operation.schema.responses[key];
512
- const $ref = isReference(schema) ? schema.$ref : void 0;
513
- if (schema && $ref) operation.schema.responses[key] = this.get($ref);
514
- });
515
- const getResponseBody = this.#getResponseBodyFactory(operation.getResponseByStatusCode(statusCode));
516
- const { contentType } = this.#options;
517
- const responseBody = getResponseBody(contentType);
518
- if (responseBody === false) return {};
519
- const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
520
- if (!schema) return {};
521
- return this.dereferenceWithRef(schema);
522
- }
523
- getRequestSchema(operation) {
524
- const { contentType } = this.#options;
525
- if (operation.schema.requestBody) operation.schema.requestBody = this.dereferenceWithRef(operation.schema.requestBody);
526
- const requestBody = operation.getRequestBody(contentType);
527
- if (requestBody === false) return;
528
- const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
529
- if (!schema) return;
530
- return this.dereferenceWithRef(schema);
531
- }
532
- getParametersSchema(operation, inKey) {
533
- const { contentType = operation.getContentType() } = this.#options;
534
- const resolveParams = (params) => params.map((p) => this.dereferenceWithRef(p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
535
- const operationParams = resolveParams(operation.schema?.parameters || []);
536
- const pathItem = this.api?.paths?.[operation.path];
537
- const pathLevelParams = resolveParams(pathItem && !isReference(pathItem) && pathItem.parameters ? pathItem.parameters : []);
538
- const paramMap = /* @__PURE__ */ new Map();
539
- for (const p of pathLevelParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
540
- for (const p of operationParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
541
- const params = Array.from(paramMap.values()).filter((v) => v.in === inKey);
542
- if (!params.length) return null;
543
- return params.reduce((schema, pathParameters) => {
544
- const property = pathParameters.content?.[contentType]?.schema ?? pathParameters.schema;
545
- const required = typeof schema.required === "boolean" ? schema.required : [...schema.required || [], pathParameters.required ? pathParameters.name : void 0].filter(Boolean);
546
- const getDefaultStyle = (location) => {
547
- if (location === "query") return "form";
548
- if (location === "path") return "simple";
549
- return "simple";
550
- };
551
- const style = pathParameters.style || getDefaultStyle(inKey);
552
- const explode = pathParameters.explode !== void 0 ? pathParameters.explode : style === "form";
553
- if (inKey === "query" && style === "form" && explode === true && property?.type === "object" && property?.additionalProperties && !property?.properties) return {
554
- ...schema,
555
- description: pathParameters.description || schema.description,
556
- deprecated: schema.deprecated,
557
- example: property.example || schema.example,
558
- additionalProperties: property.additionalProperties
559
- };
560
- return {
561
- ...schema,
562
- description: schema.description,
563
- deprecated: schema.deprecated,
564
- example: schema.example,
565
- required,
566
- properties: {
567
- ...schema.properties,
568
- [pathParameters.name]: {
569
- description: pathParameters.description,
570
- ...property
571
- }
572
- }
573
- };
574
- }, {
575
- type: "object",
576
- required: [],
577
- properties: {}
578
- });
579
- }
580
- async validate() {
581
- return validate(this.api);
582
- }
583
- flattenSchema(schema) {
584
- return flattenSchema(schema);
585
- }
586
- /**
587
- * Get schemas from OpenAPI components (schemas, responses, requestBodies).
588
- * Returns schemas in dependency order along with name mapping for collision resolution.
589
- */
590
- getSchemas(options = {}) {
591
- const contentType = options.contentType ?? this.#options.contentType;
592
- const includes = options.includes ?? [
593
- "schemas",
594
- "requestBodies",
595
- "responses"
596
- ];
597
- const shouldResolveCollisions = options.collisionDetection ?? this.#options.collisionDetection ?? false;
598
- const components = this.getDefinition().components;
599
- const schemasWithMeta = [];
600
- if (includes.includes("schemas")) {
601
- const componentSchemas = components?.schemas || {};
602
- for (const [name, schemaObject] of Object.entries(componentSchemas)) {
603
- let schema = schemaObject;
604
- if (isReference(schemaObject)) {
605
- const resolved = this.get(schemaObject.$ref);
606
- if (resolved && !isReference(resolved)) schema = resolved;
607
- }
608
- schemasWithMeta.push({
609
- schema,
610
- source: "schemas",
611
- originalName: name
612
- });
613
- }
614
- }
615
- if (includes.includes("responses")) {
616
- const responses = components?.responses || {};
617
- for (const [name, response] of Object.entries(responses)) {
618
- const schema = extractSchemaFromContent(response.content, contentType);
619
- if (schema) {
620
- let resolvedSchema = schema;
621
- if (isReference(schema)) {
622
- const resolved = this.get(schema.$ref);
623
- if (resolved && !isReference(resolved)) resolvedSchema = resolved;
624
- }
625
- schemasWithMeta.push({
626
- schema: resolvedSchema,
627
- source: "responses",
628
- originalName: name
629
- });
630
- }
631
- }
632
- }
633
- if (includes.includes("requestBodies")) {
634
- const requestBodies = components?.requestBodies || {};
635
- for (const [name, request] of Object.entries(requestBodies)) {
636
- const schema = extractSchemaFromContent(request.content, contentType);
637
- if (schema) {
638
- let resolvedSchema = schema;
639
- if (isReference(schema)) {
640
- const resolved = this.get(schema.$ref);
641
- if (resolved && !isReference(resolved)) resolvedSchema = resolved;
642
- }
643
- schemasWithMeta.push({
644
- schema: resolvedSchema,
645
- source: "requestBodies",
646
- originalName: name
647
- });
648
- }
649
- }
650
- }
651
- const { schemas, nameMapping } = shouldResolveCollisions ? resolveCollisions(schemasWithMeta) : legacyResolve(schemasWithMeta);
652
- return {
653
- schemas: sortSchemas(schemas),
654
- nameMapping
655
- };
656
- }
657
- };
658
- //#endregion
659
- //#region src/oas/utils.ts
660
- /**
661
- * Narrows `doc` to a Swagger 2.0 document.
662
- * Swagger 2.0 documents do not have an `openapi` version key.
459
+ * @example
460
+ * ```ts
461
+ * if (isOpenApiV2Document(doc)) {
462
+ * // doc is OpenAPIV2.Document
463
+ * }
464
+ * ```
663
465
  */
664
466
  function isOpenApiV2Document(doc) {
665
467
  return !!doc && (0, remeda.isPlainObject)(doc) && !("openapi" in doc);
@@ -667,9 +469,15 @@ function isOpenApiV2Document(doc) {
667
469
  /**
668
470
  * Returns `true` when a schema should be treated as nullable.
669
471
  *
670
- * Covers three nullable signals across OAS versions:
671
- * - OAS 3.0: `nullable: true` or the vendor extension `x-nullable: true`.
672
- * - OAS 3.1 / JSON Schema: `type: 'null'` or `type: ['null', ...]` (multi-type array).
472
+ * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
473
+ * `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * isNullable({ type: 'string', nullable: true }) // true
478
+ * isNullable({ type: ['string', 'null'] }) // true
479
+ * isNullable({ type: 'string' }) // false
480
+ * ```
673
481
  */
674
482
  function isNullable(schema) {
675
483
  if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
@@ -679,34 +487,51 @@ function isNullable(schema) {
679
487
  return false;
680
488
  }
681
489
  /**
682
- * Narrows `obj` to an OpenAPI `$ref` pointer object.
683
- * Delegates to the `oas` package's own `isRef` helper.
490
+ * Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * isReference({ $ref: '#/components/schemas/Pet' }) // true
495
+ * isReference({ type: 'string' }) // false
496
+ * ```
684
497
  */
685
498
  function isReference(obj) {
686
- return !!obj && (0, oas_types.isRef)(obj);
499
+ return !!obj && typeof obj === "object" && "$ref" in obj;
687
500
  }
688
501
  /**
689
- * Returns `true` when `obj` is a schema that carries a structured OAS 3.x `discriminator`
690
- * object — as opposed to a plain-string discriminator found in some Swagger 2 specs.
502
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
503
+ *
504
+ * @example
505
+ * ```ts
506
+ * isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
507
+ * isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
508
+ * ```
691
509
  */
692
510
  function isDiscriminator(obj) {
693
511
  const record = obj;
694
512
  return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
695
513
  }
514
+ //#endregion
515
+ //#region src/factory.ts
696
516
  /**
697
- * Loads, dereferences, and wraps a raw OpenAPI document into an `Oas` instance.
517
+ * Loads and dereferences an OpenAPI document, returning the raw `Document`.
698
518
  *
699
- * When given a file path string with `canBundle: true` (the default), Redocly's
700
- * bundler resolves all external `$ref`s first. Swagger 2.0 documents are
701
- * automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
519
+ * Accepts a file path string or an already-parsed document object. File paths are bundled via
520
+ * Redocly to resolve external `$ref`s. Swagger 2.0 documents are automatically up-converted
521
+ * to OpenAPI 3.0 via `swagger2openapi`.
522
+ *
523
+ * @example
524
+ * ```ts
525
+ * const document = await parseDocument('./openapi.yaml')
526
+ * const document = await parse(rawDocumentObject, { canBundle: false })
527
+ * ```
702
528
  */
703
- async function parse(pathOrApi, { oasClass = Oas, canBundle = true, enablePaths = true } = {}) {
704
- if (typeof pathOrApi === "string" && canBundle) return parse((await (0, _redocly_openapi_core.bundle)({
529
+ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true } = {}) {
530
+ if (typeof pathOrApi === "string" && canBundle) return parseDocument((await (0, _redocly_openapi_core.bundle)({
705
531
  ref: pathOrApi,
706
532
  config: await (0, _redocly_openapi_core.loadConfig)(),
707
533
  base: pathOrApi
708
534
  })).bundle.parsed, {
709
- oasClass,
710
535
  canBundle,
711
536
  enablePaths
712
537
  });
@@ -716,26 +541,28 @@ async function parse(pathOrApi, { oasClass = Oas, canBundle = true, enablePaths
716
541
  }).load();
717
542
  if (isOpenApiV2Document(document)) {
718
543
  const { openapi } = await swagger2openapi.default.convertObj(document, { anchors: true });
719
- return new oasClass(openapi);
544
+ return openapi;
720
545
  }
721
- return new oasClass(document);
546
+ return document;
722
547
  }
723
548
  /**
724
- * Deep-merges multiple OpenAPI documents into a single `Oas` instance.
549
+ * Deep-merges multiple OpenAPI documents into a single `Document`.
725
550
  *
726
- * Each document is parsed independently (without path dereferencing) and then
727
- * recursively merged using `remeda`'s `mergeDeep`. The result is re-parsed so
728
- * the returned `Oas` instance is fully initialized.
551
+ * Each document is parsed independently then recursively merged with `remeda`'s `mergeDeep`.
552
+ * Throws when the input array is empty.
729
553
  *
730
- * Throws when the input array is empty — at least one document is required.
554
+ * @example
555
+ * ```ts
556
+ * const document = await mergeDocuments(['./pets.yaml', './orders.yaml'])
557
+ * ```
731
558
  */
732
- async function merge(pathOrApi, { oasClass = Oas } = {}) {
733
- const instances = await Promise.all(pathOrApi.map((p) => parse(p, {
734
- oasClass,
559
+ async function mergeDocuments(pathOrApi) {
560
+ const documents = [];
561
+ for (const p of pathOrApi) documents.push(await parseDocument(p, {
735
562
  enablePaths: false,
736
563
  canBundle: false
737
- })));
738
- if (instances.length === 0) throw new Error("No OAS instances provided for merging.");
564
+ }));
565
+ if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
739
566
  const seed = {
740
567
  openapi: MERGE_OPENAPI_VERSION,
741
568
  info: {
@@ -745,480 +572,502 @@ async function merge(pathOrApi, { oasClass = Oas } = {}) {
745
572
  paths: {},
746
573
  components: { schemas: {} }
747
574
  };
748
- return parse(instances.reduce((acc, current) => (0, remeda.mergeDeep)(acc, current.document), seed), { oasClass });
575
+ return parseDocument(documents.reduce((acc, current) => (0, remeda.mergeDeep)(acc, current), seed));
749
576
  }
750
577
  /**
751
- * Constructs an `Oas` instance from a Kubb `Config` object.
578
+ * Creates a `Document` from an `AdapterSource`.
752
579
  *
753
- * Handles all three input forms supported by `Config`:
754
- * - `data` — an inline YAML string, JSON string, or pre-parsed object.
755
- * - Array — multiple file paths that are deep-merged via {@link merge}.
756
- * - `path` — a local file path or a remote URL.
580
+ * Handles all three source types:
581
+ * - `{ type: 'path' }` — resolves and bundles a local file path or remote URL.
582
+ * - `{ type: 'paths' }` merges multiple file paths into a single document.
583
+ * - `{ type: 'data' }` — parses an inline string (YAML/JSON) or raw object.
584
+ *
585
+ * @example
586
+ * ```ts
587
+ * const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
588
+ * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
589
+ * ```
757
590
  */
758
- function parseFromConfig(config, oasClass = Oas) {
759
- if ("data" in config.input) {
760
- if (typeof config.input.data === "object") return parse(structuredClone(config.input.data), { oasClass });
591
+ function parseFromConfig(source) {
592
+ if (source.type === "data") {
593
+ if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
761
594
  try {
762
- return parse(_stoplight_yaml.default.parse(config.input.data), { oasClass });
595
+ return parseDocument(_stoplight_yaml.default.parse(source.data));
763
596
  } catch {
764
- return parse(config.input.data, { oasClass });
597
+ return parseDocument(source.data);
765
598
  }
766
599
  }
767
- if (Array.isArray(config.input)) return merge(config.input.map((input) => node_path.default.resolve(config.root, input.path)), { oasClass });
768
- if (new URLPath(config.input.path).isURL) return parse(config.input.path, { oasClass });
769
- return parse(node_path.default.resolve(config.root, config.input.path), { oasClass });
600
+ if (source.type === "paths") return mergeDocuments(source.paths);
601
+ if (new URLPath(source.path).isURL) return parseDocument(source.path);
602
+ return parseDocument(node_path.default.resolve(node_path.default.dirname(source.path), source.path));
770
603
  }
771
604
  /**
772
- * Flattens a single-member or keyword-only `allOf` into its parent schema.
773
- *
774
- * Flattening is only performed when every `allOf` member is a "plain fragment" —
775
- * i.e. contains no structural composition keywords (see `structuralKeys`) and no
776
- * `$ref`. When any member carries structural meaning, the schema is returned unchanged.
605
+ * Validates an OpenAPI document using `oas-normalize` with colorized error output.
777
606
  *
778
- * Keyword values from allOf members are merged into the parent on a first-write basis:
779
- * outer schema values always win over fragment values.
607
+ * @example
608
+ * ```ts
609
+ * await validateDocument(document)
610
+ * ```
780
611
  */
781
- function flattenSchema(schema) {
782
- if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
783
- if (schema.allOf.some((item) => (0, oas_types.isRef)(item))) return schema;
784
- const isPlainFragment = (item) => !Object.keys(item).some((key) => structuralKeys.has(key));
785
- if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
786
- const merged = { ...schema };
787
- delete merged.allOf;
788
- for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
789
- return merged;
612
+ async function validateDocument(document) {
613
+ try {
614
+ await new oas_normalize.default(document, {
615
+ enablePaths: true,
616
+ colorizeErrors: true
617
+ }).validate({ parser: { validate: { errors: { colorize: true } } } });
618
+ } catch (_err) {}
790
619
  }
620
+ //#endregion
621
+ //#region src/refs.ts
791
622
  /**
792
- * Validates an OpenAPI document using `oas-normalize`.
793
- * Enables path validation and colorized error output.
623
+ * Resolves a local JSON pointer reference from a document.
624
+ *
625
+ * Accepts `#/...` refs. Returns `null` for empty or non-local refs.
626
+ * Throws when the pointer cannot be resolved.
627
+ *
628
+ * @example
629
+ * ```ts
630
+ * resolveRef<SchemaObject>(document, '#/components/schemas/Pet') // SchemaObject | null
631
+ * ```
794
632
  */
795
- async function validate(document) {
796
- return new oas_normalize.default(document, {
797
- enablePaths: true,
798
- colorizeErrors: true
799
- }).validate({ parser: { validate: { errors: { colorize: true } } } });
633
+ function resolveRef(document, $ref) {
634
+ const origRef = $ref;
635
+ $ref = $ref.trim();
636
+ if ($ref === "") return null;
637
+ if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
638
+ else return null;
639
+ const current = jsonpointer.default.get(document, $ref);
640
+ if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
641
+ return current;
800
642
  }
801
643
  /**
802
- * Walks a schema tree and collects the names of all `#/components/schemas/<name>` refs.
803
- * Used by `sortSchemas` to build the dependency graph.
644
+ * Resolves a `$ref` object while preserving the original `$ref` field on the result.
645
+ *
646
+ * Useful for parser flows that need both dereferenced fields and pointer
647
+ * identity (for naming/import purposes). Non-reference values are returned as-is.
648
+ *
649
+ * @example
650
+ * ```ts
651
+ * dereferenceWithRef(document, { $ref: '#/components/schemas/Pet' })
652
+ * // { $ref: '#/components/schemas/Pet', type: 'object', properties: { ... } }
653
+ * ```
804
654
  */
805
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
806
- if (Array.isArray(schema)) {
807
- for (const item of schema) collectRefs(item, refs);
808
- return refs;
809
- }
810
- if (schema && typeof schema === "object") for (const [key, value] of Object.entries(schema)) if (key === "$ref" && typeof value === "string") {
811
- const match = value.match(/^#\/components\/schemas\/(.+)$/);
812
- if (match) refs.add(match[1]);
813
- } else collectRefs(value, refs);
814
- return refs;
655
+ function dereferenceWithRef(document, schema) {
656
+ if (isReference(schema)) return {
657
+ ...schema,
658
+ ...resolveRef(document, schema.$ref),
659
+ $ref: schema.$ref
660
+ };
661
+ return schema;
815
662
  }
663
+ //#endregion
664
+ //#region src/resolvers.ts
816
665
  /**
817
- * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
666
+ * Resolves `{variable}` placeholders in an OpenAPI server URL.
818
667
  *
819
- * Schemas with no references come first; schemas that are referenced by others
820
- * come before those that reference them. This ensures code generators emit
821
- * referenced types before the types that depend on them.
668
+ * Resolution order per variable: `overrides[key]` `variable.default` left unreplaced.
669
+ * Throws when an override value is not in the variable's allowed `enum` list.
822
670
  *
823
- * Cycles are silently skipped via the `stack` guard inside `visit`.
671
+ * @example
672
+ * ```ts
673
+ * resolveServerUrl(
674
+ * { url: 'https://{env}.api.example.com', variables: { env: { default: 'dev', enum: ['dev', 'prod'] } } },
675
+ * { env: 'prod' },
676
+ * )
677
+ * // 'https://prod.api.example.com'
678
+ * ```
824
679
  */
825
- function sortSchemas(schemas) {
826
- const deps = /* @__PURE__ */ new Map();
827
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
828
- const sorted = [];
829
- const visited = /* @__PURE__ */ new Set();
830
- function visit(name, stack) {
831
- if (visited.has(name) || stack.has(name)) return;
832
- stack.add(name);
833
- for (const child of deps.get(name) ?? []) if (deps.has(child)) visit(child, stack);
834
- stack.delete(name);
835
- visited.add(name);
836
- sorted.push(name);
680
+ function resolveServerUrl(server, overrides) {
681
+ if (!server.variables) return server.url;
682
+ let url = server.url;
683
+ for (const [key, variable] of Object.entries(server.variables)) {
684
+ const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
685
+ if (value === void 0) continue;
686
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(`Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`);
687
+ url = url.replaceAll(`{${key}}`, value);
837
688
  }
838
- for (const name of Object.keys(schemas)) visit(name, /* @__PURE__ */ new Set());
839
- const result = {};
840
- for (const name of sorted) result[name] = schemas[name];
841
- return result;
689
+ return url;
842
690
  }
843
691
  /**
844
- * Extracts the inline schema from a media-type `content` map.
845
- *
846
- * Prefers `preferredContentType` when provided; otherwise falls back to the
847
- * first key in the map. Returns `null` when:
848
- * - `content` is absent.
849
- * - The target content-type has no `schema` entry.
850
- * - The schema is a `$ref` (callers resolve refs separately).
692
+ * Looks up the Kubb `SchemaType` for a given OAS `format` string.
693
+ * Returns `undefined` for formats not in `formatMap` (e.g. `int64`, `date-time`),
694
+ * which are handled separately because their output depends on parser options.
851
695
  */
852
- function extractSchemaFromContent(content, preferredContentType) {
853
- if (!content) return null;
854
- const firstContentType = Object.keys(content)[0] ?? "application/json";
855
- const schema = content[preferredContentType ?? firstContentType]?.schema;
856
- if (schema && "$ref" in schema) return null;
857
- return schema ?? null;
696
+ function getSchemaType(format) {
697
+ return formatMap[format] ?? null;
858
698
  }
859
699
  /**
860
- * Returns the PascalCase suffix appended to a component name when resolving
861
- * cross-source name collisions (schemas vs. responses vs. requestBodies).
700
+ * Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
701
+ * Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
702
+ * `boolean` maps to `'boolean'`; everything else defaults to `'string'`.
862
703
  */
863
- function getSemanticSuffix(source) {
864
- switch (source) {
865
- case "schemas": return "Schema";
866
- case "responses": return "Response";
867
- case "requestBodies": return "Request";
868
- }
704
+ function getPrimitiveType(type) {
705
+ if (type === "number" || type === "integer" || type === "bigint") return type;
706
+ if (type === "boolean") return "boolean";
707
+ return "string";
869
708
  }
870
709
  /**
871
- * Builds `GetSchemasResult` without any collision detection.
872
- * Each schema is registered under its original component name and its full
873
- * `#/components/<source>/<name>` ref path.
710
+ * Narrows a raw content-type string to the `MediaType` union recognized by Kubb.
711
+ * Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
874
712
  */
875
- function legacyResolve(schemasWithMeta) {
876
- const schemas = {};
877
- const nameMapping = /* @__PURE__ */ new Map();
878
- for (const item of schemasWithMeta) {
879
- schemas[item.originalName] = item.schema;
880
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName);
881
- }
882
- return {
883
- schemas,
884
- nameMapping
885
- };
713
+ function getMediaType(contentType) {
714
+ return Object.values(_kubb_ast.mediaTypes).includes(contentType) ? contentType : null;
886
715
  }
887
716
  /**
888
- * Builds `GetSchemasResult` with automatic name-collision resolution.
717
+ * Returns all resolved parameters for an operation, merging path-level and operation-level entries.
889
718
  *
890
- * When two or more schemas normalize to the same PascalCase name:
891
- * - If they share the same source, a numeric suffix (`2`, `3`, …) is appended.
892
- * - If they come from different sources (schemas / responses / requestBodies),
893
- * a semantic suffix (`Schema`, `Response`, `Request`) is appended.
719
+ * Operation-level parameters take precedence over path-level ones with the same `in:name` key.
720
+ * `$ref` parameters are resolved via `dereferenceWithRef` to restore backward compatibility
721
+ * with `oas` v31+ which otherwise filters them out.
894
722
  *
895
- * Non-colliding schemas are left unchanged.
723
+ * @example
724
+ * ```ts
725
+ * getParameters(document, operation)
726
+ * // [{ name: 'petId', in: 'path', required: true, schema: { type: 'integer' } }]
727
+ * ```
896
728
  */
897
- function resolveCollisions(schemasWithMeta) {
898
- const schemas = {};
899
- const nameMapping = /* @__PURE__ */ new Map();
900
- const normalizedNames = /* @__PURE__ */ new Map();
901
- for (const item of schemasWithMeta) {
902
- const normalized = pascalCase(item.originalName);
903
- const bucket = normalizedNames.get(normalized) ?? [];
904
- bucket.push(item);
905
- normalizedNames.set(normalized, bucket);
906
- }
907
- for (const [, items] of normalizedNames) {
908
- if (items.length === 1) {
909
- const item = items[0];
910
- schemas[item.originalName] = item.schema;
911
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName);
912
- continue;
913
- }
914
- if (new Set(items.map((item) => item.source)).size === 1) items.forEach((item, index) => {
915
- const uniqueName = item.originalName + (index === 0 ? "" : (index + 1).toString());
916
- schemas[uniqueName] = item.schema;
917
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
918
- });
919
- else items.forEach((item) => {
920
- const uniqueName = item.originalName + getSemanticSuffix(item.source);
921
- schemas[uniqueName] = item.schema;
922
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
923
- });
729
+ function getParameters(document, operation) {
730
+ const resolveParams = (params) => params.map((p) => dereferenceWithRef(document, p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
731
+ const operationParams = resolveParams(operation.schema?.parameters || []);
732
+ const pathItem = document.paths?.[operation.path];
733
+ const pathLevelParams = resolveParams(pathItem && !isReference(pathItem) && pathItem.parameters ? pathItem.parameters : []);
734
+ const paramMap = /* @__PURE__ */ new Map();
735
+ for (const p of pathLevelParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
736
+ for (const p of operationParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
737
+ return Array.from(paramMap.values());
738
+ }
739
+ function getResponseBody(responseBody, contentType) {
740
+ if (!responseBody) return false;
741
+ if (isReference(responseBody)) return false;
742
+ const body = responseBody;
743
+ if (!body.content) return false;
744
+ if (contentType) {
745
+ if (!(contentType in body.content)) return false;
746
+ return body.content[contentType];
924
747
  }
925
- return {
926
- schemas,
927
- nameMapping
928
- };
748
+ let availableContentType;
749
+ const contentTypes = Object.keys(body.content);
750
+ contentTypes.forEach((mt) => {
751
+ if (!availableContentType && oas_utils.matchesMimeType.json(mt)) availableContentType = mt;
752
+ });
753
+ if (!availableContentType) contentTypes.forEach((mt) => {
754
+ if (!availableContentType) availableContentType = mt;
755
+ });
756
+ if (availableContentType) return [
757
+ availableContentType,
758
+ body.content[availableContentType],
759
+ ...body.description ? [body.description] : []
760
+ ];
761
+ return false;
762
+ }
763
+ /**
764
+ * Returns the response schema for a given operation and HTTP status code.
765
+ *
766
+ * Returns an empty object `{}` when no response body schema is available.
767
+ *
768
+ * @example
769
+ * ```ts
770
+ * getResponseSchema(document, operation, 200) // SchemaObject
771
+ * getResponseSchema(document, operation, '4XX') // {}
772
+ * ```
773
+ */
774
+ function getResponseSchema(document, operation, statusCode, options = {}) {
775
+ if (operation.schema.responses) Object.keys(operation.schema.responses).forEach((key) => {
776
+ const schema = operation.schema.responses[key];
777
+ const $ref = isReference(schema) ? schema.$ref : void 0;
778
+ if (schema && $ref) operation.schema.responses[key] = resolveRef(document, $ref);
779
+ });
780
+ const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
781
+ if (responseBody === false) return {};
782
+ const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
783
+ if (!schema) return {};
784
+ return dereferenceWithRef(document, schema);
929
785
  }
930
- //#endregion
931
- //#region src/utils.ts
932
786
  /**
933
- * Extracts the schema name from a `$ref` string.
934
- * For `#/components/schemas/Order` this returns `'Order'`.
935
- * Falls back to the full ref string when no slash is present.
787
+ * Returns the request body schema for an operation, or `undefined` when absent.
788
+ *
789
+ * @example
790
+ * ```ts
791
+ * getRequestSchema(document, operation) // SchemaObject | null
792
+ * ```
936
793
  */
937
- function extractRefName($ref) {
938
- return $ref.split("/").at(-1) ?? $ref;
794
+ function getRequestSchema(document, operation, options = {}) {
795
+ if (operation.schema.requestBody) operation.schema.requestBody = dereferenceWithRef(document, operation.schema.requestBody);
796
+ const requestBody = operation.getRequestBody(options.contentType);
797
+ if (requestBody === false) return null;
798
+ const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
799
+ if (!schema) return null;
800
+ return dereferenceWithRef(document, schema);
939
801
  }
940
802
  /**
941
- * Replaces the discriminator property's schema inside an `ObjectSchemaNode`
942
- * with an enum of the given `values`.
803
+ * Flattens a keyword-only `allOf` into its parent schema.
804
+ *
805
+ * Only flattens when every member is a plain fragment — no `$ref` and no structural keywords
806
+ * (see `structuralKeys`). Outer schema values take precedence over fragment values.
807
+ * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
943
808
  *
944
- * - When `enumName` is provided the enum is named, which lets printers emit a
945
- * standalone enum declaration + type reference (e.g. `PetTypeEnum`).
946
- * - When `enumName` is omitted the enum stays anonymous, so printers inline it
947
- * as a literal union (e.g. `'dog'`).
809
+ * @example
810
+ * ```ts
811
+ * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
812
+ * // { type: 'object', properties: {}, description: 'A pet' }
948
813
  *
949
- * Returns the node unchanged when it is not an object or lacks the target property.
814
+ * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
815
+ * // returned unchanged — contains a $ref
816
+ * ```
950
817
  */
951
- function applyDiscriminatorEnum({ node, propertyName, values, enumName }) {
952
- if (node.type !== "object" || !node.properties?.length) return node;
953
- if (!node.properties.some((prop) => prop.name === propertyName)) return node;
954
- return (0, _kubb_ast.createSchema)({
955
- ...node,
956
- properties: node.properties.map((prop) => {
957
- if (prop.name !== propertyName) return prop;
958
- const enumSchema = (0, _kubb_ast.createSchema)({
959
- type: "enum",
960
- primitive: "string",
961
- enumValues: values,
962
- name: enumName,
963
- readOnly: prop.schema.readOnly,
964
- writeOnly: prop.schema.writeOnly
965
- });
966
- return (0, _kubb_ast.createProperty)({
967
- ...prop,
968
- schema: enumSchema
969
- });
970
- })
971
- });
818
+ function flattenSchema(schema) {
819
+ if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
820
+ if (schema.allOf.some((item) => (0, oas_types.isRef)(item))) return schema;
821
+ const isPlainFragment = (item) => !Object.keys(item).some((key) => structuralKeys.has(key));
822
+ if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
823
+ const merged = { ...schema };
824
+ delete merged.allOf;
825
+ for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
826
+ return merged;
972
827
  }
973
828
  /**
974
- * Merges consecutive anonymous (`name`-less) `ObjectSchemaNode`s in `members` into a
975
- * single object by combining their `properties` arrays.
829
+ * Extracts the inline schema from a media-type `content` map.
830
+ *
831
+ * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
832
+ * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
976
833
  *
977
- * Only adjacent pairs are merged — non-object or named nodes act as boundaries.
978
- * This collapses patterns like `Address & { streetNumber } & { streetName }` into
979
- * `Address & { streetNumber; streetName }`.
834
+ * @example
835
+ * ```ts
836
+ * extractSchemaFromContent(operation.content, 'application/json')
837
+ * // SchemaObject | null
838
+ * ```
980
839
  */
981
- function mergeAdjacentAnonymousObjects(members) {
982
- return members.reduce((acc, member) => {
983
- const obj = (0, _kubb_ast.narrowSchema)(member, "object");
984
- if (obj && !obj.name) {
985
- const prev = acc[acc.length - 1];
986
- const prevObj = prev ? (0, _kubb_ast.narrowSchema)(prev, "object") : null;
987
- if (prevObj && !prevObj.name) {
988
- acc[acc.length - 1] = (0, _kubb_ast.createSchema)({
989
- ...prevObj,
990
- properties: [...prevObj.properties ?? [], ...obj.properties ?? []]
991
- });
992
- return acc;
993
- }
994
- }
995
- acc.push(member);
996
- return acc;
997
- }, []);
840
+ function extractSchemaFromContent(content, preferredContentType) {
841
+ if (!content) return null;
842
+ const firstContentType = Object.keys(content)[0] ?? "application/json";
843
+ const schema = content[preferredContentType ?? firstContentType]?.schema;
844
+ if (schema && "$ref" in schema) return null;
845
+ return schema ?? null;
998
846
  }
999
847
  /**
1000
- * Simplifies a union member list by removing `enum` nodes whose `primitive` type is
1001
- * already represented by a broader scalar node in the same union.
1002
- *
1003
- * For example `['placed', 'approved'] | string` collapses to `string` because
1004
- * `string` subsumes all string literals. `'' | string` similarly becomes `string`.
1005
- *
1006
- * Only scalar primitives (`string`, `number`, `integer`, `bigint`, `boolean`) are
1007
- * considered — object, array, and ref members are left untouched.
848
+ * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
1008
849
  */
1009
- function simplifyUnionMembers(members) {
1010
- const scalarPrimitives = new Set(members.filter((m) => [
1011
- "string",
1012
- "number",
1013
- "integer",
1014
- "bigint",
1015
- "boolean"
1016
- ].includes(m.type)).map((m) => m.type));
1017
- if (!scalarPrimitives.size) return members;
1018
- return members.filter((m) => {
1019
- if (m.type !== "enum") return true;
1020
- const prim = m.primitive;
1021
- if (!prim) return true;
1022
- if (scalarPrimitives.has(prim)) return false;
1023
- if ((prim === "integer" || prim === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
1024
- return true;
1025
- });
850
+ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
851
+ if (Array.isArray(schema)) {
852
+ for (const item of schema) collectRefs(item, refs);
853
+ return refs;
854
+ }
855
+ if (schema && typeof schema === "object") for (const [key, value] of Object.entries(schema)) if (key === "$ref" && typeof value === "string") {
856
+ const match = value.match(/^#\/components\/schemas\/(.+)$/);
857
+ if (match) refs.add(match[1]);
858
+ } else collectRefs(value, refs);
859
+ return refs;
1026
860
  }
1027
861
  /**
1028
- * `nameMapping`, and calls `resolve` to obtain the `{ name, path }` pair for
1029
- * each import. When `oas` is supplied, only `$ref`s that are resolvable in the
1030
- * spec are included; omit it to skip the existence check.
862
+ * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
1031
863
  *
1032
- * This function is the pure, state-free alternative to `OasParser.getImports`.
1033
- * Because it receives `nameMapping` explicitly it can be called without holding
1034
- * a reference to the parser or the OAS instance.
864
+ * Referenced schemas appear before the schemas that depend on them, so code generators
865
+ * can emit types in the correct order. Cycles are silently skipped.
1035
866
  *
1036
867
  * @example
1037
868
  * ```ts
1038
- * // Use adapter state directly no parser reference needed
1039
- * const imports = getImports({
1040
- * node: schemaNode,
1041
- * nameMapping: adapter.options.nameMapping,
1042
- * resolve: (schemaName) => ({
1043
- * name: schemaManager.getName(schemaName, { type: 'type' }),
1044
- * path: schemaManager.getFile(schemaName).path,
1045
- * }),
1046
- * })
869
+ * const sorted = sortSchemas({ Order: orderSchema, Pet: petSchema })
870
+ * // Pet appears before Order when Order.$ref points at Pet
1047
871
  * ```
1048
872
  */
1049
- function getImports({ node, nameMapping, resolve }) {
1050
- return (0, _kubb_ast.collect)(node, { schema(schemaNode) {
1051
- if (schemaNode.type !== "ref" || !schemaNode.ref) return;
1052
- const rawName = extractRefName(schemaNode.ref);
1053
- const result = resolve(nameMapping.get(rawName) ?? rawName);
1054
- if (!result) return;
1055
- return {
1056
- name: [result.name],
1057
- path: result.path
1058
- };
1059
- } });
873
+ function sortSchemas(schemas) {
874
+ const deps = /* @__PURE__ */ new Map();
875
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
876
+ const sorted = [];
877
+ const visited = /* @__PURE__ */ new Set();
878
+ function visit(name, stack) {
879
+ if (visited.has(name) || stack.has(name)) return;
880
+ stack.add(name);
881
+ for (const child of deps.get(name) ?? []) if (deps.has(child)) visit(child, stack);
882
+ stack.delete(name);
883
+ visited.add(name);
884
+ sorted.push(name);
885
+ }
886
+ for (const name of Object.keys(schemas)) visit(name, /* @__PURE__ */ new Set());
887
+ const result = {};
888
+ for (const name of sorted) result[name] = schemas[name];
889
+ return result;
1060
890
  }
1061
- //#endregion
1062
- //#region src/parser.ts
1063
- /**
1064
- * Default values for all `Options` fields.
1065
- */
1066
- const DEFAULT_OPTIONS = {
1067
- dateType: "string",
1068
- integerType: "number",
1069
- unknownType: "any",
1070
- emptySchemaType: "any",
1071
- enumSuffix: "enum"
891
+ const semanticSuffixes = {
892
+ schemas: "Schema",
893
+ responses: "Response",
894
+ requestBodies: "Request"
1072
895
  };
1073
- /**
1074
- * Looks up the Kubb `SchemaType` for a given OAS `format` string.
1075
- * Returns `undefined` for formats not in `formatMap` (e.g. `int64`, `date-time`),
1076
- * which are handled separately because their output depends on parser options.
1077
- */
1078
- function formatToSchemaType(format) {
1079
- return formatMap[format];
1080
- }
1081
- /**
1082
- * Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
1083
- * Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
1084
- * `boolean` maps to `'boolean'`; everything else defaults to `'string'`.
1085
- */
1086
- function getPrimitiveType(type) {
1087
- if (type === "number" || type === "integer" || type === "bigint") return type;
1088
- if (type === "boolean") return "boolean";
1089
- return "string";
896
+ function getSemanticSuffix(source) {
897
+ return semanticSuffixes[source];
1090
898
  }
1091
- /**
1092
- * Narrows a raw content-type string to the `MediaType` union recognized by Kubb.
1093
- * Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
1094
- */
1095
- function toMediaType(contentType) {
1096
- return knownMediaTypes.includes(contentType) ? contentType : void 0;
899
+ function resolveSchemaRef(document, schema) {
900
+ if (!isReference(schema)) return schema;
901
+ const resolved = resolveRef(document, schema.$ref);
902
+ return resolved && !isReference(resolved) ? resolved : schema;
1097
903
  }
1098
904
  /**
1099
- * Creates an OAS parser that converts an OpenAPI/Swagger spec into
1100
- * the `@kubb/ast` tree.
905
+ * Collects component schemas from one or more sources and resolves name collisions.
1101
906
  *
1102
- * Options are passed per-call to `parse` or `convertSchema` rather than
1103
- * at construction time, keeping the factory lightweight.
907
+ * Sources default to `['schemas', 'requestBodies', 'responses']`. Returned schemas are
908
+ * topologically sorted by `$ref` dependency so generators emit types in the correct order.
1104
909
  *
1105
- * This is the **kubb-parser** stage of the compilation lifecycle:
1106
- * OpenAPI / Swagger Kubb AST
1107
- *
1108
- * No code is generated here; the resulting tree is spec-agnostic and can
1109
- * be consumed by any downstream plugin (plugin-ts, plugin-zod, …).
910
+ * When two or more schemas normalize to the same PascalCase name:
911
+ * - Same source numeric suffix (`2`, `3`, …).
912
+ * - Different sources → semantic suffix (`Schema`, `Response`, `Request`).
1110
913
  *
1111
914
  * @example
1112
915
  * ```ts
1113
- * const parser = createOasParser(oas)
1114
- * const root = parser.parse({ emptySchemaType: 'unknown' })
916
+ * const { schemas, nameMapping } = getSchemas(document, { contentType: 'application/json' })
1115
917
  * ```
1116
918
  */
1117
- function createOasParser(oas, { contentType, collisionDetection } = {}) {
1118
- const { schemas: schemaObjects, nameMapping } = oas.getSchemas({
1119
- contentType,
1120
- collisionDetection
1121
- });
1122
- /**
1123
- * Maps an `'any' | 'unknown' | 'void'` option string to the corresponding `SchemaType` constant.
1124
- * Used for both `unknownType` (unannotated schemas) and `emptySchemaType` (empty `{}` schemas).
1125
- */
1126
- function resolveTypeOption(value) {
1127
- if (value === "any") return _kubb_ast.schemaTypes.any;
1128
- if (value === "void") return _kubb_ast.schemaTypes.void;
1129
- return _kubb_ast.schemaTypes.unknown;
919
+ function getSchemas(document, { contentType }) {
920
+ const components = document.components;
921
+ const candidates = [...Object.entries(components?.schemas ?? {}).map(([name, schema]) => ({
922
+ schema: resolveSchemaRef(document, schema),
923
+ source: "schemas",
924
+ originalName: name
925
+ })), ...["responses", "requestBodies"].flatMap((source) => Object.entries(components?.[source] ?? {}).flatMap(([name, item]) => {
926
+ const schema = extractSchemaFromContent(item.content, contentType);
927
+ return schema ? [{
928
+ schema: resolveSchemaRef(document, schema),
929
+ source,
930
+ originalName: name
931
+ }] : [];
932
+ }))];
933
+ const normalizedNames = /* @__PURE__ */ new Map();
934
+ for (const item of candidates) {
935
+ const key = pascalCase(item.originalName);
936
+ const bucket = normalizedNames.get(key) ?? [];
937
+ bucket.push(item);
938
+ normalizedNames.set(key, bucket);
1130
939
  }
1131
- /**
1132
- * Resolves the AST type and datetime modifiers for a date/time format, honoring the `dateType` option.
1133
- * Returns `undefined` when `dateType` is `false`, meaning the format should fall through to `string`.
1134
- */
1135
- function getDateType(options, format) {
1136
- if (!options.dateType) return;
1137
- if (format === "date-time") {
1138
- if (options.dateType === "date") return {
1139
- type: "date",
1140
- representation: "date"
1141
- };
1142
- if (options.dateType === "stringOffset") return {
1143
- type: "datetime",
1144
- offset: true
1145
- };
1146
- if (options.dateType === "stringLocal") return {
1147
- type: "datetime",
1148
- local: true
1149
- };
1150
- return {
1151
- type: "datetime",
1152
- offset: false
1153
- };
1154
- }
1155
- if (format === "date") return {
940
+ const schemas = {};
941
+ const nameMapping = /* @__PURE__ */ new Map();
942
+ const multipleSources = (items) => new Set(items.map((i) => i.source)).size > 1;
943
+ for (const [, items] of normalizedNames) items.forEach((item, index) => {
944
+ const suffix = items.length === 1 ? "" : multipleSources(items) ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
945
+ const uniqueName = item.originalName + suffix;
946
+ schemas[uniqueName] = item.schema;
947
+ nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
948
+ });
949
+ return {
950
+ schemas: sortSchemas(schemas),
951
+ nameMapping
952
+ };
953
+ }
954
+ /**
955
+ * Resolves the AST type descriptor for a date/time format, honoring the `dateType` option.
956
+ * Returns `null` when `dateType: false`, signalling the format should fall through to `string`.
957
+ */
958
+ function getDateType(options, format) {
959
+ if (!options.dateType) return null;
960
+ if (format === "date-time") {
961
+ if (options.dateType === "date") return {
1156
962
  type: "date",
1157
- representation: options.dateType === "date" ? "date" : "string"
963
+ representation: "date"
964
+ };
965
+ if (options.dateType === "stringOffset") return {
966
+ type: "datetime",
967
+ offset: true
968
+ };
969
+ if (options.dateType === "stringLocal") return {
970
+ type: "datetime",
971
+ local: true
1158
972
  };
1159
973
  return {
1160
- type: "time",
1161
- representation: options.dateType === "date" ? "date" : "string"
974
+ type: "datetime",
975
+ offset: false
1162
976
  };
1163
977
  }
978
+ if (format === "date") return {
979
+ type: "date",
980
+ representation: options.dateType === "date" ? "date" : "string"
981
+ };
982
+ return {
983
+ type: "time",
984
+ representation: options.dateType === "date" ? "date" : "string"
985
+ };
986
+ }
987
+ /**
988
+ * Collects the shared metadata fields passed to every `createSchema` call.
989
+ */
990
+ function buildSchemaNode(schema, name, nullable, defaultValue) {
991
+ return {
992
+ name,
993
+ nullable,
994
+ title: schema.title,
995
+ description: schema.description,
996
+ deprecated: schema.deprecated,
997
+ readOnly: schema.readOnly,
998
+ writeOnly: schema.writeOnly,
999
+ default: defaultValue,
1000
+ example: schema.example
1001
+ };
1002
+ }
1003
+ //#endregion
1004
+ //#region src/parser.ts
1005
+ /**
1006
+ * Normalize a malformed `{ type: 'array', enum: [...] }` schema by moving the
1007
+ * enum values into the items sub-schema. This pattern is technically invalid OAS
1008
+ * but appears in the wild and must be handled gracefully.
1009
+ */
1010
+ function normalizeArrayEnum(schema) {
1011
+ const normalizedItems = {
1012
+ ...typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : {},
1013
+ enum: schema.enum
1014
+ };
1015
+ const { enum: _enum, ...schemaWithoutEnum } = schema;
1016
+ return {
1017
+ ...schemaWithoutEnum,
1018
+ items: normalizedItems
1019
+ };
1020
+ }
1021
+ /**
1022
+ * Builds the internal converter functions for a given `OasParserContext`.
1023
+ *
1024
+ * All `convert*` functions are defined as function declarations so they can freely
1025
+ * reference each other and `parseSchema` via JS hoisting (mutual recursion).
1026
+ */
1027
+ function createSchemaParser(ctx) {
1028
+ const document = ctx.document;
1164
1029
  /**
1165
- * Shared metadata fields included in every `createSchema` call.
1166
- * Centralizes the common properties so sub-handlers don't repeat them.
1030
+ * Tracks `$ref` paths that are currently being resolved to prevent infinite
1031
+ * recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
1167
1032
  */
1168
- function buildSchemaBase(schema, name, nullable, defaultValue) {
1169
- return {
1170
- name,
1171
- nullable,
1172
- title: schema.title,
1173
- description: schema.description,
1174
- deprecated: schema.deprecated,
1175
- readOnly: schema.readOnly,
1176
- writeOnly: schema.writeOnly,
1177
- default: defaultValue,
1178
- example: schema.example
1179
- };
1180
- }
1033
+ const resolvingRefs = /* @__PURE__ */ new Set();
1181
1034
  /**
1182
- * Converts a `$ref` schema pointer into a `RefSchemaNode`.
1035
+ * Converts a `$ref` schema into a `RefSchemaNode`.
1183
1036
  *
1184
- * In OAS 3.0 siblings of `$ref` are technically ignored by the spec, but Kubb intentionally
1185
- * preserves them so that annotations like `pattern`, `description`, and `nullable` are
1186
- * reflected in generated JSDoc and type modifiers.
1037
+ * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1038
+ * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1039
+ * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1040
+ * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1187
1041
  */
1188
- function convertRef({ schema, nullable, defaultValue }) {
1042
+ function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1043
+ let resolvedSchema;
1044
+ const refPath = schema.$ref;
1045
+ if (refPath && !resolvingRefs.has(refPath)) try {
1046
+ const referenced = resolveRef(document, refPath);
1047
+ if (referenced) {
1048
+ resolvingRefs.add(refPath);
1049
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1050
+ resolvingRefs.delete(refPath);
1051
+ }
1052
+ } catch {}
1189
1053
  return (0, _kubb_ast.createSchema)({
1054
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1190
1055
  type: "ref",
1191
- name: extractRefName(schema.$ref),
1056
+ name: (0, _kubb_ast.extractRefName)(schema.$ref),
1192
1057
  ref: schema.$ref,
1193
- nullable,
1194
- description: schema.description,
1195
- deprecated: schema.deprecated,
1196
- readOnly: schema.readOnly,
1197
- writeOnly: schema.writeOnly,
1198
- pattern: schema.type === "string" ? schema.pattern : void 0,
1199
- example: schema.example,
1200
- default: defaultValue
1058
+ schema: resolvedSchema
1201
1059
  });
1202
1060
  }
1203
1061
  /**
1204
- * Converts a `allOf` schema into either a flattened member node (single-member `allOf`)
1205
- * or an `IntersectionSchemaNode` (multi-member `allOf`).
1206
- *
1207
- * Single-member `allOf` without sibling structural keys is the common OAS 3.0 pattern for
1208
- * annotating a `$ref` or primitive with extra constraints; it is flattened to avoid
1209
- * producing needless intersection wrappers.
1210
- *
1211
- * The flatten path is skipped when the outer schema carries structural keys that cannot be
1212
- * merged into annotation fields: `properties`, `required`, or `additionalProperties`.
1213
- * Those cases must become an intersection so the constraints are preserved.
1214
- *
1215
- * Circular references through discriminator parents are detected and skipped to prevent
1216
- * infinite recursion during code generation.
1062
+ * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1217
1063
  */
1218
- function convertAllOf({ schema, name, nullable, defaultValue, options }) {
1064
+ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }) {
1219
1065
  if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1220
1066
  const [memberSchema] = schema.allOf;
1221
- const memberNode = convertSchema({ schema: memberSchema }, options);
1067
+ const memberNode = parseSchema({
1068
+ schema: memberSchema,
1069
+ name: null
1070
+ }, rawOptions);
1222
1071
  const { kind: _kind, ...memberNodeProps } = memberNode;
1223
1072
  const mergedNullable = nullable || memberNode.nullable || void 0;
1224
1073
  const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
@@ -1236,17 +1085,26 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1236
1085
  pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1237
1086
  });
1238
1087
  }
1088
+ const filteredDiscriminantValues = [];
1239
1089
  const allOfMembers = schema.allOf.filter((item) => {
1240
1090
  if (!isReference(item) || !name) return true;
1241
- const deref = oas.get(item.$ref);
1091
+ const deref = resolveRef(document, item.$ref);
1242
1092
  if (!deref || !isDiscriminator(deref)) return true;
1243
1093
  const parentUnion = deref.oneOf ?? deref.anyOf;
1244
1094
  if (!parentUnion) return true;
1245
1095
  const childRef = `#/components/schemas/${name}`;
1246
1096
  const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1247
1097
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1248
- return !inOneOf && !inMapping;
1249
- }).map((s) => convertSchema({ schema: s }, options));
1098
+ if (inOneOf || inMapping) {
1099
+ const discriminatorValue = (0, _kubb_ast.findDiscriminator)(deref.discriminator.mapping, childRef);
1100
+ if (discriminatorValue) filteredDiscriminantValues.push({
1101
+ propertyName: deref.discriminator.propertyName,
1102
+ value: discriminatorValue
1103
+ });
1104
+ return false;
1105
+ }
1106
+ return true;
1107
+ }).map((s) => parseSchema({ schema: s }, rawOptions));
1250
1108
  const syntheticStart = allOfMembers.length;
1251
1109
  if (Array.isArray(schema.required) && schema.required.length) {
1252
1110
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
@@ -1254,78 +1112,96 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1254
1112
  if (missingRequired.length) {
1255
1113
  const resolvedMembers = schema.allOf.flatMap((item) => {
1256
1114
  if (!isReference(item)) return [item];
1257
- const deref = oas.get(item.$ref);
1115
+ const deref = resolveRef(document, item.$ref);
1258
1116
  return deref && !isReference(deref) ? [deref] : [];
1259
1117
  });
1260
1118
  for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1261
- allOfMembers.push(convertSchema({ schema: {
1119
+ allOfMembers.push(parseSchema({ schema: {
1262
1120
  properties: { [key]: resolved.properties[key] },
1263
1121
  required: [key]
1264
- } }, options));
1122
+ } }, rawOptions));
1265
1123
  break;
1266
1124
  }
1267
1125
  }
1268
1126
  }
1269
1127
  if (schema.properties) {
1270
1128
  const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1271
- allOfMembers.push(convertSchema({ schema: schemaWithoutAllOf }, options));
1129
+ allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1272
1130
  }
1131
+ for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push((0, _kubb_ast.createDiscriminantNode)({
1132
+ propertyName,
1133
+ value
1134
+ }));
1273
1135
  return (0, _kubb_ast.createSchema)({
1274
1136
  type: "intersection",
1275
- members: [...allOfMembers.slice(0, syntheticStart), ...mergeAdjacentAnonymousObjects(allOfMembers.slice(syntheticStart))],
1276
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1137
+ members: [...(0, _kubb_ast.mergeAdjacentObjects)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast.mergeAdjacentObjects)(allOfMembers.slice(syntheticStart))],
1138
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1277
1139
  });
1278
1140
  }
1279
1141
  /**
1280
1142
  * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1281
- *
1282
- * Both keywords are treated identically — their members are concatenated into a single union.
1283
- * When sibling `properties` are present alongside `oneOf`/`anyOf`, each union member is
1284
- * individually intersected with the shared properties node to match the OAS pattern of
1285
- * adding common fields next to a discriminated union.
1286
1143
  */
1287
- function convertUnion({ schema, name, nullable, defaultValue, options }) {
1144
+ function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
1145
+ function pickDiscriminatorPropertyNode(node, propertyName) {
1146
+ const discriminatorProperty = (0, _kubb_ast.narrowSchema)(node, "object")?.properties?.find((property) => property.name === propertyName);
1147
+ if (!discriminatorProperty) return null;
1148
+ return (0, _kubb_ast.createSchema)({
1149
+ type: "object",
1150
+ primitive: "object",
1151
+ properties: [discriminatorProperty]
1152
+ });
1153
+ }
1288
1154
  const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1289
1155
  const unionBase = {
1290
- ...buildSchemaBase(schema, name, nullable, defaultValue),
1156
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1291
1157
  discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0
1292
1158
  };
1293
- if (schema.properties) {
1159
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1160
+ const sharedPropertiesNode = schema.properties ? (() => {
1294
1161
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1295
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1296
- const memberBaseSchema = discriminator ? Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== "discriminator")) : schemaWithoutUnion;
1297
- return (0, _kubb_ast.createSchema)({
1298
- type: "union",
1299
- ...unionBase,
1300
- members: unionMembers.map((s) => {
1301
- const ref = isReference(s) ? s.$ref : void 0;
1302
- const discriminatorValue = discriminator?.mapping && ref ? Object.entries(discriminator.mapping).find(([, v]) => v === ref)?.[0] : void 0;
1303
- let propertiesNode = convertSchema({
1304
- schema: memberBaseSchema,
1305
- name
1306
- }, options);
1307
- if (discriminatorValue && discriminator) propertiesNode = applyDiscriminatorEnum({
1308
- node: propertiesNode,
1162
+ return parseSchema({
1163
+ schema: discriminator ? Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== "discriminator")) : schemaWithoutUnion,
1164
+ name
1165
+ }, rawOptions);
1166
+ })() : void 0;
1167
+ if (sharedPropertiesNode || discriminator?.mapping) {
1168
+ const members = unionMembers.map((s) => {
1169
+ const ref = isReference(s) ? s.$ref : void 0;
1170
+ const discriminatorValue = (0, _kubb_ast.findDiscriminator)(discriminator?.mapping, ref);
1171
+ const memberNode = parseSchema({ schema: s }, rawOptions);
1172
+ if (!discriminatorValue || !discriminator) return memberNode;
1173
+ return (0, _kubb_ast.createSchema)({
1174
+ type: "intersection",
1175
+ members: [memberNode, (sharedPropertiesNode ? pickDiscriminatorPropertyNode((0, _kubb_ast.setDiscriminatorEnum)({
1176
+ node: sharedPropertiesNode,
1309
1177
  propertyName: discriminator.propertyName,
1310
1178
  values: [discriminatorValue]
1311
- });
1312
- return (0, _kubb_ast.createSchema)({
1313
- type: "intersection",
1314
- members: [convertSchema({ schema: s }, options), propertiesNode]
1315
- });
1316
- })
1179
+ }), discriminator.propertyName) : void 0) ?? (0, _kubb_ast.createDiscriminantNode)({
1180
+ propertyName: discriminator.propertyName,
1181
+ value: discriminatorValue
1182
+ })]
1183
+ });
1184
+ });
1185
+ const unionNode = (0, _kubb_ast.createSchema)({
1186
+ type: "union",
1187
+ ...unionBase,
1188
+ members
1189
+ });
1190
+ if (!sharedPropertiesNode) return unionNode;
1191
+ return (0, _kubb_ast.createSchema)({
1192
+ type: "intersection",
1193
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1194
+ members: [unionNode, sharedPropertiesNode]
1317
1195
  });
1318
1196
  }
1319
1197
  return (0, _kubb_ast.createSchema)({
1320
1198
  type: "union",
1321
1199
  ...unionBase,
1322
- members: simplifyUnionMembers(unionMembers.map((s) => convertSchema({ schema: s }, options)))
1200
+ members: (0, _kubb_ast.simplifyUnion)(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1323
1201
  });
1324
1202
  }
1325
1203
  /**
1326
- * Converts an OAS 3.1 `const` schema into either a null scalar or a single-value `EnumSchemaNode`.
1327
- * `const: null` maps to a null scalar; any other value becomes a one-item enum so that generators
1328
- * can produce a precise literal type.
1204
+ * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1329
1205
  */
1330
1206
  function convertConst({ schema, name, nullable, defaultValue }) {
1331
1207
  const constValue = schema.const;
@@ -1335,25 +1211,23 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1335
1211
  name,
1336
1212
  title: schema.title,
1337
1213
  description: schema.description,
1338
- deprecated: schema.deprecated,
1339
- nullable
1214
+ deprecated: schema.deprecated
1340
1215
  });
1341
1216
  return (0, _kubb_ast.createSchema)({
1342
1217
  type: "enum",
1343
1218
  primitive: getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string"),
1344
1219
  enumValues: [constValue],
1345
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1220
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1346
1221
  });
1347
1222
  }
1348
1223
  /**
1349
- * Handles `format`-based special types (date/time, uuid, email, blob, etc.).
1350
- * Returns `undefined` when the format should fall through to string handling
1351
- * (i.e. `format: 'date-time'` with `dateType: false`).
1224
+ * Converts a format-annotated schema into a special-type `SchemaNode`.
1225
+ * Returns `null` when the format should fall through to string handling (`dateType: false`).
1352
1226
  */
1353
- function convertFormat({ schema, name, nullable, defaultValue, mergedOptions }) {
1354
- const base = buildSchemaBase(schema, name, nullable, defaultValue);
1227
+ function convertFormat({ schema, name, nullable, defaultValue, options }) {
1228
+ const base = buildSchemaNode(schema, name, nullable, defaultValue);
1355
1229
  if (schema.format === "int64") return (0, _kubb_ast.createSchema)({
1356
- type: mergedOptions.integerType === "bigint" ? "bigint" : "integer",
1230
+ type: options.integerType === "bigint" ? "bigint" : "integer",
1357
1231
  primitive: "integer",
1358
1232
  ...base,
1359
1233
  min: schema.minimum,
@@ -1362,8 +1236,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1362
1236
  exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1363
1237
  });
1364
1238
  if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1365
- const dateType = getDateType(mergedOptions, schema.format);
1366
- if (!dateType) return void 0;
1239
+ const dateType = getDateType(options, schema.format);
1240
+ if (!dateType) return null;
1367
1241
  if (dateType.type === "datetime") return (0, _kubb_ast.createSchema)({
1368
1242
  ...base,
1369
1243
  primitive: "string",
@@ -1378,14 +1252,38 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1378
1252
  representation: dateType.representation
1379
1253
  });
1380
1254
  }
1381
- const specialType = formatToSchemaType(schema.format);
1382
- if (!specialType) return void 0;
1255
+ const specialType = getSchemaType(schema.format);
1256
+ if (!specialType) return null;
1383
1257
  const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1384
1258
  if (specialType === "number" || specialType === "integer" || specialType === "bigint") return (0, _kubb_ast.createSchema)({
1385
1259
  ...base,
1386
1260
  primitive: specialPrimitive,
1387
1261
  type: specialType
1388
1262
  });
1263
+ if (specialType === "url") return (0, _kubb_ast.createSchema)({
1264
+ ...base,
1265
+ primitive: "string",
1266
+ type: "url",
1267
+ min: schema.minLength,
1268
+ max: schema.maxLength
1269
+ });
1270
+ if (specialType === "ipv4") return (0, _kubb_ast.createSchema)({
1271
+ ...base,
1272
+ primitive: "string",
1273
+ type: "ipv4"
1274
+ });
1275
+ if (specialType === "ipv6") return (0, _kubb_ast.createSchema)({
1276
+ ...base,
1277
+ primitive: "string",
1278
+ type: "ipv6"
1279
+ });
1280
+ if (specialType === "uuid" || specialType === "email") return (0, _kubb_ast.createSchema)({
1281
+ ...base,
1282
+ primitive: "string",
1283
+ type: specialType,
1284
+ min: schema.minLength,
1285
+ max: schema.maxLength
1286
+ });
1389
1287
  return (0, _kubb_ast.createSchema)({
1390
1288
  ...base,
1391
1289
  primitive: specialPrimitive,
@@ -1394,36 +1292,20 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1394
1292
  }
1395
1293
  /**
1396
1294
  * Converts an `enum` schema into an `EnumSchemaNode`.
1397
- *
1398
- * Handles several edge cases:
1399
- * - `{ type: 'array', enum }` (technically invalid OAS) — the enum is normalized into `items`.
1400
- * - `null` in enum values (OAS 3.0 nullable enum convention) — stripped and reflected as `nullable`.
1401
- * - `x-enumNames` / `x-enum-varnames` vendor extensions — produce named enum variants with explicit labels.
1402
- * - Numeric and boolean enums require a const-map representation because most generators cannot
1403
- * use string-enum syntax for non-string values.
1404
1295
  */
1405
- function convertEnum({ schema, name, nullable, type, options }) {
1406
- if (type === "array") {
1407
- const normalizedItems = {
1408
- ...typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : {},
1409
- enum: schema.enum
1410
- };
1411
- const { enum: _enum, ...schemaWithoutEnum } = schema;
1412
- return convertSchema({
1413
- schema: {
1414
- ...schemaWithoutEnum,
1415
- items: normalizedItems
1416
- },
1417
- name
1418
- }, options);
1419
- }
1296
+ function convertEnum({ schema, name, nullable, type, rawOptions }) {
1297
+ if (type === "array") return parseSchema({
1298
+ schema: normalizeArrayEnum(schema),
1299
+ name
1300
+ }, rawOptions);
1420
1301
  const nullInEnum = schema.enum.includes(null);
1421
1302
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1422
1303
  const enumNullable = nullable || nullInEnum || void 0;
1423
1304
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1305
+ const enumPrimitive = getPrimitiveType(type);
1424
1306
  const enumBase = {
1425
1307
  type: "enum",
1426
- primitive: getPrimitiveType(type),
1308
+ primitive: enumPrimitive,
1427
1309
  name,
1428
1310
  title: schema.title,
1429
1311
  description: schema.description,
@@ -1435,81 +1317,49 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1435
1317
  example: schema.example
1436
1318
  };
1437
1319
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1438
- if (extensionKey) {
1439
- const rawNames = schema[extensionKey];
1440
- const uniqueNames = [...new Set(rawNames)];
1441
- const enumType = getPrimitiveType(type) === "number" || getPrimitiveType(type) === "integer" ? "number" : getPrimitiveType(type) === "boolean" ? "boolean" : "string";
1320
+ if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1321
+ const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1322
+ const sourceValues = extensionKey ? [...new Set(schema[extensionKey])] : [...new Set(filteredValues)];
1442
1323
  return (0, _kubb_ast.createSchema)({
1443
1324
  ...enumBase,
1444
- enumType,
1445
- namedEnumValues: uniqueNames.map((label, index) => ({
1325
+ primitive: enumPrimitiveType,
1326
+ namedEnumValues: sourceValues.map((label, index) => ({
1446
1327
  name: String(label),
1447
- value: filteredValues[index] ?? label,
1448
- format: enumType
1328
+ value: extensionKey ? filteredValues[index] ?? label : label,
1329
+ primitive: enumPrimitiveType
1449
1330
  }))
1450
1331
  });
1451
1332
  }
1452
- if (type === "number" || type === "integer") return (0, _kubb_ast.createSchema)({
1453
- ...enumBase,
1454
- enumType: "number",
1455
- namedEnumValues: [...new Set(filteredValues)].map((value) => ({
1456
- name: String(value),
1457
- value,
1458
- format: "number"
1459
- }))
1460
- });
1461
- if (type === "boolean") return (0, _kubb_ast.createSchema)({
1462
- ...enumBase,
1463
- enumType: "boolean",
1464
- namedEnumValues: [...new Set(filteredValues)].map((value) => ({
1465
- name: String(value),
1466
- value,
1467
- format: "boolean"
1468
- }))
1469
- });
1470
1333
  return (0, _kubb_ast.createSchema)({
1471
1334
  ...enumBase,
1472
1335
  enumValues: [...new Set(filteredValues)]
1473
1336
  });
1474
1337
  }
1475
1338
  /**
1476
- * Converts an object-like schema (`type: 'object'`, `properties`, `additionalProperties`,
1477
- * or `patternProperties`) into an `ObjectSchemaNode`.
1478
- *
1479
- * When a `discriminator` is present, the discriminator property's schema is replaced with an
1480
- * enum of the mapping keys so generators can produce a precise literal-union type for it.
1481
- *
1482
- * Property optionality follows OAS semantics:
1483
- * - required + not nullable → `required: true`
1484
- * - not required + not nullable → `optional: true`
1485
- * - not required + nullable → `nullish: true`
1339
+ * Converts an object-like schema into an `ObjectSchemaNode`.
1486
1340
  */
1487
- function convertObject({ schema, name, nullable, defaultValue, options, mergedOptions }) {
1341
+ function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }) {
1488
1342
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1489
1343
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1490
1344
  const resolvedPropSchema = propSchema;
1491
1345
  const propNullable = isNullable(resolvedPropSchema);
1492
- const basePropName = name ? pascalCase([name, propName].join(" ")) : void 0;
1493
- const propNode = convertSchema({
1346
+ let schemaNode = (0, _kubb_ast.setEnumName)(parseSchema({
1494
1347
  schema: resolvedPropSchema,
1495
- name: basePropName
1496
- }, options);
1497
- const isEnumNode = !!(0, _kubb_ast.narrowSchema)(propNode, "enum");
1498
- const derivedPropName = isEnumNode && name ? pascalCase([
1499
- name,
1500
- propName,
1501
- mergedOptions.enumSuffix
1502
- ].filter(Boolean).join(" ")) : basePropName;
1348
+ name: (0, _kubb_ast.childName)(name, propName)
1349
+ }, rawOptions), name, propName, options.enumSuffix);
1350
+ const tupleNode = (0, _kubb_ast.narrowSchema)(schemaNode, "tuple");
1351
+ if (tupleNode?.items) {
1352
+ const namedItems = tupleNode.items.map((item) => (0, _kubb_ast.setEnumName)(item, name, propName, options.enumSuffix));
1353
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) schemaNode = {
1354
+ ...tupleNode,
1355
+ items: namedItems
1356
+ };
1357
+ }
1503
1358
  return (0, _kubb_ast.createProperty)({
1504
1359
  name: propName,
1505
1360
  schema: {
1506
- ...isEnumNode && derivedPropName !== basePropName ? {
1507
- ...propNode,
1508
- name: derivedPropName
1509
- } : propNode,
1510
- nullable: propNullable || void 0,
1511
- optional: !required && !propNullable ? true : void 0,
1512
- nullish: !required && propNullable ? true : void 0
1361
+ ...schemaNode,
1362
+ nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1513
1363
  },
1514
1364
  required
1515
1365
  });
@@ -1517,75 +1367,67 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1517
1367
  const additionalProperties = schema.additionalProperties;
1518
1368
  let additionalPropertiesNode;
1519
1369
  if (additionalProperties === true) additionalPropertiesNode = true;
1520
- else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode = convertSchema({ schema: additionalProperties }, options);
1521
- else if (additionalProperties === false) additionalPropertiesNode = void 0;
1522
- else if (additionalProperties) additionalPropertiesNode = (0, _kubb_ast.createSchema)({ type: resolveTypeOption(mergedOptions.unknownType) });
1370
+ else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode = parseSchema({ schema: additionalProperties }, rawOptions);
1371
+ else if (additionalProperties === false) additionalPropertiesNode = false;
1372
+ else if (additionalProperties) additionalPropertiesNode = (0, _kubb_ast.createSchema)({ type: typeOptionMap.get(options.unknownType) });
1523
1373
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1524
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? (0, _kubb_ast.createSchema)({ type: resolveTypeOption(mergedOptions.unknownType) }) : convertSchema({ schema: patternSchema }, options)])) : void 0;
1374
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? (0, _kubb_ast.createSchema)({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1525
1375
  const objectNode = (0, _kubb_ast.createSchema)({
1526
1376
  type: "object",
1527
1377
  primitive: "object",
1528
1378
  properties,
1529
1379
  additionalProperties: additionalPropertiesNode,
1530
1380
  patternProperties,
1531
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1381
+ minProperties: schema.minProperties,
1382
+ maxProperties: schema.maxProperties,
1383
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1532
1384
  });
1533
1385
  if (isDiscriminator(schema) && schema.discriminator.mapping) {
1534
1386
  const discPropName = schema.discriminator.propertyName;
1535
- return applyDiscriminatorEnum({
1387
+ return (0, _kubb_ast.setDiscriminatorEnum)({
1536
1388
  node: objectNode,
1537
1389
  propertyName: discPropName,
1538
1390
  values: Object.keys(schema.discriminator.mapping),
1539
- enumName: name ? pascalCase([
1540
- name,
1541
- discPropName,
1542
- mergedOptions.enumSuffix
1543
- ].filter(Boolean).join(" ")) : void 0
1391
+ enumName: name ? (0, _kubb_ast.enumPropName)(name, discPropName, options.enumSuffix) : void 0
1544
1392
  });
1545
1393
  }
1546
1394
  return objectNode;
1547
1395
  }
1548
1396
  /**
1549
1397
  * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1550
- *
1551
- * Each `prefixItems` element maps to a positional tuple slot. An optional `items` schema
1552
- * after the prefix items is mapped to the rest parameter of the tuple.
1553
1398
  */
1554
- function convertTuple({ schema, name, nullable, defaultValue, options }) {
1399
+ function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1555
1400
  return (0, _kubb_ast.createSchema)({
1556
1401
  type: "tuple",
1557
1402
  primitive: "array",
1558
- items: (schema.prefixItems ?? []).map((item) => convertSchema({ schema: item }, options)),
1559
- rest: schema.items ? convertSchema({ schema: schema.items }, options) : void 0,
1403
+ items: (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions)),
1404
+ rest: schema.items ? parseSchema({ schema: schema.items }, rawOptions) : (0, _kubb_ast.createSchema)({ type: "any" }),
1560
1405
  min: schema.minItems,
1561
1406
  max: schema.maxItems,
1562
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1407
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1563
1408
  });
1564
1409
  }
1565
1410
  /**
1566
1411
  * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1567
- *
1568
- * When the items schema is an inline enum, a name derived from the parent array's name and
1569
- * `enumSuffix` is forwarded so generators can emit a named enum declaration.
1570
1412
  */
1571
- function convertArray({ schema, name, nullable, defaultValue, options, mergedOptions }) {
1413
+ function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1572
1414
  const rawItems = schema.items;
1573
- const itemName = rawItems?.enum?.length && name ? pascalCase([name, mergedOptions.enumSuffix].join(" ")) : void 0;
1415
+ const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast.enumPropName)(void 0, name, options.enumSuffix) : void 0;
1574
1416
  return (0, _kubb_ast.createSchema)({
1575
1417
  type: "array",
1576
1418
  primitive: "array",
1577
- items: rawItems ? [convertSchema({
1419
+ items: rawItems ? [parseSchema({
1578
1420
  schema: rawItems,
1579
1421
  name: itemName
1580
- }, options)] : [],
1422
+ }, rawOptions)] : [],
1581
1423
  min: schema.minItems,
1582
1424
  max: schema.maxItems,
1583
1425
  unique: schema.uniqueItems ?? void 0,
1584
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1426
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1585
1427
  });
1586
1428
  }
1587
1429
  /**
1588
- * Converts a `type: 'string'` schema (without a special format) into a `StringSchemaNode`.
1430
+ * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1589
1431
  */
1590
1432
  function convertString({ schema, name, nullable, defaultValue }) {
1591
1433
  return (0, _kubb_ast.createSchema)({
@@ -1594,11 +1436,11 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1594
1436
  min: schema.minLength,
1595
1437
  max: schema.maxLength,
1596
1438
  pattern: schema.pattern,
1597
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1439
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1598
1440
  });
1599
1441
  }
1600
1442
  /**
1601
- * Converts a `type: 'number'` or `type: 'integer'` schema into the corresponding `SchemaNode`.
1443
+ * Converts a `type: 'number'` or `type: 'integer'` schema.
1602
1444
  */
1603
1445
  function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1604
1446
  return (0, _kubb_ast.createSchema)({
@@ -1608,21 +1450,22 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1608
1450
  max: schema.maximum,
1609
1451
  exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1610
1452
  exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1611
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1453
+ multipleOf: schema.multipleOf,
1454
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1612
1455
  });
1613
1456
  }
1614
1457
  /**
1615
- * Converts a `type: 'boolean'` schema into a `BooleanSchemaNode`.
1458
+ * Converts a `type: 'boolean'` schema.
1616
1459
  */
1617
1460
  function convertBoolean({ schema, name, nullable, defaultValue }) {
1618
1461
  return (0, _kubb_ast.createSchema)({
1619
1462
  type: "boolean",
1620
1463
  primitive: "boolean",
1621
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1464
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1622
1465
  });
1623
1466
  }
1624
1467
  /**
1625
- * Converts an explicit `type: 'null'` or `const: null` schema into a `NullSchemaNode`.
1468
+ * Converts an explicit `type: 'null'` schema.
1626
1469
  */
1627
1470
  function convertNull({ schema, name, nullable }) {
1628
1471
  return (0, _kubb_ast.createSchema)({
@@ -1636,31 +1479,22 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1636
1479
  });
1637
1480
  }
1638
1481
  /**
1639
- * Central dispatcher: converts an OAS `SchemaObject` into a `SchemaNode`.
1482
+ * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1640
1483
  *
1641
- * Dispatch order (first match wins):
1642
- * 1. `$ref` pointer
1643
- * 2. `allOf` composition
1644
- * 3. `oneOf` / `anyOf` union
1645
- * 4. `const` literal (OAS 3.1)
1646
- * 5. `format`-based special type (date/time, uuid, blob, …)
1647
- * 6. OAS 3.1 `contentMediaType: 'application/octet-stream'` blob
1648
- * 7. OAS 3.1 multi-type array → union or fallthrough
1649
- * 8. Constraint-inferred type (minLength/maxLength → string; minimum/maximum → number)
1650
- * 9. `enum` values
1651
- * 10. Object / array / tuple / scalar by `type`
1652
- * 11. Empty schema fallback (`emptySchemaType` option)
1484
+ * Dispatch order (first match wins): `$ref` → `allOf` → `oneOf`/`anyOf` → `const` → `format`
1485
+ * octet-stream blob → multi-type array → constraint-inferred type → `enum` → object/array/tuple/scalar
1486
+ * empty-schema fallback (`emptySchemaType` option).
1653
1487
  */
1654
- function convertSchema({ schema, name }, options) {
1655
- const mergedOptions = {
1656
- ...DEFAULT_OPTIONS,
1657
- ...options
1488
+ function parseSchema({ schema, name }, rawOptions) {
1489
+ const options = {
1490
+ ...DEFAULT_PARSER_OPTIONS,
1491
+ ...rawOptions
1658
1492
  };
1659
1493
  const flattenedSchema = flattenSchema(schema);
1660
- if (flattenedSchema && flattenedSchema !== schema) return convertSchema({
1494
+ if (flattenedSchema && flattenedSchema !== schema) return parseSchema({
1661
1495
  schema: flattenedSchema,
1662
1496
  name
1663
- }, options);
1497
+ }, rawOptions);
1664
1498
  const nullable = isNullable(schema) || void 0;
1665
1499
  const defaultValue = schema.default === null && nullable ? void 0 : schema.default;
1666
1500
  const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
@@ -1670,8 +1504,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1670
1504
  nullable,
1671
1505
  defaultValue,
1672
1506
  type,
1673
- options,
1674
- mergedOptions
1507
+ rawOptions,
1508
+ options
1675
1509
  };
1676
1510
  if (isReference(schema)) return convertRef(ctx);
1677
1511
  if (schema.allOf?.length) return convertAllOf(ctx);
@@ -1684,21 +1518,21 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1684
1518
  if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return (0, _kubb_ast.createSchema)({
1685
1519
  type: "blob",
1686
1520
  primitive: "string",
1687
- ...buildSchemaBase(schema, name, nullable, defaultValue)
1521
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1688
1522
  });
1689
1523
  if (Array.isArray(schema.type) && schema.type.length > 1) {
1690
1524
  const nonNullTypes = schema.type.filter((t) => t !== "null");
1691
1525
  const arrayNullable = schema.type.includes("null") || nullable || void 0;
1692
1526
  if (nonNullTypes.length > 1) return (0, _kubb_ast.createSchema)({
1693
1527
  type: "union",
1694
- members: nonNullTypes.map((t) => convertSchema({
1528
+ members: nonNullTypes.map((t) => parseSchema({
1695
1529
  schema: {
1696
1530
  ...schema,
1697
1531
  type: t
1698
1532
  },
1699
1533
  name
1700
- }, options)),
1701
- ...buildSchemaBase(schema, name, arrayNullable, defaultValue)
1534
+ }, rawOptions)),
1535
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1702
1536
  });
1703
1537
  }
1704
1538
  if (!type) {
@@ -1715,56 +1549,71 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1715
1549
  if (type === "boolean") return convertBoolean(ctx);
1716
1550
  if (type === "null") return convertNull(ctx);
1717
1551
  return (0, _kubb_ast.createSchema)({
1718
- type: resolveTypeOption(mergedOptions.emptySchemaType),
1552
+ type: typeOptionMap.get(options.emptySchemaType),
1719
1553
  name,
1720
1554
  title: schema.title,
1721
1555
  description: schema.description
1722
1556
  });
1723
1557
  }
1724
1558
  /**
1725
- * Converts a single dereferenced OAS parameter object into a `ParameterNode`.
1726
- * When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
1559
+ * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1727
1560
  */
1728
1561
  function parseParameter(options, param) {
1729
1562
  const required = param["required"] ?? false;
1730
- const schema = param["schema"] && !isReference(param["schema"]) ? convertSchema({ schema: param["schema"] }, options) : (0, _kubb_ast.createSchema)({ type: resolveTypeOption(options.unknownType) });
1563
+ const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : (0, _kubb_ast.createSchema)({ type: typeOptionMap.get(options.unknownType) });
1731
1564
  return (0, _kubb_ast.createParameter)({
1732
1565
  name: param["name"],
1733
1566
  in: param["in"],
1734
1567
  schema: {
1735
1568
  ...schema,
1736
- optional: !required || !!schema.optional ? true : void 0
1569
+ description: param["description"] ?? schema.description
1737
1570
  },
1738
1571
  required
1739
1572
  });
1740
1573
  }
1741
1574
  /**
1742
- * Converts an OAS `Operation` into an `OperationNode`, resolving parameters,
1743
- * request body, and all response codes into their AST node equivalents.
1575
+ * Converts an OAS `Operation` into an `OperationNode`.
1744
1576
  */
1745
- function parseOperation(options, oas, operation) {
1746
- const parameters = operation.getParameters().map((param) => {
1747
- return parseParameter(options, oas.dereferenceWithRef(param));
1748
- });
1749
- const requestBodySchema = oas.getRequestSchema(operation);
1750
- const requestBody = requestBodySchema ? convertSchema({ schema: requestBodySchema }, options) : void 0;
1577
+ function parseOperation(options, operation) {
1578
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
1579
+ const requestBodySchema = getRequestSchema(document, operation, { contentType: ctx.contentType });
1580
+ const requestBodySchemaNode = requestBodySchema ? parseSchema({ schema: requestBodySchema }, options) : void 0;
1581
+ const requestBodyDescription = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.description : void 0;
1582
+ const requestBodyKeysToOmit = requestBodySchema?.properties ? Object.entries(requestBodySchema.properties).filter(([, prop]) => !isReference(prop) && prop.readOnly).map(([key]) => key) : void 0;
1583
+ const requestBodyRequired = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.required === true : false;
1584
+ const requestBodyContentType = (() => {
1585
+ if (!operation.schema.requestBody || isReference(operation.schema.requestBody)) return;
1586
+ const content = operation.schema.requestBody.content;
1587
+ return content ? Object.keys(content)[0] : void 0;
1588
+ })();
1589
+ const requestBody = requestBodySchemaNode ? {
1590
+ description: requestBodyDescription,
1591
+ schema: (0, _kubb_ast.syncOptionality)(requestBodySchemaNode, requestBodyRequired),
1592
+ keysToOmit: requestBodyKeysToOmit?.length ? requestBodyKeysToOmit : void 0,
1593
+ required: requestBodyRequired || void 0,
1594
+ contentType: requestBodyContentType
1595
+ } : void 0;
1751
1596
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1752
1597
  const responseObj = operation.getResponseByStatusCode(statusCode);
1753
- const responseSchema = oas.getResponseSchema(operation, statusCode);
1754
- const schema = responseSchema && Object.keys(responseSchema).length > 0 ? convertSchema({ schema: responseSchema }, options) : void 0;
1598
+ const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
1599
+ const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : (0, _kubb_ast.createSchema)({ type: typeOptionMap.get(options.emptySchemaType) });
1755
1600
  const description = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : void 0;
1756
1601
  const rawContent = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.content : void 0;
1602
+ const mediaType = rawContent ? getMediaType(Object.keys(rawContent)[0] ?? "") : getMediaType(operation.contentType ?? "");
1603
+ const keysToOmit = responseSchema?.properties ? Object.entries(responseSchema.properties).filter(([, prop]) => !isReference(prop) && prop.writeOnly).map(([key]) => key) : void 0;
1757
1604
  return (0, _kubb_ast.createResponse)({
1758
1605
  statusCode,
1759
1606
  description,
1760
1607
  schema,
1761
- mediaType: rawContent ? toMediaType(Object.keys(rawContent)[0] ?? "") : toMediaType(operation.contentType ?? "")
1608
+ mediaType,
1609
+ keysToOmit: keysToOmit?.length ? keysToOmit : void 0
1762
1610
  });
1763
1611
  });
1612
+ const urlPath = new URLPath(operation.path);
1764
1613
  return (0, _kubb_ast.createOperation)({
1765
1614
  operationId: operation.getOperationId(),
1766
1615
  method: operation.method.toUpperCase(),
1767
- path: new URLPath(operation.path).URL,
1616
+ path: urlPath.path,
1768
1617
  tags: operation.getTags().map((tag) => tag.name),
1769
1618
  summary: operation.getSummary() || void 0,
1770
1619
  description: operation.getDescription() || void 0,
@@ -1774,157 +1623,145 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
1774
1623
  responses
1775
1624
  });
1776
1625
  }
1777
- /**
1778
- * Converts an OpenAPI/Swagger spec (wrapped in a Kubb `Oas` instance) into
1779
- * a `RootNode` — the top-level node of the `@kubb/ast` tree.
1780
- */
1781
- function parse(options) {
1782
- const mergedOptions = {
1783
- ...DEFAULT_OPTIONS,
1784
- ...options
1785
- };
1786
- const schemas = Object.entries(schemaObjects).map(([name, schemaObject]) => convertSchema({
1787
- schema: schemaObject,
1788
- name
1789
- }, mergedOptions));
1790
- const paths = oas.getPaths();
1791
- return (0, _kubb_ast.createRoot)({
1792
- schemas,
1793
- operations: Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? parseOperation(mergedOptions, oas, operation) : null).filter((op) => op !== null))
1794
- });
1795
- }
1796
- /**
1797
- * Walks a `SchemaNode` tree and resolves all `ref` node names through the provided callbacks.
1798
- *
1799
- * `resolveName` handles all schema types; `resolveEnumName` (when provided) takes precedence
1800
- * for `enum` nodes, enabling a separate naming strategy for enums (e.g. different suffix).
1801
- *
1802
- * Collision-resolved names (from `nameMapping`) are applied before user-supplied resolvers.
1803
- */
1804
- function resolveRefs(node, resolveName, resolveEnumName) {
1805
- return (0, _kubb_ast.transform)(node, { schema(schemaNode) {
1806
- const schemaRef = (0, _kubb_ast.narrowSchema)(schemaNode, _kubb_ast.schemaTypes.ref);
1807
- if (schemaRef && (schemaRef.ref || schemaRef.name)) {
1808
- const rawRef = schemaRef.ref ?? schemaRef.name;
1809
- const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
1810
- if (resolved) return {
1811
- ...schemaNode,
1812
- name: resolved
1813
- };
1814
- }
1815
- if (schemaNode.type === "enum" && schemaNode.name) {
1816
- const resolved = (resolveEnumName ?? resolveName)(schemaNode.name);
1817
- if (resolved) return {
1818
- ...schemaNode,
1819
- name: resolved
1820
- };
1821
- }
1822
- } });
1823
- }
1824
1626
  return {
1825
- parse,
1826
- convertSchema,
1827
- resolveRefs,
1627
+ parseSchema,
1628
+ parseOperation,
1629
+ parseParameter
1630
+ };
1631
+ }
1632
+ /**
1633
+ * Converts the entire OpenAPI spec into a `RootNode` (the top-level `@kubb/ast` tree).
1634
+ *
1635
+ * This is the main entry point: `OpenAPI / Swagger → Kubb AST`.
1636
+ * No code is generated here — the resulting tree is spec-agnostic and consumed by
1637
+ * downstream plugins (`plugin-ts`, `plugin-zod`, …).
1638
+ *
1639
+ * @example
1640
+ * ```ts
1641
+ * const document = await parseFromConfig(config)
1642
+ * const root = parseOas(document, { dateType: 'date', contentType: 'application/json' })
1643
+ * ```
1644
+ */
1645
+ function parseOas(document, options = {}) {
1646
+ const { contentType, ...parserOptions } = options;
1647
+ const mergedOptions = {
1648
+ ...DEFAULT_PARSER_OPTIONS,
1649
+ ...parserOptions
1650
+ };
1651
+ const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
1652
+ const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
1653
+ document,
1654
+ contentType
1655
+ });
1656
+ const schemas = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({
1657
+ schema,
1658
+ name
1659
+ }, mergedOptions));
1660
+ const paths = new oas.default(document).getPaths();
1661
+ return {
1662
+ root: (0, _kubb_ast.createRoot)({
1663
+ schemas,
1664
+ operations: Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null))
1665
+ }),
1828
1666
  nameMapping
1829
1667
  };
1830
1668
  }
1831
1669
  //#endregion
1832
1670
  //#region src/adapter.ts
1671
+ /**
1672
+ * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
1673
+ */
1833
1674
  const adapterOasName = "oas";
1834
1675
  /**
1835
- * Creates an OpenAPI / Swagger adapter for Kubb.
1676
+ * Creates the default OpenAPI / Swagger adapter for Kubb.
1836
1677
  *
1837
- * This is the default adapter you can omit it from your config when using
1838
- * an OpenAPI spec, but supplying it explicitly lets you pass options.
1678
+ * Parses the spec, optionally validates it, resolves the base URL, and converts
1679
+ * everything into a `RootNode` that downstream plugins consume.
1839
1680
  *
1840
1681
  * @example
1841
1682
  * ```ts
1842
1683
  * import { defineConfig } from '@kubb/core'
1843
1684
  * import { adapterOas } from '@kubb/adapter-oas'
1685
+ * import { pluginTs } from '@kubb/plugin-ts'
1844
1686
  *
1845
1687
  * export default defineConfig({
1846
- * adapter: adapterOas({ validate: true, dateType: 'date' }),
1847
- * input: { path: './openapi.yaml' },
1848
- * plugins: [pluginTs(), pluginZod()],
1688
+ * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1689
+ * input: { path: './openapi.yaml' },
1690
+ * plugins: [pluginTs()],
1849
1691
  * })
1850
1692
  * ```
1851
1693
  */
1852
- const adapterOas = (0, _kubb_core.defineAdapter)((options) => {
1853
- const { validate = true, oasClass, contentType, serverIndex, serverVariables, discriminator = "strict", collisionDetection = false, dateType = "string", integerType = "number", unknownType = "any", emptySchemaType = unknownType } = options;
1854
- const nameMapping = /* @__PURE__ */ new Map();
1694
+ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1695
+ const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", 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;
1696
+ let nameMapping = /* @__PURE__ */ new Map();
1697
+ let parsedDocument;
1698
+ let rootNode;
1855
1699
  return {
1856
1700
  name: "oas",
1857
- options: {
1858
- validate,
1859
- oasClass,
1860
- contentType,
1861
- serverIndex,
1862
- serverVariables,
1863
- discriminator,
1864
- collisionDetection,
1865
- dateType,
1866
- integerType,
1867
- unknownType,
1868
- emptySchemaType,
1869
- nameMapping
1701
+ get options() {
1702
+ return {
1703
+ validate,
1704
+ contentType,
1705
+ serverIndex,
1706
+ serverVariables,
1707
+ discriminator,
1708
+ dateType,
1709
+ integerType,
1710
+ unknownType,
1711
+ emptySchemaType,
1712
+ enumSuffix,
1713
+ nameMapping
1714
+ };
1715
+ },
1716
+ get document() {
1717
+ return parsedDocument;
1718
+ },
1719
+ get rootNode() {
1720
+ return rootNode;
1870
1721
  },
1871
1722
  getImports(node, resolve) {
1872
- return getImports({
1723
+ return (0, _kubb_ast.collectImports)({
1873
1724
  node,
1874
1725
  nameMapping,
1875
- resolve
1726
+ resolve: (schemaName) => {
1727
+ const result = resolve(schemaName);
1728
+ if (!result) return;
1729
+ return {
1730
+ name: [result.name],
1731
+ path: result.path
1732
+ };
1733
+ }
1876
1734
  });
1877
1735
  },
1878
1736
  async parse(source) {
1879
- const oas = await parseFromConfig(sourceToFakeConfig(source), oasClass);
1880
- oas.setOptions({
1881
- contentType,
1882
- discriminator,
1883
- collisionDetection
1884
- });
1885
- if (validate) try {
1886
- await oas.validate();
1887
- } catch (_err) {}
1888
- const server = serverIndex !== void 0 ? oas.api.servers?.at(serverIndex) : void 0;
1737
+ const document = await parseFromConfig(source);
1738
+ if (validate) await validateDocument(document);
1739
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
1889
1740
  const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1890
- const parser = createOasParser(oas, {
1741
+ const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
1891
1742
  contentType,
1892
- collisionDetection
1743
+ dateType,
1744
+ integerType,
1745
+ unknownType,
1746
+ emptySchemaType,
1747
+ enumSuffix
1893
1748
  });
1894
- nameMapping.clear();
1895
- for (const [key, value] of parser.nameMapping) nameMapping.set(key, value);
1896
- return (0, _kubb_ast.createRoot)({
1897
- ...parser.parse({
1898
- dateType,
1899
- integerType,
1900
- unknownType,
1901
- emptySchemaType
1902
- }),
1749
+ const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
1750
+ nameMapping = parsedNameMapping;
1751
+ parsedDocument = document;
1752
+ rootNode = (0, _kubb_ast.createRoot)({
1753
+ ...node,
1903
1754
  meta: {
1904
- title: oas.api.info?.title,
1905
- version: oas.api.info?.version,
1755
+ title: document.info?.title,
1756
+ description: document.info?.description,
1757
+ version: document.info?.version,
1906
1758
  baseURL
1907
1759
  }
1908
1760
  });
1761
+ return rootNode;
1909
1762
  }
1910
1763
  };
1911
1764
  });
1912
- function sourceToFakeConfig(source) {
1913
- switch (source.type) {
1914
- case "path": return {
1915
- root: node_path.default.dirname(source.path),
1916
- input: { path: source.path }
1917
- };
1918
- case "data": return {
1919
- root: process.cwd(),
1920
- input: { data: source.data }
1921
- };
1922
- case "paths": return {
1923
- root: source.paths[0] ? node_path.default.dirname(source.paths[0]) : process.cwd(),
1924
- input: source.paths.map((p) => ({ path: p }))
1925
- };
1926
- }
1927
- }
1928
1765
  //#endregion
1929
1766
  exports.adapterOas = adapterOas;
1930
1767
  exports.adapterOasName = adapterOasName;