@kubb/adapter-oas 5.0.0-beta.4 → 5.0.0-beta.40

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,16 @@ 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);
29
+ let node_fs_promises = require("node:fs/promises");
27
30
  let _redocly_openapi_core = require("@redocly/openapi-core");
28
31
  let oas_normalize = require("oas-normalize");
29
32
  oas_normalize = __toESM(oas_normalize, 1);
30
33
  let swagger2openapi = require("swagger2openapi");
31
34
  swagger2openapi = __toESM(swagger2openapi, 1);
32
- let oas = require("oas");
33
- oas = __toESM(oas, 1);
34
35
  let oas_types = require("oas/types");
35
36
  let oas_utils = require("oas/utils");
36
37
  //#region src/constants.ts
@@ -115,6 +116,18 @@ const structuralKeys = new Set([
115
116
  * formatMap['float'] // 'number'
116
117
  * ```
117
118
  */
119
+ /**
120
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
121
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
122
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
123
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
124
+ */
125
+ const specialCasedFormats = new Set([
126
+ "int64",
127
+ "date-time",
128
+ "date",
129
+ "time"
130
+ ]);
118
131
  const formatMap = {
119
132
  uuid: "uuid",
120
133
  email: "email",
@@ -153,87 +166,6 @@ const typeOptionMap = new Map([
153
166
  ["void", _kubb_core.ast.schemaTypes.void]
154
167
  ]);
155
168
  //#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
169
  //#region ../../internals/utils/src/casing.ts
238
170
  /**
239
171
  * Shared implementation for camelCase and PascalCase conversion.
@@ -298,6 +230,23 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
298
230
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
299
231
  }
300
232
  //#endregion
233
+ //#region ../../internals/utils/src/fs.ts
234
+ /**
235
+ * Resolves to `true` when the file or directory at `path` exists.
236
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * if (await exists('./kubb.config.ts')) {
241
+ * const content = await read('./kubb.config.ts')
242
+ * }
243
+ * ```
244
+ */
245
+ async function exists(path) {
246
+ if (typeof Bun !== "undefined") return Bun.file(path).exists();
247
+ return (0, node_fs_promises.access)(path).then(() => true, () => false);
248
+ }
249
+ //#endregion
301
250
  //#region ../../internals/utils/src/object.ts
302
251
  /**
303
252
  * Returns `true` when `value` is a plain (non-null, non-array) object.
@@ -543,12 +492,13 @@ var URLPath = class {
543
492
  * @example
544
493
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
545
494
  */
546
- toTemplateString({ prefix = "", replacer } = {}) {
547
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
495
+ toTemplateString({ prefix, replacer } = {}) {
496
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
548
497
  if (i % 2 === 0) return part;
549
498
  const param = this.#transformParam(part);
550
499
  return `\${${replacer ? replacer(param) : param}}`;
551
- }).join("")}\``;
500
+ }).join("");
501
+ return `\`${prefix ?? ""}${result}\``;
552
502
  }
553
503
  /**
554
504
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -686,12 +636,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
686
636
  * ```
687
637
  */
688
638
  async function mergeDocuments(pathOrApi) {
689
- const documents = [];
690
- for (const p of pathOrApi) documents.push(await parseDocument(p, {
639
+ const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
691
640
  enablePaths: false,
692
641
  canBundle: false
693
- }));
694
- if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
642
+ })));
643
+ if (documents.length === 0) throw new _kubb_core.Diagnostics.Error({
644
+ code: _kubb_core.Diagnostics.code.inputRequired,
645
+ severity: "error",
646
+ message: "No OAS documents were provided for merging.",
647
+ help: "Pass at least one path or document to `input.path`.",
648
+ location: { kind: "config" }
649
+ });
695
650
  const seed = {
696
651
  openapi: MERGE_OPENAPI_VERSION,
697
652
  info: {
@@ -707,9 +662,9 @@ async function mergeDocuments(pathOrApi) {
707
662
  * Creates a `Document` from an `AdapterSource`.
708
663
  *
709
664
  * 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.
665
+ * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
666
+ * - `{ type: 'paths' }` merges multiple file paths into a single document.
667
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
713
668
  *
714
669
  * @example
715
670
  * ```ts
@@ -717,14 +672,31 @@ async function mergeDocuments(pathOrApi) {
717
672
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
718
673
  * ```
719
674
  */
720
- function parseFromConfig(source) {
675
+ async function parseFromConfig(source) {
721
676
  if (source.type === "data") {
722
677
  if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
723
678
  return parseDocument(source.data, { canBundle: false });
724
679
  }
725
680
  if (source.type === "paths") return mergeDocuments(source.paths);
726
681
  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));
682
+ const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
683
+ await assertInputExists(resolved);
684
+ return parseDocument(resolved);
685
+ }
686
+ /**
687
+ * Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
688
+ * URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
689
+ * its parse error instead.
690
+ */
691
+ async function assertInputExists(input) {
692
+ if (new URLPath(input).isURL) return;
693
+ if (!await exists(input)) throw new _kubb_core.Diagnostics.Error({
694
+ code: _kubb_core.Diagnostics.code.inputNotFound,
695
+ severity: "error",
696
+ message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
697
+ help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
698
+ location: { kind: "config" }
699
+ });
728
700
  }
729
701
  /**
730
702
  * Validates an OpenAPI document using `oas-normalize` with colorized error output.
@@ -746,6 +718,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
746
718
  }
747
719
  //#endregion
748
720
  //#region src/refs.ts
721
+ const _refCache = /* @__PURE__ */ new WeakMap();
749
722
  /**
750
723
  * Resolves a local JSON pointer reference from a document.
751
724
  *
@@ -761,10 +734,31 @@ function resolveRef(document, $ref) {
761
734
  const origRef = $ref;
762
735
  $ref = $ref.trim();
763
736
  if ($ref === "") return null;
764
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
765
- else return null;
737
+ if (!$ref.startsWith("#")) return null;
738
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
739
+ let docCache = _refCache.get(document);
740
+ if (!docCache) {
741
+ docCache = /* @__PURE__ */ new Map();
742
+ _refCache.set(document, docCache);
743
+ }
744
+ if (docCache.has($ref)) return docCache.get($ref);
766
745
  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}.`);
746
+ if (!current) {
747
+ const diagnostic = {
748
+ code: _kubb_core.Diagnostics.code.refNotFound,
749
+ severity: "error",
750
+ message: `Could not find a definition for ${origRef}.`,
751
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
752
+ location: {
753
+ kind: "schema",
754
+ pointer: origRef,
755
+ ref: origRef
756
+ }
757
+ };
758
+ if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.Diagnostics.Error(diagnostic);
759
+ return null;
760
+ }
761
+ docCache.set($ref, current);
768
762
  return current;
769
763
  }
770
764
  /**
@@ -788,6 +782,35 @@ function dereferenceWithRef(document, schema) {
788
782
  return schema;
789
783
  }
790
784
  //#endregion
785
+ //#region src/dialect.ts
786
+ /**
787
+ * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
788
+ *
789
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
790
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
791
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
792
+ * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
793
+ * nullability, no discriminator object, binary via `contentEncoding` and reuses
794
+ * the rest unchanged.
795
+ *
796
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
797
+ * JSON Schema vocabulary, so the converters keep that common case.
798
+ *
799
+ * @example
800
+ * ```ts
801
+ * const parser = createSchemaParser(context) // uses oasDialect
802
+ * const parser = createSchemaParser(context, oasDialect) // explicit
803
+ * ```
804
+ */
805
+ const oasDialect = _kubb_core.ast.defineSchemaDialect({
806
+ name: "oas",
807
+ isNullable,
808
+ isReference,
809
+ isDiscriminator,
810
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
811
+ resolveRef
812
+ });
813
+ //#endregion
791
814
  //#region src/resolvers.ts
792
815
  /**
793
816
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
@@ -809,7 +832,16 @@ function resolveServerUrl(server, overrides) {
809
832
  for (const [key, variable] of Object.entries(server.variables)) {
810
833
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
811
834
  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(", ")}.`);
