@kubb/adapter-oas 5.0.0-beta.5 → 5.0.0-beta.51

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
@@ -22,15 +22,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  }) : target, mod));
23
23
  //#endregion
24
24
  let _kubb_core = require("@kubb/core");
25
+ let oas = require("oas");
26
+ oas = __toESM(oas, 1);
25
27
  let node_path = require("node:path");
26
28
  node_path = __toESM(node_path, 1);
27
- let _redocly_openapi_core = require("@redocly/openapi-core");
29
+ let node_fs_promises = require("node:fs/promises");
28
30
  let oas_normalize = require("oas-normalize");
29
31
  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);
32
+ let _kubb_ast_utils = require("@kubb/ast/utils");
34
33
  let oas_types = require("oas/types");
35
34
  let oas_utils = require("oas/utils");
36
35
  //#region src/constants.ts
@@ -115,6 +114,18 @@ const structuralKeys = new Set([
115
114
  * formatMap['float'] // 'number'
116
115
  * ```
117
116
  */
117
+ /**
118
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
119
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
120
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
121
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
122
+ */
123
+ const specialCasedFormats = new Set([
124
+ "int64",
125
+ "date-time",
126
+ "date",
127
+ "time"
128
+ ]);
118
129
  const formatMap = {
119
130
  uuid: "uuid",
120
131
  email: "email",
@@ -153,87 +164,6 @@ const typeOptionMap = new Map([
153
164
  ["void", _kubb_core.ast.schemaTypes.void]
154
165
  ]);
155
166
  //#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
167
  //#region ../../internals/utils/src/casing.ts
238
168
  /**
239
169
  * Shared implementation for camelCase and PascalCase conversion.
@@ -298,6 +228,42 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
298
228
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
299
229
  }
300
230
  //#endregion
231
+ //#region ../../internals/utils/src/runtime.ts
232
+ /**
233
+ * Returns `true` when the current process is running under Bun.
234
+ *
235
+ * Detection keys off the global `Bun` object rather than `process.versions`,
236
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
237
+ * otherwise look like Node.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * if (isBun()) {
242
+ * await Bun.write(path, data)
243
+ * }
244
+ * ```
245
+ */
246
+ function isBun() {
247
+ return typeof Bun !== "undefined";
248
+ }
249
+ //#endregion
250
+ //#region ../../internals/utils/src/fs.ts
251
+ /**
252
+ * Resolves to `true` when the file or directory at `path` exists.
253
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * if (await exists('./kubb.config.ts')) {
258
+ * const content = await read('./kubb.config.ts')
259
+ * }
260
+ * ```
261
+ */
262
+ async function exists(path) {
263
+ if (isBun()) return Bun.file(path).exists();
264
+ return (0, node_fs_promises.access)(path).then(() => true, () => false);
265
+ }
266
+ //#endregion
301
267
  //#region ../../internals/utils/src/object.ts
302
268
  /**
303
269
  * Returns `true` when `value` is a plain (non-null, non-array) object.
@@ -432,6 +398,22 @@ const reservedWords = new Set([
432
398
  */
433
399
  function isValidVarName(name) {
434
400
  if (!name || reservedWords.has(name)) return false;
401
+ return isIdentifier(name);
402
+ }
403
+ /**
404
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
405
+ *
406
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
407
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
408
+ * deciding whether an object key needs quoting.
409
+ *
410
+ * @example
411
+ * ```ts
412
+ * isIdentifier('name') // true
413
+ * isIdentifier('x-total')// false
414
+ * ```
415
+ */
416
+ function isIdentifier(name) {
435
417
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
436
418
  }
437
419
  //#endregion
@@ -543,12 +525,13 @@ var URLPath = class {
543
525
  * @example
544
526
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
545
527
  */
546
- toTemplateString({ prefix = "", replacer } = {}) {
547
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
528
+ toTemplateString({ prefix, replacer } = {}) {
529
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
548
530
  if (i % 2 === 0) return part;
549
531
  const param = this.#transformParam(part);
550
532
  return `\${${replacer ? replacer(param) : param}}`;
551
- }).join("")}\``;
533
+ }).join("");
534
+ return `\`${prefix ?? ""}${result}\``;
552
535
  }
553
536
  /**
554
537
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -646,8 +629,8 @@ function isDiscriminator(obj) {
646
629
  * Loads and dereferences an OpenAPI document, returning the raw `Document`.
647
630
  *
648
631
  * 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`.
632
+ * `@apidevtools/json-schema-ref-parser` to resolve external `$ref`s. Swagger 2.0 documents are
633
+ * automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
651
634
  *
652
635
  * @example
653
636
  * ```ts
@@ -656,20 +639,20 @@ function isDiscriminator(obj) {
656
639
  * ```
657
640
  */
658
641
  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
- });
642
+ if (typeof pathOrApi === "string" && canBundle) {
643
+ const { $RefParser } = await import("@apidevtools/json-schema-ref-parser");
644
+ return parseDocument(await $RefParser.bundle(pathOrApi), {
645
+ canBundle: false,
646
+ enablePaths
647
+ });
648
+ }
667
649
  const document = await new oas_normalize.default(pathOrApi, {
668
650
  enablePaths,
669
651
  colorizeErrors: true
670
652
  }).load();
671
653
  if (isOpenApiV2Document(document)) {
672
- const { openapi } = await swagger2openapi.default.convertObj(document, { anchors: true });
654
+ const { default: swagger2openapi } = await import("swagger2openapi");
655
+ const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
673
656
  return openapi;
674
657
  }
675
658
  return document;
@@ -686,12 +669,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
686
669
  * ```
687
670
  */
688
671
  async function mergeDocuments(pathOrApi) {
689
- const documents = [];
690
- for (const p of pathOrApi) documents.push(await parseDocument(p, {
672
+ const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
691
673
  enablePaths: false,
692
674
  canBundle: false
693
- }));
694
- if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
675
+ })));
676
+ if (documents.length === 0) throw new _kubb_core.Diagnostics.Error({
677
+ code: _kubb_core.Diagnostics.code.inputRequired,
678
+ severity: "error",
679
+ message: "No OAS documents were provided for merging.",
680
+ help: "Pass at least one path or document to `input.path`.",
681
+ location: { kind: "config" }
682
+ });
695
683
  const seed = {
696
684
  openapi: MERGE_OPENAPI_VERSION,
697
685
  info: {
@@ -707,9 +695,9 @@ async function mergeDocuments(pathOrApi) {
707
695
  * Creates a `Document` from an `AdapterSource`.
708
696
  *
709
697
  * 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.
698
+ * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
699
+ * - `{ type: 'paths' }` merges multiple file paths into a single document.
700
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
713
701
  *
714
702
  * @example
715
703
  * ```ts
