@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.js CHANGED
@@ -1,10 +1,12 @@
1
- import "./chunk--u3MIqq1.js";
2
- import { ast, createAdapter } from "@kubb/core";
1
+ import "./chunk-C0LytTxp.js";
2
+ import { Diagnostics, ast, createAdapter } from "@kubb/core";
3
+ import BaseOas from "oas";
3
4
  import path from "node:path";
5
+ import { access } from "node:fs/promises";
4
6
  import { bundle, loadConfig } from "@redocly/openapi-core";
5
7
  import OASNormalize from "oas-normalize";
6
8
  import swagger2openapi from "swagger2openapi";
7
- import BaseOas from "oas";
9
+ import { childName, enumPropName, extractRefName, findDiscriminator } from "@kubb/ast/utils";
8
10
  import { isRef } from "oas/types";
9
11
  import { matchesMimeType } from "oas/utils";
10
12
  //#region src/constants.ts
@@ -89,6 +91,18 @@ const structuralKeys = new Set([
89
91
  * formatMap['float'] // 'number'
90
92
  * ```
91
93
  */
94
+ /**
95
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
96
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
97
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
98
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
99
+ */
100
+ const specialCasedFormats = new Set([
101
+ "int64",
102
+ "date-time",
103
+ "date",
104
+ "time"
105
+ ]);
92
106
  const formatMap = {
93
107
  uuid: "uuid",
94
108
  email: "email",
@@ -127,87 +141,6 @@ const typeOptionMap = new Map([
127
141
  ["void", ast.schemaTypes.void]
128
142
  ]);
129
143
  //#endregion
130
- //#region src/discriminator.ts
131
- /**
132
- * Injects discriminator enum values into child schemas so they know which value identifies them.
133
- *
134
- * Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
135
- * enum value each union member is mapped to, then adds (or replaces) that property on the matching
136
- * child object schema.
137
- *
138
- * Returns a new `InputNode` — the original is never mutated.
139
- *
140
- * @example
141
- * ```ts
142
- * const { root } = parseOas(document, options)
143
- * const next = applyDiscriminatorInheritance(root)
144
- * ```
145
- */
146
- function applyDiscriminatorInheritance(root) {
147
- const childMap = /* @__PURE__ */ new Map();
148
- for (const schema of root.schemas) {
149
- let unionNode = ast.narrowSchema(schema, "union");
150
- if (!unionNode) {
151
- const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
152
- if (intersectionMembers) for (const m of intersectionMembers) {
153
- const u = ast.narrowSchema(m, "union");
154
- if (u) {
155
- unionNode = u;
156
- break;
157
- }
158
- }
159
- }
160
- if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
161
- const { discriminatorPropertyName, members } = unionNode;
162
- for (const member of members) {
163
- const intersectionNode = ast.narrowSchema(member, "intersection");
164
- if (!intersectionNode?.members) continue;
165
- let refNode;
166
- let objNode;
167
- for (const m of intersectionNode.members) {
168
- refNode ??= ast.narrowSchema(m, "ref");
169
- objNode ??= ast.narrowSchema(m, "object");
170
- }
171
- if (!refNode?.name || !objNode) continue;
172
- const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
173
- const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : void 0;
174
- if (!enumNode?.enumValues?.length) continue;
175
- const enumValues = enumNode.enumValues.filter((v) => v !== null);
176
- if (!enumValues.length) continue;
177
- const existing = childMap.get(refNode.name);
178
- if (existing) existing.enumValues.push(...enumValues);
179
- else childMap.set(refNode.name, {
180
- propertyName: discriminatorPropertyName,
181
- enumValues: [...enumValues]
182
- });
183
- }
184
- }
185
- if (childMap.size === 0) return root;
186
- return ast.transform(root, { schema(node, { parent }) {
187
- if (parent?.kind !== "Input" || !node.name) return;
188
- const entry = childMap.get(node.name);
189
- if (!entry) return;
190
- const objectNode = ast.narrowSchema(node, "object");
191
- if (!objectNode) return;
192
- const { propertyName, enumValues } = entry;
193
- const enumSchema = ast.createSchema({
194
- type: "enum",
195
- enumValues
196
- });
197
- const newProp = ast.createProperty({
198
- name: propertyName,
199
- required: true,
200
- schema: enumSchema
201
- });
202
- const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
203
- const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
204
- return {
205
- ...objectNode,
206
- properties: newProperties
207
- };
208
- } });
209
- }
210
- //#endregion
211
144
  //#region ../../internals/utils/src/casing.ts
212
145
  /**
213
146
  * Shared implementation for camelCase and PascalCase conversion.
@@ -272,6 +205,42 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
272
205
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
273
206
  }
274
207
  //#endregion
208
+ //#region ../../internals/utils/src/runtime.ts
209
+ /**
210
+ * Returns `true` when the current process is running under Bun.
211
+ *
212
+ * Detection keys off the global `Bun` object rather than `process.versions`,
213
+ * because Bun polyfills `process.versions.node` for Node compatibility and would
214
+ * otherwise look like Node.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * if (isBun()) {
219
+ * await Bun.write(path, data)
220
+ * }
221
+ * ```
222
+ */
223
+ function isBun() {
224
+ return typeof Bun !== "undefined";
225
+ }
226
+ //#endregion
227
+ //#region ../../internals/utils/src/fs.ts
228
+ /**
229
+ * Resolves to `true` when the file or directory at `path` exists.
230
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * if (await exists('./kubb.config.ts')) {
235
+ * const content = await read('./kubb.config.ts')
236
+ * }
237
+ * ```
238
+ */
239
+ async function exists(path) {
240
+ if (isBun()) return Bun.file(path).exists();
241
+ return access(path).then(() => true, () => false);
242
+ }
243
+ //#endregion
275
244
  //#region ../../internals/utils/src/object.ts
276
245
  /**
277
246
  * Returns `true` when `value` is a plain (non-null, non-array) object.
@@ -406,6 +375,22 @@ const reservedWords = new Set([
406
375
  */
407
376
  function isValidVarName(name) {
408
377
  if (!name || reservedWords.has(name)) return false;
378
+ return isIdentifier(name);
379
+ }
380
+ /**
381
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
382
+ *
383
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
384
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
385
+ * deciding whether an object key needs quoting.
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * isIdentifier('name') // true
390
+ * isIdentifier('x-total')// false
391
+ * ```
392
+ */
393
+ function isIdentifier(name) {
409
394
  return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
410
395
  }
411
396
  //#endregion
@@ -517,12 +502,13 @@ var URLPath = class {
517
502
  * @example
518
503
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
519
504
  */
520
- toTemplateString({ prefix = "", replacer } = {}) {
521
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
505
+ toTemplateString({ prefix, replacer } = {}) {
506
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
522
507
  if (i % 2 === 0) return part;
523
508
  const param = this.#transformParam(part);
524
509
  return `\${${replacer ? replacer(param) : param}}`;
525
- }).join("")}\``;
510
+ }).join("");
511
+ return `\`${prefix ?? ""}${result}\``;
526
512
  }
527
513
  /**
528
514
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -660,12 +646,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
660
646
  * ```
661
647
  */