835
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.Diagnostics.Error({
836
+ code: _kubb_core.Diagnostics.code.invalidServerVariable,
837
+ severity: "error",
838
+ message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
839
+ help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
840
+ location: {
841
+ kind: "document",
842
+ pointer: "#/servers"
843
+ }
844
+ });
813
845
  url = url.replaceAll(`{${key}}`, value);
814
846
  }
815
847
  return url;
@@ -822,6 +854,15 @@ function getSchemaType(format) {
822
854
  return formatMap[format] ?? null;
823
855
  }
824
856
  /**
857
+ * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
858
+ * `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
859
+ * base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
860
+ * diagnostic in step with the parser as `formatMap` grows.
861
+ */
862
+ function isHandledFormat(format) {
863
+ return getSchemaType(format) !== null || specialCasedFormats.has(format);
864
+ }
865
+ /**
825
866
  * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
826
867
  * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
827
868
  */
@@ -831,12 +872,6 @@ function getPrimitiveType(type) {
831
872
  return "string";
832
873
  }
833
874
  /**
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
875
  * Returns all parameters for an operation, merging path-level and operation-level entries.
841
876
  * Operation-level parameters override path-level ones with the same `in:name` key.
842
877
  * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
@@ -924,7 +959,7 @@ function getRequestSchema(document, operation, options = {}) {
924
959
  /**
925
960
  * Flattens a keyword-only `allOf` into its parent schema.
926
961
  *
927
- * Only flattens when every member is a plain fragment no `$ref` and no structural keywords
962
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
928
963
  * (see `structuralKeys`). Outer schema values take precedence over fragment values.
929
964
  * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
930
965
  *
@@ -934,7 +969,7 @@ function getRequestSchema(document, operation, options = {}) {
934
969
  * // { type: 'object', properties: {}, description: 'A pet' }
935
970
  *
936
971
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
937
- * // returned unchanged contains a $ref
972
+ * // returned unchanged, contains a $ref
938
973
  * ```
939
974
  */
940
975
  /**
@@ -960,7 +995,7 @@ function flattenSchema(schema) {
960
995
  /**
961
996
  * Extracts the inline schema from a media-type `content` map.
962
997
  *
963
- * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
998
+ * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
964
999
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
965
1000
  *
966
1001
  * @example
@@ -979,21 +1014,22 @@ function extractSchemaFromContent(content, preferredContentType) {
979
1014
  /**
980
1015
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
981
1016
  */
982
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1017
+ function* collectRefs(schema) {
983
1018
  if (Array.isArray(schema)) {
984
- for (const item of schema) collectRefs(item, refs);
985
- return refs;
1019
+ for (const item of schema) yield* collectRefs(item);
1020
+ return;
986
1021
  }
987
1022
  if (schema && typeof schema === "object") for (const key in schema) {
988
1023
  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);
1024
+ if (!(key === "$ref" && typeof value === "string")) {
1025
+ yield* collectRefs(value);
1026
+ continue;
1027
+ }
1028
+ if (value.startsWith("#/components/schemas/")) {
1029
+ const name = value.slice(21);
1030
+ if (name) yield name;
1031
+ }
995
1032
  }
996
- return refs;
997
1033
  }
998
1034
  /**
999
1035
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -1009,7 +1045,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1009
1045
  */
1010
1046
  function sortSchemas(schemas) {
1011
1047
  const deps = /* @__PURE__ */ new Map();
1012
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
1048
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
1013
1049
  const sorted = [];
1014
1050
  const visited = /* @__PURE__ */ new Set();
1015
1051
  function visit(name, stack) {
@@ -1144,15 +1180,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1144
1180
  readOnly: schema.readOnly,
1145
1181
  writeOnly: schema.writeOnly,
1146
1182
  default: defaultValue,
1147
- example: schema.example
1183
+ example: schema.example,
1184
+ format: schema.format
1148
1185
  };
1149
1186
  }
1150
1187
  /**
1151
1188
  * Returns all request body content type keys for an operation.
1152
1189
  *
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.
1190
+ * The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
1191
+ * `getRequestSchema` already performs), so the returned list accurately reflects the
1192
+ * available content types even for referenced bodies.
1156
1193
  *
1157
1194
  * @example
1158
1195
  * ```ts
@@ -1166,6 +1203,31 @@ function getRequestBodyContentTypes(document, operation) {
1166
1203
  if (!body) return [];
1167
1204
  return body.content ? Object.keys(body.content) : [];
1168
1205
  }
1206
+ /**
1207
+ * Returns all response content type keys for an operation at a given status code.
1208
+ *
1209
+ * Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
1210
+ * so the returned list reflects the available content types even for referenced responses.
1211
+ *
1212
+ * @example
1213
+ * ```ts
1214
+ * getResponseBodyContentTypes(document, operation, 200)
1215
+ * // ['application/json', 'application/xml']
1216
+ * ```
1217
+ */
1218
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1219
+ if (operation.schema.responses) {
1220
+ const responses = operation.schema.responses;
1221
+ for (const key in responses) {
1222
+ const schema = responses[key];
1223
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
1224
+ }
1225
+ }
1226
+ const responseObj = operation.getResponseByStatusCode(statusCode);
1227
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1228
+ const body = responseObj;
1229
+ return body.content ? Object.keys(body.content) : [];
1230
+ }
1169
1231
  //#endregion
