@kubb/adapter-oas 5.0.0-beta.3 → 5.0.0-beta.31

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,213 +1,12 @@
1
1
  import "./chunk--u3MIqq1.js";
2
- import { ast, createAdapter } from "@kubb/core";
3
2
  import path from "node:path";
3
+ import { ast, createAdapter } from "@kubb/core";
4
+ import BaseOas from "oas";
4
5
  import { bundle, loadConfig } from "@redocly/openapi-core";
5
6
  import OASNormalize from "oas-normalize";
6
7
  import swagger2openapi from "swagger2openapi";
7
- import BaseOas from "oas";
8
8
  import { isRef } from "oas/types";
9
9
  import { matchesMimeType } from "oas/utils";
10
- //#region src/constants.ts
11
- /**
12
- * Default parser options applied when no explicit options are provided.
13
- *
14
- * @example
15
- * ```ts
16
- * import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
17
- *
18
- * const parser = createOasParser(oas)
19
- * const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
20
- * ```
21
- */
22
- const DEFAULT_PARSER_OPTIONS = {
23
- dateType: "string",
24
- integerType: "bigint",
25
- unknownType: "any",
26
- emptySchemaType: "any",
27
- enumSuffix: "enum"
28
- };
29
- /**
30
- * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
31
- *
32
- * Used when building or parsing `$ref` strings.
33
- *
34
- * @example
35
- * ```ts
36
- * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
37
- * ```
38
- */
39
- const SCHEMA_REF_PREFIX = "#/components/schemas/";
40
- /**
41
- * OpenAPI version string written into the stub document created during multi-spec merges.
42
- */
43
- const MERGE_OPENAPI_VERSION = "3.0.0";
44
- /**
45
- * Fallback `info.title` placed in the stub document when merging multiple API files.
46
- */
47
- const MERGE_DEFAULT_TITLE = "Merged API";
48
- /**
49
- * Fallback `info.version` placed in the stub document when merging multiple API files.
50
- */
51
- const MERGE_DEFAULT_VERSION = "1.0.0";
52
- /**
53
- * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
54
- *
55
- * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
56
- * intersection member rather than being merged into the parent.
57
- *
58
- * @example
59
- * ```ts
60
- * import { structuralKeys } from '@kubb/adapter-oas'
61
- *
62
- * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
63
- * // true when fragment has e.g. 'properties' or 'oneOf'
64
- * ```
65
- */
66
- const structuralKeys = new Set([
67
- "properties",
68
- "items",
69
- "additionalProperties",
70
- "oneOf",
71
- "anyOf",
72
- "allOf",
73
- "not"
74
- ]);
75
- /**
76
- * Static map from OAS `format` strings to Kubb `SchemaType` values.
77
- *
78
- * Only formats whose AST type differs from the OAS `type` field appear here.
79
- * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
80
- * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
81
- * `idn-hostname` map to `'url'` as the closest generic string-format type.
82
- *
83
- * @example
84
- * ```ts
85
- * import { formatMap } from '@kubb/adapter-oas'
86
- *
87
- * formatMap['uuid'] // 'uuid'
88
- * formatMap['binary'] // 'blob'
89
- * formatMap['float'] // 'number'
90
- * ```
91
- */
92
- const formatMap = {
93
- uuid: "uuid",
94
- email: "email",
95
- "idn-email": "email",
96
- uri: "url",
97
- "uri-reference": "url",
98
- url: "url",
99
- ipv4: "ipv4",
100
- ipv6: "ipv6",
101
- hostname: "url",
102
- "idn-hostname": "url",
103
- binary: "blob",
104
- byte: "blob",
105
- int32: "integer",
106
- float: "number",
107
- double: "number"
108
- };
109
- /**
110
- * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
111
- *
112
- * @example
113
- * ```ts
114
- * import { enumExtensionKeys } from '@kubb/adapter-oas'
115
- *
116
- * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
117
- * ```
118
- */
119
- const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
120
- /**
121
- * Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
122
- * Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
123
- */
124
- const typeOptionMap = new Map([
125
- ["any", ast.schemaTypes.any],
126
- ["unknown", ast.schemaTypes.unknown],
127
- ["void", ast.schemaTypes.void]
128
- ]);
129
- //#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
10
  //#region ../../internals/utils/src/casing.ts
212
11
  /**
213
12
  * Shared implementation for camelCase and PascalCase conversion.
@@ -306,6 +105,30 @@ function mergeDeep(target, source) {
306
105
  return result;
307
106
  }
308
107
  //#endregion
108
+ //#region ../../internals/utils/src/promise.ts
109
+ /**
110
+ * Returns a wrapper that caches the result of the first invocation and replays
111
+ * it for every subsequent call, ignoring later arguments.
112
+ *
113
+ * Works for sync and async factories — for async, the cached value is the
114
+ * promise itself, so concurrent callers share one in-flight execution and
115
+ * cannot race each other.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * const loadDocument = once(async (path: string) => parse(await readFile(path)))
120
+ * const a = loadDocument('./a.yaml') // parses
121
+ * const b = loadDocument('./b.yaml') // returns the cached promise from the first call
122
+ * ```
123
+ */
124
+ function once(factory) {
125
+ let cache;
126
+ return (...args) => {
127
+ if (!cache) cache = { value: factory(...args) };
128
+ return cache.value;
129
+ };
130
+ }
131
+ //#endregion
309
132
  //#region ../../internals/utils/src/reserved.ts
310
133
  /**
311
134
  * JavaScript and Java reserved words.
@@ -517,12 +340,13 @@ var URLPath = class {
517
340
  * @example
518
341
  * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
519
342
  */
520
- toTemplateString({ prefix = "", replacer } = {}) {
521
- return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
343
+ toTemplateString({ prefix, replacer } = {}) {
344
+ const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
522
345
  if (i % 2 === 0) return part;
523
346
  const param = this.#transformParam(part);
524
347
  return `\${${replacer ? replacer(param) : param}}`;
525
- }).join("")}\``;
348
+ }).join("");
349
+ return `\`${prefix ?? ""}${result}\``;
526
350
  }