@@ -717,14 +705,31 @@ async function mergeDocuments(pathOrApi) {
717
705
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
718
706
  * ```
719
707
  */
720
- function parseFromConfig(source) {
708
+ async function parseFromConfig(source) {
721
709
  if (source.type === "data") {
722
710
  if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
723
711
  return parseDocument(source.data, { canBundle: false });
724
712
  }
725
713
  if (source.type === "paths") return mergeDocuments(source.paths);
726
714
  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));
715
+ const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
716
+ await assertInputExists(resolved);
717
+ return parseDocument(resolved);
718
+ }
719
+ /**
720
+ * Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
721
+ * URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
722
+ * its parse error instead.
723
+ */
724
+ async function assertInputExists(input) {
725
+ if (new URLPath(input).isURL) return;
726
+ if (!await exists(input)) throw new _kubb_core.Diagnostics.Error({
727
+ code: _kubb_core.Diagnostics.code.inputNotFound,
728
+ severity: "error",
729
+ message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
730
+ help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
731
+ location: { kind: "config" }
732
+ });
728
733
  }
729
734
  /**
730
735
  * Validates an OpenAPI document using `oas-normalize` with colorized error output.
@@ -746,6 +751,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
746
751
  }
747
752
  //#endregion
748
753
  //#region src/refs.ts
754
+ const _refCache = /* @__PURE__ */ new WeakMap();
749
755
  /**
750
756
  * Resolves a local JSON pointer reference from a document.
751
757
  *
@@ -761,10 +767,31 @@ function resolveRef(document, $ref) {
761
767
  const origRef = $ref;
762
768
  $ref = $ref.trim();
763
769
  if ($ref === "") return null;
764
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
765
- else return null;
770
+ if (!$ref.startsWith("#")) return null;
771
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
772
+ let docCache = _refCache.get(document);
773
+ if (!docCache) {
774
+ docCache = /* @__PURE__ */ new Map();
775
+ _refCache.set(document, docCache);
776
+ }
777
+ if (docCache.has($ref)) return docCache.get($ref);
766
778
  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}.`);
779
+ if (!current) {
780
+ const diagnostic = {
781
+ code: _kubb_core.Diagnostics.code.refNotFound,
782
+ severity: "error",
783
+ message: `Could not find a definition for ${origRef}.`,
784
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
785
+ location: {
786
+ kind: "schema",
787
+ pointer: origRef,
788
+ ref: origRef
789
+ }
790
+ };
791
+ if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.Diagnostics.Error(diagnostic);
792
+ return null;
793
+ }
794
+ docCache.set($ref, current);
768
795
  return current;
769
796
  }
770
797
  /**
@@ -788,6 +815,35 @@ function dereferenceWithRef(document, schema) {
788
815
  return schema;
789
816
  }
790
817
  //#endregion
818
+ //#region src/dialect.ts
819
+ /**
820
+ * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
821
+ *
822
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
823
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
824
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
825
+ * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
826
+ * nullability, no discriminator object, binary via `contentEncoding` and reuses
827
+ * the rest unchanged.
828
+ *
829
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
830
+ * JSON Schema vocabulary, so the converters keep that common case.
831
+ *
832
+ * @example
833
+ * ```ts
834
+ * const parser = createSchemaParser(context) // uses oasDialect
835
+ * const parser = createSchemaParser(context, oasDialect) // explicit
836
+ * ```
837
+ */
838
+ const oasDialect = _kubb_core.ast.defineSchemaDialect({
839
+ name: "oas",
840
+ isNullable,
841
+ isReference,
842
+ isDiscriminator,
843
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
844
+ resolveRef
845
+ });
846
+ //#endregion
791
847
  //#region src/resolvers.ts
792
848
  /**
793
849
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
@@ -809,7 +865,16 @@ function resolveServerUrl(server, overrides) {
809
865
  for (const [key, variable] of Object.entries(server.variables)) {
810
866
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
811
867
  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(", ")}.`);
868
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.Diagnostics.Error({
869
+ code: _kubb_core.Diagnostics.code.invalidServerVariable,
870
+ severity: "error",
871
+ message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
872
+ help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
873
+ location: {
874
+ kind: "document",
875
+ pointer: "#/servers"
876
+ }
877
+ });
813
878
  url = url.replaceAll(`{${key}}`, value);
814
879
  }
815
880
  return url;
@@ -822,6 +887,15 @@ function getSchemaType(format) {
822
887
  return formatMap[format] ?? null;
823
888
  }
824
889
  /**
890
+ * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
891
+ * `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
892
+ * base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
893
+ * diagnostic in step with the parser as `formatMap` grows.
894
+ */
895
+ function isHandledFormat(format) {
896
+ return getSchemaType(format) !== null || specialCasedFormats.has(format);
897
+ }
898
+ /**
825
899
  * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
826
900
  * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
827
901
  */
@@ -831,12 +905,6 @@ function getPrimitiveType(type) {
831
905
  return "string";
832
906
  }
833
907
  /**
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
908
  * Returns all parameters for an operation, merging path-level and operation-level entries.
841
909
  * Operation-level parameters override path-level ones with the same `in:name` key.
842
910
  * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
@@ -924,7 +992,7 @@ function getRequestSchema(document, operation, options = {}) {
924
992
  /**
925
993
  * Flattens a keyword-only `allOf` into its parent schema.
926
994
  *
927
- * Only flattens when every member is a plain fragment no `$ref` and no structural keywords
995
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
928
996
  * (see `structuralKeys`). Outer schema values take precedence over fragment values.
929
997
  * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
930
998
  *
@@ -934,7 +1002,7 @@ function getRequestSchema(document, operation, options = {}) {
934
1002
  * // { type: 'object', properties: {}, description: 'A pet' }
935
1003
  *
936
1004
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
937
- * // returned unchanged contains a $ref
1005
+ * // returned unchanged, contains a $ref
938
1006
  * ```
939
1007
  */
940
1008
  /**
@@ -960,7 +1028,7 @@ function flattenSchema(schema) {
960
1028
  /**
961
1029
  * Extracts the inline schema from a media-type `content` map.
962
1030
  *
963
- * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
1031
+ * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
964
1032
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
965
1033
  *
966
1034
  * @example
@@ -979,21 +1047,22 @@ function extractSchemaFromContent(content, preferredContentType) {
979
1047
  /**
980
1048
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
981
1049
  */
982
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1050
+ function* collectRefs(schema) {
983
1051
  if (Array.isArray(schema)) {
984
- for (const item of schema) collectRefs(item, refs);
985
- return refs;
1052
+ for (const item of schema) yield* collectRefs(item);
1053
+ return;
986
1054
  }
987
1055
  if (schema && typeof schema === "object") for (const key in schema) {
988
1056
  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);
1057
+ if (!(key === "$ref" && typeof value === "string")) {
1058
+ yield* collectRefs(value);
1059
+ continue;
1060
+ }
1061
+ if (value.startsWith("#/components/schemas/")) {
1062
+ const name = value.slice(21);
1063
+ if (name) yield name;
1064
+ }
995
1065
  }
996
- return refs;
997
1066
  }
998
1067
  /**
999
1068
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -1009,7 +1078,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1009
1078
  */
1010
1079
  function sortSchemas(schemas) {
1011
1080
  const deps = /* @__PURE__ */ new Map();
1012
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
1081
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
1013
1082
  const sorted = [];
1014
1083
  const visited = /* @__PURE__ */ new Set();
1015
1084
  function visit(name, stack) {
@@ -1144,15 +1213,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1144
1213
  readOnly: schema.readOnly,
1145
1214
  writeOnly: schema.writeOnly,
1146
1215
  default: defaultValue,
1147
- example: schema.example
1216
+ example: schema.example,
1217
+ format: schema.format
1148
1218
  };
1149
1219
  }