1170
1232
  //#region src/parser.ts
1171
1233
  /**
@@ -1194,9 +1256,9 @@ function normalizeArrayEnum(schema) {
1194
1256
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1195
1257
  * made possible by hoisting of function declarations.
1196
1258
  *
1197
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1259
+ * @internal
1198
1260
  */
1199
- function createSchemaParser(ctx) {
1261
+ function createSchemaParser(ctx, dialect = oasDialect) {
1200
1262
  const document = ctx.document;
1201
1263
  /**
1202
1264
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1204,6 +1266,15 @@ function createSchemaParser(ctx) {
1204
1266
  */
1205
1267
  const resolvingRefs = /* @__PURE__ */ new Set();
1206
1268
  /**
1269
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1270
+ *
1271
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1272
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1273
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1274
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1275
+ */
1276
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1277
+ /**
1207
1278
  * Converts a `$ref` schema into a `RefSchemaNode`.
1208
1279
  *
1209
1280
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1212,16 +1283,22 @@ function createSchemaParser(ctx) {
1212
1283
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1213
1284
  */
1214
1285
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1215
- let resolvedSchema;
1286
+ let resolvedSchema = null;
1216
1287
  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);
1288
+ if (refPath && !resolvingRefs.has(refPath)) {
1289
+ if (!resolvedRefCache.has(refPath)) {
1290
+ try {
1291
+ const referenced = dialect.resolveRef(document, refPath);
1292
+ if (referenced) {
1293
+ resolvingRefs.add(refPath);
1294
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1295
+ resolvingRefs.delete(refPath);
1296
+ }
1297
+ } catch {}
1298
+ resolvedRefCache.set(refPath, resolvedSchema);
1223
1299
  }
1224
- } catch {}
1300
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1301
+ }
1225
1302
  return _kubb_core.ast.createSchema({
1226
1303
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1227
1304
  type: "ref",
@@ -1238,7 +1315,7 @@ function createSchemaParser(ctx) {
1238
1315
  const [memberSchema] = schema.allOf;
1239
1316
  const memberNode = parseSchema({
1240
1317
  schema: memberSchema,
1241
- name: null
1318
+ name
1242
1319
  }, rawOptions);
1243
1320
  const { kind: _kind, ...memberNodeProps } = memberNode;
1244
1321
  const mergedNullable = nullable || memberNode.nullable || void 0;
@@ -1254,18 +1331,19 @@ function createSchemaParser(ctx) {
1254
1331
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1255
1332
  default: mergedDefault,
1256
1333
  example: schema.example ?? memberNode.example,
1257
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1334
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1335
+ format: schema.format ?? memberNode.format
1258
1336
  });
1259
1337
  }
1260
1338
  const filteredDiscriminantValues = [];
1261
1339
  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;
1340
+ if (!dialect.isReference(item) || !name) return true;
1341
+ const deref = dialect.resolveRef(document, item.$ref);
1342
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1265
1343
  const parentUnion = deref.oneOf ?? deref.anyOf;
1266
1344
  if (!parentUnion) return true;
1267
1345
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1268
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1346
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1269
1347
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1270
1348
  if (inOneOf || inMapping) {
1271
1349
  const discriminatorValue = _kubb_core.ast.findDiscriminator(deref.discriminator.mapping, childRef);
@@ -1276,22 +1354,28 @@ function createSchemaParser(ctx) {
1276
1354
  return false;
1277
1355
  }
1278
1356
  return true;
1279
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1357
+ }).map((s) => parseSchema({
1358
+ schema: s,
1359
+ name
1360
+ }, rawOptions));
1280
1361
  const syntheticStart = allOfMembers.length;
1281
1362
  if (Array.isArray(schema.required) && schema.required.length) {
1282
1363
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1283
1364
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1284
1365
  if (missingRequired.length) {
1285
1366
  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] : [];
1367
+ if (!dialect.isReference(item)) return [item];
1368
+ const deref = dialect.resolveRef(document, item.$ref);
1369
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1289
1370
  });
1290
1371
  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));
1372
+ allOfMembers.push(parseSchema({
1373
+ schema: {
1374
+ properties: { [key]: resolved.properties[key] },
1375
+ required: [key]
1376
+ },
1377
+ name
1378
+ }, rawOptions));
1295
1379
  break;
1296
1380
  }
1297
1381
  }
@@ -1306,7 +1390,7 @@ function createSchemaParser(ctx) {
1306
1390
  }));
