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

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