@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.js CHANGED
@@ -5,18 +5,11 @@ import path from "node:path";
5
5
  import { access, readFile } from "node:fs/promises";
6
6
  import { compileErrors, validate } from "@readme/openapi-parser";
7
7
  import { upgrade } from "@scalar/openapi-upgrader";
8
- import { parse } from "yaml";
9
8
  import { bundle } from "api-ref-bundler";
9
+ import { parse } from "yaml";
10
10
  //#region src/constants.ts
11
11
  /**
12
12
  * Default parser options applied when no explicit options are provided.
13
- *
14
- * @example
15
- * ```ts
16
- * import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
17
- *
18
- * const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
19
- * ```
20
13
  */
21
14
  const DEFAULT_PARSER_OPTIONS = {
22
15
  dateType: "string",
@@ -55,14 +48,6 @@ const SUPPORTED_METHODS = /* @__PURE__ */ new Set([
55
48
  *
56
49
  * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
57
50
  * intersection member rather than being merged into the parent.
58
- *
59
- * @example
60
- * ```ts
61
- * import { structuralKeys } from '@kubb/adapter-oas'
62
- *
63
- * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
64
- * // true when fragment has e.g. 'properties' or 'oneOf'
65
- * ```
66
51
  */
67
52
  const structuralKeys = /* @__PURE__ */ new Set([
68
53
  "properties",
@@ -92,15 +77,6 @@ const specialCasedFormats = /* @__PURE__ */ new Set([
92
77
  * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
93
78
  * separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
94
79
  * and `idn-hostname` map to `'url'` as the closest generic string-format type.
95
- *
96
- * @example
97
- * ```ts
98
- * import { formatMap } from '@kubb/adapter-oas'
99
- *
100
- * formatMap['uuid'] // 'uuid'
101
- * formatMap['binary'] // 'blob'
102
- * formatMap['float'] // 'number'
103
- * ```
104
80
  */
105
81
  const formatMap = {
106
82
  uuid: "uuid",
@@ -121,24 +97,10 @@ const formatMap = {
121
97
  };
122
98
  /**
123
99
  * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
124
- *
125
- * @example
126
- * ```ts
127
- * import { enumExtensionKeys } from '@kubb/adapter-oas'
128
- *
129
- * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
130
- * ```
131
100
  */
132
101
  const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
133
102
  /**
134
103
  * Vendor extension keys that attach human-readable descriptions to enum values, checked in priority order.
135
- *
136
- * @example
137
- * ```ts
138
- * import { enumDescriptionKeys } from '@kubb/adapter-oas'
139
- *
140
- * const key = enumDescriptionKeys.find((k) => k in schema) // 'x-enumDescriptions' | 'x-enum-descriptions' | undefined
141
- * ```
142
104
  */
143
105
  const enumDescriptionKeys = ["x-enumDescriptions", "x-enum-descriptions"];
144
106
  //#endregion
@@ -382,7 +344,7 @@ async function read(path) {
382
344
  return readFile(path, { encoding: "utf8" });
383
345
  }
384
346
  //#endregion
385
- //#region src/bundler.ts
347
+ //#region src/factory.ts
386
348
  const urlRegExp = /^https?:\/+/i;
387
349
  async function readSource(sourcePath) {
388
350
  if (urlRegExp.test(sourcePath)) {
@@ -425,8 +387,6 @@ async function bundleDocument(pathOrUrl) {
425
387
  await resolver(pathOrUrl);
426
388
  return await bundle(pathOrUrl, resolver);
427
389
  }
428
- //#endregion
429
- //#region src/factory.ts
430
390
  /**
431
391
  * Loads and bundles an OpenAPI document, returning the raw `Document`.
432
392
  *
@@ -496,19 +456,12 @@ async function validateDocument(document, { throwOnError = false } = {}) {
496
456
  }
497
457
  }
498
458
  //#endregion
499
- //#region src/guards.ts
459
+ //#region src/oas.ts
500
460
  /**
501
461
  * Returns `true` when a schema should be treated as nullable.
502
462
  *
503
463
  * Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
504
464
  * `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
505
- *
506
- * @example
507
- * ```ts
508
- * isNullable({ type: 'string', nullable: true }) // true
509
- * isNullable({ type: ['string', 'null'] }) // true
510
- * isNullable({ type: 'string' }) // false
511
- * ```
512
465
  */
513
466
  function isNullable(schema) {
514
467
  if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
@@ -519,31 +472,24 @@ function isNullable(schema) {
519
472
  }
520
473
  /**
521
474
  * Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
522
- *
523
- * @example
524
- * ```ts
525
- * isReference({ $ref: '#/components/schemas/Pet' }) // true
526
- * isReference({ type: 'string' }) // false
527
- * ```
528
475
  */
529
476
  function isReference(obj) {
530
477
  return !!obj && typeof obj === "object" && "$ref" in obj;
531
478
  }
532
479
  /**
533
- * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
534
- *
535
- * @example
536
- * ```ts
537
- * isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
538
- * isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
539
- * ```
480
+ * Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object,
481
+ * excluding the Swagger 2 string form.
540
482
  */
541
483
  function isDiscriminator(obj) {
542
484
  const record = obj;
543
485
  return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
544
486
  }
545
- //#endregion
546
- //#region src/mime.ts
487
+ /**
488
+ * Returns `true` when a schema is a binary payload: an octet-stream string body.
489
+ */
490
+ function isBinary(schema) {
491
+ return schema.type === "string" && schema.contentMediaType === "application/octet-stream";
492
+ }
547
493
  /**
548
494
  * MIME type fragments that mark a media type as JSON-like.
549
495
  *
@@ -636,6 +582,23 @@ function dereferenceWithRef(document, schema) {
636
582
  };
637
583
  return schema;
638
584
  }
585
+ /**
586
+ * Resolves a `$ref` slot in place: when `container[key]` holds a `$ref`, replaces it with the
587
+ * resolved value and returns that value. Returns `null` when the slot is empty, cannot be resolved,
588
+ * or is still a `$ref` after resolving. A non-`$ref` value is returned untouched, without writing.
589
+ *
590
+ * @example
591
+ * ```ts
592
+ * derefInPlace<ResponseObject>({ document, container: operation.schema.responses, key: '200' })
593
+ * ```
594
+ */
595
+ function derefInPlace({ document, container, key }) {
596
+ const value = container[key];
597
+ if (!isReference(value)) return value ? value : null;
598
+ const resolved = resolveRef(document, value.$ref);
599
+ container[key] = resolved;
600
+ return resolved && !isReference(resolved) ? resolved : null;
601
+ }
639
602
  //#endregion
640
603
  //#region src/operation.ts
641
604
  /**
@@ -667,31 +630,22 @@ function getResponseStatusCodes({ schema }) {
667
630
  function getResponseByStatusCode({ document, operation, statusCode }) {
668
631
  const responses = operation.schema.responses;
669
632
  if (!responses || isReference(responses)) return false;
670
- const response = responses[statusCode];
671
- if (!response) return false;
672
- if (isReference(response)) {
673
- const resolved = resolveRef(document, response.$ref);
674
- responses[statusCode] = resolved;
675
- if (!resolved || isReference(resolved)) return false;
676
- return resolved;
677
- }
678
- return response;
633
+ return derefInPlace({
634
+ document,
635
+ container: responses,
636
+ key: statusCode
637
+ }) ?? false;
679
638
  }
680
639
  /**
681
640
  * Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
682
641
  * `undefined` when the operation has no request body.
683
642
  */
684
643
  function getRequestBodyContent({ document, operation }) {
685
- const { schema } = operation;
686
- let requestBody = schema.requestBody;
687
- if (!requestBody) return;
688
- if (isReference(requestBody)) {
689
- const resolved = resolveRef(document, requestBody.$ref);
690
- schema.requestBody = resolved;
691
- if (!resolved || isReference(resolved)) return;
692
- requestBody = resolved;
693
- }
694
- return requestBody.content;
644
+ return derefInPlace({
645
+ document,
646
+ container: operation.schema,
647
+ key: "requestBody"
648
+ })?.content;
695
649
  }
696
650
  /**
697
651
  * Returns the request body media type. With `mediaType` set, returns that entry or `false`.
@@ -740,14 +694,12 @@ function getOperations(document) {
740
694
  if (!paths) return operations;
741
695
  for (const path of Object.keys(paths)) {
742
696
  if (path.startsWith("x-")) continue;
743
- let pathItem = paths[path];
697
+ const pathItem = derefInPlace({
698
+ document,
699
+ container: paths,
700
+ key: path
701
+ });
744
702
  if (!pathItem) continue;
745
- if (isReference(pathItem)) {
746
- const resolved = resolveRef(document, pathItem.$ref);
747
- paths[path] = resolved;
748
- if (!resolved || isReference(resolved)) continue;
749
- pathItem = resolved;
750
- }
751
703
  const item = pathItem;
752
704
  for (const method of Object.keys(item)) {
753
705
  if (!SUPPORTED_METHODS.has(method)) continue;
@@ -763,37 +715,6 @@ function getOperations(document) {
763
715
  return operations;
764
716
  }
765
717
  //#endregion
766
- //#region src/dialect.ts
767
- /**
768
- * The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
769
- *
770
- * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
771
- * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
772
- * ref resolution) so the converter pipeline and dispatch rules stay shared. A
773
- * future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
774
- * nullability, no discriminator object, binary via `contentEncoding` and reuses
775
- * the rest unchanged.
776
- *
777
- * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
778
- * JSON Schema vocabulary, so the converters keep that common case.
779
- *
780
- * @example
781
- * ```ts
782
- * const parser = createSchemaParser(context) // uses oasDialect
783
- * const parser = createSchemaParser(context, oasDialect) // explicit
784
- * ```
785
- */
786
- const oasDialect = ast.defineDialect({
787
- name: "oas",
788
- schema: {
789
- isNullable,
790
- isReference,
791
- isDiscriminator,
792
- isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
793
- resolveRef
794
- }
795
- });
796
- //#endregion
797
718
  //#region src/resolvers.ts
798
719
  /**
799
720
  * Reads the server URL from the document's `servers` array at `server.index`,
@@ -901,19 +822,19 @@ function getResponseBody(responseBody, contentType) {
901
822
  if (!(contentType in body.content)) return false;
902
823
  return body.content[contentType];
903
824
  }
904
- let availableContentType;
905
825
  const contentTypes = Object.keys(body.content);
906
- for (const mt of contentTypes) if (isJsonMimeType(mt)) {
907
- availableContentType = mt;
908
- break;
909
- }
910
- if (!availableContentType) availableContentType = contentTypes[0];
911
- if (availableContentType) return [
912
- availableContentType,
913
- body.content[availableContentType],
914
- ...body.description ? [body.description] : []
915
- ];
916
- return false;
826
+ const availableContentType = contentTypes.find(isJsonMimeType) ?? contentTypes[0];
827
+ if (!availableContentType) return false;
828
+ return body.content[availableContentType];
829
+ }
830
+ function resolveResponseRefs(document, operation) {
831
+ const responses = operation.schema.responses;
832
+ if (!responses) return;
833
+ for (const key in responses) derefInPlace({
834
+ document,
835
+ container: responses,
836
+ key
837
+ });
917
838
  }
918
839
  /**
919
840
  * Returns the response schema for a given operation and HTTP status code.
@@ -926,14 +847,6 @@ function getResponseBody(responseBody, contentType) {
926
847
  * getResponseSchema(document, operation, '4XX') // {}
927
848
  * ```
928
849
  */
929
- function resolveResponseRefs(document, operation) {
930
- const responses = operation.schema.responses;
931
- if (!responses) return;
932
- for (const key in responses) {
933
- const schema = responses[key];
934
- if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
935
- }
936
- }
937
850
  function getResponseSchema(document, operation, statusCode, options = {}) {
938
851
  resolveResponseRefs(document, operation);
939
852
  const responseBody = getResponseBody(getResponseByStatusCode({
@@ -942,7 +855,7 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
942
855
  statusCode
943
856
  }), options.contentType);
944
857
  if (responseBody === false) return {};
945
- const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
858
+ const schema = responseBody.schema;
946
859
  if (!schema) return {};
947
860
  return dereferenceWithRef(document, schema);
948
861
  }
@@ -972,6 +885,16 @@ function getRequestSchema(document, operation, options = {}) {
972
885
  return dereferenceWithRef(document, schema);
973
886
  }
974
887
  /**
888
+ * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
889
+ * structurally significant on its own (see `structuralKeys`).
890
+ *
891
+ * A fragment with a structural keyword can't be safely merged into a parent schema.
892
+ */
893
+ function hasStructuralKeywords(fragment) {
894
+ for (const key in fragment) if (structuralKeys.has(key)) return true;
895
+ return false;
896
+ }
897
+ /**
975
898
  * Flattens a keyword-only `allOf` into its parent schema.
976
899
  *
977
900
  * Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
@@ -990,16 +913,6 @@ function getRequestSchema(document, operation, options = {}) {
990
913
  * // returned unchanged, contains a $ref
991
914
  * ```
992
915
  */
993
- /**
994
- * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
995
- * structurally significant on its own (see `structuralKeys`).
996
- *
997
- * A fragment with a structural keyword can't be safely merged into a parent schema.
998
- */
999
- function hasStructuralKeywords(fragment) {
1000
- for (const key in fragment) if (structuralKeys.has(key)) return true;
1001
- return false;
1002
- }
1003
916
  function flattenSchema(schema) {
1004
917
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
1005
918
  const allOfFragments = schema.allOf;
@@ -1251,7 +1164,7 @@ function getResponseBodyContentTypes(document, operation, statusCode) {
1251
1164
  return body.content ? Object.keys(body.content) : [];
1252
1165
  }
1253
1166
  //#endregion
1254
- //#region src/parser.ts
1167
+ //#region src/converters.ts
1255
1168
  /**
1256
1169
  * Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
1257
1170
  *
@@ -1275,7 +1188,7 @@ function normalizeArrayEnum(schema) {
1275
1188
  * Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
1276
1189
  * and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
1277
1190
  */
1278
- function createNullSchema(schema, name, nullable) {
1191
+ function createNullNode(schema, name, nullable) {
1279
1192
  return ast.factory.createSchema({
1280
1193
  type: "null",
1281
1194
  primitive: "null",
@@ -1305,653 +1218,636 @@ function nameEnums(node, options) {
1305
1218
  return named;
1306
1219
  }
1307
1220
  /**
1308
- * Factory function that creates schema and operation converters for a given OpenAPI context.
1309
- *
1310
- * Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
1311
- * Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
1312
- * which works because function declarations hoist.
1221
+ * Converts a `$ref` schema into a `RefSchemaNode`.
1313
1222
  *
1314
- * @internal
1223
+ * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1224
+ * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1225
+ * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1226
+ * Circular refs are detected in `resolveRefNode` and leave `schema` as `null`.
1315
1227
  */
1316
- function createSchemaParser(ctx, dialect = oasDialect) {
1317
- const document = ctx.document;
1318
- /**
1319
- * Tracks `$ref` paths that are currently being resolved to prevent infinite
1320
- * recursion when schemas contain circular references (e.g. `Pet parent → Pet`).
1321
- */
1322
- const resolvingRefs = /* @__PURE__ */ new Set();
1323
- /**
1324
- * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1325
- *
1326
- * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1327
- * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1328
- * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1329
- * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1330
- */
1331
- const resolvedRefCache = /* @__PURE__ */ new Map();
1332
- /**
1333
- * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1334
- * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1335
- * goes `false` for a `$ref` that points at a component the spec never declares.
1336
- */
1337
- const refExistence = /* @__PURE__ */ new Map();
1338
- function refExists(refPath) {
1339
- if (!refExistence.has(refPath)) {
1340
- let exists = false;
1341
- try {
1342
- exists = !!dialect.schema.resolveRef(document, refPath);
1343
- } catch {
1344
- exists = false;
1345
- }
1346
- refExistence.set(refPath, exists);
1347
- }
1348
- return refExistence.get(refPath) ?? false;
1349
- }
1350
- /**
1351
- * Converts a `$ref` schema into a `RefSchemaNode`.
1352
- *
1353
- * The resolved schema is stored in `node.schema`. Usage-site sibling fields
1354
- * (description, readOnly, nullable, etc.) are stored directly on the ref node.
1355
- * Use `syncSchemaRef(node)` in printers to get a merged view of both.
1356
- * Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
1357
- */
1358
- function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
1359
- let resolvedSchema = null;
1360
- const refPath = schema.$ref;
1361
- if (refPath && !resolvingRefs.has(refPath)) {
1362
- if (!resolvedRefCache.has(refPath)) {
1363
- try {
1364
- const referenced = dialect.schema.resolveRef(document, refPath);
1365
- if (referenced) {
1366
- resolvingRefs.add(refPath);
1367
- resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
1368
- resolvingRefs.delete(refPath);
1369
- }
1370
- } catch {}
1371
- resolvedRefCache.set(refPath, resolvedSchema);
1372
- }
1373
- resolvedSchema = resolvedRefCache.get(refPath) ?? null;
1374
- }
1375
- if (refPath && document.components && !refExists(refPath)) return ast.factory.createSchema({
1376
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1377
- type: "unknown"
1378
- });
1228
+ function convertRef({ schema, name, nullable, defaultValue, rawOptions, document, resolveRefNode, refExists }) {
1229
+ const refPath = schema.$ref;
1230
+ const resolvedSchema = refPath ? resolveRefNode(refPath, rawOptions) : null;
1231
+ if (refPath && document.components && !refExists(refPath)) return ast.factory.createSchema({
1232
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1233
+ type: "unknown"
1234
+ });
1235
+ return ast.factory.createSchema({
1236
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1237
+ type: "ref",
1238
+ name: extractRefName(schema.$ref),
1239
+ ref: schema.$ref,
1240
+ schema: resolvedSchema
1241
+ });
1242
+ }
1243
+ /**
1244
+ * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1245
+ */
1246
+ function convertAllOf({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
1247
+ if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1248
+ const [memberSchema] = schema.allOf;
1249
+ const memberNode = parse({
1250
+ schema: memberSchema,
1251
+ name
1252
+ }, rawOptions);
1253
+ const { kind: _kind, ...memberNodeProps } = memberNode;
1254
+ const mergedNullable = nullable || memberNode.nullable || void 0;
1255
+ const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1379
1256
  return ast.factory.createSchema({
1380
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1381
- type: "ref",
1382
- name: extractRefName(schema.$ref),
1383
- ref: schema.$ref,
1384
- schema: resolvedSchema
1257
+ ...memberNodeProps,
1258
+ name,
1259
+ title: schema.title ?? memberNode.title,
1260
+ description: schema.description ?? memberNode.description,
1261
+ deprecated: schema.deprecated ?? memberNode.deprecated,
1262
+ nullable: mergedNullable,
1263
+ readOnly: schema.readOnly ?? memberNode.readOnly,
1264
+ writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1265
+ default: mergedDefault,
1266
+ examples: extractExamples(schema) ?? memberNode.examples,
1267
+ pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1268
+ format: schema.format ?? memberNode.format
1385
1269
  });
1386
1270
  }
1387
- /**
1388
- * Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
1389
- */
1390
- function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }) {
1391
- if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
1392
- const [memberSchema] = schema.allOf;
1393
- const memberNode = parseSchema({
1394
- schema: memberSchema,
1395
- name
1396
- }, rawOptions);
1397
- const { kind: _kind, ...memberNodeProps } = memberNode;
1398
- const mergedNullable = nullable || memberNode.nullable || void 0;
1399
- const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
1400
- return ast.factory.createSchema({
1401
- ...memberNodeProps,
1402
- name,
1403
- title: schema.title ?? memberNode.title,
1404
- description: schema.description ?? memberNode.description,
1405
- deprecated: schema.deprecated ?? memberNode.deprecated,
1406
- nullable: mergedNullable,
1407
- readOnly: schema.readOnly ?? memberNode.readOnly,
1408
- writeOnly: schema.writeOnly ?? memberNode.writeOnly,
1409
- default: mergedDefault,
1410
- examples: extractExamples(schema) ?? memberNode.examples,
1411
- pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
1412
- format: schema.format ?? memberNode.format
1271
+ const filteredDiscriminantValues = [];
1272
+ const allOfMembers = schema.allOf.filter((item) => {
1273
+ if (!isReference(item) || !name) return true;
1274
+ const deref = resolveRef(document, item.$ref);
1275
+ if (!deref || !isDiscriminator(deref)) return true;
1276
+ const parentUnion = deref.oneOf ?? deref.anyOf;
1277
+ if (!parentUnion) return true;
1278
+ const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1279
+ const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1280
+ const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1281
+ if (inOneOf || inMapping) {
1282
+ const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1283
+ if (discriminatorValue) filteredDiscriminantValues.push({
1284
+ propertyName: deref.discriminator.propertyName,
1285
+ value: discriminatorValue
1413
1286
  });
1287
+ return false;
1414
1288
  }
1415
- const filteredDiscriminantValues = [];
1416
- const allOfMembers = schema.allOf.filter((item) => {
1417
- if (!dialect.schema.isReference(item) || !name) return true;
1418
- const deref = dialect.schema.resolveRef(document, item.$ref);
1419
- if (!deref || !dialect.schema.isDiscriminator(deref)) return true;
1420
- const parentUnion = deref.oneOf ?? deref.anyOf;
1421
- if (!parentUnion) return true;
1422
- const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1423
- const inOneOf = parentUnion.some((oneOfItem) => dialect.schema.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1424
- const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1425
- if (inOneOf || inMapping) {
1426
- const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
1427
- if (discriminatorValue) filteredDiscriminantValues.push({
1428
- propertyName: deref.discriminator.propertyName,
1429
- value: discriminatorValue
1430
- });
1431
- return false;
1432
- }
1433
- return true;
1434
- }).map((s) => parseSchema({
1435
- schema: s,
1436
- name
1437
- }, rawOptions));
1438
- const syntheticStart = allOfMembers.length;
1439
- if (Array.isArray(schema.required) && schema.required.length) {
1440
- const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1441
- const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1442
- if (missingRequired.length) {
1443
- const resolvedMembers = schema.allOf.flatMap((item) => {
1444
- if (!dialect.schema.isReference(item)) return [item];
1445
- const deref = dialect.schema.resolveRef(document, item.$ref);
1446
- return deref && !dialect.schema.isReference(deref) ? [deref] : [];
1447
- });
1448
- for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1449
- allOfMembers.push(parseSchema({
1450
- schema: {
1451
- properties: { [key]: resolved.properties[key] },
1452
- required: [key]
1453
- },
1289
+ return true;
1290
+ }).map((s) => parse({
1291
+ schema: s,
1292
+ name
1293
+ }, rawOptions));
1294
+ const syntheticStart = allOfMembers.length;
1295
+ if (Array.isArray(schema.required) && schema.required.length) {
1296
+ const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
1297
+ const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1298
+ if (missingRequired.length) {
1299
+ const resolvedMembers = schema.allOf.flatMap((item) => {
1300
+ if (!isReference(item)) return [item];
1301
+ const deref = resolveRef(document, item.$ref);
1302
+ return deref && !isReference(deref) ? [deref] : [];
1303
+ });
1304
+ for (const key of missingRequired) for (const resolved of resolvedMembers) {
1305
+ const prop = resolved.properties?.[key];
1306
+ if (prop) {
1307
+ const memberSchema = {
1308
+ properties: { [key]: prop },
1309
+ required: [key]
1310
+ };
1311
+ allOfMembers.push(parse({
1312
+ schema: memberSchema,
1454
1313
  name
1455
1314
  }, rawOptions));
1456
1315
  break;
1457
1316
  }
1458
1317
  }
1459
1318
  }
1460
- if (schema.properties) {
1461
- const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1462
- allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
1463
- }
1464
- for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1465
- propertyName,
1466
- value
1467
- }));
1319
+ }
1320
+ if (schema.properties) {
1321
+ const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
1322
+ allOfMembers.push(parse({ schema: schemaWithoutAllOf }, rawOptions));
1323
+ }
1324
+ for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
1325
+ propertyName,
1326
+ value
1327
+ }));
1328
+ return ast.factory.createSchema({
1329
+ type: "intersection",
1330
+ members: [...mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1331
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1332
+ });
1333
+ }
1334
+ /**
1335
+ * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1336
+ */
1337
+ function convertUnion({ schema, name, nullable, defaultValue, rawOptions, parse, document }) {
1338
+ function pickDiscriminatorPropertyNode(node, propertyName) {
1339
+ const discriminatorProperty = ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1340
+ if (!discriminatorProperty) return null;
1468
1341
  return ast.factory.createSchema({
1469
- type: "intersection",
1470
- members: [...mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
1471
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1342
+ type: "object",
1343
+ primitive: "object",
1344
+ properties: [discriminatorProperty]
1472
1345
  });
1473
1346
  }
1474
- /**
1475
- * Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
1476
- */
1477
- function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
1478
- function pickDiscriminatorPropertyNode(node, propertyName) {
1479
- const discriminatorProperty = ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
1480
- if (!discriminatorProperty) return null;
1481
- return ast.factory.createSchema({
1482
- type: "object",
1483
- primitive: "object",
1484
- properties: [discriminatorProperty]
1347
+ function resolveRefSilent($ref) {
1348
+ if (!$ref.startsWith("#")) return null;
1349
+ return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1350
+ }
1351
+ function implicitDiscriminantValue(member) {
1352
+ if (!discriminator || discriminator.mapping || !isReference(member)) return null;
1353
+ const value = extractRefName(member.$ref);
1354
+ if (!value) return null;
1355
+ const variant = resolveRefSilent(member.$ref);
1356
+ if (!variant) return null;
1357
+ const propertyName = discriminator.propertyName;
1358
+ const seen = /* @__PURE__ */ new Set([member.$ref]);
1359
+ function constrains(v) {
1360
+ const prop = v.properties?.[propertyName];
1361
+ const resolved = prop && isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1362
+ if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1363
+ const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1364
+ if (!composition) return false;
1365
+ return composition.some((m) => {
1366
+ if (!isReference(m)) return constrains(m);
1367
+ if (seen.has(m.$ref)) return false;
1368
+ seen.add(m.$ref);
1369
+ const r = resolveRefSilent(m.$ref);
1370
+ return r ? constrains(r) : false;
1485
1371
  });
1486
1372
  }
1487
- function resolveRefSilent($ref) {
1488
- if (!$ref.startsWith("#")) return null;
1489
- return decodeURIComponent($ref.substring(1)).split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document) ?? null;
1490
- }
1491
- function implicitDiscriminantValue(member) {
1492
- if (!discriminator || discriminator.mapping || !dialect.schema.isReference(member)) return null;
1493
- const value = extractRefName(member.$ref);
1494
- if (!value) return null;
1495
- const variant = resolveRefSilent(member.$ref);
1496
- if (!variant) return null;
1497
- const propertyName = discriminator.propertyName;
1498
- const seen = /* @__PURE__ */ new Set([member.$ref]);
1499
- function constrains(v) {
1500
- const prop = v.properties?.[propertyName];
1501
- const resolved = prop && dialect.schema.isReference(prop) ? resolveRefSilent(prop.$ref) : prop;
1502
- if (resolved && (Array.isArray(resolved.enum) || resolved.const !== void 0)) return true;
1503
- const composition = v.allOf ?? v.oneOf ?? v.anyOf;
1504
- if (!composition) return false;
1505
- return composition.some((m) => {
1506
- if (!dialect.schema.isReference(m)) return constrains(m);
1507
- if (seen.has(m.$ref)) return false;
1508
- seen.add(m.$ref);
1509
- const r = resolveRefSilent(m.$ref);
1510
- return r ? constrains(r) : false;
1511
- });
1512
- }
1513
- return constrains(variant) ? null : value;
1514
- }
1515
- const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1516
- const strategy = schema.oneOf ? "one" : "any";
1517
- const unionBase = {
1518
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1519
- discriminatorPropertyName: dialect.schema.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1520
- strategy
1521
- };
1522
- const discriminator = dialect.schema.isDiscriminator(schema) ? schema.discriminator : void 0;
1523
- const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1524
- const sharedPropertiesNode = schema.properties ? parseSchema({
1525
- schema: memberBaseSchema,
1526
- name
1527
- }, rawOptions) : void 0;
1528
- if (sharedPropertiesNode || discriminator) {
1529
- const members = unionMembers.map((s) => {
1530
- const ref = dialect.schema.isReference(s) ? s.$ref : void 0;
1531
- const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1532
- const memberNode = parseSchema({
1533
- schema: s,
1534
- name
1535
- }, rawOptions);
1536
- if (!discriminatorValue || !discriminator) return memberNode;
1537
- const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
1538
- propertyName: discriminator.propertyName,
1539
- values: [discriminatorValue]
1540
- })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1541
- return ast.factory.createSchema({
1542
- type: "intersection",
1543
- members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1544
- propertyName: discriminator.propertyName,
1545
- value: discriminatorValue
1546
- })]
1547
- });
1548
- });
1549
- const unionNode = ast.factory.createSchema({
1550
- type: "union",
1551
- ...unionBase,
1552
- members
1553
- });
1554
- if (!sharedPropertiesNode) return unionNode;
1373
+ return constrains(variant) ? null : value;
1374
+ }
1375
+ const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
1376
+ const strategy = schema.oneOf ? "one" : "any";
1377
+ const unionBase = {
1378
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1379
+ discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1380
+ strategy
1381
+ };
1382
+ const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1383
+ const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
1384
+ const sharedPropertiesNode = schema.properties ? parse({
1385
+ schema: memberBaseSchema,
1386
+ name
1387
+ }, rawOptions) : void 0;
1388
+ if (sharedPropertiesNode || discriminator) {
1389
+ const members = unionMembers.map((s) => {
1390
+ const ref = isReference(s) ? s.$ref : void 0;
1391
+ const discriminatorValue = findDiscriminator(discriminator?.mapping, ref) ?? implicitDiscriminantValue(s);
1392
+ const memberNode = parse({
1393
+ schema: s,
1394
+ name
1395
+ }, rawOptions);
1396
+ if (!discriminatorValue || !discriminator) return memberNode;
1397
+ const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
1398
+ propertyName: discriminator.propertyName,
1399
+ values: [discriminatorValue]
1400
+ })], { depth: "shallow" }), discriminator.propertyName) : void 0;
1555
1401
  return ast.factory.createSchema({
1556
1402
  type: "intersection",
1557
- ...buildSchemaNode(schema, name, nullable, defaultValue),
1558
- members: [unionNode, sharedPropertiesNode]
1403
+ members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
1404
+ propertyName: discriminator.propertyName,
1405
+ value: discriminatorValue
1406
+ })]
1559
1407
  });
1560
- }
1408
+ });
1561
1409
  const unionNode = ast.factory.createSchema({
1562
1410
  type: "union",
1563
1411
  ...unionBase,
1564
- members: unionMembers.map((s) => parseSchema({
1565
- schema: s,
1566
- name
1567
- }, rawOptions))
1412
+ members
1568
1413
  });
1569
- return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: "shallow" });
1570
- }
1571
- /**
1572
- * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1573
- */
1574
- function convertConst({ schema, name, nullable, defaultValue }) {
1575
- const constValue = schema.const;
1576
- if (constValue === null) return createNullSchema(schema, name);
1577
- const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1414
+ if (!sharedPropertiesNode) return unionNode;
1578
1415
  return ast.factory.createSchema({
1579
- type: "enum",
1580
- primitive: constPrimitive,
1581
- enumValues: [constValue],
1582
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1416
+ type: "intersection",
1417
+ ...buildSchemaNode(schema, name, nullable, defaultValue),
1418
+ members: [unionNode, sharedPropertiesNode]
1583
1419
  });
1584
1420
  }
1585
- /**
1586
- * Converts a format-annotated schema into a special-type `SchemaNode`.
1587
- * Returns `null` when the format should fall through to string handling (`dateType: false`).
1588
- */
1589
- function convertFormat({ schema, name, nullable, defaultValue, options }) {
1590
- const base = buildSchemaNode(schema, name, nullable, defaultValue);
1591
- if (schema.format === "int64") return ast.factory.createSchema({
1592
- type: options.integerType === "bigint" ? "bigint" : "integer",
1593
- primitive: "integer",
1594
- ...base,
1595
- min: schema.minimum,
1596
- max: schema.maximum,
1597
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1598
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1599
- });
1600
- if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1601
- const dateType = getDateType(options, schema.format);
1602
- if (!dateType) return null;
1603
- if (dateType.type === "datetime") return ast.factory.createSchema({
1604
- ...base,
1605
- primitive: "string",
1606
- type: "datetime",
1607
- offset: dateType.offset,
1608
- local: dateType.local
1609
- });
1610
- return ast.factory.createSchema({
1611
- ...base,
1612
- primitive: "string",
1613
- type: dateType.type,
1614
- representation: dateType.representation
1615
- });
1616
- }
1617
- const specialType = getSchemaType(schema.format);
1618
- if (!specialType) return null;
1619
- const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1620
- if (specialType === "number" || specialType === "integer" || specialType === "bigint") return ast.factory.createSchema({
1621
- ...base,
1622
- primitive: specialPrimitive,
1623
- type: specialType
1624
- });
1625
- if (specialType === "url") return ast.factory.createSchema({
1626
- ...base,
1627
- primitive: "string",
1628
- type: "url",
1629
- min: schema.minLength,
1630
- max: schema.maxLength
1631
- });
1632
- if (specialType === "ipv4") return ast.factory.createSchema({
1421
+ const unionNode = ast.factory.createSchema({
1422
+ type: "union",
1423
+ ...unionBase,
1424
+ members: unionMembers.map((s) => parse({
1425
+ schema: s,
1426
+ name
1427
+ }, rawOptions))
1428
+ });
1429
+ return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: "shallow" });
1430
+ }
1431
+ /**
1432
+ * Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
1433
+ */
1434
+ function convertConst({ schema, name, nullable, defaultValue }) {
1435
+ const constValue = schema.const;
1436
+ if (constValue === null) return createNullNode(schema, name);
1437
+ const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
1438
+ return ast.factory.createSchema({
1439
+ type: "enum",
1440
+ primitive: constPrimitive,
1441
+ enumValues: [constValue],
1442
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1443
+ });
1444
+ }
1445
+ /**
1446
+ * Converts a format-annotated schema into a special-type `SchemaNode`.
1447
+ * Returns `null` when the format should fall through to string handling (`dateType: false`).
1448
+ */
1449
+ function convertFormat({ schema, name, nullable, defaultValue, options }) {
1450
+ const base = buildSchemaNode(schema, name, nullable, defaultValue);
1451
+ if (schema.format === "int64") return ast.factory.createSchema({
1452
+ type: options.integerType === "bigint" ? "bigint" : "integer",
1453
+ primitive: "integer",
1454
+ ...base,
1455
+ min: schema.minimum,
1456
+ max: schema.maximum,
1457
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1458
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
1459
+ });
1460
+ if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
1461
+ const dateType = getDateType(options, schema.format);
1462
+ if (!dateType) return null;
1463
+ if (dateType.type === "datetime") return ast.factory.createSchema({
1633
1464
  ...base,
1634
1465
  primitive: "string",
1635
- type: "ipv4"
1466
+ type: "datetime",
1467
+ offset: dateType.offset,
1468
+ local: dateType.local
1636
1469
  });
1637
- if (specialType === "ipv6") return ast.factory.createSchema({
1470
+ return ast.factory.createSchema({
1638
1471
  ...base,
1639
1472
  primitive: "string",
1640
- type: "ipv6"
1473
+ type: dateType.type,
1474
+ representation: dateType.representation
1641
1475
  });
1642
- if (specialType === "uuid" || specialType === "email") return ast.factory.createSchema({
1643
- ...base,
1644
- primitive: "string",
1645
- type: specialType,
1476
+ }
1477
+ const specialType = getSchemaType(schema.format);
1478
+ if (!specialType) return null;
1479
+ const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
1480
+ const hasLength = specialType === "url" || specialType === "uuid" || specialType === "email";
1481
+ return ast.factory.createSchema({
1482
+ ...base,
1483
+ primitive: specialPrimitive,
1484
+ type: specialType,
1485
+ ...hasLength ? {
1646
1486
  min: schema.minLength,
1647
1487
  max: schema.maxLength
1648
- });
1649
- return ast.factory.createSchema({
1650
- ...base,
1651
- primitive: specialPrimitive,
1652
- type: specialType
1653
- });
1654
- }
1655
- /**
1656
- * Converts an `enum` schema into an `EnumSchemaNode`.
1657
- */
1658
- function convertEnum({ schema, name, nullable, type, rawOptions }) {
1659
- if (type === "array") return parseSchema({
1660
- schema: normalizeArrayEnum(schema),
1661
- name
1662
- }, rawOptions);
1663
- const nullInEnum = schema.enum.includes(null);
1664
- const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1665
- if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
1666
- const enumNullable = nullable || nullInEnum || void 0;
1667
- const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1668
- const enumPrimitive = getPrimitiveType(type);
1669
- const enumBase = {
1670
- type: "enum",
1671
- primitive: enumPrimitive,
1672
- name,
1673
- title: schema.title,
1674
- description: schema.description,
1675
- deprecated: schema.deprecated,
1676
- nullable: enumNullable,
1677
- readOnly: schema.readOnly,
1678
- writeOnly: schema.writeOnly,
1679
- default: enumDefault,
1680
- examples: extractExamples(schema),
1681
- format: schema.format
1682
- };
1683
- const extensionKey = enumExtensionKeys.find((key) => key in schema);
1684
- const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1685
- if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1686
- const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1687
- const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1688
- const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1689
- const uniqueValues = [...new Set(filteredValues)];
1690
- const seenNames = /* @__PURE__ */ new Set();
1691
- return ast.factory.createSchema({
1692
- ...enumBase,
1693
- primitive: enumPrimitiveType,
1694
- namedEnumValues: uniqueValues.map((value, index) => ({
1695
- name: String(rawEnumNames?.[index] ?? value),
1696
- value,
1697
- primitive: enumPrimitiveType,
1698
- description: rawEnumDescriptions?.[index]
1699
- })).filter((entry) => {
1700
- if (seenNames.has(entry.name)) return false;
1701
- seenNames.add(entry.name);
1702
- return true;
1703
- })
1704
- });
1705
- }
1488
+ } : {}
1489
+ });
1490
+ }
1491
+ /**
1492
+ * Converts an `enum` schema into an `EnumSchemaNode`.
1493
+ */
1494
+ function convertEnum({ schema, name, nullable, type, rawOptions, parse }) {
1495
+ if (type === "array") return parse({
1496
+ schema: normalizeArrayEnum(schema),
1497
+ name
1498
+ }, rawOptions);
1499
+ const nullInEnum = schema.enum.includes(null);
1500
+ const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
1501
+ if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
1502
+ const enumNullable = nullable || nullInEnum || void 0;
1503
+ const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
1504
+ const enumPrimitive = getPrimitiveType(type);
1505
+ const enumBase = {
1506
+ type: "enum",
1507
+ primitive: enumPrimitive,
1508
+ ...buildSchemaNode(schema, name, enumNullable, enumDefault)
1509
+ };
1510
+ const extensionKey = enumExtensionKeys.find((key) => key in schema);
1511
+ const descriptionKey = enumDescriptionKeys.find((key) => key in schema);
1512
+ if (extensionKey || descriptionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
1513
+ const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
1514
+ const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
1515
+ const rawEnumDescriptions = descriptionKey ? schema[descriptionKey] : void 0;
1516
+ const uniqueValues = [...new Set(filteredValues)];
1517
+ const seenNames = /* @__PURE__ */ new Set();
1706
1518
  return ast.factory.createSchema({
1707
1519
  ...enumBase,
1708
- enumValues: [...new Set(filteredValues)]
1709
- });
1710
- }
1711
- /**
1712
- * Converts an object-like schema into an `ObjectSchemaNode`.
1713
- */
1714
- function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }) {
1715
- const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1716
- const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1717
- const resolvedPropSchema = propSchema;
1718
- const propNullable = dialect.schema.isNullable(resolvedPropSchema);
1719
- const schemaNode = nameEnums(parseSchema({
1720
- schema: resolvedPropSchema,
1721
- name: childName(name, propName)
1722
- }, rawOptions), {
1723
- parentName: name,
1724
- propName,
1725
- enumSuffix: options.enumSuffix
1726
- });
1727
- return ast.factory.createProperty({
1728
- name: propName,
1729
- schema: {
1730
- ...schemaNode,
1731
- nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1732
- },
1733
- required
1734
- });
1735
- }) : [];
1736
- const additionalProperties = schema.additionalProperties;
1737
- const additionalPropertiesNode = (() => {
1738
- if (additionalProperties === true) return true;
1739
- if (additionalProperties === false) return false;
1740
- if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
1741
- if (additionalProperties) return ast.factory.createSchema({ type: options.unknownType });
1742
- })();
1743
- const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1744
- const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.factory.createSchema({ type: options.unknownType }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
1745
- const objectNode = ast.factory.createSchema({
1746
- type: "object",
1747
- primitive: "object",
1748
- properties,
1749
- additionalProperties: additionalPropertiesNode,
1750
- patternProperties,
1751
- minProperties: schema.minProperties,
1752
- maxProperties: schema.maxProperties,
1753
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1754
- });
1755
- if (dialect.schema.isDiscriminator(schema) && schema.discriminator.mapping) {
1756
- const discPropName = schema.discriminator.propertyName;
1757
- const values = Object.keys(schema.discriminator.mapping);
1758
- const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
1759
- return ast.applyMacros(objectNode, [macroDiscriminatorEnum({
1760
- propertyName: discPropName,
1761
- values,
1762
- enumName
1763
- })], { depth: "shallow" });
1764
- }
1765
- return objectNode;
1766
- }
1767
- /**
1768
- * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1769
- */
1770
- function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
1771
- const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
1772
- const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: "any" }) : parseSchema({ schema: schema.items }, rawOptions);
1773
- return ast.factory.createSchema({
1774
- type: "tuple",
1775
- primitive: "array",
1776
- items: tupleItems,
1777
- rest,
1778
- min: schema.minItems,
1779
- max: schema.maxItems,
1780
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1520
+ primitive: enumPrimitiveType,
1521
+ namedEnumValues: uniqueValues.map((value, index) => ({
1522
+ name: String(rawEnumNames?.[index] ?? value),
1523
+ value,
1524
+ primitive: enumPrimitiveType,
1525
+ description: rawEnumDescriptions?.[index]
1526
+ })).filter((entry) => {
1527
+ if (seenNames.has(entry.name)) return false;
1528
+ seenNames.add(entry.name);
1529
+ return true;
1530
+ })
1781
1531
  });
1782
1532
  }
1783
- /**
1784
- * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1785
- */
1786
- function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
1787
- const rawItems = schema.items;
1788
- const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
1789
- const items = rawItems ? [parseSchema({
1790
- schema: rawItems,
1791
- name: itemName
1792
- }, rawOptions)] : [];
1793
- return ast.factory.createSchema({
1794
- type: "array",
1795
- primitive: "array",
1796
- items,
1797
- min: schema.minItems,
1798
- max: schema.maxItems,
1799
- unique: schema.uniqueItems ?? void 0,
1800
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1533
+ return ast.factory.createSchema({
1534
+ ...enumBase,
1535
+ enumValues: [...new Set(filteredValues)]
1536
+ });
1537
+ }
1538
+ /**
1539
+ * Converts an object-like schema into an `ObjectSchemaNode`.
1540
+ */
1541
+ function convertObject({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1542
+ const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1543
+ const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1544
+ const resolvedPropSchema = propSchema;
1545
+ const propNullable = isNullable(resolvedPropSchema);
1546
+ const schemaNode = nameEnums(parse({
1547
+ schema: resolvedPropSchema,
1548
+ name: childName(name, propName)
1549
+ }, rawOptions), {
1550
+ parentName: name,
1551
+ propName,
1552
+ enumSuffix: options.enumSuffix
1801
1553
  });
1802
- }
1803
- /**
1804
- * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1805
- */
1806
- function convertString({ schema, name, nullable, defaultValue }) {
1807
- return ast.factory.createSchema({
1808
- type: "string",
1809
- primitive: "string",
1810
- min: schema.minLength,
1811
- max: schema.maxLength,
1812
- pattern: schema.pattern,
1813
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1554
+ return ast.factory.createProperty({
1555
+ name: propName,
1556
+ schema: {
1557
+ ...schemaNode,
1558
+ nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
1559
+ },
1560
+ required
1814
1561
  });
1562
+ }) : [];
1563
+ const additionalProperties = schema.additionalProperties;
1564
+ const additionalPropertiesNode = (() => {
1565
+ if (additionalProperties === true) return true;
1566
+ if (additionalProperties === false) return false;
1567
+ if (additionalProperties && Object.keys(additionalProperties).length > 0) return parse({ schema: additionalProperties }, rawOptions);
1568
+ if (additionalProperties) return ast.factory.createSchema({ type: options.unknownType });
1569
+ })();
1570
+ const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
1571
+ const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: patternSchema }, rawOptions)])) : void 0;
1572
+ const objectNode = ast.factory.createSchema({
1573
+ type: "object",
1574
+ primitive: "object",
1575
+ properties,
1576
+ additionalProperties: additionalPropertiesNode,
1577
+ patternProperties,
1578
+ minProperties: schema.minProperties,
1579
+ maxProperties: schema.maxProperties,
1580
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1581
+ });
1582
+ if (isDiscriminator(schema) && schema.discriminator.mapping) {
1583
+ const discPropName = schema.discriminator.propertyName;
1584
+ const values = Object.keys(schema.discriminator.mapping);
1585
+ const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
1586
+ return ast.applyMacros(objectNode, [macroDiscriminatorEnum({
1587
+ propertyName: discPropName,
1588
+ values,
1589
+ enumName
1590
+ })], { depth: "shallow" });
1815
1591
  }