1307
1391
  return _kubb_core.ast.createSchema({
1308
1392
  type: "intersection",
1309
- members: [..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1393
+ members: [..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1310
1394
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1311
1395
  });
1312
1396
  }
@@ -1327,10 +1411,10 @@ function createSchemaParser(ctx) {
1327
1411
  const strategy = schema.oneOf ? "one" : "any";
1328
1412
  const unionBase = {
1329
1413
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1330
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1414
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1331
1415
  strategy
1332
1416
  };
1333
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1417
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1334
1418
  const sharedPropertiesNode = schema.properties ? (() => {
1335
1419
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1336
1420
  return parseSchema({
@@ -1340,9 +1424,12 @@ function createSchemaParser(ctx) {
1340
1424
  })() : void 0;
1341
1425
  if (sharedPropertiesNode || discriminator?.mapping) {
1342
1426
  const members = unionMembers.map((s) => {
1343
- const ref = isReference(s) ? s.$ref : void 0;
1427
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1344
1428
  const discriminatorValue = _kubb_core.ast.findDiscriminator(discriminator?.mapping, ref);
1345
- const memberNode = parseSchema({ schema: s }, rawOptions);
1429
+ const memberNode = parseSchema({
1430
+ schema: s,
1431
+ name
1432
+ }, rawOptions);
1346
1433
  if (!discriminatorValue || !discriminator) return memberNode;
1347
1434
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.setDiscriminatorEnum({
1348
1435
  node: sharedPropertiesNode,
@@ -1372,7 +1459,10 @@ function createSchemaParser(ctx) {
1372
1459
  return _kubb_core.ast.createSchema({
1373
1460
  type: "union",
1374
1461
  ...unionBase,
1375
- members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1462
+ members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({
1463
+ schema: s,
1464
+ name
1465
+ }, rawOptions)))
1376
1466
  });
1377
1467
  }
1378
1468
  /**
@@ -1386,7 +1476,8 @@ function createSchemaParser(ctx) {
1386
1476
  name,
1387
1477
  title: schema.title,
1388
1478
  description: schema.description,
1389
- deprecated: schema.deprecated
1479
+ deprecated: schema.deprecated,
1480
+ format: schema.format
1390
1481
  });
1391
1482
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1392
1483
  return _kubb_core.ast.createSchema({
@@ -1476,6 +1567,15 @@ function createSchemaParser(ctx) {
1476
1567
  }, rawOptions);
1477
1568
  const nullInEnum = schema.enum.includes(null);
1478
1569
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1570
+ if (nullInEnum && filteredValues.length === 0) return _kubb_core.ast.createSchema({
1571
+ type: "null",
1572
+ primitive: "null",
1573
+ name,
1574
+ title: schema.title,
1575
+ description: schema.description,
1576
+ deprecated: schema.deprecated,
1577
+ format: schema.format
1578
+ });
1479
1579
  const enumNullable = nullable || nullInEnum || void 0;
1480
1580
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1481
1581
  const enumPrimitive = getPrimitiveType(type);
@@ -1490,7 +1590,8 @@ function createSchemaParser(ctx) {
1490
1590
  readOnly: schema.readOnly,
1491
1591
  writeOnly: schema.writeOnly,
1492
1592
  default: enumDefault,
1493
- example: schema.example
1593
+ example: schema.example,
1594
+ format: schema.format
1494
1595
  };
1495
1596
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1496
1597
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
@@ -1524,20 +1625,23 @@ function createSchemaParser(ctx) {
1524
1625
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1525
1626
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1526
1627
  const resolvedPropSchema = propSchema;
1527
- const propNullable = isNullable(resolvedPropSchema);
1628
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1528
1629
  const propNode = parseSchema({
1529
1630
  schema: resolvedPropSchema,
1530
1631
  name: _kubb_core.ast.childName(name, propName)
1531
1632
  }, 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
- }
1633
+ const schemaNode = (() => {
1634
+ const node = _kubb_core.ast.setEnumName(propNode, name, propName, options.enumSuffix);
1635
+ const tupleNode = _kubb_core.ast.narrowSchema(node, "tuple");
1636
+ if (tupleNode?.items) {
1637
+ const namedItems = tupleNode.items.map((item) => _kubb_core.ast.setEnumName(item, name, propName, options.enumSuffix));
1638
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1639
+ ...tupleNode,
1640
+ items: namedItems
1641
+ };
1642
+ }
1643
+ return node;
1644
+ })();
1541
1645
  return _kubb_core.ast.createProperty({
1542
1646
  name: propName,
1543
1647
  schema: {
@@ -1548,11 +1652,12 @@ function createSchemaParser(ctx) {
1548
1652
  });
1549
1653
  }) : [];
1550
1654
  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) });
1655
+ const additionalPropertiesNode = (() => {
1656
+ if (additionalProperties === true) return true;
1657
+ if (additionalProperties === false) return false;
1658
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1659
+ if (additionalProperties) return _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1660
+ })();
1556
1661
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1557
1662
  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
1663
  const objectNode = _kubb_core.ast.createSchema({
@@ -1565,7 +1670,7 @@ function createSchemaParser(ctx) {
1565
1670
  maxProperties: schema.maxProperties,
1566
1671
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1567
1672
  });
1568
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1673
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1569
1674
  const discPropName = schema.discriminator.propertyName;
1570
1675
  const values = Object.keys(schema.discriminator.mapping);
1571
1676
  const enumName = name ? _kubb_core.ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
@@ -1599,7 +1704,7 @@ function createSchemaParser(ctx) {
1599
1704
  */
1600
1705
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1601
1706
  const rawItems = schema.items;
1602
- const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
1707
+ const itemName = rawItems?.enum?.length && name ? _kubb_core.ast.enumPropName(null, name, options.enumSuffix) : name;
1603
1708
  const items = rawItems ? [parseSchema({
1604
1709
  schema: rawItems,
1605
1710
  name: itemName
@@ -1663,15 +1768,148 @@ function createSchemaParser(ctx) {
1663
1768
  title: schema.title,
1664
1769
  description: schema.description,
1665
1770
  deprecated: schema.deprecated,
1666
- nullable
1771
+ nullable,
1772
+ format: schema.format
1773
+ });
1774
+ }
1775
+ /**
1776
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1777
+ * into a `blob` node.
1778
+ */
1779
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1780
+ return _kubb_core.ast.createSchema({
1781
+ type: "blob",
1782
+ primitive: "string",
1783
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1667
1784
  });
1668
1785
  }
1669
1786
  /**
1787
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1788
+ *
1789
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1790
+ * falls through and handles it as that single type with nullability already folded in.
1791
+ */
1792
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1793
+ const types = schema.type;
1794
+ const nonNullTypes = types.filter((t) => t !== "null");
1795
+ if (nonNullTypes.length <= 1) return null;
1796
+ const arrayNullable = types.includes("null") || nullable || void 0;
1797
+ return _kubb_core.ast.createSchema({
1798
+ type: "union",
1799
+ members: nonNullTypes.map((t) => parseSchema({
1800
+ schema: {
1801
+ ...schema,
1802
+ type: t
1803
+ },
1804
+ name
1805
+ }, rawOptions)),
1806
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1807
+ });
1808
+ }
1809
+ /**
1810
+ * Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
1811
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1812
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1813
+ * match/convert/fall-through contract.
1814
+ */
1815
+ const schemaRules = [
1816
+ {
1817
+ name: "ref",
1818
+ match: ({ schema }) => dialect.isReference(schema),
1819
+ convert: convertRef
1820
+ },
1821
+ {
1822
+ name: "allOf",
1823
+ match: ({ schema }) => !!schema.allOf?.length,
1824
+ convert: convertAllOf
1825
+ },
1826
+ {
1827
+ name: "union",
1828
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1829
+ convert: convertUnion
1830
+ },
1831
+ {
1832
+ name: "const",
1833
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1834
+ convert: convertConst
1835
+ },
1836
+ {
1837
+ name: "format",
1838
+ match: ({ schema }) => !!schema.format,
1839
+ convert: convertFormat
1840
+ },
1841
+ {
1842
+ name: "blob",
1843
+ match: ({ schema }) => dialect.isBinary(schema),
1844
+ convert: convertBlob
1845
+ },
1846
+ {
1847
+ name: "multi-type",
1848
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1849
+ convert: convertMultiType
1850
+ },
1851
+ {
1852
+ name: "constrained-string",
1853
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1854
+ convert: convertString
1855
+ },
1856
+ {
1857
+ name: "constrained-number",
1858
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1859
+ convert: (ctx) => convertNumeric(ctx, "number")
1860
+ },
1861
+ {
1862
+ name: "enum",
1863
+ match: ({ schema }) => !!schema.enum?.length,
1864
+ convert: convertEnum
1865
+ },
1866
+ {
1867
+ name: "object",
1868
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1869
+ convert: convertObject
1870
+ },
1871
+ {
1872
+ name: "tuple",
1873
+ match: ({ schema }) => "prefixItems" in schema,
1874
+ convert: convertTuple
1875
+ },
1876
+ {
1877
+ name: "array",
1878
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1879
+ convert: convertArray
1880
+ },
1881
+ {
1882
+ name: "string",
1883
+ match: ({ type }) => type === "string",
1884
+ convert: convertString
1885
+ },
1886
+ {
1887
+ name: "number",
1888
+ match: ({ type }) => type === "number",
1889
+ convert: (ctx) => convertNumeric(ctx, "number")
1890
+ },
1891
+ {
1892
+ name: "integer",
1893
+ match: ({ type }) => type === "integer",
1894
+ convert: (ctx) => convertNumeric(ctx, "integer")
1895
+ },
1896
+ {
1897
+ name: "boolean",
1898
+ match: ({ type }) => type === "boolean",
1899
+ convert: convertBoolean
1900
+ },
1901
+ {
1902
+ name: "null",
1903
+ match: ({ type }) => type === "null",
1904
+ convert: convertNull
1905
+ }
1906
+ ];
1907
+ /**
1670
1908
  * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1671
1909
  *
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).
1910
+ * Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
1911
+ * via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
1912
+ * `emptySchemaType`.
1675
1913
  */
1676
1914
  function parseSchema({ schema, name }, rawOptions) {
1677
1915
  const options = {
@@ -1683,75 +1921,40 @@ function createSchemaParser(ctx) {
1683
1921
  schema: flattenedSchema,
1684
1922
  name
1685
1923
  }, 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 = {
1924
+ const nullable = dialect.isNullable(schema) || void 0;
1925
+ const schemaCtx = {
1690
1926
  schema,
1691
1927
  name,
1692
1928
  nullable,
1693
- defaultValue,
1694
- type,
1929
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1930
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1695
1931
  rawOptions,
1696
1932
  options
1697
1933
  };
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);
1934
+ const node = _kubb_core.ast.dispatch(schemaRules, schemaCtx);
1935
+ if (node) return node;
1739
1936
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1740
1937
  return _kubb_core.ast.createSchema({
1741
1938
  type: emptyType,
1742
1939
  name,
1743
1940
  title: schema.title,
1744
- description: schema.description
1941
+ description: schema.description,
1942
+ format: schema.format
1745
1943
  });
1746
1944
  }
1747
1945
  /**
1748
1946
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1749
1947
  */
1750
- function parseParameter(options, param) {
1948
+ function parseParameter(options, param, parentName) {
1751
1949
  const required = param["required"] ?? false;
1752
- const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1950
+ const paramName = param["name"];
1951
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
1952
+ const schema = param["schema"] ? parseSchema({
1953
+ schema: param["schema"],
1954
+ name: schemaName
1955
+ }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1753
1956
  return _kubb_core.ast.createParameter({
1754
- name: param["name"],
1957
+ name: paramName,
1755
1958
  in: param["in"],
1756
1959
  schema: {
1757
1960
  ...schema,
@@ -1788,27 +1991,33 @@ function createSchemaParser(ctx) {
1788
1991
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1789
1992
  */
1790
1993
  function collectPropertyKeysByFlag(schema, flag) {
1791
- if (!schema?.properties) return void 0;
1994
+ if (!schema?.properties) return null;
1792
1995
  const keys = [];
1793
1996
  for (const key in schema.properties) {
1794
1997
  const prop = schema.properties[key];
1795
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1998
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1796
1999
  }
1797
- return keys.length ? keys : void 0;
2000
+ return keys.length ? keys : null;
1798
2001
  }
1799
2002
  /**
1800
2003
  * Converts an OAS `Operation` into an `OperationNode`.
1801
2004
  */
1802
2005
  function parseOperation(options, operation) {
1803
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
2006
+ const operationId = operation.getOperationId();
2007
+ const operationName = operationId ? pascalCase(operationId) : void 0;
2008
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1804
2009
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1805
2010
  const requestBodyMeta = getRequestBodyMeta(operation);
2011
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1806
2012
  const content = allContentTypes.flatMap((ct) => {
1807
2013
  const schema = getRequestSchema(document, operation, { contentType: ct });
1808
2014
  if (!schema) return [];
1809
2015
  return [{
1810
2016
  contentType: ct,
1811
- schema: _kubb_core.ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
2017
+ schema: _kubb_core.ast.syncOptionality(parseSchema({
2018
+ schema,
2019
+ name: requestBodyName
2020
+ }, options), requestBodyMeta.required),
1812
2021
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1813
2022
  }];
1814
2023
  });
@@ -1819,21 +2028,36 @@ function createSchemaParser(ctx) {
1819
2028
  } : void 0;
1820
2029
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1821
2030
  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 ?? "");
2031
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2032
+ const { description } = getResponseMeta(responseObj);
2033
+ const parseEntrySchema = (contentType) => {
2034
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
2035
+ return {
2036
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2037
+ schema: raw,
2038
+ name: responseName
2039
+ }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
2040
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2041
+ };
2042
+ };
2043
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
2044
+ contentType,
2045
+ ...parseEntrySchema(contentType)
2046
+ }));
2047
+ if (content.length === 0) content.push({
2048
+ contentType: operation.contentType || "application/json",
2049
+ ...parseEntrySchema(ctx.contentType)
2050
+ });
1826
2051
  return _kubb_core.ast.createResponse({
1827
2052
  statusCode,
1828
2053
  description,
1829
- schema,
1830
- mediaType,
1831
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
2054
+ content
1832
2055
  });
1833
2056
  });
1834
2057
  const urlPath = new URLPath(operation.path);
1835
2058
  return _kubb_core.ast.createOperation({
1836
- operationId: operation.getOperationId(),
2059
+ operationId,
2060
+ protocol: "http",
1837
2061
  method: operation.method.toUpperCase(),
1838
2062
  path: urlPath.path,
1839
2063
  tags: operation.getTags().map((tag) => tag.name),
@@ -1851,59 +2075,336 @@ function createSchemaParser(ctx) {
1851
2075
  parseParameter
1852
2076
  };
1853
2077
  }
2078
+ //#endregion
2079
+ //#region src/discriminator.ts
1854
2080
  /**
1855
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
2081
+ * Builds a map of child schema names discriminator patch data by scanning the given
2082
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
1856
2083
  *
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.
2084
+ * The streaming path calls this on a small pre-parsed subset of schemas (only the
2085
+ * discriminator parents) rather than on all schemas at once.
2086
+ */
2087
+ function buildDiscriminatorChildMap(schemas) {
2088
+ const childMap = /* @__PURE__ */ new Map();
2089
+ for (const schema of schemas) {
2090
+ let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
2091
+ if (!unionNode) {
2092
+ const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
2093
+ if (intersectionMembers) for (const m of intersectionMembers) {
2094
+ const u = _kubb_core.ast.narrowSchema(m, "union");
2095
+ if (u) {
2096
+ unionNode = u;
2097
+ break;
2098
+ }
2099
+ }
2100
+ }
2101
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
2102
+ const { discriminatorPropertyName, members } = unionNode;
2103
+ for (const member of members) {
2104
+ const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
2105
+ if (!intersectionNode?.members) continue;
2106
+ let refNode = null;
2107
+ let objNode = null;
2108
+ for (const m of intersectionNode.members) {
2109
+ refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
2110
+ objNode ??= _kubb_core.ast.narrowSchema(m, "object");
2111
+ }
2112
+ if (!refNode?.name || !objNode) continue;
2113
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
2114
+ const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : null;
2115
+ if (!enumNode?.enumValues?.length) continue;
2116
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
2117
+ if (!enumValues.length) continue;
2118
+ const existing = childMap.get(refNode.name);
2119
+ if (!existing) {
2120
+ childMap.set(refNode.name, {
2121
+ propertyName: discriminatorPropertyName,
2122
+ enumValues: [...enumValues]
2123
+ });
2124
+ continue;
2125
+ }
2126
+ existing.enumValues.push(...enumValues);
2127
+ }
2128
+ }
2129
+ return childMap;
2130
+ }
2131
+ /**
2132
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
2133
+ * the discriminant property). Used by the streaming path to apply patches inline per yield
2134
+ * without buffering all schemas.
2135
+ */
2136
+ function patchDiscriminatorNode(node, entry) {
2137
+ const objectNode = _kubb_core.ast.narrowSchema(node, "object");
2138
+ if (!objectNode) return node;
2139
+ const { propertyName, enumValues } = entry;
2140
+ const enumSchema = _kubb_core.ast.createSchema({
2141
+ type: "enum",
2142
+ enumValues
2143
+ });
2144
+ const newProp = _kubb_core.ast.createProperty({
2145
+ name: propertyName,
2146
+ required: true,
2147
+ schema: enumSchema
2148
+ });
2149
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
2150
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
2151
+ return {
2152
+ ...objectNode,
2153
+ properties: newProperties
2154
+ };
2155
+ }
2156
+ //#endregion
2157
+ //#region src/schemaDiagnostics.ts
2158
+ /**
2159
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
2160
+ * top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
2161
+ * pointer as it descends so a nested field reports against its full path
2162
+ * (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
2163
+ * resolved schema is reported under its own walk. Reports land in the active build run, are a
2164
+ * no-op outside one, and repeats are deduped by the build.
2165
+ */
2166
+ function reportSchemaDiagnostics({ node, name }) {
2167
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2168
+ }
2169
+ /**
2170
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2171
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2172
+ */
2173
+ function escapePointerToken(token) {
2174
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2175
+ }
2176
+ function visit(node, pointer) {
2177
+ if (node.deprecated) _kubb_core.Diagnostics.report({
2178
+ code: _kubb_core.Diagnostics.code.deprecated,
2179
+ severity: "info",
2180
+ message: "This schema is marked as deprecated.",
2181
+ location: {
2182
+ kind: "schema",
2183
+ pointer
2184
+ }
2185
+ });
2186
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
2187
+ code: _kubb_core.Diagnostics.code.unsupportedFormat,
2188
+ severity: "warning",
2189
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2190
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2191
+ location: {
2192
+ kind: "schema",
2193
+ pointer
2194
+ }
2195
+ });
2196
+ if (node.type === "object") {
2197
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2198
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2199
+ return;
2200
+ }
2201
+ if (node.type === "array") {
2202
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2203
+ return;
2204
+ }
2205
+ if (node.type === "tuple") {
2206
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2207
+ return;
2208
+ }
2209
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2210
+ }
2211
+ //#endregion
2212
+ //#region src/stream.ts
2213
+ /**
2214
+ * Builds the deduplication plan from the already-parsed top-level schema nodes plus a
2215
+ * single extra parse pass over operations (so duplicates in request/response bodies are seen).
2216
+ *
2217
+ * Only enums and objects are candidates, and object shapes that reference a circular schema are
2218
+ * rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
2219
+ * name (collision-resolved against existing component names); shapes without a name stay inline.
2220
+ */
2221
+ function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
2222
+ const circularSchemas = new Set(circularNames);
2223
+ const usedNames = new Set(schemaNames);
2224
+ return _kubb_core.ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
2225
+ isCandidate: (node) => {
2226
+ if (node.type === "enum") return true;
2227
+ if (node.type !== "object") return false;
2228
+ if (node.name && circularSchemas.has(node.name)) return false;
2229
+ return !_kubb_core.ast.containsCircularRef(node, { circularSchemas });
2230
+ },
2231
+ nameFor: (node) => {
2232
+ const base = node.name;
2233
+ if (!base) return null;
2234
+ let name = base;
2235
+ let counter = 2;
2236
+ while (usedNames.has(name)) name = `${base}${counter++}`;
2237
+ usedNames.add(name);
2238
+ return name;
2239
+ },
2240
+ refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
2241
+ });
2242
+ }
2243
+ /**
2244
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
2245
+ * interpolating any `serverVariables` into the URL template.
2246
+ *
2247
+ * Returns `null` when `serverIndex` is omitted or out of range.
2248
+ *
2249
+ * @example Resolve the first server
2250
+ * `resolveBaseUrl({ document, serverIndex: 0 })`
2251
+ *
2252
+ * @example Override a path variable
2253
+ * `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
2254
+ */
2255
+ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
2256
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
2257
+ return server?.url ? resolveServerUrl(server, serverVariables) : null;
2258
+ }
2259
+ /**
2260
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
2261
+ *
2262
+ * Three things happen in this single pass:
2263
+ * - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
2264
+ * - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
2265
+ * - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
2266
+ * The `allNodes` array is local and drops out of scope as soon as this function returns.
2267
+ *
2268
+ * After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
2269
+ * Both are proportional to the number of aliases or discriminator parents, not total schema count.
1860
2270
  *
1861
- * Returns the AST root and a `nameMapping` for resolving schema references.
2271
+ * Each schema is parsed again during the streaming pass. This is intentional.
2272
+ * Holding the parsed nodes in memory here would defeat the streaming memory benefit.
1862
2273
  *
1863
2274
  * @example
1864
2275
  * ```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' })
2276
+ * const { refAliasMap, enumNames, circularNames } = preScan({
2277
+ * schemas,
2278
+ * parseSchema,
2279
+ * parserOptions,
2280
+ * discriminator: 'strict',
2281
+ * })
1869
2282
  * ```
1870
2283
  */
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));
2284
+ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
2285
+ const allNodes = [];
2286
+ const refAliasMap = /* @__PURE__ */ new Map();
2287
+ const enumNames = [];
2288
+ const discriminatorParentNodes = [];
2289
+ for (const [name, schema] of Object.entries(schemas)) {
2290
+ const node = parseSchema({
2291
+ schema,
2292
+ name
2293
+ }, parserOptions);
2294
+ allNodes.push(node);
2295
+ reportSchemaDiagnostics({
2296
+ node,
2297
+ name
2298
+ });
2299
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2300
+ if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
2301
+ if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2302
+ }
2303
+ const circularNames = [..._kubb_core.ast.findCircularSchemas(allNodes)];
2304
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2305
+ let dedupePlan = null;
2306
+ if (dedupe) {
2307
+ const operationNodes = [];
2308
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2309
+ if (!operation) continue;
2310
+ const operationNode = parseOperation(parserOptions, operation);
2311
+ if (operationNode) operationNodes.push(operationNode);
2312
+ }
2313
+ dedupePlan = createDedupePlan({
2314
+ schemaNodes: allNodes,
2315
+ operationNodes,
2316
+ schemaNames: Object.keys(schemas),
2317
+ circularNames
2318
+ });
2319
+ for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
2320
+ }
1888
2321
  return {
1889
- root: _kubb_core.ast.createInput({
1890
- schemas,
1891
- operations
1892
- }),
1893
- nameMapping
2322
+ refAliasMap,
2323
+ enumNames,
2324
+ circularNames,
2325
+ discriminatorChildMap,
2326
+ dedupePlan
2327
+ };
2328
+ }
2329
+ /**
2330
+ * Creates a lazy `InputStreamNode` from already-resolved adapter state.
2331
+ *
2332
+ * The schema and operation iterables each start a fresh parse pass on every
2333
+ * `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
2334
+ * stream object independently without sharing a cursor or holding all nodes in memory.
2335
+ *
2336
+ * Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
2337
+ * with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
2338
+ *
2339
+ * @example
2340
+ * ```ts
2341
+ * const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
2342
+ * for await (const schema of streamNode.schemas) {
2343
+ * // each call to for-await restarts from the first schema
2344
+ * }
2345
+ * ```
2346
+ */
2347
+ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
2348
+ const rewriteTopLevelSchema = (node) => {
2349
+ if (!dedupePlan) return node;
2350
+ const canonical = dedupePlan.canonicalBySignature.get(_kubb_core.ast.schemaSignature(node));
2351
+ if (canonical && canonical.name !== node.name) return _kubb_core.ast.createSchema({
2352
+ type: "ref",
2353
+ name: node.name ?? null,
2354
+ ref: canonical.ref,
2355
+ description: node.description,
2356
+ deprecated: node.deprecated
2357
+ });
2358
+ return _kubb_core.ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
1894
2359
  };