662
648
  async function mergeDocuments(pathOrApi) {
663
- const documents = [];
664
- for (const p of pathOrApi) documents.push(await parseDocument(p, {
649
+ const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
665
650
  enablePaths: false,
666
651
  canBundle: false
667
- }));
668
- if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
652
+ })));
653
+ if (documents.length === 0) throw new Diagnostics.Error({
654
+ code: Diagnostics.code.inputRequired,
655
+ severity: "error",
656
+ message: "No OAS documents were provided for merging.",
657
+ help: "Pass at least one path or document to `input.path`.",
658
+ location: { kind: "config" }
659
+ });
669
660
  const seed = {
670
661
  openapi: MERGE_OPENAPI_VERSION,
671
662
  info: {
@@ -681,9 +672,9 @@ async function mergeDocuments(pathOrApi) {
681
672
  * Creates a `Document` from an `AdapterSource`.
682
673
  *
683
674
  * Handles all three source types:
684
- * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
685
- * - `{ type: 'paths' }` merges multiple file paths into a single document.
686
- * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
675
+ * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
676
+ * - `{ type: 'paths' }` merges multiple file paths into a single document.
677
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
687
678
  *
688
679
  * @example
689
680
  * ```ts
@@ -691,14 +682,31 @@ async function mergeDocuments(pathOrApi) {
691
682
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
692
683
  * ```
693
684
  */
694
- function parseFromConfig(source) {
685
+ async function parseFromConfig(source) {
695
686
  if (source.type === "data") {
696
687
  if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
697
688
  return parseDocument(source.data, { canBundle: false });
698
689
  }
699
690
  if (source.type === "paths") return mergeDocuments(source.paths);
700
691
  if (new URLPath(source.path).isURL) return parseDocument(source.path);
701
- return parseDocument(path.resolve(path.dirname(source.path), source.path));
692
+ const resolved = path.resolve(path.dirname(source.path), source.path);
693
+ await assertInputExists(resolved);
694
+ return parseDocument(resolved);
695
+ }
696
+ /**
697
+ * Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
698
+ * URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
699
+ * its parse error instead.
700
+ */
701
+ async function assertInputExists(input) {
702
+ if (new URLPath(input).isURL) return;
703
+ if (!await exists(input)) throw new Diagnostics.Error({
704
+ code: Diagnostics.code.inputNotFound,
705
+ severity: "error",
706
+ message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
707
+ help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
708
+ location: { kind: "config" }
709
+ });
702
710
  }
703
711
  /**
704
712
  * Validates an OpenAPI document using `oas-normalize` with colorized error output.
@@ -720,6 +728,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
720
728
  }
721
729
  //#endregion
722
730
  //#region src/refs.ts
731
+ const _refCache = /* @__PURE__ */ new WeakMap();
723
732
  /**
724
733
  * Resolves a local JSON pointer reference from a document.
725
734
  *
@@ -735,10 +744,31 @@ function resolveRef(document, $ref) {
735
744
  const origRef = $ref;
736
745
  $ref = $ref.trim();
737
746
  if ($ref === "") return null;
738
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
739
- else return null;
747
+ if (!$ref.startsWith("#")) return null;
748
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
749
+ let docCache = _refCache.get(document);
750
+ if (!docCache) {
751
+ docCache = /* @__PURE__ */ new Map();
752
+ _refCache.set(document, docCache);
753
+ }
754
+ if (docCache.has($ref)) return docCache.get($ref);
740
755
  const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
741
- if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
756
+ if (!current) {
757
+ const diagnostic = {
758
+ code: Diagnostics.code.refNotFound,
759
+ severity: "error",
760
+ message: `Could not find a definition for ${origRef}.`,
761
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
762
+ location: {
763
+ kind: "schema",
764
+ pointer: origRef,
765
+ ref: origRef
766
+ }
767
+ };
768
+ if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
769
+ return null;
770
+ }
771
+ docCache.set($ref, current);
742
772
  return current;
743
773
  }
744
774
  /**
@@ -762,6 +792,35 @@ function dereferenceWithRef(document, schema) {
762
792
  return schema;
763
793
  }
764
794
  //#endregion
795
+ //#region src/dialect.ts
796
+ /**
797
+ * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
798
+ *
799
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
800
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
801
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
802
+ * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
803
+ * nullability, no discriminator object, binary via `contentEncoding` and reuses
804
+ * the rest unchanged.
805
+ *
806
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
807
+ * JSON Schema vocabulary, so the converters keep that common case.
808
+ *
809
+ * @example
810
+ * ```ts
811
+ * const parser = createSchemaParser(context) // uses oasDialect
812
+ * const parser = createSchemaParser(context, oasDialect) // explicit
813
+ * ```
814
+ */
815
+ const oasDialect = ast.defineSchemaDialect({
816
+ name: "oas",
817
+ isNullable,
818
+ isReference,
819
+ isDiscriminator,
820
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
821
+ resolveRef
822
+ });
823
+ //#endregion
765
824
  //#region src/resolvers.ts
766
825
  /**
767
826
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
@@ -783,7 +842,16 @@ function resolveServerUrl(server, overrides) {
783
842
  for (const [key, variable] of Object.entries(server.variables)) {
784
843
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
785
844
  if (value === void 0) continue;
786
- 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(", ")}.`);
845
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Diagnostics.Error({
846
+ code: Diagnostics.code.invalidServerVariable,
847
+ severity: "error",
848
+ message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
849
+ help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
850
+ location: {
851
+ kind: "document",
852
+ pointer: "#/servers"
853
+ }
854
+ });
787
855
  url = url.replaceAll(`{${key}}`, value);
788
856
  }
789
857
  return url;
@@ -796,6 +864,15 @@ function getSchemaType(format) {
796
864
  return formatMap[format] ?? null;
797
865
  }
798
866
  /**
867
+ * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
868
+ * `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
869
+ * base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
870
+ * diagnostic in step with the parser as `formatMap` grows.
871
+ */
872
+ function isHandledFormat(format) {
873
+ return getSchemaType(format) !== null || specialCasedFormats.has(format);
874
+ }
875
+ /**
799
876
  * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
800
877
  * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
801
878
  */
@@ -805,12 +882,6 @@ function getPrimitiveType(type) {
805
882
  return "string";
806
883
  }
807
884
  /**
808
- * Narrows a content-type string to the `MediaType` union Kubb recognizes, or returns `null`.
809
- */
810
- function getMediaType(contentType) {
811
- return Object.values(ast.mediaTypes).includes(contentType) ? contentType : null;
812
- }
813
- /**
814
885
  * Returns all parameters for an operation, merging path-level and operation-level entries.
815
886
  * Operation-level parameters override path-level ones with the same `in:name` key.
816
887
  * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
@@ -898,7 +969,7 @@ function getRequestSchema(document, operation, options = {}) {
898
969
  /**
899
970
  * Flattens a keyword-only `allOf` into its parent schema.
900
971
  *
901
- * Only flattens when every member is a plain fragment no `$ref` and no structural keywords
972
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
902
973
  * (see `structuralKeys`). Outer schema values take precedence over fragment values.
903
974
  * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
904
975
  *
@@ -908,7 +979,7 @@ function getRequestSchema(document, operation, options = {}) {
908
979
  * // { type: 'object', properties: {}, description: 'A pet' }
909
980
  *
910
981
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
911
- * // returned unchanged contains a $ref
982
+ * // returned unchanged, contains a $ref
912
983
  * ```
913
984
  */
914
985
  /**
@@ -934,7 +1005,7 @@ function flattenSchema(schema) {
934
1005
  /**
935
1006
  * Extracts the inline schema from a media-type `content` map.
936
1007
  *
937
- * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
1008
+ * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
938
1009
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
939
1010
  *
940
1011
  * @example
@@ -953,21 +1024,22 @@ function extractSchemaFromContent(content, preferredContentType) {
953
1024
  /**
954
1025
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
955
1026
  */
956
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
1027
+ function* collectRefs(schema) {
957
1028
  if (Array.isArray(schema)) {
958
- for (const item of schema) collectRefs(item, refs);
959
- return refs;
1029
+ for (const item of schema) yield* collectRefs(item);
1030
+ return;
960
1031
  }
961
1032
  if (schema && typeof schema === "object") for (const key in schema) {
962
1033
  const value = schema[key];
963
- if (key === "$ref" && typeof value === "string") {
964
- if (value.startsWith("#/components/schemas/")) {
965
- const name = value.slice(21);
966
- if (name) refs.add(name);
967
- }
968
- } else collectRefs(value, refs);
1034
+ if (!(key === "$ref" && typeof value === "string")) {
1035
+ yield* collectRefs(value);
1036
+ continue;
1037
+ }
1038
+ if (value.startsWith("#/components/schemas/")) {
1039
+ const name = value.slice(21);
1040
+ if (name) yield name;
1041
+ }
969
1042
  }
970
- return refs;
971
1043
  }
972
1044
  /**
973
1045
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -983,7 +1055,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
983
1055
  */
984
1056
  function sortSchemas(schemas) {
985
1057
  const deps = /* @__PURE__ */ new Map();
986
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
1058
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
987
1059
  const sorted = [];
988
1060
  const visited = /* @__PURE__ */ new Set();
989
1061
  function visit(name, stack) {
@@ -1118,15 +1190,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1118
1190
  readOnly: schema.readOnly,
1119
1191
  writeOnly: schema.writeOnly,
1120
1192
  default: defaultValue,
1121
- example: schema.example
1193
+ example: schema.example,
1194
+ format: schema.format
1122
1195
  };
1123
1196
  }
1124
1197
  /**
1125
1198
  * Returns all request body content type keys for an operation.
1126
1199
  *
1127
- * The requestBody is dereferenced **in-place** when it is a `$ref` the same mutation
1128
- * that `getRequestSchema` already performs so that the returned list accurately reflects
1129
- * the available content types even for referenced bodies.
1200
+ * The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
1201
+ * `getRequestSchema` already performs), so the returned list accurately reflects the
1202
+ * available content types even for referenced bodies.
1130
1203
  *
1131
1204
  * @example
1132
1205
  * ```ts
@@ -1140,6 +1213,31 @@ function getRequestBodyContentTypes(document, operation) {
1140
1213
  if (!body) return [];
1141
1214
  return body.content ? Object.keys(body.content) : [];
1142
1215
  }
1216
+ /**
1217
+ * Returns all response content type keys for an operation at a given status code.
1218
+ *
1219
+ * Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
1220
+ * so the returned list reflects the available content types even for referenced responses.
1221
+ *
1222
+ * @example
1223
+ * ```ts
1224
+ * getResponseBodyContentTypes(document, operation, 200)
1225
+ * // ['application/json', 'application/xml']
1226
+ * ```
1227
+ */
1228
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1229
+ if (operation.schema.responses) {
1230
+ const responses = operation.schema.responses;
1231
+ for (const key in responses) {
1232
+ const schema = responses[key];
1233
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
1234
+ }
1235
+ }
1236
+ const responseObj = operation.getResponseByStatusCode(statusCode);
1237
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1238
+ const body = responseObj;
1239
+ return body.content ? Object.keys(body.content) : [];
1240
+ }
1143
1241
  //#endregion
1144
1242
  //#region src/parser.ts
1145
1243
  /**
@@ -1168,9 +1266,9 @@ function normalizeArrayEnum(schema) {
1168
1266
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1169
1267
  * made possible by hoisting of function declarations.
1170
1268
  *
1171
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1269
+ * @internal
1172
1270
  */
1173
- function createSchemaParser(ctx) {
1271
+ function createSchemaParser(ctx, dialect = oasDialect) {
1174
1272
  const document = ctx.document;
1175
1273
  /**
1176
1274
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1178,6 +1276,15 @@ function createSchemaParser(ctx) {
1178
1276
  */
1179
1277
  const resolvingRefs = /* @__PURE__ */ new Set();
1180
1278
  /**
1279
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1280
+ *
1281
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1282
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1283
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1284
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1285
+ */
1286
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1287
+ /**
1181
1288
  * Converts a `$ref` schema into a `RefSchemaNode`.
1182
1289
  *
1183
1290
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1186,20 +1293,26 @@ function createSchemaParser(ctx) {
1186
1293
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1187
1294
  */
1188
1295
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1189
- let resolvedSchema;
1296
+ let resolvedSchema = null;
1190
1297
  const refPath = schema.$ref;
1191
- if (refPath && !resolvingRefs.has(refPath)) try {
1192
- const referenced = resolveRef(document, refPath);
1193
- if (referenced) {
1194
- resolvingRefs.add(refPath);
1195
- resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1196
- resolvingRefs.delete(refPath);
1298
+ if (refPath && !resolvingRefs.has(refPath)) {
1299
+ if (!resolvedRefCache.has(refPath)) {
1300
+ try {
1301
+ const referenced = dialect.resolveRef(document, refPath);
1302
+ if (referenced) {
1303
+ resolvingRefs.add(refPath);
1304
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1305
+ resolvingRefs.delete(refPath);
1306
+ }
1307
+ } catch {}
1308
+ resolvedRefCache.set(refPath, resolvedSchema);
1197
1309
  }
1198
- } catch {}
1310
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1311
+ }
1199
1312
  return ast.createSchema({
1200
1313
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1201
1314
  type: "ref",
1202
- name: ast.extractRefName(schema.$ref),
1315
+ name: extractRefName(schema.$ref),
1203
1316
  ref: schema.$ref,
1204
1317
  schema: resolvedSchema
1205
1318
  });
@@ -1212,7 +1325,7 @@ function createSchemaParser(ctx) {
1212
1325
  const [memberSchema] = schema.allOf;
1213
1326
  const memberNode = parseSchema({
1214
1327
  schema: memberSchema,
1215
- name: null
1328
+ name
1216
1329
  }, rawOptions);
1217
1330
  const { kind: _kind, ...memberNodeProps } = memberNode;
1218
1331
  const mergedNullable = nullable || memberNode.nullable || void 0;
@@ -1228,21 +1341,22 @@ function createSchemaParser(ctx) {
1228
1341
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1229
1342
  default: mergedDefault,
1230
1343
  example: schema.example ?? memberNode.example,
1231
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1344
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1345
+ format: schema.format ?? memberNode.format
1232
1346
  });
1233
1347
  }
1234
1348
  const filteredDiscriminantValues = [];
1235
1349
  const allOfMembers = schema.allOf.filter((item) => {
1236
- if (!isReference(item) || !name) return true;
1237
- const deref = resolveRef(document, item.$ref);
1238
- if (!deref || !isDiscriminator(deref)) return true;
1350
+ if (!dialect.isReference(item) || !name) return true;
1351
+ const deref = dialect.resolveRef(document, item.$ref);
1352
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1239
1353
  const parentUnion = deref.oneOf ?? deref.anyOf;
1240
1354
  if (!parentUnion) return true;
1241
1355
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1242
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1356
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1243
1357
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1244
1358
  if (inOneOf || inMapping) {
1245
- const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef);
1359
+ const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1246
1360
  if (discriminatorValue) filteredDiscriminantValues.push({
1247
1361
  propertyName: deref.discriminator.propertyName,
1248
1362
  value: discriminatorValue
@@ -1250,22 +1364,28 @@ function createSchemaParser(ctx) {
1250
1364
  return false;
1251
1365
  }
1252
1366
  return true;
1253
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1367
+ }).map((s) => parseSchema({
1368
+ schema: s,
1369
+ name
1370
+ }, rawOptions));
1254
1371
  const syntheticStart = allOfMembers.length;
1255
1372
  if (Array.isArray(schema.required) && schema.required.length) {
1256
1373
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1257
1374
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1258
1375
  if (missingRequired.length) {
1259
1376
  const resolvedMembers = schema.allOf.flatMap((item) => {
1260
- if (!isReference(item)) return [item];
1261
- const deref = resolveRef(document, item.$ref);
1262
- return deref && !isReference(deref) ? [deref] : [];
1377
+ if (!dialect.isReference(item)) return [item];
1378
+ const deref = dialect.resolveRef(document, item.$ref);
1379
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1263
1380
  });
1264
1381
  for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1265
- allOfMembers.push(parseSchema({ schema: {
1266
- properties: { [key]: resolved.properties[key] },
1267
- required: [key]
1268
- } }, rawOptions));
1382
+ allOfMembers.push(parseSchema({
1383
+ schema: {
1384
+ properties: { [key]: resolved.properties[key] },
1385
+ required: [key]
1386
+ },
1387
+ name
1388
+ }, rawOptions));
1269
1389
  break;
1270
1390
  }
1271
1391
  }
@@ -1280,7 +1400,7 @@ function createSchemaParser(ctx) {
1280
1400
  }));
1281
1401
  return ast.createSchema({
1282
1402
  type: "intersection",
1283
- members: [...ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1403
+ members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1284
1404
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1285
1405
  });
1286
1406
  }
@@ -1301,10 +1421,10 @@ function createSchemaParser(ctx) {
1301
1421
  const strategy = schema.oneOf ? "one" : "any";
1302
1422
  const unionBase = {
1303
1423
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1304
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1424
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1305
1425
  strategy
1306
1426
  };
1307
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1427
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1308
1428
  const sharedPropertiesNode = schema.properties ? (() => {
1309
1429
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1310
1430
  return parseSchema({
@@ -1314,9 +1434,12 @@ function createSchemaParser(ctx) {
1314
1434
  })() : void 0;
1315
1435
  if (sharedPropertiesNode || discriminator?.mapping) {
1316
1436
  const members = unionMembers.map((s) => {
1317
- const ref = isReference(s) ? s.$ref : void 0;
1318
- const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref);
1319
- const memberNode = parseSchema({ schema: s }, rawOptions);
1437
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1438
+ const discriminatorValue = findDiscriminator(discriminator?.mapping, ref);
1439
+ const memberNode = parseSchema({
1440
+ schema: s,
1441
+ name
1442
+ }, rawOptions);
1320
1443
  if (!discriminatorValue || !discriminator) return memberNode;
1321
1444
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.setDiscriminatorEnum({
1322
1445
  node: sharedPropertiesNode,
@@ -1346,7 +1469,10 @@ function createSchemaParser(ctx) {
1346
1469
  return ast.createSchema({
1347
1470
  type: "union",
1348
1471
  ...unionBase,
1349
- members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1472
+ members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
1473
+ schema: s,
1474
+ name
1475
+ }, rawOptions)))
1350
1476
  });
1351
1477
  }
1352
1478
  /**
@@ -1360,7 +1486,8 @@ function createSchemaParser(ctx) {
1360
1486
  name,
1361
1487
  title: schema.title,
1362
1488
  description: schema.description,
1363
- deprecated: schema.deprecated
1489
+ deprecated: schema.deprecated,
1490
+ format: schema.format
1364
1491
  });
1365
1492
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1366
1493
  return ast.createSchema({
@@ -1450,6 +1577,15 @@ function createSchemaParser(ctx) {
1450
1577
  }, rawOptions);
1451
1578
  const nullInEnum = schema.enum.includes(null);
1452
1579
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1580
+ if (nullInEnum && filteredValues.length === 0) return ast.createSchema({
1581
+ type: "null",
1582
+ primitive: "null",
1583
+ name,
1584
+ title: schema.title,
1585
+ description: schema.description,
1586
+ deprecated: schema.deprecated,
1587
+ format: schema.format
1588
+ });
1453
1589
  const enumNullable = nullable || nullInEnum || void 0;
1454
1590
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1455
1591
  const enumPrimitive = getPrimitiveType(type);
@@ -1464,7 +1600,8 @@ function createSchemaParser(ctx) {
1464
1600
  readOnly: schema.readOnly,
1465
1601
  writeOnly: schema.writeOnly,
1466
1602
  default: enumDefault,
1467
- example: schema.example
1603
+ example: schema.example,
1604
+ format: schema.format
1468
1605
  };
1469
1606
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1470
1607
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
@@ -1498,20 +1635,23 @@ function createSchemaParser(ctx) {
1498
1635
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1499
1636
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1500
1637
  const resolvedPropSchema = propSchema;
1501
- const propNullable = isNullable(resolvedPropSchema);
1638
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1502
1639
  const propNode = parseSchema({
1503
1640
  schema: resolvedPropSchema,
1504
- name: ast.childName(name, propName)
1641
+ name: childName(name, propName)
1505
1642
  }, rawOptions);
1506
- let schemaNode = ast.setEnumName(propNode, name, propName, options.enumSuffix);
1507
- const tupleNode = ast.narrowSchema(schemaNode, "tuple");
1508
- if (tupleNode?.items) {
1509
- const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
1510
- if (namedItems.some((item, i) => item !== tupleNode.items[i])) schemaNode = {
1511
- ...tupleNode,
1512
- items: namedItems
1513
- };
1514
- }
1643
+ const schemaNode = (() => {
1644
+ const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
1645
+ const tupleNode = ast.narrowSchema(node, "tuple");
1646
+ if (tupleNode?.items) {
1647
+ const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
1648
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1649
+ ...tupleNode,
1650
+ items: namedItems
1651
+ };
1652
+ }
1653
+ return node;
1654
+ })();
1515
1655
  return ast.createProperty({
1516
1656
  name: propName,
1517
1657
  schema: {
@@ -1522,11 +1662,12 @@ function createSchemaParser(ctx) {
1522
1662
  });
1523
1663
  }) : [];
1524
1664
  const additionalProperties = schema.additionalProperties;
1525
- let additionalPropertiesNode;
1526
- if (additionalProperties === true) additionalPropertiesNode = true;
1527
- else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode = parseSchema({ schema: additionalProperties }, rawOptions);
1528
- else if (additionalProperties === false) additionalPropertiesNode = false;
1529
- else if (additionalProperties) additionalPropertiesNode = ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1665
+ const additionalPropertiesNode = (() => {
1666
+ if (additionalProperties === true) return true;
1667
+ if (additionalProperties === false) return false;
1668
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1669
+ if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1670
+ })();
1530
1671
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1531
1672
  const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1532
1673
  const objectNode = ast.createSchema({
@@ -1539,10 +1680,10 @@ function createSchemaParser(ctx) {
1539
1680
  maxProperties: schema.maxProperties,
1540
1681
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1541
1682
  });
1542
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1683
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1543
1684
  const discPropName = schema.discriminator.propertyName;
1544
1685
  const values = Object.keys(schema.discriminator.mapping);
1545
- const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
1686
+ const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
1546
1687
  return ast.setDiscriminatorEnum({
1547
1688
  node: objectNode,
1548
1689
  propertyName: discPropName,
@@ -1573,7 +1714,7 @@ function createSchemaParser(ctx) {
1573
1714
  */
1574
1715
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1575
1716
  const rawItems = schema.items;
1576
- const itemName = rawItems?.enum?.length && name ? ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
1717
+ const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
1577
1718
  const items = rawItems ? [parseSchema({
1578
1719
  schema: rawItems,
1579
1720
  name: itemName
@@ -1637,15 +1778,148 @@ function createSchemaParser(ctx) {
1637
1778
  title: schema.title,
1638
1779
  description: schema.description,
1639
1780
  deprecated: schema.deprecated,
1640
- nullable
1781
+ nullable,
1782
+ format: schema.format
1641
1783
  });
1642
1784
  }
1643
1785
  /**
1786
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1787
+ * into a `blob` node.
1788
+ */
1789
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1790
+ return ast.createSchema({
1791
+ type: "blob",
1792
+ primitive: "string",
1793
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1794
+ });
1795
+ }
1796
+ /**
1797
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1798
+ *
1799
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1800
+ * falls through and handles it as that single type with nullability already folded in.
1801
+ */
1802
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1803
+ const types = schema.type;
1804
+ const nonNullTypes = types.filter((t) => t !== "null");
1805
+ if (nonNullTypes.length <= 1) return null;
1806
+ const arrayNullable = types.includes("null") || nullable || void 0;
1807
+ return ast.createSchema({
1808
+ type: "union",
1809
+ members: nonNullTypes.map((t) => parseSchema({
1810
+ schema: {
1811
+ ...schema,
1812
+ type: t
1813
+ },
1814
+ name
1815
+ }, rawOptions)),
1816
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1817
+ });
1818
+ }
1819
+ /**
1820
+ * Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
1821
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1822
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1823
+ * match/convert/fall-through contract.
1824
+ */
1825
+ const schemaRules = [
1826
+ {
1827
+ name: "ref",
1828
+ match: ({ schema }) => dialect.isReference(schema),
1829
+ convert: convertRef
1830
+ },
1831
+ {
1832
+ name: "allOf",
1833
+ match: ({ schema }) => !!schema.allOf?.length,
1834
+ convert: convertAllOf
1835
+ },
1836
+ {
1837
+ name: "union",
1838
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1839
+ convert: convertUnion
1840
+ },
1841
+ {
1842
+ name: "const",
1843
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1844
+ convert: convertConst
1845
+ },
1846
+ {
1847
+ name: "format",
1848
+ match: ({ schema }) => !!schema.format,
1849
+ convert: convertFormat
1850
+ },
1851
+ {
1852
+ name: "blob",
1853
+ match: ({ schema }) => dialect.isBinary(schema),
1854
+ convert: convertBlob
1855
+ },
1856
+ {
1857
+ name: "multi-type",
1858
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1859
+ convert: convertMultiType
1860
+ },
1861
+ {
1862
+ name: "constrained-string",
1863
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1864
+ convert: convertString
1865
+ },
1866
+ {
1867
+ name: "constrained-number",
1868
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1869
+ convert: (ctx) => convertNumeric(ctx, "number")
1870
+ },
1871
+ {
1872
+ name: "enum",
1873
+ match: ({ schema }) => !!schema.enum?.length,
1874
+ convert: convertEnum
1875
+ },
1876
+ {
1877
+ name: "object",
1878
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1879
+ convert: convertObject
1880
+ },
1881
+ {
1882
+ name: "tuple",
1883
+ match: ({ schema }) => "prefixItems" in schema,
1884
+ convert: convertTuple
1885
+ },
1886
+ {
1887
+ name: "array",
1888
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1889
+ convert: convertArray
1890
+ },
1891
+ {
1892
+ name: "string",
1893
+ match: ({ type }) => type === "string",
1894
+ convert: convertString
1895
+ },
1896
+ {
1897
+ name: "number",
1898
+ match: ({ type }) => type === "number",
1899
+ convert: (ctx) => convertNumeric(ctx, "number")
1900
+ },
1901
+ {
1902
+ name: "integer",
1903
+ match: ({ type }) => type === "integer",
1904
+ convert: (ctx) => convertNumeric(ctx, "integer")
1905
+ },
1906
+ {
1907
+ name: "boolean",
1908
+ match: ({ type }) => type === "boolean",
1909
+ convert: convertBoolean
1910
+ },
1911
+ {
1912
+ name: "null",
1913
+ match: ({ type }) => type === "null",
1914
+ convert: convertNull
1915
+ }
1916
+ ];
1917
+ /**
1644
1918
  * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1645
1919
  *
1646
- * Dispatch order (first match wins): `$ref` `allOf` `oneOf`/`anyOf` `const` → `format`
1647
- * octet-stream blob multi-type array constraint-inferred type `enum` object/array/tuple/scalar
1648
- * → empty-schema fallback (`emptySchemaType` option).
1920
+ * Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
1921
+ * via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
1922
+ * `emptySchemaType`.
1649
1923
  */
1650
1924
  function parseSchema({ schema, name }, rawOptions) {
1651
1925
  const options = {
@@ -1657,75 +1931,40 @@ function createSchemaParser(ctx) {
1657
1931
  schema: flattenedSchema,
1658
1932
  name
1659
1933
  }, rawOptions);
1660
- const nullable = isNullable(schema) || void 0;
1661
- const defaultValue = schema.default === null && nullable ? void 0 : schema.default;
1662
- const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1663
- const ctx = {
1934
+ const nullable = dialect.isNullable(schema) || void 0;
1935
+ const schemaCtx = {
1664
1936
  schema,
1665
1937
  name,
1666
1938
  nullable,
1667
- defaultValue,
1668
- type,
1939
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1940
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1669
1941
  rawOptions,
1670
1942
  options
1671
1943
  };
1672
- if (isReference(schema)) return convertRef(ctx);
1673
- if (schema.allOf?.length) return convertAllOf(ctx);
1674
- if ([...schema.oneOf ?? [], ...schema.anyOf ?? []].length) return convertUnion(ctx);
1675
- if ("const" in schema && schema.const !== void 0) return convertConst(ctx);
1676
- if (schema.format) {
1677
- const formatResult = convertFormat(ctx);
1678
- if (formatResult) return formatResult;
1679
- }
1680
- if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return ast.createSchema({
1681
- type: "blob",
1682
- primitive: "string",
1683
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1684
- });
1685
- if (Array.isArray(schema.type) && schema.type.length > 1) {
1686
- const nonNullTypes = schema.type.filter((t) => t !== "null");
1687
- const arrayNullable = schema.type.includes("null") || nullable || void 0;
1688
- if (nonNullTypes.length > 1) return ast.createSchema({
1689
- type: "union",
1690
- members: nonNullTypes.map((t) => parseSchema({
1691
- schema: {
1692
- ...schema,
1693
- type: t
1694
- },
1695
- name
1696
- }, rawOptions)),
1697
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1698
- });
1699
- }
1700
- if (!type) {
1701
- if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
1702
- if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
1703
- }
1704
- if (schema.enum?.length) return convertEnum(ctx);
1705
- if (type === "object" || schema.properties || schema.additionalProperties || "patternProperties" in schema) return convertObject(ctx);
1706
- if ("prefixItems" in schema) return convertTuple(ctx);
1707
- if (type === "array" || "items" in schema) return convertArray(ctx);
1708
- if (type === "string") return convertString(ctx);
1709
- if (type === "number") return convertNumeric(ctx, "number");
1710
- if (type === "integer") return convertNumeric(ctx, "integer");
1711
- if (type === "boolean") return convertBoolean(ctx);
1712
- if (type === "null") return convertNull(ctx);
1944
+ const node = ast.dispatch(schemaRules, schemaCtx);
1945
+ if (node) return node;
1713
1946
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1714
1947
  return ast.createSchema({
1715
1948
  type: emptyType,
1716
1949
  name,
1717
1950
  title: schema.title,
1718
- description: schema.description
1951
+ description: schema.description,
1952
+ format: schema.format
1719
1953
  });
1720
1954
  }
1721
1955
  /**
1722
1956
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1723
1957
  */
1724
- function parseParameter(options, param) {
1958
+ function parseParameter(options, param, parentName) {
1725
1959
  const required = param["required"] ?? false;
1726
- const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1960
+ const paramName = param["name"];
1961
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
1962
+ const schema = param["schema"] ? parseSchema({
1963
+ schema: param["schema"],
1964
+ name: schemaName
1965
+ }, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1727
1966
  return ast.createParameter({
1728
- name: param["name"],
1967
+ name: paramName,
1729
1968
  in: param["in"],
1730
1969
  schema: {
1731
1970
  ...schema,
@@ -1762,27 +2001,33 @@ function createSchemaParser(ctx) {
1762
2001
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1763
2002
  */
1764
2003
  function collectPropertyKeysByFlag(schema, flag) {
1765
- if (!schema?.properties) return void 0;
2004
+ if (!schema?.properties) return null;
1766
2005
  const keys = [];
1767
2006
  for (const key in schema.properties) {
1768
2007
  const prop = schema.properties[key];
1769
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
2008
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1770
2009
  }
1771
- return keys.length ? keys : void 0;
2010
+ return keys.length ? keys : null;
1772
2011
  }
1773
2012
  /**
1774
2013
  * Converts an OAS `Operation` into an `OperationNode`.
1775
2014
  */
1776
2015
  function parseOperation(options, operation) {
1777
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
2016
+ const operationId = operation.getOperationId();
2017
+ const operationName = operationId ? pascalCase(operationId) : void 0;
2018
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1778
2019
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1779
2020
  const requestBodyMeta = getRequestBodyMeta(operation);
2021
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1780
2022
  const content = allContentTypes.flatMap((ct) => {
1781
2023
  const schema = getRequestSchema(document, operation, { contentType: ct });
1782
2024
  if (!schema) return [];
1783
2025
  return [{
1784
2026
  contentType: ct,
1785
- schema: ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
2027
+ schema: ast.syncOptionality(parseSchema({
2028
+ schema,
2029
+ name: requestBodyName
2030
+ }, options), requestBodyMeta.required),
1786
2031
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1787
2032
  }];
1788
2033
  });
@@ -1793,21 +2038,36 @@ function createSchemaParser(ctx) {
1793
2038
  } : void 0;
1794
2039
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1795
2040
  const responseObj = operation.getResponseByStatusCode(statusCode);
1796
- const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
1797
- const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) });
1798
- const { description, content } = getResponseMeta(responseObj);
1799
- const mediaType = content ? getMediaType(Object.keys(content)[0] ?? "") : getMediaType(operation.contentType ?? "");
2041
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2042
+ const { description } = getResponseMeta(responseObj);
2043
+ const parseEntrySchema = (contentType) => {
2044
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
2045
+ return {
2046
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2047
+ schema: raw,
2048
+ name: responseName
2049
+ }, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
2050
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2051
+ };
2052
+ };
2053
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
2054
+ contentType,
2055
+ ...parseEntrySchema(contentType)
2056
+ }));
2057
+ if (content.length === 0) content.push({
2058
+ contentType: operation.contentType || "application/json",
2059
+ ...parseEntrySchema(ctx.contentType)
2060
+ });
1800
2061
  return ast.createResponse({
1801
2062
  statusCode,
1802
2063
  description,
1803
- schema,
1804
- mediaType,
1805
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
2064
+ content
1806
2065
  });
1807
2066
  });
1808
2067
  const urlPath = new URLPath(operation.path);
1809
2068
  return ast.createOperation({
1810
- operationId: operation.getOperationId(),
2069
+ operationId,
2070
+ protocol: "http",
1811
2071
  method: operation.method.toUpperCase(),
1812
2072
  path: urlPath.path,
1813
2073
  tags: operation.getTags().map((tag) => tag.name),
@@ -1825,59 +2085,336 @@ function createSchemaParser(ctx) {
1825
2085
  parseParameter
1826
2086
  };
1827
2087
  }
2088
+ //#endregion
2089
+ //#region src/discriminator.ts
1828
2090
  /**
1829
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
2091
+ * Builds a map of child schema names discriminator patch data by scanning the given
2092
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
1830
2093
  *
1831
- * This is the main entry point for `@kubb/adapter-oas`. It converts OpenAPI/Swagger specs into a spec-agnostic tree
1832
- * that downstream plugins (`plugin-ts`, `plugin-zod`, etc.) consume for code generation. No code is generated here —
1833
- * the tree is a pure data structure representing all schemas and operations.
2094
+ * The streaming path calls this on a small pre-parsed subset of schemas (only the
2095
+ * discriminator parents) rather than on all schemas at once.
2096
+ */
2097
+ function buildDiscriminatorChildMap(schemas) {
2098
+ const childMap = /* @__PURE__ */ new Map();
2099
+ for (const schema of schemas) {
2100
+ let unionNode = ast.narrowSchema(schema, "union");
2101
+ if (!unionNode) {
2102
+ const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
2103
+ if (intersectionMembers) for (const m of intersectionMembers) {
2104
+ const u = ast.narrowSchema(m, "union");
2105
+ if (u) {
2106
+ unionNode = u;
2107
+ break;
2108
+ }
2109
+ }
2110
+ }
2111
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
2112
+ const { discriminatorPropertyName, members } = unionNode;
2113
+ for (const member of members) {
2114
+ const intersectionNode = ast.narrowSchema(member, "intersection");
2115
+ if (!intersectionNode?.members) continue;
2116
+ let refNode = null;
2117
+ let objNode = null;
2118
+ for (const m of intersectionNode.members) {
2119
+ refNode ??= ast.narrowSchema(m, "ref");
2120
+ objNode ??= ast.narrowSchema(m, "object");
2121
+ }
2122
+ if (!refNode?.name || !objNode) continue;
2123
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
2124
+ const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
2125
+ if (!enumNode?.enumValues?.length) continue;
2126
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
2127
+ if (!enumValues.length) continue;
2128
+ const existing = childMap.get(refNode.name);
2129
+ if (!existing) {
2130
+ childMap.set(refNode.name, {
2131
+ propertyName: discriminatorPropertyName,
2132
+ enumValues: [...enumValues]
2133
+ });
2134
+ continue;
2135
+ }
2136
+ existing.enumValues.push(...enumValues);
2137
+ }
2138
+ }
2139
+ return childMap;
2140
+ }
2141
+ /**
2142
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
2143
+ * the discriminant property). Used by the streaming path to apply patches inline per yield
2144
+ * without buffering all schemas.
2145
+ */
2146
+ function patchDiscriminatorNode(node, entry) {
2147
+ const objectNode = ast.narrowSchema(node, "object");
2148
+ if (!objectNode) return node;
2149
+ const { propertyName, enumValues } = entry;
2150
+ const enumSchema = ast.createSchema({
2151
+ type: "enum",
2152
+ enumValues
2153
+ });
2154
+ const newProp = ast.createProperty({
2155
+ name: propertyName,
2156
+ required: true,
2157
+ schema: enumSchema
2158
+ });
2159
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
2160
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
2161
+ return {
2162
+ ...objectNode,
2163
+ properties: newProperties
2164
+ };
2165
+ }
2166
+ //#endregion
2167
+ //#region src/schemaDiagnostics.ts
2168
+ /**
2169
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
2170
+ * top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
2171
+ * pointer as it descends so a nested field reports against its full path
2172
+ * (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
2173
+ * resolved schema is reported under its own walk. Reports land in the active build run, are a
2174
+ * no-op outside one, and repeats are deduped by the build.
2175
+ */
2176
+ function reportSchemaDiagnostics({ node, name }) {
2177
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2178
+ }
2179
+ /**
2180
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2181
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2182
+ */
2183
+ function escapePointerToken(token) {
2184
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2185
+ }
2186
+ function visit(node, pointer) {
2187
+ if (node.deprecated) Diagnostics.report({
2188
+ code: Diagnostics.code.deprecated,
2189
+ severity: "info",
2190
+ message: "This schema is marked as deprecated.",
2191
+ location: {
2192
+ kind: "schema",
2193
+ pointer
2194
+ }
2195
+ });
2196
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
2197
+ code: Diagnostics.code.unsupportedFormat,
2198
+ severity: "warning",
2199
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2200
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2201
+ location: {
2202
+ kind: "schema",
2203
+ pointer
2204
+ }
2205
+ });
2206
+ if (node.type === "object") {
2207
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2208
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2209
+ return;
2210
+ }
2211
+ if (node.type === "array") {
2212
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2213
+ return;
2214
+ }
2215
+ if (node.type === "tuple") {
2216
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2217
+ return;
2218
+ }
2219
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2220
+ }
2221
+ //#endregion
2222
+ //#region src/stream.ts
2223
+ /**
2224
+ * Builds the deduplication plan from the already-parsed top-level schema nodes plus a
2225
+ * single extra parse pass over operations (so duplicates in request/response bodies are seen).
1834
2226
  *
1835
- * Returns the AST root and a `nameMapping` for resolving schema references.
2227
+ * Only enums and objects are candidates, and object shapes that reference a circular schema are
2228
+ * rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
2229
+ * name (collision-resolved against existing component names); shapes without a name stay inline.
2230
+ */
2231
+ function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
2232
+ const circularSchemas = new Set(circularNames);
2233
+ const usedNames = new Set(schemaNames);
2234
+ return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
2235
+ isCandidate: (node) => {
2236
+ if (node.type === "enum") return true;
2237
+ if (node.type !== "object") return false;
2238
+ if (node.name && circularSchemas.has(node.name)) return false;
2239
+ return !ast.containsCircularRef(node, { circularSchemas });
2240
+ },
2241
+ nameFor: (node) => {
2242
+ const base = node.name;
2243
+ if (!base) return null;
2244
+ let name = base;
2245
+ let counter = 2;
2246
+ while (usedNames.has(name)) name = `${base}${counter++}`;
2247
+ usedNames.add(name);
2248
+ return name;
2249
+ },
2250
+ refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
2251
+ });
2252
+ }
2253
+ /**
2254
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
2255
+ * interpolating any `serverVariables` into the URL template.
2256
+ *
2257
+ * Returns `null` when `serverIndex` is omitted or out of range.
2258
+ *
2259
+ * @example Resolve the first server
2260
+ * `resolveBaseUrl({ document, serverIndex: 0 })`
2261
+ *
2262
+ * @example Override a path variable
2263
+ * `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
2264
+ */
2265
+ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
2266
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
2267
+ return server?.url ? resolveServerUrl(server, serverVariables) : null;
2268
+ }
2269
+ /**
2270
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
2271
+ *
2272
+ * Three things happen in this single pass:
2273
+ * - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
2274
+ * - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
2275
+ * - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
2276
+ * The `allNodes` array is local and drops out of scope as soon as this function returns.
2277
+ *
2278
+ * After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
2279
+ * Both are proportional to the number of aliases or discriminator parents, not total schema count.
2280
+ *
2281
+ * Each schema is parsed again during the streaming pass. This is intentional.
2282
+ * Holding the parsed nodes in memory here would defeat the streaming memory benefit.
1836
2283
  *
1837
2284
  * @example
1838
2285
  * ```ts
1839
- * import { parseOas } from '@kubb/adapter-oas'
1840
- *
1841
- * const document = await parseFromConfig(config)
1842
- * const { root, nameMapping } = parseOas(document, { dateType: 'date', contentType: 'application/json' })
2286
+ * const { refAliasMap, enumNames, circularNames } = preScan({
2287
+ * schemas,
2288
+ * parseSchema,
2289
+ * parserOptions,
2290
+ * discriminator: 'strict',
2291
+ * })
1843
2292
  * ```
1844
2293
  */
1845
- function parseOas(document, options = {}) {
1846
- const { contentType, ...parserOptions } = options;
1847
- const mergedOptions = {
1848
- ...DEFAULT_PARSER_OPTIONS,
1849
- ...parserOptions
1850
- };
1851
- const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
1852
- const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
1853
- document,
1854
- contentType
1855
- });
1856
- const schemas = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({
1857
- schema,
1858
- name
1859
- }, mergedOptions));
1860
- const paths = new BaseOas(document).getPaths();
1861
- const operations = Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null));
2294
+ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
2295
+ const allNodes = [];
2296
+ const refAliasMap = /* @__PURE__ */ new Map();
2297
+ const enumNames = [];
2298
+ const discriminatorParentNodes = [];
2299
+ for (const [name, schema] of Object.entries(schemas)) {
2300
+ const node = parseSchema({
2301
+ schema,
2302
+ name
2303
+ }, parserOptions);
2304
+ allNodes.push(node);
2305
+ reportSchemaDiagnostics({
2306
+ node,
2307
+ name
2308
+ });
2309
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2310
+ if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
2311
+ if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2312
+ }
2313
+ const circularNames = [...ast.findCircularSchemas(allNodes)];
2314
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2315
+ let dedupePlan = null;
2316
+ if (dedupe) {
2317
+ const operationNodes = [];
2318
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2319
+ if (!operation) continue;
2320
+ const operationNode = parseOperation(parserOptions, operation);
2321
+ if (operationNode) operationNodes.push(operationNode);
2322
+ }
2323
+ dedupePlan = createDedupePlan({
2324
+ schemaNodes: allNodes,
2325
+ operationNodes,
2326
+ schemaNames: Object.keys(schemas),
2327
+ circularNames
2328
+ });
2329
+ for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
2330
+ }
1862
2331
  return {
1863
- root: ast.createInput({
1864
- schemas,
1865
- operations
1866
- }),
1867
- nameMapping
2332
+ refAliasMap,
2333
+ enumNames,
2334
+ circularNames,
2335
+ discriminatorChildMap,
2336
+ dedupePlan
2337
+ };
2338
+ }
2339
+ /**
2340
+ * Creates a lazy `InputStreamNode` from already-resolved adapter state.
2341
+ *
2342
+ * The schema and operation iterables each start a fresh parse pass on every
2343
+ * `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
2344
+ * stream object independently without sharing a cursor or holding all nodes in memory.
2345
+ *
2346
+ * Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
2347
+ * with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
2348
+ *
2349
+ * @example
2350
+ * ```ts
2351
+ * const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
2352
+ * for await (const schema of streamNode.schemas) {
2353
+ * // each call to for-await restarts from the first schema
2354
+ * }
2355
+ * ```
2356
+ */
2357
+ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
2358
+ const rewriteTopLevelSchema = (node) => {
2359
+ if (!dedupePlan) return node;
2360
+ const canonical = dedupePlan.canonicalBySignature.get(ast.schemaSignature(node));
2361
+ if (canonical && canonical.name !== node.name) return ast.createSchema({
2362
+ type: "ref",
2363
+ name: node.name ?? null,
2364
+ ref: canonical.ref,
2365
+ description: node.description,
2366
+ deprecated: node.deprecated
2367
+ });
2368
+ return ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
1868
2369
  };
