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

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