2360
+ const schemasIterable = { [Symbol.asyncIterator]() {
2361
+ return (async function* () {
2362
+ if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
2363
+ for (const [name, schema] of Object.entries(schemas)) {
2364
+ const alias = refAliasMap.get(name);
2365
+ if (alias?.name && schemas[alias.name]) {
2366
+ yield rewriteTopLevelSchema({
2367
+ ...parseSchema({
2368
+ schema: schemas[alias.name],
2369
+ name: alias.name
2370
+ }, parserOptions),
2371
+ name
2372
+ });
2373
+ continue;
2374
+ }
2375
+ const parsed = parseSchema({
2376
+ schema,
2377
+ name
2378
+ }, parserOptions);
2379
+ yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
2380
+ }
2381
+ })();
2382
+ } };
2383
+ const operationsIterable = { [Symbol.asyncIterator]() {
2384
+ return (async function* () {
2385
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2386
+ if (!operation) continue;
2387
+ const node = parseOperation(parserOptions, operation);
2388
+ if (node) yield dedupePlan ? _kubb_core.ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
2389
+ }
2390
+ })();
2391
+ } };
2392
+ return _kubb_core.ast.createStreamInput(schemasIterable, operationsIterable, meta);
1895
2393
  }
1896
2394
  //#endregion