527
351
  /**
528
352
  * Extracts all `{param}` segments from the path and returns them as a key-value map.
@@ -555,6 +379,126 @@ var URLPath = class {
555
379
  }
556
380
  };
557
381
  //#endregion
382
+ //#region src/constants.ts
383
+ /**
384
+ * Default parser options applied when no explicit options are provided.
385
+ *
386
+ * @example
387
+ * ```ts
388
+ * import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
389
+ *
390
+ * const parser = createOasParser(oas)
391
+ * const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
392
+ * ```
393
+ */
394
+ const DEFAULT_PARSER_OPTIONS = {
395
+ dateType: "string",
396
+ integerType: "bigint",
397
+ unknownType: "any",
398
+ emptySchemaType: "any",
399
+ enumSuffix: "enum"
400
+ };
401
+ /**
402
+ * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
403
+ *
404
+ * Used when building or parsing `$ref` strings.
405
+ *
406
+ * @example
407
+ * ```ts
408
+ * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
409
+ * ```
410
+ */
411
+ const SCHEMA_REF_PREFIX = "#/components/schemas/";
412
+ /**
413
+ * OpenAPI version string written into the stub document created during multi-spec merges.
414
+ */
415
+ const MERGE_OPENAPI_VERSION = "3.0.0";
416
+ /**
417
+ * Fallback `info.title` placed in the stub document when merging multiple API files.
418
+ */
419
+ const MERGE_DEFAULT_TITLE = "Merged API";
420
+ /**
421
+ * Fallback `info.version` placed in the stub document when merging multiple API files.
422
+ */
423
+ const MERGE_DEFAULT_VERSION = "1.0.0";
424
+ /**
425
+ * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
426
+ *
427
+ * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
428
+ * intersection member rather than being merged into the parent.
429
+ *
430
+ * @example
431
+ * ```ts
432
+ * import { structuralKeys } from '@kubb/adapter-oas'
433
+ *
434
+ * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
435
+ * // true when fragment has e.g. 'properties' or 'oneOf'
436
+ * ```
437
+ */
438
+ const structuralKeys = new Set([
439
+ "properties",
440
+ "items",
441
+ "additionalProperties",
442
+ "oneOf",
443
+ "anyOf",
444
+ "allOf",
445
+ "not"
446
+ ]);
447
+ /**
448
+ * Static map from OAS `format` strings to Kubb `SchemaType` values.
449
+ *
450
+ * Only formats whose AST type differs from the OAS `type` field appear here.
451
+ * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
452
+ * in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
453
+ * `idn-hostname` map to `'url'` as the closest generic string-format type.
454
+ *
455
+ * @example
456
+ * ```ts
457
+ * import { formatMap } from '@kubb/adapter-oas'
458
+ *
459
+ * formatMap['uuid'] // 'uuid'
460
+ * formatMap['binary'] // 'blob'
461
+ * formatMap['float'] // 'number'
462
+ * ```
463
+ */
464
+ const formatMap = {
465
+ uuid: "uuid",
466
+ email: "email",
467
+ "idn-email": "email",
468
+ uri: "url",
469
+ "uri-reference": "url",
470
+ url: "url",
471
+ ipv4: "ipv4",
472
+ ipv6: "ipv6",
473
+ hostname: "url",
474
+ "idn-hostname": "url",
475
+ binary: "blob",
476
+ byte: "blob",
477
+ int32: "integer",
478
+ float: "number",
479
+ double: "number"
480
+ };
481
+ /**
482
+ * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
483
+ *
484
+ * @example
485
+ * ```ts
486
+ * import { enumExtensionKeys } from '@kubb/adapter-oas'
487
+ *
488
+ * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
489
+ * ```
490
+ */
491
+ const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
492
+ /**
493
+ * Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
494
+ * Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
495
+ */
496
+ const typeOptionMap = new Map([
497
+ ["any", ast.schemaTypes.any],
498
+ ["unknown", ast.schemaTypes.unknown],
499
+ ["void", ast.schemaTypes.void]
500
+ ]);
501
+ //#endregion
558
502
  //#region src/guards.ts
559
503
  /**
560
504
  * Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
@@ -660,11 +604,10 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
660
604
  * ```
661
605
  */
662
606
  async function mergeDocuments(pathOrApi) {
663
- const documents = [];
664
- for (const p of pathOrApi) documents.push(await parseDocument(p, {
607
+ const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
665
608
  enablePaths: false,
666
609
  canBundle: false
667
- }));
610
+ })));
668
611
  if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
669
612
  const seed = {
670
613
  openapi: MERGE_OPENAPI_VERSION,
@@ -720,6 +663,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
720
663
  }
721
664
  //#endregion
722
665
  //#region src/refs.ts
666
+ const _refCache = /* @__PURE__ */ new WeakMap();
723
667
  /**
724
668
  * Resolves a local JSON pointer reference from a document.
725
669
  *
@@ -735,10 +679,17 @@ function resolveRef(document, $ref) {
735
679
  const origRef = $ref;
736
680
  $ref = $ref.trim();
737
681
  if ($ref === "") return null;
738
- if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
739
- else return null;
682
+ if (!$ref.startsWith("#")) return null;
683
+ $ref = globalThis.decodeURIComponent($ref.substring(1));
684
+ let docCache = _refCache.get(document);
685
+ if (!docCache) {
686
+ docCache = /* @__PURE__ */ new Map();
687
+ _refCache.set(document, docCache);
688
+ }
689
+ if (docCache.has($ref)) return docCache.get($ref);
740
690
  const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
741
691
  if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
692
+ docCache.set($ref, current);
742
693
  return current;
743
694
  }
744
695
  /**
@@ -762,6 +713,35 @@ function dereferenceWithRef(document, schema) {
762
713
  return schema;
763
714
  }
764
715
  //#endregion
716
+ //#region src/dialect.ts
717
+ /**
718
+ * The OpenAPI / Swagger dialect — the default used by `@kubb/adapter-oas`.
719
+ *
720
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
721
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
722
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
723
+ * future adapter (e.g. AsyncAPI) ships its own dialect — `type: ['null', …]`
724
+ * nullability, no discriminator object, binary via `contentEncoding` — and reuses
725
+ * the rest unchanged.
726
+ *
727
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
728
+ * JSON Schema vocabulary, so the converters keep that common case.
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * const parser = createSchemaParser(context) // uses oasDialect
733
+ * const parser = createSchemaParser(context, oasDialect) // explicit
734
+ * ```
735
+ */
736
+ const oasDialect = ast.defineSchemaDialect({
737
+ name: "oas",
738
+ isNullable,
739
+ isReference,
740
+ isDiscriminator,
741
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
742
+ resolveRef
743
+ });
744
+ //#endregion
765
745
  //#region src/resolvers.ts
766
746
  /**
767
747
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
@@ -805,12 +785,6 @@ function getPrimitiveType(type) {
805
785
  return "string";
806
786
  }
807
787
  /**
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
788
  * Returns all parameters for an operation, merging path-level and operation-level entries.
815
789
  * Operation-level parameters override path-level ones with the same `in:name` key.
816
790
  * `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
@@ -953,21 +927,22 @@ function extractSchemaFromContent(content, preferredContentType) {
953
927
  /**
954
928
  * Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
955
929
  */
