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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
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";
8
9
  import { isRef } from "oas/types";
9
10
  import { matchesMimeType } from "oas/utils";
10
11
  //#region src/constants.ts
@@ -89,6 +90,18 @@ const structuralKeys = new Set([
89
90
  * formatMap['float'] // 'number'
90
91
  * ```
91
92
  */
93
+ /**
94
+ * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
95
+ * `int64` and the date/time family. Keep this in sync with the `convertFormat`
96
+ * special-cases in `parser.ts`. `isHandledFormat` reads it so the
97
+ * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
98
+ */
99
+ const specialCasedFormats = new Set([
100
+ "int64",
101
+ "date-time",
102
+ "date",
103
+ "time"
104
+ ]);
92
105
  const formatMap = {
93
106
  uuid: "uuid",
94
107
  email: "email",
@@ -127,87 +140,6 @@ const typeOptionMap = new Map([
127
140
  ["void", ast.schemaTypes.void]
128
141
  ]);
129
142
  //#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
143
  //#region ../../internals/utils/src/casing.ts
212
144
  /**
213
145
  * Shared implementation for camelCase and PascalCase conversion.
@@ -272,6 +204,23 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
272
204
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
273
205
  }
274
206
  //#endregion
207
+ //#region ../../internals/utils/src/fs.ts
208
+ /**
209
+ * Resolves to `true` when the file or directory at `path` exists.
210
+ * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * if (await exists('./kubb.config.ts')) {
215
+ * const content = await read('./kubb.config.ts')
216
+ * }
217
+ * ```
218
+ */
219
+ async function exists(path) {
220
+ if (typeof Bun !== "undefined") return Bun.file(path).exists();
221
+ return access(path).then(() => true, () => false);
222
+ }
223
+ //#endregion
275
224
  //#region ../../internals/utils/src/object.ts
276
225
  /**
277
226
  * Returns `true` when `value` is a plain (non-null, non-array) object.
@@ -517,12 +466,13 @@ var URLPath = class {
517
466
  * @example
518
467
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
519
468
  */
520
- toTemplateString({ prefix = "", replacer } = {}) {
521
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
469
+ toTemplateString({ prefix, replacer } = {}) {
470
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
522
471
  if (i % 2 === 0) return part;
523
472
  const param = this.#transformParam(part);
524
473
  return `\${${replacer ? replacer(param) : param}}`;
525
- }).join("")}\``;
474
+ }).join("");
475
+ return `\`${prefix ?? ""}${result}\``;
526
476
  }
527
477
  /**
528
478
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -660,12 +610,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
660
610
  * ```
661
611
  */
662
612
  async function mergeDocuments(pathOrApi) {
663
- const documents = [];
664
- for (const p of pathOrApi) documents.push(await parseDocument(p, {
613
+ const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
665
614
  enablePaths: false,
666
615
  canBundle: false
667
- }));
668
- if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
616
+ })));
617
+ if (documents.length === 0) throw new Diagnostics.Error({
618
+ code: Diagnostics.code.inputRequired,
619
+ severity: "error",
620
+ message: "No OAS documents were provided for merging.",
621
+ help: "Pass at least one path or document to `input.path`.",
622
+ location: { kind: "config" }
623
+ });
669
624
  const seed = {
670
625
  openapi: MERGE_OPENAPI_VERSION,
671
626
  info: {
@@ -681,9 +636,9 @@ async function mergeDocuments(pathOrApi) {
681
636
  * Creates a `Document` from an `AdapterSource`.
682
637
  *
683
638
  * 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.
639
+ * - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
640
+ * - `{ type: 'paths' }` merges multiple file paths into a single document.
641
+ * - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
687
642
  *
688
643
  * @example
689
644
  * ```ts
@@ -691,14 +646,31 @@ async function mergeDocuments(pathOrApi) {
691
646
  * const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
692
647
  * ```
693
648
  */
694
- function parseFromConfig(source) {
649
+ async function parseFromConfig(source) {
695
650
  if (source.type === "data") {
696
651
  if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
697
652
  return parseDocument(source.data, { canBundle: false });
698
653
  }
699
654
  if (source.type === "paths") return mergeDocuments(source.paths);
700
655
  if (new URLPath(source.path).isURL) return parseDocument(source.path);
701
- return parseDocument(path.resolve(path.dirname(source.path), source.path));
656
+ const resolved = path.resolve(path.dirname(source.path), source.path);
657
+ await assertInputExists(resolved);
658
+ return parseDocument(resolved);
659
+ }
660
+ /**
661
+ * Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
662
+ * URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
663
+ * its parse error instead.
664
+ */
665
+ async function assertInputExists(input) {
666
+ if (new URLPath(input).isURL) return;
667
+ if (!await exists(input)) throw new Diagnostics.Error({
668
+ code: Diagnostics.code.inputNotFound,
669
+ severity: "error",
670
+ message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
671
+ help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
672
+ location: { kind: "config" }
673
+ });
702
674
  }
703
675
  /**
704
676
  * Validates an OpenAPI document using `oas-normalize` with colorized error output.
@@ -720,6 +692,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
720
692
  }
721
693
  //#endregion
722
694
  //#region src/refs.ts
695
+ const _refCache = /* @__PURE__ */ new WeakMap();
723
696
  /**
724
697
  * Resolves a local JSON pointer reference from a document.
725
698
  *
@@ -735,10 +708,31 @@ function resolveRef(document, $ref) {
735
708
  const origRef = $ref;
736
709
  $ref = $ref.trim();
737
710
  if ($ref === "") return null;
738
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
739
- else return null;
711
+ if (!$ref.startsWith("#")) return null;
712
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
713
+ let docCache = _refCache.get(document);
714
+ if (!docCache) {
715
+ docCache = /* @__PURE__ */ new Map();
716
+ _refCache.set(document, docCache);
717
+ }
718
+ if (docCache.has($ref)) return docCache.get($ref);
740
719
  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}.`);
720
+ if (!current) {
721
+ const diagnostic = {
722
+ code: Diagnostics.code.refNotFound,
723
+ severity: "error",
724
+ message: `Could not find a definition for ${origRef}.`,
725
+ help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
726
+ location: {
727
+ kind: "schema",
728
+ pointer: origRef,
729
+ ref: origRef
730
+ }
731
+ };
732
+ if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
733
+ return null;
734
+ }
735
+ docCache.set($ref, current);
742
736
  return current;
743
737
  }
744
738
  /**
@@ -762,6 +756,35 @@ function dereferenceWithRef(document, schema) {
762
756
  return schema;
763
757
  }
764
758
  //#endregion
759
+ //#region src/dialect.ts
760
+ /**
761
+ * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
762
+ *
763
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
764
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
765
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
766
+ * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
767
+ * nullability, no discriminator object, binary via `contentEncoding` and reuses
768
+ * the rest unchanged.
769
+ *
770
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
771
+ * JSON Schema vocabulary, so the converters keep that common case.
772
+ *
773
+ * @example
774
+ * ```ts
775
+ * const parser = createSchemaParser(context) // uses oasDialect
776
+ * const parser = createSchemaParser(context, oasDialect) // explicit
777
+ * ```
778
+ */
779
+ const oasDialect = ast.defineSchemaDialect({
780
+ name: "oas",
781
+ isNullable,
782
+ isReference,
783
+ isDiscriminator,
784
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
785
+ resolveRef
786
+ });
787
+ //#endregion
765
788
  //#region src/resolvers.ts
766
789
  /**
767
790
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
@@ -783,7 +806,16 @@ function resolveServerUrl(server, overrides) {
783
806
  for (const [key, variable] of Object.entries(server.variables)) {
784
807
  const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
785
808
  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(", ")}.`);
809
+ if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Diagnostics.Error({
810
+ code: Diagnostics.code.invalidServerVariable,
811
+ severity: "error",
812
+ message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
813
+ help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
814
+ location: {
815
+ kind: "document",
816
+ pointer: "#/servers"
817
+ }
818
+ });
787
819
  url = url.replaceAll(`{${key}}`, value);
788
820
  }