1150
1220
  /**
1151
1221
  * Returns all request body content type keys for an operation.
1152
1222
  *
1153
- * The requestBody is dereferenced **in-place** when it is a `$ref` the same mutation
1154
- * that `getRequestSchema` already performs so that the returned list accurately reflects
1155
- * the available content types even for referenced bodies.
1223
+ * The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
1224
+ * `getRequestSchema` already performs), so the returned list accurately reflects the
1225
+ * available content types even for referenced bodies.
1156
1226
  *
1157
1227
  * @example
1158
1228
  * ```ts
@@ -1166,6 +1236,31 @@ function getRequestBodyContentTypes(document, operation) {
1166
1236
  if (!body) return [];
1167
1237
  return body.content ? Object.keys(body.content) : [];
1168
1238
  }
1239
+ /**
1240
+ * Returns all response content type keys for an operation at a given status code.
1241
+ *
1242
+ * Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
1243
+ * so the returned list reflects the available content types even for referenced responses.
1244
+ *
1245
+ * @example
1246
+ * ```ts
1247
+ * getResponseBodyContentTypes(document, operation, 200)
1248
+ * // ['application/json', 'application/xml']
1249
+ * ```
1250
+ */
1251
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1252
+ if (operation.schema.responses) {
1253
+ const responses = operation.schema.responses;
1254
+ for (const key in responses) {
1255
+ const schema = responses[key];
1256
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
1257
+ }
1258
+ }
1259
+ const responseObj = operation.getResponseByStatusCode(statusCode);
1260
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1261
+ const body = responseObj;
1262
+ return body.content ? Object.keys(body.content) : [];
1263
+ }
1169
1264
  //#endregion
1170
1265
  //#region src/parser.ts
1171
1266
  /**
@@ -1194,9 +1289,9 @@ function normalizeArrayEnum(schema) {
1194
1289
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1195
1290
  * made possible by hoisting of function declarations.
1196
1291
  *
1197
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1292
+ * @internal
1198
1293
  */
1199
- function createSchemaParser(ctx) {
1294
+ function createSchemaParser(ctx, dialect = oasDialect) {
1200
1295
  const document = ctx.document;
1201
1296
  /**
1202
1297
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1204,6 +1299,15 @@ function createSchemaParser(ctx) {
1204
1299
  */
1205
1300
  const resolvingRefs = /* @__PURE__ */ new Set();
1206
1301
  /**
1302
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1303
+ *
1304
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1305
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1306
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1307
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1308
+ */
1309
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1310
+ /**
1207
1311
  * Converts a `$ref` schema into a `RefSchemaNode`.
1208
1312
  *
1209
1313
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1212,20 +1316,26 @@ function createSchemaParser(ctx) {
1212
1316
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1213
1317
  */
1214
1318
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1215
- let resolvedSchema;
1319
+ let resolvedSchema = null;
1216
1320
  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);
1321
+ if (refPath && !resolvingRefs.has(refPath)) {
1322
+ if (!resolvedRefCache.has(refPath)) {
1323
+ try {
1324
+ const referenced = dialect.resolveRef(document, refPath);
1325
+ if (referenced) {
1326
+ resolvingRefs.add(refPath);
1327
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1328
+ resolvingRefs.delete(refPath);
1329
+ }
1330
+ } catch {}
1331
+ resolvedRefCache.set(refPath, resolvedSchema);
1223
1332
  }
1224
- } catch {}
1333
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1334
+ }
1225
1335
  return _kubb_core.ast.createSchema({
1226
1336
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1227
1337
  type: "ref",
1228
- name: _kubb_core.ast.extractRefName(schema.$ref),
1338
+ name: (0, _kubb_ast_utils.extractRefName)(schema.$ref),
1229
1339
  ref: schema.$ref,
1230
1340
  schema: resolvedSchema
1231
1341
  });
@@ -1238,7 +1348,7 @@ function createSchemaParser(ctx) {
1238
1348
  const [memberSchema] = schema.allOf;
1239
1349
  const memberNode = parseSchema({
1240
1350
  schema: memberSchema,
1241
- name: null
1351
+ name
1242
1352
  }, rawOptions);
1243
1353
  const { kind: _kind, ...memberNodeProps } = memberNode;
1244
1354
  const mergedNullable = nullable || memberNode.nullable || void 0;
@@ -1254,21 +1364,22 @@ function createSchemaParser(ctx) {
1254
1364
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1255
1365
  default: mergedDefault,
1256
1366
  example: schema.example ?? memberNode.example,
1257
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1367
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1368
+ format: schema.format ?? memberNode.format
1258
1369
  });
1259
1370
  }
1260
1371
  const filteredDiscriminantValues = [];
1261
1372
  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;
1373
+ if (!dialect.isReference(item) || !name) return true;
1374
+ const deref = dialect.resolveRef(document, item.$ref);
1375
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1265
1376
  const parentUnion = deref.oneOf ?? deref.anyOf;
1266
1377
  if (!parentUnion) return true;
1267
1378
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1268
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1379
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1269
1380
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1270
1381
  if (inOneOf || inMapping) {
1271
- const discriminatorValue = _kubb_core.ast.findDiscriminator(deref.discriminator.mapping, childRef);
1382
+ const discriminatorValue = (0, _kubb_ast_utils.findDiscriminator)(deref.discriminator.mapping, childRef);
1272
1383
  if (discriminatorValue) filteredDiscriminantValues.push({
1273
1384
  propertyName: deref.discriminator.propertyName,
1274
1385
  value: discriminatorValue
@@ -1276,22 +1387,28 @@ function createSchemaParser(ctx) {
1276
1387
  return false;
1277
1388
  }
1278
1389
  return true;
1279
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1390
+ }).map((s) => parseSchema({
1391
+ schema: s,
1392
+ name
1393
+ }, rawOptions));
1280
1394
  const syntheticStart = allOfMembers.length;
1281
1395
  if (Array.isArray(schema.required) && schema.required.length) {
1282
1396
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1283
1397
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1284
1398
  if (missingRequired.length) {
1285
1399
  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] : [];
1400
+ if (!dialect.isReference(item)) return [item];
1401
+ const deref = dialect.resolveRef(document, item.$ref);
1402
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1289
1403
  });
1290
1404
  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));
1405
+ allOfMembers.push(parseSchema({
1406
+ schema: {
1407
+ properties: { [key]: resolved.properties[key] },
1408
+ required: [key]
1409
+ },
1410
+ name
1411
+ }, rawOptions));
1295
1412
  break;
1296
1413
  }
1297
1414
  }
@@ -1306,7 +1423,7 @@ function createSchemaParser(ctx) {
1306
1423
  }));