956
- function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
930
+ function* collectRefs(schema) {
957
931
  if (Array.isArray(schema)) {
958
- for (const item of schema) collectRefs(item, refs);
959
- return refs;
932
+ for (const item of schema) yield* collectRefs(item);
933
+ return;
960
934
  }
961
935
  if (schema && typeof schema === "object") for (const key in schema) {
962
936
  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);
937
+ if (!(key === "$ref" && typeof value === "string")) {
938
+ yield* collectRefs(value);
939
+ continue;
940
+ }
941
+ if (value.startsWith("#/components/schemas/")) {
942
+ const name = value.slice(21);
943
+ if (name) yield name;
944
+ }
969
945
  }
970
- return refs;
971
946
  }
972
947
  /**
973
948
  * Returns a copy of `schemas` topologically sorted by `$ref` dependency.
@@ -983,7 +958,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
983
958
  */
984
959
  function sortSchemas(schemas) {
985
960
  const deps = /* @__PURE__ */ new Map();
986
- for (const [name, schema] of Object.entries(schemas)) deps.set(name, Array.from(collectRefs(schema)));
961
+ for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
987
962
  const sorted = [];
988
963
  const visited = /* @__PURE__ */ new Set();
989
964
  function visit(name, stack) {
@@ -1118,7 +1093,8 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
1118
1093
  readOnly: schema.readOnly,
1119
1094
  writeOnly: schema.writeOnly,
1120
1095
  default: defaultValue,
1121
- example: schema.example
1096
+ example: schema.example,
1097
+ format: schema.format
1122
1098
  };
1123
1099
  }
1124
1100
  /**
@@ -1140,6 +1116,31 @@ function getRequestBodyContentTypes(document, operation) {
1140
1116
  if (!body) return [];
1141
1117
  return body.content ? Object.keys(body.content) : [];
1142
1118
  }
1119
+ /**
1120
+ * Returns all response content type keys for an operation at a given status code.
1121
+ *
1122
+ * Response `$ref`s are resolved in-place first — the same mutation `getResponseSchema` performs —
1123
+ * so the returned list reflects the available content types even for referenced responses.
1124
+ *
1125
+ * @example
1126
+ * ```ts
1127
+ * getResponseBodyContentTypes(document, operation, 200)
1128
+ * // ['application/json', 'application/xml']
1129
+ * ```
1130
+ */
1131
+ function getResponseBodyContentTypes(document, operation, statusCode) {
1132
+ if (operation.schema.responses) {
1133
+ const responses = operation.schema.responses;
1134
+ for (const key in responses) {
1135
+ const schema = responses[key];
1136
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
1137
+ }
1138
+ }
1139
+ const responseObj = operation.getResponseByStatusCode(statusCode);
1140
+ if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
1141
+ const body = responseObj;
1142
+ return body.content ? Object.keys(body.content) : [];
1143
+ }
1143
1144
  //#endregion
1144
1145
  //#region src/parser.ts
1145
1146
  /**
@@ -1168,9 +1169,9 @@ function normalizeArrayEnum(schema) {
1168
1169
  * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1169
1170
  * made possible by hoisting of function declarations.
1170
1171
  *
1171
- * @note Not exported; called internally by `parseOas()` and `parseSchema()`.
1172
+ * @internal
1172
1173
  */
1173
- function createSchemaParser(ctx) {
1174
+ function createSchemaParser(ctx, dialect = oasDialect) {
1174
1175
  const document = ctx.document;
1175
1176
  /**
1176
1177
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1178,6 +1179,19 @@ function createSchemaParser(ctx) {
1178
1179
  */
1179
1180
  const resolvingRefs = /* @__PURE__ */ new Set();
1180
1181
  /**
1182
+ * Cache of already-resolved `$ref` schemas within this parser instance.
1183
+ *
1184
+ * Without this, the same referenced schema (e.g. `customer`) is fully re-expanded
1185
+ * every time it appears as a `$ref` in a different parent schema. In heavily
1186
+ * cross-referenced specs like Stripe (~1 400 schemas), this causes exponential
1187
+ * blowup — `customer` alone may be referenced from dozens of top-level schemas,
1188
+ * each triggering a fresh recursive expansion of its entire sub-tree.
1189
+ *
1190
+ * Memoizing by `$ref` path reduces the overall work from O(2^depth) to O(N)
1191
+ * where N is the number of unique schema names.
1192
+ */
1193
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1194
+ /**
1181
1195
  * Converts a `$ref` schema into a `RefSchemaNode`.
1182
1196
  *
1183
1197
  * The resolved schema is stored in `node.schema`. Usage-site sibling fields
@@ -1186,16 +1200,22 @@ function createSchemaParser(ctx) {
1186
1200
  * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1187
1201
  */
1188
1202
  function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1189
- let resolvedSchema;
1203
+ let resolvedSchema = null;
1190
1204
  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);
1205
+ if (refPath && !resolvingRefs.has(refPath)) {
1206
+ if (!resolvedRefCache.has(refPath)) {
1207
+ try {
1208
+ const referenced = dialect.resolveRef(document, refPath);
1209
+ if (referenced) {
1210
+ resolvingRefs.add(refPath);
1211
+ resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1212
+ resolvingRefs.delete(refPath);
1213
+ }
1214
+ } catch {}
1215
+ resolvedRefCache.set(refPath, resolvedSchema);
1197
1216
  }
1198
- } catch {}
1217
+ resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1218
+ }
1199
1219
  return ast.createSchema({
1200
1220
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1201
1221
  type: "ref",
@@ -1212,7 +1232,7 @@ function createSchemaParser(ctx) {
1212
1232
  const [memberSchema] = schema.allOf;
1213
1233
  const memberNode = parseSchema({
1214
1234
  schema: memberSchema,
1215
- name: null
1235
+ name
1216
1236
  }, rawOptions);
1217
1237
  const { kind: _kind, ...memberNodeProps } = memberNode;
1218
1238
  const mergedNullable = nullable || memberNode.nullable || void 0;
@@ -1228,18 +1248,19 @@ function createSchemaParser(ctx) {
1228
1248
  writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1229
1249
  default: mergedDefault,
1230
1250
  example: schema.example ?? memberNode.example,
1231
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
1251
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1252
+ format: schema.format ?? memberNode.format
1232
1253
  });
1233
1254
  }
1234
1255
  const filteredDiscriminantValues = [];
1235
1256
  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;
1257
+ if (!dialect.isReference(item) || !name) return true;
1258
+ const deref = dialect.resolveRef(document, item.$ref);
1259
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1239
1260
  const parentUnion = deref.oneOf ?? deref.anyOf;
1240
1261
  if (!parentUnion) return true;
1241
1262
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1242
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1263
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1243
1264
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1244
1265
  if (inOneOf || inMapping) {
1245
1266
  const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef);
@@ -1250,22 +1271,28 @@ function createSchemaParser(ctx) {
1250
1271
  return false;
1251
1272
  }
1252
1273
  return true;
1253
- }).map((s) => parseSchema({ schema: s }, rawOptions));
1274
+ }).map((s) => parseSchema({
1275
+ schema: s,
1276
+ name
1277
+ }, rawOptions));
1254
1278
  const syntheticStart = allOfMembers.length;