789
821
  return url;
@@ -796,6 +828,15 @@ function getSchemaType(format) {
796
828
  return formatMap[format] ?? null;
797
829
  }
798
830
  /**
831
+ * Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
832
+ * `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
833
+ * base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
834
+ * diagnostic in step with the parser as `formatMap` grows.
835
+ */
836
+ function isHandledFormat(format) {
837
+ return getSchemaType(format) !== null || specialCasedFormats.has(format);
838
+ }
839
+ /**
799
840
  * Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
800
841
  * Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
801
842
  */
@@ -805,12 +846,6 @@ function getPrimitiveType(type) {
805
846
  return "string";
806
847
  }
807
848
  /**
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
849
  * Returns all parameters for an operation, merging path-level and operation-level entries.
815
850
  * Operation-level parameters override path-level ones with the same `in:name` key.
816
851
  * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
@@ -898,7 +933,7 @@ function getRequestSchema(document, operation, options = {}) {
898
933
  /**
899
934
  * Flattens a keyword-only `allOf` into its parent schema.
900
935
  *
901
- * Only flattens when every member is a plain fragment no `$ref` and no structural keywords
936
+ * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
902
937
  * (see `structuralKeys`). Outer schema values take precedence over fragment values.
903
938
  * Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
904
939
  *
@@ -908,7 +943,7 @@ function getRequestSchema(document, operation, options = {}) {
908
943
  * // { type: 'object', properties: {}, description: 'A pet' }
909
944
  *
910
945
  * flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
911
- * // returned unchanged contains a $ref
946
+ * // returned unchanged, contains a $ref
912
947
  * ```
913
948
  */
914
949
  /**
@@ -934,7 +969,7 @@ function flattenSchema(schema) {
934
969
  /**
935
970
  * Extracts the inline schema from a media-type `content` map.
936
971
  *
937
- * Prefers `preferredContentType` when given; otherwise uses the first key in the map.
972
+ * Prefers `preferredContentType` when given, otherwise uses the first key in the map.
938
973
  * Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
939
974
  *
940
975
  * @example
@@ -953,21 +988,22 @@ function extractSchemaFromContent(content, preferredContentType) {
953
988
  /**
954
989
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
955
990
  */
956
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
991
+ function* collectRefs(schema) {
957
992
  if (Array.isArray(schema)) {
958
- for (const item of schema) collectRefs(item, refs);
959
- return refs;
993
+ for (const item of schema) yield* collectRefs(item);
994
+ return;
960
995
  }
961
996
  if (schema && typeof schema === "object") for (const key in schema) {
962
997
  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);
998
+ if (!(key === "$ref" && typeof value === "string")) {
999
+ yield* collectRefs(value);
1000
+ continue;
1001
+ }
1002
+ if (value.startsWith("#/components/schemas/")) {
1003
+ const name = value.slice(21);
1004
+ if (name) yield name;
1005
+ }
969
1006
  }
970
- return refs;
971
1007
  }
972
1008
  /**
973
1009
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -983,7 +1019,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
983
1019
  */
984
1020
  function sortSchemas(schemas) {
985
1021
  const deps = /* @__PURE__ */ new Map();
986
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
1022
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
987
1023
  const sorted = [];
988
1024
  const visited = /* @__PURE__ */ new Set();
989
1025
  function visit(name, stack) {
@@ -1118,15 +1154,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1118
1154
  readOnly: schema.readOnly,
1119
1155
  writeOnly: schema.writeOnly,
1120
1156
  default: defaultValue,
1121
- example: schema.example
1157
+ example: schema.example,
1158
+ format: schema.format
1122
1159
  };
1123
1160
  }
1124
1161
  /**
1125
1162
  * Returns all request body content type keys for an operation.
1126
1163
  *
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.
1164
+ * The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
1165
+ * `getRequestSchema` already performs), so the returned list accurately reflects the
1166
+ * available content types even for referenced bodies.
1130
1167
  *
1131
1168
  * @example
1132
1169
  * ```ts
@@ -1140,6 +1177,31 @@ function getRequestBodyContentTypes(document, operation) {
1140
1177
  if (!body) return [];
1141
1178
  return body.content ? Object.keys(body.content) : [];
1142
1179
  }
1180
+ /**
1181
+ * Returns all response content type keys for an operation at a given status code.
1182
+ *
1183
+ * Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
1184
+ * so the returned list reflects the available content types even for referenced responses.
1185
+ *
1186
+ * @example
1187
+ * ```ts
1188
+ * getResponseBodyContentTypes(document, operation, 200)
1189
+ * // ['application/json', 'application/xml']
1190
+ * ```
1191
+ */
1192
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1193
+ if (operation.schema.responses) {
1194
+ const responses = operation.schema.responses;
1195
+ for (const key in responses) {
1196
+ const schema = responses[key];
1197
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
1198
+ }
1199
+ }
1200
+ const responseObj = operation.getResponseByStatusCode(statusCode);
1201
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1202
+ const body = responseObj;
1203
+ return body.content ? Object.keys(body.content) : [];
1204
+ }
1143
1205
  //#endregion
