@kubb/adapter-oas 5.0.0-beta.6 → 5.0.0-beta.60

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,15 +24,12 @@ 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 yaml = require("yaml");
30
+ let api_ref_bundler = require("api-ref-bundler");
31
+ let _kubb_ast_utils = require("@kubb/ast/utils");
32
+ let _kubb_ast = require("@kubb/ast");
36
33
  //#region src/constants.ts
37
34
  /**
38
35
  * Default parser options applied when no explicit options are provided.
@@ -64,6 +61,20 @@ const DEFAULT_PARSER_OPTIONS = {
64
61
  */
65
62
  const SCHEMA_REF_PREFIX = "#/components/schemas/";
66
63
  /**
64
+ * HTTP methods that count as operations on an OpenAPI path item. Other keys
65
+ * (`parameters`, `summary`, `$ref`, vendor extensions) are skipped when iterating operations.
66
+ */
67
+ const SUPPORTED_METHODS = new Set([
68
+ "get",
69
+ "put",
70
+ "post",
71
+ "delete",
72
+ "options",
73
+ "head",
74
+ "patch",
75
+ "trace"
76
+ ]);
77
+ /**
67
78
  * OpenAPI version string written into the stub document created during multi-spec merges.
68
79
  */
69
80
  const MERGE_OPENAPI_VERSION = "3.0.0";
@@ -103,7 +114,7 @@ const structuralKeys = new Set([
103
114
  *
104
115
  * Only formats whose AST type differs from the OAS `type` field appear here.
105
116
  * 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
117
+ * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname` and
107
118
  * `idn-hostname` map to `'url'` as the closest generic string-format type.
108
119
  *
109
120
  * @example
@@ -115,6 +126,18 @@ const structuralKeys = new Set([
115
126
  * formatMap['float'] // 'number'
116
127
  * ```
117
128
  */
129
+ /**
130
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
131
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
132
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
133
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
134
+ */
135
+ const specialCasedFormats = new Set([
136
+ "int64",
137
+ "date-time",
138
+ "date",
139
+ "time"
140
+ ]);
118
141
  const formatMap = {
119
142
  uuid: "uuid",
120
143
  email: "email",
@@ -144,8 +167,8 @@ const formatMap = {
144
167
  */
145
168
  const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
146
169
  /**
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()`.
170
+ * Maps the `unknownType`/`emptySchemaType` option string to its `ScalarSchemaType` constant.
171
+ * A `Map` (over a plain object) lets callers test key membership with `.has()`.
149
172
  */
150
173
  const typeOptionMap = new Map([
151
174
  ["any", _kubb_core.ast.schemaTypes.any],
@@ -153,87 +176,6 @@ const typeOptionMap = new Map([
153
176
  ["void", _kubb_core.ast.schemaTypes.void]
154
177
  ]);
155
178
  //#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.
165
- *
166
- * @example
167
- * ```ts
168
- * const { root } = parseOas(document, options)
169
- * const next = applyDiscriminatorInheritance(root)
170
- * ```
171
- */
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
- }
236
- //#endregion
237
179
  //#region ../../internals/utils/src/casing.ts
238
180
  /**
239
181
  * Shared implementation for camelCase and PascalCase conversion.
@@ -245,57 +187,132 @@ function applyDiscriminatorInheritance(root) {
245
187
  function toCamelOrPascal(text, pascal) {
246
188
  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
189
  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);
190
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
250
191
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
251
192
  }
252
193
  /**
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.
194
+ * Converts `text` to camelCase.
256
195
  *
257
- * Only splits on dots followed by a letter so that version numbers
258
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
196
+ * @example Word boundaries
197
+ * `camelCase('hello-world') // 'helloWorld'`
259
198
  *
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.
199
+ * @example With a prefix
200
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
265
201
  */
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("/");
202
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
203
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
269
204
  }
270
205
  /**
271
- * Converts `text` to camelCase.
272
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
206
+ * Converts `text` to PascalCase.
207
+ *
208
+ * @example Word boundaries
209
+ * `pascalCase('hello-world') // 'HelloWorld'`
210
+ *
211
+ * @example With a suffix
212
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
213
+ */
214
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
215
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
216
+ }
217
+ //#endregion
218
+ //#region ../../internals/utils/src/runtime.ts
219
+ /**
220
+ * Detects the JavaScript runtime executing the current process and exposes its name and version.
221
+ *
222
+ * Prefer the shared {@link runtime} instance over constructing your own.
223
+ */
224
+ var Runtime = class {
225
+ /**
226
+ * `true` when the current process is running under Bun.
227
+ *
228
+ * Detection keys off the global `Bun` object rather than `process.versions`,
229
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
230
+ * otherwise look like Node.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * if (runtime.isBun) {
235
+ * await Bun.write(path, data)
236
+ * }
237
+ * ```
238
+ */
239
+ get isBun() {
240
+ return typeof Bun !== "undefined";
241
+ }
242
+ /**
243
+ * `true` when the current process is running under Deno.
244
+ */
245
+ get isDeno() {
246
+ return typeof globalThis.Deno !== "undefined";
247
+ }
248
+ /**
249
+ * `true` when the current process is running under Node.
250
+ *
251
+ * Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
252
+ */
253
+ get isNode() {
254
+ return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
255
+ }
256
+ /**
257
+ * Name of the runtime executing the current process.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
262
+ * ```
263
+ */
264
+ get name() {
265
+ if (this.isBun) return "bun";
266
+ if (this.isDeno) return "deno";
267
+ return "node";
268
+ }
269
+ /**
270
+ * Version of the active runtime, or an empty string when it cannot be read.
271
+ *
272
+ * @example
273
+ * ```ts
274
+ * runtime.version // '1.3.11' under Bun, '22.22.2' under Node
275
+ * ```
276
+ */
277
+ get version() {
278
+ if (this.isBun) return process.versions.bun ?? "";
279
+ if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
280
+ return process.versions?.node ?? "";
281
+ }
282
+ };
283
+ /**
284
+ * Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
285
+ */
286
+ const runtime = new Runtime();
287
+ //#endregion
288
+ //#region ../../internals/utils/src/fs.ts
289
+ /**
290
+ * Resolves to `true` when the file or directory at `path` exists.
291
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
273
292
  *
274
293
  * @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);
294
+ * ```ts
295
+ * if (await exists('./kubb.config.ts')) {
296
+ * const content = await read('./kubb.config.ts')
297
+ * }
298
+ * ```
299
+ */
300
+ async function exists(path) {
301
+ if (runtime.isBun) return Bun.file(path).exists();
302
+ return (0, node_fs_promises.access)(path).then(() => true, () => false);
284
303
  }
285
304
  /**
286
- * Converts `text` to PascalCase.
287
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
305
+ * Reads the file at `path` as a UTF-8 string.
306
+ * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
288
307
  *
289
308
  * @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);
309
+ * ```ts
310
+ * const source = await read('./src/Pet.ts')
311
+ * ```
312
+ */
313
+ async function read(path) {
314
+ if (runtime.isBun) return Bun.file(path).text();
315
+ return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
299
316
  }
300
317
  //#endregion
301
318
  //#region ../../internals/utils/src/object.ts
@@ -432,102 +449,99 @@ const reservedWords = new Set([
432
449
  */
433
450
  function isValidVarName(name) {
434
451
  if (!name || reservedWords.has(name)) return false;
435
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
452
+ return isIdentifier(name);
436
453
  }
437
- //#endregion
438
- //#region ../../internals/utils/src/urlPath.ts
439
454
  /**
440
- * Parses and transforms an OpenAPI/Swagger path string into various URL formats.
455
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
456
+ *
457
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
458
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
459
+ * deciding whether an object key needs quoting.
441
460
  *
442
461
  * @example
443
- * const p = new URLPath('/pet/{petId}')
444
- * p.URL // '/pet/:petId'
445
- * p.template // '`/pet/${petId}`'
462
+ * ```ts
463
+ * isIdentifier('name') // true
464
+ * isIdentifier('x-total')// false
465
+ * ```
446
466
  */
447
- var URLPath = class {
448
- /**
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;
467
+ function isIdentifier(name) {
468
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
469
+ }
470
+ //#endregion
471
+ //#region ../../internals/utils/src/url.ts
472
+ function transformParam(raw, casing) {
473
+ const param = isValidVarName(raw) ? raw : camelCase(raw);
474
+ return casing === "camelcase" ? camelCase(param) : param;
475
+ }
476
+ function toParamsObject(path, { replacer, casing } = {}) {
477
+ const params = {};
478
+ for (const match of path.matchAll(/\{([^}]+)\}/g)) {
479
+ const param = transformParam(match[1], casing);
480
+ const key = replacer ? replacer(param) : param;
481
+ params[key] = key;
456
482
  }
457
- /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
483
+ return Object.keys(params).length > 0 ? params : null;
484
+ }
485
+ /**
486
+ * Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
487
+ */
488
+ var Url = class Url {
489
+ /**
490
+ * Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
458
491
  *
459
492
  * @example
460
- * ```ts
461
- * new URLPath('/pet/{petId}').URL // '/pet/:petId'
462
- * ```
493
+ * Url.canParse('https://petstore.swagger.io/v2') // true
494
+ * Url.canParse('/pet/{petId}') // false
463
495
  */
464
- get URL() {
465
- return this.toURLPath();
496
+ static canParse(url, base) {
497
+ return URL.canParse(url, base);
466
498
  }
467
- /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
499
+ /**
500
+ * Converts an OpenAPI/Swagger path to Express-style colon syntax.
468
501
  *
469
502
  * @example
470
- * ```ts
471
- * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
472
- * new URLPath('/pet/{petId}').isURL // false
473
- * ```
503
+ * Url.toPath('/pet/{petId}') // '/pet/:petId'
474
504
  */
475
- get isURL() {
476
- try {
477
- return !!new URL(this.path).href;
478
- } catch {
479
- return false;
480
- }
505
+ static toPath(path) {
506
+ return path.replace(/\{([^}]+)\}/g, ":$1");
481
507
  }
482
508
  /**
483
- * Converts the OpenAPI path to a TypeScript template literal string.
509
+ * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
510
+ * `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
511
+ * and `casing` controls parameter identifier casing.
484
512
  *
485
513
  * @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.
514
+ * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
493
515
  *
494
516
  * @example
495
- * ```ts
496
- * new URLPath('/pet/{petId}').object
497
- * // { url: '/pet/:petId', params: { petId: 'petId' } }
498
- * ```
517
+ * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
499
518
  */
500
- get object() {
501
- return this.toObject();
519
+ static toTemplateString(path, { prefix, replacer, casing } = {}) {
520
+ const result = path.split(/\{([^}]+)\}/).map((part, i) => {
521
+ if (i % 2 === 0) return part;
522
+ const param = transformParam(part, casing);
523
+ return `\${${replacer ? replacer(param) : param}}`;
524
+ }).join("");
525
+ return `\`${prefix ?? ""}${result}\``;
502
526
  }
503
- /** Returns a map of path parameter names, or `undefined` when the path has no parameters.
527
+ /**
528
+ * Returns the path and its extracted params as a structured `URLObject`, or as a stringified
529
+ * expression when `stringify` is set.
504
530
  *
505
531
  * @example
506
- * ```ts
507
- * new URLPath('/pet/{petId}').params // { petId: 'petId' }
508
- * new URLPath('/pet').params // undefined
509
- * ```
510
- */
511
- get params() {
512
- return this.getParams();
513
- }
514
- #transformParam(raw) {
515
- const param = isValidVarName(raw) ? raw : camelCase(raw);
516
- return this.#options.casing === "camelcase" ? camelCase(param) : param;
517
- }
518
- /**
519
- * Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
532
+ * Url.toObject('/pet/{petId}')
533
+ * // { url: '/pet/:petId', params: { petId: 'petId' } }
520
534
  */
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 } = {}) {
535
+ static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
528
536
  const object = {
529
- url: type === "path" ? this.toURLPath() : this.toTemplateString({ replacer }),
530
- params: this.getParams()
537
+ url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
538
+ replacer,
539
+ casing
540
+ }),
541
+ params: toParamsObject(path, {
542
+ replacer,
543
+ casing
544
+ })
531
545
  };
532
546
  if (stringify) {
533
547
  if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
@@ -536,51 +550,52 @@ var URLPath = class {
536
550
  }
537
551
  return object;
538
552
  }
539
- /**
540
- * Converts the OpenAPI path to a TypeScript template literal string.
541
- * An optional `replacer` can transform each extracted parameter name before interpolation.
542
- *
543
- * @example
544
- * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
545
- */
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("")}\``;
552
- }
553
- /**
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.
557
- *
558
- * @example
559
- * ```ts
560
- * new URLPath('/pet/{petId}/tag/{tagId}').getParams()
561
- * // { petId: 'petId', tagId: 'tagId' }
562
- * ```
563
- */
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;
571
- }
572
- /** Converts the OpenAPI path to Express-style colon syntax.
573
- *
574
- * @example
575
- * ```ts
576
- * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
577
- * ```
578
- */
579
- toURLPath() {
580
- return this.path.replace(/\{([^}]+)\}/g, ":$1");
581
- }
582
553
  };
