@kubb/adapter-oas 5.0.0-beta.75 → 5.0.0-beta.76

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