1144
1206
  //#region src/parser.ts
1145
1207
  /**
@@ -1168,9 +1230,9 @@ function normalizeArrayEnum(schema) {
1168
1230
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1169
1231
  * made possible by hoisting of function declarations.
1170
1232
  *
1171
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1233
+ * @internal
1172
1234
  */
1173
- function createSchemaParser(ctx) {
1235
+ function createSchemaParser(ctx, dialect = oasDialect) {
1174
1236
  const document = ctx.document;
1175
1237
  /**
1176
1238
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1178,6 +1240,15 @@ function createSchemaParser(ctx) {
1178
1240
  */
1179
1241
  const resolvingRefs = /* @__PURE__ */ new Set();
1180
1242
  /**
1243
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1244
+ *
1245
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1246
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1247
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1248
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1249
+ */
1250
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1251
+ /**
1181
1252
  * Converts a `$ref` schema into a `RefSchemaNode`.
1182
1253
  *
1183
1254
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1186,16 +1257,22 @@ function createSchemaParser(ctx) {
1186
1257
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1187
1258
  */
1188
1259
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1189
- let resolvedSchema;
1260
+ let resolvedSchema = null;
1190
1261
  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);
1262
+ if (refPath && !resolvingRefs.has(refPath)) {
1263
+ if (!resolvedRefCache.has(refPath)) {
1264
+ try {
1265
+ const referenced = dialect.resolveRef(document, refPath);
1266
+ if (referenced) {
1267
+ resolvingRefs.add(refPath);
1268
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1269
+ resolvingRefs.delete(refPath);
1270
+ }
1271
+ } catch {}
1272
+ resolvedRefCache.set(refPath, resolvedSchema);
1197
1273
  }
1198
- } catch {}
1274
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1275
+ }
1199
1276
  return ast.createSchema({
1200
1277
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1201
1278
  type: "ref",
@@ -1212,7 +1289,7 @@ function createSchemaParser(ctx) {
1212
1289
  const [memberSchema] = schema.allOf;
1213
1290
  const memberNode = parseSchema({
1214
1291
  schema: memberSchema,
1215
- name: null
1292
+ name
1216
1293
  }, rawOptions);
1217
1294
  const { kind: _kind, ...memberNodeProps } = memberNode;
1218
1295
  const mergedNullable = nullable || memberNode.nullable || void 0;
@@ -1228,18 +1305,19 @@ function createSchemaParser(ctx) {
1228
1305
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1229
1306
  default: mergedDefault,
1230
1307
  example: schema.example ?? memberNode.example,
1231
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1308
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1309
+ format: schema.format ?? memberNode.format
1232
1310
  });
1233
1311
  }
1234
1312
  const filteredDiscriminantValues = [];
1235
1313
  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;
1314
+ if (!dialect.isReference(item) || !name) return true;
1315
+ const deref = dialect.resolveRef(document, item.$ref);
1316
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1239
1317
  const parentUnion = deref.oneOf ?? deref.anyOf;
1240
1318
  if (!parentUnion) return true;
1241
1319
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1242
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1320
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1243
1321
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1244
1322
  if (inOneOf || inMapping) {
1245
1323
  const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef);
@@ -1250,22 +1328,28 @@ function createSchemaParser(ctx) {
1250
1328
  return false;
1251
1329
  }
1252
1330
  return true;
1253
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1331
+ }).map((s) => parseSchema({
1332
+ schema: s,
1333
+ name
1334
+ }, rawOptions));
1254
1335
  const syntheticStart = allOfMembers.length;
1255
1336
  if (Array.isArray(schema.required) && schema.required.length) {
1256
1337
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1257
1338
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1258
1339
  if (missingRequired.length) {
1259
1340
  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] : [];
1341
+ if (!dialect.isReference(item)) return [item];
1342
+ const deref = dialect.resolveRef(document, item.$ref);
1343
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1263
1344
  });
1264
1345
  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));
1346
+ allOfMembers.push(parseSchema({
1347
+ schema: {
1348
+ properties: { [key]: resolved.properties[key] },
1349
+ required: [key]
1350
+ },
1351
+ name
1352
+ }, rawOptions));
1269
1353
  break;
1270
1354
  }
1271
1355
  }
@@ -1280,7 +1364,7 @@ function createSchemaParser(ctx) {
1280
1364
  }));
1281
1365
  return ast.createSchema({
1282
1366
  type: "intersection",
1283
- members: [...ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1367
+ members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1284
1368
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1285
1369
  });
1286
1370
  }
