@kubb/adapter-oas 5.0.0-alpha.17 → 5.0.0-alpha.18

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