@kubb/adapter-oas 5.0.0-beta.9 → 5.0.0-beta.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,29 +21,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
+ let _kubb_ast = require("@kubb/ast");
24
25
  let _kubb_core = require("@kubb/core");
25
26
  let node_path = require("node:path");
26
27
  node_path = __toESM(node_path, 1);
27
- let _redocly_openapi_core = require("@redocly/openapi-core");
28
- let oas_normalize = require("oas-normalize");
29
- oas_normalize = __toESM(oas_normalize, 1);
30
- let swagger2openapi = require("swagger2openapi");
31
- swagger2openapi = __toESM(swagger2openapi, 1);
32
- let oas = require("oas");
33
- oas = __toESM(oas, 1);
34
- let oas_types = require("oas/types");
35
- let oas_utils = require("oas/utils");
28
+ let node_fs_promises = require("node:fs/promises");
29
+ let _readme_openapi_parser = require("@readme/openapi-parser");
30
+ let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
31
+ let yaml = require("yaml");
32
+ let api_ref_bundler = require("api-ref-bundler");
36
33
  //#region src/constants.ts
37
34
  /**
38
35
  * Default parser options applied when no explicit options are provided.
39
- *
40
- * @example
41
- * ```ts
42
- * import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
43
- *
44
- * const parser = createOasParser(oas)
45
- * const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
46
- * ```
47
36
  */