1255
1279
  if (Array.isArray(schema.required) && schema.required.length) {
1256
1280
  const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1257
1281
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1258
1282
  if (missingRequired.length) {
1259
1283
  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] : [];
1284
+ if (!dialect.isReference(item)) return [item];
1285
+ const deref = dialect.resolveRef(document, item.$ref);
1286
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1263
1287
  });
1264
1288
  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));
1289
+ allOfMembers.push(parseSchema({
1290
+ schema: {
1291
+ properties: { [key]: resolved.properties[key] },
1292
+ required: [key]
1293
+ },
1294
+ name
1295
+ }, rawOptions));
1269
1296
  break;
1270
1297
  }
1271
1298
  }
@@ -1280,7 +1307,7 @@ function createSchemaParser(ctx) {
1280
1307
  }));
1281
1308
  return ast.createSchema({
1282
1309
  type: "intersection",
1283
- members: [...ast.mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
1310
+ members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1284
1311
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1285
1312
  });
1286
1313
  }
@@ -1301,10 +1328,10 @@ function createSchemaParser(ctx) {
1301
1328
  const strategy = schema.oneOf ? "one" : "any";
1302
1329
  const unionBase = {
1303
1330
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1304
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1331
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1305
1332
  strategy
1306
1333
  };
1307
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1334
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1308
1335
  const sharedPropertiesNode = schema.properties ? (() => {
1309
1336
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1310
1337
  return parseSchema({
@@ -1314,9 +1341,12 @@ function createSchemaParser(ctx) {
1314
1341
  })() : void 0;
1315
1342
  if (sharedPropertiesNode || discriminator?.mapping) {
1316
1343
  const members = unionMembers.map((s) => {
1317
- const ref = isReference(s) ? s.$ref : void 0;
1344
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1318
1345
  const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref);
1319
- const memberNode = parseSchema({ schema: s }, rawOptions);
1346
+ const memberNode = parseSchema({
1347
+ schema: s,
1348
+ name
1349
+ }, rawOptions);
1320
1350
  if (!discriminatorValue || !discriminator) return memberNode;
1321
1351
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.setDiscriminatorEnum({
1322
1352
  node: sharedPropertiesNode,
@@ -1346,7 +1376,10 @@ function createSchemaParser(ctx) {
1346
1376
  return ast.createSchema({
1347
1377
  type: "union",
1348
1378
  ...unionBase,
1349
- members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
1379
+ members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
1380
+ schema: s,
1381
+ name
1382
+ }, rawOptions)))
1350
1383
  });
1351
1384
  }
1352
1385
  /**
@@ -1360,7 +1393,8 @@ function createSchemaParser(ctx) {
1360
1393
  name,
1361
1394
  title: schema.title,
1362
1395
  description: schema.description,
1363
- deprecated: schema.deprecated
1396
+ deprecated: schema.deprecated,
1397
+ format: schema.format
1364
1398
  });
1365
1399
  const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1366
1400
  return ast.createSchema({
@@ -1450,6 +1484,15 @@ function createSchemaParser(ctx) {
1450
1484
  }, rawOptions);
1451
1485
  const nullInEnum = schema.enum.includes(null);
1452
1486
  const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1487
+ if (nullInEnum && filteredValues.length === 0) return ast.createSchema({
1488
+ type: "null",
1489
+ primitive: "null",
1490
+ name,
1491
+ title: schema.title,
1492
+ description: schema.description,
1493
+ deprecated: schema.deprecated,
1494
+ format: schema.format
1495
+ });
1453
1496
  const enumNullable = nullable || nullInEnum || void 0;
1454
1497
  const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1455
1498
  const enumPrimitive = getPrimitiveType(type);
@@ -1464,7 +1507,8 @@ function createSchemaParser(ctx) {
1464
1507
  readOnly: schema.readOnly,
1465
1508
  writeOnly: schema.writeOnly,
1466
1509
  default: enumDefault,
1467
- example: schema.example
1510
+ example: schema.example,
1511
+ format: schema.format
1468
1512
  };
1469
1513
  const extensionKey = enumExtensionKeys.find((key) => key in schema);
1470
1514
  if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
@@ -1498,20 +1542,23 @@ function createSchemaParser(ctx) {
1498
1542
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1499
1543
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1500
1544
  const resolvedPropSchema = propSchema;
1501
- const propNullable = isNullable(resolvedPropSchema);
1545
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1502
1546
  const propNode = parseSchema({
1503
1547
  schema: resolvedPropSchema,
1504
1548
  name: ast.childName(name, propName)
1505
1549
  }, 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
- }
1550
+ const schemaNode = (() => {
1551
+ const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
1552
+ const tupleNode = ast.narrowSchema(node, "tuple");
1553
+ if (tupleNode?.items) {
1554
+ const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
1555
+ if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
1556
+ ...tupleNode,
1557
+ items: namedItems
1558
+ };
1559
+ }
1560
+ return node;
1561
+ })();
1515
1562
  return ast.createProperty({
1516
1563
  name: propName,
1517
1564
  schema: {
@@ -1522,11 +1569,12 @@ function createSchemaParser(ctx) {
1522
1569
  });
1523
1570
  }) : [];
1524
1571
  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) });
1572
+ const additionalPropertiesNode = (() => {
1573
+ if (additionalProperties === true) return true;
1574
+ if (additionalProperties === false) return false;
1575
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1576
+ if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1577
+ })();
1530
1578
  const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1531
1579
  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
1580
  const objectNode = ast.createSchema({
@@ -1539,7 +1587,7 @@ function createSchemaParser(ctx) {
1539
1587
  maxProperties: schema.maxProperties,
1540
1588
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1541
1589
  });
1542
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1590
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1543
1591
  const discPropName = schema.discriminator.propertyName;
1544
1592
  const values = Object.keys(schema.discriminator.mapping);
1545
1593
  const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
@@ -1573,7 +1621,7 @@ function createSchemaParser(ctx) {
1573
1621
  */
1574
1622
  function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1575
1623
  const rawItems = schema.items;
1576
- const itemName = rawItems?.enum?.length && name ? ast.enumPropName(void 0, name, options.enumSuffix) : void 0;
1624
+ const itemName = rawItems?.enum?.length && name ? ast.enumPropName(null, name, options.enumSuffix) : name;
1577
1625
  const items = rawItems ? [parseSchema({
1578
1626
  schema: rawItems,
1579
1627
  name: itemName
@@ -1637,15 +1685,148 @@ function createSchemaParser(ctx) {
1637
1685
  title: schema.title,
1638
1686
  description: schema.description,
1639
1687
  deprecated: schema.deprecated,
1640
- nullable
1688
+ nullable,
1689
+ format: schema.format
1690
+ });
1691
+ }
1692
+ /**
1693
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1694
+ * into a `blob` node.
1695
+ */
1696
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1697
+ return ast.createSchema({
1698
+ type: "blob",
1699
+ primitive: "string",
1700
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1701
+ });
1702
+ }
1703
+ /**
1704
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1705
+ *
1706
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1707
+ * falls through and handles it as that single type with nullability already folded in.
1708
+ */
1709
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1710
+ const types = schema.type;
1711
+ const nonNullTypes = types.filter((t) => t !== "null");
1712
+ if (nonNullTypes.length <= 1) return null;
1713
+ const arrayNullable = types.includes("null") || nullable || void 0;
1714
+ return ast.createSchema({
1715
+ type: "union",
1716
+ members: nonNullTypes.map((t) => parseSchema({
1717
+ schema: {
1718
+ ...schema,
1719
+ type: t
1720
+ },
1721
+ name
1722
+ }, rawOptions)),
1723
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1641
1724
  });
1642
1725
  }
1643
1726
  /**
1727
+ * Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
1728
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1729
+ * `type`. The first matching rule that produces a node wins; see {@link SchemaRule} for the
1730
+ * match/convert/fall-through contract.
1731
+ */
1732
+ const schemaRules = [
1733
+ {
1734
+ name: "ref",
1735
+ match: ({ schema }) => dialect.isReference(schema),
1736
+ convert: convertRef
1737
+ },
1738
+ {
1739
+ name: "allOf",
1740
+ match: ({ schema }) => !!schema.allOf?.length,
1741
+ convert: convertAllOf
1742
+ },
1743
+ {
1744
+ name: "union",
1745
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1746
+ convert: convertUnion
1747
+ },
1748
+ {
1749
+ name: "const",
1750
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1751
+ convert: convertConst
1752
+ },
1753
+ {
1754
+ name: "format",
1755
+ match: ({ schema }) => !!schema.format,
1756
+ convert: convertFormat
1757
+ },
1758
+ {
1759
+ name: "blob",
1760
+ match: ({ schema }) => dialect.isBinary(schema),
1761
+ convert: convertBlob
1762
+ },
1763
+ {
1764
+ name: "multi-type",
1765
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1766
+ convert: convertMultiType
1767
+ },
1768
+ {
1769
+ name: "constrained-string",
1770
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1771
+ convert: convertString
1772
+ },
1773
+ {
1774
+ name: "constrained-number",
1775
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1776
+ convert: (ctx) => convertNumeric(ctx, "number")
1777
+ },
1778
+ {
1779
+ name: "enum",
1780
+ match: ({ schema }) => !!schema.enum?.length,
1781
+ convert: convertEnum
1782
+ },
1783
+ {
1784
+ name: "object",
1785
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1786
+ convert: convertObject
1787
+ },
1788
+ {
1789
+ name: "tuple",
1790
+ match: ({ schema }) => "prefixItems" in schema,
1791
+ convert: convertTuple
1792
+ },
1793
+ {
1794
+ name: "array",
1795
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1796
+ convert: convertArray
1797
+ },
1798
+ {
1799
+ name: "string",
1800
+ match: ({ type }) => type === "string",
1801
+ convert: convertString
1802
+ },
1803
+ {
1804
+ name: "number",
1805
+ match: ({ type }) => type === "number",
1806
+ convert: (ctx) => convertNumeric(ctx, "number")
1807
+ },
1808
+ {
1809
+ name: "integer",
1810
+ match: ({ type }) => type === "integer",
1811
+ convert: (ctx) => convertNumeric(ctx, "integer")
1812
+ },
1813
+ {
1814
+ name: "boolean",
1815
+ match: ({ type }) => type === "boolean",
1816
+ convert: convertBoolean
1817
+ },
1818
+ {
1819
+ name: "null",
1820
+ match: ({ type }) => type === "null",
1821
+ convert: convertNull
1822
+ }
1823
+ ];
1824
+ /**
1644
1825
  * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1645
1826
  *
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).
1827
+ * Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
1828
+ * via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
1829
+ * `emptySchemaType`.
1649
1830
  */
1650
1831
  function parseSchema({ schema, name }, rawOptions) {
1651
1832
  const options = {
@@ -1657,75 +1838,40 @@ function createSchemaParser(ctx) {
1657
1838
  schema: flattenedSchema,
1658
1839
  name
1659
1840
  }, 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 = {
1841
+ const nullable = dialect.isNullable(schema) || void 0;
1842
+ const schemaCtx = {
1664
1843
  schema,
1665
1844
  name,
1666
1845
  nullable,
1667
- defaultValue,
1668
- type,
1846
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1847
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1669
1848
  rawOptions,
1670
1849
  options
1671
1850
  };
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);
1851
+ const node = ast.dispatch(schemaRules, schemaCtx);
1852
+ if (node) return node;
1713
1853
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1714
1854
  return ast.createSchema({
1715
1855
  type: emptyType,
1716
1856
  name,
1717
1857
  title: schema.title,
1718
- description: schema.description
1858
+ description: schema.description,
1859
+ format: schema.format
1719
1860
  });
1720
1861
  }
1721
1862
  /**
1722
1863
  * Converts a dereferenced OAS parameter object into a `ParameterNode`.
1723
1864
  */
1724
- function parseParameter(options, param) {
1865
+ function parseParameter(options, param, parentName) {
1725
1866
  const required = param["required"] ?? false;
1726
- const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1867
+ const paramName = param["name"];
1868
+ const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
1869
+ const schema = param["schema"] ? parseSchema({
1870
+ schema: param["schema"],
1871
+ name: schemaName
1872
+ }, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
1727
1873
  return ast.createParameter({
1728
- name: param["name"],
1874
+ name: paramName,
1729
1875
  in: param["in"],
1730
1876
  schema: {
1731
1877
  ...schema,
@@ -1762,27 +1908,33 @@ function createSchemaParser(ctx) {
1762
1908
  * `$ref` entries are skipped since their flags live on the dereferenced target.
1763
1909
  */
1764
1910
  function collectPropertyKeysByFlag(schema, flag) {
1765
- if (!schema?.properties) return void 0;
1911
+ if (!schema?.properties) return null;
1766
1912
  const keys = [];
1767
1913
  for (const key in schema.properties) {
1768
1914
  const prop = schema.properties[key];
1769
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1915
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1770
1916
  }
1771
- return keys.length ? keys : void 0;
1917
+ return keys.length ? keys : null;
1772
1918
  }
1773
1919
  /**
1774
1920
  * Converts an OAS `Operation` into an `OperationNode`.
1775
1921
  */
1776
1922
  function parseOperation(options, operation) {
1777
- const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
1923
+ const operationId = operation.getOperationId();
1924
+ const operationName = operationId ? pascalCase(operationId) : void 0;
1925
+ const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
1778
1926
  const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
1779
1927
  const requestBodyMeta = getRequestBodyMeta(operation);
1928
+ const requestBodyName = operationName ? `${operationName}Request` : void 0;
1780
1929
  const content = allContentTypes.flatMap((ct) => {
1781
1930
  const schema = getRequestSchema(document, operation, { contentType: ct });
1782
1931
  if (!schema) return [];
1783
1932
  return [{
1784
1933
  contentType: ct,
1785
- schema: ast.syncOptionality(parseSchema({ schema }, options), requestBodyMeta.required),
1934
+ schema: ast.syncOptionality(parseSchema({
1935
+ schema,
1936
+ name: requestBodyName
1937
+ }, options), requestBodyMeta.required),
1786
1938
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
1787
1939
  }];
1788
1940
  });
@@ -1793,21 +1945,36 @@ function createSchemaParser(ctx) {
1793
1945
  } : void 0;
1794
1946
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1795
1947
  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 ?? "");
1948
+ const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
1949
+ const { description } = getResponseMeta(responseObj);
1950
+ const parseEntrySchema = (contentType) => {
1951
+ const raw = getResponseSchema(document, operation, statusCode, { contentType });
1952
+ return {
1953
+ schema: raw && Object.keys(raw).length > 0 ? parseSchema({
1954
+ schema: raw,
1955
+ name: responseName
1956
+ }, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
1957
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
1958
+ };
1959
+ };
1960
+ const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
1961
+ contentType,
1962
+ ...parseEntrySchema(contentType)
1963
+ }));
1964
+ if (content.length === 0) content.push({
1965
+ contentType: operation.contentType || "application/json",
1966
+ ...parseEntrySchema(ctx.contentType)
1967
+ });
1800
1968
  return ast.createResponse({
1801
1969
  statusCode,
1802
1970
  description,
1803
- schema,
1804
- mediaType,
1805
- keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
1971
+ content
1806
1972
  });
1807
1973
  });
1808
1974
  const urlPath = new URLPath(operation.path);
1809
1975
  return ast.createOperation({
1810
- operationId: operation.getOperationId(),
1976
+ operationId,
1977
+ protocol: "http",
1811
1978
  method: operation.method.toUpperCase(),
1812
1979
  path: urlPath.path,
1813
1980
  tags: operation.getTags().map((tag) => tag.name),
@@ -1825,59 +1992,278 @@ function createSchemaParser(ctx) {
1825
1992
  parseParameter
1826
1993
  };
1827
1994
  }
1995
+ //#endregion
1996
+ //#region src/discriminator.ts
1997
+ /**
1998
+ * Builds a map of child schema names → discriminator patch data by scanning the given
1999
+ * top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
2000
+ *
2001
+ * Extracted from `applyDiscriminatorInheritance` so the streaming path can call it on a
2002
+ * small pre-parsed subset of schemas (only the discriminator parents) rather than on all
2003
+ * schemas at once.
2004
+ */
2005
+ function buildDiscriminatorChildMap(schemas) {
2006
+ const childMap = /* @__PURE__ */ new Map();
2007
+ for (const schema of schemas) {
2008
+ let unionNode = ast.narrowSchema(schema, "union");
2009
+ if (!unionNode) {
2010
+ const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
2011
+ if (intersectionMembers) for (const m of intersectionMembers) {
2012
+ const u = ast.narrowSchema(m, "union");
2013
+ if (u) {
2014
+ unionNode = u;
2015
+ break;
2016
+ }
2017
+ }
2018
+ }
2019
+ if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
2020
+ const { discriminatorPropertyName, members } = unionNode;
2021
+ for (const member of members) {
2022
+ const intersectionNode = ast.narrowSchema(member, "intersection");
2023
+ if (!intersectionNode?.members) continue;
2024
+ let refNode = null;
2025
+ let objNode = null;
2026
+ for (const m of intersectionNode.members) {
2027
+ refNode ??= ast.narrowSchema(m, "ref");
2028
+ objNode ??= ast.narrowSchema(m, "object");
2029
+ }
2030
+ if (!refNode?.name || !objNode) continue;
2031
+ const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
2032
+ const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
2033
+ if (!enumNode?.enumValues?.length) continue;
2034
+ const enumValues = enumNode.enumValues.filter((v) => v !== null);
2035
+ if (!enumValues.length) continue;
2036
+ const existing = childMap.get(refNode.name);
2037
+ if (!existing) {
2038
+ childMap.set(refNode.name, {
2039
+ propertyName: discriminatorPropertyName,
2040
+ enumValues: [...enumValues]
2041
+ });
2042
+ continue;
2043
+ }
2044
+ existing.enumValues.push(...enumValues);
2045
+ }
2046
+ }
2047
+ return childMap;
2048
+ }
2049
+ /**
2050
+ * Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
2051
+ * the discriminant property). Used by the streaming path to apply patches inline per yield
2052
+ * without buffering all schemas.
2053
+ */
2054
+ function patchDiscriminatorNode(node, entry) {
2055
+ const objectNode = ast.narrowSchema(node, "object");
2056
+ if (!objectNode) return node;
2057
+ const { propertyName, enumValues } = entry;
2058
+ const enumSchema = ast.createSchema({
2059
+ type: "enum",
2060
+ enumValues
2061
+ });
2062
+ const newProp = ast.createProperty({
2063
+ name: propertyName,
2064
+ required: true,
2065
+ schema: enumSchema
2066
+ });
2067
+ const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
2068
+ const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
2069
+ return {
2070
+ ...objectNode,
2071
+ properties: newProperties
2072
+ };
2073
+ }
2074
+ //#endregion
2075
+ //#region src/stream.ts
2076
+ /**
2077
+ * Builds the deduplication plan from the already-parsed top-level schema nodes plus a
2078
+ * single extra parse pass over operations (so duplicates in request/response bodies are seen).
2079
+ *
2080
+ * Only enums and objects are candidates, and object shapes that reference a circular schema are
2081
+ * rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
2082
+ * name (collision-resolved against existing component names); shapes without a name stay inline.
2083
+ */
2084
+ function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
2085
+ const circularSchemas = new Set(circularNames);
2086
+ const usedNames = new Set(schemaNames);
2087
+ return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
2088
+ isCandidate: (node) => {
2089
+ if (node.type === "enum") return true;
2090
+ if (node.type !== "object") return false;
2091
+ if (node.name && circularSchemas.has(node.name)) return false;
2092
+ return !ast.containsCircularRef(node, { circularSchemas });
2093
+ },
2094
+ nameFor: (node) => {
2095
+ const base = node.name;
2096
+ if (!base) return null;
2097
+ let name = base;
2098
+ let counter = 2;
2099
+ while (usedNames.has(name)) name = `${base}${counter++}`;
2100
+ usedNames.add(name);
2101
+ return name;
2102
+ },
2103
+ refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
2104
+ });
2105
+ }
1828
2106
  /**
1829
- * Parses an OpenAPI specification into Kubb's universal `InputNode` AST.
2107
+ * Reads the server URL from the document's `servers` array at `serverIndex`,
2108
+ * interpolating any `serverVariables` into the URL template.
1830
2109
  *
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.
2110
+ * Returns `null` when `serverIndex` is omitted or out of range.
1834
2111
  *
1835
- * Returns the AST root and a `nameMapping` for resolving schema references.
2112
+ * @example Resolve the first server
2113
+ * `resolveBaseUrl({ document, serverIndex: 0 })`
2114
+ *
2115
+ * @example Override a path variable
2116
+ * `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
2117
+ */
2118
+ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
2119
+ const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
2120
+ return server?.url ? resolveServerUrl(server, serverVariables) : null;
2121
+ }
2122
+ /**
2123
+ * Parses every schema once to build the lookup structures that streaming needs upfront.
2124
+ *
2125
+ * Three things happen in this single pass:
2126
+ * - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
2127
+ * - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
2128
+ * - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
2129
+ * The `allNodes` array is local and drops out of scope as soon as this function returns.
2130
+ *
2131
+ * After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
2132
+ * Both are proportional to the number of aliases or discriminator parents, not total schema count.
2133
+ *
2134
+ * Each schema is parsed again during the streaming pass — this is intentional.
2135
+ * Holding the parsed nodes in memory here would defeat the streaming memory benefit.
1836
2136
  *
1837
2137
  * @example
1838
2138
  * ```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' })
2139
+ * const { refAliasMap, enumNames, circularNames } = preScan({
2140
+ * schemas,
2141
+ * parseSchema,
2142
+ * parserOptions,
2143
+ * discriminator: 'strict',
2144
+ * })
1843
2145
  * ```
1844
2146
  */
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));
2147
+ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
2148
+ const allNodes = [];
2149
+ const refAliasMap = /* @__PURE__ */ new Map();
2150
+ const enumNames = [];
2151
+ const discriminatorParentNodes = [];
2152
+ for (const [name, schema] of Object.entries(schemas)) {
2153
+ const node = parseSchema({
2154
+ schema,
2155
+ name
2156
+ }, parserOptions);
2157
+ allNodes.push(node);
2158
+ if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
2159
+ if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
2160
+ if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
2161
+ }
2162
+ const circularNames = [...ast.findCircularSchemas(allNodes)];
2163
+ const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
2164
+ let dedupePlan = null;
2165
+ if (dedupe) {
2166
+ const operationNodes = [];
2167
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2168
+ if (!operation) continue;
2169
+ const operationNode = parseOperation(parserOptions, operation);
2170
+ if (operationNode) operationNodes.push(operationNode);
2171
+ }
2172
+ dedupePlan = createDedupePlan({
2173
+ schemaNodes: allNodes,
2174
+ operationNodes,
2175
+ schemaNames: Object.keys(schemas),
2176
+ circularNames
2177
+ });
2178
+ for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
2179
+ }
1862
2180
  return {
1863
- root: ast.createInput({
1864
- schemas,
1865
- operations
1866
- }),
1867
- nameMapping
2181
+ refAliasMap,
2182
+ enumNames,
2183
+ circularNames,
2184
+ discriminatorChildMap,
2185
+ dedupePlan
2186
+ };
2187
+ }
2188
+ /**
2189
+ * Creates a lazy `InputStreamNode` from already-resolved adapter state.
2190
+ *
2191
+ * The schema and operation iterables each start a fresh parse pass on every
2192
+ * `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
2193
+ * stream object independently without sharing a cursor or holding all nodes in memory.
2194
+ *
2195
+ * Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
2196
+ * with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
2197
+ *
2198
+ * @example
2199
+ * ```ts
2200
+ * const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
2201
+ * for await (const schema of streamNode.schemas) {
2202
+ * // each call to for-await restarts from the first schema
2203
+ * }
2204
+ * ```
2205
+ */
2206
+ function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
2207
+ const rewriteTopLevelSchema = (node) => {
2208
+ if (!dedupePlan) return node;
2209
+ const canonical = dedupePlan.canonicalBySignature.get(ast.schemaSignature(node));
2210
+ if (canonical && canonical.name !== node.name) return ast.createSchema({
2211
+ type: "ref",
2212
+ name: node.name ?? null,
2213
+ ref: canonical.ref,
2214
+ description: node.description,
2215
+ deprecated: node.deprecated
2216
+ });
2217
+ return ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
1868
2218
  };