2370
+ const schemasIterable = { [Symbol.asyncIterator]() {
2371
+ return (async function* () {
2372
+ if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
2373
+ for (const [name, schema] of Object.entries(schemas)) {
2374
+ const alias = refAliasMap.get(name);
2375
+ if (alias?.name && schemas[alias.name]) {
2376
+ yield rewriteTopLevelSchema({
2377
+ ...parseSchema({
2378
+ schema: schemas[alias.name],
2379
+ name: alias.name
2380
+ }, parserOptions),
2381
+ name
2382
+ });
2383
+ continue;
2384
+ }
2385
+ const parsed = parseSchema({
2386
+ schema,
2387
+ name
2388
+ }, parserOptions);
2389
+ yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
2390
+ }
2391
+ })();
2392
+ } };
2393
+ const operationsIterable = { [Symbol.asyncIterator]() {
2394
+ return (async function* () {
2395
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2396
+ if (!operation) continue;
2397
+ const node = parseOperation(parserOptions, operation);
2398
+ if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
2399
+ }
2400
+ })();
2401
+ } };
2402
+ return ast.createStreamInput(schemasIterable, operationsIterable, meta);
1869
2403
  }
1870
2404
  //#endregion
1871
2405
  //#region src/adapter.ts
1872
2406
  /**
1873
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2407
+ * Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
1874
2408
  */