48
37
  const DEFAULT_PARSER_OPTIONS = {
49
38
  dateType: "string",
@@ -64,32 +53,26 @@ const DEFAULT_PARSER_OPTIONS = {
64
53
  */
65
54
  const SCHEMA_REF_PREFIX = "#/components/schemas/";
66
55
  /**
67
- * OpenAPI version string written into the stub document created during multi-spec merges.
68
- */
69
- const MERGE_OPENAPI_VERSION = "3.0.0";
70
- /**
71
- * Fallback `info.title` placed in the stub document when merging multiple API files.
72
- */
73
- const MERGE_DEFAULT_TITLE = "Merged API";
74
- /**
75
- * Fallback `info.version` placed in the stub document when merging multiple API files.
56
+ * HTTP methods that count as operations on an OpenAPI path item. Other keys
57
+ * (`parameters`, `summary`, `$ref`, vendor extensions) are skipped when iterating operations.
76
58
  */
77
- const MERGE_DEFAULT_VERSION = "1.0.0";
59
+ const SUPPORTED_METHODS = /* @__PURE__ */ new Set([
60
+ "get",
61
+ "put",
62
+ "post",
63
+ "delete",
64
+ "options",
65
+ "head",
66
+ "patch",
67
+ "trace"
68
+ ]);
78
69
  /**
79
70
  * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
80
71
  *
81
72
  * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
82
73
  * intersection member rather than being merged into the parent.
83
- *
84
- * @example
85
- * ```ts
86
- * import { structuralKeys } from '@kubb/adapter-oas'
87
- *
88
- * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
89
- * // true when fragment has e.g. 'properties' or 'oneOf'
90
- * ```
91
74
  */
92
- const structuralKeys = new Set([
75
+ const structuralKeys = /* @__PURE__ */ new Set([
93
76
  "properties",
94
77
  "items",
95
78
  "additionalProperties",
@@ -99,21 +82,24 @@ const structuralKeys = new Set([
99
82
  "not"
100
83
  ]);
101
84
  /**
85
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
86
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
87
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
88
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
89
+ */
90
+ const specialCasedFormats = /* @__PURE__ */ new Set([
91
+ "int64",
92
+ "date-time",
93
+ "date",
94
+ "time"
95
+ ]);
96
+ /**
102
97
  * Static map from OAS `format` strings to Kubb `SchemaType` values.
103
98
  *
104
99
  * Only formats whose AST type differs from the OAS `type` field appear here.
105
- * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
106
- * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
107
- * `idn-hostname` map to `'url'` as the closest generic string-format type.
108
- *
109
- * @example
110
- * ```ts
111
- * import { formatMap } from '@kubb/adapter-oas'
112
- *
113
- * formatMap['uuid'] // 'uuid'
114
- * formatMap['binary'] // 'blob'
115
- * formatMap['float'] // 'number'
116
- * ```
100
+ * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
101
+ * separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
102
+ * and `idn-hostname` map to `'url'` as the closest generic string-format type.
117
103
  */
118
104
  const formatMap = {
119
105
  uuid: "uuid",
@@ -134,49 +120,29 @@ const formatMap = {
134
120
  };
135
121
  /**
136
122
  * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
137
- *
138
- * @example
139
- * ```ts
140
- * import { enumExtensionKeys } from '@kubb/adapter-oas'
141
- *
142
- * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
143
- * ```
144
123
  */
145
124
  const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
146
125
  /**
147
- * Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
148
- * Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
126
+ * Vendor extension keys that attach human-readable descriptions to enum values, checked in priority order.
149
127
  */
150
- const typeOptionMap = new Map([
151
- ["any", _kubb_core.ast.schemaTypes.any],
152
- ["unknown", _kubb_core.ast.schemaTypes.unknown],
153
- ["void", _kubb_core.ast.schemaTypes.void]
154
- ]);
128
+ const enumDescriptionKeys = ["x-enumDescriptions", "x-enum-descriptions"];
155
129
  //#endregion
156
130
  //#region src/discriminator.ts
157
131
  /**
158
- * Injects discriminator enum values into child schemas so they know which value identifies them.
132
+ * Maps each child schema name to its discriminator patch data by scanning the given
133
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
159
134
  *
160
- * Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
161
- * enum value each union member is mapped to, then adds (or replaces) that property on the matching
162
- * child object schema.
163
- *
164
- * Returns a new `InputNode` — the original is never mutated.
165
- *
166
- * @example
167
- * ```ts
168
- * const { root } = parseOas(document, options)
169
- * const next = applyDiscriminatorInheritance(root)
170
- * ```
135
+ * Called on a small pre-parsed subset of schemas (only the discriminator parents)
136
+ * rather than on all schemas at once.
171
137
  */
172
- function applyDiscriminatorInheritance(root) {
138
+ function buildDiscriminatorChildMap(schemas) {
173
139
  const childMap = /* @__PURE__ */ new Map();
174
- for (const schema of root.schemas) {
175
- let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
140
+ for (const schema of schemas) {
141
+ let unionNode = _kubb_ast.ast.narrowSchema(schema, "union");
176
142
  if (!unionNode) {
177
- const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
143
+ const intersectionMembers = _kubb_ast.ast.narrowSchema(schema, "intersection")?.members;
178
144
  if (intersectionMembers) for (const m of intersectionMembers) {
179
- const u = _kubb_core.ast.narrowSchema(m, "union");
145
+ const u = _kubb_ast.ast.narrowSchema(m, "union");
180
146
  if (u) {
181
147
  unionNode = u;
182
148
  break;
@@ -186,416 +152,337 @@ function applyDiscriminatorInheritance(root) {
186
152
  if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
187
153
  const { discriminatorPropertyName, members } = unionNode;
188
154
  for (const member of members) {
189
- const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
155
+ const intersectionNode = _kubb_ast.ast.narrowSchema(member, "intersection");
190
156
  if (!intersectionNode?.members) continue;
191
- let refNode;
192
- let objNode;
157
+ let refNode = null;
158
+ let objNode = null;
193
159
  for (const m of intersectionNode.members) {
194
- refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
195
- objNode ??= _kubb_core.ast.narrowSchema(m, "object");
160
+ refNode ??= _kubb_ast.ast.narrowSchema(m, "ref");
161
+ objNode ??= _kubb_ast.ast.narrowSchema(m, "object");
196
162
  }
197
163
  if (!refNode?.name || !objNode) continue;
198
164
  const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
199
- const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : void 0;
165
+ const enumNode = prop ? _kubb_ast.ast.narrowSchema(prop.schema, "enum") : null;
200
166
  if (!enumNode?.enumValues?.length) continue;
201
167
  const enumValues = enumNode.enumValues.filter((v) => v !== null);
202
168
  if (!enumValues.length) continue;
203
169
  const existing = childMap.get(refNode.name);
204
- if (existing) existing.enumValues.push(...enumValues);
205
- else childMap.set(refNode.name, {
206
- propertyName: discriminatorPropertyName,
207
- enumValues: [...enumValues]
208
- });
170
+ if (!existing) {
171
+ childMap.set(refNode.name, {
172
+ propertyName: discriminatorPropertyName,
173
+ enumValues: [...enumValues]
174
+ });
175
+ continue;
176
+ }
177
+ existing.enumValues.push(...enumValues);
209
178
  }
210
179
  }
211
- if (childMap.size === 0) return root;
212
- return _kubb_core.ast.transform(root, { schema(node, { parent }) {
213
- if (parent?.kind !== "Input" || !node.name) return;
214
- const entry = childMap.get(node.name);
215
- if (!entry) return;
216
- const objectNode = _kubb_core.ast.narrowSchema(node, "object");
217
- if (!objectNode) return;
218
- const { propertyName, enumValues } = entry;
219
- const enumSchema = _kubb_core.ast.createSchema({
220
- type: "enum",
221
- enumValues
222
- });
223
- const newProp = _kubb_core.ast.createProperty({
224
- name: propertyName,
225
- required: true,
226
- schema: enumSchema
227
- });
228
- const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
229
- const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
230
- return {
231
- ...objectNode,
232
- properties: newProperties
233
- };
234
- } });
180
+ return childMap;
235
181
  }
236
- //#endregion
237
- //#region ../../internals/utils/src/casing.ts
238
182
  /**
239
- * Shared implementation for camelCase and PascalCase conversion.
240
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
241
- * and capitalizes each word according to `pascal`.
242
- *
243
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
183
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
184
+ * the discriminant property).
244
185
  */
245
- function toCamelOrPascal(text, pascal) {
246
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
247
- if (word.length > 1 && word === word.toUpperCase()) return word;
248
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
249
- return word.charAt(0).toUpperCase() + word.slice(1);
250
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
251
- }
252
- /**
253
- * Splits `text` on `.` and applies `transformPart` to each segment.
254
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
255
- * Segments are joined with `/` to form a file path.
256
- *
257
- * Only splits on dots followed by a letter so that version numbers
258
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
259
- *
260
- * Empty segments are filtered before joining. They arise when the text starts with
261
- * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
262
- * and `'..'` transforms to an empty string). Without this filter the join would produce
263
- * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
264
- * generated files to escape the configured output directory.
265
- */
266
- function applyToFileParts(text, transformPart) {
267
- const parts = text.split(/\.(?=[a-zA-Z])/);
268
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
269
- }
270
- /**
271
- * Converts `text` to camelCase.
272
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
273
- *
274
- * @example
275
- * camelCase('hello-world') // 'helloWorld'
276
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
277
- */
278
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
279
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
280
- prefix,
281
- suffix
282
- } : {}));
283
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
284
- }
285
- /**
286
- * Converts `text` to PascalCase.
287
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
288
- *
289
- * @example
290
- * pascalCase('hello-world') // 'HelloWorld'
291
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
292
- */
293
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
294
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
295
- prefix,
296
- suffix
297
- }) : camelCase(part));
298
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
186
+ function patchDiscriminatorNode(node, entry) {
187
+ const objectNode = _kubb_ast.ast.narrowSchema(node, "object");
188
+ if (!objectNode) return node;
189
+ const { propertyName, enumValues } = entry;
190
+ const enumSchema = _kubb_ast.ast.factory.createSchema({
191
+ type: "enum",
192
+ enumValues
193
+ });
194
+ const newProp = _kubb_ast.ast.factory.createProperty({
195
+ name: propertyName,
196
+ required: true,
197
+ schema: enumSchema
198
+ });
199
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
200
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
201
+ return {
202
+ ...objectNode,
203
+ properties: newProperties
204
+ };
299
205
  }
300
- //#endregion
301
- //#region ../../internals/utils/src/object.ts
302
206
  /**
303
- * Returns `true` when `value` is a plain (non-null, non-array) object.
207
+ * Creates a single-property object schema used as a discriminator literal.
304
208
  *
305
209
  * @example
306
210
  * ```ts
307
- * isPlainObject({}) // true
308
- * isPlainObject([]) // false
309
- * isPlainObject(null) // false
211
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
212
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
310
213
  * ```
311
214
  */
312
- function isPlainObject(value) {
313
- return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
215
+ function createDiscriminantNode({ propertyName, value }) {
216
+ return _kubb_ast.ast.factory.createSchema({
217
+ type: "object",
218
+ primitive: "object",
219
+ properties: [_kubb_ast.ast.factory.createProperty({
220
+ name: propertyName,
221
+ schema: _kubb_ast.ast.factory.createSchema({
222
+ type: "enum",
223
+ primitive: "string",
224
+ enumValues: [value]
225
+ }),
226
+ required: true
227
+ })]
228
+ });
314
229
  }
315
230
  /**
316
- * Recursively merges `source` into `target`, combining nested plain objects.
317
- * Arrays and non-object values from `source` override the corresponding values in `target`.
231
+ * Returns the discriminator key whose mapping value matches `ref`, or `null` when there is no match.
318
232
  *
319
233
  * @example
320
234
  * ```ts
321
- * mergeDeep({ a: { x: 1 } }, { a: { y: 2 } })
322
- * // { a: { x: 1, y: 2 } }
235
+ * findDiscriminator({ dog: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // 'dog'
323
236
  * ```
324
237
  */
325
- function mergeDeep(target, source) {
326
- const result = { ...target };
327
- for (const key of Object.keys(source)) {
328
- const sv = source[key];
329
- const tv = result[key];
330
- result[key] = sv !== null && typeof sv === "object" && !Array.isArray(sv) && tv !== null && typeof tv === "object" && !Array.isArray(tv) ? mergeDeep(tv, sv) : sv;
331
- }
332
- return result;
238
+ function findDiscriminator(mapping, ref) {
239
+ if (!mapping || !ref) return null;
240
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
333
241
  }
334
242
  //#endregion
335
- //#region ../../internals/utils/src/reserved.ts
336
- /**
337
- * JavaScript and Java reserved words.
338
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
339
- */
340
- const reservedWords = new Set([
341
- "abstract",
342
- "arguments",
343
- "boolean",
344
- "break",
345
- "byte",
346
- "case",
347
- "catch",
348
- "char",
349
- "class",
350
- "const",
351
- "continue",
352
- "debugger",
353
- "default",
354
- "delete",
355
- "do",
356
- "double",
357
- "else",
358
- "enum",
359
- "eval",
360
- "export",
361
- "extends",
362
- "false",
363
- "final",
364
- "finally",
365
- "float",
366
- "for",
367
- "function",
368
- "goto",
369
- "if",
370
- "implements",
371
- "import",
372
- "in",
373
- "instanceof",
374
- "int",
375
- "interface",
376
- "let",
377
- "long",
378
- "native",
379
- "new",
380
- "null",
381
- "package",
382
- "private",
383
- "protected",
384
- "public",
385
- "return",
386
- "short",
387
- "static",
388
- "super",
389
- "switch",
390
- "synchronized",
391
- "this",
392
- "throw",
393
- "throws",
394
- "transient",
395
- "true",
396
- "try",
397
- "typeof",
398
- "var",
399
- "void",
400
- "volatile",
401
- "while",
402
- "with",
403
- "yield",
404
- "Array",
405
- "Date",
406
- "hasOwnProperty",
407
- "Infinity",
408
- "isFinite",
409
- "isNaN",
410
- "isPrototypeOf",
411
- "length",
412
- "Math",
413
- "name",
414
- "NaN",
415
- "Number",
416
- "Object",
417
- "prototype",
418
- "String",
419
- "toString",
420
- "undefined",
421
- "valueOf"
422
- ]);
243
+ //#region ../../internals/utils/src/casing.ts
423
244
  /**
424
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
245
+ * Shared implementation for camelCase and PascalCase conversion.
246
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
247
+ * and capitalizes each word according to `pascal`.
425
248
  *
426
- * @example
427
- * ```ts
428
- * isValidVarName('status') // true
429
- * isValidVarName('class') // false (reserved word)
430
- * isValidVarName('42foo') // false (starts with digit)
431
- * ```
249
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
250
+ */
251
+ function toCamelOrPascal(text, pascal) {
252
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
253
+ if (word.length > 1 && word === word.toUpperCase()) return word;
254
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
255
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
256
+ }
257
+ /**
258
+ * Converts `text` to PascalCase.
259
+ *
260
+ * @example Word boundaries
261
+ * `pascalCase('hello-world') // 'HelloWorld'`
262
+ *
263
+ * @example With a suffix
264
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
432
265
  */
433
- function isValidVarName(name) {
434
- if (!name || reservedWords.has(name)) return false;
435
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
266
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
267
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
436
268
  }
437
269
  //#endregion
438
- //#region ../../internals/utils/src/urlPath.ts
270
+ //#region ../../internals/utils/src/runtime.ts
439
271
  /**
440
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
272
+ * Detects the JavaScript runtime executing the current process and exposes its name and version.
441
273
  *
442
- * @example
443
- * const p = new URLPath('/pet/{petId}')
444
- * p.URL // '/pet/:petId'
445
- * p.template // '`/pet/${petId}`'
274
+ * Prefer the shared {@link runtime} instance over constructing your own.
446
275
  */
447
- var URLPath = class {
276
+ var Runtime = class {
448
277
  /**
449
- * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
450
- */
451
- path;
452
- #options;
453
- constructor(path, options = {}) {
454
- this.path = path;
455
- this.#options = options;
456
- }
457
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
278
+ * `true` when the current process is running under Bun.
458
279
  *
459
- * @example
460
- * ```ts
461
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
462
- * ```
463
- */
464
- get URL() {
465
- return this.toURLPath();
466
- }
467
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
280
+ * Detection keys off the global `Bun` object rather than `process.versions`,
281
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
282
+ * otherwise look like Node.
468
283
  *
469
284
  * @example
470
285
  * ```ts
471
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
472
- * new URLPath('/pet/{petId}').isURL // false
286
+ * if (runtime.isBun) {
287
+ * await Bun.write(path, data)
288
+ * }
473
289
  * ```
474
290
  */
475
- get isURL() {
476
- try {
477
- return !!new URL(this.path).href;
478
- } catch {
479
- return false;
480
- }
291
+ get isBun() {
292
+ return typeof Bun !== "undefined";
481
293
  }
482
294
  /**
483
- * Converts the OpenAPI path to a TypeScript template literal string.
484
- *
485
- * @example
486
- * new URLPath('/pet/{petId}').template // '`/pet/${petId}`'
487
- * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
295
+ * `true` when the current process is running under Deno.
488
296
  */
489
- get template() {
490
- return this.toTemplateString();
491
- }
492
- /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
493
- *
494
- * @example
495
- * ```ts
496
- * new URLPath('/pet/{petId}').object
497
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
498
- * ```
499
- */
500
- get object() {
501
- return this.toObject();
502
- }
503
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
504
- *
505
- * @example
506
- * ```ts
507
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
508
- * new URLPath('/pet').params // undefined
509
- * ```
510
- */
511
- get params() {
512
- return this.getParams();
513
- }
514
- #transformParam(raw) {
515
- const param = isValidVarName(raw) ? raw : camelCase(raw);
516
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
297
+ get isDeno() {
298
+ return typeof globalThis.Deno !== "undefined";
517
299
  }
518
300
  /**
519
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
520
- */
521
- #eachParam(fn) {
522
- for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
523
- const raw = match[1];
524
- fn(raw, this.#transformParam(raw));
525
- }
526
- }
527
- toObject({ type = "path", replacer, stringify } = {}) {
528
- const object = {
529
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
530
- params: this.getParams()
531
- };
532
- if (stringify) {
533
- if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
534
- if (object.params) return `{ url: '${object.url}', params: ${JSON.stringify(object.params).replaceAll("'", "").replaceAll(`"`, "")} }`;
535
- return `{ url: '${object.url}' }`;
536
- }
537
- return object;
538
- }
539
- /**
540
- * Converts the OpenAPI path to a TypeScript template literal string.
541
- * An optional `replacer` can transform each extracted parameter name before interpolation.
301
+ * `true` when the current process is running under Node.
542
302
  *
543
- * @example
544
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
303
+ * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
545
304
  */
546
- toTemplateString({ prefix = "", replacer } = {}) {
547
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
548
- if (i % 2 === 0) return part;
549
- const param = this.#transformParam(part);
550
- return `\${${replacer ? replacer(param) : param}}`;
551
- }).join("")}\``;
305
+ get isNode() {
306
+ return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
552
307
  }
553
308
  /**
554
- * Extracts all `{param}` segments from the path and returns them as a key-value map.
555
- * An optional `replacer` transforms each parameter name in both key and value positions.
556
- * Returns `undefined` when no path parameters are found.
309
+ * Name of the runtime executing the current process.
557
310
  *
558
311
  * @example
559
312
  * ```ts
560
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
561
- * // { petId: 'petId', tagId: 'tagId' }
313
+ * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
562
314
  * ```
563
315
  */
564
- getParams(replacer) {
565
- const params = {};
566
- this.#eachParam((_raw, param) => {
567
- const key = replacer ? replacer(param) : param;
568
- params[key] = key;
569
- });
570
- return Object.keys(params).length > 0 ? params : void 0;
316
+ get name() {
317
+ if (this.isBun) return "bun";
318
+ if (this.isDeno) return "deno";
319
+ return "node";
571
320
  }
572
- /** Converts the OpenAPI path to Express-style colon syntax.
321
+ /**
322
+ * Version of the active runtime, or an empty string when it cannot be read.
573
323
  *
574
324
  * @example
575
325
  * ```ts
576
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
326
+ * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
577
327
  * ```
578
328
  */
579
- toURLPath() {
580
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
329
+ get version() {
330
+ if (this.isBun) return process.versions.bun ?? "";
331
+ if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
332
+ return process.versions?.node ?? "";
581
333
  }
582
334
  };
335
+ /**
336
+ * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
337
+ */
338
+ const runtime = new Runtime();
583
339
  //#endregion
584
- //#region src/guards.ts
340
+ //#region ../../internals/utils/src/fs.ts
585
341
  /**
586
- * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
342
+ * Resolves to `true` when the file or directory at `path` exists.
343
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
587
344
  *
588
345
  * @example
589
346
  * ```ts
590
- * if (isOpenApiV2Document(doc)) {
591
- * // doc is OpenAPIV2.Document
347
+ * if (await exists('./kubb.config.ts')) {
348
+ * const content = await read('./kubb.config.ts')
592
349
  * }
593
350
  * ```
594
351
  */
595
- function isOpenApiV2Document(doc) {
596
- return !!doc && isPlainObject(doc) && !("openapi" in doc);
352
+ async function exists(path) {
353
+ if (runtime.isBun) return Bun.file(path).exists();
354
+ return (0, node_fs_promises.access)(path).then(() => true, () => false);
355
+ }
356
+ /**
357
+ * Reads the file at `path` as a UTF-8 string.
358
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * const source = await read('./src/Pet.ts')
363
+ * ```
364
+ */
365
+ async function read(path) {
366
+ if (runtime.isBun) return Bun.file(path).text();
367
+ return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
368
+ }
369
+ //#endregion
370
+ //#region src/bundler.ts
371
+ const urlRegExp = /^https?:\/+/i;
372
+ async function readSource(sourcePath) {
373
+ if (urlRegExp.test(sourcePath)) {
374
+ const url = new URL(sourcePath);
375
+ const response = await fetch(url);
376
+ if (!response.ok) throw new Error(`Cannot fetch the OAS document at ${url.href} (HTTP ${response.status})`);
377
+ return response.text();
378
+ }
379
+ return read(sourcePath);
380
+ }
381
+ async function resolveSource(sourcePath) {
382
+ const data = await readSource(sourcePath);
383
+ if (sourcePath.toLowerCase().endsWith(".md")) return data;
384
+ return (0, yaml.parse)(data);
385
+ }
386
+ /**
387
+ * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
388
+ *
389
+ * External file schemas are hoisted into named `components.schemas` entries, so a property
390
+ * pointing at `./schemas/User.yaml` ends up referencing `#/components/schemas/User`. Generators
391
+ * can then emit a named type with an import instead of inlining the shape. Sources are read with
392
+ * the Bun-aware `read` util for local YAML and JSON files, and with `fetch` for HTTP(S) URLs.
393
+ *
394
+ * @example Local file
395
+ * `const document = await bundleDocument('./openapi.yaml')`
396
+ *
397
+ * @example Remote URL
398
+ * `const document = await bundleDocument('https://example.com/openapi.yaml')`
399
+ */
400
+ async function bundleDocument(pathOrUrl) {
401
+ const cache = /* @__PURE__ */ new Map();
402
+ const resolver = (sourcePath) => {
403
+ const key = urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
404
+ const cached = cache.get(key);
405
+ if (cached) return cached;
406
+ const result = resolveSource(sourcePath);
407
+ cache.set(key, result);
408
+ return result;
409
+ };
410
+ await resolver(pathOrUrl);
411
+ return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
412
+ }
413
+ //#endregion
414
+ //#region src/factory.ts
415
+ /**
416
+ * Loads and bundles an OpenAPI document, returning the raw `Document`.
417
+ *
418
+ * A string is a file path or URL: it is bundled via `api-ref-bundler`, hoisting external file
419
+ * schemas into named `components.schemas` entries so generators can emit named types and imports.
420
+ * An object is treated as an already-parsed document. Swagger 2.0 and OpenAPI 3.0 documents are
421
+ * up-converted to OpenAPI 3.1 via `@scalar/openapi-upgrader`.
422
+ *
423
+ * @example
424
+ * ```ts
425
+ * const document = await parseDocument('./openapi.yaml')
426
+ * const document = await parseDocument(rawDocumentObject)
427
+ * ```
428
+ */
429
+ async function parseDocument(pathOrApi) {
430
+ if (typeof pathOrApi === "string") return parseDocument(await bundleDocument(pathOrApi));
431
+ return (0, _scalar_openapi_upgrader.upgrade)(pathOrApi, "3.1");
597
432
  }
598
433
  /**
434
+ * Creates a `Document` from an `AdapterSource`.
435
+ *
436
+ * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
437
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
438
+ *
439
+ * @example
440
+ * ```ts
441
+ * const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
442
+ * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
443
+ * ```
444
+ */
445
+ async function parseFromConfig(source) {
446
+ if (source.type === "data") return parseDocument(typeof source.data === "string" ? (0, yaml.parse)(source.data) : structuredClone(source.data));
447
+ if (URL.canParse(source.path)) return parseDocument(source.path);
448
+ const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
449
+ await assertInputExists(resolved);
450
+ return parseDocument(resolved);
451
+ }
452
+ /**
453
+ * Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
454
+ * URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
455
+ * its parse error instead.
456
+ */
457
+ async function assertInputExists(input) {
458
+ if (URL.canParse(input)) return;
459
+ if (!await exists(input)) throw new _kubb_core.Diagnostics.Error({
460
+ code: _kubb_core.Diagnostics.code.inputNotFound,
461
+ severity: "error",
462
+ message: `Cannot read the file set as \`input\` (or via \`kubb generate PATH\`): ${input}`,
463
+ help: "Check that the path exists and is readable, then set it as `input` or pass it as `kubb generate PATH`.",
464
+ location: { kind: "config" }
465
+ });
466
+ }
467
+ /**
468
+ * Validates an OpenAPI document using `@readme/openapi-parser` with colorized error output.
469
+ *
470
+ * @example
471
+ * ```ts
472
+ * await validateDocument(document)
473
+ * ```
474
+ */
475
+ async function validateDocument(document, { throwOnError = false } = {}) {
476
+ try {
477
+ const result = await (0, _readme_openapi_parser.validate)(structuredClone(document), { validate: { errors: { colorize: true } } });
478
+ if (!result.valid) throw new Error((0, _readme_openapi_parser.compileErrors)(result));
479
+ } catch (error) {
480
+ if (throwOnError) throw error;
481
+ }
482
+ }
483
+ //#endregion
484
+ //#region src/guards.ts
485
+ /**
599
486
  * Returns `true` when a schema should be treated as nullable.
600
487
  *
601
488
  * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
@@ -640,108 +527,45 @@ function isDiscriminator(obj) {
640
527
  const record = obj;
641
528
  return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
642
529
  }
643
- //#endregion
644
- //#region src/factory.ts
645
- /**
646
- * Loads and dereferences an OpenAPI document, returning the raw `Document`.
647
- *
648
- * Accepts a file path string or an already-parsed document object. File paths are bundled via
649
- * Redocly to resolve external `$ref`s. Swagger 2.0 documents are automatically up-converted
650
- * to OpenAPI 3.0 via `swagger2openapi`.
651
- *
652
- * @example
653
- * ```ts
654
- * const document = await parseDocument('./openapi.yaml')
655
- * const document = await parse(rawDocumentObject, { canBundle: false })
656
- * ```
657
- */
658
- async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true } = {}) {
659
- if (typeof pathOrApi === "string" && canBundle) return parseDocument((await (0, _redocly_openapi_core.bundle)({
660
- ref: pathOrApi,
661
- config: await (0, _redocly_openapi_core.loadConfig)(),
662
- base: pathOrApi
663
- })).bundle.parsed, {
664
- canBundle,
665
- enablePaths
666
- });
667
- const document = await new oas_normalize.default(pathOrApi, {
668
- enablePaths,
669
- colorizeErrors: true
670
- }).load();
671
- if (isOpenApiV2Document(document)) {
672
- const { openapi } = await swagger2openapi.default.convertObj(document, { anchors: true });
673
- return openapi;
674
- }
675
- return document;
676
- }
677
530
  /**
678
- * Deep-merges multiple OpenAPI documents into a single `Document`.
679
- *
680
- * Each document is parsed independently then recursively merged via `mergeDeep` from `@internals/utils`.
681
- * Throws when the input array is empty.
531
+ * Returns `true` when a schema is a binary payload: an octet-stream string body.
682
532
  *
683
533
  * @example
684
534
  * ```ts
685
- * const document = await mergeDocuments(['./pets.yaml', './orders.yaml'])
535
+ * isBinary({ type: 'string', contentMediaType: 'application/octet-stream' }) // true
536
+ * isBinary({ type: 'string' }) // false
686
537
  * ```
687
538
  */
688
- async function mergeDocuments(pathOrApi) {
689
- const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
690
- enablePaths: false,
691
- canBundle: false
692
- })));
693
- if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
694
- const seed = {
695
- openapi: MERGE_OPENAPI_VERSION,
696
- info: {
697
- title: MERGE_DEFAULT_TITLE,
698
- version: MERGE_DEFAULT_VERSION
699
- },
700
- paths: {},
701
- components: { schemas: {} }
702
- };
703
- return parseDocument(documents.reduce((acc, current) => mergeDeep(acc, current), seed));
539
+ function isBinary(schema) {
540
+ return schema.type === "string" && schema.contentMediaType === "application/octet-stream";
704
541
  }
542
+ //#endregion
543
+ //#region src/mime.ts
705
544
  /**
706
- * Creates a `Document` from an `AdapterSource`.
707
- *
708
- * Handles all three source types:
709
- * - `{ type: 'path' }` — resolves and bundles a local file path or remote URL.
710
- * - `{ type: 'paths' }` — merges multiple file paths into a single document.
711
- * - `{ type: 'data' }` — parses an inline string (YAML/JSON) or raw object.
545
+ * MIME type fragments that mark a media type as JSON-like.
712
546
  *
713
- * @example
714
- * ```ts
715
- * const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
716
- * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
717
- * ```
547
+ * A content type is JSON when it contains any of these substrings. The `+json` entry catches
548
+ * structured-syntax suffixes such as `application/vnd.api+json`.
718
549
  */
719
- function parseFromConfig(source) {
720
- if (source.type === "data") {
721
- if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
722
- return parseDocument(source.data, { canBundle: false });
723
- }
724
- if (source.type === "paths") return mergeDocuments(source.paths);
725
- if (new URLPath(source.path).isURL) return parseDocument(source.path);
726
- return parseDocument(node_path.default.resolve(node_path.default.dirname(source.path), source.path));
727
- }
550
+ const jsonMimeFragments = [
551
+ "application/json",
552
+ "application/x-json",
553
+ "text/json",
554
+ "text/x-json",
555
+ "+json"
556
+ ];
728
557
  /**
729
- * Validates an OpenAPI document using `oas-normalize` with colorized error output.
558
+ * Returns `true` when a media type string is JSON-like.
730
559
  *
731
560
  * @example
732
561
  * ```ts
733
- * await validateDocument(document)
562
+ * isJsonMimeType('application/json') // true
563
+ * isJsonMimeType('application/vnd.api+json') // true
564
+ * isJsonMimeType('multipart/form-data') // false
734
565
  * ```
735
566
  */
736
- async function validateDocument(document, { throwOnError = false } = {}) {
737
- try {
738
- await new oas_normalize.default(document, {
739
- enablePaths: true,
740
- colorizeErrors: true
741
- }).validate({ parser: { validate: { errors: { colorize: true } } } });
742
- } catch (error) {
743
- if (throwOnError) throw error;
744
- }
567
+ function isJsonMimeType(mimeType) {
568
+ return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
745
569
  }
746
570
  //#endregion
747
571
  //#region src/refs.ts
@@ -749,20 +573,21 @@ const _refCache = /* @__PURE__ */ new WeakMap();
749
573
  /**
750
574
  * Resolves a local JSON pointer reference from a document.
751
575
  *
752
- * Accepts `#/...` refs. Returns `null` for empty or non-local refs.
753
- * Throws when the pointer cannot be resolved.
576
+ * Accepts `#/...` refs. Returns `null` for an empty or non-local ref. When the pointer cannot be
577
+ * resolved, reports a `refNotFound` diagnostic into the active build and returns `null`. Outside a
578
+ * build there is no sink to collect it, so it throws instead.
754
579
  *
755
580
  * @example
756
581
  * ```ts
757
- * resolveRef<SchemaObject>(document, '#/components/schemas/Pet') // SchemaObject | null
582
+ * resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
758
583
  * ```
759
584
  */
760
585
  function resolveRef(document, $ref) {
761
586
  const origRef = $ref;
762
587
  $ref = $ref.trim();
763
588
  if ($ref === "") return null;
764
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
765
- else return null;
589
+ if (!$ref.startsWith("#")) return null;
590
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
766
591
  let docCache = _refCache.get(document);
767
592
  if (!docCache) {
768
593
  docCache = /* @__PURE__ */ new Map();
@@ -770,7 +595,21 @@ function resolveRef(document, $ref) {
770
595
  }
771
596
  if (docCache.has($ref)) return docCache.get($ref);
772
597
  const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
773
- if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
598
+ if (!current) {
599
+ const diagnostic = {
600
+ code: _kubb_core.Diagnostics.code.refNotFound,
601
+ severity: "error",
602
+ message: `Could not find a definition for ${origRef}.`,
603
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
604
+ location: {
605
+ kind: "schema",
606
+ pointer: origRef,
607
+ ref: origRef
608
+ }
609
+ };
610
+ if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.Diagnostics.Error(diagnostic);
611
+ return null;
612
+ }
774
613
  docCache.set($ref, current);
775
614
  return current;
776
615
  }
@@ -795,8 +634,151 @@ function dereferenceWithRef(document, schema) {
795
634
  return schema;
796
635
  }
797
636
  //#endregion
637
+ //#region src/operation.ts
638
+ /**
639
+ * Slugifies a path for the `operationId` fallback: non-alphanumerics collapse to single dashes,
640
+ * with no leading or trailing dash.
641
+ */
642
+ function slugify(value) {
643
+ return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
644
+ }
645
+ /**
646
+ * Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
647
+ */
648
+ function getOperationId({ path, method, schema }) {
649
+ const { operationId } = schema;
650
+ if (typeof operationId === "string" && operationId.length > 0) return operationId;
651
+ return `${method}_${slugify(path).toLowerCase()}`;
652
+ }
653
+ /**
654
+ * Returns the declared response status codes, skipping `x-` extensions and non-object entries.
655
+ */
656
+ function getResponseStatusCodes({ schema }) {
657
+ const responses = schema.responses;
658
+ if (!responses || isReference(responses)) return [];
659
+ return Object.keys(responses).filter((key) => !key.startsWith("x-") && !!responses[key] && typeof responses[key] === "object");
660
+ }
661
+ /**
662
+ * Returns the response object for a status code, resolving a `$ref` in place. `false` when absent.
663
+ */
664
+ function getResponseByStatusCode({ document, operation, statusCode }) {
665
+ const responses = operation.schema.responses;
666
+ if (!responses || isReference(responses)) return false;
667
+ const response = responses[statusCode];
668
+ if (!response) return false;
669
+ if (isReference(response)) {
670
+ const resolved = resolveRef(document, response.$ref);
671
+ responses[statusCode] = resolved;
672
+ if (!resolved || isReference(resolved)) return false;
673
+ return resolved;
674
+ }
675
+ return response;
676
+ }
677
+ /**
678
+ * Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
679
+ * `undefined` when the operation has no request body.
680
+ */
681
+ function getRequestBodyContent({ document, operation }) {
682
+ const { schema } = operation;
683
+ let requestBody = schema.requestBody;
684
+ if (!requestBody) return;
685
+ if (isReference(requestBody)) {
686
+ const resolved = resolveRef(document, requestBody.$ref);
687
+ schema.requestBody = resolved;
688
+ if (!resolved || isReference(resolved)) return;
689
+ requestBody = resolved;
690
+ }
691
+ return requestBody.content;
692
+ }
693
+ /**
694
+ * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
695
+ * Otherwise picks the first JSON-like media type, then the first declared one, as a
696
+ * `[mediaType, object]` tuple.
697
+ */
698
+ function getRequestContent({ document, operation, mediaType }) {
699
+ const content = getRequestBodyContent({
700
+ document,
701
+ operation
702
+ });
703
+ if (!content) return false;
704
+ if (mediaType) return mediaType in content ? content[mediaType] : false;
705
+ const mediaTypes = Object.keys(content);
706
+ const available = mediaTypes.find((mt) => isJsonMimeType(mt)) ?? mediaTypes[0];
707
+ return available ? [available, content[available]] : false;
708
+ }
709
+ /**
710
+ * Returns the primary request content type. Prefers a JSON-like media type (the last one wins
711
+ * when several are declared), then the first declared one, defaulting to `'application/json'`.
712
+ */
713
+ function getRequestContentType({ document, operation }) {
714
+ const content = getRequestBodyContent({
715
+ document,
716
+ operation
717
+ });
718
+ const mediaTypes = content ? Object.keys(content) : [];
719
+ let result = mediaTypes[0] ?? "application/json";
720
+ for (const mt of mediaTypes) if (isJsonMimeType(mt)) result = mt;
721
+ return result;
722
+ }
723
+ /**
724
+ * Builds an `Operation` for every supported HTTP method on every path, in document order.
725
+ * `x-` path keys and unresolvable path-item `$ref`s are skipped.
726
+ *
727
+ * @example
728
+ * ```ts
729
+ * for (const operation of getOperations(document)) {
730
+ * parseOperation(options, operation)
731
+ * }
732
+ * ```
733
+ */
734
+ function getOperations(document) {
735
+ const operations = [];
736
+ const paths = document.paths;
737
+ if (!paths) return operations;
738
+ for (const path of Object.keys(paths)) {
739
+ if (path.startsWith("x-")) continue;
740
+ let pathItem = paths[path];
741
+ if (!pathItem) continue;
742
+ if (isReference(pathItem)) {
743
+ const resolved = resolveRef(document, pathItem.$ref);
744
+ paths[path] = resolved;
745
+ if (!resolved || isReference(resolved)) continue;
746
+ pathItem = resolved;
747
+ }
748
+ const item = pathItem;
749
+ for (const method of Object.keys(item)) {
750
+ if (!SUPPORTED_METHODS.has(method)) continue;
751
+ const schema = item[method];
752
+ if (!schema || typeof schema !== "object") continue;
753
+ operations.push({
754
+ path,
755
+ method,
756
+ schema
757
+ });
758
+ }
759
+ }
760
+ return operations;
761
+ }
762
+ //#endregion
798
763
  //#region src/resolvers.ts
799
764
  /**
765
+ * Reads the server URL from the document's `servers` array at `server.index`,
766
+ * interpolating any `server.variables` into the URL template.
767
+ *
768
+ * Returns `null` when `server.index` is omitted or out of range.
769
+ *
770
+ * @example Resolve the first server
771
+ * `resolveBaseUrl({ document, server: { index: 0 } })`
772
+ *
773
+ * @example Override a path variable
774
+ * `resolveBaseUrl({ document, server: { index: 0, variables: { version: 'v2' } } })`
775
+ */
776
+ function resolveBaseUrl({ document, server }) {
777
+ const index = server?.index;
778
+ const entry = index !== void 0 ? document.servers?.at(index) : void 0;
779
+ return entry?.url ? resolveServerUrl(entry, server?.variables) : null;
780
+ }
781
+ /**
800
782
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
801
783
  * Resolution order: `overrides[key]` → `variable.default` → left unreplaced.
802
784
  * Throws if an override value is not in the variable's `enum` list.
@@ -816,7 +798,16 @@ function resolveServerUrl(server, overrides) {
816
798
  for (const [key, variable] of Object.entries(server.variables)) {
817
799
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
818
800
  if (value === void 0) continue;
819
- 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(", ")}.`);
801
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.Diagnostics.Error({
802
+ code: _kubb_core.Diagnostics.code.invalidServerVariable,
803
+ severity: "error",
804
+ message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
805
+ help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
806
+ location: {
807
+ kind: "document",
808
+ pointer: "#/servers"
809
+ }
810
+ });
820
811
  url = url.replaceAll(`{${key}}`, value);
821
812
  }
822
813
  return url;
@@ -829,6 +820,15 @@ function getSchemaType(format) {
829
820
  return formatMap[format] ?? null;
830
821
  }
831
822
  /**
823
+ * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry, plus the
824
+ * `specialCasedFormats` that `convertFormat` handles directly. False means the format falls back to
825
+ * the base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
826
+ * diagnostic in step with the parser as `formatMap` grows.
827
+ */
828
+ function isHandledFormat(format) {
829
+ return getSchemaType(format) !== null || specialCasedFormats.has(format);
830
+ }
831
+ /**
832
832
  * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
833
833
  * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
834
834
  */
@@ -838,15 +838,9 @@ function getPrimitiveType(type) {
838
838
  return "string";
839
839
  }
840
840
  /**
841
- * Narrows a content-type string to the `MediaType` union Kubb recognizes, or returns `null`.
842
- */
843
- function getMediaType(contentType) {
844
- return Object.values(_kubb_core.ast.mediaTypes).includes(contentType) ? contentType : null;
845
- }
846
- /**
847
841
  * Returns all parameters for an operation, merging path-level and operation-level entries.
848
842
  * Operation-level parameters override path-level ones with the same `in:name` key.
849
- * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
843
+ * Each `$ref` parameter is dereferenced via `dereferenceWithRef` before merging.
850
844
  *
851
845
  * @example
852
846
  * ```ts
@@ -873,19 +867,10 @@ function getResponseBody(responseBody, contentType) {
873
867
  if (!(contentType in body.content)) return false;
874
868
  return body.content[contentType];
875
869
  }
876
- let availableContentType;
877
870
  const contentTypes = Object.keys(body.content);
878
- for (const mt of contentTypes) if (oas_utils.matchesMimeType.json(mt)) {
879
- availableContentType = mt;
880
- break;
881
- }
882
- if (!availableContentType) availableContentType = contentTypes[0];
883
- if (availableContentType) return [
884
- availableContentType,
885
- body.content[availableContentType],
886
- ...body.description ? [body.description] : []
887
- ];
888
- return false;
871
+ const availableContentType = contentTypes.find(isJsonMimeType) ?? contentTypes[0];
872
+ if (!availableContentType) return false;
873
+ return body.content[availableContentType];
889
874
  }
890
875
  /**
891
876
  * Returns the response schema for a given operation and HTTP status code.
@@ -898,17 +883,23 @@ function getResponseBody(responseBody, contentType) {
898
883
  * getResponseSchema(document, operation, '4XX') // {}
899
884
  * ```
900
885
  */
901
- function getResponseSchema(document, operation, statusCode, options = {}) {
902
- if (operation.schema.responses) {
903
- const responses = operation.schema.responses;
904
- for (const key in responses) {
905
- const schema = responses[key];
906
- if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
907
- }
886
+ function resolveResponseRefs(document, operation) {
887
+ const responses = operation.schema.responses;
888
+ if (!responses) return;
889
+ for (const key in responses) {
890
+ const schema = responses[key];
891
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
908
892
  }
909
- const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
893
+ }
894
+ function getResponseSchema(document, operation, statusCode, options = {}) {
895
+ resolveResponseRefs(document, operation);
896
+ const responseBody = getResponseBody(getResponseByStatusCode({
897
+ document,
898
+ operation,
899
+ statusCode
900
+ }), options.contentType);
910
901
  if (responseBody === false) return {};
911
- const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
902
+ const schema = responseBody.schema;
912
903
  if (!schema) return {};
913
904
  return dereferenceWithRef(document, schema);
914
905
  }
@@ -922,16 +913,25 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
922
913
  */
923
914
  function getRequestSchema(document, operation, options = {}) {
924
915
  if (operation.schema.requestBody) operation.schema.requestBody = dereferenceWithRef(document, operation.schema.requestBody);
925
- const requestBody = operation.getRequestBody(options.contentType);
916
+ const requestBody = getRequestContent({
917
+ document,
918
+ operation,
919
+ mediaType: options.contentType
920
+ });
926
921
  if (requestBody === false) return null;
922
+ const mediaType = Array.isArray(requestBody) ? requestBody[0] : options.contentType;
927
923
  const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
924
+ if (mediaType === "application/octet-stream" && (!schema || Object.keys(schema).length === 0)) return {
925
+ type: "string",
926
+ contentMediaType: "application/octet-stream"
927
+ };
928
928
  if (!schema) return null;
929
929
  return dereferenceWithRef(document, schema);
930
930
  }
931
931
  /**
932
932
  * Flattens a keyword-only `allOf` into its parent schema.
933
933
  *
934
- * Only flattens when every member is a plain fragment no `$ref` and no structural keywords
934
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
935
935
  * (see `structuralKeys`). Outer schema values take precedence over fragment values.
936
936
  * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
937
937
  *
@@ -939,9 +939,12 @@ function getRequestSchema(document, operation, options = {}) {
939
939
  * ```ts
940
940
  * flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
941
941
  * // { type: 'object', properties: {}, description: 'A pet' }
942
+ * ```
942
943
  *
944
+ * @example
945
+ * ```ts
943
946
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
944
- * // returned unchanged contains a $ref
947
+ * // returned unchanged, contains a $ref
945
948
  * ```
946
949
  */
947
950
  /**
@@ -957,17 +960,17 @@ function hasStructuralKeywords(fragment) {
957
960
  function flattenSchema(schema) {
958
961
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
959
962
  const allOfFragments = schema.allOf;
960
- if (allOfFragments.some((item) => (0, oas_types.isRef)(item))) return schema;
963
+ if (allOfFragments.some((item) => isReference(item))) return schema;
961
964
  if (allOfFragments.some(hasStructuralKeywords)) return schema;
962
- const merged = { ...schema };
963
- delete merged.allOf;
964
- for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
965
+ const { allOf: _allOf, ...rest } = schema;
966
+ const merged = rest;
967
+ for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
965
968
  return merged;
966
969
  }
967
970
  /**
968
971
  * Extracts the inline schema from a media-type `content` map.
969
972
  *
970
- * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
973
+ * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
971
974
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
972
975
  *
973
976
  * @example
@@ -986,21 +989,22 @@ function extractSchemaFromContent(content, preferredContentType) {
986
989
  /**
987
990
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
988
991
  */
989
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
992
+ function* collectRefs(schema) {
990
993
  if (Array.isArray(schema)) {
991
- for (const item of schema) collectRefs(item, refs);
992
- return refs;
994
+ for (const item of schema) yield* collectRefs(item);
995
+ return;
993
996
  }
994
997
  if (schema && typeof schema === "object") for (const key in schema) {
995
998
  const value = schema[key];
996
- if (key === "$ref" && typeof value === "string") {
997
- if (value.startsWith("#/components/schemas/")) {
998
- const name = value.slice(21);
999
- if (name) refs.add(name);
1000
- }
1001
- } else collectRefs(value, refs);
999
+ if (!(key === "$ref" && typeof value === "string")) {
1000
+ yield* collectRefs(value);
1001
+ continue;
1002
+ }
1003
+ if (value.startsWith("#/components/schemas/")) {
1004
+ const name = value.slice(21);
1005
+ if (name) yield name;
1006
+ }
1002
1007
  }
1003
- return refs;
1004
1008
  }
1005
1009
  /**
1006
1010
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -1016,7 +1020,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1016
1020
  */
1017
1021
  function sortSchemas(schemas) {
1018
1022
  const deps = /* @__PURE__ */ new Map();
1019
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
1023
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
1020
1024
  const sorted = [];
1021
1025
  const visited = /* @__PURE__ */ new Set();
1022
1026
  function visit(name, stack) {
@@ -1037,9 +1041,6 @@ const semanticSuffixes = {
1037
1041
  responses: "Response",
1038
1042
  requestBodies: "Request"
1039
1043
  };
1040
- function getSemanticSuffix(source) {
1041
- return semanticSuffixes[source];
1042
- }
1043
1044
  function resolveSchemaRef(document, schema) {
1044
1045
  if (!isReference(schema)) return schema;
1045
1046
  const resolved = resolveRef(document, schema.$ref);
@@ -1088,13 +1089,13 @@ function getSchemas(document, { contentType }) {
1088
1089
  let hasMultipleSources = false;
1089
1090
  if (!isSingle) {
1090
1091
  const firstSource = items[0].source;
1091
- for (let i = 1; i < items.length; i++) if (items[i].source !== firstSource) {
1092
+ for (const item of items) if (item.source !== firstSource) {
1092
1093
  hasMultipleSources = true;
1093
1094
  break;
1094
1095
  }
1095
1096
  }
1096
1097
  items.forEach((item, index) => {
1097
- const suffix = isSingle ? "" : hasMultipleSources ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
1098
+ const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index === 0 ? "" : String(index + 1);
1098
1099
  const uniqueName = item.originalName + suffix;
1099
1100
  schemas[uniqueName] = item.schema;
1100
1101
  nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
@@ -1107,7 +1108,7 @@ function getSchemas(document, { contentType }) {
1107
1108
  }
1108
1109
  /**
1109
1110
  * Resolves the AST type descriptor for a date/time format, honoring the `dateType` option.
1110
- * Returns `null` when `dateType: false`, signalling the format should fall through to `string`.
1111
+ * Returns `null` when `dateType: false`, so the format falls through to `string`.
1111
1112
  */
1112
1113
  function getDateType(options, format) {
1113
1114
  if (!options.dateType) return null;
@@ -1141,6 +1142,15 @@ function getDateType(options, format) {
1141
1142
  /**
1142
1143
  * Collects the shared metadata fields passed to every `createSchema` call.
1143
1144
  */
1145
+ /**
1146
+ * Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
1147
+ * labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
1148
+ * 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
1149
+ */
1150
+ function extractExamples(schema) {
1151
+ if (Array.isArray(schema.examples)) return schema.examples;
1152
+ return schema.example !== void 0 ? [schema.example] : void 0;
1153
+ }
1144
1154
  function buildSchemaNode(schema, name, nullable, defaultValue) {
1145
1155
  return {
1146
1156
  name,
@@ -1151,15 +1161,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1151
1161
  readOnly: schema.readOnly,
1152
1162
  writeOnly: schema.writeOnly,
1153
1163
  default: defaultValue,
1154
- example: schema.example
1164
+ examples: extractExamples(schema),
1165
+ format: schema.format
1155
1166
  };
1156
1167
  }
1157
1168
  /**
1158
1169
  * Returns all request body content type keys for an operation.
1159
1170
  *
1160
- * The requestBody is dereferenced **in-place** when it is a `$ref` the same mutation
1161
- * that `getRequestSchema` already performs so that the returned list accurately reflects
1162
- * the available content types even for referenced bodies.
1171
+ * The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
1172
+ * `getRequestSchema` already performs), so the returned list accurately reflects the
1173
+ * available content types even for referenced bodies.
1163
1174
  *
1164
1175
  * @example
1165
1176
  * ```ts
@@ -1173,15 +1184,38 @@ function getRequestBodyContentTypes(document, operation) {
1173
1184
  if (!body) return [];
1174
1185
  return body.content ? Object.keys(body.content) : [];
1175
1186
  }
1187
+ /**
1188
+ * Returns all response content type keys for an operation at a given status code.
1189
+ *
1190
+ * Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
1191
+ * so the returned list reflects the available content types even for referenced responses.
1192
+ *
1193
+ * @example
1194
+ * ```ts
1195
+ * getResponseBodyContentTypes(document, operation, 200)
1196
+ * // ['application/json', 'application/xml']
1197
+ * ```
1198
+ */
1199
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1200
+ resolveResponseRefs(document, operation);
1201
+ const responseObj = getResponseByStatusCode({
1202
+ document,
1203
+ operation,
1204
+ statusCode
1205
+ });
1206
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1207
+ const body = responseObj;
1208
+ return body.content ? Object.keys(body.content) : [];
1209
+ }
1176
1210
  //#endregion
1177
1211
  //#region src/parser.ts
1178
1212
  /**
1179
1213
  * Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
1180
1214
  *
1181
1215
  * This pattern violates the OpenAPI spec but appears in real specs. The fix moves enum values
1182
- * from the array to its items sub-schema, making them valid for downstream processing.
1216
+ * from the array to its items sub-schema, so they are valid for downstream processing.
1183
1217
  *
1184
- * @note This is a defensive measure for robustness with non-compliant specs.
1218
+ * @note A defensive measure for non-compliant specs.
1185
1219
  */
1186
1220
  function normalizeArrayEnum(schema) {
1187
1221
  const normalizedItems = {
@@ -1195,13 +1229,46 @@ function normalizeArrayEnum(schema) {
1195
1229
  };
1196
1230
  }
1197
1231
  /**
1232
+ * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1233
+ * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
1234
+ */
1235
+ function createNullSchema(schema, name, nullable) {
1236
+ return _kubb_ast.ast.factory.createSchema({
1237
+ type: "null",
1238
+ primitive: "null",
1239
+ name,
1240
+ title: schema.title,
1241
+ description: schema.description,
1242
+ deprecated: schema.deprecated,
1243
+ nullable,
1244
+ format: schema.format
1245
+ });
1246
+ }
1247
+ /**
1248
+ * Names the inline enums on a property's schema, and on each item when the property is a tuple, from
1249
+ * the parent and property name. Wraps `macroEnumName` at the property construction site.
1250
+ */
1251
+ function nameEnums(node, options) {
1252
+ const macro = (0, _kubb_ast.macroEnumName)(options);
1253
+ const named = _kubb_ast.ast.applyMacros(node, [macro], { depth: "shallow" });
1254
+ const tupleNode = _kubb_ast.ast.narrowSchema(named, "tuple");
1255
+ if (tupleNode?.items) {
1256
+ const namedItems = tupleNode.items.map((item) => _kubb_ast.ast.applyMacros(item, [macro], { depth: "shallow" }));
1257
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1258
+ ...tupleNode,
1259
+ items: namedItems
1260
+ };
1261
+ }
1262
+ return named;
1263
+ }
1264
+ /**
1198
1265
  * Factory function that creates schema and operation converters for a given OpenAPI context.
1199
1266
  *
1200
1267
  * Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
1201
1268
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1202
- * made possible by hoisting of function declarations.
1269
+ * which works because function declarations hoist.
1203
1270
  *
1204
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1271
+ * @internal
1205
1272
  */
1206
1273
  function createSchemaParser(ctx) {
1207
1274
  const document = ctx.document;
@@ -1211,6 +1278,33 @@ function createSchemaParser(ctx) {
1211
1278
  */
1212
1279
  const resolvingRefs = /* @__PURE__ */ new Set();
1213
1280
  /**
1281
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1282
+ *
1283
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1284
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1285
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1286
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1287
+ */
1288
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1289
+ /**
1290
+ * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1291
+ * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1292
+ * goes `false` for a `$ref` that points at a component the spec never declares.
1293
+ */
1294
+ const refExistence = /* @__PURE__ */ new Map();
1295
+ function refExists(refPath) {
1296
+ if (!refExistence.has(refPath)) {
1297
+ let exists = false;
1298
+ try {
1299
+ exists = !!resolveRef(document, refPath);
1300
+ } catch {
1301
+ exists = false;
1302
+ }
1303
+ refExistence.set(refPath, exists);
1304
+ }
1305
+ return refExistence.get(refPath) ?? false;
1306
+ }
1307
+ /**
1214
1308
  * Converts a `$ref` schema into a `RefSchemaNode`.
1215
1309
  *
1216
1310
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1219,20 +1313,30 @@ function createSchemaParser(ctx) {
1219
1313
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1220
1314
  */
1221
1315
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1222
- let resolvedSchema;
1316
+ let resolvedSchema = null;
1223
1317
  const refPath = schema.$ref;
1224
- if (refPath && !resolvingRefs.has(refPath)) try {
1225
- const referenced = resolveRef(document, refPath);
1226
- if (referenced) {
1227
- resolvingRefs.add(refPath);
1228
- resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1229
- resolvingRefs.delete(refPath);
1318
+ if (refPath && !resolvingRefs.has(refPath)) {
1319
+ if (!resolvedRefCache.has(refPath)) {
1320
+ try {
1321
+ const referenced = resolveRef(document, refPath);
1322
+ if (referenced) {
1323
+ resolvingRefs.add(refPath);
1324
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1325
+ resolvingRefs.delete(refPath);
1326
+ }
1327
+ } catch {}
1328
+ resolvedRefCache.set(refPath, resolvedSchema);
1230
1329
  }
1231
- } catch {}
1232
- return _kubb_core.ast.createSchema({
1330
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1331
+ }
1332
+ if (refPath && document.components && !refExists(refPath)) return _kubb_ast.ast.factory.createSchema({
1333
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1334
+ type: "unknown"
1335
+ });
1336
+ return _kubb_ast.ast.factory.createSchema({
1233
1337
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1234
1338
  type: "ref",
1235
- name: _kubb_core.ast.extractRefName(schema.$ref),
1339
+ name: (0, _kubb_ast.extractRefName)(schema.$ref),
1236
1340
  ref: schema.$ref,
1237
1341
  schema: resolvedSchema
1238
1342
  });
@@ -1245,12 +1349,12 @@ function createSchemaParser(ctx) {
1245
1349
  const [memberSchema] = schema.allOf;
1246
1350
  const memberNode = parseSchema({
1247
1351
  schema: memberSchema,
1248
- name: null
1352
+ name
1249
1353
  }, rawOptions);
1250
1354
  const { kind: _kind, ...memberNodeProps } = memberNode;
1251
1355
  const mergedNullable = nullable || memberNode.nullable || void 0;
1252
1356
  const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1253
- return _kubb_core.ast.createSchema({
1357
+ return _kubb_ast.ast.factory.createSchema({
1254
1358
  ...memberNodeProps,
1255
1359
  name,
1256
1360
  title: schema.title ?? memberNode.title,
@@ -1260,8 +1364,9 @@ function createSchemaParser(ctx) {
1260
1364
  readOnly: schema.readOnly ?? memberNode.readOnly,
1261
1365
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1262
1366
  default: mergedDefault,
1263
- example: schema.example ?? memberNode.example,
1264
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1367
+ examples: extractExamples(schema) ?? memberNode.examples,
1368
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1369
+ format: schema.format ?? memberNode.format
1265
1370
  });
1266
1371
  }
1267
1372
  const filteredDiscriminantValues = [];
@@ -1275,7 +1380,7 @@ function createSchemaParser(ctx) {
1275
1380
  const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1276
1381
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1277
1382
  if (inOneOf || inMapping) {
1278
- const discriminatorValue = _kubb_core.ast.findDiscriminator(deref.discriminator.mapping, childRef);
1383
+ const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1279
1384
  if (discriminatorValue) filteredDiscriminantValues.push({
1280
1385
  propertyName: deref.discriminator.propertyName,
1281
1386
  value: discriminatorValue
@@ -1283,7 +1388,10 @@ function createSchemaParser(ctx) {
1283
1388
  return false;
1284
1389
  }
1285
1390
  return true;
1286
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1391
+ }).map((s) => parseSchema({
1392
+ schema: s,
1393
+ name
1394
+ }, rawOptions));
1287
1395
  const syntheticStart = allOfMembers.length;
1288
1396
  if (Array.isArray(schema.required) && schema.required.length) {
1289
1397
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
@@ -1295,10 +1403,13 @@ function createSchemaParser(ctx) {
1295
1403
  return deref && !isReference(deref) ? [deref] : [];
1296
1404
  });
1297
1405
  for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1298
- allOfMembers.push(parseSchema({ schema: {
1299
- properties: { [key]: resolved.properties[key] },
1300
- required: [key]
1301
- } }, rawOptions));
1406
+ allOfMembers.push(parseSchema({
1407
+ schema: {
1408
+ properties: { [key]: resolved.properties[key] },
1409
+ required: [key]
1410
+ },
1411
+ name
1412
+ }, rawOptions));
1302
1413
  break;
1303
1414
  }
1304
1415
  }
@@ -1307,13 +1418,13 @@ function createSchemaParser(ctx) {
1307
1418
  const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1308
1419
  allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1309
1420
  }
1310
- for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(_kubb_core.ast.createDiscriminantNode({
1421
+ for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1311
1422
  propertyName,
1312
1423
  value
1313
1424
  }));
1314
- return _kubb_core.ast.createSchema({
1425
+ return _kubb_ast.ast.factory.createSchema({
1315
1426
  type: "intersection",
1316
- members: [..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1427
+ members: [...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(syntheticStart))],
1317
1428
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1318
1429
  });
1319
1430
  }
@@ -1322,14 +1433,42 @@ function createSchemaParser(ctx) {
1322
1433
  */
1323
1434
  function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
1324
1435
  function pickDiscriminatorPropertyNode(node, propertyName) {
1325
- const discriminatorProperty = _kubb_core.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1436
+ const discriminatorProperty = _kubb_ast.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1326
1437
  if (!discriminatorProperty) return null;
1327
- return _kubb_core.ast.createSchema({
1438
+ return _kubb_ast.ast.factory.createSchema({
1328
1439
  type: "object",
1329
1440
  primitive: "object",
1330
1441
  properties: [discriminatorProperty]
1331
1442
  });
1332
1443
  }
1444
+ function resolveRefSilent($ref) {
1445
+ if (!$ref.startsWith("#")) return null;
1446
+ return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1447
+ }
1448
+ function implicitDiscriminantValue(member) {
1449
+ if (!discriminator || discriminator.mapping || !isReference(member)) return null;
1450
+ const value = (0, _kubb_ast.extractRefName)(member.$ref);
1451
+ if (!value) return null;
1452
+ const variant = resolveRefSilent(member.$ref);
1453
+ if (!variant) return null;
1454
+ const propertyName = discriminator.propertyName;
1455
+ const seen = /* @__PURE__ */ new Set([member.$ref]);
1456
+ function constrains(v) {
1457
+ const prop = v.properties?.[propertyName];
1458
+ const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1459
+ if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1460
+ const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1461
+ if (!composition) return false;
1462
+ return composition.some((m) => {
1463
+ if (!isReference(m)) return constrains(m);
1464
+ if (seen.has(m.$ref)) return false;
1465
+ seen.add(m.$ref);
1466
+ const r = resolveRefSilent(m.$ref);
1467
+ return r ? constrains(r) : false;
1468
+ });
1469
+ }
1470
+ return constrains(variant) ? null : value;
1471
+ }
1333
1472
  const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1334
1473
  const strategy = schema.oneOf ? "one" : "any";
1335
1474
  const unionBase = {
@@ -1338,65 +1477,62 @@ function createSchemaParser(ctx) {
1338
1477
  strategy
1339
1478
  };
1340
1479
  const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1341
- const sharedPropertiesNode = schema.properties ? (() => {
1342
- const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1343
- return parseSchema({
1344
- schema: discriminator ? Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== "discriminator")) : schemaWithoutUnion,
1345
- name
1346
- }, rawOptions);
1347
- })() : void 0;
1348
- if (sharedPropertiesNode || discriminator?.mapping) {
1480
+ const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1481
+ const sharedPropertiesNode = schema.properties ? parseSchema({
1482
+ schema: memberBaseSchema,
1483
+ name
1484
+ }, rawOptions) : void 0;
1485
+ if (sharedPropertiesNode || discriminator) {
1349
1486
  const members = unionMembers.map((s) => {
1350
1487
  const ref = isReference(s) ? s.$ref : void 0;
1351
- const discriminatorValue = _kubb_core.ast.findDiscriminator(discriminator?.mapping, ref);
1352
- const memberNode = parseSchema({ schema: s }, rawOptions);
1488
+ const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1489
+ const memberNode = parseSchema({
1490
+ schema: s,
1491
+ name
1492
+ }, rawOptions);
1353
1493
  if (!discriminatorValue || !discriminator) return memberNode;
1354
- const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.setDiscriminatorEnum({
1355
- node: sharedPropertiesNode,
1494
+ const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_ast.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1356
1495
  propertyName: discriminator.propertyName,
1357
1496
  values: [discriminatorValue]
1358
- }), discriminator.propertyName) : void 0;
1359
- return _kubb_core.ast.createSchema({
1497
+ })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1498
+ return _kubb_ast.ast.factory.createSchema({
1360
1499
  type: "intersection",
1361
- members: [memberNode, narrowedDiscriminatorNode ?? _kubb_core.ast.createDiscriminantNode({
1500
+ members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1362
1501
  propertyName: discriminator.propertyName,
1363
1502
  value: discriminatorValue
1364
1503
  })]
1365
1504
  });
1366
1505
  });
1367
- const unionNode = _kubb_core.ast.createSchema({
1506
+ const unionNode = _kubb_ast.ast.factory.createSchema({
1368
1507
  type: "union",
1369
1508
  ...unionBase,
1370
1509
  members
1371
1510
  });
1372
1511
  if (!sharedPropertiesNode) return unionNode;
1373
- return _kubb_core.ast.createSchema({
1512
+ return _kubb_ast.ast.factory.createSchema({
1374
1513
  type: "intersection",
1375
1514
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1376
1515
  members: [unionNode, sharedPropertiesNode]
1377
1516
  });
1378
1517
  }
1379
- return _kubb_core.ast.createSchema({
1518
+ const unionNode = _kubb_ast.ast.factory.createSchema({
1380
1519
  type: "union",
1381
1520
  ...unionBase,
1382
- members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1521
+ members: unionMembers.map((s) => parseSchema({
1522
+ schema: s,
1523
+ name
1524
+ }, rawOptions))
1383
1525
  });
1526
+ return _kubb_ast.ast.applyMacros(unionNode, [_kubb_ast.macroSimplifyUnion], { depth: "shallow" });
1384
1527
  }
1385
1528
  /**
1386
1529
  * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1387
1530
  */
1388
1531
  function convertConst({ schema, name, nullable, defaultValue }) {
1389
1532
  const constValue = schema.const;
1390
- if (constValue === null) return _kubb_core.ast.createSchema({
1391
- type: "null",
1392
- primitive: "null",
1393
- name,
1394
- title: schema.title,
1395
- description: schema.description,
1396
- deprecated: schema.deprecated
1397
- });
1533
+ if (constValue === null) return createNullSchema(schema, name);
1398
1534
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1399
- return _kubb_core.ast.createSchema({
1535
+ return _kubb_ast.ast.factory.createSchema({
1400
1536
  type: "enum",
1401
1537
  primitive: constPrimitive,
1402
1538
  enumValues: [constValue],
@@ -1409,7 +1545,7 @@ function createSchemaParser(ctx) {
1409
1545
  */
1410
1546
  function convertFormat({ schema, name, nullable, defaultValue, options }) {
1411
1547
  const base = buildSchemaNode(schema, name, nullable, defaultValue);
1412
- if (schema.format === "int64") return _kubb_core.ast.createSchema({
1548
+ if (schema.format === "int64") return _kubb_ast.ast.factory.createSchema({
1413
1549
  type: options.integerType === "bigint" ? "bigint" : "integer",
1414
1550
  primitive: "integer",
1415
1551
  ...base,
@@ -1421,14 +1557,14 @@ function createSchemaParser(ctx) {
1421
1557
  if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1422
1558
  const dateType = getDateType(options, schema.format);
1423
1559
  if (!dateType) return null;
1424
- if (dateType.type === "datetime") return _kubb_core.ast.createSchema({
1560
+ if (dateType.type === "datetime") return _kubb_ast.ast.factory.createSchema({
1425
1561
  ...base,
1426
1562
  primitive: "string",
1427
1563
  type: "datetime",
1428
1564
  offset: dateType.offset,
1429
1565
  local: dateType.local
1430
1566
  });
1431
- return _kubb_core.ast.createSchema({
1567
+ return _kubb_ast.ast.factory.createSchema({
1432
1568
  ...base,
1433
1569
  primitive: "string",
1434
1570
  type: dateType.type,
@@ -1438,39 +1574,15 @@ function createSchemaParser(ctx) {
1438
1574
  const specialType = getSchemaType(schema.format);
1439
1575
  if (!specialType) return null;
1440
1576
  const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1441
- if (specialType === "number" || specialType === "integer" || specialType === "bigint") return _kubb_core.ast.createSchema({
1577
+ const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
1578
+ return _kubb_ast.ast.factory.createSchema({
1442
1579
  ...base,
1443
1580
  primitive: specialPrimitive,
1444
- type: specialType
1445
- });
1446
- if (specialType === "url") return _kubb_core.ast.createSchema({
1447
- ...base,
1448
- primitive: "string",
1449
- type: "url",
1450
- min: schema.minLength,
1451
- max: schema.maxLength
1452
- });
1453
- if (specialType === "ipv4") return _kubb_core.ast.createSchema({
1454
- ...base,
1455
- primitive: "string",
1456
- type: "ipv4"
1457
- });
1458
- if (specialType === "ipv6") return _kubb_core.ast.createSchema({
1459
- ...base,
1460
- primitive: "string",
1461
- type: "ipv6"
1462
- });
1463
- if (specialType === "uuid" || specialType === "email") return _kubb_core.ast.createSchema({
1464
- ...base,
1465
- primitive: "string",
1466
1581
  type: specialType,
1467
- min: schema.minLength,
1468
- max: schema.maxLength
1469
- });
1470
- return _kubb_core.ast.createSchema({
1471
- ...base,
1472
- primitive: specialPrimitive,
1473
- type: specialType
1582
+ ...hasLength ? {
1583
+ min: schema.minLength,
1584
+ max: schema.maxLength
1585
+ } : {}
1474
1586
  });
1475
1587
  }
1476
1588
  /**
@@ -1483,35 +1595,31 @@ function createSchemaParser(ctx) {
1483
1595
  }, rawOptions);
1484
1596
  const nullInEnum = schema.enum.includes(null);
1485
1597
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1598
+ if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
1486
1599
  const enumNullable = nullable || nullInEnum || void 0;
1487
1600
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1488
1601
  const enumPrimitive = getPrimitiveType(type);
1489
1602
  const enumBase = {
1490
1603
  type: "enum",
1491
1604
  primitive: enumPrimitive,
1492
- name,
1493
- title: schema.title,
1494
- description: schema.description,
1495
- deprecated: schema.deprecated,
1496
- nullable: enumNullable,
1497
- readOnly: schema.readOnly,
1498
- writeOnly: schema.writeOnly,
1499
- default: enumDefault,
1500
- example: schema.example
1605
+ ...buildSchemaNode(schema, name, enumNullable, enumDefault)
1501
1606
  };
1502
1607
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1503
- if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1608
+ const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1609
+ if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1504
1610
  const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1505
1611
  const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1612
+ const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1506
1613
  const uniqueValues = [...new Set(filteredValues)];
1507
1614
  const seenNames = /* @__PURE__ */ new Set();
1508
- return _kubb_core.ast.createSchema({
1615
+ return _kubb_ast.ast.factory.createSchema({
1509
1616
  ...enumBase,
1510
1617
  primitive: enumPrimitiveType,
1511
1618
  namedEnumValues: uniqueValues.map((value, index) => ({
1512
1619
  name: String(rawEnumNames?.[index] ?? value),
1513
1620
  value,
1514
- primitive: enumPrimitiveType
1621
+ primitive: enumPrimitiveType,
1622
+ description: rawEnumDescriptions?.[index]
1515
1623
  })).filter((entry) => {
1516
1624
  if (seenNames.has(entry.name)) return false;
1517
1625
  seenNames.add(entry.name);
@@ -1519,7 +1627,7 @@ function createSchemaParser(ctx) {
1519
1627
  })
1520
1628
  });
1521
1629
  }
1522
- return _kubb_core.ast.createSchema({
1630
+ return _kubb_ast.ast.factory.createSchema({
1523
1631
  ...enumBase,
1524
1632
  enumValues: [...new Set(filteredValues)]
1525
1633
  });
@@ -1532,20 +1640,15 @@ function createSchemaParser(ctx) {
1532
1640
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1533
1641
  const resolvedPropSchema = propSchema;
1534
1642
  const propNullable = isNullable(resolvedPropSchema);
1535
- const propNode = parseSchema({
1643
+ const schemaNode = nameEnums(parseSchema({
1536
1644
  schema: resolvedPropSchema,
1537
- name: _kubb_core.ast.childName(name, propName)
1538
- }, rawOptions);
1539
- let schemaNode = _kubb_core.ast.setEnumName(propNode, name, propName, options.enumSuffix);
1540
- const tupleNode = _kubb_core.ast.narrowSchema(schemaNode, "tuple");
1541
- if (tupleNode?.items) {
1542
- const namedItems = tupleNode.items.map((item) => _kubb_core.ast.setEnumName(item, name, propName, options.enumSuffix));
1543
- if (namedItems.some((item, i) => item !== tupleNode.items[i])) schemaNode = {
1544
- ...tupleNode,
1545
- items: namedItems
1546
- };
1547
- }
1548
- return _kubb_core.ast.createProperty({
1645
+ name: (0, _kubb_ast.childName)(name, propName)
1646
+ }, rawOptions), {
1647
+ parentName: name,
1648
+ propName,
1649
+ enumSuffix: options.enumSuffix
1650
+ });
1651
+ return _kubb_ast.ast.factory.createProperty({
1549
1652
  name: propName,
1550
1653
  schema: {
1551
1654
  ...schemaNode,
@@ -1555,14 +1658,15 @@ function createSchemaParser(ctx) {
1555
1658
  });
1556
1659
  }) : [];
1557
1660
  const additionalProperties = schema.additionalProperties;
1558
- let additionalPropertiesNode;
1559
- if (additionalProperties === true) additionalPropertiesNode = true;
1560
- else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode = parseSchema({ schema: additionalProperties }, rawOptions);
1561
- else if (additionalProperties === false) additionalPropertiesNode = false;
1562
- else if (additionalProperties) additionalPropertiesNode = _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1661
+ const additionalPropertiesNode = (() => {
1662
+ if (additionalProperties === true) return true;
1663
+ if (additionalProperties === false) return false;
1664
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1665
+ if (additionalProperties) return _kubb_ast.ast.factory.createSchema({ type: options.unknownType });
1666
+ })();
1563
1667
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1564
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1565
- const objectNode = _kubb_core.ast.createSchema({
1668
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_ast.ast.factory.createSchema({ type: options.unknownType }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1669
+ const objectNode = _kubb_ast.ast.factory.createSchema({
1566
1670
  type: "object",
1567
1671
  primitive: "object",
1568
1672
  properties,
@@ -1575,13 +1679,12 @@ function createSchemaParser(ctx) {
1575
1679
  if (isDiscriminator(schema) && schema.discriminator.mapping) {
1576
1680
  const discPropName = schema.discriminator.propertyName;
1577
1681
  const values = Object.keys(schema.discriminator.mapping);
1578
- const enumName = name ? _kubb_core.ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
1579
- return _kubb_core.ast.setDiscriminatorEnum({
1580
- node: objectNode,
1682
+ const enumName = name ? (0, _kubb_ast.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1683
+ return _kubb_ast.ast.applyMacros(objectNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1581
1684
  propertyName: discPropName,
1582
1685
  values,
1583
1686
  enumName
1584
- });
1687
+ })], { depth: "shallow" });
1585
1688
  }
1586
1689
  return objectNode;
1587
1690
  }
@@ -1590,8 +1693,8 @@ function createSchemaParser(ctx) {
1590
1693
  */
1591
1694
  function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1592
1695
  const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
1593
- const rest = schema.items ? parseSchema({ schema: schema.items }, rawOptions) : _kubb_core.ast.createSchema({ type: "any" });
1594
- return _kubb_core.ast.createSchema({
1696
+ const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? _kubb_ast.ast.factory.createSchema({ type: "any" }) : parseSchema({ schema: schema.items }, rawOptions);
1697
+ return _kubb_ast.ast.factory.createSchema({
1595
1698
  type: "tuple",
1596
1699
  primitive: "array",
1597
1700
  items: tupleItems,
@@ -1606,12 +1709,12 @@ function createSchemaParser(ctx) {
1606
1709
  */
1607
1710
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1608
1711
  const rawItems = schema.items;
1609
- const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
1712
+ const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast.enumPropName)(null, name, options.enumSuffix) : name;
1610
1713
  const items = rawItems ? [parseSchema({
1611
1714
  schema: rawItems,
1612
1715
  name: itemName
1613
1716
  }, rawOptions)] : [];
1614
- return _kubb_core.ast.createSchema({
1717
+ return _kubb_ast.ast.factory.createSchema({
1615
1718
  type: "array",
1616
1719
  primitive: "array",
1617
1720
  items,
@@ -1625,7 +1728,7 @@ function createSchemaParser(ctx) {
1625
1728
  * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1626
1729
  */
1627
1730
  function convertString({ schema, name, nullable, defaultValue }) {
1628
- return _kubb_core.ast.createSchema({
1731
+ return _kubb_ast.ast.factory.createSchema({
1629
1732
  type: "string",
1630
1733
  primitive: "string",
1631
1734
  min: schema.minLength,
@@ -1638,7 +1741,7 @@ function createSchemaParser(ctx) {
1638
1741
  * Converts a `type: 'number'` or `type: 'integer'` schema.
1639
1742
  */
1640
1743
  function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1641
- return _kubb_core.ast.createSchema({
1744
+ return _kubb_ast.ast.factory.createSchema({
1642
1745
  type,
1643
1746
  primitive: type,
1644
1747
  min: schema.minimum,
@@ -1653,32 +1756,132 @@ function createSchemaParser(ctx) {
1653
1756
  * Converts a `type: 'boolean'` schema.
1654
1757
  */
1655
1758
  function convertBoolean({ schema, name, nullable, defaultValue }) {
1656
- return _kubb_core.ast.createSchema({
1759
+ return _kubb_ast.ast.factory.createSchema({
1657
1760
  type: "boolean",
1658
1761
  primitive: "boolean",
1659
1762
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1660
1763
  });
1661
1764
  }
1662
1765
  /**
1663
- * Converts an explicit `type: 'null'` schema.
1766
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1767
+ * into a `blob` node.
1664
1768
  */
1665
- function convertNull({ schema, name, nullable }) {
1666
- return _kubb_core.ast.createSchema({
1667
- type: "null",
1668
- primitive: "null",
1669
- name,
1670
- title: schema.title,
1671
- description: schema.description,
1672
- deprecated: schema.deprecated,
1673
- nullable
1769
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1770
+ return _kubb_ast.ast.factory.createSchema({
1771
+ type: "blob",
1772
+ primitive: "string",
1773
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1774
+ });
1775
+ }
1776
+ /**
1777
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1778
+ *
1779
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1780
+ * falls through and handles it as that single type with nullability already folded in.
1781
+ */
1782
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1783
+ const types = schema.type;
1784
+ const nonNullTypes = types.filter((t) => t !== "null");
1785
+ if (nonNullTypes.length <= 1) return null;
1786
+ const arrayNullable = types.includes("null") || nullable || void 0;
1787
+ return _kubb_ast.ast.factory.createSchema({
1788
+ type: "union",
1789
+ members: nonNullTypes.map((t) => parseSchema({
1790
+ schema: {
1791
+ ...schema,
1792
+ type: t
1793
+ },
1794
+ name
1795
+ }, rawOptions)),
1796
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1674
1797
  });
1675
1798
  }
1676
1799
  /**
1677
- * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1800
+ * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1801
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1802
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1803
+ * match/convert/fall-through contract.
1804
+ */
1805
+ const schemaRules = [
1806
+ {
1807
+ match: ({ schema }) => isReference(schema),
1808
+ convert: convertRef
1809
+ },
1810
+ {
1811
+ match: ({ schema }) => !!schema.allOf?.length,
1812
+ convert: convertAllOf
1813
+ },
1814
+ {
1815
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1816
+ convert: convertUnion
1817
+ },
1818
+ {
1819
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1820
+ convert: convertConst
1821
+ },
1822
+ {
1823
+ match: ({ schema }) => !!schema.format,
1824
+ convert: convertFormat
1825
+ },
1826
+ {
1827
+ match: ({ schema }) => isBinary(schema),
1828
+ convert: convertBlob
1829
+ },
1830
+ {
1831
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1832
+ convert: convertMultiType
1833
+ },
1834
+ {
1835
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1836
+ convert: convertString
1837
+ },
1838
+ {
1839
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1840
+ convert: (ctx) => convertNumeric(ctx, "number")
1841
+ },
1842
+ {
1843
+ match: ({ schema }) => !!schema.enum?.length,
1844
+ convert: convertEnum
1845
+ },
1846
+ {
1847
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1848
+ convert: convertObject
1849
+ },
1850
+ {
1851
+ match: ({ schema }) => "prefixItems" in schema,
1852
+ convert: convertTuple
1853
+ },
1854
+ {
1855
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1856
+ convert: convertArray
1857
+ },
1858
+ {
1859
+ match: ({ type }) => type === "string",
1860
+ convert: convertString
1861
+ },
1862
+ {
1863
+ match: ({ type }) => type === "number",
1864
+ convert: (ctx) => convertNumeric(ctx, "number")
1865
+ },
1866
+ {
1867
+ match: ({ type }) => type === "integer",
1868
+ convert: (ctx) => convertNumeric(ctx, "integer")
1869
+ },
1870
+ {
1871
+ match: ({ type }) => type === "boolean",
1872
+ convert: convertBoolean
1873
+ },
1874
+ {
1875
+ match: ({ type }) => type === "null",
1876
+ convert: ({ schema, name, nullable }) => createNullSchema(schema, name, nullable)
1877
+ }
1878
+ ];
1879
+ /**
1880
+ * Converts an OAS `SchemaObject` into a `SchemaNode`.
1678
1881
  *
1679
- * Dispatch order (first match wins): `$ref` `allOf` `oneOf`/`anyOf` `const` → `format`
1680
- * octet-stream blob multi-type array constraint-inferred type `enum` object/array/tuple/scalar
1681
- * → empty-schema fallback (`emptySchemaType` option).
1882
+ * Builds the per-schema context, then walks the ordered {@link schemaRules} table and returns
1883
+ * the first converter that produces a node. When none match, falls back to the configured
1884
+ * `emptySchemaType`.
1682
1885
  */
1683
1886
  function parseSchema({ schema, name }, rawOptions) {
1684
1887
  const options = {
@@ -1691,80 +1894,52 @@ function createSchemaParser(ctx) {
1691
1894
  name
1692
1895
  }, rawOptions);
1693
1896
  const nullable = isNullable(schema) || void 0;
1694
- const defaultValue = schema.default === null && nullable ? void 0 : schema.default;
1695
- const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1696
- const ctx = {
1897
+ const schemaCtx = {
1697
1898
  schema,
1698
1899
  name,
1699
1900
  nullable,
1700
- defaultValue,
1701
- type,
1901
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1902
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1702
1903
  rawOptions,
1703
1904
  options
1704
1905
  };
1705
- if (isReference(schema)) return convertRef(ctx);
1706
- if (schema.allOf?.length) return convertAllOf(ctx);
1707
- if ([...schema.oneOf ?? [], ...schema.anyOf ?? []].length) return convertUnion(ctx);
1708
- if ("const" in schema && schema.const !== void 0) return convertConst(ctx);
1709
- if (schema.format) {
1710
- const formatResult = convertFormat(ctx);
1711
- if (formatResult) return formatResult;
1712
- }
1713
- if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return _kubb_core.ast.createSchema({
1714
- type: "blob",
1715
- primitive: "string",
1716
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1717
- });
1718
- if (Array.isArray(schema.type) && schema.type.length > 1) {
1719
- const nonNullTypes = schema.type.filter((t) => t !== "null");
1720
- const arrayNullable = schema.type.includes("null") || nullable || void 0;
1721
- if (nonNullTypes.length > 1) return _kubb_core.ast.createSchema({
1722
- type: "union",
1723
- members: nonNullTypes.map((t) => parseSchema({
1724
- schema: {
1725
- ...schema,
1726
- type: t
1727
- },
1728
- name
1729
- }, rawOptions)),
1730
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1731
- });
1906
+ for (const rule of schemaRules) {
1907
+ if (!rule.match(schemaCtx)) continue;
1908
+ const node = rule.convert(schemaCtx);
1909
+ if (node) return node;
1732
1910
  }
1733
- if (!type) {
1734
- if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
1735
- if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
1736
- }
1737
- if (schema.enum?.length) return convertEnum(ctx);
1738
- if (type === "object" || schema.properties || schema.additionalProperties || "patternProperties" in schema) return convertObject(ctx);
1739
- if ("prefixItems" in schema) return convertTuple(ctx);
1740
- if (type === "array" || "items" in schema) return convertArray(ctx);
1741
- if (type === "string") return convertString(ctx);
1742
- if (type === "number") return convertNumeric(ctx, "number");
1743
- if (type === "integer") return convertNumeric(ctx, "integer");
1744
- if (type === "boolean") return convertBoolean(ctx);
1745
- if (type === "null") return convertNull(ctx);
1746
- const emptyType = typeOptionMap.get(options.emptySchemaType);
1747
- return _kubb_core.ast.createSchema({
1911
+ const emptyType = options.emptySchemaType;
1912
+ return _kubb_ast.ast.factory.createSchema({
1748
1913
  type: emptyType,
1749
1914
  name,
1750
1915
  title: schema.title,
1751
- description: schema.description
1916
+ description: schema.description,
1917
+ format: schema.format
1752
1918
  });
1753
1919
  }
1754
1920
  /**
1755
1921
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1756
1922
  */
1757
- function parseParameter(options, param) {
1923
+ function parseParameter(options, param, parentName) {
1758
1924
  const required = param["required"] ?? false;
1759
- const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1760
- return _kubb_core.ast.createParameter({
1761
- name: param["name"],
1925
+ const paramName = param["name"];
1926
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
1927
+ const schema = param["schema"] ? parseSchema({
1928
+ schema: param["schema"],
1929
+ name: schemaName
1930
+ }, options) : _kubb_ast.ast.factory.createSchema({ type: options.unknownType });
1931
+ const style = param["style"];
1932
+ const explode = param["explode"];
1933
+ return _kubb_ast.ast.factory.createParameter({
1934
+ name: paramName,
1762
1935
  in: param["in"],
1763
1936
  schema: {
1764
1937
  ...schema,
1765
1938
  description: param["description"] ?? schema.description
1766
1939
  },
1767
- required
1940
+ required,
1941
+ ...style !== void 0 ? { style } : {},
1942
+ ...explode !== void 0 ? { explode } : {}
1768
1943
  });
1769
1944
  }
1770
1945
  /**
@@ -1780,73 +1955,97 @@ function createSchemaParser(ctx) {
1780
1955
  };
1781
1956
  }
1782
1957
  /**
1783
- * Reads the inline response object (not a `$ref`) and returns its description plus its `content` map.
1784
- */
1785
- function getResponseMeta(responseObj) {
1786
- if (typeof responseObj !== "object" || responseObj === null || Array.isArray(responseObj)) return {};
1787
- const inline = responseObj;
1788
- return {
1789
- description: inline.description,
1790
- content: inline.content
1791
- };
1792
- }
1793
- /**
1794
1958
  * Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
1795
1959
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1796
1960
  */
1797
1961
  function collectPropertyKeysByFlag(schema, flag) {
1798
- if (!schema?.properties) return void 0;
1962
+ if (!schema?.properties) return null;
1799
1963
  const keys = [];
1800
1964
  for (const key in schema.properties) {
1801
1965
  const prop = schema.properties[key];
1802
1966
  if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1803
1967
  }
1804
- return keys.length ? keys : void 0;
1968
+ return keys.length ? keys : null;
1805
1969
  }
1806
1970
  /**
1807
1971
  * Converts an OAS `Operation` into an `OperationNode`.
1808
1972
  */
1809
1973
  function parseOperation(options, operation) {
1810
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
1974
+ const operationId = getOperationId(operation);
1975
+ const operationName = operationId ? pascalCase(operationId) : void 0;
1976
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1811
1977
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1812
1978
  const requestBodyMeta = getRequestBodyMeta(operation);
1979
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1813
1980
  const content = allContentTypes.flatMap((ct) => {
1814
1981
  const schema = getRequestSchema(document, operation, { contentType: ct });
1815
1982
  if (!schema) return [];
1816
- return [{
1983
+ return [_kubb_ast.ast.factory.createContent({
1817
1984
  contentType: ct,
1818
- schema: _kubb_core.ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
1985
+ schema: _kubb_ast.ast.optionality(parseSchema({
1986
+ schema,
1987
+ name: requestBodyName
1988
+ }, options), requestBodyMeta.required),
1819
1989
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1820
- }];
1990
+ })];
1821
1991
  });
1822
1992
  const requestBody = content.length > 0 || requestBodyMeta.description ? {
1823
1993
  description: requestBodyMeta.description,
1824
1994
  required: requestBodyMeta.required || void 0,
1825
1995
  content: content.length > 0 ? content : void 0
1826
1996
  } : void 0;
1827
- const responses = operation.getResponseStatusCodes().map((statusCode) => {
1828
- const responseObj = operation.getResponseByStatusCode(statusCode);
1829
- const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
1830
- const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) });
1831
- const { description, content } = getResponseMeta(responseObj);
1832
- const mediaType = content ? getMediaType(Object.keys(content)[0] ?? "") : getMediaType(operation.contentType ?? "");
1833
- return _kubb_core.ast.createResponse({
1997
+ const responses = getResponseStatusCodes(operation).map((statusCode) => {
1998
+ const responseObj = getResponseByStatusCode({
1999
+ document,
2000
+ operation,
2001
+ statusCode
2002
+ });
2003
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2004
+ const description = typeof responseObj === "object" && responseObj !== null ? responseObj.description : void 0;
2005
+ const parseEntrySchema = (contentType) => {
2006
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
2007
+ return {
2008
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2009
+ schema: raw,
2010
+ name: responseName
2011
+ }, options) : _kubb_ast.ast.factory.createSchema({ type: options.emptySchemaType }),
2012
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2013
+ };
2014
+ };
2015
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => _kubb_ast.ast.factory.createContent({
2016
+ contentType,
2017
+ ...parseEntrySchema(contentType)
2018
+ }));
2019
+ if (content.length === 0) content.push(_kubb_ast.ast.factory.createContent({
2020
+ contentType: getRequestContentType({
2021
+ document,
2022
+ operation
2023
+ }) || "application/json",
2024
+ ...parseEntrySchema(ctx.contentType)
2025
+ }));
2026
+ return _kubb_ast.ast.factory.createResponse({
1834
2027
  statusCode,
1835
2028
  description,
1836
- schema,
1837
- mediaType,
1838
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
2029
+ content
1839
2030
  });
1840
2031
  });
1841
- const urlPath = new URLPath(operation.path);
1842
- return _kubb_core.ast.createOperation({
1843
- operationId: operation.getOperationId(),
2032
+ const pathItem = document.paths?.[operation.path];
2033
+ const pathItemDoc = pathItem && !isReference(pathItem) ? pathItem : void 0;
2034
+ const pickDoc = (key) => {
2035
+ const own = operation.schema[key];
2036
+ if (typeof own === "string") return own;
2037
+ const fallback = pathItemDoc?.[key];
2038
+ return typeof fallback === "string" ? fallback : void 0;
2039
+ };
2040
+ return _kubb_ast.ast.factory.createOperation({
2041
+ operationId,
2042
+ protocol: "http",
1844
2043
  method: operation.method.toUpperCase(),
1845
- path: urlPath.path,
1846
- tags: operation.getTags().map((tag) => tag.name),
1847
- summary: operation.getSummary() || void 0,
1848
- description: operation.getDescription() || void 0,
1849
- deprecated: operation.isDeprecated() || void 0,
2044
+ path: operation.path,
2045
+ tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
2046
+ summary: pickDoc("summary") || void 0,
2047
+ description: pickDoc("description") || void 0,
2048
+ deprecated: operation.schema.deprecated || void 0,
1850
2049
  parameters,
1851
2050
  requestBody,
1852
2051
  responses
@@ -1858,59 +2057,122 @@ function createSchemaParser(ctx) {
1858
2057
  parseParameter
1859
2058
  };
1860
2059
  }
2060
+ //#endregion
2061
+ //#region src/promoteEnums.ts
1861
2062
  /**
1862
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
1863
- *
1864
- * This is the main entry point for `@kubb/adapter-oas`. It converts OpenAPI/Swagger specs into a spec-agnostic tree
1865
- * that downstream plugins (`plugin-ts`, `plugin-zod`, etc.) consume for code generation. No code is generated here —
1866
- * the tree is a pure data structure representing all schemas and operations.
1867
- *
1868
- * Returns the AST root and a `nameMapping` for resolving schema references.
1869
- *
1870
- * @example
1871
- * ```ts
1872
- * import { parseOas } from '@kubb/adapter-oas'
1873
- *
1874
- * const document = await parseFromConfig(config)
1875
- * const { root, nameMapping } = parseOas(document, { dateType: 'date', contentType: 'application/json' })
1876
- * ```
2063
+ * Collects inline enums to lift to the top level, keyed by the name the parser derived for them
2064
+ * (e.g. `PetStatusEnum`). An enum already defined as a top-level component is left as-is, and a
2065
+ * name that recurs maps to the first definition so each name yields one shared type.
1877
2066
  */
1878
- function parseOas(document, options = {}) {
1879
- const { contentType, ...parserOptions } = options;
1880
- const mergedOptions = {
1881
- ...DEFAULT_PARSER_OPTIONS,
1882
- ...parserOptions
1883
- };
1884
- const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
1885
- const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
1886
- document,
1887
- contentType
2067
+ function collectInlineEnums(roots, topLevelNames) {
2068
+ const promoted = /* @__PURE__ */ new Map();
2069
+ for (const root of roots) {
2070
+ const isSchemaRoot = root.kind === "Schema";
2071
+ for (const node of _kubb_ast.ast.collect(root, { schema: (schemaNode) => schemaNode })) {
2072
+ if (node.type !== "enum" || !node.name) continue;
2073
+ if (isSchemaRoot && node === root) continue;
2074
+ if (topLevelNames.has(node.name)) continue;
2075
+ if (!promoted.has(node.name)) promoted.set(node.name, {
2076
+ ...node,
2077
+ optional: void 0,
2078
+ nullish: void 0
2079
+ });
2080
+ }
2081
+ }
2082
+ return promoted;
2083
+ }
2084
+ /**
2085
+ * Replaces every promoted inline enum in `node` with a `ref` to its lifted definition, keeping the
2086
+ * occurrence's usage-slot and documentation fields.
2087
+ */
2088
+ function refPromotedEnums(node, promoted) {
2089
+ if (promoted.size === 0) return node;
2090
+ return _kubb_ast.ast.transform(node, { schema(schemaNode) {
2091
+ if (schemaNode.type !== "enum" || !schemaNode.name || !promoted.has(schemaNode.name)) return void 0;
2092
+ return _kubb_ast.ast.factory.createSchema({
2093
+ type: "ref",
2094
+ name: schemaNode.name,
2095
+ ref: `${SCHEMA_REF_PREFIX}${schemaNode.name}`,
2096
+ optional: schemaNode.optional,
2097
+ nullish: schemaNode.nullish,
2098
+ readOnly: schemaNode.readOnly,
2099
+ writeOnly: schemaNode.writeOnly,
2100
+ deprecated: schemaNode.deprecated,
2101
+ description: schemaNode.description,
2102
+ default: schemaNode.default,
2103
+ examples: schemaNode.examples
2104
+ });
2105
+ } });
2106
+ }
2107
+ //#endregion
2108
+ //#region src/schemaDiagnostics.ts
2109
+ /**
2110
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
2111
+ * top-level schema. Walks the node the parser produced, threading the RFC 6901
2112
+ * pointer as it descends so a nested field reports against its full path
2113
+ * (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
2114
+ * resolved schema is reported under its own walk. Reports land in the active build run, are a
2115
+ * no-op outside one, and repeats are deduped by the build.
2116
+ */
2117
+ function reportSchemaDiagnostics({ node, name }) {
2118
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2119
+ }
2120
+ /**
2121
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2122
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2123
+ */
2124
+ function escapePointerToken(token) {
2125
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2126
+ }
2127
+ function visit(node, pointer) {
2128
+ if (node.deprecated) _kubb_core.Diagnostics.report({
2129
+ code: _kubb_core.Diagnostics.code.deprecated,
2130
+ severity: "info",
2131
+ message: "This schema is marked as deprecated.",
2132
+ location: {
2133
+ kind: "schema",
2134
+ pointer
2135
+ }
1888
2136
  });
1889
- const schemas = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({
1890
- schema,
1891
- name
1892
- }, mergedOptions));
1893
- const paths = new oas.default(document).getPaths();
1894
- const operations = Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null));
1895
- return {
1896
- root: _kubb_core.ast.createInput({
1897
- schemas,
1898
- operations
1899
- }),
1900
- nameMapping
1901
- };
2137
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
2138
+ code: _kubb_core.Diagnostics.code.unsupportedFormat,
2139
+ severity: "warning",
2140
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2141
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2142
+ location: {
2143
+ kind: "schema",
2144
+ pointer
2145
+ }
2146
+ });
2147
+ if (node.type === "object") {
2148
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2149
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2150
+ return;
2151
+ }
2152
+ if (node.type === "array") {
2153
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2154
+ return;
2155
+ }
2156
+ if (node.type === "tuple") {
2157
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2158
+ return;
2159
+ }
2160
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
1902
2161
  }
1903
2162
  //#endregion
1904
2163
  //#region src/adapter.ts
1905
2164
  /**
1906
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2165
+ * The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
1907
2166
  */
1908
2167
  const adapterOasName = "oas";
1909
2168
  /**
1910
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2169
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2170
+ * spec from `input` (a file path, URL, inline content, or parsed object), validates
2171
+ * it, resolves the base URL, and converts every schema and operation into the
2172
+ * universal AST that every downstream plugin consumes.
1911
2173
  *
1912
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1913
- * everything into an `InputNode` that downstream plugins consume.
2174
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2175
+ * integer width, server URL) apply to every plugin in the build.
1914
2176
  *
1915
2177
  * @example
1916
2178
  * ```ts
@@ -1919,26 +2181,135 @@ const adapterOasName = "oas";
1919
2181
  * import { pluginTs } from '@kubb/plugin-ts'
1920
2182
  *
1921
2183
  * export default defineConfig({
1922
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1923
- * input: { path: './openapi.yaml' },
2184
+ * input: './petStore.yaml',
2185
+ * output: { path: './src/gen' },
2186
+ * adapter: adapterOas({
2187
+ * server: { index: 0 },
2188
+ * discriminator: 'propagate',
2189
+ * dateType: 'date',
2190
+ * }),
1924
2191
  * plugins: [pluginTs()],
1925
2192
  * })
1926
2193
  * ```
1927
2194
  */
1928
2195
  const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1929
- 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;
2196
+ const { validate = true, contentType, server, discriminator = "preserve", enums = "inline", dateType = DEFAULT_PARSER_OPTIONS.dateType, integerType = DEFAULT_PARSER_OPTIONS.integerType, unknownType = DEFAULT_PARSER_OPTIONS.unknownType, enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix, emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType } = options;
2197
+ const parserOptions = {
2198
+ ...DEFAULT_PARSER_OPTIONS,
2199
+ dateType,
2200
+ integerType,
2201
+ unknownType,
2202
+ emptySchemaType,
2203
+ enumSuffix
2204
+ };
1930
2205
  let nameMapping = /* @__PURE__ */ new Map();
1931
- let parsedDocument;
1932
- let inputNode;
2206
+ let parsedDocument = null;
2207
+ const documentCache = /* @__PURE__ */ new WeakMap();
2208
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2209
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2210
+ function ensureDocument(source) {
2211
+ const cached = documentCache.get(source);
2212
+ if (cached) return cached;
2213
+ const promise = (async () => {
2214
+ const fresh = await parseFromConfig(source);
2215
+ if (validate) await validateDocument(fresh);
2216
+ parsedDocument = fresh;
2217
+ return fresh;
2218
+ })();
2219
+ documentCache.set(source, promise);
2220
+ return promise;
2221
+ }
2222
+ function ensureSchemas(document) {
2223
+ const cached = schemasCache.get(document);
2224
+ if (cached) return cached;
2225
+ const result = getSchemas(document, { contentType });
2226
+ nameMapping = result.nameMapping;
2227
+ schemasCache.set(document, result.schemas);
2228
+ return result.schemas;
2229
+ }
2230
+ function ensureSchemaParser(document) {
2231
+ const cached = schemaParserCache.get(document);
2232
+ if (cached) return cached;
2233
+ const parser = createSchemaParser({
2234
+ document,
2235
+ contentType
2236
+ });
2237
+ schemaParserCache.set(document, parser);
2238
+ return parser;
2239
+ }
2240
+ function parseInput({ document, schemas, parser }) {
2241
+ const { parseSchema, parseOperation } = parser;
2242
+ const parsedByName = /* @__PURE__ */ new Map();
2243
+ const refAliasMap = /* @__PURE__ */ new Map();
2244
+ const enumNames = [];
2245
+ const discriminatorParentNodes = [];
2246
+ for (const [name, schema] of Object.entries(schemas)) {
2247
+ const node = parseSchema({
2248
+ schema,
2249
+ name
2250
+ }, parserOptions);
2251
+ parsedByName.set(name, node);
2252
+ reportSchemaDiagnostics({
2253
+ node,
2254
+ name
2255
+ });
2256
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2257
+ if ((0, _kubb_ast.narrowSchema)(node, "enum") && node.name) enumNames.push(node.name);
2258
+ if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2259
+ }
2260
+ const circularNames = [...(0, _kubb_ast.findCircularSchemas)([...parsedByName.values()])];
2261
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2262
+ const operationNodes = [];
2263
+ for (const operation of getOperations(document)) {
2264
+ const operationNode = parseOperation(parserOptions, operation);
2265
+ if (operationNode) operationNodes.push(operationNode);
2266
+ }
2267
+ let promotedEnums = null;
2268
+ if (enums === "root") {
2269
+ promotedEnums = collectInlineEnums([...parsedByName.values(), ...operationNodes], new Set(Object.keys(schemas)));
2270
+ for (const name of promotedEnums.keys()) enumNames.push(name);
2271
+ }
2272
+ const schemaNodes = promotedEnums ? [...promotedEnums.values()] : [];
2273
+ for (const name of Object.keys(schemas)) {
2274
+ const alias = refAliasMap.get(name);
2275
+ let node;
2276
+ if (alias?.name && parsedByName.has(alias.name)) node = {
2277
+ ...parsedByName.get(alias.name),
2278
+ name
2279
+ };
2280
+ else {
2281
+ const parsed = parsedByName.get(name);
2282
+ const child = discriminatorChildMap?.get(name);
2283
+ node = child ? patchDiscriminatorNode(parsed, child) : parsed;
2284
+ }
2285
+ schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
2286
+ }
2287
+ const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
2288
+ return _kubb_ast.ast.factory.createInput({
2289
+ schemas: schemaNodes,
2290
+ operations,
2291
+ meta: {
2292
+ title: document.info?.title,
2293
+ description: document.info?.description,
2294
+ version: document.info?.version,
2295
+ baseURL: resolveBaseUrl({
2296
+ document,
2297
+ server
2298
+ }),
2299
+ circularNames,
2300
+ enumNames
2301
+ }
2302
+ });
2303
+ }
1933
2304
  return {
1934
2305
  name: "oas",
1935
2306
  get options() {
1936
2307
  return {
1937
2308
  validate,
1938
2309
  contentType,
1939
- serverIndex,
1940
- serverVariables,
2310
+ server,
1941
2311
  discriminator,
2312
+ enums,
1942
2313
  dateType,
1943
2314
  integerType,
1944
2315
  unknownType,
@@ -1950,71 +2321,34 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1950
2321
  get document() {
1951
2322
  return parsedDocument;
1952
2323
  },
1953
- get inputNode() {
1954
- return inputNode;
1955
- },
1956
2324
  async validate(input, options) {
2325
+ await assertInputExists(input);
1957
2326
  await validateDocument(await parseDocument(input), options);
1958
2327
  },
1959
2328
  getImports(node, resolve) {
1960
- return _kubb_core.ast.collectImports({
1961
- node,
1962
- nameMapping,
1963
- resolve: (schemaName) => {
1964
- const result = resolve(schemaName);
1965
- if (!result) return;
1966
- return _kubb_core.ast.createImport({
1967
- name: [result.name],
1968
- path: result.path
1969
- });
1970
- }
1971
- });
2329
+ return (0, _kubb_ast.collect)(node, { schema(schemaNode) {
2330
+ const schemaRef = (0, _kubb_ast.narrowSchema)(schemaNode, "ref");
2331
+ if (!schemaRef?.ref) return null;
2332
+ const result = resolve(nameMapping.get(schemaRef.ref) ?? (0, _kubb_ast.extractRefName)(schemaRef.ref));
2333
+ if (!result) return null;
2334
+ return _kubb_ast.ast.factory.createImport({
2335
+ name: [result.name],
2336
+ path: result.path
2337
+ });
2338
+ } });
1972
2339
  },
1973
2340
  async parse(source) {
1974
- const document = await parseFromConfig(source);
1975
- if (validate) await validateDocument(document);
1976
- const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
1977
- const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1978
- const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
1979
- contentType,
1980
- dateType,
1981
- integerType,
1982
- unknownType,
1983
- emptySchemaType,
1984
- enumSuffix
1985
- });
1986
- const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
1987
- nameMapping = parsedNameMapping;
1988
- parsedDocument = document;
1989
- inputNode = _kubb_core.ast.createInput({
1990
- ...node,
1991
- meta: {
1992
- title: document.info?.title,
1993
- description: document.info?.description,
1994
- version: document.info?.version,
1995
- baseURL
1996
- }
2341
+ const document = await ensureDocument(source);
2342
+ return parseInput({
2343
+ document,
2344
+ schemas: ensureSchemas(document),
2345
+ parser: ensureSchemaParser(document)
1997
2346
  });
1998
- return inputNode;
1999
2347
  }
2000
2348
  };
2001
2349
  });
2002
2350
  //#endregion
2003
- //#region src/types.ts
2004
- /**
2005
- * Maps uppercase HTTP method names to lowercase for backwards compatibility.
2006
- *
2007
- * @example
2008
- * ```ts
2009
- * HttpMethods['GET'] // 'get'
2010
- * HttpMethods['POST'] // 'post'
2011
- * ```
2012
- */
2013
- const HttpMethods = Object.fromEntries(Object.entries(_kubb_core.ast.httpMethods).map(([lower, upper]) => [upper, lower]));
2014
- //#endregion
2015
- exports.HttpMethods = HttpMethods;
2016
2351
  exports.adapterOas = adapterOas;
2017
2352
  exports.adapterOasName = adapterOasName;
2018
- exports.mergeDocuments = mergeDocuments;
2019
2353
 
2020
2354
  //# sourceMappingURL=index.cjs.map