1816
- /**
1817
- * Converts a `type: 'number'` or `type: 'integer'` schema.
1818
- */
1819
- function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1820
- return ast.factory.createSchema({
1821
- type,
1822
- primitive: type,
1823
- min: schema.minimum,
1824
- max: schema.maximum,
1825
- exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1826
- exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1827
- multipleOf: schema.multipleOf,
1828
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1829
- });
1592
+ return objectNode;
1593
+ }
1594
+ /**
1595
+ * Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
1596
+ */
1597
+ function convertTuple({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1598
+ const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }, rawOptions));
1599
+ const rest = schema.items === false ? void 0 : !schema.items || schema.items === true ? ast.factory.createSchema({ type: "any" }) : parse({ schema: schema.items }, rawOptions);
1600
+ return ast.factory.createSchema({
1601
+ type: "tuple",
1602
+ primitive: "array",
1603
+ items: tupleItems,
1604
+ rest,
1605
+ min: schema.minItems,
1606
+ max: schema.maxItems,
1607
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1608
+ });
1609
+ }
1610
+ /**
1611
+ * Converts a `type: 'array'` schema into an `ArraySchemaNode`.
1612
+ */
1613
+ function convertArray({ schema, name, nullable, defaultValue, rawOptions, options, parse }) {
1614
+ const rawItems = schema.items;
1615
+ const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
1616
+ const items = rawItems ? [parse({
1617
+ schema: rawItems,
1618
+ name: itemName
1619
+ }, rawOptions)] : [];
1620
+ return ast.factory.createSchema({
1621
+ type: "array",
1622
+ primitive: "array",
1623
+ items,
1624
+ min: schema.minItems,
1625
+ max: schema.maxItems,
1626
+ unique: schema.uniqueItems ?? void 0,
1627
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1628
+ });
1629
+ }
1630
+ /**
1631
+ * Converts a `type: 'string'` schema into a `StringSchemaNode`.
1632
+ */
1633
+ function convertString({ schema, name, nullable, defaultValue }) {
1634
+ return ast.factory.createSchema({
1635
+ type: "string",
1636
+ primitive: "string",
1637
+ min: schema.minLength,
1638
+ max: schema.maxLength,
1639
+ pattern: schema.pattern,
1640
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1641
+ });
1642
+ }
1643
+ /**
1644
+ * Converts a `type: 'number'` or `type: 'integer'` schema.
1645
+ */
1646
+ function convertNumeric({ schema, name, nullable, defaultValue }, type) {
1647
+ return ast.factory.createSchema({
1648
+ type,
1649
+ primitive: type,
1650
+ min: schema.minimum,
1651
+ max: schema.maximum,
1652
+ exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
1653
+ exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
1654
+ multipleOf: schema.multipleOf,
1655
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1656
+ });
1657
+ }
1658
+ /**
1659
+ * Converts a `type: 'boolean'` schema.
1660
+ */
1661
+ function convertBoolean({ schema, name, nullable, defaultValue }) {
1662
+ return ast.factory.createSchema({
1663
+ type: "boolean",
1664
+ primitive: "boolean",
1665
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1666
+ });
1667
+ }
1668
+ /**
1669
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1670
+ * into a `blob` node.
1671
+ */
1672
+ function convertBinary({ schema, name, nullable, defaultValue }) {
1673
+ return ast.factory.createSchema({
1674
+ type: "blob",
1675
+ primitive: "string",
1676
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1677
+ });
1678
+ }
1679
+ /**
1680
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1681
+ *
1682
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parse`
1683
+ * falls through and handles it as that single type with nullability already folded in.
1684
+ */
1685
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions, parse }) {
1686
+ const types = schema.type;
1687
+ const nonNullTypes = types.filter((t) => t !== "null");
1688
+ if (nonNullTypes.length <= 1) return null;
1689
+ const arrayNullable = types.includes("null") || nullable || void 0;
1690
+ return ast.factory.createSchema({
1691
+ type: "union",
1692
+ members: nonNullTypes.map((t) => {
1693
+ return parse({
1694
+ schema: {
1695
+ ...schema,
1696
+ type: t
1697
+ },
1698
+ name
1699
+ }, rawOptions);
1700
+ }),
1701
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1702
+ });
1703
+ }
1704
+ /**
1705
+ * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1706
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1707
+ * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1708
+ * match/convert/fall-through contract.
1709
+ */
1710
+ const schemaRules = [
1711
+ {
1712
+ match: ({ schema }) => isReference(schema),
1713
+ convert: convertRef
1714
+ },
1715
+ {
1716
+ match: ({ schema }) => !!schema.allOf?.length,
1717
+ convert: convertAllOf
1718
+ },
1719
+ {
1720
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1721
+ convert: convertUnion
1722
+ },
1723
+ {
1724
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1725
+ convert: convertConst
1726
+ },
1727
+ {
1728
+ match: ({ schema }) => !!schema.format,
1729
+ convert: convertFormat
1730
+ },
1731
+ {
1732
+ match: ({ schema }) => isBinary(schema),
1733
+ convert: convertBinary
1734
+ },
1735
+ {
1736
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1737
+ convert: convertMultiType
1738
+ },
1739
+ {
1740
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1741
+ convert: convertString
1742
+ },
1743
+ {
1744
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1745
+ convert: (ctx) => convertNumeric(ctx, "number")
1746
+ },
1747
+ {
1748
+ match: ({ schema }) => !!schema.enum?.length,
1749
+ convert: convertEnum
1750
+ },
1751
+ {
1752
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1753
+ convert: convertObject
1754
+ },
1755
+ {
1756
+ match: ({ schema }) => "prefixItems" in schema,
1757
+ convert: convertTuple
1758
+ },
1759
+ {
1760
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1761
+ convert: convertArray
1762
+ },
1763
+ {
1764
+ match: ({ type }) => type === "string",
1765
+ convert: convertString
1766
+ },
1767
+ {
1768
+ match: ({ type }) => type === "number",
1769
+ convert: (ctx) => convertNumeric(ctx, "number")
1770
+ },
1771
+ {
1772
+ match: ({ type }) => type === "integer",
1773
+ convert: (ctx) => convertNumeric(ctx, "integer")
1774
+ },
1775
+ {
1776
+ match: ({ type }) => type === "boolean",
1777
+ convert: convertBoolean
1778
+ },
1779
+ {
1780
+ match: ({ type }) => type === "null",
1781
+ convert: ({ schema, name, nullable }) => createNullNode(schema, name, nullable)
1830
1782
  }
1783
+ ];
1784
+ //#endregion
1785
+ //#region src/parser.ts
1786
+ /**
1787
+ * Creates the schema and operation converters bound to one OpenAPI document.
1788
+ *
1789
+ * Owns the per-instance `$ref` state (cycle detection, resolved-node cache, existence cache) and
1790
+ * the `parseSchema` recursion seam, then dispatches each schema through the ordered `schemaRules`
1791
+ * table from `converters.ts`. Every converter is a standalone function that recurses through the
1792
+ * `parse` function passed to it, so this file only wires state to the converters.
1793
+ *
1794
+ * @internal
1795
+ */
1796
+ function createSchemaParser(ctx) {
1797
+ const document = ctx.document;
1831
1798
  /**
1832
- * Converts a `type: 'boolean'` schema.
1799
+ * Tracks `$ref` paths that are currently being resolved to prevent infinite
1800
+ * recursion when schemas contain circular references (e.g. `Pet → parent → Pet`).
1833
1801
  */
1834
- function convertBoolean({ schema, name, nullable, defaultValue }) {
1835
- return ast.factory.createSchema({
1836
- type: "boolean",
1837
- primitive: "boolean",
1838
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1839
- });
1840
- }
1802
+ const resolvingRefs = /* @__PURE__ */ new Set();
1841
1803
  /**
1842
- * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1843
- * into a `blob` node.
1804
+ * Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
1805
+ *
1806
+ * Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
1807
+ * it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
1808
+ * since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
1809
+ * Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
1844
1810
  */
1845
- function convertBlob({ schema, name, nullable, defaultValue }) {
1846
- return ast.factory.createSchema({
1847
- type: "blob",
1848
- primitive: "string",
1849
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1850
- });
1851
- }
1811
+ const resolvedRefCache = /* @__PURE__ */ new Map();
1852
1812
  /**
1853
- * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1854
- *
1855
- * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1856
- * falls through and handles it as that single type with nullability already folded in.
1813
+ * Memoized record of whether a `$ref` path resolves to a node the document actually defines.
1814
+ * A circular ref still resolves to an existing target, so this stays `true` for cycles and only
1815
+ * goes `false` for a `$ref` that points at a component the spec never declares.
1857
1816
  */
1858
- function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1859
- const types = schema.type;
1860
- const nonNullTypes = types.filter((t) => t !== "null");
1861
- if (nonNullTypes.length <= 1) return null;
1862
- const arrayNullable = types.includes("null") || nullable || void 0;
1863
- return ast.factory.createSchema({
1864
- type: "union",
1865
- members: nonNullTypes.map((t) => parseSchema({
1866
- schema: {
1867
- ...schema,
1868
- type: t
1869
- },
1870
- name
1871
- }, rawOptions)),
1872
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1873
- });
1817
+ const refExistence = /* @__PURE__ */ new Map();
1818
+ function refExists(refPath) {
1819
+ if (!refExistence.has(refPath)) {
1820
+ let exists = false;
1821
+ try {
1822
+ exists = !!resolveRef(document, refPath);
1823
+ } catch {
1824
+ exists = false;
1825
+ }
1826
+ refExistence.set(refPath, exists);
1827
+ }
1828
+ return refExistence.get(refPath) ?? false;
1874
1829
  }
1875
1830
  /**
1876
- * Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
1877
- * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1878
- * `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
1879
- * match/convert/fall-through contract.
1831
+ * Resolves a `$ref` to its parsed node, guarding against cycles and memoizing per instance.
1832
+ * Returns `null` when the ref is currently being resolved (a cycle) or cannot be resolved
1833
+ * (e.g. a minimal document in a unit test).
1880
1834
  */
1881
- const schemaRules = [
1882
- {
1883
- match: ({ schema }) => dialect.schema.isReference(schema),
1884
- convert: convertRef
1885
- },
1886
- {
1887
- match: ({ schema }) => !!schema.allOf?.length,
1888
- convert: convertAllOf
1889
- },
1890
- {
1891
- match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1892
- convert: convertUnion
1893
- },
1894
- {
1895
- match: ({ schema }) => "const" in schema && schema.const !== void 0,
1896
- convert: convertConst
1897
- },
1898
- {
1899
- match: ({ schema }) => !!schema.format,
1900
- convert: convertFormat
1901
- },
1902
- {
1903
- match: ({ schema }) => dialect.schema.isBinary(schema),
1904
- convert: convertBlob
1905
- },
1906
- {
1907
- match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1908
- convert: convertMultiType
1909
- },
1910
- {
1911
- match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1912
- convert: convertString
1913
- },
1914
- {
1915
- match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1916
- convert: (ctx) => convertNumeric(ctx, "number")
1917
- },
1918
- {
1919
- match: ({ schema }) => !!schema.enum?.length,
1920
- convert: convertEnum
1921
- },
1922
- {
1923
- match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1924
- convert: convertObject
1925
- },
1926
- {
1927
- match: ({ schema }) => "prefixItems" in schema,
1928
- convert: convertTuple
1929
- },
1930
- {
1931
- match: ({ schema, type }) => type === "array" || "items" in schema,
1932
- convert: convertArray
1933
- },
1934
- {
1935
- match: ({ type }) => type === "string",
1936
- convert: convertString
1937
- },
1938
- {
1939
- match: ({ type }) => type === "number",
1940
- convert: (ctx) => convertNumeric(ctx, "number")
1941
- },
1942
- {
1943
- match: ({ type }) => type === "integer",
1944
- convert: (ctx) => convertNumeric(ctx, "integer")
1945
- },
1946
- {
1947
- match: ({ type }) => type === "boolean",
1948
- convert: convertBoolean
1949
- },
1950
- {
1951
- match: ({ type }) => type === "null",
1952
- convert: ({ schema, name, nullable }) => createNullSchema(schema, name, nullable)
1835
+ function resolveRefNode(refPath, rawOptions) {
1836
+ if (resolvingRefs.has(refPath)) return null;
1837
+ if (!resolvedRefCache.has(refPath)) {
1838
+ let resolved = null;
1839
+ try {
1840
+ const referenced = resolveRef(document, refPath);
1841
+ if (referenced) {
1842
+ resolvingRefs.add(refPath);
1843
+ resolved = parseSchema({ schema: referenced }, rawOptions);
1844
+ resolvingRefs.delete(refPath);
1845
+ }
1846
+ } catch {}
1847
+ resolvedRefCache.set(refPath, resolved);
1953
1848
  }
1954
- ];
1849
+ return resolvedRefCache.get(refPath) ?? null;
1850
+ }
1955
1851
  /**
1956
1852
  * Converts an OAS `SchemaObject` into a `SchemaNode`.
1957
1853
  *
@@ -1969,19 +1865,23 @@ function createSchemaParser(ctx, dialect = oasDialect) {
1969
1865
  schema: flattenedSchema,
1970
1866
  name
1971
1867
  }, rawOptions);
1972
- const nullable = dialect.schema.isNullable(schema) || void 0;
1973
- const schemaCtx = {
1868
+ const nullable = isNullable(schema) || void 0;
1869
+ const context = {
1974
1870
  schema,
1975
1871
  name,
1976
1872
  nullable,
1977
1873
  defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1978
1874
  type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1979
1875
  rawOptions,
1980
- options
1876
+ options,
1877
+ parse: parseSchema,
1878
+ document,
1879
+ resolveRefNode,
1880
+ refExists
1981
1881
  };
1982
1882
  for (const rule of schemaRules) {
1983
- if (!rule.match(schemaCtx)) continue;
1984
- const node = rule.convert(schemaCtx);
1883
+ if (!rule.match(context)) continue;
1884
+ const node = rule.convert(context);
1985
1885
  if (node) return node;
1986
1886
  }
1987
1887
  const emptyType = options.emptySchemaType;
@@ -2039,7 +1939,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
2039
1939
  const keys = [];
2040
1940
  for (const key in schema.properties) {
2041
1941
  const prop = schema.properties[key];
2042
- if (prop && !dialect.schema.isReference(prop) && prop[flag]) keys.push(key);
1942
+ if (prop && !isReference(prop) && prop[flag]) keys.push(key);
2043
1943
  }
2044
1944
  return keys.length ? keys : null;
2045
1945
  }
@@ -2106,7 +2006,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
2106
2006
  });
2107
2007
  });
2108
2008
  const pathItem = document.paths?.[operation.path];
2109
- const pathItemDoc = pathItem && !dialect.schema.isReference(pathItem) ? pathItem : void 0;
2009
+ const pathItemDoc = pathItem && !isReference(pathItem) ? pathItem : void 0;
2110
2010
  const pickDoc = (key) => {
2111
2011
  const own = operation.schema[key];
2112
2012
  if (typeof own === "string") return own;
@@ -2298,13 +2198,10 @@ const adapterOas = createAdapter((options) => {
2298
2198
  function ensureSchemas(document) {
2299
2199
  const cached = schemasCache.get(document);
2300
2200
  if (cached) return cached;
2301
- const promise = Promise.resolve().then(() => {
2302
- const result = getSchemas(document, { contentType });
2303
- nameMapping = result.nameMapping;
2304
- return result.schemas;
2305
- });
2306
- schemasCache.set(document, promise);
2307
- return promise;
2201
+ const result = getSchemas(document, { contentType });
2202
+ nameMapping = result.nameMapping;
2203
+ schemasCache.set(document, result.schemas);
2204
+ return result.schemas;
2308
2205
  }
2309
2206
  function ensureSchemaParser(document) {
2310
2207
  const cached = schemaParserCache.get(document);
@@ -2420,7 +2317,7 @@ const adapterOas = createAdapter((options) => {
2420
2317
  const document = await ensureDocument(source);
2421
2318
  return parseInput({
2422
2319
  document,
2423
- schemas: await ensureSchemas(document),
2320
+ schemas: ensureSchemas(document),
2424
2321
  parser: ensureSchemaParser(document)
2425
2322
  });
2426
2323
  }