1307
1424
  return _kubb_core.ast.createSchema({
1308
1425
  type: "intersection",
1309
- members: [..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1426
+ members: [..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1310
1427
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1311
1428
  });
1312
1429
  }
@@ -1327,10 +1444,10 @@ function createSchemaParser(ctx) {
1327
1444
  const strategy = schema.oneOf ? "one" : "any";
1328
1445
  const unionBase = {
1329
1446
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1330
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1447
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1331
1448
  strategy
1332
1449
  };
1333
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1450
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1334
1451
  const sharedPropertiesNode = schema.properties ? (() => {
1335
1452
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1336
1453
  return parseSchema({
@@ -1340,9 +1457,12 @@ function createSchemaParser(ctx) {
1340
1457
  })() : void 0;
1341
1458
  if (sharedPropertiesNode || discriminator?.mapping) {
1342
1459
  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);
1460
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1461
+ const discriminatorValue = (0, _kubb_ast_utils.findDiscriminator)(discriminator?.mapping, ref);
1462
+ const memberNode = parseSchema({
1463
+ schema: s,
1464
+ name
1465
+ }, rawOptions);
1346
1466
  if (!discriminatorValue || !discriminator) return memberNode;
1347
1467
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.setDiscriminatorEnum({
1348
1468
  node: sharedPropertiesNode,
@@ -1372,7 +1492,10 @@ function createSchemaParser(ctx) {
1372
1492
  return _kubb_core.ast.createSchema({
1373
1493
  type: "union",
1374
1494
  ...unionBase,
1375
- members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1495
+ members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({
1496
+ schema: s,
1497
+ name
1498
+ }, rawOptions)))
1376
1499
  });
1377
1500
  }
1378
1501
  /**
@@ -1386,7 +1509,8 @@ function createSchemaParser(ctx) {
1386
1509
  name,
1387
1510
  title: schema.title,
1388
1511
  description: schema.description,
1389
- deprecated: schema.deprecated
1512
+ deprecated: schema.deprecated,
1513
+ format: schema.format
1390
1514
  });
1391
1515
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1392
1516
  return _kubb_core.ast.createSchema({
@@ -1476,6 +1600,15 @@ function createSchemaParser(ctx) {
1476
1600
  }, rawOptions);
1477
1601
  const nullInEnum = schema.enum.includes(null);
1478
1602
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1603
+ if (nullInEnum && filteredValues.length === 0) return _kubb_core.ast.createSchema({
1604
+ type: "null",
1605
+ primitive: "null",
1606
+ name,
1607
+ title: schema.title,
1608
+ description: schema.description,
1609
+ deprecated: schema.deprecated,
1610
+ format: schema.format
1611
+ });
1479
1612
  const enumNullable = nullable || nullInEnum || void 0;
1480
1613
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1481
1614
  const enumPrimitive = getPrimitiveType(type);
@@ -1490,7 +1623,8 @@ function createSchemaParser(ctx) {
1490
1623
  readOnly: schema.readOnly,
1491
1624
  writeOnly: schema.writeOnly,
1492
1625
  default: enumDefault,
1493
- example: schema.example
1626
+ example: schema.example,
1627
+ format: schema.format
1494
1628
  };
1495
1629
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1496
1630
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
@@ -1524,20 +1658,23 @@ function createSchemaParser(ctx) {
1524
1658
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1525
1659
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1526
1660
  const resolvedPropSchema = propSchema;
1527
- const propNullable = isNullable(resolvedPropSchema);
1661
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1528
1662
  const propNode = parseSchema({
1529
1663
  schema: resolvedPropSchema,
1530
- name: _kubb_core.ast.childName(name, propName)
1664
+ name: (0, _kubb_ast_utils.childName)(name, propName)
1531
1665
  }, 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
- }
1666
+ const schemaNode = (() => {
1667
+ const node = _kubb_core.ast.setEnumName(propNode, name, propName, options.enumSuffix);
1668
+ const tupleNode = _kubb_core.ast.narrowSchema(node, "tuple");
1669
+ if (tupleNode?.items) {
1670
+ const namedItems = tupleNode.items.map((item) => _kubb_core.ast.setEnumName(item, name, propName, options.enumSuffix));
1671
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1672
+ ...tupleNode,
1673
+ items: namedItems
1674
+ };
1675
+ }
1676
+ return node;
1677
+ })();
1541
1678
  return _kubb_core.ast.createProperty({
1542
1679
  name: propName,
1543
1680
  schema: {
@@ -1548,11 +1685,12 @@ function createSchemaParser(ctx) {
1548
1685
  });
1549
1686
  }) : [];
1550
1687
  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) });
1688
+ const additionalPropertiesNode = (() => {
1689
+ if (additionalProperties === true) return true;
1690
+ if (additionalProperties === false) return false;
1691
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1692
+ if (additionalProperties) return _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1693
+ })();
1556
1694
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1557
1695
  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
1696
  const objectNode = _kubb_core.ast.createSchema({
@@ -1565,10 +1703,10 @@ function createSchemaParser(ctx) {
1565
1703
  maxProperties: schema.maxProperties,
1566
1704
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1567
1705
  });
1568
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1706
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1569
1707
  const discPropName = schema.discriminator.propertyName;
1570
1708
  const values = Object.keys(schema.discriminator.mapping);
1571
- const enumName = name ? _kubb_core.ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
1709
+ const enumName = name ? (0, _kubb_ast_utils.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1572
1710
  return _kubb_core.ast.setDiscriminatorEnum({
1573
1711
  node: objectNode,
1574
1712
  propertyName: discPropName,
@@ -1599,7 +1737,7 @@ function createSchemaParser(ctx) {
1599
1737
  */
1600
1738
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1601
1739
  const rawItems = schema.items;
1602
- const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
1740
+ const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast_utils.enumPropName)(null, name, options.enumSuffix) : name;
1603
1741
  const items = rawItems ? [parseSchema({
1604
1742
  schema: rawItems,
1605
1743
  name: itemName
@@ -1663,15 +1801,148 @@ function createSchemaParser(ctx) {
1663
1801
  title: schema.title,
1664
1802
  description: schema.description,
1665
1803
  deprecated: schema.deprecated,
1666
- nullable
1804
+ nullable,
1805
+ format: schema.format
1806
+ });
1807
+ }
1808
+ /**
1809
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1810
+ * into a `blob` node.
1811
+ */
1812
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1813
+ return _kubb_core.ast.createSchema({
1814
+ type: "blob",
1815
+ primitive: "string",
1816
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1667
1817
  });
1668
1818
  }
1669
1819
  /**
1820
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1821
+ *
1822
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1823
+ * falls through and handles it as that single type with nullability already folded in.
1824
+ */
1825
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1826
+ const types = schema.type;
1827
+ const nonNullTypes = types.filter((t) => t !== "null");
1828
+ if (nonNullTypes.length <= 1) return null;
1829
+ const arrayNullable = types.includes("null") || nullable || void 0;
1830
+ return _kubb_core.ast.createSchema({
1831
+ type: "union",
1832
+ members: nonNullTypes.map((t) => parseSchema({
1833
+ schema: {
1834
+ ...schema,
1835
+ type: t
1836
+ },
1837
+ name
1838
+ }, rawOptions)),
1839
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1840
+ });
1841
+ }
1842
+ /**
1843
+ * Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
1844
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1845
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1846
+ * match/convert/fall-through contract.
1847
+ */
1848
+ const schemaRules = [
1849
+ {
1850
+ name: "ref",
1851
+ match: ({ schema }) => dialect.isReference(schema),
1852
+ convert: convertRef
1853
+ },
1854
+ {
1855
+ name: "allOf",
1856
+ match: ({ schema }) => !!schema.allOf?.length,
1857
+ convert: convertAllOf
1858
+ },
1859
+ {
1860
+ name: "union",
1861
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1862
+ convert: convertUnion
1863
+ },
1864
+ {
1865
+ name: "const",
1866
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1867
+ convert: convertConst
1868
+ },
1869
+ {
1870
+ name: "format",
1871
+ match: ({ schema }) => !!schema.format,
1872
+ convert: convertFormat
1873
+ },
1874
+ {
1875
+ name: "blob",
1876
+ match: ({ schema }) => dialect.isBinary(schema),
1877
+ convert: convertBlob
1878
+ },
1879
+ {
1880
+ name: "multi-type",
1881
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1882
+ convert: convertMultiType
1883
+ },
1884
+ {
1885
+ name: "constrained-string",
1886
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1887
+ convert: convertString
1888
+ },
1889
+ {
1890
+ name: "constrained-number",
1891
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1892
+ convert: (ctx) => convertNumeric(ctx, "number")
1893
+ },
1894
+ {
1895
+ name: "enum",
1896
+ match: ({ schema }) => !!schema.enum?.length,
1897
+ convert: convertEnum
1898
+ },
1899
+ {
1900
+ name: "object",
1901
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1902
+ convert: convertObject
1903
+ },
1904
+ {
1905
+ name: "tuple",
1906
+ match: ({ schema }) => "prefixItems" in schema,
1907
+ convert: convertTuple
1908
+ },
1909
+ {
1910
+ name: "array",
1911
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1912
+ convert: convertArray
1913
+ },
1914
+ {
1915
+ name: "string",
1916
+ match: ({ type }) => type === "string",
1917
+ convert: convertString
1918
+ },
1919
+ {
1920
+ name: "number",
1921
+ match: ({ type }) => type === "number",
1922
+ convert: (ctx) => convertNumeric(ctx, "number")
1923
+ },
1924
+ {
1925
+ name: "integer",
1926
+ match: ({ type }) => type === "integer",
1927
+ convert: (ctx) => convertNumeric(ctx, "integer")
1928
+ },
1929
+ {
1930
+ name: "boolean",
1931
+ match: ({ type }) => type === "boolean",
1932
+ convert: convertBoolean
1933
+ },
1934
+ {
1935
+ name: "null",
1936
+ match: ({ type }) => type === "null",
1937
+ convert: convertNull
1938
+ }
1939
+ ];
1940
+ /**
1670
1941
  * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1671
1942
  *
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).
1943
+ * Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
1944
+ * via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
1945
+ * `emptySchemaType`.
1675
1946
  */
1676
1947
  function parseSchema({ schema, name }, rawOptions) {
1677
1948
  const options = {
@@ -1683,75 +1954,40 @@ function createSchemaParser(ctx) {
1683
1954
  schema: flattenedSchema,
1684
1955
  name
1685
1956
  }, 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 = {
1957
+ const nullable = dialect.isNullable(schema) || void 0;
1958
+ const schemaCtx = {
1690
1959
  schema,
1691
1960
  name,
1692
1961
  nullable,
1693
- defaultValue,
1694
- type,
1962
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1963
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1695
1964
  rawOptions,
1696
1965
  options
1697
1966
  };
1698
- if (isReference(schema)) return convertRef(ctx);
1699
- if (schema.allOf?.length) return convertAllOf(ctx);
1700
- if ([...schema.oneOf ?? [], ...schema.anyOf ?? []].length) return convertUnion(ctx);
1701
- if ("const" in schema && schema.const !== void 0) return convertConst(ctx);
1702
- if (schema.format) {
1703
- const formatResult = convertFormat(ctx);
1704
- if (formatResult) return formatResult;
1705
- }
1706
- if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return _kubb_core.ast.createSchema({
1707
- type: "blob",
1708
- primitive: "string",
1709
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1710
- });
1711
- if (Array.isArray(schema.type) && schema.type.length > 1) {
1712
- const nonNullTypes = schema.type.filter((t) => t !== "null");
1713
- const arrayNullable = schema.type.includes("null") || nullable || void 0;
1714
- if (nonNullTypes.length > 1) return _kubb_core.ast.createSchema({
1715
- type: "union",
1716
- members: nonNullTypes.map((t) => parseSchema({
1717
- schema: {
1718
- ...schema,
1719
- type: t
1720
- },
1721
- name
1722
- }, rawOptions)),
1723
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1724
- });
1725
- }
1726
- if (!type) {
1727
- if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
1728
- if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
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);
1967
+ const node = _kubb_core.ast.dispatch(schemaRules, schemaCtx);
1968
+ if (node) return node;
1739
1969
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1740
1970
  return _kubb_core.ast.createSchema({
1741
1971
  type: emptyType,
1742
1972
  name,
1743
1973
  title: schema.title,
1744
- description: schema.description
1974
+ description: schema.description,
1975
+ format: schema.format
1745
1976
  });
1746
1977
  }
1747
1978
  /**
1748
1979
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1749
1980
  */
1750
- function parseParameter(options, param) {
1981
+ function parseParameter(options, param, parentName) {
1751
1982
  const required = param["required"] ?? false;
1752
- const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1983
+ const paramName = param["name"];
1984
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
1985
+ const schema = param["schema"] ? parseSchema({
1986
+ schema: param["schema"],
1987
+ name: schemaName
1988
+ }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1753
1989
  return _kubb_core.ast.createParameter({
1754
- name: param["name"],
1990
+ name: paramName,
1755
1991
  in: param["in"],
1756
1992
  schema: {
1757
1993
  ...schema,
@@ -1788,27 +2024,33 @@ function createSchemaParser(ctx) {
1788
2024
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1789
2025
  */
1790
2026
  function collectPropertyKeysByFlag(schema, flag) {
1791
- if (!schema?.properties) return void 0;
2027
+ if (!schema?.properties) return null;
1792
2028
  const keys = [];
1793
2029
  for (const key in schema.properties) {
1794
2030
  const prop = schema.properties[key];
1795
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
2031
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1796
2032
  }
1797
- return keys.length ? keys : void 0;
2033
+ return keys.length ? keys : null;
1798
2034
  }
1799
2035
  /**
1800
2036
  * Converts an OAS `Operation` into an `OperationNode`.
1801
2037
  */
1802
2038
  function parseOperation(options, operation) {
1803
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
2039
+ const operationId = operation.getOperationId();
2040
+ const operationName = operationId ? pascalCase(operationId) : void 0;
2041
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1804
2042
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1805
2043
  const requestBodyMeta = getRequestBodyMeta(operation);
2044
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1806
2045
  const content = allContentTypes.flatMap((ct) => {
1807
2046
  const schema = getRequestSchema(document, operation, { contentType: ct });
1808
2047
  if (!schema) return [];
1809
2048
  return [{
1810
2049
  contentType: ct,
1811
- schema: _kubb_core.ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
2050
+ schema: _kubb_core.ast.syncOptionality(parseSchema({
2051
+ schema,
2052
+ name: requestBodyName
2053
+ }, options), requestBodyMeta.required),
1812
2054
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1813
2055
  }];
1814
2056
  });
@@ -1819,21 +2061,36 @@ function createSchemaParser(ctx) {
1819
2061
  } : void 0;
1820
2062
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1821
2063
  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 ?? "");
2064
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2065
+ const { description } = getResponseMeta(responseObj);
2066
+ const parseEntrySchema = (contentType) => {
2067
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
2068
+ return {
2069
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2070
+ schema: raw,
2071
+ name: responseName
2072
+ }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
2073
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2074
+ };
2075
+ };
2076
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
2077
+ contentType,
2078
+ ...parseEntrySchema(contentType)
2079
+ }));
2080
+ if (content.length === 0) content.push({
2081
+ contentType: operation.contentType || "application/json",
2082
+ ...parseEntrySchema(ctx.contentType)
2083
+ });
1826
2084
  return _kubb_core.ast.createResponse({
1827
2085
  statusCode,
1828
2086
  description,
1829
- schema,
1830
- mediaType,
1831
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
2087
+ content
1832
2088
  });
1833
2089
  });
1834
2090
  const urlPath = new URLPath(operation.path);
1835
2091
  return _kubb_core.ast.createOperation({
1836
- operationId: operation.getOperationId(),
2092
+ operationId,
2093
+ protocol: "http",
1837
2094
  method: operation.method.toUpperCase(),
1838
2095
  path: urlPath.path,
1839
2096
  tags: operation.getTags().map((tag) => tag.name),
@@ -1851,59 +2108,336 @@ function createSchemaParser(ctx) {
1851
2108
  parseParameter
1852
2109
  };
1853
2110
  }
2111
+ //#endregion
2112
+ //#region src/discriminator.ts
1854
2113
  /**
1855
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
2114
+ * Builds a map of child schema names discriminator patch data by scanning the given
2115
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
1856
2116
  *
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.
2117
+ * The streaming path calls this on a small pre-parsed subset of schemas (only the
2118
+ * discriminator parents) rather than on all schemas at once.
2119
+ */
2120
+ function buildDiscriminatorChildMap(schemas) {
2121
+ const childMap = /* @__PURE__ */ new Map();
2122
+ for (const schema of schemas) {
2123
+ let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
2124
+ if (!unionNode) {
2125
+ const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
2126
+ if (intersectionMembers) for (const m of intersectionMembers) {
2127
+ const u = _kubb_core.ast.narrowSchema(m, "union");
2128
+ if (u) {
2129
+ unionNode = u;
2130
+ break;
2131
+ }
2132
+ }
2133
+ }
2134
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
2135
+ const { discriminatorPropertyName, members } = unionNode;
2136
+ for (const member of members) {
2137
+ const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
2138
+ if (!intersectionNode?.members) continue;
2139
+ let refNode = null;
2140
+ let objNode = null;
2141
+ for (const m of intersectionNode.members) {
2142
+ refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
2143
+ objNode ??= _kubb_core.ast.narrowSchema(m, "object");
2144
+ }
2145
+ if (!refNode?.name || !objNode) continue;
2146
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
2147
+ const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : null;
2148
+ if (!enumNode?.enumValues?.length) continue;
2149
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
2150
+ if (!enumValues.length) continue;
2151
+ const existing = childMap.get(refNode.name);
2152
+ if (!existing) {
2153
+ childMap.set(refNode.name, {
2154
+ propertyName: discriminatorPropertyName,
2155
+ enumValues: [...enumValues]
2156
+ });
2157
+ continue;
2158
+ }
2159
+ existing.enumValues.push(...enumValues);
2160
+ }
2161
+ }
2162
+ return childMap;
2163
+ }
2164
+ /**
2165
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
2166
+ * the discriminant property). Used by the streaming path to apply patches inline per yield
2167
+ * without buffering all schemas.
2168
+ */
2169
+ function patchDiscriminatorNode(node, entry) {
2170
+ const objectNode = _kubb_core.ast.narrowSchema(node, "object");
2171
+ if (!objectNode) return node;
2172
+ const { propertyName, enumValues } = entry;
2173
+ const enumSchema = _kubb_core.ast.createSchema({
2174
+ type: "enum",
2175
+ enumValues
2176
+ });
2177
+ const newProp = _kubb_core.ast.createProperty({
2178
+ name: propertyName,
2179
+ required: true,
2180
+ schema: enumSchema
2181
+ });
2182
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
2183
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
2184
+ return {
2185
+ ...objectNode,
2186
+ properties: newProperties
2187
+ };
2188
+ }
2189
+ //#endregion
2190
+ //#region src/schemaDiagnostics.ts
2191
+ /**
2192
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
2193
+ * top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
2194
+ * pointer as it descends so a nested field reports against its full path
2195
+ * (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
2196
+ * resolved schema is reported under its own walk. Reports land in the active build run, are a
2197
+ * no-op outside one, and repeats are deduped by the build.
2198
+ */
2199
+ function reportSchemaDiagnostics({ node, name }) {
2200
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2201
+ }
2202
+ /**
2203
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2204
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2205
+ */
2206
+ function escapePointerToken(token) {
2207
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2208
+ }
2209
+ function visit(node, pointer) {
2210
+ if (node.deprecated) _kubb_core.Diagnostics.report({
2211
+ code: _kubb_core.Diagnostics.code.deprecated,
2212
+ severity: "info",
2213
+ message: "This schema is marked as deprecated.",
2214
+ location: {
2215
+ kind: "schema",
2216
+ pointer
2217
+ }
2218
+ });
2219
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
2220
+ code: _kubb_core.Diagnostics.code.unsupportedFormat,
2221
+ severity: "warning",
2222
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2223
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2224
+ location: {
2225
+ kind: "schema",
2226
+ pointer
2227
+ }
2228
+ });
2229
+ if (node.type === "object") {
2230
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2231
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2232
+ return;
2233
+ }
2234
+ if (node.type === "array") {
2235
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2236
+ return;
2237
+ }
2238
+ if (node.type === "tuple") {
2239
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2240
+ return;
2241
+ }
2242
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2243
+ }
2244
+ //#endregion
2245
+ //#region src/stream.ts
2246
+ /**
2247
+ * Builds the deduplication plan from the already-parsed top-level schema nodes plus a
2248
+ * single extra parse pass over operations (so duplicates in request/response bodies are seen).
2249
+ *
2250
+ * Only enums and objects are candidates, and object shapes that reference a circular schema are
2251
+ * rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
2252
+ * name (collision-resolved against existing component names); shapes without a name stay inline.
2253
+ */
2254
+ function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
2255
+ const circularSchemas = new Set(circularNames);
2256
+ const usedNames = new Set(schemaNames);
2257
+ return _kubb_core.ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
2258
+ isCandidate: (node) => {
2259
+ if (node.type === "enum") return true;
2260
+ if (node.type !== "object") return false;
2261
+ if (node.name && circularSchemas.has(node.name)) return false;
2262
+ return !_kubb_core.ast.containsCircularRef(node, { circularSchemas });
2263
+ },
2264
+ nameFor: (node) => {
2265
+ const base = node.name;
2266
+ if (!base) return null;
2267
+ let name = base;
2268
+ let counter = 2;
2269
+ while (usedNames.has(name)) name = `${base}${counter++}`;
2270
+ usedNames.add(name);
2271
+ return name;
2272
+ },
2273
+ refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
2274
+ });
2275
+ }
2276
+ /**
2277
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
2278
+ * interpolating any `serverVariables` into the URL template.
2279
+ *
2280
+ * Returns `null` when `serverIndex` is omitted or out of range.
1860
2281
  *
1861
- * Returns the AST root and a `nameMapping` for resolving schema references.
2282
+ * @example Resolve the first server
2283
+ * `resolveBaseUrl({ document, serverIndex: 0 })`
2284
+ *
2285
+ * @example Override a path variable
2286
+ * `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
2287
+ */
2288
+ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
2289
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
2290
+ return server?.url ? resolveServerUrl(server, serverVariables) : null;
2291
+ }
2292
+ /**
2293
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
2294
+ *
2295
+ * Three things happen in this single pass:
2296
+ * - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
2297
+ * - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
2298
+ * - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
2299
+ * The `allNodes` array is local and drops out of scope as soon as this function returns.
2300
+ *
2301
+ * After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
2302
+ * Both are proportional to the number of aliases or discriminator parents, not total schema count.
2303
+ *
2304
+ * Each schema is parsed again during the streaming pass. This is intentional.
2305
+ * Holding the parsed nodes in memory here would defeat the streaming memory benefit.
1862
2306
  *
1863
2307
  * @example
1864
2308
  * ```ts
1865
- * import { parseOas } from '@kubb/adapter-oas'
1866
- *
1867
- * const document = await parseFromConfig(config)
1868
- * const { root, nameMapping } = parseOas(document, { dateType: 'date', contentType: 'application/json' })
2309
+ * const { refAliasMap, enumNames, circularNames } = preScan({
2310
+ * schemas,
2311
+ * parseSchema,
2312
+ * parserOptions,
2313
+ * discriminator: 'strict',
2314
+ * })
1869
2315
  * ```
1870
2316
  */
1871
- function parseOas(document, options = {}) {
1872
- const { contentType, ...parserOptions } = options;
1873
- const mergedOptions = {
1874
- ...DEFAULT_PARSER_OPTIONS,
1875
- ...parserOptions
1876
- };
1877
- const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
1878
- const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
1879
- document,
1880
- contentType
1881
- });
1882
- const schemas = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({
1883
- schema,
1884
- name
1885
- }, mergedOptions));
1886
- const paths = new oas.default(document).getPaths();
1887
- const operations = Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null));
2317
+ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
2318
+ const allNodes = [];
2319
+ const refAliasMap = /* @__PURE__ */ new Map();
2320
+ const enumNames = [];
2321
+ const discriminatorParentNodes = [];
2322
+ for (const [name, schema] of Object.entries(schemas)) {
2323
+ const node = parseSchema({
2324
+ schema,
2325
+ name
2326
+ }, parserOptions);
2327
+ allNodes.push(node);
2328
+ reportSchemaDiagnostics({
2329
+ node,
2330
+ name
2331
+ });
2332
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2333
+ if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
2334
+ if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2335
+ }
2336
+ const circularNames = [..._kubb_core.ast.findCircularSchemas(allNodes)];
2337
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2338
+ let dedupePlan = null;
2339
+ if (dedupe) {
2340
+ const operationNodes = [];
2341
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2342
+ if (!operation) continue;
2343
+ const operationNode = parseOperation(parserOptions, operation);
2344
+ if (operationNode) operationNodes.push(operationNode);
2345
+ }
2346
+ dedupePlan = createDedupePlan({
2347
+ schemaNodes: allNodes,
2348
+ operationNodes,
2349
+ schemaNames: Object.keys(schemas),
2350
+ circularNames
2351
+ });
2352
+ for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
2353
+ }
1888
2354
  return {
1889
- root: _kubb_core.ast.createInput({
1890
- schemas,
1891
- operations
1892
- }),
1893
- nameMapping
2355
+ refAliasMap,
2356
+ enumNames,
2357
+ circularNames,
2358
+ discriminatorChildMap,
2359
+ dedupePlan
1894
2360
  };