@@ -1301,10 +1385,10 @@ function createSchemaParser(ctx) {
1301
1385
  const strategy = schema.oneOf ? "one" : "any";
1302
1386
  const unionBase = {
1303
1387
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1304
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1388
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1305
1389
  strategy
1306
1390
  };
1307
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1391
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1308
1392
  const sharedPropertiesNode = schema.properties ? (() => {
1309
1393
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1310
1394
  return parseSchema({
@@ -1314,9 +1398,12 @@ function createSchemaParser(ctx) {
1314
1398
  })() : void 0;
1315
1399
  if (sharedPropertiesNode || discriminator?.mapping) {
1316
1400
  const members = unionMembers.map((s) => {
1317
- const ref = isReference(s) ? s.$ref : void 0;
1401
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1318
1402
  const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref);
1319
- const memberNode = parseSchema({ schema: s }, rawOptions);
1403
+ const memberNode = parseSchema({
1404
+ schema: s,
1405
+ name
1406
+ }, rawOptions);
1320
1407
  if (!discriminatorValue || !discriminator) return memberNode;
1321
1408
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.setDiscriminatorEnum({
1322
1409
  node: sharedPropertiesNode,
@@ -1346,7 +1433,10 @@ function createSchemaParser(ctx) {
1346
1433
  return ast.createSchema({
1347
1434
  type: "union",
1348
1435
  ...unionBase,
1349
- members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1436
+ members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
1437
+ schema: s,
1438
+ name
1439
+ }, rawOptions)))
1350
1440
  });
1351
1441
  }
1352
1442
  /**
@@ -1360,7 +1450,8 @@ function createSchemaParser(ctx) {
1360
1450
  name,
1361
1451
  title: schema.title,
1362
1452
  description: schema.description,
1363
- deprecated: schema.deprecated
1453
+ deprecated: schema.deprecated,
1454
+ format: schema.format
1364
1455
  });
1365
1456
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1366
1457
  return ast.createSchema({
@@ -1450,6 +1541,15 @@ function createSchemaParser(ctx) {
1450
1541
  }, rawOptions);
1451
1542
  const nullInEnum = schema.enum.includes(null);
1452
1543
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1544
+ if (nullInEnum && filteredValues.length === 0) return ast.createSchema({
1545
+ type: "null",
1546
+ primitive: "null",
1547
+ name,
1548
+ title: schema.title,
1549
+ description: schema.description,
1550
+ deprecated: schema.deprecated,
1551
+ format: schema.format
1552
+ });
1453
1553
  const enumNullable = nullable || nullInEnum || void 0;
1454
1554
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1455
1555
  const enumPrimitive = getPrimitiveType(type);
@@ -1464,7 +1564,8 @@ function createSchemaParser(ctx) {
1464
1564
  readOnly: schema.readOnly,
1465
1565
  writeOnly: schema.writeOnly,
1466
1566
  default: enumDefault,
1467
- example: schema.example
1567
+ example: schema.example,
1568
+ format: schema.format
1468
1569
  };
1469
1570
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1470
1571
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
@@ -1498,20 +1599,23 @@ function createSchemaParser(ctx) {
1498
1599
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1499
1600
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1500
1601
  const resolvedPropSchema = propSchema;
1501
- const propNullable = isNullable(resolvedPropSchema);
1602
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1502
1603
  const propNode = parseSchema({
1503
1604
  schema: resolvedPropSchema,
1504
1605
  name: ast.childName(name, propName)
1505
1606
  }, 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
- }
1607
+ const schemaNode = (() => {
1608
+ const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
1609
+ const tupleNode = ast.narrowSchema(node, "tuple");
1610
+ if (tupleNode?.items) {
1611
+ const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
1612
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1613
+ ...tupleNode,
1614
+ items: namedItems
1615
+ };
1616
+ }
1617
+ return node;
1618
+ })();
1515
1619
  return ast.createProperty({
1516
1620
  name: propName,
1517
1621
  schema: {
@@ -1522,11 +1626,12 @@ function createSchemaParser(ctx) {
1522
1626
  });
1523
1627
  }) : [];
1524
1628
  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) });
1629
+ const additionalPropertiesNode = (() => {
1630
+ if (additionalProperties === true) return true;
1631
+ if (additionalProperties === false) return false;
1632
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1633
+ if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1634
+ })();
1530
1635
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1531
1636
  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
1637
  const objectNode = ast.createSchema({
@@ -1539,7 +1644,7 @@ function createSchemaParser(ctx) {
1539
1644
  maxProperties: schema.maxProperties,
1540
1645
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1541
1646
  });
1542
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1647
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1543
1648
  const discPropName = schema.discriminator.propertyName;
1544
1649
  const values = Object.keys(schema.discriminator.mapping);
1545
1650
  const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
@@ -1573,7 +1678,7 @@ function createSchemaParser(ctx) {
1573
1678
  */
1574
1679
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1575
1680
  const rawItems = schema.items;
1576
- const itemName = rawItems?.enum?.length && name ? ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
1681
+ const itemName = rawItems?.enum?.length && name ? ast.enumPropName(null, name, options.enumSuffix) : name;
1577
1682
  const items = rawItems ? [parseSchema({
1578
1683
  schema: rawItems,
1579
1684
  name: itemName
@@ -1637,15 +1742,148 @@ function createSchemaParser(ctx) {
1637
1742
  title: schema.title,
1638
1743
  description: schema.description,
1639
1744
  deprecated: schema.deprecated,
1640
- nullable
1745
+ nullable,
1746
+ format: schema.format
1747
+ });
1748
+ }
1749
+ /**
1750
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1751
+ * into a `blob` node.
1752
+ */
1753
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1754
+ return ast.createSchema({
1755
+ type: "blob",
1756
+ primitive: "string",
1757
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1641
1758
  });
1642
1759
  }
1643
1760
  /**
1761
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1762
+ *
1763
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1764
+ * falls through and handles it as that single type with nullability already folded in.
1765
+ */
1766
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1767
+ const types = schema.type;
1768
+ const nonNullTypes = types.filter((t) => t !== "null");
1769
+ if (nonNullTypes.length <= 1) return null;
1770
+ const arrayNullable = types.includes("null") || nullable || void 0;
1771
+ return ast.createSchema({
1772
+ type: "union",
1773
+ members: nonNullTypes.map((t) => parseSchema({
1774
+ schema: {
1775
+ ...schema,
1776
+ type: t
1777
+ },
1778
+ name
1779
+ }, rawOptions)),
1780
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1781
+ });
1782
+ }
1783
+ /**
1784
+ * Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
1785
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1786
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1787
+ * match/convert/fall-through contract.
1788
+ */
1789
+ const schemaRules = [
1790
+ {
1791
+ name: "ref",
1792
+ match: ({ schema }) => dialect.isReference(schema),
1793
+ convert: convertRef
1794
+ },
1795
+ {
1796
+ name: "allOf",
1797
+ match: ({ schema }) => !!schema.allOf?.length,
1798
+ convert: convertAllOf
1799
+ },
1800
+ {
1801
+ name: "union",
1802
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1803
+ convert: convertUnion
1804
+ },
1805
+ {
1806
+ name: "const",
1807
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1808
+ convert: convertConst
1809
+ },
1810
+ {
1811
+ name: "format",
1812
+ match: ({ schema }) => !!schema.format,
1813
+ convert: convertFormat
1814
+ },
1815
+ {
1816
+ name: "blob",
1817
+ match: ({ schema }) => dialect.isBinary(schema),
1818
+ convert: convertBlob
1819
+ },
1820
+ {
1821
+ name: "multi-type",
1822
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1823
+ convert: convertMultiType
1824
+ },
1825
+ {
1826
+ name: "constrained-string",
1827
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1828
+ convert: convertString
1829
+ },
1830
+ {
1831
+ name: "constrained-number",
1832
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1833
+ convert: (ctx) => convertNumeric(ctx, "number")
1834
+ },
1835
+ {
1836
+ name: "enum",
1837
+ match: ({ schema }) => !!schema.enum?.length,
1838
+ convert: convertEnum
1839
+ },
1840
+ {
1841
+ name: "object",
1842
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1843
+ convert: convertObject
1844
+ },
1845
+ {
1846
+ name: "tuple",
1847
+ match: ({ schema }) => "prefixItems" in schema,
1848
+ convert: convertTuple
1849
+ },
1850
+ {
1851
+ name: "array",
1852
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1853
+ convert: convertArray
1854
+ },
1855
+ {
1856
+ name: "string",
1857
+ match: ({ type }) => type === "string",
1858
+ convert: convertString
1859
+ },
1860
+ {
1861
+ name: "number",
1862
+ match: ({ type }) => type === "number",
1863
+ convert: (ctx) => convertNumeric(ctx, "number")
1864
+ },
1865
+ {
1866
+ name: "integer",
1867
+ match: ({ type }) => type === "integer",
1868
+ convert: (ctx) => convertNumeric(ctx, "integer")
1869
+ },
1870
+ {
1871
+ name: "boolean",
1872
+ match: ({ type }) => type === "boolean",
1873
+ convert: convertBoolean
1874
+ },
1875
+ {
1876
+ name: "null",
1877
+ match: ({ type }) => type === "null",
1878
+ convert: convertNull
1879
+ }
1880
+ ];
1881
+ /**
1644
1882
  * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1645
1883
  *
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).
1884
+ * Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
1885
+ * via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
1886
+ * `emptySchemaType`.
1649
1887
  */
1650
1888
  function parseSchema({ schema, name }, rawOptions) {
1651
1889
  const options = {
@@ -1657,75 +1895,40 @@ function createSchemaParser(ctx) {
1657
1895
  schema: flattenedSchema,
1658
1896
  name
1659
1897
  }, 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 = {
1898
+ const nullable = dialect.isNullable(schema) || void 0;
1899
+ const schemaCtx = {
1664
1900
  schema,
1665
1901
  name,
1666
1902
  nullable,
1667
- defaultValue,
1668
- type,
1903
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1904
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1669
1905
  rawOptions,
1670
1906
  options
1671
1907
  };
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);
1908
+ const node = ast.dispatch(schemaRules, schemaCtx);
1909
+ if (node) return node;
1713
1910
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1714
1911
  return ast.createSchema({
1715
1912
  type: emptyType,
1716
1913
  name,
1717
1914
  title: schema.title,
1718
- description: schema.description
1915
+ description: schema.description,
1916
+ format: schema.format
1719
1917
  });
1720
1918
  }
1721
1919
  /**
1722
1920
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1723
1921
  */
1724
- function parseParameter(options, param) {
1922
+ function parseParameter(options, param, parentName) {
1725
1923
  const required = param["required"] ?? false;
1726
- const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1924
+ const paramName = param["name"];
1925
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
1926
+ const schema = param["schema"] ? parseSchema({
1927
+ schema: param["schema"],
1928
+ name: schemaName
1929
+ }, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1727
1930
  return ast.createParameter({
1728
- name: param["name"],
1931
+ name: paramName,
1729
1932
  in: param["in"],
1730
1933
  schema: {
1731
1934
  ...schema,
@@ -1762,27 +1965,33 @@ function createSchemaParser(ctx) {
1762
1965
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1763
1966
  */
1764
1967
  function collectPropertyKeysByFlag(schema, flag) {
1765
- if (!schema?.properties) return void 0;
1968
+ if (!schema?.properties) return null;
1766
1969
  const keys = [];
1767
1970
  for (const key in schema.properties) {
1768
1971
  const prop = schema.properties[key];
1769
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1972
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1770
1973
  }
1771
- return keys.length ? keys : void 0;
1974
+ return keys.length ? keys : null;
1772
1975
  }
1773
1976
  /**
1774
1977
  * Converts an OAS `Operation` into an `OperationNode`.
1775
1978
  */
1776
1979
  function parseOperation(options, operation) {
1777
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
1980
+ const operationId = operation.getOperationId();
1981
+ const operationName = operationId ? pascalCase(operationId) : void 0;
1982
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1778
1983
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1779
1984
  const requestBodyMeta = getRequestBodyMeta(operation);
1985
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1780
1986
  const content = allContentTypes.flatMap((ct) => {
1781
1987
  const schema = getRequestSchema(document, operation, { contentType: ct });
1782
1988
  if (!schema) return [];
1783
1989
  return [{
1784
1990
  contentType: ct,
1785
- schema: ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
1991
+ schema: ast.syncOptionality(parseSchema({
1992
+ schema,
1993
+ name: requestBodyName
1994
+ }, options), requestBodyMeta.required),
1786
1995
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1787
1996
  }];
1788
1997
  });
@@ -1793,21 +2002,36 @@ function createSchemaParser(ctx) {
1793
2002
  } : void 0;
1794
2003
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1795
2004
  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 ?? "");
2005
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
2006
+ const { description } = getResponseMeta(responseObj);
2007
+ const parseEntrySchema = (contentType) => {
2008
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
2009
+ return {
2010
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
2011
+ schema: raw,
2012
+ name: responseName
2013
+ }, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
2014
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
2015
+ };
2016
+ };
2017
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
2018
+ contentType,
2019
+ ...parseEntrySchema(contentType)
2020
+ }));
2021
+ if (content.length === 0) content.push({
2022
+ contentType: operation.contentType || "application/json",
2023
+ ...parseEntrySchema(ctx.contentType)
2024
+ });
1800
2025
  return ast.createResponse({
1801
2026
  statusCode,
1802
2027
  description,
1803
- schema,
1804
- mediaType,
1805
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
2028
+ content
1806
2029
  });
1807
2030
  });
1808
2031
  const urlPath = new URLPath(operation.path);
1809
2032
  return ast.createOperation({
1810
- operationId: operation.getOperationId(),
2033
+ operationId,
2034
+ protocol: "http",
1811
2035
  method: operation.method.toUpperCase(),
1812
2036
  path: urlPath.path,
1813
2037
  tags: operation.getTags().map((tag) => tag.name),
@@ -1825,59 +2049,336 @@ function createSchemaParser(ctx) {
1825
2049
  parseParameter
1826
2050
  };
1827
2051
  }
2052
+ //#endregion
2053
+ //#region src/discriminator.ts
2054
+ /**
2055
+ * Builds a map of child schema names → discriminator patch data by scanning the given
2056
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
2057
+ *
2058
+ * The streaming path calls this on a small pre-parsed subset of schemas (only the
2059
+ * discriminator parents) rather than on all schemas at once.
2060
+ */
2061
+ function buildDiscriminatorChildMap(schemas) {
2062
+ const childMap = /* @__PURE__ */ new Map();
2063
+ for (const schema of schemas) {
2064
+ let unionNode = ast.narrowSchema(schema, "union");
2065
+ if (!unionNode) {
2066
+ const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
2067
+ if (intersectionMembers) for (const m of intersectionMembers) {
2068
+ const u = ast.narrowSchema(m, "union");
2069
+ if (u) {
2070
+ unionNode = u;
2071
+ break;
2072
+ }
2073
+ }
2074
+ }
2075
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
2076
+ const { discriminatorPropertyName, members } = unionNode;
2077
+ for (const member of members) {
2078
+ const intersectionNode = ast.narrowSchema(member, "intersection");
2079
+ if (!intersectionNode?.members) continue;
2080
+ let refNode = null;
2081
+ let objNode = null;
2082
+ for (const m of intersectionNode.members) {
2083
+ refNode ??= ast.narrowSchema(m, "ref");
2084
+ objNode ??= ast.narrowSchema(m, "object");
2085
+ }
2086
+ if (!refNode?.name || !objNode) continue;
2087
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
2088
+ const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
2089
+ if (!enumNode?.enumValues?.length) continue;
2090
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
2091
+ if (!enumValues.length) continue;
2092
+ const existing = childMap.get(refNode.name);
2093
+ if (!existing) {
2094
+ childMap.set(refNode.name, {
2095
+ propertyName: discriminatorPropertyName,
2096
+ enumValues: [...enumValues]
2097
+ });
2098
+ continue;
2099
+ }
2100
+ existing.enumValues.push(...enumValues);
2101
+ }
2102
+ }
2103
+ return childMap;
2104
+ }
2105
+ /**
2106
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
2107
+ * the discriminant property). Used by the streaming path to apply patches inline per yield
2108
+ * without buffering all schemas.
2109
+ */
2110
+ function patchDiscriminatorNode(node, entry) {
2111
+ const objectNode = ast.narrowSchema(node, "object");
2112
+ if (!objectNode) return node;
2113
+ const { propertyName, enumValues } = entry;
2114
+ const enumSchema = ast.createSchema({
2115
+ type: "enum",
2116
+ enumValues
2117
+ });
2118
+ const newProp = ast.createProperty({
2119
+ name: propertyName,
2120
+ required: true,
2121
+ schema: enumSchema
2122
+ });
2123
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
2124
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
2125
+ return {
2126
+ ...objectNode,
2127
+ properties: newProperties
2128
+ };
2129
+ }
2130
+ //#endregion
2131
+ //#region src/schemaDiagnostics.ts
2132
+ /**
2133
+ * Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
2134
+ * top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
2135
+ * pointer as it descends so a nested field reports against its full path
2136
+ * (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
2137
+ * resolved schema is reported under its own walk. Reports land in the active build run, are a
2138
+ * no-op outside one, and repeats are deduped by the build.
2139
+ */
2140
+ function reportSchemaDiagnostics({ node, name }) {
2141
+ visit(node, `#/components/schemas/${escapePointerToken(name)}`);
2142
+ }
2143
+ /**
2144
+ * Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
2145
+ * property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
2146
+ */
2147
+ function escapePointerToken(token) {
2148
+ return token.replace(/~/g, "~0").replace(/\//g, "~1");
2149
+ }
2150
+ function visit(node, pointer) {
2151
+ if (node.deprecated) Diagnostics.report({
2152
+ code: Diagnostics.code.deprecated,
2153
+ severity: "info",
2154
+ message: "This schema is marked as deprecated.",
2155
+ location: {
2156
+ kind: "schema",
2157
+ pointer
2158
+ }
2159
+ });
2160
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
2161
+ code: Diagnostics.code.unsupportedFormat,
2162
+ severity: "warning",
2163
+ message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
2164
+ help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
2165
+ location: {
2166
+ kind: "schema",
2167
+ pointer
2168
+ }
2169
+ });
2170
+ if (node.type === "object") {
2171
+ for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
2172
+ if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
2173
+ return;
2174
+ }
2175
+ if (node.type === "array") {
2176
+ for (const item of node.items ?? []) visit(item, `${pointer}/items`);
2177
+ return;
2178
+ }
2179
+ if (node.type === "tuple") {
2180
+ for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
2181
+ return;
2182
+ }
2183
+ if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
2184
+ }
2185
+ //#endregion
2186
+ //#region src/stream.ts
2187
+ /**
2188
+ * Builds the deduplication plan from the already-parsed top-level schema nodes plus a
2189
+ * single extra parse pass over operations (so duplicates in request/response bodies are seen).
2190
+ *
2191
+ * Only enums and objects are candidates, and object shapes that reference a circular schema are
2192
+ * rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
2193
+ * name (collision-resolved against existing component names); shapes without a name stay inline.
2194
+ */
2195
+ function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
2196
+ const circularSchemas = new Set(circularNames);
2197
+ const usedNames = new Set(schemaNames);
2198
+ return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
2199
+ isCandidate: (node) => {
2200
+ if (node.type === "enum") return true;
2201
+ if (node.type !== "object") return false;
2202
+ if (node.name && circularSchemas.has(node.name)) return false;
2203
+ return !ast.containsCircularRef(node, { circularSchemas });
2204
+ },
2205
+ nameFor: (node) => {
2206
+ const base = node.name;
2207
+ if (!base) return null;
2208
+ let name = base;
2209
+ let counter = 2;
2210
+ while (usedNames.has(name)) name = `${base}${counter++}`;
2211
+ usedNames.add(name);
2212
+ return name;
2213
+ },
2214
+ refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
2215
+ });
2216
+ }
2217
+ /**
2218
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
2219
+ * interpolating any `serverVariables` into the URL template.
2220
+ *
2221
+ * Returns `null` when `serverIndex` is omitted or out of range.
2222
+ *
2223
+ * @example Resolve the first server
2224
+ * `resolveBaseUrl({ document, serverIndex: 0 })`
2225
+ *
2226
+ * @example Override a path variable
2227
+ * `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
2228
+ */
2229
+ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
2230
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
2231
+ return server?.url ? resolveServerUrl(server, serverVariables) : null;
2232
+ }
1828
2233
  /**
1829
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
2234
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
2235
+ *
2236
+ * Three things happen in this single pass:
2237
+ * - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
2238
+ * - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
2239
+ * - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
2240
+ * The `allNodes` array is local and drops out of scope as soon as this function returns.
1830
2241
  *
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.
2242
+ * After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
2243
+ * Both are proportional to the number of aliases or discriminator parents, not total schema count.
1834
2244
  *
1835
- * Returns the AST root and a `nameMapping` for resolving schema references.
2245
+ * Each schema is parsed again during the streaming pass. This is intentional.
2246
+ * Holding the parsed nodes in memory here would defeat the streaming memory benefit.
1836
2247
  *
1837
2248
  * @example
1838
2249
  * ```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' })
2250
+ * const { refAliasMap, enumNames, circularNames } = preScan({
2251
+ * schemas,
2252
+ * parseSchema,
2253
+ * parserOptions,
2254
+ * discriminator: 'strict',
2255
+ * })
1843
2256
  * ```
1844
2257
  */
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));
2258
+ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
2259
+ const allNodes = [];
2260
+ const refAliasMap = /* @__PURE__ */ new Map();
2261
+ const enumNames = [];
2262
+ const discriminatorParentNodes = [];
2263
+ for (const [name, schema] of Object.entries(schemas)) {
2264
+ const node = parseSchema({
2265
+ schema,
2266
+ name
2267
+ }, parserOptions);
2268
+ allNodes.push(node);
2269
+ reportSchemaDiagnostics({
2270
+ node,
2271
+ name
2272
+ });
2273
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2274
+ if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
2275
+ if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2276
+ }
2277
+ const circularNames = [...ast.findCircularSchemas(allNodes)];
2278
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2279
+ let dedupePlan = null;
2280
+ if (dedupe) {
2281
+ const operationNodes = [];
2282
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2283
+ if (!operation) continue;
2284
+ const operationNode = parseOperation(parserOptions, operation);
2285
+ if (operationNode) operationNodes.push(operationNode);
2286
+ }
2287
+ dedupePlan = createDedupePlan({
2288
+ schemaNodes: allNodes,
2289
+ operationNodes,
2290
+ schemaNames: Object.keys(schemas),
2291
+ circularNames
2292
+ });
2293
+ for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
2294
+ }
1862
2295
  return {
1863
- root: ast.createInput({
1864
- schemas,
1865
- operations
1866
- }),
1867
- nameMapping
2296
+ refAliasMap,
2297
+ enumNames,
2298
+ circularNames,
2299
+ discriminatorChildMap,
2300
+ dedupePlan
2301
+ };
2302
+ }
2303
+ /**
2304
+ * Creates a lazy `InputStreamNode` from already-resolved adapter state.
2305
+ *
2306
+ * The schema and operation iterables each start a fresh parse pass on every
2307
+ * `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
2308
+ * stream object independently without sharing a cursor or holding all nodes in memory.
2309
+ *
2310
+ * Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
2311
+ * with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
2312
+ *
2313
+ * @example
2314
+ * ```ts
2315
+ * const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
2316
+ * for await (const schema of streamNode.schemas) {
2317
+ * // each call to for-await restarts from the first schema
2318
+ * }
2319
+ * ```
2320
+ */
2321
+ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
2322
+ const rewriteTopLevelSchema = (node) => {
2323
+ if (!dedupePlan) return node;
2324
+ const canonical = dedupePlan.canonicalBySignature.get(ast.schemaSignature(node));
2325
+ if (canonical && canonical.name !== node.name) return ast.createSchema({
2326
+ type: "ref",
2327
+ name: node.name ?? null,
2328
+ ref: canonical.ref,
2329
+ description: node.description,
2330
+ deprecated: node.deprecated
2331
+ });
2332
+ return ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
1868
2333
  };