2219
+ const schemasIterable = { [Symbol.asyncIterator]() {
2220
+ return (async function* () {
2221
+ if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
2222
+ for (const [name, schema] of Object.entries(schemas)) {
2223
+ const alias = refAliasMap.get(name);
2224
+ if (alias?.name && schemas[alias.name]) {
2225
+ yield rewriteTopLevelSchema({
2226
+ ...parseSchema({
2227
+ schema: schemas[alias.name],
2228
+ name: alias.name
2229
+ }, parserOptions),
2230
+ name
2231
+ });
2232
+ continue;
2233
+ }
2234
+ const parsed = parseSchema({
2235
+ schema,
2236
+ name
2237
+ }, parserOptions);
2238
+ yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
2239
+ }
2240
+ })();
2241
+ } };
2242
+ const operationsIterable = { [Symbol.asyncIterator]() {
2243
+ return (async function* () {
2244
+ for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
2245
+ if (!operation) continue;
2246
+ const node = parseOperation(parserOptions, operation);
2247
+ if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
2248
+ }
2249
+ })();
2250
+ } };
2251
+ return ast.createStreamInput(schemasIterable, operationsIterable, meta);
1869
2252
  }
1870
2253
  //#endregion
1871
2254
  //#region src/adapter.ts
1872
2255
  /**
1873
- * Stable string identifier for the OAS adapter used in Kubb's adapter registry.
2256
+ * Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
1874
2257
  */
