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

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