1875
2409
  const adapterOasName = "oas";
1876
2410
  /**
1877
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2411
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2412
+ * file at `input.path`, validates it, resolves the base URL, and converts every
2413
+ * schema and operation into the universal AST that every downstream plugin
2414
+ * consumes.
1878
2415
  *
1879
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1880
- * everything into an `InputNode` that downstream plugins consume.
2416
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2417
+ * integer width, server URL) apply to every plugin in the build.
1881
2418
  *
1882
2419
  * @example
1883
2420
  * ```ts
@@ -1886,17 +2423,118 @@ const adapterOasName = "oas";
1886
2423
  * import { pluginTs } from '@kubb/plugin-ts'
1887
2424
  *
1888
2425
  * export default defineConfig({
1889
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1890
- * input: { path: './openapi.yaml' },
2426
+ * input: { path: './petStore.yaml' },
2427
+ * output: { path: './src/gen' },
2428
+ * adapter: adapterOas({
2429
+ * serverIndex: 0,
2430
+ * discriminator: 'inherit',
2431
+ * dateType: 'date',
2432
+ * }),
1891
2433
  * plugins: [pluginTs()],
1892
2434
  * })
1893
2435
  * ```
1894
2436
  */
1895
2437
  const adapterOas = createAdapter((options) => {
1896
- 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;
2438
+ 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;
2439
+ const parserOptions = {
2440
+ ...DEFAULT_PARSER_OPTIONS,
2441
+ dateType,
2442
+ integerType,
2443
+ unknownType,
2444
+ emptySchemaType,
2445
+ enumSuffix
2446
+ };
1897
2447
  let nameMapping = /* @__PURE__ */ new Map();
1898
- let parsedDocument;
1899
- let inputNode;
2448
+ let parsedDocument = null;
2449
+ const documentCache = /* @__PURE__ */ new WeakMap();
2450
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2451
+ const baseOasCache = /* @__PURE__ */ new WeakMap();
2452
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2453
+ const preScanCache = /* @__PURE__ */ new WeakMap();
2454
+ function ensureDocument(source) {
2455
+ const cached = documentCache.get(source);
2456
+ if (cached) return cached;
2457
+ const promise = (async () => {
2458
+ const fresh = await parseFromConfig(source);
2459
+ if (validate) await validateDocument(fresh);
2460
+ parsedDocument = fresh;
2461
+ return fresh;
2462
+ })();
2463
+ documentCache.set(source, promise);
2464
+ return promise;
2465
+ }
2466
+ function ensureSchemas(document) {
2467
+ const cached = schemasCache.get(document);
2468
+ if (cached) return cached;
2469
+ const promise = Promise.resolve().then(() => {
2470
+ const result = getSchemas(document, { contentType });
2471
+ nameMapping = result.nameMapping;
2472
+ return result.schemas;
2473
+ });
2474
+ schemasCache.set(document, promise);
2475
+ return promise;
2476
+ }
2477
+ function ensureBaseOas(document) {
2478
+ const cached = baseOasCache.get(document);
2479
+ if (cached) return cached;
2480
+ const baseOas = new BaseOas(document);
2481
+ baseOasCache.set(document, baseOas);
2482
+ return baseOas;
2483
+ }
2484
+ function ensureSchemaParser(document) {
2485
+ const cached = schemaParserCache.get(document);
2486
+ if (cached) return cached;
2487
+ const parser = createSchemaParser({
2488
+ document,
2489
+ contentType
2490
+ });
2491
+ schemaParserCache.set(document, parser);
2492
+ return parser;
2493
+ }
2494
+ function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
2495
+ const cached = preScanCache.get(document);
2496
+ if (cached) return cached;
2497
+ const result = preScan({
2498
+ schemas,
2499
+ parseSchema,
2500
+ parseOperation,
2501
+ baseOas,
2502
+ parserOptions,
2503
+ discriminator,
2504
+ dedupe
2505
+ });
2506
+ preScanCache.set(document, result);
2507
+ return result;
2508
+ }
2509
+ async function createStream(source) {
2510
+ const document = await ensureDocument(source);
2511
+ const schemas = await ensureSchemas(document);
2512
+ const { parseSchema, parseOperation } = ensureSchemaParser(document);
2513
+ const baseOas = ensureBaseOas(document);
2514
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
2515
+ return createInputStream({
2516
+ schemas,
2517
+ parseSchema,
2518
+ parseOperation,
2519
+ baseOas,
2520
+ parserOptions,
2521
+ refAliasMap,
2522
+ discriminatorChildMap,
2523
+ dedupePlan,
2524
+ meta: {
2525
+ title: document.info?.title,
2526
+ description: document.info?.description,
2527
+ version: document.info?.version,
2528
+ baseURL: resolveBaseUrl({
2529
+ document,
2530
+ serverIndex,
2531
+ serverVariables
2532
+ }),
2533
+ circularNames,
2534
+ enumNames
2535
+ }
2536
+ });
2537
+ }
1900
2538
  return {
1901
2539
  name: "oas",
1902
2540
  get options() {
@@ -1906,6 +2544,7 @@ const adapterOas = createAdapter((options) => {
1906
2544
  serverIndex,
1907
2545
  serverVariables,
1908
2546
  discriminator,
2547
+ dedupe,
1909
2548
  dateType,
1910
2549
  integerType,
1911
2550
  unknownType,
@@ -1917,8 +2556,9 @@ const adapterOas = createAdapter((options) => {
1917
2556
  get document() {
1918
2557
  return parsedDocument;
1919
2558
  },
1920
- get inputNode() {
1921
- return inputNode;
2559
+ async validate(input, options) {
2560
+ await assertInputExists(input);
2561
+ await validateDocument(await parseDocument(input), options);
1922
2562
  },
1923
2563
  getImports(node, resolve) {
1924
2564
  return ast.collectImports({
@@ -1926,7 +2566,7 @@ const adapterOas = createAdapter((options) => {
1926
2566
  nameMapping,
1927
2567
  resolve: (schemaName) => {
1928
2568
  const result = resolve(schemaName);
1929
- if (!result) return;
2569
+ if (!result) return null;
1930
2570
  return ast.createImport({
1931
2571
  name: [result.name],
1932
2572
  path: result.path
@@ -1935,32 +2575,15 @@ const adapterOas = createAdapter((options) => {
1935
2575
  });
1936
2576
  },
1937
2577
  async parse(source) {
1938
- const document = await parseFromConfig(source);
1939
- if (validate) await validateDocument(document);
1940
- const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
1941
- const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
1942
- const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
1943
- contentType,
1944
- dateType,
1945
- integerType,
1946
- unknownType,
1947
- emptySchemaType,
1948
- enumSuffix
1949
- });
1950
- const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
1951
- nameMapping = parsedNameMapping;
1952
- parsedDocument = document;
1953
- inputNode = ast.createInput({
1954
- ...node,
1955
- meta: {
1956
- title: document.info?.title,
1957
- description: document.info?.description,
1958
- version: document.info?.version,
1959
- baseURL
1960
- }
2578
+ const streamNode = await createStream(source);
2579
+ const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)]);
2580
+ return ast.createInput({
2581
+ schemas,
2582
+ operations,
2583
+ meta: streamNode.meta
1961
2584
  });
1962
- return inputNode;
1963
- }
2585
+ },
2586
+ stream: createStream
1964
2587
  };
1965
2588
  });
1966
2589
  //#endregion
@@ -1976,6 +2599,6 @@ const adapterOas = createAdapter((options) => {
1976
2599
  */
1977
2600
  const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
1978
2601
  //#endregion
1979
- export { HttpMethods, adapterOas, adapterOasName, mergeDocuments, parseDocument, parseFromConfig, validateDocument };
2602
+ export { HttpMethods, adapterOas, adapterOasName, mergeDocuments };
1980
2603
 
1981
2604
  //# sourceMappingURL=index.js.map