1875
2258
  const adapterOasName = "oas";
1876
2259
  /**
1877
- * Creates the default OpenAPI / Swagger adapter for Kubb.
2260
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
2261
+ * file at `input.path`, validates it, resolves the base URL, and converts every
2262
+ * schema and operation into the universal AST that every downstream plugin
2263
+ * consumes.
1878
2264
  *
1879
- * Parses the spec, optionally validates it, resolves the base URL, and converts
1880
- * everything into an `InputNode` that downstream plugins consume.
2265
+ * Configure once on `defineConfig`. The adapter's choices (date representation,
2266
+ * integer width, server URL) apply to every plugin in the build.
1881
2267
  *
1882
2268
  * @example
1883
2269
  * ```ts
@@ -1886,17 +2272,83 @@ const adapterOasName = "oas";
1886
2272
  * import { pluginTs } from '@kubb/plugin-ts'
1887
2273
  *
1888
2274
  * export default defineConfig({
1889
- * adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
1890
- * input: { path: './openapi.yaml' },
2275
+ * input: { path: './petStore.yaml' },
2276
+ * output: { path: './src/gen' },
2277
+ * adapter: adapterOas({
2278
+ * serverIndex: 0,
2279
+ * discriminator: 'inherit',
2280
+ * dateType: 'date',
2281
+ * }),
1891
2282
  * plugins: [pluginTs()],
1892
2283
  * })
1893
2284
  * ```
1894
2285
  */