2334
+ const schemasIterable = { [Symbol.asyncIterator]() {
2335
+ return (async function* () {
2336
+ if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
2337
+ for (const [name, schema] of Object.entries(schemas)) {
2338
+ const alias = refAliasMap.get(name);
2339
+ if (alias?.name && schemas[alias.name]) {
2340
+ yield rewriteTopLevelSchema({
2341
+ ...parseSchema({
2342
+ schema: schemas[alias.name],
2343
+ name: alias.name
2344
+ }, parserOptions),
2345
+ name
2346
+ });
2347
+ continue;
2348
+ }
2349
+ const parsed = parseSchema({
2350
+ schema,
2351
+ name
2352
+ }, parserOptions);
2353
+ yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
2354
+ }
2355
+ })();
2356
+ } };
2357
+ const operationsIterable = { [Symbol.asyncIterator]() {
2358
+ return (async function* () {
2359
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2360
+ if (!operation) continue;
2361
+ const node = parseOperation(parserOptions, operation);
2362
+ if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
2363
+ }
2364
+ })();
2365
+ } };
2366
+ return ast.createStreamInput(schemasIterable, operationsIterable, meta);
1869
2367
  }
1870
2368
  //#endregion
1871
2369
  //#region src/adapter.ts
1872
2370
  /**
1873
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2371
+ * Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
1874
2372
  */