583
554
  //#endregion
555
+ //#region src/bundler.ts
556
+ const urlRegExp = /^https?:\/+/i;
557
+ async function readSource(sourcePath) {
558
+ if (urlRegExp.test(sourcePath)) {
559
+ const url = new URL(sourcePath);
560
+ const response = await fetch(url);
561
+ if (!response.ok) throw new Error(`Cannot fetch the OAS document at ${url.href} (HTTP ${response.status})`);
562
+ return response.text();
563
+ }
564
+ return read(sourcePath);
565
+ }
566
+ async function resolveSource(sourcePath) {
567
+ const data = await readSource(sourcePath);
568
+ if (sourcePath.toLowerCase().endsWith(".md")) return data;
569
+ return (0, yaml.parse)(data);
570
+ }
571
+ /**
572
+ * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
573
+ *
574
+ * External file schemas are hoisted into named `components.schemas` entries, so a property
575
+ * pointing at `./schemas/User.yaml` ends up referencing `#/components/schemas/User`. Generators
576
+ * can then emit a named type with an import instead of inlining the shape. Sources are read with
577
+ * the Bun-aware `read` util for local YAML and JSON files, and with `fetch` for HTTP(S) URLs.
578
+ *
579
+ * @example Local file
580
+ * `const document = await bundleDocument('./openapi.yaml')`
581
+ *
582
+ * @example Remote URL
583
+ * `const document = await bundleDocument('https://example.com/openapi.yaml')`
584
+ */
585
+ async function bundleDocument(pathOrUrl) {
586
+ const cache = /* @__PURE__ */ new Map();
587
+ const resolver = (sourcePath) => {
588
+ const key = urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
589
+ const cached = cache.get(key);
590
+ if (cached) return cached;
591
+ const result = resolveSource(sourcePath);
592
+ cache.set(key, result);
593
+ return result;
594
+ };
595
+ await resolver(pathOrUrl);
596
+ return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
597
+ }
598
+ //#endregion
584
599
  //#region src/guards.ts