1895
2361
  }
2362
+ /**
2363
+ * Creates a lazy `InputStreamNode` from already-resolved adapter state.
2364
+ *
2365
+ * The schema and operation iterables each start a fresh parse pass on every
2366
+ * `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
2367
+ * stream object independently without sharing a cursor or holding all nodes in memory.
2368
+ *
2369
+ * Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
2370
+ * with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
2371
+ *
2372
+ * @example
2373
+ * ```ts
2374
+ * const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
2375
+ * for await (const schema of streamNode.schemas) {
2376
+ * // each call to for-await restarts from the first schema
2377
+ * }
2378
+ * ```
2379
+ */
2380
+ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
2381
+ const rewriteTopLevelSchema = (node) => {
2382
+ if (!dedupePlan) return node;
2383
+ const canonical = dedupePlan.canonicalBySignature.get(_kubb_core.ast.schemaSignature(node));
2384
+ if (canonical && canonical.name !== node.name) return _kubb_core.ast.createSchema({
2385
+ type: "ref",
2386
+ name: node.name ?? null,
2387
+ ref: canonical.ref,
2388
+ description: node.description,
2389
+ deprecated: node.deprecated
2390
+ });
2391
+ return _kubb_core.ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
2392
+ };
2393
+ const schemasIterable = { [Symbol.asyncIterator]() {
2394
+ return (async function* () {
2395
+ if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
2396
+ for (const [name, schema] of Object.entries(schemas)) {
2397
+ const alias = refAliasMap.get(name);
2398
+ if (alias?.name && schemas[alias.name]) {
2399
+ yield rewriteTopLevelSchema({
2400
+ ...parseSchema({
2401
+ schema: schemas[alias.name],
2402
+ name: alias.name
2403
+ }, parserOptions),
2404
+ name
2405
+ });
2406
+ continue;
2407
+ }
2408
+ const parsed = parseSchema({
2409
+ schema,
2410
+ name
2411
+ }, parserOptions);
2412
+ yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
2413
+ }
2414
+ })();
2415
+ } };
2416
+ const operationsIterable = { [Symbol.asyncIterator]() {
2417
+ return (async function* () {
2418
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2419
+ if (!operation) continue;
2420
+ const node = parseOperation(parserOptions, operation);
2421
+ if (node) yield dedupePlan ? _kubb_core.ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
2422
+ }
2423
+ })();
2424
+ } };
2425
+ return _kubb_core.ast.createStreamInput(schemasIterable, operationsIterable, meta);
2426
+ }
1896
2427
  //#endregion