1875
2373
  const adapterOasName = "oas";
1876
2374
  /**
1877
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2375
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2376
+ * file at `input.path`, validates it, resolves the base URL, and converts every
2377
+ * schema and operation into the universal AST that every downstream plugin
2378
+ * consumes.
1878
2379
  *
1879
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1880
- * everything into an `InputNode` that downstream plugins consume.
2380
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2381
+ * integer width, server URL) apply to every plugin in the build.
1881
2382
  *
1882
2383
  * @example
1883
2384
  * ```ts
@@ -1886,17 +2387,118 @@ const adapterOasName = "oas";
1886
2387
  * import { pluginTs } from '@kubb/plugin-ts'
1887
2388
  *
1888
2389
  * export default defineConfig({
1889
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1890
- * input: { path: './openapi.yaml' },
2390
+ * input: { path: './petStore.yaml' },
2391
+ * output: { path: './src/gen' },
2392
+ * adapter: adapterOas({
2393
+ * serverIndex: 0,
2394
+ * discriminator: 'inherit',
2395
+ * dateType: 'date',
2396
+ * }),
1891
2397
  * plugins: [pluginTs()],
1892
2398
  * })
1893
2399
  * ```
1894
2400
  */
1895
2401
  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;
2402
+ 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;
2403
+ const parserOptions = {
2404
+ ...DEFAULT_PARSER_OPTIONS,
2405
+ dateType,
2406
+ integerType,
2407
+ unknownType,
2408
+ emptySchemaType,
2409
+ enumSuffix
2410
+ };
1897
2411
  let nameMapping = /* @__PURE__ */ new Map();