585
600
  /**
586
601
  * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
@@ -645,9 +660,10 @@ function isDiscriminator(obj) {
645
660
  /**
646
661
  * Loads and dereferences an OpenAPI document, returning the raw `Document`.
647
662
  *
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`.
663
+ * Accepts a file path string or an already-parsed document object. File paths and URLs are
664
+ * bundled via `api-ref-bundler`, hoisting external file schemas into named `components.schemas`
665
+ * entries so generators can emit named types and imports. Swagger 2.0 documents are
666
+ * automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
651
667
  *
652
668
  * @example
653
669
  * ```ts
@@ -655,21 +671,12 @@ function isDiscriminator(obj) {
655
671
  * const document = await parse(rawDocumentObject, { canBundle: false })
656
672
  * ```
657
673
  */
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();
674
+ async function parseDocument(pathOrApi, { canBundle = true } = {}) {
675
+ if (typeof pathOrApi === "string" && canBundle) return parseDocument(await bundleDocument(pathOrApi), { canBundle: false });
676
+ const document = typeof pathOrApi === "string" ? (0, yaml.parse)(pathOrApi) : pathOrApi;
671
677
  if (isOpenApiV2Document(document)) {
672
- const { openapi } = await swagger2openapi.default.convertObj(document, { anchors: true });
678
+ const { default: swagger2openapi } = await import("swagger2openapi");
679
+ const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
673
680
  return openapi;
674
681
  }
675
682
  return document;
@@ -677,7 +684,7 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
677
684
  /**
678
685
  * Deep-merges multiple OpenAPI documents into a single `Document`.
679
686
  *
680
- * Each document is parsed independently then recursively merged via `mergeDeep` from `@internals/utils`.
687
+ * Each document is parsed independently, then deep-merged into one in array order.
681
688
  * Throws when the input array is empty.
682
689
  *
683
690
  * @example
@@ -686,12 +693,14 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
686
693
  * ```
687
694
  */
688
695
  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.");
696
+ const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, { canBundle: false })));
697
+ if (documents.length === 0) throw new _kubb_core.Diagnostics.Error({
698
+ code: _kubb_core.Diagnostics.code.inputRequired,
699
+ severity: "error",
700
+ message: "No OAS documents were provided for merging.",
701
+ help: "Pass at least one path or document to `input.path`.",
702
+ location: { kind: "config" }
703
+ });
695
704
  const seed = {
696
705
  openapi: MERGE_OPENAPI_VERSION,
697
706
  info: {
@@ -707,9 +716,9 @@ async function mergeDocuments(pathOrApi) {
707
716
  * Creates a `Document` from an `AdapterSource`.
708
717
  *
709
718
  * 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.
719
+ * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
720
+ * - `{ type: 'paths' }` merges multiple file paths into a single document.
721
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
713
722
  *
714
723
  * @example
715
724
  * ```ts
@@ -717,75 +726,410 @@ async function mergeDocuments(pathOrApi) {
717
726
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
718
727
  * ```
719
728
  */
720
- function parseFromConfig(source) {
729
+ async function parseFromConfig(source) {
721
730
  if (source.type === "data") {
722
731
  if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
723
732
  return parseDocument(source.data, { canBundle: false });
724
733
  }
725
734
  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));
735
+ if (Url.canParse(source.path)) return parseDocument(source.path);
736
+ const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
737
+ await assertInputExists(resolved);
738
+ return parseDocument(resolved);
739
+ }
740
+ /**
741
+ * Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
742
+ * URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
743
+ * its parse error instead.
744
+ */
745
+ async function assertInputExists(input) {
746
+ if (Url.canParse(input)) return;
747
+ if (!await exists(input)) throw new _kubb_core.Diagnostics.Error({
748
+ code: _kubb_core.Diagnostics.code.inputNotFound,
749
+ severity: "error",
750
+ message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
751
+ help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
752
+ location: { kind: "config" }
753
+ });
754
+ }
755
+ /**
756
+ * Validates an OpenAPI document using `@readme/openapi-parser` with colorized error output.
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * await validateDocument(document)
761
+ * ```
762
+ */
763
+ async function validateDocument(document, { throwOnError = false } = {}) {
764
+ try {
765
+ const result = await (0, _readme_openapi_parser.validate)(structuredClone(document), { validate: { errors: { colorize: true } } });
766
+ if (!result.valid) throw new Error((0, _readme_openapi_parser.compileErrors)(result));
767
+ } catch (error) {
768
+ if (throwOnError) throw error;
769
+ }
770
+ }
771
+ //#endregion
772
+ //#region src/refs.ts
773
+ const _refCache = /* @__PURE__ */ new WeakMap();
774
+ /**
775
+ * Resolves a local JSON pointer reference from a document.
776
+ *
777
+ * Accepts `#/...` refs. Returns `null` for empty or non-local refs.
778
+ * Throws when the pointer cannot be resolved.
779
+ *
780
+ * @example
781
+ * ```ts
782
+ * resolveRef<SchemaObject>(document, '#/components/schemas/Pet') // SchemaObject | null
783
+ * ```
784
+ */
785
+ function resolveRef(document, $ref) {
786
+ const origRef = $ref;
787
+ $ref = $ref.trim();
788
+ if ($ref === "") return null;
789
+ if (!$ref.startsWith("#")) return null;
790
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
791
+ let docCache = _refCache.get(document);
792
+ if (!docCache) {
793
+ docCache = /* @__PURE__ */ new Map();
794
+ _refCache.set(document, docCache);
795
+ }
796
+ if (docCache.has($ref)) return docCache.get($ref);
797
+ const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
798
+ if (!current) {
799
+ const diagnostic = {
800
+ code: _kubb_core.Diagnostics.code.refNotFound,
801
+ severity: "error",
802
+ message: `Could not find a definition for ${origRef}.`,
803
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
804
+ location: {
805
+ kind: "schema",
806
+ pointer: origRef,
807
+ ref: origRef
808
+ }
809
+ };
810
+ if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.Diagnostics.Error(diagnostic);
811
+ return null;
812
+ }
813
+ docCache.set($ref, current);
814
+ return current;
815
+ }
816
+ /**
817
+ * Resolves a `$ref` object while preserving the original `$ref` field on the result.
818
+ *
819
+ * Useful for parser flows that need both dereferenced fields and pointer
820
+ * identity (for naming/import purposes). Non-reference values are returned as-is.
821
+ *
822
+ * @example
823
+ * ```ts
824
+ * dereferenceWithRef(document, { $ref: '#/components/schemas/Pet' })
825
+ * // { $ref: '#/components/schemas/Pet', type: 'object', properties: { ... } }
826
+ * ```
827
+ */
828
+ function dereferenceWithRef(document, schema) {
829
+ if (isReference(schema)) return {
830
+ ...schema,
831
+ ...resolveRef(document, schema.$ref),
832
+ $ref: schema.$ref
833
+ };
834
+ return schema;
835
+ }
836
+ //#endregion
837
+ //#region src/dialect.ts
838
+ /**
839
+ * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
840
+ *
841
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
842
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
843
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
844
+ * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
845
+ * nullability, no discriminator object, binary via `contentEncoding` and reuses
846
+ * the rest unchanged.
847
+ *
848
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
849
+ * JSON Schema vocabulary, so the converters keep that common case.
850
+ *
851
+ * @example
852
+ * ```ts
853
+ * const parser = createSchemaParser(context) // uses oasDialect
854
+ * const parser = createSchemaParser(context, oasDialect) // explicit
855
+ * ```
856
+ */
857
+ const oasDialect = _kubb_core.ast.defineSchemaDialect({
858
+ name: "oas",
859
+ isNullable,
860
+ isReference,
861
+ isDiscriminator,
862
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
863
+ resolveRef
864
+ });
865
+ //#endregion
866
+ //#region src/discriminator.ts
867
+ /**
868
+ * Builds a map of child schema names → discriminator patch data by scanning the given
869
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
870
+ *
871
+ * The streaming path calls this on a small pre-parsed subset of schemas (only the
872
+ * discriminator parents) rather than on all schemas at once.
873
+ */
874
+ function buildDiscriminatorChildMap(schemas) {
875
+ const childMap = /* @__PURE__ */ new Map();
876
+ for (const schema of schemas) {
877
+ let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
878
+ if (!unionNode) {
879
+ const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
880
+ if (intersectionMembers) for (const m of intersectionMembers) {
881
+ const u = _kubb_core.ast.narrowSchema(m, "union");
882
+ if (u) {
883
+ unionNode = u;
884
+ break;
885
+ }
886
+ }
887
+ }
888
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
889
+ const { discriminatorPropertyName, members } = unionNode;
890
+ for (const member of members) {
891
+ const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
892
+ if (!intersectionNode?.members) continue;
893
+ let refNode = null;
894
+ let objNode = null;
895
+ for (const m of intersectionNode.members) {
896
+ refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
897
+ objNode ??= _kubb_core.ast.narrowSchema(m, "object");
898
+ }
899
+ if (!refNode?.name || !objNode) continue;
900
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
901
+ const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : null;
902
+ if (!enumNode?.enumValues?.length) continue;
903
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
904
+ if (!enumValues.length) continue;
905
+ const existing = childMap.get(refNode.name);
906
+ if (!existing) {
907
+ childMap.set(refNode.name, {
908
+ propertyName: discriminatorPropertyName,
909
+ enumValues: [...enumValues]
910
+ });
911
+ continue;
912
+ }
913
+ existing.enumValues.push(...enumValues);
914
+ }
915
+ }
916
+ return childMap;
917
+ }
918
+ /**
919
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
920
+ * the discriminant property). Used by the streaming path to apply patches inline per yield
921
+ * without buffering all schemas.
922
+ */
923
+ function patchDiscriminatorNode(node, entry) {
924
+ const objectNode = _kubb_core.ast.narrowSchema(node, "object");
925
+ if (!objectNode) return node;
926
+ const { propertyName, enumValues } = entry;
927
+ const enumSchema = _kubb_core.ast.factory.createSchema({
928
+ type: "enum",
929
+ enumValues
930
+ });
931
+ const newProp = _kubb_core.ast.factory.createProperty({
932
+ name: propertyName,
933
+ required: true,
934
+ schema: enumSchema
935
+ });
936
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
937
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
938
+ return {
939
+ ...objectNode,
940
+ properties: newProperties
941
+ };
942
+ }
943
+ /**
944
+ * Creates a single-property object schema used as a discriminator literal.
945
+ *
946
+ * @example
947
+ * ```ts
948
+ * createDiscriminantNode({ propertyName: 'type', value: 'dog' })
949
+ * // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
950
+ * ```
951
+ */
952
+ function createDiscriminantNode({ propertyName, value }) {
953
+ return _kubb_core.ast.factory.createSchema({
954
+ type: "object",
955
+ primitive: "object",
956
+ properties: [_kubb_core.ast.factory.createProperty({
957
+ name: propertyName,
958
+ schema: _kubb_core.ast.factory.createSchema({
959
+ type: "enum",
960
+ primitive: "string",
961
+ enumValues: [value]
962
+ }),
963
+ required: true
964
+ })]
965
+ });
966
+ }
967
+ /**
968
+ * Returns the discriminator key whose mapping value matches `ref`, or `null` when there is no match.
969
+ *
970
+ * @example
971
+ * ```ts
972
+ * findDiscriminator({ dog: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // 'dog'
973
+ * ```
974
+ */
975
+ function findDiscriminator(mapping, ref) {
976
+ if (!mapping || !ref) return null;
977
+ return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
978
+ }
979
+ //#endregion
980
+ //#region src/mime.ts
981
+ /**
982
+ * MIME type fragments that mark a media type as JSON-like.
983
+ *
984
+ * Matches the substrings used by the `oas` library's `matchesMimeType.json`, so a content type is
985
+ * JSON when it contains any of these. The `+json` entry catches structured-syntax suffixes such as
986
+ * `application/vnd.api+json`.
987
+ */
988
+ const jsonMimeFragments = [
989
+ "application/json",
990
+ "application/x-json",
991
+ "text/json",
992
+ "text/x-json",
993
+ "+json"
994
+ ];
995
+ /**
996
+ * Returns `true` when a media type string is JSON-like.
997
+ *
998
+ * @example
999
+ * ```ts
1000
+ * isJsonMimeType('application/json') // true
1001
+ * isJsonMimeType('application/vnd.api+json') // true
1002
+ * isJsonMimeType('multipart/form-data') // false
1003
+ * ```
1004
+ */
1005
+ function isJsonMimeType(mimeType) {
1006
+ return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
1007
+ }
1008
+ //#endregion
1009
+ //#region src/operation.ts
1010
+ /**
1011
+ * Slugifies a path for the `operationId` fallback: non-alphanumerics collapse to single dashes,
1012
+ * with no leading or trailing dash.
1013
+ */
1014
+ function slugify(value) {
1015
+ return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
1016
+ }
1017
+ /**
1018
+ * Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
1019
+ */
1020
+ function getOperationId({ path, method, schema }) {
1021
+ const { operationId } = schema;
1022
+ if (typeof operationId === "string" && operationId.length > 0) return operationId;
1023
+ return `${method}_${slugify(path).toLowerCase()}`;
1024
+ }
1025
+ /**
1026
+ * Returns the declared response status codes, skipping `x-` extensions and non-object entries.
1027
+ */
1028
+ function getResponseStatusCodes({ schema }) {
1029
+ const responses = schema.responses;
1030
+ if (!responses || isReference(responses)) return [];
1031
+ return Object.keys(responses).filter((key) => !key.startsWith("x-") && !!responses[key] && typeof responses[key] === "object");
1032
+ }
1033
+ /**
1034
+ * Returns the response object for a status code, resolving a `$ref` in place. `false` when absent.
1035
+ */
1036
+ function getResponseByStatusCode({ document, operation, statusCode }) {
1037
+ const responses = operation.schema.responses;
1038
+ if (!responses || isReference(responses)) return false;
1039
+ const response = responses[statusCode];
1040
+ if (!response) return false;
1041
+ if (isReference(response)) {
1042
+ const resolved = resolveRef(document, response.$ref);
1043
+ responses[statusCode] = resolved;
1044
+ if (!resolved || isReference(resolved)) return false;
1045
+ return resolved;
1046
+ }
1047
+ return response;
728
1048
  }
729
1049
  /**
730
- * Validates an OpenAPI document using `oas-normalize` with colorized error output.
731
- *
732
- * @example
733
- * ```ts
734
- * await validateDocument(document)
735
- * ```
1050
+ * Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
1051
+ * `undefined` when the operation has no request body.
736
1052
  */
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;
1053
+ function getRequestBodyContent({ document, operation }) {
1054
+ const { schema } = operation;
1055
+ let requestBody = schema.requestBody;
1056
+ if (!requestBody) return;
1057
+ if (isReference(requestBody)) {
1058
+ const resolved = resolveRef(document, requestBody.$ref);
1059
+ schema.requestBody = resolved;
1060
+ if (!resolved || isReference(resolved)) return;
1061
+ requestBody = resolved;
745
1062
  }
1063
+ return requestBody.content;
746
1064
  }
747
- //#endregion
748
- //#region src/refs.ts
749
1065
  /**
750
- * Resolves a local JSON pointer reference from a document.
751
- *
752
- * Accepts `#/...` refs. Returns `null` for empty or non-local refs.
753
- * Throws when the pointer cannot be resolved.
754
- *
755
- * @example
756
- * ```ts
757
- * resolveRef<SchemaObject>(document, '#/components/schemas/Pet') // SchemaObject | null
758
- * ```
1066
+ * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
1067
+ * Otherwise picks the first JSON-like media type, then the first declared one, as a
1068
+ * `[mediaType, object]` tuple.
759
1069
  */
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;
1070
+ function getRequestContent({ document, operation, mediaType }) {
1071
+ const content = getRequestBodyContent({
1072
+ document,
1073
+ operation
1074
+ });
1075
+ if (!content) return false;
1076
+ if (mediaType) return mediaType in content ? content[mediaType] : false;
1077
+ const mediaTypes = Object.keys(content);
1078
+ const available = mediaTypes.find((mt) => isJsonMimeType(mt)) ?? mediaTypes[0];
1079
+ return available ? [available, content[available]] : false;
769
1080
  }
770
1081
  /**
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.
1082
+ * Returns the primary request content type. Prefers a JSON-like media type (the last one wins,
1083
+ * matching the previous behavior), then the first declared one, defaulting to `'application/json'`.
1084
+ */
1085
+ function getRequestContentType({ document, operation }) {
1086
+ const content = getRequestBodyContent({
1087
+ document,
1088
+ operation
1089
+ });
1090
+ const mediaTypes = content ? Object.keys(content) : [];
1091
+ let result = mediaTypes[0] ?? "application/json";
1092
+ for (const mt of mediaTypes) if (isJsonMimeType(mt)) result = mt;
1093
+ return result;
1094
+ }
1095
+ /**
1096
+ * Builds an `Operation` for every supported HTTP method on every path, in document order.
1097
+ * `x-` path keys and unresolvable path-item `$ref`s are skipped.
775
1098
  *
776
1099
  * @example
777
1100
  * ```ts
778
- * dereferenceWithRef(document, { $ref: '#/components/schemas/Pet' })
779
- * // { $ref: '#/components/schemas/Pet', type: 'object', properties: { ... } }
1101
+ * for (const operation of getOperations(document)) {
1102
+ * parseOperation(options, operation)
1103
+ * }
780
1104
  * ```
781
1105
  */
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;
1106
+ function getOperations(document) {
1107
+ const operations = [];
1108
+ const paths = document.paths;
1109
+ if (!paths) return operations;
1110
+ for (const path of Object.keys(paths)) {
1111
+ if (path.startsWith("x-")) continue;
1112
+ let pathItem = paths[path];
1113
+ if (!pathItem) continue;
1114
+ if (isReference(pathItem)) {
1115
+ const resolved = resolveRef(document, pathItem.$ref);
1116
+ paths[path] = resolved;
1117
+ if (!resolved || isReference(resolved)) continue;
1118
+ pathItem = resolved;
1119
+ }
1120
+ const item = pathItem;
1121
+ for (const method of Object.keys(item)) {
1122
+ if (!SUPPORTED_METHODS.has(method)) continue;
1123
+ const schema = item[method];
1124
+ if (!schema || typeof schema !== "object") continue;
1125
+ operations.push({
1126
+ path,
1127
+ method,
1128
+ schema
1129
+ });
1130
+ }
1131
+ }
1132
+ return operations;
789
1133
  }
790
1134
  //#endregion
791
1135
  //#region src/resolvers.ts
@@ -809,7 +1153,16 @@ function resolveServerUrl(server, overrides) {
809
1153
  for (const [key, variable] of Object.entries(server.variables)) {
810
1154
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
811
1155
  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(", ")}.`);
1156
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.Diagnostics.Error({
1157
+ code: _kubb_core.Diagnostics.code.invalidServerVariable,
1158
+ severity: "error",
1159
+ message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
1160
+ help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
1161
+ location: {
1162
+ kind: "document",
1163
+ pointer: "#/servers"
1164
+ }
1165
+ });
813
1166
  url = url.replaceAll(`{${key}}`, value);
814
1167
  }
815
1168
  return url;
@@ -822,6 +1175,15 @@ function getSchemaType(format) {
822
1175
  return formatMap[format] ?? null;
823
1176
  }
824
1177
  /**
1178
+ * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
1179
+ * `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
1180
+ * base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
1181
+ * diagnostic in step with the parser as `formatMap` grows.
1182
+ */
1183
+ function isHandledFormat(format) {
1184
+ return getSchemaType(format) !== null || specialCasedFormats.has(format);
1185
+ }
1186
+ /**
825
1187
  * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
826
1188
  * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
827
1189
  */
@@ -831,12 +1193,6 @@ function getPrimitiveType(type) {
831
1193
  return "string";
832
1194
  }
833
1195
  /**
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
1196
  * Returns all parameters for an operation, merging path-level and operation-level entries.
841
1197
  * Operation-level parameters override path-level ones with the same `in:name` key.
842
1198
  * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
@@ -868,7 +1224,7 @@ function getResponseBody(responseBody, contentType) {
868
1224
  }
869
1225
  let availableContentType;
870
1226
  const contentTypes = Object.keys(body.content);
871
- for (const mt of contentTypes) if (oas_utils.matchesMimeType.json(mt)) {
1227
+ for (const mt of contentTypes) if (isJsonMimeType(mt)) {
872
1228
  availableContentType = mt;
873
1229
  break;
874
1230
  }
@@ -899,7 +1255,11 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
899
1255
  if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
900
1256
  }
901
1257
  }
902
- const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
1258
+ const responseBody = getResponseBody(getResponseByStatusCode({
1259
+ document,
1260
+ operation,
1261
+ statusCode
1262
+ }), options.contentType);
903
1263
  if (responseBody === false) return {};
904
1264
  const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
905
1265
  if (!schema) return {};
@@ -915,7 +1275,11 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
915
1275
  */
916
1276
  function getRequestSchema(document, operation, options = {}) {
917
1277
  if (operation.schema.requestBody) operation.schema.requestBody = dereferenceWithRef(document, operation.schema.requestBody);
918
- const requestBody = operation.getRequestBody(options.contentType);
1278
+ const requestBody = getRequestContent({
1279
+ document,
1280
+ operation,
1281
+ mediaType: options.contentType
1282
+ });
919
1283
  if (requestBody === false) return null;
920
1284
  const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
921
1285
  if (!schema) return null;
@@ -924,7 +1288,7 @@ function getRequestSchema(document, operation, options = {}) {
924
1288
  /**
925
1289
  * Flattens a keyword-only `allOf` into its parent schema.
926
1290
  *
927
- * Only flattens when every member is a plain fragment no `$ref` and no structural keywords
1291
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
928
1292
  * (see `structuralKeys`). Outer schema values take precedence over fragment values.
929
1293
  * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
930
1294
  *
@@ -934,7 +1298,7 @@ function getRequestSchema(document, operation, options = {}) {
934
1298
  * // { type: 'object', properties: {}, description: 'A pet' }
935
1299
  *
936
1300
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
937
- * // returned unchanged contains a $ref
1301
+ * // returned unchanged, contains a $ref
938
1302
  * ```
939
1303
  */
940
1304
  /**
@@ -950,7 +1314,7 @@ function hasStructuralKeywords(fragment) {
950
1314
  function flattenSchema(schema) {
951
1315
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
952
1316
  const allOfFragments = schema.allOf;
953
- if (allOfFragments.some((item) => (0, oas_types.isRef)(item))) return schema;
1317
+ if (allOfFragments.some((item) => isReference(item))) return schema;
954
1318
  if (allOfFragments.some(hasStructuralKeywords)) return schema;
955
1319
  const merged = { ...schema };
956
1320
  delete merged.allOf;
@@ -960,7 +1324,7 @@ function flattenSchema(schema) {
960
1324
  /**
961
1325
  * Extracts the inline schema from a media-type `content` map.
962
1326
  *
963
- * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
1327
+ * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
964
1328
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
965
1329
  *
966
1330
  * @example
@@ -979,21 +1343,22 @@ function extractSchemaFromContent(content, preferredContentType) {
979
1343
  /**
980
1344
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
981
1345
  */
982
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1346
+ function* collectRefs(schema) {
983
1347
  if (Array.isArray(schema)) {
984
- for (const item of schema) collectRefs(item, refs);
985
- return refs;
1348
+ for (const item of schema) yield* collectRefs(item);
1349
+ return;
986
1350
  }
987
1351
  if (schema && typeof schema === "object") for (const key in schema) {
988
1352
  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);
1353
+ if (!(key === "$ref" && typeof value === "string")) {
1354
+ yield* collectRefs(value);
1355
+ continue;
1356
+ }
1357
+ if (value.startsWith("#/components/schemas/")) {
1358
+ const name = value.slice(21);
1359
+ if (name) yield name;
1360
+ }
995
1361
  }
996
- return refs;
997
1362
  }
998
1363
  /**
999
1364
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -1009,7 +1374,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1009
1374
  */
1010
1375
  function sortSchemas(schemas) {
1011
1376
  const deps = /* @__PURE__ */ new Map();
1012
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
1377
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
1013
1378
  const sorted = [];
1014
1379
  const visited = /* @__PURE__ */ new Set();
1015
1380
  function visit(name, stack) {
@@ -1100,7 +1465,7 @@ function getSchemas(document, { contentType }) {
1100
1465
  }
1101
1466
  /**
1102
1467
  * 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`.
1468
+ * Returns `null` when `dateType: false`, so the format falls through to `string`.
1104
1469
  */
1105
1470
  function getDateType(options, format) {
1106
1471
  if (!options.dateType) return null;
@@ -1144,15 +1509,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1144
1509
  readOnly: schema.readOnly,
1145
1510
  writeOnly: schema.writeOnly,
1146
1511
  default: defaultValue,
1147
- example: schema.example
1512
+ example: schema.example,
1513
+ format: schema.format
1148
1514
  };
1149
1515
  }
1150
1516
  /**
1151
1517
  * Returns all request body content type keys for an operation.
1152
1518
  *
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.
1519
+ * The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
1520
+ * `getRequestSchema` already performs), so the returned list accurately reflects the
1521
+ * available content types even for referenced bodies.
1156
1522
  *
1157
1523
  * @example
1158
1524
  * ```ts
@@ -1166,15 +1532,44 @@ function getRequestBodyContentTypes(document, operation) {
1166
1532
  if (!body) return [];
1167
1533
  return body.content ? Object.keys(body.content) : [];
1168
1534
  }
1535
+ /**
1536
+ * Returns all response content type keys for an operation at a given status code.
1537
+ *
1538
+ * Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
1539
+ * so the returned list reflects the available content types even for referenced responses.
1540
+ *
1541
+ * @example
1542
+ * ```ts
1543
+ * getResponseBodyContentTypes(document, operation, 200)
1544
+ * // ['application/json', 'application/xml']
1545
+ * ```
1546
+ */
1547
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1548
+ if (operation.schema.responses) {
1549
+ const responses = operation.schema.responses;
1550
+ for (const key in responses) {
1551
+ const schema = responses[key];
1552
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
1553
+ }
1554
+ }
1555
+ const responseObj = getResponseByStatusCode({
1556
+ document,
1557
+ operation,
1558
+ statusCode
1559
+ });
1560
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1561
+ const body = responseObj;
1562
+ return body.content ? Object.keys(body.content) : [];
1563
+ }
1169
1564
  //#endregion
1170
1565
  //#region src/parser.ts
1171
1566
  /**
1172
1567
  * Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
1173
1568
  *
1174
1569
  * 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.
1570
+ * from the array to its items sub-schema, so they are valid for downstream processing.
1176
1571
  *
1177
- * @note This is a defensive measure for robustness with non-compliant specs.
1572
+ * @note A defensive measure for non-compliant specs.
1178
1573
  */
1179
1574
  function normalizeArrayEnum(schema) {
1180
1575
  const normalizedItems = {
@@ -1192,11 +1587,11 @@ function normalizeArrayEnum(schema) {
1192
1587
  *
1193
1588
  * Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
1194
1589
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1195
- * made possible by hoisting of function declarations.
1590
+ * which works because function declarations hoist.
1196
1591
  *
1197
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1592
+ * @internal
1198
1593
  */
1199
- function createSchemaParser(ctx) {
1594
+ function createSchemaParser(ctx, dialect = oasDialect) {
1200
1595
  const document = ctx.document;
1201
1596
  /**
1202
1597
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1204,6 +1599,15 @@ function createSchemaParser(ctx) {
1204
1599
  */
1205
1600
  const resolvingRefs = /* @__PURE__ */ new Set();
1206
1601
  /**
1602
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1603
+ *
1604
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1605
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1606
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1607
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1608
+ */
1609
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1610
+ /**
1207
1611
  * Converts a `$ref` schema into a `RefSchemaNode`.
1208
1612
  *
1209
1613
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1212,20 +1616,26 @@ function createSchemaParser(ctx) {
1212
1616
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1213
1617
  */
1214
1618
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1215
- let resolvedSchema;
1619
+ let resolvedSchema = null;
1216
1620
  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);
1621
+ if (refPath && !resolvingRefs.has(refPath)) {
1622
+ if (!resolvedRefCache.has(refPath)) {
1623
+ try {
1624
+ const referenced = dialect.resolveRef(document, refPath);
1625
+ if (referenced) {
1626
+ resolvingRefs.add(refPath);
1627
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1628
+ resolvingRefs.delete(refPath);
1629
+ }
1630
+ } catch {}
1631
+ resolvedRefCache.set(refPath, resolvedSchema);
1223
1632
  }
1224
- } catch {}
1225
- return _kubb_core.ast.createSchema({
1633
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1634
+ }
1635
+ return _kubb_core.ast.factory.createSchema({
1226
1636
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1227
1637
  type: "ref",
1228
- name: _kubb_core.ast.extractRefName(schema.$ref),
1638
+ name: (0, _kubb_ast_utils.extractRefName)(schema.$ref),
1229
1639
  ref: schema.$ref,
1230
1640
  schema: resolvedSchema
1231
1641
  });
@@ -1238,12 +1648,12 @@ function createSchemaParser(ctx) {
1238
1648
  const [memberSchema] = schema.allOf;
1239
1649
  const memberNode = parseSchema({
1240
1650
  schema: memberSchema,
1241
- name: null
1651
+ name
1242
1652
  }, rawOptions);
1243
1653
  const { kind: _kind, ...memberNodeProps } = memberNode;
1244
1654
  const mergedNullable = nullable || memberNode.nullable || void 0;
1245
1655
  const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1246
- return _kubb_core.ast.createSchema({
1656
+ return _kubb_core.ast.factory.createSchema({
1247
1657
  ...memberNodeProps,
1248
1658
  name,
1249
1659
  title: schema.title ?? memberNode.title,
@@ -1254,21 +1664,22 @@ function createSchemaParser(ctx) {
1254
1664
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1255
1665
  default: mergedDefault,
1256
1666
  example: schema.example ?? memberNode.example,
1257
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1667
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1668
+ format: schema.format ?? memberNode.format
1258
1669
  });
1259
1670
  }
1260
1671
  const filteredDiscriminantValues = [];
1261
1672
  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;
1673
+ if (!dialect.isReference(item) || !name) return true;
1674
+ const deref = dialect.resolveRef(document, item.$ref);
1675
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1265
1676
  const parentUnion = deref.oneOf ?? deref.anyOf;
1266
1677
  if (!parentUnion) return true;
1267
1678
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1268
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1679
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1269
1680
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1270
1681
  if (inOneOf || inMapping) {
1271
- const discriminatorValue = _kubb_core.ast.findDiscriminator(deref.discriminator.mapping, childRef);
1682
+ const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1272
1683
  if (discriminatorValue) filteredDiscriminantValues.push({
1273
1684
  propertyName: deref.discriminator.propertyName,
1274
1685
  value: discriminatorValue
@@ -1276,22 +1687,28 @@ function createSchemaParser(ctx) {
1276
1687
  return false;
1277
1688
  }
1278
1689
  return true;
1279
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1690
+ }).map((s) => parseSchema({
1691
+ schema: s,
1692
+ name
1693
+ }, rawOptions));
1280
1694
  const syntheticStart = allOfMembers.length;
1281
1695
  if (Array.isArray(schema.required) && schema.required.length) {
1282
1696
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1283
1697
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1284
1698
  if (missingRequired.length) {
1285
1699
  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] : [];
1700
+ if (!dialect.isReference(item)) return [item];
1701
+ const deref = dialect.resolveRef(document, item.$ref);
1702
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1289
1703
  });
1290
1704
  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));
1705
+ allOfMembers.push(parseSchema({
1706
+ schema: {
1707
+ properties: { [key]: resolved.properties[key] },
1708
+ required: [key]
1709
+ },
1710
+ name
1711
+ }, rawOptions));
1295
1712
  break;
1296
1713
  }
1297
1714
  }
@@ -1300,13 +1717,13 @@ function createSchemaParser(ctx) {
1300
1717
  const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1301
1718
  allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1302
1719
  }
1303
- for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(_kubb_core.ast.createDiscriminantNode({
1720
+ for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1304
1721
  propertyName,
1305
1722
  value
1306
1723
  }));
1307
- return _kubb_core.ast.createSchema({
1724
+ return _kubb_core.ast.factory.createSchema({
1308
1725
  type: "intersection",
1309
- members: [..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1726
+ members: [..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1310
1727
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1311
1728
  });
1312
1729
  }
@@ -1317,7 +1734,7 @@ function createSchemaParser(ctx) {
1317
1734
  function pickDiscriminatorPropertyNode(node, propertyName) {
1318
1735
  const discriminatorProperty = _kubb_core.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1319
1736
  if (!discriminatorProperty) return null;
1320
- return _kubb_core.ast.createSchema({
1737
+ return _kubb_core.ast.factory.createSchema({
1321
1738
  type: "object",
1322
1739
  primitive: "object",
1323
1740
  properties: [discriminatorProperty]
@@ -1327,10 +1744,10 @@ function createSchemaParser(ctx) {
1327
1744
  const strategy = schema.oneOf ? "one" : "any";
1328
1745
  const unionBase = {
1329
1746
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1330
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1747
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1331
1748
  strategy
1332
1749
  };
1333
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1750
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1334
1751
  const sharedPropertiesNode = schema.properties ? (() => {
1335
1752
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1336
1753
  return parseSchema({
@@ -1340,39 +1757,45 @@ function createSchemaParser(ctx) {
1340
1757
  })() : void 0;
1341
1758
  if (sharedPropertiesNode || discriminator?.mapping) {
1342
1759
  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);
1760
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1761
+ const discriminatorValue = findDiscriminator(discriminator?.mapping, ref);
1762
+ const memberNode = parseSchema({
1763
+ schema: s,
1764
+ name
1765
+ }, rawOptions);
1346
1766
  if (!discriminatorValue || !discriminator) return memberNode;
1347
1767
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.setDiscriminatorEnum({
1348
1768
  node: sharedPropertiesNode,
1349
1769
  propertyName: discriminator.propertyName,
1350
1770
  values: [discriminatorValue]
1351
1771
  }), discriminator.propertyName) : void 0;
1352
- return _kubb_core.ast.createSchema({
1772
+ return _kubb_core.ast.factory.createSchema({
1353
1773
  type: "intersection",
1354
- members: [memberNode, narrowedDiscriminatorNode ?? _kubb_core.ast.createDiscriminantNode({
1774
+ members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1355
1775
  propertyName: discriminator.propertyName,
1356
1776
  value: discriminatorValue
1357
1777
  })]
1358
1778
  });
1359
1779
  });
1360
- const unionNode = _kubb_core.ast.createSchema({
1780
+ const unionNode = _kubb_core.ast.factory.createSchema({
1361
1781
  type: "union",
1362
1782
  ...unionBase,
1363
1783
  members
1364
1784
  });
1365
1785
  if (!sharedPropertiesNode) return unionNode;
1366
- return _kubb_core.ast.createSchema({
1786
+ return _kubb_core.ast.factory.createSchema({
1367
1787
  type: "intersection",
1368
1788
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1369
1789
  members: [unionNode, sharedPropertiesNode]
1370
1790
  });
1371
1791
  }
1372
- return _kubb_core.ast.createSchema({
1792
+ return _kubb_core.ast.factory.createSchema({
1373
1793
  type: "union",
1374
1794
  ...unionBase,
1375
- members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1795
+ members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({
1796
+ schema: s,
1797
+ name
1798
+ }, rawOptions)))
1376
1799
  });
1377
1800
  }
1378
1801
  /**
@@ -1380,16 +1803,17 @@ function createSchemaParser(ctx) {
1380
1803
  */
1381
1804
  function convertConst({ schema, name, nullable, defaultValue }) {
1382
1805
  const constValue = schema.const;
1383
- if (constValue === null) return _kubb_core.ast.createSchema({
1806
+ if (constValue === null) return _kubb_core.ast.factory.createSchema({
1384
1807
  type: "null",
1385
1808
  primitive: "null",
1386
1809
  name,
1387
1810
  title: schema.title,
1388
1811
  description: schema.description,
1389
- deprecated: schema.deprecated
1812
+ deprecated: schema.deprecated,
1813
+ format: schema.format
1390
1814
  });
1391
1815
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1392
- return _kubb_core.ast.createSchema({
1816
+ return _kubb_core.ast.factory.createSchema({
1393
1817
  type: "enum",
1394
1818
  primitive: constPrimitive,
1395
1819
  enumValues: [constValue],
@@ -1402,7 +1826,7 @@ function createSchemaParser(ctx) {
1402
1826
  */
1403
1827
  function convertFormat({ schema, name, nullable, defaultValue, options }) {
1404
1828
  const base = buildSchemaNode(schema, name, nullable, defaultValue);
1405
- if (schema.format === "int64") return _kubb_core.ast.createSchema({
1829
+ if (schema.format === "int64") return _kubb_core.ast.factory.createSchema({
1406
1830
  type: options.integerType === "bigint" ? "bigint" : "integer",
1407
1831
  primitive: "integer",
1408
1832
  ...base,
@@ -1414,14 +1838,14 @@ function createSchemaParser(ctx) {
1414
1838
  if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1415
1839
  const dateType = getDateType(options, schema.format);
1416
1840
  if (!dateType) return null;
1417
- if (dateType.type === "datetime") return _kubb_core.ast.createSchema({
1841
+ if (dateType.type === "datetime") return _kubb_core.ast.factory.createSchema({
1418
1842
  ...base,
1419
1843
  primitive: "string",
1420
1844
  type: "datetime",
1421
1845
  offset: dateType.offset,
1422
1846
  local: dateType.local
1423
1847
  });
1424
- return _kubb_core.ast.createSchema({
1848
+ return _kubb_core.ast.factory.createSchema({
1425
1849
  ...base,
1426
1850
  primitive: "string",
1427
1851
  type: dateType.type,
@@ -1431,36 +1855,36 @@ function createSchemaParser(ctx) {
1431
1855
  const specialType = getSchemaType(schema.format);
1432
1856
  if (!specialType) return null;
1433
1857
  const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1434
- if (specialType === "number" || specialType === "integer" || specialType === "bigint") return _kubb_core.ast.createSchema({
1858
+ if (specialType === "number" || specialType === "integer" || specialType === "bigint") return _kubb_core.ast.factory.createSchema({
1435
1859
  ...base,
1436
1860
  primitive: specialPrimitive,
1437
1861
  type: specialType
1438
1862
  });
1439
- if (specialType === "url") return _kubb_core.ast.createSchema({
1863
+ if (specialType === "url") return _kubb_core.ast.factory.createSchema({
1440
1864
  ...base,
1441
1865
  primitive: "string",
1442
1866
  type: "url",
1443
1867
  min: schema.minLength,
1444
1868
  max: schema.maxLength
1445
1869
  });
1446
- if (specialType === "ipv4") return _kubb_core.ast.createSchema({
1870
+ if (specialType === "ipv4") return _kubb_core.ast.factory.createSchema({
1447
1871
  ...base,
1448
1872
  primitive: "string",
1449
1873
  type: "ipv4"
1450
1874
  });
1451
- if (specialType === "ipv6") return _kubb_core.ast.createSchema({
1875
+ if (specialType === "ipv6") return _kubb_core.ast.factory.createSchema({
1452
1876
  ...base,
1453
1877
  primitive: "string",
1454
1878
  type: "ipv6"
1455
1879
  });
1456
- if (specialType === "uuid" || specialType === "email") return _kubb_core.ast.createSchema({
1880
+ if (specialType === "uuid" || specialType === "email") return _kubb_core.ast.factory.createSchema({
1457
1881
  ...base,
1458
1882
  primitive: "string",
1459
1883
  type: specialType,
1460
1884
  min: schema.minLength,
1461
1885
  max: schema.maxLength
1462
1886
  });
1463
- return _kubb_core.ast.createSchema({
1887
+ return _kubb_core.ast.factory.createSchema({
1464
1888
  ...base,
1465
1889
  primitive: specialPrimitive,
1466
1890
  type: specialType
@@ -1476,6 +1900,15 @@ function createSchemaParser(ctx) {
1476
1900
  }, rawOptions);
1477
1901
  const nullInEnum = schema.enum.includes(null);
1478
1902
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1903
+ if (nullInEnum && filteredValues.length === 0) return _kubb_core.ast.factory.createSchema({
1904
+ type: "null",
1905
+ primitive: "null",
1906
+ name,
1907
+ title: schema.title,
1908
+ description: schema.description,
1909
+ deprecated: schema.deprecated,
1910
+ format: schema.format
1911
+ });
1479
1912
  const enumNullable = nullable || nullInEnum || void 0;
1480
1913
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1481
1914
  const enumPrimitive = getPrimitiveType(type);
@@ -1490,7 +1923,8 @@ function createSchemaParser(ctx) {
1490
1923
  readOnly: schema.readOnly,
1491
1924
  writeOnly: schema.writeOnly,
1492
1925
  default: enumDefault,
1493
- example: schema.example
1926
+ example: schema.example,
1927
+ format: schema.format
1494
1928
  };
1495
1929
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1496
1930
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
@@ -1498,7 +1932,7 @@ function createSchemaParser(ctx) {
1498
1932
  const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1499
1933
  const uniqueValues = [...new Set(filteredValues)];
1500
1934
  const seenNames = /* @__PURE__ */ new Set();
1501
- return _kubb_core.ast.createSchema({
1935
+ return _kubb_core.ast.factory.createSchema({
1502
1936
  ...enumBase,
1503
1937
  primitive: enumPrimitiveType,
1504
1938
  namedEnumValues: uniqueValues.map((value, index) => ({
@@ -1512,7 +1946,7 @@ function createSchemaParser(ctx) {
1512
1946
  })
1513
1947
  });
1514
1948
  }
1515
- return _kubb_core.ast.createSchema({
1949
+ return _kubb_core.ast.factory.createSchema({
1516
1950
  ...enumBase,
1517
1951
  enumValues: [...new Set(filteredValues)]
1518
1952
  });
@@ -1524,21 +1958,24 @@ function createSchemaParser(ctx) {
1524
1958
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1525
1959
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1526
1960
  const resolvedPropSchema = propSchema;
1527
- const propNullable = isNullable(resolvedPropSchema);
1961
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1528
1962
  const propNode = parseSchema({
1529
1963
  schema: resolvedPropSchema,
1530
- name: _kubb_core.ast.childName(name, propName)
1964
+ name: (0, _kubb_ast_utils.childName)(name, propName)
1531
1965
  }, 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({
1966
+ const schemaNode = (() => {
1967
+ const node = _kubb_core.ast.setEnumName(propNode, name, propName, options.enumSuffix);
1968
+ const tupleNode = _kubb_core.ast.narrowSchema(node, "tuple");
1969
+ if (tupleNode?.items) {
1970
+ const namedItems = tupleNode.items.map((item) => _kubb_core.ast.setEnumName(item, name, propName, options.enumSuffix));
1971
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1972
+ ...tupleNode,
1973
+ items: namedItems
1974
+ };
1975
+ }
1976
+ return node;
1977
+ })();
1978
+ return _kubb_core.ast.factory.createProperty({
1542
1979
  name: propName,
1543
1980
  schema: {
1544
1981
  ...schemaNode,
@@ -1548,14 +1985,15 @@ function createSchemaParser(ctx) {
1548
1985
  });
1549
1986
  }) : [];
1550
1987
  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) });
1988
+ const additionalPropertiesNode = (() => {
1989
+ if (additionalProperties === true) return true;
1990
+ if (additionalProperties === false) return false;
1991
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1992
+ if (additionalProperties) return _kubb_core.ast.factory.createSchema({ type: typeOptionMap.get(options.unknownType) });
1993
+ })();
1556
1994
  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({
1995
+ 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: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1996
+ const objectNode = _kubb_core.ast.factory.createSchema({
1559
1997
  type: "object",
1560
1998
  primitive: "object",
1561
1999
  properties,
@@ -1565,10 +2003,10 @@ function createSchemaParser(ctx) {
1565
2003
  maxProperties: schema.maxProperties,
1566
2004
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1567
2005
  });
1568
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
2006
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1569
2007
  const discPropName = schema.discriminator.propertyName;
1570
2008
  const values = Object.keys(schema.discriminator.mapping);
1571
- const enumName = name ? _kubb_core.ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
2009
+ const enumName = name ? (0, _kubb_ast_utils.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1572
2010
  return _kubb_core.ast.setDiscriminatorEnum({
1573
2011
  node: objectNode,
1574
2012
  propertyName: discPropName,
@@ -1583,8 +2021,8 @@ function createSchemaParser(ctx) {
1583
2021
  */
1584
2022
  function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1585
2023
  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({
2024
+ const rest = schema.items ? parseSchema({ schema: schema.items }, rawOptions) : _kubb_core.ast.factory.createSchema({ type: "any" });
2025
+ return _kubb_core.ast.factory.createSchema({
1588
2026
  type: "tuple",
1589
2027
  primitive: "array",
1590
2028
  items: tupleItems,
@@ -1599,12 +2037,12 @@ function createSchemaParser(ctx) {
1599
2037
  */
1600
2038
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1601
2039
  const rawItems = schema.items;
1602
- const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
2040
+ const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast_utils.enumPropName)(null, name, options.enumSuffix) : name;
1603
2041
  const items = rawItems ? [parseSchema({
1604
2042
  schema: rawItems,
1605
2043
  name: itemName
1606
2044
  }, rawOptions)] : [];
1607
- return _kubb_core.ast.createSchema({
2045
+ return _kubb_core.ast.factory.createSchema({
1608
2046
  type: "array",
1609
2047
  primitive: "array",
1610
2048
  items,
@@ -1618,7 +2056,7 @@ function createSchemaParser(ctx) {
1618
2056
  * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1619
2057
  */
1620
2058
  function convertString({ schema, name, nullable, defaultValue }) {
1621
- return _kubb_core.ast.createSchema({
2059
+ return _kubb_core.ast.factory.createSchema({
1622
2060
  type: "string",
1623
2061
  primitive: "string",
1624
2062
  min: schema.minLength,
@@ -1631,7 +2069,7 @@ function createSchemaParser(ctx) {
1631
2069
  * Converts a `type: 'number'` or `type: 'integer'` schema.
1632
2070
  */
1633
2071
  function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1634
- return _kubb_core.ast.createSchema({
2072
+ return _kubb_core.ast.factory.createSchema({
1635
2073
  type,
1636
2074
  primitive: type,
1637
2075
  min: schema.minimum,
@@ -1646,7 +2084,7 @@ function createSchemaParser(ctx) {
1646
2084
  * Converts a `type: 'boolean'` schema.
1647
2085
  */
1648
2086
  function convertBoolean({ schema, name, nullable, defaultValue }) {
1649
- return _kubb_core.ast.createSchema({
2087
+ return _kubb_core.ast.factory.createSchema({
1650
2088
  type: "boolean",
1651
2089
  primitive: "boolean",
1652
2090
  ...buildSchemaNode(schema, name, nullable, defaultValue)
@@ -1656,22 +2094,155 @@ function createSchemaParser(ctx) {
1656
2094
  * Converts an explicit `type: 'null'` schema.
1657
2095
  */
1658
2096
  function convertNull({ schema, name, nullable }) {
1659
- return _kubb_core.ast.createSchema({
2097
+ return _kubb_core.ast.factory.createSchema({
1660
2098
  type: "null",
1661
2099
  primitive: "null",
1662
2100
  name,
1663
2101
  title: schema.title,
1664
2102
  description: schema.description,
1665
2103
  deprecated: schema.deprecated,
1666
- nullable
2104
+ nullable,
2105
+ format: schema.format
2106
+ });
2107
+ }
2108
+ /**
2109
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
2110
+ * into a `blob` node.
2111
+ */
2112
+ function convertBlob({ schema, name, nullable, defaultValue }) {
2113
+ return _kubb_core.ast.factory.createSchema({
2114
+ type: "blob",
2115
+ primitive: "string",
2116
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
2117
+ });
2118
+ }
2119
+ /**
2120
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
2121
+ *
2122
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
2123
+ * falls through and handles it as that single type with nullability already folded in.
2124
+ */
2125
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
2126
+ const types = schema.type;
2127
+ const nonNullTypes = types.filter((t) => t !== "null");
2128
+ if (nonNullTypes.length <= 1) return null;
2129
+ const arrayNullable = types.includes("null") || nullable || void 0;
2130
+ return _kubb_core.ast.factory.createSchema({
2131
+ type: "union",
2132
+ members: nonNullTypes.map((t) => parseSchema({
2133
+ schema: {
2134
+ ...schema,
2135
+ type: t
2136
+ },
2137
+ name
2138
+ }, rawOptions)),
2139
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1667
2140
  });
1668
2141
  }
1669
2142
  /**
1670
- * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
2143
+ * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
2144
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
2145
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
2146
+ * match/convert/fall-through contract.
2147
+ */
2148
+ const schemaRules = [
2149
+ {
2150
+ name: "ref",
2151
+ match: ({ schema }) => dialect.isReference(schema),
2152
+ convert: convertRef
2153
+ },
2154
+ {
2155
+ name: "allOf",
2156
+ match: ({ schema }) => !!schema.allOf?.length,
2157
+ convert: convertAllOf
2158
+ },
2159
+ {
2160
+ name: "union",
2161
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
2162
+ convert: convertUnion
2163
+ },
2164
+ {
2165
+ name: "const",
2166
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
2167
+ convert: convertConst
2168
+ },
2169
+ {
2170
+ name: "format",
2171
+ match: ({ schema }) => !!schema.format,
2172
+ convert: convertFormat
2173
+ },
2174
+ {
2175
+ name: "blob",
2176
+ match: ({ schema }) => dialect.isBinary(schema),
2177
+ convert: convertBlob
2178
+ },
2179
+ {
2180
+ name: "multi-type",
2181
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
2182
+ convert: convertMultiType
2183
+ },
2184
+ {
2185
+ name: "constrained-string",
2186
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
2187
+ convert: convertString
2188
+ },
2189
+ {
2190
+ name: "constrained-number",
2191
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
2192
+ convert: (ctx) => convertNumeric(ctx, "number")
2193
+ },
2194
+ {
2195
+ name: "enum",
2196
+ match: ({ schema }) => !!schema.enum?.length,
2197
+ convert: convertEnum
2198
+ },
2199
+ {
2200
+ name: "object",
2201
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
2202
+ convert: convertObject
2203
+ },
2204
+ {
2205
+ name: "tuple",
2206
+ match: ({ schema }) => "prefixItems" in schema,
2207
+ convert: convertTuple
2208
+ },
2209
+ {
2210
+ name: "array",
2211
+ match: ({ schema, type }) => type === "array" || "items" in schema,
2212
+ convert: convertArray
2213
+ },
2214
+ {
2215
+ name: "string",
2216
+ match: ({ type }) => type === "string",
2217
+ convert: convertString
2218
+ },
2219
+ {
2220
+ name: "number",
2221
+ match: ({ type }) => type === "number",
2222
+ convert: (ctx) => convertNumeric(ctx, "number")
2223
+ },
2224
+ {
2225
+ name: "integer",
2226
+ match: ({ type }) => type === "integer",
2227
+ convert: (ctx) => convertNumeric(ctx, "integer")
2228
+ },
2229
+ {
2230
+ name: "boolean",
2231
+ match: ({ type }) => type === "boolean",
2232
+ convert: convertBoolean
2233
+ },
2234
+ {
2235
+ name: "null",
2236
+ match: ({ type }) => type === "null",
2237
+ convert: convertNull
2238
+ }
2239
+ ];
2240
+ /**
2241
+ * Converts an OAS `SchemaObject` into a `SchemaNode`.
1671
2242
  *
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).
2243
+ * Builds the per-schema context, then walks the ordered {@link schemaRules} table and returns
2244
+ * the first converter that produces a node. When none match, falls back to the configured
2245
+ * `emptySchemaType`.
1675
2246
  */
1676
2247
  function parseSchema({ schema, name }, rawOptions) {
1677
2248
  const options = {
@@ -1683,75 +2254,43 @@ function createSchemaParser(ctx) {
1683
2254
  schema: flattenedSchema,
1684
2255
  name
1685
2256
  }, 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 = {
2257
+ const nullable = dialect.isNullable(schema) || void 0;
2258
+ const schemaCtx = {
1690
2259
  schema,
1691
2260
  name,
1692
2261
  nullable,
1693
- defaultValue,
1694
- type,
2262
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
2263
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1695
2264
  rawOptions,
1696
2265
  options
1697
2266
  };
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
- });
2267
+ for (const rule of schemaRules) {
2268
+ if (!rule.match(schemaCtx)) continue;
2269
+ const node = rule.convert(schemaCtx);
2270
+ if (node) return node;
1725
2271
  }
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");
1729
- }
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
2272
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1740
- return _kubb_core.ast.createSchema({
2273
+ return _kubb_core.ast.factory.createSchema({
1741
2274
  type: emptyType,
1742
2275
  name,
1743
2276
  title: schema.title,
1744
- description: schema.description
2277
+ description: schema.description,
2278
+ format: schema.format
1745
2279
  });
1746
2280
  }
1747
2281
  /**
1748
2282
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1749
2283
  */
1750
- function parseParameter(options, param) {
2284
+ function parseParameter(options, param, parentName) {
1751
2285
  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"],
2286
+ const paramName = param["name"];
2287
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
2288
+ const schema = param["schema"] ? parseSchema({
2289
+ schema: param["schema"],
2290
+ name: schemaName
2291
+ }, options) : _kubb_core.ast.factory.createSchema({ type: typeOptionMap.get(options.unknownType) });
2292
+ return _kubb_core.ast.factory.createParameter({
2293
+ name: paramName,
1755
2294
  in: param["in"],
1756
2295
  schema: {
1757
2296
  ...schema,
@@ -1788,27 +2327,33 @@ function createSchemaParser(ctx) {
1788
2327
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1789
2328
  */
1790
2329
  function collectPropertyKeysByFlag(schema, flag) {
1791
- if (!schema?.properties) return void 0;
2330
+ if (!schema?.properties) return null;
1792
2331
  const keys = [];
1793
2332
  for (const key in schema.properties) {
1794
2333
  const prop = schema.properties[key];
1795
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
2334
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1796
2335
  }
1797
- return keys.length ? keys : void 0;
2336
+ return keys.length ? keys : null;
1798
2337
  }
1799
2338
  /**
1800
2339
  * Converts an OAS `Operation` into an `OperationNode`.
1801
2340
  */
1802
2341
  function parseOperation(options, operation) {
1803
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
2342
+ const operationId = getOperationId(operation);
2343
+ const operationName = operationId ? pascalCase(operationId) : void 0;
2344
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1804
2345
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1805
2346
  const requestBodyMeta = getRequestBodyMeta(operation);
2347
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1806
2348
  const content = allContentTypes.flatMap((ct) => {
1807
2349
  const schema = getRequestSchema(document, operation, { contentType: ct });
1808
2350
  if (!schema) return [];
1809
2351
  return [{
1810
2352
  contentType: ct,
1811
- schema: _kubb_core.ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
2353
+ schema: _kubb_core.ast.syncOptionality(parseSchema({
2354
+ schema,
2355
+ name: requestBodyName
2356
+ }, options), requestBodyMeta.required),
1812
2357
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1813
2358
  }];
1814
2359
  });
@@ -1817,29 +2362,58 @@ function createSchemaParser(ctx) {
1817
2362
  required: requestBodyMeta.required || void 0,
1818
2363
  content: content.length > 0 ? content : void 0
1819
2364
  } : 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({
2365
+ const responses = getResponseStatusCodes(operation).map((statusCode) => {
2366
+ const responseObj = getResponseByStatusCode({
2367
+ document,
2368
+ operation,
2369
+ statusCode
2370
+ });
2371
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2372
+ const { description } = getResponseMeta(responseObj);
2373
+ const parseEntrySchema = (contentType) => {
2374
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
2375
+ return {
2376
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2377
+ schema: raw,
2378
+ name: responseName
2379
+ }, options) : _kubb_core.ast.factory.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
2380
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2381
+ };
2382
+ };
2383
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
2384
+ contentType,
2385
+ ...parseEntrySchema(contentType)
2386
+ }));
2387
+ if (content.length === 0) content.push({
2388
+ contentType: getRequestContentType({
2389
+ document,
2390
+ operation
2391
+ }) || "application/json",
2392
+ ...parseEntrySchema(ctx.contentType)
2393
+ });
2394
+ return _kubb_core.ast.factory.createResponse({
1827
2395
  statusCode,
1828
2396
  description,
1829
- schema,
1830
- mediaType,
1831
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
2397
+ content
1832
2398
  });
1833
2399
  });
1834
- const urlPath = new URLPath(operation.path);
1835
- return _kubb_core.ast.createOperation({
1836
- operationId: operation.getOperationId(),
2400
+ const pathItem = document.paths?.[operation.path];
2401
+ const pathItemDoc = pathItem && !dialect.isReference(pathItem) ? pathItem : void 0;
2402
+ const pickDoc = (key) => {
2403
+ const own = operation.schema[key];
2404
+ if (typeof own === "string") return own;
2405
+ const fallback = pathItemDoc?.[key];
2406
+ return typeof fallback === "string" ? fallback : void 0;
2407
+ };
2408
+ return _kubb_core.ast.factory.createOperation({
2409
+ operationId,
2410
+ protocol: "http",
1837
2411
  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,
2412
+ path: operation.path,
2413
+ tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
2414
+ summary: pickDoc("summary") || void 0,
2415
+ description: pickDoc("description") || void 0,
2416
+ deprecated: operation.schema.deprecated || void 0,
1843
2417
  parameters,
1844
2418
  requestBody,
1845
2419
  responses
@@ -1851,59 +2425,263 @@ function createSchemaParser(ctx) {
1851
2425
  parseParameter
1852
2426
  };
1853
2427
  }
2428
+ //#endregion
2429
+ //#region src/schemaDiagnostics.ts
2430
+ /**
2431
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
2432
+ * top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
2433
+ * pointer as it descends so a nested field reports against its full path
2434
+ * (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
2435
+ * resolved schema is reported under its own walk. Reports land in the active build run, are a
2436
+ * no-op outside one, and repeats are deduped by the build.
2437
+ */
2438
+ function reportSchemaDiagnostics({ node, name }) {
2439
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2440
+ }
2441
+ /**
2442
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2443
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2444
+ */
2445
+ function escapePointerToken(token) {
2446
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2447
+ }
2448
+ function visit(node, pointer) {
2449
+ if (node.deprecated) _kubb_core.Diagnostics.report({
2450
+ code: _kubb_core.Diagnostics.code.deprecated,
2451
+ severity: "info",
2452
+ message: "This schema is marked as deprecated.",
2453
+ location: {
2454
+ kind: "schema",
2455
+ pointer
2456
+ }
2457
+ });
2458
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
2459
+ code: _kubb_core.Diagnostics.code.unsupportedFormat,
2460
+ severity: "warning",
2461
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2462
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2463
+ location: {
2464
+ kind: "schema",
2465
+ pointer
2466
+ }
2467
+ });
2468
+ if (node.type === "object") {
2469
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2470
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2471
+ return;
2472
+ }
2473
+ if (node.type === "array") {
2474
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2475
+ return;
2476
+ }
2477
+ if (node.type === "tuple") {
2478
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2479
+ return;
2480
+ }
2481
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2482
+ }
2483
+ //#endregion
2484
+ //#region src/stream.ts
2485
+ /**
2486
+ * Builds the deduplication plan from the already-parsed top-level schema nodes plus a
2487
+ * single extra parse pass over operations (so duplicates in request/response bodies are seen).
2488
+ *
2489
+ * Only enums and objects are candidates, and object shapes that reference a circular schema are
2490
+ * rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
2491
+ * name (collision-resolved against existing component names). Shapes without a name stay inline.
2492
+ */
2493
+ function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
2494
+ const circularSchemas = new Set(circularNames);
2495
+ const usedNames = new Set(schemaNames);
2496
+ return _kubb_core.ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
2497
+ isCandidate: (node) => {
2498
+ if (node.type === "enum") return true;
2499
+ if (node.type !== "object") return false;
2500
+ if (node.name && circularSchemas.has(node.name)) return false;
2501
+ return !(0, _kubb_ast_utils.containsCircularRef)(node, { circularSchemas });
2502
+ },
2503
+ nameFor: (node) => {
2504
+ const base = node.name;
2505
+ if (!base) return null;
2506
+ let name = base;
2507
+ let counter = 2;
2508
+ while (usedNames.has(name)) name = `${base}${counter++}`;
2509
+ usedNames.add(name);
2510
+ return name;
2511
+ },
2512
+ refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
2513
+ });
2514
+ }
2515
+ /**
2516
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
2517
+ * interpolating any `serverVariables` into the URL template.
2518
+ *
2519
+ * Returns `null` when `serverIndex` is omitted or out of range.
2520
+ *
2521
+ * @example Resolve the first server
2522
+ * `resolveBaseUrl({ document, serverIndex: 0 })`
2523
+ *
2524
+ * @example Override a path variable
2525
+ * `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
2526
+ */
2527
+ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
2528
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
2529
+ return server?.url ? resolveServerUrl(server, serverVariables) : null;
2530
+ }
1854
2531
  /**
1855
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
2532
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
2533
+ *
2534
+ * Three things happen in this single pass:
2535
+ * - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
2536
+ * - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
2537
+ * - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
2538
+ * The `allNodes` array is local and drops out of scope as soon as this function returns.
1856
2539
  *
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.
2540
+ * After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
2541
+ * Both are proportional to the number of aliases or discriminator parents, not total schema count.
1860
2542
  *
1861
- * Returns the AST root and a `nameMapping` for resolving schema references.
2543
+ * Each schema is parsed again during the streaming pass. This is intentional.
2544
+ * Holding the parsed nodes in memory here would defeat the streaming memory benefit.
1862
2545
  *
1863
2546
  * @example
1864
2547
  * ```ts
1865
- * import { parseOas } from '@kubb/adapter-oas'
2548
+ * const { refAliasMap, enumNames, circularNames } = preScan({
2549
+ * schemas,
2550
+ * parseSchema,
2551
+ * parserOptions,
2552
+ * discriminator: 'strict',
2553
+ * })
2554
+ * ```
2555
+ */
2556
+ function preScan({ schemas, parseSchema, parseOperation, document, parserOptions, discriminator, dedupe }) {
2557
+ const allNodes = [];
2558
+ const refAliasMap = /* @__PURE__ */ new Map();
2559
+ const enumNames = [];
2560
+ const discriminatorParentNodes = [];
2561
+ for (const [name, schema] of Object.entries(schemas)) {
2562
+ const node = parseSchema({
2563
+ schema,
2564
+ name
2565
+ }, parserOptions);
2566
+ allNodes.push(node);
2567
+ reportSchemaDiagnostics({
2568
+ node,
2569
+ name
2570
+ });
2571
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2572
+ if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
2573
+ if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2574
+ }
2575
+ const circularNames = [...(0, _kubb_ast_utils.findCircularSchemas)(allNodes)];
2576
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2577
+ let dedupePlan = null;
2578
+ if (dedupe) {
2579
+ const operationNodes = [];
2580
+ for (const operation of getOperations(document)) {
2581
+ const operationNode = parseOperation(parserOptions, operation);
2582
+ if (operationNode) operationNodes.push(operationNode);
2583
+ }
2584
+ dedupePlan = createDedupePlan({
2585
+ schemaNodes: allNodes,
2586
+ operationNodes,
2587
+ schemaNames: Object.keys(schemas),
2588
+ circularNames
2589
+ });
2590
+ for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
2591
+ }
2592
+ const aliasNames = dedupePlan?.aliasNames;
2593
+ return {
2594
+ refAliasMap,
2595
+ enumNames: aliasNames && aliasNames.size > 0 ? enumNames.filter((name) => !aliasNames.has(name)) : enumNames,
2596
+ circularNames,
2597
+ discriminatorChildMap,
2598
+ dedupePlan
2599
+ };
2600
+ }
2601
+ /**
2602
+ * Creates a lazy `InputNode<true>` from already-resolved adapter state.
2603
+ *
2604
+ * The schema and operation iterables each start a fresh parse pass on every
2605
+ * `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
2606
+ * stream object independently without sharing a cursor or holding all nodes in memory.
1866
2607
  *
1867
- * const document = await parseFromConfig(config)
1868
- * const { root, nameMapping } = parseOas(document, { dateType: 'date', contentType: 'application/json' })
2608
+ * Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
2609
+ * with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
2610
+ *
2611
+ * @example
2612
+ * ```ts
2613
+ * const streamNode = createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, meta })
2614
+ * for await (const schema of streamNode.schemas) {
2615
+ * // each call to for-await restarts from the first schema
2616
+ * }
1869
2617
  * ```
1870
2618
  */
1871
- function parseOas(document, options = {}) {
1872
- const { contentType, ...parserOptions } = options;
1873
- const mergedOptions = {
1874
- ...DEFAULT_PARSER_OPTIONS,
1875
- ...parserOptions
2619
+ function createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
2620
+ const rewriteTopLevelSchema = (node) => {
2621
+ if (!dedupePlan) return node;
2622
+ const canonical = dedupePlan.canonicalBySignature.get(_kubb_core.ast.signatureOf(node));
2623
+ if (canonical && canonical.name !== node.name) return _kubb_core.ast.factory.createSchema({
2624
+ type: "ref",
2625
+ name: node.name ?? null,
2626
+ ref: canonical.ref,
2627
+ description: node.description,
2628
+ deprecated: node.deprecated
2629
+ });
2630
+ return _kubb_core.ast.applyDedupe(node, dedupePlan, true);
1876
2631
  };
1877
- const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
1878
- const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
1879
- document,
1880
- contentType
2632
+ const schemasIterable = { [Symbol.asyncIterator]() {
2633
+ return (async function* () {
2634
+ if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
2635
+ for (const [name, schema] of Object.entries(schemas)) {
2636
+ if (dedupePlan?.aliasNames.has(name)) continue;
2637
+ const alias = refAliasMap.get(name);
2638
+ if (alias?.name && schemas[alias.name]) {
2639
+ yield rewriteTopLevelSchema({
2640
+ ...parseSchema({
2641
+ schema: schemas[alias.name],
2642
+ name: alias.name
2643
+ }, parserOptions),
2644
+ name
2645
+ });
2646
+ continue;
2647
+ }
2648
+ const parsed = parseSchema({
2649
+ schema,
2650
+ name
2651
+ }, parserOptions);
2652
+ yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
2653
+ }
2654
+ })();
2655
+ } };
2656
+ const operationsIterable = { [Symbol.asyncIterator]() {
2657
+ return (async function* () {
2658
+ for (const operation of getOperations(document)) {
2659
+ const node = parseOperation(parserOptions, operation);
2660
+ if (node) yield dedupePlan ? _kubb_core.ast.applyDedupe(node, dedupePlan) : node;
2661
+ }
2662
+ })();
2663
+ } };
2664
+ return _kubb_core.ast.factory.createInput({
2665
+ stream: true,
2666
+ schemas: schemasIterable,
2667
+ operations: operationsIterable,
2668
+ meta
1881
2669
  });
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));
1888
- return {
1889
- root: _kubb_core.ast.createInput({
1890
- schemas,
1891
- operations
1892
- }),
1893
- nameMapping
1894
- };
1895
2670
  }
1896
2671
  //#endregion
1897
2672
  //#region src/adapter.ts
1898
2673
  /**
1899
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2674
+ * Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
1900
2675
  */
1901
2676
  const adapterOasName = "oas";
1902
2677
  /**
1903
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2678
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2679
+ * file at `input.path`, validates it, resolves the base URL, and converts every
2680
+ * schema and operation into the universal AST that every downstream plugin
2681
+ * consumes.
1904
2682
  *
1905
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1906
- * everything into an `InputNode` that downstream plugins consume.
2683
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2684
+ * integer width, server URL) apply to every plugin in the build.
1907
2685
  *
1908
2686
  * @example
1909
2687
  * ```ts
@@ -1912,17 +2690,109 @@ const adapterOasName = "oas";
1912
2690
  * import { pluginTs } from '@kubb/plugin-ts'
1913
2691
  *
1914
2692
  * export default defineConfig({
1915
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1916
- * input: { path: './openapi.yaml' },
2693
+ * input: { path: './petStore.yaml' },
2694
+ * output: { path: './src/gen' },
2695
+ * adapter: adapterOas({
2696
+ * serverIndex: 0,
2697
+ * discriminator: 'inherit',
2698
+ * dateType: 'date',
2699
+ * }),
1917
2700
  * plugins: [pluginTs()],
1918
2701
  * })
1919
2702
  * ```
1920
2703
  */
1921
2704
  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;
2705
+ const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", dedupe = true, 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;
2706
+ const parserOptions = {
2707
+ ...DEFAULT_PARSER_OPTIONS,
2708
+ dateType,
2709
+ integerType,
2710
+ unknownType,
2711
+ emptySchemaType,
2712
+ enumSuffix
2713
+ };
1923
2714
  let nameMapping = /* @__PURE__ */ new Map();
1924
- let parsedDocument;
1925
- let inputNode;
2715
+ let parsedDocument = null;
2716
+ const documentCache = /* @__PURE__ */ new WeakMap();
2717
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2718
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2719
+ const preScanCache = /* @__PURE__ */ new WeakMap();
2720
+ function ensureDocument(source) {
2721
+ const cached = documentCache.get(source);
2722
+ if (cached) return cached;
2723
+ const promise = (async () => {
2724
+ const fresh = await parseFromConfig(source);
2725
+ if (validate) await validateDocument(fresh);
2726
+ parsedDocument = fresh;
2727
+ return fresh;
2728
+ })();
2729
+ documentCache.set(source, promise);
2730
+ return promise;
2731
+ }
2732
+ function ensureSchemas(document) {
2733
+ const cached = schemasCache.get(document);
2734
+ if (cached) return cached;
2735
+ const promise = Promise.resolve().then(() => {
2736
+ const result = getSchemas(document, { contentType });
2737
+ nameMapping = result.nameMapping;
2738
+ return result.schemas;
2739
+ });
2740
+ schemasCache.set(document, promise);
2741
+ return promise;
2742
+ }
2743
+ function ensureSchemaParser(document) {
2744
+ const cached = schemaParserCache.get(document);
2745
+ if (cached) return cached;
2746
+ const parser = createSchemaParser({
2747
+ document,
2748
+ contentType
2749
+ });
2750
+ schemaParserCache.set(document, parser);
2751
+ return parser;
2752
+ }
2753
+ function ensurePreScan(document, schemas, parseSchema, parseOperation) {
2754
+ const cached = preScanCache.get(document);
2755
+ if (cached) return cached;
2756
+ const result = preScan({
2757
+ schemas,
2758
+ parseSchema,
2759
+ parseOperation,
2760
+ document,
2761
+ parserOptions,
2762
+ discriminator,
2763
+ dedupe
2764
+ });
2765
+ preScanCache.set(document, result);
2766
+ return result;
2767
+ }
2768
+ async function createStream(source) {
2769
+ const document = await ensureDocument(source);
2770
+ const schemas = await ensureSchemas(document);
2771
+ const { parseSchema, parseOperation } = ensureSchemaParser(document);
2772
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation);
2773
+ return createInputStream({
2774
+ schemas,
2775
+ parseSchema,
2776
+ parseOperation,
2777
+ document,
2778
+ parserOptions,
2779
+ refAliasMap,
2780
+ discriminatorChildMap,
2781
+ dedupePlan,
2782
+ meta: {
2783
+ title: document.info?.title,
2784
+ description: document.info?.description,
2785
+ version: document.info?.version,
2786
+ baseURL: resolveBaseUrl({
2787
+ document,
2788
+ serverIndex,
2789
+ serverVariables
2790
+ }),
2791
+ circularNames,
2792
+ enumNames
2793
+ }
2794
+ });
2795
+ }
1926
2796
  return {
1927
2797
  name: "oas",
1928
2798
  get options() {
@@ -1932,6 +2802,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1932
2802
  serverIndex,
1933
2803
  serverVariables,
1934
2804
  discriminator,
2805
+ dedupe,
1935
2806
  dateType,
1936
2807
  integerType,
1937
2808
  unknownType,
@@ -1943,71 +2814,37 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1943
2814
  get document() {
1944
2815
  return parsedDocument;
1945
2816
  },
1946
- get inputNode() {
1947
- return inputNode;
1948
- },
1949
2817
  async validate(input, options) {
2818
+ await assertInputExists(input);
1950
2819
  await validateDocument(await parseDocument(input), options);
1951
2820
  },
1952
2821
  getImports(node, resolve) {
1953
- return _kubb_core.ast.collectImports({
1954
- node,
1955
- nameMapping,
1956
- resolve: (schemaName) => {
1957
- const result = resolve(schemaName);
1958
- if (!result) return;
1959
- return _kubb_core.ast.createImport({
1960
- name: [result.name],
1961
- path: result.path
1962
- });
1963
- }
1964
- });
2822
+ return (0, _kubb_ast.collect)(node, { schema(schemaNode) {
2823
+ const schemaRef = (0, _kubb_ast.narrowSchema)(schemaNode, "ref");
2824
+ if (!schemaRef?.ref) return null;
2825
+ const rawName = (0, _kubb_ast_utils.extractRefName)(schemaRef.ref);
2826
+ const result = resolve(nameMapping.get(rawName) ?? rawName);
2827
+ if (!result) return null;
2828
+ return _kubb_core.ast.factory.createImport({
2829
+ name: [result.name],
2830
+ path: result.path
2831
+ });
2832
+ } });
1965
2833
  },
1966
2834
  async parse(source) {
1967
- const document = await parseFromConfig(source);
1968
- if (validate) await validateDocument(document);
1969
- const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
1970
- const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1971
- const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
1972
- contentType,
1973
- dateType,
1974
- integerType,
1975
- unknownType,
1976
- emptySchemaType,
1977
- enumSuffix
1978
- });
1979
- const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
1980
- nameMapping = parsedNameMapping;
1981
- parsedDocument = document;
1982
- inputNode = _kubb_core.ast.createInput({
1983
- ...node,
1984
- meta: {
1985
- title: document.info?.title,
1986
- description: document.info?.description,
1987
- version: document.info?.version,
1988
- baseURL
1989
- }
2835
+ const streamNode = await createStream(source);
2836
+ const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)]);
2837
+ return _kubb_core.ast.factory.createInput({
2838
+ schemas,
2839
+ operations,
2840
+ meta: streamNode.meta
1990
2841
  });
1991
- return inputNode;
1992
- }
2842
+ },
2843
+ stream: createStream
1993
2844
  };
1994
2845
  });
1995
2846
  //#endregion
1996
- //#region src/types.ts
1997
- /**
1998
- * Maps uppercase HTTP method names to lowercase for backwards compatibility.
1999
- *
2000
- * @example
2001
- * ```ts
2002
- * HttpMethods['GET'] // 'get'
2003
- * HttpMethods['POST'] // 'post'
2004
- * ```
2005
- */
2006
- const HttpMethods = Object.fromEntries(Object.entries(_kubb_core.ast.httpMethods).map(([lower, upper]) => [upper, lower]));
2007
- //#endregion
2008
- exports.HttpMethods = HttpMethods;
2009
2847
  exports.adapterOas = adapterOas;
2010
2848
  exports.adapterOasName = adapterOasName;
2011
- exports.mergeDocuments = mergeDocuments;
2012
2849
 
2013
2850
  //# sourceMappingURL=index.cjs.map