1897
2428
  //#region src/adapter.ts
1898
2429
  /**
1899
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2430
+ * Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
1900
2431
  */
1901
2432
  const adapterOasName = "oas";
1902
2433
  /**
1903
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2434
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2435
+ * file at `input.path`, validates it, resolves the base URL, and converts every
2436
+ * schema and operation into the universal AST that every downstream plugin
2437
+ * consumes.
1904
2438
  *
1905
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1906
- * everything into an `InputNode` that downstream plugins consume.
2439
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2440
+ * integer width, server URL) apply to every plugin in the build.
1907
2441
  *
1908
2442
  * @example
1909
2443
  * ```ts
@@ -1912,17 +2446,118 @@ const adapterOasName = "oas";
1912
2446
  * import { pluginTs } from '@kubb/plugin-ts'
1913
2447
  *
1914
2448
  * export default defineConfig({
1915
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1916
- * input: { path: './openapi.yaml' },
2449
+ * input: { path: './petStore.yaml' },
2450
+ * output: { path: './src/gen' },
2451
+ * adapter: adapterOas({
2452
+ * serverIndex: 0,
2453
+ * discriminator: 'inherit',
2454
+ * dateType: 'date',
2455
+ * }),
1917
2456
  * plugins: [pluginTs()],
1918
2457
  * })
1919
2458
  * ```
1920
2459
  */
