@kubb/adapter-oas 5.0.0-beta.91 → 5.0.0-beta.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -28,18 +28,11 @@ node_path = __toESM(node_path, 1);
28
28
  let node_fs_promises = require("node:fs/promises");
29
29
  let _readme_openapi_parser = require("@readme/openapi-parser");
30
30
  let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
31
- let yaml = require("yaml");
32
31
  let api_ref_bundler = require("api-ref-bundler");
32
+ let yaml = require("yaml");
33
33
  //#region src/constants.ts
34
34
  /**
35
35
  * Default parser options applied when no explicit options are provided.
36
- *
37
- * @example
38
- * ```ts
39
- * import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
40
- *
41
- * const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
42
- * ```
43
36
  */
44
37
  const DEFAULT_PARSER_OPTIONS = {
45
38
  dateType: "string",
@@ -78,14 +71,6 @@ const SUPPORTED_METHODS = /* @__PURE__ */ new Set([
78
71
  *
79
72
  * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
80
73
  * intersection member rather than being merged into the parent.
81
- *
82
- * @example
83
- * ```ts
84
- * import { structuralKeys } from '@kubb/adapter-oas'
85
- *
86
- * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
87
- * // true when fragment has e.g. 'properties' or 'oneOf'
88
- * ```
89
74
  */
90
75
  const structuralKeys = /* @__PURE__ */ new Set([
91
76
  "properties",
@@ -115,15 +100,6 @@ const specialCasedFormats = /* @__PURE__ */ new Set([
115
100
  * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
116
101
  * separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
117
102
  * and `idn-hostname` map to `'url'` as the closest generic string-format type.
118
- *
119
- * @example
120
- * ```ts
121
- * import { formatMap } from '@kubb/adapter-oas'
122
- *
123
- * formatMap['uuid'] // 'uuid'
124
- * formatMap['binary'] // 'blob'
125
- * formatMap['float'] // 'number'
126
- * ```
127
103
  */
128
104
  const formatMap = {
129
105
  uuid: "uuid",
@@ -144,24 +120,10 @@ const formatMap = {
144
120
  };
145
121
  /**
146
122
  * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
147
- *
148
- * @example
149
- * ```ts
150
- * import { enumExtensionKeys } from '@kubb/adapter-oas'
151
- *
152
- * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
153
- * ```
154
123
  */
155
124
  const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
156
125
  /**
157
126
  * Vendor extension keys that attach human-readable descriptions to enum values, checked in priority order.
158
- *
159
- * @example
160
- * ```ts
161
- * import { enumDescriptionKeys } from '@kubb/adapter-oas'
162
- *
163
- * const key = enumDescriptionKeys.find((k) => k in schema) // 'x-enumDescriptions' | 'x-enum-descriptions' | undefined
164
- * ```
165
127
  */
166
128
  const enumDescriptionKeys = ["x-enumDescriptions", "x-enum-descriptions"];
167
129
  //#endregion
@@ -405,7 +367,7 @@ async function read(path) {
405
367
  return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
406
368
  }
407
369
  //#endregion
408
- //#region src/bundler.ts
370
+ //#region src/factory.ts
409
371
  const urlRegExp = /^https?:\/+/i;
410
372
  async function readSource(sourcePath) {
411
373
  if (urlRegExp.test(sourcePath)) {
@@ -448,8 +410,6 @@ async function bundleDocument(pathOrUrl) {
448
410
  await resolver(pathOrUrl);
449
411
  return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
450
412
  }
451
- //#endregion
452
- //#region src/factory.ts
453
413
  /**
454
414
  * Loads and bundles an OpenAPI document, returning the raw `Document`.
455
415
  *
@@ -519,19 +479,12 @@ async function validateDocument(document, { throwOnError = false } = {}) {
519
479
  }
520
480
  }
521
481
  //#endregion
522
- //#region src/guards.ts
482
+ //#region src/oas.ts
523
483
  /**
524
484
  * Returns `true` when a schema should be treated as nullable.
525
485
  *
526
486
  * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
527
487
  * `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
528
- *
529
- * @example
530
- * ```ts
531
- * isNullable({ type: 'string', nullable: true }) // true
532
- * isNullable({ type: ['string', 'null'] }) // true
533
- * isNullable({ type: 'string' }) // false
534
- * ```
535
488
  */
536
489
  function isNullable(schema) {
537
490
  if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
@@ -542,31 +495,24 @@ function isNullable(schema) {
542
495
  }
543
496
  /**
544
497
  * Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
545
- *
546
- * @example
547
- * ```ts
548
- * isReference({ $ref: '#/components/schemas/Pet' }) // true
549
- * isReference({ type: 'string' }) // false
550
- * ```
551
498
  */
552
499
  function isReference(obj) {
553
500
  return !!obj && typeof obj === "object" && "$ref" in obj;
554
501
  }
555
502
  /**
556
- * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
557
- *
558
- * @example
559
- * ```ts
560
- * isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
561
- * isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
562
- * ```
503
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
504
+ * excluding the Swagger 2 string form.
563
505
  */
564
506
  function isDiscriminator(obj) {
565
507
  const record = obj;
566
508
  return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
567
509
  }
568
- //#endregion
569
- //#region src/mime.ts
510
+ /**
511
+ * Returns `true` when a schema is a binary payload: an octet-stream string body.
512
+ */
513
+ function isBinary(schema) {
514
+ return schema.type === "string" && schema.contentMediaType === "application/octet-stream";
515
+ }
570
516
  /**
571
517
  * MIME type fragments that mark a media type as JSON-like.
572
518
  *
@@ -659,6 +605,23 @@ function dereferenceWithRef(document, schema) {
659
605
  };
660
606
  return schema;
661
607
  }
608
+ /**
609
+ * Resolves a `$ref` slot in place: when `container[key]` holds a `$ref`, replaces it with the
610
+ * resolved value and returns that value. Returns `null` when the slot is empty, cannot be resolved,
611
+ * or is still a `$ref` after resolving. A non-`$ref` value is returned untouched, without writing.
612
+ *
613
+ * @example
614
+ * ```ts
615
+ * derefInPlace<ResponseObject>({ document, container: operation.schema.responses, key: '200' })
616
+ * ```
617
+ */
618
+ function derefInPlace({ document, container, key }) {
619
+ const value = container[key];
620
+ if (!isReference(value)) return value ? value : null;
621
+ const resolved = resolveRef(document, value.$ref);
622
+ container[key] = resolved;
623
+ return resolved && !isReference(resolved) ? resolved : null;
624
+ }
662
625
  //#endregion
663
626
  //#region src/operation.ts
664
627
  /**
@@ -690,31 +653,22 @@ function getResponseStatusCodes({ schema }) {
690
653
  function getResponseByStatusCode({ document, operation, statusCode }) {
691
654
  const responses = operation.schema.responses;
692
655
  if (!responses || isReference(responses)) return false;
693
- const response = responses[statusCode];
694
- if (!response) return false;
695
- if (isReference(response)) {
696
- const resolved = resolveRef(document, response.$ref);
697
- responses[statusCode] = resolved;
698
- if (!resolved || isReference(resolved)) return false;
699
- return resolved;
700
- }
701
- return response;
656
+ return derefInPlace({
657
+ document,
658
+ container: responses,
659
+ key: statusCode
660
+ }) ?? false;
702
661
  }
703
662
  /**
704
663
  * Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
705
664
  * `undefined` when the operation has no request body.
706
665
  */
707
666
  function getRequestBodyContent({ document, operation }) {
708
- const { schema } = operation;
709
- let requestBody = schema.requestBody;
710
- if (!requestBody) return;
711
- if (isReference(requestBody)) {
712
- const resolved = resolveRef(document, requestBody.$ref);
713
- schema.requestBody = resolved;
714
- if (!resolved || isReference(resolved)) return;
715
- requestBody = resolved;
716
- }
717
- return requestBody.content;
667
+ return derefInPlace({
668
+ document,
669
+ container: operation.schema,
670
+ key: "requestBody"
671
+ })?.content;
718
672
  }
719
673
  /**
720
674
  * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
@@ -763,14 +717,12 @@ function getOperations(document) {
763
717
  if (!paths) return operations;
764
718
  for (const path of Object.keys(paths)) {
765
719
  if (path.startsWith("x-")) continue;
766
- let pathItem = paths[path];
720
+ const pathItem = derefInPlace({
721
+ document,
722
+ container: paths,
723
+ key: path
724
+ });
767
725
  if (!pathItem) continue;
768
- if (isReference(pathItem)) {
769
- const resolved = resolveRef(document, pathItem.$ref);
770
- paths[path] = resolved;
771
- if (!resolved || isReference(resolved)) continue;
772
- pathItem = resolved;
773
- }
774
726
  const item = pathItem;
775
727
  for (const method of Object.keys(item)) {
776
728
  if (!SUPPORTED_METHODS.has(method)) continue;
@@ -786,37 +738,6 @@ function getOperations(document) {
786
738
  return operations;
787
739
  }
788
740
  //#endregion
789
- //#region src/dialect.ts
790
- /**
791
- * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
792
- *
793
- * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
794
- * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
795
- * ref resolution) so the converter pipeline and dispatch rules stay shared. A
796
- * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
797
- * nullability, no discriminator object, binary via `contentEncoding` and reuses
798
- * the rest unchanged.
799
- *
800
- * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
801
- * JSON Schema vocabulary, so the converters keep that common case.
802
- *
803
- * @example
804
- * ```ts
805
- * const parser = createSchemaParser(context) // uses oasDialect
806
- * const parser = createSchemaParser(context, oasDialect) // explicit
807
- * ```
808
- */
809
- const oasDialect = _kubb_ast.ast.defineDialect({
810
- name: "oas",
811
- schema: {
812
- isNullable,
813
- isReference,
814
- isDiscriminator,
815
- isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
816
- resolveRef
817
- }
818
- });
819
- //#endregion
820
741
  //#region src/resolvers.ts
821
742
  /**
822
743
  * Reads the server URL from the document's `servers` array at `server.index`,
@@ -924,19 +845,19 @@ function getResponseBody(responseBody, contentType) {
924
845
  if (!(contentType in body.content)) return false;
925
846
  return body.content[contentType];
926
847
  }
927
- let availableContentType;
928
848
  const contentTypes = Object.keys(body.content);
929
- for (const mt of contentTypes) if (isJsonMimeType(mt)) {
930
- availableContentType = mt;
931
- break;
932
- }
933
- if (!availableContentType) availableContentType = contentTypes[0];
934
- if (availableContentType) return [
935
- availableContentType,
936
- body.content[availableContentType],
937
- ...body.description ? [body.description] : []
938
- ];
939
- return false;
849
+ const availableContentType = contentTypes.find(isJsonMimeType) ?? contentTypes[0];
850
+ if (!availableContentType) return false;
851
+ return body.content[availableContentType];
852
+ }
853
+ function resolveResponseRefs(document, operation) {
854
+ const responses = operation.schema.responses;
855
+ if (!responses) return;
856
+ for (const key in responses) derefInPlace({
857
+ document,
858
+ container: responses,
859
+ key
860
+ });
940
861
  }
941
862
  /**
942
863
  * Returns the response schema for a given operation and HTTP status code.
@@ -949,14 +870,6 @@ function getResponseBody(responseBody, contentType) {
949
870
  * getResponseSchema(document, operation, '4XX') // {}
950
871
  * ```
951
872
  */
952
- function resolveResponseRefs(document, operation) {
953
- const responses = operation.schema.responses;
954
- if (!responses) return;
955
- for (const key in responses) {
956
- const schema = responses[key];
957
- if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
958
- }
959
- }
960
873
  function getResponseSchema(document, operation, statusCode, options = {}) {
961
874
  resolveResponseRefs(document, operation);
962
875
  const responseBody = getResponseBody(getResponseByStatusCode({
@@ -965,7 +878,7 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
965
878
  statusCode
966
879
  }), options.contentType);
967
880
  if (responseBody === false) return {};
968
- const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
881
+ const schema = responseBody.schema;
969
882
  if (!schema) return {};
970
883
  return dereferenceWithRef(document, schema);
971
884
  }
@@ -995,6 +908,16 @@ function getRequestSchema(document, operation, options = {}) {
995
908
  return dereferenceWithRef(document, schema);
996
909
  }
997
910
  /**
911
+ * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
912
+ * structurally significant on its own (see `structuralKeys`).
913
+ *
914
+ * A fragment with a structural keyword can't be safely merged into a parent schema.
915
+ */
916
+ function hasStructuralKeywords(fragment) {
917
+ for (const key in fragment) if (structuralKeys.has(key)) return true;
918
+ return false;
919
+ }
920
+ /**
998
921
  * Flattens a keyword-only `allOf` into its parent schema.
999
922
  *
1000
923
  * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
@@ -1013,16 +936,6 @@ function getRequestSchema(document, operation, options = {}) {
1013
936
  * // returned unchanged, contains a $ref
1014
937
  * ```
1015
938
  */
1016
- /**
1017
- * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
1018
- * structurally significant on its own (see `structuralKeys`).
1019
- *
1020
- * A fragment with a structural keyword can't be safely merged into a parent schema.
1021
- */
1022
- function hasStructuralKeywords(fragment) {
1023
- for (const key in fragment) if (structuralKeys.has(key)) return true;
1024
- return false;
1025
- }
1026
939
  function flattenSchema(schema) {
1027
940
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
1028
941
  const allOfFragments = schema.allOf;
@@ -1274,7 +1187,7 @@ function getResponseBodyContentTypes(document, operation, statusCode) {
1274
1187
  return body.content ? Object.keys(body.content) : [];
1275
1188
  }
1276
1189
  //#endregion
1277
- //#region src/parser.ts
1190
+ //#region src/converters.ts
1278
1191
  /**
1279
1192
  * Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
1280
1193
  *
@@ -1298,7 +1211,7 @@ function normalizeArrayEnum(schema) {
1298
1211
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1299
1212
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
1300
1213
  */
1301
- function createNullSchema(schema, name, nullable) {
1214
+ function createNullNode(schema, name, nullable) {
1302
1215
  return _kubb_ast.ast.factory.createSchema({
1303
1216
  type: "null",
1304
1217
  primitive: "null",
@@ -1328,653 +1241,636 @@ function nameEnums(node, options) {
1328
1241
  return named;
1329
1242
  }
1330
1243
  /**
1331
- * Factory function that creates schema and operation converters for a given OpenAPI context.
1332
- *
1333
- * Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
1334
- * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1335
- * which works because function declarations hoist.
1244
+ * Converts a `$ref` schema into a `RefSchemaNode`.
1336
1245
  *
1337
- * @internal
1246
+ * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1247
+ * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1248
+ * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1249
+ * Circular refs are detected in `resolveRefNode` and leave `schema` as `null`.
1338
1250
  */
1339
- function createSchemaParser(ctx, dialect = oasDialect) {
1340
- const document = ctx.document;
1341
- /**
1342
- * Tracks `$ref` paths that are currently being resolved to prevent infinite
1343
- * recursion when schemas contain circular references (e.g. `Pet parent → Pet`).
1344
- */
1345
- const resolvingRefs = /* @__PURE__ */ new Set();
1346
- /**
1347
- * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1348
- *
1349
- * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1350
- * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1351
- * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1352
- * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1353
- */
1354
- const resolvedRefCache = /* @__PURE__ */ new Map();
1355
- /**
1356
- * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1357
- * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1358
- * goes `false` for a `$ref` that points at a component the spec never declares.
1359
- */
1360
- const refExistence = /* @__PURE__ */ new Map();
1361
- function refExists(refPath) {
1362
- if (!refExistence.has(refPath)) {
1363
- let exists = false;
1364
- try {
1365
- exists = !!dialect.schema.resolveRef(document, refPath);
1366
- } catch {
1367
- exists = false;
1368
- }
1369
- refExistence.set(refPath, exists);
1370
- }
1371
- return refExistence.get(refPath) ?? false;
1372
- }
1373
- /**
1374
- * Converts a `$ref` schema into a `RefSchemaNode`.
1375
- *
1376
- * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1377
- * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1378
- * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1379
- * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1380
- */
1381
- function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1382
- let resolvedSchema = null;
1383
- const refPath = schema.$ref;
1384
- if (refPath && !resolvingRefs.has(refPath)) {
1385
- if (!resolvedRefCache.has(refPath)) {
1386
- try {
1387
- const referenced = dialect.schema.resolveRef(document, refPath);
1388
- if (referenced) {
1389
- resolvingRefs.add(refPath);
1390
- resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1391
- resolvingRefs.delete(refPath);
1392
- }
1393
- } catch {}
1394
- resolvedRefCache.set(refPath, resolvedSchema);
1395
- }
1396
- resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1397
- }
1398
- if (refPath && document.components && !refExists(refPath)) return _kubb_ast.ast.factory.createSchema({
1399
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1400
- type: "unknown"
1401
- });
1251
+ function convertRef({ schema, name, nullable, defaultValue, rawOptions, document, resolveRefNode, refExists }) {
1252
+ const refPath = schema.$ref;
1253
+ const resolvedSchema = refPath ? resolveRefNode(refPath, rawOptions) : null;
1254
+ if (refPath && document.components && !refExists(refPath)) return _kubb_ast.ast.factory.createSchema({
1255
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1256
+ type: "unknown"
1257
+ });
1258
+ return _kubb_ast.ast.factory.createSchema({
1259
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1260
+ type: "ref",
1261
+ name: (0, _kubb_ast.extractRefName)(schema.$ref),
1262
+ ref: schema.$ref,
1263
+ schema: resolvedSchema
1264
+ });
1265
+ }
1266
+ /**
1267
+ * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1268
+ */
1269
+ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
1270
+ if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1271
+ const [memberSchema] = schema.allOf;
1272
+ const memberNode = parse({
1273
+ schema: memberSchema,
1274
+ name
1275
+ }, rawOptions);
1276
+ const { kind: _kind, ...memberNodeProps } = memberNode;
1277
+ const mergedNullable = nullable || memberNode.nullable || void 0;
1278
+ const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1402
1279
  return _kubb_ast.ast.factory.createSchema({
1403
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1404
- type: "ref",
1405
- name: (0, _kubb_ast.extractRefName)(schema.$ref),
1406
- ref: schema.$ref,
1407
- schema: resolvedSchema
1280
+ ...memberNodeProps,
1281
+ name,
1282
+ title: schema.title ?? memberNode.title,
1283
+ description: schema.description ?? memberNode.description,
1284
+ deprecated: schema.deprecated ?? memberNode.deprecated,
1285
+ nullable: mergedNullable,
1286
+ readOnly: schema.readOnly ?? memberNode.readOnly,
1287
+ writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1288
+ default: mergedDefault,
1289
+ examples: extractExamples(schema) ?? memberNode.examples,
1290
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1291
+ format: schema.format ?? memberNode.format
1408
1292
  });
1409
1293
  }
1410
- /**
1411
- * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1412
- */
1413
- function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }) {
1414
- if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1415
- const [memberSchema] = schema.allOf;
1416
- const memberNode = parseSchema({
1417
- schema: memberSchema,
1418
- name
1419
- }, rawOptions);
1420
- const { kind: _kind, ...memberNodeProps } = memberNode;
1421
- const mergedNullable = nullable || memberNode.nullable || void 0;
1422
- const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1423
- return _kubb_ast.ast.factory.createSchema({
1424
- ...memberNodeProps,
1425
- name,
1426
- title: schema.title ?? memberNode.title,
1427
- description: schema.description ?? memberNode.description,
1428
- deprecated: schema.deprecated ?? memberNode.deprecated,
1429
- nullable: mergedNullable,
1430
- readOnly: schema.readOnly ?? memberNode.readOnly,
1431
- writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1432
- default: mergedDefault,
1433
- examples: extractExamples(schema) ?? memberNode.examples,
1434
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1435
- format: schema.format ?? memberNode.format
1294
+ const filteredDiscriminantValues = [];
1295
+ const allOfMembers = schema.allOf.filter((item) => {
1296
+ if (!isReference(item) || !name) return true;
1297
+ const deref = resolveRef(document, item.$ref);
1298
+ if (!deref || !isDiscriminator(deref)) return true;
1299
+ const parentUnion = deref.oneOf ?? deref.anyOf;
1300
+ if (!parentUnion) return true;
1301
+ const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1302
+ const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1303
+ const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1304
+ if (inOneOf || inMapping) {
1305
+ const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1306
+ if (discriminatorValue) filteredDiscriminantValues.push({
1307
+ propertyName: deref.discriminator.propertyName,
1308
+ value: discriminatorValue
1436
1309
  });
1310
+ return false;
1437
1311
  }
1438
- const filteredDiscriminantValues = [];
1439
- const allOfMembers = schema.allOf.filter((item) => {
1440
- if (!dialect.schema.isReference(item) || !name) return true;
1441
- const deref = dialect.schema.resolveRef(document, item.$ref);
1442
- if (!deref || !dialect.schema.isDiscriminator(deref)) return true;
1443
- const parentUnion = deref.oneOf ?? deref.anyOf;
1444
- if (!parentUnion) return true;
1445
- const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1446
- const inOneOf = parentUnion.some((oneOfItem) => dialect.schema.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1447
- const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1448
- if (inOneOf || inMapping) {
1449
- const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1450
- if (discriminatorValue) filteredDiscriminantValues.push({
1451
- propertyName: deref.discriminator.propertyName,
1452
- value: discriminatorValue
1453
- });
1454
- return false;
1455
- }
1456
- return true;
1457
- }).map((s) => parseSchema({
1458
- schema: s,
1459
- name
1460
- }, rawOptions));
1461
- const syntheticStart = allOfMembers.length;
1462
- if (Array.isArray(schema.required) && schema.required.length) {
1463
- const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1464
- const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1465
- if (missingRequired.length) {
1466
- const resolvedMembers = schema.allOf.flatMap((item) => {
1467
- if (!dialect.schema.isReference(item)) return [item];
1468
- const deref = dialect.schema.resolveRef(document, item.$ref);
1469
- return deref && !dialect.schema.isReference(deref) ? [deref] : [];
1470
- });
1471
- for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1472
- allOfMembers.push(parseSchema({
1473
- schema: {
1474
- properties: { [key]: resolved.properties[key] },
1475
- required: [key]
1476
- },
1312
+ return true;
1313
+ }).map((s) => parse({
1314
+ schema: s,
1315
+ name
1316
+ }, rawOptions));
1317
+ const syntheticStart = allOfMembers.length;
1318
+ if (Array.isArray(schema.required) && schema.required.length) {
1319
+ const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1320
+ const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1321
+ if (missingRequired.length) {
1322
+ const resolvedMembers = schema.allOf.flatMap((item) => {
1323
+ if (!isReference(item)) return [item];
1324
+ const deref = resolveRef(document, item.$ref);
1325
+ return deref && !isReference(deref) ? [deref] : [];
1326
+ });
1327
+ for (const key of missingRequired) for (const resolved of resolvedMembers) {
1328
+ const prop = resolved.properties?.[key];
1329
+ if (prop) {
1330
+ const memberSchema = {
1331
+ properties: { [key]: prop },
1332
+ required: [key]
1333
+ };
1334
+ allOfMembers.push(parse({
1335
+ schema: memberSchema,
1477
1336
  name
1478
1337
  }, rawOptions));
1479
1338
  break;
1480
1339
  }
1481
1340
  }
1482
1341
  }
1483
- if (schema.properties) {
1484
- const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1485
- allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1486
- }
1487
- for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1488
- propertyName,
1489
- value
1490
- }));
1342
+ }
1343
+ if (schema.properties) {
1344
+ const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1345
+ allOfMembers.push(parse({ schema: schemaWithoutAllOf }, rawOptions));
1346
+ }
1347
+ for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1348
+ propertyName,
1349
+ value
1350
+ }));
1351
+ return _kubb_ast.ast.factory.createSchema({
1352
+ type: "intersection",
1353
+ members: [...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(syntheticStart))],
1354
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1355
+ });
1356
+ }
1357
+ /**
1358
+ * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1359
+ */
1360
+ function convertUnion({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
1361
+ function pickDiscriminatorPropertyNode(node, propertyName) {
1362
+ const discriminatorProperty = _kubb_ast.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1363
+ if (!discriminatorProperty) return null;
1491
1364
  return _kubb_ast.ast.factory.createSchema({
1492
- type: "intersection",
1493
- members: [...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast.mergeAdjacentObjectsLazy)(allOfMembers.slice(syntheticStart))],
1494
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1365
+ type: "object",
1366
+ primitive: "object",
1367
+ properties: [discriminatorProperty]
1495
1368
  });
1496
1369
  }
1497
- /**
1498
- * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1499
- */
1500
- function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
1501
- function pickDiscriminatorPropertyNode(node, propertyName) {
1502
- const discriminatorProperty = _kubb_ast.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1503
- if (!discriminatorProperty) return null;
1504
- return _kubb_ast.ast.factory.createSchema({
1505
- type: "object",
1506
- primitive: "object",
1507
- properties: [discriminatorProperty]
1370
+ function resolveRefSilent($ref) {
1371
+ if (!$ref.startsWith("#")) return null;
1372
+ return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1373
+ }
1374
+ function implicitDiscriminantValue(member) {
1375
+ if (!discriminator || discriminator.mapping || !isReference(member)) return null;
1376
+ const value = (0, _kubb_ast.extractRefName)(member.$ref);
1377
+ if (!value) return null;
1378
+ const variant = resolveRefSilent(member.$ref);
1379
+ if (!variant) return null;
1380
+ const propertyName = discriminator.propertyName;
1381
+ const seen = /* @__PURE__ */ new Set([member.$ref]);
1382
+ function constrains(v) {
1383
+ const prop = v.properties?.[propertyName];
1384
+ const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1385
+ if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1386
+ const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1387
+ if (!composition) return false;
1388
+ return composition.some((m) => {
1389
+ if (!isReference(m)) return constrains(m);
1390
+ if (seen.has(m.$ref)) return false;
1391
+ seen.add(m.$ref);
1392
+ const r = resolveRefSilent(m.$ref);
1393
+ return r ? constrains(r) : false;
1508
1394
  });
1509
1395
  }
1510
- function resolveRefSilent($ref) {
1511
- if (!$ref.startsWith("#")) return null;
1512
- return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1513
- }
1514
- function implicitDiscriminantValue(member) {
1515
- if (!discriminator || discriminator.mapping || !dialect.schema.isReference(member)) return null;
1516
- const value = (0, _kubb_ast.extractRefName)(member.$ref);
1517
- if (!value) return null;
1518
- const variant = resolveRefSilent(member.$ref);
1519
- if (!variant) return null;
1520
- const propertyName = discriminator.propertyName;
1521
- const seen = /* @__PURE__ */ new Set([member.$ref]);
1522
- function constrains(v) {
1523
- const prop = v.properties?.[propertyName];
1524
- const resolved = prop && dialect.schema.isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1525
- if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1526
- const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1527
- if (!composition) return false;
1528
- return composition.some((m) => {
1529
- if (!dialect.schema.isReference(m)) return constrains(m);
1530
- if (seen.has(m.$ref)) return false;
1531
- seen.add(m.$ref);
1532
- const r = resolveRefSilent(m.$ref);
1533
- return r ? constrains(r) : false;
1534
- });
1535
- }
1536
- return constrains(variant) ? null : value;
1537
- }
1538
- const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1539
- const strategy = schema.oneOf ? "one" : "any";
1540
- const unionBase = {
1541
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1542
- discriminatorPropertyName: dialect.schema.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1543
- strategy
1544
- };
1545
- const discriminator = dialect.schema.isDiscriminator(schema) ? schema.discriminator : void 0;
1546
- const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1547
- const sharedPropertiesNode = schema.properties ? parseSchema({
1548
- schema: memberBaseSchema,
1549
- name
1550
- }, rawOptions) : void 0;
1551
- if (sharedPropertiesNode || discriminator) {
1552
- const members = unionMembers.map((s) => {
1553
- const ref = dialect.schema.isReference(s) ? s.$ref : void 0;
1554
- const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1555
- const memberNode = parseSchema({
1556
- schema: s,
1557
- name
1558
- }, rawOptions);
1559
- if (!discriminatorValue || !discriminator) return memberNode;
1560
- const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_ast.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1561
- propertyName: discriminator.propertyName,
1562
- values: [discriminatorValue]
1563
- })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1564
- return _kubb_ast.ast.factory.createSchema({
1565
- type: "intersection",
1566
- members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1567
- propertyName: discriminator.propertyName,
1568
- value: discriminatorValue
1569
- })]
1570
- });
1571
- });
1572
- const unionNode = _kubb_ast.ast.factory.createSchema({
1573
- type: "union",
1574
- ...unionBase,
1575
- members
1576
- });
1577
- if (!sharedPropertiesNode) return unionNode;
1396
+ return constrains(variant) ? null : value;
1397
+ }
1398
+ const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1399
+ const strategy = schema.oneOf ? "one" : "any";
1400
+ const unionBase = {
1401
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1402
+ discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1403
+ strategy
1404
+ };
1405
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1406
+ const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1407
+ const sharedPropertiesNode = schema.properties ? parse({
1408
+ schema: memberBaseSchema,
1409
+ name
1410
+ }, rawOptions) : void 0;
1411
+ if (sharedPropertiesNode || discriminator) {
1412
+ const members = unionMembers.map((s) => {
1413
+ const ref = isReference(s) ? s.$ref : void 0;
1414
+ const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1415
+ const memberNode = parse({
1416
+ schema: s,
1417
+ name
1418
+ }, rawOptions);
1419
+ if (!discriminatorValue || !discriminator) return memberNode;
1420
+ const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_ast.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1421
+ propertyName: discriminator.propertyName,
1422
+ values: [discriminatorValue]
1423
+ })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1578
1424
  return _kubb_ast.ast.factory.createSchema({
1579
1425
  type: "intersection",
1580
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1581
- members: [unionNode, sharedPropertiesNode]
1426
+ members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1427
+ propertyName: discriminator.propertyName,
1428
+ value: discriminatorValue
1429
+ })]
1582
1430
  });
1583
- }
1431
+ });
1584
1432
  const unionNode = _kubb_ast.ast.factory.createSchema({
1585
1433
  type: "union",
1586
1434
  ...unionBase,
1587
- members: unionMembers.map((s) => parseSchema({
1588
- schema: s,
1589
- name
1590
- }, rawOptions))
1435
+ members
1591
1436
  });
1592
- return _kubb_ast.ast.applyMacros(unionNode, [_kubb_ast.macroSimplifyUnion], { depth: "shallow" });
1593
- }
1594
- /**
1595
- * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1596
- */
1597
- function convertConst({ schema, name, nullable, defaultValue }) {
1598
- const constValue = schema.const;
1599
- if (constValue === null) return createNullSchema(schema, name);
1600
- const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1437
+ if (!sharedPropertiesNode) return unionNode;
1601
1438
  return _kubb_ast.ast.factory.createSchema({
1602
- type: "enum",
1603
- primitive: constPrimitive,
1604
- enumValues: [constValue],
1605
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1439
+ type: "intersection",
1440
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1441
+ members: [unionNode, sharedPropertiesNode]
1606
1442
  });
1607
1443
  }
1608
- /**
1609
- * Converts a format-annotated schema into a special-type `SchemaNode`.
1610
- * Returns `null` when the format should fall through to string handling (`dateType: false`).
1611
- */
1612
- function convertFormat({ schema, name, nullable, defaultValue, options }) {
1613
- const base = buildSchemaNode(schema, name, nullable, defaultValue);
1614
- if (schema.format === "int64") return _kubb_ast.ast.factory.createSchema({
1615
- type: options.integerType === "bigint" ? "bigint" : "integer",
1616
- primitive: "integer",
1617
- ...base,
1618
- min: schema.minimum,
1619
- max: schema.maximum,
1620
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1621
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1622
- });
1623
- if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1624
- const dateType = getDateType(options, schema.format);
1625
- if (!dateType) return null;
1626
- if (dateType.type === "datetime") return _kubb_ast.ast.factory.createSchema({
1627
- ...base,
1628
- primitive: "string",
1629
- type: "datetime",
1630
- offset: dateType.offset,
1631
- local: dateType.local
1632
- });
1633
- return _kubb_ast.ast.factory.createSchema({
1634
- ...base,
1635
- primitive: "string",
1636
- type: dateType.type,
1637
- representation: dateType.representation
1638
- });
1639
- }
1640
- const specialType = getSchemaType(schema.format);
1641
- if (!specialType) return null;
1642
- const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1643
- if (specialType === "number" || specialType === "integer" || specialType === "bigint") return _kubb_ast.ast.factory.createSchema({
1644
- ...base,
1645
- primitive: specialPrimitive,
1646
- type: specialType
1647
- });
1648
- if (specialType === "url") return _kubb_ast.ast.factory.createSchema({
1649
- ...base,
1650
- primitive: "string",
1651
- type: "url",
1652
- min: schema.minLength,
1653
- max: schema.maxLength
1654
- });
1655
- if (specialType === "ipv4") return _kubb_ast.ast.factory.createSchema({
1444
+ const unionNode = _kubb_ast.ast.factory.createSchema({
1445
+ type: "union",
1446
+ ...unionBase,
1447
+ members: unionMembers.map((s) => parse({
1448
+ schema: s,
1449
+ name
1450
+ }, rawOptions))
1451
+ });
1452
+ return _kubb_ast.ast.applyMacros(unionNode, [_kubb_ast.macroSimplifyUnion], { depth: "shallow" });
1453
+ }
1454
+ /**
1455
+ * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1456
+ */
1457
+ function convertConst({ schema, name, nullable, defaultValue }) {
1458
+ const constValue = schema.const;
1459
+ if (constValue === null) return createNullNode(schema, name);
1460
+ const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1461
+ return _kubb_ast.ast.factory.createSchema({
1462
+ type: "enum",
1463
+ primitive: constPrimitive,
1464
+ enumValues: [constValue],
1465
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1466
+ });
1467
+ }
1468
+ /**
1469
+ * Converts a format-annotated schema into a special-type `SchemaNode`.
1470
+ * Returns `null` when the format should fall through to string handling (`dateType: false`).
1471
+ */
1472
+ function convertFormat({ schema, name, nullable, defaultValue, options }) {
1473
+ const base = buildSchemaNode(schema, name, nullable, defaultValue);
1474
+ if (schema.format === "int64") return _kubb_ast.ast.factory.createSchema({
1475
+ type: options.integerType === "bigint" ? "bigint" : "integer",
1476
+ primitive: "integer",
1477
+ ...base,
1478
+ min: schema.minimum,
1479
+ max: schema.maximum,
1480
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1481
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1482
+ });
1483
+ if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1484
+ const dateType = getDateType(options, schema.format);
1485
+ if (!dateType) return null;
1486
+ if (dateType.type === "datetime") return _kubb_ast.ast.factory.createSchema({
1656
1487
  ...base,
1657
1488
  primitive: "string",
1658
- type: "ipv4"
1489
+ type: "datetime",
1490
+ offset: dateType.offset,
1491
+ local: dateType.local
1659
1492
  });
1660
- if (specialType === "ipv6") return _kubb_ast.ast.factory.createSchema({
1493
+ return _kubb_ast.ast.factory.createSchema({
1661
1494
  ...base,
1662
1495
  primitive: "string",
1663
- type: "ipv6"
1496
+ type: dateType.type,
1497
+ representation: dateType.representation
1664
1498
  });
1665
- if (specialType === "uuid" || specialType === "email") return _kubb_ast.ast.factory.createSchema({
1666
- ...base,
1667
- primitive: "string",
1668
- type: specialType,
1499
+ }
1500
+ const specialType = getSchemaType(schema.format);
1501
+ if (!specialType) return null;
1502
+ const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1503
+ const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
1504
+ return _kubb_ast.ast.factory.createSchema({
1505
+ ...base,
1506
+ primitive: specialPrimitive,
1507
+ type: specialType,
1508
+ ...hasLength ? {
1669
1509
  min: schema.minLength,
1670
1510
  max: schema.maxLength
1671
- });
1672
- return _kubb_ast.ast.factory.createSchema({
1673
- ...base,
1674
- primitive: specialPrimitive,
1675
- type: specialType
1676
- });
1677
- }
1678
- /**
1679
- * Converts an `enum` schema into an `EnumSchemaNode`.
1680
- */
1681
- function convertEnum({ schema, name, nullable, type, rawOptions }) {
1682
- if (type === "array") return parseSchema({
1683
- schema: normalizeArrayEnum(schema),
1684
- name
1685
- }, rawOptions);
1686
- const nullInEnum = schema.enum.includes(null);
1687
- const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1688
- if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
1689
- const enumNullable = nullable || nullInEnum || void 0;
1690
- const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1691
- const enumPrimitive = getPrimitiveType(type);
1692
- const enumBase = {
1693
- type: "enum",
1694
- primitive: enumPrimitive,
1695
- name,
1696
- title: schema.title,
1697
- description: schema.description,
1698
- deprecated: schema.deprecated,
1699
- nullable: enumNullable,
1700
- readOnly: schema.readOnly,
1701
- writeOnly: schema.writeOnly,
1702
- default: enumDefault,
1703
- examples: extractExamples(schema),
1704
- format: schema.format
1705
- };
1706
- const extensionKey = enumExtensionKeys.find((key) => key in schema);
1707
- const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1708
- if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1709
- const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1710
- const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1711
- const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1712
- const uniqueValues = [...new Set(filteredValues)];
1713
- const seenNames = /* @__PURE__ */ new Set();
1714
- return _kubb_ast.ast.factory.createSchema({
1715
- ...enumBase,
1716
- primitive: enumPrimitiveType,
1717
- namedEnumValues: uniqueValues.map((value, index) => ({
1718
- name: String(rawEnumNames?.[index] ?? value),
1719
- value,
1720
- primitive: enumPrimitiveType,
1721
- description: rawEnumDescriptions?.[index]
1722
- })).filter((entry) => {
1723
- if (seenNames.has(entry.name)) return false;
1724
- seenNames.add(entry.name);
1725
- return true;
1726
- })
1727
- });
1728
- }
1511
+ } : {}
1512
+ });
1513
+ }
1514
+ /**
1515
+ * Converts an `enum` schema into an `EnumSchemaNode`.
1516
+ */
1517
+ function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
1518
+ if (type === "array") return parse({
1519
+ schema: normalizeArrayEnum(schema),
1520
+ name
1521
+ }, rawOptions);
1522
+ const nullInEnum = schema.enum.includes(null);
1523
+ const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1524
+ if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
1525
+ const enumNullable = nullable || nullInEnum || void 0;
1526
+ const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1527
+ const enumPrimitive = getPrimitiveType(type);
1528
+ const enumBase = {
1529
+ type: "enum",
1530
+ primitive: enumPrimitive,
1531
+ ...buildSchemaNode(schema, name, enumNullable, enumDefault)
1532
+ };
1533
+ const extensionKey = enumExtensionKeys.find((key) => key in schema);
1534
+ const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1535
+ if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1536
+ const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1537
+ const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1538
+ const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1539
+ const uniqueValues = [...new Set(filteredValues)];
1540
+ const seenNames = /* @__PURE__ */ new Set();
1729
1541
  return _kubb_ast.ast.factory.createSchema({
1730
1542
  ...enumBase,
1731
- enumValues: [...new Set(filteredValues)]
1732
- });
1733
- }
1734
- /**
1735
- * Converts an object-like schema into an `ObjectSchemaNode`.
1736
- */
1737
- function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }) {
1738
- const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1739
- const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1740
- const resolvedPropSchema = propSchema;
1741
- const propNullable = dialect.schema.isNullable(resolvedPropSchema);
1742
- const schemaNode = nameEnums(parseSchema({
1743
- schema: resolvedPropSchema,
1744
- name: (0, _kubb_ast.childName)(name, propName)
1745
- }, rawOptions), {
1746
- parentName: name,
1747
- propName,
1748
- enumSuffix: options.enumSuffix
1749
- });
1750
- return _kubb_ast.ast.factory.createProperty({
1751
- name: propName,
1752
- schema: {
1753
- ...schemaNode,
1754
- nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1755
- },
1756
- required
1757
- });
1758
- }) : [];
1759
- const additionalProperties = schema.additionalProperties;
1760
- const additionalPropertiesNode = (() => {
1761
- if (additionalProperties === true) return true;
1762
- if (additionalProperties === false) return false;
1763
- if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1764
- if (additionalProperties) return _kubb_ast.ast.factory.createSchema({ type: options.unknownType });
1765
- })();
1766
- const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1767
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_ast.ast.factory.createSchema({ type: options.unknownType }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1768
- const objectNode = _kubb_ast.ast.factory.createSchema({
1769
- type: "object",
1770
- primitive: "object",
1771
- properties,
1772
- additionalProperties: additionalPropertiesNode,
1773
- patternProperties,
1774
- minProperties: schema.minProperties,
1775
- maxProperties: schema.maxProperties,
1776
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1777
- });
1778
- if (dialect.schema.isDiscriminator(schema) && schema.discriminator.mapping) {
1779
- const discPropName = schema.discriminator.propertyName;
1780
- const values = Object.keys(schema.discriminator.mapping);
1781
- const enumName = name ? (0, _kubb_ast.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1782
- return _kubb_ast.ast.applyMacros(objectNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1783
- propertyName: discPropName,
1784
- values,
1785
- enumName
1786
- })], { depth: "shallow" });
1787
- }
1788
- return objectNode;
1789
- }
1790
- /**
1791
- * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1792
- */
1793
- function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1794
- const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
1795
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? _kubb_ast.ast.factory.createSchema({ type: "any" }) : parseSchema({ schema: schema.items }, rawOptions);
1796
- return _kubb_ast.ast.factory.createSchema({
1797
- type: "tuple",
1798
- primitive: "array",
1799
- items: tupleItems,
1800
- rest,
1801
- min: schema.minItems,
1802
- max: schema.maxItems,
1803
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1543
+ primitive: enumPrimitiveType,
1544
+ namedEnumValues: uniqueValues.map((value, index) => ({
1545
+ name: String(rawEnumNames?.[index] ?? value),
1546
+ value,
1547
+ primitive: enumPrimitiveType,
1548
+ description: rawEnumDescriptions?.[index]
1549
+ })).filter((entry) => {
1550
+ if (seenNames.has(entry.name)) return false;
1551
+ seenNames.add(entry.name);
1552
+ return true;
1553
+ })
1804
1554
  });
1805
1555
  }
1806
- /**
1807
- * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1808
- */
1809
- function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1810
- const rawItems = schema.items;
1811
- const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast.enumPropName)(null, name, options.enumSuffix) : name;
1812
- const items = rawItems ? [parseSchema({
1813
- schema: rawItems,
1814
- name: itemName
1815
- }, rawOptions)] : [];
1816
- return _kubb_ast.ast.factory.createSchema({
1817
- type: "array",
1818
- primitive: "array",
1819
- items,
1820
- min: schema.minItems,
1821
- max: schema.maxItems,
1822
- unique: schema.uniqueItems ?? void 0,
1823
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1556
+ return _kubb_ast.ast.factory.createSchema({
1557
+ ...enumBase,
1558
+ enumValues: [...new Set(filteredValues)]
1559
+ });
1560
+ }
1561
+ /**
1562
+ * Converts an object-like schema into an `ObjectSchemaNode`.
1563
+ */
1564
+ function convertObject({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1565
+ const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1566
+ const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1567
+ const resolvedPropSchema = propSchema;
1568
+ const propNullable = isNullable(resolvedPropSchema);
1569
+ const schemaNode = nameEnums(parse({
1570
+ schema: resolvedPropSchema,
1571
+ name: (0, _kubb_ast.childName)(name, propName)
1572
+ }, rawOptions), {
1573
+ parentName: name,
1574
+ propName,
1575
+ enumSuffix: options.enumSuffix
1824
1576
  });
1825
- }
1826
- /**
1827
- * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1828
- */
1829
- function convertString({ schema, name, nullable, defaultValue }) {
1830
- return _kubb_ast.ast.factory.createSchema({
1831
- type: "string",
1832
- primitive: "string",
1833
- min: schema.minLength,
1834
- max: schema.maxLength,
1835
- pattern: schema.pattern,
1836
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1577
+ return _kubb_ast.ast.factory.createProperty({
1578
+ name: propName,
1579
+ schema: {
1580
+ ...schemaNode,
1581
+ nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1582
+ },
1583
+ required
1837
1584
  });
1585
+ }) : [];
1586
+ const additionalProperties = schema.additionalProperties;
1587
+ const additionalPropertiesNode = (() => {
1588
+ if (additionalProperties === true) return true;
1589
+ if (additionalProperties === false) return false;
1590
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parse({ schema: additionalProperties }, rawOptions);
1591
+ if (additionalProperties) return _kubb_ast.ast.factory.createSchema({ type: options.unknownType });
1592
+ })();
1593
+ const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1594
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_ast.ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: patternSchema }, rawOptions)])) : void 0;
1595
+ const objectNode = _kubb_ast.ast.factory.createSchema({
1596
+ type: "object",
1597
+ primitive: "object",
1598
+ properties,
1599
+ additionalProperties: additionalPropertiesNode,
1600
+ patternProperties,
1601
+ minProperties: schema.minProperties,
1602
+ maxProperties: schema.maxProperties,
1603
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1604
+ });
1605
+ if (isDiscriminator(schema) && schema.discriminator.mapping) {
1606
+ const discPropName = schema.discriminator.propertyName;
1607
+ const values = Object.keys(schema.discriminator.mapping);
1608
+ const enumName = name ? (0, _kubb_ast.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
1609
+ return _kubb_ast.ast.applyMacros(objectNode, [(0, _kubb_ast.macroDiscriminatorEnum)({
1610
+ propertyName: discPropName,
1611
+ values,
1612
+ enumName
1613
+ })], { depth: "shallow" });
1838
1614
  }
1839
- /**
1840
- * Converts a `type: 'number'` or `type: 'integer'` schema.
1841
- */
1842
- function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1843
- return _kubb_ast.ast.factory.createSchema({
1844
- type,
1845
- primitive: type,
1846
- min: schema.minimum,
1847
- max: schema.maximum,
1848
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1849
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1850
- multipleOf: schema.multipleOf,
1851
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1852
- });
1615
+ return objectNode;
1616
+ }
1617
+ /**
1618
+ * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1619
+ */
1620
+ function convertTuple({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1621
+ const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }, rawOptions));
1622
+ const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? _kubb_ast.ast.factory.createSchema({ type: "any" }) : parse({ schema: schema.items }, rawOptions);
1623
+ return _kubb_ast.ast.factory.createSchema({
1624
+ type: "tuple",
1625
+ primitive: "array",
1626
+ items: tupleItems,
1627
+ rest,
1628
+ min: schema.minItems,
1629
+ max: schema.maxItems,
1630
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1631
+ });
1632
+ }
1633
+ /**
1634
+ * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1635
+ */
1636
+ function convertArray({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1637
+ const rawItems = schema.items;
1638
+ const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast.enumPropName)(null, name, options.enumSuffix) : name;
1639
+ const items = rawItems ? [parse({
1640
+ schema: rawItems,
1641
+ name: itemName
1642
+ }, rawOptions)] : [];
1643
+ return _kubb_ast.ast.factory.createSchema({
1644
+ type: "array",
1645
+ primitive: "array",
1646
+ items,
1647
+ min: schema.minItems,
1648
+ max: schema.maxItems,
1649
+ unique: schema.uniqueItems ?? void 0,
1650
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1651
+ });
1652
+ }
1653
+ /**
1654
+ * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1655
+ */
1656
+ function convertString({ schema, name, nullable, defaultValue }) {
1657
+ return _kubb_ast.ast.factory.createSchema({
1658
+ type: "string",
1659
+ primitive: "string",
1660
+ min: schema.minLength,
1661
+ max: schema.maxLength,
1662
+ pattern: schema.pattern,
1663
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1664
+ });
1665
+ }
1666
+ /**
1667
+ * Converts a `type: 'number'` or `type: 'integer'` schema.
1668
+ */
1669
+ function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1670
+ return _kubb_ast.ast.factory.createSchema({
1671
+ type,
1672
+ primitive: type,
1673
+ min: schema.minimum,
1674
+ max: schema.maximum,
1675
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1676
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1677
+ multipleOf: schema.multipleOf,
1678
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1679
+ });
1680
+ }
1681
+ /**
1682
+ * Converts a `type: 'boolean'` schema.
1683
+ */
1684
+ function convertBoolean({ schema, name, nullable, defaultValue }) {
1685
+ return _kubb_ast.ast.factory.createSchema({
1686
+ type: "boolean",
1687
+ primitive: "boolean",
1688
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1689
+ });
1690
+ }
1691
+ /**
1692
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1693
+ * into a `blob` node.
1694
+ */
1695
+ function convertBinary({ schema, name, nullable, defaultValue }) {
1696
+ return _kubb_ast.ast.factory.createSchema({
1697
+ type: "blob",
1698
+ primitive: "string",
1699
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1700
+ });
1701
+ }
1702
+ /**
1703
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1704
+ *
1705
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parse`
1706
+ * falls through and handles it as that single type with nullability already folded in.
1707
+ */
1708
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1709
+ const types = schema.type;
1710
+ const nonNullTypes = types.filter((t) => t !== "null");
1711
+ if (nonNullTypes.length <= 1) return null;
1712
+ const arrayNullable = types.includes("null") || nullable || void 0;
1713
+ return _kubb_ast.ast.factory.createSchema({
1714
+ type: "union",
1715
+ members: nonNullTypes.map((t) => {
1716
+ return parse({
1717
+ schema: {
1718
+ ...schema,
1719
+ type: t
1720
+ },
1721
+ name
1722
+ }, rawOptions);
1723
+ }),
1724
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1725
+ });
1726
+ }
1727
+ /**
1728
+ * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1729
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1730
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1731
+ * match/convert/fall-through contract.
1732
+ */
1733
+ const schemaRules = [
1734
+ {
1735
+ match: ({ schema }) => isReference(schema),
1736
+ convert: convertRef
1737
+ },
1738
+ {
1739
+ match: ({ schema }) => !!schema.allOf?.length,
1740
+ convert: convertAllOf
1741
+ },
1742
+ {
1743
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1744
+ convert: convertUnion
1745
+ },
1746
+ {
1747
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1748
+ convert: convertConst
1749
+ },
1750
+ {
1751
+ match: ({ schema }) => !!schema.format,
1752
+ convert: convertFormat
1753
+ },
1754
+ {
1755
+ match: ({ schema }) => isBinary(schema),
1756
+ convert: convertBinary
1757
+ },
1758
+ {
1759
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1760
+ convert: convertMultiType
1761
+ },
1762
+ {
1763
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1764
+ convert: convertString
1765
+ },
1766
+ {
1767
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1768
+ convert: (ctx) => convertNumeric(ctx, "number")
1769
+ },
1770
+ {
1771
+ match: ({ schema }) => !!schema.enum?.length,
1772
+ convert: convertEnum
1773
+ },
1774
+ {
1775
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1776
+ convert: convertObject
1777
+ },
1778
+ {
1779
+ match: ({ schema }) => "prefixItems" in schema,
1780
+ convert: convertTuple
1781
+ },
1782
+ {
1783
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1784
+ convert: convertArray
1785
+ },
1786
+ {
1787
+ match: ({ type }) => type === "string",
1788
+ convert: convertString
1789
+ },
1790
+ {
1791
+ match: ({ type }) => type === "number",
1792
+ convert: (ctx) => convertNumeric(ctx, "number")
1793
+ },
1794
+ {
1795
+ match: ({ type }) => type === "integer",
1796
+ convert: (ctx) => convertNumeric(ctx, "integer")
1797
+ },
1798
+ {
1799
+ match: ({ type }) => type === "boolean",
1800
+ convert: convertBoolean
1801
+ },
1802
+ {
1803
+ match: ({ type }) => type === "null",
1804
+ convert: ({ schema, name, nullable }) => createNullNode(schema, name, nullable)
1853
1805
  }
1806
+ ];
1807
+ //#endregion
1808
+ //#region src/parser.ts
1809
+ /**
1810
+ * Creates the schema and operation converters bound to one OpenAPI document.
1811
+ *
1812
+ * Owns the per-instance `$ref` state (cycle detection, resolved-node cache, existence cache) and
1813
+ * the `parseSchema` recursion seam, then dispatches each schema through the ordered `schemaRules`
1814
+ * table from `converters.ts`. Every converter is a standalone function that recurses through the
1815
+ * `parse` function passed to it, so this file only wires state to the converters.
1816
+ *
1817
+ * @internal
1818
+ */
1819
+ function createSchemaParser(ctx) {
1820
+ const document = ctx.document;
1854
1821
  /**
1855
- * Converts a `type: 'boolean'` schema.
1822
+ * Tracks `$ref` paths that are currently being resolved to prevent infinite
1823
+ * recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
1856
1824
  */
1857
- function convertBoolean({ schema, name, nullable, defaultValue }) {
1858
- return _kubb_ast.ast.factory.createSchema({
1859
- type: "boolean",
1860
- primitive: "boolean",
1861
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1862
- });
1863
- }
1825
+ const resolvingRefs = /* @__PURE__ */ new Set();
1864
1826
  /**
1865
- * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1866
- * into a `blob` node.
1827
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1828
+ *
1829
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1830
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1831
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1832
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1867
1833
  */
1868
- function convertBlob({ schema, name, nullable, defaultValue }) {
1869
- return _kubb_ast.ast.factory.createSchema({
1870
- type: "blob",
1871
- primitive: "string",
1872
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1873
- });
1874
- }
1834
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1875
1835
  /**
1876
- * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1877
- *
1878
- * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1879
- * falls through and handles it as that single type with nullability already folded in.
1836
+ * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1837
+ * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1838
+ * goes `false` for a `$ref` that points at a component the spec never declares.
1880
1839
  */
1881
- function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1882
- const types = schema.type;
1883
- const nonNullTypes = types.filter((t) => t !== "null");
1884
- if (nonNullTypes.length <= 1) return null;
1885
- const arrayNullable = types.includes("null") || nullable || void 0;
1886
- return _kubb_ast.ast.factory.createSchema({
1887
- type: "union",
1888
- members: nonNullTypes.map((t) => parseSchema({
1889
- schema: {
1890
- ...schema,
1891
- type: t
1892
- },
1893
- name
1894
- }, rawOptions)),
1895
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1896
- });
1840
+ const refExistence = /* @__PURE__ */ new Map();
1841
+ function refExists(refPath) {
1842
+ if (!refExistence.has(refPath)) {
1843
+ let exists = false;
1844
+ try {
1845
+ exists = !!resolveRef(document, refPath);
1846
+ } catch {
1847
+ exists = false;
1848
+ }
1849
+ refExistence.set(refPath, exists);
1850
+ }
1851
+ return refExistence.get(refPath) ?? false;
1897
1852
  }
1898
1853
  /**
1899
- * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1900
- * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1901
- * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1902
- * match/convert/fall-through contract.
1854
+ * Resolves a `$ref` to its parsed node, guarding against cycles and memoizing per instance.
1855
+ * Returns `null` when the ref is currently being resolved (a cycle) or cannot be resolved
1856
+ * (e.g. a minimal document in a unit test).
1903
1857
  */
1904
- const schemaRules = [
1905
- {
1906
- match: ({ schema }) => dialect.schema.isReference(schema),
1907
- convert: convertRef
1908
- },
1909
- {
1910
- match: ({ schema }) => !!schema.allOf?.length,
1911
- convert: convertAllOf
1912
- },
1913
- {
1914
- match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1915
- convert: convertUnion
1916
- },
1917
- {
1918
- match: ({ schema }) => "const" in schema && schema.const !== void 0,
1919
- convert: convertConst
1920
- },
1921
- {
1922
- match: ({ schema }) => !!schema.format,
1923
- convert: convertFormat
1924
- },
1925
- {
1926
- match: ({ schema }) => dialect.schema.isBinary(schema),
1927
- convert: convertBlob
1928
- },
1929
- {
1930
- match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1931
- convert: convertMultiType
1932
- },
1933
- {
1934
- match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1935
- convert: convertString
1936
- },
1937
- {
1938
- match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1939
- convert: (ctx) => convertNumeric(ctx, "number")
1940
- },
1941
- {
1942
- match: ({ schema }) => !!schema.enum?.length,
1943
- convert: convertEnum
1944
- },
1945
- {
1946
- match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1947
- convert: convertObject
1948
- },
1949
- {
1950
- match: ({ schema }) => "prefixItems" in schema,
1951
- convert: convertTuple
1952
- },
1953
- {
1954
- match: ({ schema, type }) => type === "array" || "items" in schema,
1955
- convert: convertArray
1956
- },
1957
- {
1958
- match: ({ type }) => type === "string",
1959
- convert: convertString
1960
- },
1961
- {
1962
- match: ({ type }) => type === "number",
1963
- convert: (ctx) => convertNumeric(ctx, "number")
1964
- },
1965
- {
1966
- match: ({ type }) => type === "integer",
1967
- convert: (ctx) => convertNumeric(ctx, "integer")
1968
- },
1969
- {
1970
- match: ({ type }) => type === "boolean",
1971
- convert: convertBoolean
1972
- },
1973
- {
1974
- match: ({ type }) => type === "null",
1975
- convert: ({ schema, name, nullable }) => createNullSchema(schema, name, nullable)
1858
+ function resolveRefNode(refPath, rawOptions) {
1859
+ if (resolvingRefs.has(refPath)) return null;
1860
+ if (!resolvedRefCache.has(refPath)) {
1861
+ let resolved = null;
1862
+ try {
1863
+ const referenced = resolveRef(document, refPath);
1864
+ if (referenced) {
1865
+ resolvingRefs.add(refPath);
1866
+ resolved = parseSchema({ schema: referenced }, rawOptions);
1867
+ resolvingRefs.delete(refPath);
1868
+ }
1869
+ } catch {}
1870
+ resolvedRefCache.set(refPath, resolved);
1976
1871
  }
1977
- ];
1872
+ return resolvedRefCache.get(refPath) ?? null;
1873
+ }
1978
1874
  /**
1979
1875
  * Converts an OAS `SchemaObject` into a `SchemaNode`.
1980
1876
  *
@@ -1992,19 +1888,23 @@ function createSchemaParser(ctx, dialect = oasDialect) {
1992
1888
  schema: flattenedSchema,
1993
1889
  name
1994
1890
  }, rawOptions);
1995
- const nullable = dialect.schema.isNullable(schema) || void 0;
1996
- const schemaCtx = {
1891
+ const nullable = isNullable(schema) || void 0;
1892
+ const context = {
1997
1893
  schema,
1998
1894
  name,
1999
1895
  nullable,
2000
1896
  defaultValue: schema.default === null && nullable ? void 0 : schema.default,
2001
1897
  type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
2002
1898
  rawOptions,
2003
- options
1899
+ options,
1900
+ parse: parseSchema,
1901
+ document,
1902
+ resolveRefNode,
1903
+ refExists
2004
1904
  };
2005
1905
  for (const rule of schemaRules) {
2006
- if (!rule.match(schemaCtx)) continue;
2007
- const node = rule.convert(schemaCtx);
1906
+ if (!rule.match(context)) continue;
1907
+ const node = rule.convert(context);
2008
1908
  if (node) return node;
2009
1909
  }
2010
1910
  const emptyType = options.emptySchemaType;
@@ -2062,7 +1962,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
2062
1962
  const keys = [];
2063
1963
  for (const key in schema.properties) {
2064
1964
  const prop = schema.properties[key];
2065
- if (prop && !dialect.schema.isReference(prop) && prop[flag]) keys.push(key);
1965
+ if (prop && !isReference(prop) && prop[flag]) keys.push(key);
2066
1966
  }
2067
1967
  return keys.length ? keys : null;
2068
1968
  }
@@ -2129,7 +2029,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
2129
2029
  });
2130
2030
  });
2131
2031
  const pathItem = document.paths?.[operation.path];
2132
- const pathItemDoc = pathItem && !dialect.schema.isReference(pathItem) ? pathItem : void 0;
2032
+ const pathItemDoc = pathItem && !isReference(pathItem) ? pathItem : void 0;
2133
2033
  const pickDoc = (key) => {
2134
2034
  const own = operation.schema[key];
2135
2035
  if (typeof own === "string") return own;
@@ -2321,13 +2221,10 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
2321
2221
  function ensureSchemas(document) {
2322
2222
  const cached = schemasCache.get(document);
2323
2223
  if (cached) return cached;
2324
- const promise = Promise.resolve().then(() => {
2325
- const result = getSchemas(document, { contentType });
2326
- nameMapping = result.nameMapping;
2327
- return result.schemas;
2328
- });
2329
- schemasCache.set(document, promise);
2330
- return promise;
2224
+ const result = getSchemas(document, { contentType });
2225
+ nameMapping = result.nameMapping;
2226
+ schemasCache.set(document, result.schemas);
2227
+ return result.schemas;
2331
2228
  }
2332
2229
  function ensureSchemaParser(document) {
2333
2230
  const cached = schemaParserCache.get(document);
@@ -2443,7 +2340,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
2443
2340
  const document = await ensureDocument(source);
2444
2341
  return parseInput({
2445
2342
  document,
2446
- schemas: await ensureSchemas(document),
2343
+ schemas: ensureSchemas(document),
2447
2344
  parser: ensureSchemaParser(document)
2448
2345
  });
2449
2346
  }