1898
- let parsedDocument;
1899
- let inputNode;
2412
+ let parsedDocument = null;
2413
+ const documentCache = /* @__PURE__ */ new WeakMap();
2414
+ const schemasCache = /* @__PURE__ */ new WeakMap();
2415
+ const baseOasCache = /* @__PURE__ */ new WeakMap();
2416
+ const schemaParserCache = /* @__PURE__ */ new WeakMap();
2417
+ const preScanCache = /* @__PURE__ */ new WeakMap();
2418
+ function ensureDocument(source) {
2419
+ const cached = documentCache.get(source);
2420
+ if (cached) return cached;
2421
+ const promise = (async () => {
2422
+ const fresh = await parseFromConfig(source);
2423
+ if (validate) await validateDocument(fresh);
2424
+ parsedDocument = fresh;
2425
+ return fresh;
2426
+ })();
2427
+ documentCache.set(source, promise);
2428
+ return promise;
2429
+ }
2430
+ function ensureSchemas(document) {
2431
+ const cached = schemasCache.get(document);
2432
+ if (cached) return cached;
2433
+ const promise = Promise.resolve().then(() => {
2434
+ const result = getSchemas(document, { contentType });
2435
+ nameMapping = result.nameMapping;
2436
+ return result.schemas;
2437
+ });
2438
+ schemasCache.set(document, promise);
2439
+ return promise;
2440
+ }
2441
+ function ensureBaseOas(document) {
2442
+ const cached = baseOasCache.get(document);
2443
+ if (cached) return cached;
2444
+ const baseOas = new BaseOas(document);
2445
+ baseOasCache.set(document, baseOas);
2446
+ return baseOas;
2447
+ }
2448
+ function ensureSchemaParser(document) {
2449
+ const cached = schemaParserCache.get(document);
2450
+ if (cached) return cached;
2451
+ const parser = createSchemaParser({
2452
+ document,
2453
+ contentType
2454
+ });
2455
+ schemaParserCache.set(document, parser);
2456
+ return parser;
2457
+ }
2458
+ function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
2459
+ const cached = preScanCache.get(document);
2460
+ if (cached) return cached;
2461
+ const result = preScan({
2462
+ schemas,
2463
+ parseSchema,
2464
+ parseOperation,
2465
+ baseOas,
2466
+ parserOptions,
2467
+ discriminator,
2468
+ dedupe
2469
+ });
2470
+ preScanCache.set(document, result);
2471
+ return result;
2472
+ }
2473
+ async function createStream(source) {
2474
+ const document = await ensureDocument(source);
2475
+ const schemas = await ensureSchemas(document);
2476
+ const { parseSchema, parseOperation } = ensureSchemaParser(document);
2477
+ const baseOas = ensureBaseOas(document);
2478
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
2479
+ return createInputStream({
2480
+ schemas,
2481
+ parseSchema,
2482
+ parseOperation,
2483
+ baseOas,
2484
+ parserOptions,
2485
+ refAliasMap,
2486
+ discriminatorChildMap,
2487
+ dedupePlan,
2488
+ meta: {
2489
+ title: document.info?.title,
2490
+ description: document.info?.description,
2491
+ version: document.info?.version,
2492
+ baseURL: resolveBaseUrl({
2493
+ document,
2494
+ serverIndex,
2495
+ serverVariables
2496
+ }),
2497
+ circularNames,
2498
+ enumNames
2499
+ }
2500
+ });
2501
+ }
1900
2502
  return {
1901
2503
  name: "oas",
1902
2504
  get options() {
@@ -1906,6 +2508,7 @@ const adapterOas = createAdapter((options) => {
1906
2508
  serverIndex,
1907
2509
  serverVariables,
1908
2510
  discriminator,
2511
+ dedupe,
1909
2512
  dateType,
1910
2513
  integerType,
1911
2514
  unknownType,
@@ -1917,8 +2520,9 @@ const adapterOas = createAdapter((options) => {
1917
2520
  get document() {
1918
2521
  return parsedDocument;
1919
2522
  },
1920
- get inputNode() {
1921
- return inputNode;
2523
+ async validate(input, options) {
2524
+ await assertInputExists(input);
2525
+ await validateDocument(await parseDocument(input), options);
1922
2526
  },
1923
2527
  getImports(node, resolve) {
1924
2528
  return ast.collectImports({
@@ -1926,7 +2530,7 @@ const adapterOas = createAdapter((options) => {
1926
2530
  nameMapping,
1927
2531
  resolve: (schemaName) => {
1928
2532
  const result = resolve(schemaName);
1929
- if (!result) return;
2533
+ if (!result) return null;
1930
2534
  return ast.createImport({
1931
2535
  name: [result.name],
1932
2536
  path: result.path
@@ -1935,32 +2539,20 @@ const adapterOas = createAdapter((options) => {
1935
2539
  });
1936
2540
  },
1937
2541
  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
- }
2542
+ const streamNode = await createStream(source);
2543
+ const collect = async (iter) => {
2544
+ const out = [];
2545
+ for await (const item of iter) out.push(item);
2546
+ return out;
2547
+ };
2548
+ const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
2549
+ return ast.createInput({
2550
+ schemas,
2551
+ operations,
2552
+ meta: streamNode.meta
1961
2553
  });
1962
- return inputNode;
1963
- }
2554
+ },
2555
+ stream: createStream
1964
2556
  };
1965
2557
  });
1966
2558
  //#endregion
@@ -1976,6 +2568,6 @@ const adapterOas = createAdapter((options) => {
1976
2568
  */
1977
2569
  const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
1978
2570
  //#endregion
1979
- export { HttpMethods, adapterOas, adapterOasName, mergeDocuments, parseDocument, parseFromConfig, validateDocument };
2571
+ export { HttpMethods, adapterOas, adapterOasName, mergeDocuments };
1980
2572
 
1981
2573
  //# sourceMappingURL=index.js.map