1921
2460
  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;
2461
+ 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;
2462
+ const parserOptions = {
2463
+ ...DEFAULT_PARSER_OPTIONS,
2464
+ dateType,
2465
+ integerType,
2466
+ unknownType,
2467
+ emptySchemaType,
2468
+ enumSuffix
2469
+ };
1923
2470
  let nameMapping = /* @__PURE__ */ new Map();
1924
- let parsedDocument;
1925
- let inputNode;
2471
+ let parsedDocument = null;
2472
+ const documentCache = /* @__PURE__ */ new WeakMap();
2473
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2474
+ const baseOasCache = /* @__PURE__ */ new WeakMap();
2475
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2476
+ const preScanCache = /* @__PURE__ */ new WeakMap();
2477
+ function ensureDocument(source) {
2478
+ const cached = documentCache.get(source);
2479
+ if (cached) return cached;
2480
+ const promise = (async () => {
2481
+ const fresh = await parseFromConfig(source);
2482
+ if (validate) await validateDocument(fresh);
2483
+ parsedDocument = fresh;
2484
+ return fresh;
2485
+ })();
2486
+ documentCache.set(source, promise);
2487
+ return promise;
2488
+ }
2489
+ function ensureSchemas(document) {
2490
+ const cached = schemasCache.get(document);
2491
+ if (cached) return cached;
2492
+ const promise = Promise.resolve().then(() => {
2493
+ const result = getSchemas(document, { contentType });
2494
+ nameMapping = result.nameMapping;
2495
+ return result.schemas;
2496
+ });
2497
+ schemasCache.set(document, promise);
2498
+ return promise;
2499
+ }
2500
+ function ensureBaseOas(document) {
2501
+ const cached = baseOasCache.get(document);
2502
+ if (cached) return cached;
2503
+ const baseOas = new oas.default(document);
2504
+ baseOasCache.set(document, baseOas);
2505
+ return baseOas;
2506
+ }
2507
+ function ensureSchemaParser(document) {
2508
+ const cached = schemaParserCache.get(document);
2509
+ if (cached) return cached;
2510
+ const parser = createSchemaParser({
2511
+ document,
2512
+ contentType
2513
+ });
2514
+ schemaParserCache.set(document, parser);
2515
+ return parser;
2516
+ }
2517
+ function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
2518
+ const cached = preScanCache.get(document);
2519
+ if (cached) return cached;
2520
+ const result = preScan({
2521
+ schemas,
2522
+ parseSchema,
2523
+ parseOperation,
2524
+ baseOas,
2525
+ parserOptions,
2526
+ discriminator,
2527
+ dedupe
2528
+ });
2529
+ preScanCache.set(document, result);
2530
+ return result;
2531
+ }
2532
+ async function createStream(source) {
2533
+ const document = await ensureDocument(source);
2534
+ const schemas = await ensureSchemas(document);
2535
+ const { parseSchema, parseOperation } = ensureSchemaParser(document);
2536
+ const baseOas = ensureBaseOas(document);
2537
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
2538
+ return createInputStream({
2539
+ schemas,
2540
+ parseSchema,
2541
+ parseOperation,
2542
+ baseOas,
2543
+ parserOptions,
2544
+ refAliasMap,
2545
+ discriminatorChildMap,
2546
+ dedupePlan,
2547
+ meta: {
2548
+ title: document.info?.title,
2549
+ description: document.info?.description,
2550
+ version: document.info?.version,
2551
+ baseURL: resolveBaseUrl({
2552
+ document,
2553
+ serverIndex,
2554
+ serverVariables
2555
+ }),
2556
+ circularNames,
2557
+ enumNames
2558
+ }
2559
+ });
2560
+ }
1926
2561
  return {
1927
2562
  name: "oas",
1928
2563
  get options() {
@@ -1932,6 +2567,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1932
2567
  serverIndex,
1933
2568
  serverVariables,
1934
2569
  discriminator,
2570
+ dedupe,
1935
2571
  dateType,
1936
2572
  integerType,
1937
2573
  unknownType,
@@ -1943,8 +2579,9 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1943
2579
  get document() {
1944
2580
  return parsedDocument;
1945
2581
  },
1946
- get inputNode() {
1947
- return inputNode;
2582
+ async validate(input, options) {
2583
+ await assertInputExists(input);
2584
+ await validateDocument(await parseDocument(input), options);
1948
2585
  },
1949
2586
  getImports(node, resolve) {
1950
2587
  return _kubb_core.ast.collectImports({
@@ -1952,7 +2589,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1952
2589
  nameMapping,
1953
2590
  resolve: (schemaName) => {
1954
2591
  const result = resolve(schemaName);
1955
- if (!result) return;
2592
+ if (!result) return null;
1956
2593
  return _kubb_core.ast.createImport({
1957
2594
  name: [result.name],
1958
2595
  path: result.path
@@ -1961,53 +2598,19 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1961
2598
  });
1962
2599
  },
1963
2600
  async parse(source) {
1964
- const document = await parseFromConfig(source);
1965
- if (validate) await validateDocument(document);
1966
- const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
1967
- const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1968
- const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
1969
- contentType,
1970
- dateType,
1971
- integerType,
1972
- unknownType,
1973
- emptySchemaType,
1974
- enumSuffix
2601
+ const streamNode = await createStream(source);
2602
+ const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)]);
2603
+ return _kubb_core.ast.createInput({
2604
+ schemas,
2605
+ operations,
2606
+ meta: streamNode.meta
1975
2607
  });
1976
- const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
1977
- nameMapping = parsedNameMapping;
1978
- parsedDocument = document;
1979
- inputNode = _kubb_core.ast.createInput({
1980
- ...node,
1981
- meta: {
1982
- title: document.info?.title,
1983
- description: document.info?.description,
1984
- version: document.info?.version,
1985
- baseURL
1986
- }
1987
- });
1988
- return inputNode;
1989
- }
2608
+ },
2609
+ stream: createStream
1990
2610
  };
1991
2611
  });
1992
2612
  //#endregion
1993
- //#region src/types.ts
1994
- /**
1995
- * Maps uppercase HTTP method names to lowercase for backwards compatibility.
1996
- *
1997
- * @example
1998
- * ```ts
1999
- * HttpMethods['GET'] // 'get'
2000
- * HttpMethods['POST'] // 'post'
2001
- * ```
2002
- */
2003
- const HttpMethods = Object.fromEntries(Object.entries(_kubb_core.ast.httpMethods).map(([lower, upper]) => [upper, lower]));
2004
- //#endregion
2005
- exports.HttpMethods = HttpMethods;
2006
2613
  exports.adapterOas = adapterOas;
2007
2614
  exports.adapterOasName = adapterOasName;
2008
- exports.mergeDocuments = mergeDocuments;
2009
- exports.parseDocument = parseDocument;
2010
- exports.parseFromConfig = parseFromConfig;
2011
- exports.validateDocument = validateDocument;
2012
2615
 
2013
2616
  //# sourceMappingURL=index.cjs.map