1897
2395
  //#region src/adapter.ts
1898
2396
  /**
1899
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2397
+ * Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
1900
2398
  */
1901
2399
  const adapterOasName = "oas";
1902
2400
  /**
1903
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2401
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2402
+ * file at `input.path`, validates it, resolves the base URL, and converts every
2403
+ * schema and operation into the universal AST that every downstream plugin
2404
+ * consumes.
1904
2405
  *
1905
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1906
- * everything into an `InputNode` that downstream plugins consume.
2406
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2407
+ * integer width, server URL) apply to every plugin in the build.
1907
2408
  *
1908
2409
  * @example
1909
2410
  * ```ts
@@ -1912,17 +2413,118 @@ const adapterOasName = "oas";
1912
2413
  * import { pluginTs } from '@kubb/plugin-ts'
1913
2414
  *
1914
2415
  * export default defineConfig({
1915
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1916
- * input: { path: './openapi.yaml' },
2416
+ * input: { path: './petStore.yaml' },
2417
+ * output: { path: './src/gen' },
2418
+ * adapter: adapterOas({
2419
+ * serverIndex: 0,
2420
+ * discriminator: 'inherit',
2421
+ * dateType: 'date',
2422
+ * }),
1917
2423
  * plugins: [pluginTs()],
1918
2424
  * })
1919
2425
  * ```
1920
2426
  */