1895
2286
  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;
2287
+ 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;
2288
+ const parserOptions = {
2289
+ ...DEFAULT_PARSER_OPTIONS,
2290
+ dateType,
2291
+ integerType,
2292
+ unknownType,
2293
+ emptySchemaType,
2294
+ enumSuffix
2295
+ };
1897
2296
  let nameMapping = /* @__PURE__ */ new Map();
1898
- let parsedDocument;
1899
- let inputNode;
2297
+ let parsedDocument = null;
2298
+ const ensureDocument = once(async (source) => {
2299
+ const fresh = await parseFromConfig(source);
2300
+ if (validate) await validateDocument(fresh);
2301
+ parsedDocument = fresh;
2302
+ return fresh;
2303
+ });
2304
+ const ensureSchemas = once(async (document) => {
2305
+ const result = getSchemas(document, { contentType });
2306
+ nameMapping = result.nameMapping;
2307
+ return result.schemas;
2308
+ });
2309
+ const ensureBaseOas = once((document) => new BaseOas(document));
2310
+ const ensureSchemaParser = once((document) => createSchemaParser({
2311
+ document,
2312
+ contentType
2313
+ }));
2314
+ const ensurePreScan = once((schemas, parseSchema, parseOperation, baseOas) => preScan({
2315
+ schemas,
2316
+ parseSchema,
2317
+ parseOperation,
2318
+ baseOas,
2319
+ parserOptions,
2320
+ discriminator,
2321
+ dedupe
2322
+ }));
2323
+ async function createStream(source) {
2324
+ const document = await ensureDocument(source);
2325
+ const schemas = await ensureSchemas(document);
2326
+ const { parseSchema, parseOperation } = ensureSchemaParser(document);
2327
+ const baseOas = ensureBaseOas(document);
2328
+ const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(schemas, parseSchema, parseOperation, baseOas);
2329
+ return createInputStream({
2330
+ schemas,
2331
+ parseSchema,
2332
+ parseOperation,
2333
+ baseOas,
2334
+ parserOptions,
2335
+ refAliasMap,
2336
+ discriminatorChildMap,
2337
+ dedupePlan,
2338
+ meta: {
2339
+ title: document.info?.title,
2340
+ description: document.info?.description,
2341
+ version: document.info?.version,
2342
+ baseURL: resolveBaseUrl({
2343
+ document,
2344
+ serverIndex,
2345
+ serverVariables
2346
+ }),
2347
+ circularNames,
2348
+ enumNames
2349
+ }
2350
+ });
2351
+ }
1900
2352
  return {
1901
2353
  name: "oas",
1902
2354
  get options() {
@@ -1906,6 +2358,7 @@ const adapterOas = createAdapter((options) => {
1906
2358
  serverIndex,
1907
2359
  serverVariables,
1908
2360
  discriminator,
2361
+ dedupe,
1909
2362
  dateType,
1910
2363
  integerType,
1911
2364
  unknownType,
@@ -1917,8 +2370,8 @@ const adapterOas = createAdapter((options) => {
1917
2370
  get document() {
1918
2371
  return parsedDocument;
1919
2372
  },
1920
- get inputNode() {
1921
- return inputNode;
2373
+ async validate(input, options) {
2374
+ await validateDocument(await parseDocument(input), options);
1922
2375
  },
1923
2376
  getImports(node, resolve) {
1924
2377
  return ast.collectImports({
@@ -1926,7 +2379,7 @@ const adapterOas = createAdapter((options) => {
1926
2379
  nameMapping,
1927
2380
  resolve: (schemaName) => {
1928
2381
  const result = resolve(schemaName);
1929
- if (!result) return;
2382
+ if (!result) return null;
1930
2383
  return ast.createImport({
1931
2384
  name: [result.name],
1932
2385
  path: result.path
@@ -1935,32 +2388,20 @@ const adapterOas = createAdapter((options) => {
1935
2388
  });
1936
2389
  },
1937
2390
  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
- }
2391
+ const streamNode = await createStream(source);
2392
+ const collect = async (iter) => {
2393
+ const out = [];
2394
+ for await (const item of iter) out.push(item);
2395
+ return out;
2396
+ };
2397
+ const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
2398
+ return ast.createInput({
2399
+ schemas,
2400
+ operations,
2401
+ meta: streamNode.meta
1961
2402
  });
1962
- return inputNode;
1963
- }
2403
+ },
2404
+ stream: createStream
1964
2405
  };
1965
2406
  });
1966
2407
  //#endregion
@@ -1976,6 +2417,6 @@ const adapterOas = createAdapter((options) => {
1976
2417
  */
1977
2418
  const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
1978
2419
  //#endregion
1979
- export { HttpMethods, adapterOas, adapterOasName, mergeDocuments, parseDocument, parseFromConfig, validateDocument };
2420
+ export { HttpMethods, adapterOas, adapterOasName, mergeDocuments };
1980
2421
 
1981
2422
  //# sourceMappingURL=index.js.map