1921
2427
  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;
2428
+ 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;
2429
+ const parserOptions = {
2430
+ ...DEFAULT_PARSER_OPTIONS,
2431
+ dateType,
2432
+ integerType,
2433
+ unknownType,
2434
+ emptySchemaType,
2435
+ enumSuffix
2436
+ };
1923
2437
  let nameMapping = /* @__PURE__ */ new Map();
1924
- let parsedDocument;
1925
- let inputNode;
2438
+ let parsedDocument = null;
2439
+ const documentCache = /* @__PURE__ */ new WeakMap();
2440
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2441
+ const baseOasCache = /* @__PURE__ */ new WeakMap();
2442
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2443
+ const preScanCache = /* @__PURE__ */ new WeakMap();
2444
+ function ensureDocument(source) {
2445
+ const cached = documentCache.get(source);
2446
+ if (cached) return cached;
2447
+ const promise = (async () => {
2448
+ const fresh = await parseFromConfig(source);
2449
+ if (validate) await validateDocument(fresh);
2450
+ parsedDocument = fresh;
2451
+ return fresh;
2452
+ })();
2453
+ documentCache.set(source, promise);
2454
+ return promise;
2455
+ }
2456
+ function ensureSchemas(document) {
2457
+ const cached = schemasCache.get(document);
2458
+ if (cached) return cached;
2459
+ const promise = Promise.resolve().then(() => {
2460
+ const result = getSchemas(document, { contentType });
2461
+ nameMapping = result.nameMapping;
2462
+ return result.schemas;
2463
+ });
2464
+ schemasCache.set(document, promise);
2465
+ return promise;
2466
+ }
2467
+ function ensureBaseOas(document) {
2468
+ const cached = baseOasCache.get(document);
2469
+ if (cached) return cached;
2470
+ const baseOas = new oas.default(document);
2471
+ baseOasCache.set(document, baseOas);
2472
+ return baseOas;
2473
+ }
2474
+ function ensureSchemaParser(document) {
2475
+ const cached = schemaParserCache.get(document);
2476
+ if (cached) return cached;
2477
+ const parser = createSchemaParser({
2478
+ document,
2479
+ contentType
2480
+ });
2481
+ schemaParserCache.set(document, parser);
2482
+ return parser;
2483
+ }
2484
+ function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
2485
+ const cached = preScanCache.get(document);
2486
+ if (cached) return cached;
2487
+ const result = preScan({
2488
+ schemas,
2489
+ parseSchema,
2490
+ parseOperation,
2491
+ baseOas,
2492
+ parserOptions,
2493
+ discriminator,
2494
+ dedupe
2495
+ });
2496
+ preScanCache.set(document, result);
2497
+ return result;
2498
+ }
2499
+ async function createStream(source) {
2500
+ const document = await ensureDocument(source);
2501
+ const schemas = await ensureSchemas(document);
2502
+ const { parseSchema, parseOperation } = ensureSchemaParser(document);
2503
+ const baseOas = ensureBaseOas(document);
2504
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
2505
+ return createInputStream({
2506
+ schemas,
2507
+ parseSchema,
2508
+ parseOperation,
2509
+ baseOas,
2510
+ parserOptions,
2511
+ refAliasMap,
2512
+ discriminatorChildMap,
2513
+ dedupePlan,
2514
+ meta: {
2515
+ title: document.info?.title,
2516
+ description: document.info?.description,
2517
+ version: document.info?.version,
2518
+ baseURL: resolveBaseUrl({
2519
+ document,
2520
+ serverIndex,
2521
+ serverVariables
2522
+ }),
2523
+ circularNames,
2524
+ enumNames
2525
+ }
2526
+ });
2527
+ }
1926
2528
  return {
1927
2529
  name: "oas",
1928
2530
  get options() {
@@ -1932,6 +2534,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1932
2534
  serverIndex,
1933
2535
  serverVariables,
1934
2536
  discriminator,
2537
+ dedupe,
1935
2538
  dateType,
1936
2539
  integerType,
1937
2540
  unknownType,
@@ -1943,8 +2546,9 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1943
2546
  get document() {
1944
2547
  return parsedDocument;
1945
2548
  },
1946
- get inputNode() {
1947
- return inputNode;
2549
+ async validate(input, options) {
2550
+ await assertInputExists(input);
2551
+ await validateDocument(await parseDocument(input), options);
1948
2552
  },
1949
2553
  getImports(node, resolve) {
1950
2554
  return _kubb_core.ast.collectImports({
@@ -1952,7 +2556,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1952
2556
  nameMapping,
1953
2557
  resolve: (schemaName) => {
1954
2558
  const result = resolve(schemaName);
1955
- if (!result) return;
2559
+ if (!result) return null;
1956
2560
  return _kubb_core.ast.createImport({
1957
2561
  name: [result.name],
1958
2562
  path: result.path
@@ -1961,32 +2565,20 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
1961
2565
  });
1962
2566
  },
1963
2567
  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
1975
- });
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
- }
2568
+ const streamNode = await createStream(source);
2569
+ const collect = async (iter) => {
2570
+ const out = [];
2571
+ for await (const item of iter) out.push(item);
2572
+ return out;
2573
+ };
2574
+ const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
2575
+ return _kubb_core.ast.createInput({
2576
+ schemas,
2577
+ operations,
2578
+ meta: streamNode.meta
1987
2579
  });
1988
- return inputNode;
1989
- }
2580
+ },
2581
+ stream: createStream
1990
2582
  };
1991
2583
  });
1992
2584
  //#endregion
@@ -2006,8 +2598,5 @@ exports.HttpMethods = HttpMethods;
2006
2598
  exports.adapterOas = adapterOas;
2007
2599
  exports.adapterOasName = adapterOasName;
2008
2600
  exports.mergeDocuments = mergeDocuments;
2009
- exports.parseDocument = parseDocument;
2010
- exports.parseFromConfig = parseFromConfig;
2011
- exports.validateDocument = validateDocument;
2012
2601
 
2013
2602
  //# sourceMappingURL=index.cjs.map